@event4u/agent-config 9.34.0 → 9.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +91 -0
- package/CONTRIBUTING.md +23 -14
- package/dist/agent-src/commands/roadmap/next.md +37 -5
- package/dist/agent-src/scripts/roadmap_gates.ts +119 -4
- package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +25 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/scripts/memory_lookup.ts +1 -2
- package/dist/agent-src/templates/scripts/memory_signal.ts +2 -4
- package/dist/agent-src/templates/scripts/work_engine/hooks/builtin/decision_gate.ts +35 -5
- package/dist/agent-src/templates/scripts/work_engine/hooks/builtin/staged_confirmation.ts +252 -0
- package/dist/agent-src/templates/scripts/work_engine/hooks/builtin/staged_confirmation_store.ts +307 -0
- package/dist/cli/registry.js +2 -2
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/{chunk-REMEZSVP.js → chunk-2TGMGLBL.js} +2 -2
- package/dist/cli-delegate/{chunk-YPAWXXXR.js → chunk-55RI5UXK.js} +17 -0
- package/dist/cli-delegate/{chunk-MA4GFWDY.js → chunk-H6F2CQFI.js} +1 -1
- package/dist/cli-delegate/{chunk-UVKNIJH2.js → chunk-ZAXH3ZWU.js} +3 -0
- package/dist/cli-delegate/cmd_conformance.js +2 -2
- package/dist/cli-delegate/cmd_doctor.js +2 -2
- package/dist/cli-delegate/cmd_export.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +1 -1
- package/dist/cli-delegate/cmd_prune.js +2 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +2 -2
- package/dist/cli-delegate/cmd_uninstall.js +2 -2
- package/dist/cli-delegate/cmd_validate.js +2 -2
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +4 -4
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +259 -78
- package/dist/install/install.mjs +3 -0
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +74 -2
- package/dist/server/schemas/settings.js +1 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/docs/contracts/settings-classes.md +3 -2
- package/package.json +1 -1
- package/src/agent-src/scripts/roadmap_gates.ts +119 -4
- package/src/agent-src/templates/scripts/memory_lookup.ts +1 -2
- package/src/agent-src/templates/scripts/memory_signal.ts +2 -4
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/decision_gate.ts +35 -5
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/staged_confirmation.ts +252 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/staged_confirmation_store.ts +307 -0
- package/src/config/agent-settings.template.yml +11 -2
- package/src/config/quorum-attendance-budget.json +10 -2
- package/src/scripts/_dispatch.bash +6 -2
- package/src/scripts/_lib/session_register.ts +67 -1
- package/src/scripts/ai_council/config.ts +60 -0
- package/src/scripts/ai_council/events_log.ts +103 -4
- package/src/scripts/ai_council/quorum.ts +83 -6
- package/src/scripts/council_cli.ts +36 -1
- package/src/scripts/hooks/turn_end_gate_hook.ts +172 -11
- package/src/scripts/lint_framework_leakage.ts +195 -28
- package/src/scripts/lint_framework_leakage_allowlist.json +94 -86
- package/src/scripts/memory_lookup.ts +1 -2
- package/src/scripts/schemas/command.schema.json +4 -0
- package/src/scripts/schemas/skill.schema.json +4 -0
- package/src/scripts/session_register_hook.ts +238 -18
- package/src/scripts/sessions_cli.ts +377 -12
- package/src/server/schemas/settings.ts +3 -0
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Bootstrap shim for the event4u agent-config suite. This plugin ships ONLY the dispatcher hooks plus one install-pointer skill — ALL content (skills, rules, commands) is installed via `npx -y @event4u/agent-config init`, which writes the file projection AND registers the same hooks in ~/.claude/settings.json. DEPRECATED as a content channel; kept so the marketplace listing stays discoverable and existing installs keep working hooks.",
|
|
9
|
-
"version": "9.
|
|
9
|
+
"version": "9.35.0",
|
|
10
10
|
"keywords": [
|
|
11
11
|
"agent-config",
|
|
12
12
|
"skills",
|
package/CHANGELOG.md
CHANGED
|
@@ -330,6 +330,97 @@ Entry-shape contract: [`docs/contracts/CHANGELOG-conventions.md`](docs/contracts
|
|
|
330
330
|
> that forces a new era split (`# Era: 9.35.x`, etc.) — see
|
|
331
331
|
> [`docs/contracts/CHANGELOG-conventions.md § Era splits`](docs/contracts/CHANGELOG-conventions.md).
|
|
332
332
|
|
|
333
|
+
## [9.35.0](https://github.com/event4u-app/agent-config/compare/9.34.0...9.35.0) (2026-08-12)
|
|
334
|
+
|
|
335
|
+
### Release highlights
|
|
336
|
+
|
|
337
|
+
<!-- Curated head: fill before merge, keep it under 10 lines, and leave `_none_` where it is genuinely the answer. The generated log below is unchanged. -->
|
|
338
|
+
- **Behaviour changes:** _auto-derived, rewrite before merge:_ rule/schema diffs, breaking commits or removed public surface in af968b5, 6d3c9f7.
|
|
339
|
+
- **Default changes + migration:** _none_
|
|
340
|
+
- **Security and correctness:** _none_
|
|
341
|
+
- **Honest nulls:** _none_
|
|
342
|
+
- **Known limitations:** _none_
|
|
343
|
+
|
|
344
|
+
### Features
|
|
345
|
+
|
|
346
|
+
* **leakage-allowlist:** validate the exemptions before scanning, retire position keys ([97d2c29](https://github.com/event4u-app/agent-config/commit/97d2c29ba763af06c45c5e4a4aebbed7fb531561))
|
|
347
|
+
* **sessions:** refuse a claim a peer holds, and report the branch axis ([39cb3b4](https://github.com/event4u-app/agent-config/commit/39cb3b4f2e6d3c0602e5544b7079f242f5a7cd9f))
|
|
348
|
+
* **council:** record the ADR-224 solo floor without enforcing it ([be09d0f](https://github.com/event4u-app/agent-config/commit/be09d0fa8f75e03b3d767f225d67cda53dd36ee8))
|
|
349
|
+
* **turn-end-gate:** refuse a turn that edited a file and verified nothing ([647eca9](https://github.com/event4u-app/agent-config/commit/647eca9830a6ea9b6597a3404f37aac2fe41360d))
|
|
350
|
+
* **gates:** enumerate staged confirmations behind gates --pending ([9cccf4e](https://github.com/event4u-app/agent-config/commit/9cccf4eb789f68dcc67bbf8534824212ea507845))
|
|
351
|
+
* **work-engine:** exactly-once confirmed execution for a staged action ([8588e54](https://github.com/event4u-app/agent-config/commit/8588e54377062126df888b42f02155ad9e28c3b5))
|
|
352
|
+
* **schemas:** add requires_confirmation to the skill and command contracts ([af968b5](https://github.com/event4u-app/agent-config/commit/af968b533a72c2358897fabcfe61bfb1baa7dc87))
|
|
353
|
+
* **cli:** hooks:status --pending enumerates staged confirmations ([442bde3](https://github.com/event4u-app/agent-config/commit/442bde3bb0b047ca85d8b0e5dcb973af4343209c))
|
|
354
|
+
* **work-engine:** exactly-once confirmation store plus a decision-gate seam ([9fe068c](https://github.com/event4u-app/agent-config/commit/9fe068c96a698c1d30ae74d65545b9fa301e72a3))
|
|
355
|
+
* **schemas:** declare requires_confirmation on skills and commands ([6d3c9f7](https://github.com/event4u-app/agent-config/commit/6d3c9f73ccc224517a81effb5f9874f388e140ee))
|
|
356
|
+
|
|
357
|
+
### Bug Fixes
|
|
358
|
+
|
|
359
|
+
* **tests:** a matched grep must not fail the assertion — echo | grep -q under pipefail ([e7352cc](https://github.com/event4u-app/agent-config/commit/e7352ccedbd9cd996220db0293faf6d42a67529e))
|
|
360
|
+
* **leakage-allowlist:** close the seven R2 findings, including two on this PR ([c859249](https://github.com/event4u-app/agent-config/commit/c8592492f32d6c5072eb361fde9081457855d5a7))
|
|
361
|
+
* **sessions:** drop the ROADMAP_CLAIM_REL import that lost its last reference ([0d6a1c0](https://github.com/event4u-app/agent-config/commit/0d6a1c0e4670a30f7f00475d7463b986f8fa697b))
|
|
362
|
+
* **session-register:** work in the PR review — path containment, token-boundary match, claim lifetime ([8e108ca](https://github.com/event4u-app/agent-config/commit/8e108ca07bae79a6058608a4bfd4b316e8d12e31))
|
|
363
|
+
* **testing-anti-patterns:** close the R2 findings on the new anti-pattern ([67b747e](https://github.com/event4u-app/agent-config/commit/67b747e95bfdbf2026f5e8286a960045eb864568))
|
|
364
|
+
* **session-register:** all 15 R2 findings, three of them the same shape as the bug ([871c00a](https://github.com/event4u-app/agent-config/commit/871c00a6275ed0ecf6bc3a9ea2783aa134388ec5))
|
|
365
|
+
* **session-register:** key the roadmap claim on the session, and reject a stale slug ([64a9ce7](https://github.com/event4u-app/agent-config/commit/64a9ce71ad4a410fdcbed361fb8a2829c4a306ac))
|
|
366
|
+
* **session-register:** compare the roadmap, not the branch name ([e338113](https://github.com/event4u-app/agent-config/commit/e338113b0adcb8d5ccb4239478bb9f2033cbc2e4))
|
|
367
|
+
* **staged-confirmation:** close the round-2 findings on the hardening itself ([fa47ef6](https://github.com/event4u-app/agent-config/commit/fa47ef6253b4ae1d1612db972ed39c856ee29314))
|
|
368
|
+
* **staged-confirmation:** sound the type guard and refuse a traversing token ([75dbdb3](https://github.com/event4u-app/agent-config/commit/75dbdb35fce020568fbeba978f14b521599aee67))
|
|
369
|
+
* **test:** stop pipefail reporting a matched grep as a failure ([29916d1](https://github.com/event4u-app/agent-config/commit/29916d1e96e6118d9a15e84cc6eeac6de3516705))
|
|
370
|
+
* **council:** record min_present on the quorum line, schema v3 to v4 ([53fe036](https://github.com/event4u-app/agent-config/commit/53fe0362b2d403455b6f7eb4e66952b1dd42e161))
|
|
371
|
+
* **cli:** refuse hooks:status --pending --strict instead of exiting 0 ([7e752a9](https://github.com/event4u-app/agent-config/commit/7e752a9d6da83b7c73d97cb2996fbc7d465caf4c))
|
|
372
|
+
* **confirmation:** close R2 findings 1-5 and 7 in the store and the seam ([42c6f5b](https://github.com/event4u-app/agent-config/commit/42c6f5b5b4e9f2c5835dbbf14567a6944bab13dc))
|
|
373
|
+
* **council:** judge the shadow floor against the configured roster ([9cde092](https://github.com/event4u-app/agent-config/commit/9cde092cf6c745c55d9468ebb4480ffb2a11259a))
|
|
374
|
+
* three CI reds — a raw NUL in a source, and a settings key declared once ([b128645](https://github.com/event4u-app/agent-config/commit/b128645e8aa29e1b8ccbab31fd3b5509e73da0da))
|
|
375
|
+
|
|
376
|
+
### Documentation
|
|
377
|
+
|
|
378
|
+
* **review:** re-bind the review artefact after the pipefail fix ([d4b23e1](https://github.com/event4u-app/agent-config/commit/d4b23e1c9f2620778564d81c7e0c3180e3b60eb8))
|
|
379
|
+
* **review:** re-bind the review artefact after merging main ([3fc625f](https://github.com/event4u-app/agent-config/commit/3fc625f8b5d02320f62af217f7805cf85c95e91d))
|
|
380
|
+
* **review:** re-bind the completion-review artefact to the fixed scope ([0fc9e82](https://github.com/event4u-app/agent-config/commit/0fc9e822c2f863216f7e721f471b070d50718157))
|
|
381
|
+
* **review:** record the R2 findings before any fix ([9c66161](https://github.com/event4u-app/agent-config/commit/9c66161c76484a856b1293918074bcf387683d9b))
|
|
382
|
+
* **testing-anti-patterns:** the negative test that passes for the wrong reason ([8fb3f84](https://github.com/event4u-app/agent-config/commit/8fb3f84a7a9c009a53dd383ac8a86bfe00de4fe6))
|
|
383
|
+
* state the collision that cost a PR, and the two axes that catch it ([60c0df8](https://github.com/event4u-app/agent-config/commit/60c0df808af87a748643d1fbfe2310a9a08f2c25))
|
|
384
|
+
* **review:** record the R2 completion-review findings before fixing them ([35570a5](https://github.com/event4u-app/agent-config/commit/35570a5df0be0cd402f8cc9ed3e5f5cf817a02d1))
|
|
385
|
+
* **roadmap:** close and archive the solo-floor roadmap ([128b2cd](https://github.com/event4u-app/agent-config/commit/128b2cddbadf12825bb220cc2aef261976ccb023))
|
|
386
|
+
* **adr:** answer ADR-224 three open questions in an amendment ([ff2362e](https://github.com/event4u-app/agent-config/commit/ff2362e1971dadd3b547fbfeadf2650dbb3a4c3f))
|
|
387
|
+
|
|
388
|
+
### Refactoring
|
|
389
|
+
|
|
390
|
+
* **leakage-allowlist:** key 35 exemptions on content, drop 3 that were dead ([de96358](https://github.com/event4u-app/agent-config/commit/de96358ddb309be1d4ffa4035d3b8e55e8e08458))
|
|
391
|
+
|
|
392
|
+
### Tests
|
|
393
|
+
|
|
394
|
+
* **session-register:** pin all five failure modes, each with the case that was silent ([7f35c3b](https://github.com/event4u-app/agent-config/commit/7f35c3bce8118d3112217af6d20dabdae487b301))
|
|
395
|
+
* **schemas:** validate the command surface instead of reading its shape ([5b75e64](https://github.com/event4u-app/agent-config/commit/5b75e64cc6f988212aabaaa84e82ebb4940681e0))
|
|
396
|
+
|
|
397
|
+
### Build
|
|
398
|
+
|
|
399
|
+
* refresh dist/install/install.mjs for the new settings key ([a4a99ed](https://github.com/event4u-app/agent-config/commit/a4a99edd6132f92d6e975ccba3261939964c015b))
|
|
400
|
+
|
|
401
|
+
### Chores
|
|
402
|
+
|
|
403
|
+
* **review:** re-bind the artefact and mark all seven findings fixed ([fb6283f](https://github.com/event4u-app/agent-config/commit/fb6283fa2011650986c89eb514e0be80d2ffd9c0))
|
|
404
|
+
* **memory:** drop three docblock claims about the removed agent-memory path ([307a355](https://github.com/event4u-app/agent-config/commit/307a3550fce6e81dfeb42b0124e276b762da9d66))
|
|
405
|
+
* **review:** record seven R2 findings before fixing them ([c86bf3d](https://github.com/event4u-app/agent-config/commit/c86bf3daa3e3281416c9ce426fe1560084dbb1b7))
|
|
406
|
+
* **mcp:** drop the agent-memory declaration ADR-094 left behind ([4283a8e](https://github.com/event4u-app/agent-config/commit/4283a8efad4ee9ed721404d3a68aff0b367ad690))
|
|
407
|
+
* **review:** re-bind the artefact and mark all three findings fixed ([d2a0c54](https://github.com/event4u-app/agent-config/commit/d2a0c544c80a5e20f0cd7b7d338cbf17684b7c87))
|
|
408
|
+
* **review:** record the R2 findings on the anti-pattern addition ([b382dd1](https://github.com/event4u-app/agent-config/commit/b382dd1bfe1b4c3a1301358eb667692e5bc606d0))
|
|
409
|
+
* **lint:** re-anchor the leakage allowlist after the inserted paragraph ([8bef27e](https://github.com/event4u-app/agent-config/commit/8bef27e8adae4e742350970323232981d8b499d0))
|
|
410
|
+
* **dist:** project the testing-anti-patterns addition ([c6fcd4a](https://github.com/event4u-app/agent-config/commit/c6fcd4ae50fbd6caaa7cc3d7b3fcf8751283815f))
|
|
411
|
+
* **review:** re-bind round 2 and mark its three findings fixed ([40e110c](https://github.com/event4u-app/agent-config/commit/40e110c7203b121b0e15f6ad5de0217efb1d90c8))
|
|
412
|
+
* **review:** record the R2 round-2 findings before fixing them ([0e477f3](https://github.com/event4u-app/agent-config/commit/0e477f357071e4bc2dc69786ce4e24c1ebf50e75))
|
|
413
|
+
* **review:** re-bind the R2 artefact after merging main ([756b226](https://github.com/event4u-app/agent-config/commit/756b226de64ec1a581338b07954a2e4e948b758f))
|
|
414
|
+
* **roadmap:** regenerate the dashboard after the main merge ([ba9a0ad](https://github.com/event4u-app/agent-config/commit/ba9a0ade70f40447b6eb3a43736b32376bad56e1))
|
|
415
|
+
* **review:** re-bind the R2 artefact and mark all eight findings fixed ([081ffff](https://github.com/event4u-app/agent-config/commit/081ffffbee9d0c9d28e380e148ce29f27aa5d793))
|
|
416
|
+
* **review:** record the R2 completion review for dispatch-safety Phase 2 ([661947d](https://github.com/event4u-app/agent-config/commit/661947def61068d05c832146723366fd5abda225))
|
|
417
|
+
* **config:** register shadow_floor_fire_rate as the fifth attendance metric ([70abe5c](https://github.com/event4u-app/agent-config/commit/70abe5c0118085fadd01a2eec14433814bf405c5))
|
|
418
|
+
* **roadmap:** close dispatch-safety 2.1-2.3 and 4.3, regenerate the dashboard ([bb8dd69](https://github.com/event4u-app/agent-config/commit/bb8dd69b31893ea235e3159ef48b5180e3385b6d))
|
|
419
|
+
* **dist:** project the confirmation module and the gate seam ([d6905af](https://github.com/event4u-app/agent-config/commit/d6905af2385948a7bb44dbdb3a9025834b91afb2))
|
|
420
|
+
* **roadmap:** close dispatch-safety Phase 2, record the 4.3 severity probe ([be0f893](https://github.com/event4u-app/agent-config/commit/be0f893e57001ca8a3a8c4c4a21538f37266f410))
|
|
421
|
+
|
|
422
|
+
Tests: 13213 (+145 since 9.34.0)
|
|
423
|
+
|
|
333
424
|
## [9.34.0](https://github.com/event4u-app/agent-config/compare/9.33.0...9.34.0) (2026-08-11)
|
|
334
425
|
|
|
335
426
|
### Release highlights
|
package/CONTRIBUTING.md
CHANGED
|
@@ -25,20 +25,29 @@ so we can align on direction before code is written.
|
|
|
25
25
|
|
|
26
26
|
If you are unsure whether a change is in scope: open a discussion first.
|
|
27
27
|
|
|
28
|
-
## Dependency direction —
|
|
29
|
-
|
|
30
|
-
`agent-config` is the upstream, standalone package.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
28
|
+
## Dependency direction — memory is file-first, with no external backend
|
|
29
|
+
|
|
30
|
+
`agent-config` is the upstream, standalone package. Its memory layer is
|
|
31
|
+
file-first and self-contained: curated YAML under `agents/memory/<type>/`
|
|
32
|
+
plus agent-written JSONL under `agents/memory/intake/`, read by `retrieve()`
|
|
33
|
+
in `src/scripts/memory_lookup.ts`. There is no external memory backend.
|
|
34
|
+
|
|
35
|
+
[ADR-094](docs/decisions/ADR-094-agent-memory-layer-removal.md) removed the
|
|
36
|
+
`@event4u/agent-memory` binding — the provider seam, the package detection,
|
|
37
|
+
and the MCP routing that used it "when present" — and explicitly rejected
|
|
38
|
+
leaving the integration surface inert: *"a dormant integration surface
|
|
39
|
+
advertising a capability the suite no longer has is misleading residue."*
|
|
40
|
+
|
|
41
|
+
Two rules survive that removal, and both are unconditional:
|
|
42
|
+
|
|
43
|
+
- **Never hard-depend on a package that depends on `agent-config`.** That was
|
|
44
|
+
the case for `agent-memory`, so the dependency could only ever run one way;
|
|
45
|
+
reversing it would create a cycle and break installs in consumer projects
|
|
46
|
+
that want the rule/skill layer alone.
|
|
47
|
+
- **Adding any external memory backend is a Class-B decision** under
|
|
48
|
+
[ADR-124](docs/decisions/ADR-124-embedded-engine-doctrine.md) § 5 — its own
|
|
49
|
+
ADR, a named consumer demand signal, and a measured Class-A failure — never
|
|
50
|
+
a dependency bump or a config entry.
|
|
42
51
|
|
|
43
52
|
## Quick start for contributors
|
|
44
53
|
|
|
@@ -42,9 +42,14 @@ git fetch origin --prune
|
|
|
42
42
|
git ls-tree --name-only origin/main agents/roadmaps/ # still active?
|
|
43
43
|
git ls-tree --name-only origin/main agents/roadmaps/archive/ # already closed?
|
|
44
44
|
gh pr list --state open --json number,title,headRefName # in flight?
|
|
45
|
-
agent-config sessions:list
|
|
45
|
+
agent-config sessions:list # claimed right now?
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
`sessions:list` without `--json` on purpose: the human form carries **both**
|
|
49
|
+
axes — the live records AND the unmerged branches checked out in other
|
|
50
|
+
worktrees. `--json` returns only the records unless `--branches` is passed, and
|
|
51
|
+
the branch axis is the one that does not need a peer to have claimed anything.
|
|
52
|
+
|
|
48
53
|
Excluded from the candidate set: `template.md`, `archive/`,
|
|
49
54
|
`skipped/`, `later/`, anything with `status: draft` whose promotion
|
|
50
55
|
trigger has not fired, and:
|
|
@@ -53,12 +58,24 @@ trigger has not fired, and:
|
|
|
53
58
|
|---|---|---|---|
|
|
54
59
|
| **taken by an open PR** | `gh pr list` — a roadmap slug matching an open PR's branch | The work is done or nearly done and is waiting on review | Review or merge that PR; the roadmap archives inside it |
|
|
55
60
|
| **claimed by a live session** | `sessions:list` — a live record whose `roadmap_slug` matches | Another session is working on it *right now*, before any PR exists | Nothing to do — it releases on that session's end, or by TTL |
|
|
61
|
+
| **held by a foreign worktree** | `sessions:list` — an unmerged branch in another worktree whose name names this roadmap | A peer is already writing code, whether or not it ever claimed | Pick something else; a branch on disk outranks an unwritten claim |
|
|
56
62
|
|
|
57
|
-
Keep the
|
|
63
|
+
Keep the three distinguishable in the reported screen. They are different
|
|
58
64
|
states with different recovery, and collapsing them into "unavailable"
|
|
59
65
|
loses the only information that tells the user whether to wait for a
|
|
60
66
|
review or simply pick something else.
|
|
61
67
|
|
|
68
|
+
```
|
|
69
|
+
A DIFFERENT BRANCH NAME DOES NOT MAKE IT A DIFFERENT TASK.
|
|
70
|
+
MEASURED TWICE: TWO SESSIONS BUILT ONE ROADMAP PHASE UNDER
|
|
71
|
+
`feat/dispatch-safety-confirmation` AND `feat/dispatch-safety-confirmed-execution`,
|
|
72
|
+
AND ONE OF THE TWO PRs WAS THROWN AWAY. COMPARE THE ROADMAP, NEVER THE BRANCH.
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
A slug in the register that names **no open roadmap** is not a claim — it is a
|
|
76
|
+
stale field left behind after that roadmap archived. `sessions:list` labels it
|
|
77
|
+
`← STALE`; read it as "nobody claimed anything", never as "taken".
|
|
78
|
+
|
|
62
79
|
A roadmap completes by an archive move **inside its merging PR**, so a
|
|
63
80
|
local checkout can show a closed roadmap as open. That is why this step
|
|
64
81
|
precedes the screen instead of following it.
|
|
@@ -145,9 +162,24 @@ agent-config sessions:claim <roadmap-slug>
|
|
|
145
162
|
```
|
|
146
163
|
|
|
147
164
|
Immediately after the pick, before any branch or commit. This writes the slug
|
|
148
|
-
into `agents/runtime/state/roadmap-claim
|
|
149
|
-
this session's register record, so another session's step 1 excludes it
|
|
150
|
-
one turn** and long before a PR exists.
|
|
165
|
+
into `agents/runtime/state/roadmap-claim-<session>.json`; the next heartbeat lifts
|
|
166
|
+
it into this session's register record, so another session's step 1 excludes it
|
|
167
|
+
**within one turn** and long before a PR exists.
|
|
168
|
+
|
|
169
|
+
The file is keyed on the host session id, not on the worktree, and that
|
|
170
|
+
distinction was a measured defect rather than a nicety: with one shared file per
|
|
171
|
+
checkout, four live records once carried one identical slug — naming an
|
|
172
|
+
already-archived roadmap — because every session in that checkout read the last
|
|
173
|
+
claim written there. A session that inherits a peer's claim reports work it is
|
|
174
|
+
not doing, and the peer whose claim was overwritten reports none. On a host that
|
|
175
|
+
exports no session id the legacy shared path is still written and still read, and
|
|
176
|
+
`sessions:claim` says so on that path rather than implying a guarantee it cannot
|
|
177
|
+
give.
|
|
178
|
+
|
|
179
|
+
**The claim is also what makes the duplicate-work warning possible.** Without it
|
|
180
|
+
the register can only compare branch names, and two sessions on one roadmap
|
|
181
|
+
routinely pick two different names — so a skipped claim does not merely delay
|
|
182
|
+
visibility, it removes the only axis that sees the expensive collision.
|
|
151
183
|
|
|
152
184
|
Routing the claim through a state file rather than having this command write the
|
|
153
185
|
register directly is deliberate: the roadmap is picked mid-session by the model,
|
|
@@ -46,6 +46,13 @@
|
|
|
46
46
|
* ./agent-config gates --all # every open blocker, grouped by owner
|
|
47
47
|
* ./agent-config gates --json # machine-readable
|
|
48
48
|
* ./agent-config gates --reply # reply-close form; empty when none
|
|
49
|
+
* ./agent-config gates --pending # staged actions awaiting confirmation
|
|
50
|
+
*
|
|
51
|
+
* `--pending` reads a different source — the staged-confirmation store, not the
|
|
52
|
+
* roadmap tree — and stays out of `--reply` on purpose. ADR-222 fixes the
|
|
53
|
+
* reply-close form at exactly ONE decision rendered in full; folding a second
|
|
54
|
+
* source into it would silently change which decision "the one" is, and that
|
|
55
|
+
* contract is not this step's to move.
|
|
49
56
|
*/
|
|
50
57
|
|
|
51
58
|
import * as path from 'node:path';
|
|
@@ -58,6 +65,8 @@ import {
|
|
|
58
65
|
blocker_needs_user as needsUser,
|
|
59
66
|
type Blocker,
|
|
60
67
|
} from './update_roadmap_progress.js';
|
|
68
|
+
import { listPending } from '../templates/scripts/work_engine/hooks/builtin/staged_confirmation_store.js';
|
|
69
|
+
import type { StagedAction } from '../templates/scripts/work_engine/hooks/builtin/staged_confirmation.js';
|
|
61
70
|
|
|
62
71
|
const _HERE = fileURLToPath(import.meta.url);
|
|
63
72
|
|
|
@@ -327,6 +336,89 @@ function renderJson(entries: readonly Entry[], all: boolean): string {
|
|
|
327
336
|
);
|
|
328
337
|
}
|
|
329
338
|
|
|
339
|
+
/**
|
|
340
|
+
* `--pending`: staged actions awaiting a human confirmation.
|
|
341
|
+
*
|
|
342
|
+
* A second source under the same verb, and the reason it belongs here rather
|
|
343
|
+
* than in a new command is the verb's own definition — "open decisions that
|
|
344
|
+
* need you, as actions". A staged irreversible action IS one, and the most
|
|
345
|
+
* literal kind: the roadmap blockers above are decisions a human owes a plan,
|
|
346
|
+
* this is a decision a human owes an action that is already loaded and waiting.
|
|
347
|
+
*
|
|
348
|
+
* Expired stages are counted, never listed as actionable. An expired stage
|
|
349
|
+
* cannot execute (`stageStatus` derives that from the clock, not from a sweep
|
|
350
|
+
* having run), so rendering it beside a live one would put two rows in front of
|
|
351
|
+
* the reader where only one is a decision.
|
|
352
|
+
*/
|
|
353
|
+
function renderPending(root: string, now: number): string {
|
|
354
|
+
const rows = listPending(root, now);
|
|
355
|
+
const live = rows.filter((r) => r.status === 'pending');
|
|
356
|
+
const expired = rows.filter((r) => r.status === 'expired');
|
|
357
|
+
const lines: string[] = [];
|
|
358
|
+
|
|
359
|
+
if (live.length === 0) {
|
|
360
|
+
lines.push(
|
|
361
|
+
expired.length === 0
|
|
362
|
+
? 'No staged actions awaiting confirmation.'
|
|
363
|
+
: `Nothing awaits your confirmation. ${expired.length} stage` +
|
|
364
|
+
`${expired.length !== 1 ? 's' : ''} expired unconfirmed — the action never fired.`,
|
|
365
|
+
);
|
|
366
|
+
return lines.join('\n') + '\n';
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
lines.push(
|
|
370
|
+
`${live.length} staged action${live.length !== 1 ? 's' : ''} await` +
|
|
371
|
+
`${live.length === 1 ? 's' : ''} your confirmation` +
|
|
372
|
+
(expired.length > 0 ? ` · ${expired.length} expired unconfirmed` : ''),
|
|
373
|
+
);
|
|
374
|
+
let n = 0;
|
|
375
|
+
for (const { stage } of live) {
|
|
376
|
+
n += 1;
|
|
377
|
+
lines.push(...renderStage(stage, n));
|
|
378
|
+
}
|
|
379
|
+
lines.push('');
|
|
380
|
+
return lines.join('\n') + '\n';
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function renderStage(stage: StagedAction, index: number): string[] {
|
|
384
|
+
const head = `${index} · ${stage.action}`;
|
|
385
|
+
const tail = `token: ${stage.token}`;
|
|
386
|
+
const dashes = Math.max(3, WIDTH - head.length - tail.length - 6);
|
|
387
|
+
return [
|
|
388
|
+
'',
|
|
389
|
+
`── ${head} ${'─'.repeat(dashes)} ${tail}`.trimEnd(),
|
|
390
|
+
// The object is the first field on purpose: an approval that does not
|
|
391
|
+
// name the exact object is the thing non-destructive-by-default forbids,
|
|
392
|
+
// so it is what the reader must see before anything else.
|
|
393
|
+
...field('Object:', stage.object),
|
|
394
|
+
...field('Staged by:', stage.source),
|
|
395
|
+
...field('Expires:', stage.expires_at),
|
|
396
|
+
];
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function renderPendingJson(root: string, now: number): string {
|
|
400
|
+
const rows = listPending(root, now);
|
|
401
|
+
return (
|
|
402
|
+
JSON.stringify(
|
|
403
|
+
{
|
|
404
|
+
awaitingYou: rows.filter((r) => r.status === 'pending').length,
|
|
405
|
+
expired: rows.filter((r) => r.status === 'expired').length,
|
|
406
|
+
staged: rows.map(({ stage, status }) => ({
|
|
407
|
+
token: stage.token,
|
|
408
|
+
action: stage.action,
|
|
409
|
+
object: stage.object,
|
|
410
|
+
source: stage.source,
|
|
411
|
+
stagedAt: stage.staged_at,
|
|
412
|
+
expiresAt: stage.expires_at,
|
|
413
|
+
status,
|
|
414
|
+
})),
|
|
415
|
+
},
|
|
416
|
+
null,
|
|
417
|
+
2,
|
|
418
|
+
) + '\n'
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
|
|
330
422
|
function _isDir(p: string): boolean {
|
|
331
423
|
try {
|
|
332
424
|
return fs.statSync(p).isDirectory();
|
|
@@ -340,8 +432,8 @@ function _isDir(p: string): boolean {
|
|
|
340
432
|
* `agents/roadmaps`, otherwise fall back to the git toplevel (monorepo
|
|
341
433
|
* sub-project support).
|
|
342
434
|
*/
|
|
343
|
-
function _resolveRepoRoot(start: string): string {
|
|
344
|
-
if (_isDir(path.join(start,
|
|
435
|
+
function _resolveRepoRoot(start: string, marker = path.join('agents', 'roadmaps')): string {
|
|
436
|
+
if (_isDir(path.join(start, marker))) {
|
|
345
437
|
return start;
|
|
346
438
|
}
|
|
347
439
|
try {
|
|
@@ -351,7 +443,7 @@ function _resolveRepoRoot(start: string): string {
|
|
|
351
443
|
timeout: 10_000,
|
|
352
444
|
});
|
|
353
445
|
const top = (r.stdout || '').trim();
|
|
354
|
-
if (r.status === 0 && top !== '' && _isDir(path.join(top,
|
|
446
|
+
if (r.status === 0 && top !== '' && _isDir(path.join(top, marker))) {
|
|
355
447
|
return top;
|
|
356
448
|
}
|
|
357
449
|
} catch {
|
|
@@ -365,6 +457,18 @@ function main(argv?: readonly string[]): number {
|
|
|
365
457
|
const all = args.includes('--all');
|
|
366
458
|
const json = args.includes('--json');
|
|
367
459
|
const reply = args.includes('--reply');
|
|
460
|
+
const pending = args.includes('--pending');
|
|
461
|
+
|
|
462
|
+
// `--pending` resolves against `agents/runtime/`, not `agents/roadmaps/`,
|
|
463
|
+
// and is answered BEFORE the roadmaps-directory exit below: a staged action
|
|
464
|
+
// is independent of whether this project plans in roadmaps at all, so
|
|
465
|
+
// "no roadmaps here" is not an answer to "what awaits my confirmation".
|
|
466
|
+
if (pending) {
|
|
467
|
+
const root = _resolveRepoRoot(process.cwd(), path.join('agents', 'runtime'));
|
|
468
|
+
const now = Date.now();
|
|
469
|
+
process.stdout.write(json ? renderPendingJson(root, now) : renderPending(root, now));
|
|
470
|
+
return 0;
|
|
471
|
+
}
|
|
368
472
|
|
|
369
473
|
const repoRoot = _resolveRepoRoot(process.cwd());
|
|
370
474
|
const roadmapRoot = path.join(repoRoot, 'agents', 'roadmaps');
|
|
@@ -412,5 +516,16 @@ if (_isCliEntry()) {
|
|
|
412
516
|
process.exitCode = main();
|
|
413
517
|
}
|
|
414
518
|
|
|
415
|
-
export {
|
|
519
|
+
export {
|
|
520
|
+
main,
|
|
521
|
+
needsUser,
|
|
522
|
+
regroupTodo,
|
|
523
|
+
wrap,
|
|
524
|
+
collectEntries,
|
|
525
|
+
render,
|
|
526
|
+
renderJson,
|
|
527
|
+
renderReply,
|
|
528
|
+
renderPending,
|
|
529
|
+
renderPendingJson,
|
|
530
|
+
};
|
|
416
531
|
export type { Entry };
|
|
@@ -163,6 +163,27 @@ expect([403, 404]).toContain((await api.get(`/users/${otherTenantUser.id}`)).sta
|
|
|
163
163
|
|
|
164
164
|
Deriving from seeded data is necessary but **not sufficient** — a test that only reads back the exact fields it seeded is still overfit (a mutation in the code survives it). A real test also exercises boundary (empty / null / max / Unicode), error (missing / invalid), and, on security-sensitive paths, an abuse case (IDOR, injection string, XSS payload). Coverage of *cases*, not just lines.
|
|
165
165
|
|
|
166
|
+
**The adversarial variant, and it is the one that looks safest.** A *negative* test — the abuse case just named — fails the same way, but nothing about it reads as weak: it names an attack, it asserts a refusal, it goes green. It passes because **something other than the control under test** produced that refusal. Two shapes, both from one real review:
|
|
167
|
+
|
|
168
|
+
- **The hostile value sits only in the argument.** A guard rejecting a path-traversing `token` was pinned by planting a fixture whose own `token` field held the *legitimate* value while `'../outside'` was passed as the argument. Downstream, an ordinary token comparison mismatched and returned exactly the expected refusal — delete the guard and the test still passes. The hostile value belongs in the **field under test**, not only in the call.
|
|
169
|
+
- **The assertion cannot fail.** The same test asserted the planted file still existed after a rejected claim. The two directories were siblings, so both `../x.json` joins resolved to one path and the rename could never have moved it — a green no implementation could turn red.
|
|
170
|
+
|
|
171
|
+
Gate:
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
BEFORE trusting a negative test:
|
|
175
|
+
Delete or invert the control it claims to pin. Does it FAIL?
|
|
176
|
+
RESTORE THE CONTROL IMMEDIATELY — before any other edit, before any commit.
|
|
177
|
+
The mutation is a probe, never a change. A deleted guard left deleted is
|
|
178
|
+
a shipped vulnerability produced by a test-quality check.
|
|
179
|
+
IF it still passes: it is pinning something else. Name what, then fix the test.
|
|
180
|
+
IF an assertion cannot fail for ANY implementation: delete THE ASSERTION —
|
|
181
|
+
it is decoration. Only after proving vacuity, and never to turn a red test
|
|
182
|
+
green — that is Anti-Pattern 7 below.
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
No mutation-testing rig required: comment the control out, run that one spec, put it back. A negative test nobody has ever watched fail is a claim, not evidence.
|
|
186
|
+
|
|
166
187
|
### Anti-Pattern 7 — Gaming the green (test-integrity)
|
|
167
188
|
|
|
168
189
|
Symptom: a test fails, and the "fix" edits the *test* instead of the code — `.skip` / `it.skip` / `xfail` / `@Disabled` on the failing case, deleting the failing assertion, loosening `toBe(x)` to `toBeTruthy()`, or rewriting the `expected` value to whatever the (possibly buggy) code now emits. The suite goes green while the behavior stays broken — the worst outcome, because green now *hides* the bug. This is the automation-bias trap: under pressure to finish, the agent optimizes the signal (green) instead of the target (correct behavior).
|
|
@@ -211,6 +232,7 @@ This is the test-surface instance of the `autonomous-execution` N=3 / allowlist-
|
|
|
211
232
|
- Do NOT mark a story complete until at least one test was watched failing first.
|
|
212
233
|
- Do NOT hardcode an expected value the code will emit, or pin a regex to an incidental fixed date/id — derive from inputs and seeded data, and vary the input across cases.
|
|
213
234
|
- Do NOT skip / `xfail` / delete a failing test, or rewrite its `expected` to the code's current output, to get a green run — fix the code, or state a real spec change in the diff.
|
|
235
|
+
- Do NOT trust a negative test you have never watched fail — delete the control it pins, confirm the test goes red, restore the control immediately.
|
|
214
236
|
|
|
215
237
|
## Auto-trigger keywords
|
|
216
238
|
|
|
@@ -225,6 +247,9 @@ This is the test-surface instance of the `autonomous-execution` N=3 / allowlist-
|
|
|
225
247
|
- gaming the green
|
|
226
248
|
- skip failing test
|
|
227
249
|
- test integrity
|
|
250
|
+
- negative test
|
|
251
|
+
- vacuous assertion
|
|
252
|
+
- abuse-case test
|
|
228
253
|
|
|
229
254
|
## Provenance
|
|
230
255
|
|
|
@@ -39,7 +39,7 @@ schema_version: 1
|
|
|
39
39
|
# CI guard: a release bump of `package.json` must update this value
|
|
40
40
|
# in lockstep — see scripts/check_template_pin_drift.ts (road-to-
|
|
41
41
|
# portable-runtime-and-update-check P3.3).
|
|
42
|
-
agent_config_version: "9.
|
|
42
|
+
agent_config_version: "9.35.0"
|
|
43
43
|
|
|
44
44
|
# --- Project identity ---
|
|
45
45
|
project:
|
|
@@ -388,8 +388,7 @@ function* _iter_knowledge_entries(): Generator<[string, Record<string, unknown>]
|
|
|
388
388
|
/**
|
|
389
389
|
* Naive relevance score: max over keys of (glob-match | substring).
|
|
390
390
|
*
|
|
391
|
-
*
|
|
392
|
-
* The `present` path returns a real score from agent-memory.
|
|
391
|
+
* Retrieval is best-effort by design — this is the only scoring path.
|
|
393
392
|
*/
|
|
394
393
|
export function _score(entry: Record<string, unknown>, keys: string[]): number {
|
|
395
394
|
if (keys.length === 0) {
|
|
@@ -214,10 +214,8 @@ export interface SignalRecord {
|
|
|
214
214
|
/**
|
|
215
215
|
* Append a signal entry. Returns the written record, or null when skipped.
|
|
216
216
|
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
* Phase 3. For now, the file path is the single source of truth so
|
|
220
|
-
* merge-safety is preserved in every mode.
|
|
217
|
+
* The file path is the single source of truth: there is no external backend
|
|
218
|
+
* to route to, so merge-safety holds in every mode.
|
|
221
219
|
*/
|
|
222
220
|
export function emit(
|
|
223
221
|
entry_type: string,
|
|
@@ -41,6 +41,22 @@ type Any = unknown;
|
|
|
41
41
|
|
|
42
42
|
const _BLOCK_REASON_PREFIX = 'decision_gate';
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Stage an action for confirmation and return the surface lines that name it.
|
|
46
|
+
*
|
|
47
|
+
* An injected collaborator rather than a direct import of
|
|
48
|
+
* `staged_confirmation_store.js`, for two reasons. The gate is called from a
|
|
49
|
+
* hook bus with no repo root and no clock of its own, and — the load-bearing
|
|
50
|
+
* one — an absent stager keeps the `ask` path byte-identical to what ships
|
|
51
|
+
* today. The confirmation primitive is deliberately UNBOUND until blocker
|
|
52
|
+
* `confirmation-degraded-host-semantics` is decided (5 of 8 hosts have no
|
|
53
|
+
* `pre_tool_use` slot), so reaching it has to be an explicit opt-in and not a
|
|
54
|
+
* behaviour change that arrives with an upgrade.
|
|
55
|
+
*/
|
|
56
|
+
export interface ConfirmationStager {
|
|
57
|
+
stage(input: { action: string; object: string; source: string }): readonly string[];
|
|
58
|
+
}
|
|
59
|
+
|
|
44
60
|
/**
|
|
45
61
|
* Evaluate decision-engine gates on every `AFTER_STEP`.
|
|
46
62
|
*
|
|
@@ -49,9 +65,11 @@ const _BLOCK_REASON_PREFIX = 'decision_gate';
|
|
|
49
65
|
*/
|
|
50
66
|
export class DecisionGateHook {
|
|
51
67
|
private readonly _settings: DecisionEngineSettings;
|
|
68
|
+
private readonly _stager: ConfirmationStager | null;
|
|
52
69
|
|
|
53
|
-
constructor(settings: DecisionEngineSettings) {
|
|
70
|
+
constructor(settings: DecisionEngineSettings, stager: ConfirmationStager | null = null) {
|
|
54
71
|
this._settings = settings;
|
|
72
|
+
this._stager = stager;
|
|
55
73
|
}
|
|
56
74
|
|
|
57
75
|
/** Register the gate callback on `AFTER_STEP`. */
|
|
@@ -109,10 +127,22 @@ export class DecisionGateHook {
|
|
|
109
127
|
DecisionGateHook._surface(decision, 'ask_timeout'),
|
|
110
128
|
);
|
|
111
129
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
130
|
+
// `stop` and `ask` share this exit: both halt, which is the policy —
|
|
131
|
+
// ask and action are strictly sequential, so an interactive gate does
|
|
132
|
+
// not act while it asks. What `ask` adds, and only when a stager is
|
|
133
|
+
// injected, is a token on the surface so the answer can be matched to
|
|
134
|
+
// THIS question and consumed exactly once. The halt reason is
|
|
135
|
+
// deliberately unchanged: it is the identity downstream readers match on.
|
|
136
|
+
let surface = DecisionGateHook._surface(decision);
|
|
137
|
+
if (action === 'ask' && this._stager !== null) {
|
|
138
|
+
const extra = this._stager.stage({
|
|
139
|
+
action: `work_engine:advance:${decision.gate_id}`,
|
|
140
|
+
object: `phase ${decision.phase}`,
|
|
141
|
+
source: _BLOCK_REASON_PREFIX,
|
|
142
|
+
});
|
|
143
|
+
surface = [...surface, '', ...extra];
|
|
144
|
+
}
|
|
145
|
+
throw new HookHalt(`${_BLOCK_REASON_PREFIX}:${decision.gate_id}`, surface);
|
|
116
146
|
}
|
|
117
147
|
|
|
118
148
|
// -- formatting helpers -------------------------------------------
|