@event4u/agent-config 9.14.0 → 9.16.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/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +118 -99
- package/README.md +8 -0
- package/dist/agent-src/commands/agents/audit.md +7 -7
- package/dist/agent-src/commands/agents/optimize.md +1 -1
- package/dist/agent-src/commands/analyze/reference-repo.md +1 -1
- package/dist/agent-src/commands/chat-history.md +1 -0
- package/dist/agent-src/commands/context.md +1 -0
- package/dist/agent-src/commands/council.md +1 -1
- package/dist/agent-src/commands/ghostwriter.md +1 -1
- package/dist/agent-src/commands/memory.md +2 -1
- package/dist/agent-src/commands/module.md +1 -0
- package/dist/agent-src/commands/optimize/agents-dir.md +1 -1
- package/dist/agent-src/commands/optimize/deep.md +5 -2
- package/dist/agent-src/commands/optimize/skills.md +5 -5
- package/dist/agent-src/commands/override.md +1 -0
- package/dist/agent-src/commands/prediction-pool.md +4 -4
- package/dist/agent-src/commands/research/deep.md +1 -1
- package/dist/agent-src/commands/roadmap.md +2 -1
- package/dist/agent-src/commands/rule-compliance-audit.md +1 -1
- package/dist/agent-src/commands/tdd.md +1 -0
- package/dist/agent-src/commands/tests.md +1 -0
- package/dist/agent-src/commands/upstream-contribute.md +5 -5
- package/dist/agent-src/commands/video/from-script.md +1 -1
- package/dist/agent-src/commands/video/from-song.md +1 -1
- package/dist/agent-src/commands/video/scene.md +1 -1
- package/dist/agent-src/commands/video/stitch.md +1 -1
- package/dist/agent-src/commands/video/storyboard.md +1 -1
- package/dist/agent-src/commands/worktree/create.md +2 -1
- package/dist/agent-src/contexts/augment-infrastructure.md +1 -1
- package/dist/agent-src/contexts/contracts/artifact-engagement-flow.md +5 -5
- package/dist/agent-src/contexts/contracts/command-suggestion-flow.md +3 -3
- package/dist/agent-src/contexts/contracts/emergency-triage-block.md +2 -2
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +4 -3
- package/dist/agent-src/contexts/execution/subagent-routing.md +19 -0
- package/dist/agent-src/contexts/override-system.md +1 -1
- package/dist/agent-src/ghostwriter/fictional-fixture-v1.md +1 -1
- package/dist/agent-src/rules/augment-edit-discipline.md +1 -1
- package/dist/agent-src/rules/autonomous-execution.md +2 -1
- package/dist/agent-src/rules/brand-consistency.md +1 -0
- package/dist/agent-src/rules/brand-source-of-truth.md +1 -0
- package/dist/agent-src/rules/code-comment-discipline.md +2 -0
- package/dist/agent-src/rules/communication-through-line.md +1 -0
- package/dist/agent-src/rules/content-quoting-floor.md +1 -0
- package/dist/agent-src/rules/cross-source-consistency.md +0 -1
- package/dist/agent-src/rules/downstream-changes.md +1 -0
- package/dist/agent-src/rules/external-code-graph-interop.md +1 -0
- package/dist/agent-src/rules/external-reference-deep-dive.md +1 -0
- package/dist/agent-src/rules/fast-path-marker-visibility.md +1 -0
- package/dist/agent-src/rules/finance-safety-floor.md +0 -1
- package/dist/agent-src/rules/icon-consistency.md +1 -0
- package/dist/agent-src/rules/image-likeness-and-rights.md +1 -0
- package/dist/agent-src/rules/improve-before-implement.md +1 -1
- package/dist/agent-src/rules/invite-challenge.md +1 -0
- package/dist/agent-src/rules/lethal-trifecta-guard.md +1 -0
- package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -0
- package/dist/agent-src/rules/markdown-safe-codeblocks.md +1 -0
- package/dist/agent-src/rules/media-governance-routing.md +4 -3
- package/dist/agent-src/rules/media-sync-ground-truth.md +1 -0
- package/dist/agent-src/rules/no-attribution-footers.md +1 -0
- package/dist/agent-src/rules/no-decorative-emojis-in-git-surfaces.md +1 -0
- package/dist/agent-src/rules/no-pr-progress-comments.md +1 -0
- package/dist/agent-src/rules/onboarding-gate.md +1 -0
- package/dist/agent-src/rules/output-discipline.md +1 -0
- package/dist/agent-src/rules/prefer-enums-over-literals.md +1 -0
- package/dist/agent-src/rules/preservation-guard.md +1 -0
- package/dist/agent-src/rules/question-not-instruction.md +1 -0
- package/dist/agent-src/rules/roadmap-ci-steps-policy.md +1 -0
- package/dist/agent-src/rules/role-mode-adherence.md +1 -0
- package/dist/agent-src/rules/runtime-safety.md +1 -0
- package/dist/agent-src/rules/security-sensitive-stop.md +1 -0
- package/dist/agent-src/rules/session-canary.md +9 -2
- package/dist/agent-src/rules/source-confidentiality.md +1 -0
- package/dist/agent-src/rules/source-of-truth.md +1 -0
- package/dist/agent-src/rules/spreadsheet-source-quality.md +1 -0
- package/dist/agent-src/rules/think-before-action.md +1 -0
- package/dist/agent-src/rules/token-budget-discipline.md +1 -0
- package/dist/agent-src/rules/token-efficiency.md +1 -0
- package/dist/agent-src/rules/token-optimizer-maintenance.md +5 -5
- package/dist/agent-src/rules/tool-safety.md +1 -0
- package/dist/agent-src/rules/untrusted-input-defense.md +2 -0
- package/dist/agent-src/rules/user-interaction.md +1 -0
- package/dist/agent-src/rules/user-interrupt-priority.md +1 -0
- package/dist/agent-src/skills/adr-create/SKILL.md +1 -1
- package/dist/agent-src/skills/check-refs/SKILL.md +1 -1
- package/dist/agent-src/skills/competitive-positioning/SKILL.md +1 -1
- package/dist/agent-src/skills/condense-memory/SKILL.md +1 -1
- package/dist/agent-src/skills/context-authoring/SKILL.md +1 -1
- package/dist/agent-src/skills/copilot-agents-optimization/SKILL.md +1 -1
- package/dist/agent-src/skills/description-assist/SKILL.md +1 -1
- package/dist/agent-src/skills/github-ci/SKILL.md +1 -0
- package/dist/agent-src/skills/guideline-writing/SKILL.md +1 -1
- package/dist/agent-src/skills/lint-skills/SKILL.md +1 -1
- package/dist/agent-src/skills/readme-reviewer/SKILL.md +1 -1
- package/dist/agent-src/skills/roadmap-management/SKILL.md +8 -1
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
- package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
- package/dist/agent-src/skills/skill-writing/SKILL.md +4 -4
- package/dist/agent-src/skills/token-optimizer/SKILL.md +4 -4
- package/dist/agent-src/skills/upstream-contribute/SKILL.md +6 -6
- package/dist/agent-src/skills/using-git-worktrees/SKILL.md +25 -0
- package/dist/agent-src/templates/AGENTS.md +1 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/command.md +1 -1
- package/dist/agent-src/templates/copilot-instructions.md +3 -3
- package/dist/agent-src/templates/rule.md +2 -2
- package/dist/agent-src/templates/scripts/work_engine/orchestration.ts +1 -1
- package/dist/agent-src/templates/skill-archive-note.md +5 -5
- package/dist/agent-src/templates/skill.md +1 -1
- package/dist/cli/agent-config.js +66 -19
- package/dist/cli/agent-config.js.map +1 -1
- package/dist/cli/main.js +9 -4
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/mcp/content.js +1 -1
- package/dist/cli/mcp/content.js.map +1 -1
- package/dist/cli/python/workspace_drive.js +2 -0
- package/dist/cli/python/workspace_drive.js.map +1 -1
- package/dist/cli/python/workspace_drive_health.js +2 -0
- package/dist/cli/python/workspace_drive_health.js.map +1 -1
- package/dist/cli/python/workspace_hosts.js +2 -0
- package/dist/cli/python/workspace_hosts.js.map +1 -1
- package/dist/cli/python/workspace_sessions.js +2 -0
- package/dist/cli/python/workspace_sessions.js.map +1 -1
- package/dist/cli/registry.js +1 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/{chunk-A3HJ6NTI.js → chunk-63WSP6YZ.js} +2 -1
- package/dist/cli-delegate/{chunk-EJYIGZ3T.js → chunk-BKO2AZ7Z.js} +1 -1
- package/dist/cli-delegate/{chunk-GCVFLBP2.js → chunk-H574KOUH.js} +338 -494
- package/dist/cli-delegate/chunk-NXFWFAYV.js +339 -0
- package/dist/cli-delegate/{chunk-TEGJU2JX.js → chunk-ZXXJLL5K.js} +2 -2
- package/dist/cli-delegate/cmd_conformance.js +29 -23
- package/dist/cli-delegate/cmd_doctor.js +3 -3
- package/dist/cli-delegate/cmd_export.js +4 -4
- package/dist/cli-delegate/cmd_preflight.js +2 -2
- package/dist/cli-delegate/cmd_prune.js +4 -4
- package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
- package/dist/cli-delegate/cmd_sync.js +4 -4
- package/dist/cli-delegate/cmd_uninstall.js +4 -4
- package/dist/cli-delegate/cmd_validate.js +4 -4
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +127 -95
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.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 +729 -430
- package/dist/install/install.mjs +52 -11
- package/dist/install/rule_scope.js +18 -2
- package/dist/install/rule_scope.js.map +1 -1
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +518 -411
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/claude_settings_hooks.js +30 -5
- package/dist/scripts/_lib/claude_settings_hooks.js.map +1 -1
- package/dist/scripts/_lib/scoped_projection.js +223 -0
- package/dist/scripts/_lib/scoped_projection.js.map +1 -0
- package/dist/scripts/_lib/surface_tiers.js +125 -0
- package/dist/scripts/_lib/surface_tiers.js.map +1 -0
- package/dist/scripts/_lib/token_count.js +166 -0
- package/dist/scripts/_lib/token_count.js.map +1 -0
- package/dist/scripts/_lib/trigger_eval_floors.js +8 -0
- package/dist/scripts/_lib/trigger_eval_floors.js.map +1 -1
- package/dist/scripts/condense.js +139 -9
- package/dist/scripts/condense.js.map +1 -1
- package/dist/scripts/project_thin_rules.js +450 -0
- package/dist/scripts/project_thin_rules.js.map +1 -0
- package/dist/server/routes/workspace.js +3 -1
- package/dist/server/routes/workspace.js.map +1 -1
- package/dist/server/schemas/settings.js +9 -3
- package/dist/server/schemas/settings.js.map +1 -1
- package/dist/shared/userMd/utils.js +1 -1
- package/dist/shared/userMd/utils.js.map +1 -1
- package/dist/ui/assets/index-DmR0mbk4.js +26 -0
- package/dist/ui/assets/index-DmR0mbk4.js.map +1 -0
- package/dist/ui/index.html +1 -1
- package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +1 -1
- package/package.json +14 -14
- package/src/agent-src/templates/AGENTS.md +1 -1
- package/src/agent-src/templates/scripts/work_engine/orchestration.ts +1 -1
- package/src/config/agent-settings.template.yml +61 -11
- package/src/config/budget-routing.json +7 -0
- package/src/config/ci-local-parity.yml +8 -0
- package/src/config/discovery/unassigned-artefacts.yml +23 -25
- package/src/config/evaluator-budgets.json +35 -5
- package/src/config/gate-coverage.yml +147 -0
- package/src/config/gate-violation-baselines.json +25 -0
- package/src/install/rule_scope.ts +27 -2
- package/src/scripts/_cli/cmd_conformance.ts +13 -24
- package/src/scripts/_dispatch.bash +10 -0
- package/src/scripts/_lib/claude_settings_hooks.ts +30 -4
- package/src/scripts/_lib/gate_baseline.ts +213 -0
- package/src/scripts/_lib/scoped_projection.ts +50 -0
- package/src/scripts/_lib/tier_budget_routing.ts +360 -0
- package/src/scripts/_lib/tool_adapter_registry.ts +8 -1
- package/src/scripts/_lib/trigger_eval_floors.ts +9 -0
- package/src/scripts/_lib/trigger_routers.ts +17 -5
- package/src/scripts/adr/regenerate_index.ts +4 -3
- package/src/scripts/ai-video/lib/parse-blueprint.sh +1 -1
- package/src/scripts/ai_council/compile_corpus.ts +1 -1
- package/src/scripts/anchor_scoring_run.ts +1 -1
- package/src/scripts/audit_cloud_compatibility.ts +37 -2
- package/src/scripts/audit_initial_context.ts +53 -19
- package/src/scripts/audit_likelihood.ts +31 -2
- package/src/scripts/audit_user_type_axis.ts +58 -11
- package/src/scripts/bench_hook_latency.ts +172 -41
- package/src/scripts/bench_persona_placebo.ts +1 -1
- package/src/scripts/bench_quality_run.ts +1 -1
- package/src/scripts/build_mcp_catalog.ts +12 -1
- package/src/scripts/check_always_budget.ts +122 -11
- package/src/scripts/check_augment_description_cap.ts +24 -3
- package/src/scripts/check_ci_strict_superset.ts +150 -0
- package/src/scripts/check_cli_registry_budget_sync.ts +168 -0
- package/src/scripts/check_cluster_patterns.ts +100 -0
- package/src/scripts/check_consumer_scope_flip.ts +26 -4
- package/src/scripts/check_evaluator_budgets.ts +198 -19
- package/src/scripts/check_gate_coverage.ts +338 -2
- package/src/scripts/check_generator_output_coverage.ts +2 -1
- package/src/scripts/check_iron_law_prominence.ts +1 -1
- package/src/scripts/check_kernel_rule_bundle.ts +1 -1
- package/src/scripts/check_no_new_legacy_path.ts +167 -3
- package/src/scripts/check_pack_size.ts +18 -4
- package/src/scripts/check_portability.ts +37 -7
- package/src/scripts/check_references.ts +19 -3
- package/src/scripts/check_release_pr_shape.ts +13 -0
- package/src/scripts/check_token_quality_golden.ts +1 -1
- package/src/scripts/check_token_regression.ts +9 -2
- package/src/scripts/condense.ts +155 -12
- package/src/scripts/cost/budget.mjs +77 -2
- package/src/scripts/evaluator_umbrella.sh +34 -1
- package/src/scripts/generate_capability_matrix.ts +8 -0
- package/src/scripts/generate_router_coverage_from_matrix.ts +155 -0
- package/src/scripts/hook_manifest.yaml +17 -3
- package/src/scripts/hooks/block_config_weakening.ts +337 -0
- package/src/scripts/hooks/concern_registry.ts +2 -0
- package/src/scripts/hooks/probe_throttle.ts +75 -0
- package/src/scripts/install-hooks.sh +24 -2
- package/src/scripts/install.ts +2 -2
- package/src/scripts/lint_agents_md.ts +9 -3
- package/src/scripts/lint_archived_skills.ts +1 -1
- package/src/scripts/lint_artefact_frontmatter.ts +185 -37
- package/src/scripts/lint_command_routing.ts +21 -52
- package/src/scripts/lint_command_verbs.ts +47 -28
- package/src/scripts/lint_context_spine_usage.ts +38 -12
- package/src/scripts/lint_framework_leakage.ts +25 -6
- package/src/scripts/lint_handoffs.ts +49 -2
- package/src/scripts/lint_media_policy_linkage.ts +62 -30
- package/src/scripts/lint_namespace.ts +92 -7
- package/src/scripts/lint_new_skill_gate.ts +39 -31
- package/src/scripts/lint_originality_shingles.ts +7 -1
- package/src/scripts/lint_override_kernel_guard.ts +21 -3
- package/src/scripts/lint_pack_boundaries.ts +461 -91
- package/src/scripts/lint_pack_dependencies.ts +24 -57
- package/src/scripts/lint_persona_governance.ts +1 -1
- package/src/scripts/lint_role_experiences.ts +34 -5
- package/src/scripts/lint_trigger_precision.ts +142 -0
- package/src/scripts/mcp_server/consumer_tool_catalog.json +12 -201
- package/src/scripts/mcp_server/tools.ts +1 -1
- package/src/scripts/prepack-check.mjs +40 -1
- package/src/scripts/prepack_router_targets.d.mts +7 -0
- package/src/scripts/prepack_router_targets.mjs +93 -0
- package/src/scripts/print_required_checks.ts +72 -18
- package/src/scripts/profile_staleness_hook.ts +12 -0
- package/src/scripts/record_evaluator_measurements.mjs +105 -0
- package/src/scripts/recruit_preflight.sh +1 -1
- package/src/scripts/release.ts +214 -7
- package/src/scripts/router_target_paths.d.mts +4 -0
- package/src/scripts/router_target_paths.mjs +69 -0
- package/src/scripts/router_telemetry.ts +52 -1
- package/src/scripts/routing_doctor.ts +560 -0
- package/src/scripts/rule_trigger_eval.ts +403 -0
- package/src/scripts/schemas/rule.schema.json +5 -1
- package/src/scripts/session_canary_hook.ts +96 -5
- package/src/scripts/skill_linter.ts +9 -28
- package/src/scripts/smoke/skills.sh +1 -1
- package/src/scripts/sweep_dead_scan_roots.ts +356 -10
- package/src/scripts/trigger_coverage.ts +13 -2
- package/src/scripts/update_counts.ts +1 -1
- package/src/scripts/validate_telegraph_carveouts.ts +1 -1
- package/src/scripts/wrapper_freshness_hook.ts +14 -0
- package/src/server/schemas/settings.ts +19 -5
- package/src/shared/userMd/utils.ts +1 -1
- package/dist/cli-delegate/chunk-TJIAM5FW.js +0 -133
- package/dist/ui/assets/index-BpSQt5vm.js +0 -40
- package/dist/ui/assets/index-BpSQt5vm.js.map +0 -1
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Budget-aware tier routing (road-to-tested-routing Phase 7).
|
|
3
|
+
*
|
|
4
|
+
* Deterministic decision layer for `docs/contracts/budget-routing.md`:
|
|
5
|
+
* pick the cheapest CLASSIFIER-ADEQUATE tier that has available budget;
|
|
6
|
+
* cheap exhausted but a stronger tier funded → route up, never block;
|
|
7
|
+
* everything exhausted or unreadable → session model + notice (fail-open).
|
|
8
|
+
*
|
|
9
|
+
* Two hard mechanism requirements from the contract:
|
|
10
|
+
* - `acquireBudgetPermit` is an ATOMIC reserve — O_EXCL lock file, then
|
|
11
|
+
* sum + append-pending in one critical section. Check-then-spend races
|
|
12
|
+
* two concurrent dispatches past the ceiling (council finding
|
|
13
|
+
* 2026-08-03).
|
|
14
|
+
* - `tripCooldown` / cool-down state — a 429/quota error pauses a tier
|
|
15
|
+
* (default 60 min) instead of retry-looping; `pickTier` skips cooling
|
|
16
|
+
* tiers via the same relation.
|
|
17
|
+
*
|
|
18
|
+
* Pure decision logic is I/O-free; only the permit/cool-down helpers
|
|
19
|
+
* touch the filesystem (ledger + state under agents/cost-tracking/).
|
|
20
|
+
*/
|
|
21
|
+
import * as fs from 'node:fs';
|
|
22
|
+
import * as path from 'node:path';
|
|
23
|
+
import { fileURLToPath } from 'node:url';
|
|
24
|
+
|
|
25
|
+
export type BudgetTier = 'cheap' | 'medium' | 'strong';
|
|
26
|
+
|
|
27
|
+
/** Ordered cheapest-first — the relation walks this order upward. */
|
|
28
|
+
export const TIER_ORDER: readonly BudgetTier[] = ['cheap', 'medium', 'strong'];
|
|
29
|
+
|
|
30
|
+
export interface TierBudgetState {
|
|
31
|
+
/** USD ceiling from cost.budgets.per_tier.<tier>; null = no tier cap. */
|
|
32
|
+
ceiling_usd: number | null;
|
|
33
|
+
/** Ledger-summed spend for this tier in the ceiling's window (USD). */
|
|
34
|
+
spent_usd: number;
|
|
35
|
+
/** Epoch ms until which this tier is cooling after a quota error; 0 = live. */
|
|
36
|
+
cooldown_until_ms: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type BudgetRoutingSwitch = 'ask' | 'auto' | 'off';
|
|
40
|
+
|
|
41
|
+
export interface PickTierInputs {
|
|
42
|
+
/** Cheapest tier the classifier marked adequate (quality floor). */
|
|
43
|
+
adequate_tier: BudgetTier;
|
|
44
|
+
/** Per-tier budget state, complete over TIER_ORDER. */
|
|
45
|
+
budgets: Record<BudgetTier, TierBudgetState>;
|
|
46
|
+
/** subagents.budget_routing setting. */
|
|
47
|
+
routing_switch: BudgetRoutingSwitch;
|
|
48
|
+
/** Estimated cost of the dispatch (USD) for the availability check. */
|
|
49
|
+
estimated_cost_usd: number;
|
|
50
|
+
/** Clock injection — never Date.now() in the decision path. */
|
|
51
|
+
now_ms: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface PickTierDecision {
|
|
55
|
+
/** 'tier' → dispatch on `tier`; 'session' → run on the session model. */
|
|
56
|
+
route: 'tier' | 'session';
|
|
57
|
+
tier: BudgetTier | null;
|
|
58
|
+
/** 'proceed' | 'ask' (switch=ask and a budget-motivated downshift) */
|
|
59
|
+
action: 'proceed' | 'ask';
|
|
60
|
+
/** Non-empty when the relation degraded (exhausted / cooling / off). */
|
|
61
|
+
notice: string;
|
|
62
|
+
reason: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function tierAvailable(
|
|
66
|
+
s: TierBudgetState,
|
|
67
|
+
est: number,
|
|
68
|
+
now: number,
|
|
69
|
+
): { ok: boolean; why: string } {
|
|
70
|
+
if (s.cooldown_until_ms > now) {
|
|
71
|
+
return { ok: false, why: 'cooling after quota error' };
|
|
72
|
+
}
|
|
73
|
+
if (s.ceiling_usd === null) {
|
|
74
|
+
return { ok: true, why: 'no tier cap' };
|
|
75
|
+
}
|
|
76
|
+
if (s.spent_usd + est > s.ceiling_usd) {
|
|
77
|
+
return { ok: false, why: `ceiling ${s.ceiling_usd} reached (spent ${s.spent_usd})` };
|
|
78
|
+
}
|
|
79
|
+
return { ok: true, why: 'within ceiling' };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The binding relation from docs/contracts/budget-routing.md. */
|
|
83
|
+
export function pickTier(inp: PickTierInputs): PickTierDecision {
|
|
84
|
+
if (inp.routing_switch === 'off') {
|
|
85
|
+
return {
|
|
86
|
+
route: 'session',
|
|
87
|
+
tier: null,
|
|
88
|
+
action: 'proceed',
|
|
89
|
+
notice: '',
|
|
90
|
+
reason: 'budget_routing off — session model, today\'s behavior',
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const startIdx = TIER_ORDER.indexOf(inp.adequate_tier);
|
|
94
|
+
const skipped: string[] = [];
|
|
95
|
+
for (let i = startIdx; i < TIER_ORDER.length; i += 1) {
|
|
96
|
+
const tier = TIER_ORDER[i] as BudgetTier;
|
|
97
|
+
const avail = tierAvailable(inp.budgets[tier], inp.estimated_cost_usd, inp.now_ms);
|
|
98
|
+
if (avail.ok) {
|
|
99
|
+
const degraded = i > startIdx;
|
|
100
|
+
return {
|
|
101
|
+
route: 'tier',
|
|
102
|
+
tier,
|
|
103
|
+
action: inp.routing_switch === 'ask' ? 'ask' : 'proceed',
|
|
104
|
+
notice: degraded
|
|
105
|
+
? `cheapest adequate tier unavailable (${skipped.join('; ')}) — routed up to ${tier}`
|
|
106
|
+
: '',
|
|
107
|
+
reason: `${tier}: ${avail.why}${degraded ? '' : ' — cheapest adequate tier'}`,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
skipped.push(`${tier}: ${avail.why}`);
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
route: 'session',
|
|
114
|
+
tier: null,
|
|
115
|
+
action: 'proceed',
|
|
116
|
+
notice: `all tier budgets unavailable (${skipped.join('; ')}) — session model, work not blocked`,
|
|
117
|
+
reason: 'fail-open: budget machinery degrades savings, never work',
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ── Atomic permit + cool-down (filesystem side) ────────────────────────────
|
|
122
|
+
|
|
123
|
+
export const RESERVE_FILE = 'tier-reserves.jsonl';
|
|
124
|
+
export const COOLDOWN_FILE = 'tier-cooldowns.json';
|
|
125
|
+
export const DEFAULT_COOLDOWN_MS = 60 * 60 * 1000;
|
|
126
|
+
|
|
127
|
+
// Reserve lifecycle constants — single source of truth shared with
|
|
128
|
+
// budget.mjs (the second reserve reader) via src/config/budget-routing.json.
|
|
129
|
+
// Duplicating the literal here was the exact two-truths defect the external
|
|
130
|
+
// review flagged; both readers now load the same file.
|
|
131
|
+
const _LIFECYCLE_CONFIG = path.join(
|
|
132
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
133
|
+
'..',
|
|
134
|
+
'..',
|
|
135
|
+
'config',
|
|
136
|
+
'budget-routing.json',
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
function _lifecycle(): { reserve_ttl_ms: number; lock_break_ms: number } {
|
|
140
|
+
try {
|
|
141
|
+
const raw = JSON.parse(fs.readFileSync(_LIFECYCLE_CONFIG, 'utf-8')) as Record<string, unknown>;
|
|
142
|
+
const ttl = typeof raw['reserve_ttl_ms'] === 'number' ? raw['reserve_ttl_ms'] : 600_000;
|
|
143
|
+
const brk = typeof raw['lock_break_ms'] === 'number' ? raw['lock_break_ms'] : 30_000;
|
|
144
|
+
return { reserve_ttl_ms: ttl, lock_break_ms: brk };
|
|
145
|
+
} catch {
|
|
146
|
+
return { reserve_ttl_ms: 600_000, lock_break_ms: 30_000 };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function reserveTtlMs(): number {
|
|
151
|
+
return _lifecycle().reserve_ttl_ms;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
interface ReserveEntry {
|
|
155
|
+
ts_ms: number;
|
|
156
|
+
tier: BudgetTier;
|
|
157
|
+
est_usd: number;
|
|
158
|
+
status: 'pending' | 'settled';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function _readReserves(reservePath: string): ReserveEntry[] {
|
|
162
|
+
if (!fs.existsSync(reservePath)) {
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
const out: ReserveEntry[] = [];
|
|
166
|
+
for (const line of fs.readFileSync(reservePath, 'utf-8').split('\n')) {
|
|
167
|
+
if (!line.trim()) continue;
|
|
168
|
+
try {
|
|
169
|
+
out.push(JSON.parse(line) as ReserveEntry);
|
|
170
|
+
} catch {
|
|
171
|
+
// tolerate a torn line — reserve file is advisory spend state
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Take the reserve lock; break a stale one (crash leftover) once. */
|
|
178
|
+
function _takeLock(lock: string, now_ms: number, lock_break_ms: number): number | null {
|
|
179
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
180
|
+
try {
|
|
181
|
+
return fs.openSync(lock, 'wx');
|
|
182
|
+
} catch {
|
|
183
|
+
try {
|
|
184
|
+
const age = now_ms - fs.statSync(lock).mtimeMs;
|
|
185
|
+
if (age > lock_break_ms) {
|
|
186
|
+
fs.rmSync(lock, { force: true });
|
|
187
|
+
continue; // one retry after breaking the stale lock
|
|
188
|
+
}
|
|
189
|
+
} catch {
|
|
190
|
+
continue; // lock vanished between open and stat — retry once
|
|
191
|
+
}
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Atomic budget reserve — full lifecycle (external review 2026-08-03):
|
|
200
|
+
*
|
|
201
|
+
* acquire → expire (TTL) / settle → compact-on-write
|
|
202
|
+
*
|
|
203
|
+
* Under the lock: live reserves = pending entries younger than the shared
|
|
204
|
+
* TTL (reserves are RACE PROTECTION, not spend accounting — real spend is
|
|
205
|
+
* the ledger's job); the file is COMPACTED to exactly those live entries
|
|
206
|
+
* on every acquire, so it stays bounded; a lock file older than
|
|
207
|
+
* `lock_break_ms` is a crash leftover and is broken with one retry.
|
|
208
|
+
* Returns granted=false (never throws) when the ceiling would be crossed
|
|
209
|
+
* or a fresh lock is genuinely held — the caller falls back per the
|
|
210
|
+
* relation (fail-open at the DECISION layer, conservative at the SPEND
|
|
211
|
+
* layer).
|
|
212
|
+
*/
|
|
213
|
+
export function acquireBudgetPermit(options: {
|
|
214
|
+
tracking_dir: string;
|
|
215
|
+
tier: BudgetTier;
|
|
216
|
+
estimated_cost_usd: number;
|
|
217
|
+
ceiling_usd: number | null;
|
|
218
|
+
spent_usd: number;
|
|
219
|
+
now_ms: number;
|
|
220
|
+
}): { granted: boolean; reason: string } {
|
|
221
|
+
const dir = options.tracking_dir;
|
|
222
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
223
|
+
const lock = path.join(dir, `${RESERVE_FILE}.lock`);
|
|
224
|
+
const { reserve_ttl_ms, lock_break_ms } = _lifecycle();
|
|
225
|
+
const fd = _takeLock(lock, options.now_ms, lock_break_ms);
|
|
226
|
+
if (fd === null) {
|
|
227
|
+
return { granted: false, reason: 'reserve lock busy — treat tier as unavailable this instant' };
|
|
228
|
+
}
|
|
229
|
+
try {
|
|
230
|
+
const reservePath = path.join(dir, RESERVE_FILE);
|
|
231
|
+
const live = _readReserves(reservePath).filter(
|
|
232
|
+
(e) => e.status === 'pending' && options.now_ms - e.ts_ms < reserve_ttl_ms,
|
|
233
|
+
);
|
|
234
|
+
const reserved = live
|
|
235
|
+
.filter((e) => e.tier === options.tier)
|
|
236
|
+
.reduce((acc, e) => acc + e.est_usd, 0);
|
|
237
|
+
if (
|
|
238
|
+
options.ceiling_usd !== null &&
|
|
239
|
+
options.spent_usd + reserved + options.estimated_cost_usd > options.ceiling_usd
|
|
240
|
+
) {
|
|
241
|
+
// Compact even on deny — expiry must not depend on a grant.
|
|
242
|
+
fs.writeFileSync(
|
|
243
|
+
reservePath,
|
|
244
|
+
live.map((e) => JSON.stringify(e)).join('\n') + (live.length ? '\n' : ''),
|
|
245
|
+
'utf-8',
|
|
246
|
+
);
|
|
247
|
+
return {
|
|
248
|
+
granted: false,
|
|
249
|
+
reason: `ceiling ${options.ceiling_usd} would be crossed (spent ${options.spent_usd} + reserved ${reserved})`,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
live.push({
|
|
253
|
+
ts_ms: options.now_ms,
|
|
254
|
+
tier: options.tier,
|
|
255
|
+
est_usd: options.estimated_cost_usd,
|
|
256
|
+
status: 'pending',
|
|
257
|
+
});
|
|
258
|
+
fs.writeFileSync(
|
|
259
|
+
reservePath,
|
|
260
|
+
live.map((e) => JSON.stringify(e)).join('\n') + '\n',
|
|
261
|
+
'utf-8',
|
|
262
|
+
);
|
|
263
|
+
return { granted: true, reason: 'reserved' };
|
|
264
|
+
} finally {
|
|
265
|
+
fs.closeSync(fd);
|
|
266
|
+
fs.rmSync(lock, { force: true });
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Settle the oldest live pending reserve of a tier (call when the real
|
|
272
|
+
* cost lands in the ledger, so the estimate stops counting immediately
|
|
273
|
+
* instead of waiting out the TTL). Settled entries are dropped by the
|
|
274
|
+
* next compaction. Best-effort: lock busy → no-op (the TTL is the
|
|
275
|
+
* backstop that makes settling optional, never load-bearing).
|
|
276
|
+
*/
|
|
277
|
+
export function settlePermit(options: {
|
|
278
|
+
tracking_dir: string;
|
|
279
|
+
tier: BudgetTier;
|
|
280
|
+
now_ms: number;
|
|
281
|
+
}): boolean {
|
|
282
|
+
const lock = path.join(options.tracking_dir, `${RESERVE_FILE}.lock`);
|
|
283
|
+
const { reserve_ttl_ms, lock_break_ms } = _lifecycle();
|
|
284
|
+
const fd = _takeLock(lock, options.now_ms, lock_break_ms);
|
|
285
|
+
if (fd === null) {
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
289
|
+
const reservePath = path.join(options.tracking_dir, RESERVE_FILE);
|
|
290
|
+
const live = _readReserves(reservePath).filter(
|
|
291
|
+
(e) => e.status === 'pending' && options.now_ms - e.ts_ms < reserve_ttl_ms,
|
|
292
|
+
);
|
|
293
|
+
const idx = live.findIndex((e) => e.tier === options.tier);
|
|
294
|
+
if (idx === -1) {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
live.splice(idx, 1);
|
|
298
|
+
fs.writeFileSync(
|
|
299
|
+
reservePath,
|
|
300
|
+
live.map((e) => JSON.stringify(e)).join('\n') + (live.length ? '\n' : ''),
|
|
301
|
+
'utf-8',
|
|
302
|
+
);
|
|
303
|
+
return true;
|
|
304
|
+
} finally {
|
|
305
|
+
fs.closeSync(fd);
|
|
306
|
+
fs.rmSync(lock, { force: true });
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Record a quota/429 error: the tier cools down; pickTier skips it.
|
|
312
|
+
*
|
|
313
|
+
* Deliberately lock-free: last-writer-wins on a 3-key timestamp map is
|
|
314
|
+
* benign (two concurrent trips write near-identical "until" values), so
|
|
315
|
+
* the reserve lock is not reused here (accepted in the 2026-08-03 review).
|
|
316
|
+
* Expired entries are purged on every write so the map never grows stale.
|
|
317
|
+
*/
|
|
318
|
+
export function tripCooldown(options: {
|
|
319
|
+
tracking_dir: string;
|
|
320
|
+
tier: BudgetTier;
|
|
321
|
+
now_ms: number;
|
|
322
|
+
cooldown_ms?: number;
|
|
323
|
+
}): number {
|
|
324
|
+
const until = options.now_ms + (options.cooldown_ms ?? DEFAULT_COOLDOWN_MS);
|
|
325
|
+
const p = path.join(options.tracking_dir, COOLDOWN_FILE);
|
|
326
|
+
let state: Record<string, number> = {};
|
|
327
|
+
try {
|
|
328
|
+
state = JSON.parse(fs.readFileSync(p, 'utf-8')) as Record<string, number>;
|
|
329
|
+
} catch {
|
|
330
|
+
state = {};
|
|
331
|
+
}
|
|
332
|
+
for (const [k, v] of Object.entries(state)) {
|
|
333
|
+
if (typeof v !== 'number' || v <= options.now_ms) {
|
|
334
|
+
delete state[k];
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
state[options.tier] = until;
|
|
338
|
+
fs.mkdirSync(options.tracking_dir, { recursive: true });
|
|
339
|
+
fs.writeFileSync(p, `${JSON.stringify(state, null, 2)}\n`, 'utf-8');
|
|
340
|
+
return until;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Read the cool-down map (missing/torn file → all tiers live). */
|
|
344
|
+
export function readCooldowns(tracking_dir: string): Record<BudgetTier, number> {
|
|
345
|
+
const empty: Record<BudgetTier, number> = { cheap: 0, medium: 0, strong: 0 };
|
|
346
|
+
try {
|
|
347
|
+
const raw = JSON.parse(
|
|
348
|
+
fs.readFileSync(path.join(tracking_dir, COOLDOWN_FILE), 'utf-8'),
|
|
349
|
+
) as Record<string, unknown>;
|
|
350
|
+
for (const t of TIER_ORDER) {
|
|
351
|
+
const v = raw[t];
|
|
352
|
+
if (typeof v === 'number' && Number.isFinite(v)) {
|
|
353
|
+
empty[t] = v;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return empty;
|
|
357
|
+
} catch {
|
|
358
|
+
return empty;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
@@ -31,7 +31,14 @@ export const ADAPTER_REGISTRY: readonly AdapterEntry[] = [
|
|
|
31
31
|
{
|
|
32
32
|
tool: 'Claude Code',
|
|
33
33
|
kind: 'native',
|
|
34
|
-
roots: [
|
|
34
|
+
roots: [
|
|
35
|
+
'.claude/rules',
|
|
36
|
+
'.claude/skills',
|
|
37
|
+
'.claude/commands',
|
|
38
|
+
'.claude/agents',
|
|
39
|
+
'.claude/personas',
|
|
40
|
+
'.claude/user-types',
|
|
41
|
+
],
|
|
35
42
|
generator_tag: 'claude-code',
|
|
36
43
|
},
|
|
37
44
|
{ tool: 'Cursor', kind: 'native', roots: ['.cursor/rules', '.cursor/commands'], generator_tag: 'cursor' },
|
|
@@ -29,3 +29,12 @@ export const DOMAIN_FLOORS: Readonly<Record<string, TriggerEvalFloor>> = {
|
|
|
29
29
|
export function floor_for(skill: string | null | undefined): TriggerEvalFloor {
|
|
30
30
|
return (skill !== null && skill !== undefined && DOMAIN_FLOORS[skill]) || DEFAULT_FLOOR;
|
|
31
31
|
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Advisory floor for the rules-mode canary leg (`rule_trigger_eval.ts`,
|
|
35
|
+
* road-to-tested-routing Phase 4). Deliberately loose: it is a drift canary
|
|
36
|
+
* over a ~40-case derived suite spanning ~97 rules, not a per-rule quality
|
|
37
|
+
* gate — ratchet only after live baseline data exists. A breach fails the
|
|
38
|
+
* scheduled canary job only, never a PR.
|
|
39
|
+
*/
|
|
40
|
+
export const RULES_MODE_FLOOR: TriggerEvalFloor = { minRecall: 0.5, minPrecision: 0.5 };
|
|
@@ -42,14 +42,20 @@ export interface FetchRouterOpts {
|
|
|
42
42
|
model?: string;
|
|
43
43
|
apiKey?: string;
|
|
44
44
|
fetchImpl?: FetchImpl;
|
|
45
|
+
/**
|
|
46
|
+
* Prompt header prepended to the catalogue (default: the skill-routing
|
|
47
|
+
* header). The rules-mode eval (`rule_trigger_eval.ts`) injects its own
|
|
48
|
+
* header here so it reuses this transport code unchanged.
|
|
49
|
+
*/
|
|
50
|
+
promptHeader?: string;
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
const MAX_TOKENS = 256;
|
|
48
54
|
|
|
49
55
|
/** Build the system prompt the same way AnthropicRouter does. */
|
|
50
|
-
function buildSystemPrompt(skills: SkillMeta[]): string {
|
|
56
|
+
function buildSystemPrompt(skills: SkillMeta[], header: string = ROUTING_PROMPT_HEADER): string {
|
|
51
57
|
const catalogue = skills.map((s) => `- ${s.name} :: ${s.description}`).join('\n');
|
|
52
|
-
return
|
|
58
|
+
return header + catalogue + '\n';
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
function requireKey(name: string, apiKey: string | undefined): string {
|
|
@@ -97,11 +103,13 @@ export class OpenAiRouter implements TriggerRouter {
|
|
|
97
103
|
private _model: string;
|
|
98
104
|
private _apiKey: string;
|
|
99
105
|
private _fetch: FetchImpl;
|
|
106
|
+
private _promptHeader: string | undefined;
|
|
100
107
|
|
|
101
108
|
constructor(opts: FetchRouterOpts = {}) {
|
|
102
109
|
this._model = opts.model ?? 'gpt-4o-mini';
|
|
103
110
|
this._apiKey = requireKey('OpenAiRouter', opts.apiKey);
|
|
104
111
|
this._fetch = opts.fetchImpl ?? (globalThis.fetch as unknown as FetchImpl);
|
|
112
|
+
this._promptHeader = opts.promptHeader;
|
|
105
113
|
}
|
|
106
114
|
|
|
107
115
|
route(_query: string, _skills: SkillMeta[]): [string[], number, number] {
|
|
@@ -109,7 +117,7 @@ export class OpenAiRouter implements TriggerRouter {
|
|
|
109
117
|
}
|
|
110
118
|
|
|
111
119
|
async routeAsync(query: string, skills: SkillMeta[]): Promise<[string[], number, number]> {
|
|
112
|
-
const systemPrompt = buildSystemPrompt(skills);
|
|
120
|
+
const systemPrompt = buildSystemPrompt(skills, this._promptHeader);
|
|
113
121
|
const body = JSON.stringify({
|
|
114
122
|
model: this._model,
|
|
115
123
|
max_tokens: MAX_TOKENS,
|
|
@@ -156,12 +164,14 @@ export class GeminiRouter implements TriggerRouter {
|
|
|
156
164
|
private _model: string;
|
|
157
165
|
private _apiKey: string;
|
|
158
166
|
private _fetch: FetchImpl;
|
|
167
|
+
private _promptHeader: string | undefined;
|
|
159
168
|
|
|
160
169
|
constructor(opts: FetchRouterOpts = {}) {
|
|
161
170
|
// flash, NOT pro: pro burns ~150 "thinking" tokens on trivial prompts.
|
|
162
171
|
this._model = opts.model ?? 'gemini-2.5-flash';
|
|
163
172
|
this._apiKey = requireKey('GeminiRouter', opts.apiKey);
|
|
164
173
|
this._fetch = opts.fetchImpl ?? (globalThis.fetch as unknown as FetchImpl);
|
|
174
|
+
this._promptHeader = opts.promptHeader;
|
|
165
175
|
}
|
|
166
176
|
|
|
167
177
|
route(_query: string, _skills: SkillMeta[]): [string[], number, number] {
|
|
@@ -169,7 +179,7 @@ export class GeminiRouter implements TriggerRouter {
|
|
|
169
179
|
}
|
|
170
180
|
|
|
171
181
|
async routeAsync(query: string, skills: SkillMeta[]): Promise<[string[], number, number]> {
|
|
172
|
-
const systemPrompt = buildSystemPrompt(skills);
|
|
182
|
+
const systemPrompt = buildSystemPrompt(skills, this._promptHeader);
|
|
173
183
|
const url =
|
|
174
184
|
`https://generativelanguage.googleapis.com/v1beta/models/` +
|
|
175
185
|
`${this._model}:generateContent?key=${this._apiKey}`;
|
|
@@ -221,11 +231,13 @@ export class AnthropicFetchRouter implements TriggerRouter {
|
|
|
221
231
|
private _model: string;
|
|
222
232
|
private _apiKey: string;
|
|
223
233
|
private _fetch: FetchImpl;
|
|
234
|
+
private _promptHeader: string | undefined;
|
|
224
235
|
|
|
225
236
|
constructor(opts: FetchRouterOpts = {}) {
|
|
226
237
|
this._model = opts.model ?? 'claude-haiku-4-5-20251001';
|
|
227
238
|
this._apiKey = requireKey('AnthropicFetchRouter', opts.apiKey);
|
|
228
239
|
this._fetch = opts.fetchImpl ?? (globalThis.fetch as unknown as FetchImpl);
|
|
240
|
+
this._promptHeader = opts.promptHeader;
|
|
229
241
|
}
|
|
230
242
|
|
|
231
243
|
route(_query: string, _skills: SkillMeta[]): [string[], number, number] {
|
|
@@ -233,7 +245,7 @@ export class AnthropicFetchRouter implements TriggerRouter {
|
|
|
233
245
|
}
|
|
234
246
|
|
|
235
247
|
async routeAsync(query: string, skills: SkillMeta[]): Promise<[string[], number, number]> {
|
|
236
|
-
const systemPrompt = buildSystemPrompt(skills);
|
|
248
|
+
const systemPrompt = buildSystemPrompt(skills, this._promptHeader);
|
|
237
249
|
const body = JSON.stringify({
|
|
238
250
|
model: this._model,
|
|
239
251
|
max_tokens: MAX_TOKENS,
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
* Ported from the retired Python `src/scripts/adr/regenerate_index.py` (ADR-200).
|
|
9
9
|
* The CLI contract is pinned — `--dir` (default `docs/adr/`),
|
|
10
10
|
* `--check`, exit codes (0 / 1 / 2), the stdout/stderr split, byte-identical
|
|
11
|
-
* messages
|
|
12
|
-
*
|
|
11
|
+
* messages. One deliberate deviation from the Python output: the INDEX.md
|
|
12
|
+
* header names this TypeScript generator, not the retired `.py` (the ported
|
|
13
|
+
* self-misdescription was itself a defect — ADR-hygiene roadmap, Phase 1).
|
|
13
14
|
*/
|
|
14
15
|
import * as fs from 'node:fs';
|
|
15
16
|
import * as path from 'node:path';
|
|
@@ -244,7 +245,7 @@ export function render(num: Row[], leg: Row[], outside: OutsideRow[] = []): stri
|
|
|
244
245
|
const out = [
|
|
245
246
|
'# ADR Index',
|
|
246
247
|
'',
|
|
247
|
-
'_Auto-generated by `scripts/adr/regenerate_index.
|
|
248
|
+
'_Auto-generated by `src/scripts/adr/regenerate_index.ts`. Do not edit._',
|
|
248
249
|
'',
|
|
249
250
|
];
|
|
250
251
|
if (num.length === 0 && leg.length === 0 && outside.length === 0) {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# stdout. Pure POSIX-compatible bash (no associative arrays, runs
|
|
5
5
|
# on macOS bash 3.2); jq required for JSON safety.
|
|
6
6
|
#
|
|
7
|
-
# Schema:
|
|
7
|
+
# Schema: src/skills/scene-expander/scene-blueprint.schema.yaml
|
|
8
8
|
# Contract: scripts/media/lib/adapter-contract.md
|
|
9
9
|
#
|
|
10
10
|
# Usage:
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* human-authored source-of-truth for PR review. This script reads it
|
|
11
11
|
* through the hardened {@link parse_corpus_strict} parser and writes a YAML
|
|
12
12
|
* lockfile that becomes the **runtime** source-of-truth. The pattern mirrors
|
|
13
|
-
* `dist/agent-src/` vs
|
|
13
|
+
* `dist/agent-src/` vs `src/`: human edits Markdown,
|
|
14
14
|
* `task consistency` enforces lockfile parity via the same
|
|
15
15
|
* `git diff --quiet` gate.
|
|
16
16
|
*
|
|
@@ -22,7 +22,7 @@ import * as path from 'node:path';
|
|
|
22
22
|
import process from 'node:process';
|
|
23
23
|
import { fileURLToPath } from 'node:url';
|
|
24
24
|
|
|
25
|
-
import yaml from 'js-yaml';
|
|
25
|
+
import * as yaml from 'js-yaml';
|
|
26
26
|
|
|
27
27
|
import { build_thin, RULES_SOURCE } from './project_thin_rules.js';
|
|
28
28
|
import { cohensKappa } from './check_quality_regression.js';
|
|
@@ -22,6 +22,8 @@ import * as path from 'node:path';
|
|
|
22
22
|
import process from 'node:process';
|
|
23
23
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
24
24
|
|
|
25
|
+
import { DeadScopeError, assertScanned } from './_lib/scan_scope.js';
|
|
26
|
+
|
|
25
27
|
type Json = unknown;
|
|
26
28
|
type JsonObject = Record<string, Json>;
|
|
27
29
|
|
|
@@ -29,9 +31,25 @@ const _HERE = fileURLToPath(import.meta.url);
|
|
|
29
31
|
|
|
30
32
|
// src/scripts/audit_cloud_compatibility.ts → parents[2] of the .py file = repo root.
|
|
31
33
|
export const ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
32
|
-
export const SOURCE = path.join(ROOT, '.agent-src.uncondensed');
|
|
33
34
|
export const SCAN_DIRS = ['skills', 'rules', 'commands', 'guidelines'];
|
|
34
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Where each artefact kind actually lives.
|
|
38
|
+
*
|
|
39
|
+
* Every kind used to be `SOURCE/<kind>` under one pre-ADR-051 container. All
|
|
40
|
+
* four resolved to a missing directory after the move, so `scan()` returned []
|
|
41
|
+
* and the audit emitted `"total": 0` with every histogram empty — a cloud-
|
|
42
|
+
* compatibility report over zero artefacts. The four kinds now have four
|
|
43
|
+
* unrelated homes (commands are not a directory at all but one `command.md`
|
|
44
|
+
* leaf per verb), so the mapping is explicit rather than a join.
|
|
45
|
+
*/
|
|
46
|
+
export const KIND_ROOTS: Readonly<Record<string, string>> = {
|
|
47
|
+
skills: path.join(ROOT, 'src', 'skills'),
|
|
48
|
+
rules: path.join(ROOT, 'src', 'rules'),
|
|
49
|
+
commands: path.join(ROOT, 'src', 'domains'),
|
|
50
|
+
guidelines: path.join(ROOT, 'docs', 'guidelines'),
|
|
51
|
+
};
|
|
52
|
+
|
|
35
53
|
// --- patterns (global so findall returns every non-overlapping match) ---------
|
|
36
54
|
const SCRIPT_RE = /scripts\/[a-z_]+\.(?:py|sh)|python3\s+scripts\/|bash\s+scripts\/|\.\/scripts\//g;
|
|
37
55
|
const TASK_RE = /`task\s+[a-z][a-z0-9-]+`/g;
|
|
@@ -357,7 +375,7 @@ interface Row extends ClassifyEvidence {
|
|
|
357
375
|
export function scan(): Row[] {
|
|
358
376
|
const rows: Row[] = [];
|
|
359
377
|
for (const sub of SCAN_DIRS) {
|
|
360
|
-
const base =
|
|
378
|
+
const base = KIND_ROOTS[sub] as string;
|
|
361
379
|
if (!_isDir(base)) {
|
|
362
380
|
continue;
|
|
363
381
|
}
|
|
@@ -565,6 +583,23 @@ export function main(argv: string[] | null = null): number {
|
|
|
565
583
|
}
|
|
566
584
|
|
|
567
585
|
const rows = scan();
|
|
586
|
+
// Scope assertion: a cloud-compat report over an empty corpus is not "no
|
|
587
|
+
// cloud-incompatible artefacts", it is a report about nothing — the exact
|
|
588
|
+
// `"total": 0` output this audit produced while its roots were dead.
|
|
589
|
+
try {
|
|
590
|
+
assertScanned({
|
|
591
|
+
gate: 'audit_cloud_compatibility',
|
|
592
|
+
scanned: rows.length,
|
|
593
|
+
units: 'artefact(s)',
|
|
594
|
+
roots: SCAN_DIRS.map((k) => _relativeToPosix(KIND_ROOTS[k] as string, ROOT)),
|
|
595
|
+
});
|
|
596
|
+
} catch (exc) {
|
|
597
|
+
if (!(exc instanceof DeadScopeError)) {
|
|
598
|
+
throw exc;
|
|
599
|
+
}
|
|
600
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
601
|
+
return 2;
|
|
602
|
+
}
|
|
568
603
|
const summary = summarize(rows);
|
|
569
604
|
|
|
570
605
|
if (args.details) {
|