@event4u/agent-config 12.1.0 → 14.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 +164 -186
- package/README.md +1 -1
- package/dist/agent-src/commands/cost/report.md +23 -0
- package/dist/agent-src/commands/evals/roadmap.json +8 -0
- package/dist/agent-src/commands/fix/ci.md +39 -1
- package/dist/agent-src/commands/pr/create.md +65 -4
- package/dist/agent-src/commands/roadmap/next.md +28 -5
- package/dist/agent-src/commands/work.md +16 -0
- package/dist/agent-src/contexts/contracts/command-suggestion-flow.md +47 -0
- package/dist/agent-src/contexts/execution/contract-decision-sheet.md +94 -0
- package/dist/agent-src/contexts/execution/plan-confidence-gate.md +30 -0
- package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +21 -8
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +55 -8
- package/dist/agent-src/contexts/execution/subagent-response-contract.md +42 -0
- package/dist/agent-src/contexts/execution/subagent-routing.md +148 -33
- package/dist/agent-src/contexts/execution/subagent-spawn-contract.md +29 -0
- 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/command-suggestion-policy.md +33 -0
- package/dist/agent-src/rules/design-fidelity.md +1 -1
- package/dist/agent-src/rules/design-review-after-ui-write.md +36 -14
- 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/missing-skill-recovery.md +89 -0
- package/dist/agent-src/rules/ui-audit-gate.md +34 -12
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +33 -0
- package/dist/agent-src/scripts/gate_execute.ts +395 -0
- package/dist/agent-src/scripts/resume_probe.ts +422 -0
- package/dist/agent-src/scripts/roadmap_gates.ts +138 -4
- package/dist/agent-src/scripts/update_roadmap_progress.ts +70 -4
- package/dist/agent-src/skills/ai-council/SKILL.md +61 -0
- 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/experiment-loop/SKILL.md +116 -0
- package/dist/agent-src/skills/experiment-loop/evals/output-schema.yml +27 -0
- package/dist/agent-src/skills/experiment-loop/evals/triggers.json +17 -0
- package/dist/agent-src/skills/experiment-loop/references/pivot-ladder.md +54 -0
- package/dist/agent-src/skills/experiment-loop/references/protocol.md +61 -0
- package/dist/agent-src/skills/experiment-loop/references/register-format.md +66 -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-management/SKILL.md +2 -2
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +5 -5
- package/dist/agent-src/skills/subagent-orchestration/schemas/team-review-status.json +8 -3
- 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 +43 -3
- package/dist/cli/registry.js +1 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/{chunk-K34QTSD5.js → chunk-7PZ4FSZB.js} +368 -191
- package/dist/cli-delegate/{chunk-55WZN5VF.js → chunk-EVTY7ISR.js} +2 -2
- package/dist/cli-delegate/{chunk-O52B4F5S.js → chunk-F3YUF7SA.js} +1 -1
- package/dist/cli-delegate/{chunk-LKZ5DOYW.js → chunk-XURJ4AQC.js} +11 -3
- package/dist/cli-delegate/cmd_conformance.js +224 -104
- 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 +73 -30
- 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 +7 -5
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +6 -4
- package/dist/hooks/dispatch.js +2653 -1365
- package/dist/install/install.mjs +12 -4
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +2679 -887
- package/dist/router.json +1 -1
- package/dist/scripts/condense.js +1 -1
- 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/dist/ui/assets/{index-B0aMJnuz.js → index-C1pBRm1f.js} +2 -2
- package/dist/ui/assets/index-C1pBRm1f.js.map +1 -0
- package/dist/ui/index.html +1 -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/gate_execute.ts +395 -0
- package/src/agent-src/scripts/resume_probe.ts +422 -0
- package/src/agent-src/scripts/roadmap_gates.ts +138 -4
- package/src/agent-src/scripts/update_roadmap_progress.ts +70 -4
- package/src/config/ci-local-parity.yml +13 -0
- package/src/config/evaluator-budgets.json +9 -8
- package/src/config/gate-coverage.yml +140 -12
- package/src/config/gate-violation-baselines.json +9 -3
- package/src/config/hook-token-budget.json +8 -1
- package/src/config/rule-activation-census.json +41 -0
- package/src/config/rule-stub-ceilings.json +52 -0
- package/src/scripts/_dispatch.bash +4 -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/evaluator_contract.ts +159 -0
- package/src/scripts/_lib/gh_transient.ts +80 -0
- package/src/scripts/_lib/release_material.ts +16 -5
- package/src/scripts/_lib/review_independence.ts +111 -0
- package/src/scripts/_lib/session_register.ts +14 -0
- package/src/scripts/_lib/skill_catalogue.ts +278 -5
- package/src/scripts/_lib/skill_catalogue_series.ts +411 -0
- package/src/scripts/_lib/tier_budget_routing.ts +31 -331
- package/src/scripts/_lib/turn_end_refusals.ts +482 -0
- package/src/scripts/ai_council/_py_json.ts +96 -0
- package/src/scripts/ai_council/cli_call_budget.ts +357 -0
- package/src/scripts/ai_council/clients.ts +101 -164
- package/src/scripts/ai_council/config.ts +25 -11
- package/src/scripts/ai_council/probe_store.ts +196 -0
- package/src/scripts/ai_council/qualification.ts +425 -0
- package/src/scripts/ai_council/qualification_wiring.ts +243 -0
- package/src/scripts/ai_council/quorum_wiring.ts +211 -0
- package/src/scripts/ai_team/team_dispatch.ts +40 -6
- 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 +369 -4
- package/src/scripts/check_evaluator_schema.ts +249 -0
- package/src/scripts/check_pr_ci_current.ts +409 -0
- package/src/scripts/check_review_schema.ts +195 -0
- package/src/scripts/check_rule_activation_census.ts +451 -0
- package/src/scripts/check_rule_stub_ceiling.ts +1128 -0
- package/src/scripts/check_source_size_budget.ts +428 -0
- package/src/scripts/check_suppression_hygiene.ts +5 -1
- package/src/scripts/condense.ts +1 -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/council_cli.ts +141 -191
- package/src/scripts/dispatch_r2_reviewer.ts +8 -1
- package/src/scripts/external_sources_denylist.json +1 -0
- package/src/scripts/hook_manifest.yaml +99 -6
- package/src/scripts/hooks/concern_registry.ts +6 -0
- package/src/scripts/hooks/dispatch_hook.ts +43 -1
- package/src/scripts/hooks/interruption_ledger_hook.ts +256 -0
- package/src/scripts/hooks/ship_diff_volume_hook.ts +180 -0
- package/src/scripts/hooks/skill_route_hook.ts +226 -0
- package/src/scripts/hooks/turn_end_gate_hook.ts +119 -26
- package/src/scripts/hooks/ui_route_nudge_hook.ts +6 -3
- package/src/scripts/hooks_doctor.ts +112 -0
- package/src/scripts/interruption_report.ts +443 -0
- package/src/scripts/lint_design_antipattern_parity.ts +40 -6
- package/src/scripts/lint_evidence_artifacts.ts +359 -0
- package/src/scripts/lint_harvest_provenance.ts +35 -1
- package/src/scripts/lint_roadmap_blockers.ts +65 -0
- package/src/scripts/lint_roadmap_complexity.ts +7 -1
- package/src/scripts/lint_rule_skill_pack_reach.ts +43 -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/release.ts +43 -58
- package/src/scripts/routing_doctor.ts +50 -13
- package/src/scripts/rule_activation_census.ts +292 -0
- package/src/scripts/schemas/command.schema.json +1 -1
- package/src/scripts/schemas/evaluator-output.schema.json +60 -0
- package/src/scripts/schemas/review-findings.schema.json +31 -0
- package/src/scripts/schemas/skill.schema.json +18 -1
- package/src/scripts/self_review_gate.ts +6 -0
- package/src/scripts/session_register_hook.ts +30 -1
- package/src/scripts/sync_pr_branch.ts +271 -0
- package/src/scripts/test_council_qualification.ts +213 -0
- package/src/scripts/trigger_coverage.ts +224 -3
- package/src/server/schemas/settings.ts +2 -2
- package/dist/ui/assets/index-B0aMJnuz.js.map +0 -1
- package/src/config/budget-routing.json +0 -7
|
@@ -66,6 +66,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
66
66
|
|
|
67
67
|
import { parse as parseYaml } from 'yaml';
|
|
68
68
|
|
|
69
|
+
import { GateLedger } from './_lib/gate_ledger.js';
|
|
69
70
|
import { runGateCli, runSelfTest } from './_lib/gate_self_test.js';
|
|
70
71
|
|
|
71
72
|
const _HERE = fileURLToPath(import.meta.url);
|
|
@@ -404,6 +405,48 @@ function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
|
404
405
|
const errors = findings.filter((f) => f.kind === 'unreachable-route');
|
|
405
406
|
const advisories = findings.filter((f) => f.kind === 'unrouted-skill');
|
|
406
407
|
|
|
408
|
+
// Per-target completeness accounting over BOTH populations this gate
|
|
409
|
+
// reasons about, id-prefixed so they stay distinguishable in one tally.
|
|
410
|
+
// The skips are the point: check 1 walks past every unscoped rule and
|
|
411
|
+
// check 2 past every unscoped skill, because "ships everywhere" makes the
|
|
412
|
+
// reachability question inapplicable rather than satisfied. Without the
|
|
413
|
+
// ledger those walk-pasts were indistinguishable from checks that passed —
|
|
414
|
+
// the exact "checked 20 of 300, printed one green line" shape.
|
|
415
|
+
//
|
|
416
|
+
// This tally is deliberately NOT the `scanned:` line below. That line is
|
|
417
|
+
// the machine-read coverage contract and stays the rule count; this is the
|
|
418
|
+
// human-auditable breakdown beside it, per `_lib/gate_ledger` § report.
|
|
419
|
+
const ledger = new GateLedger('lint_rule_skill_pack_reach');
|
|
420
|
+
const routedSkills = [...new Set(rules.flatMap((r) => r.routesToSkills))]
|
|
421
|
+
.filter((sk) => skillPacks.has(sk))
|
|
422
|
+
.sort();
|
|
423
|
+
ledger.plan([
|
|
424
|
+
...rules.map((r) => `rule:${r.name}`),
|
|
425
|
+
...routedSkills.map((sk) => `skill:${sk}`),
|
|
426
|
+
]);
|
|
427
|
+
const failedSubjects = new Set(findings.map((f) => f.subject));
|
|
428
|
+
for (const rule of rules) {
|
|
429
|
+
const id = `rule:${rule.name}`;
|
|
430
|
+
if (rule.packs.length === 0) {
|
|
431
|
+
ledger.skip(id, 'not_applicable_kind');
|
|
432
|
+
} else if ([...failedSubjects].some((sub) => sub.startsWith(`${rule.name} → skill:`))) {
|
|
433
|
+
ledger.fail(id, 'unreachable-route');
|
|
434
|
+
} else {
|
|
435
|
+
ledger.complete(id);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
for (const sk of routedSkills) {
|
|
439
|
+
const id = `skill:${sk}`;
|
|
440
|
+
if ((skillPacks.get(sk) ?? []).length === 0) {
|
|
441
|
+
ledger.skip(id, 'not_applicable_kind');
|
|
442
|
+
} else if (failedSubjects.has(id)) {
|
|
443
|
+
ledger.fail(id, 'unrouted-skill');
|
|
444
|
+
} else {
|
|
445
|
+
ledger.complete(id);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
ledger.report();
|
|
449
|
+
|
|
407
450
|
// --quiet suppresses the per-finding lines but never the count line: this
|
|
408
451
|
// gate is expected to stay non-empty for a while, and a quiet run that
|
|
409
452
|
// printed nothing at all would read as clean.
|
|
@@ -38,6 +38,7 @@ import * as fs from 'node:fs';
|
|
|
38
38
|
import * as path from 'node:path';
|
|
39
39
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
40
40
|
|
|
41
|
+
import { GateLedger } from './_lib/gate_ledger.js';
|
|
41
42
|
import { assertWatchlistResolves, DeadScopeError } from './_lib/scan_scope.js';
|
|
42
43
|
|
|
43
44
|
const _FILE = fileURLToPath(import.meta.url);
|
|
@@ -204,6 +205,26 @@ export function main(argv: string[] = process.argv.slice(2)): number {
|
|
|
204
205
|
const heads = collectSkillHeads(path.join(REPO, SKILLS_REL));
|
|
205
206
|
const { findings, staleAllowlist } = evaluate(heads);
|
|
206
207
|
|
|
208
|
+
// Per-target completeness accounting. The interesting outcome here is the
|
|
209
|
+
// SKIP: a grandfathered head is over the cap and deliberately unchecked, so
|
|
210
|
+
// without a ledger it was indistinguishable from a head that passed. The
|
|
211
|
+
// allowlist size is already printed, but only the ledger ties each skipped
|
|
212
|
+
// name to the reason it went unchecked.
|
|
213
|
+
const ledger = new GateLedger('lint_skill_router_head');
|
|
214
|
+
ledger.plan(heads.map((h) => h.name));
|
|
215
|
+
const failed = new Set(findings.map((f) => f.skill));
|
|
216
|
+
const allow = new Set(GRANDFATHERED);
|
|
217
|
+
for (const h of heads) {
|
|
218
|
+
if (failed.has(h.name)) {
|
|
219
|
+
ledger.fail(h.name, `${String(h.lines)} lines over the ${String(MAX_HEAD_LINES)}-line cap with no mode bodies`);
|
|
220
|
+
} else if (h.lines > MAX_HEAD_LINES && allow.has(h.name)) {
|
|
221
|
+
ledger.skip(h.name, 'declared_exemption');
|
|
222
|
+
} else {
|
|
223
|
+
ledger.complete(h.name);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
ledger.report();
|
|
227
|
+
|
|
207
228
|
for (const s of staleAllowlist) {
|
|
208
229
|
process.stderr.write(
|
|
209
230
|
`⚠️ lint_skill_router_head: '${s}' is grandfathered but no longer over the cap — remove it from GRANDFATHERED so the ratchet keeps its teeth\n`,
|
|
@@ -544,10 +544,33 @@
|
|
|
544
544
|
},
|
|
545
545
|
{
|
|
546
546
|
"name": "suggest_skill_for_task",
|
|
547
|
-
"description": "
|
|
547
|
+
"description": "Match a free-form task description to the most relevant skills, ranked by a deterministic keyword scorer over SKILL.md frontmatter. Use when a skill you need is not in the catalogue the host delivered — a measured host dropped 402 entries from its model-visible list — so asking by name is impossible while asking by task is not. Read-only: no shell, no writes, and no skill bodies are returned, only names, scores and declared personas.",
|
|
548
548
|
"side_effect": "ro",
|
|
549
|
-
"implemented_on": [
|
|
550
|
-
|
|
549
|
+
"implemented_on": [
|
|
550
|
+
"stdio"
|
|
551
|
+
],
|
|
552
|
+
"input_schema": {
|
|
553
|
+
"type": "object",
|
|
554
|
+
"required": [
|
|
555
|
+
"task"
|
|
556
|
+
],
|
|
557
|
+
"properties": {
|
|
558
|
+
"task": {
|
|
559
|
+
"type": "string",
|
|
560
|
+
"description": "Free-form description of the task to match skills against."
|
|
561
|
+
},
|
|
562
|
+
"limit": {
|
|
563
|
+
"type": "integer",
|
|
564
|
+
"minimum": 1,
|
|
565
|
+
"default": 5,
|
|
566
|
+
"description": "Maximum number of skills to return. Defaults to 5."
|
|
567
|
+
}
|
|
568
|
+
},
|
|
569
|
+
"additionalProperties": false
|
|
570
|
+
},
|
|
571
|
+
"annotations": {
|
|
572
|
+
"readOnlyHint": true
|
|
573
|
+
}
|
|
551
574
|
},
|
|
552
575
|
{
|
|
553
576
|
"name": "sync_agent_settings",
|
|
@@ -96,30 +96,12 @@ export const STUB_TOOLS: readonly StubToolSource[] = [
|
|
|
96
96
|
additionalProperties: false,
|
|
97
97
|
},
|
|
98
98
|
},
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
'Returns a ranked list of skills with name, description, and ' +
|
|
106
|
-
'match reason.',
|
|
107
|
-
side_effect: 'ro',
|
|
108
|
-
input_schema: {
|
|
109
|
-
type: 'object',
|
|
110
|
-
properties: {
|
|
111
|
-
task: { type: 'string', description: 'Free-form description of the task to find a skill for.' },
|
|
112
|
-
limit: {
|
|
113
|
-
type: 'integer',
|
|
114
|
-
minimum: 1,
|
|
115
|
-
default: 5,
|
|
116
|
-
description: 'Maximum number of skills to return. Defaults to 5.',
|
|
117
|
-
},
|
|
118
|
-
},
|
|
119
|
-
required: ['task'],
|
|
120
|
-
additionalProperties: false,
|
|
121
|
-
},
|
|
122
|
-
},
|
|
99
|
+
// `suggest_skill_for_task` moved to ALLOWLIST in mcp_server/tools.ts
|
|
100
|
+
// (road-to-inbox-harvest-2026-08-d-runtime-skill-routing 4.1) — it is
|
|
101
|
+
// implemented now, and `build_mcp_catalog` refuses a name registered in
|
|
102
|
+
// both places. Its published input schema (`task` + `limit`) was carried
|
|
103
|
+
// over verbatim rather than re-designed: the stub is a contract consumers
|
|
104
|
+
// may already have read.
|
|
123
105
|
{
|
|
124
106
|
name: 'mine_session',
|
|
125
107
|
description:
|
|
@@ -1210,7 +1210,94 @@ async function _readResourceBodyHandler(
|
|
|
1210
1210
|
// unlisted tool. Boot-time stderr log enumerates the registered set.
|
|
1211
1211
|
// ---------------------------------------------------------------------
|
|
1212
1212
|
|
|
1213
|
+
/**
|
|
1214
|
+
* `suggest_skill_for_task` — the recovery path for a truncated catalogue.
|
|
1215
|
+
*
|
|
1216
|
+
* Promoted from a discovery stub because the defect it answers is measured, not
|
|
1217
|
+
* hypothetical: on the one host that publishes its own truncation, a default
|
|
1218
|
+
* install had 402 entries dropped from the model-visible skills list — the
|
|
1219
|
+
* host's own count against its own denominator, not a subtraction over ours
|
|
1220
|
+
* (`agents/evidence/analysis/scoped-projection-host-delivery.md`). An agent
|
|
1221
|
+
* that cannot see a skill also cannot ask for it by name, so the only reachable
|
|
1222
|
+
* question is "what fits this task" — which is what the deterministic ranker
|
|
1223
|
+
* answers, over the TREE rather than over the host's catalogue.
|
|
1224
|
+
*
|
|
1225
|
+
* Read-only and shell-free by construction: it reads SKILL.md frontmatter and
|
|
1226
|
+
* returns names, scores and declared personas. No skill BODY is returned — the
|
|
1227
|
+
* caller opens what it picked, so a wrong rank costs one read rather than a
|
|
1228
|
+
* context payload.
|
|
1229
|
+
*/
|
|
1230
|
+
async function _suggestSkillForTaskHandler(
|
|
1231
|
+
args: Record<string, unknown>,
|
|
1232
|
+
consumerRoot: string,
|
|
1233
|
+
): Promise<Record<string, unknown>> {
|
|
1234
|
+
const task = typeof args['task'] === 'string' ? args['task'] : '';
|
|
1235
|
+
if (task.trim() === '') {
|
|
1236
|
+
return { status: 'error', error: 'task must be a non-empty string', suggestions: [] };
|
|
1237
|
+
}
|
|
1238
|
+
// `limit`, not `top` — the stub published `limit` and a consumer may have
|
|
1239
|
+
// read that contract already. Renaming it on promotion would break a
|
|
1240
|
+
// caller for cosmetic reasons.
|
|
1241
|
+
const limitRaw = args['limit'];
|
|
1242
|
+
const limit =
|
|
1243
|
+
typeof limitRaw === 'number' && Number.isFinite(limitRaw) ? Math.floor(limitRaw) : 5;
|
|
1244
|
+
|
|
1245
|
+
const catalogue = await import('../_lib/skill_catalogue.js');
|
|
1246
|
+
const skillsDir = catalogue.resolveSkillsRoot(consumerRoot);
|
|
1247
|
+
if (skillsDir === null) {
|
|
1248
|
+
// A missing catalogue is UNKNOWN, never "no skill fits this task". The
|
|
1249
|
+
// second reads as a ranked answer and would be a confident empty list —
|
|
1250
|
+
// the same zero-from-silence failure `capture_skill_catalogue` refuses.
|
|
1251
|
+
return {
|
|
1252
|
+
status: 'no_catalogue',
|
|
1253
|
+
searched: [...catalogue.DEFAULT_CATALOGUE_ROOTS],
|
|
1254
|
+
consumer_root: consumerRoot,
|
|
1255
|
+
suggestions: [],
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
const { rank } = await import('../skill_tools/score_skill_relevance.js');
|
|
1260
|
+
const rows = rank(task, skillsDir).slice(0, Math.max(1, limit));
|
|
1261
|
+
return {
|
|
1262
|
+
status: 'ok',
|
|
1263
|
+
skills_root: skillsDir,
|
|
1264
|
+
suggestions: rows.map(([name, score, personas]) => ({ skill: name, score, personas })),
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1213
1268
|
export const ALLOWLIST: Record<string, BuiltinTool> = {
|
|
1269
|
+
suggest_skill_for_task: {
|
|
1270
|
+
name: 'suggest_skill_for_task',
|
|
1271
|
+
side_effect: 'ro',
|
|
1272
|
+
description:
|
|
1273
|
+
'Match a free-form task description to the most relevant skills, ' +
|
|
1274
|
+
'ranked by a deterministic keyword scorer over SKILL.md ' +
|
|
1275
|
+
'frontmatter. Use when a skill you need is not in the catalogue ' +
|
|
1276
|
+
'the host delivered — a measured host dropped 402 entries from ' +
|
|
1277
|
+
'its model-visible list — so asking by name is impossible while ' +
|
|
1278
|
+
'asking by task is not. ' +
|
|
1279
|
+
'Read-only: no shell, no writes, and no skill bodies are returned, ' +
|
|
1280
|
+
'only names, scores and declared personas.',
|
|
1281
|
+
input_schema: {
|
|
1282
|
+
type: 'object',
|
|
1283
|
+
required: ['task'],
|
|
1284
|
+
properties: {
|
|
1285
|
+
task: {
|
|
1286
|
+
type: 'string',
|
|
1287
|
+
description:
|
|
1288
|
+
'Free-form description of the task to match skills against.',
|
|
1289
|
+
},
|
|
1290
|
+
limit: {
|
|
1291
|
+
type: 'integer',
|
|
1292
|
+
minimum: 1,
|
|
1293
|
+
default: 5,
|
|
1294
|
+
description: 'Maximum number of skills to return. Defaults to 5.',
|
|
1295
|
+
},
|
|
1296
|
+
},
|
|
1297
|
+
additionalProperties: false,
|
|
1298
|
+
},
|
|
1299
|
+
handler: _suggestSkillForTaskHandler,
|
|
1300
|
+
},
|
|
1214
1301
|
lint_skills: {
|
|
1215
1302
|
name: 'lint_skills',
|
|
1216
1303
|
side_effect: 'ro',
|
package/src/scripts/release.ts
CHANGED
|
@@ -124,7 +124,9 @@ import {
|
|
|
124
124
|
derive_category_hits,
|
|
125
125
|
render_derived_head_values,
|
|
126
126
|
} from './_lib/release_highlights.js';
|
|
127
|
+
import { gh_argv_label, gh_retry } from './_lib/gh_transient.js';
|
|
127
128
|
import {
|
|
129
|
+
NEXT_SECTION_RE,
|
|
128
130
|
extract_changelog_section,
|
|
129
131
|
pr_body_from_section,
|
|
130
132
|
release_notes_from_section,
|
|
@@ -616,6 +618,31 @@ function git(args: readonly string[], opts: { capture?: boolean } = {}): string
|
|
|
616
618
|
return capture ? r.stdout.trim() : '';
|
|
617
619
|
}
|
|
618
620
|
|
|
621
|
+
function _sleep_ms(ms: number): void {
|
|
622
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* `gh` with transient-failure retry and a readable death. Always captures, so
|
|
627
|
+
* the classifier can see stderr; `check: false` keeps the caller's fallback.
|
|
628
|
+
*/
|
|
629
|
+
function gh(args: readonly string[], opts: { check?: boolean } = {}): RunResult {
|
|
630
|
+
const check = opts.check ?? true;
|
|
631
|
+
const r = gh_retry(args, (argv) => run(argv, { check: false, capture: true }), {
|
|
632
|
+
sleep: (ms) => (_exec_override ? undefined : _sleep_ms(ms)),
|
|
633
|
+
notify: (m) => process.stdout.write(`${m}\n`),
|
|
634
|
+
});
|
|
635
|
+
if (check && r.returncode !== 0) {
|
|
636
|
+
die(`${gh_argv_label(args)} failed (${r.returncode}):\n${(r.stderr || r.stdout).trim()}`);
|
|
637
|
+
}
|
|
638
|
+
// Mutating calls used to inherit stdio — echo, or the PR / release URL
|
|
639
|
+
// vanishes. Probes (`check: false`) stay quiet; their stdout is JSON.
|
|
640
|
+
if (check && r.stdout.trim()) {
|
|
641
|
+
process.stdout.write(`${r.stdout.trim()}\n`);
|
|
642
|
+
}
|
|
643
|
+
return r;
|
|
644
|
+
}
|
|
645
|
+
|
|
619
646
|
/**
|
|
620
647
|
* One name per failing check, from `gh pr checks --json name,bucket` output.
|
|
621
648
|
*
|
|
@@ -879,14 +906,14 @@ function watch_pr_checks(branch: string): void {
|
|
|
879
906
|
*
|
|
880
907
|
* git's wording for this is stable across versions: the hint is `fetch first`
|
|
881
908
|
* / `Updates were rejected`, and the ref line carries `[rejected]` with
|
|
882
|
-
* `non-fast-forward` or `fetch first`.
|
|
909
|
+
* `non-fast-forward` or `fetch first`. A stale lease is a SECOND wording —
|
|
910
|
+
* `[remote rejected]` + `cannot lock ref … but expected`; pinned in the tests.
|
|
883
911
|
*/
|
|
884
912
|
export function _is_non_fast_forward(stderr: string, stdout: string): boolean {
|
|
885
913
|
const text = `${stderr}\n${stdout}`;
|
|
886
|
-
return
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
);
|
|
914
|
+
if (!/\[(?:remote )?rejected\]/i.test(text)) return false;
|
|
915
|
+
if (/cannot lock ref[^\n]*but expected/i.test(text)) return true;
|
|
916
|
+
return /(non-fast-forward|fetch first|behind its remote counterpart)/i.test(text);
|
|
890
917
|
}
|
|
891
918
|
|
|
892
919
|
function push_release_branch(branch: string): void {
|
|
@@ -923,13 +950,9 @@ function push_release_branch(branch: string): void {
|
|
|
923
950
|
|
|
924
951
|
/** mergeStateStatus of the branch's PR ('' when the probe fails). */
|
|
925
952
|
function _pr_merge_state(branch: string): string {
|
|
926
|
-
const r =
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
check: false,
|
|
930
|
-
capture: true,
|
|
931
|
-
},
|
|
932
|
-
);
|
|
953
|
+
const r = gh(['pr', 'view', branch, '--json', 'mergeStateStatus', '--jq', '.mergeStateStatus'], {
|
|
954
|
+
check: false,
|
|
955
|
+
});
|
|
933
956
|
return r.returncode === 0 ? r.stdout.trim() : '';
|
|
934
957
|
}
|
|
935
958
|
|
|
@@ -973,14 +996,11 @@ function _refresh_pr_body_from_head(branch: string, target: string): void {
|
|
|
973
996
|
}
|
|
974
997
|
const capped = _cap_body(section!.body, GH_PR_BODY_LIMIT - 200, '`CHANGELOG.md` in this PR');
|
|
975
998
|
const body = pr_body_from_section(capped, target);
|
|
976
|
-
const live =
|
|
977
|
-
check: false,
|
|
978
|
-
capture: true,
|
|
979
|
-
});
|
|
999
|
+
const live = gh(['pr', 'view', branch, '--json', 'body', '-q', '.body'], { check: false });
|
|
980
1000
|
if (live.returncode === 0 && live.stdout.replace(/\r\n/gu, '\n').trim() === body.trim()) {
|
|
981
1001
|
return;
|
|
982
1002
|
}
|
|
983
|
-
|
|
1003
|
+
gh(['pr', 'edit', branch, '--body', body]);
|
|
984
1004
|
}
|
|
985
1005
|
|
|
986
1006
|
function merge_release_pr(branch: string, wait_for_checks: boolean): void {
|
|
@@ -1147,22 +1167,8 @@ function _push_tag(tag: string): void {
|
|
|
1147
1167
|
|
|
1148
1168
|
/** Most recent PR (any state) with `release/X.Y.Z` as head, or null. */
|
|
1149
1169
|
function _pr_for_branch(branch: string): Record<string, unknown> | null {
|
|
1150
|
-
const
|
|
1151
|
-
|
|
1152
|
-
'gh',
|
|
1153
|
-
'pr',
|
|
1154
|
-
'list',
|
|
1155
|
-
'--head',
|
|
1156
|
-
branch,
|
|
1157
|
-
'--state',
|
|
1158
|
-
'all',
|
|
1159
|
-
'--json',
|
|
1160
|
-
'number,state,url',
|
|
1161
|
-
'--limit',
|
|
1162
|
-
'1',
|
|
1163
|
-
],
|
|
1164
|
-
{ check: false, capture: true },
|
|
1165
|
-
);
|
|
1170
|
+
const argv = ['pr', 'list', '--head', branch, '--state', 'all', '--json', 'number,state,url'];
|
|
1171
|
+
const r = gh([...argv, '--limit', '1'], { check: false });
|
|
1166
1172
|
if (r.returncode !== 0) {
|
|
1167
1173
|
return null;
|
|
1168
1174
|
}
|
|
@@ -1476,8 +1482,7 @@ function _previous_test_count_from_changelog(prev_tag: string | null): number |
|
|
|
1476
1482
|
}
|
|
1477
1483
|
const headEnd = m.index + m[0].length;
|
|
1478
1484
|
const rest = text.slice(headEnd);
|
|
1479
|
-
const
|
|
1480
|
-
const next_heading = next_re.exec(rest);
|
|
1485
|
+
const next_heading = NEXT_SECTION_RE.exec(rest);
|
|
1481
1486
|
const sectionEnd = headEnd + (next_heading ? next_heading.index : rest.length);
|
|
1482
1487
|
const section = text.slice(headEnd, sectionEnd);
|
|
1483
1488
|
const count_match = _TEST_COUNT_LINE_RE.exec(section);
|
|
@@ -2141,19 +2146,8 @@ function execute(
|
|
|
2141
2146
|
'`CHANGELOG.md` in this PR',
|
|
2142
2147
|
);
|
|
2143
2148
|
const pr_body = pr_body_from_section(pr_changelog, plan.target);
|
|
2144
|
-
|
|
2145
|
-
'
|
|
2146
|
-
'pr',
|
|
2147
|
-
'create',
|
|
2148
|
-
'--base',
|
|
2149
|
-
MAIN_BRANCH,
|
|
2150
|
-
'--head',
|
|
2151
|
-
branch,
|
|
2152
|
-
'--title',
|
|
2153
|
-
`release: ${plan.target}`,
|
|
2154
|
-
'--body',
|
|
2155
|
-
pr_body,
|
|
2156
|
-
]);
|
|
2149
|
+
gh(['pr', 'create', '--base', MAIN_BRANCH, '--head', branch,
|
|
2150
|
+
'--title', `release: ${plan.target}`, '--body', pr_body]);
|
|
2157
2151
|
}
|
|
2158
2152
|
|
|
2159
2153
|
// ─── 6. wait for checks ─────────────────────────────────────────────────
|
|
@@ -2236,16 +2230,7 @@ function execute(
|
|
|
2236
2230
|
GH_RELEASE_NOTES_LIMIT,
|
|
2237
2231
|
'`CHANGELOG.md`',
|
|
2238
2232
|
);
|
|
2239
|
-
|
|
2240
|
-
'gh',
|
|
2241
|
-
'release',
|
|
2242
|
-
'create',
|
|
2243
|
-
plan.target,
|
|
2244
|
-
'--title',
|
|
2245
|
-
plan.target,
|
|
2246
|
-
'--notes',
|
|
2247
|
-
notes,
|
|
2248
|
-
]);
|
|
2233
|
+
gh(['release', 'create', plan.target, '--title', plan.target, '--notes', notes]);
|
|
2249
2234
|
|
|
2250
2235
|
// ─── 9b. dispatch-chain the tag-triggered workflows (--ci only) ──────
|
|
2251
2236
|
// release-guard.yml, publish-npm.yml, and cloud-release.yml all
|
|
@@ -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) {
|