@event4u/agent-config 14.5.0 → 14.7.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.
Files changed (203) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +384 -3
  3. package/README.md +1 -1
  4. package/dist/agent-src/commands/analyze/decision.md +7 -0
  5. package/dist/agent-src/commands/analyze/inbox.md +264 -10
  6. package/dist/agent-src/commands/analyze.md +1 -1
  7. package/dist/agent-src/commands/optimize/project.md +10 -0
  8. package/dist/agent-src/commands/roadmap/process-full.md +132 -4
  9. package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +31 -0
  10. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +161 -1
  11. package/dist/agent-src/rules/context-hygiene.md +2 -0
  12. package/dist/agent-src/rules/decision-revisit-gate.md +111 -7
  13. package/dist/agent-src/rules/fix-what-you-see.md +108 -0
  14. package/dist/agent-src/rules/no-roadmap-references.md +0 -5
  15. package/dist/agent-src/rules/recurring-criticism.md +110 -0
  16. package/dist/agent-src/rules/roadmap-progress-sync.md +1 -1
  17. package/dist/agent-src/rules/rule-type-governance.md +0 -2
  18. package/dist/agent-src/rules/self-repair-loop.md +17 -0
  19. package/dist/agent-src/rules/skill-quality.md +0 -2
  20. package/dist/agent-src/rules/source-confidentiality.md +37 -7
  21. package/dist/agent-src/scripts/archival_sweep.ts +117 -0
  22. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +19 -3
  23. package/dist/agent-src/scripts/gate_budget.ts +282 -0
  24. package/dist/agent-src/scripts/gate_execute.ts +72 -31
  25. package/dist/agent-src/scripts/roadmap_gates.ts +74 -3
  26. package/dist/agent-src/scripts/update_roadmap_progress.ts +251 -7
  27. package/dist/agent-src/skills/adr-create/SKILL.md +25 -3
  28. package/dist/agent-src/skills/ai-council/SKILL.md +48 -807
  29. package/dist/agent-src/skills/ai-council/references/advanced-modes.md +320 -0
  30. package/dist/agent-src/skills/ai-council/references/cost-and-redaction.md +99 -0
  31. package/dist/agent-src/skills/ai-council/references/execution-modes.md +162 -0
  32. package/dist/agent-src/skills/ai-council/references/output-and-synthesis.md +125 -0
  33. package/dist/agent-src/skills/ai-council/references/procedure.md +143 -0
  34. package/dist/agent-src/skills/decision-review/SKILL.md +18 -0
  35. package/dist/agent-src/skills/quality-tools/SKILL.md +34 -326
  36. package/dist/agent-src/skills/quality-tools/references/js-ts-tools.md +113 -0
  37. package/dist/agent-src/skills/quality-tools/references/php-tools.md +222 -0
  38. package/dist/agent-src/skills/roadmap-management/SKILL.md +15 -409
  39. package/dist/agent-src/skills/roadmap-management/references/archival.md +226 -0
  40. package/dist/agent-src/skills/roadmap-management/references/authoring.md +107 -0
  41. package/dist/agent-src/skills/roadmap-management/references/dashboard.md +47 -0
  42. package/dist/agent-src/skills/roadmap-management/references/lifecycle.md +58 -0
  43. package/dist/agent-src/skills/skill-writing/SKILL.md +34 -570
  44. package/dist/agent-src/skills/skill-writing/references/procedure.md +275 -0
  45. package/dist/agent-src/skills/skill-writing/references/read-only-scripts.md +18 -0
  46. package/dist/agent-src/skills/skill-writing/references/section-patterns.md +298 -0
  47. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  48. package/dist/agent-src/templates/roadmaps.md +45 -2
  49. package/dist/agent-src/templates/scripts/telemetry/flush_sender.mjs +163 -0
  50. package/dist/agent-src/templates/scripts/telemetry/remote.ts +160 -7
  51. package/dist/agent-src/templates/scripts/telemetry/transport.ts +169 -0
  52. package/dist/cli/registry.js +1 -1
  53. package/dist/cli/registry.js.map +1 -1
  54. package/dist/cli-delegate/chunk-2GNWGMAL.js +172 -0
  55. package/dist/cli-delegate/{chunk-RLN73PR5.js → chunk-4VR4ORQ6.js} +10 -7
  56. package/dist/cli-delegate/chunk-6EOCNQAM.js +402 -0
  57. package/dist/cli-delegate/{chunk-XURJ4AQC.js → chunk-74E3TKWV.js} +381 -280
  58. package/dist/cli-delegate/{chunk-3UCJWMMP.js → chunk-7F3VYSM2.js} +4 -4
  59. package/dist/cli-delegate/{chunk-BKO2AZ7Z.js → chunk-B2ERU2VA.js} +1 -1
  60. package/dist/cli-delegate/{chunk-NXFWFAYV.js → chunk-G23AT6GD.js} +1 -0
  61. package/dist/cli-delegate/{chunk-63WSP6YZ.js → chunk-GJ5BVHXM.js} +72 -165
  62. package/dist/cli-delegate/{chunk-ZPSXJTLI.js → chunk-JZ4NR2TW.js} +353 -1
  63. package/dist/cli-delegate/chunk-MWRD5YOH.js +100 -0
  64. package/dist/cli-delegate/{chunk-GRRPEPRN.js → chunk-T7L2QK3K.js} +24 -5
  65. package/dist/cli-delegate/{chunk-EVTY7ISR.js → chunk-YM4GZDJG.js} +9 -1
  66. package/dist/cli-delegate/cmd_conformance.js +459 -709
  67. package/dist/cli-delegate/cmd_doctor.js +6 -6
  68. package/dist/cli-delegate/cmd_explain.js +1 -1
  69. package/dist/cli-delegate/cmd_export.js +7 -6
  70. package/dist/cli-delegate/cmd_handoff.js +6 -6
  71. package/dist/cli-delegate/cmd_packs_active.js +1 -1
  72. package/dist/cli-delegate/cmd_preflight.js +2 -2
  73. package/dist/cli-delegate/cmd_prune.js +7 -6
  74. package/dist/cli-delegate/cmd_session_recycle.js +840 -30
  75. package/dist/cli-delegate/cmd_settings_migrate.js +7 -6
  76. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  77. package/dist/cli-delegate/cmd_sync.js +7 -6
  78. package/dist/cli-delegate/cmd_uninstall.js +9 -7
  79. package/dist/cli-delegate/cmd_update.js +1 -1
  80. package/dist/cli-delegate/cmd_upgrade.js +1 -1
  81. package/dist/cli-delegate/cmd_validate.js +7 -6
  82. package/dist/discovery/deprecation-report.md +1 -1
  83. package/dist/discovery/discovery-manifest.json +72 -29
  84. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  85. package/dist/discovery/discovery-manifest.summary.md +4 -4
  86. package/dist/discovery/dormancy-report.md +1 -1
  87. package/dist/discovery/orphan-report.md +1 -1
  88. package/dist/discovery/packs.json +10 -8
  89. package/dist/discovery/trust-report.md +3 -3
  90. package/dist/discovery/workspaces.json +8 -6
  91. package/dist/hooks/dispatch.js +3127 -2022
  92. package/dist/install/claudePathsPlan.js +187 -0
  93. package/dist/install/claudePathsPlan.js.map +1 -0
  94. package/dist/install/hostLayerFingerprint.js +136 -0
  95. package/dist/install/hostLayerFingerprint.js.map +1 -0
  96. package/dist/install/install.mjs +553 -425
  97. package/dist/install/partitionEligibility.js +276 -0
  98. package/dist/install/partitionEligibility.js.map +1 -0
  99. package/dist/mcp/registry-manifest.json +3 -3
  100. package/dist/mcp/server.json +2 -2
  101. package/dist/mcp/server.mjs +1744 -1191
  102. package/dist/router.json +1 -1
  103. package/dist/scripts/_lib/agent_src.js +41 -0
  104. package/dist/scripts/_lib/agent_src.js.map +1 -1
  105. package/dist/scripts/_lib/install_layout.js +83 -0
  106. package/dist/scripts/_lib/install_layout.js.map +1 -0
  107. package/dist/scripts/_lib/installed_lock.js +444 -0
  108. package/dist/scripts/_lib/installed_lock.js.map +1 -0
  109. package/dist/scripts/condense.js +13 -164
  110. package/dist/scripts/condense.js.map +1 -1
  111. package/dist/server/schemas/settings.js +4 -0
  112. package/dist/server/schemas/settings.js.map +1 -1
  113. package/docs/contracts/settings-classes.md +12 -6
  114. package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +8 -3
  115. package/docs/guidelines/agent-infra/context-hygiene-mechanics.md +26 -0
  116. package/docs/guidelines/agent-infra/engineering-memory-data-format.md +49 -0
  117. package/docs/guidelines/agent-infra/memory-access.md +17 -0
  118. package/docs/guidelines/agent-infra/recurring-criticism-mechanics.md +96 -0
  119. package/package.json +1 -1
  120. package/src/agent-src/scripts/archival_sweep.ts +117 -0
  121. package/src/agent-src/scripts/archive_completed_roadmaps.ts +19 -3
  122. package/src/agent-src/scripts/gate_budget.ts +282 -0
  123. package/src/agent-src/scripts/gate_execute.ts +72 -31
  124. package/src/agent-src/scripts/roadmap_gates.ts +74 -3
  125. package/src/agent-src/scripts/update_roadmap_progress.ts +251 -7
  126. package/src/agent-src/templates/scripts/telemetry/flush_sender.mjs +163 -0
  127. package/src/agent-src/templates/scripts/telemetry/remote.ts +160 -7
  128. package/src/agent-src/templates/scripts/telemetry/transport.ts +169 -0
  129. package/src/config/agent-settings.template.yml +27 -2
  130. package/src/config/ci-local-parity.yml +18 -0
  131. package/src/config/estate-count-budget.json +367 -3
  132. package/src/config/gate-violation-baselines.json +41 -41
  133. package/src/config/pack-size-budget.json +3 -2
  134. package/src/config/rule-activation-census.json +14 -6
  135. package/src/config/rule-stub-ceilings.json +19 -6
  136. package/src/install/claudePathsPlan.ts +196 -0
  137. package/src/install/hostLayerFingerprint.ts +145 -0
  138. package/src/install/partitionEligibility.ts +325 -0
  139. package/src/scripts/_cli/cmd_session_recycle.ts +9 -1
  140. package/src/scripts/_dispatch.bash +6 -1
  141. package/src/scripts/_lib/ac_heading.ts +30 -0
  142. package/src/scripts/_lib/agent_src.ts +42 -0
  143. package/src/scripts/_lib/bench_ab_pinned_repo.ts +162 -0
  144. package/src/scripts/_lib/bench_ab_workspace.ts +10 -1
  145. package/src/scripts/_lib/envelope_grounding.ts +38 -8
  146. package/src/scripts/_lib/installed_lock.ts +50 -5
  147. package/src/scripts/_lib/layer_overlap_notice.ts +85 -0
  148. package/src/scripts/_lib/md_section.ts +129 -0
  149. package/src/scripts/_lib/scope_guard.sh +86 -4
  150. package/src/scripts/_lib/self_repair.ts +19 -0
  151. package/src/scripts/_lib/self_repair_class_b.ts +171 -0
  152. package/src/scripts/_lib/subagent_capsule.ts +81 -1
  153. package/src/scripts/adr/regenerate_index.ts +13 -2
  154. package/src/scripts/adr_cite_check.ts +424 -0
  155. package/src/scripts/ai_council/cli_help.ts +55 -0
  156. package/src/scripts/before_complete_hook.ts +153 -28
  157. package/src/scripts/bench_ab_v2_complexity.ts +27 -7
  158. package/src/scripts/check_adr_frontmatter.ts +187 -1
  159. package/src/scripts/check_references.ts +60 -0
  160. package/src/scripts/check_release_published.ts +32 -11
  161. package/src/scripts/check_single_delivery.ts +517 -0
  162. package/src/scripts/ci_settle.ts +170 -0
  163. package/src/scripts/condense.ts +20 -170
  164. package/src/scripts/council_cli.ts +3 -3
  165. package/src/scripts/dispatch_r2_reviewer.ts +6 -14
  166. package/src/scripts/hook_manifest.json +1 -1
  167. package/src/scripts/hook_manifest.yaml +77 -14
  168. package/src/scripts/hooks/block_no_verify.ts +61 -10
  169. package/src/scripts/hooks/block_unauthorized_git.ts +1 -1
  170. package/src/scripts/hooks/concern_registry.ts +4 -0
  171. package/src/scripts/hooks/rtk_wrap_hook.ts +8 -3
  172. package/src/scripts/hooks/run_continuation_hook.ts +887 -68
  173. package/src/scripts/hooks/ship_diff_volume_hook.ts +81 -15
  174. package/src/scripts/hooks/source_first_gate_hook.ts +464 -0
  175. package/src/scripts/hooks/state_io.ts +782 -20
  176. package/src/scripts/hooks/subagent_ledger_hook.ts +44 -15
  177. package/src/scripts/hooks/telemetry_flush_hook.ts +139 -0
  178. package/src/scripts/hooks/telemetry_self_repair.ts +128 -0
  179. package/src/scripts/hooks/telemetry_usage_hook.ts +22 -4
  180. package/src/scripts/hooks/turn_end_gate_hook.ts +81 -15
  181. package/src/scripts/install.sh +12 -2
  182. package/src/scripts/install.ts +16 -11
  183. package/src/scripts/language_mirror_hook.ts +561 -25
  184. package/src/scripts/lint_framework_leakage_allowlist.json +2 -2
  185. package/src/scripts/lint_plan_risk_register.ts +4 -3
  186. package/src/scripts/lint_skill_router_head.ts +4 -4
  187. package/src/scripts/mcp_server/tools.ts +2 -2
  188. package/src/scripts/memory_consolidation.ts +189 -0
  189. package/src/scripts/memory_eviction.ts +384 -0
  190. package/src/scripts/new_roadmap.ts +136 -0
  191. package/src/scripts/pack_mcp_content.ts +20 -5
  192. package/src/scripts/release.ts +37 -38
  193. package/src/scripts/report_layer_overlap.ts +78 -0
  194. package/src/scripts/report_memory_pointers.ts +622 -0
  195. package/src/scripts/self_repair_hook.ts +37 -5
  196. package/src/scripts/session_register_hook.ts +115 -15
  197. package/src/scripts/skill_usage_collect.ts +11 -1
  198. package/src/scripts/skill_usage_report.ts +278 -8
  199. package/src/scripts/sync_pr_branch.ts +7 -0
  200. package/src/server/schemas/settings.ts +8 -0
  201. package/dist/cli-delegate/chunk-EJITJBJX.js +0 -172
  202. package/dist/cli-delegate/chunk-PZWRPRWP.js +0 -38
  203. /package/dist/cli-delegate/{chunk-RYNADUFG.js → chunk-LWTYTX6F.js} +0 -0
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Deterministic content fingerprint of a host artefact layer.
3
+ *
4
+ * ## Why a fingerprint and not a version number
5
+ *
6
+ * The single-delivery partition is a *removal*: after it, `task generate-tools`
7
+ * writes 16 rules and zero skills into `<repo>/.claude/` and every other
8
+ * artefact is delivered ONLY by the host-global layer. That removes the build's
9
+ * own repair path — regeneration can no longer heal a stale global layer,
10
+ * because it stops writing the affected files at all.
11
+ *
12
+ * A package version proves *which installer claims to have written* the host
13
+ * directory. It does not prove that the directory holds the artefacts this
14
+ * checkout is about to omit. Measured on the maintainer machine 2026-08-20:
15
+ * `package.json` and the published release both read `14.6.0`, while **153
16
+ * skills existed only in the project layer** and 37 only in the global one. A
17
+ * version-equality predicate would have authorised the partition there and
18
+ * silently dropped 153 skills. So the predicate compares content.
19
+ *
20
+ * ## Cost — measured, and the earlier figure was wrong
21
+ *
22
+ * A prior revision of the blocker recorded 5.85 s per generation for a
23
+ * content comparison and used that number to argue the approach was too
24
+ * expensive. That figure was a **shell artefact**: it spawned one `cat` per
25
+ * file. Re-measured in Node on the same tree: **61 ms** over 664 source files,
26
+ * **103 ms** over 1019 host files. The comparison is cheap; the 5.85 s never
27
+ * described this mechanism.
28
+ *
29
+ * ## Contract
30
+ *
31
+ * Side-effect-free, no CLI entry, no `process.exit` — this module ships inside
32
+ * the consumer installer bundle, same constraint as `ruleInScope.ts`. Keep it
33
+ * that way.
34
+ */
35
+
36
+ import { createHash } from 'node:crypto';
37
+ import * as fs from 'node:fs';
38
+ import * as path from 'node:path';
39
+
40
+ /**
41
+ * Fingerprint wire version. Bump when the digest input changes shape, so an
42
+ * old lockfile compares unequal instead of comparing wrong.
43
+ */
44
+ export const FINGERPRINT_SCHEMA = 1;
45
+
46
+ /** One layer to fingerprint: a label (stable across machines) and its root. */
47
+ export interface LayerInput {
48
+ /** Stable label — `rules`, `skills`, … NOT an absolute path. */
49
+ readonly label: string;
50
+ /** Absolute directory. A missing directory contributes nothing. */
51
+ readonly root: string;
52
+ }
53
+
54
+ /** Files under `dir`, recursively, sorted by path — deterministic order. */
55
+ function collectFiles(dir: string): string[] {
56
+ const out: string[] = [];
57
+ const walk = (d: string): void => {
58
+ let entries: fs.Dirent[];
59
+ try {
60
+ entries = fs.readdirSync(d, { withFileTypes: true });
61
+ } catch {
62
+ return; // absent or unreadable → contributes nothing
63
+ }
64
+ const sorted = [...entries].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
65
+ for (const e of sorted) {
66
+ const p = path.join(d, e.name);
67
+ if (e.isDirectory()) {
68
+ walk(p);
69
+ } else if (e.isFile()) {
70
+ out.push(p);
71
+ }
72
+ // Symlinks are deliberately skipped: the project layer symlinks into
73
+ // `dist/`, the global layer holds real files, and following links
74
+ // would make two layers of identical CONTENT fingerprint differently
75
+ // depending on which side was read.
76
+ }
77
+ };
78
+ walk(dir);
79
+ return out.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
80
+ }
81
+
82
+ /**
83
+ * Content fingerprint over the given layers.
84
+ *
85
+ * The digest covers, per file, its **layer-relative** path and its bytes — so a
86
+ * rename, a deletion, an addition, and an edit each change it, while the
87
+ * absolute location of the layer does not. Layers are folded in the order
88
+ * given; pass them in a stable order.
89
+ *
90
+ * An absent layer root is not an error: it contributes zero files. That makes
91
+ * the fingerprint of "nothing installed" well-defined and unequal to any real
92
+ * layer, which is what the fail-safe branch of the partition predicate wants.
93
+ */
94
+ export function fingerprintLayers(layers: readonly LayerInput[]): string {
95
+ const h = createHash('sha256');
96
+ h.update(`v${FINGERPRINT_SCHEMA}\0`);
97
+ for (const layer of layers) {
98
+ h.update(`layer:${layer.label}\0`);
99
+ for (const file of collectFiles(layer.root)) {
100
+ h.update(path.relative(layer.root, file));
101
+ h.update('\0');
102
+ try {
103
+ h.update(fs.readFileSync(file));
104
+ } catch {
105
+ // A file that vanished between listing and reading is a partial
106
+ // install by definition. Fold a marker rather than throwing, so
107
+ // the digest differs from a complete layer instead of aborting
108
+ // the caller.
109
+ h.update('\0unreadable\0');
110
+ }
111
+ h.update('\0');
112
+ }
113
+ }
114
+ return h.digest('hex');
115
+ }
116
+
117
+ /**
118
+ * The host-global artefact layers the partition depends on, in a FIXED order.
119
+ *
120
+ * **This is the one definition, and it has to be.** The installer fingerprints
121
+ * what it wrote; the build fingerprints what it finds and compares. Two inline
122
+ * lists would drift — and a drift here does not fail loudly, it makes the
123
+ * partition permanently unreachable while every fail-safe branch reports a
124
+ * plausible reason. Import this; never re-list the layers.
125
+ *
126
+ * Only `claude-code` is covered, and the narrowness is the measurement rather
127
+ * than an omission: the 110-rule / 203,873-token duplication ADR-236 partitions
128
+ * was measured on `~/.claude/`. Widening this list widens what must be verified
129
+ * before a single artefact is withheld, so it is a deliberate addition.
130
+ *
131
+ * `commands` is in the list because the project layer writes skills AND commands
132
+ * into ONE directory (`generate_claude_commands` targets `CLAUDE_SKILLS_DIR`)
133
+ * while the host layer keeps them apart in `~/.claude/skills` and
134
+ * `~/.claude/commands`. Emptying the project directory withholds commands too,
135
+ * so commands must be part of what gets verified first. Measured 2026-08-20:
136
+ * 414 project entries against 298 host skills plus 93 host commands.
137
+ */
138
+ export function hostLayerInputs(userHome: string): LayerInput[] {
139
+ const claude = path.join(userHome, '.claude');
140
+ return [
141
+ { label: 'rules', root: path.join(claude, 'rules') },
142
+ { label: 'skills', root: path.join(claude, 'skills') },
143
+ { label: 'commands', root: path.join(claude, 'commands') },
144
+ ];
145
+ }
@@ -0,0 +1,325 @@
1
+ /**
2
+ * The single-delivery partition predicate — one artefact, one layer.
3
+ *
4
+ * ## What it decides
5
+ *
6
+ * Two layers deliver agent artefacts: a machine-local project layer at
7
+ * `<repo>/.claude/` (gitignored, 0 tracked files, rewritten by every
8
+ * `task generate-tools`) and a host-global layer at `~/.claude/` (written by
9
+ * `agent-config install`, never by the build). Measured 2026-08-19 on a freshly
10
+ * regenerated tree: **110 rules arrive twice**, and standing rule prose is
11
+ * 203,873 tokens against a 110,000 cap (185.3 %).
12
+ *
13
+ * ADR-236 partitions them: an artefact that exists ONLY for this package stays
14
+ * in the project layer; everything else is delivered only globally. That takes
15
+ * `<repo>/.claude/` from 111 rules and 338 skills to **16 rules and zero
16
+ * skills**.
17
+ *
18
+ * ## Why the predicate is fail-safe and never fails the build
19
+ *
20
+ * The partition is a removal, so the build loses its repair path: it can no
21
+ * longer heal a stale global layer by regenerating, because it stops writing
22
+ * those files. Every uncertainty therefore resolves to `standalone/full` — the
23
+ * pre-partition behaviour — and **never** to a refusal:
24
+ *
25
+ * `.github/workflows/consistency.yml:169` runs `task generate-tools` on a fresh
26
+ * checkout where, by that workflow's own comment at `:172-174`, the host rule
27
+ * trees are gitignored and absent. An option that made a missing global layer a
28
+ * hard failure was eliminated by that fact in the 2026-08-19 council round, not
29
+ * by preference. Under-governing a checkout is the one regression this estate
30
+ * exists to prevent; breaking the Consistency pipeline is the other. Full
31
+ * projection is the only branch that does neither.
32
+ *
33
+ * ## Why content and not a version number
34
+ *
35
+ * Rationale and the 153-skill measurement that decided it: see
36
+ * `hostLayerFingerprint.ts`. Version equality is checked too, but as a cheap
37
+ * pre-filter — it is necessary, never sufficient.
38
+ *
39
+ * ## Contract
40
+ *
41
+ * Side-effect-free, no I/O of its own (callers supply the facts), no CLI entry,
42
+ * no `process.exit`. Ships inside the consumer installer bundle, same
43
+ * constraint as `ruleInScope.ts`.
44
+ */
45
+
46
+ import * as fs from 'node:fs';
47
+ import * as os from 'node:os';
48
+
49
+ import { parseFrontmatter } from './ruleInScope.js';
50
+ import { fingerprintLayers, hostLayerInputs } from './hostLayerFingerprint.js';
51
+ import {
52
+ current_package_version,
53
+ read_lockfile,
54
+ write_lockfile,
55
+ } from '../scripts/_lib/installed_lock.js';
56
+
57
+ /** The workspace id that marks an artefact as existing only for this package. */
58
+ export const MAINTAINER_WORKSPACE = 'agent-config-maintainer';
59
+
60
+ /** Delivery mode the build selected, and prints. */
61
+ export type PartitionMode = 'standalone/full' | 'dual-layer/partitioned';
62
+
63
+ export interface PartitionVerdict {
64
+ readonly mode: PartitionMode;
65
+ /** One clause, printable, saying WHY this mode was selected. */
66
+ readonly reason: string;
67
+ }
68
+
69
+ /** The lockfile fields the predicate reads — a structural subset. */
70
+ export interface LockfileFacts {
71
+ readonly agent_config_version?: string | undefined;
72
+ readonly host_layer_fingerprint?: string | undefined;
73
+ }
74
+
75
+ export interface PartitionInputs {
76
+ /** `package.json` version of the checkout being built. */
77
+ readonly projectVersion: string;
78
+ /** Parsed `installed.lock`, or `null` when no global install is recorded. */
79
+ readonly lockfile: LockfileFacts | null;
80
+ /** Does the host layer exist on disk at all? */
81
+ readonly hostLayerPresent: boolean;
82
+ /**
83
+ * Fingerprint this checkout expects the host layer to carry. Lazy on
84
+ * purpose: every disqualifying branch above it returns without paying the
85
+ * ~100 ms hash.
86
+ */
87
+ readonly expectedFingerprint: () => string;
88
+ }
89
+
90
+ /**
91
+ * Select the delivery mode. Total function: always returns a verdict, never
92
+ * throws, never refuses.
93
+ *
94
+ * The order of the guards is the fail-safe order — cheapest and most decisive
95
+ * first, so the fingerprint is computed only when everything else already
96
+ * agrees.
97
+ */
98
+ export function partitionVerdict(inputs: PartitionInputs): PartitionVerdict {
99
+ if (!inputs.hostLayerPresent) {
100
+ return {
101
+ mode: 'standalone/full',
102
+ reason: 'no host-global layer on this machine',
103
+ };
104
+ }
105
+ const lock = inputs.lockfile;
106
+ if (lock === null) {
107
+ return {
108
+ mode: 'standalone/full',
109
+ reason: 'host layer present but no install record (installed.lock absent)',
110
+ };
111
+ }
112
+ const recorded = lock.agent_config_version;
113
+ if (!recorded) {
114
+ return {
115
+ mode: 'standalone/full',
116
+ reason: 'install record carries no version',
117
+ };
118
+ }
119
+ // Exact equality, deliberately not `>=`. A NEWER global layer is not a
120
+ // superset: a later release may have renamed or removed an artefact this
121
+ // checkout still expects, and ordering does not establish substitutability.
122
+ if (recorded !== inputs.projectVersion) {
123
+ return {
124
+ mode: 'standalone/full',
125
+ reason: `version mismatch (installed ${recorded}, building ${inputs.projectVersion})`,
126
+ };
127
+ }
128
+ const installedFp = lock.host_layer_fingerprint;
129
+ if (!installedFp) {
130
+ return {
131
+ mode: 'standalone/full',
132
+ reason: 'install predates host-layer fingerprinting — re-run `agent-config install` to enable the partition',
133
+ };
134
+ }
135
+ let expected: string;
136
+ try {
137
+ expected = inputs.expectedFingerprint();
138
+ } catch {
139
+ return {
140
+ mode: 'standalone/full',
141
+ reason: 'could not compute the expected host-layer fingerprint',
142
+ };
143
+ }
144
+ if (installedFp !== expected) {
145
+ return {
146
+ mode: 'standalone/full',
147
+ reason: 'host-layer content differs from this checkout — re-run `agent-config install`',
148
+ };
149
+ }
150
+ return {
151
+ mode: 'dual-layer/partitioned',
152
+ reason: `host layer verified at ${recorded} (fingerprint ${installedFp.slice(0, 12)})`,
153
+ };
154
+ }
155
+
156
+ /**
157
+ * Does this artefact exist ONLY for this package?
158
+ *
159
+ * True iff `workspaces:` is present, non-empty, and its every entry is
160
+ * {@link MAINTAINER_WORKSPACE}. Measured 2026-08-20: exactly **16** rules in
161
+ * `src/rules/` satisfy this — the figure ADR-236 partitions on.
162
+ *
163
+ * The direction of the default is the opposite of `rule_in_scope`'s, and that
164
+ * is deliberate rather than an inconsistency. There, an untagged artefact ships
165
+ * — over-shipping is the safe error for a scope filter. Here, an untagged
166
+ * artefact is NOT package-only, so it is delivered globally and withheld from
167
+ * the project layer. Both defaults resolve toward "the artefact is generally
168
+ * useful"; only one of them is about withholding.
169
+ */
170
+ export function isExclusivelyPackageOnly(source_path: string): boolean {
171
+ let meta: Record<string, unknown>;
172
+ try {
173
+ [meta] = parseFrontmatter(fs.readFileSync(source_path, 'utf-8'));
174
+ } catch {
175
+ return false; // unreadable → not package-only → delivered globally
176
+ }
177
+ const raw = meta['workspaces'];
178
+ if (!Array.isArray(raw) || raw.length === 0) {
179
+ return false;
180
+ }
181
+ return raw.every((w) => String(w) === MAINTAINER_WORKSPACE);
182
+ }
183
+
184
+ /** Emitter for the ONE mode line; see the visibility note on the resolver. */
185
+ export type Announce = (message: string) => void;
186
+
187
+ let _memo: PartitionVerdict | null = null;
188
+ let _announce: Announce = (m) => process.stdout.write(`${m}\n`);
189
+
190
+ /**
191
+ * Install the emitter used for the mode line. Callers pass a function visible at
192
+ * their DEFAULT output level — the first implementation used one that prints only
193
+ * at `verbose`, which withheld ~100 rules while saying nothing in a normal run.
194
+ */
195
+ export function setPartitionAnnounce(fn: Announce): void {
196
+ _announce = fn;
197
+ }
198
+
199
+ /** Test seam — drop the memo so one process can exercise both delivery modes. */
200
+ export function _resetPartitionVerdictForTest(): void {
201
+ _memo = null;
202
+ }
203
+
204
+ /**
205
+ * Is the partition active for this generation? Memoised per process, because the
206
+ * fingerprint costs ~100 ms and the answer cannot change mid-run.
207
+ *
208
+ * This is the single entry point the generators call, deliberately: wiring a
209
+ * feature into two files that are already past the 1,500-line source ceiling
210
+ * costs a ratchet violation per line, so the decision, its fail-safe ordering,
211
+ * its printed reason and its memo all live here and the call sites are one line
212
+ * each.
213
+ */
214
+ export function partitionActive(projectRoot: string): boolean {
215
+ return resolvePartitionVerdict(projectRoot).mode === 'dual-layer/partitioned';
216
+ }
217
+
218
+ /**
219
+ * Resolve the delivery mode for one generation, and say so once.
220
+ *
221
+ * Lives here rather than in `condense.ts` for two reasons. The decision belongs
222
+ * beside {@link partitionVerdict}, whose fail-safe ordering it depends on; and
223
+ * `condense.ts` is 1,500+ lines, where the source-size ratchet counts every
224
+ * added line as a violation — so a block that has no reason to live there is a
225
+ * cost as well as a misplacement.
226
+ *
227
+ * **Both council seats (2026-08-20, 2/2) required that generation PRINT the mode
228
+ * it selected** rather than partition silently: a withheld artefact nobody
229
+ * announced is exactly the under-governance the partition exists to remove. The
230
+ * caller supplies `announce`, and the level matters — the first implementation
231
+ * used an `info()` that prints only at `verbose`, so it withheld ~100 rules while
232
+ * saying nothing in a default run. Pass a function that is visible at the default
233
+ * level. Residual, stated: at an explicitly silent output level the line is
234
+ * dropped; that is an operator choice and the partition stays fail-safe anyway.
235
+ *
236
+ * The fingerprint compares the host layer against **what the installer recorded
237
+ * when it wrote that layer**, not against a re-derivation from source. That
238
+ * avoids guessing the installer's byte representation — a mismatch there would
239
+ * make the partition permanently unreachable rather than merely inactive — and it
240
+ * is the property the partition needs: the omitted artefacts are still present,
241
+ * in the form this version's installer left them.
242
+ *
243
+ * **Known residual:** an installer that crashes mid-write and still reaches the
244
+ * lockfile would fingerprint its own partial layer, and that fingerprint then
245
+ * verifies. Ordering narrows the window (the lockfile is written last) without
246
+ * closing it; a per-artefact manifest would close it and is not built here.
247
+ */
248
+ export function resolvePartitionVerdict(projectRoot: string): PartitionVerdict {
249
+ if (_memo !== null) {
250
+ return _memo;
251
+ }
252
+ const layers = hostLayerInputs(os.homedir());
253
+ const present = layers.some((l) => {
254
+ try {
255
+ return fs.statSync(l.root).isDirectory();
256
+ } catch {
257
+ return false;
258
+ }
259
+ });
260
+ let lock: LockfileFacts | null = null;
261
+ try {
262
+ lock = read_lockfile();
263
+ } catch {
264
+ lock = null; // unreadable record → fail safe
265
+ }
266
+ _memo = partitionVerdict({
267
+ projectVersion: current_package_version(projectRoot),
268
+ lockfile: lock,
269
+ hostLayerPresent: present,
270
+ expectedFingerprint: () => fingerprintLayers(layers),
271
+ });
272
+ _announce(`projection mode: ${_memo.mode} — ${_memo.reason}`);
273
+ return _memo;
274
+ }
275
+
276
+ /**
277
+ * Stamp the host-layer content fingerprint into the install record.
278
+ *
279
+ * The write side of the partition, and what makes it reachable at all: the build
280
+ * withholds artefacts only when this fingerprint matches what it finds on disk.
281
+ * No fingerprint recorded → full projection, which is the safe direction.
282
+ *
283
+ * **Call it AFTER the deploy and after the failed-tool postcheck.** The install
284
+ * writes its lockfile once before the redeploy; a fingerprint taken there would
285
+ * describe the PREVIOUS install and then verify against a layer this run
286
+ * replaced. Both council seats required the record be written last, and this is
287
+ * the function that respects it.
288
+ *
289
+ * On failure it reports and writes NOTHING. A missing fingerprint is fail-safe; a
290
+ * wrong one would authorise a partition against an unverified layer, so the two
291
+ * errors are not symmetric and this must never prefer the second.
292
+ *
293
+ * Lives here rather than inline in `install.ts` for the same reason
294
+ * {@link resolvePartitionVerdict} does: it belongs beside the predicate it
295
+ * serves, and `install.ts` is 5,000+ lines where the source-size ratchet counts
296
+ * every added line.
297
+ *
298
+ * @returns the fingerprint written, or `null` when nothing was recorded.
299
+ */
300
+ export function stampHostLayerFingerprint(
301
+ installedVersion: string,
302
+ tools: readonly string[],
303
+ lockfilePath: string,
304
+ /** Skip entirely — e.g. when the claude-code deploy itself failed. */
305
+ skip: boolean,
306
+ report: Announce,
307
+ ): string | null {
308
+ if (skip) {
309
+ return null;
310
+ }
311
+ try {
312
+ const fingerprint = fingerprintLayers(hostLayerInputs(os.homedir()));
313
+ write_lockfile(installedVersion, [...tools], {
314
+ path: lockfilePath,
315
+ host_layer_fingerprint: fingerprint,
316
+ });
317
+ report(`Host-layer fingerprint recorded: ${fingerprint.slice(0, 12)} (enables single delivery)`);
318
+ return fingerprint;
319
+ } catch (e) {
320
+ report(
321
+ `Host-layer fingerprint NOT recorded (${String(e)}) — the project layer keeps the full projection.`,
322
+ );
323
+ return null;
324
+ }
325
+ }
@@ -41,6 +41,7 @@ import {
41
41
  } from '../_lib/subagent_capsule.js';
42
42
  import { collectGrounding } from '../_lib/envelope_grounding.js';
43
43
  import { atomic_write_json } from '../hooks/state_io.js';
44
+ import { env_session_id } from '../sessions_cli.js';
44
45
 
45
46
  export interface RecycleResult {
46
47
  code: 0 | 1 | 2;
@@ -185,7 +186,14 @@ export function runSessionRecycle(
185
186
  // composed. A model-written branch is a claim; this is a reading. The
186
187
  // composer cannot override them — a "next_task" it got wrong is a
187
188
  // proposal, a "head" it got wrong is a silent stale resume.
188
- const grounding = collectGrounding(projectRoot);
189
+ // The session id is the ONLY channel to this session's verify state, which
190
+ // the producer keys per session. `env_session_id` is the canonical resolver
191
+ // (package variable first, then the host's `CLAUDE_CODE_SESSION_ID`) and is
192
+ // imported rather than re-implemented — a second copy of that precedence
193
+ // order would drift from the one a test covers. A host that exports neither
194
+ // yields `null`, and `last_verify` is then simply absent from the envelope
195
+ // (the delete-then-set below already treats an unreadable fact as absent).
196
+ const grounding = collectGrounding(projectRoot, env_session_id());
189
197
  // Drop the composer's factual keys UNCONDITIONALLY first. Guarding each
190
198
  // assignment on `!== null` would leave a model-composed branch or head
191
199
  // standing whenever the git read fails — the consumer would then compare
@@ -220,6 +220,7 @@ Tier 2 — maintenance / internal (hooks, MCP, memory, telemetry):
220
220
  (deliberate recycle instead of auto-compact). Flags:
221
221
  --file <json> | --template; default reads stdin
222
222
  roadmap:progress Regenerate agents/roadmaps-progress.md from open roadmaps
223
+ (archives completed roadmaps; --no-archive to skip)
223
224
  roadmap:progress-check Fail if agents/roadmaps-progress.md is stale (for CI)
224
225
  roadmap:archive Archive completed roadmaps (branch-touched by default;
225
226
  --all for every complete one; --dry-run to preview)
@@ -717,10 +718,14 @@ cmd_brand_status() {
717
718
  exec_ts "$PACKAGE_ROOT/src/scripts/_cli/cmd_brand_status.ts" "$@"
718
719
  }
719
720
 
721
+ # `--archive`: a completed roadmap is archived here rather than reported as a
722
+ # warning the caller has to act on. `roadmap:progress-check` never passes it —
723
+ # a CI gate must not mutate the tree it is checking. Pass `--no-archive` to get
724
+ # the dashboard-only behaviour back.
720
725
  cmd_roadmap_progress() {
721
726
  local script
722
727
  script="$(resolve_script "dist/agent-src/scripts/update_roadmap_progress.ts" ".augment/scripts/update_roadmap_progress.ts")"
723
- exec_ts "$script" "$@"
728
+ exec_ts "$script" --archive "$@"
724
729
  }
725
730
 
726
731
  cmd_roadmap_progress_check() {
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The one matcher for a roadmap's `## Acceptance criteria` heading.
3
+ *
4
+ * It lives here because the same defect has now been found three times, in
5
+ * three separately-authored copies of this regex:
6
+ *
7
+ * 1. `dispatch_r2_reviewer.extractAcceptanceCriteria` was case-sensitive, so
8
+ * `## Acceptance criteria` extracted nothing (zcs-close R2 review, 2026-08-09).
9
+ * 2. The same function was end-anchored, so a heading carrying a qualifier —
10
+ * `## Acceptance criteria (per phase, on promotion to ready)` — extracted
11
+ * nothing (measured 2026-08-18).
12
+ * 3. `lint_plan_risk_register.extractFeatures` carried BOTH faults after the
13
+ * other two were fixed, so the substantial-change heuristic hashed the empty
14
+ * string for eight of the thirty-two ready roadmaps — including the roadmap
15
+ * that repaired it. Any edit to those roadmaps' criteria was invisible to the
16
+ * gate, which is the silent half of the failure: the gate still exited 0.
17
+ *
18
+ * Two copies drift. Three copies drift twice. The predicate is a shared constant
19
+ * so the next author cannot fix one call site and leave another blind.
20
+ *
21
+ * `\b` after `criteria` is the deliberate near-miss guard: `## Acceptance
22
+ * criteriaXYZ` is a different heading and must not match. It is asserted from
23
+ * both directions in `tests/scripts/lint_plan_risk_register.test.ts`.
24
+ */
25
+ export const AC_HEADING_RE = /^##\s+acceptance criteria\b/i;
26
+
27
+ /** True when `line` is a roadmap's acceptance-criteria section heading. */
28
+ export function isAcceptanceCriteriaHeading(line: string): boolean {
29
+ return AC_HEADING_RE.test(line);
30
+ }
@@ -277,10 +277,49 @@ export function _domains_command_logical(p: string): string | null {
277
277
  return `commands/${subpath}.md`;
278
278
  }
279
279
 
280
+ /**
281
+ * Is this domains path inside a `__`-prefixed SCRATCH pack?
282
+ *
283
+ * ## Why this exists — a measured cross-test contamination
284
+ *
285
+ * A pack id is a lowercase slug (`slug_prefix` is validated against
286
+ * `^[a-z][a-z0-9-]*$`), so a directory whose name starts with `__` is not a
287
+ * pack that can ship. It is a test scratch marker, and the walkers must not
288
+ * count it.
289
+ *
290
+ * The defect this closes, measured 2026-08-20 on a full local suite run:
291
+ * `check_artefact_count_messaging`'s "live gate — real repo surfaces" test
292
+ * PASSED in isolation and FAILED in the suite, reporting
293
+ * `commands says 200, expected 207`. The +7 is exact and traceable:
294
+ * `lint_originality.test.ts` writes seven `command.md` files into the REAL tree
295
+ * at `src/domains/__origtest_batch/c1..c7/` (it must — that gate classifies an
296
+ * artefact by its PATH, so a tmpdir would not be a "command" at all), and
297
+ * removes them in `afterEach`. Vitest runs test files in parallel workers, so
298
+ * while those seven exist a concurrent worker counting the live tree sees 207.
299
+ *
300
+ * This is a SCOPE defect, not a flaky test: a gate asserting over the developer's
301
+ * live tree is not hermetic, and the remedy is a fixture boundary rather than a
302
+ * relaxed expectation. The boundary is placed here, at the point where a path
303
+ * becomes a counted artefact, because that is the one place that fixes it for
304
+ * every counter at once — and it weakens nothing: the count of real artefacts is
305
+ * still asserted exactly.
306
+ *
307
+ * Checked before shipping: no pack under `src/domains/` uses a `__` prefix, and
308
+ * `lint_originality` does not consume these walkers (it reads its corpus from
309
+ * the `--changed` paths it is handed), so its own batch test is unaffected.
310
+ */
311
+ function _isScratchPack(domains_root: string, full: string): boolean {
312
+ const rel = path.relative(domains_root, full);
313
+ const first = rel.split(path.sep)[0];
314
+ return first !== undefined && first.startsWith('__');
315
+ }
316
+
280
317
  /**
281
318
  * Yield `[physical_path, logical_relpath]` for every domains command, in
282
319
  * deterministic order. Naturally inert until `src/domains/*\/**\/command.md`
283
320
  * files exist. Mirrors `_iter_domains_commands`.
321
+ *
322
+ * Skips `__`-prefixed scratch packs — see `_isScratchPack`.
284
323
  */
285
324
  export function* _iter_domains_commands(): Generator<[string, string]> {
286
325
  const dom = _roots.SRC_DOMAINS;
@@ -291,6 +330,9 @@ export function* _iter_domains_commands(): Generator<[string, string]> {
291
330
  if (!_isFile(p)) {
292
331
  continue;
293
332
  }
333
+ if (_isScratchPack(dom, p)) {
334
+ continue;
335
+ }
294
336
  const logical = _domains_command_logical(p);
295
337
  if (logical !== null) {
296
338
  yield [p, logical];