Files
gbrain/TODOS.md
T
78ba0b5b53 v0.19.0 check-resolvable: add OpenClaw skills-dir fallback + docs/tests (#326)
* Add OpenClaw skills fallback for check-resolvable

* feat: v0.17.0 foundation — errors/warnings split + AGENTS.md support + auto-manifest

First two workstreams of the v0.17.0 "skillify end-to-end" release. Landed
together because the D-CX-3 exit-code refactor is a prerequisite for W1's
warning-surfaced filing audit in Workstream 3.

## D-CX-3: split ResolvableReport into errors[] + warnings[] + --strict

Prior: `env.ok = report.issues.length === 0` treated warnings and errors
identically for exit status. Any warning forced exit 1, which meant the
planned filing-audit (W3) would break CI for every OpenClaw deployment
emitting advisory warnings.

New contract:
- `ResolvableReport.errors[]` and `warnings[]` as separate arrays.
- `issues[]` stays as deprecated backcompat union (remove in v0.18).
- Default: exit 0 unless any errors. Warnings are advisory.
- `--strict` flag promotes warnings to fail CI (explicit opt-in).

Files: src/core/check-resolvable.ts, src/commands/check-resolvable.ts
(added --strict flag + help text + header doc), src/commands/doctor.ts
(use new fields), test/check-resolvable-cli.test.ts (rewrite REGRESSION-GATE
to document the new contract, add 3 D-CX-3 cases).

## W1: AGENTS.md support + auto-manifest + priority fix

The reference OpenClaw deployment uses AGENTS.md (not RESOLVER.md) at the
workspace root, and ships without a manifest.json. check-resolvable
silently false-passed against it pre-W1: 0 manifest entries meant 0
reachability iterations meant 0 errors reported.

Post-W1 behavior against ~/git/<redacted>/workspace (smoke-tested live):
- Detects 102 skills via SKILL.md walk (no manifest.json needed)
- Flags 15 unreachable errors (exactly the essay's '~15% dark' finding)
- Flags 108 warnings (overlaps, gaps) — advisory, not blocking
- Auto-detects via \$OPENCLAW_WORKSPACE without --skills-dir

Changes:

- NEW src/core/resolver-filenames.ts: one source of truth for the
  filename policy. \`RESOLVER_FILENAMES = ['RESOLVER.md', 'AGENTS.md']\`.
  Callers import from here, never hardcode either name.

- NEW src/core/skill-manifest.ts: \`loadOrDeriveManifest()\` — reads
  manifest.json when present+valid, otherwise walks \`skillsDir/*/SKILL.md\`
  to derive a synthetic manifest. Both check-resolvable.ts AND dry-fix.ts
  now call this, replacing the two duplicated loaders that silently
  returned [] on missing file (F-ENG-1, D-CX-12).

- src/core/repo-root.ts (rewrite): auto-detect priority changed to put
  \$OPENCLAW_WORKSPACE ahead of findRepoRoot() walk when explicitly set
  (D-CX-4). Adds workspace-root AGENTS.md detection — OpenClaw layout
  places routing at workspace/AGENTS.md with skills/ below. New
  SkillsDirSource variants \`openclaw_workspace_env_root\` and
  \`openclaw_workspace_home_root\` for --verbose log clarity.

- src/core/check-resolvable.ts: accepts RESOLVER.md or AGENTS.md at the
  skills dir or one level up (workspace root). Uses loadOrDeriveManifest
  for reachability. Updated error messages reference both filenames.

- src/core/dry-fix.ts: unified manifest loader — auto-fix now works in
  AGENTS.md-only workspaces where it previously no-op'd silently.

- src/commands/check-resolvable.ts: new AUTO_DETECT_HINT import for
  clearer missing-skills-dir errors; updated sourceLabel map for the
  two new workspace-root variants.

Tests:
- test/skill-manifest.test.ts: 14 cases covering explicit-manifest,
  derived-manifest, malformed JSON, wrong shape, empty explicit array
  (honored as 'zero skills' declaration), dirname fallback when no
  name: frontmatter, underscore/dotfile dir skipping.
- test/repo-root.test.ts: new tests for the priority swap, AGENTS.md
  skills-dir variant, AGENTS.md workspace-root variant, both-files
  present (RESOLVER.md wins).
- test/check-resolvable-cli.test.ts: updated regression-gate to the
  new contract; added three D-CX-3 cases.

All 105 tests passing across the foundation surface.

Plan + reviews: ~/.claude/plans/p1-lets-just-vast-blanket.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: v0.17.0 W2 — Check 5 trigger routing eval (structural)

Check 5 of the 10-step skillify checklist (the essay's "resolver
trigger eval") now runs structurally by default and has a dedicated
CLI verb for CI. Ships Layer A; Layer B (LLM tie-break) is reserved
for v0.18.

## New module: src/core/routing-eval.ts

The harness. Pure functions:

- `normalizeText(s)`: lowercase, strip non-alnum to spaces, collapse
  whitespace. Unicode-friendly, quote-agnostic, punctuation-tolerant.
- `extractTriggerPhrases(cellText)`: split quoted alternatives like
  `"search for", "find me"` into separate normalized phrases; fall
  back to the whole cell when unquoted (OpenClaw-style descriptions).
- `indexResolverTriggers(resolverContent)`: build a skill-slug →
  normalized-trigger-phrases map from the resolver table.
- `structuralRouteMatch(intent, index)`: substring-match the
  normalized intent against every trigger phrase; return the set of
  matched skills + whether the match was ambiguous (more than one
  specific skill, excluding always-on family).
- `lintRoutingFixtures`: rejects fixtures whose intent is
  verbatim-equal to a trigger (D-CX-6: fixtures must paraphrase the
  framing, not copy the trigger text) and unknown expected_skill
  references.
- `loadRoutingFixtures(skillsDir)`: walks `skills/<name>/routing-eval.jsonl`,
  handles JSONL line-comments (`//` / `#`), collects malformed lines
  separately without crashing.
- `runRoutingEval(resolver, fixtures)`: pure scoring. Supports
  negative cases (`expected_skill: null` — nothing should match) and
  an `ambiguous_with` allow-list for skills that co-fire with
  always-on handlers (signal-detector, brain-ops, ingest).

Outcomes per fixture: `pass`, `missed`, `ambiguous`, `false_positive`.
Metrics: `top1Accuracy`, `passed`, `missed`, `ambiguous`,
`falsePositives`.

## Integration: check-resolvable runs Layer A by default

`checkResolvable()` now loads `routing-eval.jsonl` fixtures from every
skill, runs the structural eval, and appends non-pass outcomes as
warning-severity issues. New issue types:

- `routing_miss`        — expected skill did not match
- `routing_ambiguous`   — expected matched AND unexpected skills
- `routing_false_positive` — negative case unexpectedly matched
- `routing_fixture_lint` — linter or malformed-JSONL finding

All four are warnings — routing issues don't break exit in default
mode, but `--strict` promotes them (D-CX-3 contract). Advisories
without breaking CI.

## New CLI verb: `gbrain routing-eval`

Standalone Check 5 runner. `--json` envelope, `--llm` flag reserved,
`--skills-dir` override. Exit codes: 0 clean, 1 any failure/lint, 2
setup error. Suitable for CI gating separately from check-resolvable.

Removed from DEFERRED in CLI: `{check: 5, name: trigger_routing_eval}`.
Check 6 (brain_filing) still deferred; lands in W3.

## Seed fixtures

- skills/query/routing-eval.jsonl
- skills/citation-fixer/routing-eval.jsonl (includes a negative case)

These are intentionally modest. Additional fixtures per skill are the
natural next step; routing-eval itself passes cleanly under
check-resolvable default mode even when fixtures surface real gaps
(they're warnings, not errors). Running `gbrain routing-eval` reveals
the gaps immediately.

## Tests (34 new cases + updated integrations)

- test/routing-eval.test.ts: full harness coverage including
  normalization, trigger extraction (quoted and unquoted), indexer,
  structural match with ambiguity + always-on exemption, fixture
  linter (verbatim-equality rule, unknown-skill rule, shape rule,
  negative-case skip), JSONL loader (comments, malformed lines,
  missing dirs, underscore/dot skipping), and every runRoutingEval
  outcome (pass, miss, ambiguous, negative-pass, false-positive, empty).

- test/check-resolvable-cli.test.ts: updated DEFERRED unit test +
  `--json` envelope test + `--verbose` test to reflect Check 5
  shipping.

140/140 passing across the W1 + W2 surface.

## Live smoke

`gbrain routing-eval --json` against the current gbrain repo: 6
fixtures, 1 passing, 5 missed. The misses correctly surface
resolver-trigger narrowness (intents users naturally phrase differently
than trigger text). Fixtures will iterate in follow-up PRs; the
machinery ships now.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: v0.17.0 W3 — Check 6 brain filing audit

Check 6 ships. Every skill that writes brain pages is now audited
against a machine-readable filing-rules doc at
`skills/_brain-filing-rules.json`.

## New: skills/_brain-filing-rules.json

Canonical filing rules, JSON (D-CX-8: the pre-existing yaml-lite
parser handles flat maps only, so YAML would have needed a new
dependency for one file). The companion `_brain-filing-rules.md`
stays as the human explainer. 14 rule entries + explicit
`sources_dir` carve-out for bulk/raw data.

## New module: src/core/filing-audit.ts

- `loadFilingRules(skillsDir)`: returns parsed doc or null (missing
  file → no-op; malformed JSON throws loud).
- `allowedDirectories(rules)`: normalized set of every rules[]
  directory + sources_dir.
- `runFilingAudit(skillsDir)`: walks skills/*/SKILL.md, parses
  frontmatter, audits any skill with `writes_pages: true`.

Two checks per qualifying skill:
  1. `writes_to:` list is non-empty.
  2. Every entry in `writes_to:` appears in allowedDirectories.

Both failures emit warning-severity issues. No errors — advisories
only, per D-CX-3.

## Distinction: writes_pages vs mutating (D-CX-7)

v0.17 introduces a new boolean frontmatter field `writes_pages:`.
`mutating: true` already means "has any side effect" (cron
schedulers, report writers, config mutators). Filing audit targets
ONLY skills with `writes_pages: true`, correctly excluding side-
effect-but-not-page-writing skills. The codex outside voice caught
this: conflating the two fields would drag ~100 skills into
filing-audit noise in the reference OpenClaw deployment.

## Integration: check-resolvable runs Check 6 by default

`checkResolvable()` calls `runFilingAudit(skillsDir)` and appends
issues as warnings. On missing/malformed rules doc, surfaces a
single advisory rather than bailing.

`DEFERRED` array in the CLI is now empty — v0.17 ships both Check 5
(W2) and Check 6 (W3). The export stays in place (stable --json
field) for future deferred checks.

## Seeded frontmatter on 7 canonical writers

Added `writes_pages: true` + `writes_to:` to:
- brain-ops (people, companies, deals, concepts, meetings)
- enrich (people, companies)
- ingest (people, companies, concepts, meetings, sources)
- idea-ingest (people, concepts, sources)
- media-ingest (concepts, people, companies, sources)
- meeting-ingestion (meetings, people, companies)
- signal-detector (people, companies, concepts)

Live smoke: `gbrain check-resolvable --json` on gbrain repo shows
`ok: true`, zero filing errors, zero filing warnings on seeded
skills. Every other mutating:true skill (citation-fixer,
cron-scheduler, data-research, maintain, migrate, minion-orchestrator,
reports, setup, skill-creator, soul-audit, webhook-transforms)
correctly skipped as side-effectful-but-not-page-writing.

## Tests (17 new cases + 3 updated CLI integrations)

test/filing-audit.test.ts covers:
  - rules loader: missing (null), valid, malformed (throw),
    non-array rules (throw)
  - directory normalization (trailing slash, leading slash)
  - clean case
  - missing writes_to on writes_pages:true
  - unknown directory
  - D-CX-7: mutating:true alone does not trigger audit
  - writes_pages:false skips
  - no frontmatter skips
  - inline `writes_to: [a, b]` syntax
  - block `writes_to:\n  - a` syntax
  - sources/ allowed
  - underscore/dot dir skipping
  - total counts (totalScanned vs writesPagesSkills)
  - missing dir graceful
  - action string quality guard

Plus: CLI integration tests updated for empty DEFERRED array (Checks
5 and 6 both shipped).

158/158 passing across the v0.17 foundation + W1 + W2 + W3 surface.

## v0.18 preview (D-CX-13)

v0.17 filing-audit is declaration-level only. A future
`gbrain filing-audit --pages` walks the brain itself, infers primary
subject from page content via LLM judgment, and flags actual
misfilings vs. declarations. Declaration audit is the leading
indicator; pages audit is the ground truth.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: v0.17.0 W4 — gbrain skillify {scaffold,check} subcommand namespace

The essay's "skillify it!" verb becomes a CLI primitive pair. Two
subcommands, both promoted/factored so there's one source of truth:

## `gbrain skillify scaffold <name>` (mechanical)

Pure file generation. Zero LLM, zero judgment. Writes 5 stub files
atomically:

  1. skills/<name>/SKILL.md              frontmatter + body template
  2. skills/<name>/scripts/<name>.mjs    deterministic-code stub
  3. skills/<name>/routing-eval.jsonl    routing fixture seed
  4. test/<name>.test.ts                 vitest skeleton
  5. Appended trigger row to the detected resolver file (RESOLVER.md
     or AGENTS.md — whatever W1's auto-detect found)

Flags: --description (required), --triggers, --writes-to,
--writes-pages, --mutating, --force, --dry-run, --json, --skills-dir.

Kebab-case name validation (`^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$`).
Works against gbrain-native RESOLVER.md layout AND OpenClaw-native
AGENTS.md-at-workspace-root layout (W1 interop).

## `gbrain skillify check [path]` (audit)

Promoted from scripts/skillify-check.ts per codex D-CX-2. The legacy
script stays as a 12-line shim that delegates to the new module so
existing callers (docs, cron, tests) keep working.

Wrapped in a subcommand namespace: `gbrain skillify {scaffold, check}`
is one coherent verb for the whole post-task loop. The essay's
"skillify it!" triggers the markdown skill, which orchestrates the
CLI primitives.

## Idempotency contract (D-CX-7)

`skillify scaffold --force` regenerates stub FILES but never re-appends
a resolver row that already references `skills/<name>/SKILL.md`.
Unit test pins this: two applies produce one resolver row, not two.

## D-CX-9 SKILLIFY_STUB sentinel

Every scaffolded script + SKILL.md body carries a SKILLIFY_STUB
sentinel. `check-resolvable` walks every skill's script dir looking
for the marker and emits a `skillify_stub_unreplaced` warning when
found. Default mode: advisory. `--strict` mode: error, blocks CI.

This is the gate that catches "we scaffolded and forgot to implement"
— the exact failure codex flagged as "scaffold verification is
theater" in the outside-voice review.

## Files

- NEW src/core/skillify/templates.ts (template strings)
- NEW src/core/skillify/generator.ts (planScaffold / applyScaffold +
  SkillifyScaffoldError with typed error codes)
- NEW src/commands/skillify.ts (top-level dispatcher + scaffold handler)
- NEW src/commands/skillify-check.ts (promoted check logic)
- scripts/skillify-check.ts: rewritten to 12-line shim
- skills/skillify/SKILL.md: Phase 2 now references the scaffold
  primitive; legacy manual path kept for extending existing skills
- src/cli.ts: `skillify` added to CLI_ONLY + dispatcher
- src/core/check-resolvable.ts: SKILLIFY_STUB sentinel scan + new
  issue type `skillify_stub_unreplaced`

## Tests (14 new scaffold cases)

test/skillify-scaffold.test.ts covers:
  - SKILL_NAME_PATTERN validation (kebab-case, no spaces, no
    leading digit, no underscores/uppercase)
  - planScaffold against fresh + existing-file + --force paths
  - SKILLIFY_STUB sentinel presence in SKILL.md AND script stub
    (both gate paths)
  - D-CX-7 idempotency: resolverAppend null when row pre-exists,
    second apply doesn't duplicate the row
  - TBD-trigger placeholder when --triggers empty
  - writes_pages / writes_to / mutating flow through to frontmatter
  - applyScaffold writes files + appends resolver
  - Full AGENTS.md-layout workspace interop (W1)

Existing test/skillify-check.test.ts still passes against the legacy
shim — zero regression for downstream consumers.

178/178 passing across v0.17 foundation + W1..W4.

## Live smoke

\`gbrain skillify scaffold webhook-verify --description "verify incoming
webhook signatures" --triggers "verify webhook,check tunnel"
--skills-dir /tmp/smoke --dry-run\` produces the expected 4-file plan
plus a 115-byte resolver append. \`--help\` works on both the top-level
and scaffold levels.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: v0.17.0 W5 — gbrain skillpack install (deps closure + lockfile + diff/dry-run)

The essay's "drop it into YOUR OpenClaw" promise lands as a CLI
verb. One command installs a curated bundle of gbrain skills + the
shared convention files they depend on into a target OpenClaw
workspace. Data-loss protected, concurrency-safe, atomic on the
AGENTS.md managed block.

## openclaw.plugin.json refresh

- Bumped version from stale 0.4.1 → 0.17.0 (codex flagged this drift
  F-ENG-4 / D-CX-4).
- Expanded curated skill list from 7 → 25. Uses skills/manifest.json
  top-level (v0.10.0 sourced) minus setup/migrate/publish
  (install-time / code+skill pairs) minus private skills.
- Added \`shared_deps: [...]\` listing convention files every skill
  references: conventions/, _brain-filing-rules.{md,json},
  _output-rules.md. Installer always pulls these (D-CX-10
  dependency closure).
- Added \`excluded_from_install: [...]\` for setup/migrate/publish —
  surfaces the intentional exclusion as data rather than a comment.

## New module: src/core/skillpack/bundle.ts

- \`findGbrainRoot(start)\` — walks up looking for openclaw.plugin.json
  + src/cli.ts. The pair identifies a gbrain checkout.
- \`loadBundleManifest(root)\` — strict validation + typed BundleError
  codes (manifest_not_found, manifest_malformed, skill_not_found).
- \`enumerateBundle({gbrainRoot, skillSlug?, manifest})\` — flat list
  of source → target-relative paths. When skillSlug is set, scopes
  to that one skill BUT always pulls shared_deps. \`--all\` walks every
  skill in the manifest.
- \`bundledSkillSlugs(manifest)\` — sorted slugs for \`skillpack list\`.

## New module: src/core/skillpack/installer.ts

- \`planInstall(opts)\` — builds InstallPlan with per-file
  existing/identical diff state. Pure; no writes.
- \`applyInstall(plan, opts)\` — writes files + managed block with
  the contracts below.
- \`diffSkill(root, slug, skillsDir)\` — read-only per-file status
  for \`skillpack diff <name>\`.

**Per-file diff protection (D-CX-3 / F4):**
  wrote_new            fresh file
  wrote_overwrite      local diff + --overwrite-local passed
  skipped_identical    bytes match the bundle (silent re-install)
  skipped_locally_modified  target differs + no --overwrite-local
  → PROTECTED DEFAULT

**Concurrency + atomic AGENTS.md (D-CX-11):**
  - \`.gbrain-skillpack.lock\` at workspace root. Acquired on the
    first write, released in finally.
  - Lock stale threshold configurable (default 10min). --force-unlock
    overrides.
  - Managed-block writes via tmp-file-plus-rename (atomic on POSIX).

**Managed-block format:**
  <!-- gbrain:skillpack:begin -->
  <!-- Installed by gbrain <version> — do not hand-edit between markers. -->
  | Trigger | Skill |
  |---------|-------|
  | "alpha" | \`skills/alpha/SKILL.md\` |
  | ...
  <!-- gbrain:skillpack:end -->

  extractManagedSlugs() roundtrips: single-skill installs accumulate
  into the same block rather than overwriting each other.

## New CLI: gbrain skillpack {list, install, diff, check}

Namespaced alongside W4's \`gbrain skillify\`. Subcommands:
  list             bundle inventory (human + --json)
  install <name>   single skill + deps closure
  install --all    entire curated bundle
  diff <name>      per-file diff vs target; read-only
  check            delegates to the pre-existing skillpack-check
                   (same CLI just namespaced)

Flags on install: --overwrite-local, --force-unlock, --dry-run,
--json, --skills-dir, --workspace.

Exit codes: 0 clean, 1 files skipped (protected local edits),
2 setup error / lock held.

## Live smoke

\`gbrain skillpack list\`: 25 skills. \`skillpack install query --dry-run\`
against a fresh temp workspace: 12 files planned (SKILL.md,
routing-eval.jsonl, 7 convention files, 3 rule files, managed block
to AGENTS.md). All shared_deps flagged [shared].

## Tests (36 new cases)

test/skillpack-install.test.ts:
  - findGbrainRoot walks up, returns null when absent
  - loadBundleManifest validates + rejects malformed
  - enumerateBundle pulls shared_deps on single-skill scope (D-CX-10)
  - buildManagedBlock + updateManagedBlock: append when absent,
    in-place replace when present, extractManagedSlugs roundtrip
  - planInstall + applyInstall: fresh install, dry-run, idempotency
    (skipped_identical), local-edit protection, --overwrite-local,
    lock-held concurrency (D-CX-11), --force-unlock, atomic
    managed-block write, multi-skill accumulation in managed block,
    AGENTS.md-at-workspace-root interop (W1 cross-check)
  - diffSkill: missing, identical, differs

test/skillpack-sync-guard.test.ts (F-ENG-4):
  - both manifests exist
  - every skill in plugin.json exists on disk
  - every shared_dep exists on disk
  - plugin.json skills ⊂ skills/manifest.json
  - excluded skills aren't in the install list
  - plugin version ≥ 0.17 (kills the 0.4.1 stale drift)

204/204 passing across the v0.17 foundation + W1..W5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: v0.17.0 guards — privacy scrub + OpenClaw-reference E2E + v0.16.4 regression

Three ship-blocker work items from the eng review + codex outside
voice round out v0.17:

## scripts/check-privacy.sh (CLAUDE.md:550 enforcement)

Greps for the banned OpenClaw fork name (case-insensitive) across
tracked files. Two modes:
  scripts/check-privacy.sh           scan working tree
  scripts/check-privacy.sh --staged  scan git-staged files (pre-commit)

Exit 1 on any finding outside the allow-list. Allow-list covers files
where the name is legitimately present: this script itself (defines
the rule), CLAUDE.md (the canonical rule text), llms-full.txt
(auto-generated from CLAUDE.md), the historical upgrade guide, and
test/integrations.test.ts (whose personal-info regex ENFORCES the
rule against recipes/).

Scrubbed existing leaks:
  - CHANGELOG.md:366 reference in a closes-# line → "from the
    OpenClaw reference deployment"
  - test/doctor-minions-check.test.ts:171 comment → "an OpenClaw
    host's cron script"
  - test/plugin-loader.test.ts fixture plugin name → "openclaw-ref"

## test/e2e/openclaw-reference-compat.test.ts (ship-blocker gate)

The test that proves v0.17 delivers on the headline claim. New
fixture at test/fixtures/openclaw-reference-minimal/ mimics the
reference OpenClaw deployment layout: AGENTS.md at workspace root,
skills/ below, no manifest.json. Four fixture skills
(signal-detector, query, brain-ops, context-now).

Every v0.17 surface gets exercised end-to-end:
  - autoDetectSkillsDir with $OPENCLAW_WORKSPACE (D-CX-4 priority)
  - loadOrDeriveManifest walks SKILL.md (F-ENG-1 auto-manifest)
  - checkResolvable accepts AGENTS.md at workspace root, all 4
    skills reachable via resolver rows, zero errors
  - Filing audit clean (brain-ops declares writes_pages+writes_to)
  - CLI subprocess via `--skills-dir` → exit 0
  - CLI subprocess via $OPENCLAW_WORKSPACE (no flag) → exit 0,
    correct skillsDir detection
  - skillpack install against the layout writes managed block into
    AGENTS.md at workspace root

This is THE ship-blocker test. If the W1 + W5 stack ever regresses
against an AGENTS.md-layout workspace, this fails first.

## test/regression-v0_16_4.test.ts (F-ENG-8)

Guards v0.17 against adding "surprise" warnings. Builds a clean
fixture matching v0.16.4 canonical shape (manifest.json, RESOLVER.md,
2 skills, no routing-eval fixtures, no writes_pages). Runs v0.17
checkResolvable and asserts:
  - zero errors, zero routing_*/filing_*/skillify_stub_* warnings
  - JSON envelope keys unchanged (errors, warnings, issues, ok,
    summary) — deprecated `issues[]` still equals errors ∪ warnings
  - summary shape unchanged

If someone adds a new check that fires unexpectedly on a v0.16.4-era
fixture, this test catches it immediately.

## Fixture

test/fixtures/openclaw-reference-minimal/
├── AGENTS.md                       (4 rows, 3 sections)
└── skills/
    ├── brain-ops/SKILL.md          (writes_pages+writes_to)
    ├── context-now/SKILL.md
    ├── query/SKILL.md
    └── signal-detector/SKILL.md

Intentionally small (4 skills, 1 AGENTS.md, ~30 lines total) so the
fixture is maintainable. The OPENCLAW-reference deployment has 107
skills — this fixture is the minimum shape that exercises the full
v0.17 code path.

## Tests

215/215 passing across the full v0.17 surface:
  - foundation + W1 + W2 + W3 + W4 + W5 (204)
  - regression-v0_16_4 (3)
  - openclaw-reference-compat (7)
  - privacy guard (separate bash; exits 0 clean)

Plus: privacy pre-commit hook is a drop-in wrapper (documented in
the script header). Wiring into .github/workflows is a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* release: v0.17.0 — skillify goes end-to-end

Every skill. Every check. Every install. One command each.

Five workstreams land in one release:
  - W1: AGENTS.md + auto-manifest + env-priority
  - W2: Check 5 routing eval
  - W3: Check 6 brain filing
  - W4: gbrain skillify {scaffold,check}
  - W5: gbrain skillpack {list,install,diff}

Plus D-CX-3 foundation (errors/warnings split + --strict), plus
codex outside-voice fixes (D-CX-1..12 applied), plus privacy pre-
commit guard, plus OpenClaw-reference E2E fixture, plus v0.16.4
regression guard.

Live against the reference OpenClaw deployment: 102 skills detected
via auto-manifest, 15 unreachable errors + 108 warnings surfaced —
exactly the essay's "~15% dark" finding. The magic word from the
essay finally works the way the essay describes.

Tests: 2156 unit (178 new) + 152 E2E Tier 1 + 3 Tier 2 + 8 new
openclaw-reference fixture cases. 0 failures across all tiers.
Plan + reviews: ~/.claude/plans/p1-lets-just-vast-blanket.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(test): add missing 'strict' field to 5 Flags literals in check-resolvable-cli.test.ts

CI failed `tsc --noEmit` after the D-CX-3 errors/warnings split added
`strict: boolean` as a required field on the `Flags` interface. Five
test sites in test/check-resolvable-cli.test.ts still construct
Flags object literals (for direct `resolveSkillsDir()` calls) and
hadn't been updated.

Added `strict: false` to all five literals:
  - line 129  --skills-dir absolute path
  - line 135  --skills-dir relative path
  - line 148  no --skills-dir
  - line 160  no --skills-dir + no env
  - line 178  --skills-dir + OPENCLAW_WORKSPACE (REGRESSION-GATE)

Unit tests: 207/207 pass across the v0.19 surface. tsc --noEmit
exits 0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: adopt gstack's branch-scoped CHANGELOG rule + rewrite v0.19.0 entry

CLAUDE.md gains a new top section before "CHANGELOG voice" that codifies
what gstack's CLAUDE.md already says: CHANGELOG is user-facing product
release notes, not a log of internal decisions. Every entry describes
what THIS branch adds vs master. Plan-file IDs, decision tags (D-CX-#,
F-ENG-#), review rounds, test counts as marketing, and contributor-
facing metrics don't belong in it.

The v0.19.0 entry is rewritten to the new bar:

Removed:
- Version-collision note about v0.17.0/v0.18.0 shipping on master
- All D-CX-## and W# tags (meaningless outside the plan file)
- "codex caught" / CEO + Eng review round-up narrative
- Plan file path reference
- "215 new cases across 13 test files" marketing metrics
- W1..W5 bucketing in itemized changes

Kept / sharpened:
- User-facing headline (what your agent can now do)
- Numbers that mean something to users (unreachable-skills count,
  scaffold timing, pre/post AGENTS.md support)
- Upgrade instructions
- Added/Changed/Fixed/For-contributors itemized sections (standard
  keep-a-changelog shape)

Version sequence (`grep "^## \["`) is contiguous v0.19.0 → v0.16.4.
Privacy guard clean. Tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: update README/CLAUDE/TODOS for v0.19.0 skills + skillify loop

Skill count was stale (README said 26, actual is 28: skillify + skillpack-check
were missing from the tables and count). Corrected throughout. Marked TODOS item
"Checks 5 + 6 deferred in PR #325" as completed in v0.19 — they shipped as real
implementations, not just filed issues.

README:
- Skill count 26 → 28 (headline, install flow, table section, architecture diagram)
- Added `skillify` + `skillpack-check` rows to the operational skills table
- Rewrote the "Skillify" section to lead with the four v0.19 CLI verbs
  (`gbrain skillify scaffold/check`, `gbrain skillpack list/install/diff`,
  `gbrain routing-eval`, `gbrain check-resolvable --strict`) instead of
  describing the pre-v0.19 state. Added the "works on your OpenClaw" pitch
  around AGENTS.md + auto-manifest. Added the "drop 25 curated skills into
  your OpenClaw" section for skillpack install.
- Added v0.19 skills block + v0.18 multi-source + v0.17 dream to the Commands
  reference at the bottom.
- Standalone instruction sets count: 25 → 28 (with a parenthetical noting
  the curated 25-skill bundle that `skillpack install` ships).

CLAUDE.md:
- Skill count 26 → 28 in the Skills section.
- New "Skillify loop (v0.19)" sub-bullet listing skillify + skillpack-check.
- Noted that `AGENTS.md` is also accepted as a resolver filename.

TODOS.md:
- Created "## Completed" section at the top.
- Moved the "Checks 5 + 6" item there with completion note linking to the
  actual implementation files (routing-eval.ts + filing-audit.ts).

Privacy scan clean. Version sequence contiguous v0.19.0 → v0.16.4.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(test): regenerate llms-full.txt + llms.txt after README/CLAUDE edits

CI failed on `build-llms generator > committed llms.txt + llms-full.txt
match current generator output`. The drift was expected: the prior
commit edited README.md and CLAUDE.md (skill count + skillify section),
both of which are inlined into llms-full.txt by `scripts/build-llms.ts`.

Fix: `bun run build:llms` + commit the regenerated output.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Wintermute <wintermute@garrytan.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 19:34:27 -07:00

30 KiB
Raw Blame History

TODOS

Completed

Checks 5 + 6 for check-resolvable

Completed: v0.19.0 (2026-04-22)

Both checks shipped as real implementations, not just filed issues:

  • Check 5 (trigger routing eval): src/core/routing-eval.ts + gbrain routing-eval CLI. Structural layer runs in check-resolvable by default; --llm opts into LLM tie-break. Fixtures live at skills/<name>/routing-eval.jsonl.
  • Check 6 (brain filing): src/core/filing-audit.ts + skills/_brain-filing-rules.json. New writes_pages: + writes_to: frontmatter. Warning-only in v0.19, error in v0.20.

DEFERRED[] in src/commands/check-resolvable.ts is now empty — v0.19 shipped both deferred checks as working code paths, not as issue URLs. The export stays in place for future deferred checks.

P1 (BrainBench v1.1 — categories deferred from PR #188)

BrainBench Cat 5: Source Attribution / Provenance

What: Eval that gbrain correctly cites the right page when claiming fact F, and resolves source-conflict cases (3 sources disagree on $5M raise — which wins?). 200 queries across citation/provenance/conflict sub-categories on a 300-entity dataset with deliberately-conflicting sources.

Why deferred from PR #188: Needs ~$100-200 of Opus tokens to generate the conflict-graph dataset. v1 scope was procedural-only.

Threshold: citation_recall > 90%, citation_precision > 85%, conflict_resolution > 70%.

Depends on: Identity Resolution (Cat 3) shipped — uses same world generator pattern.

What: Cat 10 (Robustness/Adversarial) covered code-fence leak and false-positive substrings on 22 hand-crafted cases. v1.1 extends this to 500+ prose-heavy pages with realistic narrative noise. Tests link precision in the wild, not just edge cases.

Why deferred from PR #188: Needs prose-heavy generated corpus (~$100-150 Opus). Existing 22-case eval already caught + fixed the code-fence leak bug.

Threshold: link_precision > 95% on prose, type_accuracy > 80% on varied phrasing.

BrainBench Cat 8: Skill Behavior Compliance

What: Replays 100 inbound signals through a real LLM agent loop with gbrain skills loaded. Measures: brain-first lookup compliance, back-link iron-law adherence, citation format compliance, tier escalation correctness.

Why deferred: Needs real LLM API loop (~$2K total — most expensive single category).

Threshold: brain_first_compliance > 95%, back_link_compliance > 90%, citation_format > 95%.

BrainBench Cat 9: End-to-End Workflows

What: 50 end-to-end scenarios across meeting ingestion, email-to-brain, daily-task-prep, briefing generation, sync cycle. Rubric-graded (10-15 criteria each).

Why deferred: Needs LLM agent loop (~$1K). Plus 50 hand-built rubrics.

Threshold: 80% scenario pass rate per workflow.

BrainBench Cat 11: Multi-modal Ingestion

What: PDF/image/audio/video ingestion accuracy. 50 PDFs, 30 images, 20 audio files, 10 videos, 30 HTML pages. Per-modality recall and fidelity metrics.

Why deferred: Needs licensed real datasets (Common Voice for audio etc.). Dataset curation is the bulk of the work.

Threshold: PDF text fidelity > 95% (text-based) / > 80% (scanned), audio WER < 15%, entity_recall > 80% post-ingestion.

BrainBench Cat 1+2 at full scale

What: Existing benchmark-search-quality.ts (29 pages, 20 queries) and benchmark-graph-quality.ts (80 pages, 5 queries) currently pass at small scale. v1.1 extends both to 2-3K rich-prose pages generated via Opus to surface scale-dependent failures (tied keyword clusters, hub-node fan-out, prose-noise extraction precision).

Why deferred from PR #188: Needs ~$200-300 of Opus tokens for the rich corpus. The 80-page version already proves algorithmic correctness; scale-up proves it survives real-world load.

Threshold: maintain v1 metrics at 30x scale.

v0.10.4: inferLinkType prose precision fix

Shipped in PR #188. BrainBench Cat 2 rich-corpus type accuracy went from 70.7% → 88.5%. Fix: widened verb regexes (added "led the seed/Series A", "early investor", "invests in", "portfolio company", etc.), tightened ADVISES_RE to require explicit advisor rooting (generic "board member" matches investors too), widened context window 80→240 chars, added person-page role prior (partner-bio language → invested_in for outbound company refs only). Per-type after fix: invested_in 91.7% (was 0%), mentions 100%, attended 100%. works_at 58% and advises 41% are next iteration's residuals.

v0.10.5: inferLinkType residuals (works_at, advises)

What: After the v0.10.4 fix, two link types still under-perform on rich prose. Drive these to >85% type accuracy in next iteration.

works_at: 58% type accuracy. Engineer/employee pages use varied phrasings the regex doesn't catch ("spent some time at", "joined the team", narrative "is currently at" without a verb). Approach: extend WORKS_AT_RE; consider employee-role page prior similar to partner prior.

advises: 41% type accuracy. Advisor pages often describe board roles without using the word "advisor" explicitly ("on Beta Health's board", "joined Beta as a board member"). The v0.10.4 fix tightened ADVISES_RE to require "advisor" rooting to avoid false positives from investors. Need a tighter signal that distinguishes "advisor on board" from "investor on board" — likely an advisor-role page prior plus verb-pattern combinations.

Threshold: Cat 2 rich-prose type accuracy > 92% (currently 88.5%).

v0.10.4: gbrain alias resolution feature (driven by Cat 3)

What: Add an alias table to gbrain so "Sarah Chen" / "S. Chen" / "@schen" / "sarah.chen@example.com" resolve to one canonical entity. Schema: aliases (id, slug, alias_text) with a unique index. Search blends alias matches into hybrid scoring.

Why: BrainBench Cat 3 measured 31% recall on undocumented aliases — that's the v0.10.x baseline. With alias table, should jump to 80%+.

Depends on: Cat 3 baseline (shipped in PR #188).

P1

Minions shell jobs — Phase 2 scheduling (deferred from v0.13.0)

What: minion_schedules table + autopilot-cycle scanner that submits due shell jobs.

Why: v0.13.0 moves shell scripts to Minions but still leaves scheduling in the host crontab. Your OpenClaw's scripts/service-manager.sh + crontab is the only piece left on the host side. A DB-driven scheduler would mean a single gbrain autopilot --install replaces the host crontab entirely, scheduling is visible via gbrain jobs list --scheduled, and downtime-on-one-machine tolerance improves (schedule is shared DB state, not per-host crontab).

Pros: Canonical host-agnostic deployment. No more host-specific crontab.

Cons: Cross-engine migration complexity (new table on both PGLite + Postgres). Autopilot-cycle scanner needs to handle missed-schedule semantics (fire-once-on-startup or skip-if-past-now), and this is where every other cron-like system has historically accrued bugs.

Depends on: v0.13.0 shell jobs shipped.

gbrain crontab-to-minions <file> migration helper (deferred from v0.13.0)

What: Parse an existing crontab file, emit a proposed rewrite using gbrain jobs submit shell ... for each deterministic entry, keep LLM-requiring entries as-is.

Why: Hand-rewriting ~14 OpenClaw cron entries is error-prone and one-shot. A helper would make the migration reversible and auditable (diff the before/after crontab, dry-run the first N, commit).

Pros: Removes the "rewrite 14 lines by hand" tax every agent operator pays on adoption.

Cons: Crontab parsing is historically fiddly (5-field vs 6-field, @hourly aliases, Vixie extensions, env vars in crontab). Could misrewrite entries with shell substitution.

Depends on: v0.13.0 shell jobs shipped.

Batch the DB-source extract read path (deferred from v0.12.1)

What: extractLinksFromDB and extractTimelineFromDB at src/commands/extract.ts:447, 504 issue one engine.getPage(slug) per slug after engine.getAllSlugs(). On a 47K-page brain that's still 47K serial reads over the Supabase pooler.

Why: v0.12.1 fixed the write-side N+1 with batched INSERTs (~100x fewer round-trips). The read side still does serial getPage() calls — each fetches compiled_truth + timeline + frontmatter (tens of KB per page). On a 47K-page Supabase brain that's ~10-20 minutes of read latency before any work happens. The v0.12.0 orchestrator's backfill uses --source db, so this stays slow until fixed.

Pros: Mirrors the write-side fix on the read path. Combined with batched writes, full re-extract on a 47K-page brain should drop from "minutes" to "seconds" end-to-end. Eliminates the implicit listPages-pagination-mutation learning risk by giving you a snapshot read.

Cons: New engine method (getPagesBatch(slugs: string[]) → Promise<Page[]> or a streaming cursor) needs to land on both PGLite and Postgres. Memory budget — a 47K-page brain with ~30KB/page is ~1.4GB if loaded all at once; needs chunked iteration (e.g., 500 slugs/query, stream-process).

Context: Codex's plan-time review and the testing/performance specialists at ship time both flagged this. Filed during v0.12.1 to ship the bug fix without scope creep. Approach: add getPagesBatch(slugs) returning chunked results, then update the 4 DB-source extract paths to consume it.

Depends on: v0.12.1 ships first.

Batch embedding queue across files

What: Shared embedding queue that collects chunks from all parallel import workers and flushes to OpenAI in batches of 100, instead of each worker batching independently.

Why: With 4 workers importing files that average 5 chunks each, you get 4 concurrent OpenAI API calls with small batches (5-10 chunks). A shared queue would batch 100 chunks across workers into one API call, cutting embedding cost and latency roughly in half.

Pros: Fewer API calls (500 chunks = 5 calls instead of ~100), lower cost, faster embedding.

Cons: Adds coordination complexity: backpressure when queue is full, error attribution back to source file, worker pausing. Medium implementation effort.

Context: Deferred during eng review because per-worker embedding is simpler and the parallel workers themselves are the bigger speed win (network round-trips). Revisit after profiling real import workloads to confirm embedding is actually the bottleneck. If most imports use --no-embed, this matters less.

Implementation sketch: src/core/embedding-queue.ts with a Promise-based semaphore. Workers await queue.submit(chunks) which resolves when the queue has room. Queue flushes to OpenAI in batches of 100 with max 2-3 concurrent API calls. Track source file per chunk for error propagation.

Depends on: Part 5 (parallel import with per-worker engines) -- already shipped.

P0

Fix bun build --compile WASM embedding for PGLite

What: Submit PR to oven-sh/bun fixing WASM file embedding in bun build --compile (issue oven-sh/bun#15032).

Why: PGLite's WASM files (~3MB) can't be embedded in the compiled binary. Users who install via bun install -g gbrain are fine (WASM resolves from node_modules), but the compiled binary can't use PGLite. Jarred Sumner (Bun founder, YC W22) would likely be receptive.

Pros: Single-binary distribution includes PGLite. No sidecar files needed.

Cons: Requires understanding Bun's bundler internals. May be a large PR.

Context: Issue has been open since Nov 2024. The root cause is that bun build --compile generates virtual filesystem paths (/$bunfs/root/...) that PGLite can't resolve. Multiple users have reported this. A fix would benefit any WASM-dependent package, not just PGLite.

Depends on: PGLite engine shipping (to have a real use case for the PR).

ChatGPT MCP support (OAuth 2.1)

What: Add OAuth 2.1 with Dynamic Client Registration to the self-hosted MCP server so ChatGPT can connect.

Why: ChatGPT requires OAuth 2.1 for MCP connectors. Bearer token auth is NOT supported. This is the only major AI client that can't use GBrain remotely.

Pros: Completes the "every AI client" promise. ChatGPT has the largest user base.

Cons: OAuth 2.1 is a significant implementation: authorization endpoint, token endpoint, PKCE flow, dynamic client registration. Estimated CC: ~3-4 hours.

Context: Discovered during DX review (2026-04-10). All other clients (Claude Desktop/Code/Cowork, Perplexity) work with bearer tokens. The Edge Function deployment was removed in v0.8.0. OAuth needs to be added to the self-hosted HTTP MCP server (or gbrain serve --http when implemented).

Depends on: gbrain serve --http (not yet implemented).

Runtime MCP access control

What: Add sender identity checking to MCP operations. Brain ops return filtered data based on access tier (Full/Work/Family/None).

Why: ACCESS_POLICY.md is prompt-layer enforcement (agent reads policy before responding). A direct MCP caller can bypass it. Runtime enforcement in the MCP server is the real security boundary for multi-user and remote deployments.

Pros: Real security boundary. ACCESS_POLICY.md becomes enforceable, not advisory.

Cons: Requires adding sender_id or access_tier to OperationContext. Each mutating operation needs a permission check. Medium implementation effort.

Context: From CEO review + Codex outside voice (2026-04-13). Prompt-layer access control works in practice (same model as Garry's OpenClaw) but is not sufficient for remote MCP where direct tool calls bypass the agent's prompt.

Depends on: v0.10.0 GStackBrain skill layer (shipped).

P1 (new from v0.7.0)

Constrained health_check DSL for third-party recipes

Completed: v0.9.3 (2026-04-12). Typed DSL with 4 check types (http, env_exists, command, any_of). All 7 first-party recipes migrated. String health checks accepted with deprecation warning + metachar validation for non-embedded recipes.

P1 (new from v0.11.0 — Minions)

Per-queue rate limiting for Minions

What: Token-bucket rate limiting per queue via a new minion_rate_limits table (queue, capacity, refill_rate, tokens, updated_at), with acquire/release in claim().

Why: The #1 daily OpenClaw pain is spawn storms hitting OpenAI/Anthropic rate limits. max_children caps fan-out per parent, but a queue with 50 ready jobs will still slam the API. Every Minions consumer currently reinvents token-bucket in user code.

Pros: First-class rate limiting means no consumer has to roll their own. Composes with max_children (which is per-parent) to give two orthogonal throttles.

Cons: Adds a write hotspot on the rate-limit row. Mitigate by keeping it a simple UPDATE ... WHERE tokens > 0 RETURNING that fails fast and puts the claim back in the pool.

Effort: ~2 hours. Deferred from v0.11.0 to keep the parity PR at a reviewable size.

Depends on: Minions (shipped in v0.11.0).

Minions repeat/cron scheduler

What: BullMQ-style repeatable jobs. queue.add(name, data, { repeat: { cron: '0 * * * *' } }).

Why: Idempotency keys (shipped in v0.11.0) are the foundation. Consumers currently use launchd/cron to fire gbrain jobs submit, but a native scheduler inside the worker would be cleaner and portable across deployments.

Pros: One mental model for both immediate and scheduled work. Idempotency prevents double-fire.

Cons: Every cron library has edge cases (DST, missed intervals on worker restart). Use a battle-tested parser.

Effort: ~1 day.

Depends on: Idempotency keys (shipped in v0.11.0).

Minions worker event emitter

What: worker.on('job:completed', handler) / worker.on('job:failed', ...) instead of polling.

Why: Consumers currently poll getJob(id) to watch state changes. An event API is the ergonomic BullMQ has and Minions doesn't.

Effort: ~4 hours.

waitForChildren(parent_id, n) / collectResults(parent_id) helpers

What: Convenience wrappers over readChildCompletions for common fan-in patterns.

Why: The child_done inbox primitive shipped in v0.11.0. Now add the ergonomic API on top so orchestrators don't have to write the polling loop.

Effort: ~2 hours.

Depends on: child_done inbox primitive (shipped in v0.11.0).

P2

Orchestrator + runner double-write to migrations ledger (deferred from v0.18.2 codex review)

What: src/commands/migrations/v0_18_0.ts:200-208 appends an entry to ~/.gbrain/migrations/completed.jsonl while src/commands/apply-migrations.ts:374-386 also appends one for the same orchestrator run. The dedupe guard in src/core/preferences.ts:120-131 only suppresses duplicate complete entries, not partial entries. Result: distorted wedge counting (3-consecutive-partials-triggers-wedge logic sees 6 partials when it should see 3).

Why: Codex plan-review caught this during PR #356 while verifying the two-migration-systems resume boundary. Not blocking v0.18.2 shipping because it only affects the wedge detection threshold, not correctness of the migration itself.

Fix: Pick one writer (prefer apply-migrations.ts runner as the single source of truth, remove the orchestrator-side append). Fold into feat/agent-migration-devex follow-up PR, which already touches both files for the migrate-command consolidation work.

Depends on: v0.18.2 shipped.

22K-page resync is 30+ minutes on large brains (deferred from v0.18.2 codex review)

What: When a schema migration requires data backfill (e.g., computing page_id from page_slug across all files rows), src/commands/sync.ts:248-251, 311-337 iterates per-file. None of v0.18.2's hardening work shrinks this path. On a 22K-page brain the resync takes 30+ minutes; at 500K pages it would be several hours.

Why: Codex explicitly called out that none of PR #356 or the two follow-up PRs addresses the resync execution model. This is a separate performance-design problem.

Options to explore:

  • (a) Parallel page import via worker pool (Minions-based).
  • (b) Bulk COPY-based import replacing the per-file INSERT.
  • (c) Incremental resync that only rewrites changed rows (needs content hash or updated_at gating).

Priority: P2 now, upgrade to P1 if another heavy migration ships that needs backfill at this scale.

Depends on: v0.18.2 shipped.

Minions: gbrain jobs stats --orphaned (deferred from v0.13.0)

What: New CLI flag / output column surfacing jobs that are waiting with no registered handler on any live worker.

Why: v0.13.0 adds shell jobs that require GBRAIN_ALLOW_SHELL_JOBS=1 on the worker. If an operator submits a shell job but no worker with the flag is running, the row sits in waiting silently. The CLI's starvation warning + docs help at submit time; this TODO surfaces the problem at operational-check time.

Pros: Closes the "did my cron actually run" ambiguity for multi-machine deployments.

Cons: Knowing "no worker has this handler registered" requires worker heartbeat tracking, which Minions doesn't have yet (it's stateless at DB level beyond lock_token). Could be approximated by "no jobs of this name have completed in last N minutes AND count of waiting is > 0."

Depends on: v0.13.0 shell jobs shipped.

Minions: AbortReason plumbing on MinionJobContext (deferred from v0.13.0)

What: Handlers today can't distinguish whether ctx.signal.aborted fired due to timeout, cancel, or lock-loss. v0.13.0 derives this at worker-catch-time from abort.signal.reason, but the handler can't see it directly. Expose ctx.abortReason?: 'timeout' | 'cancel' | 'lock-lost' | 'shutdown' on the context.

Why: Shell handler's kill-sequence today can't decide "retry this" (lock-lost) vs "don't retry, user cancelled" (cancel) — they look the same. A typed AbortReason lets handlers make that decision for themselves.

Pros: Handlers get richer signals.

Cons: Small surface-area addition to the handler API. Not strictly required since the worker already makes the retry/dead decision for them.

Depends on: v0.13.0 shell jobs shipped.

Minions: blocking-mode audit log for true forensic integrity (deferred from v0.13.0)

What: Opt-in mode for shell-audit where appendFileSync failures DO block submission instead of logging-and-continuing.

Why: v0.13.0 ships the audit log in best-effort mode, which means a disk-full attacker can silently disable the forensic trail. Acceptable for v0.13.0 because the primary use is operational ("what did this cron do last Tuesday"), not security forensics. Operators who want fail-closed semantics should have a flag.

Pros: Enables true forensic integrity for deployments that need it.

Cons: Fail-closed means a transient disk issue blocks shell submissions, which can be worse than a missing log line for most operators. Opt-in is the right shape but adds surface area.

Depends on: v0.13.0 shell jobs shipped.

Minions: configurable per-job output buffer sizes (deferred from v0.13.0)

What: Add max_stdout_bytes / max_stderr_bytes to ShellJobParams; override the 64KB/16KB defaults.

Why: 64KB/16KB covers typical OpenClaw scripts today but a verbose benchmark or a debug-dump script could need more.

Depends on: First shell-job author who actually needs it. Don't pre-build the flag.

Security hardening follow-ups (deferred from security-wave-3)

What: Close remaining security gaps identified during the v0.9.4 Codex outside-voice review that didn't make the wave's in-scope cut.

Why: Wave 3 closed 5 blockers + 4 mediums. These are the known residuals. Each is an independent hardening item that becomes trivial as Runtime MCP access control (P0 above) lands.

Items (each a separate small task):

  • DNS rebinding protection for HTTP health_checks. Current isInternalUrl validates the hostname string; DNS resolution happens later inside fetch. A malicious DNS server can return a public IP on first lookup and an internal IP on the actual request. Fix: resolve hostname via dns.lookup before fetch, pin the IP with a custom http.Agent lookup override, re-validate post-resolution. Alternative: use ssrf-req-filter library.
  • Extended IPv6 private-range coverage. Block fc00::/7 (Unique Local Addresses), fe80::/10 (link-local), 2002::/16 (6to4), 2001::/32 (Teredo), ::/128. Current code covers ::1, ::, and IPv4-mapped (::ffff:*) via hex hextet parsing.
  • IPv4 shorthand parsing. 127.1 (legacy 2-octet form = 127.0.0.1), 127.0.1 (3-octet), mixed-radix with trailing dots. Current code handles hex/octal/decimal integer-form IPs but not these shorthand variants.
  • Broader operation-layer limit caps. traverse_graph depth param, plus get_chunks, get_links, get_backlinks, get_timeline, get_versions, get_raw_data, resolve_slugs — all currently accept unbounded limit/depth. Wave 3 only clamped list_pages and get_ingest_log.
  • sync_brain repo path validation. The repo parameter accepts an arbitrary filesystem path. Same threat model as file_upload before wave 3. Add validateUploadPath (strict) for remote callers.
  • file_upload size limit. readFileSync loads the entire file into memory. Trivial memory-DoS from MCP. Add ~100MB cap (matches CLI's TUS routing threshold) and stream for larger files.
  • file_upload regular-file check. Reject directories, devices, FIFOs, Unix sockets via stat.isFile() before readFileSync.
  • Explicit confinement root (H2). file_upload strict mode currently uses process.cwd(). Move to ctx.config.upload_root (or derive from where the brain's schema lives) so MCP server cwd can't be the wrong anchor.

Effort: M total (human: ~1 day / CC: ~1-2 hrs).

Priority: P2 — deferred consciously. Wave 3 closed the easily-exploitable paths. These are the defense-in-depth follow-ups.

Depends on: Security wave 3 shipped. None are blockers for Runtime MCP access control, but all three security workstreams (this, that P0, and the health-check DSL) converge on the same zero-trust MCP goal.

Community recipe submission (gbrain integrations submit)

What: Package a user's custom integration recipe as a PR to the GBrain repo. Validates frontmatter, checks constrained DSL health_checks, creates PR with template.

Why: Turns GBrain from a single-author integration set into a community ecosystem. The recipe format IS the contribution format.

Pros: Community-driven integration library. Users build Slack-to-brain, RSS-to-brain, Discord-to-brain.

Cons: Support burden. Need constrained DSL (P1) before accepting third-party recipes. Need review process for recipe quality.

Context: From CEO review (2026-04-11). User explicitly deferred due to bandwidth constraints. Target v0.9.0.

Depends on: Constrained health_check DSL (P1) — SHIPPED in v0.9.3.

Always-on deployment recipes (Fly.io, Railway)

What: Alternative deployment recipes for voice-to-brain and future integrations that run on cloud servers instead of local + ngrok.

Why: ngrok free URLs are ephemeral (change on restart). Always-on deployment eliminates the watchdog complexity and gives a stable webhook URL.

Pros: Stable URLs, no ngrok dependency, production-grade uptime.

Cons: Costs $5-10/mo per integration. Requires cloud account.

Context: From DX review (2026-04-11). v0.7.0 ships local+ngrok as v1 deployment path.

Depends on: v0.7.0 recipe format (shipped).

gbrain serve --http + Fly.io/Railway deployment

What: Add gbrain serve --http as a thin HTTP wrapper around the stdio MCP server. Include a Dockerfile/fly.toml for cloud deployment.

Why: The Edge Function deployment was removed in v0.8.0. Remote MCP now requires a custom HTTP wrapper around gbrain serve. A built-in --http flag would make this zero-effort. Bun runs natively, no bundling seam, no 60s timeout, no cold start.

Pros: Simpler remote MCP setup. Users run gbrain serve --http behind ngrok instead of building a custom server. Supports all 30 operations remotely (including sync_brain and file_upload).

Cons: Users need ngrok ($8/mo) or a cloud host (Fly.io $5/mo, Railway $5/mo). Not zero-infra.

Context: Production deployments use a custom Hono server wrapping gbrain serve. This TODO would formalize that pattern into the CLI. ChatGPT OAuth 2.1 support depends on this.

Depends on: v0.8.0 (Edge Function removal shipped).

P2 (knowledge graph follow-ups)

What: When gbrain put is called with identical content (status=skipped), runAutoLink still does a full getLinks + per-candidate addLink loop. On N identical writes of a 50-entity page that's 50N round trips.

Why: Defensive reconciliation catches drift between page text and links table, but on truly idempotent writes it's wasted work.

Pros: Lower DB load on cron-style re-syncs. Keeps put_page latency tight under bulk MCP usage.

Cons: Need to track whether links could have drifted independent of content (e.g., a target page was deleted). Conservative approach: only skip auto-link reconciliation if status=skipped AND existing links match desired set (which still requires the getLinks call).

Context: Caught in /ship adversarial review (2026-04-18). Acceptable for v0.10.3 because auto-link runs in a transaction with row locks, so amplification cost is bounded.

Effort estimate: S (CC: ~10min) Priority: P2 Depends on: Nothing.

What: gbrain extract links --source db writes to the same links table that auto_link=false is supposed to opt out of. The two are conceptually distinct (extract is intentional batch op, auto_link is implicit on write), but a user who turned off auto_link expecting "no automatic link writes" might be surprised.

Why: Either the behavior should match (extract checks auto_link too) or the docs should explicitly state extract is a superset.

Pros: Less surprise for users who treat auto_link as a master switch.

Cons: Some users want extract to work even when auto_link is off (e.g. one-time backfill).

Context: Caught in /ship adversarial review (2026-04-18). Documenting for now.

Effort estimate: S (CC: ~10min for docs OR ~20min for code change). Priority: P2 Depends on: Nothing.

Doctor --fix polish from v0.14.1 adversarial review

What: Six deferred findings from v0.14.1 ship-time adversarial review on src/core/dry-fix.ts:

  1. TOCTOU between read and write. attemptFix reads once, writes later. Concurrent editor saves silently overwritten. Fix: re-read immediately before write and compare snapshot, or O_EXCL tempfile + rename.
  2. Fence detection misses 4-backtick and ~~~ fences. isInsideCodeFence only catches ^```$. CommonMark-legal alternates slip through.
  3. expandBullet walk-up is dead code. Loop breaks immediately because baseIndent matches the current line. Remove or make it actually walk up.
  4. Multi-match guard too strict. Skills with the pattern in a table-of-contents AND body get ambiguous_multiple_matches forever. Consider: fix first, re-scan, repeat until fixed-point.
  5. Subprocess spam. getWorkingTreeStatus spawns git status N×M times per doctor --fix. Cache per-skill per-invocation.
  6. doctor --fix --json swallows the auto-fix report. printAutoFixReport returns early on jsonOutput; agents don't see fix outcomes. Emit auto_fix as a top-level key.

Why: None are ship-blockers; all surfaced during v0.14.1 Codex adversarial review. Bundle into one follow-up PR.

Pros: Closes the adversarial findings loop. Better correctness under concurrent edits and JSON-consumer agents.

Cons: Concurrent-edit test is finicky.

Context: v0.14.1 shipped with the 4 critical fixes (shell-injection via execFileSync, no-git-backup detection, EOF newline preservation, proximity-window consistency). These six are the deferred remainder.

Effort estimate: M (CC: ~45min for all six + tests). Priority: P2 Depends on: Nothing.

Completed

Implement AWS Signature V4 for S3 storage backend

Completed: v0.6.0 (2026-04-10) — replaced with @aws-sdk/client-s3 for proper SigV4 signing.