@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
|
@@ -155,8 +155,6 @@ import { materialiseSubtree } from './_lib/base_tree.js';
|
|
|
155
155
|
import { SKILLS_POSIX, measureSkillEstate } from './_lib/skill_estate.js';
|
|
156
156
|
import { CONCERN_MANIFEST_POSIX, countConcerns } from './_lib/concern_estate.js';
|
|
157
157
|
import { runGateCli, runSelfTest, type SelfTestCase } from './_lib/gate_self_test.js';
|
|
158
|
-
import { countDeclaredCarriers, declaresCarrier } from './_lib/carrier_status.js';
|
|
159
|
-
import { carrierEstateCases } from './_lib/estate_carrier_cases.js';
|
|
160
158
|
|
|
161
159
|
const GATE = 'check_estate_count';
|
|
162
160
|
const BUDGET_REL = path.join('src', 'config', 'estate-count-budget.json');
|
|
@@ -438,38 +436,12 @@ function countIn(roadmapRoot: string, sub: string): number {
|
|
|
438
436
|
}
|
|
439
437
|
}
|
|
440
438
|
|
|
441
|
-
/**
|
|
442
|
-
* Top-level roadmaps `collect()` drops for declaring `status: carrier`, added
|
|
443
|
-
* back so that FLIPPING a status is count-neutral.
|
|
444
|
-
*
|
|
445
|
-
* Measured before this: flipping one file to `carrier` moved `active_roadmaps
|
|
446
|
-
* 3 → 2` and the gate printed "estate within its ratchet", because a count
|
|
447
|
-
* below the floor is a drawdown and free. The floor is measured at the base
|
|
448
|
-
* ref, so once such a flip merges the lowered number becomes the next floor —
|
|
449
|
-
* the file cannot be flipped back without paying, and any roadmap can be
|
|
450
|
-
* laundered out of the count by adding one word.
|
|
451
|
-
*
|
|
452
|
-
* A reclassification is not a disposal, so the metric counts the file on both
|
|
453
|
-
* sides. Same argument `open_blockers` makes below for spanning `later/`.
|
|
454
|
-
* Deleting a carrier is still a shrink and still earns no offset
|
|
455
|
-
* (`classifyDiff`); adding one is growth that takes the claim path.
|
|
456
|
-
*
|
|
457
|
-
* This is therefore the one roadmap metric that does NOT equal the dashboard
|
|
458
|
-
* header, stated here rather than left to be tripped over.
|
|
459
|
-
*/
|
|
460
|
-
function countActiveCarriers(roadmapRoot: string): number {
|
|
461
|
-
return countDeclaredCarriers(roadmapRoot, isRoadmapCandidate, fs, path.join);
|
|
462
|
-
}
|
|
463
|
-
|
|
464
439
|
/**
|
|
465
440
|
* Roadmaps parked in `later/` whose blockers count, parsed for them.
|
|
466
441
|
*
|
|
467
|
-
* Skips
|
|
468
|
-
*
|
|
469
|
-
*
|
|
470
|
-
* them out — a ratchet verdict that moved on a relocation, for no stated
|
|
471
|
-
* reason. Three of the four status readers learned `carrier` when the status
|
|
472
|
-
* shipped; this is the fourth.
|
|
442
|
+
* Skips exactly what `collect()` skips on the active side, through the same
|
|
443
|
+
* `isUnscheduled` predicate. Skipping a different set here would make a ratchet
|
|
444
|
+
* verdict move on a relocation, for no stated reason.
|
|
473
445
|
*/
|
|
474
446
|
function laterRoadmaps(roadmapRoot: string): Array<{ open_blockers: readonly unknown[] }> {
|
|
475
447
|
const dir = path.join(roadmapRoot, 'later');
|
|
@@ -496,10 +468,13 @@ function laterRoadmaps(roadmapRoot: string): Array<{ open_blockers: readonly unk
|
|
|
496
468
|
/**
|
|
497
469
|
* Count the estate, three ways.
|
|
498
470
|
*
|
|
499
|
-
* `active_roadmaps` is `collect()
|
|
500
|
-
*
|
|
501
|
-
*
|
|
502
|
-
*
|
|
471
|
+
* `active_roadmaps` is `collect()`, so the parser is shared with the dashboard
|
|
472
|
+
* and the number equals the dashboard header. It used to carry an add-back for
|
|
473
|
+
* the top-level roadmaps `collect()` dropped for declaring `status: carrier`,
|
|
474
|
+
* which existed only to keep FLIPPING to that status count-neutral — a flip
|
|
475
|
+
* lowered the count, a count below the floor is a free drawdown, and the
|
|
476
|
+
* lowered number then became the next floor. ADR-262 deleted the status, so
|
|
477
|
+
* there is no flip to neutralise and no divergence from the header left.
|
|
503
478
|
* `later_roadmaps` is a directory listing because parked files are
|
|
504
479
|
* outside that corpus by design — filtered through the same `is_roadmap_candidate`
|
|
505
480
|
* predicate as the active side, so `later/README.md` is not a roadmap here either.
|
|
@@ -524,7 +499,7 @@ export function countEstate(repoRoot: string): EstateCounts {
|
|
|
524
499
|
rows.reduce((n, r) => n + r.open_blockers.length, 0);
|
|
525
500
|
const skills = measureSkillEstate(repoRoot);
|
|
526
501
|
return {
|
|
527
|
-
active_roadmaps: stats.length
|
|
502
|
+
active_roadmaps: stats.length,
|
|
528
503
|
later_roadmaps: countIn(roadmapRoot, 'later'),
|
|
529
504
|
open_blockers: openOf(stats) + openOf(laterRoadmaps(roadmapRoot)),
|
|
530
505
|
skill_count: skills.skill_count,
|
|
@@ -590,9 +565,6 @@ export function exemptionReason(text: string): string | null {
|
|
|
590
565
|
return reason === '' ? null : reason;
|
|
591
566
|
}
|
|
592
567
|
|
|
593
|
-
/** Re-export; `_lib/carrier_status.ts` holds the one implementation. */
|
|
594
|
-
export const isCarrierText = declaresCarrier;
|
|
595
|
-
|
|
596
568
|
/**
|
|
597
569
|
* Classify the change's effect on the active roadmap tree.
|
|
598
570
|
*
|
|
@@ -604,13 +576,6 @@ export const isCarrierText = declaresCarrier;
|
|
|
604
576
|
export function classifyDiff(
|
|
605
577
|
nameStatus: string,
|
|
606
578
|
readFile: (rel: string) => string | null,
|
|
607
|
-
/**
|
|
608
|
-
* The file's content at the BASE ref, for paths this change removed — a
|
|
609
|
-
* deleted carrier cannot be read from the working tree and its status
|
|
610
|
-
* decides whether the deletion earns an offset. Defaults to null, which
|
|
611
|
-
* reproduces the previous scoring for any caller without a pre-image.
|
|
612
|
-
*/
|
|
613
|
-
readBase: (rel: string) => string | null = () => null,
|
|
614
579
|
): OffsetLedger {
|
|
615
580
|
const added: string[] = [];
|
|
616
581
|
const offsets: string[] = [];
|
|
@@ -624,10 +589,8 @@ export function classifyDiff(
|
|
|
624
589
|
const from = cols[1] ?? '';
|
|
625
590
|
const to = cols[2] ?? '';
|
|
626
591
|
if (isActiveTopLevel(from) && isDisposed(to)) {
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
if (isParked(to)) parked.push(to);
|
|
630
|
-
}
|
|
592
|
+
offsets.push(from);
|
|
593
|
+
if (isParked(to)) parked.push(to);
|
|
631
594
|
} else if (isDisposed(from) && isActiveTopLevel(to)) {
|
|
632
595
|
added.push(to);
|
|
633
596
|
}
|
|
@@ -638,10 +601,7 @@ export function classifyDiff(
|
|
|
638
601
|
if (status === 'A') {
|
|
639
602
|
added.push(file);
|
|
640
603
|
} else if (status === 'D') {
|
|
641
|
-
|
|
642
|
-
if (!isCarrierText(readBase(file))) {
|
|
643
|
-
offsets.push(file);
|
|
644
|
-
}
|
|
604
|
+
offsets.push(file);
|
|
645
605
|
}
|
|
646
606
|
}
|
|
647
607
|
for (const file of added) {
|
|
@@ -832,20 +792,13 @@ export function evaluate(
|
|
|
832
792
|
if (!diff.ok) {
|
|
833
793
|
offsetSkipReason = `git diff against ${baseRef} failed — one-in-one-out not evaluated`;
|
|
834
794
|
} else {
|
|
835
|
-
offsets = classifyDiff(
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
}
|
|
843
|
-
},
|
|
844
|
-
(rel) => {
|
|
845
|
-
const show = git(['show', `${baseRef}:${rel}`], repoRoot);
|
|
846
|
-
return show.ok ? show.stdout : null;
|
|
847
|
-
},
|
|
848
|
-
);
|
|
795
|
+
offsets = classifyDiff(diff.stdout, (rel) => {
|
|
796
|
+
try {
|
|
797
|
+
return fs.readFileSync(path.join(repoRoot, rel), 'utf-8');
|
|
798
|
+
} catch {
|
|
799
|
+
return null;
|
|
800
|
+
}
|
|
801
|
+
});
|
|
849
802
|
}
|
|
850
803
|
const patch = git(
|
|
851
804
|
['diff', '--unified=0', '--find-renames', `${baseRef}...HEAD`, '--', ROADMAPS_REL],
|
|
@@ -982,9 +935,6 @@ function selfTest(): number {
|
|
|
982
935
|
const roadmap = (name: string, extra = ''): string =>
|
|
983
936
|
`# Roadmap: ${name}\n\n## Phase 1\n\n- [ ] **1.1** s\n${extra}`;
|
|
984
937
|
|
|
985
|
-
/** A `status: carrier` roadmap — counted as estate, and never an offset. */
|
|
986
|
-
const CARRIER = `---\nstatus: carrier\n---\n# Roadmap: carrier\n\n## Phase 1\n\n- [~] **1.1** s\n`;
|
|
987
|
-
|
|
988
938
|
/**
|
|
989
939
|
* A `status: draft` roadmap — invisible to `collect()`, so adding one cannot
|
|
990
940
|
* move `active_roadmaps`. That is what makes it the fixture for a case that
|
|
@@ -1197,14 +1147,25 @@ function selfTest(): number {
|
|
|
1197
1147
|
after: (dir) => write(dir, 'agents/roadmaps/later/road-to-parked.md', roadmap('P')),
|
|
1198
1148
|
}),
|
|
1199
1149
|
},
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1150
|
+
{
|
|
1151
|
+
// Survives the deletion of the carrier cases this used to control
|
|
1152
|
+
// for (ADR-262): it asserts the plain rule those cases were the
|
|
1153
|
+
// exception to — a removal offsets an addition — over a DRAFT
|
|
1154
|
+
// addition, which `collect()` cannot see, so only the offset
|
|
1155
|
+
// scoring can decide it.
|
|
1156
|
+
name: 'deleting an ordinary roadmap offsets a new draft → accept',
|
|
1157
|
+
expect: 'accept',
|
|
1158
|
+
run: () =>
|
|
1159
|
+
fixture({
|
|
1160
|
+
roadmaps: 3,
|
|
1161
|
+
base: 'main',
|
|
1162
|
+
before: (dir) => write(dir, 'agents/roadmaps/road-to-spare.md', roadmap('S')),
|
|
1163
|
+
after: (dir) => {
|
|
1164
|
+
fs.rmSync(path.join(dir, 'agents/roadmaps/road-to-spare.md'));
|
|
1165
|
+
write(dir, 'agents/roadmaps/road-to-new.md', DRAFT);
|
|
1166
|
+
},
|
|
1167
|
+
}),
|
|
1168
|
+
},
|
|
1208
1169
|
{
|
|
1209
1170
|
name: 'a new open blocker with no claim → reject',
|
|
1210
1171
|
expect: 'reject',
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* A host outside `lean_projection.hosts` receives EXACTLY what `eager-all` writes.
|
|
4
|
+
*
|
|
5
|
+
* `road-to-delivery-for-every-host` step 1.4, and the gate behind its AC-2.
|
|
6
|
+
*
|
|
7
|
+
* WHY A SEPARATE GATE FROM `check_rule_projection_integrity`
|
|
8
|
+
* ---------------------------------------------------------
|
|
9
|
+
* That gate reads the trees AS THEY STAND — no regeneration, by design, because
|
|
10
|
+
* its job is to catch a STALE tree and regenerating erases the evidence. Its 1.3
|
|
11
|
+
* host axis therefore asks a content question about the entries that exist:
|
|
12
|
+
* "is any of these a stub in a tree that should hold bodies?"
|
|
13
|
+
*
|
|
14
|
+
* That is necessary and it is not sufficient. It cannot see a projector change
|
|
15
|
+
* that alters a non-delivery host's output in some way OTHER than stubbing it —
|
|
16
|
+
* a different frontmatter rewrite, a dropped rule, an added byte. The AC is
|
|
17
|
+
* byte-identity, and byte-identity can only be established by producing both
|
|
18
|
+
* trees and comparing them.
|
|
19
|
+
*
|
|
20
|
+
* So this gate GENERATES, twice, into a temporary root it owns:
|
|
21
|
+
*
|
|
22
|
+
* 1. `mode: eager-all` → the reference trees
|
|
23
|
+
* 2. `mode: delivery`, the configured `hosts:` → the trees under test
|
|
24
|
+
*
|
|
25
|
+
* and requires every tree whose host is NOT in `hosts` to be byte-identical
|
|
26
|
+
* across the two runs. It never writes into the repository: the fixture root is
|
|
27
|
+
* a `mkdtemp` directory, seeded from `dist/agent-src/rules`, and removed on exit.
|
|
28
|
+
*
|
|
29
|
+
* WHAT IT DELIBERATELY DOES NOT DO
|
|
30
|
+
* --------------------------------
|
|
31
|
+
* It does not assert that the DELIVERY host's tree changed. A flip that thins
|
|
32
|
+
* nothing is a different defect and `check_rule_projection_integrity` plus the
|
|
33
|
+
* unit corpus already cover it; asserting it here would make this gate fail on a
|
|
34
|
+
* legitimate `hosts: []`, which is the safe configuration.
|
|
35
|
+
*
|
|
36
|
+
* AND IT ASSERTS NOTHING WHEN EVERY THINNABLE HOST IS ENROLLED. With
|
|
37
|
+
* `hosts: [claude-code, cursor, cline]` there is no non-delivery tree, so the
|
|
38
|
+
* comparison has no subject. That case now prints a `⚠️ NOTHING COMPARED` line
|
|
39
|
+
* naming the gate that does carry the question, instead of the `✅ … 0 …
|
|
40
|
+
* byte-identical` line it printed until 2026-09-08 (R2 finding 12) — a green
|
|
41
|
+
* sentence for an assertion that was never made.
|
|
42
|
+
*
|
|
43
|
+
* IT ALSO DOES NOT CONSULT `lean_projection.mode`, and the divergence from
|
|
44
|
+
* `check_rule_projection_integrity._lean_projection_settings_for` — which
|
|
45
|
+
* checks the mode FIRST so "a `hosts:` list left behind after a rollback
|
|
46
|
+
* exempts nothing" — is deliberate. Recorded 2026-09-08 because R2 finding 13
|
|
47
|
+
* read it as an oversight, and running the mode-first variant here reddens the
|
|
48
|
+
* gate on this repository's own settings, which carry no `lean_projection`
|
|
49
|
+
* block at all.
|
|
50
|
+
*
|
|
51
|
+
* The two gates ask different questions. The sibling asks a question ABOUT THE
|
|
52
|
+
* CURRENT TREE — "is any entry on disk a stub in a tree with no delivery
|
|
53
|
+
* slot?" — so the live mode is part of its subject. This gate asks a
|
|
54
|
+
* COUNTERFACTUAL ABOUT THE PROJECTOR — "under a thinning mode with the
|
|
55
|
+
* configured hosts, would every other host's tree still be byte-identical?" —
|
|
56
|
+
* and supplies `mode: delivery` to its own test fixture to ask it. Reading the
|
|
57
|
+
* live mode would make the exemption set and the fixture disagree: with the
|
|
58
|
+
* mode off, `configured` would be empty while the fixture still thinned, and
|
|
59
|
+
* every stub in the delivery host's tree would be reported as a parity break.
|
|
60
|
+
*
|
|
61
|
+
* The consequence the finding names is real and is not a hole: after a rollback
|
|
62
|
+
* to `eager-all`, a leftover `hosts: [claude-code]` still excludes
|
|
63
|
+
* `.claude/rules` from THIS comparison. Nothing is hidden by that, because
|
|
64
|
+
* under `eager-all` nothing is thinned and the on-disk state is the sibling
|
|
65
|
+
* gate's subject.
|
|
66
|
+
*
|
|
67
|
+
* It also does not read the repository's own `.claude/rules` etc. Those are
|
|
68
|
+
* gitignored and absent on a fresh CI checkout, and a gate that silently passes
|
|
69
|
+
* when its subject is missing is the shape this repository's scan-scope
|
|
70
|
+
* discipline exists to reject — hence the fixture root, which always exists.
|
|
71
|
+
*
|
|
72
|
+
* Exit: 0 parity holds · 1 a non-delivery host's tree differs · 2 the gate could
|
|
73
|
+
* not run (no rule corpus to seed from).
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
import * as fs from 'node:fs';
|
|
77
|
+
import * as os from 'node:os';
|
|
78
|
+
import * as path from 'node:path';
|
|
79
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
80
|
+
|
|
81
|
+
import { GateLedger, UnaccountedTargetsError } from './_lib/gate_ledger.js';
|
|
82
|
+
import { leanProjectionHostsRaw } from './_lib/hook_settings.js';
|
|
83
|
+
import { resolveLeanProjectionHosts } from './_lib/lean_projection_mode.js';
|
|
84
|
+
import { reportScanned } from './_lib/scan_scope.js';
|
|
85
|
+
import {
|
|
86
|
+
_getStateForTest,
|
|
87
|
+
_resetStateForTest as _pointCondenseAt,
|
|
88
|
+
generate_rule_symlinks,
|
|
89
|
+
} from './condense.js';
|
|
90
|
+
|
|
91
|
+
const _HERE = path.resolve(fileURLToPath(import.meta.url));
|
|
92
|
+
export const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
93
|
+
|
|
94
|
+
/** The tool dirs this gate compares, and the host each belongs to. */
|
|
95
|
+
export const TREES: Readonly<Record<string, string>> = {
|
|
96
|
+
'.claude/rules': 'claude-code',
|
|
97
|
+
'.cursor/rules': 'cursor',
|
|
98
|
+
'.clinerules': 'cline',
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/** Every entry in `dir` as `name -> content`, reading through symlinks. */
|
|
102
|
+
export function treeContents(root: string, rel: string): Record<string, string> {
|
|
103
|
+
const dir = path.join(root, rel);
|
|
104
|
+
const out: Record<string, string> = {};
|
|
105
|
+
if (!fs.existsSync(dir)) return out;
|
|
106
|
+
for (const name of fs.readdirSync(dir).sort()) {
|
|
107
|
+
if (name === 'README.md') continue;
|
|
108
|
+
try {
|
|
109
|
+
out[name] = fs.readFileSync(path.join(dir, name), 'utf-8');
|
|
110
|
+
} catch {
|
|
111
|
+
out[name] = '<unreadable>';
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Copy the rule corpus into a fresh fixture root carrying `settings`. */
|
|
118
|
+
function seedFixture(repoRoot: string, tmp: string, label: string, settings: string): string {
|
|
119
|
+
const root = path.join(tmp, label);
|
|
120
|
+
const dist = path.join(root, 'dist', 'agent-src', 'rules');
|
|
121
|
+
fs.mkdirSync(dist, { recursive: true });
|
|
122
|
+
const src = path.join(repoRoot, 'dist', 'agent-src', 'rules');
|
|
123
|
+
for (const name of fs.readdirSync(src).filter((f) => f.endsWith('.md'))) {
|
|
124
|
+
fs.copyFileSync(path.join(src, name), path.join(dist, name));
|
|
125
|
+
}
|
|
126
|
+
fs.writeFileSync(path.join(root, '.agent-settings.yml'), settings, 'utf-8');
|
|
127
|
+
return root;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface ParityFinding {
|
|
131
|
+
readonly tree: string;
|
|
132
|
+
readonly host: string;
|
|
133
|
+
readonly rule: string;
|
|
134
|
+
readonly reason: string;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Compare two generated trees, reporting every difference in a non-delivery host's tree. */
|
|
138
|
+
export function compareTrees(
|
|
139
|
+
eagerRoot: string,
|
|
140
|
+
testRoot: string,
|
|
141
|
+
deliveryHosts: readonly string[],
|
|
142
|
+
): ParityFinding[] {
|
|
143
|
+
const out: ParityFinding[] = [];
|
|
144
|
+
for (const [tree, host] of Object.entries(TREES)) {
|
|
145
|
+
if (deliveryHosts.includes(host)) continue;
|
|
146
|
+
const a = treeContents(eagerRoot, tree);
|
|
147
|
+
const b = treeContents(testRoot, tree);
|
|
148
|
+
for (const name of new Set([...Object.keys(a), ...Object.keys(b)])) {
|
|
149
|
+
const left = a[name];
|
|
150
|
+
const right = b[name];
|
|
151
|
+
if (left === right) continue;
|
|
152
|
+
const reason =
|
|
153
|
+
left === undefined
|
|
154
|
+
? 'present under the thinning mode and absent under eager-all'
|
|
155
|
+
: right === undefined
|
|
156
|
+
? 'written by eager-all and MISSING under the thinning mode'
|
|
157
|
+
: `content differs (${String(left.length)} vs ${String(right.length)} bytes)`;
|
|
158
|
+
out.push({ tree, host, rule: name, reason });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The notice for a comparison with NO SUBJECT, or `null` when there is one.
|
|
166
|
+
*
|
|
167
|
+
* R2 finding 12: with every thinnable host in `lean_projection.hosts` there is
|
|
168
|
+
* no non-delivery tree left, so this gate's assertion is VACUOUS — and it used
|
|
169
|
+
* to say so inside the same `✅ … byte-identical` sentence it prints when the
|
|
170
|
+
* assertion actually held. Zero compared is not a pass; it is an absence of
|
|
171
|
+
* subject, and the reader has to be told which gate carries the question
|
|
172
|
+
* instead.
|
|
173
|
+
*
|
|
174
|
+
* Deliberately NOT an exit-1: enrolling all three hosts is a legal
|
|
175
|
+
* configuration, and reddening a legal configuration is how a gate gets
|
|
176
|
+
* switched off. Pure and exported so the wording is under test without running
|
|
177
|
+
* the two-fixture generation.
|
|
178
|
+
*/
|
|
179
|
+
export function parityScopeNotice(configured: readonly string[]): string | null {
|
|
180
|
+
const comparable = Object.entries(TREES).filter(([, h]) => !configured.includes(h));
|
|
181
|
+
if (comparable.length > 0) return null;
|
|
182
|
+
return (
|
|
183
|
+
`⚠️ check_host_tree_parity: NOTHING COMPARED — every thinnable host is in ` +
|
|
184
|
+
`lean_projection.hosts [${configured.join(', ') || '(none)'}], so no non-delivery tree ` +
|
|
185
|
+
`exists and this gate asserts nothing about this configuration. A stub in a tree with ` +
|
|
186
|
+
`no bound delivery slot is reported by check_rule_projection_integrity's host axis ` +
|
|
187
|
+
`(thinnedTreeFindings); the byte-identity question here has no subject.\n`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function main(argv: readonly string[] = []): number {
|
|
192
|
+
const root = REPO_ROOT;
|
|
193
|
+
const dist = path.join(root, 'dist', 'agent-src', 'rules');
|
|
194
|
+
if (!fs.existsSync(dist) || fs.readdirSync(dist).filter((f) => f.endsWith('.md')).length === 0) {
|
|
195
|
+
process.stderr.write(
|
|
196
|
+
`❌ check_host_tree_parity: no rule corpus at dist/agent-src/rules — run \`task sync\` first.\n`,
|
|
197
|
+
);
|
|
198
|
+
return 2;
|
|
199
|
+
}
|
|
200
|
+
const configured = resolveLeanProjectionHosts(leanProjectionHostsRaw(root)).hosts;
|
|
201
|
+
const hostList = configured.join(', ') || '(none)';
|
|
202
|
+
|
|
203
|
+
const saved = _getStateForTest();
|
|
204
|
+
const savedHome = process.env['HOME'];
|
|
205
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'host-parity-'));
|
|
206
|
+
let findings: ParityFinding[];
|
|
207
|
+
try {
|
|
208
|
+
// An empty HOME: user-scope dedup must find no twin, or the two runs
|
|
209
|
+
// would differ for a reason that has nothing to do with the host axis.
|
|
210
|
+
const home = path.join(tmp, 'home');
|
|
211
|
+
fs.mkdirSync(home, { recursive: true });
|
|
212
|
+
process.env['HOME'] = home;
|
|
213
|
+
|
|
214
|
+
const eagerRoot = seedFixture(root, tmp, 'eager', 'lean_projection:\n mode: eager-all\n');
|
|
215
|
+
_pointCondenseAt(eagerRoot);
|
|
216
|
+
generate_rule_symlinks();
|
|
217
|
+
|
|
218
|
+
const hostsBlock = configured.length === 0 ? '' : ` hosts: [${configured.join(', ')}]\n`;
|
|
219
|
+
const testRoot = seedFixture(root, tmp, 'test', `lean_projection:\n mode: delivery\n${hostsBlock}`);
|
|
220
|
+
_pointCondenseAt(testRoot);
|
|
221
|
+
generate_rule_symlinks();
|
|
222
|
+
|
|
223
|
+
findings = compareTrees(eagerRoot, testRoot, configured);
|
|
224
|
+
} finally {
|
|
225
|
+
_pointCondenseAt(saved.PROJECT_ROOT);
|
|
226
|
+
if (savedHome === undefined) delete process.env['HOME'];
|
|
227
|
+
else process.env['HOME'] = savedHome;
|
|
228
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const compared = Object.entries(TREES).filter(([, h]) => !configured.includes(h)).length;
|
|
232
|
+
reportScanned({
|
|
233
|
+
gate: 'check_host_tree_parity',
|
|
234
|
+
scanned: compared,
|
|
235
|
+
units: 'non-delivery host rule tree(s)',
|
|
236
|
+
roots: Object.keys(TREES),
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// One ledger target per non-delivery tree, not per entry: the assertion is
|
|
240
|
+
// about a TREE being byte-identical, and a per-entry ledger would report
|
|
241
|
+
// 200-odd completed targets for what is three yes/no questions.
|
|
242
|
+
const ledger = new GateLedger('check_host_tree_parity');
|
|
243
|
+
const compareable = Object.entries(TREES).filter(([, h]) => !configured.includes(h)).map(([t]) => t);
|
|
244
|
+
ledger.plan(compareable);
|
|
245
|
+
const broken = new Set(findings.map((f) => f.tree));
|
|
246
|
+
for (const tree of compareable) {
|
|
247
|
+
if (broken.has(tree)) ledger.fail(tree, 'differs from the eager-all projection');
|
|
248
|
+
else ledger.complete(tree);
|
|
249
|
+
}
|
|
250
|
+
try {
|
|
251
|
+
if (argv.includes('--quiet')) ledger.finalize();
|
|
252
|
+
else ledger.report();
|
|
253
|
+
} catch (exc) {
|
|
254
|
+
if (exc instanceof UnaccountedTargetsError) {
|
|
255
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
256
|
+
return 1;
|
|
257
|
+
}
|
|
258
|
+
throw exc;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (findings.length > 0) {
|
|
262
|
+
process.stderr.write(
|
|
263
|
+
`❌ check_host_tree_parity: ${String(findings.length)} entry(ies) differ between ` +
|
|
264
|
+
`eager-all and delivery on a host that is NOT in lean_projection.hosts [${hostList}]:\n`,
|
|
265
|
+
);
|
|
266
|
+
for (const f of findings) {
|
|
267
|
+
process.stderr.write(` · ${f.tree}/${f.rule} (${f.host}) — ${f.reason}\n`);
|
|
268
|
+
}
|
|
269
|
+
process.stderr.write(
|
|
270
|
+
'\nA host outside `lean_projection.hosts` must receive exactly what `eager-all` writes ' +
|
|
271
|
+
'(K3, and the completeness invariant). Fix the projector, never the expectation.\n',
|
|
272
|
+
);
|
|
273
|
+
return 1;
|
|
274
|
+
}
|
|
275
|
+
const notice = parityScopeNotice(configured);
|
|
276
|
+
if (notice !== null) {
|
|
277
|
+
process.stderr.write(notice);
|
|
278
|
+
return 0;
|
|
279
|
+
}
|
|
280
|
+
if (!argv.includes('--quiet')) {
|
|
281
|
+
process.stdout.write(
|
|
282
|
+
`✅ check_host_tree_parity: ${String(compared)} non-delivery host tree(s) byte-identical to ` +
|
|
283
|
+
`eager-all · delivery hosts [${hostList}]\n`,
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
return 0;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (process.argv[1] !== undefined && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {
|
|
290
|
+
process.exit(main(process.argv.slice(2)));
|
|
291
|
+
}
|