@event4u/agent-config 10.0.0 → 10.2.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 +172 -5
- package/README.md +1 -1
- package/dist/agent-src/commands/roadmap/next.md +38 -0
- package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +22 -1
- package/dist/agent-src/contexts/contracts/frugality-charter.md +1 -1
- package/dist/agent-src/rules/context-hygiene.md +39 -0
- package/dist/agent-src/rules/token-budget-discipline.md +3 -2
- package/dist/agent-src/rules/token-efficiency.md +22 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +17 -0
- package/dist/agent-src/skills/adr-create/SKILL.md +5 -0
- package/dist/agent-src/skills/ai-council/SKILL.md +28 -6
- package/dist/agent-src/skills/check-refs/SKILL.md +5 -0
- package/dist/agent-src/skills/condense-memory/SKILL.md +1 -1
- package/dist/agent-src/skills/lint-skills/SKILL.md +5 -0
- package/dist/agent-src/skills/md-language-check/SKILL.md +5 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
- package/dist/agent-src/skills/token-optimizer/SKILL.md +5 -1
- package/dist/agent-src/templates/agent-settings.md +12 -28
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +2 -2
- package/dist/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +0 -1
- package/dist/cli/registry.js +1 -1
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/{chunk-3JUTT26I.js → chunk-6ENSXSBW.js} +1 -1
- package/dist/cli-delegate/{chunk-TVGXH7VA.js → chunk-6LOPHJLD.js} +1 -1
- package/dist/cli-delegate/{chunk-XT2YTJCZ.js → chunk-AYKQGIBG.js} +4 -0
- package/dist/cli-delegate/{chunk-HJSKTMIF.js → chunk-BV3WR67D.js} +3 -19
- package/dist/cli-delegate/{chunk-HTIU3W56.js → chunk-BWMXLD4B.js} +6 -2
- package/dist/cli-delegate/{chunk-2H5SE7DU.js → chunk-E4RUZKXB.js} +165 -38
- package/dist/cli-delegate/{chunk-XTC5ARB6.js → chunk-IR3IVRVC.js} +7 -2
- package/dist/cli-delegate/{chunk-LRXJK6GN.js → chunk-JMQZ7MM5.js} +1 -1
- package/dist/cli-delegate/{chunk-NX5AXSLD.js → chunk-X3VOARZD.js} +3 -3
- package/dist/cli-delegate/cmd_analyze_session.js +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +1 -1
- package/dist/cli-delegate/cmd_conformance.js +881 -204
- package/dist/cli-delegate/cmd_doctor.js +3 -3
- package/dist/cli-delegate/cmd_explain.js +2 -2
- package/dist/cli-delegate/cmd_export.js +4 -4
- package/dist/cli-delegate/cmd_handoff.js +6 -5
- package/dist/cli-delegate/cmd_mcp_available.js +1 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +2 -2
- package/dist/cli-delegate/cmd_prune.js +4 -4
- package/dist/cli-delegate/cmd_route_audit.js +2 -2
- package/dist/cli-delegate/cmd_route_explain.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +44 -9
- package/dist/cli-delegate/cmd_settings_get.js +2 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
- package/dist/cli-delegate/cmd_settings_set.js +2 -2
- package/dist/cli-delegate/cmd_sync.js +4 -4
- package/dist/cli-delegate/cmd_uninstall.js +4 -4
- package/dist/cli-delegate/cmd_update.js +1 -1
- package/dist/cli-delegate/cmd_upgrade.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +4 -4
- package/dist/cli-delegate/cmd_versions.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +15 -15
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +88 -20
- package/dist/install/install.mjs +9 -21
- package/dist/mcp/registry-manifest.json +1 -1
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +1359 -492
- package/dist/scripts/_lib/agent_settings.js +25 -2
- package/dist/scripts/_lib/agent_settings.js.map +1 -1
- package/dist/scripts/_lib/compile_time_toggles.js +4 -3
- package/dist/scripts/_lib/compile_time_toggles.js.map +1 -1
- package/dist/server/io/yamlIO.js +0 -2
- package/dist/server/io/yamlIO.js.map +1 -1
- package/dist/server/schemas/settings.js +3 -9
- package/dist/server/schemas/settings.js.map +1 -1
- package/dist/shared/settingsClasses.js +64 -2
- package/dist/shared/settingsClasses.js.map +1 -1
- package/dist/ui/assets/{index-B5qGYOJo.js → index-5fXOHoVh.js} +2 -2
- package/dist/ui/assets/index-5fXOHoVh.js.map +1 -0
- package/dist/ui/index.html +1 -1
- package/docs/contracts/settings-classes.md +307 -145
- package/docs/guidelines/agent-infra/layered-settings.md +1 -2
- package/docs/guidelines/agent-infra/symptom-driven-harvest-loop.md +104 -0
- package/package.json +1 -1
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +17 -0
- package/src/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +0 -1
- package/src/config/agent-settings.template.yml +12 -30
- package/src/config/gate-violation-baselines.json +9 -3
- package/src/config/profiles/balanced.ini +0 -2
- package/src/config/profiles/full.ini +1 -3
- package/src/config/profiles/minimal.ini +0 -2
- package/src/scripts/_cli/cmd_session_recycle.ts +66 -9
- package/src/scripts/_cli/handoff_sessions.ts +3 -2
- package/src/scripts/_lib/agent_settings.ts +25 -2
- package/src/scripts/_lib/bench_telegraph.ts +1 -1
- package/src/scripts/_lib/cc_transcript.ts +25 -0
- package/src/scripts/_lib/compile_time_toggles.ts +4 -3
- package/src/scripts/_lib/untrusted_content.test.ts +175 -0
- package/src/scripts/_lib/untrusted_content.ts +184 -0
- package/src/scripts/ai_council/clients.ts +228 -33
- package/src/scripts/cache_realization_report.ts +3 -2
- package/src/scripts/check_rule_projection_integrity.ts +3 -1
- package/src/scripts/conformance_scan.ts +282 -9
- package/src/scripts/cost/track.mjs +10 -4
- package/src/scripts/council_cli.ts +33 -3
- package/src/scripts/external_sources_denylist.json +3 -1
- package/src/scripts/hook_manifest.yaml +17 -6
- package/src/scripts/hooks/block_unauthorized_git.ts +54 -8
- package/src/scripts/hooks/delegation_nudge_hook.ts +33 -8
- package/src/scripts/hooks/evidence_independence.ts +61 -9
- package/src/scripts/hooks/session_eol_hook.ts +123 -3
- package/src/scripts/hooks/turn_end_gate_hook.ts +31 -0
- package/src/scripts/lint_settings_classes.ts +273 -11
- package/src/scripts/lint_symptom_intake.ts +241 -0
- package/src/scripts/report_consultation_rate.ts +2 -1
- package/src/scripts/report_skill_activation.ts +6 -4
- package/src/scripts/report_skill_obligation_violations.ts +2 -1
- package/src/scripts/schemas/skill.schema.json +42 -0
- package/src/scripts/skill_linter.ts +57 -2
- package/src/server/io/yamlIO.ts +0 -2
- package/src/server/schemas/settings.ts +3 -19
- package/src/shared/settingsClasses.ts +93 -2
- package/dist/ui/assets/index-B5qGYOJo.js.map +0 -1
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Symptom-driven harvest loop
|
|
2
|
+
|
|
3
|
+
> The procedure that turns an **operator's production symptom** into a roadmap
|
|
4
|
+
> this repository can execute. It is the inverse of the source-driven harvest:
|
|
5
|
+
> that one starts from external material pushed onto the repo and asks what is
|
|
6
|
+
> worth adopting; this one starts from a failure someone hit in production and
|
|
7
|
+
> pulls external material in **per confirmed defect**.
|
|
8
|
+
>
|
|
9
|
+
> Body of `road-to-symptom-driven-harvest-loop` Phase 2. It lives here rather
|
|
10
|
+
> than inline in [`roadmap-writing`](../../../src/skills/roadmap-writing/SKILL.md)
|
|
11
|
+
> because that skill is already ~4.8k tokens — well past the 3,500-token band —
|
|
12
|
+
> and a procedure needed a few times a quarter should not be paid for on every
|
|
13
|
+
> roadmap-authoring turn. The skill carries the pointer.
|
|
14
|
+
|
|
15
|
+
## When this applies
|
|
16
|
+
|
|
17
|
+
An operator reports a failure in their own words — "runs never end", "the agent
|
|
18
|
+
screenshots instead of reading the code" — rather than a defect with a file and
|
|
19
|
+
a line. File it first under [`agents/tickets/symptoms/`](../../../agents/tickets/symptoms/README.md);
|
|
20
|
+
that entry is what this loop resolves.
|
|
21
|
+
|
|
22
|
+
Not this loop: a defect already traced to a file (that is a roadmap item), a
|
|
23
|
+
feature request (roadmap or ticket), or external material arriving unprompted
|
|
24
|
+
(the source-driven harvest family).
|
|
25
|
+
|
|
26
|
+
## The four steps
|
|
27
|
+
|
|
28
|
+
**1. Confirm against the live tree, at a pinned commit, before any external
|
|
29
|
+
search.** One `file:line` per claim, and the pin recorded next to it. The
|
|
30
|
+
ordering is the load-bearing part: searching externally first produces a plan
|
|
31
|
+
about the ecosystem's problems rather than this tree's, and every subsequent
|
|
32
|
+
round inherits that frame. A symptom that does not reproduce as a confirmed
|
|
33
|
+
defect gets a `null:` block and stops here — that is a result.
|
|
34
|
+
|
|
35
|
+
**2. Triangulate externally, per confirmed defect, never additively.** For each
|
|
36
|
+
defect, and only for each defect: the host tool's own issue tracker, its official
|
|
37
|
+
docs **fetched fresh**, and practitioner reports. Inverted form throughout — the
|
|
38
|
+
defect is the anchor, the source is drawn in to explain or bound it. A source
|
|
39
|
+
that matches no confirmed defect is not evidence for this roadmap; note it and
|
|
40
|
+
drop it.
|
|
41
|
+
|
|
42
|
+
**3. Iterate at least two further rounds, each stating its delta.** A round that
|
|
43
|
+
changes nothing ends the loop early and says so. This is the step that is
|
|
44
|
+
skipped under time pressure and the step that pays: on the first run of this
|
|
45
|
+
loop, round 2 refuted a standing repo blocker from the official payload spec,
|
|
46
|
+
imported a constraint that reshaped a whole phase from "block on invalid" to
|
|
47
|
+
"command-hook, disk-fallback, block-once", and round 3 found a reopen-clause
|
|
48
|
+
deadlock that was the *only* argument for reopening cancelled work at all. A
|
|
49
|
+
one-pass version would have shipped a roadmap re-proposing cancelled items with
|
|
50
|
+
no reopening argument and a mechanism the host would have broken.
|
|
51
|
+
|
|
52
|
+
**4. Emit with a symptom→defect map and per-phase falsifiers.** The map is the
|
|
53
|
+
artifact the operator can read: their words in one column, confirmed defects with
|
|
54
|
+
anchors in the next, owning phase in the third. Every phase carries the
|
|
55
|
+
measurement that would delete it.
|
|
56
|
+
|
|
57
|
+
## Step 5, added by the first three runs: re-verify at adoption
|
|
58
|
+
|
|
59
|
+
A roadmap drafted at a pin and adopted later is a **snapshot of an opinion about
|
|
60
|
+
a tree that has since moved**. Before adopting, re-verify every repo claim at the
|
|
61
|
+
current tip and record the deltas.
|
|
62
|
+
|
|
63
|
+
This is not ceremony. Adopting the first three runs of this loop, 81 commits
|
|
64
|
+
past their shared pin, re-verification found 32 of 36 claims still true — and:
|
|
65
|
+
|
|
66
|
+
- one defect had **fixed itself** (a function measured as having zero production
|
|
67
|
+
callers had acquired one), which deleted a roadmap step before anyone built it;
|
|
68
|
+
- one claim was **wrong at the pin too** (a count stated as six where the tree
|
|
69
|
+
binds seven), which no amount of re-reading the draft would have surfaced;
|
|
70
|
+
- three anchors had **drifted** to new line numbers while their content held;
|
|
71
|
+
- one cross-cutting conflict was invisible until two drafts were read together
|
|
72
|
+
(a proposed "adopt the artifact's code" duty against an existing
|
|
73
|
+
`NEVER ADOPT EXTERNAL CODE VERBATIM` Iron Law).
|
|
74
|
+
|
|
75
|
+
The cheapest of those to find was the already-fixed one, and it was worth the
|
|
76
|
+
most: an item prevented costs nothing to maintain.
|
|
77
|
+
|
|
78
|
+
## Worked example — why round 2 is mandatory
|
|
79
|
+
|
|
80
|
+
`hook_manifest.yaml` carried a comment stating that in-process subagents "inherit
|
|
81
|
+
the host process env and cannot be marked per-spawn", and that comment had stood
|
|
82
|
+
for months as a recorded blocker on a whole design axis. It was true about the
|
|
83
|
+
mechanism it named — the env var — and false about the conclusion.
|
|
84
|
+
|
|
85
|
+
Round 2 fetched the host's current payload specification, which states that tool
|
|
86
|
+
events inside a subagent carry `agent_id` and `agent_type` precisely so
|
|
87
|
+
subagent calls can be distinguished from main-thread ones. The env cannot mark a
|
|
88
|
+
spawn; the payload already did. One fetch refuted a months-old blocker, and the
|
|
89
|
+
axis it had closed reopened.
|
|
90
|
+
|
|
91
|
+
The general shape: **a repo comment records what was tried, not what is
|
|
92
|
+
possible.** Round 1 finds the comment and believes it, because it is this tree's
|
|
93
|
+
own evidence. Only an external round can date it.
|
|
94
|
+
|
|
95
|
+
## See also
|
|
96
|
+
|
|
97
|
+
- [`roadmap-writing`](../../../src/skills/roadmap-writing/SKILL.md) — the authoring
|
|
98
|
+
skill this procedure extends; it owns phases, falsifiers, and the output format.
|
|
99
|
+
- [`agents/tickets/symptoms/README.md`](../../../agents/tickets/symptoms/README.md) —
|
|
100
|
+
the intake convention and the two resolution blocks.
|
|
101
|
+
- [`source-discovery-gate`](../../../src/rules/source-discovery-gate.md) — the
|
|
102
|
+
no-structural-claim-without-evidence rule step 1 is an application of.
|
|
103
|
+
- [`external-reference-deep-dive`](../../../src/rules/external-reference-deep-dive.md) —
|
|
104
|
+
how to read a named external source properly in step 2.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@event4u/agent-config",
|
|
3
3
|
"mcpName": "io.github.event4u-app/agent-config",
|
|
4
|
-
"version": "10.
|
|
4
|
+
"version": "10.2.0",
|
|
5
5
|
"description": "Governed skills, rules and commands for AI coding tools (Claude Code, Cursor, Windsurf, Copilot) — every claim machine-checked, including \"zero runtime daemon\".",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"private": false,
|
|
@@ -335,6 +335,23 @@ function archive_completed(
|
|
|
335
335
|
if (stats.open_ !== 0 || stats.deferred !== 0) {
|
|
336
336
|
continue; // not complete
|
|
337
337
|
}
|
|
338
|
+
// An unresolved blocker outlives its steps. Closing every box does not
|
|
339
|
+
// answer a question the roadmap raised for a human, and archiving on
|
|
340
|
+
// step-count alone is how `b-highlights-mechanism` left the active tree
|
|
341
|
+
// unanswered in the 9.29 roadmap — four releases before the failure it
|
|
342
|
+
// predicted shipped in the 9.36.0 changelog head. Same discipline
|
|
343
|
+
// `roadmap-progress-sync` Iron Law 3 applies to `[~]` steps: a roadmap
|
|
344
|
+
// carrying an open decision stays visible until the decision is made.
|
|
345
|
+
const open_blockers = stats.open_blockers;
|
|
346
|
+
if (open_blockers.length > 0) {
|
|
347
|
+
const ids = open_blockers.map((b) => b.id).join(', ');
|
|
348
|
+
process.stderr.write(
|
|
349
|
+
` ⚠️ ${stats.rel}: all steps closed but ${open_blockers.length} ` +
|
|
350
|
+
`blocker(s) still open (${ids}) — not archived. Resolve them, ` +
|
|
351
|
+
'or record the decision, then re-run the sweep.\n',
|
|
352
|
+
);
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
338
355
|
const old_rel = `agents/roadmaps/${stats.rel}`;
|
|
339
356
|
if (changed_only && !(touched as Set<string>).has(old_rel)) {
|
|
340
357
|
continue; // complete, but not this branch's work
|
|
@@ -210,7 +210,6 @@ export const MERGEABLE_KEYS: readonly string[] = [
|
|
|
210
210
|
'memory.cadence',
|
|
211
211
|
'personal.bot_icon',
|
|
212
212
|
'personal.autonomy',
|
|
213
|
-
'telegraph.speak_scope',
|
|
214
213
|
// Knowledge-card global cross-project sharing is a USER-GLOBAL setting
|
|
215
214
|
// (ADR-100 / road-to-structure-grounding-v2). Whitelisted so the
|
|
216
215
|
// ~/.event4u/agent-config/agent-settings.yml values are honoured.
|
|
@@ -166,33 +166,25 @@ lean_projection:
|
|
|
166
166
|
|
|
167
167
|
# --- Telegraph condensation (output-side) ---
|
|
168
168
|
#
|
|
169
|
-
#
|
|
170
|
-
#
|
|
171
|
-
#
|
|
172
|
-
# `off` is the shipped default per docs/adrs/telegraph/0001-default-off-until-bench.md
|
|
173
|
-
# (Status: accepted) and docs/contracts/condensation-default-kill-criterion.md.
|
|
169
|
+
# `speak: false` is the shipped default per
|
|
170
|
+
# docs/adrs/telegraph/0001-default-off-until-bench.md (Status: accepted) and
|
|
171
|
+
# docs/contracts/condensation-default-kill-criterion.md.
|
|
174
172
|
# Reason it is off: the only output-side bench (telegraph-v1, n=10, 2026-05-16)
|
|
175
173
|
# measured median vs_terse -9.27% (API counts) / -5.47% (exact cl100k_base
|
|
176
174
|
# re-analysis) — telegraph emits MORE tokens than a plain "be terse" instruction.
|
|
177
175
|
# The kill-criterion table therefore lands on "criterion not met — defer" and the
|
|
178
176
|
# telemetry multiplier stays suspended (0.9155 < 1.0).
|
|
179
177
|
#
|
|
180
|
-
# NOTE: `speak_scope: off` disables the BEHAVIOUR but not the COST — the projector
|
|
181
|
-
# gates this rule on `telegraph.speak`, never on speak_scope, so under
|
|
182
|
-
# `lean_projection.mode: eager-all` the ~982-token body still ships every session.
|
|
183
|
-
# Set `speak: false` for zero-cost dormancy.
|
|
184
|
-
#
|
|
185
178
|
# Stated explicitly here because until 2026-07-29 this default existed only in
|
|
186
|
-
# prose — and two prose sources disagreed
|
|
187
|
-
#
|
|
179
|
+
# prose — and two prose sources disagreed. Do not flip without a passing
|
|
180
|
+
# output-side bench.
|
|
188
181
|
telegraph:
|
|
189
182
|
# speak: false = DORMANT (the shipped default per docs/adrs/telegraph/0002).
|
|
190
183
|
# The rule is omitted from dist/router.json entirely, so its ~982-token body
|
|
191
|
-
# never ships. This
|
|
192
|
-
#
|
|
184
|
+
# never ships. This is the only lever that stops the cost: compile_router
|
|
185
|
+
# gates on `speak` and on nothing else.
|
|
193
186
|
# Set true only after an output-side bench clears the kill-criterion bar.
|
|
194
187
|
speak: false
|
|
195
|
-
speak_scope: "off"
|
|
196
188
|
|
|
197
189
|
# --- Token-rich skills governance ---
|
|
198
190
|
#
|
|
@@ -420,14 +412,6 @@ chat_history:
|
|
|
420
412
|
# per_tool = one entry per tool call (most verbose)
|
|
421
413
|
frequency: __CHAT_HISTORY_FREQUENCY__
|
|
422
414
|
|
|
423
|
-
# Max file size in KB before overflow handling kicks in (integer)
|
|
424
|
-
max_size_kb: __CHAT_HISTORY_MAX_SIZE_KB__
|
|
425
|
-
|
|
426
|
-
# Overflow behavior
|
|
427
|
-
# rotate = drop oldest entries to stay under max_size_kb
|
|
428
|
-
# condense = mark for summarization on the next agent turn
|
|
429
|
-
on_overflow: __CHAT_HISTORY_ON_OVERFLOW__
|
|
430
|
-
|
|
431
415
|
# Per-entry-type text-length caps (chars). 0 = verbatim, no slice.
|
|
432
416
|
# N > 0 = collapse whitespace, slice to N chars, append " … [+K chars]"
|
|
433
417
|
# so the log self-reports truncation. Defaults match the hardcoded
|
|
@@ -656,12 +640,6 @@ quality:
|
|
|
656
640
|
# gates delegated to remote CI", never claim an unrun pipeline passed.
|
|
657
641
|
local_auto_run: false
|
|
658
642
|
|
|
659
|
-
# Wait for remote CI to finish on the PR / pipeline (true, false)
|
|
660
|
-
# true = after push, poll GitHub check-runs / pipeline status and
|
|
661
|
-
# report green / red before handing back
|
|
662
|
-
# false = push and hand back immediately; user inspects CI
|
|
663
|
-
# themselves (default — saves agent runtime and tokens)
|
|
664
|
-
wait_for_remote_ci: false
|
|
665
643
|
|
|
666
644
|
# --- Design fidelity ---
|
|
667
645
|
#
|
|
@@ -744,6 +722,11 @@ screenshots:
|
|
|
744
722
|
# ship-and-hope. Illustrative / no-data screenshots (diagrams, logos,
|
|
745
723
|
# placeholder-only UI) may embed with a stated justification.
|
|
746
724
|
# off = no data-bearing gate (legacy; the anonymization taxonomy still applies).
|
|
725
|
+
#
|
|
726
|
+
# NOTE: nothing reads this key yet — the gate it names is unconditional prose in
|
|
727
|
+
# `doc-screenshot-hygiene`. Kept rather than deleted so the missing reader is a
|
|
728
|
+
# visible gap instead of a silently unguarded action; see settings-classes.md
|
|
729
|
+
# § The six unread keys for the disagreement about which repair is right.
|
|
747
730
|
data_bearing_gate: "on"
|
|
748
731
|
|
|
749
732
|
code_style:
|
|
@@ -1365,7 +1348,6 @@ explain:
|
|
|
1365
1348
|
# rule refuses every legal-review-prep skill until `acknowledged: true`.
|
|
1366
1349
|
legal_review_prep:
|
|
1367
1350
|
acknowledged: false
|
|
1368
|
-
consented_at: ""
|
|
1369
1351
|
# Gate legal work-product behind a multi-model council / deep-research pass
|
|
1370
1352
|
# (defense-in-depth + audit trail; fail-closed). Off lets single-model legal
|
|
1371
1353
|
# output through — not recommended for this high-risk pack.
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
"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
23
|
},
|
|
24
24
|
"check_gate_completeness": {
|
|
25
|
-
"count":
|
|
26
|
-
"landed": "2026-08-
|
|
27
|
-
"note": "Ledger adoption ratchet.
|
|
25
|
+
"count": 216,
|
|
26
|
+
"landed": "2026-08-12",
|
|
27
|
+
"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
28
|
"falsifier": "./scripts-run src/scripts/check_gate_completeness"
|
|
29
29
|
},
|
|
30
30
|
"check_depth_budget": {
|
|
@@ -37,6 +37,12 @@
|
|
|
37
37
|
"landed": "2026-08-06",
|
|
38
38
|
"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
39
|
"falsifier": "./scripts-run src/scripts/check_gate_coverage"
|
|
40
|
+
},
|
|
41
|
+
"lint_settings_classes:derivable-surface": {
|
|
42
|
+
"count": 83,
|
|
43
|
+
"landed": "2026-08-12",
|
|
44
|
+
"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.",
|
|
45
|
+
"falsifier": "./scripts-run src/scripts/lint_settings_classes"
|
|
40
46
|
}
|
|
41
47
|
}
|
|
42
48
|
}
|
|
@@ -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
|
|
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
|
|
@@ -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
|
-
|
|
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 — ${
|
|
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>
|
|
215
|
-
' --
|
|
216
|
-
'
|
|
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
|
|
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
|
|
108
|
+
return projectStoreSlug(cwd);
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
function _snippet(text: string, max: number = SUMMARY_SNIPPET_CHARS): string {
|
|
@@ -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 (
|
|
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
|
-
//
|
|
64
|
-
// rule's
|
|
65
|
-
//
|
|
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'];
|