@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
|
@@ -107,4 +107,4 @@ inflate it, so it is not claimed — skipping this rule is caught by nothing.
|
|
|
107
107
|
- [`decision-revisit-gate`](decision-revisit-gate.md) — the other entrance; owns the five steps and the owner-reserved routing this rule reuses instead of restating. Separate file only because that one sits four lines under the 200-line cap.
|
|
108
108
|
- [`self-repair-loop`](self-repair-loop.md) — the single-occurrence intake; this rule is what its `occurrences` counter is for.
|
|
109
109
|
- [`active-remediation`](active-remediation.md) — the terminal "leave it" this rule does not override (see `collision_ok`).
|
|
110
|
-
- [`/analyze:inbox`](../
|
|
110
|
+
- [`/analyze:inbox`](../commands/analyze/inbox.md) § Phase 4c — the artifact-side detection that routes here.
|
|
@@ -97,6 +97,25 @@ in the estate?**
|
|
|
97
97
|
[`no-cheap-questions`](no-cheap-questions.md) forbids. The gate protects the
|
|
98
98
|
item, not the maintainer's attention.
|
|
99
99
|
|
|
100
|
+
### `deferred_policy` — a declared contract removes the round, never the route
|
|
101
|
+
|
|
102
|
+
An accepted execution contract may declare `deferred_policy`
|
|
103
|
+
([`roadmap-execution-contract § 2b`](../contexts/execution/roadmap-execution-contract.md)).
|
|
104
|
+
It changes **when the round happens**, never **who decides**:
|
|
105
|
+
|
|
106
|
+
| Declared | Effect on the gate |
|
|
107
|
+
|---|---|
|
|
108
|
+
| absent / `wait` | Unchanged — the synchronous menu runs. |
|
|
109
|
+
| `spawn-follow-up-draft` | The council-routed carry disposition runs **automatically** at closure, with no options round. Legal only because the table above already routes it to the council and Accept pre-authorized the run. |
|
|
110
|
+
| `cancel-with-memo` | The run writes the memo and the recommendation; the `[-]` conversion stays **user**. A declared field never moves a row from user to council. |
|
|
111
|
+
|
|
112
|
+
Recorded-or-it-did-not-happen applies unchanged to an automatic spawn, and
|
|
113
|
+
fail-closed binds hardest here: a spawn naming a follow-up that does not exist
|
|
114
|
+
yet reaches the user, because a contract cannot pre-authorize a promise.
|
|
115
|
+
Reversible — `wait` is the default, so removing the field restores always-wait
|
|
116
|
+
(`decision 2026-08-20`, AI council 2/2; record
|
|
117
|
+
`agents/evidence/council/drain-blocker-dispositions-a.md`).
|
|
118
|
+
|
|
100
119
|
## Later disposition — blocked-for-later roadmaps are parked, never left active
|
|
101
120
|
|
|
102
121
|
```
|
|
@@ -91,7 +91,7 @@ substitute for not writing the attribution in the first place.
|
|
|
91
91
|
|
|
92
92
|
## Why this rule is not path-scoped
|
|
93
93
|
|
|
94
|
-
Under
|
|
94
|
+
Under ADR-236 (`docs/decisions/ADR-236-one-artefact-one-layer.md`) this rule is
|
|
95
95
|
delivered by the PROJECT layer only — it exists to maintain this package, so the
|
|
96
96
|
global layer no longer carries an unscoped twin of it. A `paths:`-scoped rule is
|
|
97
97
|
**not re-injected after `/compact`** (ADR-227:79-80), so scoping it would mean the
|
|
@@ -69,9 +69,15 @@ delegating unbounded execution of everything the task's data happens to say.
|
|
|
69
69
|
|
|
70
70
|
No deterministic gate inspects fetched/tool/RAG content for injected
|
|
71
71
|
instructions — the quarantine is carried by the model (ADR-135 lists it in the
|
|
72
|
-
CRITICAL policy class
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
CRITICAL policy class). Counting an adjacent lint here would be coverage
|
|
73
|
+
inflation, so none is counted.
|
|
74
|
+
|
|
75
|
+
**A content-scanning hook now exists and this field is unchanged.**
|
|
76
|
+
`injection_scan_hook.ts` is warn-only and default-OFF: it cannot refuse, and a
|
|
77
|
+
hook that cannot refuse does not enforce. This section used to say no such
|
|
78
|
+
backstop existed and that a future one would change the field — the first half
|
|
79
|
+
is now false, the second always was. Detail:
|
|
80
|
+
[`untrusted-input-spotlighting § The content-scanning hook`](../docs/guidelines/agent-infra/untrusted-input-spotlighting.md).
|
|
75
81
|
|
|
76
82
|
Body migrated to [`guideline:agent-infra/untrusted-input-spotlighting`](../docs/guidelines/agent-infra/untrusted-input-spotlighting.md) (per P4 of `road-to-kernel-and-router.md`) — runtime defense protocol (separate / spotlight / refuse role-takeover / no silent egress / untrusted agent-instruction files), hidden-instruction awareness (invisible Unicode + confusables), injection-signal taxonomy, least-agency → existing-gate OWASP mapping.
|
|
77
83
|
Trigger-set above activates this routing on demand, independent of the discipline profile (ADR-110).
|
|
@@ -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
|
);
|
|
@@ -45,6 +45,33 @@ Do NOT use when:
|
|
|
45
45
|
supersede it instead of duplicating.
|
|
46
46
|
- A skill, rule, or guideline is the better home (use those skills).
|
|
47
47
|
|
|
48
|
+
## Admission gate — classify before you create
|
|
49
|
+
|
|
50
|
+
Run this **before** picking a surface or a number. An ADR is warranted only
|
|
51
|
+
when the decision is architecturally significant on at least one axis:
|
|
52
|
+
|
|
53
|
+
1. **Hard or costly to reverse** — a one-way door: public API shape, DB
|
|
54
|
+
schema, wire format, published identifier, migration.
|
|
55
|
+
2. **Broadly constraining** — it binds work outside the module that made it.
|
|
56
|
+
3. **Crosses a governed surface** — consumer contract, API, security or
|
|
57
|
+
privacy floor, package structure.
|
|
58
|
+
|
|
59
|
+
None of the three holds → it is **not** an ADR. Route it to its real home: a
|
|
60
|
+
decision note in `agents/decisions/`, a config value, a measurement record in
|
|
61
|
+
`docs/CLAIMS.md`, an experiment, or a roadmap item.
|
|
62
|
+
|
|
63
|
+
Explicitly **not** ADRs: a temporary numeric threshold · a benchmark value ·
|
|
64
|
+
a model mapping · one-off release sequencing · a reversible local
|
|
65
|
+
implementation detail.
|
|
66
|
+
|
|
67
|
+
**The tree's own reference case.** ADR-002 encodes `25 000 → 26 000` and a
|
|
68
|
+
`4.0k` override ceiling as architecture law (`ADR-002:55`, `:62`), and
|
|
69
|
+
ADR-114 then had to add another override while recording that 7 of 9 kernel
|
|
70
|
+
rules already carry them (`ADR-114:74`). The *principle* — a kernel budget
|
|
71
|
+
exists, is measured, and is capped — is the ADR. The numbers belong in a
|
|
72
|
+
versioned budget contract with a regression gate, so a recalibration stops
|
|
73
|
+
needing an architecture supersession.
|
|
74
|
+
|
|
48
75
|
## Goal
|
|
49
76
|
|
|
50
77
|
- Sequential `ADR-NNN-<slug>.md` numbering with no gaps.
|
|
@@ -114,6 +141,11 @@ Use the surface-specific template. All sections are required; "—"
|
|
|
114
141
|
is acceptable for genuinely empty Alternatives or References blocks
|
|
115
142
|
but never for Status, Context, Decision, or Consequences.
|
|
116
143
|
|
|
144
|
+
`## Evidence` and `## Assumptions` are required headings too, with one
|
|
145
|
+
carve-out each: `## Evidence` may be empty only when the record grades itself
|
|
146
|
+
`E0` and says so in the section, and `## Assumptions` may be "—" only when
|
|
147
|
+
every load-bearing claim in the rationale carries a basis ref.
|
|
148
|
+
|
|
117
149
|
**`review_trigger` is required and it names a CONDITION, not a date.** A
|
|
118
150
|
decision is a call made under conditions that held at the time; the trigger
|
|
119
151
|
records which change would make it worth re-deciding. "Review annually" is
|
|
@@ -124,11 +156,52 @@ the event: *"when a second consumer reports the same preservation surprise"*,
|
|
|
124
156
|
below the pre-registered threshold"*. Enforced from 2026-07-25 forward; earlier
|
|
125
157
|
ADRs are grandfathered by date.
|
|
126
158
|
|
|
159
|
+
**Staged — and `terminal` is not one of the stages.** A new or materially
|
|
160
|
+
amended accepted ADR needs a substantive trigger now. An *existing* accepted
|
|
161
|
+
record may carry `review_trigger: unclassified` while the migration runs, and
|
|
162
|
+
that exception count only ever decreases. `terminal`, `none`, an empty value
|
|
163
|
+
and permanence phrasing ("forever", "never revisit", "settled forever") are
|
|
164
|
+
invalid at **every** stage — `check_adr_frontmatter.ts` rejects them, because
|
|
165
|
+
"no trigger — terminal decision" is permanence with softer wording. `terminal`
|
|
166
|
+
is not a migration state; it is the thing the staging exists to stop becoming
|
|
167
|
+
permanent. Superseded, rejected and deprecated records are historical and need
|
|
168
|
+
no active trigger.
|
|
169
|
+
|
|
127
170
|
When you later reopen one, say which **premise moved** and what evidences the
|
|
128
171
|
move — not "we were wrong". If the original was right under its own conditions,
|
|
129
172
|
record that too. A premise that turns out false while the decision stays correct
|
|
130
173
|
gets a logged correction block, never a silent edit.
|
|
131
174
|
|
|
175
|
+
**Three descriptive axes ship on a new record** — `provenance`, `evidence`,
|
|
176
|
+
`authority_basis`. Vocabulary, defaults and the mutation policy are owned by
|
|
177
|
+
[`adr-layout § Provenance and evidence`](../../../docs/contracts/adr-layout.md);
|
|
178
|
+
`check_adr_frontmatter.ts` validates the shape. What the author has to get
|
|
179
|
+
right while drafting:
|
|
180
|
+
|
|
181
|
+
- Every load-bearing factual claim in the rationale either **points at a basis
|
|
182
|
+
ref** — `file:line`, a URL, a `docs/CLAIMS.md` claim id, a benchmark id — or
|
|
183
|
+
is **labelled an assumption** under `## Assumptions`. There is no third
|
|
184
|
+
state, and an unlabelled guess reads as a finding to the next reader.
|
|
185
|
+
- **An empty `## Evidence` section means `E0` by construction, and the record
|
|
186
|
+
says so.** An honest E0 is publishable, exactly as an honest null is; a
|
|
187
|
+
confident grade over an empty section is not.
|
|
188
|
+
- `discovery: incomplete` is the honest default on E0 and stays that value
|
|
189
|
+
until a defined evidence search has run and found nothing. `complete`
|
|
190
|
+
asserts absence — a claim the author owns.
|
|
191
|
+
- **A grade of E2 or above must name a `basis`.** The validator rejects an
|
|
192
|
+
E2/E3/E4 with an empty basis list; grade it lower rather than asserting a
|
|
193
|
+
source you cannot cite.
|
|
194
|
+
- **A council attribution never lifts a grade above E0.** N models agreeing is
|
|
195
|
+
`provenance: agentic` with `agentic_mode: council` — sources and
|
|
196
|
+
measurements raise `strength`, consensus does not. A council is deliberately
|
|
197
|
+
not its own `kind`.
|
|
198
|
+
- A human product decision records `strength: E0` with
|
|
199
|
+
`authority_basis: owner_intent` and does **not** fake a grade. Its authority
|
|
200
|
+
comes from owning the purpose.
|
|
201
|
+
- **A grade prices review burden, never authority.** Nothing about writing
|
|
202
|
+
`E0` makes the record cheaper to overturn *by whom* — see
|
|
203
|
+
[`adr-layout § The reopen record`](../../../docs/contracts/adr-layout.md).
|
|
204
|
+
|
|
132
205
|
**Flat-surface template** (`docs/decisions/ADR-NNN-<slug>.md`):
|
|
133
206
|
|
|
134
207
|
```markdown
|
|
@@ -145,6 +218,17 @@ phase: <roadmap> · <phase-id>
|
|
|
145
218
|
review_trigger: <the CONDITION that would reopen this decision>
|
|
146
219
|
protected_dimensions: [...] # optional — purpose | security_floor | privacy_floor | external_commitment | governance | none
|
|
147
220
|
reopen_policy: directional | owner | unclassified # optional; absent → unclassified
|
|
221
|
+
|
|
222
|
+
provenance:
|
|
223
|
+
kind: human | agentic | mixed | unknown
|
|
224
|
+
decision_makers: [...] # who actually selected it
|
|
225
|
+
human_directed: true | false | unknown
|
|
226
|
+
agentic_mode: single | council | delegated # optional, descriptive only
|
|
227
|
+
evidence:
|
|
228
|
+
strength: E0 | E1 | E2 | E3 | E4
|
|
229
|
+
discovery: complete | incomplete # required when strength is E0
|
|
230
|
+
basis: [...] # file:line | URL | CLAIMS id | benchmark id
|
|
231
|
+
authority_basis: evidence | owner_intent # optional; absent → evidence
|
|
148
232
|
---
|
|
149
233
|
|
|
150
234
|
# ADR-NNN — <Decision Title>
|
|
@@ -153,24 +237,59 @@ reopen_policy: directional | owner | unclassified # optional; absent → uncla
|
|
|
153
237
|
|
|
154
238
|
**<Proposed | Accepted | …>** · YYYY-MM-DD.
|
|
155
239
|
|
|
156
|
-
## Context / Decision / Consequences / Alternatives / References
|
|
240
|
+
## Context / Decision / Evidence / Assumptions / Consequences / Alternatives / References
|
|
157
241
|
```
|
|
158
242
|
|
|
243
|
+
`## Evidence` carries one line per basis ref backing a load-bearing claim.
|
|
244
|
+
`## Assumptions` carries every load-bearing claim that has no basis ref — that
|
|
245
|
+
is what makes the grade honest rather than decorative.
|
|
246
|
+
|
|
159
247
|
**Per-area template** (`docs/adrs/<area>/NNNN-<slug>.md`):
|
|
160
248
|
|
|
161
249
|
```markdown
|
|
250
|
+
---
|
|
251
|
+
adr: NNNN
|
|
252
|
+
area: <area>
|
|
253
|
+
status: proposed | accepted | superseded | deprecated
|
|
254
|
+
date: YYYY-MM-DD
|
|
255
|
+
decision: <slug>
|
|
256
|
+
supersedes: —
|
|
257
|
+
superseded_by: —
|
|
258
|
+
type: retrospective | prospective
|
|
259
|
+
review_trigger: <the CONDITION that would reopen this decision>
|
|
260
|
+
|
|
261
|
+
provenance:
|
|
262
|
+
kind: human | agentic | mixed | unknown
|
|
263
|
+
decision_makers: [...]
|
|
264
|
+
human_directed: true | false | unknown
|
|
265
|
+
agentic_mode: single | council | delegated # optional, descriptive only
|
|
266
|
+
evidence:
|
|
267
|
+
strength: E0 | E1 | E2 | E3 | E4
|
|
268
|
+
discovery: complete | incomplete # required when strength is E0
|
|
269
|
+
basis: [...] # file:line | URL | CLAIMS id | benchmark id
|
|
270
|
+
authority_basis: evidence | owner_intent # optional; absent → evidence
|
|
271
|
+
---
|
|
272
|
+
|
|
162
273
|
# ADR NNNN — <Decision Title>
|
|
163
274
|
|
|
164
275
|
> Area: `<area>` · Status: accepted · Date: YYYY-MM-DD · Type: retrospective | new
|
|
165
276
|
> Roadmap: `agents/roadmaps/<file>.md` <phase-id>
|
|
166
277
|
> Supersedes: —
|
|
167
278
|
|
|
168
|
-
## Context / Decision / Considered alternatives / Consequences / References
|
|
279
|
+
## Context / Decision / Evidence / Assumptions / Considered alternatives / Consequences / References
|
|
169
280
|
```
|
|
170
281
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
282
|
+
The frontmatter block is the same shape as the flat surface — the contract
|
|
283
|
+
says so ([`adr-layout § Frontmatter`](../../../docs/contracts/adr-layout.md):
|
|
284
|
+
"identical across both surfaces"), and `audit_adr_coverage.ts`'s parser reads
|
|
285
|
+
it when present. The quote-style header stays as the human-readable banner,
|
|
286
|
+
and every existing per-area record now carries frontmatter beside it, so their
|
|
287
|
+
generated README tables render real values — see
|
|
288
|
+
[`docs/adrs/telegraph/README.md`](../../../docs/adrs/telegraph/README.md),
|
|
289
|
+
whose rows read `accepted` / `2026-05-16`. A record carrying the banner alone
|
|
290
|
+
is not blank either: `parse_blockquote_meta` reads the
|
|
291
|
+
`> Area: … · Status: … · Date: …` line as a fallback, and only a record with
|
|
292
|
+
neither renders "—". Cite the area's contract from the README in
|
|
174
293
|
[`docs/adrs/<area>/README.md`](../../../docs/adrs/).
|
|
175
294
|
|
|
176
295
|
### 5. Regenerate the index
|