@event4u/agent-config 9.14.0 → 9.15.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 +110 -112
- 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/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 +2 -0
- 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/ui/assets/index-DJY_vQVn.js +40 -0
- package/dist/ui/assets/index-DJY_vQVn.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 +11 -11
- 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 +59 -11
- 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 +234 -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/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/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_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 +337 -1
- 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_regression.ts +9 -2
- package/src/scripts/condense.ts +155 -12
- package/src/scripts/cost/budget.mjs +63 -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 +105 -1
- 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 +485 -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/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,234 @@
|
|
|
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
|
+
|
|
24
|
+
export type BudgetTier = 'cheap' | 'medium' | 'strong';
|
|
25
|
+
|
|
26
|
+
/** Ordered cheapest-first — the relation walks this order upward. */
|
|
27
|
+
export const TIER_ORDER: readonly BudgetTier[] = ['cheap', 'medium', 'strong'];
|
|
28
|
+
|
|
29
|
+
export interface TierBudgetState {
|
|
30
|
+
/** USD ceiling from cost.budgets.per_tier.<tier>; null = no tier cap. */
|
|
31
|
+
ceiling_usd: number | null;
|
|
32
|
+
/** Ledger-summed spend for this tier in the ceiling's window (USD). */
|
|
33
|
+
spent_usd: number;
|
|
34
|
+
/** Epoch ms until which this tier is cooling after a quota error; 0 = live. */
|
|
35
|
+
cooldown_until_ms: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type BudgetRoutingSwitch = 'ask' | 'auto' | 'off';
|
|
39
|
+
|
|
40
|
+
export interface PickTierInputs {
|
|
41
|
+
/** Cheapest tier the classifier marked adequate (quality floor). */
|
|
42
|
+
adequate_tier: BudgetTier;
|
|
43
|
+
/** Per-tier budget state, complete over TIER_ORDER. */
|
|
44
|
+
budgets: Record<BudgetTier, TierBudgetState>;
|
|
45
|
+
/** subagents.budget_routing setting. */
|
|
46
|
+
routing_switch: BudgetRoutingSwitch;
|
|
47
|
+
/** Estimated cost of the dispatch (USD) for the availability check. */
|
|
48
|
+
estimated_cost_usd: number;
|
|
49
|
+
/** Clock injection — never Date.now() in the decision path. */
|
|
50
|
+
now_ms: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface PickTierDecision {
|
|
54
|
+
/** 'tier' → dispatch on `tier`; 'session' → run on the session model. */
|
|
55
|
+
route: 'tier' | 'session';
|
|
56
|
+
tier: BudgetTier | null;
|
|
57
|
+
/** 'proceed' | 'ask' (switch=ask and a budget-motivated downshift) */
|
|
58
|
+
action: 'proceed' | 'ask';
|
|
59
|
+
/** Non-empty when the relation degraded (exhausted / cooling / off). */
|
|
60
|
+
notice: string;
|
|
61
|
+
reason: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function tierAvailable(
|
|
65
|
+
s: TierBudgetState,
|
|
66
|
+
est: number,
|
|
67
|
+
now: number,
|
|
68
|
+
): { ok: boolean; why: string } {
|
|
69
|
+
if (s.cooldown_until_ms > now) {
|
|
70
|
+
return { ok: false, why: 'cooling after quota error' };
|
|
71
|
+
}
|
|
72
|
+
if (s.ceiling_usd === null) {
|
|
73
|
+
return { ok: true, why: 'no tier cap' };
|
|
74
|
+
}
|
|
75
|
+
if (s.spent_usd + est > s.ceiling_usd) {
|
|
76
|
+
return { ok: false, why: `ceiling ${s.ceiling_usd} reached (spent ${s.spent_usd})` };
|
|
77
|
+
}
|
|
78
|
+
return { ok: true, why: 'within ceiling' };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The binding relation from docs/contracts/budget-routing.md. */
|
|
82
|
+
export function pickTier(inp: PickTierInputs): PickTierDecision {
|
|
83
|
+
if (inp.routing_switch === 'off') {
|
|
84
|
+
return {
|
|
85
|
+
route: 'session',
|
|
86
|
+
tier: null,
|
|
87
|
+
action: 'proceed',
|
|
88
|
+
notice: '',
|
|
89
|
+
reason: 'budget_routing off — session model, today\'s behavior',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const startIdx = TIER_ORDER.indexOf(inp.adequate_tier);
|
|
93
|
+
const skipped: string[] = [];
|
|
94
|
+
for (let i = startIdx; i < TIER_ORDER.length; i += 1) {
|
|
95
|
+
const tier = TIER_ORDER[i] as BudgetTier;
|
|
96
|
+
const avail = tierAvailable(inp.budgets[tier], inp.estimated_cost_usd, inp.now_ms);
|
|
97
|
+
if (avail.ok) {
|
|
98
|
+
const degraded = i > startIdx;
|
|
99
|
+
return {
|
|
100
|
+
route: 'tier',
|
|
101
|
+
tier,
|
|
102
|
+
action: inp.routing_switch === 'ask' ? 'ask' : 'proceed',
|
|
103
|
+
notice: degraded
|
|
104
|
+
? `cheapest adequate tier unavailable (${skipped.join('; ')}) — routed up to ${tier}`
|
|
105
|
+
: '',
|
|
106
|
+
reason: `${tier}: ${avail.why}${degraded ? '' : ' — cheapest adequate tier'}`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
skipped.push(`${tier}: ${avail.why}`);
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
route: 'session',
|
|
113
|
+
tier: null,
|
|
114
|
+
action: 'proceed',
|
|
115
|
+
notice: `all tier budgets unavailable (${skipped.join('; ')}) — session model, work not blocked`,
|
|
116
|
+
reason: 'fail-open: budget machinery degrades savings, never work',
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ── Atomic permit + cool-down (filesystem side) ────────────────────────────
|
|
121
|
+
|
|
122
|
+
export const RESERVE_FILE = 'tier-reserves.jsonl';
|
|
123
|
+
export const COOLDOWN_FILE = 'tier-cooldowns.json';
|
|
124
|
+
export const DEFAULT_COOLDOWN_MS = 60 * 60 * 1000;
|
|
125
|
+
|
|
126
|
+
interface ReserveEntry {
|
|
127
|
+
ts_ms: number;
|
|
128
|
+
tier: BudgetTier;
|
|
129
|
+
est_usd: number;
|
|
130
|
+
status: 'pending';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Atomic budget reserve: O_EXCL lock → sum ledger+reserves → append a
|
|
135
|
+
* pending entry → release. Returns granted=false (never throws) when the
|
|
136
|
+
* ceiling would be crossed or the lock cannot be taken quickly — the
|
|
137
|
+
* caller falls back per the relation (fail-open at the DECISION layer,
|
|
138
|
+
* conservative at the SPEND layer).
|
|
139
|
+
*/
|
|
140
|
+
export function acquireBudgetPermit(options: {
|
|
141
|
+
tracking_dir: string;
|
|
142
|
+
tier: BudgetTier;
|
|
143
|
+
estimated_cost_usd: number;
|
|
144
|
+
ceiling_usd: number | null;
|
|
145
|
+
spent_usd: number;
|
|
146
|
+
now_ms: number;
|
|
147
|
+
}): { granted: boolean; reason: string } {
|
|
148
|
+
const dir = options.tracking_dir;
|
|
149
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
150
|
+
const lock = path.join(dir, `${RESERVE_FILE}.lock`);
|
|
151
|
+
let fd: number;
|
|
152
|
+
try {
|
|
153
|
+
fd = fs.openSync(lock, 'wx');
|
|
154
|
+
} catch {
|
|
155
|
+
return { granted: false, reason: 'reserve lock busy — treat tier as unavailable this instant' };
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
const reservePath = path.join(dir, RESERVE_FILE);
|
|
159
|
+
let reserved = 0;
|
|
160
|
+
if (fs.existsSync(reservePath)) {
|
|
161
|
+
for (const line of fs.readFileSync(reservePath, 'utf-8').split('\n')) {
|
|
162
|
+
if (!line.trim()) continue;
|
|
163
|
+
try {
|
|
164
|
+
const e = JSON.parse(line) as ReserveEntry;
|
|
165
|
+
if (e.tier === options.tier && e.status === 'pending') {
|
|
166
|
+
reserved += e.est_usd;
|
|
167
|
+
}
|
|
168
|
+
} catch {
|
|
169
|
+
// tolerate a torn line — reserve file is advisory spend state
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (
|
|
174
|
+
options.ceiling_usd !== null &&
|
|
175
|
+
options.spent_usd + reserved + options.estimated_cost_usd > options.ceiling_usd
|
|
176
|
+
) {
|
|
177
|
+
return {
|
|
178
|
+
granted: false,
|
|
179
|
+
reason: `ceiling ${options.ceiling_usd} would be crossed (spent ${options.spent_usd} + reserved ${reserved})`,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
const entry: ReserveEntry = {
|
|
183
|
+
ts_ms: options.now_ms,
|
|
184
|
+
tier: options.tier,
|
|
185
|
+
est_usd: options.estimated_cost_usd,
|
|
186
|
+
status: 'pending',
|
|
187
|
+
};
|
|
188
|
+
fs.appendFileSync(reservePath, `${JSON.stringify(entry)}\n`, 'utf-8');
|
|
189
|
+
return { granted: true, reason: 'reserved' };
|
|
190
|
+
} finally {
|
|
191
|
+
fs.closeSync(fd);
|
|
192
|
+
fs.rmSync(lock, { force: true });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Record a quota/429 error: the tier cools down; pickTier skips it. */
|
|
197
|
+
export function tripCooldown(options: {
|
|
198
|
+
tracking_dir: string;
|
|
199
|
+
tier: BudgetTier;
|
|
200
|
+
now_ms: number;
|
|
201
|
+
cooldown_ms?: number;
|
|
202
|
+
}): number {
|
|
203
|
+
const until = options.now_ms + (options.cooldown_ms ?? DEFAULT_COOLDOWN_MS);
|
|
204
|
+
const p = path.join(options.tracking_dir, COOLDOWN_FILE);
|
|
205
|
+
let state: Record<string, number> = {};
|
|
206
|
+
try {
|
|
207
|
+
state = JSON.parse(fs.readFileSync(p, 'utf-8')) as Record<string, number>;
|
|
208
|
+
} catch {
|
|
209
|
+
state = {};
|
|
210
|
+
}
|
|
211
|
+
state[options.tier] = until;
|
|
212
|
+
fs.mkdirSync(options.tracking_dir, { recursive: true });
|
|
213
|
+
fs.writeFileSync(p, `${JSON.stringify(state, null, 2)}\n`, 'utf-8');
|
|
214
|
+
return until;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Read the cool-down map (missing/torn file → all tiers live). */
|
|
218
|
+
export function readCooldowns(tracking_dir: string): Record<BudgetTier, number> {
|
|
219
|
+
const empty: Record<BudgetTier, number> = { cheap: 0, medium: 0, strong: 0 };
|
|
220
|
+
try {
|
|
221
|
+
const raw = JSON.parse(
|
|
222
|
+
fs.readFileSync(path.join(tracking_dir, COOLDOWN_FILE), 'utf-8'),
|
|
223
|
+
) as Record<string, unknown>;
|
|
224
|
+
for (const t of TIER_ORDER) {
|
|
225
|
+
const v = raw[t];
|
|
226
|
+
if (typeof v === 'number' && Number.isFinite(v)) {
|
|
227
|
+
empty[t] = v;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return empty;
|
|
231
|
+
} catch {
|
|
232
|
+
return empty;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
@@ -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,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) {
|
|
@@ -59,9 +59,22 @@ const MONOLITH_TOOLS = ['.windsurfrules'];
|
|
|
59
59
|
// costing?" question as the rule/description surfaces, for the MCP slice. The
|
|
60
60
|
// source-of-truth catalog is the one shipped by this package's MCP server.
|
|
61
61
|
const MCP_CATALOG = path.join(REPO_ROOT, 'src', 'scripts', 'mcp_server', 'consumer_tool_catalog.json');
|
|
62
|
-
//
|
|
63
|
-
//
|
|
62
|
+
// The catalog is served by TWO transports with two different tool lists, so
|
|
63
|
+
// pricing it once produced a number no client ever loads:
|
|
64
|
+
//
|
|
65
|
+
// - stdio (`src/scripts/mcp_server/`) registers only the implemented tools
|
|
66
|
+
// (`REGISTRY` in mcp_server/tools.ts) — the 12 discovery stubs are
|
|
67
|
+
// deliberately unregistered (ADR-132), so a local client sees 19.
|
|
68
|
+
// - worker (`internal/workers/mcp/`) maps EVERY catalog entry into
|
|
69
|
+
// `tools/list` (`stubs.ts::listTools`), tagging stubs `_meta.stub` and
|
|
70
|
+
// answering calls with the not_implemented envelope — so it sees 31.
|
|
71
|
+
//
|
|
72
|
+
// Pricing all 31 under one label reported a hybrid client that cannot exist,
|
|
73
|
+
// and made the over-subscription flag fire on a count no local install pays.
|
|
74
|
+
// Keying by transport keeps the per-server shape and makes each number belong
|
|
75
|
+
// to a real client. `implemented_on` in the catalog is what distinguishes them.
|
|
64
76
|
const MCP_SERVER_NAME = 'agent-config';
|
|
77
|
+
const MCP_TRANSPORTS = ['stdio', 'worker'] as const;
|
|
65
78
|
// Soft advisory ceiling on tool count per server. "Few used" cannot be known at
|
|
66
79
|
// audit time (no runtime call data), so over-subscription is a count-based
|
|
67
80
|
// heuristic: many tools => maintainer should review which earn their cost.
|
|
@@ -396,24 +409,40 @@ export function mcp_tool_schemas(): Record<string, McpServer> {
|
|
|
396
409
|
return {};
|
|
397
410
|
}
|
|
398
411
|
const tools = (catalog.tools as unknown[]) ?? [];
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
.map((t) => _measure_tool_schema(t));
|
|
402
|
-
// priced.sort(key=lambda r: (-int(r["tokens_gpt"]), str(r["name"])))
|
|
403
|
-
priced.sort(
|
|
404
|
-
(a, b) => b.tokens_gpt - a.tokens_gpt || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0),
|
|
412
|
+
const records = tools.filter(
|
|
413
|
+
(t): t is Record<string, unknown> => typeof t === 'object' && t !== null && !Array.isArray(t),
|
|
405
414
|
);
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
+
|
|
416
|
+
/** Tools a given transport actually returns from `tools/list`. */
|
|
417
|
+
const served = (transport: string): Record<string, unknown>[] => {
|
|
418
|
+
// The Worker serves the whole catalog (stubs included, flagged
|
|
419
|
+
// `_meta.stub`); stdio serves only what `implemented_on` names.
|
|
420
|
+
if (transport === 'worker') return records;
|
|
421
|
+
return records.filter(
|
|
422
|
+
(t) => Array.isArray(t.implemented_on) && (t.implemented_on as unknown[]).includes(transport),
|
|
423
|
+
);
|
|
415
424
|
};
|
|
416
|
-
|
|
425
|
+
|
|
426
|
+
const out: Record<string, McpServer> = {};
|
|
427
|
+
for (const transport of MCP_TRANSPORTS) {
|
|
428
|
+
const priced: McpPriced[] = served(transport).map((t) => _measure_tool_schema(t));
|
|
429
|
+
// priced.sort(key=lambda r: (-int(r["tokens_gpt"]), str(r["name"])))
|
|
430
|
+
priced.sort(
|
|
431
|
+
(a, b) => b.tokens_gpt - a.tokens_gpt || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0),
|
|
432
|
+
);
|
|
433
|
+
out[`${MCP_SERVER_NAME} (${transport})`] = {
|
|
434
|
+
tool_count: priced.length,
|
|
435
|
+
chars: priced.reduce((acc, r) => acc + r.chars, 0),
|
|
436
|
+
tokens_gpt: priced.reduce((acc, r) => acc + r.tokens_gpt, 0),
|
|
437
|
+
tokens_claude: priced.reduce((acc, r) => acc + r.tokens_claude, 0),
|
|
438
|
+
tokens_gpt_exact:
|
|
439
|
+
priced.length > 0 ? priced.every((r) => Boolean(r.tokens_gpt_exact)) : true,
|
|
440
|
+
over_subscription: priced.length > MCP_OVERSUBSCRIPTION_TOOL_CAP,
|
|
441
|
+
oversubscription_cap: MCP_OVERSUBSCRIPTION_TOOL_CAP,
|
|
442
|
+
tools: priced.map((r) => ({ name: r.name, tokens_gpt: r.tokens_gpt, chars: r.chars })),
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
return out;
|
|
417
446
|
}
|
|
418
447
|
|
|
419
448
|
export function build(): Record<string, unknown> {
|
|
@@ -678,7 +707,12 @@ export function main(argv: string[] | null = null): number {
|
|
|
678
707
|
const rf = rfVals.length > 0 ? (rfVals[0] as Measure) : ({} as Measure);
|
|
679
708
|
const dc = data.description_catalog as Record<string, Measure>;
|
|
680
709
|
const mcp = (data.mcp_tool_schemas as Record<string, McpServer>) ?? {};
|
|
681
|
-
|
|
710
|
+
// MAX, not sum: the entries are two TRANSPORTS of the same server, and
|
|
711
|
+
// a client connects over exactly one of them. Summing would bill the
|
|
712
|
+
// 19 implemented tools twice and gate on a client that cannot exist —
|
|
713
|
+
// the same conflation the per-transport split exists to remove. The
|
|
714
|
+
// budget is what the worst-case single client actually loads.
|
|
715
|
+
const mcp_gpt = Object.values(mcp).reduce((acc, s) => Math.max(acc, s.tokens_gpt), 0);
|
|
682
716
|
const checks: Record<string, number> = {
|
|
683
717
|
'rules.gpt': rf.tokens_gpt ?? 0,
|
|
684
718
|
// The always-SCANNED skill descriptions (258 source skills) — the
|
|
@@ -30,6 +30,8 @@ import * as fs from 'node:fs';
|
|
|
30
30
|
import * as path from 'node:path';
|
|
31
31
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
32
32
|
|
|
33
|
+
import { DeadScopeError, assertScanned } from './_lib/scan_scope.js';
|
|
34
|
+
|
|
33
35
|
const _HERE = fileURLToPath(import.meta.url);
|
|
34
36
|
// src/scripts/audit_likelihood.py → parent.parent.parent == repo root.
|
|
35
37
|
export const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
@@ -38,9 +40,20 @@ export const AUDIT_JSON = path.join(REPORT_DIR, 'auto-rules-audit.json');
|
|
|
38
40
|
export const AUDIT_MD = path.join(REPORT_DIR, 'auto-rules-audit.md');
|
|
39
41
|
export const LIKELIHOOD_JSON = path.join(REPORT_DIR, 'auto-rules-likelihood.json');
|
|
40
42
|
|
|
43
|
+
/**
|
|
44
|
+
* The corpus a rule's trigger terms are scored against.
|
|
45
|
+
*
|
|
46
|
+
* The first two globs named the pre-ADR-051 source container, so skills and
|
|
47
|
+
* commands — by far the largest and most trigger-dense part of the corpus —
|
|
48
|
+
* contributed nothing. The audit still printed a score for every rule, derived
|
|
49
|
+
* from contexts + guidelines alone; that biases every score downward and makes
|
|
50
|
+
* the low-likelihood flag unreliable in the direction that matters (false
|
|
51
|
+
* "nothing triggers this"). Commands are now one `command.md` leaf per verb
|
|
52
|
+
* under `src/domains`.
|
|
53
|
+
*/
|
|
41
54
|
const CORPUS_GLOBS: readonly string[] = [
|
|
42
|
-
'
|
|
43
|
-
'
|
|
55
|
+
'src/skills/**/SKILL.md',
|
|
56
|
+
'src/domains/**/command.md',
|
|
44
57
|
'agents/settings/contexts/**/*.md',
|
|
45
58
|
'docs/guidelines/**/*.md',
|
|
46
59
|
];
|
|
@@ -403,6 +416,22 @@ export function main(): number {
|
|
|
403
416
|
}
|
|
404
417
|
const rules: Json[] = JSON.parse(fs.readFileSync(AUDIT_JSON, 'utf-8'))['rules'];
|
|
405
418
|
const corpus = build_corpus();
|
|
419
|
+
// Scope assertion: an empty corpus scores every rule as low-likelihood
|
|
420
|
+
// while looking exactly like a completed audit.
|
|
421
|
+
try {
|
|
422
|
+
assertScanned({
|
|
423
|
+
gate: 'audit_likelihood',
|
|
424
|
+
scanned: corpus.size,
|
|
425
|
+
units: 'distinct corpus token(s)',
|
|
426
|
+
roots: CORPUS_GLOBS,
|
|
427
|
+
});
|
|
428
|
+
} catch (exc) {
|
|
429
|
+
if (!(exc instanceof DeadScopeError)) {
|
|
430
|
+
throw exc;
|
|
431
|
+
}
|
|
432
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
433
|
+
return 2;
|
|
434
|
+
}
|
|
406
435
|
const scores = rules.map((r) => score(r, corpus));
|
|
407
436
|
fs.writeFileSync(
|
|
408
437
|
LIKELIHOOD_JSON,
|