@aihq/harness 0.2.0-rc.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.
- package/NOTICE +41 -0
- package/README.md +33 -8
- package/dist/{chunk-S7XFTZJW.js → chunk-7GVBGS5N.js} +1423 -637
- package/dist/chunk-7GVBGS5N.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +64 -5
- package/dist/index.js +3 -1
- package/package.json +5 -2
- package/dist/chunk-S7XFTZJW.js.map +0 -1
|
@@ -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
|
-
|
|
609
|
-
|
|
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 ${
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
6972
|
-
import { join as
|
|
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(
|
|
6983
|
-
return JSON.stringify([...
|
|
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((
|
|
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
|
|
7808
|
-
const kept = base.filter((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
|
|
8160
|
-
import { join as
|
|
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/
|
|
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(
|
|
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(
|
|
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]) => !
|
|
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(
|
|
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`,
|
|
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
|
-
|
|
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
|
-
|
|
8556
|
+
join27(ctx.root, ctx.contextDir, "cross-repo-architecture.md")
|
|
8320
8557
|
],
|
|
8321
8558
|
[
|
|
8322
8559
|
`${ctx.contextDir}/repo-discipline.md`,
|
|
8323
|
-
|
|
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(
|
|
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) =>
|
|
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
|
|
8687
|
+
var VALID3 = new Set(SUPPORTED_CLIS);
|
|
8451
8688
|
function isWorkspaceRoot2(ctx) {
|
|
8452
|
-
const raw = readIfExists(
|
|
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) =>
|
|
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) =>
|
|
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(
|
|
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) => !
|
|
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(
|
|
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(
|
|
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 ?
|
|
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) :
|
|
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
|
|
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
|
-
|
|
8721
|
-
model
|
|
8961
|
+
body,
|
|
8962
|
+
{ ...model, stale }
|
|
8722
8963
|
);
|
|
8723
8964
|
}
|
|
8724
8965
|
|
|
8725
8966
|
// src/trust/commands.ts
|
|
8726
|
-
import { existsSync as
|
|
8727
|
-
import { basename as basename6, join as
|
|
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
|
|
8734
|
-
lstatSync as
|
|
8974
|
+
existsSync as existsSync16,
|
|
8975
|
+
lstatSync as lstatSync4,
|
|
8735
8976
|
mkdtempSync,
|
|
8736
|
-
readdirSync as
|
|
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
|
|
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(
|
|
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 (
|
|
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:
|
|
8873
|
-
metadataPath:
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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 (!
|
|
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 =
|
|
9428
|
-
if (!
|
|
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
|
|
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(
|
|
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(
|
|
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 =
|
|
9945
|
-
return
|
|
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(
|
|
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 =
|
|
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
|
|
10348
|
+
import { existsSync as existsSync19 } from "fs";
|
|
10108
10349
|
import { homedir as homedir2 } from "os";
|
|
10109
|
-
import { join as
|
|
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
|
|
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",
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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 &&
|
|
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 && !
|
|
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
|
|
11095
|
+
import { join as join36 } from "path";
|
|
10855
11096
|
var CHECK2 = "mcp: npx launcher";
|
|
10856
11097
|
function mcpNeedsNpx(ctx) {
|
|
10857
|
-
const raw = readIfExists(
|
|
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
|
|
10979
|
-
import { join as
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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 :
|
|
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 =
|
|
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
|
|
11989
|
-
import { join as
|
|
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(
|
|
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 (
|
|
12013
|
-
return
|
|
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
|
|
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
|
|
13088
|
-
var USAGE_PATH =
|
|
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(
|
|
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
|
|
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(
|
|
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 =
|
|
13308
|
-
var GIT_HOOK_PATH =
|
|
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(
|
|
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/
|
|
13581
|
-
import {
|
|
13582
|
-
|
|
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/
|
|
13585
|
-
|
|
13586
|
-
|
|
13587
|
-
|
|
13588
|
-
|
|
13589
|
-
|
|
13590
|
-
|
|
13591
|
-
|
|
13592
|
-
|
|
13593
|
-
|
|
13594
|
-
|
|
13595
|
-
|
|
13596
|
-
|
|
13597
|
-
|
|
13598
|
-
|
|
13599
|
-
|
|
13600
|
-
|
|
13601
|
-
|
|
13602
|
-
|
|
13603
|
-
|
|
13604
|
-
|
|
13605
|
-
|
|
13606
|
-
|
|
13607
|
-
|
|
13608
|
-
|
|
13609
|
-
|
|
13610
|
-
|
|
13611
|
-
|
|
13612
|
-
|
|
13613
|
-
|
|
13614
|
-
|
|
13615
|
-
|
|
13616
|
-
|
|
13617
|
-
|
|
13618
|
-
|
|
13619
|
-
|
|
13620
|
-
|
|
13621
|
-
|
|
13622
|
-
|
|
13623
|
-
|
|
13624
|
-
|
|
13625
|
-
|
|
13626
|
-
|
|
13627
|
-
|
|
13628
|
-
|
|
13629
|
-
|
|
13630
|
-
|
|
13631
|
-
|
|
13632
|
-
|
|
13633
|
-
|
|
13634
|
-
|
|
13635
|
-
|
|
13636
|
-
|
|
13637
|
-
|
|
13638
|
-
|
|
13639
|
-
|
|
13640
|
-
|
|
13641
|
-
|
|
13642
|
-
|
|
13643
|
-
|
|
13644
|
-
|
|
13645
|
-
|
|
13646
|
-
|
|
13647
|
-
|
|
13648
|
-
|
|
13649
|
-
|
|
13650
|
-
|
|
13651
|
-
|
|
13652
|
-
|
|
13653
|
-
|
|
13654
|
-
|
|
13655
|
-
|
|
13656
|
-
|
|
13657
|
-
|
|
13658
|
-
|
|
13659
|
-
|
|
13660
|
-
|
|
13661
|
-
|
|
13662
|
-
|
|
13663
|
-
|
|
13664
|
-
|
|
13665
|
-
|
|
13666
|
-
|
|
13667
|
-
|
|
13668
|
-
|
|
13669
|
-
|
|
13670
|
-
|
|
13671
|
-
|
|
13672
|
-
|
|
13673
|
-
|
|
13674
|
-
|
|
13675
|
-
|
|
13676
|
-
|
|
13677
|
-
|
|
13678
|
-
|
|
13679
|
-
|
|
13680
|
-
|
|
13681
|
-
|
|
13682
|
-
|
|
13683
|
-
|
|
13684
|
-
|
|
13685
|
-
|
|
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",
|
|
@@ -14602,7 +15516,7 @@ var TOOL_HINTS = {
|
|
|
14602
15516
|
comby: "comby \u2014 brew install comby \xB7 bash <(curl -sL get.comby.dev)",
|
|
14603
15517
|
jq: "jq \u2014 brew install jq \xB7 apt install jq \xB7 scoop install jq",
|
|
14604
15518
|
gh: "GitHub CLI \u2014 brew install gh \xB7 winget install GitHub.cli \xB7 cli.github.com",
|
|
14605
|
-
"code-review-graph": "pip install code-review-graph (or uvx code-review-graph serve)",
|
|
15519
|
+
"code-review-graph": "pip install code-review-graph==2.3.6 (or uvx code-review-graph@2.3.6 serve)",
|
|
14606
15520
|
claude: "Claude Code \u2014 npm i -g @anthropic-ai/claude-code",
|
|
14607
15521
|
codex: "Codex CLI \u2014 npm i -g @openai/codex",
|
|
14608
15522
|
cursor: "Cursor editor \u2014 cursor.com",
|
|
@@ -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
|
|
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 =
|
|
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
|
|
15186
|
-
import { join as
|
|
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
|
|
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 =
|
|
16134
|
+
const absDir = join46(root, relDir);
|
|
15221
16135
|
for (const entry2 of safeReadDir2(absDir)) {
|
|
15222
16136
|
const rel = `${relDir}/${entry2}`;
|
|
15223
|
-
if (isDir2(
|
|
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(
|
|
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(
|
|
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);
|
|
@@ -15351,53 +16265,11 @@ function scanLoadGroups(root, contextDir, budgetTokens, opts = {}) {
|
|
|
15351
16265
|
worst,
|
|
15352
16266
|
worstTokens,
|
|
15353
16267
|
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;
|
|
16268
|
+
overBudget: worstTokens > budgetTokens,
|
|
16269
|
+
onDemandFiles,
|
|
16270
|
+
onDemandTokens: onDemandFiles.reduce((n, f) => n + f.tokens, 0)
|
|
16271
|
+
};
|
|
15381
16272
|
}
|
|
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
|
-
};
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
15696
|
-
import { join as
|
|
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
|
|
15700
|
-
import { join as
|
|
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(
|
|
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(
|
|
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 =
|
|
16591
|
+
const adaptersDir = join49(root, dir, "adapters");
|
|
15741
16592
|
let entries;
|
|
15742
16593
|
try {
|
|
15743
|
-
entries =
|
|
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(
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
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 =
|
|
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 (!
|
|
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(
|
|
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(
|
|
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
|
|
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) =>
|
|
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
|
|
16357
|
-
import { join as
|
|
16358
|
-
var RECORDER_PATH2 =
|
|
16359
|
-
var GIT_POST_COMMIT_PATH =
|
|
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 (!
|
|
16362
|
-
return readIfExists(
|
|
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 --
|
|
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 --
|
|
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 && 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 --
|
|
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
|
|
21087
|
-
import { join as
|
|
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 (!
|
|
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) =>
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
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(
|
|
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 =
|
|
21261
|
-
return
|
|
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 =
|
|
22207
|
+
const mClaude = join52(homeDir(ctx), ".claude");
|
|
21266
22208
|
const meta = readEccMeta(homeDir(ctx));
|
|
21267
|
-
const eccAgentNames = eccNamespaced(
|
|
21268
|
-
const eccSkillNames = eccNamespaced(
|
|
21269
|
-
const rulesBase =
|
|
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:
|
|
22215
|
+
rules: existsSync27(join52(rulesBase, "ecc")) ? countMdRecursive(join52(rulesBase, "ecc")) : countMdRecursive(rulesBase)
|
|
21274
22216
|
};
|
|
21275
22217
|
const repoAgents = [
|
|
21276
|
-
...listNames(
|
|
21277
|
-
...listNames(
|
|
22218
|
+
...listNames(join52(r, ".claude", "agents"), { ext: ".md" }),
|
|
22219
|
+
...listNames(join52(r, ".kiro", "agents"), { ext: ".md" })
|
|
21278
22220
|
];
|
|
21279
22221
|
const repoSkills = [
|
|
21280
|
-
...listNames(
|
|
21281
|
-
...listNames(
|
|
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(
|
|
21287
|
-
hooks: countHooks(
|
|
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 (!
|
|
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(
|
|
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 =
|
|
22305
|
+
const dirAbs = join52(root, ".aih", "runs");
|
|
21364
22306
|
let files;
|
|
21365
22307
|
try {
|
|
21366
|
-
files =
|
|
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(
|
|
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(
|
|
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
|
|
21519
|
-
import { join as
|
|
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
|
|
21523
|
-
import { join as
|
|
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",
|
|
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 =
|
|
22513
|
+
const dir = join53(root, ".aih", "workspace-snapshots");
|
|
21569
22514
|
let files;
|
|
21570
22515
|
try {
|
|
21571
|
-
files =
|
|
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 ?
|
|
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 =
|
|
22532
|
+
const dir = join54(root, repo.path, ".aih", "reports");
|
|
21588
22533
|
let files;
|
|
21589
22534
|
try {
|
|
21590
|
-
files =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
21694
|
-
const exists =
|
|
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 =
|
|
21701
|
-
const canon =
|
|
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 =
|
|
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(
|
|
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 =
|
|
21742
|
-
const consumerExists = edge.consumerPath ?
|
|
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
|
-
|
|
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(
|
|
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 (!
|
|
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
|
|
22947
|
+
return join55(".aih", `workspace-report.${format === "html" ? "html" : "md"}`);
|
|
22003
22948
|
}
|
|
22004
|
-
return
|
|
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:
|
|
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
|
|
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,152 +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
|
-
options: [
|
|
22326
|
-
{ pm: "uv", argv: ["uv", "tool", "install", "code-review-graph"] },
|
|
22327
|
-
{ pm: "pip", argv: ["pip", "install", "code-review-graph"] }
|
|
22328
|
-
],
|
|
22329
|
-
manual: "pip install code-review-graph"
|
|
22330
|
-
}
|
|
22331
|
-
];
|
|
22332
|
-
var PM_BINARIES = [
|
|
22333
|
-
["winget", "winget"],
|
|
22334
|
-
["scoop", "scoop"],
|
|
22335
|
-
["brew", "brew"],
|
|
22336
|
-
["apt-get", "apt"],
|
|
22337
|
-
["cargo", "cargo"],
|
|
22338
|
-
["npm", "npm"],
|
|
22339
|
-
["uv", "uv"],
|
|
22340
|
-
["pip", "pip"]
|
|
22341
|
-
];
|
|
22342
|
-
async function onPath4(ctx, bin) {
|
|
22343
|
-
const argv = ctx.host.platform === "windows" ? ["where", bin] : ["which", bin];
|
|
22344
|
-
const res = await ctx.run(argv);
|
|
22345
|
-
return !res.spawnError && res.code === 0 && res.stdout.trim().length > 0;
|
|
22346
|
-
}
|
|
22347
|
-
async function detectPms(ctx) {
|
|
22348
|
-
const found = /* @__PURE__ */ new Set();
|
|
22349
|
-
await Promise.all(
|
|
22350
|
-
PM_BINARIES.map(async ([bin, key]) => {
|
|
22351
|
-
if (await onPath4(ctx, bin)) found.add(key);
|
|
22352
|
-
})
|
|
22353
|
-
);
|
|
22354
|
-
return found;
|
|
22355
|
-
}
|
|
22356
|
-
async function missingTools(ctx) {
|
|
22357
|
-
const checked = await Promise.all(
|
|
22358
|
-
TOOLS.map(async (t) => ({ t, present: await onPath4(ctx, t.bin) }))
|
|
22359
|
-
);
|
|
22360
|
-
return checked.filter((c) => !c.present).map((c) => c.t);
|
|
22361
|
-
}
|
|
22362
|
-
function chooseOption(t, pms) {
|
|
22363
|
-
return t.options.find((o) => pms.has(o.pm));
|
|
22364
|
-
}
|
|
22365
|
-
var WIN_CMD_SHIMS = /* @__PURE__ */ new Set(["npm", "npx", "pnpm", "scoop", "yarn"]);
|
|
22366
|
-
function execArgv(platform, argv) {
|
|
22367
|
-
return platform === "windows" && argv[0] !== void 0 && WIN_CMD_SHIMS.has(argv[0]) ? ["cmd", "/c", ...argv] : argv;
|
|
22368
|
-
}
|
|
22369
|
-
|
|
22370
23185
|
// src/tools/index.ts
|
|
22371
|
-
function howToInstall(t, pms) {
|
|
22372
|
-
const opt = chooseOption(t, pms);
|
|
22373
|
-
return opt ? `\`${opt.argv.join(" ")}\`` : `manually \u2014 ${t.manual}`;
|
|
22374
|
-
}
|
|
22375
|
-
async function verifyTool(ctx, t, pms) {
|
|
22376
|
-
if (await onPath4(ctx, t.bin)) {
|
|
22377
|
-
return { name: t.tool, verdict: "pass", detail: `${t.bin} on PATH` };
|
|
22378
|
-
}
|
|
22379
|
-
return {
|
|
22380
|
-
name: t.tool,
|
|
22381
|
-
verdict: t.tier === "core" ? "fail" : "skip",
|
|
22382
|
-
detail: `${t.bin} not on PATH \u2014 install: ${howToInstall(t, pms)}`,
|
|
22383
|
-
code: "env.tool-install-blocked"
|
|
22384
|
-
};
|
|
22385
|
-
}
|
|
22386
23186
|
function summaryText2(missing, pms) {
|
|
22387
23187
|
const pmList = pms.size > 0 ? [...pms].sort().join(", ") : "none detected";
|
|
22388
23188
|
return lines(
|
|
@@ -22403,31 +23203,14 @@ async function toolsPlan(ctx) {
|
|
|
22403
23203
|
doc("tools \u2014 all present", "All agent shell tools are on PATH. Nothing to install.")
|
|
22404
23204
|
);
|
|
22405
23205
|
}
|
|
22406
|
-
const actions =
|
|
22407
|
-
for (const t of missing) {
|
|
22408
|
-
const opt = chooseOption(t, pms);
|
|
22409
|
-
if (opt) {
|
|
22410
|
-
actions.push(
|
|
22411
|
-
exec(`install ${t.tool} (${opt.pm})`, execArgv(ctx.host.platform, opt.argv), {
|
|
22412
|
-
allowFailure: true
|
|
22413
|
-
})
|
|
22414
|
-
);
|
|
22415
|
-
} else {
|
|
22416
|
-
actions.push(
|
|
22417
|
-
doc(
|
|
22418
|
-
`install ${t.tool} manually (no supported package manager found)`,
|
|
22419
|
-
`No detected package manager can install ${t.tool}. Install it manually: ${t.manual}`
|
|
22420
|
-
)
|
|
22421
|
-
);
|
|
22422
|
-
}
|
|
22423
|
-
}
|
|
23206
|
+
const actions = installActionsFor(ctx, missing, pms);
|
|
22424
23207
|
for (const t of missing) {
|
|
22425
23208
|
actions.push(probe(`${t.tool} installed`, (c) => verifyTool(c, t, pms)));
|
|
22426
23209
|
}
|
|
22427
23210
|
actions.push(doc("tools summary", summaryText2(missing, pms)));
|
|
22428
23211
|
return plan("tools", ...actions);
|
|
22429
23212
|
}
|
|
22430
|
-
var
|
|
23213
|
+
var command27 = {
|
|
22431
23214
|
name: "tools",
|
|
22432
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",
|
|
22433
23216
|
// Diagnose by default so a bare `aih tools` surfaces what's missing + how to get it,
|
|
@@ -22466,7 +23249,7 @@ async function trackPlan(ctx) {
|
|
|
22466
23249
|
aihIgnoreWrite(ctx.root)
|
|
22467
23250
|
);
|
|
22468
23251
|
}
|
|
22469
|
-
var
|
|
23252
|
+
var command28 = {
|
|
22470
23253
|
name: "track",
|
|
22471
23254
|
summary: "Record a metrics sample (git + repo state) to .aih/history.jsonl \u2014 powers `aih report` trends",
|
|
22472
23255
|
plan: trackPlan
|
|
@@ -22474,8 +23257,8 @@ var command26 = {
|
|
|
22474
23257
|
|
|
22475
23258
|
// src/trust/scan.ts
|
|
22476
23259
|
import { createHash as createHash7 } from "crypto";
|
|
22477
|
-
import { lstatSync as
|
|
22478
|
-
import { basename as basename11, extname as extname3, isAbsolute as isAbsolute8, join as
|
|
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";
|
|
22479
23262
|
|
|
22480
23263
|
// src/trust/acknowledge.ts
|
|
22481
23264
|
var TRUST_DANGER_CODES = /* @__PURE__ */ new Set([
|
|
@@ -22564,10 +23347,10 @@ function applyTrustAcknowledgements(checks, ctx) {
|
|
|
22564
23347
|
function quoteArg(value) {
|
|
22565
23348
|
return /^[A-Za-z0-9_./:@-]+$/.test(value) ? value : `"${value.replace(/(["\\])/g, "\\$1")}"`;
|
|
22566
23349
|
}
|
|
22567
|
-
function acknowledgeCommandHint(
|
|
23350
|
+
function acknowledgeCommandHint(command30, source, checks) {
|
|
22568
23351
|
const fingerprints2 = checks.filter((check2) => check2.verdict === "fail" && check2.fingerprint !== void 0).filter((check2) => !isDanger(check2) && isAcknowledgeableOrigin(check2)).map((check2) => check2.fingerprint);
|
|
22569
23352
|
if (fingerprints2.length === 0) return void 0;
|
|
22570
|
-
return `To acknowledge the current trust-origin finding(s), rerun: aih ${
|
|
23353
|
+
return `To acknowledge the current trust-origin finding(s), rerun: aih ${command30} ${quoteArg(
|
|
22571
23354
|
source
|
|
22572
23355
|
)} --acknowledge ${quoteArg(fingerprints2.join(","))} --reason ${quoteArg("<reason>")}`;
|
|
22573
23356
|
}
|
|
@@ -22865,8 +23648,8 @@ function scanTrustDependencyNames(root, internalScopes, posture = "vibe") {
|
|
|
22865
23648
|
|
|
22866
23649
|
// src/trust/detectors.ts
|
|
22867
23650
|
import { createHash as createHash4 } from "crypto";
|
|
22868
|
-
import { lstatSync as
|
|
22869
|
-
import { basename as basename9, extname, isAbsolute as isAbsolute7, join as
|
|
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";
|
|
22870
23653
|
var SKILLSPECTOR_ANALYZER = "skillspector@docker";
|
|
22871
23654
|
var DETECTOR_UNAVAILABLE = "trust.detector-unavailable";
|
|
22872
23655
|
var MAX_SCRIPT_SCAN_BYTES = 512 * 1024;
|
|
@@ -22999,7 +23782,7 @@ function scanNativeMaliciousCode(root) {
|
|
|
22999
23782
|
root,
|
|
23000
23783
|
(abs) => {
|
|
23001
23784
|
const rel = toPosix2(relative7(root, abs));
|
|
23002
|
-
return isScriptLike(rel) &&
|
|
23785
|
+
return isScriptLike(rel) && lstatSync5(abs).size <= MAX_SCRIPT_SCAN_BYTES;
|
|
23003
23786
|
},
|
|
23004
23787
|
TRUST_SKIP_DIRS
|
|
23005
23788
|
);
|
|
@@ -23088,7 +23871,7 @@ function sarifLocation(result) {
|
|
|
23088
23871
|
}
|
|
23089
23872
|
function sarifFingerprint(code, root, location, detail) {
|
|
23090
23873
|
const line = location.startLine ?? 1;
|
|
23091
|
-
const sourceLine = fileLine(
|
|
23874
|
+
const sourceLine = fileLine(join56(root, location.uri), line) ?? detail;
|
|
23092
23875
|
return `${code.replace(/\./g, "-")}:skillspector:${location.uri}:${line}:${sha8(sourceLine)}`;
|
|
23093
23876
|
}
|
|
23094
23877
|
function sarifChecks(stdout, root, posture) {
|
|
@@ -23379,8 +24162,8 @@ function scanTrustDocument(path, source) {
|
|
|
23379
24162
|
|
|
23380
24163
|
// src/trust/manifest.ts
|
|
23381
24164
|
import { createHash as createHash6 } from "crypto";
|
|
23382
|
-
import { lstatSync as
|
|
23383
|
-
import { basename as basename10, extname as extname2, join as
|
|
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";
|
|
23384
24167
|
import { parseDocument } from "yaml";
|
|
23385
24168
|
var AUTO_EXEC_CODE = "trust.auto-exec-hook";
|
|
23386
24169
|
var LIFECYCLE_SCRIPTS = /* @__PURE__ */ new Set([
|
|
@@ -23594,7 +24377,7 @@ function isClaudeHooksDir(rel) {
|
|
|
23594
24377
|
function scanHookDirs(root) {
|
|
23595
24378
|
const checks = [];
|
|
23596
24379
|
const visit = (abs) => {
|
|
23597
|
-
const st =
|
|
24380
|
+
const st = lstatSync6(abs);
|
|
23598
24381
|
if (!st.isDirectory()) return;
|
|
23599
24382
|
const rel = toPosix3(relative8(root, abs));
|
|
23600
24383
|
if (abs !== root && TRUST_SKIP_DIRS.has(basename10(abs)) && !isClaudeHooksDir(rel)) return;
|
|
@@ -23604,7 +24387,7 @@ function scanHookDirs(root) {
|
|
|
23604
24387
|
);
|
|
23605
24388
|
return;
|
|
23606
24389
|
}
|
|
23607
|
-
for (const entry2 of
|
|
24390
|
+
for (const entry2 of readdirSync14(abs)) visit(join57(abs, entry2));
|
|
23608
24391
|
};
|
|
23609
24392
|
visit(root);
|
|
23610
24393
|
return checks;
|
|
@@ -23652,8 +24435,8 @@ function stringValue(value) {
|
|
|
23652
24435
|
function stringArray(value) {
|
|
23653
24436
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
23654
24437
|
}
|
|
23655
|
-
function commandName(
|
|
23656
|
-
const last =
|
|
24438
|
+
function commandName(command30) {
|
|
24439
|
+
const last = command30.split(/[\\/]/).at(-1) ?? command30;
|
|
23657
24440
|
return last.replace(/\.(?:cmd|exe)$/i, "").toLowerCase();
|
|
23658
24441
|
}
|
|
23659
24442
|
function isPlaceholderOrEmpty2(value) {
|
|
@@ -23689,8 +24472,8 @@ function hasLiteralCredential(raw, args) {
|
|
|
23689
24472
|
function hasExactPackage(args) {
|
|
23690
24473
|
return args.some((arg) => EXACT_PACKAGE_RE.test(arg.trim()));
|
|
23691
24474
|
}
|
|
23692
|
-
function hasFloatingLaunch(
|
|
23693
|
-
const cmd =
|
|
24475
|
+
function hasFloatingLaunch(command30, args) {
|
|
24476
|
+
const cmd = command30 === void 0 ? "" : commandName(command30);
|
|
23694
24477
|
return cmd === "npx" || cmd === "uvx" || args.some((arg) => arg === "-y" || arg === "--yes" || /@latest(?:$|\b)/i.test(arg));
|
|
23695
24478
|
}
|
|
23696
24479
|
function hosted(url, description, credentials) {
|
|
@@ -23718,7 +24501,7 @@ function flagged(description, credentials) {
|
|
|
23718
24501
|
}
|
|
23719
24502
|
function classifyIncomingMcp(rawServer) {
|
|
23720
24503
|
const raw = isRecord6(rawServer) ? rawServer : void 0;
|
|
23721
|
-
const
|
|
24504
|
+
const command30 = raw ? stringValue(raw.command) : void 0;
|
|
23722
24505
|
const args = raw ? stringArray(raw.args) : [];
|
|
23723
24506
|
const url = raw ? stringValue(raw.url) : void 0;
|
|
23724
24507
|
const description = raw ? stringValue(raw.description) ?? "" : "";
|
|
@@ -23727,19 +24510,19 @@ function classifyIncomingMcp(rawServer) {
|
|
|
23727
24510
|
if (url !== void 0 && /^https?:\/\//i.test(url.trim())) {
|
|
23728
24511
|
return hosted(url.trim(), description, credentials);
|
|
23729
24512
|
}
|
|
23730
|
-
if (
|
|
24513
|
+
if (command30 === void 0 || command30.trim().length === 0) {
|
|
23731
24514
|
return flagged(description, credentials);
|
|
23732
24515
|
}
|
|
23733
24516
|
return {
|
|
23734
24517
|
type: "stdio",
|
|
23735
|
-
command:
|
|
24518
|
+
command: command30,
|
|
23736
24519
|
args,
|
|
23737
24520
|
description,
|
|
23738
24521
|
env,
|
|
23739
24522
|
classification: "local",
|
|
23740
24523
|
egress: "local-only",
|
|
23741
24524
|
credentials,
|
|
23742
|
-
supplyChain: hasFloatingLaunch(
|
|
24525
|
+
supplyChain: hasFloatingLaunch(command30, args) ? "unpinned" : hasExactPackage(args) ? "pinned" : "unpinned"
|
|
23743
24526
|
};
|
|
23744
24527
|
}
|
|
23745
24528
|
|
|
@@ -23765,10 +24548,10 @@ function toPosix4(path) {
|
|
|
23765
24548
|
function collectFilesUnder(root, accept, skipDirs = TRUST_SKIP_DIRS) {
|
|
23766
24549
|
const out = [];
|
|
23767
24550
|
const visit = (abs) => {
|
|
23768
|
-
const st =
|
|
24551
|
+
const st = lstatSync7(abs);
|
|
23769
24552
|
if (st.isDirectory()) {
|
|
23770
24553
|
if (abs !== root && skipDirs.has(basename11(abs))) return;
|
|
23771
|
-
for (const entry2 of
|
|
24554
|
+
for (const entry2 of readdirSync15(abs)) visit(join58(abs, entry2));
|
|
23772
24555
|
return;
|
|
23773
24556
|
}
|
|
23774
24557
|
if (st.isFile() && accept(abs)) out.push(abs);
|
|
@@ -23859,12 +24642,12 @@ function openCodeServer(server) {
|
|
|
23859
24642
|
if (server.type === "remote") {
|
|
23860
24643
|
return { ...server, url: stringValue2(server.url) };
|
|
23861
24644
|
}
|
|
23862
|
-
const
|
|
23863
|
-
const executable =
|
|
24645
|
+
const command30 = Array.isArray(server.command) ? server.command : [];
|
|
24646
|
+
const executable = command30[0];
|
|
23864
24647
|
return {
|
|
23865
24648
|
...server,
|
|
23866
24649
|
command: typeof executable === "string" ? executable : void 0,
|
|
23867
|
-
args:
|
|
24650
|
+
args: command30.slice(1).filter((item) => typeof item === "string"),
|
|
23868
24651
|
env: server.environment ?? server.env
|
|
23869
24652
|
};
|
|
23870
24653
|
}
|
|
@@ -23916,7 +24699,7 @@ function incomingMcpChecks(root, mcpConfigFiles, posture) {
|
|
|
23916
24699
|
for (const rel of mcpConfigFiles) {
|
|
23917
24700
|
let parsed;
|
|
23918
24701
|
try {
|
|
23919
|
-
parsed = JSON.parse(readFileSync10(
|
|
24702
|
+
parsed = JSON.parse(readFileSync10(join58(root, rel), "utf8"));
|
|
23920
24703
|
} catch {
|
|
23921
24704
|
checks.push(malformedMcpConfigCheck(rel));
|
|
23922
24705
|
continue;
|
|
@@ -24221,12 +25004,12 @@ var trustScanCommand = {
|
|
|
24221
25004
|
|
|
24222
25005
|
// src/workspace/acquire.ts
|
|
24223
25006
|
import { randomUUID } from "crypto";
|
|
24224
|
-
import { existsSync as
|
|
24225
|
-
import { basename as basename12, extname as extname4, join as
|
|
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";
|
|
24226
25009
|
|
|
24227
25010
|
// src/internals/commander-options.ts
|
|
24228
|
-
function optionSource(
|
|
24229
|
-
return
|
|
25011
|
+
function optionSource(command30, key) {
|
|
25012
|
+
return command30.getOptionValueSourceWithGlobals?.(key) ?? command30.getOptionValueSource?.(key);
|
|
24230
25013
|
}
|
|
24231
25014
|
|
|
24232
25015
|
// src/support/setup.ts
|
|
@@ -24359,11 +25142,11 @@ async function workspaceAddPhase1Plan(ctx, resolvedSource) {
|
|
|
24359
25142
|
function collectSkillDirs(root) {
|
|
24360
25143
|
const out = [];
|
|
24361
25144
|
const visit = (abs) => {
|
|
24362
|
-
const st =
|
|
25145
|
+
const st = lstatSync8(abs);
|
|
24363
25146
|
if (!st.isDirectory()) return;
|
|
24364
25147
|
if (abs !== root && SKIP_DIRS.has(basename12(abs))) return;
|
|
24365
|
-
if (
|
|
24366
|
-
for (const entry2 of
|
|
25148
|
+
if (existsSync30(join59(abs, "SKILL.md"))) out.push(abs);
|
|
25149
|
+
for (const entry2 of readdirSync16(abs)) visit(join59(abs, entry2));
|
|
24367
25150
|
};
|
|
24368
25151
|
visit(root);
|
|
24369
25152
|
return out.sort((a, b) => sourceDirSortKey(root, a).localeCompare(sourceDirSortKey(root, b)));
|
|
@@ -24371,10 +25154,10 @@ function collectSkillDirs(root) {
|
|
|
24371
25154
|
function collectFiles(root) {
|
|
24372
25155
|
const out = [];
|
|
24373
25156
|
const visit = (abs) => {
|
|
24374
|
-
const st =
|
|
25157
|
+
const st = lstatSync8(abs);
|
|
24375
25158
|
if (st.isDirectory()) {
|
|
24376
25159
|
if (abs !== root && SKIP_DIRS.has(basename12(abs))) return;
|
|
24377
|
-
for (const entry2 of
|
|
25160
|
+
for (const entry2 of readdirSync16(abs)) visit(join59(abs, entry2));
|
|
24378
25161
|
return;
|
|
24379
25162
|
}
|
|
24380
25163
|
if (st.isFile()) out.push(abs);
|
|
@@ -24597,7 +25380,7 @@ function hasFailedExec(result) {
|
|
|
24597
25380
|
}
|
|
24598
25381
|
function readSetupText(root) {
|
|
24599
25382
|
for (const rel of ["SETUP.md", "docs/SETUP.md", ".aih/SETUP.md"]) {
|
|
24600
|
-
const text = readIfExists(
|
|
25383
|
+
const text = readIfExists(join59(root, rel));
|
|
24601
25384
|
if (text !== void 0) return text;
|
|
24602
25385
|
}
|
|
24603
25386
|
return void 0;
|
|
@@ -24632,16 +25415,16 @@ ${template.body}`);
|
|
|
24632
25415
|
}
|
|
24633
25416
|
write(supportSummary(support, saved));
|
|
24634
25417
|
}
|
|
24635
|
-
function contextFromCommand(
|
|
25418
|
+
function contextFromCommand(command30, deps) {
|
|
24636
25419
|
const env = deps.env ?? process.env;
|
|
24637
25420
|
const run = deps.run ?? defaultRunner;
|
|
24638
|
-
const opts =
|
|
25421
|
+
const opts = command30.optsWithGlobals();
|
|
24639
25422
|
const resolvedRoot = opts.root ?? env.AIH_ROOT ?? process.cwd();
|
|
24640
25423
|
const marker = readAihConfig(resolvedRoot);
|
|
24641
|
-
const contextDirSource = optionSource(
|
|
25424
|
+
const contextDirSource = optionSource(command30, "contextDir");
|
|
24642
25425
|
const contextDirFromFlag = contextDirSource === "cli" ? opts.contextDir : void 0;
|
|
24643
25426
|
const contextDirFromMarker = contextDirFromFlag === void 0 ? marker?.contextDir : void 0;
|
|
24644
|
-
const postureFlagSource = optionSource(
|
|
25427
|
+
const postureFlagSource = optionSource(command30, "posture") === "cli" ? "cli" : void 0;
|
|
24645
25428
|
const resolvedPosture = resolvePosture({
|
|
24646
25429
|
root: resolvedRoot,
|
|
24647
25430
|
env,
|
|
@@ -24669,7 +25452,7 @@ function contextFromCommand(command28, deps) {
|
|
|
24669
25452
|
host,
|
|
24670
25453
|
env,
|
|
24671
25454
|
options: {
|
|
24672
|
-
source:
|
|
25455
|
+
source: command30.processedArgs[0],
|
|
24673
25456
|
pin: opts.pin,
|
|
24674
25457
|
ref: opts.ref,
|
|
24675
25458
|
force: opts.force,
|
|
@@ -24679,16 +25462,16 @@ function contextFromCommand(command28, deps) {
|
|
|
24679
25462
|
}
|
|
24680
25463
|
};
|
|
24681
25464
|
}
|
|
24682
|
-
async function runWorkspaceAdd(
|
|
25465
|
+
async function runWorkspaceAdd(command30, deps = {}) {
|
|
24683
25466
|
const write = deps.write ?? ((text) => process.stdout.write(text));
|
|
24684
25467
|
const env = deps.env ?? process.env;
|
|
24685
|
-
const opts =
|
|
25468
|
+
const opts = command30.optsWithGlobals();
|
|
24686
25469
|
const runId = (deps.newRunId ?? (() => `run_${randomUUID().slice(0, 8)}`))();
|
|
24687
25470
|
const startedAt = (deps.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
24688
25471
|
let json = false;
|
|
24689
25472
|
let source;
|
|
24690
25473
|
try {
|
|
24691
|
-
const ctx = contextFromCommand(
|
|
25474
|
+
const ctx = contextFromCommand(command30, deps);
|
|
24692
25475
|
json = ctx.json;
|
|
24693
25476
|
source = sourceFromContext(ctx);
|
|
24694
25477
|
const phase1 = await workspaceAddPhase1Plan(ctx, source);
|
|
@@ -24750,12 +25533,12 @@ ${summarizeResult(phase2Result)}
|
|
|
24750
25533
|
}
|
|
24751
25534
|
|
|
24752
25535
|
// src/workspace/index.ts
|
|
24753
|
-
import { existsSync as
|
|
24754
|
-
import { basename as basename13, join as
|
|
25536
|
+
import { existsSync as existsSync32 } from "fs";
|
|
25537
|
+
import { basename as basename13, join as join61, posix as posix12 } from "path";
|
|
24755
25538
|
|
|
24756
25539
|
// src/workspace/detect.ts
|
|
24757
|
-
import { existsSync as
|
|
24758
|
-
import { join as
|
|
25540
|
+
import { existsSync as existsSync31, readdirSync as readdirSync17, statSync as statSync6 } from "fs";
|
|
25541
|
+
import { join as join60 } from "path";
|
|
24759
25542
|
function normalizeRepoPath(raw) {
|
|
24760
25543
|
const value = raw.trim().replace(/\\/g, "/");
|
|
24761
25544
|
if (value.length === 0) return "";
|
|
@@ -24772,9 +25555,9 @@ function normalizeRepoPath(raw) {
|
|
|
24772
25555
|
return parts.join("/");
|
|
24773
25556
|
}
|
|
24774
25557
|
function isGitRepo(parent, repo) {
|
|
24775
|
-
const dir =
|
|
25558
|
+
const dir = join60(parent, repo);
|
|
24776
25559
|
try {
|
|
24777
|
-
return statSync6(dir).isDirectory() &&
|
|
25560
|
+
return statSync6(dir).isDirectory() && existsSync31(join60(dir, ".git"));
|
|
24778
25561
|
} catch {
|
|
24779
25562
|
return false;
|
|
24780
25563
|
}
|
|
@@ -24782,7 +25565,7 @@ function isGitRepo(parent, repo) {
|
|
|
24782
25565
|
function detectChildRepos(parent, explicit = []) {
|
|
24783
25566
|
if (explicit.length > 0) {
|
|
24784
25567
|
const repos = [...new Set(explicit.map(normalizeRepoPath).filter((r) => r.length > 0))];
|
|
24785
|
-
const missing = repos.filter((r) => !
|
|
25568
|
+
const missing = repos.filter((r) => !existsSync31(join60(parent, r)));
|
|
24786
25569
|
if (missing.length > 0) {
|
|
24787
25570
|
throw new AihError(
|
|
24788
25571
|
`workspace --repos entries do not exist under the parent: ${missing.join(", ")}`,
|
|
@@ -24800,14 +25583,14 @@ function detectChildRepos(parent, explicit = []) {
|
|
|
24800
25583
|
}
|
|
24801
25584
|
let entries;
|
|
24802
25585
|
try {
|
|
24803
|
-
entries =
|
|
25586
|
+
entries = readdirSync17(parent);
|
|
24804
25587
|
} catch {
|
|
24805
25588
|
return [];
|
|
24806
25589
|
}
|
|
24807
25590
|
return entries.filter((name) => !name.startsWith(".")).filter((name) => {
|
|
24808
|
-
const dir =
|
|
25591
|
+
const dir = join60(parent, name);
|
|
24809
25592
|
try {
|
|
24810
|
-
return statSync6(dir).isDirectory() &&
|
|
25593
|
+
return statSync6(dir).isDirectory() && existsSync31(join60(dir, ".git"));
|
|
24811
25594
|
} catch {
|
|
24812
25595
|
return false;
|
|
24813
25596
|
}
|
|
@@ -25174,7 +25957,7 @@ function hasObjectRepos(raw) {
|
|
|
25174
25957
|
function childScaffoldedProbe(repo, dir) {
|
|
25175
25958
|
return probe(`child ${repo} scaffolded`, (ctx) => {
|
|
25176
25959
|
const name = `child ${repo} scaffolded`;
|
|
25177
|
-
const present =
|
|
25960
|
+
const present = existsSync32(join61(ctx.root, repo, dir, "RULE_ROUTER.md"));
|
|
25178
25961
|
return present ? { name, verdict: "pass", detail: `${repo}/${dir}/ canon present` } : { name, verdict: "skip", detail: `not scaffolded \u2014 run \`aih init ./${repo} --apply\`` };
|
|
25179
25962
|
});
|
|
25180
25963
|
}
|
|
@@ -25246,7 +26029,7 @@ async function workspacePlan(ctx) {
|
|
|
25246
26029
|
for (const repo of repos) actions.push(childScaffoldedProbe(repo, dir));
|
|
25247
26030
|
return plan("workspace", ...actions);
|
|
25248
26031
|
}
|
|
25249
|
-
var
|
|
26032
|
+
var command29 = {
|
|
25250
26033
|
name: "workspace",
|
|
25251
26034
|
summary: "Scaffold a multi-repo workspace: cross-repo map, combined graph/filesystem MCP, .code-workspace (parent-only)",
|
|
25252
26035
|
options: [
|
|
@@ -25264,7 +26047,7 @@ var command27 = {
|
|
|
25264
26047
|
|
|
25265
26048
|
// src/commands/run.ts
|
|
25266
26049
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
25267
|
-
import { basename as basename14, join as
|
|
26050
|
+
import { basename as basename14, join as join63 } from "path";
|
|
25268
26051
|
|
|
25269
26052
|
// src/internals/prompt.ts
|
|
25270
26053
|
import { createInterface } from "readline";
|
|
@@ -25291,7 +26074,7 @@ import { isAbsolute as isAbsolute9 } from "path";
|
|
|
25291
26074
|
|
|
25292
26075
|
// src/program.ts
|
|
25293
26076
|
import { Command } from "commander";
|
|
25294
|
-
var VERSION = "0.
|
|
26077
|
+
var VERSION = "0.3.0";
|
|
25295
26078
|
function buildProgram() {
|
|
25296
26079
|
const program = new Command();
|
|
25297
26080
|
program.name("aih").description("Enterprise AI Bootstrapping Harness \u2014 governed, proxy-safe AI coding setup").version(VERSION).showHelpAfterError("(add --help for usage)");
|
|
@@ -25363,8 +26146,8 @@ function reportToSarif(report, toolName = "aih") {
|
|
|
25363
26146
|
|
|
25364
26147
|
// src/logging/run-log.ts
|
|
25365
26148
|
import { appendFileSync, mkdirSync as mkdirSync2 } from "fs";
|
|
25366
|
-
import { join as
|
|
25367
|
-
var RUNS_DIR =
|
|
26149
|
+
import { join as join62 } from "path";
|
|
26150
|
+
var RUNS_DIR = join62(".aih", "runs");
|
|
25368
26151
|
function statusFor(verifyFailed, execFailed) {
|
|
25369
26152
|
if (verifyFailed) return "failed";
|
|
25370
26153
|
if (execFailed) return "partial";
|
|
@@ -25413,9 +26196,9 @@ function monthFile(date) {
|
|
|
25413
26196
|
}
|
|
25414
26197
|
function appendRunLog(root, entry2, date) {
|
|
25415
26198
|
try {
|
|
25416
|
-
const dir =
|
|
26199
|
+
const dir = join62(root, RUNS_DIR);
|
|
25417
26200
|
mkdirSync2(dir, { recursive: true });
|
|
25418
|
-
appendFileSync(
|
|
26201
|
+
appendFileSync(join62(dir, monthFile(date)), `${JSON.stringify(entry2)}
|
|
25419
26202
|
`);
|
|
25420
26203
|
} catch {
|
|
25421
26204
|
}
|
|
@@ -25426,7 +26209,7 @@ var delay = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
25426
26209
|
var SETUP_FILES = ["SETUP.md", "docs/SETUP.md", ".aih/SETUP.md"];
|
|
25427
26210
|
function readSetupText2(root) {
|
|
25428
26211
|
for (const rel of SETUP_FILES) {
|
|
25429
|
-
const text = readIfExists(
|
|
26212
|
+
const text = readIfExists(join63(root, rel));
|
|
25430
26213
|
if (text !== void 0) return text;
|
|
25431
26214
|
}
|
|
25432
26215
|
return void 0;
|
|
@@ -25448,12 +26231,12 @@ function extractOptions(spec, opts) {
|
|
|
25448
26231
|
}
|
|
25449
26232
|
return picked;
|
|
25450
26233
|
}
|
|
25451
|
-
async function runCapability(spec,
|
|
26234
|
+
async function runCapability(spec, command30, deps = {}) {
|
|
25452
26235
|
const env = deps.env ?? process.env;
|
|
25453
26236
|
const write = deps.write ?? ((t) => process.stdout.write(t));
|
|
25454
26237
|
const run = deps.run ?? defaultRunner;
|
|
25455
|
-
const opts =
|
|
25456
|
-
const defaultPositionalRoot = Array.isArray(
|
|
26238
|
+
const opts = command30.optsWithGlobals();
|
|
26239
|
+
const defaultPositionalRoot = Array.isArray(command30.processedArgs) ? command30.processedArgs[0] : void 0;
|
|
25457
26240
|
const positionalRoot = deps.positionalRoot === false ? void 0 : deps.positionalRoot ?? defaultPositionalRoot;
|
|
25458
26241
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
25459
26242
|
const startedAt = now();
|
|
@@ -25469,10 +26252,10 @@ async function runCapability(spec, command28, deps = {}) {
|
|
|
25469
26252
|
try {
|
|
25470
26253
|
const resolvedRoot = positionalRoot ?? opts.root ?? env.AIH_ROOT ?? process.cwd();
|
|
25471
26254
|
const marker = readAihConfig(resolvedRoot);
|
|
25472
|
-
const contextDirSource = optionSource(
|
|
26255
|
+
const contextDirSource = optionSource(command30, "contextDir");
|
|
25473
26256
|
const contextDirFromFlag = contextDirSource === "cli" ? opts.contextDir : void 0;
|
|
25474
26257
|
const contextDirFromMarker = contextDirFromFlag === void 0 ? marker?.contextDir : void 0;
|
|
25475
|
-
const postureFlagSource = optionSource(
|
|
26258
|
+
const postureFlagSource = optionSource(command30, "posture") === "cli" ? "cli" : void 0;
|
|
25476
26259
|
const resolvedPosture = resolvePosture({
|
|
25477
26260
|
root: resolvedRoot,
|
|
25478
26261
|
env,
|
|
@@ -25490,7 +26273,7 @@ async function runCapability(spec, command28, deps = {}) {
|
|
|
25490
26273
|
});
|
|
25491
26274
|
json = settings.json;
|
|
25492
26275
|
const host = makeHostAdapter({ run, env });
|
|
25493
|
-
const wantConfirm = opts.detect === true && opts.yes !== true && !settings.json;
|
|
26276
|
+
const wantConfirm = (opts.detect === true || spec.wantsInstallPrompt === true) && opts.yes !== true && !settings.json;
|
|
25494
26277
|
const prompter = deps.prompter ?? (wantConfirm && isInteractive(env) ? makeReadlinePrompter() : void 0);
|
|
25495
26278
|
const watchSec = positiveInt(opts.refresh);
|
|
25496
26279
|
const liveOpen = opts.open === true || opts.demo === true || watchSec !== void 0;
|
|
@@ -25670,18 +26453,20 @@ var CAPABILITIES = [
|
|
|
25670
26453
|
command17,
|
|
25671
26454
|
command5,
|
|
25672
26455
|
command8,
|
|
25673
|
-
command24,
|
|
25674
26456
|
command26,
|
|
25675
|
-
command21,
|
|
25676
26457
|
command25,
|
|
26458
|
+
command28,
|
|
26459
|
+
command21,
|
|
26460
|
+
command27,
|
|
25677
26461
|
command10,
|
|
25678
26462
|
command7,
|
|
25679
26463
|
command,
|
|
25680
|
-
|
|
26464
|
+
command29,
|
|
25681
26465
|
command2,
|
|
26466
|
+
command23,
|
|
25682
26467
|
command22
|
|
25683
26468
|
];
|
|
25684
|
-
var READONLY = [command11,
|
|
26469
|
+
var READONLY = [command11, command24, verifyCommand];
|
|
25685
26470
|
var ALL_COMMANDS = [...CAPABILITIES, ...READONLY];
|
|
25686
26471
|
function addSharedFlags(cmd) {
|
|
25687
26472
|
return cmd.option("--apply", "execute the plan (default: dry-run; nothing is written)").option(
|
|
@@ -25707,8 +26492,8 @@ function registerCommands(program) {
|
|
|
25707
26492
|
else cmd.option(o.flags, o.description);
|
|
25708
26493
|
}
|
|
25709
26494
|
cmd.action(
|
|
25710
|
-
async (_rootArg, _options,
|
|
25711
|
-
process.exitCode = await runCapability(spec,
|
|
26495
|
+
async (_rootArg, _options, command30) => {
|
|
26496
|
+
process.exitCode = await runCapability(spec, command30);
|
|
25712
26497
|
}
|
|
25713
26498
|
);
|
|
25714
26499
|
if (spec.name === "workspace") {
|
|
@@ -25718,8 +26503,8 @@ function registerCommands(program) {
|
|
|
25718
26503
|
if (o.default !== void 0) add.option(o.flags, o.description, o.default);
|
|
25719
26504
|
else add.option(o.flags, o.description);
|
|
25720
26505
|
}
|
|
25721
|
-
add.action(async (_source, _options,
|
|
25722
|
-
process.exitCode = await runWorkspaceAdd(
|
|
26506
|
+
add.action(async (_source, _options, command30) => {
|
|
26507
|
+
process.exitCode = await runWorkspaceAdd(command30);
|
|
25723
26508
|
});
|
|
25724
26509
|
const snap = cmd.command(snapshotCommand.name).description(snapshotCommand.summary).argument("[root]", "target workspace root (defaults to --root or cwd)");
|
|
25725
26510
|
addSharedFlags(snap);
|
|
@@ -25728,8 +26513,8 @@ function registerCommands(program) {
|
|
|
25728
26513
|
else snap.option(o.flags, o.description);
|
|
25729
26514
|
}
|
|
25730
26515
|
snap.action(
|
|
25731
|
-
async (_rootArg, _options,
|
|
25732
|
-
process.exitCode = await runCapability(snapshotCommand,
|
|
26516
|
+
async (_rootArg, _options, command30) => {
|
|
26517
|
+
process.exitCode = await runCapability(snapshotCommand, command30);
|
|
25733
26518
|
}
|
|
25734
26519
|
);
|
|
25735
26520
|
const task = cmd.command(taskPlanCommand.name).description(taskPlanCommand.summary).argument("<task>", "workspace task description");
|
|
@@ -25738,8 +26523,8 @@ function registerCommands(program) {
|
|
|
25738
26523
|
if (o.default !== void 0) task.option(o.flags, o.description, o.default);
|
|
25739
26524
|
else task.option(o.flags, o.description);
|
|
25740
26525
|
}
|
|
25741
|
-
task.action(async (taskText, _options,
|
|
25742
|
-
process.exitCode = await runCapability(taskPlanCommand,
|
|
26526
|
+
task.action(async (taskText, _options, command30) => {
|
|
26527
|
+
process.exitCode = await runCapability(taskPlanCommand, command30, {
|
|
25743
26528
|
positionalRoot: false,
|
|
25744
26529
|
optionOverrides: { task: taskText }
|
|
25745
26530
|
});
|
|
@@ -25753,8 +26538,8 @@ function registerCommands(program) {
|
|
|
25753
26538
|
if (o.default !== void 0) allow.option(o.flags, o.description, o.default);
|
|
25754
26539
|
else allow.option(o.flags, o.description);
|
|
25755
26540
|
}
|
|
25756
|
-
allow.action(async (source, _options,
|
|
25757
|
-
process.exitCode = await runCapability(trustAllowCommand,
|
|
26541
|
+
allow.action(async (source, _options, command30) => {
|
|
26542
|
+
process.exitCode = await runCapability(trustAllowCommand, command30, {
|
|
25758
26543
|
positionalRoot: false,
|
|
25759
26544
|
optionOverrides: { source }
|
|
25760
26545
|
});
|
|
@@ -25765,8 +26550,8 @@ function registerCommands(program) {
|
|
|
25765
26550
|
if (o.default !== void 0) list.option(o.flags, o.description, o.default);
|
|
25766
26551
|
else list.option(o.flags, o.description);
|
|
25767
26552
|
}
|
|
25768
|
-
list.action(async (_options,
|
|
25769
|
-
process.exitCode = await runCapability(trustListCommand,
|
|
26553
|
+
list.action(async (_options, command30) => {
|
|
26554
|
+
process.exitCode = await runCapability(trustListCommand, command30, { positionalRoot: false });
|
|
25770
26555
|
});
|
|
25771
26556
|
const pin = trust.command(trustPinCommand.name).description(trustPinCommand.summary).argument("<source>", "GitHub owner/repo trust source");
|
|
25772
26557
|
addSharedFlags(pin);
|
|
@@ -25774,8 +26559,8 @@ function registerCommands(program) {
|
|
|
25774
26559
|
if (o.default !== void 0) pin.option(o.flags, o.description, o.default);
|
|
25775
26560
|
else pin.option(o.flags, o.description);
|
|
25776
26561
|
}
|
|
25777
|
-
pin.action(async (source, _options,
|
|
25778
|
-
process.exitCode = await runCapability(trustPinCommand,
|
|
26562
|
+
pin.action(async (source, _options, command30) => {
|
|
26563
|
+
process.exitCode = await runCapability(trustPinCommand, command30, {
|
|
25779
26564
|
positionalRoot: false,
|
|
25780
26565
|
optionOverrides: { source }
|
|
25781
26566
|
});
|
|
@@ -25786,8 +26571,8 @@ function registerCommands(program) {
|
|
|
25786
26571
|
if (o.default !== void 0) scan.option(o.flags, o.description, o.default);
|
|
25787
26572
|
else scan.option(o.flags, o.description);
|
|
25788
26573
|
}
|
|
25789
|
-
scan.action(async (target, _options,
|
|
25790
|
-
process.exitCode = await runCapability(trustScanCommand,
|
|
26574
|
+
scan.action(async (target, _options, command30) => {
|
|
26575
|
+
process.exitCode = await runCapability(trustScanCommand, command30, {
|
|
25791
26576
|
positionalRoot: false,
|
|
25792
26577
|
optionOverrides: { target }
|
|
25793
26578
|
});
|
|
@@ -25799,8 +26584,8 @@ function registerCommands(program) {
|
|
|
25799
26584
|
else verify.option(o.flags, o.description);
|
|
25800
26585
|
}
|
|
25801
26586
|
verify.action(
|
|
25802
|
-
async (id, _options,
|
|
25803
|
-
process.exitCode = await runCapability(trustVerifyCommand,
|
|
26587
|
+
async (id, _options, command30) => {
|
|
26588
|
+
process.exitCode = await runCapability(trustVerifyCommand, command30, {
|
|
25804
26589
|
positionalRoot: false,
|
|
25805
26590
|
optionOverrides: { id }
|
|
25806
26591
|
});
|
|
@@ -25832,6 +26617,7 @@ export {
|
|
|
25832
26617
|
probeMany,
|
|
25833
26618
|
exec,
|
|
25834
26619
|
envBlock,
|
|
26620
|
+
remove,
|
|
25835
26621
|
plan,
|
|
25836
26622
|
stripTrailingNewlines,
|
|
25837
26623
|
lines,
|
|
@@ -25873,4 +26659,4 @@ export {
|
|
|
25873
26659
|
VERSION,
|
|
25874
26660
|
buildProgram
|
|
25875
26661
|
};
|
|
25876
|
-
//# sourceMappingURL=chunk-
|
|
26662
|
+
//# sourceMappingURL=chunk-7GVBGS5N.js.map
|