@event4u/agent-config 12.0.0 → 13.0.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 +273 -0
- package/README.md +1 -1
- package/agents/templates/.ai-council.yml.example +46 -3
- package/dist/agent-src/commands/cost/report.md +23 -0
- package/dist/agent-src/contexts/execution/subagent-routing.md +148 -33
- package/dist/agent-src/contexts/model-recommendations.md +6 -4
- package/dist/agent-src/contexts/subagent-configuration.md +69 -7
- package/dist/agent-src/rules/autonomous-execution.md +1 -1
- package/dist/agent-src/rules/design-fidelity.md +1 -1
- package/dist/agent-src/rules/design-review-after-ui-write.md +10 -5
- package/dist/agent-src/rules/evaluator-independence.md +27 -5
- package/dist/agent-src/rules/git-history-discipline.md +5 -1
- package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -0
- package/dist/agent-src/rules/missing-skill-recovery.md +89 -0
- package/dist/agent-src/rules/ui-audit-gate.md +9 -4
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +33 -0
- package/dist/agent-src/scripts/roadmap_gates.ts +30 -2
- package/dist/agent-src/scripts/update_roadmap_progress.ts +47 -1
- package/dist/agent-src/skills/authz-review/SKILL.md +4 -0
- package/dist/agent-src/skills/design-system-capture/SKILL.md +4 -0
- package/dist/agent-src/skills/design-system-capture/references/snapshot-preference-order.md +77 -0
- package/dist/agent-src/skills/merge-conflicts/SKILL.md +4 -0
- package/dist/agent-src/skills/prompt-optimizer/SKILL.md +17 -1
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +11 -11
- package/dist/agent-src/skills/systematic-debugging/SKILL.md +4 -0
- package/dist/agent-src/skills/threat-modeling/SKILL.md +3 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +41 -3
- package/dist/cli/registry.js +1 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/{chunk-NN4KW4HZ.js → chunk-3B72LSZL.js} +123 -2338
- package/dist/cli-delegate/{chunk-55WZN5VF.js → chunk-EVTY7ISR.js} +2 -2
- package/dist/cli-delegate/chunk-N7FEST6D.js +3895 -0
- package/dist/cli-delegate/{chunk-L565YFXH.js → chunk-XURJ4AQC.js} +566 -298
- package/dist/cli-delegate/cmd_conformance.js +4 -4
- package/dist/cli-delegate/cmd_doctor.js +2 -2
- package/dist/cli-delegate/cmd_export.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +1 -1
- package/dist/cli-delegate/cmd_prune.js +2 -2
- 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/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +44 -23
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +6 -5
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +5 -4
- package/dist/hooks/dispatch.js +10625 -8541
- package/dist/install/install.mjs +754 -484
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +1302 -167
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/model_tier.js +9 -0
- package/dist/scripts/_lib/model_tier.js.map +1 -1
- package/dist/scripts/condense.js +50 -0
- package/dist/scripts/condense.js.map +1 -1
- package/dist/server/schemas/settings.js +2 -2
- package/dist/server/schemas/settings.js.map +1 -1
- package/docs/MIGRATION.md +20 -1
- package/docs/guidelines/design-fidelity-mechanics.md +21 -0
- package/docs/guidelines/design-handover-extraction.md +87 -0
- package/docs/guidelines/prompt-templates.md +24 -1
- package/package.json +1 -1
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +33 -0
- package/src/agent-src/scripts/roadmap_gates.ts +30 -2
- package/src/agent-src/scripts/update_roadmap_progress.ts +47 -1
- package/src/config/evaluator-budgets.json +9 -8
- package/src/config/gate-coverage.yml +63 -11
- package/src/config/gate-violation-baselines.json +12 -0
- package/src/config/hook-token-budget.json +15 -2
- package/src/config/preamble-payload-budget.json +28 -0
- package/src/scripts/_dispatch.bash +10 -0
- package/src/scripts/_lib/bench_ab_complexity.ts +439 -0
- package/src/scripts/_lib/bench_ab_safety_tier.ts +142 -0
- package/src/scripts/_lib/bench_ab_scoring_v2.ts +28 -3
- package/src/scripts/_lib/bench_ab_search_adherence.ts +423 -0
- package/src/scripts/_lib/bench_ab_size_claim.ts +206 -0
- package/src/scripts/_lib/bench_ab_workspace.ts +111 -0
- package/src/scripts/_lib/git_common_dir.ts +173 -0
- package/src/scripts/_lib/model_tier.ts +9 -0
- package/src/scripts/_lib/release_highlights.ts +108 -7
- package/src/scripts/_lib/skill_catalogue.ts +1115 -0
- package/src/scripts/_lib/tier_budget_routing.ts +31 -331
- package/src/scripts/ai_council/clients.ts +197 -18
- package/src/scripts/ai_council/config.ts +16 -1
- package/src/scripts/ai_council/low_impact.ts +14 -1
- package/src/scripts/ai_council/orchestrator.ts +14 -1
- package/src/scripts/ai_council/transport_resolver.ts +32 -2
- package/src/scripts/bench_ab_v2_complexity.ts +252 -0
- package/src/scripts/bench_ab_v2_run.ts +3 -42
- package/src/scripts/bench_ab_v2_safety.ts +296 -0
- package/src/scripts/bench_ab_v2_search.ts +343 -0
- package/src/scripts/bench_ab_v2_stats.ts +191 -0
- package/src/scripts/build_archive_index.ts +383 -0
- package/src/scripts/capture_skill_catalogue.ts +279 -317
- package/src/scripts/check_branch_freshness.ts +7 -1
- package/src/scripts/check_release_published.ts +6 -2
- package/src/scripts/check_release_trunk_sync.ts +18 -2
- package/src/scripts/check_source_size_budget.ts +428 -0
- package/src/scripts/check_suppression_hygiene.ts +5 -1
- package/src/scripts/condense.ts +50 -1
- package/src/scripts/cost/backfill_rates.mjs +298 -0
- package/src/scripts/cost/budget.mjs +12 -32
- package/src/scripts/cost_summary.ts +25 -0
- package/src/scripts/dispatch_r2_reviewer.ts +15 -5
- package/src/scripts/evidence_report.ts +15 -4
- package/src/scripts/external_sources_denylist.json +1 -0
- package/src/scripts/hook_manifest.yaml +75 -15
- package/src/scripts/hooks/concern_registry.ts +4 -0
- package/src/scripts/hooks/orchestration_record_hook.ts +21 -0
- package/src/scripts/hooks/skill_route_hook.ts +226 -0
- package/src/scripts/hooks/tool_result_bytes_hook.ts +219 -0
- package/src/scripts/hooks/ui_route_nudge_hook.ts +6 -3
- package/src/scripts/hot_context_hook.ts +13 -1
- package/src/scripts/install.ts +129 -0
- package/src/scripts/janitor.ts +77 -2
- package/src/scripts/lint_design_antipattern_parity.ts +40 -6
- package/src/scripts/lint_harvest_provenance.ts +35 -1
- package/src/scripts/lint_plan_risk_register.ts +22 -9
- package/src/scripts/lint_roadmap_blockers.ts +109 -6
- package/src/scripts/lint_rule_skill_pack_reach.ts +43 -0
- package/src/scripts/lint_scheduled_deprecations.ts +738 -0
- package/src/scripts/lint_skill_router_head.ts +21 -0
- package/src/scripts/mcp_server/consumer_tool_catalog.json +26 -3
- package/src/scripts/mcp_server/tool_catalog_source.ts +6 -24
- package/src/scripts/mcp_server/tools.ts +87 -0
- package/src/scripts/migration_status.ts +17 -1
- package/src/scripts/probe_review_binding_drift.ts +731 -0
- package/src/scripts/release.ts +100 -6
- package/src/scripts/routing_doctor.ts +50 -13
- package/src/scripts/schemas/command.schema.json +3 -3
- package/src/scripts/schemas/skill.schema.json +20 -2
- package/src/scripts/schemas/subagent.schema.json +2 -2
- package/src/scripts/session_eol_report.ts +81 -0
- package/src/scripts/trigger_coverage.ts +224 -3
- package/src/scripts/workspace_doctor.ts +464 -0
- package/src/scripts/worktree_cleanup_check.ts +2 -1
- package/src/server/schemas/settings.ts +2 -2
- package/dist/cli-delegate/chunk-AYKQGIBG.js +0 -1594
- package/src/config/budget-routing.json +0 -7
package/src/scripts/release.ts
CHANGED
|
@@ -536,7 +536,7 @@ function die(msg: string, code = 2): never {
|
|
|
536
536
|
throw new SystemExitError(code);
|
|
537
537
|
}
|
|
538
538
|
|
|
539
|
-
interface RunResult {
|
|
539
|
+
export interface RunResult {
|
|
540
540
|
returncode: number;
|
|
541
541
|
stdout: string;
|
|
542
542
|
stderr: string;
|
|
@@ -879,14 +879,14 @@ function watch_pr_checks(branch: string): void {
|
|
|
879
879
|
*
|
|
880
880
|
* git's wording for this is stable across versions: the hint is `fetch first`
|
|
881
881
|
* / `Updates were rejected`, and the ref line carries `[rejected]` with
|
|
882
|
-
* `non-fast-forward` or `fetch first`.
|
|
882
|
+
* `non-fast-forward` or `fetch first`. A stale lease is a SECOND wording —
|
|
883
|
+
* `[remote rejected]` + `cannot lock ref … but expected`; pinned in the tests.
|
|
883
884
|
*/
|
|
884
885
|
export function _is_non_fast_forward(stderr: string, stdout: string): boolean {
|
|
885
886
|
const text = `${stderr}\n${stdout}`;
|
|
886
|
-
return
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
);
|
|
887
|
+
if (!/\[(?:remote )?rejected\]/i.test(text)) return false;
|
|
888
|
+
if (/cannot lock ref[^\n]*but expected/i.test(text)) return true;
|
|
889
|
+
return /(non-fast-forward|fetch first|behind its remote counterpart)/i.test(text);
|
|
890
890
|
}
|
|
891
891
|
|
|
892
892
|
function push_release_branch(branch: string): void {
|
|
@@ -1623,6 +1623,93 @@ function set_template_pin(p: string, version: string): void {
|
|
|
1623
1623
|
fs.writeFileSync(p, lines.join('\n'), 'utf-8');
|
|
1624
1624
|
}
|
|
1625
1625
|
|
|
1626
|
+
/**
|
|
1627
|
+
* Refuse a MAJOR cut that carries a scheduled deprecation due at or before it.
|
|
1628
|
+
*
|
|
1629
|
+
* **The trigger is the shape of the TARGET, not a comparison with the current
|
|
1630
|
+
* version.** A major target is `X.0.0` — and that one predicate covers every
|
|
1631
|
+
* path to a major: the `--as major` flag, an explicit `--version 13.0.0`,
|
|
1632
|
+
* auto-detection from a `feat!:` commit, AND `--resume`, where
|
|
1633
|
+
* `_detect_in_flight_target()` returns the already-bumped `package.json`
|
|
1634
|
+
* version so `target === current` and any current-vs-target comparison
|
|
1635
|
+
* silently returns. Resume was the fourth path an earlier version of this
|
|
1636
|
+
* guard missed while its own comment claimed three paths converged.
|
|
1637
|
+
*
|
|
1638
|
+
* Refusing a resumed release can strand a partially-completed one, and that is
|
|
1639
|
+
* the deliberate trade: a stranded release is recoverable by fixing the table
|
|
1640
|
+
* and resuming again, whereas a major shipped over a missed commitment is the
|
|
1641
|
+
* failure this whole surface exists to prevent.
|
|
1642
|
+
*
|
|
1643
|
+
* The target version is PASSED to the gate. Without it the gate falls back to
|
|
1644
|
+
* `package.json`, which at the cut to N still reads N-1 — so a row committed
|
|
1645
|
+
* to N reads as one major early and passes, and only rows already a major late
|
|
1646
|
+
* could ever be refused. That is the lateness being prevented, so measuring
|
|
1647
|
+
* against the shipped version would have made the refusal fire exactly one
|
|
1648
|
+
* major too late, forever.
|
|
1649
|
+
*
|
|
1650
|
+
* Runs under `--dry-run` too, unlike the rest of preflight: this check is one
|
|
1651
|
+
* subprocess reading two files, so the "keep a preview fast" rationale that
|
|
1652
|
+
* excludes the ~15s test-trend collection does not apply — and a preview that
|
|
1653
|
+
* reports green for the single condition that will refuse the real run is the
|
|
1654
|
+
* case an operator runs a preview to discover. It REPORTS there rather than
|
|
1655
|
+
* dying: `--dry-run` exiting 0 before `execute()` and before `preflight()` is a
|
|
1656
|
+
* contract this file's own tests assert, and an earlier revision of this guard
|
|
1657
|
+
* broke it by sitting above the dry-run branch with no preview mode.
|
|
1658
|
+
*
|
|
1659
|
+
* @param runner Seam for the gate invocation. Production passes nothing and
|
|
1660
|
+
* gets the real `run`; tests inject a stub, because the alternative — reaching
|
|
1661
|
+
* this branch only by mutating `docs/MIGRATION.md` — would make the refusal
|
|
1662
|
+
* path testable exclusively through a tracked-file edit.
|
|
1663
|
+
*/
|
|
1664
|
+
export function assert_scheduled_deprecations_clear(
|
|
1665
|
+
target: string,
|
|
1666
|
+
runner: (args: readonly string[]) => RunResult = (args) => run(args, { check: false, capture: true }),
|
|
1667
|
+
opts: { previewOnly?: boolean } = {},
|
|
1668
|
+
): void {
|
|
1669
|
+
const [, minor, patch] = parse_version(target);
|
|
1670
|
+
if (minor !== 0 || patch !== 0) {
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
const res = runner([
|
|
1674
|
+
'./scripts-run',
|
|
1675
|
+
'src/scripts/lint_scheduled_deprecations',
|
|
1676
|
+
'--cutting',
|
|
1677
|
+
target,
|
|
1678
|
+
]);
|
|
1679
|
+
if (res.returncode === 0) {
|
|
1680
|
+
return;
|
|
1681
|
+
}
|
|
1682
|
+
process.stderr.write(res.stdout);
|
|
1683
|
+
process.stderr.write(res.stderr);
|
|
1684
|
+
if (res.returncode !== 1) {
|
|
1685
|
+
// The gate reserves 1 for a finding and 2 for a usage/environment
|
|
1686
|
+
// failure. Diagnosing the latter as "the table has an overdue row"
|
|
1687
|
+
// sends the releaser to edit a file that is not the problem.
|
|
1688
|
+
die(
|
|
1689
|
+
`refusing the ${target} cut: the scheduled-deprecations check could not run ` +
|
|
1690
|
+
`(exit ${String(res.returncode)}). That is an environment or usage failure, not a ` +
|
|
1691
|
+
'finding in docs/MIGRATION.md — fix the invocation or the checkout, then re-run.',
|
|
1692
|
+
);
|
|
1693
|
+
}
|
|
1694
|
+
if (opts.previewOnly === true) {
|
|
1695
|
+
// The preview's job is to SHOW what the real run will refuse. Dying
|
|
1696
|
+
// here would break the `--dry-run exits 0` contract asserted elsewhere
|
|
1697
|
+
// in this file's tests — trading a documented exit code for a message
|
|
1698
|
+
// that has already been printed above.
|
|
1699
|
+
process.stderr.write(
|
|
1700
|
+
`\n(dry-run) the ${target} cut WOULD BE REFUSED for the reason above. ` +
|
|
1701
|
+
'Previewing only; exit code unchanged.\n',
|
|
1702
|
+
);
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1705
|
+
die(
|
|
1706
|
+
`refusing the ${target} cut: the scheduled-deprecations table in docs/MIGRATION.md ` +
|
|
1707
|
+
'has a row due at or before this major, or one that cannot be resolved. Act on ' +
|
|
1708
|
+
"it — perform the removal in its own change, or revise the row's commitment and " +
|
|
1709
|
+
'record why the surface stays — then re-run.',
|
|
1710
|
+
);
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1626
1713
|
// ─── preflight ────────────────────────────────────────────────────────────────
|
|
1627
1714
|
|
|
1628
1715
|
/**
|
|
@@ -2578,6 +2665,13 @@ function main(argv: readonly string[] | null = null): number {
|
|
|
2578
2665
|
}
|
|
2579
2666
|
parse_version(target);
|
|
2580
2667
|
|
|
2668
|
+
// Runs in dry-run too — a preview that hides the one condition which will
|
|
2669
|
+
// refuse the real run is the case previews are for — but REPORTS there
|
|
2670
|
+
// instead of dying: `--dry-run returns 0` is a contract this file's tests
|
|
2671
|
+
// assert, and breaking it to surface a warning would trade a documented
|
|
2672
|
+
// exit code for a message.
|
|
2673
|
+
assert_scheduled_deprecations_clear(target, undefined, { previewOnly: args.dry_run });
|
|
2674
|
+
|
|
2581
2675
|
if (!args.dry_run) {
|
|
2582
2676
|
preflight(target, { resume: args.resume, ci: args.ci });
|
|
2583
2677
|
}
|
|
@@ -63,7 +63,7 @@ import {
|
|
|
63
63
|
classifyTask,
|
|
64
64
|
type ActivationInputs,
|
|
65
65
|
} from "./_lib/auto_dispatch.js";
|
|
66
|
-
import { TIER_ORDER, readCooldowns } from "./_lib/tier_budget_routing.js";
|
|
66
|
+
import { COOLDOWN_FILE, TIER_ORDER, readCooldowns } from "./_lib/tier_budget_routing.js";
|
|
67
67
|
import {
|
|
68
68
|
instructionsLoadedRecord,
|
|
69
69
|
measureStandingDelivery,
|
|
@@ -111,6 +111,13 @@ export interface OrchestrationReport {
|
|
|
111
111
|
downshift: boolean;
|
|
112
112
|
/** cost.budgets.per_tier ceilings + live cool-down state per tier. */
|
|
113
113
|
tier_budgets: Record<string, { ceiling_usd: number | null; cooldown_until_ms: number }>;
|
|
114
|
+
/**
|
|
115
|
+
* Whether the cool-down store exists on disk. `readCooldowns` returns all
|
|
116
|
+
* zeros both when no tier is cooling AND when nothing has ever written the
|
|
117
|
+
* file, so a bare zero cannot distinguish "measured: live" from "no reading
|
|
118
|
+
* exists". This flag is what separates them.
|
|
119
|
+
*/
|
|
120
|
+
cooldown_store_present: boolean;
|
|
114
121
|
host_manifest: HostCapabilityManifest;
|
|
115
122
|
/**
|
|
116
123
|
* The host identifier the capability registry was keyed on, and whether it
|
|
@@ -158,14 +165,23 @@ export interface OrchestrationReport {
|
|
|
158
165
|
}
|
|
159
166
|
|
|
160
167
|
/**
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
168
|
+
* Tier-decision coverage in the orchestration record.
|
|
169
|
+
*
|
|
170
|
+
* REPOINTED 2026-08-16. This used to check "delivery evidence" for budget-aware
|
|
171
|
+
* tier routing, on the reasoning that policy can be present while delivery is
|
|
172
|
+
* silently absent — the session-canary blind-spot class. That layer is now
|
|
173
|
+
* ARCHIVED (`docs/contracts/budget-routing.md`), so the old rationale no longer
|
|
174
|
+
* holds and the old warning would have been permanently true and un-actionable:
|
|
175
|
+
* nothing dispatches through a tier decision by design, so telling the operator
|
|
176
|
+
* "the relation may not be running" is a dead advisory of exactly the shape this
|
|
177
|
+
* repository keeps removing elsewhere.
|
|
178
|
+
*
|
|
179
|
+
* What it measures now is narrower and still worth having: how many recorded
|
|
180
|
+
* dispatches carry a `tier` at all. That is the SAME field the archived layer's
|
|
181
|
+
* revisit-if depends on — a saving can only be computed once both the chosen and
|
|
182
|
+
* the realized tier are recorded — so this is the instrument that would show the
|
|
183
|
+
* reopen condition arriving. It reports coverage rather than warning about it.
|
|
184
|
+
* Read-only over agents/runtime/state/audit/*.jsonl; no new state.
|
|
169
185
|
*/
|
|
170
186
|
export function check_budget_delivery(
|
|
171
187
|
workspace_root: string,
|
|
@@ -202,9 +218,10 @@ export function check_budget_delivery(
|
|
|
202
218
|
}
|
|
203
219
|
if (out.eligible_dispatches > 0 && out.budget_evidence_lines === 0) {
|
|
204
220
|
out.warning =
|
|
205
|
-
`
|
|
206
|
-
|
|
207
|
-
`the
|
|
221
|
+
`tier-decision coverage is 0 of ${out.eligible_dispatches} recorded dispatch(es) — ` +
|
|
222
|
+
`expected while budget routing stays archived (docs/contracts/budget-routing.md). ` +
|
|
223
|
+
`This is the counter its revisit-if reads: a non-zero value here is the signal ` +
|
|
224
|
+
`that per-tier dispatch has reappeared and the decision can be reopened`;
|
|
208
225
|
}
|
|
209
226
|
return out;
|
|
210
227
|
}
|
|
@@ -356,6 +373,7 @@ export function collect_orchestration(
|
|
|
356
373
|
const ledger_present = fs.existsSync(path.join(tracking_dir, "sessions.jsonl"));
|
|
357
374
|
const per_tier = (cost_budgets["per_tier"] ?? {}) as Record<string, unknown>;
|
|
358
375
|
const cooldowns = readCooldowns(tracking_dir);
|
|
376
|
+
const cooldown_store_present = fs.existsSync(path.join(tracking_dir, COOLDOWN_FILE));
|
|
359
377
|
const tier_budgets: OrchestrationReport["tier_budgets"] = {};
|
|
360
378
|
for (const t of TIER_ORDER) {
|
|
361
379
|
const v = per_tier[t];
|
|
@@ -382,6 +400,7 @@ export function collect_orchestration(
|
|
|
382
400
|
halted,
|
|
383
401
|
downshift,
|
|
384
402
|
tier_budgets,
|
|
403
|
+
cooldown_store_present,
|
|
385
404
|
host_manifest,
|
|
386
405
|
host_platform: platform,
|
|
387
406
|
host_platform_assumed: platform_assumed,
|
|
@@ -636,10 +655,28 @@ function _render(report: DoctorReport): string {
|
|
|
636
655
|
);
|
|
637
656
|
const tierBits = Object.entries(o.tier_budgets).map(([t, s]) => {
|
|
638
657
|
const cap = s.ceiling_usd === null ? "no cap" : `$${s.ceiling_usd}`;
|
|
639
|
-
|
|
658
|
+
// `> Date.now()`, not `> 0`. The purger went with the archived permit
|
|
659
|
+
// lifecycle (`tripCooldown` rewrote the map on every write), so a stale
|
|
660
|
+
// entry now has nothing to clear it and `> 0` would render COOLING for
|
|
661
|
+
// ever. Latent rather than live — there is no writer either — but the
|
|
662
|
+
// comparison should be the one that stays correct if a writer returns.
|
|
663
|
+
const cool = s.cooldown_until_ms > Date.now() ? " COOLING" : "";
|
|
640
664
|
return `${t}=${cap}${cool}`;
|
|
641
665
|
});
|
|
642
666
|
lines.push(` tier budgets (rolling-24h): ${tierBits.join(" · ")}`);
|
|
667
|
+
// `readCooldowns` returns all zeros both when no tier is cooling and when
|
|
668
|
+
// nothing has ever written the store, so a missing COOLING marker alone
|
|
669
|
+
// cannot be read as a measured "this tier is live". Report which of the two
|
|
670
|
+
// it is — the same unavailable-vs-false distinction the capability-
|
|
671
|
+
// provenance line above makes two lines earlier. Derived from the store's
|
|
672
|
+
// existence rather than from a caller count, so it stays true if the
|
|
673
|
+
// cool-down producer is ever wired.
|
|
674
|
+
if (!o.cooldown_store_present) {
|
|
675
|
+
lines.push(
|
|
676
|
+
` cool-down state: no reading on disk (${COOLDOWN_FILE} absent) — ` +
|
|
677
|
+
`absence of COOLING above is not evidence of a live tier`,
|
|
678
|
+
);
|
|
679
|
+
}
|
|
643
680
|
if (o.delivery.warning) {
|
|
644
681
|
lines.push(` ⚠️ ${o.delivery.warning}`);
|
|
645
682
|
} else if (o.delivery.eligible_dispatches > 0) {
|
|
@@ -158,8 +158,8 @@
|
|
|
158
158
|
},
|
|
159
159
|
"model_tier": {
|
|
160
160
|
"type": "string",
|
|
161
|
-
"enum": ["lite", "medium", "high", "inherit"],
|
|
162
|
-
"description": "Vendor-neutral capability band the command needs (ADR-035). Commands project as Claude skills, so high/medium/lite produce a native Claude `model:`
|
|
161
|
+
"enum": ["lite", "medium", "high", "frontier", "inherit"],
|
|
162
|
+
"description": "Vendor-neutral capability band the command needs (ADR-035, amended by ADR-232). Commands project as Claude skills, so frontier/high/medium/lite produce a native Claude `model:` when model.auto_switch=auto; the generator owns that mapping. inherit keeps the session model. `frontier` is the band above the strongest generally-recommended one - opt-in and expected to stay rare. Non-Claude agents are suggestion-only. No vendor model names here."
|
|
163
163
|
},
|
|
164
164
|
"context": {
|
|
165
165
|
"type": "string",
|
|
@@ -254,7 +254,7 @@
|
|
|
254
254
|
},
|
|
255
255
|
"requires_confirmation": {
|
|
256
256
|
"type": "boolean",
|
|
257
|
-
"description": "Optional, additive: the command's action STAGES rather than executes, so an irreversible step waits for an explicit human confirmation naming the exact object (non-destructive-by-default § Never act while asking). Twin of the same key in skill.schema.json's execution block; commands carry no execution block, so it sits top-level. A declaration only — it names the action class, never the object, which exists at stage time and not in frontmatter. Mechanism: stageAction/confirmOnce in work_engine/hooks/builtin/staged_confirmation.ts; enumerate with `agent-config gates --pending`. Nothing BINDS on it yet (blocker confirmation-degraded-host-semantics:
|
|
257
|
+
"description": "Optional, additive: the command's action STAGES rather than executes, so an irreversible step waits for an explicit human confirmation naming the exact object (non-destructive-by-default § Never act while asking). Twin of the same key in skill.schema.json's execution block; commands carry no execution block, so it sits top-level. A declaration only — it names the action class, never the object, which exists at stage time and not in frontmatter. Mechanism: stageAction/confirmOnce in work_engine/hooks/builtin/staged_confirmation.ts; enumerate with `agent-config gates --pending`. Nothing BINDS on it yet (blocker confirmation-degraded-host-semantics: only claude both binds pre_tool_use and honours a deny — docs/contracts/hook-architecture-v1.md, section Which hosts carry pre_tool_use)."
|
|
258
258
|
},
|
|
259
259
|
"auto_detect": {
|
|
260
260
|
"type": "boolean",
|
|
@@ -168,9 +168,10 @@
|
|
|
168
168
|
"lite",
|
|
169
169
|
"medium",
|
|
170
170
|
"high",
|
|
171
|
+
"frontier",
|
|
171
172
|
"inherit"
|
|
172
173
|
],
|
|
173
|
-
"description": "Vendor-neutral capability band the skill needs (ADR-035). high/medium/lite project to a native Claude `model:`
|
|
174
|
+
"description": "Vendor-neutral capability band the skill needs (ADR-035, amended by ADR-232). frontier/high/medium/lite project to a native Claude `model:` when model.auto_switch=auto; the generator owns that mapping exclusively. inherit keeps the session model. `frontier` means reasoning capability above the strongest generally-recommended band - opt-in and expected to stay rare, so work that genuinely needs it says so instead of inheriting it silently. Non-Claude agents are suggestion-only - the model-recommendation rule names the tier and the agent resolves its own model. No vendor model names here."
|
|
174
175
|
},
|
|
175
176
|
"context": {
|
|
176
177
|
"type": "string",
|
|
@@ -275,7 +276,7 @@
|
|
|
275
276
|
},
|
|
276
277
|
"requires_confirmation": {
|
|
277
278
|
"type": "boolean",
|
|
278
|
-
"description": "Optional, additive: the skill's action STAGES rather than executes, so an irreversible step waits for an explicit human confirmation naming the exact object (non-destructive-by-default § Never act while asking). A declaration only — it names the action class, never the object, which exists at stage time and not in frontmatter. The mechanism is stageAction/confirmOnce in work_engine/hooks/builtin/staged_confirmation.ts; enumerate what is pending with `agent-config gates --pending`. Nothing BINDS on this flag yet:
|
|
279
|
+
"description": "Optional, additive: the skill's action STAGES rather than executes, so an irreversible step waits for an explicit human confirmation naming the exact object (non-destructive-by-default § Never act while asking). A declaration only — it names the action class, never the object, which exists at stage time and not in frontmatter. The mechanism is stageAction/confirmOnce in work_engine/hooks/builtin/staged_confirmation.ts; enumerate what is pending with `agent-config gates --pending`. Nothing BINDS on this flag yet: only claude both binds pre_tool_use and honours a deny (docs/contracts/hook-architecture-v1.md, section Which hosts carry pre_tool_use — 2 further hosts bind it and ignore the verdict, 3 alias it unbound, 2 have no pre-tool surface), so a guard would degrade to prose on 7 of 8 hosts while the flag reads like a control — that decision is deferred behind blocker confirmation-degraded-host-semantics."
|
|
279
280
|
},
|
|
280
281
|
"command": {
|
|
281
282
|
"type": "array",
|
|
@@ -286,6 +287,23 @@
|
|
|
286
287
|
}
|
|
287
288
|
}
|
|
288
289
|
},
|
|
290
|
+
"triggers": {
|
|
291
|
+
"type": "array",
|
|
292
|
+
"minItems": 1,
|
|
293
|
+
"items": {
|
|
294
|
+
"type": "object",
|
|
295
|
+
"additionalProperties": false,
|
|
296
|
+
"properties": {
|
|
297
|
+
"keyword": {"type": "string", "minLength": 1},
|
|
298
|
+
"phrase": {"type": "string", "minLength": 1},
|
|
299
|
+
"file_pattern": {"type": "string", "minLength": 1},
|
|
300
|
+
"path_prefix": {"type": "string", "minLength": 1},
|
|
301
|
+
"command": {"type": "string", "minLength": 1},
|
|
302
|
+
"reason": {"type": "string"}
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
"description": "Activation triggers for the SKILL catalogue, shaped after the rule-side field so the two stay legible together — same five match keys (keyword, phrase, file_pattern, path_prefix, command), same additionalProperties:false, and deliberately no `intent` key, which was removed rule-side for giving authors false confidence that an activation path existed. OPTIONAL and empty of adopters on the day it ships: the capability becomes declarable before anything declares it, so a seeded tranche is measurable against the router corpus before it is written. Read by scripts/trigger_coverage.ts --scope skill through the same router_match.ts matcher the rule scope uses — not a fork. Schema: docs/contracts/rule-router.md."
|
|
306
|
+
},
|
|
289
307
|
"workspaces": {
|
|
290
308
|
"type": "array",
|
|
291
309
|
"minItems": 1,
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
},
|
|
35
35
|
"model_tier": {
|
|
36
36
|
"type": "string",
|
|
37
|
-
"enum": ["lite", "medium", "high", "inherit"],
|
|
38
|
-
"description": "Vendor-neutral capability band (ADR-034/035). high/medium/lite project to a native Claude `model:`
|
|
37
|
+
"enum": ["lite", "medium", "high", "frontier", "inherit"],
|
|
38
|
+
"description": "Vendor-neutral capability band (ADR-034/035, amended by ADR-232). frontier/high/medium/lite project to a native Claude `model:` via model_tier.ts; `inherit` is a documented passthrough sentinel (→ CC `model: inherit`), not a new mapping. `frontier` is the band above the strongest generally-recommended one - opt-in, expected to stay rare, and never the resolution of `inherit`."
|
|
39
39
|
},
|
|
40
40
|
"effort": {
|
|
41
41
|
"type": "string",
|
|
@@ -61,6 +61,33 @@ export interface EolReport {
|
|
|
61
61
|
compact_summary_records: number;
|
|
62
62
|
marker_drift: boolean;
|
|
63
63
|
};
|
|
64
|
+
/**
|
|
65
|
+
* State-destroyed vs state-captured, as ONE reading
|
|
66
|
+
* (road-to-inbox-harvest-2026-08-d-context-ledger Step 2.2).
|
|
67
|
+
*
|
|
68
|
+
* Before this, a compaction count and an advisory count sat in two places
|
|
69
|
+
* and nobody correlated them, so "how often was state destroyed with no
|
|
70
|
+
* capture in front of it" had no answer at all.
|
|
71
|
+
*
|
|
72
|
+
* HONEST LIMIT, stated in the shape rather than in prose only: the two
|
|
73
|
+
* sides come from different corpora — compactions are scanned out of
|
|
74
|
+
* transcripts, the advisory stamps live in per-session state files under a
|
|
75
|
+
* hashed key — and no field joins a transcript to its state file. So this
|
|
76
|
+
* is an AGGREGATE comparison over the same store, not a per-session join,
|
|
77
|
+
* and `join_basis` says so on every emitted report. A reader may compare
|
|
78
|
+
* the totals; a reader may NOT conclude that a specific compaction lacked
|
|
79
|
+
* a capture.
|
|
80
|
+
*/
|
|
81
|
+
capture: {
|
|
82
|
+
/** Session-eol state files found. `null` ⇒ the directory is absent. */
|
|
83
|
+
state_files: number | null;
|
|
84
|
+
/** State files carrying an `advisory_fired_at` stamp. */
|
|
85
|
+
advisory_fired: number;
|
|
86
|
+
/** State files that additionally recorded "advised, but no envelope". */
|
|
87
|
+
advised_without_envelope: number;
|
|
88
|
+
/** Always `"aggregate"` today. See the doc comment. */
|
|
89
|
+
join_basis: 'aggregate';
|
|
90
|
+
};
|
|
64
91
|
/** Pearson r of file bytes vs final context tokens over the readable set. */
|
|
65
92
|
bytes_tokens_pearson_r: number | null;
|
|
66
93
|
threshold_crossings: Record<string, number>;
|
|
@@ -130,6 +157,47 @@ export function scanSessionFile(file: string): SessionSummary | null {
|
|
|
130
157
|
return { file, bytes, counters, last_ts };
|
|
131
158
|
}
|
|
132
159
|
|
|
160
|
+
/** Where `session_eol_hook` persists its per-session state (gitignored). */
|
|
161
|
+
export const SESSION_EOL_STATE_REL = path.join('agents', 'runtime', 'state', 'session-eol');
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The capture side of the Step-2.2 reading.
|
|
165
|
+
*
|
|
166
|
+
* `state_files: null` distinguishes "the directory is not there" from "it is
|
|
167
|
+
* there and empty" — an instrument that reports 0 for both cannot tell a quiet
|
|
168
|
+
* estate from one it cannot see, which is the failure the sibling ledger fixed
|
|
169
|
+
* for its own open-record set.
|
|
170
|
+
*/
|
|
171
|
+
export function scanCaptureState(root: string): EolReport['capture'] {
|
|
172
|
+
const dir = path.join(root, SESSION_EOL_STATE_REL);
|
|
173
|
+
let names: string[];
|
|
174
|
+
try {
|
|
175
|
+
names = fs.readdirSync(dir).filter((n) => n.endsWith('.json'));
|
|
176
|
+
} catch {
|
|
177
|
+
return { state_files: null, advisory_fired: 0, advised_without_envelope: 0, join_basis: 'aggregate' };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
let advisoryFired = 0;
|
|
181
|
+
let advisedWithoutEnvelope = 0;
|
|
182
|
+
for (const name of names) {
|
|
183
|
+
try {
|
|
184
|
+
const parsed: unknown = JSON.parse(fs.readFileSync(path.join(dir, name), 'utf-8'));
|
|
185
|
+
if (typeof parsed !== 'object' || parsed === null) continue;
|
|
186
|
+
const rec = parsed as Record<string, unknown>;
|
|
187
|
+
if (typeof rec['advisory_fired_at'] === 'string') advisoryFired++;
|
|
188
|
+
if (typeof rec['missing_envelope_warned_at'] === 'string') advisedWithoutEnvelope++;
|
|
189
|
+
} catch {
|
|
190
|
+
// A torn or hand-edited state file is data we do not have.
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
state_files: names.length,
|
|
195
|
+
advisory_fired: advisoryFired,
|
|
196
|
+
advised_without_envelope: advisedWithoutEnvelope,
|
|
197
|
+
join_basis: 'aggregate',
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
133
201
|
export function buildReport(root: string, now: Date = new Date()): EolReport {
|
|
134
202
|
const files = listTranscriptFiles({ root });
|
|
135
203
|
const sessions: SessionSummary[] = [];
|
|
@@ -176,6 +244,7 @@ export function buildReport(root: string, now: Date = new Date()): EolReport {
|
|
|
176
244
|
compact_summary_records: summaryRecords,
|
|
177
245
|
marker_drift: summaryRecords !== allEvents.length,
|
|
178
246
|
},
|
|
247
|
+
capture: scanCaptureState(root),
|
|
179
248
|
bytes_tokens_pearson_r: pearson(
|
|
180
249
|
withUsage
|
|
181
250
|
.filter((s) => (s.counters.final_context_tokens as number) > 1_000)
|
|
@@ -216,6 +285,18 @@ export function renderText(r: EolReport): string {
|
|
|
216
285
|
` trigger pre-tokens: min=${fmt(c.pre_tokens.min)} median=${fmt(c.pre_tokens.median)} max=${fmt(c.pre_tokens.max)}`,
|
|
217
286
|
);
|
|
218
287
|
}
|
|
288
|
+
// Step 2.2: the capture side, printed directly under the destruction side
|
|
289
|
+
// so the two are read together. `join_basis` is printed every time — a
|
|
290
|
+
// reader must not mistake an aggregate comparison for a per-session join.
|
|
291
|
+
const cap = r.capture;
|
|
292
|
+
if (cap.state_files === null) {
|
|
293
|
+
lines.push(` capture: no session-eol state directory — capture side UNOBSERVED (not zero)`);
|
|
294
|
+
} else {
|
|
295
|
+
lines.push(
|
|
296
|
+
` capture: ${cap.advisory_fired} of ${cap.state_files} state file(s) advised, ` +
|
|
297
|
+
`${cap.advised_without_envelope} advised with no envelope [join: ${cap.join_basis}]`,
|
|
298
|
+
);
|
|
299
|
+
}
|
|
219
300
|
if (c.post_tokens) {
|
|
220
301
|
lines.push(` post-tokens median=${fmt(c.post_tokens.median)}`);
|
|
221
302
|
}
|