@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
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binary-payload eligibility — the mechanically verified predicate that
|
|
3
|
+
* reopened the `binary` class.
|
|
4
|
+
*
|
|
5
|
+
* WHAT THIS IS, SAID PLAINLY.
|
|
6
|
+
*
|
|
7
|
+
* `f573d628c` closed `binary` as a hard zero on 2026-09-07. This REOPENS it,
|
|
8
|
+
* 24 hours later, and that IS A WEAKENING OF A CONTROL. An AI council
|
|
9
|
+
* (2026-09-08, 2 seats, unanimous, deep tier) authorised it, and one seat
|
|
10
|
+
* explicitly refused the framing that this is merely "correcting an
|
|
11
|
+
* over-generalisation" — that distinction is cosmetic. The control was closed
|
|
12
|
+
* for the wrong reason, so reopening it is a weakening: justified, and still a
|
|
13
|
+
* weakening. It is written down here rather than softened because a control
|
|
14
|
+
* that gets quietly relaxed is worse than one that never existed.
|
|
15
|
+
*
|
|
16
|
+
* WHY THE CLOSURE WAS WRONG.
|
|
17
|
+
*
|
|
18
|
+
* Decision A over-generalised. Its reasoning was "the three observed binaries
|
|
19
|
+
* are unused, therefore no binary can ever be required" — an invalid
|
|
20
|
+
* generalisation from an observed sample to a universal rule. The vendored
|
|
21
|
+
* tree-sitter grammars are the direct counterexample: bytes the production path
|
|
22
|
+
* loads and the engine requires. A textual placeholder was lossless for three
|
|
23
|
+
* PNGs nothing decoded; for a grammar it does not degrade a fixture, it breaks
|
|
24
|
+
* the engine.
|
|
25
|
+
*
|
|
26
|
+
* WHAT REPLACES THE HARD ZERO — AND WHAT DOES NOT.
|
|
27
|
+
*
|
|
28
|
+
* Not a prose exception. A binary is admitted only when EVERY condition below
|
|
29
|
+
* is verified mechanically, on the bytes that are actually in the tarball:
|
|
30
|
+
*
|
|
31
|
+
* 1. exactly one manifest entry for the path
|
|
32
|
+
* 2. no wildcard or directory grant anywhere in the manifest
|
|
33
|
+
* 3. the packed tarball really contains the path
|
|
34
|
+
* 4. sha256(bytes) equals the manifest hash
|
|
35
|
+
* 5. size equals the manifest size
|
|
36
|
+
* 6. kind is `tree-sitter-wasm-grammar`
|
|
37
|
+
* 7. WASM magic and version are valid
|
|
38
|
+
* 8. the PRODUCTION loader reports the manifest's grammar_id
|
|
39
|
+
* 9. the PRODUCTION loader reports the manifest's ABI
|
|
40
|
+
* 10. a defined invalid-header mutation of the bytes is REJECTED
|
|
41
|
+
* 11. a defined unsupported-ABI expectation is REJECTED
|
|
42
|
+
*
|
|
43
|
+
* 8 and 9 go through `probeGrammarFile`, the engine's own loader, deliberately:
|
|
44
|
+
* a second implementation inside the gate could accept bytes the engine
|
|
45
|
+
* rejects, which is exactly the gap that would let a broken grammar ship while
|
|
46
|
+
* the gate called it valid.
|
|
47
|
+
*
|
|
48
|
+
* 10 and 11 run on every invocation, not in a separate test, because a
|
|
49
|
+
* predicate whose refusals nobody exercises is a predicate nobody has checked.
|
|
50
|
+
*
|
|
51
|
+
* `archive` IS NOT REOPENED.
|
|
52
|
+
*
|
|
53
|
+
* This collision produced evidence about grammars and none whatsoever about
|
|
54
|
+
* archives. `archive` stays a hard zero with no manifest path, and this module
|
|
55
|
+
* offers none.
|
|
56
|
+
*
|
|
57
|
+
* ELIGIBILITY NEVER TOUCHES THE MEASUREMENT.
|
|
58
|
+
*
|
|
59
|
+
* The gate reports `observed` as the true count and byte total on every run.
|
|
60
|
+
* Compliance is `observed <= allowed`, where `allowed` is the number of entries
|
|
61
|
+
* that passed every condition above — never `observed` minus something excused.
|
|
62
|
+
* A file that fails one condition lowers `allowed`; it never disappears from
|
|
63
|
+
* `observed`.
|
|
64
|
+
*/
|
|
65
|
+
import { createHash } from 'node:crypto';
|
|
66
|
+
import * as fs from 'node:fs';
|
|
67
|
+
import * as path from 'node:path';
|
|
68
|
+
|
|
69
|
+
/** One manifest row. Path-exact; `why` is documentation, never a condition. */
|
|
70
|
+
export interface PackedBinaryEntry {
|
|
71
|
+
readonly path: string;
|
|
72
|
+
readonly sha256: string;
|
|
73
|
+
readonly size: number;
|
|
74
|
+
readonly kind: string;
|
|
75
|
+
readonly grammar_id: string;
|
|
76
|
+
readonly abi: number;
|
|
77
|
+
readonly why?: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface PackedBinaryManifest {
|
|
81
|
+
readonly schema_version: number;
|
|
82
|
+
readonly entries: readonly PackedBinaryEntry[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The one kind this predicate knows how to verify. */
|
|
86
|
+
export const VERIFIABLE_KIND = 'tree-sitter-wasm-grammar';
|
|
87
|
+
|
|
88
|
+
/** What the production loader reported for a file. */
|
|
89
|
+
export interface GrammarProbe {
|
|
90
|
+
readonly abi: number;
|
|
91
|
+
readonly grammar_id: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** A per-file verdict, with the failing condition named. */
|
|
95
|
+
export interface EligibilityVerdict {
|
|
96
|
+
readonly path: string;
|
|
97
|
+
readonly eligible: boolean;
|
|
98
|
+
/** Every condition that failed, in predicate order. Empty iff eligible. */
|
|
99
|
+
readonly failures: readonly string[];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** WASM preamble: `\0asm` followed by little-endian version 1. */
|
|
103
|
+
const WASM_MAGIC = [0x00, 0x61, 0x73, 0x6d] as const;
|
|
104
|
+
const WASM_VERSION = [0x01, 0x00, 0x00, 0x00] as const;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Condition 7, on its own so the header mutation in condition 10 has something
|
|
108
|
+
* specific to defeat.
|
|
109
|
+
*/
|
|
110
|
+
export function wasmMagicAndVersionValid(bytes: Uint8Array): boolean {
|
|
111
|
+
if (bytes.length < 8) return false;
|
|
112
|
+
for (let i = 0; i < 4; i += 1) if (bytes[i] !== WASM_MAGIC[i]) return false;
|
|
113
|
+
for (let i = 0; i < 4; i += 1) if (bytes[4 + i] !== WASM_VERSION[i]) return false;
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The DEFINED invalid-header mutation for condition 10.
|
|
119
|
+
*
|
|
120
|
+
* One byte of the magic, flipped. Deliberately NOT `corrupt(bytes)`: arbitrary
|
|
121
|
+
* corruption can land in a custom section and leave a still-valid module, or
|
|
122
|
+
* hit padding and change nothing, so a test built on it can pass while proving
|
|
123
|
+
* nothing. This targets a property the predicate actually validates.
|
|
124
|
+
*/
|
|
125
|
+
export function definedInvalidHeaderMutation(bytes: Uint8Array): Uint8Array {
|
|
126
|
+
const out = Uint8Array.from(bytes);
|
|
127
|
+
if (out.length > 1) out[1] = (out[1] as number) ^ 0xff;
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The DEFINED unsupported-ABI expectation for condition 11.
|
|
133
|
+
*
|
|
134
|
+
* The ABI is not a byte at a known offset — it is what the loader reports after
|
|
135
|
+
* parsing the module — so this mutates the EXPECTATION rather than the bytes:
|
|
136
|
+
* an entry claiming an ABI the grammar does not have must be refused. Same
|
|
137
|
+
* validated property, targeted from the side where it can be stated exactly.
|
|
138
|
+
*/
|
|
139
|
+
export function definedUnsupportedAbiEntry(entry: PackedBinaryEntry): PackedBinaryEntry {
|
|
140
|
+
return { ...entry, abi: entry.abi + 1 };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Condition 2, evaluated over the whole manifest rather than per file. */
|
|
144
|
+
export function wildcardGrants(manifest: PackedBinaryManifest): string[] {
|
|
145
|
+
return manifest.entries
|
|
146
|
+
.filter((e) => e.path.includes('*') || e.path.includes('?') || e.path.endsWith('/'))
|
|
147
|
+
.map((e) => e.path);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Paths carrying more than one row — condition 1's uniqueness half. */
|
|
151
|
+
export function duplicatePaths(manifest: PackedBinaryManifest): string[] {
|
|
152
|
+
const seen = new Map<string, number>();
|
|
153
|
+
for (const e of manifest.entries) seen.set(e.path, (seen.get(e.path) ?? 0) + 1);
|
|
154
|
+
return [...seen.entries()].filter(([, n]) => n > 1).map(([p]) => p);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Evaluate every condition for one packed file.
|
|
159
|
+
*
|
|
160
|
+
* `probe` is injected so the caller decides how the production loader is
|
|
161
|
+
* reached — in the gate it is a child process, in a test it can be a stub that
|
|
162
|
+
* returns a wrong ABI. `null` means the loader could not answer, which fails
|
|
163
|
+
* conditions 8 and 9 rather than passing them by default.
|
|
164
|
+
*/
|
|
165
|
+
export function evaluateEntry(options: {
|
|
166
|
+
readonly filePath: string;
|
|
167
|
+
readonly packedPaths: ReadonlySet<string>;
|
|
168
|
+
readonly manifest: PackedBinaryManifest;
|
|
169
|
+
readonly bytes: Uint8Array | null;
|
|
170
|
+
readonly probe: GrammarProbe | null;
|
|
171
|
+
}): EligibilityVerdict {
|
|
172
|
+
const { filePath, packedPaths, manifest, bytes, probe } = options;
|
|
173
|
+
const failures: string[] = [];
|
|
174
|
+
|
|
175
|
+
const rows = manifest.entries.filter((e) => e.path === filePath);
|
|
176
|
+
if (rows.length === 0) failures.push('no manifest entry for this path');
|
|
177
|
+
if (rows.length > 1) failures.push('more than one manifest entry for this path');
|
|
178
|
+
const wild = wildcardGrants(manifest);
|
|
179
|
+
if (wild.length > 0) failures.push(`manifest carries wildcard or directory grants: ${wild.join(', ')}`);
|
|
180
|
+
if (!packedPaths.has(filePath)) failures.push('path is not in the packed tarball');
|
|
181
|
+
|
|
182
|
+
const entry = rows[0];
|
|
183
|
+
if (entry === undefined) return { path: filePath, eligible: false, failures };
|
|
184
|
+
|
|
185
|
+
if (bytes === null) {
|
|
186
|
+
failures.push('bytes could not be read');
|
|
187
|
+
return { path: filePath, eligible: false, failures };
|
|
188
|
+
}
|
|
189
|
+
const digest = createHash('sha256').update(bytes).digest('hex');
|
|
190
|
+
if (digest !== entry.sha256) failures.push(`sha256 mismatch: ${digest} != ${entry.sha256}`);
|
|
191
|
+
if (bytes.length !== entry.size) failures.push(`size mismatch: ${String(bytes.length)} != ${String(entry.size)}`);
|
|
192
|
+
if (entry.kind !== VERIFIABLE_KIND) failures.push(`kind is not ${VERIFIABLE_KIND}: ${entry.kind}`);
|
|
193
|
+
if (!wasmMagicAndVersionValid(bytes)) failures.push('WASM magic or version invalid');
|
|
194
|
+
|
|
195
|
+
if (probe === null) {
|
|
196
|
+
failures.push('the production loader could not read this file');
|
|
197
|
+
} else {
|
|
198
|
+
if (probe.grammar_id !== entry.grammar_id) {
|
|
199
|
+
failures.push(`grammar_id mismatch: loader says ${probe.grammar_id}, manifest says ${entry.grammar_id}`);
|
|
200
|
+
}
|
|
201
|
+
if (probe.abi !== entry.abi) {
|
|
202
|
+
failures.push(`ABI mismatch: loader says ${String(probe.abi)}, manifest says ${String(entry.abi)}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Conditions 10 and 11 — the predicate refusing what it must refuse. Run
|
|
207
|
+
// here, on every invocation, so a refusal path is never merely asserted in
|
|
208
|
+
// a test somebody can skip.
|
|
209
|
+
if (wasmMagicAndVersionValid(definedInvalidHeaderMutation(bytes))) {
|
|
210
|
+
failures.push('self-check failed: a defined invalid-header mutation was NOT rejected');
|
|
211
|
+
}
|
|
212
|
+
if (probe !== null && probe.abi === definedUnsupportedAbiEntry(entry).abi) {
|
|
213
|
+
failures.push('self-check failed: a defined unsupported-ABI expectation was NOT rejected');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return { path: filePath, eligible: failures.length === 0, failures };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Read and shape-check the manifest. Throws rather than degrading to empty. */
|
|
220
|
+
export function loadManifest(repoRoot: string): PackedBinaryManifest {
|
|
221
|
+
const p = path.join(repoRoot, 'src', 'config', 'packed-binary-manifest.json');
|
|
222
|
+
const raw = JSON.parse(fs.readFileSync(p, 'utf-8')) as PackedBinaryManifest;
|
|
223
|
+
if (!Array.isArray(raw.entries)) throw new Error(`packed-binary-manifest: entries must be an array (${p})`);
|
|
224
|
+
return raw;
|
|
225
|
+
}
|
|
@@ -253,10 +253,23 @@ function _parse_cli(argv: string[]): { cwd: string; installed: string; forward:
|
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
// Script entry — mirrors the Python `if __name__ == "__main__"` block.
|
|
256
|
+
// `build:delegate-aux` bundles this file with `--splitting`, which can move the
|
|
257
|
+
// module body into a shared chunk: `import.meta.url` then names the CHUNK, the
|
|
258
|
+
// URL comparison below never matches, and the entry silently no-ops. Inside that
|
|
259
|
+
// bundle the invoked file name is the reliable signal (same pattern as
|
|
260
|
+
// src/scripts/_cli/cmd_migrate.ts). Defined by that build only.
|
|
261
|
+
declare const __AGENT_CONFIG_CLI_DELEGATE__: boolean | undefined;
|
|
256
262
|
function _isCliEntry(): boolean {
|
|
257
263
|
if (process.argv[1] === undefined) {
|
|
258
264
|
return false;
|
|
259
265
|
}
|
|
266
|
+
if (typeof __AGENT_CONFIG_CLI_DELEGATE__ !== 'undefined' && __AGENT_CONFIG_CLI_DELEGATE__) {
|
|
267
|
+
// A miss falls THROUGH to the URL comparison below, which is the
|
|
268
|
+
// symlinked-invocation case that fallback exists for.
|
|
269
|
+
if (path.basename(process.argv[1], '.js') === 'pin_resolver') {
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
260
273
|
const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
|
|
261
274
|
if (import.meta.url === argvUrl) {
|
|
262
275
|
return true;
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* of them resolves here, so the substitution is STATED rather than hidden.
|
|
19
19
|
*
|
|
20
20
|
* · `agent-config code-graph` — the native code-graph engine (ADR-124). It
|
|
21
|
-
*
|
|
21
|
+
* shipped `hooks.code_graph.enabled: false` (a flag retired 2026-09-07)
|
|
22
22
|
* (`src/config/agent-settings.template.yml:1373-1374`), and
|
|
23
23
|
* `agent-config code-graph detect` in this checkout answers
|
|
24
24
|
* `no code-graph source detected`. There is no index to select against.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Roadmap `status:` values this repository has RETIRED, and the diagnostic a
|
|
3
|
+
* reader gets when one survives in a file.
|
|
4
|
+
*
|
|
5
|
+
* `status: carrier` was deleted by ADR-262. It had two jobs welded together: a
|
|
6
|
+
* structural one (this roadmap is the live destination of an archived parent's
|
|
7
|
+
* `[~]` carries) and an authority one (an autonomous run may not act on it
|
|
8
|
+
* until a human flips it to `ready`). The second was authored by the repository
|
|
9
|
+
* about its own maintainer, was enforced by nothing, and contradicted the drain
|
|
10
|
+
* command's own FORBIDDEN NON-HALT REASONS. The first is a property of the
|
|
11
|
+
* deferral EDGES, which `lint_deferral_integrity` already verifies from both
|
|
12
|
+
* ends, so nothing was left for a node-level status to carry.
|
|
13
|
+
*
|
|
14
|
+
* Deleting a vocabulary term silently is the failure this module exists to
|
|
15
|
+
* prevent: an old file would keep a value nothing reads, and the next author
|
|
16
|
+
* would infer a meaning from its presence. So a retired value is REJECTED with
|
|
17
|
+
* a migration diagnostic rather than ignored.
|
|
18
|
+
*
|
|
19
|
+
* The read is frontmatter-scoped on purpose, and that is inherited rather than
|
|
20
|
+
* newly decided: a fenced `status: carrier` inside a roadmap documenting the
|
|
21
|
+
* syntax is not a declaration, and a body-wide match turned exactly that into a
|
|
22
|
+
* hard failure before a review caught it.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** Retired `status:` values, mapped to the record that retired each one. */
|
|
26
|
+
export const RETIRED_STATUS_VALUES: ReadonlyMap<string, string> = new Map([
|
|
27
|
+
['carrier', 'ADR-262'],
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
/** The leading `---\n…\n---` block, or null when the text carries none. */
|
|
31
|
+
export function frontmatterBlock(text: string): string | null {
|
|
32
|
+
const m = /^---\n([\s\S]*?)\n---/.exec(text);
|
|
33
|
+
return m === null ? null : (m[1] ?? '');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The frontmatter `status:` value, lower-cased, or null when absent. */
|
|
37
|
+
export function frontmatterStatus(text: string | null | undefined): string | null {
|
|
38
|
+
if (text === null || text === undefined) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const fm = frontmatterBlock(text);
|
|
42
|
+
if (fm === null) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const m = /^status:[ \t]*([A-Za-z-]+)[ \t]*$/m.exec(fm);
|
|
46
|
+
return m === null ? null : (m[1] ?? '').toLowerCase();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The retired value this text declares, or null when it declares none.
|
|
51
|
+
*
|
|
52
|
+
* Returns the value rather than a boolean so the caller can name it in the
|
|
53
|
+
* diagnostic — "status: carrier is retired" is actionable, "invalid status" is
|
|
54
|
+
* not.
|
|
55
|
+
*/
|
|
56
|
+
export function declaresRetiredStatus(text: string | null | undefined): string | null {
|
|
57
|
+
const status = frontmatterStatus(text);
|
|
58
|
+
if (status === null) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return RETIRED_STATUS_VALUES.has(status) ? status : null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The migration diagnostic for a retired value. Names the record and the fix. */
|
|
65
|
+
export function retiredStatusDiagnostic(value: string): string {
|
|
66
|
+
const record = RETIRED_STATUS_VALUES.get(value) ?? 'a decision record';
|
|
67
|
+
return (
|
|
68
|
+
`declares the retired \`status: ${value}\`, deleted by ${record}. ` +
|
|
69
|
+
'A `status:` value no longer decides whether an autonomous run may act on ' +
|
|
70
|
+
'a roadmap. Use `status: ready` and put any genuine human-ACTION gate in a ' +
|
|
71
|
+
'`## Blockers` entry, or park the file under `agents/roadmaps/later/` with ' +
|
|
72
|
+
'an `entry_condition`. Both are read by a gate; a status is not.'
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -75,40 +75,146 @@
|
|
|
75
75
|
*/
|
|
76
76
|
|
|
77
77
|
import * as fs from 'node:fs';
|
|
78
|
+
import * as os from 'node:os';
|
|
78
79
|
import * as path from 'node:path';
|
|
79
80
|
|
|
80
|
-
/**
|
|
81
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Catalogue roots under the WORKSPACE, in precedence order.
|
|
83
|
+
*
|
|
84
|
+
* `src/skills` is the authored tree and exists only in a maintainer checkout.
|
|
85
|
+
* `.claude/skills` is the project-scope projection — a consumer's own skills
|
|
86
|
+
* live there, and so do this repository's flat-command wrappers.
|
|
87
|
+
*/
|
|
88
|
+
export const PROJECT_CATALOGUE_ROOTS = ['src/skills', '.claude/skills'] as const;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The catalogue root under the user's HOME — what the host itself delivers.
|
|
92
|
+
*
|
|
93
|
+
* Home-relative rather than workspace-relative, which is why it cannot live in
|
|
94
|
+
* the list above: `~/.claude/skills` is one directory for every project on the
|
|
95
|
+
* machine, and on a consumer machine it is where `agent-config install` puts the
|
|
96
|
+
* shipped skills.
|
|
97
|
+
*/
|
|
98
|
+
export const HOST_CATALOGUE_ROOT = '.claude/skills';
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The PROJECTION root, on its own, for the consumers that need exactly it.
|
|
102
|
+
*
|
|
103
|
+
* `capture_skill_catalogue` compares what a host DELIVERED against what was
|
|
104
|
+
* PROJECTED, so the authored tree is the wrong answer for it even though the
|
|
105
|
+
* ranker wants the authored tree first. Both questions were sharing
|
|
106
|
+
* `DEFAULT_CATALOGUE_ROOTS` and the 2026-09-07 reorder silently changed the
|
|
107
|
+
* second one — the finding a neutral review returned the same day. One constant
|
|
108
|
+
* for two questions is the defect; this is the second question, named.
|
|
109
|
+
*/
|
|
110
|
+
export const PROJECTION_CATALOGUE_ROOT = '.claude/skills';
|
|
82
111
|
|
|
83
112
|
/**
|
|
84
|
-
*
|
|
113
|
+
* Every root the resolver looks at, as human-readable LABELS.
|
|
114
|
+
*
|
|
115
|
+
* **Labels, never path inputs — this changed on 2026-09-07.**
|
|
116
|
+
*
|
|
117
|
+
* It used to be the resolver's own candidate list and callers joined it onto a
|
|
118
|
+
* root. It now carries a `~/`-prefixed entry, which `path.join(repo, ...)` turns
|
|
119
|
+
* into `<repo>/~/.claude/skills` — a candidate that can never exist. Use
|
|
120
|
+
* {@link resolveSkillCatalogueRoots} to RESOLVE and this list only to REPORT
|
|
121
|
+
* what was searched.
|
|
122
|
+
*
|
|
123
|
+
* For the `searched:` / `tried:` fields of the no-catalogue answers only — a
|
|
124
|
+
* reader needs to know `~` was consulted, and an absolute home path in a
|
|
125
|
+
* machine-readable payload is both noise and a small privacy leak.
|
|
126
|
+
*/
|
|
127
|
+
export const DEFAULT_CATALOGUE_ROOTS = [
|
|
128
|
+
...PROJECT_CATALOGUE_ROOTS,
|
|
129
|
+
`~/${HOST_CATALOGUE_ROOT}`,
|
|
130
|
+
] as const;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* EVERY readable, non-empty catalogue root, in precedence order.
|
|
134
|
+
*
|
|
135
|
+
* Shared by the `skill-route` concern and the `suggest_skill_for_task` MCP
|
|
136
|
+
* handler on purpose: two resolvers over one catalogue is how a ranker and the
|
|
137
|
+
* tool that exposes it start ranking different trees.
|
|
138
|
+
*
|
|
139
|
+
* **Why a LIST, and not the first hit — owner decision, 2026-09-07.**
|
|
140
|
+
*
|
|
141
|
+
* This returned one root and stopped at the first match, which made the ranker
|
|
142
|
+
* see one tree and the session see another. A consumer project can carry its own
|
|
143
|
+
* skills in `<project>/.claude/skills` while the shipped catalogue sits in
|
|
144
|
+
* `~/.claude/skills`; first-hit-wins ranked the smaller of the two and reported
|
|
145
|
+
* the result as if it were the catalogue. Ranking the UNION ranks what the
|
|
146
|
+
* session can actually reach, which is the only set the answer is about.
|
|
147
|
+
*
|
|
148
|
+
* **The order, and what it is for.**
|
|
85
149
|
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* `
|
|
89
|
-
*
|
|
90
|
-
*
|
|
150
|
+
* Precedence applies to a NAME COLLISION, never to which roots are read:
|
|
151
|
+
*
|
|
152
|
+
* 1. `src/skills` — the authored tree. In this repository it is the truth, and
|
|
153
|
+
* a maintainer editing a skill must see their edit ranked, not the
|
|
154
|
+
* projection of the last `generate-tools` run.
|
|
155
|
+
* 2. `<project>/.claude/skills` — project scope beats machine scope, the same
|
|
156
|
+
* direction every other per-project override in this suite takes.
|
|
157
|
+
* 3. `~/${HOST_CATALOGUE_ROOT}` — the host-global catalogue.
|
|
158
|
+
*
|
|
159
|
+
* **The two defects this replaced, both real.**
|
|
160
|
+
*
|
|
161
|
+
* It used to try `.claude/skills` FIRST, "because a CONSUMER install carries the
|
|
162
|
+
* former and only a maintainer checkout carries the latter". The premise was
|
|
163
|
+
* right and the conclusion did not follow: a consumer has no `src/skills`, so
|
|
164
|
+
* reading it first costs one `existsSync`.
|
|
165
|
+
*
|
|
166
|
+
* The ADR-236 amendment of 2026-09-07 turned that order into a live defect. The
|
|
167
|
+
* project layer now withholds every skill `~/.claude/skills` carries, so this
|
|
168
|
+
* repository's `.claude/skills` holds only the ~49 flat-command WRAPPERS —
|
|
169
|
+
* non-empty, so the old order resolved it, and each wrapper carries a `SKILL.md`,
|
|
170
|
+
* so nothing downstream could tell it was not the catalogue. Measured the same
|
|
171
|
+
* day: the `skill-route` hook ranked over 49 command wrappers instead of 299
|
|
172
|
+
* skills. Under the union those wrappers are still ranked, which is correct —
|
|
173
|
+
* they are reachable — but they no longer displace the catalogue.
|
|
174
|
+
*
|
|
175
|
+
* The non-empty guard keeps its own separate job: an EMPTY root read as a match
|
|
176
|
+
* made the ranker report an empty catalogue as an empty RESULT, the silent
|
|
177
|
+
* failure `road-to-inbox-harvest-2026-08-f-skill-selection-evidence` is about.
|
|
178
|
+
* Measured in that run: a worktree that had never run `generate-tools` ranked
|
|
179
|
+
* zero skills and exited 0 on a task that scores 47 against `src/skills`.
|
|
180
|
+
*
|
|
181
|
+
* @param home override for the user's home; production callers omit it.
|
|
91
182
|
*/
|
|
92
|
-
export function
|
|
93
|
-
|
|
94
|
-
|
|
183
|
+
export function resolveSkillCatalogueRoots(workspaceRoot: string, home?: string): string[] {
|
|
184
|
+
const userHome = home ?? process.env['HOME'] ?? os.homedir();
|
|
185
|
+
const candidates = [
|
|
186
|
+
...PROJECT_CATALOGUE_ROOTS.map((c) => path.join(workspaceRoot, c)),
|
|
187
|
+
path.join(userHome, HOST_CATALOGUE_ROOT),
|
|
188
|
+
];
|
|
189
|
+
const roots: string[] = [];
|
|
190
|
+
const seen = new Set<string>();
|
|
191
|
+
for (const abs of candidates) {
|
|
192
|
+
// A workspace that IS the home directory would offer `~/.claude/skills`
|
|
193
|
+
// twice and rank every skill in it twice. Cheap to exclude, and the
|
|
194
|
+
// duplicate would be invisible in the output.
|
|
195
|
+
if (seen.has(abs)) continue;
|
|
196
|
+
seen.add(abs);
|
|
95
197
|
try {
|
|
96
198
|
if (!fs.existsSync(abs) || !fs.statSync(abs).isDirectory()) continue;
|
|
97
|
-
// NON-EMPTY, and that qualifier is the whole fix. `.claude/skills`
|
|
98
|
-
// is a gitignored projection, so in a fresh worktree it EXISTS and
|
|
99
|
-
// holds nothing — and an empty root resolved as a match makes the
|
|
100
|
-
// ranker report an empty catalogue as an empty RESULT, which is the
|
|
101
|
-
// silent failure `road-to-inbox-harvest-2026-08-f-skill-selection-evidence`
|
|
102
|
-
// is about, arriving through a second door. Measured in that run: a
|
|
103
|
-
// worktree that had never run `generate-tools` ranked zero skills
|
|
104
|
-
// and exited 0 on a task that scores 47 against `src/skills`.
|
|
105
199
|
if (fs.readdirSync(abs).length === 0) continue;
|
|
106
|
-
|
|
200
|
+
roots.push(abs);
|
|
107
201
|
} catch {
|
|
108
202
|
// Unreadable candidate is not a match; try the next.
|
|
109
203
|
}
|
|
110
204
|
}
|
|
111
|
-
return
|
|
205
|
+
return roots;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* The highest-precedence root, or `null` when none is readable.
|
|
210
|
+
*
|
|
211
|
+
* Kept for the surfaces that genuinely name ONE directory — a report line, a
|
|
212
|
+
* `skills_root` field. Anything that RANKS should take
|
|
213
|
+
* {@link resolveSkillCatalogueRoots} instead: ranking the first root only is the
|
|
214
|
+
* defect that function exists to remove.
|
|
215
|
+
*/
|
|
216
|
+
export function resolveSkillsRoot(workspaceRoot: string, home?: string): string | null {
|
|
217
|
+
return resolveSkillCatalogueRoots(workspaceRoot, home)[0] ?? null;
|
|
112
218
|
}
|
|
113
219
|
|
|
114
220
|
/** Where observations accumulate. Append-only, one JSON object per line. */
|
|
@@ -223,6 +329,35 @@ function descriptionOf(frontmatter: string): string {
|
|
|
223
329
|
* the order a host listing follows and therefore the order a positional
|
|
224
330
|
* hypothesis is measured against.
|
|
225
331
|
*/
|
|
332
|
+
/**
|
|
333
|
+
* The catalogue as the RANKER sees it — every readable root, first name winning.
|
|
334
|
+
*
|
|
335
|
+
* Added 2026-09-07 after a second neutral review. `capture_skill_catalogue`'s D-4
|
|
336
|
+
* join says it reads "the catalogue the runtime ranker reads", and once the ranker
|
|
337
|
+
* moved to the union a single-root read stopped being that. Worse, the root the
|
|
338
|
+
* capture had been pointed at — `.claude/skills` — is the one the ADR-236
|
|
339
|
+
* amendment EMPTIED of skills: the join then read 49 command wrappers as the
|
|
340
|
+
* catalogue and published `pointableBare: 0` against `unpointableBare: 16`, a
|
|
341
|
+
* clean-looking verdict off the wrong tree, with the empty-guard silent because
|
|
342
|
+
* 49 is not 0.
|
|
343
|
+
*
|
|
344
|
+
* Positions are re-derived over the merged list, because a position is a property
|
|
345
|
+
* of the catalogue the host is shown, not of the root a name came from.
|
|
346
|
+
*/
|
|
347
|
+
export function readCatalogueAcross(roots: readonly string[]): CatalogueEntry[] {
|
|
348
|
+
const seen = new Set<string>();
|
|
349
|
+
const merged: CatalogueEntry[] = [];
|
|
350
|
+
for (const root of roots) {
|
|
351
|
+
for (const entry of readProjectedCatalogue(root)) {
|
|
352
|
+
if (seen.has(entry.name)) continue;
|
|
353
|
+
seen.add(entry.name);
|
|
354
|
+
merged.push(entry);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
merged.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
358
|
+
return merged.map((e, i) => ({ ...e, position: i + 1 }));
|
|
359
|
+
}
|
|
360
|
+
|
|
226
361
|
export function readProjectedCatalogue(root: string): CatalogueEntry[] {
|
|
227
362
|
// Membership is decided by "does <name>/SKILL.md resolve", never by
|
|
228
363
|
// `Dirent.isDirectory()`. The host-facing projection is a tree of SYMLINKS
|