@event4u/agent-config 14.21.0 → 14.23.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 +164 -187
- package/CREDITS.md +37 -0
- package/README.md +1 -1
- package/dist/agent-src/commands/analyze/inbox.md +380 -5
- package/dist/agent-src/commands/analyze/repo.md +4 -2
- package/dist/agent-src/commands/pr/merge.md +47 -38
- package/dist/agent-src/commands/roadmap/next.md +8 -0
- 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/contexts/execution/autonomy-mechanics.md +12 -0
- package/dist/agent-src/rules/external-code-graph-interop.md +19 -14
- package/dist/agent-src/rules/notes-first-reasoning.md +36 -25
- 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 +120 -27
- 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 +82 -1
- package/dist/agent-src/skills/git-workflow/references/push-closes-its-loop.md +9 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +17 -0
- package/dist/agent-src/skills/tailwind-engineer/SKILL.md +52 -2
- 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/registry.js +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 +542 -0
- package/dist/cli-delegate/check_update_banner.js +923 -0
- package/dist/cli-delegate/{chunk-ZVBOHSVF.js → chunk-2KKXJW25.js} +23 -9
- package/dist/cli-delegate/chunk-5EK5WQN6.js +28 -0
- package/dist/cli-delegate/chunk-7XGD2OEU.js +96 -0
- package/dist/cli-delegate/chunk-DBH4AHDK.js +155 -0
- package/dist/cli-delegate/{chunk-4FS4WRXP.js → chunk-IAI3XL43.js} +15 -3
- package/dist/cli-delegate/chunk-JO25G5H7.js +20 -0
- package/dist/cli-delegate/{chunk-643Z7FIC.js → chunk-KCNTUSPI.js} +7 -1
- package/dist/cli-delegate/chunk-LTYVIY76.js +7961 -0
- package/dist/cli-delegate/chunk-QAJS5YWY.js +999 -0
- 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 +2 -2
- package/dist/cli-delegate/cmd_handoff.js +45 -13
- package/dist/cli-delegate/cmd_prune.js +2 -2
- package/dist/cli-delegate/cmd_session_recycle.js +4 -14
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +2 -2
- package/dist/cli-delegate/cmd_uninstall.js +2 -2
- package/dist/cli-delegate/cmd_validate.js +2 -2
- 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 +13 -13
- 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 +1813 -946
- 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 +21 -3
- 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 +10870 -7641
- package/dist/scripts/_lib/lean_projection_mode.js +108 -0
- package/dist/scripts/_lib/router_match.js +243 -0
- package/dist/scripts/_lib/rule_injection.js +216 -0
- package/dist/scripts/condense.js +52 -45
- package/dist/scripts/project_thin_rules.js +139 -19
- package/dist/server/schemas/settings.js +16 -3
- package/docs/MIGRATION.md +105 -1
- package/docs/contracts/settings-classes.md +40 -8
- package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +73 -0
- package/docs/guidelines/agent-infra/notes-horizon-mechanics.md +90 -0
- package/llms.txt +1 -1
- package/package.json +7 -4
- package/src/agent-src/scripts/_cli_entry.ts +51 -0
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +120 -27
- 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/agent-settings.template.yml +47 -1
- package/src/config/continuity-surface.json +516 -0
- package/src/config/estate-count-budget.json +1 -1
- package/src/config/evaluator-budgets.json +4 -4
- 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/hook-token-budget.json +7 -6
- 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/config/routing-coverage-seed.json +9 -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/_cli/cmd_session_recycle.ts +2 -25
- package/src/scripts/_cli/handoff_sessions.ts +14 -44
- 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_slot.ts +295 -0
- package/src/scripts/_lib/continuity_surface.ts +296 -0
- package/src/scripts/_lib/continuity_writer.ts +253 -0
- package/src/scripts/_lib/corpus_manifest.ts +26 -9
- package/src/scripts/_lib/hook_settings.ts +86 -1
- package/src/scripts/_lib/injection_effect.ts +151 -0
- package/src/scripts/_lib/layer_overlap_notice.ts +50 -6
- package/src/scripts/_lib/lean_projection_mode.ts +127 -0
- package/src/scripts/_lib/packed_binary_predicate.ts +262 -0
- package/src/scripts/_lib/pin_resolver.ts +13 -0
- package/src/scripts/_lib/recycle_envelope_paths.ts +109 -21
- package/src/scripts/_lib/regression_neighbourhood.ts +159 -22
- package/src/scripts/_lib/retired_status.ts +74 -0
- package/src/scripts/_lib/session_eol.ts +44 -0
- package/src/scripts/_lib/session_index_trust.ts +346 -0
- package/src/scripts/_lib/skill_catalogue.ts +156 -21
- package/src/scripts/_lib/standing_bound_ratchet.ts +154 -0
- package/src/scripts/_lib/subagent_capsule.ts +117 -8
- package/src/scripts/_lib/vendored_grammar_upstream.ts +280 -0
- 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/audit_mcp_tools.ts +69 -25
- package/src/scripts/build_mcp_catalog.ts +24 -1
- 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_host_tree_parity.ts +291 -0
- package/src/scripts/check_pack_size.ts +262 -23
- package/src/scripts/check_preamble_payload_budget.ts +136 -7
- package/src/scripts/check_roadmap_trackable.ts +19 -16
- package/src/scripts/check_rule_layer_partition.ts +54 -34
- package/src/scripts/check_rule_projection_integrity.ts +102 -2
- package/src/scripts/check_single_delivery.ts +97 -31
- package/src/scripts/check_update_banner.ts +13 -0
- package/src/scripts/ci_settle.ts +205 -5
- package/src/scripts/code_graph/build.ts +217 -15
- package/src/scripts/code_graph/cli.ts +334 -15
- package/src/scripts/code_graph/detect.ts +38 -0
- 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 +144 -11
- package/src/scripts/code_graph/resolution_tiers.ts +238 -0
- package/src/scripts/code_graph/sqlite_store.ts +262 -6
- package/src/scripts/code_graph/types.ts +117 -3
- package/src/scripts/code_graph/validate.ts +26 -1
- package/src/scripts/code_graph/verbs.ts +590 -0
- package/src/scripts/condense.ts +54 -42
- 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 +118 -1
- package/src/scripts/hook_effect_doctor.ts +82 -1
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +87 -12
- package/src/scripts/hooks/block_speaking_inbox_dir.ts +182 -16
- package/src/scripts/hooks/chain_nudge_hook.ts +282 -0
- package/src/scripts/hooks/code_graph_context_hook.ts +201 -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/push_settle_hook.ts +3 -2
- package/src/scripts/hooks/rule_inject_hook.ts +108 -21
- package/src/scripts/hooks/session_eol_hook.ts +120 -2
- package/src/scripts/hooks/skill_route_hook.ts +11 -4
- package/src/scripts/hot_context_hook.ts +62 -7
- 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/lint_roadmap_blockers.ts +164 -1
- package/src/scripts/mcp_server/consumer_tool_catalog.json +152 -2
- package/src/scripts/mcp_server/graph_tools.ts +294 -0
- package/src/scripts/mcp_server/path_util.ts +31 -0
- package/src/scripts/mcp_server/suggest_skill.ts +72 -0
- package/src/scripts/mcp_server/tool_catalog_source.ts +7 -1
- package/src/scripts/mcp_server/tools.ts +5 -69
- 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/model_rule_injection.ts +61 -4
- package/src/scripts/project_thin_rules.ts +149 -16
- 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/report_standing_payload_by_host.ts +650 -0
- package/src/scripts/schemas/agent-settings.schema.json +7 -1
- package/src/scripts/session_memory_index.ts +81 -8
- 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/server/schemas/settings.ts +23 -4
- package/src/vendor/grammars/README.md +86 -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
|
@@ -22,10 +22,18 @@
|
|
|
22
22
|
* default is off, per the roadmap's "off unless proven".
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
import { CURATED_TYPES, retrieve_v1 } from './memory_lookup.js';
|
|
25
|
+
import { CURATED_TYPES, MEMORY_ROOT, retrieve_v1 } from './memory_lookup.js';
|
|
26
26
|
import { load_agent_settings } from './_lib/agent_settings.js';
|
|
27
|
+
import {
|
|
28
|
+
capRows,
|
|
29
|
+
orderRows,
|
|
30
|
+
SESSION_INDEX_ROW_CAP,
|
|
31
|
+
verifyMemoryRoot,
|
|
32
|
+
type TrustGrant,
|
|
33
|
+
type TrustVerdict,
|
|
34
|
+
} from './_lib/session_index_trust.js';
|
|
27
35
|
|
|
28
|
-
export
|
|
36
|
+
export { SESSION_INDEX_ROW_CAP };
|
|
29
37
|
|
|
30
38
|
interface IndexRow {
|
|
31
39
|
id: string;
|
|
@@ -54,21 +62,72 @@ export function session_index_enabled(root: string): boolean {
|
|
|
54
62
|
* Expects the caller to have chdir'd to the workspace root — memory roots
|
|
55
63
|
* are cwd-relative, same contract as the MCP tool handlers.
|
|
56
64
|
*/
|
|
65
|
+
/**
|
|
66
|
+
* How many entries retrieval is asked for before the declared order picks.
|
|
67
|
+
*
|
|
68
|
+
* R2 finding 2: asking retrieval for the CAP made P4 decorative. Every curated
|
|
69
|
+
* hit scores an identical 0.1 with an empty key set, so `retrieve` sliced the
|
|
70
|
+
* first 30 in STORE order at `memory_lookup.ts:995` and `orderRows` then sorted
|
|
71
|
+
* a set the cap had already chosen. Measured by the reviewer: a 40-entry corpus
|
|
72
|
+
* with the 5 cheapest last emitted zero cheap rows.
|
|
73
|
+
*
|
|
74
|
+
* The number must therefore exceed any realistic curated corpus, not merely the
|
|
75
|
+
* cap. 500 is a STATED DEFAULT, not a measured optimum — the cost of asking for
|
|
76
|
+
* more than exists is bounded by what exists, since `detail: 'index'` returns a
|
|
77
|
+
* row rather than a body. Its falsifier is recorded in the roadmap: a corpus
|
|
78
|
+
* larger than this bound makes store order decide again, silently.
|
|
79
|
+
*/
|
|
80
|
+
export const SESSION_INDEX_RETRIEVAL_LIMIT = 500;
|
|
81
|
+
|
|
57
82
|
export function session_index_rows(cap: number = SESSION_INDEX_ROW_CAP): IndexRow[] {
|
|
58
|
-
|
|
83
|
+
// Retrieval is asked for far more than the cap, so the declared order below
|
|
84
|
+
// is what chooses which rows survive truncation. P4 and P6 are one
|
|
85
|
+
// decision: retrieve wide, order, THEN cap — and never the reverse.
|
|
86
|
+
const envelope = retrieve_v1([...CURATED_TYPES], [], SESSION_INDEX_RETRIEVAL_LIMIT, { detail: 'index' });
|
|
59
87
|
const entries = (envelope['entries'] as Array<Record<string, unknown>>) ?? [];
|
|
60
|
-
|
|
88
|
+
const rows: IndexRow[] = entries.map((e) => ({
|
|
61
89
|
id: String(e['id'] ?? ''),
|
|
62
90
|
title: String(e['title'] ?? ''),
|
|
63
91
|
tokens_estimate: Number(e['tokens_estimate'] ?? 0),
|
|
64
92
|
}));
|
|
93
|
+
return capRows(orderRows(rows), cap);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The trust gate, separated from the render so a caller can log WHY nothing
|
|
98
|
+
* was injected. `null` from {@link build_session_index_block} is ambiguous by
|
|
99
|
+
* construction — empty corpus and refused root look identical — and the
|
|
100
|
+
* distinction is the point of the contract, so it is available here.
|
|
101
|
+
*
|
|
102
|
+
* The workspace root is REQUIRED. `MEMORY_ROOT` is relative, so without a root
|
|
103
|
+
* to resolve it against there is nothing to check identity or containment
|
|
104
|
+
* against, and a default would be a bypass wearing a convenience.
|
|
105
|
+
*/
|
|
106
|
+
export function session_index_trust(workspaceRoot: string, now?: Date): TrustVerdict {
|
|
107
|
+
// `exactOptionalPropertyTypes` refuses `now: undefined` on an optional
|
|
108
|
+
// field, so the key is omitted rather than passed as undefined.
|
|
109
|
+
return verifyMemoryRoot({
|
|
110
|
+
workspaceRoot,
|
|
111
|
+
relativeMemoryRoot: MEMORY_ROOT,
|
|
112
|
+
...(now === undefined ? {} : { now }),
|
|
113
|
+
});
|
|
65
114
|
}
|
|
66
115
|
|
|
67
116
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
117
|
+
* RENDER a block from whatever `MEMORY_ROOT` currently resolves to. Checks
|
|
118
|
+
* nothing, serves nothing.
|
|
119
|
+
*
|
|
120
|
+
* Split out on R2 findings 6 and 7. The trust check used to be an OPTIONAL
|
|
121
|
+
* parameter, which made the serving path opt-in — and `session_index_cost()`
|
|
122
|
+
* omitted it, so the one function whose name suggests it only measures was also
|
|
123
|
+
* the one that could render unchecked. Now the two operations have two names:
|
|
124
|
+
* this one renders (for cost measurement and for the unit seam that injects an
|
|
125
|
+
* absolute fixture root via `_setMemoryRoot`, which by construction sits
|
|
126
|
+
* outside any workspace), and {@link serve_session_index_block} is the only way
|
|
127
|
+
* to obtain a block for INJECTION — it takes a granted verdict, so there is no
|
|
128
|
+
* unchecked serving path to forget.
|
|
70
129
|
*/
|
|
71
|
-
export function
|
|
130
|
+
export function render_session_index_block(cap: number = SESSION_INDEX_ROW_CAP): string | null {
|
|
72
131
|
const rows = session_index_rows(cap);
|
|
73
132
|
if (rows.length === 0) return null;
|
|
74
133
|
const lines = [
|
|
@@ -81,13 +140,27 @@ export function build_session_index_block(cap: number = SESSION_INDEX_ROW_CAP):
|
|
|
81
140
|
return lines.join('\n');
|
|
82
141
|
}
|
|
83
142
|
|
|
143
|
+
/**
|
|
144
|
+
* The only path to a block that will be INJECTED.
|
|
145
|
+
*
|
|
146
|
+
* Takes a granted verdict rather than a root, so the check happens once, at the
|
|
147
|
+
* caller, and cannot be skipped here — R2 finding 7 was that the hook and the
|
|
148
|
+
* builder each computed it, for a diagnostic the dispatcher does not forward.
|
|
149
|
+
* The type is `TrustGrant`, not `TrustVerdict`: a refusal cannot be passed in,
|
|
150
|
+
* so "did you check?" is answered by the signature instead of by a convention.
|
|
151
|
+
*/
|
|
152
|
+
export function serve_session_index_block(_grant: TrustGrant, cap: number = SESSION_INDEX_ROW_CAP): string | null {
|
|
153
|
+
return render_session_index_block(cap);
|
|
154
|
+
}
|
|
155
|
+
|
|
84
156
|
/**
|
|
85
157
|
* Deterministic fixed cost of the block in real tokens (cl100k_base) —
|
|
86
158
|
* the measurable arm of the roadmap's ship-criterion. Lazy-loads the
|
|
87
159
|
* tokenizer so the enabled=off fast path never pays it.
|
|
88
160
|
*/
|
|
89
161
|
export async function session_index_cost(cap: number = SESSION_INDEX_ROW_CAP): Promise<number> {
|
|
90
|
-
|
|
162
|
+
// Renders rather than serves: this measures a cost, it injects nothing.
|
|
163
|
+
const block = render_session_index_block(cap);
|
|
91
164
|
if (block === null) return 0;
|
|
92
165
|
const { gpt_tokens } = await import('./_lib/token_count.js');
|
|
93
166
|
return gpt_tokens(block).tokens;
|
|
@@ -66,9 +66,16 @@ export const ROOT = path.resolve(path.dirname(_HERE), '..', '..', '..');
|
|
|
66
66
|
* start ranking different trees. `null` when no candidate root exists, and the
|
|
67
67
|
* CLI turns that into a distinct exit code rather than an empty list.
|
|
68
68
|
*/
|
|
69
|
-
import { DEFAULT_CATALOGUE_ROOTS,
|
|
69
|
+
import { DEFAULT_CATALOGUE_ROOTS, resolveSkillCatalogueRoots } from '../_lib/skill_catalogue.js';
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Every readable catalogue root, in precedence order — possibly empty.
|
|
73
|
+
*
|
|
74
|
+
* The default the CLI and the hook rank across. `DEFAULT_SKILLS_DIR` below is
|
|
75
|
+
* the first of them and stays exported for the surfaces that name one directory.
|
|
76
|
+
*/
|
|
77
|
+
export const DEFAULT_SKILLS_ROOTS: readonly string[] = resolveSkillCatalogueRoots(ROOT);
|
|
78
|
+
export const DEFAULT_SKILLS_DIR: string | null = DEFAULT_SKILLS_ROOTS[0] ?? null;
|
|
72
79
|
|
|
73
80
|
/** Mirror Python len(str) — count Unicode code points, not UTF-16 units. */
|
|
74
81
|
function pyLen(s: string): number {
|
|
@@ -164,6 +171,27 @@ export interface Skill {
|
|
|
164
171
|
triggerText: string[];
|
|
165
172
|
}
|
|
166
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Load one root, or several with the FIRST occurrence of a name winning.
|
|
176
|
+
*
|
|
177
|
+
* Precedence by name, never by root: every root is read, and a collision
|
|
178
|
+
* resolves to the earliest root that carries it — `resolveSkillCatalogueRoots`
|
|
179
|
+
* owns what that order means. Reading only the first root is the defect this
|
|
180
|
+
* replaced; silently ranking one name twice would be the next one.
|
|
181
|
+
*/
|
|
182
|
+
function _load_skills_across(roots: readonly string[]): Skill[] {
|
|
183
|
+
const out: Skill[] = [];
|
|
184
|
+
const seen = new Set<string>();
|
|
185
|
+
for (const root of roots) {
|
|
186
|
+
for (const s of _load_skills(root)) {
|
|
187
|
+
if (seen.has(s.name)) continue;
|
|
188
|
+
seen.add(s.name);
|
|
189
|
+
out.push(s);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return out;
|
|
193
|
+
}
|
|
194
|
+
|
|
167
195
|
function _load_skills(skillsDir: string): Skill[] {
|
|
168
196
|
const skills: Skill[] = [];
|
|
169
197
|
for (const skillMd of _globSkillMd(skillsDir)) {
|
|
@@ -246,9 +274,16 @@ function _score(taskTerms: Set<string>, skill: Skill, opts: RankOptions = {}): n
|
|
|
246
274
|
|
|
247
275
|
export type RankRow = [string, number, string[]];
|
|
248
276
|
|
|
249
|
-
|
|
277
|
+
/**
|
|
278
|
+
* @param skillsDir one root, or several to rank across (see `_load_skills_across`).
|
|
279
|
+
*/
|
|
280
|
+
export function rank(
|
|
281
|
+
task: string,
|
|
282
|
+
skillsDir: string | readonly string[],
|
|
283
|
+
opts: RankOptions = {},
|
|
284
|
+
): RankRow[] {
|
|
250
285
|
const taskTerms = _tokenize(task);
|
|
251
|
-
const skills =
|
|
286
|
+
const skills = _load_skills_across(typeof skillsDir === 'string' ? [skillsDir] : skillsDir);
|
|
252
287
|
const rows: RankRow[] = [];
|
|
253
288
|
for (const s of skills) {
|
|
254
289
|
const score = _score(taskTerms, s, opts);
|
|
@@ -384,6 +419,15 @@ interface Args {
|
|
|
384
419
|
task: string;
|
|
385
420
|
/** `null` when no catalogue root exists — a DIFFERENT answer from an empty ranking. */
|
|
386
421
|
skills_dir: string | null;
|
|
422
|
+
/**
|
|
423
|
+
* Did the operator NAME the directory?
|
|
424
|
+
*
|
|
425
|
+
* An explicit `--skills-dir` means one root and only that root: an operator
|
|
426
|
+
* pointing at a tree is asking about that tree, and quietly unioning the
|
|
427
|
+
* host catalogue into their answer would make the flag mean something else.
|
|
428
|
+
* Absent, the default ranks across every readable root.
|
|
429
|
+
*/
|
|
430
|
+
skills_dir_explicit: boolean;
|
|
387
431
|
top: number;
|
|
388
432
|
json: boolean;
|
|
389
433
|
sample: boolean;
|
|
@@ -407,6 +451,7 @@ export function parse_args(argv: string[]): Args {
|
|
|
407
451
|
const args: Args = {
|
|
408
452
|
task: '',
|
|
409
453
|
skills_dir: DEFAULT_SKILLS_DIR,
|
|
454
|
+
skills_dir_explicit: false,
|
|
410
455
|
top: 0,
|
|
411
456
|
json: false,
|
|
412
457
|
sample: false,
|
|
@@ -431,8 +476,10 @@ export function parse_args(argv: string[]): Args {
|
|
|
431
476
|
_argError('argument --skills-dir: expected one argument');
|
|
432
477
|
}
|
|
433
478
|
args.skills_dir = v;
|
|
479
|
+
args.skills_dir_explicit = true;
|
|
434
480
|
} else if (a.startsWith('--skills-dir=')) {
|
|
435
481
|
args.skills_dir = a.slice('--skills-dir='.length);
|
|
482
|
+
args.skills_dir_explicit = true;
|
|
436
483
|
} else if (a === '--top') {
|
|
437
484
|
const v = argv[++i];
|
|
438
485
|
if (v === undefined) {
|
|
@@ -497,7 +544,24 @@ export function main(argv: string[] | null = null): number {
|
|
|
497
544
|
// operator who passes a wrong path is in exactly the position the silent
|
|
498
545
|
// failure was about, and answering them with "(no relevant skills found)"
|
|
499
546
|
// is the same wrong answer with a different cause.
|
|
500
|
-
|
|
547
|
+
// One root when the operator named it; otherwise every readable root, so the
|
|
548
|
+
// ranking covers what the session can actually reach.
|
|
549
|
+
const roots: readonly string[] = args.skills_dir_explicit
|
|
550
|
+
? args.skills_dir === null
|
|
551
|
+
? []
|
|
552
|
+
: [args.skills_dir]
|
|
553
|
+
: DEFAULT_SKILLS_ROOTS;
|
|
554
|
+
// An EXPLICIT directory is always deficit-checked, even when it resolved to a
|
|
555
|
+
// one-element list: a wrong path must not degrade to "(no relevant skills
|
|
556
|
+
// found)", which is the silent failure the check exists for and which the
|
|
557
|
+
// first draft of this branch reintroduced. For the DEFAULT, the deficit is
|
|
558
|
+
// "not one readable root anywhere" — a single empty candidate is no longer a
|
|
559
|
+
// deficit, because the others may still carry the catalogue.
|
|
560
|
+
const unreadable = args.skills_dir_explicit
|
|
561
|
+
? catalogueDeficit(args.skills_dir)
|
|
562
|
+
: roots.length === 0
|
|
563
|
+
? catalogueDeficit(null)
|
|
564
|
+
: null;
|
|
501
565
|
if (unreadable !== null) {
|
|
502
566
|
if (args.json) {
|
|
503
567
|
process.stdout.write(
|
|
@@ -520,7 +584,7 @@ export function main(argv: string[] | null = null): number {
|
|
|
520
584
|
}
|
|
521
585
|
// Non-null past the deficit check above, which returns before this line
|
|
522
586
|
// whenever the root cannot be ranked.
|
|
523
|
-
const rows = rank(task,
|
|
587
|
+
const rows = rank(task, roots);
|
|
524
588
|
if (args.json) {
|
|
525
589
|
const sliced = args.top ? rows.slice(0, args.top) : rows;
|
|
526
590
|
const payload = sliced.map(([n, s, p]) => ({ name: n, score: s, personas: p }));
|
|
@@ -32,7 +32,7 @@ import { audit, type PersonaRow } from './audit_persona_coverage.js';
|
|
|
32
32
|
/** Sentinel for "no catalogue root resolved" — never a readable path. */
|
|
33
33
|
const NO_CATALOGUE = '<no-skills-catalogue>';
|
|
34
34
|
|
|
35
|
-
import { DEFAULT_SKILLS_DIR, rank } from './score_skill_relevance.js';
|
|
35
|
+
import { DEFAULT_SKILLS_DIR, DEFAULT_SKILLS_ROOTS, rank } from './score_skill_relevance.js';
|
|
36
36
|
|
|
37
37
|
const _HERE = fileURLToPath(import.meta.url);
|
|
38
38
|
// src/scripts/skill_tools/suggest_skill_for_task.ts → parents[3] of the .py
|
|
@@ -78,13 +78,27 @@ export interface Combo {
|
|
|
78
78
|
why: string;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
/**
|
|
82
|
+
* @param skillsDir the root the PERSONA audit reads — one directory by necessity.
|
|
83
|
+
* @param rankRoots roots to RANK across; defaults to `[skillsDir]`.
|
|
84
|
+
*
|
|
85
|
+
* The two parameters are separate because the two questions are. Ranking wants
|
|
86
|
+
* every catalogue the session can reach (`resolveSkillCatalogueRoots`); the
|
|
87
|
+
* persona-coverage audit walks one tree and pairs it with one persona directory.
|
|
88
|
+
* Collapsing them was the state a neutral review found on 2026-09-07: the MCP
|
|
89
|
+
* handler ranked the union while this CLI ranked the first root, so a consumer
|
|
90
|
+
* carrying its own project skills got a different answer from each — exactly the
|
|
91
|
+
* two-resolvers-over-one-catalogue split the resolver's docstring warns about,
|
|
92
|
+
* reproduced by the change that claimed to fix it.
|
|
93
|
+
*/
|
|
81
94
|
export function suggest(
|
|
82
95
|
task: string,
|
|
83
96
|
skillsDir: string,
|
|
84
97
|
personasDir: string,
|
|
85
98
|
top = 3,
|
|
99
|
+
rankRoots: readonly string[] = [skillsDir],
|
|
86
100
|
): Combo[] {
|
|
87
|
-
const ranked = rank(task,
|
|
101
|
+
const ranked = rank(task, rankRoots).slice(0, top);
|
|
88
102
|
const personaRows = audit(skillsDir, personasDir);
|
|
89
103
|
const status = _persona_status(personaRows);
|
|
90
104
|
return ranked.map(([name, score, personas]) => ({
|
|
@@ -193,6 +207,8 @@ const PROG = 'suggest_skill_for_task.py';
|
|
|
193
207
|
interface Args {
|
|
194
208
|
task: string;
|
|
195
209
|
skills_dir: string;
|
|
210
|
+
/** Did the operator NAME the directory? One tree then, and only that tree. */
|
|
211
|
+
skills_dir_explicit: boolean;
|
|
196
212
|
personas_dir: string;
|
|
197
213
|
top: number;
|
|
198
214
|
json: boolean;
|
|
@@ -219,6 +235,7 @@ export function parse_args(argv: string[]): Args {
|
|
|
219
235
|
// DEFAULT_SKILLS_DIR removed. When no catalogue resolves, this reads
|
|
220
236
|
// as the literal marker below and the caller's own check reports it.
|
|
221
237
|
skills_dir: DEFAULT_SKILLS_DIR ?? NO_CATALOGUE,
|
|
238
|
+
skills_dir_explicit: false,
|
|
222
239
|
personas_dir: DEFAULT_PERSONAS,
|
|
223
240
|
top: 3,
|
|
224
241
|
json: false,
|
|
@@ -244,8 +261,10 @@ export function parse_args(argv: string[]): Args {
|
|
|
244
261
|
_argError('argument --skills-dir: expected one argument');
|
|
245
262
|
}
|
|
246
263
|
args.skills_dir = v;
|
|
264
|
+
args.skills_dir_explicit = true;
|
|
247
265
|
} else if (a.startsWith('--skills-dir=')) {
|
|
248
266
|
args.skills_dir = a.slice('--skills-dir='.length);
|
|
267
|
+
args.skills_dir_explicit = true;
|
|
249
268
|
} else if (a === '--personas-dir') {
|
|
250
269
|
const v = argv[++i];
|
|
251
270
|
if (v === undefined) {
|
|
@@ -279,7 +298,22 @@ export function main(argv: string[] | null = null): number {
|
|
|
279
298
|
if (!task) {
|
|
280
299
|
_argError('--task is required (or pass --sample)');
|
|
281
300
|
}
|
|
282
|
-
|
|
301
|
+
// Rank across every readable root; audit personas against the first. An
|
|
302
|
+
// explicit --skills-dir names one tree and is honoured as one tree.
|
|
303
|
+
//
|
|
304
|
+
// Keyed on the FLAG, not on value-equality against the default. Corrected
|
|
305
|
+
// after a second neutral review: comparing paths meant
|
|
306
|
+
// `--skills-dir <the resolved default>` silently ranked the union including
|
|
307
|
+
// `~/.claude/skills`, so the comment above was false for exactly the operator
|
|
308
|
+
// who spelled the default out. And when no catalogue resolves at all,
|
|
309
|
+
// `skills_dir` is the `NO_CATALOGUE` sentinel while `DEFAULT_SKILLS_DIR` is
|
|
310
|
+
// null, so the old comparison was false there too — the branch was dead in
|
|
311
|
+
// the state it was written for.
|
|
312
|
+
const rankRoots =
|
|
313
|
+
!args.skills_dir_explicit && DEFAULT_SKILLS_ROOTS.length > 0
|
|
314
|
+
? DEFAULT_SKILLS_ROOTS
|
|
315
|
+
: [args.skills_dir];
|
|
316
|
+
const combos = suggest(task, args.skills_dir, args.personas_dir, args.top, rankRoots);
|
|
283
317
|
if (args.json) {
|
|
284
318
|
process.stdout.write(pyJsonDumpsIndent2({ task, suggestions: combos }));
|
|
285
319
|
process.stdout.write('\n');
|
|
@@ -31,7 +31,8 @@ const confidenceBand = z.enum(['off', 'low', 'medium', 'high']);
|
|
|
31
31
|
const onBlock = z.enum(['stop', 'ask', 'warn']);
|
|
32
32
|
const onBlockFallback = z.enum(['stop', 'warn']);
|
|
33
33
|
const modelAutoSwitch = z.enum(['auto', 'suggest', 'off']);
|
|
34
|
-
const leanProjectionMode = z.enum(['eager-all', 'thin']);
|
|
34
|
+
const leanProjectionMode = z.enum(['eager-all', 'thin', 'delivery']);
|
|
35
|
+
const leanProjectionHost = z.enum(['claude-code', 'cursor', 'cline']);
|
|
35
36
|
const projectionMode = z.enum(['legacy-all', 'scoped']);
|
|
36
37
|
const memoryCadence = z.enum(['auto', 'always', 'never']);
|
|
37
38
|
const projectAudience = z.enum(['self', 'internal', 'client', 'public']);
|
|
@@ -64,9 +65,19 @@ export const settingsSchema = z.object({
|
|
|
64
65
|
),
|
|
65
66
|
lean_projection: z.object({
|
|
66
67
|
mode: leanProjectionMode.default('eager-all').describe(
|
|
67
|
-
'How the per-tool projector emits the rule layer. eager-all = every rule body inlined into every projection (
|
|
68
|
-
),
|
|
69
|
-
|
|
68
|
+
'How the per-tool projector emits the rule layer. eager-all = every rule body inlined into every projection (safe; what an ABSENT key still resolves to, per ADR-267 decision point 4). thin = kernel rules full-bodied + non-kernel rules as router-resolved pointers (~45k GPT-tok lighter per session), with no delivery mechanism. delivery = thin stubs PLUS the rule-inject hook concern, which delivers a body back on a trigger match; this is the value the SHIPPED TEMPLATE carries for Claude Code since ADR-267, and the activation charge for it is paid in src/config/hook-token-budget.json (user_prompt_submit slot sum 4,096 -> 16,384).',
|
|
69
|
+
),
|
|
70
|
+
hosts: z.array(leanProjectionHost).default(['claude-code']).describe(
|
|
71
|
+
'Which hosts a thinning mode may thin (ADR-267). Absent resolves to [claude-code]. '
|
|
72
|
+
+ 'Only the three hosts with a per-rule rule tree are accepted, and this enum is the '
|
|
73
|
+
+ 'AUTHORING layer: an id outside it is a hard validation FAILURE here and under '
|
|
74
|
+
+ 'validate_agent_settings, never a warning. resolveLeanProjectionHosts is the '
|
|
75
|
+
+ 'READ-TIME backstop for a hand-edited .agent-settings.yml that passed neither, and '
|
|
76
|
+
+ 'THERE the id is dropped with a warning (R2 finding 6 — the two layers answer '
|
|
77
|
+
+ 'differently and neither said so). Every host outside this list receives exactly '
|
|
78
|
+
+ 'what eager-all writes, asserted byte-for-byte by check_host_tree_parity.',
|
|
79
|
+
),
|
|
80
|
+
}).default({ mode: 'eager-all', hosts: ['claude-code'] }),
|
|
70
81
|
cost: z.object({
|
|
71
82
|
budgets: z.object({
|
|
72
83
|
daily: z.number().min(0).default(0).describe(
|
|
@@ -438,6 +449,14 @@ export const settingsSchema = z.object({
|
|
|
438
449
|
),
|
|
439
450
|
}),
|
|
440
451
|
}),
|
|
452
|
+
continuity: z.object({
|
|
453
|
+
auto_record: z.enum(['on', 'off']).default('off').describe(
|
|
454
|
+
'Deterministic continuity-record writer at session end (road-to-continuity-writer-activation Phase 1). on = the session-eol concern writes the continuity_record capsule variant on Stop for a substantive session that has claimed a roadmap; every field is computed from on-disk state, with no model spend and no subprocess. off (default) = no automatic record — while session:recycle is still the normal path, a second producer on it before the parity evidence is in would be unverified.',
|
|
455
|
+
),
|
|
456
|
+
run_checkpoints: z.enum(['on', 'off']).default('on').describe(
|
|
457
|
+
'Deterministic run-checkpoint production at session end (road-to-continuity-writer-activation Phase 1.4). on (default) = a session above the recycle threshold and inside a running roadmap contract leaves agents/runtime/state/checkpoints/<run>.json, so a killed run resumes from a derived checkpoint rather than from bookkeeping. off = no checkpoint; continuity writing, the recycle advisory and the context-fill surface are unaffected. Default ON because it is the behaviour the tree already had — the switch exists to make the three session-end handlers independently disableable, not to change what ships.',
|
|
458
|
+
),
|
|
459
|
+
}),
|
|
441
460
|
memory: z.object({
|
|
442
461
|
cadence: memoryCadence.default('always').describe(
|
|
443
462
|
'Cadence of the 🧠 memory-visibility line after a memory-consulting step. always (default) = show whenever a memory type was asked; auto = show only when 3+ types were consulted (less noise); never = suppress. Distinct from rule_loading_tier — owns its own key since the 2026-06-01 untangle.',
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Vendored tree-sitter grammars
|
|
2
|
+
|
|
3
|
+
Compiled WebAssembly grammars for the native code-graph engine
|
|
4
|
+
(`src/scripts/code_graph/`). They are vendored — carried in this repository and
|
|
5
|
+
published inside the npm tarball — so that a consumer who runs
|
|
6
|
+
`npm install @event4u/agent-config` can build a code graph on the next command
|
|
7
|
+
with no manual parser install (ADR-259).
|
|
8
|
+
|
|
9
|
+
## Why vendored rather than a dependency
|
|
10
|
+
|
|
11
|
+
`tree-sitter-wasms` carries 36 grammars (49 MiB on disk). Depending on it at
|
|
12
|
+
runtime would hand every consumer all 36, which is the option ADR-259 §
|
|
13
|
+
Alternatives rejects on exactly that measurement. Only the three grammars below
|
|
14
|
+
are loadable by the engine — `GRAMMAR_WASM` in `src/scripts/code_graph/types.ts`
|
|
15
|
+
has three entries — so the other 33 would be payload with no reader.
|
|
16
|
+
|
|
17
|
+
**No grammar is ever fetched at runtime** (Kill register K1). These files are in
|
|
18
|
+
the tarball; the loader reads them from disk.
|
|
19
|
+
|
|
20
|
+
## The set
|
|
21
|
+
|
|
22
|
+
| Language | File | SHA-256 |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| php | `tree-sitter-php.wasm` | `55bb617b6f01e14bab997861f0b20a2420cf6ba3199ffeb295b9ec398966d8a3` |
|
|
25
|
+
| typescript (also serves `.tsx`) | `tree-sitter-typescript.wasm` | `8515404dceed38e1ed86aa34b09fcf3379fff1b4ff9dd3967bcd6d1eb5ac3d8f` |
|
|
26
|
+
| javascript | `tree-sitter-javascript.wasm` | `63812b9e275d26851264734868d27a1656bd44a2ef6eb3e85e6b03728c595ab5` |
|
|
27
|
+
|
|
28
|
+
All three are built against **grammar ABI 14** (`EXPECTED_GRAMMAR_ABI` in
|
|
29
|
+
`src/scripts/code_graph/types.ts`). The loader asserts the ABI on every load, so
|
|
30
|
+
a drifted grammar fails loudly rather than parsing wrongly.
|
|
31
|
+
|
|
32
|
+
## Provenance
|
|
33
|
+
|
|
34
|
+
Copied byte-for-byte from `tree-sitter-wasms@0.1.13`
|
|
35
|
+
(<https://github.com/Gregoor/tree-sitter-wasms>, Unlicense), which compiles them
|
|
36
|
+
from the upstream `tree-sitter/tree-sitter-{php,typescript,javascript}` grammars
|
|
37
|
+
(MIT). The compiled artifacts are derivatives of the MIT grammar sources, so
|
|
38
|
+
they are annotated MIT in `REUSE.toml` and credited in `CREDITS.md`.
|
|
39
|
+
|
|
40
|
+
`tree-sitter-wasms` remains a **devDependency**: it is the source these copies
|
|
41
|
+
are refreshed from, and the ABI smoke test in `tests/scripts/code_graph.test.ts`
|
|
42
|
+
runs against it. It is deliberately not a runtime dependency.
|
|
43
|
+
|
|
44
|
+
## Verification
|
|
45
|
+
|
|
46
|
+
Two checks, and they answer different questions.
|
|
47
|
+
|
|
48
|
+
`src/config/packed-binary-manifest.json` plus `_lib/packed_binary_predicate.ts`
|
|
49
|
+
verify that these bytes are what the manifest says they are — eleven conditions,
|
|
50
|
+
all of them re-derived from the bytes. Every one of them reads the manifest, so
|
|
51
|
+
a commit that edits a row and its file together satisfies all eleven.
|
|
52
|
+
|
|
53
|
+
`_lib/vendored_grammar_upstream.ts` verifies that these bytes are what the
|
|
54
|
+
**locked upstream** says they are: it byte-compares each file against
|
|
55
|
+
`node_modules/tree-sitter-wasms/out/`, after asserting that the installed
|
|
56
|
+
version is the one `package-lock.json` pins. An absent or unlocked upstream is a
|
|
57
|
+
refusal, never a pass. It is exercised by
|
|
58
|
+
`tests/scripts/vendored_grammar_upstream.test.ts`, which also drives every
|
|
59
|
+
refusal path and a real mutated grammar to failure.
|
|
60
|
+
|
|
61
|
+
What neither check covers is a `package-lock.json` row pointing at a hostile
|
|
62
|
+
package; that is review's job, and `packed_binary_predicate.ts`'s docblock says
|
|
63
|
+
so in those words.
|
|
64
|
+
|
|
65
|
+
## Refreshing
|
|
66
|
+
|
|
67
|
+
The pair is ABI-coupled and must move together:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npm i -D web-tree-sitter@<v> tree-sitter-wasms@<v>
|
|
71
|
+
for g in php typescript javascript; do
|
|
72
|
+
cp node_modules/tree-sitter-wasms/out/tree-sitter-$g.wasm src/vendor/grammars/
|
|
73
|
+
done
|
|
74
|
+
shasum -a 256 src/vendor/grammars/*.wasm # update the table above
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Then update, in the same change:
|
|
78
|
+
|
|
79
|
+
- the three entries in `src/config/packed-binary-manifest.json` (they are
|
|
80
|
+
path-, sha256- and size-bound, so any refresh invalidates them by design).
|
|
81
|
+
This used to name `BINARY_PAYLOAD_EXCEPTIONS` in `src/scripts/check_pack_size.ts`,
|
|
82
|
+
a constant that no longer exists — `grep -rn BINARY_PAYLOAD_EXCEPTIONS` returned
|
|
83
|
+
this line and nothing else, so a refresher following it would have skipped the
|
|
84
|
+
manifest and reddened the pack-size gate with no pointer to why;
|
|
85
|
+
- `EXPECTED_GRAMMAR_ABI` in `src/scripts/code_graph/types.ts` if the ABI moved;
|
|
86
|
+
- the install-size line in `MIGRATION.md`.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The `status: carrier` predicate, and the reasoning five readers share.
|
|
3
|
-
*
|
|
4
|
-
* A carrier is a roadmap whose job is to hold obligations deferred out of a
|
|
5
|
-
* parent that is already archived. Every item in it has an unmet resumption
|
|
6
|
-
* trigger, and it has no `## Phase` headings, so it is skipped wherever a draft
|
|
7
|
-
* is: the dashboard, `/roadmap:process-*`, the trackability check and the plan
|
|
8
|
-
* risk register. It differs from a draft in what it costs to remove — a draft
|
|
9
|
-
* may be deleted freely, a carrier may not.
|
|
10
|
-
*
|
|
11
|
-
* This module exists because the predicate had two implementations within one
|
|
12
|
-
* change and five callers within one week. Two implementations of one frontmatter
|
|
13
|
-
* test drift, and the one nobody runs locally drifts first — the same
|
|
14
|
-
* memory-twin failure this repository has already recorded against a fixture
|
|
15
|
-
* corpus. One test, one place, and the reasoning above lives beside it rather
|
|
16
|
-
* than being restated in each caller.
|
|
17
|
-
*
|
|
18
|
-
* The read is frontmatter-scoped on purpose. A fenced `status: carrier` inside a
|
|
19
|
-
* roadmap that documents the syntax is not a declaration, and a body-wide match
|
|
20
|
-
* turned exactly that into a hard failure before a review caught it.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
/** The frontmatter values that mean "not schedulable work, and not disposable". */
|
|
24
|
-
export const CARRIER_VALUES: ReadonlySet<string> = new Set(['carrier']);
|
|
25
|
-
|
|
26
|
-
/** The leading `---\n…\n---` block, or null when the text carries none. */
|
|
27
|
-
export function frontmatterBlock(text: string): string | null {
|
|
28
|
-
const m = /^---\n([\s\S]*?)\n---/.exec(text);
|
|
29
|
-
return m === null ? null : (m[1] ?? '');
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** True when `text`'s FRONTMATTER declares one of {@link CARRIER_VALUES}. */
|
|
33
|
-
export function declaresCarrier(text: string | null | undefined): boolean {
|
|
34
|
-
if (text === null || text === undefined) {
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
const fm = frontmatterBlock(text);
|
|
38
|
-
if (fm === null) {
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
const m = /^status:[ \t]*([A-Za-z-]+)[ \t]*$/m.exec(fm);
|
|
42
|
-
return m !== null && CARRIER_VALUES.has((m[1] ?? '').toLowerCase());
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Count the top-level roadmaps in `roadmapRoot` that declare themselves carriers.
|
|
47
|
-
*
|
|
48
|
-
* Lives here rather than in the estate gate because it is the same fact as the
|
|
49
|
-
* predicate above, read over a directory instead of a string, and the gate that
|
|
50
|
-
* needs it is already at its source-size ceiling.
|
|
51
|
-
*
|
|
52
|
-
* @param isCandidate the caller's own roadmap-filename test, so this module does
|
|
53
|
-
* not acquire a second opinion about what counts as a roadmap.
|
|
54
|
-
*/
|
|
55
|
-
export function countDeclaredCarriers(
|
|
56
|
-
roadmapRoot: string,
|
|
57
|
-
isCandidate: (name: string) => boolean,
|
|
58
|
-
fsLike: {
|
|
59
|
-
readdirSync: (p: string) => string[];
|
|
60
|
-
statSync: (p: string) => { isFile: () => boolean };
|
|
61
|
-
readFileSync: (p: string, enc: 'utf-8') => string;
|
|
62
|
-
},
|
|
63
|
-
join: (...parts: string[]) => string,
|
|
64
|
-
): number {
|
|
65
|
-
let names: string[];
|
|
66
|
-
try {
|
|
67
|
-
names = fsLike.readdirSync(roadmapRoot);
|
|
68
|
-
} catch {
|
|
69
|
-
return 0;
|
|
70
|
-
}
|
|
71
|
-
let n = 0;
|
|
72
|
-
for (const name of names) {
|
|
73
|
-
if (!name.endsWith('.md') || !isCandidate(name)) {
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
const abs = join(roadmapRoot, name);
|
|
77
|
-
try {
|
|
78
|
-
if (!fsLike.statSync(abs).isFile()) {
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
if (declaresCarrier(fsLike.readFileSync(abs, 'utf-8'))) {
|
|
82
|
-
n += 1;
|
|
83
|
-
}
|
|
84
|
-
} catch {
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return n;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Fixture design — why two of the estate self-test cases are shaped as they are.
|
|
93
|
-
*
|
|
94
|
-
* The reasoning behind two of `check_estate_count`'s self-test cases, kept here
|
|
95
|
-
* because the gate that holds them is at its source-size ceiling and because
|
|
96
|
-
* both facts are about the carrier status rather than about that gate.
|
|
97
|
-
*
|
|
98
|
-
* **The deletion case's addition is a DRAFT, and that is the whole design.**
|
|
99
|
-
* A carrier holds obligations lifted out of an already-archived parent, so
|
|
100
|
-
* paying an offset for removing one rewards the loss: the deletion buys nothing
|
|
101
|
-
* and the addition stays unpaid. With an *ordinary* addition the growth half and
|
|
102
|
-
* the unpaid half coincide — growth iff `n > a`, unpaid iff `n > a` — so the
|
|
103
|
-
* case rejected on two independent grounds and stayed red with the carrier
|
|
104
|
-
* branch reverted, asserting nothing about the feature it named. A draft is
|
|
105
|
-
* invisible to `collect()`, so the count half cannot move and only the offset
|
|
106
|
-
* scoring decides.
|
|
107
|
-
*
|
|
108
|
-
* **The count case asserts the flip BACK, not the flip TO.** A reclassification
|
|
109
|
-
* is not a disposal in either direction, but the flip *to* carrier cannot be
|
|
110
|
-
* asserted by exit code: it lowers the count, a count below the floor is a
|
|
111
|
-
* drawdown, and a drawdown is free whichever way carriers are counted. Its
|
|
112
|
-
* consequence is assertable — with carriers uncounted the flip lowers the floor,
|
|
113
|
-
* so flipping the same file back reads as growth and must be paid for. That is
|
|
114
|
-
* the fixture's state, with the carrier already in the base tree, and it rejects
|
|
115
|
-
* with the carrier term removed from the count. The neutrality of the forward
|
|
116
|
-
* flip is asserted directly in `check_estate_count.test.ts`, where a count is
|
|
117
|
-
* readable.
|
|
118
|
-
*/
|