@aihq/harness 0.2.0 → 0.3.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.
@@ -83,15 +83,33 @@ function retryTransient(op) {
83
83
  }
84
84
  var FsTransaction = class {
85
85
  staged = [];
86
+ stagedRemovals = [];
86
87
  stage(path, contents, mode) {
87
88
  this.staged.push({ path, contents, mode });
88
89
  }
90
+ /**
91
+ * Stage a file REMOVAL as a reversible move to `legacyPath` (under gitignored
92
+ * `.aih/legacy/`). The move IS the backup: rollback (and the user) restore by
93
+ * moving it back. Symlinks are refused at commit (moving a link then restoring it
94
+ * would recreate a regular file). No-op if the source is already gone.
95
+ */
96
+ stageRemoval(path, legacyPath) {
97
+ this.stagedRemovals.push({ path, legacyPath });
98
+ }
89
99
  preview() {
90
100
  return [...this.staged];
91
101
  }
92
102
  commit() {
93
103
  const applied = [];
104
+ const removed = [];
94
105
  const staged = dedupeByPath(this.staged);
106
+ const removals = dedupeRemovals(this.stagedRemovals);
107
+ const writePaths = new Set(staged.map((w) => w.path));
108
+ for (const r of removals) {
109
+ if (writePaths.has(r.path)) {
110
+ throw new FsTxnError(`transaction both writes and removes the same path: ${r.path}`);
111
+ }
112
+ }
95
113
  try {
96
114
  for (const w of staged) {
97
115
  mkdirSync(dirname(w.path), { recursive: true });
@@ -114,16 +132,59 @@ var FsTransaction = class {
114
132
  retryTransient(() => renameSync(tmpPath, w.path));
115
133
  applied.push({ path: w.path, backup, created: !existed });
116
134
  }
135
+ for (const r of removals) {
136
+ const info = lstatSafe(r.path);
137
+ if (info === void 0) continue;
138
+ if (info.isSymbolicLink()) {
139
+ throw new Error(`refusing to remove a symlink: ${r.path}`);
140
+ }
141
+ mkdirSync(dirname(r.legacyPath), { recursive: true });
142
+ const dest = freeLegacyDest(r.legacyPath);
143
+ retryTransient(() => renameSync(r.path, dest));
144
+ removed.push({ path: r.path, legacyPath: dest });
145
+ }
117
146
  return {
118
147
  written: applied.map((a) => a.path),
119
- backups: applied.flatMap((a) => a.backup ? [a.backup] : [])
148
+ backups: applied.flatMap((a) => a.backup ? [a.backup] : []),
149
+ removed
120
150
  };
121
151
  } catch (err) {
152
+ rollbackRemovals(removed);
122
153
  rollback(applied);
123
154
  throw new FsTxnError(`transaction failed and was rolled back: ${err.message}`);
124
155
  }
125
156
  }
126
157
  };
158
+ function dedupeRemovals(staged) {
159
+ const byPath = /* @__PURE__ */ new Map();
160
+ for (const r of staged) byPath.set(r.path, r);
161
+ return [...byPath.values()];
162
+ }
163
+ function freeLegacyDest(base) {
164
+ const check2 = (p) => {
165
+ const st = lstatSafe(p);
166
+ if (st === void 0) return "free";
167
+ if (st.isSymbolicLink()) throw new Error(`refusing to move onto a symlinked legacy path: ${p}`);
168
+ return "file";
169
+ };
170
+ if (check2(base) === "free") return base;
171
+ for (let n = 1; n < 1e5; n++) {
172
+ const cand = `${base}.${n}`;
173
+ if (check2(cand) === "free") return cand;
174
+ }
175
+ throw new Error(`too many prior rescues at ${base}`);
176
+ }
177
+ function rollbackRemovals(removed) {
178
+ for (const r of [...removed].reverse()) {
179
+ try {
180
+ if (existsSync(r.legacyPath) && !existsSync(r.path)) {
181
+ mkdirSync(dirname(r.path), { recursive: true });
182
+ renameSync(r.legacyPath, r.path);
183
+ }
184
+ } catch {
185
+ }
186
+ }
187
+ }
127
188
  function dedupeByPath(staged) {
128
189
  const byPath = /* @__PURE__ */ new Map();
129
190
  for (const w of staged) byPath.set(w.path, w);
@@ -242,6 +303,9 @@ function exec(describe, argv, opts = {}) {
242
303
  function envBlock(path, scope, shell, vars, describe) {
243
304
  return { kind: "envblock", path, scope, shell, vars, describe };
244
305
  }
306
+ function remove(path, describe) {
307
+ return { kind: "remove", path, describe };
308
+ }
245
309
  function plan(capability, ...actions) {
246
310
  return { capability, actions };
247
311
  }
@@ -427,7 +491,7 @@ var VerificationReport = class {
427
491
  };
428
492
 
429
493
  // src/internals/execute.ts
430
- import { existsSync as existsSync2, realpathSync } from "fs";
494
+ import { existsSync as existsSync2, lstatSync as lstatSync2, realpathSync } from "fs";
431
495
  import { dirname as dirname2, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2 } from "path";
432
496
 
433
497
  // src/guardrails/gitleaks.ts
@@ -537,11 +601,30 @@ async function dirtyWriteTargets(plan2, ctx) {
537
601
  const dirty = await dirtyPaths(ctx);
538
602
  return targets.filter((t) => dirty.has(t));
539
603
  }
604
+ async function dirtyRemoveTargets(plan2, ctx) {
605
+ const targets = [];
606
+ for (const a of plan2.actions) {
607
+ if (a.kind !== "remove") continue;
608
+ const rel = relative(ctx.root, resolve(ctx.root, a.path));
609
+ if (rel.length > 0 && !rel.startsWith("..") && !isAbsolute(rel))
610
+ targets.push(normalizeRel(rel));
611
+ }
612
+ if (targets.length === 0) return [];
613
+ const dirty = await dirtyPaths(ctx);
614
+ return targets.filter((t) => dirty.has(t));
615
+ }
540
616
 
541
617
  // src/internals/execute.ts
542
618
  function resolvePath(ctx, p) {
543
619
  return resolve2(ctx.root, p);
544
620
  }
621
+ function lstatKind(p) {
622
+ try {
623
+ return { isSymlink: lstatSync2(p).isSymbolicLink() };
624
+ } catch {
625
+ return void 0;
626
+ }
627
+ }
545
628
  function realpathSafe(p) {
546
629
  try {
547
630
  return realpathSync(p);
@@ -605,10 +688,12 @@ async function executePlan(plan2, ctx, opts = {}) {
605
688
  if (ctx.apply && opts.skipWorktreeGate !== true && ctx.options.force !== true) {
606
689
  const dirtyTargets = new Set(await dirtyWriteTargets(plan2, ctx));
607
690
  const clobbered = dirtyTargets.size === 0 ? [] : changedDirtyTargets(plan2, ctx, dirtyTargets);
608
- if (clobbered.length > 0) {
609
- const list = clobbered.join(", ");
691
+ const removedDirty = await dirtyRemoveTargets(plan2, ctx);
692
+ const blocked = [...clobbered, ...removedDirty];
693
+ if (blocked.length > 0) {
694
+ const list = blocked.join(", ");
610
695
  throw new DirtyWorktreeError(
611
- `Refusing to overwrite uncommitted changes in: ${list}. Commit or stash ${clobbered.length > 1 ? "them" : "it"} first, or pass --force.`
696
+ `Refusing to overwrite uncommitted changes in: ${list}. Commit or stash ${blocked.length > 1 ? "them" : "it"} first, or pass --force.`
612
697
  );
613
698
  }
614
699
  }
@@ -617,6 +702,7 @@ async function executePlan(plan2, ctx, opts = {}) {
617
702
  const docs = [];
618
703
  const probes = [];
619
704
  const digests = [];
705
+ const removes = [];
620
706
  const digestActions = [];
621
707
  const execActions = [];
622
708
  const envBlockActions = [];
@@ -659,6 +745,29 @@ async function executePlan(plan2, ctx, opts = {}) {
659
745
  envBlockActions.push(action);
660
746
  } else if (action.kind === "digest") {
661
747
  digestActions.push(action);
748
+ } else if (action.kind === "remove") {
749
+ const absPath = resolvePath(ctx, action.path);
750
+ assertContained(ctx.root, absPath);
751
+ const info = lstatKind(absPath);
752
+ if (info?.isSymlink) {
753
+ throw new PathContainmentError(
754
+ `refusing to remove a symlink: ${action.path} (aih only removes files it wrote)`
755
+ );
756
+ }
757
+ if (info === void 0) {
758
+ removes.push({ path: action.path, describe: action.describe, effect: "absent" });
759
+ } else {
760
+ const legacyRel = `.aih/legacy/${normalizeRel(action.path)}`;
761
+ const legacyAbs = resolvePath(ctx, legacyRel);
762
+ assertContained(ctx.root, legacyAbs);
763
+ if (ctx.apply) txn.stageRemoval(absPath, legacyAbs);
764
+ removes.push({
765
+ path: action.path,
766
+ describe: action.describe,
767
+ effect: "remove",
768
+ to: legacyRel
769
+ });
770
+ }
662
771
  } else {
663
772
  probes.push({ describe: action.describe });
664
773
  }
@@ -748,15 +857,23 @@ async function executePlan(plan2, ctx, opts = {}) {
748
857
  execs,
749
858
  digests,
750
859
  backups,
860
+ removed: removes,
751
861
  report
752
862
  };
753
863
  }
754
864
  function summarizeResult(result) {
755
- const head = result.applied ? `Applied ${result.capability}` : `Plan for ${result.capability} (dry-run \u2014 nothing written; pass --apply to execute)`;
865
+ const removedAny = result.removed.some((r) => r.effect === "remove");
866
+ const mutated = result.writes.length > 0 || result.execs.some((e) => e.ran) || result.backups.length > 0 || removedAny;
867
+ const head = result.applied ? mutated ? `Applied ${result.capability}` : `${result.capability}: nothing to apply \u2014 the plan produced no writes or execs` : `Plan for ${result.capability} (dry-run \u2014 nothing written; pass --apply to execute)`;
756
868
  const out = [head];
757
869
  for (const w of result.writes) {
758
870
  out.push(` [${w.effect}] ${w.path} \u2014 ${w.describe}`);
759
871
  }
872
+ for (const r of result.removed) {
873
+ out.push(
874
+ r.effect === "remove" ? ` [remove] ${r.path} \u2014 ${r.describe} (\u2192 ${r.to})` : ` [absent] ${r.path} \u2014 ${r.describe}`
875
+ );
876
+ }
760
877
  for (const d of result.docs) {
761
878
  out.push(` [doc]${d.path ? ` ${d.path}` : ""} \u2014 ${d.describe}`);
762
879
  }
@@ -2387,6 +2504,17 @@ ${rendered}
2387
2504
  if (!next.endsWith("\n")) next += "\n";
2388
2505
  return usesCrlf ? next.replace(/\n/g, "\r\n") : next;
2389
2506
  }
2507
+ function stripManagedBlock(existing, marker) {
2508
+ const normalized = existing.replace(/\r\n/g, "\n");
2509
+ const pattern = new RegExp(
2510
+ `\\n*<!-- BEGIN ${escapeRegExp2(marker)}[\\s\\S]*?${escapeRegExp2(endLine(marker))}\\n*`
2511
+ );
2512
+ if (!pattern.test(normalized)) return existing;
2513
+ const stripped = normalized.replace(pattern, "\n\n").replace(/^\n+/, "").replace(/\n+$/, "");
2514
+ const next = stripped.length > 0 ? `${stripped}
2515
+ ` : "";
2516
+ return /\r\n/.test(existing) ? next.replace(/\n/g, "\r\n") : next;
2517
+ }
2390
2518
  function extractManagedBlock(text, marker) {
2391
2519
  const normalized = text.replace(/\r\n/g, "\n");
2392
2520
  const pattern = new RegExp(
@@ -5263,7 +5391,7 @@ var command5 = {
5263
5391
  };
5264
5392
 
5265
5393
  // src/vdi/index.ts
5266
- import { lstatSync as lstatSync2, readlinkSync } from "fs";
5394
+ import { lstatSync as lstatSync3, readlinkSync } from "fs";
5267
5395
  import { join as join17, resolve as resolve4 } from "path";
5268
5396
 
5269
5397
  // src/internals/shell-safety.ts
@@ -5314,7 +5442,7 @@ function samePath(a, b) {
5314
5442
  function linkState(linkPath, target) {
5315
5443
  let st;
5316
5444
  try {
5317
- st = lstatSync2(linkPath);
5445
+ st = lstatSync3(linkPath);
5318
5446
  } catch {
5319
5447
  return "absent";
5320
5448
  }
@@ -6968,8 +7096,8 @@ var command10 = {
6968
7096
  };
6969
7097
 
6970
7098
  // src/doctor.ts
6971
- import { existsSync as existsSync17 } from "fs";
6972
- import { join as join32 } from "path";
7099
+ import { existsSync as existsSync18 } from "fs";
7100
+ import { join as join33 } from "path";
6973
7101
 
6974
7102
  // src/org-policy/drift.ts
6975
7103
  import { resolve as resolve7 } from "path";
@@ -6979,8 +7107,8 @@ import { join as join24 } from "path";
6979
7107
  function stdioCommand(server) {
6980
7108
  return [server.command, ...server.args];
6981
7109
  }
6982
- function commandKey(command28) {
6983
- return JSON.stringify([...command28]);
7110
+ function commandKey(command30) {
7111
+ return JSON.stringify([...command30]);
6984
7112
  }
6985
7113
  function sortedCommands(commands) {
6986
7114
  return [...commands].sort((a, b) => commandKey(a).localeCompare(commandKey(b)));
@@ -7021,7 +7149,7 @@ function managedCommands(root) {
7021
7149
  (entry2) => entry2 && typeof entry2 === "object" && Array.isArray(entry2.serverCommand) ? entry2.serverCommand.filter(
7022
7150
  (arg) => typeof arg === "string"
7023
7151
  ) : []
7024
- ).filter((command28) => command28.length > 0)
7152
+ ).filter((command30) => command30.length > 0)
7025
7153
  );
7026
7154
  }
7027
7155
  function mcpManagedAllowlistCheck(ctx) {
@@ -7804,8 +7932,8 @@ function cloneRules(rules) {
7804
7932
  return rules.map((rule) => ({ ...rule }));
7805
7933
  }
7806
7934
  function applyCommandDelta(base, delta) {
7807
- const remove = new Set(delta?.remove ?? []);
7808
- const kept = base.filter((rule) => !remove.has(rule.pattern)).map((rule) => ({ ...rule }));
7935
+ const remove2 = new Set(delta?.remove ?? []);
7936
+ const kept = base.filter((rule) => !remove2.has(rule.pattern)).map((rule) => ({ ...rule }));
7809
7937
  const seen = new Set(kept.map((rule) => rule.pattern));
7810
7938
  for (const rule of delta?.add ?? []) {
7811
7939
  if (seen.has(rule.pattern)) continue;
@@ -8156,8 +8284,8 @@ async function contractTruthCheck(ctx) {
8156
8284
  }
8157
8285
 
8158
8286
  // src/report/cli-coverage.ts
8159
- import { existsSync as existsSync14 } from "fs";
8160
- import { join as join27 } from "path";
8287
+ import { existsSync as existsSync15 } from "fs";
8288
+ import { join as join28 } from "path";
8161
8289
 
8162
8290
  // src/mcp/render.ts
8163
8291
  import { isAbsolute as isAbsolute5, join as join25 } from "path";
@@ -8235,9 +8363,118 @@ function existingMcpTomlNames(existing, scope) {
8235
8363
  return names;
8236
8364
  }
8237
8365
 
8238
- // src/report/cli-loadability.ts
8239
- import { existsSync as existsSync13 } from "fs";
8366
+ // src/prune/detect.ts
8367
+ import { existsSync as existsSync13, readdirSync as readdirSync7 } from "fs";
8240
8368
  import { join as join26 } from "path";
8369
+ var VALID2 = new Set(SUPPORTED_CLIS);
8370
+ var isCli = (s) => VALID2.has(s);
8371
+ function keptTargets(ctx) {
8372
+ const marker = (readAihConfig(ctx.root)?.targets ?? []).filter(isCli);
8373
+ if (marker.length > 0) return { targeted: marker, source: "marker" };
8374
+ if (ctx.targets && ctx.targets.length > 0) return { targeted: [...ctx.targets], source: "ctx" };
8375
+ return { targeted: [], source: "none" };
8376
+ }
8377
+ function onDiskClis(ctx) {
8378
+ return SUPPORTED_CLIS.filter(
8379
+ (c) => existsSync13(join26(ctx.root, ctx.contextDir, "adapters", `${c}.md`))
8380
+ );
8381
+ }
8382
+ function repoMcpPath(cli) {
8383
+ const m = entry(cli).mcp;
8384
+ if (!m.configPath || isExternalMcp(m.configPath)) return void 0;
8385
+ return m.configPath;
8386
+ }
8387
+ var settingsPath = (cli) => entry(cli).settings?.configPath;
8388
+ function keptPaths(targeted) {
8389
+ const kept = new Set(bootloadersFor(targeted));
8390
+ for (const c of targeted) {
8391
+ const mcp = repoMcpPath(c);
8392
+ if (mcp) kept.add(mcp);
8393
+ const set = settingsPath(c);
8394
+ if (set) kept.add(set);
8395
+ }
8396
+ return kept;
8397
+ }
8398
+ function kiroHookFiles(root) {
8399
+ try {
8400
+ return readdirSync7(join26(root, ".kiro", "hooks")).filter((n) => n.startsWith("aih-") && n.endsWith(".kiro.hook")).sort().map((n) => `.kiro/hooks/${n}`);
8401
+ } catch {
8402
+ return [];
8403
+ }
8404
+ }
8405
+ function stalePruneSet(ctx) {
8406
+ const { targeted, source } = keptTargets(ctx);
8407
+ if (source === "none") return { targeted, source, dropped: [], artifacts: [] };
8408
+ const keptSet = new Set(targeted);
8409
+ const dropped = onDiskClis(ctx).filter((c) => !keptSet.has(c));
8410
+ if (dropped.length === 0) return { targeted, source, dropped, artifacts: [] };
8411
+ const kept = keptPaths(targeted);
8412
+ const onDisk = (rel) => existsSync13(join26(ctx.root, rel));
8413
+ const artifacts = [];
8414
+ for (const cli of dropped) {
8415
+ artifacts.push({
8416
+ kind: "adapter",
8417
+ path: `${ctx.contextDir}/adapters/${cli}.md`,
8418
+ disposition: "file",
8419
+ clis: [cli]
8420
+ });
8421
+ }
8422
+ for (const rel of bootloadersFor(dropped)) {
8423
+ if (kept.has(rel) || !onDisk(rel)) continue;
8424
+ artifacts.push({
8425
+ kind: "bootloader",
8426
+ path: rel,
8427
+ disposition: "block",
8428
+ clis: dropped.filter((c) => entry(c).bootloaders.includes(rel))
8429
+ });
8430
+ }
8431
+ for (const rel of [...new Set(dropped.map(repoMcpPath).filter((p) => !!p))]) {
8432
+ if (kept.has(rel) || !onDisk(rel)) continue;
8433
+ artifacts.push({
8434
+ kind: "mcp",
8435
+ path: rel,
8436
+ disposition: "advisory",
8437
+ clis: dropped.filter((c) => repoMcpPath(c) === rel)
8438
+ });
8439
+ }
8440
+ for (const rel of [...new Set(dropped.map(settingsPath).filter((p) => !!p))]) {
8441
+ if (kept.has(rel) || !onDisk(rel)) continue;
8442
+ artifacts.push({
8443
+ kind: "settings",
8444
+ path: rel,
8445
+ disposition: "advisory",
8446
+ clis: dropped.filter((c) => settingsPath(c) === rel)
8447
+ });
8448
+ }
8449
+ if (dropped.includes("kiro")) {
8450
+ const steering = ".kiro/steering/agent-tools.md";
8451
+ if (onDisk(steering)) {
8452
+ artifacts.push({
8453
+ kind: "kiro-steering",
8454
+ path: steering,
8455
+ disposition: "file",
8456
+ clis: ["kiro"]
8457
+ });
8458
+ }
8459
+ for (const hook of kiroHookFiles(ctx.root)) {
8460
+ artifacts.push({ kind: "kiro-hook", path: hook, disposition: "file", clis: ["kiro"] });
8461
+ }
8462
+ }
8463
+ return { targeted, source, dropped, artifacts };
8464
+ }
8465
+ function staleAdvisory(set) {
8466
+ if (set.artifacts.length === 0) return void 0;
8467
+ const n = set.artifacts.length;
8468
+ return lines(
8469
+ "",
8470
+ ` STALE \u2014 ${n} artifact${n === 1 ? "" : "s"} for ${set.dropped.length} dropped CLI(s) (${set.dropped.join(", ")}) no longer targeted.`,
8471
+ " Preview what aih would prune: aih prune (aih leaves these untouched until you apply)"
8472
+ );
8473
+ }
8474
+
8475
+ // src/report/cli-loadability.ts
8476
+ import { existsSync as existsSync14 } from "fs";
8477
+ import { join as join27 } from "path";
8241
8478
  function frontmatterOf(text) {
8242
8479
  const t = text.charCodeAt(0) === 65279 ? text.slice(1) : text;
8243
8480
  const m = t.match(/^---\n([\s\S]*?)\n---/);
@@ -8260,7 +8497,7 @@ function activationCheck(ctx, cli, present) {
8260
8497
  }
8261
8498
  const bad = [];
8262
8499
  for (const rel of present) {
8263
- const fm = frontmatterOf(readIfExists(join26(ctx.root, rel)) ?? "");
8500
+ const fm = frontmatterOf(readIfExists(join27(ctx.root, rel)) ?? "");
8264
8501
  if (!fm || fm[act.key] !== act.value) bad.push(rel);
8265
8502
  }
8266
8503
  return bad.length === 0 ? { name: "activation", ok: true, detail: `${act.key}: ${act.value} set` } : {
@@ -8272,7 +8509,7 @@ function activationCheck(ctx, cli, present) {
8272
8509
  function hygieneCheck(ctx, present) {
8273
8510
  const bad = [];
8274
8511
  for (const rel of present) {
8275
- const text = readIfExists(join26(ctx.root, rel)) ?? "";
8512
+ const text = readIfExists(join27(ctx.root, rel)) ?? "";
8276
8513
  if (text.charCodeAt(0) === 65279) bad.push(`${rel} (BOM before content)`);
8277
8514
  else if (/^---\n/.test(text) && frontmatterOf(text) === void 0) {
8278
8515
  bad.push(`${rel} (unterminated frontmatter)`);
@@ -8285,11 +8522,11 @@ function hygieneCheck(ctx, present) {
8285
8522
  };
8286
8523
  }
8287
8524
  function targetChainCheck(targets, okDetail, missingPrefix) {
8288
- const missing = targets.filter(([, p]) => !existsSync13(p)).map(([label]) => label);
8525
+ const missing = targets.filter(([, p]) => !existsSync14(p)).map(([label]) => label);
8289
8526
  return missing.length === 0 ? { name: "router-chain", ok: true, detail: okDetail } : { name: "router-chain", ok: false, detail: `${missingPrefix}: ${missing.join(", ")}` };
8290
8527
  }
8291
8528
  function isWorkspaceRoot(ctx) {
8292
- const raw = readIfExists(join26(ctx.root, ".aih-workspace.json"));
8529
+ const raw = readIfExists(join27(ctx.root, ".aih-workspace.json"));
8293
8530
  if (raw === void 0) return false;
8294
8531
  try {
8295
8532
  const parsed = JSON.parse(raw);
@@ -8301,10 +8538,10 @@ function isWorkspaceRoot(ctx) {
8301
8538
  function repoRouterChainCheck(ctx) {
8302
8539
  return targetChainCheck(
8303
8540
  [
8304
- [`${ctx.contextDir}/RULE_ROUTER.md`, join26(ctx.root, ctx.contextDir, "RULE_ROUTER.md")],
8541
+ [`${ctx.contextDir}/RULE_ROUTER.md`, join27(ctx.root, ctx.contextDir, "RULE_ROUTER.md")],
8305
8542
  [
8306
8543
  `${ctx.contextDir}/rules/agent-behavior-core.md`,
8307
- join26(ctx.root, ctx.contextDir, "rules", "agent-behavior-core.md")
8544
+ join27(ctx.root, ctx.contextDir, "rules", "agent-behavior-core.md")
8308
8545
  ]
8309
8546
  ],
8310
8547
  "router + behavior core reachable",
@@ -8316,11 +8553,11 @@ function workspaceRouterChainCheck(ctx) {
8316
8553
  [
8317
8554
  [
8318
8555
  `${ctx.contextDir}/cross-repo-architecture.md`,
8319
- join26(ctx.root, ctx.contextDir, "cross-repo-architecture.md")
8556
+ join27(ctx.root, ctx.contextDir, "cross-repo-architecture.md")
8320
8557
  ],
8321
8558
  [
8322
8559
  `${ctx.contextDir}/repo-discipline.md`,
8323
- join26(ctx.root, ctx.contextDir, "repo-discipline.md")
8560
+ join27(ctx.root, ctx.contextDir, "repo-discipline.md")
8324
8561
  ]
8325
8562
  ],
8326
8563
  "workspace canon docs reachable",
@@ -8336,7 +8573,7 @@ function contextCapCheck(ctx, cli, present) {
8336
8573
  if (cap === void 0) {
8337
8574
  return { name: "context-cap", ok: void 0, detail: "no documented per-bootloader cap" };
8338
8575
  }
8339
- const chars = present.reduce((n, rel) => n + (readIfExists(join26(ctx.root, rel))?.length ?? 0), 0);
8576
+ const chars = present.reduce((n, rel) => n + (readIfExists(join27(ctx.root, rel))?.length ?? 0), 0);
8340
8577
  return chars <= cap ? { name: "context-cap", ok: true, detail: `${chars} \u2264 ${cap} char cap` } : {
8341
8578
  name: "context-cap",
8342
8579
  ok: false,
@@ -8344,7 +8581,7 @@ function contextCapCheck(ctx, cli, present) {
8344
8581
  };
8345
8582
  }
8346
8583
  function loadabilityFor(ctx, cli) {
8347
- const present = entry(cli).bootloaders.filter((p) => existsSync13(join26(ctx.root, p)));
8584
+ const present = entry(cli).bootloaders.filter((p) => existsSync14(join27(ctx.root, p)));
8348
8585
  if (present.length === 0) {
8349
8586
  return {
8350
8587
  cli,
@@ -8447,9 +8684,9 @@ function loadGroupDigest(model) {
8447
8684
  }
8448
8685
 
8449
8686
  // src/report/cli-coverage.ts
8450
- var VALID2 = new Set(SUPPORTED_CLIS);
8687
+ var VALID3 = new Set(SUPPORTED_CLIS);
8451
8688
  function isWorkspaceRoot2(ctx) {
8452
- const raw = readIfExists(join27(ctx.root, ".aih-workspace.json"));
8689
+ const raw = readIfExists(join28(ctx.root, ".aih-workspace.json"));
8453
8690
  if (raw === void 0) return false;
8454
8691
  try {
8455
8692
  const parsed = JSON.parse(raw);
@@ -8459,7 +8696,7 @@ function isWorkspaceRoot2(ctx) {
8459
8696
  }
8460
8697
  }
8461
8698
  function resolveTargetSet(ctx) {
8462
- const marker = (readAihConfig(ctx.root)?.targets ?? []).filter((t) => VALID2.has(t));
8699
+ const marker = (readAihConfig(ctx.root)?.targets ?? []).filter((t) => VALID3.has(t));
8463
8700
  if (marker.length > 0) return { targeted: marker, source: "marker" };
8464
8701
  if (ctx.targets && ctx.targets.length > 0) return { targeted: ctx.targets, source: "ctx" };
8465
8702
  const cli = ctx.options.cli;
@@ -8471,7 +8708,7 @@ function resolveTargetSet(ctx) {
8471
8708
  if (present.length > 0) return { targeted: present, source: "detect" };
8472
8709
  }
8473
8710
  const wired = SUPPORTED_CLIS.filter(
8474
- (c) => existsSync14(join27(ctx.root, ctx.contextDir, "adapters", `${c}.md`))
8711
+ (c) => existsSync15(join28(ctx.root, ctx.contextDir, "adapters", `${c}.md`))
8475
8712
  );
8476
8713
  if (wired.length > 0) return { targeted: wired, source: "wired" };
8477
8714
  return { targeted: ["claude"], source: "default-claude" };
@@ -8479,7 +8716,7 @@ function resolveTargetSet(ctx) {
8479
8716
  function workspaceBootloaderCell(ctx, cli) {
8480
8717
  const paths = entry(cli).bootloaders;
8481
8718
  const label = paths.join(" + ");
8482
- const missing = paths.filter((rel) => readIfExists(join27(ctx.root, rel)) === void 0);
8719
+ const missing = paths.filter((rel) => readIfExists(join28(ctx.root, rel)) === void 0);
8483
8720
  if (missing.length > 0) {
8484
8721
  return {
8485
8722
  state: "missing",
@@ -8492,7 +8729,7 @@ function workspaceBootloaderCell(ctx, cli) {
8492
8729
  `${ctx.contextDir}/cross-repo-architecture.md`,
8493
8730
  `${ctx.contextDir}/repo-discipline.md`
8494
8731
  ];
8495
- const missingTargets = workspaceDocs.filter((rel) => !existsSync14(join27(ctx.root, rel)));
8732
+ const missingTargets = workspaceDocs.filter((rel) => !existsSync15(join28(ctx.root, rel)));
8496
8733
  if (missingTargets.length > 0) {
8497
8734
  return {
8498
8735
  state: "missing",
@@ -8502,7 +8739,7 @@ function workspaceBootloaderCell(ctx, cli) {
8502
8739
  };
8503
8740
  }
8504
8741
  const missingPointers = paths.flatMap((rel) => {
8505
- const text = readIfExists(join27(ctx.root, rel)) ?? "";
8742
+ const text = readIfExists(join28(ctx.root, rel)) ?? "";
8506
8743
  return workspaceDocs.filter((docPath) => !text.includes(docPath)).map((docPath) => `${rel} -> ${docPath}`);
8507
8744
  });
8508
8745
  if (missingPointers.length > 0) {
@@ -8528,7 +8765,7 @@ function bootloaderCell(ctx, cli) {
8528
8765
  const drifted = [];
8529
8766
  const noRouter = [];
8530
8767
  for (const rel of paths) {
8531
- const text = readIfExists(join27(ctx.root, rel));
8768
+ const text = readIfExists(join28(ctx.root, rel));
8532
8769
  if (text === void 0) {
8533
8770
  missing.push(rel);
8534
8771
  continue;
@@ -8565,7 +8802,7 @@ function mcpCell(ctx, cli) {
8565
8802
  }
8566
8803
  if (m.support !== "native") {
8567
8804
  const repoRelative = !isExternalMcp(m.configPath);
8568
- const annot = repoRelative ? existsSync14(join27(ctx.root, m.configPath)) ? `${m.configPath} present` : `${m.configPath} not found` : `global ${m.configPath}`;
8805
+ const annot = repoRelative ? existsSync15(join28(ctx.root, m.configPath)) ? `${m.configPath} present` : `${m.configPath} not found` : `global ${m.configPath}`;
8569
8806
  return {
8570
8807
  state: "manual",
8571
8808
  path: m.configPath,
@@ -8574,7 +8811,7 @@ function mcpCell(ctx, cli) {
8574
8811
  };
8575
8812
  }
8576
8813
  const external = isExternalMcp(m.configPath);
8577
- const abs = external ? mcpConfigAbs(homeDir(ctx), m.configPath) : join27(ctx.root, m.configPath);
8814
+ const abs = external ? mcpConfigAbs(homeDir(ctx), m.configPath) : join28(ctx.root, m.configPath);
8578
8815
  const scopeNote = external ? " (global)" : "";
8579
8816
  const scope = external ? "global" : "repo";
8580
8817
  const raw = readIfExists(abs);
@@ -8618,7 +8855,7 @@ function settingsCell(ctx, cli) {
8618
8855
  if (!s.writable) {
8619
8856
  return { state: "manual", path: s.configPath, detail: `manual \u2014 ${s.configPath}` };
8620
8857
  }
8621
- return existsSync14(join27(ctx.root, s.configPath)) ? { state: "wired", path: s.configPath, detail: `${s.configPath} present` } : {
8858
+ return existsSync15(join28(ctx.root, s.configPath)) ? { state: "wired", path: s.configPath, detail: `${s.configPath} present` } : {
8622
8859
  state: "missing",
8623
8860
  path: s.configPath,
8624
8861
  detail: `${s.configPath} not found`,
@@ -8715,31 +8952,35 @@ function renderCliCoverage(model) {
8715
8952
  }
8716
8953
  function cliCoverageDigest(ctx) {
8717
8954
  const model = scanCliCoverage(ctx);
8955
+ const stale = stalePruneSet(ctx);
8956
+ const advisory = staleAdvisory(stale);
8957
+ const body = advisory ? `${renderCliCoverage(model)}
8958
+ ${advisory}` : renderCliCoverage(model);
8718
8959
  return digest(
8719
8960
  `AI CLI wiring \u2014 ${model.structurallyConfigured} of ${model.totalTargeted} configured, ${model.provenLoadable} loadable`,
8720
- renderCliCoverage(model),
8721
- model
8961
+ body,
8962
+ { ...model, stale }
8722
8963
  );
8723
8964
  }
8724
8965
 
8725
8966
  // src/trust/commands.ts
8726
- import { existsSync as existsSync16 } from "fs";
8727
- import { basename as basename6, join as join30 } from "path";
8967
+ import { existsSync as existsSync17 } from "fs";
8968
+ import { basename as basename6, join as join31 } from "path";
8728
8969
 
8729
8970
  // src/trust/fetch.ts
8730
8971
  import { createHash as createHash2 } from "crypto";
8731
8972
  import {
8732
8973
  chmodSync as chmodSync2,
8733
- existsSync as existsSync15,
8734
- lstatSync as lstatSync3,
8974
+ existsSync as existsSync16,
8975
+ lstatSync as lstatSync4,
8735
8976
  mkdtempSync,
8736
- readdirSync as readdirSync7,
8977
+ readdirSync as readdirSync8,
8737
8978
  readFileSync as readFileSync5,
8738
8979
  realpathSync as realpathSync2,
8739
8980
  rmSync as rmSync2
8740
8981
  } from "fs";
8741
8982
  import { tmpdir } from "os";
8742
- import { basename as basename5, isAbsolute as isAbsolute6, join as join28, relative as relative4, resolve as resolve8 } from "path";
8983
+ import { basename as basename5, isAbsolute as isAbsolute6, join as join29, relative as relative4, resolve as resolve8 } from "path";
8743
8984
  import process2 from "process";
8744
8985
  var GITHUB_SOURCE = /^([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)$/;
8745
8986
  var SAFE_ENV_KEYS = /* @__PURE__ */ new Set([
@@ -8778,7 +9019,7 @@ function sourceIdForGitHub(owner, repo) {
8778
9019
  return slugify(`${owner}-${repo}`);
8779
9020
  }
8780
9021
  function quarantineRoot() {
8781
- const root = mkdtempSync(join28(tmpdir(), "aih-quarantine-"));
9022
+ const root = mkdtempSync(join29(tmpdir(), "aih-quarantine-"));
8782
9023
  try {
8783
9024
  chmodSync2(root, 448);
8784
9025
  } catch {
@@ -8827,7 +9068,7 @@ function resolveTrustSource(raw, opts = {
8827
9068
  const trimmed = raw.trim();
8828
9069
  if (trimmed.length === 0) throw new AihError("workspace add requires a source", "AIH_TRUST");
8829
9070
  const local = isAbsolute6(trimmed) ? trimmed : resolve8(opts.root, trimmed);
8830
- if (existsSync15(local)) {
9071
+ if (existsSync16(local)) {
8831
9072
  const root2 = assertTrustTreeSafe(local, { skipDirs: opts.skipDirs });
8832
9073
  return {
8833
9074
  kind: "local",
@@ -8869,14 +9110,14 @@ function resolveTrustSource(raw, opts = {
8869
9110
  ref,
8870
9111
  pin: opts.pin,
8871
9112
  quarantineRoot: root,
8872
- treePath: join28(root, "tree"),
8873
- metadataPath: join28(root, "metadata.json"),
9113
+ treePath: join29(root, "tree"),
9114
+ metadataPath: join29(root, "metadata.json"),
8874
9115
  display: `${owner}/${repo}@${ref}`
8875
9116
  };
8876
9117
  }
8877
9118
  function statSafe(path) {
8878
9119
  try {
8879
- return lstatSync3(path);
9120
+ return lstatSync4(path);
8880
9121
  } catch {
8881
9122
  return void 0;
8882
9123
  }
@@ -8902,7 +9143,7 @@ function assertTrustTreeSafe(root, opts = {}) {
8902
9143
  const realRoot = realpathSync2(absRoot);
8903
9144
  const visit = (abs) => {
8904
9145
  if (abs !== realRoot && opts.skipDirs?.has(basename5(abs))) return;
8905
- const st = lstatSync3(abs);
9146
+ const st = lstatSync4(abs);
8906
9147
  if (st.isSymbolicLink()) {
8907
9148
  throw new AihError(`refusing symlink inside trust source: ${abs}`, "AIH_TRUST");
8908
9149
  }
@@ -8911,7 +9152,7 @@ function assertTrustTreeSafe(root, opts = {}) {
8911
9152
  }
8912
9153
  assertContained2(realRoot, realpathSync2(abs));
8913
9154
  if (!st.isDirectory()) return;
8914
- for (const entry2 of readdirSync7(abs)) visit(join28(abs, entry2));
9155
+ for (const entry2 of readdirSync8(abs)) visit(join29(abs, entry2));
8915
9156
  };
8916
9157
  visit(realRoot);
8917
9158
  return realRoot;
@@ -9186,7 +9427,7 @@ function gradeTrustCheck(check2, posture) {
9186
9427
  }
9187
9428
 
9188
9429
  // src/trust/lock.ts
9189
- import { join as join29 } from "path";
9430
+ import { join as join30 } from "path";
9190
9431
  var LOWER_FULL_SHA = /^[0-9a-f]{40}$/;
9191
9432
  function isRecord2(value) {
9192
9433
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -9257,7 +9498,7 @@ function parseTrustLockSource(value) {
9257
9498
  };
9258
9499
  }
9259
9500
  function readTrustLock(root) {
9260
- const raw = readIfExists(join29(root, ".aih", "trust-lock.json"));
9501
+ const raw = readIfExists(join30(root, ".aih", "trust-lock.json"));
9261
9502
  if (raw === void 0) return { schemaVersion: 1, sources: [] };
9262
9503
  try {
9263
9504
  const parsed = JSON.parse(raw);
@@ -9378,7 +9619,7 @@ function artifactTarget(ctx, source, artifactPath2) {
9378
9619
  for (const skill of sortedSkills) {
9379
9620
  for (const prefix of [`skills/${skill}/`, `${skill}/`]) {
9380
9621
  if (normalized.startsWith(prefix)) {
9381
- return join30(
9622
+ return join31(
9382
9623
  ctx.root,
9383
9624
  ctx.contextDir,
9384
9625
  "skills",
@@ -9390,12 +9631,12 @@ function artifactTarget(ctx, source, artifactPath2) {
9390
9631
  }
9391
9632
  }
9392
9633
  const fallbackSkill = sortedSkills[0] ?? basename6(source.id);
9393
- return join30(ctx.root, ctx.contextDir, "skills", source.id, fallbackSkill, normalized);
9634
+ return join31(ctx.root, ctx.contextDir, "skills", source.id, fallbackSkill, normalized);
9394
9635
  }
9395
9636
  function localDriftChecks(ctx, source) {
9396
9637
  return source.artifactHashes.map((artifact) => {
9397
9638
  const target = artifactTarget(ctx, source, artifact.path);
9398
- if (!existsSync16(target)) {
9639
+ if (!existsSync17(target)) {
9399
9640
  return {
9400
9641
  name: "trust local drift",
9401
9642
  verdict: "fail",
@@ -9424,8 +9665,8 @@ function localDriftChecks(ctx, source) {
9424
9665
  });
9425
9666
  }
9426
9667
  function trustLockLocalDriftChecks(ctx) {
9427
- const lockPath = join30(ctx.root, ".aih", "trust-lock.json");
9428
- if (!existsSync16(lockPath)) {
9668
+ const lockPath = join31(ctx.root, ".aih", "trust-lock.json");
9669
+ if (!existsSync17(lockPath)) {
9429
9670
  return [
9430
9671
  {
9431
9672
  name: "trust local drift",
@@ -9714,7 +9955,7 @@ function workspaceGitignoreMissing(repos, gitignore) {
9714
9955
  }
9715
9956
 
9716
9957
  // src/workspace/manifest.ts
9717
- import { join as join31, posix as posix6 } from "path";
9958
+ import { join as join32, posix as posix6 } from "path";
9718
9959
  var ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
9719
9960
  function cleanPrintable(value, label) {
9720
9961
  if (/[\r\n\t|]/.test(value)) {
@@ -9884,7 +10125,7 @@ function parseWorkspaceManifest(raw, defaultContextDir = "ai-coding") {
9884
10125
  };
9885
10126
  }
9886
10127
  function readWorkspaceManifest(root, defaultContextDir = "ai-coding") {
9887
- const raw = readIfExists(join31(root, ".aih-workspace.json"));
10128
+ const raw = readIfExists(join32(root, ".aih-workspace.json"));
9888
10129
  if (raw === void 0) return void 0;
9889
10130
  try {
9890
10131
  return parseWorkspaceManifest(parseJsoncText(raw), defaultContextDir);
@@ -9893,7 +10134,7 @@ function readWorkspaceManifest(root, defaultContextDir = "ai-coding") {
9893
10134
  }
9894
10135
  }
9895
10136
  function workspaceManifestExists(root) {
9896
- return readIfExists(join31(root, ".aih-workspace.json")) !== void 0;
10137
+ return readIfExists(join32(root, ".aih-workspace.json")) !== void 0;
9897
10138
  }
9898
10139
  function workspaceReposFromPaths(paths, router = defaultRouter()) {
9899
10140
  return paths.map((raw) => {
@@ -9941,8 +10182,8 @@ var command11 = {
9941
10182
  detail: ctx.host.verified ? `${ctx.host.platform} (verified)` : `${ctx.host.platform} (unverified path) \u2014 proceeding on the generic path; file an issue if a step misbehaves`
9942
10183
  })),
9943
10184
  probe("canonical context dir", () => {
9944
- const dir = join32(ctx.root, contextDir);
9945
- return existsSync17(dir) ? { name: "context-dir", verdict: "pass", detail: dir } : {
10185
+ const dir = join33(ctx.root, contextDir);
10186
+ return existsSync18(dir) ? { name: "context-dir", verdict: "pass", detail: dir } : {
9946
10187
  name: "context-dir",
9947
10188
  verdict: "skip",
9948
10189
  detail: `${contextDir} not scaffolded \u2014 run: aih scaffold --apply`,
@@ -10071,7 +10312,7 @@ var command11 = {
10071
10312
  probe("workspace child repos gitignored", () => {
10072
10313
  const missing = workspaceGitignoreMissing(
10073
10314
  repoPaths,
10074
- readIfExists(join32(ctx.root, ".gitignore"))
10315
+ readIfExists(join33(ctx.root, ".gitignore"))
10075
10316
  );
10076
10317
  return missing.length === 0 ? {
10077
10318
  name: "workspace-child-gitignore",
@@ -10086,7 +10327,7 @@ var command11 = {
10086
10327
  ] : [];
10087
10328
  const wsProbes = repos.map(
10088
10329
  (repo) => probe(`workspace child ${repo.id} scaffolded`, () => {
10089
- const present = existsSync17(join32(ctx.root, repo.path, repo.router));
10330
+ const present = existsSync18(join33(ctx.root, repo.path, repo.router));
10090
10331
  return present ? {
10091
10332
  name: `child:${repo.id}`,
10092
10333
  verdict: "pass",
@@ -10104,9 +10345,9 @@ var command11 = {
10104
10345
  };
10105
10346
 
10106
10347
  // src/ecc/index.ts
10107
- import { existsSync as existsSync18 } from "fs";
10348
+ import { existsSync as existsSync19 } from "fs";
10108
10349
  import { homedir as homedir2 } from "os";
10109
- import { join as join33 } from "path";
10350
+ import { join as join34 } from "path";
10110
10351
 
10111
10352
  // src/ecc/install.ts
10112
10353
  var ECC_INSTALL_TARGETS = [
@@ -10255,7 +10496,7 @@ function eccLanguages(stack) {
10255
10496
  var ECC_REPO_URL = "https://github.com/affaan-m/ECC.git";
10256
10497
  function eccCacheDir(ctx) {
10257
10498
  const home = ctx.env.USERPROFILE || ctx.env.HOME || homedir2();
10258
- return join33(home, ".claude", "ecc");
10499
+ return join34(home, ".claude", "ecc");
10259
10500
  }
10260
10501
  function kiroEccActions(ctx) {
10261
10502
  const explicit = typeof ctx.options.eccPath === "string" ? ctx.options.eccPath.trim() : "";
@@ -10263,7 +10504,7 @@ function kiroEccActions(ctx) {
10263
10504
  const posix13 = dir.replace(/\\/g, "/");
10264
10505
  const installExec = exec(
10265
10506
  `Install ECC for Kiro \u2014 run ${posix13}/.kiro/install.sh into ${ctx.root}/.kiro/ (under --apply)`,
10266
- ["bash", join33(dir, ".kiro", "install.sh"), ctx.root]
10507
+ ["bash", join34(dir, ".kiro", "install.sh"), ctx.root]
10267
10508
  );
10268
10509
  if (explicit) {
10269
10510
  return [
@@ -10278,7 +10519,7 @@ function kiroEccActions(ctx) {
10278
10519
  )
10279
10520
  ];
10280
10521
  }
10281
- const hasCache = existsSync18(join33(dir, ".git"));
10522
+ const hasCache = existsSync19(join34(dir, ".git"));
10282
10523
  const ref = (ctx.env.AIH_ECC_REF ?? "").trim();
10283
10524
  const fetchExecs = [];
10284
10525
  if (ref) {
@@ -10409,7 +10650,7 @@ var command12 = {
10409
10650
  };
10410
10651
 
10411
10652
  // src/guardrails/index.ts
10412
- import { join as join34 } from "path";
10653
+ import { join as join35 } from "path";
10413
10654
 
10414
10655
  // src/guardrails/taxonomy.ts
10415
10656
  function taxonomyDoc() {
@@ -10506,7 +10747,7 @@ function ciNote() {
10506
10747
  ].join("\n");
10507
10748
  }
10508
10749
  function preCommitActions(ctx, stack) {
10509
- const existing = readIfExists(join34(ctx.root, PRECOMMIT_PATH));
10750
+ const existing = readIfExists(join35(ctx.root, PRECOMMIT_PATH));
10510
10751
  const userAuthored = existing !== void 0 && !existing.includes(PRECOMMIT_MARKER);
10511
10752
  if (!userAuthored) {
10512
10753
  return [
@@ -10633,7 +10874,7 @@ var command13 = {
10633
10874
  };
10634
10875
 
10635
10876
  // src/heal/index.ts
10636
- import { join as join37 } from "path";
10877
+ import { join as join38 } from "path";
10637
10878
 
10638
10879
  // src/heal/common.ts
10639
10880
  var HEAL_SCOPES = ["certs", "npm", "path", "mcp"];
@@ -10675,7 +10916,7 @@ function classifyTool(res, isWindows) {
10675
10916
  }
10676
10917
 
10677
10918
  // src/heal/cert-verify.ts
10678
- import { existsSync as existsSync19 } from "fs";
10919
+ import { existsSync as existsSync20 } from "fs";
10679
10920
 
10680
10921
  // src/heal/templates.ts
10681
10922
  var NPM_HEAL_SCRIPT = "heal-npm.mjs";
@@ -10789,10 +11030,10 @@ var ENV_KEY = "NODE_EXTRA_CA_CERTS";
10789
11030
  var CHECK = "cert: NODE_EXTRA_CA_CERTS";
10790
11031
  function caCheck(env, tlsOk, tlsFailed) {
10791
11032
  const p = env[ENV_KEY];
10792
- if (p && existsSync19(p) && readIfExists(p)?.includes("BEGIN CERTIFICATE")) {
11033
+ if (p && existsSync20(p) && readIfExists(p)?.includes("BEGIN CERTIFICATE")) {
10793
11034
  return { name: CHECK, verdict: "pass", detail: `${p} (valid PEM)` };
10794
11035
  }
10795
- if (p && !existsSync19(p)) {
11036
+ if (p && !existsSync20(p)) {
10796
11037
  return {
10797
11038
  name: CHECK,
10798
11039
  verdict: "fail",
@@ -10851,10 +11092,10 @@ var certStep = {
10851
11092
  };
10852
11093
 
10853
11094
  // src/heal/mcp-probe.ts
10854
- import { join as join35 } from "path";
11095
+ import { join as join36 } from "path";
10855
11096
  var CHECK2 = "mcp: npx launcher";
10856
11097
  function mcpNeedsNpx(ctx) {
10857
- const raw = readIfExists(join35(ctx.root, ".mcp.json"));
11098
+ const raw = readIfExists(join36(ctx.root, ".mcp.json"));
10858
11099
  if (raw === void 0) return { configured: false, usesNpx: false };
10859
11100
  return { configured: true, usesNpx: raw.includes("npx") };
10860
11101
  }
@@ -10975,11 +11216,11 @@ var npmStep = {
10975
11216
  };
10976
11217
 
10977
11218
  // src/heal/path-heal.ts
10978
- import { existsSync as existsSync20 } from "fs";
10979
- import { join as join36 } from "path";
11219
+ import { existsSync as existsSync21 } from "fs";
11220
+ import { join as join37 } from "path";
10980
11221
  function userBinDir(ctx) {
10981
11222
  const home = ctx.env.HOME ?? ctx.env.USERPROFILE ?? ctx.root;
10982
- return join36(home, ".local", "bin");
11223
+ return join37(home, ".local", "bin");
10983
11224
  }
10984
11225
  function onPath2(ctx, dir) {
10985
11226
  const raw = ctx.env.PATH ?? ctx.env.Path ?? "";
@@ -10990,7 +11231,7 @@ function onPath2(ctx, dir) {
10990
11231
  }
10991
11232
  async function planPathHeal(ctx) {
10992
11233
  const dir = userBinDir(ctx);
10993
- const exists = existsSync20(dir);
11234
+ const exists = existsSync21(dir);
10994
11235
  const present = exists && onPath2(ctx, dir);
10995
11236
  let check2;
10996
11237
  if (!exists) {
@@ -11054,7 +11295,7 @@ async function healPlan(ctx) {
11054
11295
  }
11055
11296
  actions.push(
11056
11297
  writeText(
11057
- join37(".aih", "heal-last.json"),
11298
+ join38(".aih", "heal-last.json"),
11058
11299
  `${JSON.stringify({ scopes: [...scopes].sort() })}
11059
11300
  `,
11060
11301
  "record heal scope set \u2192 .aih/heal-last.json"
@@ -11083,7 +11324,7 @@ var command14 = {
11083
11324
  };
11084
11325
 
11085
11326
  // src/mcp/index.ts
11086
- import { join as join38, posix as posix7 } from "path";
11327
+ import { join as join39, posix as posix7 } from "path";
11087
11328
 
11088
11329
  // src/mcp/gateway.ts
11089
11330
  var OAUTH_SCOPE = "api://agentgateway/mcp_access";
@@ -11516,7 +11757,7 @@ async function planMcp(ctx) {
11516
11757
  const where = external ? ` ${p.configPath} (global \u2014 affects all your projects)` : ` ${p.configPath}`;
11517
11758
  const describe = p.configPath === ".mcp.json" ? scope === "remote" ? "Configure project-aware servers + the opt-in hosted enterprise toolset (remote scope), merged into any existing .mcp.json" : `Configure project-aware MCP servers (${scope} scope)${tailored ? ` \u2014 ${tailored}` : ""}, merged into any existing .mcp.json` : `${e.label} MCP servers (${scope} scope) \u2192${where}, merged into any existing`;
11518
11759
  if (p.configFormat === "toml") {
11519
- const abs = external ? writePath : join38(ctx.root, p.configPath);
11760
+ const abs = external ? writePath : join39(ctx.root, p.configPath);
11520
11761
  const existing = readIfExists(abs) ?? "";
11521
11762
  const have = existingMcpTomlNames(existing, MCP_TOML_SCOPE);
11522
11763
  const fresh = Object.fromEntries(Object.entries(servers).filter(([n]) => !have.has(n)));
@@ -11541,7 +11782,7 @@ async function planMcp(ctx) {
11541
11782
  }
11542
11783
  const placeholders = envPlaceholders(servers);
11543
11784
  if (placeholders.length > 0) {
11544
- const abs = join38(ctx.root, ".env.example");
11785
+ const abs = join39(ctx.root, ".env.example");
11545
11786
  const body = [
11546
11787
  "# Secrets referenced by MCP servers (.mcp.json). Copy to .env (untracked) and fill",
11547
11788
  "# in real values \u2014 never commit them. aih manages only the block below.",
@@ -11985,14 +12226,14 @@ var command17 = {
11985
12226
  import { posix as posix8 } from "path";
11986
12227
 
11987
12228
  // src/internals/git-hooks.ts
11988
- import { existsSync as existsSync21 } from "fs";
11989
- import { join as join39 } from "path";
12229
+ import { existsSync as existsSync22 } from "fs";
12230
+ import { join as join40 } from "path";
11990
12231
  var GITHOOKS_PATH_COMMAND = "git config core.hooksPath .githooks";
11991
12232
  function normalizeHooksPath(value) {
11992
12233
  return value.trim().replace(/^["']|["']$/g, "").replace(/\\/g, "/").replace(/^\.\//, "");
11993
12234
  }
11994
12235
  function usesManagedHooksPath(root) {
11995
- const text = readIfExists(join39(root, ".git", "config"));
12236
+ const text = readIfExists(join40(root, ".git", "config"));
11996
12237
  if (text === void 0) return false;
11997
12238
  let inCore = false;
11998
12239
  for (const line of text.replace(/\r\n/g, "\n").split("\n")) {
@@ -12009,8 +12250,8 @@ function usesManagedHooksPath(root) {
12009
12250
  return false;
12010
12251
  }
12011
12252
  function preCommitHookActive(root) {
12012
- if (existsSync21(join39(root, ".git", "hooks", "pre-commit"))) return true;
12013
- return existsSync21(join39(root, ".githooks", "pre-commit")) && usesManagedHooksPath(root);
12253
+ if (existsSync22(join40(root, ".git", "hooks", "pre-commit"))) return true;
12254
+ return existsSync22(join40(root, ".githooks", "pre-commit")) && usesManagedHooksPath(root);
12014
12255
  }
12015
12256
 
12016
12257
  // src/scaffold/hooks.ts
@@ -12869,7 +13110,7 @@ var command20 = {
12869
13110
  };
12870
13111
 
12871
13112
  // src/usage/index.ts
12872
- import { join as join42, resolve as resolve10 } from "path";
13113
+ import { join as join43, resolve as resolve10 } from "path";
12873
13114
 
12874
13115
  // src/usage/aggregate.ts
12875
13116
  function top(map, limit = 12) {
@@ -13084,11 +13325,11 @@ function gitPostCommitChainSnippet() {
13084
13325
  }
13085
13326
 
13086
13327
  // src/usage/events.ts
13087
- import { join as join40 } from "path";
13088
- var USAGE_PATH = join40(".aih", "usage.jsonl");
13328
+ import { join as join41 } from "path";
13329
+ var USAGE_PATH = join41(".aih", "usage.jsonl");
13089
13330
  var KINDS = /* @__PURE__ */ new Set(["commit", "skill", "mcp", "session", "tool"]);
13090
13331
  function readUsage(ctx) {
13091
- const raw = readIfExists(join40(ctx.root, USAGE_PATH));
13332
+ const raw = readIfExists(join41(ctx.root, USAGE_PATH));
13092
13333
  if (!raw) return [];
13093
13334
  const out = [];
13094
13335
  for (const line of raw.split(/\r?\n/)) {
@@ -13104,7 +13345,7 @@ function readUsage(ctx) {
13104
13345
  }
13105
13346
 
13106
13347
  // src/usage/hooks.ts
13107
- import { join as join41 } from "path";
13348
+ import { join as join42 } from "path";
13108
13349
  var USAGE_HOOK_SCOPE = "usage-metering";
13109
13350
  function hookCommand(cli) {
13110
13351
  return `node .aih/usage-record.mjs --from ${cli}`;
@@ -13275,7 +13516,7 @@ function usageHookActions(ctx, clis) {
13275
13516
  actions.push(
13276
13517
  writeText(
13277
13518
  path,
13278
- kimiToml(readIfExists(join41(ctx.root, path))),
13519
+ kimiToml(readIfExists(join42(ctx.root, path))),
13279
13520
  "Kimi PostToolUse usage hook, merged into an aih-managed TOML block"
13280
13521
  )
13281
13522
  );
@@ -13304,8 +13545,8 @@ function usageHookActions(ctx, clis) {
13304
13545
  }
13305
13546
 
13306
13547
  // src/usage/index.ts
13307
- var RECORDER_PATH = join42(".aih", "usage-record.mjs");
13308
- var GIT_HOOK_PATH = join42(".git", "hooks", "post-commit");
13548
+ var RECORDER_PATH = join43(".aih", "usage-record.mjs");
13549
+ var GIT_HOOK_PATH = join43(".git", "hooks", "post-commit");
13309
13550
  var TOOL_HOOK = {
13310
13551
  claude: "`.claude/settings.json` hooks \u2192 `PostToolUse` (captures Skill / mcp__ tool calls)",
13311
13552
  codex: "`.codex/hooks.json` \u2192 `PostToolUse`/`Stop`; project `.codex` must be trusted and command hooks reviewed via `/hooks`",
@@ -13395,7 +13636,7 @@ async function usagePlan(ctx) {
13395
13636
  ...usageHookActions(ctx, clis),
13396
13637
  coverageDoc(clis)
13397
13638
  ];
13398
- const existingHook = readIfExists(join42(ctx.root, GIT_HOOK_PATH));
13639
+ const existingHook = readIfExists(join43(ctx.root, GIT_HOOK_PATH));
13399
13640
  if (existingHook !== void 0 && !existingHook.includes("usage-record.mjs")) {
13400
13641
  actions.push(
13401
13642
  doc(
@@ -13577,112 +13818,779 @@ var command22 = {
13577
13818
  plan: initPlan2
13578
13819
  };
13579
13820
 
13580
- // src/report/index.ts
13581
- import { existsSync as existsSync28, readFileSync as readFileSync6 } from "fs";
13582
- import { basename as basename7, join as join53, resolve as resolve11 } from "path";
13821
+ // src/prune/index.ts
13822
+ import { join as join44 } from "path";
13823
+ var SOURCE_LABEL2 = {
13824
+ marker: ".aih-config.json",
13825
+ ctx: "init orchestrator",
13826
+ none: "none"
13827
+ };
13828
+ var KIND_LABEL = {
13829
+ adapter: "adapter note",
13830
+ bootloader: "bootloader canon block",
13831
+ mcp: "MCP config",
13832
+ settings: "settings",
13833
+ "kiro-steering": "Kiro steering",
13834
+ "kiro-hook": "Kiro hook"
13835
+ };
13836
+ function bootloaderMinusBlock(ctx, rel) {
13837
+ const text = readIfExists(join44(ctx.root, rel));
13838
+ if (text === void 0) return void 0;
13839
+ const onDisk = extractManagedBlock(text, SHARED_MARKER);
13840
+ if (onDisk === void 0) return void 0;
13841
+ if (onDisk !== sharedCanonicalBlockBody(ctx.contextDir).trim()) return void 0;
13842
+ const stripped = stripManagedBlock(text, SHARED_MARKER);
13843
+ return stripped === text ? void 0 : stripped;
13844
+ }
13845
+ function advisoryLines(set) {
13846
+ const advisory = set.artifacts.filter((a) => a.disposition === "advisory");
13847
+ if (advisory.length === 0) return [];
13848
+ return [
13849
+ "",
13850
+ "Manual review \u2014 aih can't safely edit these (its entries carry no on-disk marker,",
13851
+ "and names may collide with yours), so it will NOT touch them:",
13852
+ ...advisory.map(
13853
+ (a) => ` [manual] ${a.path} \u2014 remove ${a.clis.join(", ")}'s entries by hand if unused`
13854
+ )
13855
+ ];
13856
+ }
13857
+ function contextBody(set, moved, subtracted) {
13858
+ if (set.source === "none") {
13859
+ return lines(
13860
+ "No committed target set (.aih-config.json) to diff against \u2014 nothing is treated",
13861
+ "as stale. Run `aih bootstrap-ai` to record which CLIs this repo targets, then",
13862
+ "`aih prune` will remove artifacts for any CLI you later drop."
13863
+ );
13864
+ }
13865
+ if (set.dropped.length === 0) {
13866
+ return lines(
13867
+ "No stale per-CLI artifacts \u2014 every CLI wired into this repo is still targeted.",
13868
+ `Kept (${SOURCE_LABEL2[set.source]}): ${set.targeted.join(", ") || "none"}.`
13869
+ );
13870
+ }
13871
+ return lines(
13872
+ `Kept (${SOURCE_LABEL2[set.source]}): ${set.targeted.join(", ")}`,
13873
+ `Dropped: ${set.dropped.join(", ")}`,
13874
+ "",
13875
+ `${moved} file(s) move to .aih/legacy/ (reversible), ${subtracted} bootloader block(s)`,
13876
+ "subtracted in place; the actions above list each. Pass --apply to execute.",
13877
+ ...advisoryLines(set)
13878
+ );
13879
+ }
13880
+ function actionFor(ctx, a) {
13881
+ const who = `${a.clis.join(", ")} dropped`;
13882
+ if (a.disposition === "file") {
13883
+ return remove(a.path, `stale ${KIND_LABEL[a.kind]} (${who})`);
13884
+ }
13885
+ if (a.disposition === "block") {
13886
+ const stripped = bootloaderMinusBlock(ctx, a.path);
13887
+ return stripped === void 0 ? void 0 : writeText(a.path, stripped, `subtract aih canon block from ${a.path} (${who})`);
13888
+ }
13889
+ return void 0;
13890
+ }
13891
+ function prunePlan(ctx) {
13892
+ const set = stalePruneSet(ctx);
13893
+ const actions = [];
13894
+ if (set.artifacts.some((a) => a.disposition === "file")) {
13895
+ actions.push(aihIgnoreWrite(ctx.root));
13896
+ }
13897
+ let moved = 0;
13898
+ let subtracted = 0;
13899
+ for (const a of set.artifacts) {
13900
+ const action = actionFor(ctx, a);
13901
+ if (!action) continue;
13902
+ actions.push(action);
13903
+ if (action.kind === "remove") moved += 1;
13904
+ else if (action.kind === "write") subtracted += 1;
13905
+ }
13906
+ const headline = set.dropped.length > 0 ? `Stale artifacts \u2014 ${set.artifacts.length} for ${set.dropped.length} dropped CLI(s)` : "Stale artifacts \u2014 none";
13907
+ actions.push(digest(headline, contextBody(set, moved, subtracted), set));
13908
+ return plan("prune", ...actions);
13909
+ }
13910
+ var command23 = {
13911
+ name: "prune",
13912
+ summary: "Remove stale per-CLI artifacts left by CLIs this repo no longer targets",
13913
+ plan: prunePlan
13914
+ };
13583
13915
 
13584
- // src/support/findings.ts
13585
- var NO_CODE_CHANGES = "No project code changes are required.";
13586
- var CODE_META = {
13587
- "env.node-runtime": {
13588
- audience: "internal-it",
13589
- failSeverity: "blocking",
13590
- title: "required language runtime missing or unsupported",
13591
- affectedArea: "local developer tooling",
13592
- evidence: "The required language runtime (Node.js 20 or newer) is missing or below the supported version on this machine.",
13593
- action: "Please provision the supported language runtime on this machine via the approved software catalog or installer, then have the developer reopen their shell.",
13594
- acceptance: [
13595
- "The supported runtime version is available on this machine.",
13596
- "Project setup checks pass without further manual steps.",
13597
- NO_CODE_CHANGES
13598
- ]
13599
- },
13600
- "env.git-missing": {
13601
- audience: "developer",
13602
- failSeverity: "degraded",
13603
- title: "git not found on PATH",
13604
- action: "Install git (winget/apt/brew or your software catalog) and reopen the shell."
13605
- },
13606
- "env.dev-tool-missing": {
13607
- audience: "developer",
13608
- failSeverity: "degraded",
13609
- title: "Dev tools (rg/fd/jq) missing",
13610
- action: "Install rg/fd/jq (winget/scoop/brew), or on a locked-down VDI add your local bundle to PATH."
13611
- },
13612
- "env.tool-install-blocked": {
13613
- audience: "internal-it",
13614
- failSeverity: "degraded",
13615
- title: "required developer tool could not be installed",
13616
- affectedArea: "local developer tooling / software installation",
13617
- evidence: "A required command-line tool is not available, and an automated install could not complete on this machine (no supported package manager is present, privileges are insufficient, or the package source is blocked).",
13618
- action: "Please provision the missing tool via the approved software catalog / package manager, or unblock the package source on this machine so the developer can install it.",
13619
- acceptance: [
13620
- "The tool resolves on PATH.",
13621
- "Project tooling checks pass without further manual steps.",
13622
- NO_CODE_CHANGES
13623
- ]
13624
- },
13625
- "cert.ca-missing": {
13626
- audience: "internal-it",
13627
- failSeverity: "blocking",
13628
- title: "corporate CA not trusted by development tools",
13629
- affectedArea: "workstation certificate trust / development toolchain trust",
13630
- evidence: "TLS verification is failing because the approved corporate CA is not available to the tools running on this machine.\nThe Node CA bundle setting (NODE_EXTRA_CA_CERTS) is not set or does not point to a valid corporate CA bundle.",
13631
- action: "Please make the approved corporate root certificate authority available on this machine and configure the development tools to trust it. For Node-based tools, this usually means setting NODE_EXTRA_CA_CERTS to the approved corporate CA bundle path, in addition to any required OS trust-store configuration.",
13632
- acceptance: [
13633
- "Development tools can complete TLS verification against approved internal and package sources.",
13634
- "Package access works without disabling TLS checks.",
13635
- NO_CODE_CHANGES
13636
- ]
13637
- },
13638
- "tls.verify-failed": {
13639
- audience: "internal-it",
13640
- failSeverity: "blocking",
13641
- title: "TLS verification to approved package sources is failing",
13642
- affectedArea: "workstation certificate trust / development toolchain trust",
13643
- evidence: "TLS connections to approved package registries are failing on this machine, consistent with an intercepting proxy whose certificate is not trusted by the development tools.",
13644
- action: "Please ensure the intercepting proxy's certificate is trusted on this machine (OS trust store and the development toolchain) and that the approved package registry endpoints are reachable.",
13645
- acceptance: [
13646
- "Development tools can complete TLS verification against approved internal and package sources.",
13647
- "Package access works without disabling TLS checks.",
13648
- NO_CODE_CHANGES
13649
- ]
13650
- },
13651
- "npm.runtime-broken": {
13652
- audience: "internal-it",
13653
- failSeverity: "blocking",
13654
- title: "package manager is broken on this machine",
13655
- affectedArea: "approved package registry access",
13656
- evidence: "The package manager fails to run or cannot install from approved registries on this machine.",
13657
- action: "Please repair the package manager installation on this machine so it runs and can install from approved registries; if the registry is unreachable, restore corporate TLS trust first.",
13658
- acceptance: [
13659
- "The package manager runs and can install from approved sources.",
13660
- "No TLS checks are disabled to achieve this.",
13661
- NO_CODE_CHANGES
13662
- ]
13663
- },
13664
- "path.missing": {
13665
- audience: "developer",
13666
- failSeverity: "degraded",
13667
- title: "Tool directory not on PATH",
13668
- action: "Add the user tool directory to PATH (see `aih heal`) and reopen the shell."
13669
- },
13670
- "mcp.blocked": {
13671
- audience: "dev-platform",
13672
- failSeverity: "blocking",
13673
- title: "package launcher cannot reach the approved registry",
13674
- affectedArea: "approved package registry access",
13675
- evidence: "The developer-platform package launcher cannot reach the approved package registry on this machine.",
13676
- action: "Please restore access from this machine to the approved package registry for the launcher (certificate trust plus endpoint allowlisting), or approve a vendored, locally-installed server set.",
13677
- acceptance: [
13678
- "The launcher can reach approved package sources.",
13679
- "No TLS checks are disabled to achieve this.",
13680
- NO_CODE_CHANGES
13681
- ]
13682
- },
13683
- "mcp.uv-missing": {
13684
- audience: "dev-platform",
13685
- failSeverity: "degraded",
13916
+ // src/status.ts
13917
+ import { existsSync as existsSync23 } from "fs";
13918
+ import { join as join45 } from "path";
13919
+ var ARTIFACTS = [
13920
+ ["context-dir", ""],
13921
+ // resolved against ctx.contextDir below
13922
+ ["gitleaks", ".gitleaks.toml"],
13923
+ ["pre-commit", ".pre-commit-config.yaml"],
13924
+ ["devcontainer", ".devcontainer/devcontainer.json"]
13925
+ ];
13926
+ function inventory(root, contextDir) {
13927
+ return ARTIFACTS.map(([name, rel]) => {
13928
+ const relative10 = name === "context-dir" ? contextDir : rel;
13929
+ return { name, relative: relative10, present: existsSync23(join45(root, relative10)) };
13930
+ });
13931
+ }
13932
+ function enforcementDetail(name, root, relative10) {
13933
+ if (name === "pre-commit") {
13934
+ return preCommitHookActive(root) ? `${relative10} (git hook installed \u2014 active)` : `${relative10} present, but no active git pre-commit hook was found \u2014 run \`${GITHOOKS_PATH_COMMAND}\` after scaffold`;
13935
+ }
13936
+ return relative10;
13937
+ }
13938
+ var command24 = {
13939
+ name: "status",
13940
+ summary: "Show what the harness has configured for this repo/workstation (read-only)",
13941
+ readOnly: true,
13942
+ options: [],
13943
+ plan: (ctx) => plan(
13944
+ "status",
13945
+ ...inventory(ctx.root, ctx.contextDir).map(
13946
+ (a) => probe(
13947
+ `presence: ${a.name}`,
13948
+ () => a.present ? {
13949
+ name: a.name,
13950
+ verdict: "pass",
13951
+ detail: enforcementDetail(a.name, ctx.root, a.relative)
13952
+ } : { name: a.name, verdict: "skip", detail: `${a.relative} not present` }
13953
+ )
13954
+ )
13955
+ )
13956
+ };
13957
+
13958
+ // src/report/grade.ts
13959
+ var GRADE_BANDS = [
13960
+ { min: 85, grade: "mature" },
13961
+ { min: 70, grade: "solid" },
13962
+ { min: 50, grade: "emerging" },
13963
+ { min: 0, grade: "nascent" }
13964
+ ];
13965
+ function gradeOf(score) {
13966
+ for (const b of GRADE_BANDS) if (score >= b.min) return b.grade;
13967
+ return "nascent";
13968
+ }
13969
+ function dimScore(checks) {
13970
+ if (checks.length === 0) return 0;
13971
+ const passed = checks.filter((c) => c.passed).length;
13972
+ return Math.round(passed / checks.length * 100);
13973
+ }
13974
+ function check(id, passed, remediation, source) {
13975
+ return { id, passed, remediation, source };
13976
+ }
13977
+ function dim(name, weight, checks) {
13978
+ return { name, weight, score: dimScore(checks), checks };
13979
+ }
13980
+
13981
+ // src/report/readiness.ts
13982
+ var SCORE_CAP_WITH_BLOCKER = 69;
13983
+ var READY_THRESHOLD = 70;
13984
+ async function verdictOf(action, ctx) {
13985
+ if (action?.kind !== "probe") return void 0;
13986
+ return action.run(ctx);
13987
+ }
13988
+ async function healCheck(step2, ctx, shared, code) {
13989
+ const actions = await step2.plan(ctx, shared);
13990
+ for (const action of actions) {
13991
+ const c = await verdictOf(action, ctx);
13992
+ if (c && c.code === code) return c;
13993
+ if (c && code === void 0) return c;
13994
+ }
13995
+ return void 0;
13996
+ }
13997
+ async function firstHealCheck(step2, ctx, shared) {
13998
+ const actions = await step2.plan(ctx, shared);
13999
+ for (const action of actions) {
14000
+ const c = await verdictOf(action, ctx);
14001
+ if (c) return c;
14002
+ }
14003
+ return void 0;
14004
+ }
14005
+ async function nodeVerdict(ctx) {
14006
+ const res = await ctx.run(versionArgv(ctx.host.platform, "node"));
14007
+ if (classifyTool(res, ctx.host.platform === "windows") !== "ok") return "fail";
14008
+ const major = Number(res.stdout.match(/v?(\d+)\./)?.[1] ?? Number.NaN);
14009
+ return Number.isFinite(major) && major >= 20 ? "pass" : "fail";
14010
+ }
14011
+ async function npmVerdict(ctx, nodeOk) {
14012
+ if (!nodeOk) return "skip";
14013
+ const res = await ctx.run(versionArgv(ctx.host.platform, "npm"));
14014
+ return classifyTool(res, ctx.host.platform === "windows") === "ok" ? "pass" : "fail";
14015
+ }
14016
+ async function gitVerdict(ctx) {
14017
+ const res = await ctx.run(["git", "--version"]);
14018
+ return res.spawnError ? "fail" : "pass";
14019
+ }
14020
+ async function coreToolsMissing(ctx) {
14021
+ const missing = [];
14022
+ for (const bin of ["rg", "fd", "jq"]) {
14023
+ const argv = ctx.host.platform === "windows" ? ["where", bin] : ["which", bin];
14024
+ const res = await ctx.run(argv);
14025
+ if (res.spawnError || res.code !== 0 || res.stdout.trim().length === 0) missing.push(bin);
14026
+ }
14027
+ return missing;
14028
+ }
14029
+ function secretsPresent(root) {
14030
+ return scanSecrets(root).matches.length > 0 || scanConfigSecrets(root).length > 0;
14031
+ }
14032
+ async function buildChecks(ctx) {
14033
+ const { root, contextDir } = ctx;
14034
+ const posture = postureFromContext(ctx);
14035
+ const out = [];
14036
+ const tlsRegistry = await tlsCheck(ctx, "tls: registry", REGISTRY_URL);
14037
+ const shared = { tlsRegistry, tlsPypi: tlsRegistry };
14038
+ const node = await nodeVerdict(ctx);
14039
+ const nodeOk = node === "pass";
14040
+ out.push({
14041
+ id: "node-runtime",
14042
+ title: "Node.js runtime (>= 20) on PATH",
14043
+ severity: "gate",
14044
+ dimension: "machine",
14045
+ verdict: node,
14046
+ cmd: "install Node 20+ (nvm/winget/brew) and re-open the shell"
14047
+ });
14048
+ out.push({
14049
+ id: "npm-runtime",
14050
+ title: "npm present and runnable",
14051
+ severity: "gate",
14052
+ dimension: "machine",
14053
+ verdict: await npmVerdict(ctx, nodeOk),
14054
+ cmd: "aih heal --scope npm"
14055
+ });
14056
+ const certCode = tlsRegistry.verdict === "fail" ? "tls.verify-failed" : "cert.ca-missing";
14057
+ const cert = await healCheck(certStep, ctx, shared, certCode) ?? await firstHealCheck(certStep, ctx, shared);
14058
+ out.push({
14059
+ id: "tls-ca-trust",
14060
+ title: "Corporate TLS/CA trust intact",
14061
+ severity: "gate",
14062
+ dimension: "machine",
14063
+ verdict: tlsRegistry.verdict === "fail" ? "fail" : cert?.verdict ?? "skip",
14064
+ cmd: "aih heal --scope certs"
14065
+ });
14066
+ const path = await firstHealCheck(pathStep, ctx, shared);
14067
+ out.push({
14068
+ id: "toolbin-on-path",
14069
+ title: "Installed-tool dir on PATH",
14070
+ severity: "gate",
14071
+ dimension: "machine",
14072
+ verdict: path?.verdict ?? "skip",
14073
+ cmd: "aih heal --scope path"
14074
+ });
14075
+ out.push({
14076
+ id: "git-present",
14077
+ title: "git available on PATH",
14078
+ severity: posture === "vibe" ? "warn" : "gate",
14079
+ dimension: "machine",
14080
+ verdict: await gitVerdict(ctx),
14081
+ cmd: "install git (winget/apt/brew) and re-run"
14082
+ });
14083
+ const coreMissing = await coreToolsMissing(ctx);
14084
+ out.push({
14085
+ id: "core-shell-tools",
14086
+ title: "Core shell tools (rg, fd, jq) on PATH",
14087
+ severity: "gate",
14088
+ dimension: "machine",
14089
+ verdict: coreMissing.length === 0 ? "pass" : "fail",
14090
+ cmd: "install rg, fd, jq (winget/scoop/brew) or add your VDI bundle to PATH"
14091
+ });
14092
+ const coverage = scanCliCoverage(ctx);
14093
+ const anyLoadable = coverage.provenLoadable > 0 || coverage.structurallyConfigured > 0;
14094
+ out.push({
14095
+ id: "runnable-ai-cli",
14096
+ title: "A configured AI CLI for this repo",
14097
+ severity: "warn",
14098
+ dimension: "machine",
14099
+ verdict: anyLoadable ? "pass" : "fail",
14100
+ cmd: "install a CLI (claude/codex/\u2026) then: aih bootstrap-ai --apply"
14101
+ });
14102
+ const contract = await contractTruthCheck(ctx);
14103
+ out.push({
14104
+ id: "contract-fresh",
14105
+ title: "Repo contract present, portable, and current",
14106
+ severity: posture === "enterprise" ? "gate" : "warn",
14107
+ dimension: "repo-contract",
14108
+ verdict: contract.verdict,
14109
+ cmd: "aih contract --apply"
14110
+ });
14111
+ const stack = scanRepo(root, { maxDepth: 8, contextDir });
14112
+ const hasRunnable = Boolean(stack.testRunner || stack.buildCommand || stack.startCommand);
14113
+ out.push({
14114
+ id: "declared-commands",
14115
+ title: "Declared build/test/start command",
14116
+ severity: "warn",
14117
+ dimension: "repo-contract",
14118
+ verdict: hasRunnable ? "pass" : "fail",
14119
+ cmd: "add a test/build/start script to package.json"
14120
+ });
14121
+ out.push({
14122
+ id: "no-committed-secret",
14123
+ title: "No plaintext/hardcoded secret committed",
14124
+ severity: posture === "vibe" ? "warn" : "gate",
14125
+ dimension: "repo-contract",
14126
+ verdict: secretsPresent(root) ? "fail" : "pass",
14127
+ cmd: "move secrets to env refs; run: aih secrets --apply"
14128
+ });
14129
+ const targeted = coverage.rows.filter((r) => r.targeted);
14130
+ const wontLoad = targeted.some((r) => r.load.verdict === "wontLoad");
14131
+ const anyLoads = targeted.some((r) => r.load.verdict === "loads");
14132
+ out.push({
14133
+ id: "bootloader-loads",
14134
+ title: "Targeted CLI bootloader actually loads",
14135
+ severity: "gate",
14136
+ dimension: "harness-wiring",
14137
+ verdict: wontLoad ? "fail" : anyLoads ? "pass" : "skip",
14138
+ cmd: "aih bootstrap-ai --apply"
14139
+ });
14140
+ const bootloaderMissing = targeted.some((r) => r.bootloader.state === "missing");
14141
+ const anyTargeted = targeted.length > 0;
14142
+ out.push({
14143
+ id: "bootloader-wired",
14144
+ title: "Targeted CLI bootloader present + in sync",
14145
+ severity: "warn",
14146
+ dimension: "harness-wiring",
14147
+ verdict: !anyTargeted ? "skip" : bootloaderMissing ? "fail" : "pass",
14148
+ cmd: "aih bootstrap-ai --apply"
14149
+ });
14150
+ const inv = inventory(root, contextDir);
14151
+ const preCommitConfig = inv.find((i) => i.name === "pre-commit")?.present ?? false;
14152
+ out.push({
14153
+ id: "pre-commit-active",
14154
+ title: "pre-commit hook active (not just configured)",
14155
+ severity: "warn",
14156
+ dimension: "harness-wiring",
14157
+ verdict: !preCommitConfig ? "skip" : preCommitHookActive(root) ? "pass" : "fail",
14158
+ cmd: "git config core.hooksPath .githooks"
14159
+ });
14160
+ const gitleaks = inv.find((i) => i.name === "gitleaks")?.present ?? false;
14161
+ out.push({
14162
+ id: "guardrail-artifacts",
14163
+ title: "Leak-prevention guardrail configured",
14164
+ severity: "warn",
14165
+ dimension: "harness-wiring",
14166
+ verdict: gitleaks ? "pass" : "fail",
14167
+ cmd: "aih guardrails --apply"
14168
+ });
14169
+ const mcp = await healCheck(mcpStep, ctx, shared, "mcp.blocked") ?? await firstHealCheck(mcpStep, ctx, shared);
14170
+ out.push({
14171
+ id: "mcp-launches",
14172
+ title: "Declared npx MCP servers can launch",
14173
+ severity: "gate",
14174
+ dimension: "harness-wiring",
14175
+ verdict: mcp?.verdict === "fail" ? "fail" : "skip",
14176
+ cmd: "aih heal --scope mcp"
14177
+ });
14178
+ return out;
14179
+ }
14180
+ function dimensionOf(name, checks) {
14181
+ const warns = checks.filter(
14182
+ (c) => c.dimension === name && c.severity === "warn" && c.verdict !== "skip"
14183
+ );
14184
+ if (warns.length === 0) return { name, weight: 1, score: 100, checks: [] };
14185
+ const results = warns.map(
14186
+ (c) => check(c.id, c.verdict === "pass", c.cmd, c.title)
14187
+ );
14188
+ return dim(name, 1, results);
14189
+ }
14190
+ async function computeReadiness(ctx) {
14191
+ const checks = await buildChecks(ctx);
14192
+ const blockers = checks.filter((c) => c.severity === "gate" && c.verdict === "fail").map((c) => ({ id: c.id, title: c.title, cmd: c.cmd, dimension: c.dimension }));
14193
+ const warns = checks.filter((c) => c.severity === "warn" && c.verdict === "fail").map((c) => ({ id: c.id, title: c.title, cmd: c.cmd, dimension: c.dimension }));
14194
+ const dims = [
14195
+ dimensionOf("machine", checks),
14196
+ dimensionOf("repo-contract", checks),
14197
+ dimensionOf("harness-wiring", checks)
14198
+ ];
14199
+ const rawScore = Math.round(dims.reduce((n, d) => n + d.score, 0) / dims.length);
14200
+ const hasBlocker = blockers.length > 0;
14201
+ const score = hasBlocker ? Math.min(rawScore, SCORE_CAP_WITH_BLOCKER) : rawScore;
14202
+ const grade = gradeOf(score);
14203
+ const banner = hasBlocker ? "NOT READY" : score >= READY_THRESHOLD ? "READY" : "READY, WITH GAPS";
14204
+ const stack = scanRepo(ctx.root, { maxDepth: 8, contextDir: ctx.contextDir });
14205
+ const firstCommand = stack.startCommand ?? stack.testRunner ?? null;
14206
+ return { banner, blockers, warns, score, rawScore, grade, dims, firstCommand };
14207
+ }
14208
+ function readinessDigest(ctx) {
14209
+ return {
14210
+ kind: "digest",
14211
+ describe: "Developer readiness",
14212
+ run: async () => {
14213
+ const r = await computeReadiness(ctx);
14214
+ const data = {
14215
+ banner: r.banner,
14216
+ blockers: r.blockers,
14217
+ score: r.score,
14218
+ rawScore: r.rawScore,
14219
+ grade: r.grade,
14220
+ warns: r.warns,
14221
+ firstCommand: r.firstCommand
14222
+ };
14223
+ return { text: renderReadinessBody(r), data };
14224
+ }
14225
+ };
14226
+ }
14227
+ function renderReadinessBody(r) {
14228
+ const { banner, blockers, warns, dims, score, grade } = r;
14229
+ const mark2 = (s) => s >= READY_THRESHOLD ? "\u2713" : s >= 50 ? "~" : "\xB7";
14230
+ return lines(
14231
+ `${banner} \u2014 ${score}/100 (${grade})`,
14232
+ "",
14233
+ ...blockers.length > 0 ? remediationBlock(
14234
+ ` ${blockers.length} blocker${blockers.length === 1 ? "" : "s"} \u2014 must fix before an agent can work:`,
14235
+ blockers.map((b) => ({ command: b.cmd, label: b.title }))
14236
+ ) : [" No blockers \u2014 nothing stops an agent from working here."],
14237
+ "",
14238
+ ...dims.map((d) => ` ${mark2(d.score)} ${d.name.padEnd(16)} ${d.score}/100`),
14239
+ "",
14240
+ warns.length > 0 ? ` ${warns.length} warn${warns.length === 1 ? "" : "s"} dinging the score (see the dimension lines above).` : " No warnings \u2014 every applicable check passes."
14241
+ );
14242
+ }
14243
+
14244
+ // src/tools/install.ts
14245
+ var TOOLS = [
14246
+ {
14247
+ tool: "ripgrep (rg)",
14248
+ bin: "rg",
14249
+ tier: "core",
14250
+ options: [
14251
+ { pm: "winget", argv: ["winget", "install", "-e", "--id", "BurntSushi.ripgrep.MSVC"] },
14252
+ { pm: "scoop", argv: ["scoop", "install", "ripgrep"] },
14253
+ { pm: "brew", argv: ["brew", "install", "ripgrep"] },
14254
+ { pm: "apt", argv: ["sudo", "apt-get", "install", "-y", "ripgrep"] },
14255
+ { pm: "cargo", argv: ["cargo", "install", "ripgrep"] }
14256
+ ],
14257
+ manual: "https://github.com/BurntSushi/ripgrep#installation"
14258
+ },
14259
+ {
14260
+ tool: "fd",
14261
+ bin: "fd",
14262
+ tier: "core",
14263
+ options: [
14264
+ { pm: "winget", argv: ["winget", "install", "-e", "--id", "sharkdp.fd"] },
14265
+ { pm: "scoop", argv: ["scoop", "install", "fd"] },
14266
+ { pm: "brew", argv: ["brew", "install", "fd"] },
14267
+ { pm: "apt", argv: ["sudo", "apt-get", "install", "-y", "fd-find"] },
14268
+ { pm: "cargo", argv: ["cargo", "install", "fd-find"] }
14269
+ ],
14270
+ manual: "https://github.com/sharkdp/fd#installation"
14271
+ },
14272
+ {
14273
+ tool: "jq",
14274
+ bin: "jq",
14275
+ tier: "core",
14276
+ options: [
14277
+ { pm: "winget", argv: ["winget", "install", "-e", "--id", "jqlang.jq"] },
14278
+ { pm: "scoop", argv: ["scoop", "install", "jq"] },
14279
+ { pm: "brew", argv: ["brew", "install", "jq"] },
14280
+ { pm: "apt", argv: ["sudo", "apt-get", "install", "-y", "jq"] }
14281
+ ],
14282
+ manual: "https://jqlang.github.io/jq/download/"
14283
+ },
14284
+ {
14285
+ tool: "ast-grep (sg)",
14286
+ bin: "sg",
14287
+ tier: "optional",
14288
+ options: [
14289
+ { pm: "brew", argv: ["brew", "install", "ast-grep"] },
14290
+ { pm: "cargo", argv: ["cargo", "install", "ast-grep", "--locked"] },
14291
+ { pm: "npm", argv: ["npm", "install", "-g", "@ast-grep/cli"] }
14292
+ ],
14293
+ manual: "https://ast-grep.github.io/guide/quick-start.html"
14294
+ },
14295
+ {
14296
+ tool: "comby",
14297
+ bin: "comby",
14298
+ tier: "optional",
14299
+ options: [{ pm: "brew", argv: ["brew", "install", "comby"] }],
14300
+ manual: "https://comby.dev/docs/get-started"
14301
+ },
14302
+ {
14303
+ tool: "tree",
14304
+ bin: "tree",
14305
+ tier: "optional",
14306
+ options: [
14307
+ { pm: "scoop", argv: ["scoop", "install", "tree"] },
14308
+ { pm: "brew", argv: ["brew", "install", "tree"] },
14309
+ { pm: "apt", argv: ["sudo", "apt-get", "install", "-y", "tree"] }
14310
+ ],
14311
+ manual: "install `tree` from your OS package manager"
14312
+ },
14313
+ {
14314
+ tool: "GitHub CLI (gh)",
14315
+ bin: "gh",
14316
+ tier: "optional",
14317
+ options: [
14318
+ { pm: "winget", argv: ["winget", "install", "-e", "--id", "GitHub.cli"] },
14319
+ { pm: "scoop", argv: ["scoop", "install", "gh"] },
14320
+ { pm: "brew", argv: ["brew", "install", "gh"] },
14321
+ { pm: "apt", argv: ["sudo", "apt-get", "install", "-y", "gh"] }
14322
+ ],
14323
+ manual: "https://cli.github.com"
14324
+ },
14325
+ {
14326
+ tool: "code-review-graph",
14327
+ bin: "code-review-graph",
14328
+ tier: "optional",
14329
+ // Pinned to match the uvx MCP runners (src/mcp/servers.ts + src/workspace/templates.ts);
14330
+ // bump in lockstep. PEP 508 `==` form — pip/uv reject the uvx `@2.3.6` shorthand.
14331
+ options: [
14332
+ { pm: "uv", argv: ["uv", "tool", "install", "code-review-graph==2.3.6"] },
14333
+ { pm: "pip", argv: ["pip", "install", "code-review-graph==2.3.6"] }
14334
+ ],
14335
+ manual: "pip install code-review-graph==2.3.6"
14336
+ }
14337
+ ];
14338
+ var PM_BINARIES = [
14339
+ ["winget", "winget"],
14340
+ ["scoop", "scoop"],
14341
+ ["brew", "brew"],
14342
+ ["apt-get", "apt"],
14343
+ ["cargo", "cargo"],
14344
+ ["npm", "npm"],
14345
+ ["uv", "uv"],
14346
+ ["pip", "pip"]
14347
+ ];
14348
+ async function onPath3(ctx, bin) {
14349
+ const argv = ctx.host.platform === "windows" ? ["where", bin] : ["which", bin];
14350
+ const res = await ctx.run(argv);
14351
+ return !res.spawnError && res.code === 0 && res.stdout.trim().length > 0;
14352
+ }
14353
+ async function detectPms(ctx) {
14354
+ const found = /* @__PURE__ */ new Set();
14355
+ await Promise.all(
14356
+ PM_BINARIES.map(async ([bin, key]) => {
14357
+ if (await onPath3(ctx, bin)) found.add(key);
14358
+ })
14359
+ );
14360
+ return found;
14361
+ }
14362
+ async function missingTools(ctx) {
14363
+ const checked = await Promise.all(
14364
+ TOOLS.map(async (t) => ({ t, present: await onPath3(ctx, t.bin) }))
14365
+ );
14366
+ return checked.filter((c) => !c.present).map((c) => c.t);
14367
+ }
14368
+ function chooseOption(t, pms) {
14369
+ return t.options.find((o) => pms.has(o.pm));
14370
+ }
14371
+ var WIN_CMD_SHIMS = /* @__PURE__ */ new Set(["npm", "npx", "pnpm", "scoop", "yarn"]);
14372
+ function execArgv(platform, argv) {
14373
+ return platform === "windows" && argv[0] !== void 0 && WIN_CMD_SHIMS.has(argv[0]) ? ["cmd", "/c", ...argv] : argv;
14374
+ }
14375
+ function installActionsFor(ctx, tools, pms) {
14376
+ const actions = [];
14377
+ for (const t of tools) {
14378
+ const opt = chooseOption(t, pms);
14379
+ if (opt) {
14380
+ actions.push(
14381
+ exec(`install ${t.tool} (${opt.pm})`, execArgv(ctx.host.platform, opt.argv), {
14382
+ allowFailure: true
14383
+ })
14384
+ );
14385
+ } else {
14386
+ actions.push(
14387
+ doc(
14388
+ `install ${t.tool} manually (no supported package manager found)`,
14389
+ `No detected package manager can install ${t.tool}. Install it manually: ${t.manual}`
14390
+ )
14391
+ );
14392
+ }
14393
+ }
14394
+ return actions;
14395
+ }
14396
+ function howToInstall(t, pms) {
14397
+ const opt = chooseOption(t, pms);
14398
+ return opt ? `\`${opt.argv.join(" ")}\`` : `manually \u2014 ${t.manual}`;
14399
+ }
14400
+ async function verifyTool(ctx, t, pms) {
14401
+ if (await onPath3(ctx, t.bin)) {
14402
+ return { name: t.tool, verdict: "pass", detail: `${t.bin} on PATH` };
14403
+ }
14404
+ return {
14405
+ name: t.tool,
14406
+ verdict: t.tier === "core" ? "fail" : "skip",
14407
+ detail: `${t.bin} not on PATH \u2014 install: ${howToInstall(t, pms)}`,
14408
+ code: "env.tool-install-blocked"
14409
+ };
14410
+ }
14411
+
14412
+ // src/ready/index.ts
14413
+ function handoffLine(firstCommand) {
14414
+ return firstCommand ? `Your first command: ${firstCommand} (declared in the repo \u2014 this runs the real work; aih stops here)` : "No runnable command declared \u2014 see setup.md before starting.";
14415
+ }
14416
+ function gateCheck(r) {
14417
+ const name = "readiness \u2014 no blockers";
14418
+ if (r.blockers.length > 0) {
14419
+ return {
14420
+ name,
14421
+ verdict: "fail",
14422
+ detail: `${r.blockers.length} blocker(s): ${r.blockers.map((b) => b.id).join(", ")}`,
14423
+ code: "ready.blocked"
14424
+ };
14425
+ }
14426
+ return { name, verdict: "pass", detail: "no blockers \u2014 an agent can start here" };
14427
+ }
14428
+ function specsForMissing(missingBins) {
14429
+ const want = new Set(missingBins);
14430
+ return TOOLS.filter((t) => want.has(t.bin));
14431
+ }
14432
+ async function shouldInstall(ctx, bins) {
14433
+ if (ctx.apply) return true;
14434
+ if (!ctx.prompter) return false;
14435
+ const answer = await ctx.prompter.ask(`Install ${bins.join(", ")} now? [y/N]: `);
14436
+ const yes = answer.trim().toLowerCase();
14437
+ return yes === "y" || yes === "yes";
14438
+ }
14439
+ async function coreToolInstallActions(ctx, specs) {
14440
+ const pms = await detectPms(ctx);
14441
+ const actions = installActionsFor(ctx, specs, pms);
14442
+ for (const t of specs) {
14443
+ actions.push(probe(`${t.tool} installed`, (c) => verifyTool(c, t, pms)));
14444
+ }
14445
+ actions.push(
14446
+ doc(
14447
+ "core tools \u2014 re-run to confirm",
14448
+ "Installed the missing core shell tools. Re-run `aih ready` to confirm the readiness gate now passes."
14449
+ )
14450
+ );
14451
+ return actions;
14452
+ }
14453
+ async function readyPlan(ctx) {
14454
+ const r = await computeReadiness(ctx);
14455
+ const body = `${renderReadinessBody(r)}
14456
+ ${handoffLine(r.firstCommand)}
14457
+ `;
14458
+ const data = {
14459
+ banner: r.banner,
14460
+ blockers: r.blockers,
14461
+ warns: r.warns,
14462
+ score: r.score,
14463
+ rawScore: r.rawScore,
14464
+ grade: r.grade,
14465
+ firstCommand: r.firstCommand
14466
+ };
14467
+ const actions = [
14468
+ digest("Developer readiness", body, data),
14469
+ probe("readiness \u2014 no blockers", () => gateCheck(r))
14470
+ ];
14471
+ const missingCore = await coreToolsMissing(ctx);
14472
+ if (missingCore.length > 0 && await shouldInstall(ctx, missingCore)) {
14473
+ ctx.apply = true;
14474
+ actions.push(...await coreToolInstallActions(ctx, specsForMissing(missingCore)));
14475
+ }
14476
+ return plan("ready", ...actions);
14477
+ }
14478
+ var command25 = {
14479
+ name: "ready",
14480
+ summary: "Readiness gate \u2014 can a developer start work with an AI agent here? (graded, blocker-aware)",
14481
+ alwaysVerify: true,
14482
+ // Offer the "Install rg, fd, jq now? [y/N]" confirmation on a bare `aih ready` in a
14483
+ // TTY (not just under `--detect`) — the install is what a first-time repo opener wants.
14484
+ wantsInstallPrompt: true,
14485
+ plan: readyPlan
14486
+ };
14487
+
14488
+ // src/report/index.ts
14489
+ import { existsSync as existsSync29, readFileSync as readFileSync6 } from "fs";
14490
+ import { basename as basename7, join as join55, resolve as resolve11 } from "path";
14491
+
14492
+ // src/support/findings.ts
14493
+ var NO_CODE_CHANGES = "No project code changes are required.";
14494
+ var CODE_META = {
14495
+ "env.node-runtime": {
14496
+ audience: "internal-it",
14497
+ failSeverity: "blocking",
14498
+ title: "required language runtime missing or unsupported",
14499
+ affectedArea: "local developer tooling",
14500
+ evidence: "The required language runtime (Node.js 20 or newer) is missing or below the supported version on this machine.",
14501
+ action: "Please provision the supported language runtime on this machine via the approved software catalog or installer, then have the developer reopen their shell.",
14502
+ acceptance: [
14503
+ "The supported runtime version is available on this machine.",
14504
+ "Project setup checks pass without further manual steps.",
14505
+ NO_CODE_CHANGES
14506
+ ]
14507
+ },
14508
+ "env.git-missing": {
14509
+ audience: "developer",
14510
+ failSeverity: "degraded",
14511
+ title: "git not found on PATH",
14512
+ action: "Install git (winget/apt/brew or your software catalog) and reopen the shell."
14513
+ },
14514
+ "env.dev-tool-missing": {
14515
+ audience: "developer",
14516
+ failSeverity: "degraded",
14517
+ title: "Dev tools (rg/fd/jq) missing",
14518
+ action: "Install rg/fd/jq (winget/scoop/brew), or on a locked-down VDI add your local bundle to PATH."
14519
+ },
14520
+ "env.tool-install-blocked": {
14521
+ audience: "internal-it",
14522
+ failSeverity: "degraded",
14523
+ title: "required developer tool could not be installed",
14524
+ affectedArea: "local developer tooling / software installation",
14525
+ evidence: "A required command-line tool is not available, and an automated install could not complete on this machine (no supported package manager is present, privileges are insufficient, or the package source is blocked).",
14526
+ action: "Please provision the missing tool via the approved software catalog / package manager, or unblock the package source on this machine so the developer can install it.",
14527
+ acceptance: [
14528
+ "The tool resolves on PATH.",
14529
+ "Project tooling checks pass without further manual steps.",
14530
+ NO_CODE_CHANGES
14531
+ ]
14532
+ },
14533
+ "cert.ca-missing": {
14534
+ audience: "internal-it",
14535
+ failSeverity: "blocking",
14536
+ title: "corporate CA not trusted by development tools",
14537
+ affectedArea: "workstation certificate trust / development toolchain trust",
14538
+ evidence: "TLS verification is failing because the approved corporate CA is not available to the tools running on this machine.\nThe Node CA bundle setting (NODE_EXTRA_CA_CERTS) is not set or does not point to a valid corporate CA bundle.",
14539
+ action: "Please make the approved corporate root certificate authority available on this machine and configure the development tools to trust it. For Node-based tools, this usually means setting NODE_EXTRA_CA_CERTS to the approved corporate CA bundle path, in addition to any required OS trust-store configuration.",
14540
+ acceptance: [
14541
+ "Development tools can complete TLS verification against approved internal and package sources.",
14542
+ "Package access works without disabling TLS checks.",
14543
+ NO_CODE_CHANGES
14544
+ ]
14545
+ },
14546
+ "tls.verify-failed": {
14547
+ audience: "internal-it",
14548
+ failSeverity: "blocking",
14549
+ title: "TLS verification to approved package sources is failing",
14550
+ affectedArea: "workstation certificate trust / development toolchain trust",
14551
+ evidence: "TLS connections to approved package registries are failing on this machine, consistent with an intercepting proxy whose certificate is not trusted by the development tools.",
14552
+ action: "Please ensure the intercepting proxy's certificate is trusted on this machine (OS trust store and the development toolchain) and that the approved package registry endpoints are reachable.",
14553
+ acceptance: [
14554
+ "Development tools can complete TLS verification against approved internal and package sources.",
14555
+ "Package access works without disabling TLS checks.",
14556
+ NO_CODE_CHANGES
14557
+ ]
14558
+ },
14559
+ "npm.runtime-broken": {
14560
+ audience: "internal-it",
14561
+ failSeverity: "blocking",
14562
+ title: "package manager is broken on this machine",
14563
+ affectedArea: "approved package registry access",
14564
+ evidence: "The package manager fails to run or cannot install from approved registries on this machine.",
14565
+ action: "Please repair the package manager installation on this machine so it runs and can install from approved registries; if the registry is unreachable, restore corporate TLS trust first.",
14566
+ acceptance: [
14567
+ "The package manager runs and can install from approved sources.",
14568
+ "No TLS checks are disabled to achieve this.",
14569
+ NO_CODE_CHANGES
14570
+ ]
14571
+ },
14572
+ "path.missing": {
14573
+ audience: "developer",
14574
+ failSeverity: "degraded",
14575
+ title: "Tool directory not on PATH",
14576
+ action: "Add the user tool directory to PATH (see `aih heal`) and reopen the shell."
14577
+ },
14578
+ "mcp.blocked": {
14579
+ audience: "dev-platform",
14580
+ failSeverity: "blocking",
14581
+ title: "package launcher cannot reach the approved registry",
14582
+ affectedArea: "approved package registry access",
14583
+ evidence: "The developer-platform package launcher cannot reach the approved package registry on this machine.",
14584
+ action: "Please restore access from this machine to the approved package registry for the launcher (certificate trust plus endpoint allowlisting), or approve a vendored, locally-installed server set.",
14585
+ acceptance: [
14586
+ "The launcher can reach approved package sources.",
14587
+ "No TLS checks are disabled to achieve this.",
14588
+ NO_CODE_CHANGES
14589
+ ]
14590
+ },
14591
+ "mcp.uv-missing": {
14592
+ audience: "dev-platform",
14593
+ failSeverity: "degraded",
13686
14594
  title: "required package launcher not available",
13687
14595
  affectedArea: "MCP / developer platform configuration",
13688
14596
  evidence: "A required package launcher (uv) is not available on this machine.",
@@ -13859,6 +14767,12 @@ var CODE_META = {
13859
14767
  title: "repo contract reports a non-portable path",
13860
14768
  action: "`aih report` found a non-portable path in the committed `project.json`. Re-run `aih contract --apply` to regenerate it from the live tree; verify with `aih doctor` (the `contract truth` probe)."
13861
14769
  },
14770
+ "ready.blocked": {
14771
+ audience: "developer",
14772
+ failSeverity: "blocking",
14773
+ title: "developer readiness is blocked",
14774
+ action: "`aih ready` found one or more blockers that stop an agent from starting here. Clear each named blocker with its own fix (see the readiness digest \u2014 e.g. `aih heal`, `aih contract --apply`, `aih secrets --apply`, `aih bootstrap-ai --apply`), then re-run `aih ready`."
14775
+ },
13862
14776
  "trust.hidden-unicode": {
13863
14777
  audience: "developer",
13864
14778
  failSeverity: "blocking",
@@ -15107,14 +16021,14 @@ function buildHero(digests) {
15107
16021
  return `<section class="hero">${adoptionPct !== void 0 ? `<div class="ring-card">${ring(adoptionPct)}<span class="ring-label">adoption</span></div>` : ""}<div class="kpis">${tiles.join("")}</div></section>`;
15108
16022
  }
15109
16023
  var BRAND = "Enterprise AI Bootstrapping Harness Report";
15110
- var KIND_LABEL = {
16024
+ var KIND_LABEL2 = {
15111
16025
  escalation: "Escalation",
15112
16026
  improvement: "Improvement",
15113
16027
  "self-fix": "Self-fix"
15114
16028
  };
15115
16029
  function ticketCard(t) {
15116
16030
  const isSelf = t.kind === "self-fix";
15117
- const kindLabel = KIND_LABEL[t.kind] ?? t.kind;
16031
+ const kindLabel = KIND_LABEL2[t.kind] ?? t.kind;
15118
16032
  const copyLabel = isSelf ? "Copy note" : "Copy email";
15119
16033
  const full = `${t.subject}
15120
16034
 
@@ -15182,8 +16096,8 @@ function reportHtml(title, digests, opts = {}) {
15182
16096
  }
15183
16097
 
15184
16098
  // src/report/bloat.ts
15185
- import { readdirSync as readdirSync8, statSync as statSync4 } from "fs";
15186
- import { join as join43 } from "path";
16099
+ import { readdirSync as readdirSync9, statSync as statSync4 } from "fs";
16100
+ import { join as join46 } from "path";
15187
16101
  var CHARS_PER_TOKEN = 4;
15188
16102
  var DEFAULT_CONTEXT_BUDGET_TOKENS = 4e4;
15189
16103
  var ROOT_CONTEXT_FILES = [
@@ -15196,7 +16110,7 @@ var ROOT_CONTEXT_FILES = [
15196
16110
  var EXTRA_CONTEXT_DIRS = [".cursor/rules"];
15197
16111
  function safeReadDir2(dir) {
15198
16112
  try {
15199
- return readdirSync8(dir).sort();
16113
+ return readdirSync9(dir).sort();
15200
16114
  } catch {
15201
16115
  return [];
15202
16116
  }
@@ -15217,10 +16131,10 @@ function isDir2(path) {
15217
16131
  }
15218
16132
  }
15219
16133
  function walk2(root, relDir, out) {
15220
- const absDir = join43(root, relDir);
16134
+ const absDir = join46(root, relDir);
15221
16135
  for (const entry2 of safeReadDir2(absDir)) {
15222
16136
  const rel = `${relDir}/${entry2}`;
15223
- if (isDir2(join43(absDir, entry2))) walk2(root, rel, out);
16137
+ if (isDir2(join46(absDir, entry2))) walk2(root, rel, out);
15224
16138
  else out.push(rel);
15225
16139
  }
15226
16140
  }
@@ -15228,14 +16142,14 @@ function estimateTokens(bytes) {
15228
16142
  return Math.ceil(bytes / CHARS_PER_TOKEN);
15229
16143
  }
15230
16144
  function fileFootprint(root, rel) {
15231
- const bytes = fileSize(join43(root, rel));
16145
+ const bytes = fileSize(join46(root, rel));
15232
16146
  return bytes === void 0 ? void 0 : { path: rel, bytes, tokens: estimateTokens(bytes) };
15233
16147
  }
15234
16148
  function scanContextBloat(root, contextDir, budgetTokens = DEFAULT_CONTEXT_BUDGET_TOKENS, opts = {}) {
15235
16149
  const accept = opts.accept ?? (() => true);
15236
16150
  const rels = new Set(ROOT_CONTEXT_FILES);
15237
16151
  for (const dir of [contextDir, ...EXTRA_CONTEXT_DIRS]) {
15238
- if (!isDir2(join43(root, dir))) continue;
16152
+ if (!isDir2(join46(root, dir))) continue;
15239
16153
  const found = [];
15240
16154
  walk2(root, dir, found);
15241
16155
  for (const rel of found) rels.add(rel);
@@ -15338,66 +16252,24 @@ function scanLoadGroups(root, contextDir, budgetTokens, opts = {}) {
15338
16252
  };
15339
16253
  });
15340
16254
  groups.sort(
15341
- (a, b) => Number(b.present) - Number(a.present) || b.tokens - a.tokens || a.label.localeCompare(b.label)
15342
- );
15343
- const worst = groups.find((g) => g.present) ?? null;
15344
- const worstTokens = worst?.tokens ?? 0;
15345
- const bootloaderSet = new Set(Object.values(CLI_BOOTLOADERS).flat());
15346
- const onDemandFiles = scanContextBloat(root, contextDir, budgetTokens, opts).files.filter(
15347
- (f) => !bootloaderSet.has(f.path)
15348
- );
15349
- return {
15350
- groups,
15351
- worst,
15352
- worstTokens,
15353
- budgetTokens,
15354
- overBudget: worstTokens > budgetTokens,
15355
- onDemandFiles,
15356
- onDemandTokens: onDemandFiles.reduce((n, f) => n + f.tokens, 0)
15357
- };
15358
- }
15359
-
15360
- // src/status.ts
15361
- import { existsSync as existsSync22 } from "fs";
15362
- import { join as join44 } from "path";
15363
- var ARTIFACTS = [
15364
- ["context-dir", ""],
15365
- // resolved against ctx.contextDir below
15366
- ["gitleaks", ".gitleaks.toml"],
15367
- ["pre-commit", ".pre-commit-config.yaml"],
15368
- ["devcontainer", ".devcontainer/devcontainer.json"]
15369
- ];
15370
- function inventory(root, contextDir) {
15371
- return ARTIFACTS.map(([name, rel]) => {
15372
- const relative10 = name === "context-dir" ? contextDir : rel;
15373
- return { name, relative: relative10, present: existsSync22(join44(root, relative10)) };
15374
- });
15375
- }
15376
- function enforcementDetail(name, root, relative10) {
15377
- if (name === "pre-commit") {
15378
- return preCommitHookActive(root) ? `${relative10} (git hook installed \u2014 active)` : `${relative10} present, but no active git pre-commit hook was found \u2014 run \`${GITHOOKS_PATH_COMMAND}\` after scaffold`;
15379
- }
15380
- return relative10;
15381
- }
15382
- var command23 = {
15383
- name: "status",
15384
- summary: "Show what the harness has configured for this repo/workstation (read-only)",
15385
- readOnly: true,
15386
- options: [],
15387
- plan: (ctx) => plan(
15388
- "status",
15389
- ...inventory(ctx.root, ctx.contextDir).map(
15390
- (a) => probe(
15391
- `presence: ${a.name}`,
15392
- () => a.present ? {
15393
- name: a.name,
15394
- verdict: "pass",
15395
- detail: enforcementDetail(a.name, ctx.root, a.relative)
15396
- } : { name: a.name, verdict: "skip", detail: `${a.relative} not present` }
15397
- )
15398
- )
15399
- )
15400
- };
16255
+ (a, b) => Number(b.present) - Number(a.present) || b.tokens - a.tokens || a.label.localeCompare(b.label)
16256
+ );
16257
+ const worst = groups.find((g) => g.present) ?? null;
16258
+ const worstTokens = worst?.tokens ?? 0;
16259
+ const bootloaderSet = new Set(Object.values(CLI_BOOTLOADERS).flat());
16260
+ const onDemandFiles = scanContextBloat(root, contextDir, budgetTokens, opts).files.filter(
16261
+ (f) => !bootloaderSet.has(f.path)
16262
+ );
16263
+ return {
16264
+ groups,
16265
+ worst,
16266
+ worstTokens,
16267
+ budgetTokens,
16268
+ overBudget: worstTokens > budgetTokens,
16269
+ onDemandFiles,
16270
+ onDemandTokens: onDemandFiles.reduce((n, f) => n + f.tokens, 0)
16271
+ };
16272
+ }
15401
16273
 
15402
16274
  // src/report/events.ts
15403
16275
  var SHOWN = 50;
@@ -15605,7 +16477,7 @@ function governanceRollupDigest(ctx) {
15605
16477
  }
15606
16478
 
15607
16479
  // src/report/graph.ts
15608
- import { join as join45 } from "path";
16480
+ import { join as join47 } from "path";
15609
16481
  function parseStats(text) {
15610
16482
  try {
15611
16483
  const v = JSON.parse(text);
@@ -15623,7 +16495,7 @@ function parseStats(text) {
15623
16495
  }
15624
16496
  }
15625
16497
  function readGraph(ctx) {
15626
- const file = readIfExists(join45(ctx.root, ".aih", "graph.json"));
16498
+ const file = readIfExists(join47(ctx.root, ".aih", "graph.json"));
15627
16499
  return file ? parseStats(file) : void 0;
15628
16500
  }
15629
16501
  async function graphDigests(ctx) {
@@ -15658,7 +16530,7 @@ async function graphDigests(ctx) {
15658
16530
  }
15659
16531
 
15660
16532
  // src/report/guardrail.ts
15661
- import { join as join46 } from "path";
16533
+ import { join as join48 } from "path";
15662
16534
  function parseCounts(text) {
15663
16535
  try {
15664
16536
  const v = JSON.parse(text);
@@ -15673,7 +16545,7 @@ function parseCounts(text) {
15673
16545
  }
15674
16546
  }
15675
16547
  function guardrailDigest(ctx) {
15676
- const file = readIfExists(join46(ctx.root, ".aih", "guardrail-scan.json"));
16548
+ const file = readIfExists(join48(ctx.root, ".aih", "guardrail-scan.json"));
15677
16549
  if (!file) return void 0;
15678
16550
  const c = parseCounts(file);
15679
16551
  if (!c) return void 0;
@@ -15692,37 +16564,16 @@ function guardrailDigest(ctx) {
15692
16564
  }
15693
16565
 
15694
16566
  // src/report/history.ts
15695
- import { existsSync as existsSync24 } from "fs";
15696
- import { join as join48 } from "path";
16567
+ import { existsSync as existsSync25 } from "fs";
16568
+ import { join as join50 } from "path";
15697
16569
 
15698
16570
  // src/report/scorecard.ts
15699
- import { existsSync as existsSync23, readdirSync as readdirSync9 } from "fs";
15700
- import { join as join47 } from "path";
16571
+ import { existsSync as existsSync24, readdirSync as readdirSync10 } from "fs";
16572
+ import { join as join49 } from "path";
15701
16573
  var ADAPTER_MAX_LINES = 75;
15702
- var GRADE_BANDS = [
15703
- { min: 85, grade: "mature" },
15704
- { min: 70, grade: "solid" },
15705
- { min: 50, grade: "emerging" },
15706
- { min: 0, grade: "nascent" }
15707
- ];
15708
- function gradeOf(score) {
15709
- for (const b of GRADE_BANDS) if (score >= b.min) return b.grade;
15710
- return "nascent";
15711
- }
15712
- function dimScore(checks) {
15713
- if (checks.length === 0) return 0;
15714
- const passed = checks.filter((c) => c.passed).length;
15715
- return Math.round(passed / checks.length * 100);
15716
- }
15717
- function check(id, passed, remediation, source) {
15718
- return { id, passed, remediation, source };
15719
- }
15720
- function dim(name, weight, checks) {
15721
- return { name, weight, score: dimScore(checks), checks };
15722
- }
15723
16574
  function bootloadersInSync(root, present, sharedBody) {
15724
16575
  for (const rel of present) {
15725
- const text = readIfExists(join47(root, rel));
16576
+ const text = readIfExists(join49(root, rel));
15726
16577
  if (text === void 0) continue;
15727
16578
  if (extractManagedBlock(text, SHARED_MARKER) !== sharedBody) return false;
15728
16579
  }
@@ -15730,23 +16581,23 @@ function bootloadersInSync(root, present, sharedBody) {
15730
16581
  }
15731
16582
  function bootloadersPointToRouter(root, present) {
15732
16583
  for (const rel of present) {
15733
- const text = readIfExists(join47(root, rel));
16584
+ const text = readIfExists(join49(root, rel));
15734
16585
  if (text === void 0) continue;
15735
16586
  if (!text.includes("RULE_ROUTER.md")) return false;
15736
16587
  }
15737
16588
  return true;
15738
16589
  }
15739
16590
  function adaptersThin(root, dir) {
15740
- const adaptersDir = join47(root, dir, "adapters");
16591
+ const adaptersDir = join49(root, dir, "adapters");
15741
16592
  let entries;
15742
16593
  try {
15743
- entries = readdirSync9(adaptersDir);
16594
+ entries = readdirSync10(adaptersDir);
15744
16595
  } catch {
15745
16596
  return true;
15746
16597
  }
15747
16598
  for (const name of entries) {
15748
16599
  if (!name.endsWith(".md")) continue;
15749
- const text = readIfExists(join47(adaptersDir, name));
16600
+ const text = readIfExists(join49(adaptersDir, name));
15750
16601
  if (text === void 0) continue;
15751
16602
  const count = text.replace(/\r\n/g, "\n").split("\n").length;
15752
16603
  if (count > ADAPTER_MAX_LINES) return false;
@@ -15802,20 +16653,20 @@ function buildDimensions(ctx) {
15802
16653
  const { root, contextDir: dir } = ctx;
15803
16654
  const inv = inventory(root, dir);
15804
16655
  const has = (name) => inv.find((i) => i.name === name)?.present ?? false;
15805
- const present = bootloaderPaths(SUPPORTED_CLIS).filter((rel) => existsSync23(join47(root, rel)));
16656
+ const present = bootloaderPaths(SUPPORTED_CLIS).filter((rel) => existsSync24(join49(root, rel)));
15806
16657
  const sharedBody = sharedCanonicalBlockBody(dir).trim();
15807
16658
  const preCommitHook2 = preCommitHookActive(root);
15808
16659
  return [
15809
16660
  dim("layering", 1, [
15810
16661
  check(
15811
16662
  "router-present",
15812
- existsSync23(join47(root, dir, "RULE_ROUTER.md")),
16663
+ existsSync24(join49(root, dir, "RULE_ROUTER.md")),
15813
16664
  "run: aih scaffold --apply",
15814
16665
  `${dir}/RULE_ROUTER.md`
15815
16666
  ),
15816
16667
  check(
15817
16668
  "core-rules-doc",
15818
- existsSync23(join47(root, dir, "rules", "agent-behavior-core.md")),
16669
+ existsSync24(join49(root, dir, "rules", "agent-behavior-core.md")),
15819
16670
  "run: aih scaffold --apply",
15820
16671
  `${dir}/rules/agent-behavior-core.md`
15821
16672
  )
@@ -15823,7 +16674,7 @@ function buildDimensions(ctx) {
15823
16674
  dim("sharing", 1, [
15824
16675
  check(
15825
16676
  "shared-block-source",
15826
- existsSync23(join47(root, dir, "adapters", "_shared-canonical-block.md")),
16677
+ existsSync24(join49(root, dir, "adapters", "_shared-canonical-block.md")),
15827
16678
  "run: aih scaffold --apply",
15828
16679
  `${dir}/adapters/_shared-canonical-block.md`
15829
16680
  ),
@@ -15865,7 +16716,7 @@ function buildDimensions(ctx) {
15865
16716
  dim("discoverability", 0.5, [
15866
16717
  check(
15867
16718
  "regeneration-doc",
15868
- existsSync23(join47(root, dir, "REGENERATION.md")),
16719
+ existsSync24(join49(root, dir, "REGENERATION.md")),
15869
16720
  "run: aih scaffold --apply",
15870
16721
  `${dir}/REGENERATION.md`
15871
16722
  ),
@@ -15889,7 +16740,7 @@ function buildDimensions(ctx) {
15889
16740
  }
15890
16741
  var mark = (score) => score >= 70 ? "\u2713" : score >= 50 ? "~" : "\xB7";
15891
16742
  function scorecardDigest(ctx) {
15892
- if (!existsSync23(join47(ctx.root, ctx.contextDir, "RULE_ROUTER.md"))) return void 0;
16743
+ if (!existsSync24(join49(ctx.root, ctx.contextDir, "RULE_ROUTER.md"))) return void 0;
15893
16744
  const dims = buildDimensions(ctx);
15894
16745
  const totalWeight = dims.reduce((n, d) => n + d.weight, 0);
15895
16746
  const overall = Math.round(dims.reduce((n, d) => n + d.score * d.weight, 0) / totalWeight);
@@ -15921,7 +16772,7 @@ function scorecardDigest(ctx) {
15921
16772
  }
15922
16773
 
15923
16774
  // src/report/history.ts
15924
- var HISTORY_PATH = join48(".aih", "history.jsonl");
16775
+ var HISTORY_PATH = join50(".aih", "history.jsonl");
15925
16776
  var TREND_WINDOW = 14;
15926
16777
  var SPARK = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
15927
16778
  async function locDelta(ctx) {
@@ -15936,11 +16787,11 @@ async function locDelta(ctx) {
15936
16787
  return { added, removed, net: added - removed };
15937
16788
  }
15938
16789
  function countDrift(ctx) {
15939
- if (!existsSync24(join48(ctx.root, ctx.contextDir, "RULE_ROUTER.md"))) return 0;
16790
+ if (!existsSync25(join50(ctx.root, ctx.contextDir, "RULE_ROUTER.md"))) return 0;
15940
16791
  const sharedBody = sharedCanonicalBlockBody(ctx.contextDir).trim();
15941
16792
  let n = 0;
15942
16793
  for (const rel of bootloaderPaths(SUPPORTED_CLIS)) {
15943
- const text = readIfExists(join48(ctx.root, rel));
16794
+ const text = readIfExists(join50(ctx.root, rel));
15944
16795
  if (text === void 0) continue;
15945
16796
  const block = extractManagedBlock(text, SHARED_MARKER);
15946
16797
  if (block !== void 0 && block.trim() !== sharedBody) n++;
@@ -15987,7 +16838,7 @@ async function collectSnapshot(ctx) {
15987
16838
  };
15988
16839
  }
15989
16840
  function readHistory(ctx) {
15990
- const raw = readIfExists(join48(ctx.root, HISTORY_PATH));
16841
+ const raw = readIfExists(join50(ctx.root, HISTORY_PATH));
15991
16842
  if (!raw) return [];
15992
16843
  const rows = [];
15993
16844
  for (const line of raw.split(/\r?\n/)) {
@@ -16313,13 +17164,13 @@ function leakPreventionsDigest(ctx) {
16313
17164
  // src/report/tools.ts
16314
17165
  var CORE_TOOLS = ["rg", "fd", "jq"];
16315
17166
  var OPTIONAL_TOOLS = ["sg", "comby", "tree", "gh", "code-review-graph"];
16316
- async function onPath3(ctx, bin) {
17167
+ async function onPath4(ctx, bin) {
16317
17168
  const argv = ctx.host.platform === "windows" ? ["where", bin] : ["which", bin];
16318
17169
  const res = await ctx.run(argv);
16319
17170
  return !res.spawnError && res.code === 0 && res.stdout.trim().length > 0;
16320
17171
  }
16321
17172
  async function toolsInstalledDigest(ctx) {
16322
- const check2 = (name) => onPath3(ctx, name).then((present) => ({ name, present }));
17173
+ const check2 = (name) => onPath4(ctx, name).then((present) => ({ name, present }));
16323
17174
  const core = await Promise.all(CORE_TOOLS.map(check2));
16324
17175
  const optional = await Promise.all(OPTIONAL_TOOLS.map(check2));
16325
17176
  const corePresent = core.filter((r) => r.present);
@@ -16353,13 +17204,13 @@ async function toolsInstalledDigest(ctx) {
16353
17204
  }
16354
17205
 
16355
17206
  // src/report/usage.ts
16356
- import { existsSync as existsSync25 } from "fs";
16357
- import { join as join49 } from "path";
16358
- var RECORDER_PATH2 = join49(".aih", "usage-record.mjs");
16359
- var GIT_POST_COMMIT_PATH = join49(".git", "hooks", "post-commit");
17207
+ import { existsSync as existsSync26 } from "fs";
17208
+ import { join as join51 } from "path";
17209
+ var RECORDER_PATH2 = join51(".aih", "usage-record.mjs");
17210
+ var GIT_POST_COMMIT_PATH = join51(".git", "hooks", "post-commit");
16360
17211
  function usageCaptureInstalled(ctx) {
16361
- if (!existsSync25(join49(ctx.root, RECORDER_PATH2))) return false;
16362
- return readIfExists(join49(ctx.root, GIT_POST_COMMIT_PATH))?.includes("usage-record.mjs") === true;
17212
+ if (!existsSync26(join51(ctx.root, RECORDER_PATH2))) return false;
17213
+ return readIfExists(join51(ctx.root, GIT_POST_COMMIT_PATH))?.includes("usage-record.mjs") === true;
16363
17214
  }
16364
17215
  var bar = (items, label) => {
16365
17216
  if (items.length === 0) return [` ${label}: (none captured yet)`];
@@ -19007,6 +19858,14 @@ var V9_DEMO = {
19007
19858
  deltas: ["\u25B2 +4 vs last run", "per-turn \u22123%", "drift 1\u21921", "5 open actions"],
19008
19859
  usageThisWeek: { actions: 1204, wowPct: 18 }
19009
19860
  },
19861
+ // Coherent with the rest of the demo: the host blockers `wins` shows were all healed,
19862
+ // so an agent can start (READY, WITH GAPS) — the guardrails gap is a warn, not a gate.
19863
+ ready: {
19864
+ banner: "READY, WITH GAPS",
19865
+ score: 88,
19866
+ grade: "solid",
19867
+ blockers: []
19868
+ },
19010
19869
  actions: [
19011
19870
  {
19012
19871
  sev: "high",
@@ -19030,7 +19889,7 @@ var V9_DEMO = {
19030
19889
  sev: "low",
19031
19890
  title: "Vet context7 MCP egress",
19032
19891
  body: "context7 is third-party egress \u2014 confirm approved.",
19033
- cmd: "aih mcp --review"
19892
+ cmd: "aih mcp --verify"
19034
19893
  },
19035
19894
  {
19036
19895
  sev: "low",
@@ -19217,6 +20076,7 @@ var V9_DEMO = {
19217
20076
  },
19218
20077
  gates: {
19219
20078
  "sec-hero": "live",
20079
+ "sec-ready": "live",
19220
20080
  "sec-actions": "live",
19221
20081
  "sec-wins": "live",
19222
20082
  "sec-context": "live",
@@ -19298,6 +20158,36 @@ function renderHero(h) {
19298
20158
  const worstSvg = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color:var(--warn)"><path d="M12 9v4M12 17h.01M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/></svg>';
19299
20159
  return `<span class="hero-eyebrow">Harness wiring \xB7 developer console</span><h2 class="hero-headline">${headline}</h2><p class="hero-sub">${sub}</p><div class="hero-score-row"><span class="hero-score-big">${h.wiringScore}<span class="of">/100</span></span><span class="hero-score-tier">${escHtml2(h.scoreLabel)}</span></div><div class="deltarow">${deltas}</div>${usage}<div class="worst">${worstSvg}<span>Weakest axis: <b>${escHtml2(h.worstAxis.name)} ${h.worstAxis.value}</b> \u2014 closing it moves the score most.</span></div><p class="hero-sub" style="font-size:.74rem;color:var(--dim);margin-top:.3rem">Score = harness <b>wiring present + in sync</b>, not rule quality.</p>`;
19300
20160
  }
20161
+ var ICON_READY = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 6 9 17l-5-5"/></svg>';
20162
+ var ICON_NOT_READY = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6M9 9l6 6"/></svg>';
20163
+ var ICON_GAPS = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/><path d="M12 9v4M12 17h.01"/></svg>';
20164
+ function readyStyle(banner) {
20165
+ if (banner === "READY") return { cls: "ok", icon: ICON_READY };
20166
+ if (banner === "NOT READY") return { cls: "bad", icon: ICON_NOT_READY };
20167
+ return { cls: "warn", icon: ICON_GAPS };
20168
+ }
20169
+ function renderReady(r) {
20170
+ const { cls, icon } = readyStyle(r.banner);
20171
+ const softVar = `var(--${cls}-soft)`;
20172
+ const strongVar = `var(--${cls})`;
20173
+ const sub = r.banner === "READY" ? "an agent can make a correct first change here" : r.banner === "NOT READY" ? "a blocker below stops an agent from working" : "an agent can work, but with gaps in the gears";
20174
+ const badge = `<span class="badge ${cls}">${r.score}/100 \xB7 ${escHtml2(r.grade)}</span>`;
20175
+ const statusBox = `<div class="drift-status" style="background:${softVar};border-color:color-mix(in oklab,${strongVar} 22%,transparent)"><div class="dicon" style="background:${strongVar}">${icon}</div><div class="dtext"><b style="color:${strongVar}">${escHtml2(r.banner)}</b><span>${sub}</span></div></div>`;
20176
+ const scoreRow = `<div class="donut-meta" style="margin-top:.6rem"><div class="row"><span class="k">Readiness score</span><span class="v">${r.score}/100 (${escHtml2(r.grade)})</span></div><div class="row"><span class="k">Blockers</span><span class="v" style="color:${r.blockers.length > 0 ? "var(--bad)" : "var(--ok)"}">${r.blockers.length === 0 ? "none" : `${r.blockers.length} \u2014 must fix`}</span></div></div>`;
20177
+ const verdict = `<div class="card span-5"><div class="card-head"><h3>Can I start?</h3>${badge}</div><div class="card-body">${statusBox}${scoreRow}<div class="method" style="margin-top:.6rem">Same gate as <code>aih ready</code> \u2014 machine \xB7 repo-contract \xB7 harness-wiring, over aih's read-only probes.</div></div></div>`;
20178
+ let blockersBody;
20179
+ if (r.blockers.length === 0) {
20180
+ blockersBody = `<div class="drift-status"><div class="dicon">${ICON_READY}</div><div class="dtext"><b>No blockers</b><span>nothing stops an agent from working here</span></div></div>`;
20181
+ } else {
20182
+ const rows = r.blockers.map(
20183
+ (b) => `<div class="drift-file"><span class="fd" style="background:var(--bad)"></span><span class="fn">${escHtml2(b.title)}</span><span class="fs"><code style="${CODE_STYLE}">${escHtml2(b.cmd)}</code></span><span class="ft bad">blocker</span></div>`
20184
+ ).join("");
20185
+ blockersBody = `<div class="drift-files">${rows}</div><div class="method" style="margin-top:.6rem">${r.blockers.length} blocker${r.blockers.length === 1 ? "" : "s"} above; see <b>What to fix first</b> for the full ranked list.</div>`;
20186
+ }
20187
+ const blockerBadge = r.blockers.length > 0 ? `<span class="badge bad">${r.blockers.length} blocker${r.blockers.length === 1 ? "" : "s"}</span>` : '<span class="badge ok">clear</span>';
20188
+ const blockers = `<div class="card span-7"><div class="card-head"><h3>Blockers \xB7 must fix before an agent can work</h3>${blockerBadge}</div><div class="card-body">${blockersBody}</div></div>`;
20189
+ return verdict + blockers;
20190
+ }
19301
20191
  var SEV_CARD = {
19302
20192
  high: {
19303
20193
  cls: "bad",
@@ -20147,7 +21037,7 @@ body[data-demo="on"] .demo-banner{display:flex}
20147
21037
  <div class="worst"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color:var(--warn)"><path d="M12 9v4M12 17h.01M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/></svg><span>Weakest axis: <b>Guardrails 40</b> \u2014 closing it moves the score most.</span></div>
20148
21038
  <p class="hero-sub" style="font-size:.74rem;color:var(--dim);margin-top:.3rem">Score = harness <b>wiring present + in sync</b>, not rule quality.</p>
20149
21039
  </div>
20150
- </section><section class="section" id="sec-actions"><div class="sec-head"><div class="sec-eyebrow"><span class="sec-no">\u2605</span><span class="sec-rule"></span><span class="sec-count">1 high \xB7 2 med \xB7 2 low</span></div><h2 class="sec-title">What to fix first \u2014 5 ranked actions</h2><p class="sec-insight">The spine of this report: every finding below, triaged by leverage, each with the exact command. When this list is empty, the harness is clean \u2014 nothing else here needs your attention.</p></div><div class="anom-strip"><div class="anom-card bad"><div class="anom-head"><div class="anom-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/><path d="M12 9v4M12 17h.01"/></svg></div><h4>Wire guardrails</h4><span class="sev">high</span></div><p class="anom-body">Guardrails at <b>40/100</b> \u2014 gitleaks config present but the pre-commit hook is <b>not installed</b> (present \u2260 enforced). Highest leverage + closes a real security gap.</p><div class="anom-evidence"><code style="font-family:var(--mono);font-size:.7rem;background:var(--surface-3);border:1px solid var(--border-2);padding:.2rem .5rem;border-radius:5px;color:var(--fg-2);white-space:nowrap;overflow:auto;flex:1">aih bootstrap-ai --scope guardrails --apply</code></div></div><div class="anom-card warn"><div class="anom-head"><div class="anom-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 8v4M12 16h.01"/></svg></div><h4>Add AGENTS.md</h4><span class="sev">med</span></div><p class="anom-body">Missing bootloader \u2014 <b>codex, opencode, zed</b> cannot load canon here. Only failing adoption check.</p><div class="anom-evidence"><code style="font-family:var(--mono);font-size:.7rem;background:var(--surface-3);border:1px solid var(--border-2);padding:.2rem .5rem;border-radius:5px;color:var(--fg-2);white-space:nowrap;overflow:auto;flex:1">aih scaffold --cli codex --apply</code></div></div><div class="anom-card warn"><div class="anom-head"><div class="anom-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 8v4M12 16h.01"/></svg></div><h4>Realign drifted canon</h4><span class="sev">med</span></div><p class="anom-body"><code>RULE_ROUTER.md</code> is <b>+42 tok</b> out of sync (2h). Realign before it reaches the other CLIs.</p><div class="anom-evidence"><code style="font-family:var(--mono);font-size:.7rem;background:var(--surface-3);border:1px solid var(--border-2);padding:.2rem .5rem;border-radius:5px;color:var(--fg-2);white-space:nowrap;overflow:auto;flex:1">aih bootstrap-ai --apply</code></div></div><div class="anom-card ok"><div class="anom-head"><div class="anom-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m9 18 6-6-6-6"/></svg></div><h4>Vet context7 MCP egress</h4><span class="sev">low</span></div><p class="anom-body">context7 is a <b>third-party</b> MCP server (sends queries off-box). Confirm it is approved for this repo.</p><div class="anom-evidence"><code style="font-family:var(--mono);font-size:.7rem;background:var(--surface-3);border:1px solid var(--border-2);padding:.2rem .5rem;border-radius:5px;color:var(--fg-2);white-space:nowrap;overflow:auto;flex:1">aih mcp --review</code></div></div><div class="anom-card ok"><div class="anom-head"><div class="anom-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m9 18 6-6-6-6"/></svg></div><h4>Wire usage + track hooks</h4><span class="sev">low</span></div><p class="anom-body">Activity, trends and time-to-green need the recorder + per-commit snapshots wired.</p><div class="anom-evidence"><code style="font-family:var(--mono);font-size:.7rem;background:var(--surface-3);border:1px solid var(--border-2);padding:.2rem .5rem;border-radius:5px;color:var(--fg-2);white-space:nowrap;overflow:auto;flex:1">aih usage --apply &amp;&amp; aih track --apply</code></div></div></div></section><section class="section" id="sec-wins"><div class="sec-head"><div class="sec-eyebrow"><span class="sec-no">\u2713</span><span class="sec-rule"></span><span class="sec-count">quick wins \xB7 since Jun 1</span></div><h2 class="sec-title">aih cleared 4 blockers to get you running</h2><p class="sec-insight">This is why aih exists. The runtime your AI tools assume \u2014 <b>corporate TLS trust, npm, PATH, MCP launch</b> \u2014 was broken on this host; aih diagnosed and repaired it, and has kept it green across <b>12 runs</b>. The cumulative payoff over the period, not just today.</p></div><div class="grid">
21040
+ </section><section class="section" id="sec-ready"><div class="sec-head"><div class="sec-eyebrow"><span class="sec-no">\u25C6</span><span class="sec-rule"></span><span class="sec-count">88/100 \xB7 solid</span></div><h2 class="sec-title">READY, WITH GAPS \u2014 an agent can start here</h2><p class="sec-insight">The single "can I start?" gate: the maturity score above rates harness <i>wiring</i>; this rates whether an agent can make a correct first change on THIS machine right now. A blocker is a hard stop \u2014 the full remediation list is in <b>What to fix first</b> below.</p></div><div class="grid"><div class="card span-5"><div class="card-head"><h3>Can I start?</h3><span class="badge warn">88/100 \xB7 solid</span></div><div class="card-body"><div class="drift-status" style="background:var(--warn-soft);border-color:color-mix(in oklab,var(--warn) 22%,transparent)"><div class="dicon" style="background:var(--warn)"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/><path d="M12 9v4M12 17h.01"/></svg></div><div class="dtext"><b style="color:var(--warn)">READY, WITH GAPS</b><span>an agent can work, but with gaps in the gears</span></div></div><div class="donut-meta" style="margin-top:.6rem"><div class="row"><span class="k">Readiness score</span><span class="v">88/100 (solid)</span></div><div class="row"><span class="k">Blockers</span><span class="v" style="color:var(--ok)">none</span></div></div><div class="method" style="margin-top:.6rem">Same gate as <code>aih ready</code> \u2014 machine \xB7 repo-contract \xB7 harness-wiring, over aih's read-only probes.</div></div></div><div class="card span-7"><div class="card-head"><h3>Blockers \xB7 must fix before an agent can work</h3><span class="badge ok">clear</span></div><div class="card-body"><div class="drift-status"><div class="dicon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 6 9 17l-5-5"/></svg></div><div class="dtext"><b>No blockers</b><span>nothing stops an agent from working here</span></div></div></div></div></div></section><section class="section" id="sec-actions"><div class="sec-head"><div class="sec-eyebrow"><span class="sec-no">\u2605</span><span class="sec-rule"></span><span class="sec-count">1 high \xB7 2 med \xB7 2 low</span></div><h2 class="sec-title">What to fix first \u2014 5 ranked actions</h2><p class="sec-insight">The spine of this report: every finding below, triaged by leverage, each with the exact command. When this list is empty, the harness is clean \u2014 nothing else here needs your attention.</p></div><div class="anom-strip"><div class="anom-card bad"><div class="anom-head"><div class="anom-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/><path d="M12 9v4M12 17h.01"/></svg></div><h4>Wire guardrails</h4><span class="sev">high</span></div><p class="anom-body">Guardrails at <b>40/100</b> \u2014 gitleaks config present but the pre-commit hook is <b>not installed</b> (present \u2260 enforced). Highest leverage + closes a real security gap.</p><div class="anom-evidence"><code style="font-family:var(--mono);font-size:.7rem;background:var(--surface-3);border:1px solid var(--border-2);padding:.2rem .5rem;border-radius:5px;color:var(--fg-2);white-space:nowrap;overflow:auto;flex:1">aih bootstrap-ai --scope guardrails --apply</code></div></div><div class="anom-card warn"><div class="anom-head"><div class="anom-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 8v4M12 16h.01"/></svg></div><h4>Add AGENTS.md</h4><span class="sev">med</span></div><p class="anom-body">Missing bootloader \u2014 <b>codex, opencode, zed</b> cannot load canon here. Only failing adoption check.</p><div class="anom-evidence"><code style="font-family:var(--mono);font-size:.7rem;background:var(--surface-3);border:1px solid var(--border-2);padding:.2rem .5rem;border-radius:5px;color:var(--fg-2);white-space:nowrap;overflow:auto;flex:1">aih scaffold --cli codex --apply</code></div></div><div class="anom-card warn"><div class="anom-head"><div class="anom-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 8v4M12 16h.01"/></svg></div><h4>Realign drifted canon</h4><span class="sev">med</span></div><p class="anom-body"><code>RULE_ROUTER.md</code> is <b>+42 tok</b> out of sync (2h). Realign before it reaches the other CLIs.</p><div class="anom-evidence"><code style="font-family:var(--mono);font-size:.7rem;background:var(--surface-3);border:1px solid var(--border-2);padding:.2rem .5rem;border-radius:5px;color:var(--fg-2);white-space:nowrap;overflow:auto;flex:1">aih bootstrap-ai --apply</code></div></div><div class="anom-card ok"><div class="anom-head"><div class="anom-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m9 18 6-6-6-6"/></svg></div><h4>Vet context7 MCP egress</h4><span class="sev">low</span></div><p class="anom-body">context7 is a <b>third-party</b> MCP server (sends queries off-box). Confirm it is approved for this repo.</p><div class="anom-evidence"><code style="font-family:var(--mono);font-size:.7rem;background:var(--surface-3);border:1px solid var(--border-2);padding:.2rem .5rem;border-radius:5px;color:var(--fg-2);white-space:nowrap;overflow:auto;flex:1">aih mcp --verify</code></div></div><div class="anom-card ok"><div class="anom-head"><div class="anom-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m9 18 6-6-6-6"/></svg></div><h4>Wire usage + track hooks</h4><span class="sev">low</span></div><p class="anom-body">Activity, trends and time-to-green need the recorder + per-commit snapshots wired.</p><div class="anom-evidence"><code style="font-family:var(--mono);font-size:.7rem;background:var(--surface-3);border:1px solid var(--border-2);padding:.2rem .5rem;border-radius:5px;color:var(--fg-2);white-space:nowrap;overflow:auto;flex:1">aih usage --apply &amp;&amp; aih track --apply</code></div></div></div></section><section class="section" id="sec-wins"><div class="sec-head"><div class="sec-eyebrow"><span class="sec-no">\u2713</span><span class="sec-rule"></span><span class="sec-count">quick wins \xB7 since Jun 1</span></div><h2 class="sec-title">aih cleared 4 blockers to get you running</h2><p class="sec-insight">This is why aih exists. The runtime your AI tools assume \u2014 <b>corporate TLS trust, npm, PATH, MCP launch</b> \u2014 was broken on this host; aih diagnosed and repaired it, and has kept it green across <b>12 runs</b>. The cumulative payoff over the period, not just today.</p></div><div class="grid">
20151
21041
  <div class="card span-8"><div class="card-head"><h3>Remediation ledger \xB7 what aih fixed</h3><span class="badge ok">runtime green</span></div><div class="card-body"><div class="drift-status"><div class="dicon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 6 9 17l-5-5"/></svg></div><div class="dtext"><b>Runtime is green \u2014 4 blockers cleared</b><span>the environment certs / npm / PATH / MCP assume now works</span></div></div><div class="drift-files"><div class="drift-file"><span class="fd"></span><span class="fn">Certificate trust chain</span><span class="fs">corporate CA \u2192 trusted (registry)</span><span class="ft ok">fixed \xB7 3d</span></div><div class="drift-file"><span class="fd"></span><span class="fn">npm runtime</span><span class="fs">self-signed cert in chain \u2192 healed</span><span class="ft ok">fixed \xB7 3d</span></div><div class="drift-file"><span class="fd"></span><span class="fn">PATH resolution</span><span class="fs">rg / fd / jq now resolve</span><span class="ft ok">fixed \xB7 3d</span></div><div class="drift-file"><span class="fd"></span><span class="fn">MCP pre-flight</span><span class="fs">npx can launch MCP servers</span><span class="ft ok">fixed \xB7 2h</span></div></div><div class="method" style="margin-top:.5rem">Re-run anytime \u2014 <code>aih heal --scope all</code> diagnoses and repairs.</div></div></div>
20152
21042
  <div class="card span-4"><div class="card-head"><h3>Over the period</h3><span class="badge muted">run ledger</span></div><div class="card-body"><div style="display:flex;gap:1.2rem;flex-wrap:wrap;margin-bottom:.6rem"><div class="heatmap-stat"><b>4</b><span>blockers cleared</span></div><div class="heatmap-stat"><b>12</b><span>aih runs</span></div></div><div style="font-size:.66rem;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);font-weight:600;margin-bottom:.2rem">Open blockers over time</div><svg viewBox="0 0 200 40" preserveAspectRatio="none" style="width:100%;height:40px"><path d="M0,40 L0.0,4.0 L28.6,10.4 L57.1,10.4 L85.7,23.2 L114.3,29.6 L142.9,29.6 L171.4,36.0 L200.0,36.0 L200,40 Z" fill="url(#sparkArea)"/><polyline points="0.0,4.0 28.6,10.4 57.1,10.4 85.7,23.2 114.3,29.6 142.9,29.6 171.4,36.0 200.0,36.0" fill="none" stroke="var(--ok)" stroke-width="2"/></svg><div style="margin-top:.4rem;font-size:.72rem;color:var(--dim)">5 \u2192 0 since first run \xB7 Jun 1</div></div></div>
20153
21043
  </div></section><section class="section" id="sec-context"><div class="sec-head"><div class="sec-eyebrow"><span class="sec-no">01</span><span class="sec-rule"></span><span class="sec-count">context</span></div><h2 class="sec-title">Per-turn context is lean \u2014 62% headroom</h2><p class="sec-insight">The cost that matters is what a CLI loads <b>per turn</b>: <b class="ok">12,200 of 32,000 tokens</b> (claude, the heaviest), down 3% since last run. The full on-disk corpus is larger (18,400 tok / 42 files) but never loaded at once.</p></div><div class="grid">
@@ -20193,7 +21083,7 @@ var RADAR={"labels":["Layering","Sharing","Wiring","Guardrails","Discover"],"val
20193
21083
  function buildRadar(el,values,labels){var cx=150,cy=150,r=105,n=values.length,max=100,ang=[];for(var i=0;i<n;i++)ang.push(-Math.PI/2+i*2*Math.PI/n);var g='';[25,50,75,100].forEach(function(l){var p=[];for(var i=0;i<n;i++){var rr=r*l/max;p.push((cx+rr*Math.cos(ang[i])).toFixed(1)+','+(cy+rr*Math.sin(ang[i])).toFixed(1));}g+='<polygon points="'+p.join(' ')+'" fill="none" stroke="var(--border)" stroke-width="1" opacity="'+(0.4+l/200)+'"/>';});for(var i=0;i<n;i++)g+='<line x1="'+cx+'" y1="'+cy+'" x2="'+(cx+r*Math.cos(ang[i])).toFixed(1)+'" y2="'+(cy+r*Math.sin(ang[i])).toFixed(1)+'" stroke="var(--border)" stroke-width="1" opacity="0.5"/>';var dp=[];for(var i=0;i<n;i++){var rr=r*Math.max(0,Math.min(values[i],max))/max;dp.push((cx+rr*Math.cos(ang[i])).toFixed(1)+','+(cy+rr*Math.sin(ang[i])).toFixed(1));}g+='<polygon points="'+dp.join(' ')+'" fill="url(#radarGrad)" stroke="var(--accent)" stroke-width="2" stroke-linejoin="round"/>';for(var i=0;i<n;i++){var rr=r*values[i]/max,px=(cx+rr*Math.cos(ang[i])).toFixed(1),py=(cy+rr*Math.sin(ang[i])).toFixed(1),low=values[i]<70;g+='<circle cx="'+px+'" cy="'+py+'" r="4" fill="'+(low?'var(--warn)':'var(--accent)')+'" stroke="var(--surface)" stroke-width="2"/>';}for(var i=0;i<n;i++){var lr=r+22,lx=cx+lr*Math.cos(ang[i]),ly=cy+lr*Math.sin(ang[i]),a='middle';if(Math.abs(lx-cx)>10)a=lx>cx?'start':'end';var low=values[i]<70;g+='<text x="'+lx.toFixed(1)+'" y="'+(ly+3).toFixed(1)+'" text-anchor="'+a+'" font-size="10" font-weight="600" fill="var(--fg-2)">'+labels[i]+'</text><text x="'+lx.toFixed(1)+'" y="'+(ly+16).toFixed(1)+'" text-anchor="'+a+'" font-family="var(--mono)" font-size="9" fill="'+(low?'var(--warn)':'var(--muted)')+'">'+values[i]+'</text>';}el.innerHTML=g;}
20194
21084
  function buildLegend(el,v,l){var h='';for(var i=0;i<v.length;i++){h+='<span class="radar-leg'+(v[i]<70?' warn':'')+'"><span class="dot"></span>'+l[i]+' <b>'+v[i]+'</b></span>';}el.innerHTML=h;}
20195
21085
  function buildHeat(el){var h='';for(var i=0;i<105;i++){var v=(Math.sin(i*0.6)*0.5+0.5)*0.7+Math.min(1,i/60)*0.4;if(i%7===5||i%7===6)v*=0.35;if(i>90)v*=1.4;v=Math.max(0,Math.min(1,v));var c='';if(v>0.15)c='l1';if(v>0.35)c='l2';if(v>0.55)c='l3';if(v>0.75)c='l4';h+='<span class="cell '+c+'"></span>';}el.innerHTML=h;}
20196
- var PI=[["\u2605","What to fix first","sec-actions"],["\u2713","What aih unblocked","sec-wins"],["01","Context","sec-context"],["02","Activity","sec-activity"],["03","Guardrails + ECC","sec-quality"],["04","Drift + integrity","sec-drift"],["05","MCP plumbing","sec-mcp"],["06","Setup + tooling","sec-adoption"],["07","Enterprise support","sec-support"],["08","Over the period","sec-period"],["09","Skill investment","sec-skills"],["\u2191","Hero","sec-hero"]];
21086
+ var PI=[["\u25C6","Developer readiness","sec-ready"],["\u2605","What to fix first","sec-actions"],["\u2713","What aih unblocked","sec-wins"],["01","Context","sec-context"],["02","Activity","sec-activity"],["03","Guardrails + ECC","sec-quality"],["04","Drift + integrity","sec-drift"],["05","MCP plumbing","sec-mcp"],["06","Setup + tooling","sec-adoption"],["07","Enterprise support","sec-support"],["08","Over the period","sec-period"],["09","Skill investment","sec-skills"],["\u2191","Hero","sec-hero"]];
20197
21087
  var pOpen=false;
20198
21088
  function aihPalette(){var p=document.getElementById('palette');pOpen=!pOpen;p.classList.toggle('open',pOpen);if(pOpen){var i=document.getElementById('palette-input');i.value='';i.focus();renderP('');}}
20199
21089
  function renderP(q){q=(q||'').toLowerCase();var f=PI.filter(function(it){return !q||it[1].toLowerCase().indexOf(q)>=0;});document.getElementById('palette-list').innerHTML=f.length?f.map(function(it){return '<div class="palette-item" onclick="jumpP(\\''+it[2]+'\\')"><span class="pn">'+it[0]+'</span><span class="pt">'+it[1]+'</span><span class="pc">section</span></div>';}).join(''):'<div class="palette-empty">No matches</div>';}
@@ -20274,8 +21164,36 @@ function buildHero2(digests, actionCount, driftCount) {
20274
21164
  // usageThisWeek omitted in LIVE until the usage recorder + a w/w baseline exist.
20275
21165
  };
20276
21166
  }
21167
+ function buildReady(digests) {
21168
+ const r = bag2(digests, "Developer readiness");
21169
+ if (!r || typeof r.banner !== "string") return void 0;
21170
+ const banner = r.banner === "READY" || r.banner === "NOT READY" || r.banner === "READY, WITH GAPS" ? r.banner : "READY, WITH GAPS";
21171
+ const blockers = (Array.isArray(r.blockers) ? r.blockers : []).map((b) => ({
21172
+ id: String(b.id ?? ""),
21173
+ title: String(b.title ?? ""),
21174
+ cmd: String(b.cmd ?? "")
21175
+ }));
21176
+ return {
21177
+ banner,
21178
+ score: numOr2(r.score, 0),
21179
+ grade: typeof r.grade === "string" ? r.grade : "",
21180
+ blockers
21181
+ };
21182
+ }
20277
21183
  function deriveActions(digests) {
20278
21184
  const out = [];
21185
+ const ready = bag2(digests, "Developer readiness");
21186
+ const blockers = ready && Array.isArray(ready.blockers) ? ready.blockers : [];
21187
+ for (const b of blockers) {
21188
+ const title = String(b.title ?? "");
21189
+ if (!title) continue;
21190
+ out.push({
21191
+ sev: "high",
21192
+ title: `Fix: ${title}`,
21193
+ body: "Hard readiness blocker \u2014 an agent cannot make a correct first change until this clears.",
21194
+ cmd: String(b.cmd ?? "")
21195
+ });
21196
+ }
20279
21197
  const sc = bag2(digests, "Harness maturity");
20280
21198
  if (sc) {
20281
21199
  const dims = Array.isArray(sc.dimensions) ? sc.dimensions : [];
@@ -20320,7 +21238,7 @@ function deriveActions(digests) {
20320
21238
  sev: "low",
20321
21239
  title: `Vet ${name} MCP egress`,
20322
21240
  body: `${name} is a third-party MCP server (queries leave the box) \u2014 confirm it is approved for this repo.`,
20323
- cmd: "aih mcp --review"
21241
+ cmd: "aih mcp --verify"
20324
21242
  });
20325
21243
  }
20326
21244
  }
@@ -20637,6 +21555,7 @@ function emptyGates() {
20637
21555
  const g = {};
20638
21556
  for (const id of [
20639
21557
  "sec-hero",
21558
+ "sec-ready",
20640
21559
  "sec-actions",
20641
21560
  "sec-wins",
20642
21561
  "sec-context",
@@ -20663,6 +21582,7 @@ function buildAihDataV9(digests) {
20663
21582
  const actions = deriveActions(digests);
20664
21583
  let drift = buildDrift(digests);
20665
21584
  const hero = buildHero2(digests, actions.length, drift?.drifted.length);
21585
+ const ready = buildReady(digests);
20666
21586
  const context = buildContext(digests);
20667
21587
  const usage = buildUsage(digests);
20668
21588
  const baseActivity = buildActivity(digests);
@@ -20698,6 +21618,7 @@ function buildAihDataV9(digests) {
20698
21618
  ...outcome ? { outcomeDeltas: outcome } : {}
20699
21619
  } : void 0;
20700
21620
  if (hero) gates["sec-hero"] = "live";
21621
+ if (ready) gates["sec-ready"] = "live";
20701
21622
  gates["sec-actions"] = "live";
20702
21623
  gates["sec-wins"] = wins ? "live" : "empty";
20703
21624
  if (context) gates["sec-context"] = "live";
@@ -20716,6 +21637,7 @@ function buildAihDataV9(digests) {
20716
21637
  gates["cap-usage"] = usage ? "live" : "preview";
20717
21638
  return {
20718
21639
  ...hero ? { hero } : {},
21640
+ ...ready ? { ready } : {},
20719
21641
  actions,
20720
21642
  ...wins ? { wins } : {},
20721
21643
  ...context ? { context } : {},
@@ -20761,6 +21683,26 @@ function assembleViewV9(data, demo) {
20761
21683
  } else {
20762
21684
  sections["sec-hero"] = { state: "empty", container: ".hero-narrative", html: heroStub() };
20763
21685
  }
21686
+ if (data.ready) {
21687
+ const r = data.ready;
21688
+ const clean = r.blockers.length === 0;
21689
+ sections["sec-ready"] = {
21690
+ state: "live",
21691
+ container: ".grid",
21692
+ title: clean ? `${r.banner} \u2014 an agent can start here` : `${r.banner} \u2014 ${r.blockers.length} blocker${r.blockers.length === 1 ? "" : "s"} before an agent can work`,
21693
+ insight: 'The single "can I start?" gate: the maturity score above rates harness <i>wiring</i>; this rates whether an agent can make a correct first change on THIS machine right now. A blocker is a hard stop \u2014 the full remediation list is in <b>What to fix first</b> below.',
21694
+ count: `${r.score}/100 \xB7 ${escHtml2(r.grade)}`,
21695
+ html: renderReady(r)
21696
+ };
21697
+ } else {
21698
+ sections["sec-ready"] = emptySection(
21699
+ "Developer readiness \u2014 not measured here",
21700
+ "Readiness is computed on the local report path only.",
21701
+ "readiness",
21702
+ "Developer readiness",
21703
+ "Readiness is only measured on a local <code>aih report</code> run."
21704
+ );
21705
+ }
20764
21706
  {
20765
21707
  const actions = data.actions ?? [];
20766
21708
  const high = actions.filter((a) => a.sev === "high").length;
@@ -21083,20 +22025,20 @@ var RADAR=(window.AIH_DATA&&AIH_DATA.radar)?AIH_DATA.radar:${RADAR_FALLBACK};`
21083
22025
  }
21084
22026
 
21085
22027
  // src/report/v9-panels.ts
21086
- import { existsSync as existsSync26, readdirSync as readdirSync10 } from "fs";
21087
- import { join as join50 } from "path";
22028
+ import { existsSync as existsSync27, readdirSync as readdirSync11 } from "fs";
22029
+ import { join as join52 } from "path";
21088
22030
  function estTokens(s) {
21089
22031
  return Math.round(s.length / 4);
21090
22032
  }
21091
22033
  function driftDigest(ctx) {
21092
22034
  const dir = ctx.contextDir;
21093
- if (!existsSync26(join50(ctx.root, dir, "RULE_ROUTER.md"))) return void 0;
22035
+ if (!existsSync27(join52(ctx.root, dir, "RULE_ROUTER.md"))) return void 0;
21094
22036
  const sharedBody = sharedCanonicalBlockBody(dir).trim();
21095
- const present = bootloaderPaths(SUPPORTED_CLIS).filter((rel) => existsSync26(join50(ctx.root, rel)));
22037
+ const present = bootloaderPaths(SUPPORTED_CLIS).filter((rel) => existsSync27(join52(ctx.root, rel)));
21096
22038
  const drifted = [];
21097
22039
  const synced = [];
21098
22040
  for (const rel of present) {
21099
- const text = readIfExists(join50(ctx.root, rel));
22041
+ const text = readIfExists(join52(ctx.root, rel));
21100
22042
  if (text === void 0) continue;
21101
22043
  const block = extractManagedBlock(text, SHARED_MARKER);
21102
22044
  if (block === void 0) continue;
@@ -21133,7 +22075,7 @@ function egressLabel(egress) {
21133
22075
  return "unknown";
21134
22076
  }
21135
22077
  function configuredServerNames(root) {
21136
- const text = readIfExists(join50(root, ".mcp.json"));
22078
+ const text = readIfExists(join52(root, ".mcp.json"));
21137
22079
  if (text === void 0) return void 0;
21138
22080
  try {
21139
22081
  const parsed = JSON.parse(text);
@@ -21192,7 +22134,7 @@ ${t.body}`;
21192
22134
  }
21193
22135
  function countEntries(abs, opts) {
21194
22136
  try {
21195
- return readdirSync10(abs, { withFileTypes: true }).filter((d) => {
22137
+ return readdirSync11(abs, { withFileTypes: true }).filter((d) => {
21196
22138
  if (opts.dirsOnly) return d.isDirectory();
21197
22139
  if (opts.ext) return d.isFile() && d.name.endsWith(opts.ext);
21198
22140
  return true;
@@ -21221,7 +22163,7 @@ function countHooks(abs) {
21221
22163
  }
21222
22164
  function listNames(abs, opts) {
21223
22165
  try {
21224
- return readdirSync10(abs, { withFileTypes: true }).filter(
22166
+ return readdirSync11(abs, { withFileTypes: true }).filter(
21225
22167
  (d) => opts.dirsOnly ? d.isDirectory() : opts.ext ? d.isFile() && d.name.endsWith(opts.ext) : true
21226
22168
  ).map((d) => opts.ext ? d.name.slice(0, -opts.ext.length) : d.name);
21227
22169
  } catch {
@@ -21231,19 +22173,19 @@ function listNames(abs, opts) {
21231
22173
  function countMdRecursive(abs) {
21232
22174
  let dirents;
21233
22175
  try {
21234
- dirents = readdirSync10(abs, { withFileTypes: true });
22176
+ dirents = readdirSync11(abs, { withFileTypes: true });
21235
22177
  } catch {
21236
22178
  return 0;
21237
22179
  }
21238
22180
  let n = 0;
21239
22181
  for (const e of dirents) {
21240
- if (e.isDirectory()) n += countMdRecursive(join50(abs, e.name));
22182
+ if (e.isDirectory()) n += countMdRecursive(join52(abs, e.name));
21241
22183
  else if (e.isFile() && e.name.endsWith(".md")) n++;
21242
22184
  }
21243
22185
  return n;
21244
22186
  }
21245
22187
  function readEccMeta(home) {
21246
- const text = readIfExists(join50(home, ".claude", "ecc", "install-state.json"));
22188
+ const text = readIfExists(join52(home, ".claude", "ecc", "install-state.json"));
21247
22189
  if (text === void 0) return void 0;
21248
22190
  try {
21249
22191
  const s = JSON.parse(text);
@@ -21257,34 +22199,34 @@ function readEccMeta(home) {
21257
22199
  }
21258
22200
  }
21259
22201
  function eccNamespaced(base, opts) {
21260
- const ns = join50(base, "ecc");
21261
- return existsSync26(ns) ? listNames(ns, opts) : listNames(base, opts);
22202
+ const ns = join52(base, "ecc");
22203
+ return existsSync27(ns) ? listNames(ns, opts) : listNames(base, opts);
21262
22204
  }
21263
22205
  function eccInventoryDigest(ctx) {
21264
22206
  const r = ctx.root;
21265
- const mClaude = join50(homeDir(ctx), ".claude");
22207
+ const mClaude = join52(homeDir(ctx), ".claude");
21266
22208
  const meta = readEccMeta(homeDir(ctx));
21267
- const eccAgentNames = eccNamespaced(join50(mClaude, "agents"), { ext: ".md" });
21268
- const eccSkillNames = eccNamespaced(join50(mClaude, "skills"), { dirsOnly: true });
21269
- const rulesBase = join50(mClaude, "rules");
22209
+ const eccAgentNames = eccNamespaced(join52(mClaude, "agents"), { ext: ".md" });
22210
+ const eccSkillNames = eccNamespaced(join52(mClaude, "skills"), { dirsOnly: true });
22211
+ const rulesBase = join52(mClaude, "rules");
21270
22212
  const machine = {
21271
22213
  agents: eccAgentNames.length,
21272
22214
  skills: eccSkillNames.length,
21273
- rules: existsSync26(join50(rulesBase, "ecc")) ? countMdRecursive(join50(rulesBase, "ecc")) : countMdRecursive(rulesBase)
22215
+ rules: existsSync27(join52(rulesBase, "ecc")) ? countMdRecursive(join52(rulesBase, "ecc")) : countMdRecursive(rulesBase)
21274
22216
  };
21275
22217
  const repoAgents = [
21276
- ...listNames(join50(r, ".claude", "agents"), { ext: ".md" }),
21277
- ...listNames(join50(r, ".kiro", "agents"), { ext: ".md" })
22218
+ ...listNames(join52(r, ".claude", "agents"), { ext: ".md" }),
22219
+ ...listNames(join52(r, ".kiro", "agents"), { ext: ".md" })
21278
22220
  ];
21279
22221
  const repoSkills = [
21280
- ...listNames(join50(r, ".claude", "skills"), { dirsOnly: true }),
21281
- ...listNames(join50(r, ".kiro", "skills"), { dirsOnly: true })
22222
+ ...listNames(join52(r, ".claude", "skills"), { dirsOnly: true }),
22223
+ ...listNames(join52(r, ".kiro", "skills"), { dirsOnly: true })
21282
22224
  ];
21283
22225
  const repo = {
21284
22226
  agents: repoAgents.length,
21285
22227
  skills: repoSkills.length,
21286
- rules: countEntries(join50(r, ".claude", "rules"), { ext: ".md" }) + countEntries(join50(r, ".kiro", "steering"), { ext: ".md" }),
21287
- hooks: countHooks(join50(r, ".claude", "settings.json"))
22228
+ rules: countEntries(join52(r, ".claude", "rules"), { ext: ".md" }) + countEntries(join52(r, ".kiro", "steering"), { ext: ".md" }),
22229
+ hooks: countHooks(join52(r, ".claude", "settings.json"))
21288
22230
  };
21289
22231
  if (machine.agents + machine.skills + machine.rules + repo.agents + repo.skills + repo.rules + repo.hooks === 0) {
21290
22232
  return void 0;
@@ -21319,7 +22261,7 @@ function eccInventoryDigest(ctx) {
21319
22261
  }
21320
22262
  function coherenceDigest(ctx) {
21321
22263
  const dir = ctx.contextDir;
21322
- if (!existsSync26(join50(ctx.root, dir, "RULE_ROUTER.md"))) return void 0;
22264
+ if (!existsSync27(join52(ctx.root, dir, "RULE_ROUTER.md"))) return void 0;
21323
22265
  const targeted = scanCliCoverage(ctx).rows.filter((row) => row.targeted);
21324
22266
  if (targeted.length < 2) return void 0;
21325
22267
  const sharedBody = sharedCanonicalBlockBody(dir).trim();
@@ -21328,7 +22270,7 @@ function coherenceDigest(ctx) {
21328
22270
  for (const row of targeted) {
21329
22271
  let text;
21330
22272
  for (const p of CLI_BOOTLOADERS[row.cli] ?? []) {
21331
- const t = readIfExists(join50(ctx.root, p));
22273
+ const t = readIfExists(join52(ctx.root, p));
21332
22274
  if (t !== void 0) {
21333
22275
  text = t;
21334
22276
  break;
@@ -21360,16 +22302,16 @@ function coherenceDigest(ctx) {
21360
22302
  }
21361
22303
  var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
21362
22304
  function readLedger(root) {
21363
- const dirAbs = join50(root, ".aih", "runs");
22305
+ const dirAbs = join52(root, ".aih", "runs");
21364
22306
  let files;
21365
22307
  try {
21366
- files = readdirSync10(dirAbs).filter((f) => f.endsWith(".jsonl")).sort();
22308
+ files = readdirSync11(dirAbs).filter((f) => f.endsWith(".jsonl")).sort();
21367
22309
  } catch {
21368
22310
  return [];
21369
22311
  }
21370
22312
  const out = [];
21371
22313
  for (const f of files) {
21372
- const text = readIfExists(join50(dirAbs, f));
22314
+ const text = readIfExists(join52(dirAbs, f));
21373
22315
  if (text === void 0) continue;
21374
22316
  for (const line of text.split("\n")) {
21375
22317
  const t = line.trim();
@@ -21466,7 +22408,7 @@ function winsDigest(ctx) {
21466
22408
  const last = heal[heal.length - 1];
21467
22409
  const allGreen = last?.status === "success" && (last?.verification?.fail ?? 0) === 0;
21468
22410
  const probed = (() => {
21469
- const text = readIfExists(join50(ctx.root, ".aih", "heal-last.json"));
22411
+ const text = readIfExists(join52(ctx.root, ".aih", "heal-last.json"));
21470
22412
  if (text === void 0) return void 0;
21471
22413
  try {
21472
22414
  const p = JSON.parse(text);
@@ -21505,6 +22447,9 @@ function winsDigest(ctx) {
21505
22447
  }
21506
22448
  async function v9ExtraDigests(ctx) {
21507
22449
  return [
22450
+ // Readiness ALWAYS renders (even a harness-less repo earns a verdict), so its
22451
+ // `sec-ready` panel is always LIVE on the local report path.
22452
+ readinessDigest(ctx),
21508
22453
  driftDigest(ctx),
21509
22454
  mcpServersDigest(ctx),
21510
22455
  eccInventoryDigest(ctx),
@@ -21515,14 +22460,14 @@ async function v9ExtraDigests(ctx) {
21515
22460
  }
21516
22461
 
21517
22462
  // src/report/workspace.ts
21518
- import { existsSync as existsSync27, readdirSync as readdirSync12, statSync as statSync5 } from "fs";
21519
- import { join as join52, relative as relative5 } from "path";
22463
+ import { existsSync as existsSync28, readdirSync as readdirSync13, statSync as statSync5 } from "fs";
22464
+ import { join as join54, relative as relative5 } from "path";
21520
22465
 
21521
22466
  // src/workspace/state.ts
21522
- import { readdirSync as readdirSync11 } from "fs";
21523
- import { join as join51 } from "path";
22467
+ import { readdirSync as readdirSync12 } from "fs";
22468
+ import { join as join53 } from "path";
21524
22469
  async function gitChildRead(ctx, repo, args) {
21525
- const res = await ctx.run(["git", "-C", join51(ctx.root, repo.path), ...args]);
22470
+ const res = await ctx.run(["git", "-C", join53(ctx.root, repo.path), ...args]);
21526
22471
  if (res.spawnError || res.code !== 0) return void 0;
21527
22472
  return res.stdout.replace(/\s+$/, "");
21528
22473
  }
@@ -21565,15 +22510,15 @@ async function collectWorkspaceSnapshot(ctx, manifest2, opts = {}) {
21565
22510
  };
21566
22511
  }
21567
22512
  function latestWorkspaceSnapshotPath(root) {
21568
- const dir = join51(root, ".aih", "workspace-snapshots");
22513
+ const dir = join53(root, ".aih", "workspace-snapshots");
21569
22514
  let files;
21570
22515
  try {
21571
- files = readdirSync11(dir).filter((f) => f.endsWith(".json")).sort();
22516
+ files = readdirSync12(dir).filter((f) => f.endsWith(".json")).sort();
21572
22517
  } catch {
21573
22518
  return void 0;
21574
22519
  }
21575
22520
  const latest = files.at(-1);
21576
- return latest ? join51(dir, latest) : void 0;
22521
+ return latest ? join53(dir, latest) : void 0;
21577
22522
  }
21578
22523
 
21579
22524
  // src/report/workspace.ts
@@ -21584,17 +22529,17 @@ function daysSince(iso) {
21584
22529
  return Math.max(0, Math.floor((Date.now() - ms) / 864e5));
21585
22530
  }
21586
22531
  function newestReport(root, repo) {
21587
- const dir = join52(root, repo.path, ".aih", "reports");
22532
+ const dir = join54(root, repo.path, ".aih", "reports");
21588
22533
  let files;
21589
22534
  try {
21590
- files = readdirSync12(dir).filter((name) => /\.(html|md|json)$/i.test(name)).sort();
22535
+ files = readdirSync13(dir).filter((name) => /\.(html|md|json)$/i.test(name)).sort();
21591
22536
  } catch {
21592
22537
  return {};
21593
22538
  }
21594
22539
  let chosen;
21595
22540
  for (const name of files) {
21596
22541
  try {
21597
- const mtimeMs = statSync5(join52(dir, name)).mtimeMs;
22542
+ const mtimeMs = statSync5(join54(dir, name)).mtimeMs;
21598
22543
  if (!chosen || mtimeMs > chosen.mtimeMs) chosen = { name, mtimeMs };
21599
22544
  } catch {
21600
22545
  }
@@ -21606,7 +22551,7 @@ function newestReport(root, repo) {
21606
22551
  };
21607
22552
  }
21608
22553
  function latestHistory(root, repo) {
21609
- const text = readIfExists(join52(root, repo.path, ".aih", "history.jsonl"));
22554
+ const text = readIfExists(join54(root, repo.path, ".aih", "history.jsonl"));
21610
22555
  if (text === void 0) return {};
21611
22556
  let latest;
21612
22557
  for (const line of text.split(/\r?\n/)) {
@@ -21626,7 +22571,7 @@ function lineCount(text) {
21626
22571
  return text.split(/\r?\n/).filter((line) => line.trim().length > 0).length;
21627
22572
  }
21628
22573
  function workspaceMcpStatus(root) {
21629
- const text = readIfExists(join52(root, ".mcp.json"));
22574
+ const text = readIfExists(join54(root, ".mcp.json"));
21630
22575
  if (text === void 0) return { status: "UNKNOWN", detail: "no parent .mcp.json" };
21631
22576
  try {
21632
22577
  const parsed = JSON.parse(text);
@@ -21654,7 +22599,7 @@ function workspaceMcpStatus(root) {
21654
22599
  }
21655
22600
  }
21656
22601
  function readConfigStatus(root, repo, manifest2) {
21657
- const text = readIfExists(join52(root, repo.path, ".aih-config.json"));
22602
+ const text = readIfExists(join54(root, repo.path, ".aih-config.json"));
21658
22603
  if (text === void 0) return { status: "MISSING", detail: "no child .aih-config.json" };
21659
22604
  try {
21660
22605
  const parsed = JSON.parse(text);
@@ -21690,18 +22635,18 @@ function aggregateStatus(row) {
21690
22635
  return "OK";
21691
22636
  }
21692
22637
  async function childRow(ctx, manifest2, repo, missingIgnores) {
21693
- const abs = join52(ctx.root, repo.path);
21694
- const exists = existsSync27(abs);
22638
+ const abs = join54(ctx.root, repo.path);
22639
+ const exists = existsSync28(abs);
21695
22640
  const gitState = exists ? await readWorkspaceRepoState(ctx, repo) : void 0;
21696
22641
  const git = gitState?.git === true ? { status: "OK", ...gitState } : {
21697
22642
  status: exists ? "MISSING" : "MISSING",
21698
22643
  detail: exists ? "not a git repo" : "path missing"
21699
22644
  };
21700
- const routerAbs = join52(ctx.root, repo.path, repo.router);
21701
- const canon = existsSync27(routerAbs) ? { status: "OK" } : { status: "NOT_ONBOARDED", detail: `${repo.path}/${repo.router} missing` };
22645
+ const routerAbs = join54(ctx.root, repo.path, repo.router);
22646
+ const canon = existsSync28(routerAbs) ? { status: "OK" } : { status: "NOT_ONBOARDED", detail: `${repo.path}/${repo.router} missing` };
21702
22647
  const config = exists ? readConfigStatus(ctx.root, repo, manifest2) : { status: "MISSING" };
21703
22648
  const parentIgnored = manifest2.git && missingIgnores.includes(repo.path) ? { status: "WARN", detail: "parent git does not ignore child repo path" } : { status: "OK" };
21704
- const historyPath = join52(ctx.root, repo.path, ".aih", "history.jsonl");
22649
+ const historyPath = join54(ctx.root, repo.path, ".aih", "history.jsonl");
21705
22650
  const historyText = readIfExists(historyPath);
21706
22651
  const latest = historyText === void 0 ? {} : latestHistory(ctx.root, repo);
21707
22652
  const ageDays = latest.ts ? daysSince(latest.ts) : void 0;
@@ -21710,7 +22655,7 @@ async function childRow(ctx, manifest2, repo, missingIgnores) {
21710
22655
  ...latest.ts ? { latestSample: latest.ts } : {},
21711
22656
  ...ageDays !== void 0 ? { ageDays } : {}
21712
22657
  };
21713
- const usageText = readIfExists(join52(ctx.root, repo.path, ".aih", "usage.jsonl"));
22658
+ const usageText = readIfExists(join54(ctx.root, repo.path, ".aih", "usage.jsonl"));
21714
22659
  const usageEvents = lineCount(usageText);
21715
22660
  const usage = usageText === void 0 ? { status: "NOT_COLLECTED", detail: "no .aih/usage.jsonl" } : { status: "OK", events: usageEvents };
21716
22661
  const reportInfo = newestReport(ctx.root, repo);
@@ -21738,8 +22683,8 @@ function contractStatus(root, edge) {
21738
22683
  if (!edge.contractPath) {
21739
22684
  return { ...edge, status: "UNKNOWN", detail: "no contractPath declared" };
21740
22685
  }
21741
- const contractExists = existsSync27(join52(root, edge.contractPath));
21742
- const consumerExists = edge.consumerPath ? existsSync27(join52(root, edge.consumerPath)) : true;
22686
+ const contractExists = existsSync28(join54(root, edge.contractPath));
22687
+ const consumerExists = edge.consumerPath ? existsSync28(join54(root, edge.consumerPath)) : true;
21743
22688
  const status = !contractExists ? edge.consumerPath && consumerExists ? "PARTIAL" : "MISSING" : consumerExists ? "OK" : "PARTIAL";
21744
22689
  const detail = status === "OK" ? "declared contract evidence exists" : [
21745
22690
  contractExists ? void 0 : `missing ${edge.contractPath}`,
@@ -21778,7 +22723,7 @@ function snapshotTime(snapshot) {
21778
22723
  function workspaceSnapshot(root, manifest2, rows) {
21779
22724
  const candidates = [
21780
22725
  latestWorkspaceSnapshotPath(root),
21781
- join52(root, manifest2.contextDir, "workspace-lock.json")
22726
+ join54(root, manifest2.contextDir, "workspace-lock.json")
21782
22727
  ].filter((path) => path !== void 0);
21783
22728
  const loaded = candidates.map((path) => ({ path, snapshot: readSnapshot(path) })).filter(
21784
22729
  (entry2) => entry2.snapshot !== void 0
@@ -21903,7 +22848,7 @@ async function workspaceReportDigest(ctx) {
21903
22848
  if (!manifest2) return void 0;
21904
22849
  const missingIgnores = manifest2.git === true ? workspaceGitignoreMissing(
21905
22850
  manifest2.repos.map((repo) => repo.path),
21906
- readIfExists(join52(ctx.root, ".gitignore"))
22851
+ readIfExists(join54(ctx.root, ".gitignore"))
21907
22852
  ) : [];
21908
22853
  const rows = [];
21909
22854
  for (const repo of manifest2.repos) rows.push(await childRow(ctx, manifest2, repo, missingIgnores));
@@ -21967,7 +22912,7 @@ function loadGroupHeadline(model) {
21967
22912
  }
21968
22913
  function readOrgExport(ctx, file) {
21969
22914
  const path = resolve11(ctx.root, file);
21970
- if (!existsSync28(path)) throw new AihError(`--org export not found: ${file}`, "AIH_REPORT");
22915
+ if (!existsSync29(path)) throw new AihError(`--org export not found: ${file}`, "AIH_REPORT");
21971
22916
  let text;
21972
22917
  try {
21973
22918
  text = readFileSync6(path, "utf8");
@@ -21999,9 +22944,9 @@ function artifactPath(ctx, scope, format) {
21999
22944
  const out = ctx.options.out;
22000
22945
  if (typeof out === "string" && out.length > 0) return out;
22001
22946
  if (scope === "workspace") {
22002
- return join53(".aih", `workspace-report.${format === "html" ? "html" : "md"}`);
22947
+ return join55(".aih", `workspace-report.${format === "html" ? "html" : "md"}`);
22003
22948
  }
22004
- return join53(".aih", "reports", `${scope}-report.${format === "html" ? "html" : "md"}`);
22949
+ return join55(".aih", "reports", `${scope}-report.${format === "html" ? "html" : "md"}`);
22005
22950
  }
22006
22951
  function adoptionFrom(digests) {
22007
22952
  const d = digests.find((x) => x.describe.startsWith("Configuration"));
@@ -22065,7 +23010,7 @@ async function buildReport(ctx) {
22065
23010
  usageEvents: usageEventsFrom(panels),
22066
23011
  // Telemetry is "wired" once `aih usage --apply` wrote its recorder — even before
22067
23012
  // an event lands. Drives dropping the "wire telemetry" step after it's done.
22068
- telemetryWired: existsSync28(join53(ctx.root, ".aih", "usage-record.mjs")),
23013
+ telemetryWired: existsSync29(join55(ctx.root, ".aih", "usage-record.mjs")),
22069
23014
  toolsMissing: toolsMissingFrom(panels),
22070
23015
  scaleGraphMissing: scaleGraphMissingFrom(panels),
22071
23016
  // Runnable AI CLIs on this machine (Machine tooling `present`) that this repo
@@ -22160,7 +23105,7 @@ async function reportPlan(ctx) {
22160
23105
  }
22161
23106
  return plan("report", ...actions);
22162
23107
  }
22163
- var command24 = {
23108
+ var command26 = {
22164
23109
  name: "report",
22165
23110
  // alwaysVerify so the `--gate` probe runs and drives the exit code. A bare
22166
23111
  // `aih report` has no probes, so this is a no-op there (empty report → exit 0).
@@ -22237,154 +23182,7 @@ var command24 = {
22237
23182
  plan: reportPlan
22238
23183
  };
22239
23184
 
22240
- // src/tools/install.ts
22241
- var TOOLS = [
22242
- {
22243
- tool: "ripgrep (rg)",
22244
- bin: "rg",
22245
- tier: "core",
22246
- options: [
22247
- { pm: "winget", argv: ["winget", "install", "-e", "--id", "BurntSushi.ripgrep.MSVC"] },
22248
- { pm: "scoop", argv: ["scoop", "install", "ripgrep"] },
22249
- { pm: "brew", argv: ["brew", "install", "ripgrep"] },
22250
- { pm: "apt", argv: ["sudo", "apt-get", "install", "-y", "ripgrep"] },
22251
- { pm: "cargo", argv: ["cargo", "install", "ripgrep"] }
22252
- ],
22253
- manual: "https://github.com/BurntSushi/ripgrep#installation"
22254
- },
22255
- {
22256
- tool: "fd",
22257
- bin: "fd",
22258
- tier: "core",
22259
- options: [
22260
- { pm: "winget", argv: ["winget", "install", "-e", "--id", "sharkdp.fd"] },
22261
- { pm: "scoop", argv: ["scoop", "install", "fd"] },
22262
- { pm: "brew", argv: ["brew", "install", "fd"] },
22263
- { pm: "apt", argv: ["sudo", "apt-get", "install", "-y", "fd-find"] },
22264
- { pm: "cargo", argv: ["cargo", "install", "fd-find"] }
22265
- ],
22266
- manual: "https://github.com/sharkdp/fd#installation"
22267
- },
22268
- {
22269
- tool: "jq",
22270
- bin: "jq",
22271
- tier: "core",
22272
- options: [
22273
- { pm: "winget", argv: ["winget", "install", "-e", "--id", "jqlang.jq"] },
22274
- { pm: "scoop", argv: ["scoop", "install", "jq"] },
22275
- { pm: "brew", argv: ["brew", "install", "jq"] },
22276
- { pm: "apt", argv: ["sudo", "apt-get", "install", "-y", "jq"] }
22277
- ],
22278
- manual: "https://jqlang.github.io/jq/download/"
22279
- },
22280
- {
22281
- tool: "ast-grep (sg)",
22282
- bin: "sg",
22283
- tier: "optional",
22284
- options: [
22285
- { pm: "brew", argv: ["brew", "install", "ast-grep"] },
22286
- { pm: "cargo", argv: ["cargo", "install", "ast-grep", "--locked"] },
22287
- { pm: "npm", argv: ["npm", "install", "-g", "@ast-grep/cli"] }
22288
- ],
22289
- manual: "https://ast-grep.github.io/guide/quick-start.html"
22290
- },
22291
- {
22292
- tool: "comby",
22293
- bin: "comby",
22294
- tier: "optional",
22295
- options: [{ pm: "brew", argv: ["brew", "install", "comby"] }],
22296
- manual: "https://comby.dev/docs/get-started"
22297
- },
22298
- {
22299
- tool: "tree",
22300
- bin: "tree",
22301
- tier: "optional",
22302
- options: [
22303
- { pm: "scoop", argv: ["scoop", "install", "tree"] },
22304
- { pm: "brew", argv: ["brew", "install", "tree"] },
22305
- { pm: "apt", argv: ["sudo", "apt-get", "install", "-y", "tree"] }
22306
- ],
22307
- manual: "install `tree` from your OS package manager"
22308
- },
22309
- {
22310
- tool: "GitHub CLI (gh)",
22311
- bin: "gh",
22312
- tier: "optional",
22313
- options: [
22314
- { pm: "winget", argv: ["winget", "install", "-e", "--id", "GitHub.cli"] },
22315
- { pm: "scoop", argv: ["scoop", "install", "gh"] },
22316
- { pm: "brew", argv: ["brew", "install", "gh"] },
22317
- { pm: "apt", argv: ["sudo", "apt-get", "install", "-y", "gh"] }
22318
- ],
22319
- manual: "https://cli.github.com"
22320
- },
22321
- {
22322
- tool: "code-review-graph",
22323
- bin: "code-review-graph",
22324
- tier: "optional",
22325
- // Pinned to match the uvx MCP runners (src/mcp/servers.ts + src/workspace/templates.ts);
22326
- // bump in lockstep. PEP 508 `==` form — pip/uv reject the uvx `@2.3.6` shorthand.
22327
- options: [
22328
- { pm: "uv", argv: ["uv", "tool", "install", "code-review-graph==2.3.6"] },
22329
- { pm: "pip", argv: ["pip", "install", "code-review-graph==2.3.6"] }
22330
- ],
22331
- manual: "pip install code-review-graph==2.3.6"
22332
- }
22333
- ];
22334
- var PM_BINARIES = [
22335
- ["winget", "winget"],
22336
- ["scoop", "scoop"],
22337
- ["brew", "brew"],
22338
- ["apt-get", "apt"],
22339
- ["cargo", "cargo"],
22340
- ["npm", "npm"],
22341
- ["uv", "uv"],
22342
- ["pip", "pip"]
22343
- ];
22344
- async function onPath4(ctx, bin) {
22345
- const argv = ctx.host.platform === "windows" ? ["where", bin] : ["which", bin];
22346
- const res = await ctx.run(argv);
22347
- return !res.spawnError && res.code === 0 && res.stdout.trim().length > 0;
22348
- }
22349
- async function detectPms(ctx) {
22350
- const found = /* @__PURE__ */ new Set();
22351
- await Promise.all(
22352
- PM_BINARIES.map(async ([bin, key]) => {
22353
- if (await onPath4(ctx, bin)) found.add(key);
22354
- })
22355
- );
22356
- return found;
22357
- }
22358
- async function missingTools(ctx) {
22359
- const checked = await Promise.all(
22360
- TOOLS.map(async (t) => ({ t, present: await onPath4(ctx, t.bin) }))
22361
- );
22362
- return checked.filter((c) => !c.present).map((c) => c.t);
22363
- }
22364
- function chooseOption(t, pms) {
22365
- return t.options.find((o) => pms.has(o.pm));
22366
- }
22367
- var WIN_CMD_SHIMS = /* @__PURE__ */ new Set(["npm", "npx", "pnpm", "scoop", "yarn"]);
22368
- function execArgv(platform, argv) {
22369
- return platform === "windows" && argv[0] !== void 0 && WIN_CMD_SHIMS.has(argv[0]) ? ["cmd", "/c", ...argv] : argv;
22370
- }
22371
-
22372
23185
  // src/tools/index.ts
22373
- function howToInstall(t, pms) {
22374
- const opt = chooseOption(t, pms);
22375
- return opt ? `\`${opt.argv.join(" ")}\`` : `manually \u2014 ${t.manual}`;
22376
- }
22377
- async function verifyTool(ctx, t, pms) {
22378
- if (await onPath4(ctx, t.bin)) {
22379
- return { name: t.tool, verdict: "pass", detail: `${t.bin} on PATH` };
22380
- }
22381
- return {
22382
- name: t.tool,
22383
- verdict: t.tier === "core" ? "fail" : "skip",
22384
- detail: `${t.bin} not on PATH \u2014 install: ${howToInstall(t, pms)}`,
22385
- code: "env.tool-install-blocked"
22386
- };
22387
- }
22388
23186
  function summaryText2(missing, pms) {
22389
23187
  const pmList = pms.size > 0 ? [...pms].sort().join(", ") : "none detected";
22390
23188
  return lines(
@@ -22405,31 +23203,14 @@ async function toolsPlan(ctx) {
22405
23203
  doc("tools \u2014 all present", "All agent shell tools are on PATH. Nothing to install.")
22406
23204
  );
22407
23205
  }
22408
- const actions = [];
22409
- for (const t of missing) {
22410
- const opt = chooseOption(t, pms);
22411
- if (opt) {
22412
- actions.push(
22413
- exec(`install ${t.tool} (${opt.pm})`, execArgv(ctx.host.platform, opt.argv), {
22414
- allowFailure: true
22415
- })
22416
- );
22417
- } else {
22418
- actions.push(
22419
- doc(
22420
- `install ${t.tool} manually (no supported package manager found)`,
22421
- `No detected package manager can install ${t.tool}. Install it manually: ${t.manual}`
22422
- )
22423
- );
22424
- }
22425
- }
23206
+ const actions = installActionsFor(ctx, missing, pms);
22426
23207
  for (const t of missing) {
22427
23208
  actions.push(probe(`${t.tool} installed`, (c) => verifyTool(c, t, pms)));
22428
23209
  }
22429
23210
  actions.push(doc("tools summary", summaryText2(missing, pms)));
22430
23211
  return plan("tools", ...actions);
22431
23212
  }
22432
- var command25 = {
23213
+ var command27 = {
22433
23214
  name: "tools",
22434
23215
  summary: "Install the agent shell tools the harness leans on (rg/fd/jq + ast-grep/comby/tree/gh/code-review-graph); escalates a blocked install as a ticket",
22435
23216
  // Diagnose by default so a bare `aih tools` surfaces what's missing + how to get it,
@@ -22468,7 +23249,7 @@ async function trackPlan(ctx) {
22468
23249
  aihIgnoreWrite(ctx.root)
22469
23250
  );
22470
23251
  }
22471
- var command26 = {
23252
+ var command28 = {
22472
23253
  name: "track",
22473
23254
  summary: "Record a metrics sample (git + repo state) to .aih/history.jsonl \u2014 powers `aih report` trends",
22474
23255
  plan: trackPlan
@@ -22476,8 +23257,8 @@ var command26 = {
22476
23257
 
22477
23258
  // src/trust/scan.ts
22478
23259
  import { createHash as createHash7 } from "crypto";
22479
- import { lstatSync as lstatSync6, readdirSync as readdirSync14, readFileSync as readFileSync10 } from "fs";
22480
- import { basename as basename11, extname as extname3, isAbsolute as isAbsolute8, join as join56, relative as relative9, resolve as resolve12 } from "path";
23260
+ import { lstatSync as lstatSync7, readdirSync as readdirSync15, readFileSync as readFileSync10 } from "fs";
23261
+ import { basename as basename11, extname as extname3, isAbsolute as isAbsolute8, join as join58, relative as relative9, resolve as resolve12 } from "path";
22481
23262
 
22482
23263
  // src/trust/acknowledge.ts
22483
23264
  var TRUST_DANGER_CODES = /* @__PURE__ */ new Set([
@@ -22566,10 +23347,10 @@ function applyTrustAcknowledgements(checks, ctx) {
22566
23347
  function quoteArg(value) {
22567
23348
  return /^[A-Za-z0-9_./:@-]+$/.test(value) ? value : `"${value.replace(/(["\\])/g, "\\$1")}"`;
22568
23349
  }
22569
- function acknowledgeCommandHint(command28, source, checks) {
23350
+ function acknowledgeCommandHint(command30, source, checks) {
22570
23351
  const fingerprints2 = checks.filter((check2) => check2.verdict === "fail" && check2.fingerprint !== void 0).filter((check2) => !isDanger(check2) && isAcknowledgeableOrigin(check2)).map((check2) => check2.fingerprint);
22571
23352
  if (fingerprints2.length === 0) return void 0;
22572
- return `To acknowledge the current trust-origin finding(s), rerun: aih ${command28} ${quoteArg(
23353
+ return `To acknowledge the current trust-origin finding(s), rerun: aih ${command30} ${quoteArg(
22573
23354
  source
22574
23355
  )} --acknowledge ${quoteArg(fingerprints2.join(","))} --reason ${quoteArg("<reason>")}`;
22575
23356
  }
@@ -22867,8 +23648,8 @@ function scanTrustDependencyNames(root, internalScopes, posture = "vibe") {
22867
23648
 
22868
23649
  // src/trust/detectors.ts
22869
23650
  import { createHash as createHash4 } from "crypto";
22870
- import { lstatSync as lstatSync4, readFileSync as readFileSync8 } from "fs";
22871
- import { basename as basename9, extname, isAbsolute as isAbsolute7, join as join54, relative as relative7 } from "path";
23651
+ import { lstatSync as lstatSync5, readFileSync as readFileSync8 } from "fs";
23652
+ import { basename as basename9, extname, isAbsolute as isAbsolute7, join as join56, relative as relative7 } from "path";
22872
23653
  var SKILLSPECTOR_ANALYZER = "skillspector@docker";
22873
23654
  var DETECTOR_UNAVAILABLE = "trust.detector-unavailable";
22874
23655
  var MAX_SCRIPT_SCAN_BYTES = 512 * 1024;
@@ -23001,7 +23782,7 @@ function scanNativeMaliciousCode(root) {
23001
23782
  root,
23002
23783
  (abs) => {
23003
23784
  const rel = toPosix2(relative7(root, abs));
23004
- return isScriptLike(rel) && lstatSync4(abs).size <= MAX_SCRIPT_SCAN_BYTES;
23785
+ return isScriptLike(rel) && lstatSync5(abs).size <= MAX_SCRIPT_SCAN_BYTES;
23005
23786
  },
23006
23787
  TRUST_SKIP_DIRS
23007
23788
  );
@@ -23090,7 +23871,7 @@ function sarifLocation(result) {
23090
23871
  }
23091
23872
  function sarifFingerprint(code, root, location, detail) {
23092
23873
  const line = location.startLine ?? 1;
23093
- const sourceLine = fileLine(join54(root, location.uri), line) ?? detail;
23874
+ const sourceLine = fileLine(join56(root, location.uri), line) ?? detail;
23094
23875
  return `${code.replace(/\./g, "-")}:skillspector:${location.uri}:${line}:${sha8(sourceLine)}`;
23095
23876
  }
23096
23877
  function sarifChecks(stdout, root, posture) {
@@ -23381,8 +24162,8 @@ function scanTrustDocument(path, source) {
23381
24162
 
23382
24163
  // src/trust/manifest.ts
23383
24164
  import { createHash as createHash6 } from "crypto";
23384
- import { lstatSync as lstatSync5, readdirSync as readdirSync13, readFileSync as readFileSync9 } from "fs";
23385
- import { basename as basename10, extname as extname2, join as join55, relative as relative8 } from "path";
24165
+ import { lstatSync as lstatSync6, readdirSync as readdirSync14, readFileSync as readFileSync9 } from "fs";
24166
+ import { basename as basename10, extname as extname2, join as join57, relative as relative8 } from "path";
23386
24167
  import { parseDocument } from "yaml";
23387
24168
  var AUTO_EXEC_CODE = "trust.auto-exec-hook";
23388
24169
  var LIFECYCLE_SCRIPTS = /* @__PURE__ */ new Set([
@@ -23596,7 +24377,7 @@ function isClaudeHooksDir(rel) {
23596
24377
  function scanHookDirs(root) {
23597
24378
  const checks = [];
23598
24379
  const visit = (abs) => {
23599
- const st = lstatSync5(abs);
24380
+ const st = lstatSync6(abs);
23600
24381
  if (!st.isDirectory()) return;
23601
24382
  const rel = toPosix3(relative8(root, abs));
23602
24383
  if (abs !== root && TRUST_SKIP_DIRS.has(basename10(abs)) && !isClaudeHooksDir(rel)) return;
@@ -23606,7 +24387,7 @@ function scanHookDirs(root) {
23606
24387
  );
23607
24388
  return;
23608
24389
  }
23609
- for (const entry2 of readdirSync13(abs)) visit(join55(abs, entry2));
24390
+ for (const entry2 of readdirSync14(abs)) visit(join57(abs, entry2));
23610
24391
  };
23611
24392
  visit(root);
23612
24393
  return checks;
@@ -23654,8 +24435,8 @@ function stringValue(value) {
23654
24435
  function stringArray(value) {
23655
24436
  return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
23656
24437
  }
23657
- function commandName(command28) {
23658
- const last = command28.split(/[\\/]/).at(-1) ?? command28;
24438
+ function commandName(command30) {
24439
+ const last = command30.split(/[\\/]/).at(-1) ?? command30;
23659
24440
  return last.replace(/\.(?:cmd|exe)$/i, "").toLowerCase();
23660
24441
  }
23661
24442
  function isPlaceholderOrEmpty2(value) {
@@ -23691,8 +24472,8 @@ function hasLiteralCredential(raw, args) {
23691
24472
  function hasExactPackage(args) {
23692
24473
  return args.some((arg) => EXACT_PACKAGE_RE.test(arg.trim()));
23693
24474
  }
23694
- function hasFloatingLaunch(command28, args) {
23695
- const cmd = command28 === void 0 ? "" : commandName(command28);
24475
+ function hasFloatingLaunch(command30, args) {
24476
+ const cmd = command30 === void 0 ? "" : commandName(command30);
23696
24477
  return cmd === "npx" || cmd === "uvx" || args.some((arg) => arg === "-y" || arg === "--yes" || /@latest(?:$|\b)/i.test(arg));
23697
24478
  }
23698
24479
  function hosted(url, description, credentials) {
@@ -23720,7 +24501,7 @@ function flagged(description, credentials) {
23720
24501
  }
23721
24502
  function classifyIncomingMcp(rawServer) {
23722
24503
  const raw = isRecord6(rawServer) ? rawServer : void 0;
23723
- const command28 = raw ? stringValue(raw.command) : void 0;
24504
+ const command30 = raw ? stringValue(raw.command) : void 0;
23724
24505
  const args = raw ? stringArray(raw.args) : [];
23725
24506
  const url = raw ? stringValue(raw.url) : void 0;
23726
24507
  const description = raw ? stringValue(raw.description) ?? "" : "";
@@ -23729,19 +24510,19 @@ function classifyIncomingMcp(rawServer) {
23729
24510
  if (url !== void 0 && /^https?:\/\//i.test(url.trim())) {
23730
24511
  return hosted(url.trim(), description, credentials);
23731
24512
  }
23732
- if (command28 === void 0 || command28.trim().length === 0) {
24513
+ if (command30 === void 0 || command30.trim().length === 0) {
23733
24514
  return flagged(description, credentials);
23734
24515
  }
23735
24516
  return {
23736
24517
  type: "stdio",
23737
- command: command28,
24518
+ command: command30,
23738
24519
  args,
23739
24520
  description,
23740
24521
  env,
23741
24522
  classification: "local",
23742
24523
  egress: "local-only",
23743
24524
  credentials,
23744
- supplyChain: hasFloatingLaunch(command28, args) ? "unpinned" : hasExactPackage(args) ? "pinned" : "unpinned"
24525
+ supplyChain: hasFloatingLaunch(command30, args) ? "unpinned" : hasExactPackage(args) ? "pinned" : "unpinned"
23745
24526
  };
23746
24527
  }
23747
24528
 
@@ -23767,10 +24548,10 @@ function toPosix4(path) {
23767
24548
  function collectFilesUnder(root, accept, skipDirs = TRUST_SKIP_DIRS) {
23768
24549
  const out = [];
23769
24550
  const visit = (abs) => {
23770
- const st = lstatSync6(abs);
24551
+ const st = lstatSync7(abs);
23771
24552
  if (st.isDirectory()) {
23772
24553
  if (abs !== root && skipDirs.has(basename11(abs))) return;
23773
- for (const entry2 of readdirSync14(abs)) visit(join56(abs, entry2));
24554
+ for (const entry2 of readdirSync15(abs)) visit(join58(abs, entry2));
23774
24555
  return;
23775
24556
  }
23776
24557
  if (st.isFile() && accept(abs)) out.push(abs);
@@ -23861,12 +24642,12 @@ function openCodeServer(server) {
23861
24642
  if (server.type === "remote") {
23862
24643
  return { ...server, url: stringValue2(server.url) };
23863
24644
  }
23864
- const command28 = Array.isArray(server.command) ? server.command : [];
23865
- const executable = command28[0];
24645
+ const command30 = Array.isArray(server.command) ? server.command : [];
24646
+ const executable = command30[0];
23866
24647
  return {
23867
24648
  ...server,
23868
24649
  command: typeof executable === "string" ? executable : void 0,
23869
- args: command28.slice(1).filter((item) => typeof item === "string"),
24650
+ args: command30.slice(1).filter((item) => typeof item === "string"),
23870
24651
  env: server.environment ?? server.env
23871
24652
  };
23872
24653
  }
@@ -23918,7 +24699,7 @@ function incomingMcpChecks(root, mcpConfigFiles, posture) {
23918
24699
  for (const rel of mcpConfigFiles) {
23919
24700
  let parsed;
23920
24701
  try {
23921
- parsed = JSON.parse(readFileSync10(join56(root, rel), "utf8"));
24702
+ parsed = JSON.parse(readFileSync10(join58(root, rel), "utf8"));
23922
24703
  } catch {
23923
24704
  checks.push(malformedMcpConfigCheck(rel));
23924
24705
  continue;
@@ -24223,12 +25004,12 @@ var trustScanCommand = {
24223
25004
 
24224
25005
  // src/workspace/acquire.ts
24225
25006
  import { randomUUID } from "crypto";
24226
- import { existsSync as existsSync29, lstatSync as lstatSync7, readdirSync as readdirSync15, readFileSync as readFileSync11 } from "fs";
24227
- import { basename as basename12, extname as extname4, join as join57, posix as posix9 } from "path";
25007
+ import { existsSync as existsSync30, lstatSync as lstatSync8, readdirSync as readdirSync16, readFileSync as readFileSync11 } from "fs";
25008
+ import { basename as basename12, extname as extname4, join as join59, posix as posix9 } from "path";
24228
25009
 
24229
25010
  // src/internals/commander-options.ts
24230
- function optionSource(command28, key) {
24231
- return command28.getOptionValueSourceWithGlobals?.(key) ?? command28.getOptionValueSource?.(key);
25011
+ function optionSource(command30, key) {
25012
+ return command30.getOptionValueSourceWithGlobals?.(key) ?? command30.getOptionValueSource?.(key);
24232
25013
  }
24233
25014
 
24234
25015
  // src/support/setup.ts
@@ -24361,11 +25142,11 @@ async function workspaceAddPhase1Plan(ctx, resolvedSource) {
24361
25142
  function collectSkillDirs(root) {
24362
25143
  const out = [];
24363
25144
  const visit = (abs) => {
24364
- const st = lstatSync7(abs);
25145
+ const st = lstatSync8(abs);
24365
25146
  if (!st.isDirectory()) return;
24366
25147
  if (abs !== root && SKIP_DIRS.has(basename12(abs))) return;
24367
- if (existsSync29(join57(abs, "SKILL.md"))) out.push(abs);
24368
- for (const entry2 of readdirSync15(abs)) visit(join57(abs, entry2));
25148
+ if (existsSync30(join59(abs, "SKILL.md"))) out.push(abs);
25149
+ for (const entry2 of readdirSync16(abs)) visit(join59(abs, entry2));
24369
25150
  };
24370
25151
  visit(root);
24371
25152
  return out.sort((a, b) => sourceDirSortKey(root, a).localeCompare(sourceDirSortKey(root, b)));
@@ -24373,10 +25154,10 @@ function collectSkillDirs(root) {
24373
25154
  function collectFiles(root) {
24374
25155
  const out = [];
24375
25156
  const visit = (abs) => {
24376
- const st = lstatSync7(abs);
25157
+ const st = lstatSync8(abs);
24377
25158
  if (st.isDirectory()) {
24378
25159
  if (abs !== root && SKIP_DIRS.has(basename12(abs))) return;
24379
- for (const entry2 of readdirSync15(abs)) visit(join57(abs, entry2));
25160
+ for (const entry2 of readdirSync16(abs)) visit(join59(abs, entry2));
24380
25161
  return;
24381
25162
  }
24382
25163
  if (st.isFile()) out.push(abs);
@@ -24599,7 +25380,7 @@ function hasFailedExec(result) {
24599
25380
  }
24600
25381
  function readSetupText(root) {
24601
25382
  for (const rel of ["SETUP.md", "docs/SETUP.md", ".aih/SETUP.md"]) {
24602
- const text = readIfExists(join57(root, rel));
25383
+ const text = readIfExists(join59(root, rel));
24603
25384
  if (text !== void 0) return text;
24604
25385
  }
24605
25386
  return void 0;
@@ -24634,16 +25415,16 @@ ${template.body}`);
24634
25415
  }
24635
25416
  write(supportSummary(support, saved));
24636
25417
  }
24637
- function contextFromCommand(command28, deps) {
25418
+ function contextFromCommand(command30, deps) {
24638
25419
  const env = deps.env ?? process.env;
24639
25420
  const run = deps.run ?? defaultRunner;
24640
- const opts = command28.optsWithGlobals();
25421
+ const opts = command30.optsWithGlobals();
24641
25422
  const resolvedRoot = opts.root ?? env.AIH_ROOT ?? process.cwd();
24642
25423
  const marker = readAihConfig(resolvedRoot);
24643
- const contextDirSource = optionSource(command28, "contextDir");
25424
+ const contextDirSource = optionSource(command30, "contextDir");
24644
25425
  const contextDirFromFlag = contextDirSource === "cli" ? opts.contextDir : void 0;
24645
25426
  const contextDirFromMarker = contextDirFromFlag === void 0 ? marker?.contextDir : void 0;
24646
- const postureFlagSource = optionSource(command28, "posture") === "cli" ? "cli" : void 0;
25427
+ const postureFlagSource = optionSource(command30, "posture") === "cli" ? "cli" : void 0;
24647
25428
  const resolvedPosture = resolvePosture({
24648
25429
  root: resolvedRoot,
24649
25430
  env,
@@ -24671,7 +25452,7 @@ function contextFromCommand(command28, deps) {
24671
25452
  host,
24672
25453
  env,
24673
25454
  options: {
24674
- source: command28.processedArgs[0],
25455
+ source: command30.processedArgs[0],
24675
25456
  pin: opts.pin,
24676
25457
  ref: opts.ref,
24677
25458
  force: opts.force,
@@ -24681,16 +25462,16 @@ function contextFromCommand(command28, deps) {
24681
25462
  }
24682
25463
  };
24683
25464
  }
24684
- async function runWorkspaceAdd(command28, deps = {}) {
25465
+ async function runWorkspaceAdd(command30, deps = {}) {
24685
25466
  const write = deps.write ?? ((text) => process.stdout.write(text));
24686
25467
  const env = deps.env ?? process.env;
24687
- const opts = command28.optsWithGlobals();
25468
+ const opts = command30.optsWithGlobals();
24688
25469
  const runId = (deps.newRunId ?? (() => `run_${randomUUID().slice(0, 8)}`))();
24689
25470
  const startedAt = (deps.now ?? (() => /* @__PURE__ */ new Date()))();
24690
25471
  let json = false;
24691
25472
  let source;
24692
25473
  try {
24693
- const ctx = contextFromCommand(command28, deps);
25474
+ const ctx = contextFromCommand(command30, deps);
24694
25475
  json = ctx.json;
24695
25476
  source = sourceFromContext(ctx);
24696
25477
  const phase1 = await workspaceAddPhase1Plan(ctx, source);
@@ -24752,12 +25533,12 @@ ${summarizeResult(phase2Result)}
24752
25533
  }
24753
25534
 
24754
25535
  // src/workspace/index.ts
24755
- import { existsSync as existsSync31 } from "fs";
24756
- import { basename as basename13, join as join59, posix as posix12 } from "path";
25536
+ import { existsSync as existsSync32 } from "fs";
25537
+ import { basename as basename13, join as join61, posix as posix12 } from "path";
24757
25538
 
24758
25539
  // src/workspace/detect.ts
24759
- import { existsSync as existsSync30, readdirSync as readdirSync16, statSync as statSync6 } from "fs";
24760
- import { join as join58 } from "path";
25540
+ import { existsSync as existsSync31, readdirSync as readdirSync17, statSync as statSync6 } from "fs";
25541
+ import { join as join60 } from "path";
24761
25542
  function normalizeRepoPath(raw) {
24762
25543
  const value = raw.trim().replace(/\\/g, "/");
24763
25544
  if (value.length === 0) return "";
@@ -24774,9 +25555,9 @@ function normalizeRepoPath(raw) {
24774
25555
  return parts.join("/");
24775
25556
  }
24776
25557
  function isGitRepo(parent, repo) {
24777
- const dir = join58(parent, repo);
25558
+ const dir = join60(parent, repo);
24778
25559
  try {
24779
- return statSync6(dir).isDirectory() && existsSync30(join58(dir, ".git"));
25560
+ return statSync6(dir).isDirectory() && existsSync31(join60(dir, ".git"));
24780
25561
  } catch {
24781
25562
  return false;
24782
25563
  }
@@ -24784,7 +25565,7 @@ function isGitRepo(parent, repo) {
24784
25565
  function detectChildRepos(parent, explicit = []) {
24785
25566
  if (explicit.length > 0) {
24786
25567
  const repos = [...new Set(explicit.map(normalizeRepoPath).filter((r) => r.length > 0))];
24787
- const missing = repos.filter((r) => !existsSync30(join58(parent, r)));
25568
+ const missing = repos.filter((r) => !existsSync31(join60(parent, r)));
24788
25569
  if (missing.length > 0) {
24789
25570
  throw new AihError(
24790
25571
  `workspace --repos entries do not exist under the parent: ${missing.join(", ")}`,
@@ -24802,14 +25583,14 @@ function detectChildRepos(parent, explicit = []) {
24802
25583
  }
24803
25584
  let entries;
24804
25585
  try {
24805
- entries = readdirSync16(parent);
25586
+ entries = readdirSync17(parent);
24806
25587
  } catch {
24807
25588
  return [];
24808
25589
  }
24809
25590
  return entries.filter((name) => !name.startsWith(".")).filter((name) => {
24810
- const dir = join58(parent, name);
25591
+ const dir = join60(parent, name);
24811
25592
  try {
24812
- return statSync6(dir).isDirectory() && existsSync30(join58(dir, ".git"));
25593
+ return statSync6(dir).isDirectory() && existsSync31(join60(dir, ".git"));
24813
25594
  } catch {
24814
25595
  return false;
24815
25596
  }
@@ -25176,7 +25957,7 @@ function hasObjectRepos(raw) {
25176
25957
  function childScaffoldedProbe(repo, dir) {
25177
25958
  return probe(`child ${repo} scaffolded`, (ctx) => {
25178
25959
  const name = `child ${repo} scaffolded`;
25179
- const present = existsSync31(join59(ctx.root, repo, dir, "RULE_ROUTER.md"));
25960
+ const present = existsSync32(join61(ctx.root, repo, dir, "RULE_ROUTER.md"));
25180
25961
  return present ? { name, verdict: "pass", detail: `${repo}/${dir}/ canon present` } : { name, verdict: "skip", detail: `not scaffolded \u2014 run \`aih init ./${repo} --apply\`` };
25181
25962
  });
25182
25963
  }
@@ -25248,7 +26029,7 @@ async function workspacePlan(ctx) {
25248
26029
  for (const repo of repos) actions.push(childScaffoldedProbe(repo, dir));
25249
26030
  return plan("workspace", ...actions);
25250
26031
  }
25251
- var command27 = {
26032
+ var command29 = {
25252
26033
  name: "workspace",
25253
26034
  summary: "Scaffold a multi-repo workspace: cross-repo map, combined graph/filesystem MCP, .code-workspace (parent-only)",
25254
26035
  options: [
@@ -25266,7 +26047,7 @@ var command27 = {
25266
26047
 
25267
26048
  // src/commands/run.ts
25268
26049
  import { randomUUID as randomUUID2 } from "crypto";
25269
- import { basename as basename14, join as join61 } from "path";
26050
+ import { basename as basename14, join as join63 } from "path";
25270
26051
 
25271
26052
  // src/internals/prompt.ts
25272
26053
  import { createInterface } from "readline";
@@ -25293,7 +26074,7 @@ import { isAbsolute as isAbsolute9 } from "path";
25293
26074
 
25294
26075
  // src/program.ts
25295
26076
  import { Command } from "commander";
25296
- var VERSION = "0.2.0";
26077
+ var VERSION = "0.3.0";
25297
26078
  function buildProgram() {
25298
26079
  const program = new Command();
25299
26080
  program.name("aih").description("Enterprise AI Bootstrapping Harness \u2014 governed, proxy-safe AI coding setup").version(VERSION).showHelpAfterError("(add --help for usage)");
@@ -25365,8 +26146,8 @@ function reportToSarif(report, toolName = "aih") {
25365
26146
 
25366
26147
  // src/logging/run-log.ts
25367
26148
  import { appendFileSync, mkdirSync as mkdirSync2 } from "fs";
25368
- import { join as join60 } from "path";
25369
- var RUNS_DIR = join60(".aih", "runs");
26149
+ import { join as join62 } from "path";
26150
+ var RUNS_DIR = join62(".aih", "runs");
25370
26151
  function statusFor(verifyFailed, execFailed) {
25371
26152
  if (verifyFailed) return "failed";
25372
26153
  if (execFailed) return "partial";
@@ -25415,9 +26196,9 @@ function monthFile(date) {
25415
26196
  }
25416
26197
  function appendRunLog(root, entry2, date) {
25417
26198
  try {
25418
- const dir = join60(root, RUNS_DIR);
26199
+ const dir = join62(root, RUNS_DIR);
25419
26200
  mkdirSync2(dir, { recursive: true });
25420
- appendFileSync(join60(dir, monthFile(date)), `${JSON.stringify(entry2)}
26201
+ appendFileSync(join62(dir, monthFile(date)), `${JSON.stringify(entry2)}
25421
26202
  `);
25422
26203
  } catch {
25423
26204
  }
@@ -25428,7 +26209,7 @@ var delay = (ms) => new Promise((r) => setTimeout(r, ms));
25428
26209
  var SETUP_FILES = ["SETUP.md", "docs/SETUP.md", ".aih/SETUP.md"];
25429
26210
  function readSetupText2(root) {
25430
26211
  for (const rel of SETUP_FILES) {
25431
- const text = readIfExists(join61(root, rel));
26212
+ const text = readIfExists(join63(root, rel));
25432
26213
  if (text !== void 0) return text;
25433
26214
  }
25434
26215
  return void 0;
@@ -25450,12 +26231,12 @@ function extractOptions(spec, opts) {
25450
26231
  }
25451
26232
  return picked;
25452
26233
  }
25453
- async function runCapability(spec, command28, deps = {}) {
26234
+ async function runCapability(spec, command30, deps = {}) {
25454
26235
  const env = deps.env ?? process.env;
25455
26236
  const write = deps.write ?? ((t) => process.stdout.write(t));
25456
26237
  const run = deps.run ?? defaultRunner;
25457
- const opts = command28.optsWithGlobals();
25458
- const defaultPositionalRoot = Array.isArray(command28.processedArgs) ? command28.processedArgs[0] : void 0;
26238
+ const opts = command30.optsWithGlobals();
26239
+ const defaultPositionalRoot = Array.isArray(command30.processedArgs) ? command30.processedArgs[0] : void 0;
25459
26240
  const positionalRoot = deps.positionalRoot === false ? void 0 : deps.positionalRoot ?? defaultPositionalRoot;
25460
26241
  const now = deps.now ?? (() => /* @__PURE__ */ new Date());
25461
26242
  const startedAt = now();
@@ -25471,10 +26252,10 @@ async function runCapability(spec, command28, deps = {}) {
25471
26252
  try {
25472
26253
  const resolvedRoot = positionalRoot ?? opts.root ?? env.AIH_ROOT ?? process.cwd();
25473
26254
  const marker = readAihConfig(resolvedRoot);
25474
- const contextDirSource = optionSource(command28, "contextDir");
26255
+ const contextDirSource = optionSource(command30, "contextDir");
25475
26256
  const contextDirFromFlag = contextDirSource === "cli" ? opts.contextDir : void 0;
25476
26257
  const contextDirFromMarker = contextDirFromFlag === void 0 ? marker?.contextDir : void 0;
25477
- const postureFlagSource = optionSource(command28, "posture") === "cli" ? "cli" : void 0;
26258
+ const postureFlagSource = optionSource(command30, "posture") === "cli" ? "cli" : void 0;
25478
26259
  const resolvedPosture = resolvePosture({
25479
26260
  root: resolvedRoot,
25480
26261
  env,
@@ -25492,7 +26273,7 @@ async function runCapability(spec, command28, deps = {}) {
25492
26273
  });
25493
26274
  json = settings.json;
25494
26275
  const host = makeHostAdapter({ run, env });
25495
- const wantConfirm = opts.detect === true && opts.yes !== true && !settings.json;
26276
+ const wantConfirm = (opts.detect === true || spec.wantsInstallPrompt === true) && opts.yes !== true && !settings.json;
25496
26277
  const prompter = deps.prompter ?? (wantConfirm && isInteractive(env) ? makeReadlinePrompter() : void 0);
25497
26278
  const watchSec = positiveInt(opts.refresh);
25498
26279
  const liveOpen = opts.open === true || opts.demo === true || watchSec !== void 0;
@@ -25672,18 +26453,20 @@ var CAPABILITIES = [
25672
26453
  command17,
25673
26454
  command5,
25674
26455
  command8,
25675
- command24,
25676
26456
  command26,
25677
- command21,
25678
26457
  command25,
26458
+ command28,
26459
+ command21,
26460
+ command27,
25679
26461
  command10,
25680
26462
  command7,
25681
26463
  command,
25682
- command27,
26464
+ command29,
25683
26465
  command2,
26466
+ command23,
25684
26467
  command22
25685
26468
  ];
25686
- var READONLY = [command11, command23, verifyCommand];
26469
+ var READONLY = [command11, command24, verifyCommand];
25687
26470
  var ALL_COMMANDS = [...CAPABILITIES, ...READONLY];
25688
26471
  function addSharedFlags(cmd) {
25689
26472
  return cmd.option("--apply", "execute the plan (default: dry-run; nothing is written)").option(
@@ -25709,8 +26492,8 @@ function registerCommands(program) {
25709
26492
  else cmd.option(o.flags, o.description);
25710
26493
  }
25711
26494
  cmd.action(
25712
- async (_rootArg, _options, command28) => {
25713
- process.exitCode = await runCapability(spec, command28);
26495
+ async (_rootArg, _options, command30) => {
26496
+ process.exitCode = await runCapability(spec, command30);
25714
26497
  }
25715
26498
  );
25716
26499
  if (spec.name === "workspace") {
@@ -25720,8 +26503,8 @@ function registerCommands(program) {
25720
26503
  if (o.default !== void 0) add.option(o.flags, o.description, o.default);
25721
26504
  else add.option(o.flags, o.description);
25722
26505
  }
25723
- add.action(async (_source, _options, command28) => {
25724
- process.exitCode = await runWorkspaceAdd(command28);
26506
+ add.action(async (_source, _options, command30) => {
26507
+ process.exitCode = await runWorkspaceAdd(command30);
25725
26508
  });
25726
26509
  const snap = cmd.command(snapshotCommand.name).description(snapshotCommand.summary).argument("[root]", "target workspace root (defaults to --root or cwd)");
25727
26510
  addSharedFlags(snap);
@@ -25730,8 +26513,8 @@ function registerCommands(program) {
25730
26513
  else snap.option(o.flags, o.description);
25731
26514
  }
25732
26515
  snap.action(
25733
- async (_rootArg, _options, command28) => {
25734
- process.exitCode = await runCapability(snapshotCommand, command28);
26516
+ async (_rootArg, _options, command30) => {
26517
+ process.exitCode = await runCapability(snapshotCommand, command30);
25735
26518
  }
25736
26519
  );
25737
26520
  const task = cmd.command(taskPlanCommand.name).description(taskPlanCommand.summary).argument("<task>", "workspace task description");
@@ -25740,8 +26523,8 @@ function registerCommands(program) {
25740
26523
  if (o.default !== void 0) task.option(o.flags, o.description, o.default);
25741
26524
  else task.option(o.flags, o.description);
25742
26525
  }
25743
- task.action(async (taskText, _options, command28) => {
25744
- process.exitCode = await runCapability(taskPlanCommand, command28, {
26526
+ task.action(async (taskText, _options, command30) => {
26527
+ process.exitCode = await runCapability(taskPlanCommand, command30, {
25745
26528
  positionalRoot: false,
25746
26529
  optionOverrides: { task: taskText }
25747
26530
  });
@@ -25755,8 +26538,8 @@ function registerCommands(program) {
25755
26538
  if (o.default !== void 0) allow.option(o.flags, o.description, o.default);
25756
26539
  else allow.option(o.flags, o.description);
25757
26540
  }
25758
- allow.action(async (source, _options, command28) => {
25759
- process.exitCode = await runCapability(trustAllowCommand, command28, {
26541
+ allow.action(async (source, _options, command30) => {
26542
+ process.exitCode = await runCapability(trustAllowCommand, command30, {
25760
26543
  positionalRoot: false,
25761
26544
  optionOverrides: { source }
25762
26545
  });
@@ -25767,8 +26550,8 @@ function registerCommands(program) {
25767
26550
  if (o.default !== void 0) list.option(o.flags, o.description, o.default);
25768
26551
  else list.option(o.flags, o.description);
25769
26552
  }
25770
- list.action(async (_options, command28) => {
25771
- process.exitCode = await runCapability(trustListCommand, command28, { positionalRoot: false });
26553
+ list.action(async (_options, command30) => {
26554
+ process.exitCode = await runCapability(trustListCommand, command30, { positionalRoot: false });
25772
26555
  });
25773
26556
  const pin = trust.command(trustPinCommand.name).description(trustPinCommand.summary).argument("<source>", "GitHub owner/repo trust source");
25774
26557
  addSharedFlags(pin);
@@ -25776,8 +26559,8 @@ function registerCommands(program) {
25776
26559
  if (o.default !== void 0) pin.option(o.flags, o.description, o.default);
25777
26560
  else pin.option(o.flags, o.description);
25778
26561
  }
25779
- pin.action(async (source, _options, command28) => {
25780
- process.exitCode = await runCapability(trustPinCommand, command28, {
26562
+ pin.action(async (source, _options, command30) => {
26563
+ process.exitCode = await runCapability(trustPinCommand, command30, {
25781
26564
  positionalRoot: false,
25782
26565
  optionOverrides: { source }
25783
26566
  });
@@ -25788,8 +26571,8 @@ function registerCommands(program) {
25788
26571
  if (o.default !== void 0) scan.option(o.flags, o.description, o.default);
25789
26572
  else scan.option(o.flags, o.description);
25790
26573
  }
25791
- scan.action(async (target, _options, command28) => {
25792
- process.exitCode = await runCapability(trustScanCommand, command28, {
26574
+ scan.action(async (target, _options, command30) => {
26575
+ process.exitCode = await runCapability(trustScanCommand, command30, {
25793
26576
  positionalRoot: false,
25794
26577
  optionOverrides: { target }
25795
26578
  });
@@ -25801,8 +26584,8 @@ function registerCommands(program) {
25801
26584
  else verify.option(o.flags, o.description);
25802
26585
  }
25803
26586
  verify.action(
25804
- async (id, _options, command28) => {
25805
- process.exitCode = await runCapability(trustVerifyCommand, command28, {
26587
+ async (id, _options, command30) => {
26588
+ process.exitCode = await runCapability(trustVerifyCommand, command30, {
25806
26589
  positionalRoot: false,
25807
26590
  optionOverrides: { id }
25808
26591
  });
@@ -25834,6 +26617,7 @@ export {
25834
26617
  probeMany,
25835
26618
  exec,
25836
26619
  envBlock,
26620
+ remove,
25837
26621
  plan,
25838
26622
  stripTrailingNewlines,
25839
26623
  lines,
@@ -25875,4 +26659,4 @@ export {
25875
26659
  VERSION,
25876
26660
  buildProgram
25877
26661
  };
25878
- //# sourceMappingURL=chunk-2P5QRFQK.js.map
26662
+ //# sourceMappingURL=chunk-7GVBGS5N.js.map