@event4u/agent-config 14.21.0 → 14.22.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/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +198 -0
- package/CREDITS.md +37 -0
- package/dist/agent-src/commands/analyze/inbox.md +380 -5
- package/dist/agent-src/commands/analyze/repo.md +4 -2
- package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +69 -0
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +5 -5
- package/dist/agent-src/rules/external-code-graph-interop.md +19 -14
- package/dist/agent-src/rules/token-efficiency.md +26 -3
- package/dist/agent-src/scripts/_cli_entry.ts +51 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +14 -23
- package/dist/agent-src/scripts/roadmap_gates.ts +4 -20
- package/dist/agent-src/scripts/roadmap_set_step.ts +5 -2
- package/dist/agent-src/scripts/stubs_due.ts +4 -18
- package/dist/agent-src/scripts/update_roadmap_progress.ts +7 -24
- package/dist/agent-src/skills/code-intelligence/SKILL.md +25 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +17 -0
- package/dist/agent-src/templates/agent-settings.md +0 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli-delegate/_cli_entry.js +8 -0
- package/dist/cli-delegate/_lib/pin_resolver.js +170 -0
- package/dist/cli-delegate/archival_sweep.js +8 -0
- package/dist/cli-delegate/archive_completed_roadmaps.js +494 -0
- package/dist/cli-delegate/check_update_banner.js +923 -0
- package/dist/cli-delegate/chunk-5EK5WQN6.js +28 -0
- package/dist/cli-delegate/chunk-7XGD2OEU.js +96 -0
- package/dist/cli-delegate/{chunk-643Z7FIC.js → chunk-BIXEDQPZ.js} +6 -0
- package/dist/cli-delegate/chunk-DBH4AHDK.js +155 -0
- package/dist/cli-delegate/chunk-JO25G5H7.js +20 -0
- package/dist/cli-delegate/chunk-LTYVIY76.js +7961 -0
- package/dist/cli-delegate/chunk-QAJS5YWY.js +999 -0
- package/dist/cli-delegate/{chunk-ZVBOHSVF.js → chunk-TGIPZBCW.js} +1 -1
- package/dist/cli-delegate/{chunk-YS6XTLEK.js → chunk-TYSY33MD.js} +56 -6
- package/dist/cli-delegate/chunk-ULET4BYS.js +328 -0
- package/dist/cli-delegate/chunk-YDQSHTD7.js +187 -0
- package/dist/cli-delegate/chunk-YWVXLR44.js +48 -0
- package/dist/cli-delegate/chunk-ZZQBUHFK.js +273 -0
- package/dist/cli-delegate/cmd_conformance.js +2 -2
- package/dist/cli-delegate/cmd_export.js +1 -1
- package/dist/cli-delegate/cmd_handoff.js +26 -2
- package/dist/cli-delegate/cmd_prune.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +2 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +1 -1
- package/dist/cli-delegate/cmd_uninstall.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +1 -1
- package/dist/cli-delegate/dashboard_mode.js +16 -0
- package/dist/cli-delegate/gate_budget.js +24 -0
- package/dist/cli-delegate/gate_execute.js +25 -0
- package/dist/cli-delegate/guarded_baseline.js +18 -0
- package/dist/cli-delegate/resume_probe.js +22 -0
- package/dist/cli-delegate/roadmap_gates.js +638 -0
- package/dist/cli-delegate/roadmap_set_step.js +173 -0
- package/dist/cli-delegate/stub_queue.js +47 -0
- package/dist/cli-delegate/stubs_due.js +38 -0
- package/dist/cli-delegate/ticket_bundles.js +59 -0
- package/dist/cli-delegate/update_roadmap_progress.js +60 -0
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +8 -8
- 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 +1193 -917
- package/dist/install/claudeLayerCarriage.js +237 -0
- package/dist/install/claudePathsPlan.js +79 -0
- package/dist/install/hostLayerFingerprint.js +1 -1
- package/dist/install/install.mjs +6 -0
- package/dist/install/partitionEligibility.js +231 -117
- package/dist/install/ruleLayerPartition.js +10 -7
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +39219 -38995
- package/dist/scripts/condense.js +32 -25
- package/docs/MIGRATION.md +49 -1
- package/docs/contracts/settings-classes.md +1 -1
- package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +73 -0
- package/package.json +7 -4
- package/src/agent-src/scripts/_cli_entry.ts +51 -0
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +14 -23
- package/src/agent-src/scripts/roadmap_gates.ts +4 -20
- package/src/agent-src/scripts/roadmap_set_step.ts +5 -2
- package/src/agent-src/scripts/stubs_due.ts +4 -18
- package/src/agent-src/scripts/update_roadmap_progress.ts +7 -24
- package/src/config/continuity-surface.json +509 -0
- package/src/config/estate-count-budget.json +1 -1
- package/src/config/gate-coverage.yml +29 -8
- package/src/config/gate-violation-baselines.json +8 -8
- package/src/config/gitignore-block.txt +19 -0
- package/src/config/host-injection-effect.json +73 -0
- package/src/config/packed-binary-manifest.json +33 -0
- package/src/config/preamble-payload-budget.json +112 -105
- package/src/config/publish-surface.json +4 -2
- package/src/install/claudeLayerCarriage.ts +273 -0
- package/src/install/claudePathsPlan.ts +76 -0
- package/src/install/hostLayerFingerprint.ts +1 -1
- package/src/install/partitionEligibility.ts +261 -127
- package/src/install/ruleLayerPartition.ts +19 -23
- package/src/scripts/_dispatch.bash +77 -27
- package/src/scripts/_lib/auto_dispatch.ts +2 -2
- package/src/scripts/_lib/catalogue_layer_parity.ts +146 -0
- package/src/scripts/_lib/continuity_surface.ts +296 -0
- package/src/scripts/_lib/corpus_manifest.ts +26 -9
- package/src/scripts/_lib/hook_settings.ts +1 -1
- package/src/scripts/_lib/injection_effect.ts +151 -0
- package/src/scripts/_lib/layer_overlap_notice.ts +50 -6
- package/src/scripts/_lib/packed_binary_predicate.ts +225 -0
- package/src/scripts/_lib/pin_resolver.ts +13 -0
- package/src/scripts/_lib/regression_neighbourhood.ts +1 -1
- package/src/scripts/_lib/retired_status.ts +74 -0
- package/src/scripts/_lib/skill_catalogue.ts +156 -21
- package/src/scripts/_lib/subagent_capsule.ts +117 -8
- package/src/scripts/ai_council/api_transport.ts +5 -0
- package/src/scripts/ai_council/deliberation_metrics.ts +353 -0
- package/src/scripts/ai_council/prompts.ts +9 -0
- package/src/scripts/ai_council/synthesis_disagreement.ts +206 -0
- package/src/scripts/capture_skill_catalogue.ts +24 -11
- package/src/scripts/check_candidate_lines.ts +499 -0
- package/src/scripts/check_continuity_surface.ts +475 -0
- package/src/scripts/check_dependency_floors.ts +13 -6
- package/src/scripts/check_estate_count.ts +40 -79
- package/src/scripts/check_pack_size.ts +262 -23
- package/src/scripts/check_roadmap_trackable.ts +19 -16
- package/src/scripts/check_rule_layer_partition.ts +54 -34
- package/src/scripts/check_single_delivery.ts +97 -31
- package/src/scripts/check_update_banner.ts +13 -0
- package/src/scripts/code_graph/build.ts +138 -15
- package/src/scripts/code_graph/cli.ts +76 -8
- package/src/scripts/code_graph/extract.ts +11 -0
- package/src/scripts/code_graph/grammar_probe.ts +43 -0
- package/src/scripts/code_graph/loader.ts +88 -14
- package/src/scripts/code_graph/query.ts +121 -11
- package/src/scripts/code_graph/resolution_tiers.ts +238 -0
- package/src/scripts/code_graph/sqlite_store.ts +247 -6
- package/src/scripts/code_graph/types.ts +66 -1
- package/src/scripts/code_graph/validate.ts +23 -0
- package/src/scripts/condense.ts +34 -22
- package/src/scripts/corpus_manifest.ts +5 -5
- package/src/scripts/council_cli.ts +11 -12
- package/src/scripts/count_intent_disagreement.ts +426 -0
- package/src/scripts/handoff_context_hook.ts +79 -0
- package/src/scripts/hook_effect_doctor.ts +82 -1
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +64 -12
- package/src/scripts/hooks/chain_nudge_hook.ts +282 -0
- package/src/scripts/hooks/code_graph_context_hook.ts +212 -0
- package/src/scripts/hooks/concern_registry.ts +4 -2
- package/src/scripts/hooks/host_semantics.ts +1 -1
- package/src/scripts/hooks/orchestration_record_hook.ts +2 -2
- package/src/scripts/hooks/rule_inject_hook.ts +1 -1
- package/src/scripts/hooks/skill_route_hook.ts +11 -4
- package/src/scripts/inbox_source_census.ts +322 -0
- package/src/scripts/install-hooks.sh +82 -0
- package/src/scripts/{lint_carrier_integrity.ts → lint_deferral_integrity.ts} +60 -214
- package/src/scripts/lint_plan_risk_register.ts +13 -17
- package/src/scripts/mcp_server/suggest_skill.ts +72 -0
- package/src/scripts/mcp_server/tools.ts +2 -56
- package/src/scripts/media/lib/adapter-contract.md +7 -2
- package/src/scripts/media/lib/fixtures/flux/asset-0001.png +0 -0
- package/src/scripts/media/lib/fixtures/gemini-image/asset-0001.png +0 -0
- package/src/scripts/media/lib/fixtures/ideogram/asset-0001.png +0 -0
- package/src/scripts/rdp_candidates_delta.ts +276 -0
- package/src/scripts/rdp_quality_eval.ts +217 -19
- package/src/scripts/release_mix_taxonomy.json +3 -2
- package/src/scripts/report_host_injection_effect.ts +189 -0
- package/src/scripts/report_layer_overlap.ts +41 -0
- package/src/scripts/report_skill_menu_census.ts +341 -0
- package/src/scripts/skill_tools/score_skill_relevance.ts +70 -6
- package/src/scripts/skill_tools/suggest_skill_for_task.ts +37 -3
- package/src/vendor/grammars/README.md +61 -0
- package/src/vendor/grammars/tree-sitter-javascript.wasm +0 -0
- package/src/vendor/grammars/tree-sitter-php.wasm +0 -0
- package/src/vendor/grammars/tree-sitter-typescript.wasm +0 -0
- package/src/scripts/_lib/carrier_status.ts +0 -118
- package/src/scripts/_lib/estate_carrier_cases.ts +0 -72
- package/src/scripts/hooks/code_graph_nudge_hook.ts +0 -191
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The single-delivery partition predicate — one artefact, one layer.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* **What it decides.**
|
|
5
5
|
*
|
|
6
6
|
* Two layers deliver agent artefacts: a machine-local project layer at
|
|
7
7
|
* `<repo>/.claude/` (gitignored, 0 tracked files, rewritten by every
|
|
@@ -11,16 +11,23 @@
|
|
|
11
11
|
* 203,873 tokens against a 110,000 cap (185.3 %).
|
|
12
12
|
*
|
|
13
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.
|
|
15
|
-
* `<repo>/.claude/` from 111 rules and 338 skills to **16 rules and zero
|
|
16
|
-
* skills**.
|
|
14
|
+
* in the project layer; everything else is delivered only globally.
|
|
17
15
|
*
|
|
18
|
-
*
|
|
16
|
+
* The figure this note carried — "16 rules and zero skills" — was the 2026-08-19
|
|
17
|
+
* projection of the design. Measured 2026-09-07 on the amended implementation:
|
|
18
|
+
* **13 rules** (15 package-only, 2 of them byte-identically deduped at user
|
|
19
|
+
* scope) and **zero skills**, plus 49 flat-command wrappers, which are a
|
|
20
|
+
* different family and are withheld per name like everything else.
|
|
21
|
+
*
|
|
22
|
+
* **Why the predicate is fail-safe and never fails the build.**
|
|
19
23
|
*
|
|
20
24
|
* The partition is a removal, so the build loses its repair path: it can no
|
|
21
25
|
* longer heal a stale global layer by regenerating, because it stops writing
|
|
22
|
-
* those files.
|
|
23
|
-
*
|
|
26
|
+
* those files. **How that fail-safe is expressed changed on 2026-09-07** and the
|
|
27
|
+
* sentence below is the OLD form, kept because the constraint it records still
|
|
28
|
+
* binds: uncertainty used to resolve to a repo-wide `standalone/full`, a mode
|
|
29
|
+
* this file no longer has. It now resolves PER ARTEFACT — an unreadable host
|
|
30
|
+
* layer withholds nothing — and still never to a refusal:
|
|
24
31
|
*
|
|
25
32
|
* `.github/workflows/consistency.yml:169` runs `task generate-tools` on a fresh
|
|
26
33
|
* checkout where, by that workflow's own comment at `:172-174`, the host rule
|
|
@@ -30,22 +37,68 @@
|
|
|
30
37
|
* exists to prevent; breaking the Consistency pipeline is the other. Full
|
|
31
38
|
* projection is the only branch that does neither.
|
|
32
39
|
*
|
|
33
|
-
*
|
|
40
|
+
* **Why content and not a version number.**
|
|
34
41
|
*
|
|
35
42
|
* Rationale and the 153-skill measurement that decided it: see
|
|
36
43
|
* `hostLayerFingerprint.ts`. Version equality is checked too, but as a cheap
|
|
37
44
|
* pre-filter — it is necessary, never sufficient.
|
|
38
45
|
*
|
|
39
|
-
*
|
|
46
|
+
* **Contract.**
|
|
40
47
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
48
|
+
* `verifyHostLayer` itself is side-effect-free and takes its facts from the
|
|
49
|
+
* caller. The MODULE is not: `resolveHostLayerVerdict` reads the filesystem and
|
|
50
|
+
* the lockfile, and since 2026-09-07 it re-exports `keepInProjectLayer` /
|
|
51
|
+
* `claudeLayerHolds`, which read directories. The earlier blanket "no I/O of its
|
|
52
|
+
* own" was true of the pure predicate and false of the file. No CLI entry, no
|
|
53
|
+
* `process.exit`. Ships inside the consumer installer bundle, same
|
|
43
54
|
* constraint as `ruleInScope.ts`.
|
|
44
55
|
*/
|
|
45
56
|
|
|
46
57
|
import * as fs from 'node:fs';
|
|
47
58
|
import * as os from 'node:os';
|
|
48
59
|
|
|
60
|
+
import { claudeLayerHolds, keepInProjectLayer } from './claudeLayerCarriage.js';
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* **`emittedWrapperSlugs` — why the skill sweep's protected set narrowed.**
|
|
64
|
+
*
|
|
65
|
+
* `generate_claude_skills` sweeps `.claude/skills/` of anything absent from the
|
|
66
|
+
* set it is about to write, and it spares command-wrapper slugs so the command
|
|
67
|
+
* emitter — which runs afterwards into the same directory — does not lose
|
|
68
|
+
* entries it is about to create. That protected set used to be EVERY command
|
|
69
|
+
* slug, which was safe only while skills were projected in full: a slug that is
|
|
70
|
+
* also a skill name was protected twice over.
|
|
71
|
+
*
|
|
72
|
+
* Once `~/.claude/skills` began withholding by name (2026-09-07) the
|
|
73
|
+
* over-protection became a leak — precisely the one
|
|
74
|
+
* `tests/scripts/single_delivery_emission.test.ts` exists for. A command-named
|
|
75
|
+
* skill survives in `.claude/skills/` while both counters read zero, because the
|
|
76
|
+
* skill sweep spares it and the command prune skips symlinks.
|
|
77
|
+
*
|
|
78
|
+
* So the set mirrors the emitter's own skips — **all of them**, which a neutral
|
|
79
|
+
* review found it did not on 2026-09-07: the note said "two" and named two while
|
|
80
|
+
* the emitter had three, so up to 8 builtin-reserved slugs (`bug`, `review`,
|
|
81
|
+
* `worktree`, `agents`, `context`, `cost`, `memory`, `skills`) were protected
|
|
82
|
+
* from a sweep that should reach them. Benign in the shipped pipeline order,
|
|
83
|
+
* because the command emitter's own stale-dir sweep cleans them afterwards — but
|
|
84
|
+
* the invariant the note asserted did not hold, and it reopens for any run that
|
|
85
|
+
* calls `generate_claude_skills` without `generate_claude_commands`.
|
|
86
|
+
*
|
|
87
|
+
* The four skips: a slug that is also a skill name · every CLUSTERED command
|
|
88
|
+
* (those reach the host as `/cluster:sub` and need no wrapper) · a Claude Code
|
|
89
|
+
* built-in name · a slug `~/.claude/skills` already carries.
|
|
90
|
+
* `condense.ts::_emitted_wrapper_slugs` is that set.
|
|
91
|
+
*
|
|
92
|
+
* **The re-export below.**
|
|
93
|
+
*
|
|
94
|
+
* `keepInProjectLayer` is re-exported so `condense.ts` reaches both halves of
|
|
95
|
+
* the partition through ONE import line. Not stylistic: that file sits ~1,200 lines past the 1,500-line
|
|
96
|
+
* source ceiling, where `check_source_size_budget` counts every added line as
|
|
97
|
+
* one unit of excess and its test asserts `baseline == live` — so a second
|
|
98
|
+
* import statement there is a blocking gate failure, while the same line here
|
|
99
|
+
* (535 lines, under the cap) costs nothing.
|
|
100
|
+
*/
|
|
101
|
+
export { claudeLayerHolds, keepInProjectLayer } from './claudeLayerCarriage.js';
|
|
49
102
|
import { parseFrontmatter } from './ruleInScope.js';
|
|
50
103
|
import { fingerprintLayers, hostLayerInputs } from './hostLayerFingerprint.js';
|
|
51
104
|
import {
|
|
@@ -57,12 +110,25 @@ import {
|
|
|
57
110
|
/** The workspace id that marks an artefact as existing only for this package. */
|
|
58
111
|
export const MAINTAINER_WORKSPACE = 'agent-config-maintainer';
|
|
59
112
|
|
|
60
|
-
/**
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
113
|
+
/**
|
|
114
|
+
* Is the recorded install verified against the host layer on disk?
|
|
115
|
+
*
|
|
116
|
+
* This used to be a `PartitionMode` — `'standalone/full' | 'dual-layer/partitioned'` —
|
|
117
|
+
* and it used to DECIDE what the project layer got written. It no longer does
|
|
118
|
+
* (owner decision, 2026-09-07): the project layer withholds per artefact on the
|
|
119
|
+
* host layer's own contents, via `claudeLayerCarriage` and
|
|
120
|
+
* `globalRuleLayers.hostLayerCarries`. What survives here is the diagnostic —
|
|
121
|
+
* whether the layer a run is withholding against is the one this checkout's
|
|
122
|
+
* installer stamped.
|
|
123
|
+
*
|
|
124
|
+
* The type was renamed rather than kept, because a name that says "delivery mode"
|
|
125
|
+
* while deciding nothing about delivery is the half-alive machinery this change
|
|
126
|
+
* exists to remove.
|
|
127
|
+
*/
|
|
128
|
+
export interface HostLayerVerdict {
|
|
129
|
+
/** True only when version AND content fingerprint both match the record. */
|
|
130
|
+
readonly verified: boolean;
|
|
131
|
+
/** One clause, printable, saying WHY — including how to fix a `false`. */
|
|
66
132
|
readonly reason: string;
|
|
67
133
|
}
|
|
68
134
|
|
|
@@ -72,7 +138,7 @@ export interface LockfileFacts {
|
|
|
72
138
|
readonly host_layer_fingerprint?: string | undefined;
|
|
73
139
|
}
|
|
74
140
|
|
|
75
|
-
export interface
|
|
141
|
+
export interface HostLayerInputs {
|
|
76
142
|
/** `package.json` version of the checkout being built. */
|
|
77
143
|
readonly projectVersion: string;
|
|
78
144
|
/** Parsed `installed.lock`, or `null` when no global install is recorded. */
|
|
@@ -88,31 +154,36 @@ export interface PartitionInputs {
|
|
|
88
154
|
}
|
|
89
155
|
|
|
90
156
|
/**
|
|
91
|
-
*
|
|
92
|
-
* throws, never refuses.
|
|
157
|
+
* Verify the host layer against the install record. Total function: always
|
|
158
|
+
* returns a verdict, never throws, never refuses.
|
|
159
|
+
*
|
|
160
|
+
* The order of the guards is cheapest-and-most-decisive first, so the
|
|
161
|
+
* fingerprint is computed only when everything else already agrees.
|
|
93
162
|
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
163
|
+
* **A `false` no longer changes what gets written.** It changes what the run
|
|
164
|
+
* SAYS — see `reportHostLayerVerdict`, whose warning names the remedy. The
|
|
165
|
+
* withhold decision moved to per-artefact evidence, where an unverified install
|
|
166
|
+
* that nonetheless carries an artefact is not a reason to deliver that artefact
|
|
167
|
+
* twice.
|
|
97
168
|
*/
|
|
98
|
-
export function
|
|
169
|
+
export function verifyHostLayer(inputs: HostLayerInputs): HostLayerVerdict {
|
|
99
170
|
if (!inputs.hostLayerPresent) {
|
|
100
171
|
return {
|
|
101
|
-
|
|
172
|
+
verified: false,
|
|
102
173
|
reason: 'no host-global layer on this machine',
|
|
103
174
|
};
|
|
104
175
|
}
|
|
105
176
|
const lock = inputs.lockfile;
|
|
106
177
|
if (lock === null) {
|
|
107
178
|
return {
|
|
108
|
-
|
|
179
|
+
verified: false,
|
|
109
180
|
reason: 'host layer present but no install record (installed.lock absent)',
|
|
110
181
|
};
|
|
111
182
|
}
|
|
112
183
|
const recorded = lock.agent_config_version;
|
|
113
184
|
if (!recorded) {
|
|
114
185
|
return {
|
|
115
|
-
|
|
186
|
+
verified: false,
|
|
116
187
|
reason: 'install record carries no version',
|
|
117
188
|
};
|
|
118
189
|
}
|
|
@@ -121,15 +192,15 @@ export function partitionVerdict(inputs: PartitionInputs): PartitionVerdict {
|
|
|
121
192
|
// checkout still expects, and ordering does not establish substitutability.
|
|
122
193
|
if (recorded !== inputs.projectVersion) {
|
|
123
194
|
return {
|
|
124
|
-
|
|
195
|
+
verified: false,
|
|
125
196
|
reason: `version mismatch (installed ${recorded}, building ${inputs.projectVersion})`,
|
|
126
197
|
};
|
|
127
198
|
}
|
|
128
199
|
const installedFp = lock.host_layer_fingerprint;
|
|
129
200
|
if (!installedFp) {
|
|
130
201
|
return {
|
|
131
|
-
|
|
132
|
-
reason: 'install predates host-layer fingerprinting — re-run `agent-config install` to
|
|
202
|
+
verified: false,
|
|
203
|
+
reason: 'install predates host-layer fingerprinting — re-run `agent-config install` to record one',
|
|
133
204
|
};
|
|
134
205
|
}
|
|
135
206
|
let expected: string;
|
|
@@ -137,18 +208,18 @@ export function partitionVerdict(inputs: PartitionInputs): PartitionVerdict {
|
|
|
137
208
|
expected = inputs.expectedFingerprint();
|
|
138
209
|
} catch {
|
|
139
210
|
return {
|
|
140
|
-
|
|
211
|
+
verified: false,
|
|
141
212
|
reason: 'could not compute the expected host-layer fingerprint',
|
|
142
213
|
};
|
|
143
214
|
}
|
|
144
215
|
if (installedFp !== expected) {
|
|
145
216
|
return {
|
|
146
|
-
|
|
217
|
+
verified: false,
|
|
147
218
|
reason: 'host-layer content differs from this checkout — re-run `agent-config install`',
|
|
148
219
|
};
|
|
149
220
|
}
|
|
150
221
|
return {
|
|
151
|
-
|
|
222
|
+
verified: true,
|
|
152
223
|
reason: `host layer verified at ${recorded} (fingerprint ${installedFp.slice(0, 12)})`,
|
|
153
224
|
};
|
|
154
225
|
}
|
|
@@ -167,7 +238,7 @@ export function partitionVerdict(inputs: PartitionInputs): PartitionVerdict {
|
|
|
167
238
|
* the project layer. Both defaults resolve toward "the artefact is generally
|
|
168
239
|
* useful"; only one of them is about withholding.
|
|
169
240
|
*
|
|
170
|
-
*
|
|
241
|
+
* **The state space, MEASURED rather than enumerated defensively (2026-08-21).**
|
|
171
242
|
*
|
|
172
243
|
* Three of this function's branches — unreadable file, absent `workspaces:`,
|
|
173
244
|
* empty list — all resolve to `false`, and the closure review asked whether one
|
|
@@ -206,71 +277,78 @@ export function isExclusivelyPackageOnly(source_path: string): boolean {
|
|
|
206
277
|
return raw.every((w) => String(w) === MAINTAINER_WORKSPACE);
|
|
207
278
|
}
|
|
208
279
|
|
|
209
|
-
/** Emitter for the ONE
|
|
280
|
+
/** Emitter for the ONE projection line; see the visibility note on the resolver. */
|
|
210
281
|
export type Announce = (message: string) => void;
|
|
211
282
|
|
|
212
|
-
let _memo:
|
|
283
|
+
let _memo: HostLayerVerdict | null = null;
|
|
213
284
|
let _announce: Announce = (m) => process.stdout.write(`${m}\n`);
|
|
214
285
|
|
|
215
286
|
/**
|
|
216
|
-
* Install the emitter used for the
|
|
217
|
-
* their DEFAULT output level — the first implementation used one that
|
|
218
|
-
* at `verbose`, which withheld ~100 rules while saying nothing in a
|
|
287
|
+
* Install the emitter used for the projection line. Callers pass a function
|
|
288
|
+
* visible at their DEFAULT output level — the first implementation used one that
|
|
289
|
+
* prints only at `verbose`, which withheld ~100 rules while saying nothing in a
|
|
290
|
+
* normal run.
|
|
219
291
|
*/
|
|
220
292
|
export function setPartitionAnnounce(fn: Announce): void {
|
|
221
293
|
_announce = fn;
|
|
222
294
|
}
|
|
223
295
|
|
|
224
|
-
/** Test seam — drop the memo so one process can exercise both
|
|
225
|
-
export function
|
|
296
|
+
/** Test seam — drop the memo so one process can exercise both verdicts. */
|
|
297
|
+
export function _resetHostLayerVerdictForTest(): void {
|
|
226
298
|
_memo = null;
|
|
227
299
|
}
|
|
228
300
|
|
|
229
301
|
/**
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
302
|
+
* Verify the host layer once per generation, and say so once.
|
|
303
|
+
*
|
|
304
|
+
* **What this no longer does.**
|
|
305
|
+
*
|
|
306
|
+
* It used to answer `partitionActive(projectRoot)`, and every generator gated its
|
|
307
|
+
* withhold on that one boolean. **That entry point is deleted** (owner decision,
|
|
308
|
+
* 2026-09-07). The project layer's contents are now decided per artefact, on the
|
|
309
|
+
* host layer's own contents, by `claudeLayerCarriage.keepInProjectLayer` and
|
|
310
|
+
* `globalRuleLayers.hostLayerCarries`. A version number is not evidence about an
|
|
311
|
+
* artefact, and using it as one delivered 261 skills and 29 personas twice per
|
|
312
|
+
* session for as long as an install lagged a release.
|
|
313
|
+
*
|
|
314
|
+
* So what survives is the DIAGNOSTIC. **Both council seats (2026-08-20, 2/2)
|
|
315
|
+
* required that generation print the mode it selected** rather than partition
|
|
316
|
+
* silently, and that requirement is still live — but this function is no longer
|
|
317
|
+
* where it is met, and the first version of this note claimed otherwise.
|
|
318
|
+
*
|
|
319
|
+
* **Corrected 2026-09-07 after a neutral review.** The note read "it still
|
|
320
|
+
* prints". It did not: removing `partitionActive` removed `condense.ts`'s only
|
|
321
|
+
* call into this resolver, so the emitter that file installed became dead and
|
|
322
|
+
* `task generate-tools` withheld ~299 skills and 29 personas while printing
|
|
323
|
+
* nothing about the layer it withheld against. The line is now emitted by
|
|
324
|
+
* `report_layer_overlap`, the step that already runs immediately after the
|
|
325
|
+
* generator in the same chain — see the comment there for why it lives in that
|
|
326
|
+
* file rather than in the 2,700-line generator.
|
|
327
|
+
*
|
|
328
|
+
* **What that line does and does not carry**, since a second review asked: it
|
|
329
|
+
* reports the VERIFICATION state of the layer being withheld against, not a
|
|
330
|
+
* withhold count, and this verdict decides nothing. The counts are the
|
|
331
|
+
* generator's own summary one line above (`skills=N`,
|
|
332
|
+
* `command_skills=N (M withheld …)`). So the council requirement is met by the
|
|
333
|
+
* pair of lines rather than by this one — stated that way instead of claiming
|
|
334
|
+
* the line alone honours it.
|
|
335
|
+
*
|
|
336
|
+
* The caller still supplies `announce`, and the level still matters — the first
|
|
337
|
+
* implementation used an `info()` that prints only at `verbose`. Residual,
|
|
338
|
+
* stated: at an explicitly silent output level the line is dropped.
|
|
260
339
|
*
|
|
261
340
|
* The fingerprint compares the host layer against **what the installer recorded
|
|
262
|
-
* when it wrote that layer**, not against a re-derivation from source
|
|
263
|
-
*
|
|
264
|
-
* make the partition permanently unreachable rather than merely inactive — and it
|
|
265
|
-
* is the property the partition needs: the omitted artefacts are still present,
|
|
266
|
-
* in the form this version's installer left them.
|
|
341
|
+
* when it wrote that layer**, not against a re-derivation from source — the
|
|
342
|
+
* property that keeps a mismatch informative rather than permanent.
|
|
267
343
|
*
|
|
268
344
|
* **Known residual:** an installer that crashes mid-write and still reaches the
|
|
269
345
|
* lockfile would fingerprint its own partial layer, and that fingerprint then
|
|
270
346
|
* verifies. Ordering narrows the window (the lockfile is written last) without
|
|
271
|
-
* closing it
|
|
347
|
+
* closing it. It now costs less than it did: the withhold no longer trusts this
|
|
348
|
+
* verdict, so a wrongly-verified layer misreports a line rather than authorising
|
|
349
|
+
* a removal.
|
|
272
350
|
*/
|
|
273
|
-
export function
|
|
351
|
+
export function resolveHostLayerVerdict(projectRoot: string): HostLayerVerdict {
|
|
274
352
|
if (_memo !== null) {
|
|
275
353
|
return _memo;
|
|
276
354
|
}
|
|
@@ -286,15 +364,19 @@ export function resolvePartitionVerdict(projectRoot: string): PartitionVerdict {
|
|
|
286
364
|
try {
|
|
287
365
|
lock = read_lockfile();
|
|
288
366
|
} catch {
|
|
289
|
-
lock = null; // unreadable record →
|
|
367
|
+
lock = null; // unreadable record → report it as unverified
|
|
290
368
|
}
|
|
291
|
-
_memo =
|
|
369
|
+
_memo = verifyHostLayer({
|
|
292
370
|
projectVersion: current_package_version(projectRoot),
|
|
293
371
|
lockfile: lock,
|
|
294
372
|
hostLayerPresent: present,
|
|
295
373
|
expectedFingerprint: () => fingerprintLayers(layers),
|
|
296
374
|
});
|
|
297
|
-
_announce(
|
|
375
|
+
_announce(
|
|
376
|
+
_memo.verified
|
|
377
|
+
? `projection: project layer carries only what ~/.claude lacks — ${_memo.reason}`
|
|
378
|
+
: `projection: project layer carries only what ~/.claude lacks · ⚠️ host layer UNVERIFIED — ${_memo.reason}`,
|
|
379
|
+
);
|
|
298
380
|
return _memo;
|
|
299
381
|
}
|
|
300
382
|
|
|
@@ -316,7 +398,7 @@ export function resolvePartitionVerdict(projectRoot: string): PartitionVerdict {
|
|
|
316
398
|
* errors are not symmetric and this must never prefer the second.
|
|
317
399
|
*
|
|
318
400
|
* Lives here rather than inline in `install.ts` for the same reason
|
|
319
|
-
* {@link
|
|
401
|
+
* {@link resolveHostLayerVerdict} does: it belongs beside the predicate it
|
|
320
402
|
* serves, and `install.ts` is 5,000+ lines where the source-size ratchet counts
|
|
321
403
|
* every added line.
|
|
322
404
|
*
|
|
@@ -360,29 +442,31 @@ export function stampHostLayerFingerprint(
|
|
|
360
442
|
* names**, and neither `check_single_delivery` nor `_lib/layer_overlap_notice`
|
|
361
443
|
* looked, because `personas` was in neither's `TYPES`.
|
|
362
444
|
*
|
|
363
|
-
*
|
|
445
|
+
* **Three properties the caller depends on.**
|
|
364
446
|
*
|
|
365
|
-
* **Scoped to `.claude/` only.**
|
|
366
|
-
*
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
*
|
|
447
|
+
* **Scoped to `.claude/` only.** The evidence is `~/.claude/personas`. It says
|
|
448
|
+
* nothing about `~/.cursor`, so withholding a cursor persona on the strength of
|
|
449
|
+
* a claude directory listing would deliver it nowhere — the one failure the
|
|
450
|
+
* fail-safe design exists to prevent. Every other tool directory keeps the full
|
|
451
|
+
* projection.
|
|
370
452
|
*
|
|
371
|
-
* **
|
|
453
|
+
* **Withheld PER NAME, on that directory's own contents** (owner decision,
|
|
454
|
+
* 2026-09-07). This used to gate on `partitionActive` — a version and fingerprint
|
|
455
|
+
* check against `installed.lock` — which meant an install one release behind
|
|
456
|
+
* delivered all 29 personas twice. A persona `~/.claude/personas` actually holds
|
|
457
|
+
* is withheld whatever the lockfile says; one it lacks stays, on its own, without
|
|
458
|
+
* keeping the other 28 duplicates alive with it.
|
|
459
|
+
*
|
|
460
|
+
* **Reconciliation is the shorter list, not a second code path.** The caller's
|
|
372
461
|
* stale-symlink sweep removes any link whose name is absent from the list it was
|
|
373
|
-
* given for that directory, so
|
|
374
|
-
*
|
|
462
|
+
* given for that directory, so a shorter list empties what an earlier version
|
|
463
|
+
* populated. A gate that only declined to WRITE would leave the existing
|
|
375
464
|
* duplicate standing — a partition that stops new duplication and keeps the old
|
|
376
465
|
* is not a partition.
|
|
377
|
-
*
|
|
378
|
-
* Verified before shipping: every one of the 29 project personas is present in
|
|
379
|
-
* the global layer (32 there, a strict superset). The partition is a removal and
|
|
380
|
-
* has no repair path, so withholding is only safe once the surviving layer is
|
|
381
|
-
* known to carry what is withheld.
|
|
382
466
|
*/
|
|
383
467
|
export function personaPartition(
|
|
384
|
-
projectRoot: string,
|
|
385
468
|
all: readonly string[],
|
|
469
|
+
home?: string,
|
|
386
470
|
): {
|
|
387
471
|
readonly all: readonly string[];
|
|
388
472
|
listFor: (toolDir: string) => readonly string[];
|
|
@@ -398,34 +482,42 @@ export function personaPartition(
|
|
|
398
482
|
*/
|
|
399
483
|
countFor: (toolDir: string) => number;
|
|
400
484
|
} {
|
|
401
|
-
const
|
|
485
|
+
const claudeList = keepInProjectLayer('personas', all, home);
|
|
486
|
+
const withheld = all.length - claudeList.length;
|
|
402
487
|
return {
|
|
403
488
|
all,
|
|
404
|
-
listFor: (toolDir) => (
|
|
405
|
-
note:
|
|
406
|
-
|
|
489
|
+
listFor: (toolDir) => personaListFor(toolDir, all, claudeList),
|
|
490
|
+
note:
|
|
491
|
+
withheld > 0
|
|
492
|
+
? ` — .claude/ withheld ${String(withheld)}: ADR-236, those personas arrive from ~/.claude`
|
|
493
|
+
: '',
|
|
494
|
+
countFor: (toolDir) => personaListFor(toolDir, all, claudeList).length,
|
|
407
495
|
};
|
|
408
496
|
}
|
|
409
497
|
|
|
410
498
|
/**
|
|
411
|
-
* The pure half of {@link personaPartition}:
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
* Split out so the decision is testable in BOTH directions
|
|
415
|
-
*
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
*
|
|
499
|
+
* The pure half of {@link personaPartition}: a Claude tool directory gets the
|
|
500
|
+
* narrowed list, every other directory gets the full one.
|
|
501
|
+
*
|
|
502
|
+
* Split out so the decision is testable in BOTH directions without reading this
|
|
503
|
+
* machine's `~/.claude`. A test over the un-injected path can only assert
|
|
504
|
+
* whatever the machine happens to be — a test that passes either way and
|
|
505
|
+
* therefore proves nothing. The two properties worth pinning are exactly the two
|
|
506
|
+
* this signature exposes: `.claude/` gets the narrowed list, and NOTHING else
|
|
507
|
+
* ever does.
|
|
420
508
|
*/
|
|
421
|
-
export function
|
|
422
|
-
|
|
509
|
+
export function personaListFor(
|
|
510
|
+
toolDir: string,
|
|
511
|
+
all: readonly string[],
|
|
512
|
+
claudeList: readonly string[],
|
|
513
|
+
): readonly string[] {
|
|
514
|
+
return toolDir.startsWith('.claude/') ? claudeList : all;
|
|
423
515
|
}
|
|
424
516
|
|
|
425
517
|
/**
|
|
426
518
|
* Does the project layer withhold the colon-form `/cluster:sub` commands?
|
|
427
519
|
*
|
|
428
|
-
*
|
|
520
|
+
* **The claim this replaces, and the measurement that revised it.**
|
|
429
521
|
*
|
|
430
522
|
* `generate_claude_project_commands` was written on the reasoning that "Claude
|
|
431
523
|
* Code dedupes project and user scope by name, so the two copies of
|
|
@@ -447,16 +539,24 @@ export function personaWithheldFor(toolDir: string, active: boolean): boolean {
|
|
|
447
539
|
* exists, those 40 symlinks are written, deduped away, and LOSE. They are dead
|
|
448
540
|
* weight there — not a second listing, and not a reachability guarantee either.
|
|
449
541
|
*
|
|
450
|
-
*
|
|
542
|
+
* **Why the predicate is the DIRECTORY, not `installed.lock`.**
|
|
451
543
|
*
|
|
452
|
-
*
|
|
453
|
-
* `installed.lock
|
|
454
|
-
*
|
|
455
|
-
*
|
|
456
|
-
*
|
|
457
|
-
*
|
|
544
|
+
* This used to be `partitionActive` — true only when a global layer was present
|
|
545
|
+
* AND verified against `installed.lock` by version and fingerprint. That gets
|
|
546
|
+
* the measurement above backwards in the one case that matters: the project copy
|
|
547
|
+
* loses whenever the global copy EXISTS, and whether the recorded version is a
|
|
548
|
+
* release behind has no bearing on whether it exists. An install one release old
|
|
549
|
+
* therefore kept writing 40 symlinks the host deduped away — dead weight, chosen
|
|
550
|
+
* on a fact about a version number.
|
|
458
551
|
*
|
|
459
|
-
*
|
|
552
|
+
* So the evidence is the host directory itself, asked per name (owner decision,
|
|
553
|
+
* 2026-09-07): `~/.claude/commands/<cluster>/<sub>.md` for a clustered command,
|
|
554
|
+
* `~/.claude/commands/<slug>.md` for a flat one. Present → the project copy loses
|
|
555
|
+
* and is withheld. Absent → the project copy is the only reachable one and stays.
|
|
556
|
+
* Unreadable layer → nothing is withheld. The fail-safe direction is unchanged;
|
|
557
|
+
* it is now per artefact rather than per repository.
|
|
558
|
+
*
|
|
559
|
+
* **Honest limits.**
|
|
460
560
|
*
|
|
461
561
|
* Self-report, n=1 per condition, one host version, one machine. What is NOT
|
|
462
562
|
* claimed: that older or newer hosts dedupe the same way, or that precedence is
|
|
@@ -468,22 +568,56 @@ export function personaWithheldFor(toolDir: string, active: boolean): boolean {
|
|
|
468
568
|
* It cannot. When this predicate returns true the project copy is never written,
|
|
469
569
|
* so there is no second copy for the host to double-list or for the gate to
|
|
470
570
|
* count — it would report zero overlap while the assumption underneath had
|
|
471
|
-
* failed.
|
|
472
|
-
*
|
|
473
|
-
*
|
|
571
|
+
* failed. The 2026-09-07 change does not repair that: reading the directory is
|
|
572
|
+
* evidence about PRESENCE, never about how the host resolves two copies of a
|
|
573
|
+
* name, so a host that stopped deduping is still undetected here.
|
|
474
574
|
*
|
|
475
575
|
* What actually holds: reachability survives a dedup change either way, because
|
|
476
576
|
* the global copy is delivered regardless. What is lost is only the project
|
|
477
577
|
* copy's redundancy, and nothing detects that. A real detector would have to
|
|
478
578
|
* pin the host version this measurement was taken against and re-probe when it
|
|
479
579
|
* moves — not built here, and named as absent rather than implied away.
|
|
580
|
+
*
|
|
581
|
+
* **Flat wrappers ARE withheld — corrected 2026-09-07 after a neutral review.**
|
|
582
|
+
*
|
|
583
|
+
* A CLUSTERED command reaches Claude Code as `/cluster:sub` from a `.md` under
|
|
584
|
+
* `commands/`, and `~/.claude/commands/` carries 41 such cluster directories — so
|
|
585
|
+
* the project copy has somewhere to lose to, and this predicate withholds it.
|
|
586
|
+
*
|
|
587
|
+
* A FLAT command reaches the host as a hyphen-named wrapper under `skills/`,
|
|
588
|
+
* because the host does not register flat command FILES (probed ≤ 2.1.204).
|
|
589
|
+
*
|
|
590
|
+
* **The first version of this note then claimed `~/.claude/skills` carries NONE
|
|
591
|
+
* of those wrappers, that "nothing else delivers them", and left
|
|
592
|
+
* `generate_claude_commands` ungated on that basis. All three were wrong.**
|
|
593
|
+
* `install.ts::_apply_claude_flat_command_wrappers`, wired for every
|
|
594
|
+
* `claude-code` deploy, writes `~/.claude/skills/<slug>/SKILL.md` for every
|
|
595
|
+
* VISIBLE flat command and deletes the flat file. The claim was a snapshot of one
|
|
596
|
+
* machine whose last install had not run that pass — 53 flat `.md` files still
|
|
597
|
+
* sat in `~/.claude/commands` — presented as a property of the installer.
|
|
598
|
+
*
|
|
599
|
+
* What it would have cost: the next `agent-config install` writes ~17 wrappers
|
|
600
|
+
* globally while the generator keeps writing all 49 project wrappers, so 17 flat
|
|
601
|
+
* commands arrive TWICE per session — the duplication class ADR-236 exists to
|
|
602
|
+
* remove — and `check_single_delivery` reports a `skills` overlap the same note
|
|
603
|
+
* said could not happen.
|
|
604
|
+
*
|
|
605
|
+
* So the wrapper emitter now applies the same per-name rule as everything else:
|
|
606
|
+
* `claudeLayerHolds('skills', slug)` withholds a wrapper the host layer carries,
|
|
607
|
+
* and keeps one it does not. Fail-safe per artefact, no repo-wide switch.
|
|
608
|
+
*
|
|
609
|
+
* A number in that note was wrong too, and is corrected here rather than left to
|
|
610
|
+
* be quoted: the project layer carries **49** wrappers, not 153. `153` was
|
|
611
|
+
* transplanted from the 153-skill measurement in `hostLayerFingerprint.ts`.
|
|
612
|
+
*
|
|
613
|
+
* @param globalName the host-side name — `<cluster>/<sub>.md` or `<slug>.md`.
|
|
480
614
|
*/
|
|
481
|
-
export function
|
|
482
|
-
return
|
|
615
|
+
export function commandWithheld(globalName: string, home?: string): boolean {
|
|
616
|
+
return claudeLayerHolds('commands', globalName, home);
|
|
483
617
|
}
|
|
484
618
|
|
|
485
619
|
/**
|
|
486
|
-
*
|
|
620
|
+
* **The caller's early return, flagged by review and kept.**
|
|
487
621
|
*
|
|
488
622
|
* `generate_claude_project_commands` applies this predicate and then returns
|
|
489
623
|
* early when `src/domains/` is absent — BEFORE its stale-link sweep. So a tree
|