@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
|
@@ -397,27 +397,74 @@ export function restore_hot_context(
|
|
|
397
397
|
|
|
398
398
|
/**
|
|
399
399
|
* Build the compact memory index when `memory.session_index: on`; `null`
|
|
400
|
-
* on the default-off path, empty corpus,
|
|
401
|
-
*
|
|
402
|
-
*
|
|
400
|
+
* on the default-off path, empty corpus, a refused root, an already-served
|
|
401
|
+
* session, or any failure (never blocks).
|
|
402
|
+
*
|
|
403
|
+
* Memory roots are cwd-relative (same contract as the MCP tool handlers), so
|
|
404
|
+
* the build runs chdir-wrapped to the workspace root — and that relativity is
|
|
405
|
+
* exactly why the trust contract exists: whatever cwd this lands on is where
|
|
406
|
+
* `agents/memory` is read from. `_lib/session_index_trust.ts` carries the six
|
|
407
|
+
* properties the 2026-09-07 D3 ruling required (identity, canonicalization,
|
|
408
|
+
* freshness, ordering, duplicate invocation, size limits); this function is
|
|
409
|
+
* where two of them bind, because they need runtime facts the module cannot
|
|
410
|
+
* see — the workspace root and the session id.
|
|
411
|
+
*
|
|
412
|
+
* Refusals are logged with their code rather than swallowed. `null` alone
|
|
413
|
+
* cannot distinguish "no curated memory here" from "this root pointed at
|
|
414
|
+
* another tree", and that distinction is the whole point of the contract.
|
|
403
415
|
*/
|
|
404
|
-
function _session_index_block_or_null(root: string): string | null {
|
|
416
|
+
function _session_index_block_or_null(root: string, sessionId: string): string | null {
|
|
405
417
|
try {
|
|
406
418
|
// Lazy require (ESM-safe via createRequire) keeps the default-off
|
|
407
419
|
// fast path free of the memory_lookup + settings-cascade import cost.
|
|
408
420
|
const req = createRequire(import.meta.url);
|
|
409
421
|
const mod = req('./session_memory_index.js') as {
|
|
410
422
|
session_index_enabled: (root: string) => boolean;
|
|
411
|
-
|
|
423
|
+
serve_session_index_block: (grant: unknown, cap?: number) => string | null;
|
|
424
|
+
session_index_trust: (workspaceRoot: string) => { ok: boolean; code?: string; detail?: string };
|
|
412
425
|
};
|
|
413
426
|
if (!mod.session_index_enabled(root)) return null;
|
|
427
|
+
|
|
428
|
+
// P1-P3, computed ONCE here and handed to the server as a grant. The
|
|
429
|
+
// builder used to re-check it, which R2 finding 7 named as a duplicate
|
|
430
|
+
// for a diagnostic nothing forwards; `serve_session_index_block` now
|
|
431
|
+
// takes the grant, so the check cannot be skipped and is not repeated.
|
|
432
|
+
const verdict = mod.session_index_trust(root);
|
|
433
|
+
if (!verdict.ok) {
|
|
434
|
+
process.stderr.write(`hot-context-hook: session index refused [${String(verdict.code)}]: ${String(verdict.detail)}\n`);
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
|
|
414
438
|
const prev = process.cwd();
|
|
439
|
+
let block: string | null;
|
|
415
440
|
try {
|
|
416
441
|
process.chdir(root);
|
|
417
|
-
|
|
442
|
+
block = mod.serve_session_index_block(verdict);
|
|
418
443
|
} finally {
|
|
419
444
|
process.chdir(prev);
|
|
420
445
|
}
|
|
446
|
+
|
|
447
|
+
// P5 — duplicate invocation, claimed LAST. `session_start` can fire
|
|
448
|
+
// more than once for one session (resume, host reconnect, compaction
|
|
449
|
+
// boundary on some hosts), and two injections double a fixed cost while
|
|
450
|
+
// presenting two corpora as one session's memory.
|
|
451
|
+
//
|
|
452
|
+
// The ordering is the whole property and it took two corrections to get
|
|
453
|
+
// right. Claiming before the verdict let a refused root burn the
|
|
454
|
+
// session's one claim; claiming before the RENDER let an empty corpus
|
|
455
|
+
// burn it too (R2 finding 5) — so a session that started before its
|
|
456
|
+
// memory was curated would never receive an index afterwards. Nothing
|
|
457
|
+
// is claimed until there is something to serve.
|
|
458
|
+
if (block === null) return null;
|
|
459
|
+
const trust = req('./_lib/session_index_trust.js') as {
|
|
460
|
+
claimSessionOnce: (workspaceRoot: string, sessionId: string) => { ok: boolean; code?: string };
|
|
461
|
+
};
|
|
462
|
+
const claim = trust.claimSessionOnce(root, sessionId);
|
|
463
|
+
if (!claim.ok) {
|
|
464
|
+
process.stderr.write(`hot-context-hook: session index already served [${String(claim.code)}]\n`);
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
return block;
|
|
421
468
|
} catch (exc) {
|
|
422
469
|
process.stderr.write(`hot-context-hook: session index skipped: ${String(exc)}\n`);
|
|
423
470
|
return null;
|
|
@@ -486,7 +533,15 @@ export function main(): number {
|
|
|
486
533
|
// Opt-in compact memory index (road-to-memory-retrieval-economy
|
|
487
534
|
// P5) — default OFF; rides the same injection surface. Memory
|
|
488
535
|
// roots are cwd-relative, so resolve from the workspace root.
|
|
489
|
-
|
|
536
|
+
// R2 finding 1: reading the payload alone left P5 INERT whenever
|
|
537
|
+
// the host supplied no payload id. `dispatch_hook.ts` resolves
|
|
538
|
+
// `envelope.session_id` from the payload OR `AGENT_SESSION_ID`, and
|
|
539
|
+
// every sibling concern reads the envelope first — proven by two
|
|
540
|
+
// dispatcher runs that emitted the block twice and never latched.
|
|
541
|
+
const sessionId = String(
|
|
542
|
+
envelope.session_id ?? payload.session_id ?? payload.sessionId ?? '',
|
|
543
|
+
);
|
|
544
|
+
const indexBlock = _session_index_block_or_null(root, sessionId);
|
|
490
545
|
if (indexBlock !== null) {
|
|
491
546
|
blocks.push(indexBlock);
|
|
492
547
|
}
|
|
@@ -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
|