@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,322 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* inbox_source_census — the denominator an inbox run cannot shrink by reading badly.
|
|
4
|
+
*
|
|
5
|
+
* THE GAP THIS CLOSES.
|
|
6
|
+
*
|
|
7
|
+
* `/analyze:inbox` already ends with a **point ledger**: points extracted in its
|
|
8
|
+
* Phase 3 against points dispositioned in Phases 4–5b, per bucket, as counts
|
|
9
|
+
* that must balance. That ledger proves the run answered what it read. It
|
|
10
|
+
* cannot prove the run read what arrived — a point nobody extracted appears in
|
|
11
|
+
* neither column, and the ledger balances perfectly without it. Under-extraction
|
|
12
|
+
* is invisible to the only completeness check the command has.
|
|
13
|
+
*
|
|
14
|
+
* Measured shape of the input that exposed it: round `inbox-2026-09-r` arrived
|
|
15
|
+
* as 12 topic folders, 67 files and ~55,800 lines, where one folder alone held
|
|
16
|
+
* six revisions of a plan beside a 58 KB transcript. Read per file, the
|
|
17
|
+
* revisions look like six sources; read as a set, they are one argument whose
|
|
18
|
+
* later revisions silently drop items the earlier ones carried.
|
|
19
|
+
*
|
|
20
|
+
* WHAT THIS COUNTS — AND WHAT IT DELIBERATELY DOES NOT.
|
|
21
|
+
*
|
|
22
|
+
* Only what is **mechanical and genre-independent**:
|
|
23
|
+
*
|
|
24
|
+
* 1. **Source sets** — files grouped by stem, so a revision family
|
|
25
|
+
* (`x.v1/x.v2`, `x.consolidated.v1`, `x-loop-1`, `x-2026-09-06`) reports as
|
|
26
|
+
* ONE set with its members ordered, instead of as N unrelated files.
|
|
27
|
+
* 2. **Anchors** — the four countable structures a source can carry: turn
|
|
28
|
+
* separators, headings, checkbox steps, and numbered items. Every class is
|
|
29
|
+
* counted on every file; the run picks the ones its genre uses.
|
|
30
|
+
* 3. **Decision ids** — the `D2` / `E1` / `K7` tokens a source argues in. If
|
|
31
|
+
* those do not appear in what the run emits, the emitted plan is
|
|
32
|
+
* undecidable by construction and no reader can tell, because the ids left
|
|
33
|
+
* with the source.
|
|
34
|
+
*
|
|
35
|
+
* It does NOT guess genre, judge relevance, or extract demands. Those are the
|
|
36
|
+
* reading, and the whole point of a denominator is that it is produced without
|
|
37
|
+
* one — a census that needed judgement would be as shrinkable as the reading it
|
|
38
|
+
* is supposed to bound.
|
|
39
|
+
*
|
|
40
|
+
* HONEST LIMIT.
|
|
41
|
+
*
|
|
42
|
+
* An anchor count is a floor on what must be accounted for, never a target and
|
|
43
|
+
* never a quality measure. A 400-anchor source can carry three demands and a
|
|
44
|
+
* 12-anchor transcript can carry twelve. What the number buys is that a run
|
|
45
|
+
* reporting "41 anchors → 41 accounted" can be challenged by anyone holding the
|
|
46
|
+
* folder, where a prose summary cannot.
|
|
47
|
+
*
|
|
48
|
+
* USAGE.
|
|
49
|
+
*
|
|
50
|
+
* ./scripts-run src/scripts/inbox_source_census --root agents/tmp/<round-id>
|
|
51
|
+
* ./scripts-run src/scripts/inbox_source_census --root <dir> --json
|
|
52
|
+
*
|
|
53
|
+
* Exit 2 when the root is missing, unreadable, or enumerates zero files — a
|
|
54
|
+
* census over an empty set is vacuous and must not read as coverage.
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
import * as fs from 'node:fs';
|
|
58
|
+
import * as path from 'node:path';
|
|
59
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
60
|
+
|
|
61
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
62
|
+
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
63
|
+
|
|
64
|
+
/** Extensions worth counting. Binaries and archives carry no anchors. */
|
|
65
|
+
const TEXT_EXT = new Set(['.md', '.txt', '.markdown', '.mdx']);
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Version markers that make two files revisions of one source rather than two
|
|
69
|
+
* sources. Ordered most-specific first: `consolidated.v2` must not be stripped
|
|
70
|
+
* to `consolidated` by the bare `.vN` rule.
|
|
71
|
+
*/
|
|
72
|
+
const VERSION_MARKERS: readonly RegExp[] = [
|
|
73
|
+
/[.-]consolidated[.-]v\d+$/i,
|
|
74
|
+
/[.-]consolidated$/i,
|
|
75
|
+
/[.-]loop[.-]?\d+.*$/i,
|
|
76
|
+
/[.-]v\d+$/i,
|
|
77
|
+
/[.-]\d{4}-\d{2}-\d{2}$/,
|
|
78
|
+
/[.-](final|draft|deep|master)$/i,
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
export interface AnchorCounts {
|
|
82
|
+
/** `----`-style turn separators — the transcript spine. */
|
|
83
|
+
separators: number;
|
|
84
|
+
/** ATX headings, h1 through h4. */
|
|
85
|
+
headings: number;
|
|
86
|
+
/** `- [ ]` / `- [x]` / `- [~]` / `- [-]` roadmap steps. */
|
|
87
|
+
steps: number;
|
|
88
|
+
/** `1.`-style numbered items and `**bold lead-in**` paragraph openers. */
|
|
89
|
+
enumerated: number;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface FileCensus {
|
|
93
|
+
file: string;
|
|
94
|
+
bytes: number;
|
|
95
|
+
lines: number;
|
|
96
|
+
anchors: AnchorCounts;
|
|
97
|
+
/** Sum of the four anchor classes — the file's floor. */
|
|
98
|
+
total: number;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface SourceSet {
|
|
102
|
+
/** The shared stem the members were grouped under. */
|
|
103
|
+
stem: string;
|
|
104
|
+
/** Members, oldest-looking first (lexical on the version marker). */
|
|
105
|
+
members: FileCensus[];
|
|
106
|
+
/** True when more than one member shares the stem. */
|
|
107
|
+
isRevisionSet: boolean;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface TopicCensus {
|
|
111
|
+
topic: string;
|
|
112
|
+
sets: SourceSet[];
|
|
113
|
+
decisionIds: string[];
|
|
114
|
+
fileCount: number;
|
|
115
|
+
anchorTotal: number;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Strip a trailing version marker to the stem two revisions share.
|
|
120
|
+
*
|
|
121
|
+
* Applied repeatedly, most-specific first, so `road-to-x.consolidated.v3`
|
|
122
|
+
* reduces through `road-to-x.consolidated` to `road-to-x` — otherwise a
|
|
123
|
+
* `.consolidated` family and a plain `.vN` family of the same plan report as
|
|
124
|
+
* two sets and the diff obligation never fires on the pair.
|
|
125
|
+
*/
|
|
126
|
+
export function stemOf(filename: string): string {
|
|
127
|
+
let s = filename.replace(/\.(md|markdown|mdx|txt)$/i, '');
|
|
128
|
+
let changed = true;
|
|
129
|
+
while (changed) {
|
|
130
|
+
changed = false;
|
|
131
|
+
for (const re of VERSION_MARKERS) {
|
|
132
|
+
const next = s.replace(re, '');
|
|
133
|
+
if (next !== s && next.length > 0) {
|
|
134
|
+
s = next;
|
|
135
|
+
changed = true;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return s;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Count the four anchor classes. Fenced code is skipped — a fence is content. */
|
|
144
|
+
export function countAnchors(text: string): AnchorCounts {
|
|
145
|
+
const out: AnchorCounts = { separators: 0, headings: 0, steps: 0, enumerated: 0 };
|
|
146
|
+
let inFence = false;
|
|
147
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
148
|
+
const line = raw.trimEnd();
|
|
149
|
+
if (/^\s*(```|~~~)/.test(line)) {
|
|
150
|
+
inFence = !inFence;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (inFence) continue;
|
|
154
|
+
if (/^-{4,}$|^={4,}$|^\*{4,}$/.test(line.trim())) {
|
|
155
|
+
out.separators += 1;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (/^#{1,4} \S/.test(line)) {
|
|
159
|
+
out.headings += 1;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (/^\s*[-*] \[[ xX~-]\]/.test(line)) {
|
|
163
|
+
out.steps += 1;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (/^\s*\d+[.)] \S/.test(line) || /^\*\*[^*]+\*\*/.test(line)) {
|
|
167
|
+
out.enumerated += 1;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return out;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Decision / open-question ids the source argues in — `D2`, `E1`, `K7`, `AC3`.
|
|
175
|
+
*
|
|
176
|
+
* Deliberately narrow: one to three capitals followed by one or two digits, as
|
|
177
|
+
* a whole token. A wider pattern matches version strings, HTTP codes and every
|
|
178
|
+
* `P0` in ordinary prose, and a noisy id set is worse than none — the run would
|
|
179
|
+
* stop reading the row.
|
|
180
|
+
*/
|
|
181
|
+
export function decisionIds(text: string): string[] {
|
|
182
|
+
const hits = text.match(/\b[A-Z]{1,3}\d{1,2}\b/g) ?? [];
|
|
183
|
+
return [...new Set(hits)].sort();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function censusFile(abs: string, rel: string): FileCensus {
|
|
187
|
+
const text = fs.readFileSync(abs, 'utf-8');
|
|
188
|
+
const anchors = countAnchors(text);
|
|
189
|
+
return {
|
|
190
|
+
file: rel,
|
|
191
|
+
bytes: Buffer.byteLength(text),
|
|
192
|
+
lines: text.split(/\r?\n/).length,
|
|
193
|
+
anchors,
|
|
194
|
+
total: anchors.separators + anchors.headings + anchors.steps + anchors.enumerated,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Group one topic folder's files into source sets and count its anchors. */
|
|
199
|
+
export function censusTopic(dir: string, topic: string): TopicCensus {
|
|
200
|
+
const names = fs
|
|
201
|
+
.readdirSync(dir, { withFileTypes: true })
|
|
202
|
+
.filter((d) => d.isFile() && TEXT_EXT.has(path.extname(d.name).toLowerCase()))
|
|
203
|
+
.map((d) => d.name)
|
|
204
|
+
.sort();
|
|
205
|
+
|
|
206
|
+
const byStem = new Map<string, FileCensus[]>();
|
|
207
|
+
const ids = new Set<string>();
|
|
208
|
+
for (const name of names) {
|
|
209
|
+
const abs = path.join(dir, name);
|
|
210
|
+
const fc = censusFile(abs, name);
|
|
211
|
+
for (const id of decisionIds(fs.readFileSync(abs, 'utf-8'))) ids.add(id);
|
|
212
|
+
const stem = stemOf(name);
|
|
213
|
+
const bucket = byStem.get(stem);
|
|
214
|
+
if (bucket) bucket.push(fc);
|
|
215
|
+
else byStem.set(stem, [fc]);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const sets: SourceSet[] = [...byStem.entries()]
|
|
219
|
+
.map(([stem, members]) => ({
|
|
220
|
+
stem,
|
|
221
|
+
members: members.sort((a, b) => a.file.localeCompare(b.file)),
|
|
222
|
+
isRevisionSet: members.length > 1,
|
|
223
|
+
}))
|
|
224
|
+
.sort((a, b) => a.stem.localeCompare(b.stem));
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
topic,
|
|
228
|
+
sets,
|
|
229
|
+
decisionIds: [...ids].sort(),
|
|
230
|
+
fileCount: names.length,
|
|
231
|
+
anchorTotal: sets.reduce(
|
|
232
|
+
(n, s) => n + s.members.reduce((m, f) => m + f.total, 0),
|
|
233
|
+
0,
|
|
234
|
+
),
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Walk a round root: each immediate subdirectory is a topic, plus the root itself. */
|
|
239
|
+
export function censusRoot(root: string): TopicCensus[] {
|
|
240
|
+
const out: TopicCensus[] = [];
|
|
241
|
+
const rootLevel = censusTopic(root, '.');
|
|
242
|
+
if (rootLevel.fileCount > 0) out.push(rootLevel);
|
|
243
|
+
for (const d of fs.readdirSync(root, { withFileTypes: true })) {
|
|
244
|
+
if (!d.isDirectory()) continue;
|
|
245
|
+
const t = censusTopic(path.join(root, d.name), d.name);
|
|
246
|
+
if (t.fileCount > 0) out.push(t);
|
|
247
|
+
}
|
|
248
|
+
return out;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function render(topics: TopicCensus[], write: (s: string) => unknown): void {
|
|
252
|
+
let files = 0;
|
|
253
|
+
let anchors = 0;
|
|
254
|
+
let revisionSets = 0;
|
|
255
|
+
for (const t of topics) {
|
|
256
|
+
write(`\n## ${t.topic}\n`);
|
|
257
|
+
for (const s of t.sets) {
|
|
258
|
+
const tag = s.isRevisionSet ? ` — REVISION SET (${String(s.members.length)}), diff consecutive members` : '';
|
|
259
|
+
write(` ${s.stem}${tag}\n`);
|
|
260
|
+
for (const f of s.members) {
|
|
261
|
+
const a = f.anchors;
|
|
262
|
+
write(
|
|
263
|
+
` ${f.file} ${String(f.bytes)}B/${String(f.lines)}L ` +
|
|
264
|
+
`sep ${String(a.separators)} · head ${String(a.headings)} · ` +
|
|
265
|
+
`step ${String(a.steps)} · enum ${String(a.enumerated)} = ${String(f.total)}\n`,
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
if (s.isRevisionSet) revisionSets += 1;
|
|
269
|
+
}
|
|
270
|
+
if (t.decisionIds.length > 0) write(` decision ids: ${t.decisionIds.join(' ')}\n`);
|
|
271
|
+
files += t.fileCount;
|
|
272
|
+
anchors += t.anchorTotal;
|
|
273
|
+
}
|
|
274
|
+
write(
|
|
275
|
+
`\ntopics ${String(topics.length)} · files ${String(files)} · ` +
|
|
276
|
+
`revision sets ${String(revisionSets)} · anchors ${String(anchors)}\n` +
|
|
277
|
+
'Every anchor leaves the run carrying a ledger row or named in one `no-demand` line.\n' +
|
|
278
|
+
'Every revision set owes a consecutive-member diff (/analyze:inbox Phase 2b).\n',
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
283
|
+
const rootIdx = argv.indexOf('--root');
|
|
284
|
+
const rootArg = rootIdx === -1 ? undefined : argv[rootIdx + 1];
|
|
285
|
+
if (rootArg === undefined) {
|
|
286
|
+
process.stderr.write('usage: inbox_source_census --root <dir> [--json]\n');
|
|
287
|
+
return 2;
|
|
288
|
+
}
|
|
289
|
+
const root = path.resolve(REPO_ROOT, rootArg);
|
|
290
|
+
let stat: fs.Stats;
|
|
291
|
+
try {
|
|
292
|
+
stat = fs.statSync(root);
|
|
293
|
+
} catch {
|
|
294
|
+
process.stderr.write(`❌ inbox_source_census: root not readable: ${root}\n`);
|
|
295
|
+
return 2;
|
|
296
|
+
}
|
|
297
|
+
if (!stat.isDirectory()) {
|
|
298
|
+
process.stderr.write(`❌ inbox_source_census: root is not a directory: ${root}\n`);
|
|
299
|
+
return 2;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const topics = censusRoot(root);
|
|
303
|
+
const files = topics.reduce((n, t) => n + t.fileCount, 0);
|
|
304
|
+
if (files === 0) {
|
|
305
|
+
process.stderr.write(
|
|
306
|
+
`❌ inbox_source_census: 0 text file(s) under ${root} — a census over an ` +
|
|
307
|
+
'empty set is vacuous and must not read as coverage.\n',
|
|
308
|
+
);
|
|
309
|
+
return 2;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (argv.includes('--json')) {
|
|
313
|
+
process.stdout.write(`${JSON.stringify({ root, topics }, null, 2)}\n`);
|
|
314
|
+
return 0;
|
|
315
|
+
}
|
|
316
|
+
render(topics, (s) => process.stdout.write(s));
|
|
317
|
+
return 0;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
321
|
+
process.exit(main());
|
|
322
|
+
}
|
|
@@ -30,6 +30,18 @@ if [ -n "${AGENT_CONFIG_HOOKS_DIR+set}" ]; then
|
|
|
30
30
|
exit 2
|
|
31
31
|
fi
|
|
32
32
|
HOOKS_DIR="$AGENT_CONFIG_HOOKS_DIR"
|
|
33
|
+
elif CONFIGURED_HOOKS_PATH="$(git -C "$PROJECT_ROOT" config --get core.hooksPath 2>/dev/null)" \
|
|
34
|
+
&& [ -n "$CONFIGURED_HOOKS_PATH" ]; then
|
|
35
|
+
# core.hooksPath wins over .git/hooks WHEREVER IT IS SET — git stops looking
|
|
36
|
+
# in the common dir entirely once it is configured. Installing into
|
|
37
|
+
# .git/hooks while git reads somewhere else writes six hooks that never run,
|
|
38
|
+
# and the freshness gate would then compare the copy nothing executes.
|
|
39
|
+
# Resolved relative to PROJECT_ROOT because git interprets a relative
|
|
40
|
+
# core.hooksPath against the top level of the working tree, not against CWD.
|
|
41
|
+
case "$CONFIGURED_HOOKS_PATH" in
|
|
42
|
+
/*) HOOKS_DIR="$CONFIGURED_HOOKS_PATH" ;;
|
|
43
|
+
*) HOOKS_DIR="$PROJECT_ROOT/$CONFIGURED_HOOKS_PATH" ;;
|
|
44
|
+
esac
|
|
33
45
|
elif COMMON_GIT_DIR="$(git -C "$PROJECT_ROOT" rev-parse --git-common-dir 2>/dev/null)"; then
|
|
34
46
|
case "$COMMON_GIT_DIR" in /*) : ;; *) COMMON_GIT_DIR="$PROJECT_ROOT/$COMMON_GIT_DIR" ;; esac
|
|
35
47
|
HOOKS_DIR="$COMMON_GIT_DIR/hooks"
|
|
@@ -574,3 +586,73 @@ EOF
|
|
|
574
586
|
append_auto_sync_block "post-merge" "1"
|
|
575
587
|
append_auto_sync_block "post-checkout" "3"
|
|
576
588
|
echo "✅ Auto-sync block appended to post-merge / post-checkout hooks." >&2
|
|
589
|
+
|
|
590
|
+
# Code-graph freshness, from git, with no daemon ----------------------------
|
|
591
|
+
#
|
|
592
|
+
# road-to-a-graph-that-is-shipped 1.3. A code graph goes stale the moment a
|
|
593
|
+
# commit lands, and the only thing that knew were the reader — after it had
|
|
594
|
+
# already answered from a stale index. These two hooks close that without a
|
|
595
|
+
# resident process: git tells us HEAD moved, and a detached child rebuilds.
|
|
596
|
+
#
|
|
597
|
+
# Four properties, each load-bearing:
|
|
598
|
+
#
|
|
599
|
+
# · BACKGROUND. The refresh is `&`-detached with stdout/stderr closed, so a
|
|
600
|
+
# commit never waits on a parse. Git's own runtime is unchanged.
|
|
601
|
+
# · SINGLE-FLIGHT. `mkdir` is atomic on every POSIX filesystem, so of two
|
|
602
|
+
# concurrent commits exactly one creates the lock and exactly one refresh
|
|
603
|
+
# runs. This is Risk 3 in the roadmap's register — "git hooks pile up
|
|
604
|
+
# rebuilds" — and a lock file written with `>` would not have closed it,
|
|
605
|
+
# because two processes can both truncate-and-write the same path.
|
|
606
|
+
# · BUDGETED. `--budget-seconds` is the engine's own wall-clock ceiling; a
|
|
607
|
+
# child that exceeds it is SIGKILLed and the OLD cache is left untouched
|
|
608
|
+
# (cli.ts's refresh contract). Kill register K7 — never block on a stale
|
|
609
|
+
# graph — holds by construction here, since nothing waits at all.
|
|
610
|
+
# · NEVER BUILDS ONE UNBIDDEN. The block is a no-op unless a cache already
|
|
611
|
+
# exists. Building a graph for someone who never asked for one is the pure
|
|
612
|
+
# cost Risk 1 names; refreshing one they built is the service.
|
|
613
|
+
#
|
|
614
|
+
# The lock is a directory under the gitignored runtime state, so a crashed
|
|
615
|
+
# child leaves a stale lock that the next refresh clears by age rather than
|
|
616
|
+
# blocking forever.
|
|
617
|
+
|
|
618
|
+
append_code_graph_refresh_block() {
|
|
619
|
+
local name="$1"
|
|
620
|
+
cat >> "$HOOKS_DIR/$name" << EOF
|
|
621
|
+
|
|
622
|
+
# --- code-graph background refresh ------------------------------------------
|
|
623
|
+
EOF
|
|
624
|
+
if [ "$name" = "post-checkout" ]; then
|
|
625
|
+
cat >> "$HOOKS_DIR/$name" << 'EOF'
|
|
626
|
+
# A file-checkout ($3 = 0) moves no commits, so there is nothing to be behind.
|
|
627
|
+
if [ "${3:-1}" = "0" ]; then
|
|
628
|
+
exit 0
|
|
629
|
+
fi
|
|
630
|
+
EOF
|
|
631
|
+
fi
|
|
632
|
+
cat >> "$HOOKS_DIR/$name" << 'EOF'
|
|
633
|
+
cg_cache="agents/runtime/state/code-graph-v1.json"
|
|
634
|
+
cg_lock="agents/runtime/state/code-graph-refresh.lock"
|
|
635
|
+
if [ -f "$cg_cache" ] && [ -x ./agent-config ]; then
|
|
636
|
+
# Clear a lock left by a killed child: older than the largest budget any
|
|
637
|
+
# caller passes, so it cannot race a refresh that is legitimately running.
|
|
638
|
+
if [ -d "$cg_lock" ]; then
|
|
639
|
+
if [ -z "$(find "$cg_lock" -maxdepth 0 -mmin -5 2>/dev/null)" ]; then
|
|
640
|
+
rmdir "$cg_lock" 2>/dev/null || true
|
|
641
|
+
fi
|
|
642
|
+
fi
|
|
643
|
+
# Atomic: exactly one of N concurrent hooks wins this, and the losers do
|
|
644
|
+
# nothing rather than queueing a second rebuild behind the first.
|
|
645
|
+
if mkdir "$cg_lock" 2>/dev/null; then
|
|
646
|
+
(
|
|
647
|
+
trap 'rmdir "$cg_lock" 2>/dev/null || true' EXIT
|
|
648
|
+
./agent-config code-graph refresh --budget-seconds 60 >/dev/null 2>&1 || true
|
|
649
|
+
) >/dev/null 2>&1 &
|
|
650
|
+
fi
|
|
651
|
+
fi
|
|
652
|
+
exit 0
|
|
653
|
+
EOF
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
append_code_graph_refresh_block "post-commit"
|
|
657
|
+
append_code_graph_refresh_block "post-checkout"
|
|
658
|
+
echo "✅ Code-graph refresh block appended to post-commit / post-checkout hooks." >&2
|