@event4u/agent-config 10.1.0 → 10.3.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 (158) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +110 -167
  3. package/README.md +1 -1
  4. package/dist/agent-src/commands/analyze/reference-repo.md +62 -0
  5. package/dist/agent-src/commands/roadmap/create.md +4 -1
  6. package/dist/agent-src/commands/roadmap/next.md +38 -0
  7. package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +22 -1
  8. package/dist/agent-src/contexts/contracts/frugality-charter.md +1 -1
  9. package/dist/agent-src/personas/advisors/first-principles.md +6 -0
  10. package/dist/agent-src/rules/active-remediation.md +53 -2
  11. package/dist/agent-src/rules/code-provenance.md +66 -4
  12. package/dist/agent-src/rules/content-quoting-floor.md +6 -0
  13. package/dist/agent-src/rules/context-hygiene.md +39 -0
  14. package/dist/agent-src/rules/design-fidelity.md +49 -3
  15. package/dist/agent-src/rules/token-budget-discipline.md +3 -2
  16. package/dist/agent-src/rules/token-efficiency.md +22 -0
  17. package/dist/agent-src/skills/ai-council/SKILL.md +28 -6
  18. package/dist/agent-src/skills/condense-memory/SKILL.md +1 -1
  19. package/dist/agent-src/skills/design-review/references/verification-automation.md +23 -1
  20. package/dist/agent-src/skills/fe-design/SKILL.md +27 -3
  21. package/dist/agent-src/skills/judge-synthesis/SKILL.md +30 -0
  22. package/dist/agent-src/skills/learning-to-rule-or-skill/SKILL.md +11 -0
  23. package/dist/agent-src/skills/roadmap-writing/SKILL.md +9 -9
  24. package/dist/agent-src/skills/skill-writing/SKILL.md +4 -0
  25. package/dist/agent-src/skills/token-optimizer/SKILL.md +5 -1
  26. package/dist/agent-src/templates/agent-settings.md +12 -28
  27. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +2 -2
  28. package/dist/agent-src/templates/persona.md +26 -0
  29. package/dist/agent-src/templates/roadmaps.md +27 -7
  30. package/dist/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +0 -1
  31. package/dist/agent-src/templates/skill.md +35 -1
  32. package/dist/cli/commands/commands.candidates.test.js +257 -0
  33. package/dist/cli/commands/commands.candidates.test.js.map +1 -0
  34. package/dist/cli/commands/commands.js +199 -0
  35. package/dist/cli/commands/commands.js.map +1 -1
  36. package/dist/cli/main.js +1 -0
  37. package/dist/cli/main.js.map +1 -1
  38. package/dist/cli/registry.js +2 -2
  39. package/dist/cli/registry.js.map +1 -1
  40. package/dist/cli-delegate/{chunk-3JUTT26I.js → chunk-6ENSXSBW.js} +1 -1
  41. package/dist/cli-delegate/{chunk-TVGXH7VA.js → chunk-6LOPHJLD.js} +1 -1
  42. package/dist/cli-delegate/{chunk-XT2YTJCZ.js → chunk-AYKQGIBG.js} +4 -0
  43. package/dist/cli-delegate/{chunk-HJSKTMIF.js → chunk-BV3WR67D.js} +3 -19
  44. package/dist/cli-delegate/{chunk-HTIU3W56.js → chunk-BWMXLD4B.js} +6 -2
  45. package/dist/cli-delegate/{chunk-2H5SE7DU.js → chunk-FV34EGEX.js} +166 -39
  46. package/dist/cli-delegate/{chunk-XTC5ARB6.js → chunk-IR3IVRVC.js} +7 -2
  47. package/dist/cli-delegate/{chunk-LRXJK6GN.js → chunk-JMQZ7MM5.js} +1 -1
  48. package/dist/cli-delegate/{chunk-NX5AXSLD.js → chunk-X3VOARZD.js} +3 -3
  49. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  50. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  51. package/dist/cli-delegate/cmd_conformance.js +881 -204
  52. package/dist/cli-delegate/cmd_doctor.js +3 -3
  53. package/dist/cli-delegate/cmd_explain.js +2 -2
  54. package/dist/cli-delegate/cmd_export.js +4 -4
  55. package/dist/cli-delegate/cmd_handoff.js +6 -5
  56. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  57. package/dist/cli-delegate/cmd_migrate.js +2 -2
  58. package/dist/cli-delegate/cmd_packs_active.js +2 -2
  59. package/dist/cli-delegate/cmd_prune.js +4 -4
  60. package/dist/cli-delegate/cmd_route_audit.js +2 -2
  61. package/dist/cli-delegate/cmd_route_explain.js +1 -1
  62. package/dist/cli-delegate/cmd_session_recycle.js +44 -9
  63. package/dist/cli-delegate/cmd_settings_get.js +2 -2
  64. package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
  65. package/dist/cli-delegate/cmd_settings_set.js +2 -2
  66. package/dist/cli-delegate/cmd_sync.js +4 -4
  67. package/dist/cli-delegate/cmd_uninstall.js +4 -4
  68. package/dist/cli-delegate/cmd_update.js +1 -1
  69. package/dist/cli-delegate/cmd_upgrade.js +1 -1
  70. package/dist/cli-delegate/cmd_validate.js +4 -4
  71. package/dist/cli-delegate/cmd_versions.js +1 -1
  72. package/dist/discovery/deprecation-report.md +1 -1
  73. package/dist/discovery/discovery-manifest.json +21 -21
  74. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  75. package/dist/discovery/discovery-manifest.summary.md +1 -1
  76. package/dist/discovery/dormancy-report.md +1 -1
  77. package/dist/discovery/orphan-report.md +1 -1
  78. package/dist/discovery/packs.json +2 -2
  79. package/dist/discovery/trust-report.md +1 -1
  80. package/dist/discovery/workspaces.json +2 -2
  81. package/dist/hooks/dispatch.js +204 -44
  82. package/dist/install/install.mjs +9 -21
  83. package/dist/mcp/registry-manifest.json +2 -2
  84. package/dist/mcp/server.json +2 -2
  85. package/dist/mcp/server.mjs +1337 -490
  86. package/dist/router.json +1 -1
  87. package/dist/scripts/_lib/agent_settings.js +25 -2
  88. package/dist/scripts/_lib/agent_settings.js.map +1 -1
  89. package/dist/scripts/_lib/compile_time_toggles.js +4 -3
  90. package/dist/scripts/_lib/compile_time_toggles.js.map +1 -1
  91. package/dist/server/io/yamlIO.js +0 -2
  92. package/dist/server/io/yamlIO.js.map +1 -1
  93. package/dist/server/schemas/settings.js +3 -9
  94. package/dist/server/schemas/settings.js.map +1 -1
  95. package/dist/shared/settingsClasses.js +64 -2
  96. package/dist/shared/settingsClasses.js.map +1 -1
  97. package/dist/ui/assets/{index-B5qGYOJo.js → index-5fXOHoVh.js} +2 -2
  98. package/dist/ui/assets/index-5fXOHoVh.js.map +1 -0
  99. package/dist/ui/index.html +1 -1
  100. package/docs/contracts/settings-classes.md +307 -145
  101. package/docs/guidelines/agent-infra/active-remediation-mechanics.md +54 -0
  102. package/docs/guidelines/agent-infra/layered-settings.md +1 -2
  103. package/docs/guidelines/agent-infra/symptom-driven-harvest-loop.md +104 -0
  104. package/docs/guidelines/design-antipatterns.md +79 -10
  105. package/docs/guidelines/design-fidelity-mechanics.md +120 -2
  106. package/package.json +1 -1
  107. package/src/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +0 -1
  108. package/src/config/agent-settings.template.yml +12 -30
  109. package/src/config/gate-coverage.yml +42 -8
  110. package/src/config/gate-violation-baselines.json +15 -3
  111. package/src/config/profiles/balanced.ini +0 -2
  112. package/src/config/profiles/full.ini +1 -3
  113. package/src/config/profiles/minimal.ini +0 -2
  114. package/src/config/review-prompt-binding-baseline.json +19 -0
  115. package/src/config/rule-enforcement-baseline.json +0 -1
  116. package/src/scripts/_cli/cmd_session_recycle.ts +66 -9
  117. package/src/scripts/_cli/handoff_sessions.ts +3 -2
  118. package/src/scripts/_dispatch.bash +1 -0
  119. package/src/scripts/_lib/agent_settings.ts +25 -2
  120. package/src/scripts/_lib/bench_telegraph.ts +1 -1
  121. package/src/scripts/_lib/cc_transcript.ts +25 -0
  122. package/src/scripts/_lib/compile_time_toggles.ts +4 -3
  123. package/src/scripts/ai_council/clients.ts +243 -34
  124. package/src/scripts/cache_realization_report.ts +3 -2
  125. package/src/scripts/check_ci_local_parity.ts +60 -6
  126. package/src/scripts/check_review_prompt_binding.ts +553 -0
  127. package/src/scripts/check_rule_projection_integrity.ts +3 -1
  128. package/src/scripts/check_suppression_hygiene.ts +19 -3
  129. package/src/scripts/conformance_scan.ts +282 -9
  130. package/src/scripts/cost/track.mjs +10 -4
  131. package/src/scripts/council_cli.ts +33 -3
  132. package/src/scripts/design_slop_rules.test.ts +37 -6
  133. package/src/scripts/design_slop_rules.ts +150 -22
  134. package/src/scripts/dispatch_r2_reviewer.ts +15 -1
  135. package/src/scripts/external_sources_denylist.json +3 -1
  136. package/src/scripts/hook_manifest.yaml +17 -6
  137. package/src/scripts/hooks/block_unauthorized_git.ts +54 -8
  138. package/src/scripts/hooks/delegation_nudge_hook.ts +33 -8
  139. package/src/scripts/hooks/evidence_independence.ts +61 -9
  140. package/src/scripts/hooks/session_eol_hook.ts +123 -3
  141. package/src/scripts/hooks/turn_end_gate_hook.ts +31 -0
  142. package/src/scripts/hooks/ui_route_nudge_hook.ts +55 -2
  143. package/src/scripts/lint_design_antipattern_parity.ts +201 -0
  144. package/src/scripts/lint_harvest_provenance.ts +483 -0
  145. package/src/scripts/lint_settings_classes.ts +273 -11
  146. package/src/scripts/lint_skill_router_head.ts +254 -0
  147. package/src/scripts/lint_symptom_intake.ts +241 -0
  148. package/src/scripts/report_consultation_rate.ts +128 -2
  149. package/src/scripts/report_skill_activation.ts +6 -4
  150. package/src/scripts/report_skill_obligation_violations.ts +2 -1
  151. package/src/scripts/run_skill_evals.ts +10 -5
  152. package/src/scripts/schemas/persona.schema.json +8 -0
  153. package/src/scripts/schemas/provenance-harvest.schema.json +39 -0
  154. package/src/scripts/sweep_dead_scan_roots.ts +51 -0
  155. package/src/server/io/yamlIO.ts +0 -2
  156. package/src/server/schemas/settings.ts +3 -19
  157. package/src/shared/settingsClasses.ts +93 -2
  158. package/dist/ui/assets/index-B5qGYOJo.js.map +0 -1
@@ -1,6 +1,12 @@
1
1
  {
2
2
  "$comment": "Violation ratchet for gates whose dead scan root was repaired. A repair REVEALS pre-existing debt; it does not create it. Each entry records the count revealed at repair time — the gate fails only when its count RISES. Lowering a count is a normal commit; raising one is a defect. Entries expire: a baseline that has not dropped within 56 days of `landed` (or of `reaffirmed.date`) fails its gate, because a number that never moves is suppression with extra steps. Mechanism + rationale: src/scripts/_lib/gate_baseline.ts. Decision: AI council 2026-08-02, roadmap road-to-gates-that-can-fail blocker `dead-gate-finding-triage`.",
3
3
  "gates": {
4
+ "ci-parity:local-only": {
5
+ "count": 167,
6
+ "landed": "2026-08-13",
7
+ "note": "Gates that `task ci` runs and no workflow does — the direction this manifest's own header calls the one that let real defects merge (16 stale index rows reached the trunk behind a local-only check-index). Revealed, not created, by repairing the checker's extraction: check_ci_local_parity built its CI-side set by regexing RAW workflow text for `task <name>` and expanding each closure, comments included. Several workflow comments contain the literal string `task ci` while stating that no workflow invokes it, so `ci` was read as an invocation and its full 247-gate closure entered the 'runs in CI' set. The count was therefore 0 BY CONSTRUCTION, and the prose documenting the gap was what suppressed it. Measured at the repair: CI-side 273 → 106, undeclared_local_only 0 → 167 of 247 local gates (68%). Baselined rather than hard-failed because 167 findings nobody can clear in the change that reveals them is the gate-that-lands-as-N-instant-blockers failure this repository has refused before; shrink-only keeps the property that matters, namely that a NEW gate registered in `task ci` with no workflow reds immediately. Lowering happens by wiring gates into workflows or declaring them under `local_only:` with a reason. Note the punchline, because it bounds the risk of this entry: check_ci_local_parity ITSELF runs in no workflow, so it is its own finding and this ratchet has no remote reach either — clearing the backlog is what gives it teeth.",
8
+ "falsifier": "./scripts-run src/scripts/check_ci_local_parity"
9
+ },
4
10
  "check_no_new_legacy_path:hardcoded-scan-roots": {
5
11
  "count": 48,
6
12
  "landed": "2026-08-09",
@@ -22,9 +28,9 @@
22
28
  "note": "Markdown links between source artefacts that cross a pack boundary the source pack's `requires` graph does not reach — revealed when the scan root moved off the deleted packages/ tree to the live corpus (0 -> 753 artefacts scanned). 82 distinct pack pairs across 131 source files; largest single class is engineering-base -> meta (73), skills in a consumer-installable pack citing this package's own maintenance skills. Each is a link that dangles for a consumer who installs the source pack without the target's. Judged real drift rather than an over-strict gate because it is long-tailed — top bucket 22%, no single systematic cause — unlike the lint_artefact_frontmatter case, where one required shape had 0-of-288 adoption. Fix per finding: add the edge to packs.yml requires, retarget the link, or move the artefact."
23
29
  },
24
30
  "check_gate_completeness": {
25
- "count": 217,
26
- "landed": "2026-08-05",
27
- "note": "Ledger adoption ratchet. 217 of 221 CI-registered gates do not yet carry per-target completeness accounting (`_lib/gate_ledger.ts`) or a `// ledger-exempt:` marker. This is deliberately NOT a sweep: road-to-skill-ecosystem-gate-integrity Phase 1 adopts exactly three gates (lint_framework_leakage, check_references, lint_roadmap_complexity) plus the checker itself to prove the interface, and the remainder migrates as gates are touched. Shrink-only from here — the number may not grow, so a NEW gate must adopt or exempt.",
31
+ "count": 216,
32
+ "landed": "2026-08-12",
33
+ "note": "Ledger adoption ratchet. 216 of 244 registered gates do not yet carry per-target completeness accounting (`_lib/gate_ledger.ts`) or a `// ledger-exempt:` marker. This is deliberately NOT a sweep: road-to-skill-ecosystem-gate-integrity Phase 1 adopts exactly three gates (lint_framework_leakage, check_references, lint_roadmap_complexity) plus the checker itself to prove the interface, and the remainder migrates as gates are touched. Shrink-only from here — the number may not grow, so a NEW gate must adopt or exempt.",
28
34
  "falsifier": "./scripts-run src/scripts/check_gate_completeness"
29
35
  },
30
36
  "check_depth_budget": {
@@ -37,6 +43,12 @@
37
43
  "landed": "2026-08-06",
38
44
  "note": "Self-test adoption ratchet, expressed as a shrink-only NON-adopter count. 24 of the 32 gates registered enforced in gate-coverage.yml carry neither a `--self-test` (`_lib/gate_self_test.ts`) nor a `// self-test-exempt: <reason>` marker, so nothing proves they DISCRIMINATE — an enforced `scanned:` floor proves a gate read something, only a self-test proves the reading changes the verdict. Deliberately NOT an adoption column ratcheted upward: check_gate_coverage.ts's own report_hardening_ratchet rejects that shape in this same file (\"grades the solution instead of the problem\"), and the sibling adoption ratchet (check_gate_completeness) is already a non-adopter count. Population is the manifest, not every script under src/scripts: padding the manifest to move this number would mean adding a floor per row. Shrink-only from here — a NEW registered gate must adopt or exempt. Migration mechanism, same as the ledger ratchet above: gates adopt as they are touched, not in a sweep — the eight current adopters were each added alongside a real change to that gate. The 56-day non-stagnation clause applies unchanged, so a count parked here fails on its own.",
39
45
  "falsifier": "./scripts-run src/scripts/check_gate_coverage"
46
+ },
47
+ "lint_settings_classes:derivable-surface": {
48
+ "count": 83,
49
+ "landed": "2026-08-12",
50
+ "note": "Anti-regrowth ratchet for the settings disposition axis. 83 of 134 template leaves are classified `derivable` — the mechanism can decide from the situation, so the key should not exist — and each names the replacement that would decide instead (`lint_settings_classes` rejects the bare label, which is what keeps the class from becoming a synonym for \"inconvenient to keep\"). This is a DELETION QUEUE, not 83 deletions: a row stays until the mechanism it names actually exists, and deleting a key ahead of its mechanism is a silently-changed default rather than a simplification. Shrink-only from here, which is the anti-regrowth half — a NEW key classified `derivable` raises the number and reds, so a key whose answer the situation already carries cannot be added at all. The 56-day non-stagnation clause is the other half and is wanted here rather than tolerated: a queue that never drains is a stated direction nobody is walking. The queue opened at 88 of 140 on 2026-08-12 and drained to 83 of 134 the same day: six keys had no reader whatsoever (telegraph.speak_scope, chat_history.max_size_kb, chat_history.on_overflow, quality.wait_for_remote_ci, screenshots.data_bearing_gate, legal_review_prep.consented_at), five of them `derivable` and one a `consent` row reclassified because a published-egress confirmation is a Hard Floor no setting may lift. An unread key is the only deletion that cannot silently change a default, which is why that batch went first. The classification itself is docs/contracts/settings-classes.md.",
51
+ "falsifier": "./scripts-run src/scripts/lint_settings_classes"
40
52
  }
41
53
  }
42
54
  }
@@ -17,5 +17,3 @@ discipline_profile=auto
17
17
 
18
18
  ; Chat history (crash recovery) — balanced = phase-level traceability
19
19
  chat_history_frequency=per_phase
20
- chat_history_max_size_kb=256
21
- chat_history_on_overflow=rotate
@@ -13,7 +13,5 @@ rule_loading_tier=full
13
13
  ; via the legacy mapping (minimal->off, balanced->essential, full->full).
14
14
  discipline_profile=full
15
15
 
16
- ; Chat history (crash recovery) — full = tool-level traceability + summarize on overflow
16
+ ; Chat history (crash recovery) — full = tool-level traceability
17
17
  chat_history_frequency=per_tool
18
- chat_history_max_size_kb=512
19
- chat_history_on_overflow=condense
@@ -15,5 +15,3 @@ discipline_profile=off
15
15
 
16
16
  ; Chat history (crash recovery) — minimal = lightest footprint
17
17
  chat_history_frequency=per_turn
18
- chat_history_max_size_kb=128
19
- chat_history_on_overflow=rotate
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema_note": "Consumed by src/scripts/check_review_prompt_binding.ts. An entry EXEMPTS one committed review prompt from the sha256(prompt.md) == prompt_hash check. Both hashes are pinned on purpose: if either moves, the entry stops matching and the gate reds, so a later repair and a further corruption are equally visible. A pre-loaded verdict is NEVER suppressed by the hash exemption alone — it needs its own `steered_ack` block on the entry, which pins the exact matched phrase, so growth of the shared steering predicate cannot turn committed prompts into permanent unfixable blocks and cannot be waved through silently either.",
3
+ "entries": [
4
+ {
5
+ "slug": "zcs-close-2026-08-09",
6
+ "declared": "48eae5ad829b375c4667fcd72ecc482cbd62ae90788134ca46a81eacce267d49",
7
+ "actual": "f83ee959abb7f8c333bc89f117d3dee93f0e7b2a816c4593471aeda9b18c302d",
8
+ "reason": "Explained and closed at the cause: the archival sweep rewrote a roadmap path INSIDE this committed reviewer prompt while migrating inbound references. The cause was fixed on 2026-08-11 by _FROZEN_RECORD_PREFIXES in the sweep. Not repaired because repair is not available here: the recorded hash was produced by the ORIGINAL prompt text, so rewriting the hash to match the corrupted text would fabricate a binding that never existed, and restoring the text from history would erase the only evidence that the sweep reached inside a committed round at all. An earlier revision of this entry cited section 2.7 as forbidding the edit; that citation was wrong and is corrected here, because section 2.7 says the rename is an archival step and never an edit ban on a live artefact.",
9
+ "falsifier": "npx tsx src/scripts/check_review_prompt_binding.ts --json # the recorded actual hash stops matching the committed prompt.md, i.e. the record moved after all"
10
+ },
11
+ {
12
+ "slug": "feat-parallel-session-collision-hardening",
13
+ "declared": "d996e8e86a6e21a7cad66963f70219e59fe8cb96bca0bd1adceeea7ad2565c4b",
14
+ "actual": "dae1f14e90be94e71415e520b08222ea819488188d670de280d874803de1035c",
15
+ "reason": "Current and UNEXPLAINED, and it is the empirical justification for this gate. The prompt.md has a single commit dated 2026-08-12, after the sweep fix, so the sweep did not cause it; the declared hash never matched the committed text. CRLF/LF and trailing-newline variants were all tried and none reproduces the declared hash. Not repaired because no repair is knowable: with the cause unidentified, neither side can be established as the correct one, and editing either to make them agree would assert a binding nobody has evidence for. Recording it keeps the discrepancy visible until someone can explain it.",
16
+ "falsifier": "npx tsx src/scripts/check_review_prompt_binding.ts --json # a cause is found and the entry is replaced by the repair, or the recorded actual hash stops matching"
17
+ }
18
+ ]
19
+ }
@@ -1,5 +1,4 @@
1
1
  [
2
- "active-remediation.md",
3
2
  "agent-authority.md",
4
3
  "analysis-skill-routing.md",
5
4
  "architecture.md",
@@ -20,6 +20,7 @@
20
20
  * agent-config session:recycle --file <envelope.json> # read from file
21
21
  * agent-config session:recycle < envelope.json # read from stdin
22
22
  * agent-config session:recycle --template # print a skeleton
23
+ * agent-config session:recycle --project <path> # name the repo explicitly
23
24
  *
24
25
  * Exit codes: 0 written (or template printed) · 1 invalid / refused · 2 usage.
25
26
  */
@@ -29,7 +30,7 @@ import * as path from 'node:path';
29
30
  import process from 'node:process';
30
31
  import { fileURLToPath, pathToFileURL } from 'node:url';
31
32
 
32
- import { resolve_project_root } from '../_lib/agent_settings.js';
33
+ import { ORIGIN_CWD_FALLBACK, resolve_project_root } from '../_lib/agent_settings.js';
33
34
  import {
34
35
  RECYCLE_ENVELOPE_MAX_BYTES,
35
36
  RECYCLE_ENVELOPE_REL,
@@ -77,6 +78,7 @@ interface ParsedArgv {
77
78
  ok: boolean;
78
79
  message?: string;
79
80
  file?: string;
81
+ project?: string;
80
82
  template?: boolean;
81
83
  verify?: boolean;
82
84
  }
@@ -94,6 +96,11 @@ export function parseArgv(argv: readonly string[]): ParsedArgv {
94
96
  if (!value) return { ok: false, message: '--file requires a path' };
95
97
  parsed.file = value;
96
98
  i += 1;
99
+ } else if (a === '--project') {
100
+ const value = argv[i + 1];
101
+ if (!value) return { ok: false, message: '--project requires a path' };
102
+ parsed.project = value;
103
+ i += 1;
97
104
  } else if (a === '-h' || a === '--help') {
98
105
  return { ok: false, message: 'usage' };
99
106
  } else {
@@ -110,11 +117,50 @@ export function parseArgv(argv: readonly string[]): ParsedArgv {
110
117
  */
111
118
  export function runSessionRecycle(
112
119
  input: string,
113
- opts: { cwd: string; now?: Date; verify?: boolean },
120
+ opts: { cwd: string; now?: Date; verify?: boolean; project?: string },
114
121
  ): RecycleResult {
115
122
  const out: string[] = [];
116
123
  const err: string[] = [];
117
- const [projectRoot] = resolve_project_root(null, { cwd: opts.cwd });
124
+
125
+ // `--project` is validated by the resolver (existence + directory), which
126
+ // signals by THROWING. Uncaught, that would surface as a stack trace from
127
+ // a command whose entire subject is legible failure.
128
+ let projectRoot: string;
129
+ let origin: string;
130
+ try {
131
+ [projectRoot, origin] = resolve_project_root(opts.project ?? null, { cwd: opts.cwd });
132
+ } catch (exc) {
133
+ return { code: 1, out, err: [`recycle envelope refused — ${String(exc)}`] };
134
+ }
135
+
136
+ // An unanchored cwd is the ONE case where a successful write is worse than
137
+ // a refusal. `resolve_project_root` falls back to the cwd itself when it
138
+ // reaches the filesystem root without finding an anchor, so a call from
139
+ // outside any repo — the shape a consumer with a toolchain pin is forced
140
+ // into — writes a valid envelope into a directory the successor session
141
+ // never looks at, and then prints a resume instruction whose next step is
142
+ // `/clear`. The exit code says it worked; the session is gone anyway.
143
+ //
144
+ // Refused BEFORE the parse: everything downstream reads this root, so a
145
+ // wrong one also produces a wrong `workspace` field and grounding read
146
+ // from the wrong git tree. `--verify` is refused too — validating against
147
+ // a root the write would not use answers a question nobody asked.
148
+ if (origin === ORIGIN_CWD_FALLBACK) {
149
+ return {
150
+ code: 1,
151
+ out,
152
+ err: [
153
+ `recycle envelope refused — no project anchor at or above ${projectRoot}`,
154
+ 'Writing here would put the envelope where the successor session never reads it,',
155
+ 'and the resume instruction would then advise /clear on a session that cannot resume.',
156
+ 'Name the repository explicitly — it must be the WORKSPACE ROOT OF THE SESSION',
157
+ 'that will resume, because that is the directory the successor reads the envelope',
158
+ 'from; a path that merely happens to be a project is not enough:',
159
+ ` agent-config session:recycle --project <session-workspace-root> …`,
160
+ ` AGENT_CONFIG_PROJECT_ROOT=<session-workspace-root> agent-config session:recycle …`,
161
+ ],
162
+ };
163
+ }
118
164
 
119
165
  let parsed: unknown;
120
166
  try {
@@ -179,23 +225,28 @@ export function runSessionRecycle(
179
225
  };
180
226
  }
181
227
 
228
+ // Absolute, not `RECYCLE_ENVELOPE_REL`: the relative form is identical for
229
+ // every root, so it cannot tell the reader WHICH tree was written — the
230
+ // one thing in doubt at the moment this line is read.
231
+ const target = path.join(projectRoot, RECYCLE_ENVELOPE_REL);
232
+
182
233
  // `--verify` stops HERE: every rejection above has already run, and the
183
234
  // only thing skipped is the write. Validating through a different path
184
235
  // than the one that writes would make the check a second implementation
185
236
  // to keep in sync — the failure this suite refuses elsewhere.
186
237
  if (opts.verify === true) {
187
238
  out.push(`recycle envelope VALID — ${bytes} bytes, not written (--verify).`);
239
+ out.push(`Target would be: ${target}`);
188
240
  return { code: 0, out, err };
189
241
  }
190
242
 
191
- const target = path.join(projectRoot, RECYCLE_ENVELOPE_REL);
192
243
  try {
193
244
  atomic_write_json(target, envelope);
194
245
  } catch (exc) {
195
246
  return { code: 1, out, err: [`could not write ${target}: ${String(exc)}`] };
196
247
  }
197
248
 
198
- out.push(`recycle envelope written — ${RECYCLE_ENVELOPE_REL} (${bytes} bytes)`);
249
+ out.push(`recycle envelope written — ${target} (${bytes} bytes)`);
199
250
  out.push('');
200
251
  out.push('Resume instruction:');
201
252
  out.push(' 1. End this session now: run /clear (or start a fresh session in this workspace).');
@@ -210,10 +261,15 @@ export function main(argv: string[] = process.argv.slice(2)): number {
210
261
  const parsed = parseArgv(argv);
211
262
  if (!parsed.ok) {
212
263
  const usage = [
213
- 'usage: agent-config session:recycle [--file <envelope.json>] [--template] [--verify]',
214
- ' --file <path> read the envelope JSON from a file (default: stdin)',
215
- ' --template print a skeleton envelope and exit',
216
- ' --verify validate only — same rejections, no write',
264
+ 'usage: agent-config session:recycle [--file <envelope.json>] [--project <path>] [--template] [--verify]',
265
+ ' --file <path> read the envelope JSON from a file (default: stdin)',
266
+ ' --project <path> the workspace root of the session that will resume — required',
267
+ ' when the working directory is outside any project (the command',
268
+ ' refuses rather than writing an envelope the successor cannot',
269
+ ' find). The successor reads the envelope from ITS OWN workspace',
270
+ ' root, so a path that is merely a valid project is not enough.',
271
+ ' --template print a skeleton envelope and exit',
272
+ ' --verify validate only — same rejections, no write',
217
273
  ].join('\n');
218
274
  if (parsed.message === 'usage') {
219
275
  process.stdout.write(`${usage}\n`);
@@ -246,6 +302,7 @@ export function main(argv: string[] = process.argv.slice(2)): number {
246
302
  const result = runSessionRecycle(input, {
247
303
  cwd: process.cwd(),
248
304
  ...(parsed.verify === true ? { verify: true } : {}),
305
+ ...(parsed.project !== undefined ? { project: parsed.project } : {}),
249
306
  });
250
307
  for (const line of result.out) process.stdout.write(`${line}\n`);
251
308
  for (const line of result.err) process.stderr.write(`${line}\n`);
@@ -30,6 +30,7 @@ import * as fs from 'node:fs';
30
30
  import * as os from 'node:os';
31
31
  import * as path from 'node:path';
32
32
 
33
+ import { projectStoreSlug } from '../_lib/cc_transcript.js';
33
34
  import { derive_session_tag, list_sessions, read_entries } from '../chat_history.js';
34
35
  import { eolSessionKey, readEolCounters, type StoredEolCounters } from '../_lib/session_eol.js';
35
36
 
@@ -102,9 +103,9 @@ export interface ListOptions {
102
103
  workspaceRoot?: string;
103
104
  }
104
105
 
105
- /** Mirrors the Claude Code project-dir naming: `/` and `.` → `-`. */
106
+ /** Mirrors the Claude Code project-dir naming — see `_lib/cc_transcript.projectStoreSlug`. */
106
107
  export function encode_project_path(cwd: string): string {
107
- return cwd.replace(/[/.]/g, '-');
108
+ return projectStoreSlug(cwd);
108
109
  }
109
110
 
110
111
  function _snippet(text: string, max: number = SUMMARY_SNIPPET_CHARS): string {
@@ -80,6 +80,7 @@ TS-shell native (run via the installed `agent-config` binary):
80
80
  Names the degraded case (settings file without a
81
81
  profile.id → zero packs) that nothing else reports.
82
82
  commands [ls|explain] List/explain the command surface from the discovery manifest
83
+ (ls --candidates: surface-reduction report, report-only)
83
84
  mcp-server Turnkey read-only stdio MCP server (no repo clone; ADR-085)
84
85
  doctor-shell Probe the TS-shell environment
85
86
  rtk:detect rtk (Rust Token Killer) presence + identity readout (--json)
@@ -283,7 +283,6 @@ export const MERGEABLE_KEYS: readonly string[] = [
283
283
  'personal.bot_icon',
284
284
  'personal.pr_comment_bot_icon',
285
285
  'personal.autonomy',
286
- 'telegraph.speak_scope',
287
286
  // Knowledge-card global cross-project sharing is a USER-GLOBAL setting
288
287
  // (ADR-100 / road-to-structure-grounding-v2). Whitelisted so the
289
288
  // ~/.event4u/agent-config/agent-settings.yml values are honoured.
@@ -908,8 +907,27 @@ export function load_agent_settings(
908
907
  * made the soak impossible; whether the gate fires is now decided by each
909
908
  * detector's own trigger conditions. See `turn_end_gate_hook.ts` § "Always
910
909
  * armed".
910
+ *
911
+ * The five `derivable` leaves below (2026-08-12, road-to-zero-settings Phase
912
+ * 2.1) are a different case from every entry above them, and the difference is
913
+ * the whole safety argument: no code path ever *consulted* them. Each appeared
914
+ * in the template, the schema and the reference page, and several in the setup
915
+ * wizard, while the decision they looked like they governed was taken
916
+ * elsewhere. A key nothing reads cannot change a default by leaving — so this
917
+ * is the one deletion batch that needs no replacement mechanism, only the
918
+ * per-key statement of what already decides.
919
+ *
920
+ * A sixth unread key, `screenshots.data_bearing_gate`, was found in the same
921
+ * pass and deliberately NOT deleted — it is `consent`, and the open question is
922
+ * whether the repair is to build its missing reader or to delete a promise a
923
+ * Hard Floor can never honour. See `settings-classes.md` § The six unread keys.
924
+ *
925
+ * Every reason string must name what decides INSTEAD, never just "removed".
926
+ * Exported so `lint_settings_classes` can check the deletion side of the
927
+ * surface: a reason that names no replacement, or an entry whose key is live in
928
+ * the template again, is a contradiction the loader itself cannot notice.
911
929
  */
912
- const REMOVED_KEYS: ReadonlyMap<string, string> = new Map([
930
+ export const REMOVED_KEYS: ReadonlyMap<string, string> = new Map([
913
931
  ['subagents.enabled', 'always-on orchestration'],
914
932
  ['subagents.auto', 'always-on orchestration'],
915
933
  ['subagents.host_capabilities', 'always-on orchestration'],
@@ -919,6 +937,11 @@ const REMOVED_KEYS: ReadonlyMap<string, string> = new Map([
919
937
  ['hooks.turn_end_gate.promissory', 'the turn-end gate is always armed'],
920
938
  ['hooks.turn_end_gate.language', 'the turn-end gate is always armed'],
921
939
  ['hooks.turn_end_gate.verification', 'the turn-end gate is always armed'],
940
+ ['telegraph.speak_scope', 'the rule body states its own scope; compile_router gates on telegraph.speak alone'],
941
+ ['chat_history.max_size_kb', 'the rotate command takes --max-kb from argv; session-count pruning bounds the file'],
942
+ ['chat_history.on_overflow', 'the overflow mode comes from the rotate command --mode argv'],
943
+ ['quality.wait_for_remote_ci', 'whether to poll follows from the push plus a detectable remote pipeline'],
944
+ ['legal_review_prep.consented_at', 'the provenance sidecar settings:set writes and consentVerdict reads'],
922
945
  ]);
923
946
 
924
947
  /** Keys already warned about in THIS process — the "once per run" dedupe. */
@@ -23,7 +23,7 @@ import YAML from 'yaml';
23
23
 
24
24
  // ── system prompts per arm ──────────────────────────────────────────────
25
25
 
26
- export const SYSTEM_PROMPT_CONDENSED = `You are speaking in TELEGRAPH-SPEAK mode (speak_scope=aggressive).
26
+ export const SYSTEM_PROMPT_CONDENSED = `You are speaking in TELEGRAPH-SPEAK mode (condense aggressively).
27
27
 
28
28
  Condense all body prose to telegraph grammar:
29
29
  - Drop articles (the, a, an).
@@ -35,6 +35,31 @@ import { homedir } from 'node:os';
35
35
  /** Default base directory holding one subdirectory per Claude Code project. */
36
36
  export const DEFAULT_PROJECTS_ROOT = path.join(homedir(), '.claude', 'projects');
37
37
 
38
+ /**
39
+ * The store directory name Claude Code derives from a working directory.
40
+ *
41
+ * Every character outside `[A-Za-z0-9-]` becomes `-`: `/Users/x/.claude` →
42
+ * `-Users-x--claude`, and a worktree `feat+turn-end-gate-always-on` →
43
+ * `feat-turn-end-gate-always-on`.
44
+ *
45
+ * This lives here because it was wrong in six places at once. The sweep on
46
+ * 2026-08-12 found `replace(/[/.]/g, '-')` copied across every transcript-reading
47
+ * report; each one computed a directory that does not exist for any path
48
+ * carrying a `+` and returned an empty corpus — indistinguishable from a clean
49
+ * result. One of those copies already carried a comment about the round BEFORE
50
+ * that, when only `/` was replaced and dotted segments missed. Widening the
51
+ * class by one character per incident is how a defect survives two fixes, so
52
+ * the class is closed instead: anything not `[A-Za-z0-9-]` is slugged.
53
+ */
54
+ export function projectStoreSlug(cwd: string): string {
55
+ return cwd.replace(/[^A-Za-z0-9-]/g, '-');
56
+ }
57
+
58
+ /** Absolute path to a working directory's transcript store. */
59
+ export function projectStoreDir(cwd: string, root: string = DEFAULT_PROJECTS_ROOT): string {
60
+ return path.join(root, projectStoreSlug(cwd));
61
+ }
62
+
38
63
  export type TranscriptBucket = 'main' | 'subagent';
39
64
 
40
65
  /** The six token counters every downstream metric (cost, weight, share) is built from. */
@@ -60,9 +60,10 @@ export const COMPILE_TIME_TOGGLES: Record<string, (s: ToggleSettings) => boolean
60
60
  // family master switch, and flipping it would silence sibling telegraph settings
61
61
  // that were never measured negative.
62
62
  //
63
- // This predicate deliberately does NOT read `speak_scope`. That key disables the
64
- // rule's *behaviour* and never removed its token cost — the trap the measurement
65
- // exposed.
63
+ // The predicate gates on `telegraph.speak` because that is the only lever that
64
+ // removes the rule's ~982-token body from the router and the projection. A knob
65
+ // that merely narrowed the grammar at runtime would leave the token cost in place
66
+ // — the trap the measurement exposed.
66
67
  'telegraph-speak': (s: ToggleSettings): boolean => {
67
68
  const tg = (s['telegraph'] as ToggleSettings | undefined) ?? {};
68
69
  const enabled = tg['enabled'] === undefined ? true : tg['enabled'];