@event4u/agent-config 9.15.0 → 9.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +21 -0
  3. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  4. package/dist/cli/mcp/content.js +1 -1
  5. package/dist/cli/mcp/content.js.map +1 -1
  6. package/dist/discovery/deprecation-report.md +1 -1
  7. package/dist/discovery/discovery-manifest.json +1 -1
  8. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  9. package/dist/discovery/discovery-manifest.summary.md +1 -1
  10. package/dist/discovery/orphan-report.md +1 -1
  11. package/dist/discovery/packs.json +1 -1
  12. package/dist/discovery/trust-report.md +1 -1
  13. package/dist/discovery/workspaces.json +1 -1
  14. package/dist/mcp/registry-manifest.json +1 -1
  15. package/dist/mcp/server.json +2 -2
  16. package/dist/server/routes/workspace.js +1 -1
  17. package/dist/server/routes/workspace.js.map +1 -1
  18. package/dist/shared/userMd/utils.js +1 -1
  19. package/dist/shared/userMd/utils.js.map +1 -1
  20. package/dist/ui/assets/index-DmR0mbk4.js +26 -0
  21. package/dist/ui/assets/index-DmR0mbk4.js.map +1 -0
  22. package/dist/ui/index.html +1 -1
  23. package/package.json +4 -4
  24. package/src/config/agent-settings.template.yml +3 -1
  25. package/src/config/budget-routing.json +7 -0
  26. package/src/scripts/_lib/tier_budget_routing.ts +154 -28
  27. package/src/scripts/anchor_scoring_run.ts +1 -1
  28. package/src/scripts/bench_persona_placebo.ts +1 -1
  29. package/src/scripts/bench_quality_run.ts +1 -1
  30. package/src/scripts/check_cli_registry_budget_sync.ts +168 -0
  31. package/src/scripts/check_gate_coverage.ts +1 -1
  32. package/src/scripts/check_token_quality_golden.ts +1 -1
  33. package/src/scripts/cost/budget.mjs +18 -4
  34. package/src/scripts/release.ts +110 -7
  35. package/src/scripts/routing_doctor.ts +76 -1
  36. package/src/shared/userMd/utils.ts +1 -1
  37. package/dist/ui/assets/index-DJY_vQVn.js +0 -40
  38. package/dist/ui/assets/index-DJY_vQVn.js.map +0 -1
@@ -634,13 +634,16 @@ export function _failed_checks_report(names: readonly string[]): string {
634
634
  * A short grace period gives GitHub time to register workflow runs on a
635
635
  * freshly-pushed branch.
636
636
  */
637
- function watch_pr_checks(): void {
637
+ function watch_pr_checks(branch: string): void {
638
638
  // time.sleep(5) — blocking grace period. Never reached on any test path.
639
639
  const until = Date.now() + 5000;
640
640
  while (Date.now() < until) {
641
641
  // busy-wait stand-in for time.sleep(5) without an event-loop yield.
642
642
  }
643
- const proc = spawnSync('gh', ['pr', 'checks', '--watch'], {
643
+ // The branch is passed explicitly — `gh` inferring the PR from HEAD
644
+ // resolved to `main` mid-run once (2026-08-03, 9.16.0 resume) and the
645
+ // whole release died on "no pull requests found for branch main".
646
+ const proc = spawnSync('gh', ['pr', 'checks', branch, '--watch'], {
644
647
  cwd: REPO_ROOT,
645
648
  encoding: 'utf-8',
646
649
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -667,7 +670,7 @@ function watch_pr_checks(): void {
667
670
  if (output) {
668
671
  process.stderr.write(output + '\n');
669
672
  }
670
- const summary = spawnSync('gh', ['pr', 'checks', '--json', 'name,bucket'], {
673
+ const summary = spawnSync('gh', ['pr', 'checks', branch, '--json', 'name,bucket'], {
671
674
  cwd: REPO_ROOT,
672
675
  encoding: 'utf-8',
673
676
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -679,6 +682,105 @@ function watch_pr_checks(): void {
679
682
  die(`PR checks failed (exit ${returncode})`);
680
683
  }
681
684
 
685
+ /**
686
+ * Push the release branch, integrating a remote that moved under us.
687
+ *
688
+ * A GitHub-side "Update branch" click (or a concurrent resume run) advances
689
+ * `origin/<branch>` while the local ref stays behind; a bare `git push` is
690
+ * then rejected with "fetch first" and the whole release run dies (measured
691
+ * 2026-08-03, 9.15.0). Same-branch commits are never ours to drop, so the
692
+ * recovery is merge-and-retry, never force-push.
693
+ */
694
+ function push_release_branch(branch: string): void {
695
+ const first = run(['git', 'push', '-u', REMOTE, branch], { check: false });
696
+ if (first.returncode === 0) {
697
+ return;
698
+ }
699
+ process.stdout.write(`↻ ${REMOTE}/${branch} moved — integrating and retrying push\n`);
700
+ run(['git', 'fetch', REMOTE, branch]);
701
+ run(['git', 'merge', '--no-edit', `${REMOTE}/${branch}`]);
702
+ run(['git', 'push', '-u', REMOTE, branch]);
703
+ }
704
+
705
+ /** mergeStateStatus of the branch's PR ('' when the probe fails). */
706
+ function _pr_merge_state(branch: string): string {
707
+ const r = run(
708
+ ['gh', 'pr', 'view', branch, '--json', 'mergeStateStatus', '--jq', '.mergeStateStatus'],
709
+ {
710
+ check: false,
711
+ capture: true,
712
+ },
713
+ );
714
+ return r.returncode === 0 ? r.stdout.trim() : '';
715
+ }
716
+
717
+ /**
718
+ * Bound on update-and-retry rounds in merge_release_pr. Three is deliberate:
719
+ * main receiving unrelated merges three times inside one checks-wait window
720
+ * means the queue is hot and a human should decide, not a loop.
721
+ */
722
+ const _MERGE_UPDATE_ROUNDS = 3;
723
+
724
+ /**
725
+ * Merge the release PR, absorbing a moving base branch.
726
+ *
727
+ * Branch protection requires the head to be up to date with `main`, and
728
+ * `main` moves constantly (dependabot, unrelated merges) — so between the
729
+ * checks-wait and the merge the PR routinely flips to BEHIND and a bare
730
+ * `gh pr merge` dies with "the head branch is not up to date with the base
731
+ * branch" (measured 2026-08-03, 9.16.0). Recovery is mechanical and safe by
732
+ * construction: merging `origin/main` into `release/X.Y.Z` makes the fix
733
+ * files identical on both sides, so the release-PR diff stays inside the
734
+ * version-bump allowlist (docs/contracts/release-pr-gating.md § Mid-release
735
+ * fixes) — do it here instead of aborting the run.
736
+ */
737
+ function merge_release_pr(branch: string, wait_for_checks: boolean): void {
738
+ if (git(['rev-parse', '--abbrev-ref', 'HEAD'], { capture: true }) !== branch) {
739
+ run(['git', 'checkout', branch]);
740
+ }
741
+ for (let round = 0; round <= _MERGE_UPDATE_ROUNDS; round++) {
742
+ if (_pr_merge_state(branch) !== 'BEHIND') {
743
+ const merged = run(['gh', 'pr', 'merge', branch, '--merge', '--delete-branch'], {
744
+ check: false,
745
+ });
746
+ if (merged.returncode === 0) {
747
+ return;
748
+ }
749
+ if (_pr_merge_state(branch) !== 'BEHIND') {
750
+ // Not the moving-base failure — surface it unchanged.
751
+ throw new CalledProcessError(merged.returncode, [
752
+ 'gh',
753
+ 'pr',
754
+ 'merge',
755
+ branch,
756
+ '--merge',
757
+ '--delete-branch',
758
+ ]);
759
+ }
760
+ // BEHIND appeared between the probe and the merge — fall through
761
+ // to the update below.
762
+ }
763
+ if (round === _MERGE_UPDATE_ROUNDS) {
764
+ break;
765
+ }
766
+ process.stdout.write(
767
+ `↻ head behind ${MAIN_BRANCH} (update round ${round + 1}/${_MERGE_UPDATE_ROUNDS}) — ` +
768
+ `merging ${REMOTE}/${MAIN_BRANCH} into ${branch} and re-running checks\n`,
769
+ );
770
+ run(['git', 'fetch', REMOTE, MAIN_BRANCH]);
771
+ run(['git', 'merge', '--no-edit', `${REMOTE}/${MAIN_BRANCH}`]);
772
+ push_release_branch(branch);
773
+ if (wait_for_checks) {
774
+ watch_pr_checks(branch);
775
+ }
776
+ }
777
+ die(
778
+ `release PR is still behind ${MAIN_BRANCH} after ${_MERGE_UPDATE_ROUNDS} update round(s) — ` +
779
+ `${MAIN_BRANCH} is moving faster than the checks complete. ` +
780
+ 'Re-run when the queue quiets down: task release -- --resume --yes',
781
+ );
782
+ }
783
+
682
784
  function have(bin: string): boolean {
683
785
  const res = spawnSync('which', [bin], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
684
786
  if (res.error) {
@@ -1596,9 +1698,10 @@ function execute(
1596
1698
  _step(4, total, 'PR already merged — skip push');
1597
1699
  } else {
1598
1700
  // `git push -u` is naturally idempotent — it prints "Everything
1599
- // up-to-date" when remote already matches. No probe needed.
1701
+ // up-to-date" when remote already matches. push_release_branch
1702
+ // additionally absorbs a remote that moved under us.
1600
1703
  _step(4, total, `Push ${branch} to ${REMOTE}`);
1601
- run(['git', 'push', '-u', REMOTE, branch]);
1704
+ push_release_branch(branch);
1602
1705
  }
1603
1706
 
1604
1707
  // ─── 5. PR ──────────────────────────────────────────────────────────────
@@ -1637,7 +1740,7 @@ function execute(
1637
1740
  _step(6, total, 'PR already merged — skip checks wait');
1638
1741
  } else if (wait_for_checks) {
1639
1742
  _step(6, total, 'Wait for PR checks');
1640
- watch_pr_checks();
1743
+ watch_pr_checks(branch);
1641
1744
  } else {
1642
1745
  _step(6, total, 'Skip waiting for checks (--no-wait)');
1643
1746
  }
@@ -1647,7 +1750,7 @@ function execute(
1647
1750
  _step(7, total, `PR #${pr_info!['number']} already merged — skip`);
1648
1751
  } else {
1649
1752
  _step(7, total, 'Merge pull request (merge commit) and delete branch');
1650
- run(['gh', 'pr', 'merge', '--merge', '--delete-branch']);
1753
+ merge_release_pr(branch, wait_for_checks);
1651
1754
  }
1652
1755
 
1653
1756
  // ─── 8. tag main + push tag ─────────────────────────────────────────────
@@ -119,6 +119,67 @@ export interface OrchestrationReport {
119
119
  mode: string | null;
120
120
  reason: string;
121
121
  } | null;
122
+ /** Budget-routing delivery evidence (external review 2026-08-03, Finding 2). */
123
+ delivery: {
124
+ eligible_dispatches: number;
125
+ budget_evidence_lines: number;
126
+ warning: string;
127
+ };
128
+ }
129
+
130
+ /**
131
+ * Adherence ≠ delivery: budget routing binds via policy prose, so the
132
+ * doctor checks the DELIVERY evidence — orchestration audit lines that
133
+ * carry a tier decision. Eligible dispatches recorded while
134
+ * `budget_routing != off` but ZERO tier-carrying lines → WARN with the
135
+ * exact reason. Read-only over agents/runtime/state/audit/*.jsonl; no
136
+ * new state. This closes the same blind-spot class the session-canary
137
+ * incident exposed (policy present, delivery silently absent).
138
+ */
139
+ export function check_budget_delivery(
140
+ workspace_root: string,
141
+ budget_routing: string,
142
+ ): OrchestrationReport["delivery"] {
143
+ const out = { eligible_dispatches: 0, budget_evidence_lines: 0, warning: "" };
144
+ if (budget_routing === "off") {
145
+ return out;
146
+ }
147
+ const auditDir = path.join(workspace_root, "agents", "runtime", "state", "audit");
148
+ let files: string[] = [];
149
+ try {
150
+ files = fs.readdirSync(auditDir).filter((f) => f.endsWith(".jsonl"));
151
+ } catch {
152
+ return out; // no audit dir — nothing dispatched, nothing to warn about
153
+ }
154
+ for (const f of files) {
155
+ let text = "";
156
+ try {
157
+ text = fs.readFileSync(path.join(auditDir, f), "utf-8");
158
+ } catch {
159
+ continue;
160
+ }
161
+ for (const line of text.split("\n")) {
162
+ if (!line.trim()) continue;
163
+ try {
164
+ const rec = JSON.parse(line) as Record<string, unknown>;
165
+ const spawns = Number(rec["spawn_count"] ?? 0);
166
+ if (!Number.isFinite(spawns) || spawns < 1) continue;
167
+ out.eligible_dispatches += 1;
168
+ if (typeof rec["tier"] === "string" && rec["tier"] !== "") {
169
+ out.budget_evidence_lines += 1;
170
+ }
171
+ } catch {
172
+ // non-record line — audit files are shared JSONL surfaces
173
+ }
174
+ }
175
+ }
176
+ if (out.eligible_dispatches > 0 && out.budget_evidence_lines === 0) {
177
+ out.warning =
178
+ `budget_routing=${budget_routing} is policy-bound but shows no delivery evidence: ` +
179
+ `${out.eligible_dispatches} orchestration dispatch(es) recorded, 0 carry a tier decision — ` +
180
+ `the relation may not be running (adherence != delivery)`;
181
+ }
182
+ return out;
122
183
  }
123
184
 
124
185
  export interface DoctorReport {
@@ -160,8 +221,14 @@ export function collect_orchestration(
160
221
  const cost_budgets = (cost["budgets"] ?? {}) as Record<string, unknown>;
161
222
  const tracking_dir = path.join(workspace_root, "agents", "cost-tracking");
162
223
  const ledger_present = fs.existsSync(path.join(tracking_dir, "sessions.jsonl"));
224
+ // YAML 1.1 trap: an unquoted `off` parses as boolean false — coerce it
225
+ // back to the enum string so the check honors the user's intent.
163
226
  const budget_routing =
164
- typeof sub["budget_routing"] === "string" ? (sub["budget_routing"] as string) : "ask";
227
+ sub["budget_routing"] === false
228
+ ? "off"
229
+ : typeof sub["budget_routing"] === "string"
230
+ ? (sub["budget_routing"] as string)
231
+ : "ask";
165
232
  const per_tier = (cost_budgets["per_tier"] ?? {}) as Record<string, unknown>;
166
233
  const cooldowns = readCooldowns(tracking_dir);
167
234
  const tier_budgets: OrchestrationReport["tier_budgets"] = {};
@@ -197,6 +264,7 @@ export function collect_orchestration(
197
264
  cost_budgets,
198
265
  ledger_present,
199
266
  sample,
267
+ delivery: check_budget_delivery(workspace_root, budget_routing),
200
268
  };
201
269
  }
202
270
 
@@ -407,6 +475,13 @@ function _render(report: DoctorReport): string {
407
475
  return `${t}=${cap}${cool}`;
408
476
  });
409
477
  lines.push(` tier budgets (rolling-24h): ${tierBits.join(" · ")}`);
478
+ if (o.delivery.warning) {
479
+ lines.push(` ⚠️ ${o.delivery.warning}`);
480
+ } else if (o.delivery.eligible_dispatches > 0) {
481
+ lines.push(
482
+ ` delivery evidence: ${o.delivery.budget_evidence_lines}/${o.delivery.eligible_dispatches} dispatches carry a tier decision`,
483
+ );
484
+ }
410
485
  lines.push(` activation probe: ${o.activation.action} — ${o.activation.reason}`);
411
486
  if (o.sample) {
412
487
  lines.push(
@@ -23,7 +23,7 @@
23
23
  * Drop once no consumer ships the legacy file.
24
24
  */
25
25
 
26
- import yaml from 'js-yaml';
26
+ import * as yaml from 'js-yaml';
27
27
 
28
28
  /** Frontmatter fence used by the legacy `.agent-user.md` format. */
29
29
  const FRONTMATTER_FENCE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;