@mutmutco/cli 2.35.0 → 2.36.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/dist/main.cjs CHANGED
@@ -3392,7 +3392,7 @@ var program = new Command();
3392
3392
 
3393
3393
  // src/index.ts
3394
3394
  var import_promises5 = require("node:fs/promises");
3395
- var import_node_fs16 = require("node:fs");
3395
+ var import_node_fs18 = require("node:fs");
3396
3396
 
3397
3397
  // src/rules-sync.ts
3398
3398
  function normalizeEol(s) {
@@ -4447,6 +4447,7 @@ function buildNoteCapture(summary, o, id, evidence) {
4447
4447
  next: o.next,
4448
4448
  decision: o.decision,
4449
4449
  queueOp,
4450
+ handoffClose: o.handoffClose,
4450
4451
  state,
4451
4452
  source,
4452
4453
  evidence: Object.keys(ev).length ? ev : void 0,
@@ -5080,6 +5081,260 @@ function registerSagaCommands(program3) {
5080
5081
  });
5081
5082
  }
5082
5083
 
5084
+ // src/handoff-commands.ts
5085
+ var import_node_crypto4 = require("node:crypto");
5086
+
5087
+ // src/handoff.ts
5088
+ var HANDOFF_PREFIX = "handoff:";
5089
+ function clean(value) {
5090
+ return value?.trim() ?? "";
5091
+ }
5092
+ function normalizeHandoffKey(value) {
5093
+ const key = clean(value);
5094
+ if (!key) throw new Error("handoff key is required");
5095
+ return key;
5096
+ }
5097
+ function serializeHandoff(record) {
5098
+ return `${HANDOFF_PREFIX}${JSON.stringify(record)}`;
5099
+ }
5100
+ function parseHandoffText(text) {
5101
+ if (!text.startsWith(HANDOFF_PREFIX)) return null;
5102
+ try {
5103
+ const raw = JSON.parse(text.slice(HANDOFF_PREFIX.length));
5104
+ const state = raw.state;
5105
+ const key = clean(raw.key);
5106
+ const northStarSlug = clean(raw.northStarSlug);
5107
+ const summary = clean(raw.summary);
5108
+ const sourceSessionId = clean(raw.sourceSessionId);
5109
+ const createdAt = clean(raw.createdAt);
5110
+ if (state !== "open" && state !== "claimed" && state !== "cancelled") return null;
5111
+ if (!key || !northStarSlug || !summary || !sourceSessionId || !createdAt) return null;
5112
+ return {
5113
+ state,
5114
+ key,
5115
+ northStarSlug,
5116
+ summary,
5117
+ sourceSessionId,
5118
+ createdAt,
5119
+ claimedBySessionId: clean(raw.claimedBySessionId) || void 0,
5120
+ closedAt: clean(raw.closedAt) || void 0
5121
+ };
5122
+ } catch {
5123
+ return null;
5124
+ }
5125
+ }
5126
+ function listHandoffs(head, opts = {}) {
5127
+ const parsed = (head.queued ?? []).map((item, index) => ({ done: item.done, index, record: parseHandoffText(item.text) })).filter((x) => x.record !== null);
5128
+ return parsed.filter((x) => opts.includeClosed || x.record.state === "open" && !x.done).map((x) => ({ index: x.index, done: x.done, record: x.record }));
5129
+ }
5130
+ function findOpenHandoff(head, key) {
5131
+ const normalized = normalizeHandoffKey(key).toLowerCase();
5132
+ return listHandoffs(head).find((item) => item.record.key.toLowerCase() === normalized || item.record.northStarSlug.toLowerCase() === normalized);
5133
+ }
5134
+ function planOpenHandoff(input) {
5135
+ const key = normalizeHandoffKey(input.key);
5136
+ const northStarSlug = clean(input.northStarSlug);
5137
+ const summary = clean(input.summary);
5138
+ const sourceSessionId = clean(input.sourceSessionId);
5139
+ if (!northStarSlug) throw new Error("north star slug is required");
5140
+ if (!summary) throw new Error("handoff summary is required");
5141
+ if (!sourceSessionId) throw new Error("source session id is required");
5142
+ return {
5143
+ state: "open",
5144
+ key,
5145
+ northStarSlug,
5146
+ summary,
5147
+ sourceSessionId,
5148
+ createdAt: input.createdAt
5149
+ };
5150
+ }
5151
+ function closeHandoff(record, state, at, claimedBySessionId) {
5152
+ return {
5153
+ ...record,
5154
+ state,
5155
+ closedAt: at,
5156
+ claimedBySessionId: claimedBySessionId || record.claimedBySessionId
5157
+ };
5158
+ }
5159
+ function formatHandoffLine(item) {
5160
+ const r = item.record;
5161
+ const suffix = r.state === "claimed" && r.claimedBySessionId ? ` -> ${r.claimedBySessionId}` : "";
5162
+ return `${r.key} [${r.state}] northstar:${r.northStarSlug} source:${r.sourceSessionId}${suffix} - ${r.summary}`;
5163
+ }
5164
+
5165
+ // src/handoff-commands.ts
5166
+ var FOREGROUND_FETCH = { attempts: 2, timeoutMs: 8e3 };
5167
+ var SESSION_START_FETCH = { attempts: 1, timeoutMs: 3e3 };
5168
+ async function fetchState(url, qs, retry = FOREGROUND_FETCH) {
5169
+ const res = await fetchWithRetry(fetch, `${url}/saga/state?${qs}`, { headers: await hubHeaders() }, retry);
5170
+ if (!res.ok) return null;
5171
+ const body = await res.json();
5172
+ if ("state" in body) return body;
5173
+ return { key: null, state: { head: body.head } };
5174
+ }
5175
+ async function fetchScopedSessions(url, project2, branch, retry = FOREGROUND_FETCH) {
5176
+ const qs = new URLSearchParams({ project: project2, branch });
5177
+ const res = await fetchWithRetry(fetch, `${url}/saga/sessions?${qs}`, { headers: await hubHeaders() }, retry);
5178
+ if (!res.ok) return [];
5179
+ const body = await res.json();
5180
+ return body.sessions ?? [];
5181
+ }
5182
+ async function fetchSessionHead(url, key, retry = FOREGROUND_FETCH) {
5183
+ const qs = new URLSearchParams(key);
5184
+ const got = await fetchState(url, qs, retry);
5185
+ return got?.state?.head ?? null;
5186
+ }
5187
+ async function fetchCurrentState() {
5188
+ const cfg = await loadConfig();
5189
+ if (!cfg.sagaApiUrl) return null;
5190
+ const key = await sagaKey(cfg);
5191
+ const got = await fetchState(cfg.sagaApiUrl, new URLSearchParams(key));
5192
+ return got ? { key, head: got.state?.head ?? {} } : null;
5193
+ }
5194
+ async function collectScopedHandoffs(opts = {}) {
5195
+ const cfg = await loadConfig();
5196
+ if (!cfg.sagaApiUrl) return { handoffs: [], sessions: [] };
5197
+ const key = await sagaKey(cfg);
5198
+ const retry = opts.retry ?? FOREGROUND_FETCH;
5199
+ const sessions = await fetchScopedSessions(cfg.sagaApiUrl, key.project, key.branch, retry);
5200
+ const seen = /* @__PURE__ */ new Set();
5201
+ const handoffs = [];
5202
+ for (const session of sessions) {
5203
+ const head = await fetchSessionHead(cfg.sagaApiUrl, session, retry);
5204
+ if (!head) continue;
5205
+ for (const item of listHandoffs(head, { includeClosed: opts.includeClosed })) {
5206
+ const dedupe = `${item.record.sourceSessionId}:${item.record.key}:${item.record.state}`;
5207
+ if (seen.has(dedupe)) continue;
5208
+ seen.add(dedupe);
5209
+ handoffs.push(item);
5210
+ }
5211
+ }
5212
+ return { handoffs, sessions };
5213
+ }
5214
+ async function locateOpenHandoff(key, retry = FOREGROUND_FETCH) {
5215
+ const cfg = await loadConfig();
5216
+ if (!cfg.sagaApiUrl) return null;
5217
+ const scopeKey = await sagaKey(cfg);
5218
+ const sessions = await fetchScopedSessions(cfg.sagaApiUrl, scopeKey.project, scopeKey.branch, retry);
5219
+ for (const session of sessions) {
5220
+ const head = await fetchSessionHead(cfg.sagaApiUrl, session, retry);
5221
+ if (!head) continue;
5222
+ const item = findOpenHandoff(head, key);
5223
+ if (item) {
5224
+ return {
5225
+ key: { project: session.project, branch: session.branch, sessionId: session.sessionId },
5226
+ item
5227
+ };
5228
+ }
5229
+ }
5230
+ return null;
5231
+ }
5232
+ async function locateClaimedHandoff(key, claimedBySessionId, retry = FOREGROUND_FETCH) {
5233
+ const { handoffs } = await collectScopedHandoffs({ includeClosed: true, retry });
5234
+ const normalized = key.toLowerCase();
5235
+ return handoffs.find((item) => {
5236
+ const r = item.record;
5237
+ return r.state === "claimed" && r.claimedBySessionId === claimedBySessionId && (r.key.toLowerCase() === normalized || r.northStarSlug.toLowerCase() === normalized);
5238
+ }) ?? null;
5239
+ }
5240
+ async function postKeyedNote(key, summary, options) {
5241
+ const cfg = await loadConfig();
5242
+ if (!cfg.sagaApiUrl) fail("handoff: Hub API URL not configured");
5243
+ const sha = await gitOut(["rev-parse", "--short", "HEAD"]);
5244
+ const capture = buildNoteCapture(summary, options, (0, import_node_crypto4.randomUUID)(), { sha: sha || void 0, branch: key.branch });
5245
+ const result = await postCaptureOnce(cfg.sagaApiUrl, { ...capture, ...key });
5246
+ if (!result.ok) fail(`handoff: write failed${result.status ? ` (HTTP ${result.status})` : ""}${result.message ? `: ${result.message}` : ""}`);
5247
+ }
5248
+ function deriveOpenFields(summary, opts, head, key) {
5249
+ const northStarSlug = opts.northStarSlug ?? head.anchor?.slug;
5250
+ const handoffKey = opts.key ?? northStarSlug;
5251
+ const text = summary?.trim() || head.next?.trim() || head.anchor?.intent?.trim();
5252
+ return planOpenHandoff({
5253
+ key: handoffKey ?? "",
5254
+ northStarSlug: northStarSlug ?? "",
5255
+ summary: text ?? "",
5256
+ sourceSessionId: key.sessionId,
5257
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
5258
+ });
5259
+ }
5260
+ async function runHandoffOpen(summary, opts, io = consoleIo) {
5261
+ const current = await fetchCurrentState();
5262
+ if (!current) return fail("handoff open: saga state unavailable");
5263
+ let record;
5264
+ try {
5265
+ record = deriveOpenFields(summary, opts, current.head, current.key);
5266
+ } catch (e) {
5267
+ return fail(`handoff open: ${e.message}`);
5268
+ }
5269
+ await postKeyedNote(current.key, `handoff opened ${record.key}`, { queueAdd: serializeHandoff(record), anchorSlug: record.northStarSlug });
5270
+ if (opts.json) return io.log(JSON.stringify({ ok: true, handoff: record }));
5271
+ io.log(`handoff open: ${formatHandoffLine({ index: -1, done: false, record })}`);
5272
+ }
5273
+ async function runHandoffList(opts, io = consoleIo) {
5274
+ const { handoffs } = await collectScopedHandoffs({ includeClosed: opts.all });
5275
+ if (opts.json) {
5276
+ io.log(JSON.stringify({ handoffs }, null, 2));
5277
+ } else if (!handoffs.length) {
5278
+ io.log("handoff list: none");
5279
+ } else {
5280
+ for (const h of handoffs) io.log(formatHandoffLine(h));
5281
+ }
5282
+ return handoffs;
5283
+ }
5284
+ async function runHandoffOffer(io = consoleIo, opts = {}) {
5285
+ const retry = opts.fast ? SESSION_START_FETCH : FOREGROUND_FETCH;
5286
+ const { handoffs } = await collectScopedHandoffs({ retry });
5287
+ if (!handoffs.length) return;
5288
+ io.log("Open handoffs:");
5289
+ for (const h of handoffs) io.log(`- ${formatHandoffLine(h)}`);
5290
+ io.log("Use `mmi-cli handoff accept <key>` to claim, `mmi-cli handoff cancel <key>` to close, or decline in chat to leave it open.");
5291
+ }
5292
+ async function closeSourceHandoff(key, state, opts = {}, io = consoleIo) {
5293
+ const current = await sagaKey(await loadConfig(), resolveSessionId());
5294
+ const located = await locateOpenHandoff(key);
5295
+ if (!located) {
5296
+ if (state === "claimed") {
5297
+ const prior = await locateClaimedHandoff(key, current.sessionId);
5298
+ if (prior) {
5299
+ if (opts.json) return io.log(JSON.stringify({ ok: true, handoff: prior.record, idempotent: true }));
5300
+ return io.log(`handoff claimed: ${formatHandoffLine(prior)} (already accepted)`);
5301
+ }
5302
+ }
5303
+ return fail(`handoff ${state}: no open handoff matching ${key}`);
5304
+ }
5305
+ const closed = closeHandoff(located.item.record, state, (/* @__PURE__ */ new Date()).toISOString(), state === "claimed" ? current.sessionId : void 0);
5306
+ await postKeyedNote(located.key, `handoff ${state} ${located.item.record.key}`, {
5307
+ handoffClose: { index: located.item.index, closedText: serializeHandoff(closed) }
5308
+ });
5309
+ if (state === "claimed") {
5310
+ await postKeyedNote(current, `handoff accepted ${located.item.record.key}`, {
5311
+ anchor: located.item.record.summary,
5312
+ anchorSlug: located.item.record.northStarSlug,
5313
+ anchorForce: true,
5314
+ decision: `accepted handoff ${located.item.record.key} from session ${located.item.record.sourceSessionId}; northstar ${located.item.record.northStarSlug}`,
5315
+ verified: true
5316
+ });
5317
+ }
5318
+ if (opts.json) return io.log(JSON.stringify({ ok: true, handoff: closed }));
5319
+ io.log(`handoff ${state}: ${formatHandoffLine({ index: located.item.index, done: true, record: closed })}`);
5320
+ }
5321
+ async function runHandoffDecline(key, opts = {}, io = consoleIo) {
5322
+ const located = await locateOpenHandoff(key);
5323
+ if (!located) return fail(`handoff decline: no open handoff matching ${key}`);
5324
+ if (opts.json) return io.log(JSON.stringify({ ok: true, unchanged: true, handoff: located.item.record }));
5325
+ io.log(`handoff decline: left open for re-offer \u2014 ${formatHandoffLine(located.item)}`);
5326
+ }
5327
+ function registerHandoffCommands(program3) {
5328
+ const handoff = program3.command("handoff").description("explicit saga + North Star handoff lifecycle");
5329
+ handoff.command("open [summary]").description("open a handoff bound to the current North Star slug").option("--key <slug|#issue>", "handoff key (defaults to the current North Star slug)").option("--north-star-slug <slug>", "North Star slug to bind (defaults to current saga anchor slug)").option("--json", "machine-readable output").action((summary, opts) => runHandoffOpen(summary, opts));
5330
+ handoff.command("list").description("list open handoffs for the current repo/branch").option("--all", "include claimed/cancelled records").option("--json", "machine-readable output").action(async (opts) => {
5331
+ await runHandoffList(opts);
5332
+ });
5333
+ handoff.command("accept <key>").description("claim an open handoff and bind this session to its North Star").option("--json", "machine-readable output").action((key, opts) => closeSourceHandoff(key, "claimed", opts));
5334
+ handoff.command("cancel <key>").description("close an open handoff without claiming it").option("--json", "machine-readable output").action((key, opts) => closeSourceHandoff(key, "cancelled", opts));
5335
+ handoff.command("decline <key>").description("leave an open handoff unchanged so it is re-offered later").option("--json", "machine-readable output").action((key, opts) => runHandoffDecline(key, opts));
5336
+ }
5337
+
5083
5338
  // src/honcho-commands.ts
5084
5339
  var import_node_child_process5 = require("node:child_process");
5085
5340
  var import_promises3 = require("node:fs/promises");
@@ -5640,7 +5895,7 @@ async function resolveHonchoConfig(cfg = {}, opts = {}) {
5640
5895
  }
5641
5896
 
5642
5897
  // src/honcho-ingest.ts
5643
- var import_node_crypto4 = require("node:crypto");
5898
+ var import_node_crypto5 = require("node:crypto");
5644
5899
  var DEFAULT_HONCHO_MAX_CHARS = 4e3;
5645
5900
  var DEFAULT_HONCHO_CARD_MAX_CHARS = 1200;
5646
5901
  var REDACTED = "[REDACTED]";
@@ -5726,7 +5981,7 @@ function buildIngestPayload(args) {
5726
5981
  const maxChars = args.maxChars ?? DEFAULT_HONCHO_MAX_CHARS;
5727
5982
  const messages = args.messages.map((m) => ({ role: m.role, content: capContent(redactSecrets(m.content), maxChars) })).filter((m) => m.content.trim().length > 0);
5728
5983
  return {
5729
- id: args.id ?? (0, import_node_crypto4.randomUUID)(),
5984
+ id: args.id ?? (0, import_node_crypto5.randomUUID)(),
5730
5985
  workspace: args.workspace,
5731
5986
  peer: args.peer,
5732
5987
  session: args.session,
@@ -6225,8 +6480,205 @@ async function syncDocs(deps, docs2 = SYNCED_DOCS) {
6225
6480
  }
6226
6481
 
6227
6482
  // src/session-start.ts
6483
+ var import_node_fs12 = require("node:fs");
6484
+ var import_node_path10 = require("node:path");
6485
+
6486
+ // src/scratch-gc.ts
6228
6487
  var import_node_fs11 = require("node:fs");
6229
6488
  var import_node_path9 = require("node:path");
6489
+ var TMP_STALE_MS = 6e4;
6490
+ var FLUSH_LOCK_GC_MS = 6e5;
6491
+ var HEAD_TS_STALE_MS = 48 * 36e5;
6492
+ var LAST_SKIP_STALE_MS = 24 * 36e5;
6493
+ var CONFLICT_COPY_STALE_MS = 24 * 36e5;
6494
+ var PLAN_ADVISORY_AGE_MS = 30 * 24 * 36e5;
6495
+ var SCRATCH_GC_THROTTLE_MS = 24 * 36e5;
6496
+ function scratchGcThrottlePath(mmiRoot) {
6497
+ return (0, import_node_path9.join)(mmiRoot, "head-ts", ".scratch-gc-last");
6498
+ }
6499
+ function scratchGcDue(stampPath, now = Date.now(), read = import_node_fs11.readFileSync) {
6500
+ try {
6501
+ return now - (Number(read(stampPath, "utf8").trim()) || 0) >= SCRATCH_GC_THROTTLE_MS;
6502
+ } catch {
6503
+ return true;
6504
+ }
6505
+ }
6506
+ function markScratchGcRun(stampPath, now = Date.now()) {
6507
+ try {
6508
+ (0, import_node_fs11.mkdirSync)((0, import_node_path9.dirname)(stampPath), { recursive: true });
6509
+ (0, import_node_fs11.writeFileSync)(stampPath, String(now), "utf8");
6510
+ } catch {
6511
+ }
6512
+ }
6513
+ function executeScratchGc(repoRoot, opts, now = Date.now()) {
6514
+ const snap = collectScratchSnapshot(repoRoot);
6515
+ const plan2 = planScratchGc(snap, now);
6516
+ if (!opts.apply) return { plan: plan2 };
6517
+ return { plan: plan2, applied: applyScratchGc(plan2, snap.mmiRoot, now) };
6518
+ }
6519
+ var NEVER_BASENAMES = /* @__PURE__ */ new Set([".session", "config.json", "saga-pending.jsonl"]);
6520
+ var TMP_SIDECAR_BASES = ["saga-pending.jsonl", "last-skip.json"];
6521
+ var CONFLICT_COPY_ALLOWLIST = /* @__PURE__ */ new Set(["saga-pending.jsonl", "last-skip.json"]);
6522
+ function conflictCopyOriginal(name) {
6523
+ const m = /^(.+) \d+(\.[^.]+)$/.exec(name);
6524
+ return m ? `${m[1]}${m[2]}` : null;
6525
+ }
6526
+ function isTmpSidecar(name) {
6527
+ return name.endsWith(".tmp") && TMP_SIDECAR_BASES.some((b) => name.startsWith(`${b}.`));
6528
+ }
6529
+ function planScratchGc(snap, now = Date.now()) {
6530
+ const candidates = [];
6531
+ const normalizePath = (p) => p.replace(/\\/g, "/");
6532
+ const mmiPaths = new Set(snap.mmiFiles.map((f) => normalizePath(f.path)));
6533
+ const headTsPrefix = `${snap.mmiRoot.replace(/[\\/]+$/, "")}/head-ts/`.replace(/\\/g, "/");
6534
+ const days = (ms) => `${Math.floor(ms / 864e5)}d`;
6535
+ for (const f of snap.mmiFiles) {
6536
+ const age = now - f.mtimeMs;
6537
+ const add = (family, reason) => candidates.push({ path: f.path, family, tier: "safe-auto", reason, bytes: f.bytes });
6538
+ if (isTmpSidecar(f.name)) {
6539
+ if (age > TMP_STALE_MS) add("tmp-sidecar", `crashed atomic-write sidecar (${days(age)} old)`);
6540
+ continue;
6541
+ }
6542
+ if (f.name === "saga-flush.lock") {
6543
+ if (age > FLUSH_LOCK_GC_MS) add("flush-lock", `abandoned flush lock (${days(age)} old)`);
6544
+ continue;
6545
+ }
6546
+ if (NEVER_BASENAMES.has(f.name)) continue;
6547
+ if (f.path.replace(/\\/g, "/").startsWith(headTsPrefix) && !f.name.startsWith(".")) {
6548
+ if (age > HEAD_TS_STALE_MS) add("head-ts", `stale throttle stamp (${days(age)} old)`);
6549
+ continue;
6550
+ }
6551
+ if (f.name === "last-skip.json") {
6552
+ if (age > LAST_SKIP_STALE_MS) add("last-skip", `stale ingest-skip hint (${days(age)} old)`);
6553
+ continue;
6554
+ }
6555
+ const orig = conflictCopyOriginal(f.name);
6556
+ if (orig && CONFLICT_COPY_ALLOWLIST.has(orig) && mmiPaths.has(normalizePath((0, import_node_path9.join)(f.dir, orig))) && age > CONFLICT_COPY_STALE_MS) {
6557
+ add("conflict-copy", `cloud-sync conflict copy of ${orig} (${days(age)} old)`);
6558
+ }
6559
+ }
6560
+ if (snap.syncQueueSlugs) {
6561
+ for (const f of snap.planMdFiles) {
6562
+ const slug = f.name.replace(/\.md$/, "");
6563
+ if (snap.syncQueueSlugs.has(slug)) continue;
6564
+ if (now - f.mtimeMs > PLAN_ADVISORY_AGE_MS) {
6565
+ candidates.push({
6566
+ path: f.path,
6567
+ family: "plan",
6568
+ tier: "advisory",
6569
+ reason: `synced plan older than ${Math.floor(PLAN_ADVISORY_AGE_MS / 864e5)}d`,
6570
+ bytes: f.bytes
6571
+ });
6572
+ }
6573
+ }
6574
+ }
6575
+ return {
6576
+ candidates,
6577
+ safeAuto: candidates.filter((c) => c.tier === "safe-auto"),
6578
+ advisory: candidates.filter((c) => c.tier === "advisory")
6579
+ };
6580
+ }
6581
+ function formatScratchGcPlan(plan2, applied) {
6582
+ if (plan2.candidates.length === 0) return "scratch GC: nothing to prune \u2014 local saga/plan/honcho scratch is clean.";
6583
+ const lines = [];
6584
+ const bytes = plan2.safeAuto.reduce((n, c) => n + c.bytes, 0);
6585
+ lines.push(
6586
+ `scratch GC: ${applied ? "pruned" : "would prune"} ${plan2.safeAuto.length} stale file(s) (${bytes} bytes)` + (plan2.advisory.length ? `; ${plan2.advisory.length} advisory (kept):` : ":")
6587
+ );
6588
+ for (const c of plan2.safeAuto.slice(0, 40)) lines.push(` - [${c.family}] ${c.path} \u2014 ${c.reason}`);
6589
+ if (plan2.safeAuto.length > 40) lines.push(` ... +${plan2.safeAuto.length - 40} more`);
6590
+ for (const c of plan2.advisory.slice(0, 20)) lines.push(` \xB7 [advisory] ${c.path} \u2014 ${c.reason} (kept; review manually)`);
6591
+ return lines.join("\n");
6592
+ }
6593
+ function scratchGcBannerLine(safeAutoPruned, advisory) {
6594
+ const parts = [];
6595
+ if (safeAutoPruned > 0) parts.push(`pruned ${safeAutoPruned} stale local file(s)`);
6596
+ if (advisory > 0) parts.push(`${advisory} old synced plan(s) prunable \u2014 \`mmi-cli gc --scratch --dry-run\` to review`);
6597
+ return parts.length ? `[cleanup] ${parts.join("; ")}` : void 0;
6598
+ }
6599
+ function applyScratchGc(plan2, mmiRoot, now = Date.now()) {
6600
+ const result = { pruned: [], skipped: 0, bytes: 0 };
6601
+ let anchor;
6602
+ try {
6603
+ anchor = (0, import_node_fs11.realpathSync)(mmiRoot).replace(/\\/g, "/").replace(/\/+$/, "");
6604
+ } catch {
6605
+ return result;
6606
+ }
6607
+ for (const c of plan2.safeAuto) {
6608
+ try {
6609
+ const real = (0, import_node_fs11.realpathSync)(c.path).replace(/\\/g, "/");
6610
+ if (real !== anchor && !real.startsWith(`${anchor}/`)) {
6611
+ result.skipped += 1;
6612
+ continue;
6613
+ }
6614
+ const st = (0, import_node_fs11.statSync)(c.path);
6615
+ const floor = c.family === "flush-lock" ? FLUSH_LOCK_GC_MS : c.family === "tmp-sidecar" ? TMP_STALE_MS : c.family === "head-ts" ? HEAD_TS_STALE_MS : c.family === "last-skip" ? LAST_SKIP_STALE_MS : CONFLICT_COPY_STALE_MS;
6616
+ if (now - st.mtimeMs <= floor) {
6617
+ result.skipped += 1;
6618
+ continue;
6619
+ }
6620
+ (0, import_node_fs11.unlinkSync)(c.path);
6621
+ result.pruned.push(c.path);
6622
+ result.bytes += c.bytes;
6623
+ } catch {
6624
+ result.skipped += 1;
6625
+ }
6626
+ }
6627
+ return result;
6628
+ }
6629
+ function collectScratchSnapshot(repoRoot, deps = {}) {
6630
+ const readdir = deps.readdir ?? import_node_fs11.readdirSync;
6631
+ const stat = deps.stat ?? import_node_fs11.statSync;
6632
+ const readFile5 = deps.readFile ?? import_node_fs11.readFileSync;
6633
+ const mmiRoot = (0, import_node_path9.join)(repoRoot, ".mmi");
6634
+ const plansRoot = (0, import_node_path9.join)(repoRoot, "plans");
6635
+ const mmiFiles = [];
6636
+ try {
6637
+ for (const ent of readdir(mmiRoot, { recursive: true, withFileTypes: true })) {
6638
+ if (!ent.isFile()) continue;
6639
+ const dir = ent.parentPath ?? ent.path ?? mmiRoot;
6640
+ const full = (0, import_node_path9.join)(dir, ent.name);
6641
+ try {
6642
+ const st = stat(full);
6643
+ mmiFiles.push({ path: full, dir, name: ent.name, mtimeMs: st.mtimeMs, bytes: st.size });
6644
+ } catch {
6645
+ }
6646
+ }
6647
+ } catch {
6648
+ }
6649
+ const planMdFiles = [];
6650
+ try {
6651
+ for (const ent of readdir(plansRoot, { withFileTypes: true })) {
6652
+ if (!ent.isFile() || !ent.name.endsWith(".md")) continue;
6653
+ const full = (0, import_node_path9.join)(plansRoot, ent.name);
6654
+ try {
6655
+ const st = stat(full);
6656
+ planMdFiles.push({ path: full, dir: plansRoot, name: ent.name, mtimeMs: st.mtimeMs, bytes: st.size });
6657
+ } catch {
6658
+ }
6659
+ }
6660
+ } catch {
6661
+ }
6662
+ let syncQueueSlugs = null;
6663
+ let queueRaw;
6664
+ try {
6665
+ queueRaw = readFile5((0, import_node_path9.join)(plansRoot, ".sync-queue.json"), "utf8");
6666
+ } catch {
6667
+ syncQueueSlugs = /* @__PURE__ */ new Set();
6668
+ }
6669
+ if (queueRaw !== void 0) {
6670
+ try {
6671
+ const parsed = JSON.parse(queueRaw);
6672
+ const entries = Array.isArray(parsed) ? parsed : parsed.entries ?? [];
6673
+ syncQueueSlugs = new Set(entries.map((e) => e.slug).filter((s) => typeof s === "string"));
6674
+ } catch {
6675
+ syncQueueSlugs = null;
6676
+ }
6677
+ }
6678
+ return { mmiRoot, mmiFiles, planMdFiles, syncQueueSlugs };
6679
+ }
6680
+
6681
+ // src/session-start.ts
6230
6682
  async function runBufferedStep(step) {
6231
6683
  const lines = [];
6232
6684
  const io = {
@@ -6254,6 +6706,7 @@ function buildSessionStartPlan(verbs) {
6254
6706
  parallel: [
6255
6707
  { name: "rules sync", run: verbs.rulesSync },
6256
6708
  { name: "saga show", run: verbs.sagaShow },
6709
+ { name: "handoff offer", run: verbs.handoffOffer },
6257
6710
  // honcho profile (#1162): the behavioral-memory prior, flushed right after the saga resume. A fast
6258
6711
  // peer-card GET, fail-soft + silent when off/empty — it never hangs or noises the banner.
6259
6712
  { name: "honcho profile", run: verbs.honchoContext },
@@ -6277,12 +6730,12 @@ function spawnDetachedSelf(args, deps) {
6277
6730
  }
6278
6731
  function planStoreLines(cwd) {
6279
6732
  const mdFiles = (dir, minSize = 0) => {
6280
- const p = (0, import_node_path9.join)(cwd, dir);
6281
- if (!(0, import_node_fs11.existsSync)(p)) return [];
6733
+ const p = (0, import_node_path10.join)(cwd, dir);
6734
+ if (!(0, import_node_fs12.existsSync)(p)) return [];
6282
6735
  try {
6283
- return (0, import_node_fs11.readdirSync)(p).filter((f) => f.toLowerCase().endsWith(".md")).filter((f) => {
6736
+ return (0, import_node_fs12.readdirSync)(p).filter((f) => f.toLowerCase().endsWith(".md")).filter((f) => {
6284
6737
  try {
6285
- return (0, import_node_fs11.statSync)((0, import_node_path9.join)(p, f)).size >= minSize;
6738
+ return (0, import_node_fs12.statSync)((0, import_node_path10.join)(p, f)).size >= minSize;
6286
6739
  } catch {
6287
6740
  return false;
6288
6741
  }
@@ -6300,6 +6753,19 @@ function planStoreLines(cwd) {
6300
6753
  out.push(`[plan-store] ${localPlans.length} local plan(s) in plans/ \u2014 ensure new/changed ones are \`mmi-cli plan push\`ed (S3-backed, not git).`);
6301
6754
  return out;
6302
6755
  }
6756
+ function scratchGcLines(cwd, env = process.env, now = Date.now()) {
6757
+ if (env.MMI_NO_AUTO_GC) return [];
6758
+ try {
6759
+ const stamp = scratchGcThrottlePath((0, import_node_path10.join)(cwd, ".mmi"));
6760
+ if (!scratchGcDue(stamp, now)) return [];
6761
+ const run = executeScratchGc(cwd, { apply: true }, now);
6762
+ markScratchGcRun(stamp, now);
6763
+ const line = scratchGcBannerLine(run.applied?.pruned.length ?? 0, run.plan.advisory.length);
6764
+ return line ? [line] : [];
6765
+ } catch {
6766
+ return [];
6767
+ }
6768
+ }
6303
6769
  function northstarPointer(injected = false) {
6304
6770
  if (injected) {
6305
6771
  return "North Stars: `mmi-cli northstar relevant` for more matches; `northstar pull <slug>` for the full SSOT.";
@@ -6347,6 +6813,71 @@ function recoverPriorityFromEvents(events) {
6347
6813
  return found;
6348
6814
  }
6349
6815
 
6816
+ // src/board-dependency.ts
6817
+ var DEPENDS_ON_LINE = /^\s*[-*]?\s*\*\*Depends on:\*\*\s*(.+)$/im;
6818
+ var ISSUE_REF = /([a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+)#(\d+)/g;
6819
+ function parseDependsOnRefs(body) {
6820
+ const match = body.match(DEPENDS_ON_LINE);
6821
+ if (!match) return [];
6822
+ const refs = [];
6823
+ for (const m of match[1].matchAll(ISSUE_REF)) {
6824
+ refs.push({ repo: m[1], number: Number(m[2]) });
6825
+ }
6826
+ return refs;
6827
+ }
6828
+ async function issueOpen(client, repo, number) {
6829
+ try {
6830
+ const data = await client.rest("GET", `repos/${repo}/issues/${number}`);
6831
+ return data?.state === "OPEN";
6832
+ } catch {
6833
+ return void 0;
6834
+ }
6835
+ }
6836
+ async function dependencyBlocksClaim(client, body) {
6837
+ const refs = parseDependsOnRefs(body);
6838
+ if (!refs.length) return { blocked: false, openDependencies: [] };
6839
+ const openDependencies = [];
6840
+ for (const ref of refs) {
6841
+ const open = await issueOpen(client, ref.repo, ref.number);
6842
+ if (open === true) openDependencies.push(`${ref.repo}#${ref.number}`);
6843
+ }
6844
+ return { blocked: openDependencies.length > 0, openDependencies };
6845
+ }
6846
+ async function filterDependencyBlockedClaimables(items, client, opts = {}) {
6847
+ const claimable = [];
6848
+ const blocked = [];
6849
+ const warnings = [];
6850
+ for (const item of items) {
6851
+ if (item.contentType !== "Issue") {
6852
+ claimable.push(item);
6853
+ continue;
6854
+ }
6855
+ let body = item.details?.body;
6856
+ if (body === void 0) {
6857
+ if (opts.requireBundledBody) {
6858
+ claimable.push(item);
6859
+ continue;
6860
+ }
6861
+ try {
6862
+ const data = await client.rest("GET", `repos/${item.repository}/issues/${item.number}`);
6863
+ body = data?.body ?? "";
6864
+ } catch (e) {
6865
+ warnings.push(`dependency check skipped for ${item.ref}: ${e.message}`);
6866
+ claimable.push(item);
6867
+ continue;
6868
+ }
6869
+ }
6870
+ const gate = await dependencyBlocksClaim(client, body);
6871
+ if (gate.blocked) {
6872
+ blocked.push(item);
6873
+ warnings.push(`${item.ref} blocked on open ${gate.openDependencies.join(", ")}`);
6874
+ } else {
6875
+ claimable.push(item);
6876
+ }
6877
+ }
6878
+ return { claimable, blocked, warnings };
6879
+ }
6880
+
6350
6881
  // ../infra/board-vocab.mjs
6351
6882
  var BOARD_STATUSES = ["Todo", "In Progress", "In Review", "Done"];
6352
6883
 
@@ -6670,6 +7201,13 @@ async function readBoard(options, deps = {}) {
6670
7201
  if (options.includeBundleDetails) {
6671
7202
  await attachBundleDetails(report, client, options.allowPartial ?? false);
6672
7203
  }
7204
+ for (const scope of ["primary", "secondary"]) {
7205
+ const filtered = await filterDependencyBlockedClaimables(report[scope].claimable, client, {
7206
+ requireBundledBody: Boolean(options.includeBundleDetails)
7207
+ });
7208
+ report[scope].claimable = filtered.claimable;
7209
+ report.warnings.push(...filtered.warnings);
7210
+ }
6673
7211
  return report;
6674
7212
  }
6675
7213
  function findBoardItem(items, selector) {
@@ -6751,6 +7289,11 @@ async function prepareClaimContext(options, selectors, deps, collected) {
6751
7289
  warnings: collected.warnings,
6752
7290
  partial: collected.partial
6753
7291
  };
7292
+ for (const scope of ["primary", "secondary"]) {
7293
+ const filtered = await filterDependencyBlockedClaimables(report[scope].claimable, client);
7294
+ report[scope].claimable = filtered.claimable;
7295
+ report.warnings.push(...filtered.warnings);
7296
+ }
6754
7297
  return { cfg, client, items: collected.items, writable: writable.repos, report };
6755
7298
  }
6756
7299
  async function claimOneBoardItem(ctx, selector, options) {
@@ -6759,6 +7302,21 @@ async function claimOneBoardItem(ctx, selector, options) {
6759
7302
  if (flatItem.status === "Todo" && flatItem.assignees.length === 0 && !ctx.writable.has(flatItem.repository.toLowerCase())) {
6760
7303
  throw new Error(`${flatItem.ref} is not claimable: viewer does not have write access to ${flatItem.repository}`);
6761
7304
  }
7305
+ if (flatItem.contentType === "Issue") {
7306
+ let body = flatItem.details?.body;
7307
+ if (body === void 0) {
7308
+ try {
7309
+ const data = await client.rest("GET", `repos/${flatItem.repository}/issues/${flatItem.number}`);
7310
+ body = data?.body ?? "";
7311
+ } catch {
7312
+ body = "";
7313
+ }
7314
+ }
7315
+ const gate = await dependencyBlocksClaim(client, body);
7316
+ if (gate.blocked) {
7317
+ throw new Error(`${flatItem.ref} is not claimable: blocked on open ${gate.openDependencies.join(", ")}`);
7318
+ }
7319
+ }
6762
7320
  let item = findClaimableItem(report, selector);
6763
7321
  if (item.contentType !== "Issue") throw new Error(`${item.ref} is not an issue`);
6764
7322
  const fresh = (await fetchIssueProjectItem(client, cfg, { repo: item.repository, number: item.number })).item;
@@ -7209,6 +7767,109 @@ async function runBoardSlice(io, deps, opts) {
7209
7767
  }
7210
7768
  }
7211
7769
 
7770
+ // src/worktree.ts
7771
+ var import_node_fs13 = require("node:fs");
7772
+ var import_node_path11 = require("node:path");
7773
+ var LOCAL_ONLY_FILES = [".claude/settings.local.json"];
7774
+ var PKG = "package.json";
7775
+ var LOCKFILE = "package-lock.json";
7776
+ var NODE_MODULES = "node_modules";
7777
+ var realFsProbe = {
7778
+ isDir: (p) => {
7779
+ try {
7780
+ return (0, import_node_fs13.statSync)(p).isDirectory();
7781
+ } catch {
7782
+ return false;
7783
+ }
7784
+ },
7785
+ isFile: (p) => {
7786
+ try {
7787
+ return (0, import_node_fs13.statSync)(p).isFile();
7788
+ } catch {
7789
+ return false;
7790
+ }
7791
+ },
7792
+ listDirs: (p) => {
7793
+ try {
7794
+ return (0, import_node_fs13.readdirSync)(p, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
7795
+ } catch {
7796
+ return [];
7797
+ }
7798
+ }
7799
+ };
7800
+ function scanInstallDirs(root, fs2 = realFsProbe) {
7801
+ const factsFor = (dir) => {
7802
+ const abs = dir ? (0, import_node_path11.join)(root, dir) : root;
7803
+ return {
7804
+ dir,
7805
+ hasPackageJson: fs2.isFile((0, import_node_path11.join)(abs, PKG)),
7806
+ hasLockfile: fs2.isFile((0, import_node_path11.join)(abs, LOCKFILE)),
7807
+ hasNodeModules: fs2.isDir((0, import_node_path11.join)(abs, NODE_MODULES))
7808
+ };
7809
+ };
7810
+ const children = fs2.listDirs(root).filter((name) => name !== NODE_MODULES && name !== ".git");
7811
+ return [factsFor(""), ...children.map(factsFor).filter((f) => f.hasPackageJson)];
7812
+ }
7813
+ function npmInstallTargets(dirs) {
7814
+ return dirs.filter((d) => d.hasPackageJson && !d.hasNodeModules).map((d) => ({ dir: d.dir, command: d.hasLockfile ? "npm ci" : "npm install" }));
7815
+ }
7816
+ function isLinkedWorktree(root, fs2 = realFsProbe) {
7817
+ return fs2.isFile((0, import_node_path11.join)(root, ".git"));
7818
+ }
7819
+ function worktreeAutoProvisionBanner(root, fs2 = realFsProbe) {
7820
+ if (!isLinkedWorktree(root, fs2)) return null;
7821
+ const pending = npmInstallTargets(scanInstallDirs(root, fs2));
7822
+ if (!pending.length) return null;
7823
+ const where = pending.map((t) => t.dir || ".").join(", ");
7824
+ return `[worktree] provisioning tooling in the background (deps in ${where} + local config) \u2014 \`mmi-cli worktree setup\` to redo`;
7825
+ }
7826
+ function defaultCopyFile(from, to) {
7827
+ (0, import_node_fs13.mkdirSync)((0, import_node_path11.dirname)(to), { recursive: true });
7828
+ (0, import_node_fs13.copyFileSync)(from, to);
7829
+ }
7830
+ async function provisionWorktree(worktreeRoot, deps) {
7831
+ const fs2 = deps.fs ?? realFsProbe;
7832
+ const copyFile = deps.copyFile ?? defaultCopyFile;
7833
+ const log = deps.log ?? (() => {
7834
+ });
7835
+ const allDirs = scanInstallDirs(worktreeRoot, fs2);
7836
+ const targets = npmInstallTargets(allDirs);
7837
+ const skippedInstall = allDirs.filter((d) => d.hasPackageJson && d.hasNodeModules).map((d) => d.dir);
7838
+ const installed = [];
7839
+ for (const target of targets) {
7840
+ const cwd = target.dir ? (0, import_node_path11.join)(worktreeRoot, target.dir) : worktreeRoot;
7841
+ log(`installing deps: ${target.command} in ${target.dir || "."}`);
7842
+ await deps.runInstall(target.command, cwd);
7843
+ installed.push(target);
7844
+ }
7845
+ const copied = [];
7846
+ const copySkipped = [];
7847
+ const primary = await deps.primaryCheckout();
7848
+ for (const rel of LOCAL_ONLY_FILES) {
7849
+ const dest = (0, import_node_path11.join)(worktreeRoot, rel);
7850
+ if (fs2.isFile(dest)) {
7851
+ copySkipped.push({ file: rel, reason: "already-present" });
7852
+ continue;
7853
+ }
7854
+ if (!primary) {
7855
+ copySkipped.push({ file: rel, reason: "no-primary" });
7856
+ continue;
7857
+ }
7858
+ if (!fs2.isFile((0, import_node_path11.join)(primary, rel))) {
7859
+ copySkipped.push({ file: rel, reason: "absent-in-primary" });
7860
+ continue;
7861
+ }
7862
+ copyFile((0, import_node_path11.join)(primary, rel), dest);
7863
+ copied.push(rel);
7864
+ log(`copied local config: ${rel}`);
7865
+ }
7866
+ return { worktree: worktreeRoot, installed, skippedInstall, copied, copySkipped };
7867
+ }
7868
+ function defaultWorktreePath(repoRoot, branch) {
7869
+ const safe = branch.replace(/[/\\]+/g, "-");
7870
+ return (0, import_node_path11.join)((0, import_node_path11.dirname)(repoRoot), "mmi-worktrees", safe);
7871
+ }
7872
+
7212
7873
  // src/frontmatter.ts
7213
7874
  function splitFrontmatter(content) {
7214
7875
  const match = /^---\n([\s\S]*?)\n---(?:\n|$)/.exec(content);
@@ -7404,7 +8065,7 @@ async function runNorthstarContext(io, deps) {
7404
8065
  }
7405
8066
 
7406
8067
  // src/index.ts
7407
- var import_node_path14 = require("node:path");
8068
+ var import_node_path16 = require("node:path");
7408
8069
 
7409
8070
  // src/merge-ci-policy.ts
7410
8071
  function resolveMergeCiPolicy(input) {
@@ -7816,8 +8477,8 @@ var import_node_os5 = require("node:os");
7816
8477
  // src/gh-create.ts
7817
8478
  var import_promises4 = require("node:fs/promises");
7818
8479
  var import_node_os3 = require("node:os");
7819
- var import_node_path10 = require("node:path");
7820
- var import_node_crypto5 = require("node:crypto");
8480
+ var import_node_path12 = require("node:path");
8481
+ var import_node_crypto6 = require("node:crypto");
7821
8482
  var ISSUE_TYPES = ["bug", "feature", "task"];
7822
8483
  var GH_MUTATION_TIMEOUT_MS = 12e4;
7823
8484
  function timeoutKillNote(err, timeoutMs) {
@@ -7857,7 +8518,7 @@ async function bodyArgsViaFile(args, deps = {}) {
7857
8518
  } };
7858
8519
  const write = deps.write ?? import_promises4.writeFile;
7859
8520
  const remove = deps.remove ?? import_promises4.unlink;
7860
- const file = (0, import_node_path10.join)(deps.dir ?? (0, import_node_os3.tmpdir)(), `mmi-gh-body-${process.pid}-${(0, import_node_crypto5.randomBytes)(4).toString("hex")}.md`);
8521
+ const file = (0, import_node_path12.join)(deps.dir ?? (0, import_node_os3.tmpdir)(), `mmi-gh-body-${process.pid}-${(0, import_node_crypto6.randomBytes)(4).toString("hex")}.md`);
7861
8522
  await write(file, args[i + 1], "utf8");
7862
8523
  return {
7863
8524
  args: [...args.slice(0, i), "--body-file", file, ...args.slice(i + 2)],
@@ -8182,7 +8843,7 @@ ${buildReportBody(body, sourceRepo)}`;
8182
8843
 
8183
8844
  // src/skill-lesson.ts
8184
8845
  var SKILL_LESSON_LABEL = "skill-lesson";
8185
- var SKILL_NAMES = ["bootstrap", "browser-automation", "build", "grind", "hotfix", "mmi", "rcand", "release", "secrets", "stage"];
8846
+ var SKILL_NAMES = ["bootstrap", "browser-automation", "build", "grind", "handoff", "hotfix", "mmi", "rcand", "release", "secrets", "stage"];
8186
8847
  function assertSkillName(name) {
8187
8848
  const match = SKILL_NAMES.find((skill) => skill === name);
8188
8849
  if (!match) throw new Error(`unknown skill "${name}" \u2014 expected one of: ${SKILL_NAMES.join(", ")}`);
@@ -8682,12 +9343,12 @@ function buildGcPlan(inputs) {
8682
9343
  skipped.push({ branch, reason: "current-branch" });
8683
9344
  continue;
8684
9345
  }
8685
- const worktree = worktrees.get(branch);
8686
- if (worktree?.dirty) {
8687
- skipped.push({ branch, reason: "dirty-worktree", detail: worktree.path });
9346
+ const worktree2 = worktrees.get(branch);
9347
+ if (worktree2?.dirty) {
9348
+ skipped.push({ branch, reason: "dirty-worktree", detail: worktree2.path });
8688
9349
  continue;
8689
9350
  }
8690
- branches.push({ branch, prState: state.state, prNumbers: state.numbers, worktreePath: worktree?.path });
9351
+ branches.push({ branch, prState: state.state, prNumbers: state.numbers, worktreePath: worktree2?.path });
8691
9352
  }
8692
9353
  const trackingRefs = [...new Set(inputs.staleTrackingRefs ?? [])].map((ref) => {
8693
9354
  const branch = branchForTrackingRef(ref, remote);
@@ -9226,9 +9887,9 @@ function stalePosixFields(config, shell2) {
9226
9887
  }
9227
9888
  function sanitizeLocalStage(local, stale) {
9228
9889
  if (!stale.length) return local;
9229
- const clean3 = { ...local };
9230
- for (const field of stale) delete clean3[field];
9231
- return clean3;
9890
+ const clean4 = { ...local };
9891
+ for (const field of stale) delete clean4[field];
9892
+ return clean4;
9232
9893
  }
9233
9894
  function staleNote(staleFields, outcome) {
9234
9895
  const list = staleFields.join(", ");
@@ -9274,9 +9935,9 @@ function decideStage(inputs) {
9274
9935
 
9275
9936
  // src/cursor-plugin-seed.ts
9276
9937
  var import_node_child_process7 = require("node:child_process");
9277
- var import_node_fs12 = require("node:fs");
9938
+ var import_node_fs14 = require("node:fs");
9278
9939
  var import_node_os4 = require("node:os");
9279
- var import_node_path11 = require("node:path");
9940
+ var import_node_path13 = require("node:path");
9280
9941
  var import_node_util6 = require("node:util");
9281
9942
  function isSemverVersion(v) {
9282
9943
  return typeof v === "string" && /^v?\d+\.\d+\.\d+/.test(v.trim());
@@ -9293,17 +9954,17 @@ function ghReleaseTarballApiArgs(tag) {
9293
9954
  }
9294
9955
  function cursorUserGlobalStatePath() {
9295
9956
  if (process.platform === "win32") {
9296
- const base2 = process.env.APPDATA || (0, import_node_path11.join)((0, import_node_os4.homedir)(), "AppData", "Roaming");
9297
- return (0, import_node_path11.join)(base2, "Cursor", "User", "globalStorage", "state.vscdb");
9957
+ const base2 = process.env.APPDATA || (0, import_node_path13.join)((0, import_node_os4.homedir)(), "AppData", "Roaming");
9958
+ return (0, import_node_path13.join)(base2, "Cursor", "User", "globalStorage", "state.vscdb");
9298
9959
  }
9299
9960
  if (process.platform === "darwin") {
9300
- return (0, import_node_path11.join)((0, import_node_os4.homedir)(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
9961
+ return (0, import_node_path13.join)((0, import_node_os4.homedir)(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
9301
9962
  }
9302
- return (0, import_node_path11.join)((0, import_node_os4.homedir)(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
9963
+ return (0, import_node_path13.join)((0, import_node_os4.homedir)(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
9303
9964
  }
9304
9965
  async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
9305
9966
  const dbPath = cursorUserGlobalStatePath();
9306
- if (!(0, import_node_fs12.existsSync)(dbPath)) return void 0;
9967
+ if (!(0, import_node_fs14.existsSync)(dbPath)) return void 0;
9307
9968
  try {
9308
9969
  const { stdout } = await execFileP5("sqlite3", [dbPath, `SELECT value FROM ItemTable WHERE key = '${CURSOR_THIRD_PARTY_STATE_KEY}';`], {
9309
9970
  timeout: 5e3
@@ -9317,57 +9978,57 @@ async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
9317
9978
  }
9318
9979
  }
9319
9980
  function syncDirContents(src, dest) {
9320
- (0, import_node_fs12.mkdirSync)(dest, { recursive: true });
9321
- for (const name of (0, import_node_fs12.readdirSync)(dest)) {
9322
- (0, import_node_fs12.rmSync)((0, import_node_path11.join)(dest, name), { recursive: true, force: true });
9981
+ (0, import_node_fs14.mkdirSync)(dest, { recursive: true });
9982
+ for (const name of (0, import_node_fs14.readdirSync)(dest)) {
9983
+ (0, import_node_fs14.rmSync)((0, import_node_path13.join)(dest, name), { recursive: true, force: true });
9323
9984
  }
9324
- (0, import_node_fs12.cpSync)(src, dest, { recursive: true });
9985
+ (0, import_node_fs14.cpSync)(src, dest, { recursive: true });
9325
9986
  }
9326
9987
  function releaseTag(releasedVersion) {
9327
9988
  return releasedVersion.startsWith("v") ? releasedVersion : `v${releasedVersion}`;
9328
9989
  }
9329
9990
  async function extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5) {
9330
- const tarFile = (0, import_node_path11.join)(tmpRoot, "archive.tar");
9991
+ const tarFile = (0, import_node_path13.join)(tmpRoot, "archive.tar");
9331
9992
  try {
9332
9993
  await execFileP5("git", gitFetchReleaseTagArgs(hubCheckout, tag), { timeout: 6e4 });
9333
9994
  await execFileP5("git", ["-C", hubCheckout, "archive", "--format=tar", `--output=${tarFile}`, tag, "plugins/mmi"], {
9334
9995
  timeout: 6e4
9335
9996
  });
9336
9997
  await execFileP5("tar", ["-xf", tarFile, "-C", tmpRoot], { timeout: 6e4 });
9337
- const pluginMmi = (0, import_node_path11.join)(tmpRoot, "plugins", "mmi");
9338
- return (0, import_node_fs12.existsSync)((0, import_node_path11.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
9998
+ const pluginMmi = (0, import_node_path13.join)(tmpRoot, "plugins", "mmi");
9999
+ return (0, import_node_fs14.existsSync)((0, import_node_path13.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
9339
10000
  } catch {
9340
10001
  return void 0;
9341
10002
  }
9342
10003
  }
9343
10004
  async function downloadPluginMmiViaGh(tag, tmpRoot) {
9344
- const tarPath = (0, import_node_path11.join)(tmpRoot, "repo.tgz");
10005
+ const tarPath = (0, import_node_path13.join)(tmpRoot, "repo.tgz");
9345
10006
  try {
9346
- (0, import_node_fs12.mkdirSync)(tmpRoot, { recursive: true });
10007
+ (0, import_node_fs14.mkdirSync)(tmpRoot, { recursive: true });
9347
10008
  const { stdout } = await execFileBuffer("gh", ghReleaseTarballApiArgs(tag), {
9348
10009
  timeout: 12e4,
9349
10010
  maxBuffer: 100 * 1024 * 1024,
9350
10011
  encoding: "buffer",
9351
10012
  windowsHide: true
9352
10013
  });
9353
- (0, import_node_fs12.writeFileSync)(tarPath, stdout);
10014
+ (0, import_node_fs14.writeFileSync)(tarPath, stdout);
9354
10015
  await execFileBuffer("tar", ["-xzf", tarPath, "-C", tmpRoot], { timeout: 12e4, windowsHide: true });
9355
- const top = (0, import_node_fs12.readdirSync)(tmpRoot).find((entry) => entry !== "repo.tgz");
10016
+ const top = (0, import_node_fs14.readdirSync)(tmpRoot).find((entry) => entry !== "repo.tgz");
9356
10017
  if (!top) return void 0;
9357
- const pluginMmi = (0, import_node_path11.join)(tmpRoot, top, "plugins", "mmi");
9358
- return (0, import_node_fs12.existsSync)((0, import_node_path11.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
10018
+ const pluginMmi = (0, import_node_path13.join)(tmpRoot, top, "plugins", "mmi");
10019
+ return (0, import_node_fs14.existsSync)((0, import_node_path13.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
9359
10020
  } catch {
9360
10021
  return void 0;
9361
10022
  }
9362
10023
  }
9363
10024
  async function resolvePluginMmiSource(releasedVersion, hubCheckout, tmpRoot, execFileP5) {
9364
- (0, import_node_fs12.mkdirSync)(tmpRoot, { recursive: true });
10025
+ (0, import_node_fs14.mkdirSync)(tmpRoot, { recursive: true });
9365
10026
  const tag = releaseTag(releasedVersion);
9366
10027
  if (hubCheckout) {
9367
10028
  const fromHub = await extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5);
9368
10029
  if (fromHub) return fromHub;
9369
10030
  }
9370
- return downloadPluginMmiViaGh(tag, (0, import_node_path11.join)(tmpRoot, "gh"));
10031
+ return downloadPluginMmiViaGh(tag, (0, import_node_path13.join)(tmpRoot, "gh"));
9371
10032
  }
9372
10033
  function cursorPluginPinsNeedingSeed(pins, releasedVersion) {
9373
10034
  if (!isSemverVersion(releasedVersion)) return pins.filter((pin) => !pin.hasPluginJson || !pin.hasHooksJson || pin.isEmpty);
@@ -9388,7 +10049,7 @@ async function applyCursorPluginCacheSeed(input) {
9388
10049
  for (const pin of pinsToSeed) {
9389
10050
  syncDirContents(source, pin.path);
9390
10051
  }
9391
- (0, import_node_fs12.rmSync)(tmpRoot, { recursive: true, force: true });
10052
+ (0, import_node_fs14.rmSync)(tmpRoot, { recursive: true, force: true });
9392
10053
  return true;
9393
10054
  }
9394
10055
 
@@ -9436,8 +10097,12 @@ var MANAGED_GITIGNORE_LINES = [
9436
10097
  ".claude/worktrees/",
9437
10098
  ".mmi/.session",
9438
10099
  ".mmi/head-ts/",
9439
- // Recursive so any reuse of the parameterized queue engine in a new .mmi subdir (honcho today, others
9440
- // later) is ignored too `**` matches zero dirs, so this still covers the root saga queue.
10100
+ // Honcho's whole runtime dir its ingest-throttle stamp (`last-skip.json`) is NOT a queue file, so the
10101
+ // recursive patterns below miss it and it dirtied the working tree (blocked a release clean-tree check,
10102
+ // #1472). The dir is pure scratch, so ignore it wholesale, like `.mmi/head-ts/`.
10103
+ ".mmi/honcho/",
10104
+ // Recursive so any reuse of the parameterized queue engine in a new .mmi subdir is ignored too —
10105
+ // `**` matches zero dirs, so this still covers the root saga queue.
9441
10106
  ".mmi/**/saga-pending.jsonl*",
9442
10107
  ".mmi/**/saga-flush.lock",
9443
10108
  ".aws-sam/",
@@ -10106,8 +10771,8 @@ async function runStageLiveDown(deps, t) {
10106
10771
 
10107
10772
  // src/stage-runner.ts
10108
10773
  var import_node_child_process8 = require("node:child_process");
10109
- var import_node_fs13 = require("node:fs");
10110
- var import_node_path12 = require("node:path");
10774
+ var import_node_fs15 = require("node:fs");
10775
+ var import_node_path14 = require("node:path");
10111
10776
  var import_node_net2 = require("node:net");
10112
10777
  var import_node_util7 = require("node:util");
10113
10778
  var execFileP4 = (0, import_node_util7.promisify)(import_node_child_process8.execFile);
@@ -10152,7 +10817,7 @@ function detectStaleEnvFile(exampleContent, targetContent, mtimes) {
10152
10817
  return void 0;
10153
10818
  }
10154
10819
  function stageStatePath(cwd = process.cwd()) {
10155
- return (0, import_node_path12.join)(cwd, "tmp", "stage", "state.json");
10820
+ return (0, import_node_path14.join)(cwd, "tmp", "stage", "state.json");
10156
10821
  }
10157
10822
  function mergeEnvSecretsIntoFile(content, secrets2) {
10158
10823
  const lines = content.split(/\r?\n/);
@@ -10235,9 +10900,9 @@ async function shell(command, cwd, timeoutMs) {
10235
10900
  });
10236
10901
  }
10237
10902
  function readState(path2) {
10238
- if (!(0, import_node_fs13.existsSync)(path2)) return null;
10903
+ if (!(0, import_node_fs15.existsSync)(path2)) return null;
10239
10904
  try {
10240
- return JSON.parse((0, import_node_fs13.readFileSync)(path2, "utf8"));
10905
+ return JSON.parse((0, import_node_fs15.readFileSync)(path2, "utf8"));
10241
10906
  } catch {
10242
10907
  return null;
10243
10908
  }
@@ -10289,7 +10954,7 @@ async function stopStage(opts = {}) {
10289
10954
  return { ok: true, action: "stop", statePath, message: "no previous stage state found" };
10290
10955
  }
10291
10956
  await killTree(state.pid);
10292
- (0, import_node_fs13.rmSync)(statePath, { force: true });
10957
+ (0, import_node_fs15.rmSync)(statePath, { force: true });
10293
10958
  return { ok: true, action: "stop", statePath, pid: state.pid, message: `stopped previous stage pid ${state.pid}` };
10294
10959
  }
10295
10960
  async function startStage(config = {}, opts = {}) {
@@ -10298,7 +10963,7 @@ async function startStage(config = {}, opts = {}) {
10298
10963
  const cwd = opts.cwd ?? process.cwd();
10299
10964
  const statePath = opts.statePath ?? stageStatePath(cwd);
10300
10965
  const dir = statePath.slice(0, Math.max(statePath.lastIndexOf("/"), statePath.lastIndexOf("\\")));
10301
- (0, import_node_fs13.mkdirSync)(dir, { recursive: true });
10966
+ (0, import_node_fs15.mkdirSync)(dir, { recursive: true });
10302
10967
  let stagePort;
10303
10968
  if (config.portRange) {
10304
10969
  const [s, e] = config.portRange;
@@ -10308,14 +10973,14 @@ async function startStage(config = {}, opts = {}) {
10308
10973
  }
10309
10974
  const sub = (s) => s != null && stagePort != null ? s.replace(/\$\{?STAGE_PORT\}?/g, String(stagePort)) : s;
10310
10975
  if (config.ensureEnv) {
10311
- const target = (0, import_node_path12.join)(cwd, config.ensureEnv.target);
10312
- const example = (0, import_node_path12.join)(cwd, config.ensureEnv.example);
10313
- if (!(0, import_node_fs13.existsSync)(target) && (0, import_node_fs13.existsSync)(example)) {
10314
- (0, import_node_fs13.copyFileSync)(example, target);
10315
- } else if ((0, import_node_fs13.existsSync)(target) && (0, import_node_fs13.existsSync)(example)) {
10316
- const stale = detectStaleEnvFile((0, import_node_fs13.readFileSync)(example, "utf8"), (0, import_node_fs13.readFileSync)(target, "utf8"), {
10317
- exampleMtimeMs: (0, import_node_fs13.statSync)(example).mtimeMs,
10318
- targetMtimeMs: (0, import_node_fs13.statSync)(target).mtimeMs
10976
+ const target = (0, import_node_path14.join)(cwd, config.ensureEnv.target);
10977
+ const example = (0, import_node_path14.join)(cwd, config.ensureEnv.example);
10978
+ if (!(0, import_node_fs15.existsSync)(target) && (0, import_node_fs15.existsSync)(example)) {
10979
+ (0, import_node_fs15.copyFileSync)(example, target);
10980
+ } else if ((0, import_node_fs15.existsSync)(target) && (0, import_node_fs15.existsSync)(example)) {
10981
+ const stale = detectStaleEnvFile((0, import_node_fs15.readFileSync)(example, "utf8"), (0, import_node_fs15.readFileSync)(target, "utf8"), {
10982
+ exampleMtimeMs: (0, import_node_fs15.statSync)(example).mtimeMs,
10983
+ targetMtimeMs: (0, import_node_fs15.statSync)(target).mtimeMs
10319
10984
  });
10320
10985
  if (stale) {
10321
10986
  const msg = `stale ${config.ensureEnv.target} (${stale}) \u2014 delete it or refresh from ${config.ensureEnv.example} before re-running /stage`;
@@ -10323,8 +10988,8 @@ async function startStage(config = {}, opts = {}) {
10323
10988
  console.error(`mmi-cli stage: ${msg} (allowed via --allow-stale-env)`);
10324
10989
  }
10325
10990
  }
10326
- if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0, import_node_fs13.existsSync)(target)) {
10327
- (0, import_node_fs13.writeFileSync)(target, mergeEnvSecretsIntoFile((0, import_node_fs13.readFileSync)(target, "utf8"), opts.vaultEnvMerge), "utf8");
10991
+ if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0, import_node_fs15.existsSync)(target)) {
10992
+ (0, import_node_fs15.writeFileSync)(target, mergeEnvSecretsIntoFile((0, import_node_fs15.readFileSync)(target, "utf8"), opts.vaultEnvMerge), "utf8");
10328
10993
  }
10329
10994
  }
10330
10995
  const extraEnv = {};
@@ -10349,13 +11014,13 @@ async function startStage(config = {}, opts = {}) {
10349
11014
  healthUrl: sub(config.healthUrl?.trim()) || void 0,
10350
11015
  port: stagePort
10351
11016
  };
10352
- (0, import_node_fs13.writeFileSync)(statePath, JSON.stringify(state, null, 2), "utf8");
11017
+ (0, import_node_fs15.writeFileSync)(statePath, JSON.stringify(state, null, 2), "utf8");
10353
11018
  try {
10354
11019
  if (state.healthUrl) await waitForHealth(state.healthUrl, opts.timeoutMs ?? 6e4, config.healthAnyStatus);
10355
11020
  else await waitForProcessStability(child);
10356
11021
  } catch (e) {
10357
11022
  await killTree(state.pid);
10358
- (0, import_node_fs13.rmSync)(statePath, { force: true });
11023
+ (0, import_node_fs15.rmSync)(statePath, { force: true });
10359
11024
  throw e;
10360
11025
  }
10361
11026
  const result = { ok: true, action: "start", statePath, pid: state.pid, port: stagePort, message: `started stage pid ${state.pid}${stagePort != null ? ` on port ${stagePort}` : ""}` };
@@ -10471,7 +11136,7 @@ function resolveDeployModel2(meta, repo) {
10471
11136
  if (isDeployModel(m)) return m;
10472
11137
  return resolveDeployModel(meta, repo);
10473
11138
  }
10474
- function clean(out) {
11139
+ function clean2(out) {
10475
11140
  return out.trim();
10476
11141
  }
10477
11142
  function requireValue(value, label) {
@@ -10522,11 +11187,11 @@ async function verifyPublishedRelease(deps, repo, tag, expectedTarget, expectedS
10522
11187
  if (release.targetCommitish !== expectedTarget) {
10523
11188
  throw new Error(`Release ${tag} targetCommitish is ${String(release.targetCommitish || "(missing)")}, expected ${expectedTarget}`);
10524
11189
  }
10525
- const tagSha = requireValue(clean(await deps.run("git", ["rev-parse", `${tag}^{commit}`])), "release tag sha");
11190
+ const tagSha = requireValue(clean2(await deps.run("git", ["rev-parse", `${tag}^{commit}`])), "release tag sha");
10526
11191
  if (tagSha !== expectedSha) {
10527
11192
  throw new Error(`Release ${tag} tag points at ${tagSha}, expected ${expectedSha}`);
10528
11193
  }
10529
- const branchOut = clean(await deps.run("git", ["ls-remote", "origin", `refs/heads/${expectedTarget}`]));
11194
+ const branchOut = clean2(await deps.run("git", ["ls-remote", "origin", `refs/heads/${expectedTarget}`]));
10530
11195
  const branchSha = requireValue(branchOut.split(/\s+/)[0] ?? "", `origin/${expectedTarget} sha`);
10531
11196
  if (branchSha !== expectedSha) {
10532
11197
  throw new Error(`origin/${expectedTarget} points at ${branchSha}, expected ${expectedSha}`);
@@ -10586,7 +11251,7 @@ function ensurePositiveCount(out, emptyMessage) {
10586
11251
  if (count <= 0) throw new Error(emptyMessage);
10587
11252
  }
10588
11253
  async function remoteBranchExists(deps, branch) {
10589
- const out = clean(await deps.run("git", ["ls-remote", "origin", `refs/heads/${branch}`]));
11254
+ const out = clean2(await deps.run("git", ["ls-remote", "origin", `refs/heads/${branch}`]));
10590
11255
  return out.length > 0;
10591
11256
  }
10592
11257
  async function loadReleaseTrackBranchHints(deps) {
@@ -10598,10 +11263,10 @@ async function loadReleaseTrackBranchHints(deps) {
10598
11263
  return { hasDevelopmentBranch, hasMainBranch, hasRcBranch };
10599
11264
  }
10600
11265
  async function buildTrainApplyContext(deps) {
10601
- const repo = requireValue(clean(await deps.run("gh", ["repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner"])), "repo");
11266
+ const repo = requireValue(clean2(await deps.run("gh", ["repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner"])), "repo");
10602
11267
  const [owner, name] = repo.split("/");
10603
11268
  if (!owner || !name) throw new Error(`repo must be owner/name, got ${repo}`);
10604
- const login = requireValue(clean(await deps.run("gh", ["api", "user", "--jq", ".login"])), "GitHub login");
11269
+ const login = requireValue(clean2(await deps.run("gh", ["api", "user", "--jq", ".login"])), "GitHub login");
10605
11270
  const verdict = await deps.trainAuthority(repo);
10606
11271
  if (!verdict.ok) throw new Error(`${commandAuthorityLabel(owner)}: train authority could not be verified (${verdict.error})`);
10607
11272
  if (!verdict.train) {
@@ -10622,7 +11287,7 @@ async function requireCleanTree(deps) {
10622
11287
  if (status.trim()) throw new Error("working tree must be clean before train apply");
10623
11288
  }
10624
11289
  async function requireBranch(deps, branch) {
10625
- const current = clean(await deps.run("git", ["rev-parse", "--abbrev-ref", "HEAD"]));
11290
+ const current = clean2(await deps.run("git", ["rev-parse", "--abbrev-ref", "HEAD"]));
10626
11291
  if (current !== branch) throw new Error(`must run from ${branch}, currently on ${current || "(unknown)"}`);
10627
11292
  }
10628
11293
  var HUB_REPO3 = "mutmutco/MMI-Hub";
@@ -10791,7 +11456,7 @@ async function discoverRequiredCheckContexts(deps, ctx, branch) {
10791
11456
  return [...contexts];
10792
11457
  }
10793
11458
  async function findOpenAlignmentPr(deps, ctx) {
10794
- const out = clean(await deps.run("gh", ["pr", "list", "--repo", ctx.repo, "--base", "development", "--head", "main", "--state", "open", "--json", "number,url"]));
11459
+ const out = clean2(await deps.run("gh", ["pr", "list", "--repo", ctx.repo, "--base", "development", "--head", "main", "--state", "open", "--json", "number,url"]));
10795
11460
  if (!out) return void 0;
10796
11461
  const rows = JSON.parse(out);
10797
11462
  const row = rows.find((r) => typeof r.number === "number" && typeof r.url === "string");
@@ -10819,14 +11484,14 @@ async function rollDevelopmentForward(deps, ctx, tag) {
10819
11484
  note: `alignment PR already open: ${existing.url} \u2014 land it with \`gh pr merge ${existing.number} --merge\``
10820
11485
  };
10821
11486
  }
10822
- const ahead = clean(await deps.run("git", ["rev-list", "--count", "origin/development..main"]));
11487
+ const ahead = clean2(await deps.run("git", ["rev-list", "--count", "origin/development..main"]));
10823
11488
  if (ahead === "0") {
10824
11489
  return { status: "aligned", note: "development already contains the released main; nothing to roll forward" };
10825
11490
  }
10826
11491
  const body = `Carries the ${tag} release (including the version fold) from \`main\` back to \`development\`.
10827
11492
 
10828
11493
  \`development\` requires status checks, so the release train opens this alignment PR instead of a direct push of the un-checked merge commit (#1143). Land it with a **true merge** (\`gh pr merge --merge\`, not squash) so the merge parentage survives and the misalignment guard stays satisfied.`;
10829
- const url = clean(await deps.run("gh", ["pr", "create", "--repo", ctx.repo, "--base", "development", "--head", "main", "--title", `chore(release): align development to ${tag}`, "--body", body]));
11494
+ const url = clean2(await deps.run("gh", ["pr", "create", "--repo", ctx.repo, "--base", "development", "--head", "main", "--title", `chore(release): align development to ${tag}`, "--body", body]));
10830
11495
  const number = parsePrNumber(url);
10831
11496
  return {
10832
11497
  status: "pr-pending",
@@ -10892,10 +11557,10 @@ async function waitForRequiredTrainChecks(deps, ctx, sha, required) {
10892
11557
  }
10893
11558
  async function ensureTagPushed(deps, tag, sha) {
10894
11559
  const remoteOut = await deps.run("git", ["ls-remote", "origin", `refs/tags/${tag}`]);
10895
- const remoteSha = clean(remoteOut).split(/\s+/)[0] || "";
11560
+ const remoteSha = clean2(remoteOut).split(/\s+/)[0] || "";
10896
11561
  let localSha = "";
10897
11562
  try {
10898
- localSha = clean(await deps.run("git", ["rev-parse", "--verify", `refs/tags/${tag}^{commit}`]));
11563
+ localSha = clean2(await deps.run("git", ["rev-parse", "--verify", `refs/tags/${tag}^{commit}`]));
10899
11564
  } catch {
10900
11565
  }
10901
11566
  if (remoteSha) {
@@ -10918,7 +11583,7 @@ async function ensureTagPushed(deps, tag, sha) {
10918
11583
  }
10919
11584
  async function resolveRcResumeTag(deps, base2, sha) {
10920
11585
  const out = await deps.run("git", ["ls-remote", "--tags", "origin", `refs/tags/${base2}-rc.*`]);
10921
- const onSha = clean(out).split("\n").map((line) => line.trim()).filter(Boolean).map((line) => line.split(/\s+/)).filter(([refSha]) => refSha === sha).map(([, ref]) => ref.replace(/^refs\/tags\//, "").replace(/\^\{\}$/, "")).filter((ref) => new RegExp(`^${base2.replace(/\./g, "\\.")}-rc\\.\\d+$`).test(ref));
11586
+ const onSha = clean2(out).split("\n").map((line) => line.trim()).filter(Boolean).map((line) => line.split(/\s+/)).filter(([refSha]) => refSha === sha).map(([, ref]) => ref.replace(/^refs\/tags\//, "").replace(/\^\{\}$/, "")).filter((ref) => new RegExp(`^${base2.replace(/\./g, "\\.")}-rc\\.\\d+$`).test(ref));
10922
11587
  const unique = [...new Set(onSha)];
10923
11588
  if (unique.length === 0) return { tag: null };
10924
11589
  const rcNum = (t) => Number.parseInt(t.replace(/^.*-rc\./, ""), 10);
@@ -11043,13 +11708,13 @@ async function runTrainApply(command, deps, options = {}) {
11043
11708
  "nothing to promote: origin/development is not ahead of origin/rc"
11044
11709
  );
11045
11710
  const deployModel2 = await preflight(deps, ctx, "rc", meta);
11046
- const releaseBase = requireValue(clean(await deps.run("node", ["scripts/next-version.mjs", "cycle"])), "release base");
11711
+ const releaseBase = requireValue(clean2(await deps.run("node", ["scripts/next-version.mjs", "cycle"])), "release base");
11047
11712
  await deps.run("git", ["checkout", "rc"]);
11048
11713
  await ffOnlyPull(deps, "rc");
11049
11714
  await deps.run("git", ["merge", "development", "--no-edit"]);
11050
- const rcSha = requireValue(clean(await deps.run("git", ["rev-parse", "rc"])), "rc sha");
11715
+ const rcSha = requireValue(clean2(await deps.run("git", ["rev-parse", "rc"])), "rc sha");
11051
11716
  const resume = await resolveRcResumeTag(deps, releaseBase, rcSha);
11052
- const tag2 = resume.tag ?? requireValue(clean(await deps.run("node", ["scripts/next-version.mjs", "rc"])), "rc tag");
11717
+ const tag2 = resume.tag ?? requireValue(clean2(await deps.run("node", ["scripts/next-version.mjs", "rc"])), "rc tag");
11053
11718
  const resumeNote = resume.tag ? resume.note : void 0;
11054
11719
  await ensureTagPushed(deps, tag2, rcSha);
11055
11720
  const requiredChecks2 = await discoverRequiredCheckContexts(deps, ctx, "rc");
@@ -11067,7 +11732,7 @@ async function runTrainApply(command, deps, options = {}) {
11067
11732
  "nothing to release: origin/development is not ahead of origin/main"
11068
11733
  );
11069
11734
  const deployModel2 = await preflight(deps, ctx, "main", meta);
11070
- const tag2 = requireValue(clean(await deps.run("node", ["scripts/next-version.mjs", "cycle"])), "release tag");
11735
+ const tag2 = requireValue(clean2(await deps.run("node", ["scripts/next-version.mjs", "cycle"])), "release tag");
11071
11736
  const foldPaths2 = await resolveFoldPaths(deps, deployModel2);
11072
11737
  const tolerated2 = [...foldPaths2, ...RELEASE_TOLERATED_PATHS];
11073
11738
  const predicted2 = await predictMergeConflicts(deps, "origin/main", "origin/development");
@@ -11085,12 +11750,12 @@ async function runTrainApply(command, deps, options = {}) {
11085
11750
  await mergeWithSpineResolution(deps, "development", "development -> main", "theirs", tolerated2);
11086
11751
  }
11087
11752
  const versionFold2 = await foldReleaseVersion(deps, deployModel2, tag2, foldPaths2);
11088
- const releaseSha2 = requireValue(clean(await deps.run("git", ["rev-parse", "main"])), "release sha");
11753
+ const releaseSha2 = requireValue(clean2(await deps.run("git", ["rev-parse", "main"])), "release sha");
11089
11754
  await ensureTagPushed(deps, tag2, releaseSha2);
11090
11755
  const requiredChecks2 = await discoverRequiredCheckContexts(deps, ctx, "main");
11091
11756
  const checks2 = await waitForRequiredTrainChecks(deps, ctx, releaseSha2, requiredChecks2);
11092
11757
  await deps.run("git", ["push", "origin", "main"]);
11093
- const releaseUrl2 = clean(await deps.run("gh", ["release", "create", tag2, "--target", "main", "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
11758
+ const releaseUrl2 = clean2(await deps.run("gh", ["release", "create", tag2, "--target", "main", "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
11094
11759
  await verifyPublishedRelease(deps, ctx.repo, tag2, "main", releaseSha2);
11095
11760
  const announceNote2 = deps.announce ? (await deps.announce({ repo: ctx.repo, tag: tag2, summaryFile: options.announceSummaryFile })).note : void 0;
11096
11761
  const autoRunSince2 = (deps.now ?? Date.now)();
@@ -11139,8 +11804,8 @@ async function runTrainApply(command, deps, options = {}) {
11139
11804
  "nothing to release: origin/development is not ahead of origin/main"
11140
11805
  );
11141
11806
  const deployModel2 = await preflight(deps, ctx, "main", meta);
11142
- const tag2 = requireValue(clean(await deps.run("node", ["scripts/next-version.mjs", "cycle"])), "release tag");
11143
- const rcShaAtRelease = hasRcBranch ? clean(await deps.run("git", ["rev-parse", "origin/rc"])) : "";
11807
+ const tag2 = requireValue(clean2(await deps.run("node", ["scripts/next-version.mjs", "cycle"])), "release tag");
11808
+ const rcShaAtRelease = hasRcBranch ? clean2(await deps.run("git", ["rev-parse", "origin/rc"])) : "";
11144
11809
  const foldPaths2 = await resolveFoldPaths(deps, deployModel2);
11145
11810
  const tolerated2 = [...foldPaths2, ...RELEASE_TOLERATED_PATHS];
11146
11811
  const predicted2 = await predictMergeConflicts(deps, "origin/main", "origin/development");
@@ -11158,12 +11823,12 @@ async function runTrainApply(command, deps, options = {}) {
11158
11823
  await mergeWithSpineResolution(deps, "development", "development -> main", "theirs", tolerated2);
11159
11824
  }
11160
11825
  const versionFold2 = await foldReleaseVersion(deps, deployModel2, tag2, foldPaths2);
11161
- const releaseSha2 = requireValue(clean(await deps.run("git", ["rev-parse", "main"])), "release sha");
11826
+ const releaseSha2 = requireValue(clean2(await deps.run("git", ["rev-parse", "main"])), "release sha");
11162
11827
  await ensureTagPushed(deps, tag2, releaseSha2);
11163
11828
  const requiredChecks2 = await discoverRequiredCheckContexts(deps, ctx, "main");
11164
11829
  const checks2 = await waitForRequiredTrainChecks(deps, ctx, releaseSha2, requiredChecks2);
11165
11830
  await deps.run("git", ["push", "origin", "main"]);
11166
- const releaseUrl2 = clean(await deps.run("gh", ["release", "create", tag2, "--target", "main", "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
11831
+ const releaseUrl2 = clean2(await deps.run("gh", ["release", "create", tag2, "--target", "main", "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
11167
11832
  await verifyPublishedRelease(deps, ctx.repo, tag2, "main", releaseSha2);
11168
11833
  const announceNote2 = deps.announce ? (await deps.announce({ repo: ctx.repo, tag: tag2, summaryFile: options.announceSummaryFile })).note : void 0;
11169
11834
  const autoRunSince2 = (deps.now ?? Date.now)();
@@ -11230,7 +11895,7 @@ async function runTrainApply(command, deps, options = {}) {
11230
11895
  `hotfix-coverage: ${coverage.uncovered.length} main-only commit(s) not proven in origin/rc \u2014 the candidate would revert a prod hotfix: ${list}. Re-cut /rcand from development, or have the authorized human verify the content is in the candidate and rerun release with --ack <sha>[,<sha>\u2026].`
11231
11896
  );
11232
11897
  }
11233
- const releasedRcSha = clean(await deps.run("git", ["rev-parse", "origin/rc"]));
11898
+ const releasedRcSha = clean2(await deps.run("git", ["rev-parse", "origin/rc"]));
11234
11899
  await deps.run("git", ["checkout", "main"]);
11235
11900
  await ffOnlyPull(deps, "main");
11236
11901
  if (predicted.length === 0) {
@@ -11238,15 +11903,15 @@ async function runTrainApply(command, deps, options = {}) {
11238
11903
  } else {
11239
11904
  await mergeWithSpineResolution(deps, "rc", "rc -> main", "theirs", tolerated);
11240
11905
  }
11241
- const tag = requireValue(clean(await deps.run("node", ["scripts/next-version.mjs", "release"])), "release tag");
11906
+ const tag = requireValue(clean2(await deps.run("node", ["scripts/next-version.mjs", "release"])), "release tag");
11242
11907
  const versionFold = await foldReleaseVersion(deps, deployModel, tag, foldPaths);
11243
- const releaseSha = requireValue(clean(await deps.run("git", ["rev-parse", "main"])), "release sha");
11908
+ const releaseSha = requireValue(clean2(await deps.run("git", ["rev-parse", "main"])), "release sha");
11244
11909
  await ensureTagPushed(deps, tag, releaseSha);
11245
11910
  const requiredChecks = await discoverRequiredCheckContexts(deps, ctx, "main");
11246
11911
  const checks = await waitForRequiredTrainChecks(deps, ctx, releaseSha, requiredChecks);
11247
11912
  await deps.run("git", ["push", "origin", "main"]);
11248
11913
  const autoRunSince = (deps.now ?? Date.now)();
11249
- const releaseUrl = clean(await deps.run("gh", ["release", "create", tag, "--target", "main", "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
11914
+ const releaseUrl = clean2(await deps.run("gh", ["release", "create", tag, "--target", "main", "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
11250
11915
  await verifyPublishedRelease(deps, ctx.repo, tag, "main", releaseSha);
11251
11916
  const announceNote = deps.announce ? (await deps.announce({ repo: ctx.repo, tag, summaryFile: options.announceSummaryFile })).note : void 0;
11252
11917
  const d = await dispatchDeploy(deps, ctx, "main", "main", deployModel, watch, autoRunSince, releaseSha, "report");
@@ -11374,7 +12039,7 @@ async function retireRcRuntime(deps, ctx, model, deployStatus, releasedRcSha) {
11374
12039
  }
11375
12040
  try {
11376
12041
  await deps.run("git", ["fetch", "origin", "rc"]);
11377
- const rcNow = clean(await deps.run("git", ["rev-parse", "origin/rc"]));
12042
+ const rcNow = clean2(await deps.run("git", ["rev-parse", "origin/rc"]));
11378
12043
  if (rcNow !== releasedRcSha) {
11379
12044
  return {
11380
12045
  status: "skipped",
@@ -11406,7 +12071,7 @@ async function runTenantRedeploy(deps, options) {
11406
12071
  const repo = options.repo;
11407
12072
  const [owner, name] = repo.split("/");
11408
12073
  if (!owner || !name) throw new Error(`repo must be owner/name, got ${repo}`);
11409
- const login = requireValue(clean(await deps.run("gh", ["api", "user", "--jq", ".login"])), "GitHub login");
12074
+ const login = requireValue(clean2(await deps.run("gh", ["api", "user", "--jq", ".login"])), "GitHub login");
11410
12075
  const verdict = await deps.trainAuthority(repo);
11411
12076
  if (!verdict.ok) throw new Error(`${commandAuthorityLabel(owner)}: train authority could not be verified (${verdict.error})`);
11412
12077
  if (!verdict.train) throw new Error(`${commandAuthorityLabel(owner)}: @${login} is ${verdict.role} \u2014 no train authority on ${repo}`);
@@ -11562,7 +12227,7 @@ var HOTFIX_RUN_FIND_ATTEMPTS = 10;
11562
12227
  var HOTFIX_RUN_FIND_DELAY_MS = 15e3;
11563
12228
  var HOTFIX_VERIFY_ATTEMPTS = 5;
11564
12229
  var HOTFIX_VERIFY_RETRY_MS = 6e3;
11565
- function clean2(out) {
12230
+ function clean3(out) {
11566
12231
  return out.trim();
11567
12232
  }
11568
12233
  function sleeper(deps) {
@@ -11620,7 +12285,7 @@ async function resolveHotfixSource(deps, ctx, from) {
11620
12285
  if (!sha2) throw new Error(`PR #${num} has no merge commit recorded \u2014 name the commit SHA explicitly`);
11621
12286
  return { sha: sha2, label: `PR #${num} (${sha2.slice(0, 7)})` };
11622
12287
  }
11623
- const sha = clean2(await deps.run("git", ["rev-parse", "--verify", `${from}^{commit}`]));
12288
+ const sha = clean3(await deps.run("git", ["rev-parse", "--verify", `${from}^{commit}`]));
11624
12289
  if (!sha) throw new Error(`could not resolve commit ${from}`);
11625
12290
  return { sha, label: sha.slice(0, 7) };
11626
12291
  }
@@ -11648,7 +12313,7 @@ async function runHotfixStart(deps, options) {
11648
12313
  };
11649
12314
  }
11650
12315
  const { sha, label } = await resolveHotfixSource(deps, ctx, options.from);
11651
- const remoteBranch = clean2(await deps.run("git", ["ls-remote", "origin", `refs/heads/${branch}`]));
12316
+ const remoteBranch = clean3(await deps.run("git", ["ls-remote", "origin", `refs/heads/${branch}`]));
11652
12317
  if (remoteBranch) {
11653
12318
  await deps.run("git", ["checkout", branch]);
11654
12319
  const isRulesSource2 = deps.isRulesSource ? await deps.isRulesSource() : false;
@@ -11681,7 +12346,7 @@ async function runHotfixStart(deps, options) {
11681
12346
  await deps.run("git", ["push", "-u", "origin", branch]);
11682
12347
  }
11683
12348
  const bumpNote = deployModel === "hub-serverless" ? " with the locked distribution bump" : "";
11684
- const prUrl = clean2(await deps.run("gh", [
12349
+ const prUrl = clean3(await deps.run("gh", [
11685
12350
  "pr",
11686
12351
  "create",
11687
12352
  "--repo",
@@ -11801,7 +12466,7 @@ async function runHotfixRelease(deps, versionInput, options = {}) {
11801
12466
  }
11802
12467
  let verifyNote;
11803
12468
  if (deployModel === "hub-serverless") {
11804
- const previousRef = clean2(await deps.run("git", ["rev-parse", "--abbrev-ref", "HEAD"]));
12469
+ const previousRef = clean3(await deps.run("git", ["rev-parse", "--abbrev-ref", "HEAD"]));
11805
12470
  const publishSucceeded = runs.find((r) => r.workflow === "publish.yml")?.conclusion === "success";
11806
12471
  try {
11807
12472
  await deps.run("git", ["-c", "advice.detachedHead=false", "checkout", tag]);
@@ -11886,9 +12551,9 @@ async function runHotfixStatus(deps, versionInput) {
11886
12551
  return { ...ctx, command: "hotfix-status", ...facts, ...deriveHotfixState(facts) };
11887
12552
  }
11888
12553
  async function gatherHotfixFacts(deps, ctx, tag, version) {
11889
- const branchExists = Boolean(clean2(await deps.run("git", ["ls-remote", "origin", `refs/heads/${hotfixBranch(tag)}`])));
12554
+ const branchExists = Boolean(clean3(await deps.run("git", ["ls-remote", "origin", `refs/heads/${hotfixBranch(tag)}`])));
11890
12555
  const pr2 = await findHotfixPr(deps, ctx, tag);
11891
- const remoteTag = clean2(await deps.run("git", ["ls-remote", "origin", `refs/tags/${tag}`]));
12556
+ const remoteTag = clean3(await deps.run("git", ["ls-remote", "origin", `refs/tags/${tag}`]));
11892
12557
  const tagPushed = Boolean(remoteTag);
11893
12558
  const tagSha = remoteTag.split(/\s+/)[0] || "";
11894
12559
  let releaseExists = false;
@@ -11922,7 +12587,7 @@ async function gatherHotfixFacts(deps, ctx, tag, version) {
11922
12587
  }
11923
12588
  }
11924
12589
  }
11925
- const npmVersion = await deps.run("npm", ["view", "@mutmutco/cli", "version", "--silent"]).then(clean2, () => "unknown");
12590
+ const npmVersion = await deps.run("npm", ["view", "@mutmutco/cli", "version", "--silent"]).then(clean3, () => "unknown");
11926
12591
  return { tag, version, branchExists, pr: pr2 ? { number: pr2.number, state: pr2.state, url: pr2.url } : null, tagPushed, releaseExists, runs, npmVersion };
11927
12592
  }
11928
12593
  async function findInFlightHotfixVersion(deps, ctx) {
@@ -11945,7 +12610,7 @@ async function findInFlightHotfixVersion(deps, ctx) {
11945
12610
  const m = typeof row.headRefName === "string" && /^hotfix\/(v\d+\.\d+\.\d+)/.exec(row.headRefName);
11946
12611
  if (m) tags.add(m[1]);
11947
12612
  }
11948
- const branchOut = clean2(await deps.run("git", ["ls-remote", "origin", "refs/heads/hotfix/v*"]));
12613
+ const branchOut = clean3(await deps.run("git", ["ls-remote", "origin", "refs/heads/hotfix/v*"]));
11949
12614
  for (const line of branchOut.split("\n").filter(Boolean)) {
11950
12615
  const ref = line.split(/\s+/)[1] ?? "";
11951
12616
  const m = /^refs\/heads\/hotfix\/(v\d+\.\d+\.\d+)/.exec(ref);
@@ -12094,7 +12759,7 @@ async function announceRelease(deps, args) {
12094
12759
  }
12095
12760
 
12096
12761
  // src/port-registry.ts
12097
- var import_node_fs14 = require("node:fs");
12762
+ var import_node_fs16 = require("node:fs");
12098
12763
 
12099
12764
  // ../infra/port-geometry.mjs
12100
12765
  var PORT_BLOCK = 100;
@@ -12108,8 +12773,8 @@ function nextPortBlock(registry2) {
12108
12773
  return [base2, base2 + PORT_SPAN];
12109
12774
  }
12110
12775
  function loadPortRegistry(path2) {
12111
- if (!(0, import_node_fs14.existsSync)(path2)) return {};
12112
- const raw = JSON.parse((0, import_node_fs14.readFileSync)(path2, "utf8"));
12776
+ if (!(0, import_node_fs16.existsSync)(path2)) return {};
12777
+ const raw = JSON.parse((0, import_node_fs16.readFileSync)(path2, "utf8"));
12113
12778
  const out = {};
12114
12779
  for (const [key, value] of Object.entries(raw)) {
12115
12780
  if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
@@ -12123,9 +12788,9 @@ function ensurePortRange(repo, path2) {
12123
12788
  const existing = registry2[repo];
12124
12789
  if (existing) return existing;
12125
12790
  const range = nextPortBlock(registry2);
12126
- const raw = (0, import_node_fs14.existsSync)(path2) ? JSON.parse((0, import_node_fs14.readFileSync)(path2, "utf8")) : {};
12791
+ const raw = (0, import_node_fs16.existsSync)(path2) ? JSON.parse((0, import_node_fs16.readFileSync)(path2, "utf8")) : {};
12127
12792
  raw[repo] = range;
12128
- (0, import_node_fs14.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
12793
+ (0, import_node_fs16.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
12129
12794
  return range;
12130
12795
  }
12131
12796
  function portCursorSeed(registry2) {
@@ -13919,8 +14584,8 @@ function resolveKbSource(rawBase) {
13919
14584
  return { owner: m[1], repo: m[2], ref: m[3] };
13920
14585
  }
13921
14586
  function buildKbGetArgs(src, path2) {
13922
- const clean3 = path2.replace(/^\/+/, "");
13923
- return ["api", `repos/${src.owner}/${src.repo}/contents/${clean3}?ref=${src.ref}`, "-H", "Accept: application/vnd.github.raw"];
14587
+ const clean4 = path2.replace(/^\/+/, "");
14588
+ return ["api", `repos/${src.owner}/${src.repo}/contents/${clean4}?ref=${src.ref}`, "-H", "Accept: application/vnd.github.raw"];
13924
14589
  }
13925
14590
  function buildKbTreeArgs(src) {
13926
14591
  return ["api", `repos/${src.owner}/${src.repo}/git/trees/${src.ref}?recursive=1`];
@@ -13937,10 +14602,10 @@ function parseKbTree(stdout, prefix) {
13937
14602
  }
13938
14603
 
13939
14604
  // src/plan.ts
13940
- var import_node_path13 = require("node:path");
14605
+ var import_node_path15 = require("node:path");
13941
14606
  var PLANS_DIR = "plans";
13942
- var META_FILE = (0, import_node_path13.join)(PLANS_DIR, ".plan-meta.json");
13943
- var planPath = (slug) => (0, import_node_path13.join)(PLANS_DIR, `${slug}.md`);
14607
+ var META_FILE = (0, import_node_path15.join)(PLANS_DIR, ".plan-meta.json");
14608
+ var planPath = (slug) => (0, import_node_path15.join)(PLANS_DIR, `${slug}.md`);
13944
14609
  var metaKey = (project2, slug) => `${project2}/${slug}`;
13945
14610
  function parseMeta(raw) {
13946
14611
  if (!raw) return {};
@@ -13965,7 +14630,7 @@ function hashContent(s) {
13965
14630
  function staleHint(slug) {
13966
14631
  return `remote "${slug}" is newer \u2014 run \`mmi-cli northstar pull ${slug}\` first (your local is based on an older version), or re-push with \`--force\` to overwrite`;
13967
14632
  }
13968
- var INDEX_FILE = (0, import_node_path13.join)(PLANS_DIR, ".index.json");
14633
+ var INDEX_FILE = (0, import_node_path15.join)(PLANS_DIR, ".index.json");
13969
14634
  var INDEX_TTL_MS = 6e4;
13970
14635
  function parseIndex(raw) {
13971
14636
  if (!raw) return null;
@@ -13994,7 +14659,7 @@ function mergeIndex(idx, scope, plans, now) {
13994
14659
  const mergedScope = idx.scope === null ? null : [.../* @__PURE__ */ new Set([...idx.scope, ...scope])];
13995
14660
  return { fetchedAt: now, scope: mergedScope, plans: [...kept, ...plans] };
13996
14661
  }
13997
- var QUEUE_FILE = (0, import_node_path13.join)(PLANS_DIR, ".sync-queue.json");
14662
+ var QUEUE_FILE = (0, import_node_path15.join)(PLANS_DIR, ".sync-queue.json");
13998
14663
  var QUEUE_MAX_ATTEMPTS = 10;
13999
14664
  function isValidQueueEntry(e) {
14000
14665
  if (!e || typeof e !== "object") return false;
@@ -14443,11 +15108,11 @@ async function planGraduate(deps, slug, opts = {}) {
14443
15108
  }
14444
15109
 
14445
15110
  // src/atomic-write.ts
14446
- var import_node_fs15 = require("node:fs");
15111
+ var import_node_fs17 = require("node:fs");
14447
15112
  function atomicWriteFileSync(path2, content) {
14448
15113
  const tmp = `${path2}.${process.pid}.tmp`;
14449
- (0, import_node_fs15.writeFileSync)(tmp, content, "utf8");
14450
- (0, import_node_fs15.renameSync)(tmp, path2);
15114
+ (0, import_node_fs17.writeFileSync)(tmp, content, "utf8");
15115
+ (0, import_node_fs17.renameSync)(tmp, path2);
14451
15116
  }
14452
15117
 
14453
15118
  // src/oauth.ts
@@ -14678,7 +15343,7 @@ async function fetchHubVersionInfo(baseUrl) {
14678
15343
  }
14679
15344
  function readRepoVersion() {
14680
15345
  try {
14681
- return JSON.parse((0, import_node_fs16.readFileSync)((0, import_node_path14.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
15346
+ return JSON.parse((0, import_node_fs18.readFileSync)((0, import_node_path16.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
14682
15347
  } catch {
14683
15348
  return void 0;
14684
15349
  }
@@ -14804,10 +15469,10 @@ async function runRulesSync(opts, io = consoleIo) {
14804
15469
  for (const entry of fetched) {
14805
15470
  if ("error" in entry) continue;
14806
15471
  const { file, source } = entry;
14807
- const current = (0, import_node_fs16.existsSync)(file) ? await (0, import_promises5.readFile)(file, "utf8") : null;
15472
+ const current = (0, import_node_fs18.existsSync)(file) ? await (0, import_promises5.readFile)(file, "utf8") : null;
14808
15473
  if (needsUpdate(source, current)) {
14809
15474
  const slash = file.lastIndexOf("/");
14810
- if (slash > 0) (0, import_node_fs16.mkdirSync)(file.slice(0, slash), { recursive: true });
15475
+ if (slash > 0) (0, import_node_fs18.mkdirSync)(file.slice(0, slash), { recursive: true });
14811
15476
  await (0, import_promises5.writeFile)(file, normalizeEol(source), "utf8");
14812
15477
  changed++;
14813
15478
  if (!opts.quiet) io.log(`mmi-cli rules: updated ${file}`);
@@ -14833,7 +15498,7 @@ async function runDocsSync(opts, io = consoleIo) {
14833
15498
  return null;
14834
15499
  }
14835
15500
  },
14836
- localContent: async (f) => (0, import_node_fs16.existsSync)(f) ? await (0, import_promises5.readFile)(f, "utf8") : null,
15501
+ localContent: async (f) => (0, import_node_fs18.existsSync)(f) ? await (0, import_promises5.readFile)(f, "utf8") : null,
14837
15502
  writeDoc: async (f, c) => {
14838
15503
  await (0, import_promises5.writeFile)(f, c, "utf8");
14839
15504
  }
@@ -14845,6 +15510,7 @@ async function runDocsSync(opts, io = consoleIo) {
14845
15510
  var docs = program2.command("docs").description("repo-owned authoritative docs");
14846
15511
  docs.command("sync").option("--quiet", "stay silent unless something changed or errored").description("refresh README.md / architecture.md from the repo default branch (keeper-authored); never clobbers uncommitted edits").action((opts) => runDocsSync(opts));
14847
15512
  registerSagaCommands(program2);
15513
+ registerHandoffCommands(program2);
14848
15514
  registerHonchoCommands(program2);
14849
15515
  async function runWhoami(io = consoleIo) {
14850
15516
  const cfg = await loadConfig();
@@ -14858,8 +15524,18 @@ async function runWhoami(io = consoleIo) {
14858
15524
  program2.command("whoami").description('resolve the logged-in human: {login, source, sessionExpiresAt} JSON; source "unknown" (exit 0) when neither the Hub session nor gh can name them').option("--json", "machine-readable output (default)").action(async () => {
14859
15525
  await runWhoami();
14860
15526
  });
14861
- program2.command("gc").description("dry-run cleanup for merged/closed PR branches and stale tracking refs").option("--dry-run", "show what would be deleted (default)").option("--apply", "delete only the listed clean merged/closed PR branches and stale tracking refs").option("--json", "machine-readable output").option("--remote <name>", "remote name", "origin").option("--limit <n>", "PRs to inspect per state", "200").action(async (o) => {
15527
+ program2.command("gc").description("dry-run cleanup for merged/closed PR branches and stale tracking refs (--scratch: stale local saga/plan/honcho files)").option("--dry-run", "show what would be deleted (default)").option("--apply", "delete only the listed clean merged/closed PR branches and stale tracking refs").option("--json", "machine-readable output").option("--scratch", "prune stale local saga/plan/honcho scratch already recorded remotely (#1474) instead of git branches/refs").option("--remote <name>", "remote name", "origin").option("--limit <n>", "PRs to inspect per state", "200").action(async (o) => {
14862
15528
  if (o.apply && o.dryRun) return fail("gc: choose either --dry-run or --apply");
15529
+ if (o.scratch) {
15530
+ try {
15531
+ const root = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
15532
+ const run = executeScratchGc(root, { apply: Boolean(o.apply) });
15533
+ if (o.json) return console.log(JSON.stringify({ dryRun: !o.apply, ...run }, null, 2));
15534
+ return console.log(formatScratchGcPlan(run.plan, Boolean(o.apply)));
15535
+ } catch (e) {
15536
+ return fail(`gc --scratch: ${e.message}`);
15537
+ }
15538
+ }
14863
15539
  const limit = Number.parseInt(o.limit, 10);
14864
15540
  if (!Number.isFinite(limit) || limit < 1) return fail("gc: --limit must be a positive integer");
14865
15541
  try {
@@ -14887,6 +15563,120 @@ ${renderGcApplyResult(applyResult)}`);
14887
15563
  fail(`gc: ${e.message}`);
14888
15564
  }
14889
15565
  });
15566
+ var NPM_PROVISION_TIMEOUT_MS = 3e5;
15567
+ var WORKTREE_SETUP_LOCK_TTL_MS = 10 * 6e4;
15568
+ function runWorktreeInstall(command, cwd, quiet) {
15569
+ const [bin, ...args] = command.split(" ");
15570
+ const file = isWin ? "cmd.exe" : bin;
15571
+ const spawnArgs = isWin ? ["/c", bin, ...args] : args;
15572
+ return new Promise((resolve, reject) => {
15573
+ const child = (0, import_node_child_process10.spawn)(file, spawnArgs, { cwd, stdio: quiet ? "ignore" : "inherit", windowsHide: true });
15574
+ const timer = setTimeout(() => {
15575
+ try {
15576
+ child.kill();
15577
+ } catch {
15578
+ }
15579
+ reject(new Error(`${command} timed out after ${NPM_PROVISION_TIMEOUT_MS}ms in ${cwd}`));
15580
+ }, NPM_PROVISION_TIMEOUT_MS);
15581
+ child.on("error", (e) => {
15582
+ clearTimeout(timer);
15583
+ reject(e);
15584
+ });
15585
+ child.on("exit", (code) => {
15586
+ clearTimeout(timer);
15587
+ if (code === 0) resolve();
15588
+ else reject(new Error(`${command} exited ${code} in ${cwd}`));
15589
+ });
15590
+ });
15591
+ }
15592
+ async function primaryCheckoutRoot(worktreeRoot) {
15593
+ try {
15594
+ const out = (await execFileP2("git", ["-C", worktreeRoot, "rev-parse", "--path-format=absolute", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
15595
+ return out ? (0, import_node_path16.dirname)(out) : void 0;
15596
+ } catch {
15597
+ return void 0;
15598
+ }
15599
+ }
15600
+ function makeProvisionDeps(worktreeRoot, quiet, log) {
15601
+ return {
15602
+ runInstall: (command, cwd) => runWorktreeInstall(command, cwd, quiet),
15603
+ primaryCheckout: () => primaryCheckoutRoot(worktreeRoot),
15604
+ log
15605
+ };
15606
+ }
15607
+ function acquireWorktreeSetupLock(worktreeRoot) {
15608
+ const lockPath = (0, import_node_path16.join)(worktreeRoot, ".mmi", "worktree-setup.lock");
15609
+ const take = () => {
15610
+ const fd = (0, import_node_fs18.openSync)(lockPath, "wx");
15611
+ try {
15612
+ (0, import_node_fs18.writeSync)(fd, String(Date.now()));
15613
+ } finally {
15614
+ (0, import_node_fs18.closeSync)(fd);
15615
+ }
15616
+ return () => {
15617
+ try {
15618
+ (0, import_node_fs18.rmSync)(lockPath, { force: true });
15619
+ } catch {
15620
+ }
15621
+ };
15622
+ };
15623
+ try {
15624
+ (0, import_node_fs18.mkdirSync)((0, import_node_path16.dirname)(lockPath), { recursive: true });
15625
+ return take();
15626
+ } catch {
15627
+ try {
15628
+ if (Date.now() - (0, import_node_fs18.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
15629
+ (0, import_node_fs18.rmSync)(lockPath, { force: true });
15630
+ return take();
15631
+ }
15632
+ } catch {
15633
+ }
15634
+ return null;
15635
+ }
15636
+ }
15637
+ var worktree = program2.command("worktree").description("self-provisioning worktrees \u2014 install deps + copy local-only config");
15638
+ worktree.command("create <branch>").description("create a worktree from a base ref and provision it (install deps + copy local-only config)").option("--from <ref>", "base ref to branch from", "origin/development").option("--path <path>", "worktree path (default: ../mmi-worktrees/<branch>)").option("--remote <name>", "remote to fetch the base from", "origin").option("--json", "machine-readable output").action(async (branch, o) => {
15639
+ try {
15640
+ const repoRoot = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
15641
+ const wtPath = o.path ?? defaultWorktreePath(repoRoot, branch);
15642
+ const baseBranch = o.from.startsWith(`${o.remote}/`) ? o.from.slice(o.remote.length + 1) : void 0;
15643
+ if (baseBranch) await execFileP2("git", ["fetch", o.remote, baseBranch], { timeout: GH_MUTATION_TIMEOUT_MS }).catch(() => void 0);
15644
+ await execFileP2("git", ["worktree", "add", wtPath, "-b", branch, o.from], { timeout: GH_MUTATION_TIMEOUT_MS });
15645
+ const report = await provisionWorktree(wtPath, makeProvisionDeps(wtPath, Boolean(o.json), (m) => {
15646
+ if (!o.json) console.error(` ${m}`);
15647
+ }));
15648
+ if (o.json) return console.log(JSON.stringify({ branch, path: wtPath, base: o.from, ...report }, null, 2));
15649
+ console.log(`worktree ready: ${wtPath} (branch ${branch} from ${o.from})`);
15650
+ console.log(` installed: ${report.installed.map((i) => i.dir || ".").join(", ") || "none"}`);
15651
+ console.log(` copied: ${report.copied.join(", ") || "none"}`);
15652
+ } catch (e) {
15653
+ fail(`worktree create: ${e.message}`);
15654
+ }
15655
+ });
15656
+ worktree.command("setup [path]").description("provision an existing worktree (install missing deps + copy local-only config); fired automatically by SessionStart").option("--quiet", "no output on success (for the detached SessionStart auto-fire worker)").option("--json", "machine-readable output").action(async (path2, o) => {
15657
+ const root = path2 ?? process.cwd();
15658
+ const release = acquireWorktreeSetupLock(root);
15659
+ if (!release) {
15660
+ if (!o.quiet && !o.json) console.log("worktree setup: another provision is in progress \u2014 skipping");
15661
+ return;
15662
+ }
15663
+ try {
15664
+ const report = await provisionWorktree(root, makeProvisionDeps(root, Boolean(o.quiet), (m) => {
15665
+ if (!o.quiet && !o.json) console.error(` ${m}`);
15666
+ }));
15667
+ if (o.json) return console.log(JSON.stringify({ path: root, ...report }, null, 2));
15668
+ if (!o.quiet) {
15669
+ console.log(`worktree provisioned: ${root}`);
15670
+ console.log(` installed: ${report.installed.map((i) => i.dir || ".").join(", ") || "none"}`);
15671
+ console.log(` copied: ${report.copied.join(", ") || "none"}`);
15672
+ }
15673
+ } catch (e) {
15674
+ if (o.quiet) return void console.error(`[worktree-setup] ${e.message}`);
15675
+ fail(`worktree setup: ${e.message}`);
15676
+ } finally {
15677
+ release();
15678
+ }
15679
+ });
14890
15680
  var kb = program2.command("kb").description("org knowledgebase (read-only)");
14891
15681
  kb.command("get <path>").description("print a KB document by path").action(async (path2) => {
14892
15682
  const src = resolveKbSource((await loadConfig()).kbSource);
@@ -15006,7 +15796,7 @@ function detachPlanSync() {
15006
15796
  }
15007
15797
  }
15008
15798
  function makePlanDeps(cfg, io = consoleIo) {
15009
- const ensureDir = () => (0, import_node_fs16.mkdirSync)(PLANS_DIR, { recursive: true });
15799
+ const ensureDir = () => (0, import_node_fs18.mkdirSync)(PLANS_DIR, { recursive: true });
15010
15800
  return {
15011
15801
  apiUrl: cfg.sagaApiUrl,
15012
15802
  fetch: (url, init = {}) => fetch(url, { ...init, signal: init.signal ?? AbortSignal.timeout(1e4) }),
@@ -15014,24 +15804,24 @@ function makePlanDeps(cfg, io = consoleIo) {
15014
15804
  project: async () => (await sagaKey(cfg)).project,
15015
15805
  readLocal: (slug) => {
15016
15806
  try {
15017
- return (0, import_node_fs16.readFileSync)(planPath(slug), "utf8");
15807
+ return (0, import_node_fs18.readFileSync)(planPath(slug), "utf8");
15018
15808
  } catch {
15019
15809
  return null;
15020
15810
  }
15021
15811
  },
15022
15812
  writeLocal: (slug, content) => {
15023
15813
  ensureDir();
15024
- (0, import_node_fs16.writeFileSync)(planPath(slug), content, "utf8");
15814
+ (0, import_node_fs18.writeFileSync)(planPath(slug), content, "utf8");
15025
15815
  },
15026
15816
  removeLocal: (slug) => {
15027
15817
  try {
15028
- (0, import_node_fs16.rmSync)(planPath(slug));
15818
+ (0, import_node_fs18.rmSync)(planPath(slug));
15029
15819
  } catch {
15030
15820
  }
15031
15821
  },
15032
15822
  readMetaRaw: () => {
15033
15823
  try {
15034
- return (0, import_node_fs16.readFileSync)(META_FILE, "utf8");
15824
+ return (0, import_node_fs18.readFileSync)(META_FILE, "utf8");
15035
15825
  } catch {
15036
15826
  return null;
15037
15827
  }
@@ -15042,7 +15832,7 @@ function makePlanDeps(cfg, io = consoleIo) {
15042
15832
  },
15043
15833
  readIndexRaw: () => {
15044
15834
  try {
15045
- return (0, import_node_fs16.readFileSync)(INDEX_FILE, "utf8");
15835
+ return (0, import_node_fs18.readFileSync)(INDEX_FILE, "utf8");
15046
15836
  } catch {
15047
15837
  return null;
15048
15838
  }
@@ -15053,7 +15843,7 @@ function makePlanDeps(cfg, io = consoleIo) {
15053
15843
  },
15054
15844
  readQueueRaw: () => {
15055
15845
  try {
15056
- return (0, import_node_fs16.readFileSync)(QUEUE_FILE, "utf8");
15846
+ return (0, import_node_fs18.readFileSync)(QUEUE_FILE, "utf8");
15057
15847
  } catch {
15058
15848
  return null;
15059
15849
  }
@@ -15622,7 +16412,7 @@ oauth.command("verify").description("probe Google authorize with an arbitrary po
15622
16412
  if (mismatch) process.exitCode = 1;
15623
16413
  });
15624
16414
  var issue = program2.command("issue").description("issues \u2014 reliable create with structured output");
15625
- issue.command("create").description("create an issue (type \u2192 label) and print {number,url,label} JSON").requiredOption("--type <type>", "bug | feature | task (sets the matching label)").option("--title <title>", "issue title").option("--title-file <path|->", "read the issue title from a UTF-8 file, or from stdin with -").option("--body <body>", "issue body (markdown)").option("--body-file <path|->", "read issue body from a UTF-8 file, or from stdin with -").requiredOption("--priority <priority>", "urgent | high | medium | low (sets the board Priority field only \u2014 never a priority:* label, #416)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--label <label...>", "extra label(s) to attach (repeatable; auto-created if missing)").option("--parent <ref>", "file as a native sub-issue of this parent (#123, owner/repo#123, or URL)").option("--no-related", "skip the auto related-issues comment").action(async (o) => {
16415
+ issue.command("create").description("create an issue (type \u2192 label) and print {number,url,label} JSON").requiredOption("--type <type>", "bug | feature | task (sets the matching label)").option("--title <title>", "issue title").option("--title-file <path|->", "read the issue title from a UTF-8 file, or from stdin with -").option("--body <body>", "issue body (markdown)").option("--body-file <path|->", "read issue body from a UTF-8 file, or from stdin with -").option("--priority <priority>", "urgent | high | medium | low (sets the board Priority field only \u2014 never a priority:* label, #416)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--label <label...>", "extra label(s) to attach (repeatable; auto-created if missing)").option("--parent <ref>", "file as a native sub-issue of this parent (#123, owner/repo#123, or URL)").option("--no-related", "skip the auto related-issues comment").action(async (o) => {
15626
16416
  let args;
15627
16417
  let priority;
15628
16418
  let body;
@@ -15630,6 +16420,7 @@ issue.command("create").description("create an issue (type \u2192 label) and pri
15630
16420
  try {
15631
16421
  title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises5.readFile, readStdin });
15632
16422
  body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises5.readFile, readStdin });
16423
+ if (o.priority === void 0) throw new Error("missing --priority <priority> \u2014 expected one of: urgent, high, medium, low");
15633
16424
  priority = normalizePriority(o.priority);
15634
16425
  args = buildIssueArgs({ type: o.type, title, body, priority, repo: o.repo, labels: o.label });
15635
16426
  if (o.parent !== void 0) parseIssueRef(o.parent);
@@ -15980,9 +16771,9 @@ pr.command("create").description("create a PR and print {number,url} JSON").opti
15980
16771
  console.log(JSON.stringify(created));
15981
16772
  });
15982
16773
  async function listCiWorkflowPaths(cwd = process.cwd()) {
15983
- const wfDir = (0, import_node_path14.join)(cwd, ".github", "workflows");
15984
- if (!(0, import_node_fs16.existsSync)(wfDir)) return [];
15985
- return (0, import_node_fs16.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).map((name) => `.github/workflows/${name}`);
16774
+ const wfDir = (0, import_node_path16.join)(cwd, ".github", "workflows");
16775
+ if (!(0, import_node_fs18.existsSync)(wfDir)) return [];
16776
+ return (0, import_node_fs18.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).map((name) => `.github/workflows/${name}`);
15986
16777
  }
15987
16778
  async function resolveMergeCiPolicyForCheckout(repoOpt) {
15988
16779
  const repo = repoOpt ?? await resolveRepo();
@@ -16104,7 +16895,7 @@ async function createDeferredWorktreeStore() {
16104
16895
  },
16105
16896
  write: async (entries) => {
16106
16897
  try {
16107
- await (0, import_promises5.mkdir)((0, import_node_path14.dirname)(registryPath), { recursive: true });
16898
+ await (0, import_promises5.mkdir)((0, import_node_path16.dirname)(registryPath), { recursive: true });
16108
16899
  await (0, import_promises5.writeFile)(registryPath, serializeDeferredWorktrees(entries), "utf8");
16109
16900
  } catch {
16110
16901
  }
@@ -16123,7 +16914,7 @@ function worktreeRemoveDeps(execGit) {
16123
16914
  }
16124
16915
  function teardownWorktreeStage(worktreePath) {
16125
16916
  return runWorktreeStageTeardown(worktreePath, {
16126
- hasStageState: (wt) => (0, import_node_fs16.existsSync)(stageStatePath(wt)),
16917
+ hasStageState: (wt) => (0, import_node_fs18.existsSync)(stageStatePath(wt)),
16127
16918
  stopRecordedStage: async (wt) => (await stopStage({ cwd: wt })).pid,
16128
16919
  listComposeProjects: async () => {
16129
16920
  const { stdout } = await execFileP2("docker", ["compose", "ls", "--all", "--format", "json"], { timeout: GC_GH_TIMEOUT_MS });
@@ -16186,7 +16977,7 @@ pr.command("merge <number>").description("merge a PR (squash by default) and cle
16186
16977
  } : await cleanupPrMergeLocalBranch(headRef, {
16187
16978
  beforeWorktrees,
16188
16979
  startingPath,
16189
- pathExists: (p) => (0, import_node_fs16.existsSync)(p),
16980
+ pathExists: (p) => (0, import_node_fs18.existsSync)(p),
16190
16981
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
16191
16982
  teardownWorktreeStage,
16192
16983
  deferredStore,
@@ -16358,7 +17149,7 @@ function rawValues(flag) {
16358
17149
  return out;
16359
17150
  }
16360
17151
  function printLine(value) {
16361
- (0, import_node_fs16.writeSync)(1, `${value}
17152
+ (0, import_node_fs18.writeSync)(1, `${value}
16362
17153
  `);
16363
17154
  }
16364
17155
  function stageKeepAlive() {
@@ -16375,8 +17166,8 @@ async function resolveStage() {
16375
17166
  local,
16376
17167
  shell: shellFor(),
16377
17168
  registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
16378
- hasCompose: (0, import_node_fs16.existsSync)((0, import_node_path14.join)(process.cwd(), "docker-compose.yml")),
16379
- hasEnvExample: (0, import_node_fs16.existsSync)((0, import_node_path14.join)(process.cwd(), ".env.example"))
17169
+ hasCompose: (0, import_node_fs18.existsSync)((0, import_node_path16.join)(process.cwd(), "docker-compose.yml")),
17170
+ hasEnvExample: (0, import_node_fs18.existsSync)((0, import_node_path16.join)(process.cwd(), ".env.example"))
16380
17171
  });
16381
17172
  }
16382
17173
  async function fetchStageVaultEnvMerge() {
@@ -16428,9 +17219,9 @@ program2.command("port-range <repo>").description("assign (idempotently) + print
16428
17219
  printLine(o.json ? JSON.stringify({ repo, portRange: [start2, end2], source: "meta" }) : `${repo}: stage.portRange [${start2}, ${end2}]`);
16429
17220
  return;
16430
17221
  }
16431
- const path2 = (0, import_node_path14.join)(process.cwd(), "infra", "port-ranges.json");
17222
+ const path2 = (0, import_node_path16.join)(process.cwd(), "infra", "port-ranges.json");
16432
17223
  const allocate = async (seed) => {
16433
- const { stdout } = await execFileP2("node", [(0, import_node_path14.join)(process.cwd(), "infra", "port-ddb.mjs"), String(seed)], { timeout: 15e3 });
17224
+ const { stdout } = await execFileP2("node", [(0, import_node_path16.join)(process.cwd(), "infra", "port-ddb.mjs"), String(seed)], { timeout: 15e3 });
16434
17225
  const parsed = JSON.parse(stdout);
16435
17226
  if (!Array.isArray(parsed.range) || parsed.range.length !== 2) throw new Error("port-ddb: no range in output");
16436
17227
  return parsed.range;
@@ -16801,7 +17592,7 @@ bootstrap.command("verify <repo>").description("audit whether an existing repo i
16801
17592
  const report = await verifyBootstrap(repo, o.class, {
16802
17593
  client: defaultGitHubClient(),
16803
17594
  projectMeta: meta,
16804
- readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs16.existsSync)(path2) ? (0, import_node_fs16.readFileSync)(path2, "utf8") : null,
17595
+ readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs18.existsSync)(path2) ? (0, import_node_fs18.readFileSync)(path2, "utf8") : null,
16805
17596
  // requiredGcpApis is stored as an array by a JSON write, but `project set --var KEY=VALUE` stores a raw
16806
17597
  // comma-string — accept either so the seeded value verifies regardless of how it was written.
16807
17598
  requiredGcpApis: (() => {
@@ -16844,12 +17635,12 @@ bootstrap.command("apply <repo>").description("idempotent seed apply from skills
16844
17635
  return fail(`bootstrap apply: ${e.message}`);
16845
17636
  }
16846
17637
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
16847
- if (!(0, import_node_fs16.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; run from the MMI-Hub repo root`);
16848
- const manifest = loadBootstrapSeeds((0, import_node_fs16.readFileSync)(manifestPath, "utf8"));
17638
+ if (!(0, import_node_fs18.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; run from the MMI-Hub repo root`);
17639
+ const manifest = loadBootstrapSeeds((0, import_node_fs18.readFileSync)(manifestPath, "utf8"));
16849
17640
  const baseBranch = o.class === "content" ? "main" : "development";
16850
17641
  const slug = parsedRepo.slug;
16851
17642
  const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
16852
- const readFile5 = (p) => (0, import_node_fs16.existsSync)(p) ? (0, import_node_fs16.readFileSync)(p, "utf8") : null;
17643
+ const readFile5 = (p) => (0, import_node_fs18.existsSync)(p) ? (0, import_node_fs18.readFileSync)(p, "utf8") : null;
16853
17644
  const enc2 = (p) => p.split("/").map(encodeURIComponent).join("/");
16854
17645
  const rawVars = {};
16855
17646
  for (const value of rawValues("--var")) {
@@ -17078,16 +17869,16 @@ access.command("audit").description("audit collaborator roles + train-branch pus
17078
17869
  if (o.class !== "deployable" && o.class !== "content") return failGraceful("access audit: --class must be deployable or content");
17079
17870
  targets = [{ repo: o.repo, class: o.class }];
17080
17871
  } else {
17081
- const projectsJson = registryProjects ? JSON.stringify({ projects: registryProjects }) : (0, import_node_fs16.existsSync)("projects.json") ? (0, import_node_fs16.readFileSync)("projects.json", "utf8") : null;
17872
+ const projectsJson = registryProjects ? JSON.stringify({ projects: registryProjects }) : (0, import_node_fs18.existsSync)("projects.json") ? (0, import_node_fs18.readFileSync)("projects.json", "utf8") : null;
17082
17873
  if (!projectsJson) return failGraceful("access audit: no project registry \u2014 Hub API unreachable and projects.json not found; run from the MMI-Hub repo root or pass --repo <owner/repo>");
17083
- const fanoutJson = (0, import_node_fs16.existsSync)(".github/fanout-targets.json") ? (0, import_node_fs16.readFileSync)(".github/fanout-targets.json", "utf8") : null;
17874
+ const fanoutJson = (0, import_node_fs18.existsSync)(".github/fanout-targets.json") ? (0, import_node_fs18.readFileSync)(".github/fanout-targets.json", "utf8") : null;
17084
17875
  targets = loadAccessTargets(projectsJson, fanoutJson);
17085
17876
  }
17086
17877
  const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
17087
- const fileMatrix = (0, import_node_fs16.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs16.readFileSync)("access-matrix.json", "utf8")) : {};
17878
+ const fileMatrix = (0, import_node_fs18.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs18.readFileSync)("access-matrix.json", "utf8")) : {};
17088
17879
  const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
17089
17880
  const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
17090
- const fileContracts = (0, import_node_fs16.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs16.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
17881
+ const fileContracts = (0, import_node_fs18.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs18.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
17091
17882
  const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
17092
17883
  const report = await auditOrgAccess(targets, deps, matrix, dataAccess);
17093
17884
  console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
@@ -17096,20 +17887,20 @@ access.command("audit").description("audit collaborator roles + train-branch pus
17096
17887
  var isWin = process.platform === "win32";
17097
17888
  var installedPluginsPath = (surface = detectSurface(process.env)) => {
17098
17889
  const homeDir = surface === "codex" ? ".codex" : ".claude";
17099
- return (0, import_node_path14.join)((0, import_node_os5.homedir)(), homeDir, "plugins", "installed_plugins.json");
17890
+ return (0, import_node_path16.join)((0, import_node_os5.homedir)(), homeDir, "plugins", "installed_plugins.json");
17100
17891
  };
17101
17892
  function readInstalledPlugins() {
17102
17893
  try {
17103
- return JSON.parse((0, import_node_fs16.readFileSync)(installedPluginsPath(), "utf8"));
17894
+ return JSON.parse((0, import_node_fs18.readFileSync)(installedPluginsPath(), "utf8"));
17104
17895
  } catch {
17105
17896
  return null;
17106
17897
  }
17107
17898
  }
17108
17899
  function installedPluginSources() {
17109
17900
  return ["claude", "codex"].map((surface) => {
17110
- const recordPath = (0, import_node_path14.join)((0, import_node_os5.homedir)(), `.${surface}`, "plugins", "installed_plugins.json");
17901
+ const recordPath = (0, import_node_path16.join)((0, import_node_os5.homedir)(), `.${surface}`, "plugins", "installed_plugins.json");
17111
17902
  try {
17112
- return { surface, installed: JSON.parse((0, import_node_fs16.readFileSync)(recordPath, "utf8")), recordPath };
17903
+ return { surface, installed: JSON.parse((0, import_node_fs18.readFileSync)(recordPath, "utf8")), recordPath };
17113
17904
  } catch {
17114
17905
  return { surface, installed: null, recordPath };
17115
17906
  }
@@ -17117,7 +17908,7 @@ function installedPluginSources() {
17117
17908
  }
17118
17909
  function readClaudeSettings() {
17119
17910
  try {
17120
- return JSON.parse((0, import_node_fs16.readFileSync)((0, import_node_path14.join)(process.cwd(), ".claude", "settings.json"), "utf8"));
17911
+ return JSON.parse((0, import_node_fs18.readFileSync)((0, import_node_path16.join)(process.cwd(), ".claude", "settings.json"), "utf8"));
17121
17912
  } catch {
17122
17913
  return null;
17123
17914
  }
@@ -17139,7 +17930,7 @@ function writeProjectInstallRecord(record) {
17139
17930
  const list = file.plugins[MMI_PLUGIN_ID] ?? [];
17140
17931
  list.push(record);
17141
17932
  file.plugins[MMI_PLUGIN_ID] = list;
17142
- (0, import_node_fs16.writeFileSync)(installedPluginsPath(), `${JSON.stringify(file, null, 2)}
17933
+ (0, import_node_fs18.writeFileSync)(installedPluginsPath(), `${JSON.stringify(file, null, 2)}
17143
17934
  `, "utf8");
17144
17935
  return true;
17145
17936
  } catch {
@@ -17152,9 +17943,9 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
17152
17943
  if (!file) return false;
17153
17944
  if (!file.plugins) file.plugins = {};
17154
17945
  const path2 = installedPluginsPath();
17155
- (0, import_node_fs16.copyFileSync)(path2, `${path2}.bak`);
17946
+ (0, import_node_fs18.copyFileSync)(path2, `${path2}.bak`);
17156
17947
  file.plugins[pluginId] = records;
17157
- (0, import_node_fs16.writeFileSync)(path2, `${JSON.stringify(file, null, 2)}
17948
+ (0, import_node_fs18.writeFileSync)(path2, `${JSON.stringify(file, null, 2)}
17158
17949
  `, "utf8");
17159
17950
  return true;
17160
17951
  } catch {
@@ -17162,35 +17953,35 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
17162
17953
  }
17163
17954
  }
17164
17955
  function cursorPluginCacheRoot() {
17165
- return (0, import_node_path14.join)((0, import_node_os5.homedir)(), ".cursor", "plugins", "cache", "mmi", "mmi");
17956
+ return (0, import_node_path16.join)((0, import_node_os5.homedir)(), ".cursor", "plugins", "cache", "mmi", "mmi");
17166
17957
  }
17167
17958
  function cursorPluginCachePinSnapshots() {
17168
17959
  const root = cursorPluginCacheRoot();
17169
17960
  try {
17170
- return (0, import_node_fs16.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => {
17171
- const path2 = (0, import_node_path14.join)(root, entry.name);
17172
- const pluginJson = (0, import_node_path14.join)(path2, ".cursor-plugin", "plugin.json");
17173
- const hooksJson = (0, import_node_path14.join)(path2, "hooks", "hooks.json");
17174
- const cliBundle = (0, import_node_path14.join)(path2, "cli", "dist", "index.cjs");
17961
+ return (0, import_node_fs18.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => {
17962
+ const path2 = (0, import_node_path16.join)(root, entry.name);
17963
+ const pluginJson = (0, import_node_path16.join)(path2, ".cursor-plugin", "plugin.json");
17964
+ const hooksJson = (0, import_node_path16.join)(path2, "hooks", "hooks.json");
17965
+ const cliBundle = (0, import_node_path16.join)(path2, "cli", "dist", "index.cjs");
17175
17966
  let version;
17176
17967
  try {
17177
- const raw = JSON.parse((0, import_node_fs16.readFileSync)(pluginJson, "utf8"));
17968
+ const raw = JSON.parse((0, import_node_fs18.readFileSync)(pluginJson, "utf8"));
17178
17969
  version = typeof raw.version === "string" ? raw.version : void 0;
17179
17970
  } catch {
17180
17971
  version = void 0;
17181
17972
  }
17182
17973
  let isEmpty = true;
17183
17974
  try {
17184
- isEmpty = (0, import_node_fs16.readdirSync)(path2).length === 0;
17975
+ isEmpty = (0, import_node_fs18.readdirSync)(path2).length === 0;
17185
17976
  } catch {
17186
17977
  isEmpty = true;
17187
17978
  }
17188
17979
  return {
17189
17980
  name: entry.name,
17190
17981
  path: path2,
17191
- hasPluginJson: (0, import_node_fs16.existsSync)(pluginJson),
17192
- hasHooksJson: (0, import_node_fs16.existsSync)(hooksJson),
17193
- hasCliBundle: (0, import_node_fs16.existsSync)(cliBundle),
17982
+ hasPluginJson: (0, import_node_fs18.existsSync)(pluginJson),
17983
+ hasHooksJson: (0, import_node_fs18.existsSync)(hooksJson),
17984
+ hasCliBundle: (0, import_node_fs18.existsSync)(cliBundle),
17194
17985
  isEmpty,
17195
17986
  version
17196
17987
  };
@@ -17200,19 +17991,19 @@ function cursorPluginCachePinSnapshots() {
17200
17991
  }
17201
17992
  }
17202
17993
  function hubCheckoutForCursorSeed() {
17203
- const manifest = (0, import_node_path14.join)(process.cwd(), "plugins", "mmi", ".cursor-plugin", "plugin.json");
17204
- return (0, import_node_fs16.existsSync)(manifest) ? process.cwd() : void 0;
17994
+ const manifest = (0, import_node_path16.join)(process.cwd(), "plugins", "mmi", ".cursor-plugin", "plugin.json");
17995
+ return (0, import_node_fs18.existsSync)(manifest) ? process.cwd() : void 0;
17205
17996
  }
17206
17997
  function mmiPluginCacheRootSnapshots() {
17207
17998
  const roots = [
17208
- { surface: "claude", root: (0, import_node_path14.join)((0, import_node_os5.homedir)(), ".claude", "plugins", "cache", "mmi", "mmi") },
17209
- { surface: "codex", root: (0, import_node_path14.join)((0, import_node_os5.homedir)(), ".codex", "plugins", "cache", "mmi", "mmi") }
17999
+ { surface: "claude", root: (0, import_node_path16.join)((0, import_node_os5.homedir)(), ".claude", "plugins", "cache", "mmi", "mmi") },
18000
+ { surface: "codex", root: (0, import_node_path16.join)((0, import_node_os5.homedir)(), ".codex", "plugins", "cache", "mmi", "mmi") }
17210
18001
  ];
17211
18002
  return roots.flatMap(({ surface, root }) => {
17212
18003
  try {
17213
- const entries = (0, import_node_fs16.readdirSync)(root, { withFileTypes: true }).map((entry) => ({
18004
+ const entries = (0, import_node_fs18.readdirSync)(root, { withFileTypes: true }).map((entry) => ({
17214
18005
  name: entry.name,
17215
- path: (0, import_node_path14.join)(root, entry.name),
18006
+ path: (0, import_node_path16.join)(root, entry.name),
17216
18007
  isDirectory: entry.isDirectory()
17217
18008
  }));
17218
18009
  return [{ surface, root, entries }];
@@ -17223,7 +18014,7 @@ function mmiPluginCacheRootSnapshots() {
17223
18014
  }
17224
18015
  function hasNestedMmiChild(versionDir) {
17225
18016
  try {
17226
- return (0, import_node_fs16.statSync)((0, import_node_path14.join)(versionDir, "mmi")).isDirectory();
18017
+ return (0, import_node_fs18.statSync)((0, import_node_path16.join)(versionDir, "mmi")).isDirectory();
17227
18018
  } catch {
17228
18019
  return false;
17229
18020
  }
@@ -17234,26 +18025,28 @@ function nestedPluginTreeSnapshot() {
17234
18025
  );
17235
18026
  }
17236
18027
  function uniqueQuarantineTarget(path2) {
17237
- if (!(0, import_node_fs16.existsSync)(path2)) return path2;
18028
+ if (!(0, import_node_fs18.existsSync)(path2)) return path2;
17238
18029
  for (let i = 1; i < 100; i += 1) {
17239
18030
  const candidate = `${path2}-${i}`;
17240
- if (!(0, import_node_fs16.existsSync)(candidate)) return candidate;
18031
+ if (!(0, import_node_fs18.existsSync)(candidate)) return candidate;
17241
18032
  }
17242
18033
  return `${path2}-${Date.now()}`;
17243
18034
  }
17244
18035
  function quarantinePluginCacheDirs(plan2) {
17245
18036
  let moved = 0;
18037
+ const failed = [];
17246
18038
  for (const move of plan2) {
17247
18039
  try {
17248
- if (!(0, import_node_fs16.existsSync)(move.from)) continue;
18040
+ if (!(0, import_node_fs18.existsSync)(move.from)) continue;
17249
18041
  const target = uniqueQuarantineTarget(move.to);
17250
- (0, import_node_fs16.mkdirSync)((0, import_node_path14.dirname)(target), { recursive: true });
17251
- (0, import_node_fs16.renameSync)(move.from, target);
18042
+ (0, import_node_fs18.mkdirSync)((0, import_node_path16.dirname)(target), { recursive: true });
18043
+ (0, import_node_fs18.renameSync)(move.from, target);
17252
18044
  moved += 1;
17253
18045
  } catch {
18046
+ failed.push(move);
17254
18047
  }
17255
18048
  }
17256
- return moved;
18049
+ return { moved, failed };
17257
18050
  }
17258
18051
  async function robocopyMirrorEmpty(emptyDir, target) {
17259
18052
  try {
@@ -17266,23 +18059,23 @@ async function robocopyMirrorEmpty(emptyDir, target) {
17266
18059
  }
17267
18060
  async function clearNestedPluginTreeDir(targetPath) {
17268
18061
  try {
17269
- if (!(0, import_node_fs16.existsSync)(targetPath)) return true;
18062
+ if (!(0, import_node_fs18.existsSync)(targetPath)) return true;
17270
18063
  if (isWin) {
17271
- const emptyDir = (0, import_node_path14.join)((0, import_node_os5.tmpdir)(), `mmi-empty-${Date.now()}`);
17272
- (0, import_node_fs16.mkdirSync)(emptyDir, { recursive: true });
18064
+ const emptyDir = (0, import_node_path16.join)((0, import_node_os5.tmpdir)(), `mmi-empty-${Date.now()}`);
18065
+ (0, import_node_fs18.mkdirSync)(emptyDir, { recursive: true });
17273
18066
  try {
17274
18067
  await robocopyMirrorEmpty(emptyDir, targetPath);
17275
- (0, import_node_fs16.rmSync)(targetPath, { recursive: true, force: true });
18068
+ (0, import_node_fs18.rmSync)(targetPath, { recursive: true, force: true });
17276
18069
  } finally {
17277
18070
  try {
17278
- (0, import_node_fs16.rmSync)(emptyDir, { recursive: true, force: true });
18071
+ (0, import_node_fs18.rmSync)(emptyDir, { recursive: true, force: true });
17279
18072
  } catch {
17280
18073
  }
17281
18074
  }
17282
- return !(0, import_node_fs16.existsSync)(targetPath);
18075
+ return !(0, import_node_fs18.existsSync)(targetPath);
17283
18076
  }
17284
- (0, import_node_fs16.rmSync)(targetPath, { recursive: true, force: true });
17285
- return !(0, import_node_fs16.existsSync)(targetPath);
18077
+ (0, import_node_fs18.rmSync)(targetPath, { recursive: true, force: true });
18078
+ return !(0, import_node_fs18.existsSync)(targetPath);
17286
18079
  } catch {
17287
18080
  return false;
17288
18081
  }
@@ -17295,11 +18088,11 @@ async function applyNestedPluginTreeCleanup(paths, log) {
17295
18088
  }
17296
18089
  return true;
17297
18090
  }
17298
- var gitignorePath = () => (0, import_node_path14.join)(process.cwd(), ".gitignore");
18091
+ var gitignorePath = () => (0, import_node_path16.join)(process.cwd(), ".gitignore");
17299
18092
  function readTextFile(path2) {
17300
18093
  try {
17301
- if (!(0, import_node_fs16.existsSync)(path2)) return null;
17302
- return (0, import_node_fs16.readFileSync)(path2, "utf8");
18094
+ if (!(0, import_node_fs18.existsSync)(path2)) return null;
18095
+ return (0, import_node_fs18.readFileSync)(path2, "utf8");
17303
18096
  } catch {
17304
18097
  return null;
17305
18098
  }
@@ -17308,9 +18101,9 @@ function playwrightMcpConfigSnapshots() {
17308
18101
  const cwd = process.cwd();
17309
18102
  const home = (0, import_node_os5.homedir)();
17310
18103
  const candidates = [
17311
- (0, import_node_path14.join)(cwd, ".cursor", "mcp.json"),
17312
- (0, import_node_path14.join)(home, ".cursor", "mcp.json"),
17313
- (0, import_node_path14.join)(home, ".codex", "config.toml")
18104
+ (0, import_node_path16.join)(cwd, ".cursor", "mcp.json"),
18105
+ (0, import_node_path16.join)(home, ".cursor", "mcp.json"),
18106
+ (0, import_node_path16.join)(home, ".codex", "config.toml")
17314
18107
  ];
17315
18108
  const out = [];
17316
18109
  for (const path2 of candidates) {
@@ -17323,7 +18116,7 @@ function strayBrowserArtifactPaths() {
17323
18116
  const cwd = process.cwd();
17324
18117
  return STRAY_BROWSER_ARTIFACT_DIRS.filter((rel) => {
17325
18118
  try {
17326
- return (0, import_node_fs16.existsSync)((0, import_node_path14.join)(cwd, rel));
18119
+ return (0, import_node_fs18.existsSync)((0, import_node_path16.join)(cwd, rel));
17327
18120
  } catch {
17328
18121
  return false;
17329
18122
  }
@@ -17331,14 +18124,14 @@ function strayBrowserArtifactPaths() {
17331
18124
  }
17332
18125
  function readGitignore() {
17333
18126
  try {
17334
- return (0, import_node_fs16.readFileSync)(gitignorePath(), "utf8");
18127
+ return (0, import_node_fs18.readFileSync)(gitignorePath(), "utf8");
17335
18128
  } catch {
17336
18129
  return null;
17337
18130
  }
17338
18131
  }
17339
18132
  function writeGitignore(content) {
17340
18133
  try {
17341
- (0, import_node_fs16.writeFileSync)(gitignorePath(), content, "utf8");
18134
+ (0, import_node_fs18.writeFileSync)(gitignorePath(), content, "utf8");
17342
18135
  return true;
17343
18136
  } catch {
17344
18137
  return false;
@@ -17377,7 +18170,7 @@ async function runDoctor(opts, io = consoleIo) {
17377
18170
  let onPath = pathProbe;
17378
18171
  if (!onPath) {
17379
18172
  const root = process.env.CLAUDE_PLUGIN_ROOT;
17380
- if (root && (0, import_node_fs16.existsSync)(`${root}/bin/mmi-cli${isWin ? ".cmd" : ""}`)) onPath = true;
18173
+ if (root && (0, import_node_fs18.existsSync)(`${root}/bin/mmi-cli${isWin ? ".cmd" : ""}`)) onPath = true;
17381
18174
  }
17382
18175
  checks.push({ ok: onPath, label: "mmi-cli on PATH", fix: "auto-provisioned at session start \u2014 reopen the session, or install the MMI plugin" });
17383
18176
  const surface = detectSurface(process.env);
@@ -17494,7 +18287,8 @@ async function runDoctor(opts, io = consoleIo) {
17494
18287
  installedVersions: installedPluginVersions(installed)
17495
18288
  });
17496
18289
  if (!cacheCleanupCheck.ok && cacheCleanupCheck.quarantinePlan && repairLocal) {
17497
- const moved = quarantinePluginCacheDirs(cacheCleanupCheck.quarantinePlan);
18290
+ const { moved, failed } = quarantinePluginCacheDirs(cacheCleanupCheck.quarantinePlan);
18291
+ const attempted = moved + failed.length;
17498
18292
  if (moved > 0) {
17499
18293
  const surfaces = [...new Set(cacheCleanupCheck.leftovers?.map((entry) => entry.surface) ?? [])].join("/");
17500
18294
  const names = cacheCleanupCheck.leftovers?.map((entry) => entry.name).join(", ");
@@ -17505,8 +18299,12 @@ async function runDoctor(opts, io = consoleIo) {
17505
18299
  isOrgRepo: Boolean(cfg.sagaApiUrl),
17506
18300
  roots: mmiPluginCacheRootSnapshots(),
17507
18301
  activeVersion: resolveClientVersion(),
17508
- releasedVersion
18302
+ releasedVersion,
18303
+ installedVersions: installedPluginVersions(installed)
17509
18304
  }),
18305
+ attemptedCount: attempted,
18306
+ failedCount: failed.length,
18307
+ ...failed.length > 0 ? { failedMoves: failed } : {},
17510
18308
  ...moved > 0 ? { cleanedCount: moved } : {}
17511
18309
  };
17512
18310
  }
@@ -17544,7 +18342,7 @@ async function runDoctor(opts, io = consoleIo) {
17544
18342
  isOrgRepo: Boolean(cfg.sagaApiUrl),
17545
18343
  surface,
17546
18344
  cacheRoot: cursorCacheRoot,
17547
- cacheRootExists: (0, import_node_fs16.existsSync)(cursorCacheRoot),
18345
+ cacheRootExists: (0, import_node_fs18.existsSync)(cursorCacheRoot),
17548
18346
  pins: cursorPins,
17549
18347
  hubCheckout: hubCheckoutForCursorSeed(),
17550
18348
  releasedVersion
@@ -17555,7 +18353,7 @@ async function runDoctor(opts, io = consoleIo) {
17555
18353
  releasedVersion,
17556
18354
  hubCheckout: hubCheckoutForCursorSeed(),
17557
18355
  execFileP: execFileP2,
17558
- mkdtemp: (prefix) => (0, import_promises5.mkdtemp)((0, import_node_path14.join)((0, import_node_os5.tmpdir)(), prefix)),
18356
+ mkdtemp: (prefix) => (0, import_promises5.mkdtemp)((0, import_node_path16.join)((0, import_node_os5.tmpdir)(), prefix)),
17559
18357
  log: (m) => io.err(m)
17560
18358
  });
17561
18359
  if (seeded) {
@@ -17564,7 +18362,7 @@ async function runDoctor(opts, io = consoleIo) {
17564
18362
  isOrgRepo: Boolean(cfg.sagaApiUrl),
17565
18363
  surface,
17566
18364
  cacheRoot: cursorCacheRoot,
17567
- cacheRootExists: (0, import_node_fs16.existsSync)(cursorCacheRoot),
18365
+ cacheRootExists: (0, import_node_fs18.existsSync)(cursorCacheRoot),
17568
18366
  pins: cursorPins,
17569
18367
  hubCheckout: hubCheckoutForCursorSeed(),
17570
18368
  releasedVersion
@@ -17647,6 +18445,7 @@ program2.command("session-start").description("run the SessionStart verbs (rules
17647
18445
  const { parallel, sequential } = buildSessionStartPlan({
17648
18446
  rulesSync: (io) => runRulesSync({ quiet: true }, io),
17649
18447
  sagaShow: (io) => runSagaShow({ quiet: true }, io),
18448
+ handoffOffer: (io) => runHandoffOffer(io, { fast: true }),
17650
18449
  // honcho profile (#1162): inject the behavioral-memory prior (peer card). Bounded + fail-soft +
17651
18450
  // silent when off/empty, so it never delays or noises the session banner.
17652
18451
  honchoContext: (io) => runHonchoContext({ quiet: true, banner: true }, io),
@@ -17680,6 +18479,12 @@ program2.command("session-start").description("run the SessionStart verbs (rules
17680
18479
  await runSessionStart(parallel, sequential, consoleIo);
17681
18480
  consoleIo.log(northstarPointer(northstarInjected));
17682
18481
  for (const line of planStoreLines(process.cwd())) consoleIo.log(line);
18482
+ for (const line of scratchGcLines(process.cwd())) consoleIo.log(line);
18483
+ const worktreeBanner = worktreeAutoProvisionBanner(process.cwd());
18484
+ if (worktreeBanner) {
18485
+ spawnDetachedSelf(["worktree", "setup", "--quiet"], { spawn: import_node_child_process10.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
18486
+ consoleIo.log(worktreeBanner);
18487
+ }
17683
18488
  });
17684
18489
  installProcessBackstop();
17685
18490
  program2.parseAsync().catch((e) => failGraceful(e.message));