@event4u/agent-config 14.7.0 → 14.9.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 +175 -390
- package/README.md +1 -1
- package/agents/templates/.ai-council.yml.example +34 -4
- package/dist/agent-src/commands/evals/git-pr-merge.json +13 -0
- package/dist/agent-src/commands/evals/roadmap.json +17 -1
- package/dist/agent-src/commands/pr/create.md +14 -2
- package/dist/agent-src/commands/pr/merge.md +383 -0
- package/dist/agent-src/commands/review/changes.md +95 -14
- package/dist/agent-src/commands/roadmap/process-full.md +157 -3
- package/dist/agent-src/commands/roadmap.md +1 -1
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +46 -36
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +37 -2
- package/dist/agent-src/contexts/execution/host-capability-manifest.md +77 -0
- package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +100 -2
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +154 -2
- package/dist/agent-src/contexts/execution/subagent-response-contract.md +80 -0
- package/dist/agent-src/rules/artifact-drafting-protocol.md +5 -1
- package/dist/agent-src/rules/decision-revisit-gate.md +65 -33
- package/dist/agent-src/rules/recurring-criticism.md +1 -1
- package/dist/agent-src/rules/roadmap-progress-sync.md +19 -0
- package/dist/agent-src/rules/source-confidentiality.md +1 -1
- package/dist/agent-src/rules/untrusted-input-defense.md +9 -3
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +7 -4
- package/dist/agent-src/scripts/dashboard_mode.ts +212 -0
- package/dist/agent-src/scripts/update_roadmap_progress.ts +47 -48
- package/dist/agent-src/skills/adr-create/SKILL.md +124 -5
- package/dist/agent-src/skills/code-review/evals/evals.json +87 -46
- package/dist/agent-src/skills/decision-record/SKILL.md +61 -3
- package/dist/agent-src/skills/decision-review/SKILL.md +65 -0
- package/dist/agent-src/skills/humanizer/SKILL.md +39 -0
- package/dist/agent-src/skills/humanizer/evals/strip_fixtures.json +57 -0
- package/dist/agent-src/skills/humanizer/references/fixtures.md +54 -0
- package/dist/agent-src/skills/judge-spec-compliance/SKILL.md +154 -0
- package/dist/agent-src/skills/judge-synthesis/SKILL.md +45 -0
- package/dist/agent-src/skills/merge-conflicts/SKILL.md +26 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +18 -18
- package/dist/agent-src/skills/security/SKILL.md +8 -0
- package/dist/agent-src/skills/security-audit/SKILL.md +12 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +28 -2
- package/dist/cli/registry.js +3 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/{chunk-FBKL6JWT.js → chunk-77GR3VD4.js} +68 -43
- package/dist/cli-delegate/{chunk-JZ4NR2TW.js → chunk-7PRE6ZV2.js} +1 -1
- package/dist/cli-delegate/{chunk-UEZ7D35A.js → chunk-CYH5SP6M.js} +4 -23
- package/dist/cli-delegate/{chunk-Q26HT4QT.js → chunk-E46KPN6K.js} +251 -150
- package/dist/cli-delegate/{chunk-74E3TKWV.js → chunk-H4VIVD4C.js} +5 -5
- package/dist/cli-delegate/chunk-I3EIN2YG.js +36 -0
- package/dist/cli-delegate/{chunk-4VR4ORQ6.js → chunk-LPPWO5CE.js} +4 -3
- package/dist/cli-delegate/{chunk-7F3VYSM2.js → chunk-PLSG6UL5.js} +14 -14
- package/dist/cli-delegate/cmd_adr_effective.js +1162 -0
- package/dist/cli-delegate/cmd_conformance.js +101 -25
- package/dist/cli-delegate/cmd_converge.js +3 -3
- package/dist/cli-delegate/cmd_doctor.js +7 -7
- package/dist/cli-delegate/cmd_export.js +6 -6
- package/dist/cli-delegate/cmd_handoff.js +7 -6
- package/dist/cli-delegate/cmd_memory_get.js +2 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_preflight.js +5 -5
- package/dist/cli-delegate/cmd_prune.js +6 -6
- package/dist/cli-delegate/cmd_route_audit.js +6 -5
- package/dist/cli-delegate/cmd_route_explain.js +3 -3
- package/dist/cli-delegate/cmd_session_recycle.js +3 -3
- package/dist/cli-delegate/cmd_settings_get.js +4 -4
- package/dist/cli-delegate/cmd_settings_migrate.js +6 -6
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +6 -6
- package/dist/cli-delegate/cmd_uninstall.js +6 -6
- package/dist/cli-delegate/cmd_update.js +3 -3
- package/dist/cli-delegate/cmd_upgrade.js +3 -3
- package/dist/cli-delegate/cmd_validate.js +6 -6
- package/dist/cli-delegate/cmd_versions.js +3 -3
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +83 -30
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +4 -4
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +11 -9
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +9 -7
- package/dist/hooks/dispatch.js +1295 -812
- package/dist/install/globalRuleLayers.js +169 -0
- package/dist/install/globalRuleLayers.js.map +1 -0
- package/dist/install/partitionEligibility.js +156 -0
- package/dist/install/partitionEligibility.js.map +1 -1
- package/dist/install/ruleLayerPartition.js +157 -0
- package/dist/install/ruleLayerPartition.js.map +1 -0
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +1358 -805
- package/dist/scripts/_lib/claude_plugin.js +162 -0
- package/dist/scripts/_lib/claude_plugin.js.map +1 -0
- package/dist/scripts/_lib/environment_detector.js +397 -0
- package/dist/scripts/_lib/environment_detector.js.map +1 -0
- package/dist/scripts/_lib/prune_empty_dirs.js +85 -0
- package/dist/scripts/_lib/prune_empty_dirs.js.map +1 -0
- package/dist/scripts/_lib/spawn_env.js +129 -0
- package/dist/scripts/_lib/spawn_env.js.map +1 -0
- package/dist/scripts/ai_council/modes.js +199 -0
- package/dist/scripts/ai_council/modes.js.map +1 -0
- package/dist/scripts/ai_council/transport_resolver.js +418 -0
- package/dist/scripts/ai_council/transport_resolver.js.map +1 -0
- package/dist/scripts/condense.js +52 -89
- package/dist/scripts/condense.js.map +1 -1
- package/dist/server/routes/wizard.js +3 -2
- package/dist/server/routes/wizard.js.map +1 -1
- package/dist/ui/assets/index-Cxgy63kz.js +26 -0
- package/dist/ui/assets/index-Cxgy63kz.js.map +1 -0
- package/dist/ui/index.html +1 -1
- package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +45 -1
- package/docs/guidelines/agent-infra/false-green.md +47 -1
- package/docs/guidelines/agent-infra/traceability-field-mechanics.md +50 -0
- package/docs/guidelines/agent-infra/untrusted-input-spotlighting.md +17 -0
- package/package.json +7 -3
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +7 -4
- package/src/agent-src/scripts/dashboard_mode.ts +212 -0
- package/src/agent-src/scripts/update_roadmap_progress.ts +47 -48
- package/src/config/ci-local-parity.yml +21 -0
- package/src/config/estate-count-budget.json +13 -471
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +458 -12
- package/src/config/gate-violation-baselines.json +18 -8
- package/src/config/gitignore-block.txt +15 -2
- package/src/config/hook-latency-budget.json +5 -0
- package/src/config/publish-surface.json +60 -0
- package/src/config/requirements-trace-budget.json +32 -0
- package/src/config/rule-stub-ceilings.json +25 -4
- package/src/install/globalRuleLayers.ts +197 -0
- package/src/install/partitionEligibility.ts +177 -0
- package/src/install/ruleLayerPartition.ts +212 -0
- package/src/scripts/_cli/cmd_adr_effective.ts +484 -0
- package/src/scripts/_dispatch.bash +18 -0
- package/src/scripts/_lib/adr_frontmatter.test.ts +365 -0
- package/src/scripts/_lib/adr_frontmatter.ts +459 -0
- package/src/scripts/_lib/base_tree.ts +175 -0
- package/src/scripts/_lib/billing_grant.ts +165 -0
- package/src/scripts/_lib/billing_grant_cli.ts +123 -0
- package/src/scripts/_lib/council_fallback_posture.ts +53 -6
- package/src/scripts/_lib/council_fallback_wiring.ts +61 -1
- package/src/scripts/_lib/envelope_grounding.ts +11 -1
- package/src/scripts/_lib/host_capability.ts +59 -5
- package/src/scripts/_lib/layer_overlap_notice.ts +11 -2
- package/src/scripts/_lib/prune_empty_dirs.ts +80 -0
- package/src/scripts/_lib/quota_parked.ts +133 -0
- package/src/scripts/_lib/retrieval_sanitize.ts +62 -0
- package/src/scripts/_lib/review_telemetry.ts +196 -0
- package/src/scripts/_lib/subagent_response.ts +92 -0
- package/src/scripts/adr/evidence_census.ts +899 -0
- package/src/scripts/adr/regenerate_index.ts +25 -38
- package/src/scripts/adr_cite_check.ts +540 -39
- package/src/scripts/ai_council/clients.ts +12 -1
- package/src/scripts/ai_council/config.ts +72 -115
- package/src/scripts/ai_council/fallback_config.ts +19 -7
- package/src/scripts/ai_council/mid_flight_fallback.ts +88 -7
- package/src/scripts/ai_council/orchestrator.ts +2 -2
- package/src/scripts/ai_council/py_format.ts +124 -0
- package/src/scripts/ai_council/transport_resolver.ts +65 -2
- package/src/scripts/audit_adr_coverage.ts +134 -21
- package/src/scripts/bench_hook_latency.ts +204 -0
- package/src/scripts/build_archive_index.ts +34 -0
- package/src/scripts/check_adr_frontmatter.ts +574 -39
- package/src/scripts/check_council_pin_staleness.ts +300 -0
- package/src/scripts/check_estate_count.ts +420 -293
- package/src/scripts/check_gate_coverage.ts +91 -0
- package/src/scripts/check_hook_bundle_content.ts +219 -0
- package/src/scripts/check_new_adr_evidence.ts +529 -0
- package/src/scripts/check_no_stub_inventory_table.ts +292 -0
- package/src/scripts/check_pack_size.ts +234 -1
- package/src/scripts/check_publish_surface.ts +297 -0
- package/src/scripts/check_references.ts +18 -0
- package/src/scripts/check_requirements_trace.ts +378 -0
- package/src/scripts/check_rule_layer_partition.ts +531 -0
- package/src/scripts/check_secret_leak.ts +166 -12
- package/src/scripts/check_single_delivery.ts +137 -4
- package/src/scripts/check_standing_rule_delivery.ts +8 -2
- package/src/scripts/code_graph/cli.ts +44 -5
- package/src/scripts/code_graph/extract.ts +87 -0
- package/src/scripts/condense.ts +61 -91
- package/src/scripts/council_cli.ts +12 -16
- package/src/scripts/detect_ai_tells.ts +87 -0
- package/src/scripts/dispatch_r2_reviewer.ts +42 -0
- package/src/scripts/external_sources_denylist.json +4 -1
- package/src/scripts/generate_capabilities_index.ts +41 -0
- package/src/scripts/git_authorization_hook.ts +54 -4
- package/src/scripts/hooks/block_unauthorized_git.ts +41 -3
- package/src/scripts/hooks/dispatch_hook.ts +154 -187
- package/src/scripts/hooks/dispatch_issues.ts +38 -19
- package/src/scripts/hooks/fallback_yaml.ts +66 -0
- package/src/scripts/hooks/host_semantics.ts +21 -0
- package/src/scripts/hooks/injection_budget.ts +13 -3
- package/src/scripts/hooks/py_json_dumps.ts +103 -0
- package/src/scripts/hooks/run_continuation_hook.ts +3 -1
- package/src/scripts/hooks/state_io.ts +88 -1
- package/src/scripts/hooks/stdin_failure_policy.ts +96 -0
- package/src/scripts/hooks/subagent_ledger_hook.ts +76 -11
- package/src/scripts/injection_scan_hook.ts +257 -15
- package/src/scripts/lint_adr_sweep_routing.ts +365 -0
- package/src/scripts/lint_hook_manifest.ts +49 -0
- package/src/scripts/lint_provenance_vocabulary.ts +472 -4
- package/src/scripts/lint_workflow_security.ts +53 -10
- package/src/scripts/lint_workflow_security_allowlist.json +2 -18
- package/src/scripts/report_envelope_rate.ts +166 -0
- package/src/scripts/review_axis_report.ts +218 -0
- package/src/scripts/routing_doctor.ts +5 -1
- package/src/scripts/run_supervise.ts +46 -1
- package/src/scripts/schemas/evals.schema.json +5 -0
- package/src/scripts/sync_pr_branch.ts +170 -20
- package/dist/cli/commands/commands.candidates.test.js +0 -257
- package/dist/cli/commands/commands.candidates.test.js.map +0 -1
- package/dist/cli/commands/profiles.test.js +0 -88
- package/dist/cli/commands/profiles.test.js.map +0 -1
- package/dist/cli/commands/recordTriggerEval.test.js +0 -113
- package/dist/cli/commands/recordTriggerEval.test.js.map +0 -1
- package/dist/cli/configRoot.test.js +0 -97
- package/dist/cli/configRoot.test.js.map +0 -1
- package/dist/cli/mcp/content.test.js +0 -54
- package/dist/cli/mcp/content.test.js.map +0 -1
- package/dist/cli/mcp/dispatch.test.js +0 -108
- package/dist/cli/mcp/dispatch.test.js.map +0 -1
- package/dist/server/writeRoot.test.js +0 -91
- package/dist/server/writeRoot.test.js.map +0 -1
- package/dist/shared/capabilities.test.js +0 -39
- package/dist/shared/capabilities.test.js.map +0 -1
- package/dist/ui/assets/index-C1pBRm1f.js +0 -26
- package/dist/ui/assets/index-C1pBRm1f.js.map +0 -1
- package/dist/cli-delegate/{chunk-PPQO4NGR.js → chunk-6RJA2F5A.js} +3 -3
- package/dist/cli-delegate/{chunk-6EOCNQAM.js → chunk-E4XIZDIB.js} +0 -0
- package/dist/cli-delegate/{chunk-GJ5BVHXM.js → chunk-PMEKGDED.js} +3 -3
package/dist/ui/index.html
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
document.documentElement.setAttribute('data-theme', theme);
|
|
28
28
|
})();
|
|
29
29
|
</script>
|
|
30
|
-
<script type="module" crossorigin src="/assets/index-
|
|
30
|
+
<script type="module" crossorigin src="/assets/index-Cxgy63kz.js"></script>
|
|
31
31
|
<link rel="stylesheet" crossorigin href="/assets/index-jSfMW8_t.css">
|
|
32
32
|
</head>
|
|
33
33
|
<body>
|
|
@@ -87,10 +87,54 @@ those artifacts:
|
|
|
87
87
|
to obtain.
|
|
88
88
|
- **Scope is the batch, nothing more.** An artifact NOT declared in the
|
|
89
89
|
roadmap (discovered mid-run) triggers the full interactive protocol —
|
|
90
|
-
or, under the contract, the scope-out-of-roadmap halt.
|
|
90
|
+
or, under the contract, the scope-out-of-roadmap halt. That halt is the
|
|
91
|
+
**default** and stays the default; see § Late artifacts below for the one
|
|
92
|
+
declared value that changes it.
|
|
91
93
|
- Batch mode never skips the Research pass itself — it relocates and
|
|
92
94
|
batches it. `artifact_protocol: skip` does not exist.
|
|
93
95
|
|
|
96
|
+
### Late artifacts — `halt` by default, `auto-research` only when declared
|
|
97
|
+
|
|
98
|
+
The bullet above is the whole rule when the contract says nothing. A contract
|
|
99
|
+
may instead declare `late_artifacts: auto-research`
|
|
100
|
+
([`roadmap-execution-contract § 2a`](../../../src/agent-src/contexts/execution/roadmap-execution-contract.md)),
|
|
101
|
+
and then a mid-run discovery runs **this** procedure rather than halting:
|
|
102
|
+
|
|
103
|
+
1. **Re-run Phase B against current state** — the same overlap scan the batch
|
|
104
|
+
ran at contract time, now over the artifact the run just found. Nothing new
|
|
105
|
+
is invented: this is the accepted-as-non-interactive procedure, later.
|
|
106
|
+
2. **Extend verdict → extend silently.** The nearest match is edited. This is
|
|
107
|
+
the outcome the overlap scan exists to produce and it needs no approval it
|
|
108
|
+
did not already have.
|
|
109
|
+
3. **Create verdict → derive Phase A from what is already on the record.** The
|
|
110
|
+
understand-answers come from the roadmap step text plus the decision-sheet
|
|
111
|
+
answers the user accepted. No new question is opened.
|
|
112
|
+
4. **Genuine overlap conflict → halt.** Two plausible extend targets, or a
|
|
113
|
+
match whose scope contradicts the step, is a real decision and stops the
|
|
114
|
+
run.
|
|
115
|
+
5. **Cap: three per run, then halt regardless of verdict.**
|
|
116
|
+
|
|
117
|
+
`halt` remains the shipped default (`decision 2026-08-20`, AI council 2/2 on
|
|
118
|
+
the `autonomy-defaults-sheet` fork; record
|
|
119
|
+
`agents/evidence/council/drain-blocker-dispositions-a.md`). The reasoning is
|
|
120
|
+
short: the cap bounds how far an unplanned artifact can drift the run, but it
|
|
121
|
+
does not decide whether the drift is wanted, and the conservative side of that
|
|
122
|
+
fork is the behaviour the tree already had. Making `auto-research` a declared
|
|
123
|
+
value rather than an inherited one is also what makes its own kill criterion —
|
|
124
|
+
the late-artifact revisit rate — attributable to the runs that chose it.
|
|
125
|
+
Reverting is one word in each of the two tables; no mechanism depends on the
|
|
126
|
+
default's direction.
|
|
127
|
+
|
|
128
|
+
**Kill criterion for `auto-research`, at the site rather than in a roadmap:**
|
|
129
|
+
a late-artifact revisit rate above 20 % — an auto-authored or auto-extended
|
|
130
|
+
artifact later reworked or reverted — removes the value and returns every
|
|
131
|
+
late discovery to `halt`. **No instrument today:** nothing counts late
|
|
132
|
+
artifacts or their revisits, so the rate is unmeasured rather than low, and
|
|
133
|
+
the cap of three is the only live bound. Recorded here because a criterion
|
|
134
|
+
that lives only in the roadmap that flipped the default becomes unreachable
|
|
135
|
+
the moment that roadmap is archived.
|
|
136
|
+
|
|
137
|
+
|
|
94
138
|
## Complexity budget — the six questions before a new artefact
|
|
95
139
|
|
|
96
140
|
Folded here from the 9.4.0 review (Prio 5), which named the failure mode:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# False Green — the ways a passing result can be wrong here
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Eleven failure modes, each with the detection command that distinguishes "found nothing" from "looked at nothing". Every one is drawn from this repository's own history, not from a generic checklist.
|
|
4
4
|
|
|
5
5
|
_Origin: `road-to-skill-ecosystem-gate-integrity` Phase 3. The recurring shape is not that a gate reports a wrong finding — it is that a gate reports **no** finding for a reason unrelated to the property it guards, and no output distinguishes the two cases._
|
|
6
6
|
|
|
@@ -128,6 +128,52 @@ empty rendered template equals an absent target file. The gate reports
|
|
|
128
128
|
comparing. Two absences are the absence of a comparison, not the presence of a
|
|
129
129
|
match. The estate-wide audit is below.
|
|
130
130
|
|
|
131
|
+
### 10. Hook-bypass override
|
|
132
|
+
|
|
133
|
+
The gate never ran because the hook that runs it was disabled for that
|
|
134
|
+
invocation. `git commit --no-verify` (and its short `-n` form) skips every
|
|
135
|
+
pre-commit hook; `core.hooksPath` repointed at an empty directory skips them
|
|
136
|
+
permanently and silently. Both produce a commit that looks exactly like a
|
|
137
|
+
commit whose hooks passed. The same shape reaches the runtime hook layer: a
|
|
138
|
+
concern bound in the manifest is still inert on a host that does not honour a
|
|
139
|
+
deny, so "the guard is registered" is not "the guard fired".
|
|
140
|
+
|
|
141
|
+
<!-- example-fence-allow: git-no-verify -- the fence teaches how to DETECT the core.hooksPath bypass; the forbidden form has to appear for the diagnostic to be readable, and every line here is a read-only query -->
|
|
142
|
+
```bash
|
|
143
|
+
git config --get core.hooksPath # empty = repo default, good
|
|
144
|
+
./scripts-run src/scripts/hooks/block_no_verify --help # the tool-call guard
|
|
145
|
+
agent-config hooks:status # per-host bindings, not the manifest
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Structural fix:** the bypass is blocked at tool-call time by
|
|
149
|
+
`block-no-verify` on the one host that honours a deny, and the authoritative
|
|
150
|
+
gate is remote CI, which no local override can skip. Never read a local green
|
|
151
|
+
as evidence when the hook path was overridden — re-run in CI, or run the gate
|
|
152
|
+
directly rather than through the hook.
|
|
153
|
+
|
|
154
|
+
### 11. Cached-green reuse
|
|
155
|
+
|
|
156
|
+
The green belongs to a different input than the one being judged. Three real
|
|
157
|
+
shapes here. A gate that reads `dist/agent-src/` passes against a **stale
|
|
158
|
+
projection** while the `src/` change it was meant to judge is not in the tree it
|
|
159
|
+
scanned — the recorded trap is a worktree whose stale `dist` faked generator
|
|
160
|
+
drift, and its mirror, a golden capture reading `dist` where the assertion was
|
|
161
|
+
about `src`. A count ratchet sits inside its limit for so long that "within
|
|
162
|
+
budget" stops meaning anything, which is why a baseline carries an expiry at
|
|
163
|
+
all. And the conversational form: trusting a verification run earlier in the
|
|
164
|
+
same session, after the tree has moved underneath it.
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
bash src/scripts/condense.sh --changed # dist != rewrite(src) => the scan root is stale
|
|
168
|
+
./scripts-run src/scripts/check_condensation --summary # byte-exactness dist vs rewrite(src)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Structural fix:** regenerate before judging (`task sync` then
|
|
172
|
+
`task generate-tools`, in that order), and treat a baseline unchanged past
|
|
173
|
+
`STALE_AFTER_DAYS` (56, `_lib/gate_baseline.ts:39`) as suppression rather than
|
|
174
|
+
as headroom. A completion claim rests on a run made **after** the last edit, not
|
|
175
|
+
on the freshest run you happen to remember.
|
|
176
|
+
|
|
131
177
|
## The audited sync/parity gates
|
|
132
178
|
|
|
133
179
|
Comparison-shaped gates carry entry #9's failure mode by default: `diff a b`
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Traceability field mechanics — `traceability:` in the roadmap execution contract
|
|
2
|
+
|
|
3
|
+
> Migrated from [`roadmap-execution-contract`](../../../src/agent-src/contexts/execution/roadmap-execution-contract.md)
|
|
4
|
+
> § 2c, 2026-08-22, because the section pushed that file past the 16,000-char
|
|
5
|
+
> depth ceiling. The contract keeps the field shape and the reused grammars; the
|
|
6
|
+
> semantics a reader needs before writing a row live here. Established P4
|
|
7
|
+
> pattern: the obligation stays in the contract, the mechanics move.
|
|
8
|
+
|
|
9
|
+
## Identity semantics — stated, because a slug grammar does not imply them
|
|
10
|
+
|
|
11
|
+
- **Namespace:** the roadmap file. Two roadmaps MAY use the same
|
|
12
|
+
`requirement_id` when they reference the same external requirement; within one
|
|
13
|
+
file an id is unique.
|
|
14
|
+
- **Cardinality:** one row is one `(requirement, acceptance)` pair with its
|
|
15
|
+
evidence. A requirement with two criteria is two rows.
|
|
16
|
+
- **Renames:** ids are content-addressed, not path-addressed — moving or renaming
|
|
17
|
+
a roadmap does not change them.
|
|
18
|
+
|
|
19
|
+
## Revision semantics — when a ref is evaluated
|
|
20
|
+
|
|
21
|
+
**At the current head, always.** A ref that resolved at declaration time and no
|
|
22
|
+
longer resolves is `unresolved`, and that is the intended reading rather than a
|
|
23
|
+
defect in the reading: evidence that has been deleted is exactly what a
|
|
24
|
+
traceability inventory exists to surface. The consequence is stated rather than
|
|
25
|
+
discovered: **a completed roadmap can move from resolved to unresolved with no
|
|
26
|
+
roadmap edit**, because the tree moved under it.
|
|
27
|
+
|
|
28
|
+
## What "gate" means here, of the three things it could mean
|
|
29
|
+
|
|
30
|
+
The council named three unresolved senses. Exactly one ships:
|
|
31
|
+
|
|
32
|
+
| Sense | Status |
|
|
33
|
+
|---|---|
|
|
34
|
+
| **Listing** declarations | **ships** — inventory, exit 0 always |
|
|
35
|
+
| **Resolving** references | ships as part of the listing, reported as a count |
|
|
36
|
+
| **Enforcing** roadmap validity | **does not ship**, and does not ship until the relation model above has been exercised on a real corpus |
|
|
37
|
+
|
|
38
|
+
## The `[AC:<id>]` prefix on a `verify:` line
|
|
39
|
+
|
|
40
|
+
A `verify:` line may carry a leading `[AC:<acceptance_id>]` to bind that check to
|
|
41
|
+
a criterion.
|
|
42
|
+
|
|
43
|
+
**Nothing parses `verify:` lines structurally today** — verified 2026-08-22: a
|
|
44
|
+
grep across `src/scripts/*.ts` finds the template prose emitted by
|
|
45
|
+
`new_roadmap.ts` and one comment in `lint_evidence_artifacts.ts`, and nothing
|
|
46
|
+
else. Both halves of that matter. **Nothing breaks**, because there is no parser
|
|
47
|
+
to break. And **there is nothing to build on** either: the inventory below is
|
|
48
|
+
the first structural reader of those lines, so any claim about `[AC:…]` coverage
|
|
49
|
+
rests entirely on it.
|
|
50
|
+
|
|
@@ -135,6 +135,23 @@ recommends. The mapping (no new gate needed):
|
|
|
135
135
|
| LLM01 #6 — segregate and identify external content | [`untrusted-input-defense`](../../../src/rules/untrusted-input-defense.md) + this guideline |
|
|
136
136
|
| LLM06 — least agency / post-action gating | [`runtime-safety`](../../../src/rules/runtime-safety.md) (manual/assisted/automated), [`verify-before-complete`](../../../src/rules/verify-before-complete.md) |
|
|
137
137
|
|
|
138
|
+
## The content-scanning hook
|
|
139
|
+
|
|
140
|
+
`injection_scan_hook.ts` binds to `post_tool_use`, reads tool output, and
|
|
141
|
+
reports **15 measured encoding channels plus four phrase families** — measured
|
|
142
|
+
over the frozen corpus at `internal/bench/corpora/encoding-channels/` at
|
|
143
|
+
**99.00 % recall and a 0.85 % false-positive rate**. It is warn-only
|
|
144
|
+
(`fail_closed: false`, `severity: advisory` in `hook_manifest.yaml`) and ships
|
|
145
|
+
**default-OFF**.
|
|
146
|
+
|
|
147
|
+
Read those two numbers as properties of that corpus, not of the wild: a channel
|
|
148
|
+
nobody put in the corpus is a channel the recall figure says nothing about.
|
|
149
|
+
|
|
150
|
+
It changes nothing about the rule's `enforced_by` field, and the reason is in
|
|
151
|
+
[`untrusted-input-defense § Enforcement`](../../../src/rules/untrusted-input-defense.md):
|
|
152
|
+
a hook that cannot refuse does not enforce. The detector narrows what an
|
|
153
|
+
injection can do unnoticed; it does not make the quarantine mechanical.
|
|
154
|
+
|
|
138
155
|
## Limits
|
|
139
156
|
|
|
140
157
|
Detection and spotlighting are **probabilistic** layers, not guarantees
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@event4u/agent-config",
|
|
3
3
|
"mcpName": "io.github.event4u-app/agent-config",
|
|
4
|
-
"version": "14.
|
|
4
|
+
"version": "14.9.0",
|
|
5
5
|
"description": "Governed skills, rules and commands for AI coding tools (Claude Code, Cursor, Windsurf, Copilot) — every claim machine-checked, including \"zero runtime daemon\".",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"private": false,
|
|
@@ -55,7 +55,11 @@
|
|
|
55
55
|
"LICENSE",
|
|
56
56
|
"MIGRATION.md",
|
|
57
57
|
"README.md",
|
|
58
|
-
"llms.txt"
|
|
58
|
+
"llms.txt",
|
|
59
|
+
"!dist/**/*.test.js",
|
|
60
|
+
"!dist/**/*.test.js.map",
|
|
61
|
+
"!dist/**/*.spec.js",
|
|
62
|
+
"!dist/**/*.spec.js.map"
|
|
59
63
|
],
|
|
60
64
|
"bin": {
|
|
61
65
|
"agent-config": "dist/cli/agent-config.js"
|
|
@@ -77,7 +81,7 @@
|
|
|
77
81
|
"build:mcp-catalog": "./scripts-run src/scripts/build_mcp_catalog --write --strict",
|
|
78
82
|
"build": "npm run build:cli && npm run build:install-bundle && npm run build:hooks && npm run build:mcp-bundle && npm run build:cli-delegate && npm run build:ui",
|
|
79
83
|
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.scripts.json",
|
|
80
|
-
"lint:ts": "eslint --cache 'src/**/*.ts'",
|
|
84
|
+
"lint:ts": "eslint --cache --max-warnings 0 'src/**/*.ts'",
|
|
81
85
|
"test:ts": "vitest run",
|
|
82
86
|
"lint:publint": "publint",
|
|
83
87
|
"prepack": "npm run build && npm run build:discovery && npm run build:mcp-manifest && node src/scripts/prepack-check.mjs",
|
|
@@ -398,10 +398,13 @@ function _regen_dashboard(root: string, dry_run: boolean): void {
|
|
|
398
398
|
// Python: `_run([sys.executable, str(script)], root)`; the twin spawns the
|
|
399
399
|
// dashboard generator twin through the same `tsx` runtime, cwd=root.
|
|
400
400
|
_runTwin(root, script);
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
401
|
+
// The dashboard is NOT staged. It is gitignored in this repository, and
|
|
402
|
+
// `git add -- <ignored-path>` exits 1 with "The following paths are ignored"
|
|
403
|
+
// -- a code `_run` returns and this call site discarded, so the previous
|
|
404
|
+
// `git add` here was a silent no-op from the moment the file was untracked
|
|
405
|
+
// (2026-08-21) and nothing objected. Regenerating it is still correct; the
|
|
406
|
+
// developer reads it on disk, and `task roadmap-progress-check` compares a
|
|
407
|
+
// present copy against a fresh render.
|
|
405
408
|
}
|
|
406
409
|
|
|
407
410
|
/**
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// The `--check` tracked/untracked mode table for the roadmap dashboard.
|
|
2
|
+
//
|
|
3
|
+
// Extracted from `update_roadmap_progress.ts` so the verdict is a pure function
|
|
4
|
+
// over observable state — the generator does the I/O, this decides. It also
|
|
5
|
+
// keeps that script under its per-file size cap.
|
|
6
|
+
|
|
7
|
+
import { spawnSync } from 'node:child_process';
|
|
8
|
+
import * as fs from 'node:fs';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Is the dashboard expected to be a committed artefact? Explicit, never
|
|
12
|
+
* inferred from git state — an unconditional "absent means pass" cannot tell a
|
|
13
|
+
* correctly-untracked repo from a generator that silently stopped producing
|
|
14
|
+
* output. `tracked` is the default and the historical behaviour.
|
|
15
|
+
*/
|
|
16
|
+
export type DashboardMode = 'tracked' | 'untracked';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Is `abs` in the git index? Used only by `--check --untracked-mode`, to tell
|
|
20
|
+
* "correctly absent" from "still committed". No git at all answers `false`,
|
|
21
|
+
* which is correct: nothing is tracked there.
|
|
22
|
+
*/
|
|
23
|
+
export function isTrackedInGit(abs: string, repo_root: string): boolean {
|
|
24
|
+
const r = spawnSync('git', ['ls-files', '--error-unmatch', '--', abs], {
|
|
25
|
+
cwd: repo_root,
|
|
26
|
+
encoding: 'utf-8',
|
|
27
|
+
timeout: 10_000,
|
|
28
|
+
});
|
|
29
|
+
return r.status === 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface DashboardStateInput {
|
|
33
|
+
mode: DashboardMode;
|
|
34
|
+
/** Is the file on disk? */
|
|
35
|
+
present: boolean;
|
|
36
|
+
/** Is the file in the git index? Only consulted in `untracked` mode. */
|
|
37
|
+
trackedInGit: boolean;
|
|
38
|
+
/** Does the on-disk content equal a fresh render? Only meaningful if present. */
|
|
39
|
+
current: boolean;
|
|
40
|
+
/** Repo-relative path, for the messages. */
|
|
41
|
+
rel: string;
|
|
42
|
+
/**
|
|
43
|
+
* How to regenerate, for the stale/missing messages. Defaults to the
|
|
44
|
+
* dashboard's own command. A second caller — `build_archive_index --check`
|
|
45
|
+
* — reuses this table for a different generated artefact, and a message
|
|
46
|
+
* telling that reader to run the dashboard generator would be wrong.
|
|
47
|
+
*/
|
|
48
|
+
regen?: string;
|
|
49
|
+
/**
|
|
50
|
+
* What the artefact is called in the "deliberately does not commit the X"
|
|
51
|
+
* hint. Defaults to `dashboard` so every existing message is byte-identical.
|
|
52
|
+
*/
|
|
53
|
+
noun?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface DashboardStateVerdict {
|
|
57
|
+
/** True when the dashboard half of `--check` fails. */
|
|
58
|
+
stale: boolean;
|
|
59
|
+
/** Diagnostic for stderr, or null. */
|
|
60
|
+
error: string | null;
|
|
61
|
+
/** Success line for stdout, or null when `stale`. */
|
|
62
|
+
ok: string | null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// The SOURCE path, not `.augment/scripts/…`: that tree is a gitignored
|
|
66
|
+
// projection built by `task sync`, so the advice failed outright in a fresh
|
|
67
|
+
// worktree and in any CI step ordered before the sync.
|
|
68
|
+
const REGEN =
|
|
69
|
+
'Run `./scripts-run src/agent-src/scripts/update_roadmap_progress` ' +
|
|
70
|
+
'to regenerate (or `task roadmap-progress` in Taskfile projects).';
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The mode table:
|
|
74
|
+
*
|
|
75
|
+
* tracked: absent -> fail | present+stale -> fail | present+current -> pass
|
|
76
|
+
* untracked: in-index -> fail | absent -> pass | present+stale -> fail
|
|
77
|
+
* | present+current -> pass
|
|
78
|
+
*
|
|
79
|
+
* Absence is distinguished ONLY in untracked mode; a present file is
|
|
80
|
+
* freshness-checked in both, so untracked never means unchecked.
|
|
81
|
+
*/
|
|
82
|
+
export function evaluateDashboardState(inp: DashboardStateInput): DashboardStateVerdict {
|
|
83
|
+
const { mode, present, trackedInGit, current, rel } = inp;
|
|
84
|
+
const regen = inp.regen ?? REGEN;
|
|
85
|
+
const noun = inp.noun ?? 'dashboard';
|
|
86
|
+
|
|
87
|
+
if (mode === 'untracked' && trackedInGit) {
|
|
88
|
+
// Migration incomplete: declared untracked, still carried by git.
|
|
89
|
+
// Print the fix; never run it — git-index ops are the user's.
|
|
90
|
+
return {
|
|
91
|
+
stale: true,
|
|
92
|
+
error:
|
|
93
|
+
`❌ ${rel} is still tracked by git, but this repository runs ` +
|
|
94
|
+
'`--untracked-mode`. Untrack it, keeping the working-tree file:\n' +
|
|
95
|
+
` git rm --cached ${rel}\n`,
|
|
96
|
+
ok: null,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!present) {
|
|
101
|
+
if (mode === 'tracked') {
|
|
102
|
+
return {
|
|
103
|
+
stale: true,
|
|
104
|
+
error:
|
|
105
|
+
`❌ ${rel} is missing. ` +
|
|
106
|
+
regen.replace('to regenerate', 'to generate it') +
|
|
107
|
+
' If this repository deliberately does not commit the ' +
|
|
108
|
+
`${noun}, pass \`--untracked-mode\`.\n`,
|
|
109
|
+
ok: null,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
// untracked + absent: the declared state. "up to date" would mislead.
|
|
113
|
+
return {
|
|
114
|
+
stale: false,
|
|
115
|
+
error: null,
|
|
116
|
+
ok: `✅ ${rel} is not committed here (--untracked-mode) and no stale copy is on disk.\n`,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!current) {
|
|
121
|
+
return { stale: true, error: `❌ ${rel} is stale. ${regen}\n`, ok: null };
|
|
122
|
+
}
|
|
123
|
+
return { stale: false, error: null, ok: `✅ ${rel} is up to date.\n` };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* `evaluateDashboardState` against the real filesystem and git index. The
|
|
128
|
+
* generator hands over the paths; every read the verdict needs happens here, so
|
|
129
|
+
* the caller's check branch stays three lines.
|
|
130
|
+
*/
|
|
131
|
+
export function evaluateDashboardOnDisk(opts: {
|
|
132
|
+
mode: DashboardMode;
|
|
133
|
+
target: string;
|
|
134
|
+
repo_root: string;
|
|
135
|
+
rendered: string;
|
|
136
|
+
rel: string;
|
|
137
|
+
regen?: string;
|
|
138
|
+
noun?: string;
|
|
139
|
+
}): DashboardStateVerdict {
|
|
140
|
+
const present = fs.existsSync(opts.target);
|
|
141
|
+
return evaluateDashboardState({
|
|
142
|
+
mode: opts.mode,
|
|
143
|
+
present,
|
|
144
|
+
trackedInGit: opts.mode === 'untracked' && isTrackedInGit(opts.target, opts.repo_root),
|
|
145
|
+
current: present && fs.readFileSync(opts.target, { encoding: 'utf-8' }) === opts.rendered,
|
|
146
|
+
rel: opts.rel,
|
|
147
|
+
...(opts.regen === undefined ? {} : { regen: opts.regen }),
|
|
148
|
+
...(opts.noun === undefined ? {} : { noun: opts.noun }),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** One roadmap row, as far as the estate report below needs it. */
|
|
153
|
+
export interface EstateRow {
|
|
154
|
+
rel: string;
|
|
155
|
+
done: number;
|
|
156
|
+
total_active: number;
|
|
157
|
+
deferred: number;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* The `--check` report: the artefact's own verdict, then the estate-wide
|
|
162
|
+
* conditions — completed-but-unarchived, and Iron Law 3 deferred items.
|
|
163
|
+
*
|
|
164
|
+
* `dashboardOnly` drops the estate half. Two obligations with two blast radii:
|
|
165
|
+
* the artefact verdict is about ONE file and is safe behind a required status
|
|
166
|
+
* check, while the estate conditions are about roadmaps an unrelated change
|
|
167
|
+
* never touched, so requiring them turns any pre-existing estate defect into a
|
|
168
|
+
* merge block on every PR. It did, on 2026-08-22.
|
|
169
|
+
*
|
|
170
|
+
* Extracted from the generator so the report is a pure function of the verdict
|
|
171
|
+
* and two lists — and because the generator sits exactly on its 1500-line
|
|
172
|
+
* source ceiling, where the honest way to add a feature is to take lines out.
|
|
173
|
+
*/
|
|
174
|
+
export function reportCheckVerdict(inp: {
|
|
175
|
+
verdict: DashboardStateVerdict;
|
|
176
|
+
complete: readonly EstateRow[];
|
|
177
|
+
pending: readonly EstateRow[];
|
|
178
|
+
dashboardOnly: boolean;
|
|
179
|
+
warnMergeGated: () => void;
|
|
180
|
+
gatedCount: number;
|
|
181
|
+
}): { rc: number; stdout: string; stderr: string } {
|
|
182
|
+
const { verdict, dashboardOnly } = inp;
|
|
183
|
+
const complete = dashboardOnly ? [] : inp.complete;
|
|
184
|
+
const pending = dashboardOnly ? [] : inp.pending;
|
|
185
|
+
let err = verdict.error ?? '';
|
|
186
|
+
if (complete.length) {
|
|
187
|
+
err +=
|
|
188
|
+
'❌ Completed roadmaps are still in `agents/roadmaps/` — ' +
|
|
189
|
+
'move them to `agents/roadmaps/archive/` (per the ' +
|
|
190
|
+
'`roadmap-progress-sync` rule):\n';
|
|
191
|
+
for (const r of complete) err += ` - ${r.rel} (${r.done}/${r.total_active} done)\n`;
|
|
192
|
+
}
|
|
193
|
+
if (pending.length) {
|
|
194
|
+
err +=
|
|
195
|
+
'❌ Iron Law 3 — roadmaps with unresolved `[~]` deferred ' +
|
|
196
|
+
'items must NOT auto-archive. Resolve via `roadmap-management § 4b` ' +
|
|
197
|
+
'(spawn follow-up, restore, or cancel):\n';
|
|
198
|
+
for (const r of pending) {
|
|
199
|
+
err += ` - ${r.rel} (${r.done}/${r.total_active} done · ${r.deferred} deferred)\n`;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (!dashboardOnly && inp.gatedCount > 0) inp.warnMergeGated();
|
|
203
|
+
const rc = verdict.stale || complete.length > 0 || pending.length > 0 ? 1 : 0;
|
|
204
|
+
return { rc, stdout: rc === 0 ? (verdict.ok ?? '') : '', stderr: err };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** `--tracked-mode` / `--untracked-mode` → the mode, or null for any other token. */
|
|
208
|
+
export function parseModeToken(tok: string): DashboardMode | null {
|
|
209
|
+
if (tok === '--tracked-mode') return 'tracked';
|
|
210
|
+
if (tok === '--untracked-mode') return 'untracked';
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
@@ -64,6 +64,12 @@ import * as fs from 'node:fs';
|
|
|
64
64
|
import * as path from 'node:path';
|
|
65
65
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
66
66
|
import { run_archival_sweep } from './archival_sweep.js';
|
|
67
|
+
import {
|
|
68
|
+
evaluateDashboardOnDisk,
|
|
69
|
+
parseModeToken,
|
|
70
|
+
reportCheckVerdict,
|
|
71
|
+
type DashboardMode,
|
|
72
|
+
} from './dashboard_mode.js';
|
|
67
73
|
import type * as YamlModule from 'yaml';
|
|
68
74
|
|
|
69
75
|
const _HERE = fileURLToPath(import.meta.url);
|
|
@@ -998,8 +1004,9 @@ function render(roadmaps: RoadmapStats[], bundles: Bundle[] | null, roadmap_root
|
|
|
998
1004
|
// script name covers the rest.
|
|
999
1005
|
'> Auto-generated — do not edit. Regenerate with `task roadmap-progress` ' +
|
|
1000
1006
|
'or by running the `update_roadmap_progress` script for your install; ' +
|
|
1001
|
-
'rewritten on every roadmap create / execute / completion change ' +
|
|
1002
|
-
'
|
|
1007
|
+
'rewritten on every roadmap create / execute / completion change. ' +
|
|
1008
|
+
'A repository that does not commit this file has no git history for ' +
|
|
1009
|
+
'it — regenerate to see the current state.\n>\n' +
|
|
1003
1010
|
header_meta,
|
|
1004
1011
|
);
|
|
1005
1012
|
lines.push('## Overall\n');
|
|
@@ -1214,18 +1221,32 @@ class ArgparseExit extends Error {
|
|
|
1214
1221
|
const _PROG = 'update_roadmap_progress.py';
|
|
1215
1222
|
|
|
1216
1223
|
function _usage(): string {
|
|
1217
|
-
return
|
|
1224
|
+
return (
|
|
1225
|
+
`usage: ${_PROG} [-h] [--check] [--tracked-mode | --untracked-mode]\n` +
|
|
1226
|
+
` [--archive | --no-archive] [--repo-root REPO_ROOT] [--dashboard-only]\n`
|
|
1227
|
+
);
|
|
1218
1228
|
}
|
|
1219
1229
|
|
|
1220
1230
|
interface Args {
|
|
1221
1231
|
check: boolean;
|
|
1232
|
+
mode: DashboardMode;
|
|
1222
1233
|
archive: boolean;
|
|
1223
1234
|
repo_root: string;
|
|
1235
|
+
/**
|
|
1236
|
+
* Assert ONLY this artefact's own state — tracked / absent / stale — not
|
|
1237
|
+
* the estate-wide conditions `--check` also carries. Two obligations, two
|
|
1238
|
+
* blast radii: why that split exists, and what it cost when it did not,
|
|
1239
|
+
* is on the `roadmap-dashboard-untracked-check` task in
|
|
1240
|
+
* `taskfiles/content.yml`.
|
|
1241
|
+
*/
|
|
1242
|
+
dashboard_only: boolean;
|
|
1224
1243
|
}
|
|
1225
1244
|
|
|
1226
1245
|
function _parseArgs(argv: readonly string[]): Args {
|
|
1227
1246
|
let check = false;
|
|
1247
|
+
let mode: DashboardMode = 'tracked';
|
|
1228
1248
|
let archive = false;
|
|
1249
|
+
let dashboard_only = false;
|
|
1229
1250
|
let repo_root = process.cwd();
|
|
1230
1251
|
const emitError = (msg: string): never => {
|
|
1231
1252
|
process.stderr.write(_usage());
|
|
@@ -1241,6 +1262,12 @@ function _parseArgs(argv: readonly string[]): Args {
|
|
|
1241
1262
|
} else if (tok === '--check') {
|
|
1242
1263
|
check = true;
|
|
1243
1264
|
i += 1;
|
|
1265
|
+
} else if (parseModeToken(tok) !== null) {
|
|
1266
|
+
mode = parseModeToken(tok) as DashboardMode;
|
|
1267
|
+
i += 1;
|
|
1268
|
+
} else if (tok === '--dashboard-only') {
|
|
1269
|
+
dashboard_only = true;
|
|
1270
|
+
i += 1;
|
|
1244
1271
|
} else if (tok === '--archive') {
|
|
1245
1272
|
archive = true;
|
|
1246
1273
|
i += 1;
|
|
@@ -1267,7 +1294,10 @@ function _parseArgs(argv: readonly string[]): Args {
|
|
|
1267
1294
|
// rather than silently letting one win.
|
|
1268
1295
|
emitError('argument --archive: not allowed with --check');
|
|
1269
1296
|
}
|
|
1270
|
-
|
|
1297
|
+
if (dashboard_only && !check) {
|
|
1298
|
+
emitError('argument --dashboard-only: only meaningful with --check');
|
|
1299
|
+
}
|
|
1300
|
+
return { check, mode, archive, repo_root, dashboard_only };
|
|
1271
1301
|
}
|
|
1272
1302
|
|
|
1273
1303
|
/**
|
|
@@ -1334,13 +1364,10 @@ function main(argv?: readonly string[]): number {
|
|
|
1334
1364
|
}
|
|
1335
1365
|
const roadmaps = collect(roadmap_root);
|
|
1336
1366
|
const new_text = render(roadmaps, collect_bundles(repo_root), roadmap_root);
|
|
1337
|
-
const current = fs.existsSync(target) ? fs.readFileSync(target, { encoding: 'utf-8' }) : '';
|
|
1338
1367
|
const complete = unarchived_complete(roadmaps);
|
|
1339
1368
|
const pending = pending_iron_law_3(roadmaps);
|
|
1340
1369
|
const gated = merge_gated_pending(roadmaps);
|
|
1341
1370
|
|
|
1342
|
-
const _relToRepo = (p: string): string => _relPosix(repo_root, p);
|
|
1343
|
-
|
|
1344
1371
|
const _warn_merge_gated = (): void => {
|
|
1345
1372
|
process.stderr.write(
|
|
1346
1373
|
'⏳ Merge-gated roadmaps (every open item gated on a PR) — ' +
|
|
@@ -1357,50 +1384,22 @@ function main(argv?: readonly string[]): number {
|
|
|
1357
1384
|
};
|
|
1358
1385
|
|
|
1359
1386
|
if (args.check) {
|
|
1360
|
-
const
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
}
|
|
1369
|
-
if (
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
'move them to `agents/roadmaps/archive/` (per the ' +
|
|
1373
|
-
'`roadmap-progress-sync` rule):\n',
|
|
1374
|
-
);
|
|
1375
|
-
for (const r of complete) {
|
|
1376
|
-
process.stderr.write(` - ${r.rel} (${r.done}/${r.total_active} done)\n`);
|
|
1377
|
-
}
|
|
1378
|
-
}
|
|
1379
|
-
if (pending.length) {
|
|
1380
|
-
process.stderr.write(
|
|
1381
|
-
'❌ Iron Law 3 — roadmaps with unresolved `[~]` deferred ' +
|
|
1382
|
-
'items must NOT auto-archive. Resolve via `roadmap-management § 4b` ' +
|
|
1383
|
-
'(spawn follow-up, restore, or cancel):\n',
|
|
1384
|
-
);
|
|
1385
|
-
for (const r of pending) {
|
|
1386
|
-
process.stderr.write(
|
|
1387
|
-
` - ${r.rel} (${r.done}/${r.total_active} done · ` +
|
|
1388
|
-
`${r.deferred} deferred)\n`,
|
|
1389
|
-
);
|
|
1390
|
-
}
|
|
1391
|
-
}
|
|
1392
|
-
if (gated.length) {
|
|
1393
|
-
_warn_merge_gated();
|
|
1394
|
-
}
|
|
1395
|
-
if (stale || complete.length || pending.length) {
|
|
1396
|
-
return 1;
|
|
1397
|
-
}
|
|
1398
|
-
process.stdout.write(`✅ ${_relToRepo(target)} is up to date.\n`);
|
|
1399
|
-
return 0;
|
|
1387
|
+
const rel = _relPosix(repo_root, target);
|
|
1388
|
+
const r = reportCheckVerdict({
|
|
1389
|
+
verdict: evaluateDashboardOnDisk({ mode: args.mode, target, repo_root, rendered: new_text, rel }),
|
|
1390
|
+
complete,
|
|
1391
|
+
pending,
|
|
1392
|
+
dashboardOnly: args.dashboard_only,
|
|
1393
|
+
warnMergeGated: _warn_merge_gated,
|
|
1394
|
+
gatedCount: gated.length,
|
|
1395
|
+
});
|
|
1396
|
+
if (r.stderr) process.stderr.write(r.stderr);
|
|
1397
|
+
if (r.stdout) process.stdout.write(r.stdout);
|
|
1398
|
+
return r.rc;
|
|
1400
1399
|
}
|
|
1401
1400
|
fs.writeFileSync(target, new_text, { encoding: 'utf-8' });
|
|
1402
1401
|
process.stdout.write(
|
|
1403
|
-
`✅ Wrote ${
|
|
1402
|
+
`✅ Wrote ${_relPosix(repo_root, target)} · ` +
|
|
1404
1403
|
`${roadmaps.length} roadmap(s) · ` +
|
|
1405
1404
|
`${roadmaps.reduce((s, r) => s + r.done, 0)}/${roadmaps.reduce((s, r) => s + r.total_active, 0)} steps done.\n`,
|
|
1406
1405
|
);
|
|
@@ -184,6 +184,27 @@ ci_only:
|
|
|
184
184
|
# worth remote enforcement, OR it should simply be added to a workflow. Prefer
|
|
185
185
|
# adding it: a local-only gate is the direction that let real defects merge.
|
|
186
186
|
local_only:
|
|
187
|
+
- id: check_hook_bundle_content
|
|
188
|
+
class: developer-machine-scoped
|
|
189
|
+
reason: >
|
|
190
|
+
Compares `dist/hooks/dispatch.js` — the bundle that ACTUALLY EXECUTES the
|
|
191
|
+
hooks — against a rebuild from this checkout's sources. `git ls-files
|
|
192
|
+
dist/hooks/` returns ZERO: the bundle is an untracked local build
|
|
193
|
+
artefact, so a fresh CI checkout has none and the gate can only report
|
|
194
|
+
"nothing to compare". Wiring it into a workflow would measure an artefact
|
|
195
|
+
no CI run has, which is the empty-scope false green this repo's
|
|
196
|
+
gate-coverage manifest exists to refuse. Declared rather than wired for
|
|
197
|
+
the same reason `check_single_delivery` below is. The blind spot it
|
|
198
|
+
closes is real and was measured on 2026-08-21: an edit to the git guard's
|
|
199
|
+
authorization window was never rebuilt into the bundle and the guard kept
|
|
200
|
+
enforcing the old value for a full hour. The sibling
|
|
201
|
+
`check_hook_bundle_freshness` did not miss it — that gate is pre-push and
|
|
202
|
+
was never run, and an ordinary edit moves the source mtime, so it would
|
|
203
|
+
have reddened. What it structurally cannot see is the narrower set this
|
|
204
|
+
gate covers: an mtime-preserving write, a `touch` on the bundle, a `cp -p`
|
|
205
|
+
restore. Ordering is not equivalence. That asymmetry is the finding: a green
|
|
206
|
+
CI cannot speak for the bundle a maintainer is running, and only a local
|
|
207
|
+
gate can. Cost measured, like its neighbours in the pre-push set: 0.27s.
|
|
187
208
|
- id: check_single_delivery
|
|
188
209
|
class: developer-machine-scoped
|
|
189
210
|
reason: >
|