@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
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* ADR-236 decided that a rule is delivered from exactly one layer: package-only
|
|
9
9
|
* rules stay in the project tree, everything else arrives from the host's global
|
|
10
10
|
* directory and is withheld from the project tree. Measured in a freshly
|
|
11
|
-
* generated worktree with
|
|
11
|
+
* generated worktree with a verified host layer, it holds for one host in five:
|
|
12
12
|
*
|
|
13
13
|
* ```
|
|
14
14
|
* .claude/rules 13 files, 13 package-only, 0 global-only
|
|
@@ -51,7 +51,7 @@ import * as fs from 'node:fs';
|
|
|
51
51
|
import * as os from 'node:os';
|
|
52
52
|
import * as path from 'node:path';
|
|
53
53
|
|
|
54
|
-
import { isExclusivelyPackageOnly
|
|
54
|
+
import { isExclusivelyPackageOnly } from '../install/partitionEligibility.js';
|
|
55
55
|
import { PROJECT_RULE_DIRS, globalRuleLayerNames, globalRuleLayerPath } from '../install/globalRuleLayers.js';
|
|
56
56
|
import { GateLedger, UnaccountedTargetsError } from './_lib/gate_ledger.js';
|
|
57
57
|
import { reportScanned } from './_lib/scan_scope.js';
|
|
@@ -333,7 +333,7 @@ function selfTest(): number {
|
|
|
333
333
|
/**
|
|
334
334
|
* `--prune`: delete exactly the files this gate reports as duplicated.
|
|
335
335
|
*
|
|
336
|
-
*
|
|
336
|
+
* **Why a gate ships a repair at all.**
|
|
337
337
|
*
|
|
338
338
|
* The documented repair is `task generate-tools`, and on this repository's own main
|
|
339
339
|
* checkout that command is **structurally inert**: `agents/.agent-tools.yml` is
|
|
@@ -381,17 +381,39 @@ function prune(audit: PartitionAudit, root: string): number {
|
|
|
381
381
|
}
|
|
382
382
|
|
|
383
383
|
/**
|
|
384
|
-
* Does
|
|
385
|
-
*
|
|
384
|
+
* Does THIS directory's duplication mean an emitter failed, or that the
|
|
385
|
+
* per-directory fail-safe legitimately kept the full projection?
|
|
386
386
|
*
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
*
|
|
390
|
-
*
|
|
391
|
-
*
|
|
387
|
+
* **The discriminator, corrected 2026-09-07 after a neutral review.**
|
|
388
|
+
*
|
|
389
|
+
* It used to be the repo-wide host-layer verdict — first a `PartitionMode`
|
|
390
|
+
* string, then the `verified` boolean the 2026-09-07 change reduced it to. Both
|
|
391
|
+
* were wrong in BOTH directions once the withhold stopped reading
|
|
392
|
+
* `installed.lock`:
|
|
393
|
+
*
|
|
394
|
+
* - FALSE NEGATIVE: an install one release behind makes `verified === false`
|
|
395
|
+
* for the whole release window, so a genuine emitter failure — the defect
|
|
396
|
+
* this gate exists to catch — printed and returned 0.
|
|
397
|
+
* - FALSE POSITIVE: a verified layer that legitimately lacks ONE rule makes
|
|
398
|
+
* `partitionRulesForDir` keep all ~104 by design (rules are all-or-nothing,
|
|
399
|
+
* `ruleLayerPartition.ts`), and the gate exited 1 on a correct tree.
|
|
400
|
+
* `rule_partition_per_host.test.ts` pins that state as legal.
|
|
401
|
+
*
|
|
402
|
+
* The right signal was already computed and never asked: `soleCarrier` — the
|
|
403
|
+
* global-scope rules this directory projects that its host layer does NOT hold.
|
|
404
|
+
* Non-empty means the fail-safe fired for a reason a reader can check; empty
|
|
405
|
+
* means the layer carries every withheld name and the emitter simply did not
|
|
406
|
+
* withhold. Per directory, from data on hand, and independent of any lockfile —
|
|
407
|
+
* so the verdict no longer differs between a maintainer machine mid-release and
|
|
408
|
+
* a CI checkout.
|
|
409
|
+
*
|
|
410
|
+
* Also note what a stale layer can NOT do under per-name withholding: if the
|
|
411
|
+
* layer holds the name, the project copy is withheld whatever its CONTENT. So
|
|
412
|
+
* staleness never produces a duplicate, and the old message's second
|
|
413
|
+
* justification was keyed on the wrong fact.
|
|
392
414
|
*/
|
|
393
|
-
export function partitionEnforces(
|
|
394
|
-
return
|
|
415
|
+
export function partitionEnforces(dir: Pick<DirAudit, 'soleCarrier'>): boolean {
|
|
416
|
+
return dir.soleCarrier.length === 0;
|
|
395
417
|
}
|
|
396
418
|
|
|
397
419
|
export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
@@ -485,34 +507,32 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
|
485
507
|
// partition gate and `check_bridge_derivation` deadlock, each red in the state
|
|
486
508
|
// the other requires.
|
|
487
509
|
//
|
|
488
|
-
//
|
|
489
|
-
//
|
|
490
|
-
//
|
|
491
|
-
//
|
|
492
|
-
// measuring the release.
|
|
510
|
+
// Split PER DIRECTORY, not on a repo-wide verdict — see `partitionEnforces` for
|
|
511
|
+
// why the verdict was wrong in both directions. A directory that is the sole
|
|
512
|
+
// carrier of at least one global-scope rule kept the full projection by design;
|
|
513
|
+
// one that carries none is an emitter that did not withhold.
|
|
493
514
|
//
|
|
494
|
-
//
|
|
495
|
-
//
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
515
|
+
// In CI both layers are absent, `offenders` is empty, and neither branch is
|
|
516
|
+
// reached — so nothing here changes the CI reading.
|
|
517
|
+
const failing = offenders.filter((d) => partitionEnforces(d));
|
|
518
|
+
for (const d of offenders.filter((d) => !partitionEnforces(d))) {
|
|
519
|
+
process.stdout.write(
|
|
520
|
+
`⚠️ ${d.dir}: ${String(d.duplicated.length)} rule(s) also present in ` +
|
|
521
|
+
`${d.globalLayer ?? '(unknown)'} — kept by design: this directory is the sole ` +
|
|
522
|
+
`carrier of ${String(d.soleCarrier.length)} global-scope rule(s) ` +
|
|
523
|
+
`(e.g. ${d.soleCarrier.slice(0, 3).join(', ')}), and the rule partition is ` +
|
|
524
|
+
`all-or-nothing per directory.\n`,
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
if (failing.length === 0) {
|
|
506
528
|
process.stdout.write(
|
|
507
|
-
|
|
508
|
-
`
|
|
509
|
-
`working. Reported, not enforced — run \`agent-config install\` to activate the ` +
|
|
510
|
-
`partition here, then this gate has teeth again.\n`,
|
|
529
|
+
`✅ check_rule_layer_partition: every duplication is the per-directory fail-safe ` +
|
|
530
|
+
`working — no directory duplicates a rule its host layer fully carries.\n`,
|
|
511
531
|
);
|
|
512
532
|
return 0;
|
|
513
533
|
}
|
|
514
534
|
|
|
515
|
-
for (const d of
|
|
535
|
+
for (const d of failing) {
|
|
516
536
|
process.stderr.write(
|
|
517
537
|
`${report ? '⚠️ ' : '❌ '} ${d.dir}: ${String(d.duplicated.length)} rule(s) also present in ` +
|
|
518
538
|
`${d.globalLayer ?? '(unknown)'} — e.g. ${d.duplicated.slice(0, 5).join(', ')}\n`,
|
|
@@ -98,6 +98,13 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
98
98
|
import { GateLedger, UnaccountedTargetsError } from './_lib/gate_ledger.js';
|
|
99
99
|
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
100
100
|
import { _resetStateForTest as _pointCondenseAt, projected_rule_trees } from './condense.js';
|
|
101
|
+
import { leanProjectionHostsRaw, leanProjectionModeRaw } from './_lib/hook_settings.js';
|
|
102
|
+
import {
|
|
103
|
+
normalizeLeanProjectionMode,
|
|
104
|
+
resolveLeanProjectionHosts,
|
|
105
|
+
writesThinFiles,
|
|
106
|
+
} from './_lib/lean_projection_mode.js';
|
|
107
|
+
import { is_thin_entry } from './project_thin_rules.js';
|
|
101
108
|
|
|
102
109
|
const _HERE = path.resolve(fileURLToPath(import.meta.url));
|
|
103
110
|
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
@@ -105,7 +112,24 @@ const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
|
105
112
|
/** Where the projected rules are condensed to — the freshness comparison's left side. */
|
|
106
113
|
const DIST_RULES = path.join('dist', 'agent-src', 'rules');
|
|
107
114
|
|
|
108
|
-
export type ProjectionFindingKind = 'missing' | 'stale' | 'dangling';
|
|
115
|
+
export type ProjectionFindingKind = 'missing' | 'stale' | 'dangling' | 'thinned';
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Tool dir → the host id whose tree it is.
|
|
119
|
+
*
|
|
120
|
+
* Mirrors `condense._DIR_TOOL_ID` for the three dirs this gate covers. It is a
|
|
121
|
+
* second spelling of a mapping `condense` already owns, which is a real cost —
|
|
122
|
+
* accepted because `_DIR_TOOL_ID` is module-private and exporting it would widen
|
|
123
|
+
* `condense`'s surface for one consumer, in a file already ~1,200 lines past the
|
|
124
|
+
* source-size ceiling where every added line is one unit of ratchet debt.
|
|
125
|
+
* `thinnedTreeFindings` fails CLOSED on an unmapped dir (see there), so a dir
|
|
126
|
+
* added upstream and missed here is reported rather than silently exempted.
|
|
127
|
+
*/
|
|
128
|
+
export const TREE_HOST_ID: Readonly<Record<string, string>> = {
|
|
129
|
+
'.claude/rules': 'claude-code',
|
|
130
|
+
'.cursor/rules': 'cursor',
|
|
131
|
+
'.clinerules': 'cline',
|
|
132
|
+
};
|
|
109
133
|
|
|
110
134
|
export interface ProjectionFinding {
|
|
111
135
|
/** Repo-relative tool dir, e.g. `.claude/rules`. */
|
|
@@ -138,9 +162,59 @@ function _lstatOrNull(p: string): fs.Stats | null {
|
|
|
138
162
|
* computed here so the caller controls which root the generator's emit plan was
|
|
139
163
|
* derived from, and so a test can pin an expectation without a settings file.
|
|
140
164
|
*/
|
|
165
|
+
/**
|
|
166
|
+
* The host axis (road-to-delivery-for-every-host 1.3).
|
|
167
|
+
*
|
|
168
|
+
* A stub is a COMPLETE projection for a host inside `lean_projection.hosts` —
|
|
169
|
+
* that is what delivery mode means, and failing it there would make the mode
|
|
170
|
+
* unusable. For every other host a stub is the D1 defect: the body left the
|
|
171
|
+
* tree and no concern is bound to bring it back, so the rule reaches that host
|
|
172
|
+
* at no scope at all while every file-count check still passes.
|
|
173
|
+
*
|
|
174
|
+
* Fails CLOSED in two directions, both deliberate:
|
|
175
|
+
* · a tool dir absent from `TREE_HOST_ID` maps to `''`, which is in no host
|
|
176
|
+
* list, so an unmapped dir is treated as NOT a delivery host and its stubs
|
|
177
|
+
* are reported. A new dir is then a loud finding rather than a silent
|
|
178
|
+
* exemption.
|
|
179
|
+
* · an unreadable entry is skipped rather than guessed at — `missing` and
|
|
180
|
+
* `dangling` already own that case, and inventing a third verdict from a
|
|
181
|
+
* failed read would double-report one defect.
|
|
182
|
+
*/
|
|
183
|
+
export function thinnedTreeFindings(
|
|
184
|
+
repoRoot: string,
|
|
185
|
+
expected: Readonly<Record<string, readonly string[]>>,
|
|
186
|
+
deliveryHosts: readonly string[],
|
|
187
|
+
): ProjectionFinding[] {
|
|
188
|
+
const out: ProjectionFinding[] = [];
|
|
189
|
+
for (const [tree, rules] of Object.entries(expected)) {
|
|
190
|
+
const hostId = TREE_HOST_ID[tree] ?? '';
|
|
191
|
+
if (deliveryHosts.includes(hostId)) continue;
|
|
192
|
+
for (const rule of rules) {
|
|
193
|
+
let text: string;
|
|
194
|
+
try {
|
|
195
|
+
text = fs.readFileSync(path.join(repoRoot, tree, rule), 'utf-8');
|
|
196
|
+
} catch {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (!is_thin_entry(text)) continue;
|
|
200
|
+
out.push({
|
|
201
|
+
tree,
|
|
202
|
+
rule,
|
|
203
|
+
kind: 'thinned',
|
|
204
|
+
message:
|
|
205
|
+
`pointer stub in a tree whose host (${hostId === '' ? 'unmapped' : hostId}) is not in ` +
|
|
206
|
+
`lean_projection.hosts — the body reaches this host at no scope, and no concern ` +
|
|
207
|
+
`is bound to deliver it back`,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return out;
|
|
212
|
+
}
|
|
213
|
+
|
|
141
214
|
export function auditRuleProjection(
|
|
142
215
|
repoRoot: string,
|
|
143
216
|
expected: Readonly<Record<string, readonly string[]>>,
|
|
217
|
+
deliveryHosts: readonly string[] = [],
|
|
144
218
|
): ProjectionAudit {
|
|
145
219
|
const ledger = new GateLedger('check_rule_projection_integrity');
|
|
146
220
|
const findings: ProjectionFinding[] = [];
|
|
@@ -248,9 +322,34 @@ export function auditRuleProjection(
|
|
|
248
322
|
}
|
|
249
323
|
}
|
|
250
324
|
|
|
325
|
+
// Appended after the completeness/freshness pass rather than folded into it:
|
|
326
|
+
// a stub entry EXISTS and is FRESH, so it is legitimately `complete` on the
|
|
327
|
+
// ledger. The host axis is a separate question about the entry's CONTENT,
|
|
328
|
+
// and conflating the two would make a delivery host's stub a ledger failure.
|
|
329
|
+
findings.push(...thinnedTreeFindings(repoRoot, expected, deliveryHosts));
|
|
330
|
+
|
|
251
331
|
return { findings, treePresent, ledger };
|
|
252
332
|
}
|
|
253
333
|
|
|
334
|
+
/**
|
|
335
|
+
* The `lean_projection` block as it stands under `root`.
|
|
336
|
+
*
|
|
337
|
+
* Read with the HOOK-side indentation reader rather than a YAML parse, on
|
|
338
|
+
* purpose: this gate runs first in the pre-push chain and on a fresh CI
|
|
339
|
+
* checkout, and a parser failure there must not fail a rule audit for a reason
|
|
340
|
+
* unrelated to rules. `_lib/lean_projection_mode.ts` owns what the values MEAN,
|
|
341
|
+
* so this gate and the projector cannot disagree.
|
|
342
|
+
*
|
|
343
|
+
* `mode` is checked before `hosts`: with the mode off no host is a delivery
|
|
344
|
+
* host, so a `hosts:` list left behind after a rollback exempts nothing and
|
|
345
|
+
* every stub in every tree is reported.
|
|
346
|
+
*/
|
|
347
|
+
function _lean_projection_settings_for(root: string): { hosts: readonly string[] } {
|
|
348
|
+
const mode = normalizeLeanProjectionMode(leanProjectionModeRaw(root));
|
|
349
|
+
if (!writesThinFiles(mode)) return { hosts: [] };
|
|
350
|
+
return { hosts: resolveLeanProjectionHosts(leanProjectionHostsRaw(root)).hosts };
|
|
351
|
+
}
|
|
352
|
+
|
|
254
353
|
/** Group findings by tree, preserving rule order, for a report a reader can act on. */
|
|
255
354
|
export function renderFindings(audit: ProjectionAudit): string[] {
|
|
256
355
|
const lines: string[] = [];
|
|
@@ -297,7 +396,8 @@ export function main(argv?: readonly string[]): number {
|
|
|
297
396
|
|
|
298
397
|
let audit: ProjectionAudit;
|
|
299
398
|
try {
|
|
300
|
-
|
|
399
|
+
const lean = _lean_projection_settings_for(root);
|
|
400
|
+
audit = auditRuleProjection(root, projected_rule_trees(), lean.hosts);
|
|
301
401
|
} catch (exc) {
|
|
302
402
|
if (exc instanceof DeadScopeError) {
|
|
303
403
|
process.stderr.write(`❌ ${exc.message}\n`);
|
|
@@ -34,15 +34,31 @@
|
|
|
34
34
|
* fix, which teaches readers to ignore it — the exact failure this estate has
|
|
35
35
|
* recorded twice already.
|
|
36
36
|
*
|
|
37
|
-
* The reason
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* `**Status:** resolved
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* the
|
|
37
|
+
* The reason has been corrected twice rather than left standing. It was first
|
|
38
|
+
* that `road-to-single-delivery` Phase 2 was HALTED on blocker
|
|
39
|
+
* `partition-current-layer-undecidable` — no longer true; that blocker reads
|
|
40
|
+
* `**Status:** resolved` and the partition shipped under ADR-236 Phase 2. It was
|
|
41
|
+
* then a per-machine activation: the partition withheld only where the host layer
|
|
42
|
+
* verified against `installed.lock`, so an install one release behind kept the
|
|
43
|
+
* FULL projection by design and the overlap here was the fail-safe working.
|
|
44
|
+
*
|
|
45
|
+
* That second reason is void as of the ADR-236 amendment of 2026-09-07. The
|
|
46
|
+
* withhold no longer reads `installed.lock` at all — it reads each host
|
|
47
|
+
* directory's own contents, per NAME. An overlap this gate reports is therefore
|
|
48
|
+
* one of exactly two things now, and neither is a repo-wide fail-safe:
|
|
49
|
+
*
|
|
50
|
+
* - an artefact the host layer genuinely does NOT hold, kept on its own merits
|
|
51
|
+
* (the per-name fail-safe, and the right answer);
|
|
52
|
+
* - an emitter that failed to withhold what the evidence said to withhold
|
|
53
|
+
* (a defect).
|
|
54
|
+
*
|
|
55
|
+
* It still reports rather than enforcing, for the unchanged reason above. The
|
|
56
|
+
* second reason an earlier draft of this note gave — that the `commands` row's
|
|
57
|
+
* cluster-DIRECTORY key could read a correct tree as a collision — was true when
|
|
58
|
+
* written and was fixed in the same change: that row keys on the posix subpath
|
|
59
|
+
* now (see TYPES). Left recorded rather than deleted, because the argument is
|
|
60
|
+
* what justified `--enforce` staying opt-in and a reader should see that it no
|
|
61
|
+
* longer applies.
|
|
46
62
|
*
|
|
47
63
|
* WHERE THIS CHECK IS MEANINGFUL — read this before quoting one of its numbers
|
|
48
64
|
* (road-to-session-closeout 3.3). It is a DEVELOPER-MACHINE check. It reads two
|
|
@@ -100,13 +116,23 @@ const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
|
100
116
|
* (`commands/<cluster>/<sub>.md`), so this gate's `commands` row compares cluster
|
|
101
117
|
* DIRECTORY names — a coarser key than the thing the host registers.
|
|
102
118
|
*
|
|
103
|
-
* That gap
|
|
104
|
-
*
|
|
105
|
-
* host layer
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
119
|
+
* That gap USED to be recorded rather than closed, on a measurement: while the
|
|
120
|
+
* colon-form generator was gated by one repo-wide verdict, a machine with a
|
|
121
|
+
* verified host layer projected ZERO commands and a machine without one had
|
|
122
|
+
* nothing to compare — so the coarse key was imprecise exactly where it had no
|
|
123
|
+
* work to do.
|
|
124
|
+
*
|
|
125
|
+
* **The ADR-236 amendment of 2026-09-07 falsified that, and it was OBSERVED, not
|
|
126
|
+
* predicted.** Withholding per name leaves the project layer holding exactly the
|
|
127
|
+
* commands the host lacks, and those can share a cluster with commands the host
|
|
128
|
+
* has: measured the same day, `.claude/commands/analyze/` held `repo.md` and
|
|
129
|
+
* `roadmap-repos.md` while `~/.claude/commands/analyze/` held `inbox.md` and
|
|
130
|
+
* seven others. Zero duplication, and the directory key reported a collision.
|
|
131
|
+
*
|
|
132
|
+
* So the `commands` row now keys on the POSIX SUBPATH (`<cluster>/<sub>.md`),
|
|
133
|
+
* which is the host's own unit. `readLayer` takes a `recursive` flag for it;
|
|
134
|
+
* `shape` stays a top-level reading so the shape-mismatch line keeps comparing
|
|
135
|
+
* the same thing it always did.
|
|
110
136
|
*
|
|
111
137
|
* `personas` and `user-types` were added 2026-08-21 (closure correction). See
|
|
112
138
|
* `unknownProjectFamilies` for what now prevents the next omission.
|
|
@@ -182,7 +208,7 @@ interface LayerReading {
|
|
|
182
208
|
shape: LayerShape;
|
|
183
209
|
}
|
|
184
210
|
|
|
185
|
-
function readLayer(dir: string): LayerReading | null {
|
|
211
|
+
function readLayer(dir: string, recursive = false): LayerReading | null {
|
|
186
212
|
let entries: fs.Dirent[];
|
|
187
213
|
try {
|
|
188
214
|
if (!fs.statSync(dir).isDirectory()) return null;
|
|
@@ -191,7 +217,11 @@ function readLayer(dir: string): LayerReading | null {
|
|
|
191
217
|
return null;
|
|
192
218
|
}
|
|
193
219
|
const shape: LayerShape = { symlinks: 0, dirs: 0, files: 0 };
|
|
194
|
-
|
|
220
|
+
// README.md is excluded from BOTH the names and the shape. Counting it in the
|
|
221
|
+
// shape while filtering it from the names printed a self-contradicting line
|
|
222
|
+
// (`personas global 31 · shape (l0/d2/f30)` — 32 entries, 31 names), caught by
|
|
223
|
+
// a second neutral review.
|
|
224
|
+
for (const e of entries.filter((x) => x.name !== 'README.md')) {
|
|
195
225
|
// isSymbolicLink() is checked FIRST and deliberately: readdir with
|
|
196
226
|
// withFileTypes does not follow links, but a reader who tested isDirectory
|
|
197
227
|
// first would classify a symlink-to-directory as a directory and lose the
|
|
@@ -200,7 +230,41 @@ function readLayer(dir: string): LayerReading | null {
|
|
|
200
230
|
else if (e.isDirectory()) shape.dirs += 1;
|
|
201
231
|
else shape.files += 1;
|
|
202
232
|
}
|
|
203
|
-
|
|
233
|
+
if (!recursive) {
|
|
234
|
+
// README.md excluded for EVERY family, not only commands. It was excluded
|
|
235
|
+
// on the recursive path from the start and nowhere else, so a README in
|
|
236
|
+
// both layers would have counted as one duplicated artefact for rules,
|
|
237
|
+
// skills, personas, user-types and agents and not for commands — a latent
|
|
238
|
+
// asymmetry a neutral review caught on 2026-09-07. It names no artefact in
|
|
239
|
+
// any family.
|
|
240
|
+
return { names: entries.map((e) => e.name).filter((n) => n !== 'README.md').sort(), shape };
|
|
241
|
+
}
|
|
242
|
+
return { names: _mdSubpaths(dir).sort(), shape };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Every `*.md` under `dir`, as a posix path relative to it.
|
|
247
|
+
*
|
|
248
|
+
* The identity key for the `commands` row — see the note on TYPES. `README.md` is
|
|
249
|
+
* excluded because both layers carry one and it names no command.
|
|
250
|
+
*/
|
|
251
|
+
function _mdSubpaths(dir: string): string[] {
|
|
252
|
+
const out: string[] = [];
|
|
253
|
+
const walk = (abs: string, rel: string): void => {
|
|
254
|
+
let kids: fs.Dirent[];
|
|
255
|
+
try {
|
|
256
|
+
kids = fs.readdirSync(abs, { withFileTypes: true });
|
|
257
|
+
} catch {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
for (const k of kids) {
|
|
261
|
+
const childRel = rel === '' ? k.name : `${rel}/${k.name}`;
|
|
262
|
+
if (k.isDirectory()) walk(path.join(abs, k.name), childRel);
|
|
263
|
+
else if (k.name.endsWith('.md') && k.name !== 'README.md') out.push(childRel);
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
walk(dir, '');
|
|
267
|
+
return out;
|
|
204
268
|
}
|
|
205
269
|
|
|
206
270
|
/**
|
|
@@ -230,8 +294,9 @@ export function declaresPaths(file: string): 'yes' | 'no' | 'unreadable' {
|
|
|
230
294
|
export function readType(type: ArtefactType, globalRoot: string, projectRoot: string): TypeReading {
|
|
231
295
|
const gDir = path.join(globalRoot, type);
|
|
232
296
|
const pDir = path.join(projectRoot, type);
|
|
233
|
-
const
|
|
234
|
-
const
|
|
297
|
+
const recursive = type === 'commands';
|
|
298
|
+
const gLayer = readLayer(gDir, recursive);
|
|
299
|
+
const pLayer = readLayer(pDir, recursive);
|
|
235
300
|
const globalNames = gLayer?.names ?? null;
|
|
236
301
|
const projectNames = pLayer?.names ?? null;
|
|
237
302
|
const globalShape = gLayer?.shape ?? null;
|
|
@@ -500,11 +565,12 @@ export function main(argv?: readonly string[]): number {
|
|
|
500
565
|
process.stdout.write(
|
|
501
566
|
'usage: check_single_delivery [--global DIR] [--project DIR] [--enforce] [--quiet]\n' +
|
|
502
567
|
'\n' +
|
|
503
|
-
'Reports by default and exits 0: the
|
|
504
|
-
'
|
|
505
|
-
'
|
|
506
|
-
'
|
|
507
|
-
'
|
|
568
|
+
'Reports by default and exits 0: since the ADR-236 amendment of\n' +
|
|
569
|
+
'2026-09-07 the project layer withholds per ARTEFACT NAME on each host\n' +
|
|
570
|
+
"directory's own contents, so an artefact the host layer does not hold is\n" +
|
|
571
|
+
'kept BY DESIGN and reads as overlap here. --enforce exits 1 on any\n' +
|
|
572
|
+
'overlap. It stays opt-in because a machine whose global layer is stale\n' +
|
|
573
|
+
'cannot fix the overlap by regenerating — only by `agent-config install`.\n',
|
|
508
574
|
);
|
|
509
575
|
return 0;
|
|
510
576
|
} else if (a !== undefined) {
|
|
@@ -626,11 +692,11 @@ export function main(argv?: readonly string[]): number {
|
|
|
626
692
|
}
|
|
627
693
|
process.stdout.write(
|
|
628
694
|
`⚠️ check_single_delivery: ${detail}. Reported, not enforced — ` +
|
|
629
|
-
'the
|
|
630
|
-
|
|
631
|
-
'
|
|
632
|
-
'and
|
|
633
|
-
'
|
|
695
|
+
'since the ADR-236 amendment of 2026-09-07 the project layer withholds per ' +
|
|
696
|
+
"ARTEFACT NAME on each host directory's own contents, never on a version in " +
|
|
697
|
+
'`installed.lock`. So an artefact the host layer does not hold is kept BY DESIGN ' +
|
|
698
|
+
'and appears here; a stale global layer is fixed by `agent-config install`. Use ' +
|
|
699
|
+
'--enforce where every layer is known current.\n',
|
|
634
700
|
);
|
|
635
701
|
return 0;
|
|
636
702
|
}
|
|
@@ -157,10 +157,23 @@ async function main(argv: readonly string[] = process.argv.slice(2)): Promise<nu
|
|
|
157
157
|
return 0;
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
// `build:delegate-aux` bundles this file with `--splitting`, which can move the
|
|
161
|
+
// module body into a shared chunk: `import.meta.url` then names the CHUNK, the
|
|
162
|
+
// URL comparison below never matches, and the entry silently no-ops. Inside that
|
|
163
|
+
// bundle the invoked file name is the reliable signal (same pattern as
|
|
164
|
+
// src/scripts/_cli/cmd_migrate.ts). Defined by that build only.
|
|
165
|
+
declare const __AGENT_CONFIG_CLI_DELEGATE__: boolean | undefined;
|
|
160
166
|
function _isCliEntry(): boolean {
|
|
161
167
|
if (process.argv[1] === undefined) {
|
|
162
168
|
return false;
|
|
163
169
|
}
|
|
170
|
+
if (typeof __AGENT_CONFIG_CLI_DELEGATE__ !== 'undefined' && __AGENT_CONFIG_CLI_DELEGATE__) {
|
|
171
|
+
// A miss falls THROUGH to the URL comparison below, which is the
|
|
172
|
+
// symlinked-invocation case that fallback exists for.
|
|
173
|
+
if (path.basename(process.argv[1], '.js') === 'check_update_banner') {
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
164
177
|
const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
|
|
165
178
|
if (import.meta.url === argvUrl) {
|
|
166
179
|
return true;
|