@iamu-dev/iamu 0.2.0-beta.6 → 0.2.0-beta.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +1685 -283
- package/dist/eval/gemini-eval-cli.js +976 -0
- package/dist/index.js +1681 -279
- package/dist/templates/prompts/walk.md +16 -4
- package/dist/templates/skills/iamu-new.md +11 -4
- package/dist/templates/skills/iamu-propose.md +7 -3
- package/dist/templates/skills/iamu-run.md +23 -8
- package/dist/templates/skills/iamu-setup.md +112 -62
- package/package.json +5 -2
- package/dist/commands/start-run.d.ts +0 -52
package/dist/index.js
CHANGED
|
@@ -823,7 +823,14 @@ var init_scenario = __esm({
|
|
|
823
823
|
reviews: z15.array(z15.string().min(1).max(64)).max(16).default([]),
|
|
824
824
|
baseline_score: z15.number().min(0).max(100).nullish(),
|
|
825
825
|
current_score: z15.number().min(0).max(100).nullish(),
|
|
826
|
-
target_score: z15.number().min(0).max(100).default(95)
|
|
826
|
+
target_score: z15.number().min(0).max(100).default(95),
|
|
827
|
+
/** Environment state preconditions and approximation guidance. */
|
|
828
|
+
state: z15.object({
|
|
829
|
+
/** Description of data/records expected to exist in the environment. */
|
|
830
|
+
description: z15.string().max(2048).nullish(),
|
|
831
|
+
/** Guidance on how to squint or approximate if exact records are absent. */
|
|
832
|
+
approximation: z15.string().max(2048).nullish()
|
|
833
|
+
}).nullish()
|
|
827
834
|
});
|
|
828
835
|
}
|
|
829
836
|
});
|
|
@@ -10480,9 +10487,275 @@ var init_prune = __esm({
|
|
|
10480
10487
|
}
|
|
10481
10488
|
});
|
|
10482
10489
|
|
|
10483
|
-
// ../../packages/local-store/dist/
|
|
10490
|
+
// ../../packages/local-store/dist/explore-map.js
|
|
10491
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
10484
10492
|
import { promises as fs19 } from "fs";
|
|
10485
10493
|
import path15 from "path";
|
|
10494
|
+
function templateToken(token) {
|
|
10495
|
+
if (token === "")
|
|
10496
|
+
return token;
|
|
10497
|
+
if (/^\d+$/.test(token))
|
|
10498
|
+
return ":id";
|
|
10499
|
+
if (UUID.test(token))
|
|
10500
|
+
return ":id";
|
|
10501
|
+
if (DATE.test(token))
|
|
10502
|
+
return ":date";
|
|
10503
|
+
if (/^[0-9a-f]{8,}$/i.test(token) && /\d/.test(token))
|
|
10504
|
+
return ":id";
|
|
10505
|
+
if (token.length >= 16 && /\d/.test(token) && /[a-z]/i.test(token)) {
|
|
10506
|
+
return ":id";
|
|
10507
|
+
}
|
|
10508
|
+
return token;
|
|
10509
|
+
}
|
|
10510
|
+
function templatePath(pathname) {
|
|
10511
|
+
const trimmed = pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname || "/";
|
|
10512
|
+
return trimmed.split("/").map((seg) => templateToken(decodeSegment(seg))).join("/");
|
|
10513
|
+
}
|
|
10514
|
+
function encodeQueryValue(v) {
|
|
10515
|
+
return v.startsWith(":") ? v : encodeURIComponent(v);
|
|
10516
|
+
}
|
|
10517
|
+
function decodeSegment(seg) {
|
|
10518
|
+
try {
|
|
10519
|
+
return decodeURIComponent(seg);
|
|
10520
|
+
} catch {
|
|
10521
|
+
return seg;
|
|
10522
|
+
}
|
|
10523
|
+
}
|
|
10524
|
+
function explorePlaceKey(raw) {
|
|
10525
|
+
if (!raw)
|
|
10526
|
+
return null;
|
|
10527
|
+
let u;
|
|
10528
|
+
try {
|
|
10529
|
+
u = new URL(raw);
|
|
10530
|
+
} catch {
|
|
10531
|
+
return null;
|
|
10532
|
+
}
|
|
10533
|
+
if (u.protocol !== "http:" && u.protocol !== "https:")
|
|
10534
|
+
return null;
|
|
10535
|
+
const params = [...u.searchParams.entries()].filter(([k]) => !k.toLowerCase().startsWith("utm_")).map(([k, v]) => [k, templateToken(v)]).sort(([a, av], [b, bv]) => a.localeCompare(b) || av.localeCompare(bv));
|
|
10536
|
+
const query = params.map(([k, v]) => `${encodeURIComponent(k)}=${encodeQueryValue(v)}`).join("&");
|
|
10537
|
+
let key = templatePath(u.pathname);
|
|
10538
|
+
if (query)
|
|
10539
|
+
key += `?${query}`;
|
|
10540
|
+
const route = /^#!?(\/.*)$/.exec(u.hash);
|
|
10541
|
+
if (route) {
|
|
10542
|
+
const [hashPath] = route[1].split("?");
|
|
10543
|
+
key += `#${templatePath(hashPath)}`;
|
|
10544
|
+
}
|
|
10545
|
+
return key;
|
|
10546
|
+
}
|
|
10547
|
+
function exploreViewSignature(headings) {
|
|
10548
|
+
return headings.map((h) => h.trim().replace(/\s+/g, " ").replace(/\d+/g, "#").toLowerCase()).filter(Boolean).join(" | ");
|
|
10549
|
+
}
|
|
10550
|
+
function exploreSignature(headings, title) {
|
|
10551
|
+
return exploreViewSignature(headings) || title.trim().toLowerCase();
|
|
10552
|
+
}
|
|
10553
|
+
function matchExploreSurface(map2, url, signature) {
|
|
10554
|
+
const place = explorePlaceKey(url);
|
|
10555
|
+
if (!place)
|
|
10556
|
+
return null;
|
|
10557
|
+
const atPlace = map2.surfaces.filter((s) => s.place === place);
|
|
10558
|
+
return atPlace.find((s) => s.signature === signature) ?? (atPlace.length === 1 ? atPlace[0] : null);
|
|
10559
|
+
}
|
|
10560
|
+
function exploreSurfaceKey(place, view) {
|
|
10561
|
+
return view === null ? place : `${place}~${fnv1a(view)}`;
|
|
10562
|
+
}
|
|
10563
|
+
function fnv1a(s) {
|
|
10564
|
+
let h = 2166136261;
|
|
10565
|
+
for (let i = 0; i < s.length; i++) {
|
|
10566
|
+
h ^= s.charCodeAt(i);
|
|
10567
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
10568
|
+
}
|
|
10569
|
+
return h.toString(16).padStart(8, "0");
|
|
10570
|
+
}
|
|
10571
|
+
function exploreDir(projectRoot) {
|
|
10572
|
+
return path15.join(cacheDir(projectRoot), "explore");
|
|
10573
|
+
}
|
|
10574
|
+
function composeExploreId(now, randomHex) {
|
|
10575
|
+
const ts = now.toISOString().replace(/[:.]/g, "-").replace(/Z$/, "");
|
|
10576
|
+
return `${ts}-${randomHex ?? randomBytes2(3).toString("hex")}`;
|
|
10577
|
+
}
|
|
10578
|
+
function isValidExploreId(id) {
|
|
10579
|
+
return MAP_ID.test(id);
|
|
10580
|
+
}
|
|
10581
|
+
function mapPath(projectRoot, id) {
|
|
10582
|
+
if (!isValidExploreId(id))
|
|
10583
|
+
throw new Error(`Not an explore map id: ${id}`);
|
|
10584
|
+
return path15.join(exploreDir(projectRoot), id, "map.json");
|
|
10585
|
+
}
|
|
10586
|
+
async function writeExploreMap(projectRoot, map2) {
|
|
10587
|
+
const file = mapPath(projectRoot, map2.id);
|
|
10588
|
+
await fs19.mkdir(path15.dirname(file), { recursive: true });
|
|
10589
|
+
await atomicWriteFile(file, `${JSON.stringify(map2, null, 2)}
|
|
10590
|
+
`);
|
|
10591
|
+
return file;
|
|
10592
|
+
}
|
|
10593
|
+
async function readExploreMap(projectRoot, id) {
|
|
10594
|
+
const raw = await fs19.readFile(mapPath(projectRoot, id), "utf8");
|
|
10595
|
+
const map2 = JSON.parse(raw);
|
|
10596
|
+
if (map2?.schema !== EXPLORE_MAP_SCHEMA || !Array.isArray(map2.surfaces)) {
|
|
10597
|
+
throw new Error(`Unreadable explore map: ${id}`);
|
|
10598
|
+
}
|
|
10599
|
+
return map2;
|
|
10600
|
+
}
|
|
10601
|
+
async function listExploreMaps(projectRoot) {
|
|
10602
|
+
try {
|
|
10603
|
+
const entries = await fs19.readdir(exploreDir(projectRoot), {
|
|
10604
|
+
withFileTypes: true
|
|
10605
|
+
});
|
|
10606
|
+
return entries.filter((e) => e.isDirectory() && isValidExploreId(e.name)).map((e) => e.name).sort();
|
|
10607
|
+
} catch (err) {
|
|
10608
|
+
if (err.code === "ENOENT")
|
|
10609
|
+
return [];
|
|
10610
|
+
throw err;
|
|
10611
|
+
}
|
|
10612
|
+
}
|
|
10613
|
+
function exploreCoverage(map2, steps) {
|
|
10614
|
+
const byPlace = /* @__PURE__ */ new Map();
|
|
10615
|
+
for (const s of map2.surfaces) {
|
|
10616
|
+
byPlace.set(s.place, [...byPlace.get(s.place) ?? [], s]);
|
|
10617
|
+
}
|
|
10618
|
+
const reachedBy = /* @__PURE__ */ new Map();
|
|
10619
|
+
const unresolvedBy = /* @__PURE__ */ new Map();
|
|
10620
|
+
const runOnlyBy = /* @__PURE__ */ new Map();
|
|
10621
|
+
const add = (m, k, s) => m.set(k, [...m.get(k) ?? [], s]);
|
|
10622
|
+
for (const step of steps) {
|
|
10623
|
+
const place = explorePlaceKey(step.url);
|
|
10624
|
+
if (!place)
|
|
10625
|
+
continue;
|
|
10626
|
+
const atPlace = byPlace.get(place);
|
|
10627
|
+
if (!atPlace) {
|
|
10628
|
+
add(runOnlyBy, place, step);
|
|
10629
|
+
continue;
|
|
10630
|
+
}
|
|
10631
|
+
const hit = tieStep(map2, atPlace, step);
|
|
10632
|
+
if (hit.length === 0)
|
|
10633
|
+
add(unresolvedBy, place, step);
|
|
10634
|
+
for (const s of hit)
|
|
10635
|
+
add(reachedBy, s.key, step);
|
|
10636
|
+
}
|
|
10637
|
+
const surfaces = map2.surfaces.map((s) => {
|
|
10638
|
+
const base = {
|
|
10639
|
+
key: s.key,
|
|
10640
|
+
label: s.label,
|
|
10641
|
+
place: s.place,
|
|
10642
|
+
url: s.url,
|
|
10643
|
+
depth: s.depth
|
|
10644
|
+
};
|
|
10645
|
+
const reached = reachedBy.get(s.key);
|
|
10646
|
+
if (reached)
|
|
10647
|
+
return { ...base, status: "reached", runs: distinctRuns(reached) };
|
|
10648
|
+
const unresolved = unresolvedBy.get(s.place);
|
|
10649
|
+
if (unresolved) {
|
|
10650
|
+
return { ...base, status: "place_only", runs: distinctRuns(unresolved) };
|
|
10651
|
+
}
|
|
10652
|
+
return { ...base, status: "unreached", runs: [] };
|
|
10653
|
+
});
|
|
10654
|
+
const runOnly = [...runOnlyBy].map(([place, list2]) => ({
|
|
10655
|
+
place,
|
|
10656
|
+
steps: list2.length,
|
|
10657
|
+
runs: distinctRuns(list2)
|
|
10658
|
+
}));
|
|
10659
|
+
runOnly.sort((a, b) => b.steps - a.steps || a.place.localeCompare(b.place));
|
|
10660
|
+
return {
|
|
10661
|
+
map_id: map2.id,
|
|
10662
|
+
runs: new Set(steps.map((s) => s.run_id)).size,
|
|
10663
|
+
steps: steps.length,
|
|
10664
|
+
reached: surfaces.filter((s) => s.status === "reached").length,
|
|
10665
|
+
place_only: surfaces.filter((s) => s.status === "place_only").length,
|
|
10666
|
+
unreached: surfaces.filter((s) => s.status === "unreached").length,
|
|
10667
|
+
surfaces,
|
|
10668
|
+
run_only_places: runOnly
|
|
10669
|
+
};
|
|
10670
|
+
}
|
|
10671
|
+
function tieStep(map2, atPlace, step) {
|
|
10672
|
+
if (typeof step.signature === "string") {
|
|
10673
|
+
const m = matchExploreSurface(map2, step.url, step.signature);
|
|
10674
|
+
return m ? [m] : [];
|
|
10675
|
+
}
|
|
10676
|
+
if (atPlace.length === 1)
|
|
10677
|
+
return [atPlace[0]];
|
|
10678
|
+
const name = step.surface?.trim();
|
|
10679
|
+
if (!name)
|
|
10680
|
+
return [];
|
|
10681
|
+
return atPlace.filter((s) => [s.label, ...s.headings].filter(Boolean).some((n) => surfaceMatches(n, name) || surfaceMatches(name, n)));
|
|
10682
|
+
}
|
|
10683
|
+
function distinctRuns(steps) {
|
|
10684
|
+
return [...new Set(steps.map((s) => s.run_id))].sort();
|
|
10685
|
+
}
|
|
10686
|
+
async function readAllRunSteps(projectRoot) {
|
|
10687
|
+
const out = [];
|
|
10688
|
+
for (const runId of await listRuns(projectRoot)) {
|
|
10689
|
+
let steps;
|
|
10690
|
+
try {
|
|
10691
|
+
steps = await readStepsForRun(projectRoot, runId);
|
|
10692
|
+
} catch {
|
|
10693
|
+
continue;
|
|
10694
|
+
}
|
|
10695
|
+
for (const s of steps) {
|
|
10696
|
+
out.push({ run_id: runId, url: s.url, surface: s.surface });
|
|
10697
|
+
}
|
|
10698
|
+
}
|
|
10699
|
+
return out;
|
|
10700
|
+
}
|
|
10701
|
+
function actionSignature(a) {
|
|
10702
|
+
const where = a.outcome.kind === "surface" ? a.outcome.surface : a.outcome.kind === "skipped" ? `skipped:${a.outcome.reason}` : a.outcome.kind === "external" ? `external:${a.outcome.url}` : a.outcome.kind;
|
|
10703
|
+
return `${a.role} "${a.name}" \u2192 ${where}`;
|
|
10704
|
+
}
|
|
10705
|
+
function diffExploreMaps(a, b) {
|
|
10706
|
+
const aByKey = new Map(a.surfaces.map((s) => [s.key, s]));
|
|
10707
|
+
const bByKey = new Map(b.surfaces.map((s) => [s.key, s]));
|
|
10708
|
+
const ref = (s) => ({
|
|
10709
|
+
key: s.key,
|
|
10710
|
+
label: s.label
|
|
10711
|
+
});
|
|
10712
|
+
const onlyA = a.surfaces.filter((s) => !bByKey.has(s.key)).map(ref);
|
|
10713
|
+
const onlyB = b.surfaces.filter((s) => !aByKey.has(s.key)).map(ref);
|
|
10714
|
+
const changed = [];
|
|
10715
|
+
let common2 = 0;
|
|
10716
|
+
for (const sa of a.surfaces) {
|
|
10717
|
+
const sb = bByKey.get(sa.key);
|
|
10718
|
+
if (!sb)
|
|
10719
|
+
continue;
|
|
10720
|
+
common2++;
|
|
10721
|
+
const setA = new Set(sa.actions.map(actionSignature));
|
|
10722
|
+
const setB = new Set(sb.actions.map(actionSignature));
|
|
10723
|
+
const inA = [...setA].filter((x) => !setB.has(x)).sort();
|
|
10724
|
+
const inB = [...setB].filter((x) => !setA.has(x)).sort();
|
|
10725
|
+
if (inA.length > 0 || inB.length > 0) {
|
|
10726
|
+
changed.push({ ...ref(sa), only_a: inA, only_b: inB });
|
|
10727
|
+
}
|
|
10728
|
+
}
|
|
10729
|
+
const union = common2 + onlyA.length + onlyB.length;
|
|
10730
|
+
return {
|
|
10731
|
+
a: a.id,
|
|
10732
|
+
b: b.id,
|
|
10733
|
+
common: common2,
|
|
10734
|
+
only_a: onlyA,
|
|
10735
|
+
only_b: onlyB,
|
|
10736
|
+
surface_overlap: union === 0 ? 1 : common2 / union,
|
|
10737
|
+
changed_actions: changed
|
|
10738
|
+
};
|
|
10739
|
+
}
|
|
10740
|
+
var EXPLORE_MAP_SCHEMA, UUID, DATE, MAP_ID;
|
|
10741
|
+
var init_explore_map = __esm({
|
|
10742
|
+
"../../packages/local-store/dist/explore-map.js"() {
|
|
10743
|
+
"use strict";
|
|
10744
|
+
init_atomic();
|
|
10745
|
+
init_paths();
|
|
10746
|
+
init_revisit();
|
|
10747
|
+
init_runs();
|
|
10748
|
+
init_walkthrough_canonical2();
|
|
10749
|
+
EXPLORE_MAP_SCHEMA = 1;
|
|
10750
|
+
UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
10751
|
+
DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
10752
|
+
MAP_ID = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}-[0-9a-f]{6}$/;
|
|
10753
|
+
}
|
|
10754
|
+
});
|
|
10755
|
+
|
|
10756
|
+
// ../../packages/local-store/dist/human-test-link.js
|
|
10757
|
+
import { promises as fs20 } from "fs";
|
|
10758
|
+
import path16 from "path";
|
|
10486
10759
|
function humanTestLinks(verdicts) {
|
|
10487
10760
|
const best = /* @__PURE__ */ new Map();
|
|
10488
10761
|
for (const v of verdicts) {
|
|
@@ -10505,7 +10778,7 @@ async function linkFindingsToHumanTests(projectRoot, runId, verdicts) {
|
|
|
10505
10778
|
const dir = entityDirPath(projectRoot, "finding");
|
|
10506
10779
|
let names;
|
|
10507
10780
|
try {
|
|
10508
|
-
names = await
|
|
10781
|
+
names = await fs20.readdir(dir);
|
|
10509
10782
|
} catch {
|
|
10510
10783
|
return [];
|
|
10511
10784
|
}
|
|
@@ -10513,11 +10786,11 @@ async function linkFindingsToHumanTests(projectRoot, runId, verdicts) {
|
|
|
10513
10786
|
for (const name of names) {
|
|
10514
10787
|
if (!name.endsWith(".md") || name.startsWith("."))
|
|
10515
10788
|
continue;
|
|
10516
|
-
const file =
|
|
10789
|
+
const file = path16.join(dir, name);
|
|
10517
10790
|
let raw;
|
|
10518
10791
|
let fm;
|
|
10519
10792
|
try {
|
|
10520
|
-
raw = await
|
|
10793
|
+
raw = await fs20.readFile(file, "utf8");
|
|
10521
10794
|
fm = parseFrontmatter(raw).frontmatter;
|
|
10522
10795
|
} catch {
|
|
10523
10796
|
continue;
|
|
@@ -10748,14 +11021,14 @@ var init_cloud_client = __esm({
|
|
|
10748
11021
|
});
|
|
10749
11022
|
|
|
10750
11023
|
// ../../packages/local-store/dist/last-pull.js
|
|
10751
|
-
import { promises as
|
|
10752
|
-
import
|
|
11024
|
+
import { promises as fs21 } from "fs";
|
|
11025
|
+
import path17 from "path";
|
|
10753
11026
|
function lastPullPath(projectRoot) {
|
|
10754
|
-
return
|
|
11027
|
+
return path17.join(cacheDir(projectRoot), "last-pull.json");
|
|
10755
11028
|
}
|
|
10756
11029
|
async function readLastPull(projectRoot) {
|
|
10757
11030
|
try {
|
|
10758
|
-
const raw = await
|
|
11031
|
+
const raw = await fs21.readFile(lastPullPath(projectRoot), "utf8");
|
|
10759
11032
|
const parsed = JSON.parse(raw);
|
|
10760
11033
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
10761
11034
|
return parsed;
|
|
@@ -11041,7 +11314,7 @@ var init_frontmatter2 = __esm({
|
|
|
11041
11314
|
});
|
|
11042
11315
|
|
|
11043
11316
|
// ../../packages/local-store/dist/pull/write.js
|
|
11044
|
-
import { promises as
|
|
11317
|
+
import { promises as fs22 } from "fs";
|
|
11045
11318
|
async function loadLocalIndex(projectRoot, type2) {
|
|
11046
11319
|
const byId = /* @__PURE__ */ new Map();
|
|
11047
11320
|
const bySlug = /* @__PURE__ */ new Map();
|
|
@@ -11070,7 +11343,7 @@ async function classifyExisting(projectRoot, type2, cloudId, localSlug, targetSl
|
|
|
11070
11343
|
if (localSlug !== targetSlug)
|
|
11071
11344
|
return "untracked";
|
|
11072
11345
|
const lastSynced = localIndex.lastPushById[cloudId];
|
|
11073
|
-
const bytes = await
|
|
11346
|
+
const bytes = await fs22.readFile(entityFilePath(projectRoot, type2, localSlug), "utf8");
|
|
11074
11347
|
return lastSynced && hashNormalizedContent(bytes) === lastSynced ? "fast_forward" : "diverged";
|
|
11075
11348
|
}
|
|
11076
11349
|
async function writeOrSkip(projectRoot, type2, cloudId, slug, frontmatter, body, localIndex, mode, cloudVersion) {
|
|
@@ -11131,13 +11404,13 @@ async function writeOrSkip(projectRoot, type2, cloudId, slug, frontmatter, body,
|
|
|
11131
11404
|
}
|
|
11132
11405
|
if (existingSlugForId && existingSlugForId !== slug) {
|
|
11133
11406
|
try {
|
|
11134
|
-
await
|
|
11407
|
+
await fs22.unlink(entityFilePath(projectRoot, type2, existingSlugForId));
|
|
11135
11408
|
localIndex.bySlug.delete(existingSlugForId);
|
|
11136
11409
|
} catch {
|
|
11137
11410
|
}
|
|
11138
11411
|
}
|
|
11139
11412
|
await writeEntity(projectRoot, type2, frontmatter, text);
|
|
11140
|
-
const bytes = await
|
|
11413
|
+
const bytes = await fs22.readFile(entityFilePath(projectRoot, type2, slug), "utf8");
|
|
11141
11414
|
const hash = hashNormalizedContent(bytes);
|
|
11142
11415
|
localIndex.byId.set(cloudId, slug);
|
|
11143
11416
|
localIndex.bySlug.set(slug, cloudId);
|
|
@@ -11191,7 +11464,7 @@ var init_write = __esm({
|
|
|
11191
11464
|
});
|
|
11192
11465
|
|
|
11193
11466
|
// ../../packages/local-store/dist/pull/entities.js
|
|
11194
|
-
import { promises as
|
|
11467
|
+
import { promises as fs23 } from "fs";
|
|
11195
11468
|
function buildContextFrontmatter(payload) {
|
|
11196
11469
|
const cloudCtx = payload.context ?? {};
|
|
11197
11470
|
const glossary = (payload.glossary ?? []).map((g) => ({
|
|
@@ -11246,7 +11519,7 @@ async function pullContext(projectRoot, cloud, projectId, mode, result) {
|
|
|
11246
11519
|
const frontmatter = buildContextFrontmatter(payload);
|
|
11247
11520
|
let hasLocal = false;
|
|
11248
11521
|
try {
|
|
11249
|
-
await
|
|
11522
|
+
await fs23.access(contextPath(projectRoot));
|
|
11250
11523
|
hasLocal = true;
|
|
11251
11524
|
} catch {
|
|
11252
11525
|
hasLocal = false;
|
|
@@ -11276,7 +11549,7 @@ async function pullContext(projectRoot, cloud, projectId, mode, result) {
|
|
|
11276
11549
|
frontmatter,
|
|
11277
11550
|
notes_body: existingNotes
|
|
11278
11551
|
});
|
|
11279
|
-
const bytes = await
|
|
11552
|
+
const bytes = await fs23.readFile(contextPath(projectRoot), "utf8");
|
|
11280
11553
|
const hash = hashNormalizedContent(bytes);
|
|
11281
11554
|
await recordLastPull(projectRoot, "context:root", hash);
|
|
11282
11555
|
await recordLastPush(projectRoot, "context:root", hash);
|
|
@@ -11806,6 +12079,7 @@ var init_dist2 = __esm({
|
|
|
11806
12079
|
init_prune();
|
|
11807
12080
|
init_fix_state();
|
|
11808
12081
|
init_revisit();
|
|
12082
|
+
init_explore_map();
|
|
11809
12083
|
init_walkthrough_canonical2();
|
|
11810
12084
|
init_journey_diff();
|
|
11811
12085
|
init_finding_match();
|
|
@@ -11910,15 +12184,15 @@ function resolveApiUrl(configured) {
|
|
|
11910
12184
|
assertSecureApiUrl(url, configured ? "API URL" : "IAMU_API_URL");
|
|
11911
12185
|
return url;
|
|
11912
12186
|
}
|
|
11913
|
-
function parseStore(raw,
|
|
12187
|
+
function parseStore(raw, path30) {
|
|
11914
12188
|
let parsed;
|
|
11915
12189
|
try {
|
|
11916
12190
|
parsed = JSON.parse(raw);
|
|
11917
12191
|
} catch (err) {
|
|
11918
|
-
throw new CredentialsFileError(
|
|
12192
|
+
throw new CredentialsFileError(path30, "not valid JSON", err);
|
|
11919
12193
|
}
|
|
11920
12194
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
11921
|
-
throw new CredentialsFileError(
|
|
12195
|
+
throw new CredentialsFileError(path30, "expected a JSON object");
|
|
11922
12196
|
}
|
|
11923
12197
|
return parsed;
|
|
11924
12198
|
}
|
|
@@ -11926,26 +12200,26 @@ function isNotFound(err) {
|
|
|
11926
12200
|
return typeof err === "object" && err !== null && err.code === "ENOENT";
|
|
11927
12201
|
}
|
|
11928
12202
|
async function readCredentialsStore() {
|
|
11929
|
-
const
|
|
12203
|
+
const path30 = getCredentialsPath();
|
|
11930
12204
|
let raw;
|
|
11931
12205
|
try {
|
|
11932
|
-
raw = await readFile(
|
|
12206
|
+
raw = await readFile(path30, "utf-8");
|
|
11933
12207
|
} catch (err) {
|
|
11934
12208
|
if (isNotFound(err)) return {};
|
|
11935
12209
|
warnUnreadableStoreOnce(err);
|
|
11936
12210
|
throw err;
|
|
11937
12211
|
}
|
|
11938
12212
|
try {
|
|
11939
|
-
return parseStore(raw,
|
|
12213
|
+
return parseStore(raw, path30);
|
|
11940
12214
|
} catch (err) {
|
|
11941
12215
|
warnUnreadableStoreOnce(err);
|
|
11942
12216
|
throw err;
|
|
11943
12217
|
}
|
|
11944
12218
|
}
|
|
11945
12219
|
async function writeCredentialsStore(store) {
|
|
11946
|
-
const
|
|
11947
|
-
await mkdir(dirname(
|
|
11948
|
-
await atomicWriteFile(
|
|
12220
|
+
const path30 = getCredentialsPath();
|
|
12221
|
+
await mkdir(dirname(path30), { recursive: true, mode: 448 });
|
|
12222
|
+
await atomicWriteFile(path30, `${JSON.stringify(store, null, 2)}
|
|
11949
12223
|
`, {
|
|
11950
12224
|
mode: 384
|
|
11951
12225
|
});
|
|
@@ -11965,16 +12239,16 @@ function resolveApiKey(apiUrl) {
|
|
|
11965
12239
|
} catch {
|
|
11966
12240
|
return null;
|
|
11967
12241
|
}
|
|
11968
|
-
const
|
|
12242
|
+
const path30 = getCredentialsPath();
|
|
11969
12243
|
let raw;
|
|
11970
12244
|
try {
|
|
11971
|
-
raw = readFileSync(
|
|
12245
|
+
raw = readFileSync(path30, "utf-8");
|
|
11972
12246
|
} catch (err) {
|
|
11973
12247
|
if (!isNotFound(err)) warnUnreadableStoreOnce(err);
|
|
11974
12248
|
return null;
|
|
11975
12249
|
}
|
|
11976
12250
|
try {
|
|
11977
|
-
const entry = parseStore(raw,
|
|
12251
|
+
const entry = parseStore(raw, path30)[url];
|
|
11978
12252
|
if (typeof entry !== "object" || entry === null) return null;
|
|
11979
12253
|
const token = entry.token;
|
|
11980
12254
|
return typeof token === "string" && token ? token : null;
|
|
@@ -12013,12 +12287,12 @@ var init_credentials = __esm({
|
|
|
12013
12287
|
* @param reason - What is wrong with it.
|
|
12014
12288
|
* @param cause - Underlying parse error, if any.
|
|
12015
12289
|
*/
|
|
12016
|
-
constructor(
|
|
12290
|
+
constructor(path30, reason, cause) {
|
|
12017
12291
|
super(
|
|
12018
|
-
`Credentials file ${
|
|
12292
|
+
`Credentials file ${path30} is corrupt (${reason}). Fix it by hand, or delete it and run \`iamu auth login\` again.`,
|
|
12019
12293
|
{
|
|
12020
12294
|
cause,
|
|
12021
|
-
suggestedAction: `Delete ${
|
|
12295
|
+
suggestedAction: `Delete ${path30} and run \`iamu auth login\`, or set IAMU_TOKEN.`
|
|
12022
12296
|
}
|
|
12023
12297
|
);
|
|
12024
12298
|
}
|
|
@@ -12304,15 +12578,6 @@ function mcpJson(cmd) {
|
|
|
12304
12578
|
2
|
|
12305
12579
|
);
|
|
12306
12580
|
}
|
|
12307
|
-
function vscodeMcpJson(cmd) {
|
|
12308
|
-
return JSON.stringify(
|
|
12309
|
-
{
|
|
12310
|
-
servers: { iamu: { type: "stdio", command: cmd[0], args: cmd.slice(1) } }
|
|
12311
|
-
},
|
|
12312
|
-
null,
|
|
12313
|
-
2
|
|
12314
|
-
);
|
|
12315
|
-
}
|
|
12316
12581
|
function detectHosts(projectRoot) {
|
|
12317
12582
|
return HOST_ADAPTERS.filter(
|
|
12318
12583
|
(host) => host.detectionPaths.some((p) => existsSync3(join3(projectRoot, p)))
|
|
@@ -12361,13 +12626,17 @@ var init_registry = __esm({
|
|
|
12361
12626
|
{
|
|
12362
12627
|
name: "GitHub Copilot",
|
|
12363
12628
|
value: "github-copilot",
|
|
12364
|
-
detectionPaths: [
|
|
12365
|
-
|
|
12366
|
-
|
|
12367
|
-
|
|
12368
|
-
|
|
12369
|
-
|
|
12370
|
-
|
|
12629
|
+
detectionPaths: [
|
|
12630
|
+
".github/copilot-instructions.md",
|
|
12631
|
+
".github/skills",
|
|
12632
|
+
".copilot"
|
|
12633
|
+
],
|
|
12634
|
+
supportsSkills: true,
|
|
12635
|
+
getSkillsDir: (root) => join3(root, ".github", "skills"),
|
|
12636
|
+
getCommandPath: (root, id) => join3(root, ".github", "skills", `iamu-${id}`, "SKILL.md"),
|
|
12637
|
+
formatCommand: richFormat,
|
|
12638
|
+
getMcpConfigPath: (root) => join3(root, ".mcp.json"),
|
|
12639
|
+
formatMcpConfig: mcpJson
|
|
12371
12640
|
},
|
|
12372
12641
|
{
|
|
12373
12642
|
name: "Cline",
|
|
@@ -12542,9 +12811,17 @@ async function needsWrite(filePath, content) {
|
|
|
12542
12811
|
return true;
|
|
12543
12812
|
}
|
|
12544
12813
|
}
|
|
12545
|
-
function isCliWrittenSkill(
|
|
12814
|
+
function isCliWrittenSkill(path30) {
|
|
12815
|
+
try {
|
|
12816
|
+
return /^generatedBy:/m.test(readFileSync3(path30, "utf-8"));
|
|
12817
|
+
} catch {
|
|
12818
|
+
return false;
|
|
12819
|
+
}
|
|
12820
|
+
}
|
|
12821
|
+
function isCliWrittenPrompt(path30) {
|
|
12546
12822
|
try {
|
|
12547
|
-
|
|
12823
|
+
const content = readFileSync3(path30, "utf-8");
|
|
12824
|
+
return content.includes("iamu") || /^generatedBy:/m.test(content);
|
|
12548
12825
|
} catch {
|
|
12549
12826
|
return false;
|
|
12550
12827
|
}
|
|
@@ -12555,8 +12832,8 @@ function planSkillFiles(hosts, projectRoot, version) {
|
|
|
12555
12832
|
for (const host of hosts) {
|
|
12556
12833
|
if (pluginProvidesSkills(host, projectRoot)) {
|
|
12557
12834
|
for (const name of skillTemplates.keys()) {
|
|
12558
|
-
const
|
|
12559
|
-
if (isCliWrittenSkill(
|
|
12835
|
+
const path30 = join4(host.getSkillsDir(projectRoot), name, "SKILL.md");
|
|
12836
|
+
if (isCliWrittenSkill(path30)) files.push({ host, path: path30, content: null });
|
|
12560
12837
|
}
|
|
12561
12838
|
} else if (host.supportsSkills) {
|
|
12562
12839
|
const skillsDir = host.getSkillsDir(projectRoot);
|
|
@@ -12577,6 +12854,19 @@ function planSkillFiles(hosts, projectRoot, version) {
|
|
|
12577
12854
|
}
|
|
12578
12855
|
}
|
|
12579
12856
|
}
|
|
12857
|
+
if (host.value === "github-copilot") {
|
|
12858
|
+
for (const cmd of commandsFromSkills(skillTemplates)) {
|
|
12859
|
+
const legacyPrompt = join4(
|
|
12860
|
+
projectRoot,
|
|
12861
|
+
".github",
|
|
12862
|
+
"prompts",
|
|
12863
|
+
`iamu-${cmd.id}.prompt.md`
|
|
12864
|
+
);
|
|
12865
|
+
if (isCliWrittenPrompt(legacyPrompt)) {
|
|
12866
|
+
files.push({ host, path: legacyPrompt, content: null });
|
|
12867
|
+
}
|
|
12868
|
+
}
|
|
12869
|
+
}
|
|
12580
12870
|
} else {
|
|
12581
12871
|
for (const cmd of commandsFromSkills(skillTemplates)) {
|
|
12582
12872
|
files.push({
|
|
@@ -12601,6 +12891,15 @@ function detectInstalledHosts(projectRoot) {
|
|
|
12601
12891
|
const skillDir = join4(host.getSkillsDir(projectRoot), "iamu-setup");
|
|
12602
12892
|
if (existsSync5(skillDir)) return true;
|
|
12603
12893
|
}
|
|
12894
|
+
if (host.value === "github-copilot") {
|
|
12895
|
+
const legacyPrompt = join4(
|
|
12896
|
+
projectRoot,
|
|
12897
|
+
".github",
|
|
12898
|
+
"prompts",
|
|
12899
|
+
"iamu-setup.prompt.md"
|
|
12900
|
+
);
|
|
12901
|
+
if (existsSync5(legacyPrompt)) return true;
|
|
12902
|
+
}
|
|
12604
12903
|
return false;
|
|
12605
12904
|
});
|
|
12606
12905
|
}
|
|
@@ -14054,7 +14353,7 @@ import {
|
|
|
14054
14353
|
readlinkSync,
|
|
14055
14354
|
realpathSync as realpathSync2
|
|
14056
14355
|
} from "fs";
|
|
14057
|
-
import
|
|
14356
|
+
import path18 from "path";
|
|
14058
14357
|
function git(args, cwd) {
|
|
14059
14358
|
return execFileSync("git", args, {
|
|
14060
14359
|
cwd,
|
|
@@ -14107,8 +14406,8 @@ function detectWorktreeState(projectRoot, iamuDirName = IAMU_DIR) {
|
|
|
14107
14406
|
const top = git(["rev-parse", "--show-toplevel"], projectRoot).toString("utf8").trim();
|
|
14108
14407
|
if (!top) return void 0;
|
|
14109
14408
|
const realTop = realpathSync2(top);
|
|
14110
|
-
const realIamu =
|
|
14111
|
-
const iamuRel =
|
|
14409
|
+
const realIamu = path18.join(realpathSync2(projectRoot), iamuDirName);
|
|
14410
|
+
const iamuRel = path18.relative(realTop, realIamu).split(path18.sep).join("/");
|
|
14112
14411
|
const pathspec = iamuRel.startsWith("..") ? [":(top)"] : [":(top)", `:(top,exclude,literal)${iamuRel}`];
|
|
14113
14412
|
const status2 = git(
|
|
14114
14413
|
[
|
|
@@ -14138,7 +14437,7 @@ function detectWorktreeState(projectRoot, iamuDirName = IAMU_DIR) {
|
|
|
14138
14437
|
);
|
|
14139
14438
|
for (const file of sorted.slice(0, MAX_UNTRACKED_HASHED)) {
|
|
14140
14439
|
const rel = file.replace(/\/$/, "");
|
|
14141
|
-
hash.update(`${file}\0${hashFile(
|
|
14440
|
+
hash.update(`${file}\0${hashFile(path18.join(realTop, rel))}
|
|
14142
14441
|
`);
|
|
14143
14442
|
}
|
|
14144
14443
|
if (sorted.length > MAX_UNTRACKED_HASHED) {
|
|
@@ -14311,7 +14610,7 @@ __export(start_run_exports, {
|
|
|
14311
14610
|
resolveStartUrl: () => resolveStartUrl,
|
|
14312
14611
|
startRun: () => startRun
|
|
14313
14612
|
});
|
|
14314
|
-
import
|
|
14613
|
+
import path19 from "path";
|
|
14315
14614
|
import chalk11 from "chalk";
|
|
14316
14615
|
function resolveStartUrl(target, scenario) {
|
|
14317
14616
|
const base = target ?? scenario.target_url ?? null;
|
|
@@ -14487,7 +14786,7 @@ async function startRun(scenarioSlug, opts) {
|
|
|
14487
14786
|
...viewport ? { viewport } : {}
|
|
14488
14787
|
};
|
|
14489
14788
|
await atomicWriteFile(
|
|
14490
|
-
|
|
14789
|
+
path19.join(run.absPath, "walkthrough.json"),
|
|
14491
14790
|
JSON.stringify(manifest, null, 2) + "\n"
|
|
14492
14791
|
);
|
|
14493
14792
|
if (opts.json) {
|
|
@@ -14495,7 +14794,7 @@ async function startRun(scenarioSlug, opts) {
|
|
|
14495
14794
|
JSON.stringify(
|
|
14496
14795
|
{
|
|
14497
14796
|
run_id: run.runId,
|
|
14498
|
-
path:
|
|
14797
|
+
path: path19.relative(projectRoot, run.absPath),
|
|
14499
14798
|
scenario_slug: scenarioSlug,
|
|
14500
14799
|
target_url: start2.url,
|
|
14501
14800
|
target_reachable: targetReachable,
|
|
@@ -14543,7 +14842,7 @@ async function startRun(scenarioSlug, opts) {
|
|
|
14543
14842
|
);
|
|
14544
14843
|
}
|
|
14545
14844
|
console.log(
|
|
14546
|
-
` ${chalk11.dim("path:")} ${
|
|
14845
|
+
` ${chalk11.dim("path:")} ${path19.relative(projectRoot, run.absPath)}`
|
|
14547
14846
|
);
|
|
14548
14847
|
console.log();
|
|
14549
14848
|
console.log(` ${chalk11.bold("Next steps for blind execution:")}`);
|
|
@@ -14584,8 +14883,8 @@ var report_exports = {};
|
|
|
14584
14883
|
__export(report_exports, {
|
|
14585
14884
|
report: () => report
|
|
14586
14885
|
});
|
|
14587
|
-
import { promises as
|
|
14588
|
-
import
|
|
14886
|
+
import { promises as fs24 } from "fs";
|
|
14887
|
+
import path20 from "path";
|
|
14589
14888
|
import chalk12 from "chalk";
|
|
14590
14889
|
async function report(scenario, opts) {
|
|
14591
14890
|
const ctx = await resolveProject(opts);
|
|
@@ -14604,8 +14903,8 @@ async function report(scenario, opts) {
|
|
|
14604
14903
|
for (const rid of runs.reverse()) {
|
|
14605
14904
|
try {
|
|
14606
14905
|
const dir = runDir(projectRoot, rid);
|
|
14607
|
-
const mRaw = await
|
|
14608
|
-
|
|
14906
|
+
const mRaw = await fs24.readFile(
|
|
14907
|
+
path20.join(dir, "walkthrough.json"),
|
|
14609
14908
|
"utf8"
|
|
14610
14909
|
);
|
|
14611
14910
|
const manifest = JSON.parse(mRaw);
|
|
@@ -14613,8 +14912,8 @@ async function report(scenario, opts) {
|
|
|
14613
14912
|
if (scenario && manifest.scenario_slug !== scenario) continue;
|
|
14614
14913
|
let canonical = null;
|
|
14615
14914
|
try {
|
|
14616
|
-
const cRaw = await
|
|
14617
|
-
|
|
14915
|
+
const cRaw = await fs24.readFile(
|
|
14916
|
+
path20.join(dir, "canonical.json"),
|
|
14618
14917
|
"utf8"
|
|
14619
14918
|
);
|
|
14620
14919
|
canonical = JSON.parse(cRaw);
|
|
@@ -15891,6 +16190,35 @@ async function init(opts) {
|
|
|
15891
16190
|
""
|
|
15892
16191
|
)
|
|
15893
16192
|
);
|
|
16193
|
+
const testabilityPath = join5(iamuDir2, "testability.md");
|
|
16194
|
+
if (!existsSync7(testabilityPath)) {
|
|
16195
|
+
await writeFile3(
|
|
16196
|
+
testabilityPath,
|
|
16197
|
+
serializeFrontmatter(
|
|
16198
|
+
{
|
|
16199
|
+
status: "draft",
|
|
16200
|
+
target_url: "http://localhost:3000"
|
|
16201
|
+
},
|
|
16202
|
+
`# Project Testability & Environment
|
|
16203
|
+
|
|
16204
|
+
Target URL and execution harness for walkthroughs.
|
|
16205
|
+
Audited and populated during /iamu:init.
|
|
16206
|
+
|
|
16207
|
+
## Target & Run Instructions
|
|
16208
|
+
- Default URL: http://localhost:3000
|
|
16209
|
+
|
|
16210
|
+
## Reachable Surfaces
|
|
16211
|
+
(Run /iamu:init to discover testable routes)
|
|
16212
|
+
|
|
16213
|
+
## Mocks & Data Fixtures
|
|
16214
|
+
(Run /iamu:init to inspect existing harnesses)
|
|
16215
|
+
|
|
16216
|
+
## Untestable Boundaries
|
|
16217
|
+
(None documented yet)
|
|
16218
|
+
`
|
|
16219
|
+
)
|
|
16220
|
+
);
|
|
16221
|
+
}
|
|
15894
16222
|
}
|
|
15895
16223
|
const promptTemplates = loadPromptTemplates();
|
|
15896
16224
|
const promptsDir = join5(iamuDir2, ".prompts");
|
|
@@ -16320,7 +16648,10 @@ var init_browse_actions = __esm({
|
|
|
16320
16648
|
usage: "goto <url>",
|
|
16321
16649
|
minArgs: 1,
|
|
16322
16650
|
run: async (p, args) => {
|
|
16323
|
-
await p.goto(args[0], {
|
|
16651
|
+
await p.goto(args[0], {
|
|
16652
|
+
waitUntil: "domcontentloaded",
|
|
16653
|
+
timeout: 1e4
|
|
16654
|
+
});
|
|
16324
16655
|
}
|
|
16325
16656
|
},
|
|
16326
16657
|
click: {
|
|
@@ -16481,6 +16812,12 @@ var init_browse_driver = __esm({
|
|
|
16481
16812
|
commandLine: (pid) => readProcessCommandLine(pid),
|
|
16482
16813
|
kill(pid) {
|
|
16483
16814
|
try {
|
|
16815
|
+
if (process.platform !== "win32") {
|
|
16816
|
+
try {
|
|
16817
|
+
process.kill(-pid, "SIGTERM");
|
|
16818
|
+
} catch {
|
|
16819
|
+
}
|
|
16820
|
+
}
|
|
16484
16821
|
process.kill(pid, "SIGTERM");
|
|
16485
16822
|
} catch {
|
|
16486
16823
|
}
|
|
@@ -16491,7 +16828,7 @@ var init_browse_driver = __esm({
|
|
|
16491
16828
|
|
|
16492
16829
|
// src/commands/step-numbering.ts
|
|
16493
16830
|
import { open, readdir, unlink } from "fs/promises";
|
|
16494
|
-
import
|
|
16831
|
+
import path21 from "path";
|
|
16495
16832
|
function stepFileName(stepNumber2) {
|
|
16496
16833
|
return `${String(stepNumber2).padStart(3, "0")}.json`;
|
|
16497
16834
|
}
|
|
@@ -16513,7 +16850,7 @@ async function reserveStepFile(stepsDir, limit) {
|
|
|
16513
16850
|
if (candidate > limit.maxSteps) {
|
|
16514
16851
|
throw new Error(stepLimitMessage(limit.maxSteps, limit.runId));
|
|
16515
16852
|
}
|
|
16516
|
-
const stepPath =
|
|
16853
|
+
const stepPath = path21.join(stepsDir, stepFileName(candidate));
|
|
16517
16854
|
try {
|
|
16518
16855
|
const handle = await open(stepPath, "wx");
|
|
16519
16856
|
await handle.close();
|
|
@@ -16545,7 +16882,7 @@ __export(step_exports, {
|
|
|
16545
16882
|
stepCommand: () => stepCommand
|
|
16546
16883
|
});
|
|
16547
16884
|
import { copyFile, mkdir as mkdir5, stat } from "fs/promises";
|
|
16548
|
-
import
|
|
16885
|
+
import path22 from "path";
|
|
16549
16886
|
import chalk19 from "chalk";
|
|
16550
16887
|
function parseMaxSteps(val) {
|
|
16551
16888
|
if (typeof val === "number" && Number.isFinite(val) && val > 0) return val;
|
|
@@ -16564,8 +16901,8 @@ async function recordStep(input) {
|
|
|
16564
16901
|
if (!runDirPath) {
|
|
16565
16902
|
throw new Error(`Run directory not found for run "${input.runId}"`);
|
|
16566
16903
|
}
|
|
16567
|
-
const stepsDir =
|
|
16568
|
-
const screenshotsDir =
|
|
16904
|
+
const stepsDir = path22.join(runDirPath, "steps");
|
|
16905
|
+
const screenshotsDir = path22.join(runDirPath, "screenshots");
|
|
16569
16906
|
await mkdir5(stepsDir, { recursive: true });
|
|
16570
16907
|
await mkdir5(screenshotsDir, { recursive: true });
|
|
16571
16908
|
const shotSource = input.shot ? await existingShot(input.shot) : null;
|
|
@@ -16579,11 +16916,11 @@ async function recordStep(input) {
|
|
|
16579
16916
|
let finalScreenshotPath = null;
|
|
16580
16917
|
if (shotSource) {
|
|
16581
16918
|
const destName = `step-${stepNumber2}.png`;
|
|
16582
|
-
finalScreenshotPath =
|
|
16583
|
-
if (
|
|
16919
|
+
finalScreenshotPath = path22.join(screenshotsDir, destName);
|
|
16920
|
+
if (path22.resolve(finalScreenshotPath) !== shotSource) {
|
|
16584
16921
|
await copyFile(shotSource, finalScreenshotPath);
|
|
16585
16922
|
}
|
|
16586
|
-
screenshotRef =
|
|
16923
|
+
screenshotRef = path22.join("screenshots", destName);
|
|
16587
16924
|
}
|
|
16588
16925
|
const parsedStep = runStepSchema.parse({
|
|
16589
16926
|
step_number: stepNumber2,
|
|
@@ -16608,7 +16945,7 @@ async function recordStep(input) {
|
|
|
16608
16945
|
}
|
|
16609
16946
|
}
|
|
16610
16947
|
async function existingShot(shot) {
|
|
16611
|
-
const resolved =
|
|
16948
|
+
const resolved = path22.resolve(shot);
|
|
16612
16949
|
const isFile = await stat(resolved).then((s) => s.isFile()).catch(() => false);
|
|
16613
16950
|
if (!isFile) {
|
|
16614
16951
|
throw new Error(
|
|
@@ -16650,7 +16987,7 @@ async function stepCommand(runArg, opts) {
|
|
|
16650
16987
|
JSON.stringify(
|
|
16651
16988
|
{
|
|
16652
16989
|
step_number: res.step.step_number,
|
|
16653
|
-
path:
|
|
16990
|
+
path: path22.relative(projectRoot, res.stepPath),
|
|
16654
16991
|
action: res.step.action,
|
|
16655
16992
|
note: res.step.note,
|
|
16656
16993
|
url: res.step.url,
|
|
@@ -16677,7 +17014,7 @@ async function stepCommand(runArg, opts) {
|
|
|
16677
17014
|
console.log(` ${chalk19.dim("shot:")} ${res.step.screenshot}`);
|
|
16678
17015
|
}
|
|
16679
17016
|
console.log(
|
|
16680
|
-
` ${chalk19.dim("file:")} ${
|
|
17017
|
+
` ${chalk19.dim("file:")} ${path22.relative(projectRoot, res.stepPath)}`
|
|
16681
17018
|
);
|
|
16682
17019
|
console.log();
|
|
16683
17020
|
} catch (err) {
|
|
@@ -16720,21 +17057,21 @@ import {
|
|
|
16720
17057
|
} from "fs";
|
|
16721
17058
|
import { rm as rm2 } from "fs/promises";
|
|
16722
17059
|
import { homedir as homedir3 } from "os";
|
|
16723
|
-
import
|
|
17060
|
+
import path23 from "path";
|
|
16724
17061
|
import chalk20 from "chalk";
|
|
16725
17062
|
function stateDir() {
|
|
16726
|
-
return
|
|
16727
|
-
process.env.IAMU_HOME ??
|
|
17063
|
+
return path23.join(
|
|
17064
|
+
process.env.IAMU_HOME ?? path23.join(homedir3(), ".iamu"),
|
|
16728
17065
|
"browse"
|
|
16729
17066
|
);
|
|
16730
17067
|
}
|
|
16731
17068
|
function profileDir() {
|
|
16732
|
-
return
|
|
17069
|
+
return path23.join(stateDir(), "profile");
|
|
16733
17070
|
}
|
|
16734
17071
|
function readBrowseState() {
|
|
16735
17072
|
try {
|
|
16736
17073
|
return JSON.parse(
|
|
16737
|
-
readFileSync6(
|
|
17074
|
+
readFileSync6(path23.join(stateDir(), "state.json"), "utf8")
|
|
16738
17075
|
);
|
|
16739
17076
|
} catch {
|
|
16740
17077
|
return null;
|
|
@@ -16742,7 +17079,7 @@ function readBrowseState() {
|
|
|
16742
17079
|
}
|
|
16743
17080
|
function writeState(state) {
|
|
16744
17081
|
mkdirSync(stateDir(), { recursive: true });
|
|
16745
|
-
writeFileSync(
|
|
17082
|
+
writeFileSync(path23.join(stateDir(), "state.json"), JSON.stringify(state));
|
|
16746
17083
|
}
|
|
16747
17084
|
function fail(message, opts) {
|
|
16748
17085
|
if (opts?.json) {
|
|
@@ -16805,7 +17142,7 @@ async function requestedViewport(opts) {
|
|
|
16805
17142
|
let manifest;
|
|
16806
17143
|
try {
|
|
16807
17144
|
manifest = JSON.parse(
|
|
16808
|
-
readFileSync6(
|
|
17145
|
+
readFileSync6(path23.join(dir, "walkthrough.json"), "utf8")
|
|
16809
17146
|
);
|
|
16810
17147
|
} catch {
|
|
16811
17148
|
fail(
|
|
@@ -16857,6 +17194,8 @@ function chromeArgs(opts, port, headless) {
|
|
|
16857
17194
|
`--window-size=${opts.width ?? "1280"},${opts.height ?? "900"}`,
|
|
16858
17195
|
"--no-first-run",
|
|
16859
17196
|
"--no-default-browser-check",
|
|
17197
|
+
"--password-store=basic",
|
|
17198
|
+
"--use-mock-keychain",
|
|
16860
17199
|
...headless ? ["--headless=new", "--disable-dev-shm-usage", "--disable-gpu"] : [],
|
|
16861
17200
|
...shouldDisableSandbox() ? ["--no-sandbox"] : [],
|
|
16862
17201
|
"about:blank"
|
|
@@ -16909,7 +17248,7 @@ async function stop(opts, driver) {
|
|
|
16909
17248
|
await driver.closeBrowser(state.port);
|
|
16910
17249
|
if (state.pid) await reapBrowser(state.pid, profileDir(), driver);
|
|
16911
17250
|
try {
|
|
16912
|
-
unlinkSync(
|
|
17251
|
+
unlinkSync(path23.join(stateDir(), "state.json"));
|
|
16913
17252
|
} catch {
|
|
16914
17253
|
}
|
|
16915
17254
|
report2(opts, { stopped: true }, [` ${chalk20.green("\u2713")} Browser stopped`]);
|
|
@@ -16975,8 +17314,8 @@ async function withPage(opts, driver, fn, target) {
|
|
|
16975
17314
|
let screenshot = null;
|
|
16976
17315
|
if (opts.shot) {
|
|
16977
17316
|
await waitForVisualStability(page);
|
|
16978
|
-
screenshot =
|
|
16979
|
-
mkdirSync(
|
|
17317
|
+
screenshot = path23.resolve(opts.shot);
|
|
17318
|
+
mkdirSync(path23.dirname(screenshot), { recursive: true });
|
|
16980
17319
|
await page.screenshot({ path: screenshot, fullPage: Boolean(opts.full) });
|
|
16981
17320
|
}
|
|
16982
17321
|
return { url: page.url(), title: await page.title(), screenshot };
|
|
@@ -17055,7 +17394,7 @@ function printStep(opts, record, projectRoot) {
|
|
|
17055
17394
|
if (opts.json) {
|
|
17056
17395
|
const doc = {
|
|
17057
17396
|
step_number: step.step_number,
|
|
17058
|
-
path:
|
|
17397
|
+
path: path23.relative(projectRoot, record.stepPath),
|
|
17059
17398
|
action: step.action,
|
|
17060
17399
|
note: step.note,
|
|
17061
17400
|
url: step.url,
|
|
@@ -17129,10 +17468,10 @@ async function browseStep(args, opts, driver) {
|
|
|
17129
17468
|
const runDirPath = resolveRunDir(ctx.projectRoot, opts.run);
|
|
17130
17469
|
if (!runDirPath) return;
|
|
17131
17470
|
const maxSteps = parseMaxSteps(opts.maxSteps);
|
|
17132
|
-
if (await peekNextStepNumber(
|
|
17471
|
+
if (await peekNextStepNumber(path23.join(runDirPath, "steps")) > maxSteps) {
|
|
17133
17472
|
return fail(stepLimitMessage(maxSteps, opts.run), opts);
|
|
17134
17473
|
}
|
|
17135
|
-
const pendingShot =
|
|
17474
|
+
const pendingShot = path23.join(
|
|
17136
17475
|
runDirPath,
|
|
17137
17476
|
"screenshots",
|
|
17138
17477
|
`.pending-${randomUUID7()}.png`
|
|
@@ -17203,11 +17542,14 @@ var doctor_exports = {};
|
|
|
17203
17542
|
__export(doctor_exports, {
|
|
17204
17543
|
checkCdpResponsive: () => checkCdpResponsive,
|
|
17205
17544
|
checkPortAvailable: () => checkPortAvailable,
|
|
17545
|
+
checkProjectHarnesses: () => checkProjectHarnesses,
|
|
17546
|
+
checkProjectTestabilityContract: () => checkProjectTestabilityContract,
|
|
17547
|
+
checkTargetHealth: () => checkTargetHealth,
|
|
17206
17548
|
doctor: () => doctor,
|
|
17207
17549
|
runDoctorChecks: () => runDoctorChecks
|
|
17208
17550
|
});
|
|
17209
17551
|
import { createServer } from "net";
|
|
17210
|
-
import { existsSync as existsSync10 } from "fs";
|
|
17552
|
+
import { existsSync as existsSync10, promises as fs25 } from "fs";
|
|
17211
17553
|
import { join as join7 } from "path";
|
|
17212
17554
|
import chalk21 from "chalk";
|
|
17213
17555
|
function checkPortAvailable(port) {
|
|
@@ -17485,11 +17827,186 @@ async function checkAgentIntegrations(projectRoot) {
|
|
|
17485
17827
|
}
|
|
17486
17828
|
return checks;
|
|
17487
17829
|
}
|
|
17488
|
-
async function
|
|
17830
|
+
async function checkTargetHealth(targetUrl) {
|
|
17831
|
+
const checks = [];
|
|
17832
|
+
let parsedUrl;
|
|
17833
|
+
try {
|
|
17834
|
+
parsedUrl = new URL(targetUrl);
|
|
17835
|
+
} catch {
|
|
17836
|
+
checks.push({
|
|
17837
|
+
category: "target",
|
|
17838
|
+
name: "Target URL Format",
|
|
17839
|
+
status: "error",
|
|
17840
|
+
message: `Invalid target URL: "${targetUrl}"`,
|
|
17841
|
+
remedy: "Provide a valid URL with protocol (e.g. http://localhost:3000)."
|
|
17842
|
+
});
|
|
17843
|
+
return checks;
|
|
17844
|
+
}
|
|
17845
|
+
try {
|
|
17846
|
+
const res = await fetch(targetUrl, {
|
|
17847
|
+
method: "GET",
|
|
17848
|
+
redirect: "manual",
|
|
17849
|
+
signal: AbortSignal.timeout(3e3)
|
|
17850
|
+
});
|
|
17851
|
+
if ([301, 302, 303, 307, 308].includes(res.status)) {
|
|
17852
|
+
const location = res.headers.get("location") || "";
|
|
17853
|
+
const isExternalAuth = /auth0\.com|okta\.com|clerk\.com|accounts\.google\.com|cognito|login\.microsoftonline\.com/i.test(
|
|
17854
|
+
location
|
|
17855
|
+
) || location.includes("/oauth");
|
|
17856
|
+
if (isExternalAuth) {
|
|
17857
|
+
let redirectHost = location;
|
|
17858
|
+
try {
|
|
17859
|
+
redirectHost = new URL(location, targetUrl).host;
|
|
17860
|
+
} catch {
|
|
17861
|
+
}
|
|
17862
|
+
checks.push({
|
|
17863
|
+
category: "target",
|
|
17864
|
+
name: "Authentication Boundary",
|
|
17865
|
+
status: "warn",
|
|
17866
|
+
message: `Target immediately redirects to external auth (${redirectHost})`,
|
|
17867
|
+
remedy: "Configure a dev auth bypass or prepare for interactive login handshake before walking."
|
|
17868
|
+
});
|
|
17869
|
+
return checks;
|
|
17870
|
+
}
|
|
17871
|
+
checks.push({
|
|
17872
|
+
category: "target",
|
|
17873
|
+
name: "Target Reachability",
|
|
17874
|
+
status: "ok",
|
|
17875
|
+
message: `HTTP ${res.status} redirect -> ${location}`
|
|
17876
|
+
});
|
|
17877
|
+
return checks;
|
|
17878
|
+
}
|
|
17879
|
+
if (res.status >= 500) {
|
|
17880
|
+
checks.push({
|
|
17881
|
+
category: "target",
|
|
17882
|
+
name: "Target Health",
|
|
17883
|
+
status: "error",
|
|
17884
|
+
message: `Target returned HTTP ${res.status} ${res.statusText || "Server Error"}`,
|
|
17885
|
+
remedy: "Check dev server logs for unhandled exceptions or missing environment variables."
|
|
17886
|
+
});
|
|
17887
|
+
return checks;
|
|
17888
|
+
}
|
|
17889
|
+
checks.push({
|
|
17890
|
+
category: "target",
|
|
17891
|
+
name: "Target Reachability",
|
|
17892
|
+
status: "ok",
|
|
17893
|
+
message: `HTTP ${res.status} OK (${parsedUrl.origin})`
|
|
17894
|
+
});
|
|
17895
|
+
} catch (err) {
|
|
17896
|
+
const message = err.message;
|
|
17897
|
+
checks.push({
|
|
17898
|
+
category: "target",
|
|
17899
|
+
name: "Target Reachability",
|
|
17900
|
+
status: "warn",
|
|
17901
|
+
message: `Cannot connect to ${targetUrl} (${message})`,
|
|
17902
|
+
remedy: "Ensure your dev server is running and listening on the specified port."
|
|
17903
|
+
});
|
|
17904
|
+
}
|
|
17905
|
+
return checks;
|
|
17906
|
+
}
|
|
17907
|
+
async function checkProjectHarnesses(projectRoot) {
|
|
17908
|
+
const checks = [];
|
|
17909
|
+
const pkgPath = join7(projectRoot, "package.json");
|
|
17910
|
+
if (!existsSync10(pkgPath)) return checks;
|
|
17911
|
+
try {
|
|
17912
|
+
const raw = await fs25.readFile(pkgPath, "utf8");
|
|
17913
|
+
const pkg = JSON.parse(raw);
|
|
17914
|
+
const scripts = pkg.scripts || {};
|
|
17915
|
+
const allDeps = {
|
|
17916
|
+
...pkg.dependencies || {},
|
|
17917
|
+
...pkg.devDependencies || {}
|
|
17918
|
+
};
|
|
17919
|
+
const devScripts = ["dev", "start", "dev:mock", "serve"].filter(
|
|
17920
|
+
(s) => s in scripts
|
|
17921
|
+
);
|
|
17922
|
+
if (devScripts.length > 0) {
|
|
17923
|
+
checks.push({
|
|
17924
|
+
category: "target",
|
|
17925
|
+
name: "Dev Server Script",
|
|
17926
|
+
status: "ok",
|
|
17927
|
+
message: `Available scripts: ${devScripts.map((s) => `pnpm ${s}`).join(", ")}`
|
|
17928
|
+
});
|
|
17929
|
+
} else {
|
|
17930
|
+
checks.push({
|
|
17931
|
+
category: "target",
|
|
17932
|
+
name: "Dev Server Script",
|
|
17933
|
+
status: "warn",
|
|
17934
|
+
message: "No standard dev script (dev, start, dev:mock) found in package.json",
|
|
17935
|
+
remedy: "Add a `dev` or `start` script to package.json to boot your app locally."
|
|
17936
|
+
});
|
|
17937
|
+
}
|
|
17938
|
+
if (allDeps.msw || existsSync10(join7(projectRoot, "src/mocks")) || existsSync10(join7(projectRoot, "mocks"))) {
|
|
17939
|
+
checks.push({
|
|
17940
|
+
category: "target",
|
|
17941
|
+
name: "Mock Harness (MSW)",
|
|
17942
|
+
status: "ok",
|
|
17943
|
+
message: "Mock Service Worker detected. API mocks can decouple walks from live backends."
|
|
17944
|
+
});
|
|
17945
|
+
}
|
|
17946
|
+
const seedScript = Object.keys(scripts).find(
|
|
17947
|
+
(s) => /seed|db:seed|fixtures/i.test(s)
|
|
17948
|
+
);
|
|
17949
|
+
if (seedScript) {
|
|
17950
|
+
checks.push({
|
|
17951
|
+
category: "target",
|
|
17952
|
+
name: "Database Seed",
|
|
17953
|
+
status: "ok",
|
|
17954
|
+
message: `Seed script detected (${seedScript}). Preconditions can be reset between runs.`
|
|
17955
|
+
});
|
|
17956
|
+
}
|
|
17957
|
+
} catch {
|
|
17958
|
+
}
|
|
17959
|
+
return checks;
|
|
17960
|
+
}
|
|
17961
|
+
async function checkProjectTestabilityContract(projectRoot) {
|
|
17962
|
+
const checks = [];
|
|
17963
|
+
const testabilityPath = join7(projectRoot, "iamu", "testability.md");
|
|
17964
|
+
if (!existsSync10(testabilityPath)) {
|
|
17965
|
+
checks.push({
|
|
17966
|
+
category: "target",
|
|
17967
|
+
name: "Testability Contract (testability.md)",
|
|
17968
|
+
status: "warn",
|
|
17969
|
+
message: "iamu/testability.md not found",
|
|
17970
|
+
remedy: "Run `/iamu:init` to audit your codebase and record testable routes, auth strategies, and mock harnesses."
|
|
17971
|
+
});
|
|
17972
|
+
return { checks };
|
|
17973
|
+
}
|
|
17974
|
+
try {
|
|
17975
|
+
const raw = await fs25.readFile(testabilityPath, "utf8");
|
|
17976
|
+
const { frontmatter } = parseFrontmatter(raw);
|
|
17977
|
+
const targetUrl = typeof frontmatter.target_url === "string" ? frontmatter.target_url : void 0;
|
|
17978
|
+
const details = [];
|
|
17979
|
+
if (targetUrl) details.push(`Target: ${targetUrl}`);
|
|
17980
|
+
if (frontmatter.dev_command) {
|
|
17981
|
+
details.push(`Command: ${frontmatter.dev_command}`);
|
|
17982
|
+
}
|
|
17983
|
+
if (frontmatter.auth_strategy) {
|
|
17984
|
+
details.push(`Auth: ${frontmatter.auth_strategy}`);
|
|
17985
|
+
}
|
|
17986
|
+
checks.push({
|
|
17987
|
+
category: "target",
|
|
17988
|
+
name: "Testability Contract (testability.md)",
|
|
17989
|
+
status: "ok",
|
|
17990
|
+
message: `Configured (${details.join(" \xB7 ") || "present"})`
|
|
17991
|
+
});
|
|
17992
|
+
return { checks, targetUrl };
|
|
17993
|
+
} catch (err) {
|
|
17994
|
+
checks.push({
|
|
17995
|
+
category: "target",
|
|
17996
|
+
name: "Testability Contract (testability.md)",
|
|
17997
|
+
status: "warn",
|
|
17998
|
+
message: `Unreadable testability contract: ${err.message}`,
|
|
17999
|
+
remedy: "Repair iamu/testability.md frontmatter."
|
|
18000
|
+
});
|
|
18001
|
+
return { checks };
|
|
18002
|
+
}
|
|
18003
|
+
}
|
|
18004
|
+
async function runDoctorChecks(projectRoot, opts) {
|
|
17489
18005
|
const checks = [
|
|
17490
18006
|
...checkRuntimeChecks(),
|
|
17491
18007
|
...await checkBrowserChecks()
|
|
17492
18008
|
];
|
|
18009
|
+
let effectiveTarget = opts?.target;
|
|
17493
18010
|
if (!projectRoot || !existsSync10(join7(projectRoot, "iamu"))) {
|
|
17494
18011
|
checks.push({
|
|
17495
18012
|
category: "project",
|
|
@@ -17509,6 +18026,15 @@ async function runDoctorChecks(projectRoot) {
|
|
|
17509
18026
|
checks.push(...await checkProjectConfiguration(projectRoot));
|
|
17510
18027
|
checks.push(...await checkProjectContextAndEntities(projectRoot));
|
|
17511
18028
|
checks.push(...await checkAgentIntegrations(projectRoot));
|
|
18029
|
+
checks.push(...await checkProjectHarnesses(projectRoot));
|
|
18030
|
+
const testability = await checkProjectTestabilityContract(projectRoot);
|
|
18031
|
+
checks.push(...testability.checks);
|
|
18032
|
+
if (!effectiveTarget && testability.targetUrl) {
|
|
18033
|
+
effectiveTarget = testability.targetUrl;
|
|
18034
|
+
}
|
|
18035
|
+
}
|
|
18036
|
+
if (effectiveTarget) {
|
|
18037
|
+
checks.push(...await checkTargetHealth(effectiveTarget));
|
|
17512
18038
|
}
|
|
17513
18039
|
const okCount = checks.filter((c) => c.status === "ok").length;
|
|
17514
18040
|
const warnCount = checks.filter((c) => c.status === "warn").length;
|
|
@@ -17525,7 +18051,7 @@ async function runDoctorChecks(projectRoot) {
|
|
|
17525
18051
|
}
|
|
17526
18052
|
async function doctor(opts) {
|
|
17527
18053
|
const projectRoot = findProjectRoot(process.cwd()) ?? void 0;
|
|
17528
|
-
const result = await runDoctorChecks(projectRoot);
|
|
18054
|
+
const result = await runDoctorChecks(projectRoot, { target: opts?.target });
|
|
17529
18055
|
if (opts?.json) {
|
|
17530
18056
|
console.log(JSON.stringify(result, null, 2));
|
|
17531
18057
|
if (!result.ok) process.exitCode = 1;
|
|
@@ -17539,7 +18065,8 @@ async function doctor(opts) {
|
|
|
17539
18065
|
{ key: "browser", title: "Browser Automation" },
|
|
17540
18066
|
{ key: "project", title: "Project Configuration" },
|
|
17541
18067
|
{ key: "agent", title: "Agent Integrations" },
|
|
17542
|
-
{ key: "cloud", title: "Cloud & Authentication" }
|
|
18068
|
+
{ key: "cloud", title: "Cloud & Authentication" },
|
|
18069
|
+
{ key: "target", title: "Target & Testability" }
|
|
17543
18070
|
];
|
|
17544
18071
|
for (const cat of categories) {
|
|
17545
18072
|
const catChecks = result.checks.filter((c) => c.category === cat.key);
|
|
@@ -18017,13 +18544,13 @@ __export(validate_exports, {
|
|
|
18017
18544
|
validate: () => validate
|
|
18018
18545
|
});
|
|
18019
18546
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
18020
|
-
import { promises as
|
|
18021
|
-
import
|
|
18547
|
+
import { promises as fs26 } from "fs";
|
|
18548
|
+
import path24 from "path";
|
|
18022
18549
|
import chalk23 from "chalk";
|
|
18023
18550
|
function inferTypeFromPath(filePath, projectRoot) {
|
|
18024
|
-
const rel =
|
|
18025
|
-
if (rel.startsWith("..") ||
|
|
18026
|
-
const segments = rel.split(
|
|
18551
|
+
const rel = path24.relative(path24.join(projectRoot, "iamu"), filePath);
|
|
18552
|
+
if (rel.startsWith("..") || path24.isAbsolute(rel)) return null;
|
|
18553
|
+
const segments = rel.split(path24.sep);
|
|
18027
18554
|
if (segments.length < 2) return null;
|
|
18028
18555
|
return TYPE_BY_ALIAS.get(segments[0]) ?? null;
|
|
18029
18556
|
}
|
|
@@ -18053,7 +18580,7 @@ function validateFile(filePath, raw, projectRoot) {
|
|
|
18053
18580
|
);
|
|
18054
18581
|
return result;
|
|
18055
18582
|
}
|
|
18056
|
-
if (
|
|
18583
|
+
if (path24.basename(filePath) === "context.yml") {
|
|
18057
18584
|
applyZodValidation(contextFrontmatterSchema, frontmatter, result);
|
|
18058
18585
|
const warnings = [];
|
|
18059
18586
|
const hasNotes = Boolean(
|
|
@@ -18113,17 +18640,17 @@ async function buildRepairLookups(projectRoot) {
|
|
|
18113
18640
|
}
|
|
18114
18641
|
}
|
|
18115
18642
|
const runScenario = /* @__PURE__ */ new Map();
|
|
18116
|
-
const runsDir2 =
|
|
18643
|
+
const runsDir2 = path24.join(projectRoot, "iamu", ".runs");
|
|
18117
18644
|
let runIds = [];
|
|
18118
18645
|
try {
|
|
18119
|
-
runIds = await
|
|
18646
|
+
runIds = await fs26.readdir(runsDir2);
|
|
18120
18647
|
} catch {
|
|
18121
18648
|
}
|
|
18122
18649
|
for (const runId of runIds) {
|
|
18123
18650
|
try {
|
|
18124
18651
|
const manifest = JSON.parse(
|
|
18125
|
-
await
|
|
18126
|
-
|
|
18652
|
+
await fs26.readFile(
|
|
18653
|
+
path24.join(runsDir2, runId, "walkthrough.json"),
|
|
18127
18654
|
"utf8"
|
|
18128
18655
|
)
|
|
18129
18656
|
);
|
|
@@ -18145,7 +18672,7 @@ async function fixFindings(projectRoot, results, dryRun) {
|
|
|
18145
18672
|
let raw;
|
|
18146
18673
|
let parsed;
|
|
18147
18674
|
try {
|
|
18148
|
-
raw = await
|
|
18675
|
+
raw = await fs26.readFile(result.path, "utf8");
|
|
18149
18676
|
parsed = parseFrontmatter(raw);
|
|
18150
18677
|
} catch {
|
|
18151
18678
|
continue;
|
|
@@ -18154,7 +18681,7 @@ async function fixFindings(projectRoot, results, dryRun) {
|
|
|
18154
18681
|
parsed.frontmatter,
|
|
18155
18682
|
{
|
|
18156
18683
|
...lookups,
|
|
18157
|
-
fileSlug:
|
|
18684
|
+
fileSlug: path24.basename(result.path, ".md"),
|
|
18158
18685
|
newId: randomUUID8,
|
|
18159
18686
|
body: parsed.body
|
|
18160
18687
|
}
|
|
@@ -18189,9 +18716,9 @@ async function validate(filePath, opts) {
|
|
|
18189
18716
|
if (process.exitCode === 1) return;
|
|
18190
18717
|
const results = [];
|
|
18191
18718
|
if (filePath) {
|
|
18192
|
-
const abs =
|
|
18719
|
+
const abs = path24.isAbsolute(filePath) ? filePath : path24.resolve(process.cwd(), filePath);
|
|
18193
18720
|
try {
|
|
18194
|
-
const raw = await
|
|
18721
|
+
const raw = await fs26.readFile(abs, "utf8");
|
|
18195
18722
|
results.push(validateFile(abs, raw, projectRoot));
|
|
18196
18723
|
} catch (err) {
|
|
18197
18724
|
commandError(
|
|
@@ -18201,24 +18728,24 @@ async function validate(filePath, opts) {
|
|
|
18201
18728
|
return;
|
|
18202
18729
|
}
|
|
18203
18730
|
} else if (opts.run) {
|
|
18204
|
-
const findingsDir =
|
|
18731
|
+
const findingsDir = path24.join(projectRoot, "iamu", ENTITY_DIRS.finding);
|
|
18205
18732
|
let names = [];
|
|
18206
18733
|
try {
|
|
18207
|
-
names = await
|
|
18734
|
+
names = await fs26.readdir(findingsDir);
|
|
18208
18735
|
} catch {
|
|
18209
18736
|
}
|
|
18210
18737
|
for (const name of names) {
|
|
18211
18738
|
if (!name.endsWith(".md")) continue;
|
|
18212
|
-
const abs =
|
|
18739
|
+
const abs = path24.join(findingsDir, name);
|
|
18213
18740
|
try {
|
|
18214
|
-
const raw = await
|
|
18741
|
+
const raw = await fs26.readFile(abs, "utf8");
|
|
18215
18742
|
const { frontmatter } = parseFrontmatter(raw);
|
|
18216
18743
|
if (frontmatter.run === opts.run) {
|
|
18217
18744
|
results.push(validateFile(abs, raw, projectRoot));
|
|
18218
18745
|
}
|
|
18219
18746
|
} catch {
|
|
18220
18747
|
results.push({
|
|
18221
|
-
path:
|
|
18748
|
+
path: path24.relative(projectRoot, abs),
|
|
18222
18749
|
valid: false,
|
|
18223
18750
|
errors: ["Cannot read file or parse frontmatter"]
|
|
18224
18751
|
});
|
|
@@ -18251,31 +18778,31 @@ async function validate(filePath, opts) {
|
|
|
18251
18778
|
} else {
|
|
18252
18779
|
const typesToCheck = typeFilter ? ENTITY_TYPES.filter((t) => typeFilter.has(t)) : ENTITY_TYPES;
|
|
18253
18780
|
for (const type2 of typesToCheck) {
|
|
18254
|
-
const iamuTypeDir =
|
|
18781
|
+
const iamuTypeDir = path24.join(projectRoot, "iamu", ENTITY_DIRS[type2]);
|
|
18255
18782
|
let names;
|
|
18256
18783
|
try {
|
|
18257
|
-
names = await
|
|
18784
|
+
names = await fs26.readdir(iamuTypeDir);
|
|
18258
18785
|
} catch {
|
|
18259
18786
|
continue;
|
|
18260
18787
|
}
|
|
18261
18788
|
for (const name of names) {
|
|
18262
18789
|
if (!name.endsWith(".md")) continue;
|
|
18263
|
-
const abs =
|
|
18790
|
+
const abs = path24.join(iamuTypeDir, name);
|
|
18264
18791
|
try {
|
|
18265
|
-
const raw = await
|
|
18792
|
+
const raw = await fs26.readFile(abs, "utf8");
|
|
18266
18793
|
results.push(validateFile(abs, raw, projectRoot));
|
|
18267
18794
|
} catch {
|
|
18268
18795
|
results.push({
|
|
18269
|
-
path:
|
|
18796
|
+
path: path24.relative(projectRoot, abs),
|
|
18270
18797
|
valid: false,
|
|
18271
18798
|
errors: ["Cannot read file"]
|
|
18272
18799
|
});
|
|
18273
18800
|
}
|
|
18274
18801
|
}
|
|
18275
18802
|
}
|
|
18276
|
-
const ctxPath =
|
|
18803
|
+
const ctxPath = path24.join(projectRoot, "iamu", "context.yml");
|
|
18277
18804
|
try {
|
|
18278
|
-
const raw = await
|
|
18805
|
+
const raw = await fs26.readFile(ctxPath, "utf8");
|
|
18279
18806
|
results.push(validateFile(ctxPath, raw, projectRoot));
|
|
18280
18807
|
} catch {
|
|
18281
18808
|
}
|
|
@@ -18294,19 +18821,19 @@ async function validate(filePath, opts) {
|
|
|
18294
18821
|
valid,
|
|
18295
18822
|
errors: results.filter((r) => !r.valid).flatMap(
|
|
18296
18823
|
(r) => r.errors.map((e) => ({
|
|
18297
|
-
path:
|
|
18824
|
+
path: path24.relative(projectRoot, r.path),
|
|
18298
18825
|
message: e
|
|
18299
18826
|
}))
|
|
18300
18827
|
),
|
|
18301
18828
|
warnings: results.filter((r) => r.warnings && r.warnings.length > 0).flatMap(
|
|
18302
18829
|
(r) => (r.warnings ?? []).map((w) => ({
|
|
18303
|
-
path:
|
|
18830
|
+
path: path24.relative(projectRoot, r.path),
|
|
18304
18831
|
message: w
|
|
18305
18832
|
}))
|
|
18306
18833
|
),
|
|
18307
18834
|
...opts.fix ? {
|
|
18308
18835
|
repaired: repaired.map((r) => ({
|
|
18309
|
-
path:
|
|
18836
|
+
path: path24.relative(projectRoot, r.path),
|
|
18310
18837
|
changes: r.repairs,
|
|
18311
18838
|
valid: r.valid
|
|
18312
18839
|
}))
|
|
@@ -18328,7 +18855,7 @@ async function validate(filePath, opts) {
|
|
|
18328
18855
|
}
|
|
18329
18856
|
let hasErrors = false;
|
|
18330
18857
|
for (const r of results) {
|
|
18331
|
-
const relPath =
|
|
18858
|
+
const relPath = path24.relative(projectRoot, r.path);
|
|
18332
18859
|
if (r.valid) {
|
|
18333
18860
|
if (r.warnings && r.warnings.length > 0) {
|
|
18334
18861
|
console.log(` ${symbols.warning} ` + chalk23.yellow(relPath));
|
|
@@ -18533,13 +19060,21 @@ function defaultFrontmatter(type2, id, slug, name, opts) {
|
|
|
18533
19060
|
url: opts.url,
|
|
18534
19061
|
authority_level: opts.authority ?? "informational"
|
|
18535
19062
|
};
|
|
18536
|
-
case "scenario":
|
|
19063
|
+
case "scenario": {
|
|
19064
|
+
const stateObj = opts.stateDescription || opts.stateApproximation ? {
|
|
19065
|
+
state: {
|
|
19066
|
+
...opts.stateDescription ? { description: opts.stateDescription } : {},
|
|
19067
|
+
...opts.stateApproximation ? { approximation: opts.stateApproximation } : {}
|
|
19068
|
+
}
|
|
19069
|
+
} : {};
|
|
18537
19070
|
return {
|
|
18538
19071
|
...base,
|
|
18539
19072
|
outline: opts.outline ?? null,
|
|
18540
19073
|
persona: opts.persona ?? null,
|
|
18541
|
-
...opts.entry ? { entry_path: opts.entry } : {}
|
|
19074
|
+
...opts.entry ? { entry_path: opts.entry } : {},
|
|
19075
|
+
...stateObj
|
|
18542
19076
|
};
|
|
19077
|
+
}
|
|
18543
19078
|
default:
|
|
18544
19079
|
return base;
|
|
18545
19080
|
}
|
|
@@ -18634,34 +19169,825 @@ var init_new_entity = __esm({
|
|
|
18634
19169
|
}
|
|
18635
19170
|
});
|
|
18636
19171
|
|
|
18637
|
-
// src/commands/
|
|
18638
|
-
|
|
18639
|
-
|
|
18640
|
-
|
|
18641
|
-
}
|
|
18642
|
-
|
|
18643
|
-
|
|
18644
|
-
|
|
18645
|
-
|
|
18646
|
-
|
|
18647
|
-
|
|
18648
|
-
|
|
18649
|
-
|
|
18650
|
-
|
|
18651
|
-
|
|
18652
|
-
|
|
18653
|
-
|
|
18654
|
-
|
|
18655
|
-
|
|
18656
|
-
|
|
18657
|
-
|
|
18658
|
-
|
|
18659
|
-
|
|
18660
|
-
|
|
18661
|
-
}
|
|
18662
|
-
|
|
18663
|
-
|
|
18664
|
-
|
|
19172
|
+
// src/commands/explore-crawl.ts
|
|
19173
|
+
function candidateSig(c) {
|
|
19174
|
+
return `${c.role}
|
|
19175
|
+
${c.name}
|
|
19176
|
+
${c.occurrence}`;
|
|
19177
|
+
}
|
|
19178
|
+
function originOf(url) {
|
|
19179
|
+
try {
|
|
19180
|
+
return new URL(url).origin;
|
|
19181
|
+
} catch {
|
|
19182
|
+
return null;
|
|
19183
|
+
}
|
|
19184
|
+
}
|
|
19185
|
+
function skipReason(c, origin, deny) {
|
|
19186
|
+
if (deny.some((re) => re.test(c.name) || c.href && re.test(c.href))) {
|
|
19187
|
+
return "denied";
|
|
19188
|
+
}
|
|
19189
|
+
if (c.submit) return "submit";
|
|
19190
|
+
if (UNSAFE_NAME.test(c.name)) return "unsafe_name";
|
|
19191
|
+
if (c.href) {
|
|
19192
|
+
let u = null;
|
|
19193
|
+
try {
|
|
19194
|
+
u = new URL(c.href);
|
|
19195
|
+
} catch {
|
|
19196
|
+
}
|
|
19197
|
+
if (u && u.protocol !== "javascript:") {
|
|
19198
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") return "external";
|
|
19199
|
+
if (u.origin !== origin) return "external";
|
|
19200
|
+
if (UNSAFE_PATH.test(u.pathname)) return "unsafe_name";
|
|
19201
|
+
}
|
|
19202
|
+
if (c.download) return "download";
|
|
19203
|
+
}
|
|
19204
|
+
return null;
|
|
19205
|
+
}
|
|
19206
|
+
async function settle(page, settleMs) {
|
|
19207
|
+
await page.waitForLoadState("domcontentloaded", { timeout: NAV_TIMEOUT_MS }).catch(() => {
|
|
19208
|
+
});
|
|
19209
|
+
await page.waitForLoadState("networkidle", { timeout: settleMs }).catch(() => {
|
|
19210
|
+
});
|
|
19211
|
+
}
|
|
19212
|
+
async function readPage(page) {
|
|
19213
|
+
return page.evaluate(READ_PAGE);
|
|
19214
|
+
}
|
|
19215
|
+
async function markDocument(page) {
|
|
19216
|
+
await page.evaluate("window.__iamuDoc = true").catch(() => {
|
|
19217
|
+
});
|
|
19218
|
+
}
|
|
19219
|
+
async function isSameDocument(page) {
|
|
19220
|
+
return page.evaluate("window.__iamuDoc === true").catch(() => false);
|
|
19221
|
+
}
|
|
19222
|
+
async function clickCandidate(page, want) {
|
|
19223
|
+
const state = await readPage(page);
|
|
19224
|
+
const c = state.candidates.find(
|
|
19225
|
+
(x) => candidateSig(x) === candidateSig(want)
|
|
19226
|
+
);
|
|
19227
|
+
if (!c) return false;
|
|
19228
|
+
await page.locator(`[data-iamu-x="${c.index}"]`).click({ timeout: CLICK_TIMEOUT_MS });
|
|
19229
|
+
return true;
|
|
19230
|
+
}
|
|
19231
|
+
async function crawl(page, opts) {
|
|
19232
|
+
const origin = originOf(opts.startUrl);
|
|
19233
|
+
if (!origin) throw new Error(`Not a URL: ${opts.startUrl}`);
|
|
19234
|
+
let blocked = [];
|
|
19235
|
+
await page.route("**/*", (route) => {
|
|
19236
|
+
const req = route.request();
|
|
19237
|
+
if (SAFE_METHODS.has(req.method())) return route.continue();
|
|
19238
|
+
let where = req.url();
|
|
19239
|
+
try {
|
|
19240
|
+
const u = new URL(where);
|
|
19241
|
+
where = u.origin === origin ? u.pathname : `${u.origin}${u.pathname}`;
|
|
19242
|
+
} catch {
|
|
19243
|
+
}
|
|
19244
|
+
blocked.push(`${req.method()} ${where}`);
|
|
19245
|
+
return route.abort("blockedbyclient");
|
|
19246
|
+
});
|
|
19247
|
+
const onDialog = (d) => {
|
|
19248
|
+
d.dismiss().catch(() => {
|
|
19249
|
+
});
|
|
19250
|
+
};
|
|
19251
|
+
const onPopup = (p) => {
|
|
19252
|
+
if (p !== page) p.close().catch(() => {
|
|
19253
|
+
});
|
|
19254
|
+
};
|
|
19255
|
+
page.on("dialog", onDialog);
|
|
19256
|
+
page.context().on("page", onPopup);
|
|
19257
|
+
try {
|
|
19258
|
+
return await crawlFrom(page, opts, origin, () => {
|
|
19259
|
+
const out = blocked;
|
|
19260
|
+
blocked = [];
|
|
19261
|
+
return out;
|
|
19262
|
+
});
|
|
19263
|
+
} finally {
|
|
19264
|
+
page.off("dialog", onDialog);
|
|
19265
|
+
page.context().off("page", onPopup);
|
|
19266
|
+
await page.unroute("**/*").catch(() => {
|
|
19267
|
+
});
|
|
19268
|
+
}
|
|
19269
|
+
}
|
|
19270
|
+
async function crawlFrom(page, opts, origin, takeBlocked) {
|
|
19271
|
+
const deny = opts.deny ?? [];
|
|
19272
|
+
const settleMs = opts.settleMs ?? 1500;
|
|
19273
|
+
const { budget } = opts;
|
|
19274
|
+
const stats = {
|
|
19275
|
+
surfaces: 0,
|
|
19276
|
+
actions_tried: 0,
|
|
19277
|
+
skipped: 0,
|
|
19278
|
+
blocked_requests: 0,
|
|
19279
|
+
errors: 0,
|
|
19280
|
+
truncated: false
|
|
19281
|
+
};
|
|
19282
|
+
const surfaces = [];
|
|
19283
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
19284
|
+
const byPlaceView = /* @__PURE__ */ new Map();
|
|
19285
|
+
const signatureOf = (s) => exploreSignature(s.headings, s.title);
|
|
19286
|
+
function addSurface(state, place, view, path30, depth) {
|
|
19287
|
+
const key = exploreSurfaceKey(place, view);
|
|
19288
|
+
const surface = {
|
|
19289
|
+
key,
|
|
19290
|
+
url: state.url,
|
|
19291
|
+
place,
|
|
19292
|
+
view,
|
|
19293
|
+
signature: signatureOf(state),
|
|
19294
|
+
label: state.headings[0] ?? (state.title || place),
|
|
19295
|
+
headings: state.headings,
|
|
19296
|
+
fields: state.fields,
|
|
19297
|
+
depth,
|
|
19298
|
+
path: path30,
|
|
19299
|
+
actions: []
|
|
19300
|
+
};
|
|
19301
|
+
surfaces.push(surface);
|
|
19302
|
+
byKey.set(key, surface);
|
|
19303
|
+
byPlaceView.set(`${place}
|
|
19304
|
+
${surface.signature}`, key);
|
|
19305
|
+
stats.surfaces++;
|
|
19306
|
+
opts.log?.(` ${" ".repeat(depth)}${key} ${surface.label}`);
|
|
19307
|
+
return surface;
|
|
19308
|
+
}
|
|
19309
|
+
async function goto(url) {
|
|
19310
|
+
await page.goto(url, {
|
|
19311
|
+
waitUntil: "domcontentloaded",
|
|
19312
|
+
timeout: NAV_TIMEOUT_MS
|
|
19313
|
+
});
|
|
19314
|
+
await settle(page, settleMs);
|
|
19315
|
+
}
|
|
19316
|
+
async function replay(path30) {
|
|
19317
|
+
if (!("goto" in (path30[0] ?? {}))) await goto(opts.startUrl);
|
|
19318
|
+
for (const step of path30) {
|
|
19319
|
+
if ("goto" in step) {
|
|
19320
|
+
await goto(step.goto);
|
|
19321
|
+
} else {
|
|
19322
|
+
if (!await clickCandidate(page, step.click)) return false;
|
|
19323
|
+
await settle(page, settleMs);
|
|
19324
|
+
}
|
|
19325
|
+
}
|
|
19326
|
+
return true;
|
|
19327
|
+
}
|
|
19328
|
+
await goto(opts.startUrl);
|
|
19329
|
+
if (await page.evaluate("Boolean(navigator.serviceWorker?.controller)").catch(() => false)) {
|
|
19330
|
+
throw new Error(
|
|
19331
|
+
"A service worker controls this page, and the write guard cannot see its requests. Explore without --attach (service workers are blocked there)."
|
|
19332
|
+
);
|
|
19333
|
+
}
|
|
19334
|
+
const first = await readPage(page);
|
|
19335
|
+
const firstPlace = explorePlaceKey(first.url);
|
|
19336
|
+
if (!firstPlace || originOf(first.url) !== origin) {
|
|
19337
|
+
throw new Error(`The start URL led off the product, to ${first.url}`);
|
|
19338
|
+
}
|
|
19339
|
+
addSurface(first, firstPlace, null, [], 0);
|
|
19340
|
+
for (let i = 0; i < surfaces.length; i++) {
|
|
19341
|
+
const surface = surfaces[i];
|
|
19342
|
+
if (!await replay(surface.path)) {
|
|
19343
|
+
stats.errors++;
|
|
19344
|
+
surface.actions.push({
|
|
19345
|
+
role: "",
|
|
19346
|
+
name: "",
|
|
19347
|
+
occurrence: 0,
|
|
19348
|
+
outcome: { kind: "error", message: LOST }
|
|
19349
|
+
});
|
|
19350
|
+
continue;
|
|
19351
|
+
}
|
|
19352
|
+
const home = await readPage(page);
|
|
19353
|
+
const seen = new Set(home.candidates.map(candidateSig));
|
|
19354
|
+
const work = home.candidates.map((c) => ({ c, via: [] }));
|
|
19355
|
+
const homeList = home.candidates.map(candidateSig).join("\n");
|
|
19356
|
+
let pristine = true;
|
|
19357
|
+
for (let w = 0; w < work.length; w++) {
|
|
19358
|
+
const { c, via } = work[w];
|
|
19359
|
+
const action = {
|
|
19360
|
+
role: c.role,
|
|
19361
|
+
name: c.name,
|
|
19362
|
+
occurrence: c.occurrence,
|
|
19363
|
+
...c.href ? { href: c.href } : {},
|
|
19364
|
+
...via.length > 0 ? { via } : {},
|
|
19365
|
+
outcome: { kind: "same" }
|
|
19366
|
+
};
|
|
19367
|
+
surface.actions.push(action);
|
|
19368
|
+
const reason = skipReason(c, origin, deny);
|
|
19369
|
+
if (reason) {
|
|
19370
|
+
action.outcome = { kind: "skipped", reason };
|
|
19371
|
+
stats.skipped++;
|
|
19372
|
+
continue;
|
|
19373
|
+
}
|
|
19374
|
+
if (surface.depth >= budget.max_depth || stats.actions_tried >= budget.max_actions) {
|
|
19375
|
+
action.outcome = { kind: "skipped", reason: "budget" };
|
|
19376
|
+
stats.skipped++;
|
|
19377
|
+
stats.truncated = true;
|
|
19378
|
+
continue;
|
|
19379
|
+
}
|
|
19380
|
+
stats.actions_tried++;
|
|
19381
|
+
takeBlocked();
|
|
19382
|
+
const click = {
|
|
19383
|
+
click: { role: c.role, name: c.name, occurrence: c.occurrence }
|
|
19384
|
+
};
|
|
19385
|
+
let outcome;
|
|
19386
|
+
let revealed = 0;
|
|
19387
|
+
let left = false;
|
|
19388
|
+
let lost = false;
|
|
19389
|
+
try {
|
|
19390
|
+
if (!pristine || via.length > 0) {
|
|
19391
|
+
if (!await replay([...surface.path, ...via])) {
|
|
19392
|
+
throw new Error(LOST);
|
|
19393
|
+
}
|
|
19394
|
+
}
|
|
19395
|
+
pristine = false;
|
|
19396
|
+
const before = await readPage(page);
|
|
19397
|
+
await markDocument(page);
|
|
19398
|
+
if (c.newTab && c.href) {
|
|
19399
|
+
await goto(c.href);
|
|
19400
|
+
} else {
|
|
19401
|
+
if (!await clickCandidate(page, c)) {
|
|
19402
|
+
throw new Error("control is gone");
|
|
19403
|
+
}
|
|
19404
|
+
await settle(page, settleMs);
|
|
19405
|
+
}
|
|
19406
|
+
const after = await readPage(page);
|
|
19407
|
+
if (originOf(after.url) !== origin) {
|
|
19408
|
+
outcome = { kind: "external", url: after.url };
|
|
19409
|
+
left = true;
|
|
19410
|
+
} else {
|
|
19411
|
+
const place = explorePlaceKey(after.url) ?? before.url;
|
|
19412
|
+
const sig = signatureOf(after);
|
|
19413
|
+
const sameDoc = !(c.newTab && c.href) && await isSameDocument(page);
|
|
19414
|
+
const beforePlace = explorePlaceKey(before.url);
|
|
19415
|
+
let key = byPlaceView.get(`${place}
|
|
19416
|
+
${sig}`);
|
|
19417
|
+
let view = null;
|
|
19418
|
+
if (!key) {
|
|
19419
|
+
const inPage = sameDoc && place === beforePlace;
|
|
19420
|
+
view = inPage ? sig : null;
|
|
19421
|
+
key = exploreSurfaceKey(place, view);
|
|
19422
|
+
if (byKey.has(key)) byPlaceView.set(`${place}
|
|
19423
|
+
${sig}`, key);
|
|
19424
|
+
}
|
|
19425
|
+
if (key === surface.key) {
|
|
19426
|
+
outcome = { kind: "same" };
|
|
19427
|
+
const list2 = after.candidates.map(candidateSig).join("\n");
|
|
19428
|
+
for (const n of after.candidates) {
|
|
19429
|
+
const s = candidateSig(n);
|
|
19430
|
+
if (seen.has(s)) continue;
|
|
19431
|
+
seen.add(s);
|
|
19432
|
+
work.push({ c: n, via: [...via, click] });
|
|
19433
|
+
revealed++;
|
|
19434
|
+
}
|
|
19435
|
+
if (list2 === homeList) pristine = true;
|
|
19436
|
+
} else {
|
|
19437
|
+
outcome = { kind: "surface", surface: key };
|
|
19438
|
+
if (!byKey.has(key)) {
|
|
19439
|
+
if (stats.surfaces >= budget.max_surfaces) {
|
|
19440
|
+
stats.truncated = true;
|
|
19441
|
+
} else {
|
|
19442
|
+
const path30 = !sameDoc && view === null ? [{ goto: after.url }] : [...surface.path, ...via, click];
|
|
19443
|
+
addSurface(after, place, view, path30, surface.depth + 1);
|
|
19444
|
+
}
|
|
19445
|
+
}
|
|
19446
|
+
}
|
|
19447
|
+
}
|
|
19448
|
+
} catch (err) {
|
|
19449
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
19450
|
+
outcome = { kind: "error", message: msg.split("\n")[0].slice(0, 200) };
|
|
19451
|
+
stats.errors++;
|
|
19452
|
+
lost = msg === LOST;
|
|
19453
|
+
}
|
|
19454
|
+
action.outcome = outcome;
|
|
19455
|
+
if (revealed > 0) action.revealed = revealed;
|
|
19456
|
+
const writes = takeBlocked();
|
|
19457
|
+
if (writes.length > 0) {
|
|
19458
|
+
action.blocked_requests = [...new Set(writes)];
|
|
19459
|
+
stats.blocked_requests += writes.length;
|
|
19460
|
+
}
|
|
19461
|
+
if (left) pristine = false;
|
|
19462
|
+
if (lost) break;
|
|
19463
|
+
}
|
|
19464
|
+
}
|
|
19465
|
+
return { surfaces, stats };
|
|
19466
|
+
}
|
|
19467
|
+
var UNSAFE_NAME, UNSAFE_PATH, SAFE_METHODS, CLICK_TIMEOUT_MS, LOST, NAV_TIMEOUT_MS, READ_PAGE;
|
|
19468
|
+
var init_explore_crawl = __esm({
|
|
19469
|
+
"src/commands/explore-crawl.ts"() {
|
|
19470
|
+
"use strict";
|
|
19471
|
+
init_dist2();
|
|
19472
|
+
UNSAFE_NAME = new RegExp(
|
|
19473
|
+
`(?<![\\p{L}\\p{N}])(${[
|
|
19474
|
+
// English
|
|
19475
|
+
"delete",
|
|
19476
|
+
"remove",
|
|
19477
|
+
"destroy",
|
|
19478
|
+
"erase",
|
|
19479
|
+
"pay",
|
|
19480
|
+
"pay now",
|
|
19481
|
+
"purchase",
|
|
19482
|
+
"buy",
|
|
19483
|
+
"checkout",
|
|
19484
|
+
"place order",
|
|
19485
|
+
"order now",
|
|
19486
|
+
"send",
|
|
19487
|
+
"submit",
|
|
19488
|
+
"save",
|
|
19489
|
+
"confirm",
|
|
19490
|
+
"approve",
|
|
19491
|
+
"reject",
|
|
19492
|
+
"decline",
|
|
19493
|
+
"publish",
|
|
19494
|
+
"archive",
|
|
19495
|
+
"unsubscribe",
|
|
19496
|
+
"log ?out",
|
|
19497
|
+
"sign ?out",
|
|
19498
|
+
"reset",
|
|
19499
|
+
"transfer",
|
|
19500
|
+
"invite",
|
|
19501
|
+
"upload",
|
|
19502
|
+
"import",
|
|
19503
|
+
"deactivate",
|
|
19504
|
+
"close account",
|
|
19505
|
+
// Swedish
|
|
19506
|
+
"ta bort",
|
|
19507
|
+
"radera",
|
|
19508
|
+
"betala",
|
|
19509
|
+
"k\xF6p",
|
|
19510
|
+
"best\xE4ll",
|
|
19511
|
+
"skicka",
|
|
19512
|
+
"skicka in",
|
|
19513
|
+
"spara",
|
|
19514
|
+
"bekr\xE4fta",
|
|
19515
|
+
"godk\xE4nn",
|
|
19516
|
+
"avsl\xE5",
|
|
19517
|
+
"publicera",
|
|
19518
|
+
"arkivera",
|
|
19519
|
+
"logga ut",
|
|
19520
|
+
"\xE5terst\xE4ll",
|
|
19521
|
+
"\xF6verf\xF6r",
|
|
19522
|
+
"bjud in",
|
|
19523
|
+
"ladda upp"
|
|
19524
|
+
].join("|")})(?![\\p{L}\\p{N}])`,
|
|
19525
|
+
"iu"
|
|
19526
|
+
);
|
|
19527
|
+
UNSAFE_PATH = /(^|\/)(log-?out|sign-?out|logga-?ut|delete|remove|destroy|unsubscribe)(\/|$)/i;
|
|
19528
|
+
SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
19529
|
+
CLICK_TIMEOUT_MS = 3e3;
|
|
19530
|
+
LOST = "could not return to surface";
|
|
19531
|
+
NAV_TIMEOUT_MS = 15e3;
|
|
19532
|
+
READ_PAGE = String.raw`(() => {
|
|
19533
|
+
const SELECTOR = [
|
|
19534
|
+
"a[href]", "button", "summary",
|
|
19535
|
+
'input[type="button"]', 'input[type="submit"]',
|
|
19536
|
+
'input[type="reset"]', 'input[type="image"]',
|
|
19537
|
+
'[role="button"]', '[role="link"]', '[role="tab"]',
|
|
19538
|
+
'[role="menuitem"]', '[role="menuitemradio"]', '[role="menuitemcheckbox"]',
|
|
19539
|
+
].join(",");
|
|
19540
|
+
const clean = (s) => (s ?? "").replace(/\s+/g, " ").trim();
|
|
19541
|
+
const visible = (el) => {
|
|
19542
|
+
if (typeof el.checkVisibility === "function" &&
|
|
19543
|
+
!el.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true })) {
|
|
19544
|
+
return false;
|
|
19545
|
+
}
|
|
19546
|
+
const r = el.getBoundingClientRect();
|
|
19547
|
+
return r.width > 0 && r.height > 0;
|
|
19548
|
+
};
|
|
19549
|
+
const textOf = (id) => clean(document.getElementById(id)?.innerText);
|
|
19550
|
+
const nameOf = (el) => {
|
|
19551
|
+
const label = clean(el.getAttribute("aria-label"));
|
|
19552
|
+
if (label) return label;
|
|
19553
|
+
const by = el.getAttribute("aria-labelledby");
|
|
19554
|
+
if (by) {
|
|
19555
|
+
const t = clean(by.split(/\s+/).map(textOf).join(" "));
|
|
19556
|
+
if (t) return t;
|
|
19557
|
+
}
|
|
19558
|
+
if (el instanceof HTMLInputElement) {
|
|
19559
|
+
return clean(el.value) || clean(el.alt) || clean(el.title);
|
|
19560
|
+
}
|
|
19561
|
+
const text = clean(el.innerText);
|
|
19562
|
+
if (text) return text.slice(0, 80);
|
|
19563
|
+
const img = el.querySelector("img[alt]");
|
|
19564
|
+
return clean(img?.getAttribute("alt")) || clean(el.title);
|
|
19565
|
+
};
|
|
19566
|
+
const roleOf = (el) =>
|
|
19567
|
+
el.getAttribute("role") || (el.tagName === "A" ? "link" : "button");
|
|
19568
|
+
|
|
19569
|
+
for (const el of document.querySelectorAll("[data-iamu-x]")) {
|
|
19570
|
+
el.removeAttribute("data-iamu-x");
|
|
19571
|
+
}
|
|
19572
|
+
const seen = new Map();
|
|
19573
|
+
const candidates = [];
|
|
19574
|
+
let index = 0;
|
|
19575
|
+
for (const el of document.querySelectorAll(SELECTOR)) {
|
|
19576
|
+
if (!visible(el)) continue;
|
|
19577
|
+
if (el.disabled || el.getAttribute("aria-disabled") === "true") continue;
|
|
19578
|
+
const role = roleOf(el);
|
|
19579
|
+
const name = nameOf(el);
|
|
19580
|
+
const sig = role + "\n" + name;
|
|
19581
|
+
const occurrence = seen.get(sig) ?? 0;
|
|
19582
|
+
seen.set(sig, occurrence + 1);
|
|
19583
|
+
let submit = false;
|
|
19584
|
+
if (el instanceof HTMLButtonElement) {
|
|
19585
|
+
// A button in a form submits unless it says type="button".
|
|
19586
|
+
submit = el.form
|
|
19587
|
+
? el.type === "submit" || el.type === "reset"
|
|
19588
|
+
: el.getAttribute("type") === "submit";
|
|
19589
|
+
} else if (el instanceof HTMLInputElement) {
|
|
19590
|
+
submit = ["submit", "reset", "image"].includes(el.type);
|
|
19591
|
+
}
|
|
19592
|
+
const anchor = el instanceof HTMLAnchorElement ? el : null;
|
|
19593
|
+
el.setAttribute("data-iamu-x", String(index));
|
|
19594
|
+
candidates.push({
|
|
19595
|
+
index, role, name, occurrence,
|
|
19596
|
+
href: anchor ? anchor.href : null,
|
|
19597
|
+
download: anchor ? anchor.hasAttribute("download") : false,
|
|
19598
|
+
newTab: anchor ? anchor.target === "_blank" : false,
|
|
19599
|
+
submit,
|
|
19600
|
+
});
|
|
19601
|
+
index++;
|
|
19602
|
+
}
|
|
19603
|
+
|
|
19604
|
+
const headings = [];
|
|
19605
|
+
for (const el of document.querySelectorAll(
|
|
19606
|
+
'h1, h2, [role="heading"][aria-level="1"], [role="heading"][aria-level="2"]',
|
|
19607
|
+
)) {
|
|
19608
|
+
if (!visible(el)) continue;
|
|
19609
|
+
const t = clean(el.innerText);
|
|
19610
|
+
if (t) headings.push(t.slice(0, 120));
|
|
19611
|
+
}
|
|
19612
|
+
|
|
19613
|
+
const fields = [];
|
|
19614
|
+
for (const f of document.querySelectorAll("input, select, textarea")) {
|
|
19615
|
+
if (["hidden", "submit", "button", "reset", "image"].includes(f.type)) continue;
|
|
19616
|
+
if (!visible(f)) continue;
|
|
19617
|
+
const forLabel = f.id
|
|
19618
|
+
? document.querySelector('label[for="' + CSS.escape(f.id) + '"]')
|
|
19619
|
+
: null;
|
|
19620
|
+
const name =
|
|
19621
|
+
clean(f.getAttribute("aria-label")) ||
|
|
19622
|
+
clean(forLabel?.innerText) ||
|
|
19623
|
+
clean(f.closest("label")?.innerText) ||
|
|
19624
|
+
clean(f.placeholder) ||
|
|
19625
|
+
clean(f.name);
|
|
19626
|
+
if (name && !fields.includes(name)) fields.push(name.slice(0, 80));
|
|
19627
|
+
if (fields.length >= 40) break;
|
|
19628
|
+
}
|
|
19629
|
+
|
|
19630
|
+
return { url: location.href, title: clean(document.title), headings, fields, candidates };
|
|
19631
|
+
})()`;
|
|
19632
|
+
}
|
|
19633
|
+
});
|
|
19634
|
+
|
|
19635
|
+
// src/commands/explore.ts
|
|
19636
|
+
var explore_exports = {};
|
|
19637
|
+
__export(explore_exports, {
|
|
19638
|
+
exploreCoverageCommand: () => exploreCoverageCommand,
|
|
19639
|
+
exploreDiffCommand: () => exploreDiffCommand,
|
|
19640
|
+
exploreList: () => exploreList,
|
|
19641
|
+
exploreRun: () => exploreRun
|
|
19642
|
+
});
|
|
19643
|
+
import { promises as fs27 } from "fs";
|
|
19644
|
+
import path25 from "path";
|
|
19645
|
+
import chalk26 from "chalk";
|
|
19646
|
+
function fail2(message, json2) {
|
|
19647
|
+
if (json2) console.error(JSON.stringify({ error: message }));
|
|
19648
|
+
else console.error(chalk26.red(` ${message}`));
|
|
19649
|
+
process.exitCode = 1;
|
|
19650
|
+
}
|
|
19651
|
+
function positiveInt(raw, fallback, flag) {
|
|
19652
|
+
if (raw === void 0) return fallback;
|
|
19653
|
+
const n = Number(raw);
|
|
19654
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
19655
|
+
throw new Error(`${flag} must be a positive whole number, got "${raw}"`);
|
|
19656
|
+
}
|
|
19657
|
+
return n;
|
|
19658
|
+
}
|
|
19659
|
+
async function testabilityTarget(projectRoot) {
|
|
19660
|
+
try {
|
|
19661
|
+
const raw = await fs27.readFile(
|
|
19662
|
+
path25.join(projectRoot, "iamu", "testability.md"),
|
|
19663
|
+
"utf8"
|
|
19664
|
+
);
|
|
19665
|
+
const { frontmatter } = parseFrontmatter(raw);
|
|
19666
|
+
return typeof frontmatter.target_url === "string" ? frontmatter.target_url : null;
|
|
19667
|
+
} catch {
|
|
19668
|
+
return null;
|
|
19669
|
+
}
|
|
19670
|
+
}
|
|
19671
|
+
async function openPage(opts) {
|
|
19672
|
+
const { chromium } = await import("playwright-core");
|
|
19673
|
+
const viewport = parseViewport(opts.viewport) ?? {
|
|
19674
|
+
width: 1280,
|
|
19675
|
+
height: 800
|
|
19676
|
+
};
|
|
19677
|
+
if (opts.attach) {
|
|
19678
|
+
const state = readBrowseState();
|
|
19679
|
+
if (!state) {
|
|
19680
|
+
throw new Error(
|
|
19681
|
+
"No browser to attach to \u2014 start one with `iamu browse start` and sign in first."
|
|
19682
|
+
);
|
|
19683
|
+
}
|
|
19684
|
+
const browser2 = await chromium.connectOverCDP(
|
|
19685
|
+
`http://127.0.0.1:${state.port}`
|
|
19686
|
+
);
|
|
19687
|
+
const context = browser2.contexts()[0] ?? await browser2.newContext();
|
|
19688
|
+
const page2 = await context.newPage();
|
|
19689
|
+
await page2.setViewportSize(viewport);
|
|
19690
|
+
return {
|
|
19691
|
+
page: page2,
|
|
19692
|
+
close: async () => {
|
|
19693
|
+
await page2.close().catch(() => {
|
|
19694
|
+
});
|
|
19695
|
+
await browser2.close().catch(() => {
|
|
19696
|
+
});
|
|
19697
|
+
}
|
|
19698
|
+
};
|
|
19699
|
+
}
|
|
19700
|
+
const chrome = findChrome();
|
|
19701
|
+
if (!chrome) {
|
|
19702
|
+
throw new Error(
|
|
19703
|
+
"No Chrome or Chromium found \u2014 install one or set IAMU_BROWSER."
|
|
19704
|
+
);
|
|
19705
|
+
}
|
|
19706
|
+
const browser = await chromium.launch({
|
|
19707
|
+
executablePath: chrome,
|
|
19708
|
+
headless: true,
|
|
19709
|
+
chromiumSandbox: !shouldDisableSandbox()
|
|
19710
|
+
});
|
|
19711
|
+
const page = await browser.newPage({ viewport, serviceWorkers: "block" });
|
|
19712
|
+
return { page, close: () => browser.close().catch(() => {
|
|
19713
|
+
}) };
|
|
19714
|
+
}
|
|
19715
|
+
async function exploreRun(url, opts) {
|
|
19716
|
+
const ctx = await resolveProject(opts);
|
|
19717
|
+
if (!ctx) return;
|
|
19718
|
+
const target = url ?? await testabilityTarget(ctx.projectRoot);
|
|
19719
|
+
if (!target) {
|
|
19720
|
+
fail2(
|
|
19721
|
+
"No URL to explore \u2014 pass one (iamu explore http://localhost:3000) or set target_url in iamu/testability.md.",
|
|
19722
|
+
opts.json
|
|
19723
|
+
);
|
|
19724
|
+
return;
|
|
19725
|
+
}
|
|
19726
|
+
let startUrl;
|
|
19727
|
+
let budget;
|
|
19728
|
+
let deny;
|
|
19729
|
+
try {
|
|
19730
|
+
startUrl = new URL(target).href;
|
|
19731
|
+
budget = {
|
|
19732
|
+
max_surfaces: positiveInt(
|
|
19733
|
+
opts.maxSurfaces,
|
|
19734
|
+
DEFAULTS.max_surfaces,
|
|
19735
|
+
"--max-surfaces"
|
|
19736
|
+
),
|
|
19737
|
+
max_actions: positiveInt(
|
|
19738
|
+
opts.maxActions,
|
|
19739
|
+
DEFAULTS.max_actions,
|
|
19740
|
+
"--max-actions"
|
|
19741
|
+
),
|
|
19742
|
+
max_depth: positiveInt(opts.maxDepth, DEFAULTS.max_depth, "--max-depth")
|
|
19743
|
+
};
|
|
19744
|
+
deny = (opts.deny ?? []).map((d) => new RegExp(d, "i"));
|
|
19745
|
+
} catch (err) {
|
|
19746
|
+
fail2(err.message, opts.json);
|
|
19747
|
+
return;
|
|
19748
|
+
}
|
|
19749
|
+
const quiet = opts.json || process.env.IAMU_QUIET === "1";
|
|
19750
|
+
if (!quiet) {
|
|
19751
|
+
console.error(chalk26.dim(` Exploring ${startUrl} (read-only)\u2026`));
|
|
19752
|
+
}
|
|
19753
|
+
const now = /* @__PURE__ */ new Date();
|
|
19754
|
+
let opened = null;
|
|
19755
|
+
let result;
|
|
19756
|
+
try {
|
|
19757
|
+
opened = await openPage(opts);
|
|
19758
|
+
result = await crawl(opened.page, {
|
|
19759
|
+
startUrl,
|
|
19760
|
+
budget,
|
|
19761
|
+
deny,
|
|
19762
|
+
log: quiet ? void 0 : (line) => console.error(chalk26.dim(line))
|
|
19763
|
+
});
|
|
19764
|
+
} catch (err) {
|
|
19765
|
+
fail2(err.message.split("\n")[0], opts.json);
|
|
19766
|
+
return;
|
|
19767
|
+
} finally {
|
|
19768
|
+
await opened?.close();
|
|
19769
|
+
}
|
|
19770
|
+
const build = detectBuild({ cwd: ctx.projectRoot });
|
|
19771
|
+
const map2 = {
|
|
19772
|
+
schema: 1,
|
|
19773
|
+
id: composeExploreId(now),
|
|
19774
|
+
created_at: now.toISOString(),
|
|
19775
|
+
target_url: startUrl,
|
|
19776
|
+
...build ? { build: { ...build } } : {},
|
|
19777
|
+
budget,
|
|
19778
|
+
stats: result.stats,
|
|
19779
|
+
surfaces: result.surfaces
|
|
19780
|
+
};
|
|
19781
|
+
const file = await writeExploreMap(ctx.projectRoot, map2);
|
|
19782
|
+
const rel = path25.relative(process.cwd(), file);
|
|
19783
|
+
if (opts.json) {
|
|
19784
|
+
console.log(JSON.stringify({ id: map2.id, path: rel, stats: map2.stats }));
|
|
19785
|
+
return;
|
|
19786
|
+
}
|
|
19787
|
+
const s = map2.stats;
|
|
19788
|
+
console.log(
|
|
19789
|
+
`
|
|
19790
|
+
${chalk26.bold(`${s.surfaces} surfaces`)} \xB7 ${s.actions_tried} controls tried \xB7 ${s.skipped} skipped \xB7 ${s.blocked_requests} writes blocked${s.errors ? ` \xB7 ${s.errors} errors` : ""}`
|
|
19791
|
+
);
|
|
19792
|
+
if (s.truncated) {
|
|
19793
|
+
console.log(
|
|
19794
|
+
chalk26.yellow(
|
|
19795
|
+
" Stopped by the budget before the product ran out \u2014 raise --max-surfaces / --max-actions / --max-depth to go further."
|
|
19796
|
+
)
|
|
19797
|
+
);
|
|
19798
|
+
}
|
|
19799
|
+
console.log(` Map: ${rel}`);
|
|
19800
|
+
console.log(chalk26.dim(" Next: iamu explore coverage"));
|
|
19801
|
+
}
|
|
19802
|
+
async function resolveMapId(projectRoot, id, offsetFromEnd = 1) {
|
|
19803
|
+
if (id && id !== "latest") return id;
|
|
19804
|
+
const ids = await listExploreMaps(projectRoot);
|
|
19805
|
+
return ids.at(-offsetFromEnd) ?? null;
|
|
19806
|
+
}
|
|
19807
|
+
async function exploreList(opts) {
|
|
19808
|
+
const ctx = await resolveProject(opts);
|
|
19809
|
+
if (!ctx) return;
|
|
19810
|
+
const rows = [];
|
|
19811
|
+
for (const id of await listExploreMaps(ctx.projectRoot)) {
|
|
19812
|
+
try {
|
|
19813
|
+
const m = await readExploreMap(ctx.projectRoot, id);
|
|
19814
|
+
rows.push({ id, target_url: m.target_url, surfaces: m.surfaces.length });
|
|
19815
|
+
} catch {
|
|
19816
|
+
}
|
|
19817
|
+
}
|
|
19818
|
+
if (opts.json) {
|
|
19819
|
+
console.log(JSON.stringify({ maps: rows }));
|
|
19820
|
+
return;
|
|
19821
|
+
}
|
|
19822
|
+
if (rows.length === 0) {
|
|
19823
|
+
console.log(" No maps yet. Run: iamu explore <url>");
|
|
19824
|
+
return;
|
|
19825
|
+
}
|
|
19826
|
+
for (const r of rows) {
|
|
19827
|
+
console.log(
|
|
19828
|
+
` ${r.id} ${r.surfaces} surfaces ${chalk26.dim(r.target_url)}`
|
|
19829
|
+
);
|
|
19830
|
+
}
|
|
19831
|
+
}
|
|
19832
|
+
async function exploreCoverageCommand(mapId, opts) {
|
|
19833
|
+
const ctx = await resolveProject(opts);
|
|
19834
|
+
if (!ctx) return;
|
|
19835
|
+
const id = await resolveMapId(ctx.projectRoot, mapId);
|
|
19836
|
+
if (!id) {
|
|
19837
|
+
fail2("No map yet. Run: iamu explore <url>", opts.json);
|
|
19838
|
+
return;
|
|
19839
|
+
}
|
|
19840
|
+
let map2;
|
|
19841
|
+
try {
|
|
19842
|
+
map2 = await readExploreMap(ctx.projectRoot, id);
|
|
19843
|
+
} catch (err) {
|
|
19844
|
+
fail2(err.message, opts.json);
|
|
19845
|
+
return;
|
|
19846
|
+
}
|
|
19847
|
+
const cov = exploreCoverage(map2, await readAllRunSteps(ctx.projectRoot));
|
|
19848
|
+
if (opts.json) {
|
|
19849
|
+
console.log(JSON.stringify(cov));
|
|
19850
|
+
return;
|
|
19851
|
+
}
|
|
19852
|
+
console.log(
|
|
19853
|
+
`
|
|
19854
|
+
Map ${id} (${map2.surfaces.length} surfaces) against ${cov.runs} runs, ${cov.steps} steps`
|
|
19855
|
+
);
|
|
19856
|
+
console.log(
|
|
19857
|
+
` ${chalk26.green(`${cov.reached} reached`)} \xB7 ${chalk26.yellow(`${cov.place_only} URL reached, view unclear`)} \xB7 ${chalk26.red(`${cov.unreached} never reached`)}`
|
|
19858
|
+
);
|
|
19859
|
+
const section = (title, rows, color) => {
|
|
19860
|
+
if (rows.length === 0) return;
|
|
19861
|
+
console.log(`
|
|
19862
|
+
${color(title)}`);
|
|
19863
|
+
for (const r of rows) {
|
|
19864
|
+
console.log(
|
|
19865
|
+
` ${r.key.padEnd(32)} ${r.label}${chalk26.dim(` (depth ${r.depth})`)}`
|
|
19866
|
+
);
|
|
19867
|
+
}
|
|
19868
|
+
};
|
|
19869
|
+
section(
|
|
19870
|
+
"Never reached by a run",
|
|
19871
|
+
cov.surfaces.filter((s) => s.status === "unreached"),
|
|
19872
|
+
chalk26.red
|
|
19873
|
+
);
|
|
19874
|
+
section(
|
|
19875
|
+
"URL reached, but no step names this view",
|
|
19876
|
+
cov.surfaces.filter((s) => s.status === "place_only"),
|
|
19877
|
+
chalk26.yellow
|
|
19878
|
+
);
|
|
19879
|
+
if (cov.run_only_places.length > 0) {
|
|
19880
|
+
console.log(`
|
|
19881
|
+
${chalk26.cyan("Walked by runs, not in the map")}`);
|
|
19882
|
+
for (const p of cov.run_only_places) {
|
|
19883
|
+
console.log(
|
|
19884
|
+
` ${p.place.padEnd(32)} ${p.steps} steps, ${p.runs.length} runs`
|
|
19885
|
+
);
|
|
19886
|
+
}
|
|
19887
|
+
}
|
|
19888
|
+
if (cov.runs === 0) {
|
|
19889
|
+
console.log(
|
|
19890
|
+
chalk26.dim(
|
|
19891
|
+
"\n No runs yet \u2014 every surface counts as never reached. Walk a scenario first (iamu-run)."
|
|
19892
|
+
)
|
|
19893
|
+
);
|
|
19894
|
+
}
|
|
19895
|
+
}
|
|
19896
|
+
async function exploreDiffCommand(a, b, opts) {
|
|
19897
|
+
const ctx = await resolveProject(opts);
|
|
19898
|
+
if (!ctx) return;
|
|
19899
|
+
const idA = a ?? await resolveMapId(ctx.projectRoot, void 0, 2);
|
|
19900
|
+
const idB = b ?? await resolveMapId(ctx.projectRoot, void 0, 1);
|
|
19901
|
+
if (!idA || !idB || idA === idB) {
|
|
19902
|
+
fail2(
|
|
19903
|
+
"Need two maps to compare \u2014 explore again (iamu explore <url>), or name both maps.",
|
|
19904
|
+
opts.json
|
|
19905
|
+
);
|
|
19906
|
+
return;
|
|
19907
|
+
}
|
|
19908
|
+
let mapA;
|
|
19909
|
+
let mapB;
|
|
19910
|
+
try {
|
|
19911
|
+
mapA = await readExploreMap(ctx.projectRoot, idA);
|
|
19912
|
+
mapB = await readExploreMap(ctx.projectRoot, idB);
|
|
19913
|
+
} catch (err) {
|
|
19914
|
+
fail2(err.message, opts.json);
|
|
19915
|
+
return;
|
|
19916
|
+
}
|
|
19917
|
+
const d = diffExploreMaps(mapA, mapB);
|
|
19918
|
+
if (opts.json) {
|
|
19919
|
+
console.log(JSON.stringify(d));
|
|
19920
|
+
return;
|
|
19921
|
+
}
|
|
19922
|
+
const pct = Math.round(d.surface_overlap * 100);
|
|
19923
|
+
console.log(`
|
|
19924
|
+
${idA} \u2192 ${idB}`);
|
|
19925
|
+
console.log(
|
|
19926
|
+
` ${chalk26.bold(`${pct}% of surfaces match`)} (${d.common} shared, ${d.only_a.length} only in the first, ${d.only_b.length} only in the second)`
|
|
19927
|
+
);
|
|
19928
|
+
if (d.only_a.length > 0) {
|
|
19929
|
+
console.log(`
|
|
19930
|
+
${chalk26.red("Only in the first")}`);
|
|
19931
|
+
for (const s of d.only_a) console.log(` ${s.key.padEnd(32)} ${s.label}`);
|
|
19932
|
+
}
|
|
19933
|
+
if (d.only_b.length > 0) {
|
|
19934
|
+
console.log(`
|
|
19935
|
+
${chalk26.green("Only in the second")}`);
|
|
19936
|
+
for (const s of d.only_b) console.log(` ${s.key.padEnd(32)} ${s.label}`);
|
|
19937
|
+
}
|
|
19938
|
+
if (d.changed_actions.length > 0) {
|
|
19939
|
+
console.log(
|
|
19940
|
+
`
|
|
19941
|
+
${chalk26.yellow(`Controls differ on ${d.changed_actions.length} shared surfaces`)}`
|
|
19942
|
+
);
|
|
19943
|
+
for (const c of d.changed_actions) {
|
|
19944
|
+
console.log(` ${c.key} ${c.label}`);
|
|
19945
|
+
for (const x of c.only_a) console.log(chalk26.red(` - ${x}`));
|
|
19946
|
+
for (const x of c.only_b) console.log(chalk26.green(` + ${x}`));
|
|
19947
|
+
}
|
|
19948
|
+
}
|
|
19949
|
+
}
|
|
19950
|
+
var DEFAULTS;
|
|
19951
|
+
var init_explore = __esm({
|
|
19952
|
+
"src/commands/explore.ts"() {
|
|
19953
|
+
"use strict";
|
|
19954
|
+
init_dist2();
|
|
19955
|
+
init_build_info();
|
|
19956
|
+
init_browse();
|
|
19957
|
+
init_context4();
|
|
19958
|
+
init_explore_crawl();
|
|
19959
|
+
DEFAULTS = { max_surfaces: 40, max_actions: 300, max_depth: 6 };
|
|
19960
|
+
}
|
|
19961
|
+
});
|
|
19962
|
+
|
|
19963
|
+
// src/commands/completion.ts
|
|
19964
|
+
var completion_exports = {};
|
|
19965
|
+
__export(completion_exports, {
|
|
19966
|
+
completion: () => completion
|
|
19967
|
+
});
|
|
19968
|
+
function extractCommandMeta(program) {
|
|
19969
|
+
const result = [];
|
|
19970
|
+
for (const cmd of program.commands) {
|
|
19971
|
+
if (cmd._hidden) continue;
|
|
19972
|
+
const name = cmd.name();
|
|
19973
|
+
const description = cmd.description() || "";
|
|
19974
|
+
const options = cmd.options.map((opt) => {
|
|
19975
|
+
return opt.long ?? opt.short ?? opt.flags;
|
|
19976
|
+
});
|
|
19977
|
+
const subcommands = [];
|
|
19978
|
+
for (const sub of cmd.commands) {
|
|
19979
|
+
if (sub._hidden) continue;
|
|
19980
|
+
subcommands.push({
|
|
19981
|
+
name: sub.name(),
|
|
19982
|
+
description: sub.description() || "",
|
|
19983
|
+
options: sub.options.map(
|
|
19984
|
+
(opt) => opt.long ?? opt.short ?? opt.flags
|
|
19985
|
+
)
|
|
19986
|
+
});
|
|
19987
|
+
}
|
|
19988
|
+
result.push({ name, description, options, subcommands });
|
|
19989
|
+
}
|
|
19990
|
+
return result;
|
|
18665
19991
|
}
|
|
18666
19992
|
function generateBash(program) {
|
|
18667
19993
|
const metas = extractCommandMeta(program);
|
|
@@ -18830,7 +20156,7 @@ __export(instructions_walk_exports, {
|
|
|
18830
20156
|
});
|
|
18831
20157
|
import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
|
|
18832
20158
|
import { join as join9 } from "path";
|
|
18833
|
-
import
|
|
20159
|
+
import chalk27 from "chalk";
|
|
18834
20160
|
function selfCommand() {
|
|
18835
20161
|
const q = (p) => /[\s"'$]/.test(p) ? JSON.stringify(p) : p;
|
|
18836
20162
|
return `${q(process.execPath)} ${q(process.argv[1] ?? "iamu")}`;
|
|
@@ -18933,6 +20259,17 @@ function composeWalkerPrompt(p) {
|
|
|
18933
20259
|
"",
|
|
18934
20260
|
"## First-person brief",
|
|
18935
20261
|
p.brief ?? "",
|
|
20262
|
+
...p.context ? ["", "## Product domain & regulatory context", p.context.trim()] : [],
|
|
20263
|
+
...p.state?.description || p.state?.approximation ? [
|
|
20264
|
+
"",
|
|
20265
|
+
"## Expected state & semantic squinting",
|
|
20266
|
+
p.state.description ? `Expected state: ${p.state.description}` : "",
|
|
20267
|
+
p.state.approximation ? `Approximation strategy: ${p.state.approximation}` : "",
|
|
20268
|
+
"Squinting Guardrails:",
|
|
20269
|
+
"\u2022 If the exact target record/option is not available, apply the approximation strategy above to pick the closest plausible entity within the domain flow.",
|
|
20270
|
+
"\u2022 Squinting does NOT permit drifting to unrelated settings, account profiles, or off-target pages. Stay strictly within the scenario journey.",
|
|
20271
|
+
"\u2022 If the system is completely empty or unseeded, and your persona role cannot create data, do NOT report false product bugs about the empty state. Note that preconditions are unseeded/blocked."
|
|
20272
|
+
].filter(Boolean) : [],
|
|
18936
20273
|
...revisitSection(p.revisit ?? []),
|
|
18937
20274
|
"",
|
|
18938
20275
|
"## Access",
|
|
@@ -19081,7 +20418,8 @@ async function instructionsWalk(opts) {
|
|
|
19081
20418
|
const result = {
|
|
19082
20419
|
scenario: {
|
|
19083
20420
|
slug: sfm.slug,
|
|
19084
|
-
brief: scenario.body.trim() || null
|
|
20421
|
+
brief: scenario.body.trim() || null,
|
|
20422
|
+
state: sfm.state ?? null
|
|
19085
20423
|
},
|
|
19086
20424
|
persona: {
|
|
19087
20425
|
slug: sfm.persona,
|
|
@@ -19135,7 +20473,9 @@ async function instructionsWalk(opts) {
|
|
|
19135
20473
|
runDirPath,
|
|
19136
20474
|
projectRoot,
|
|
19137
20475
|
surface: opts.surface ?? "browse",
|
|
19138
|
-
viewport
|
|
20476
|
+
viewport,
|
|
20477
|
+
state: sfm.state ?? null,
|
|
20478
|
+
context: contextYml
|
|
19139
20479
|
}) : null
|
|
19140
20480
|
};
|
|
19141
20481
|
if (opts.json) {
|
|
@@ -19143,30 +20483,35 @@ async function instructionsWalk(opts) {
|
|
|
19143
20483
|
return;
|
|
19144
20484
|
}
|
|
19145
20485
|
console.log();
|
|
19146
|
-
console.log(
|
|
19147
|
-
console.log(` ${
|
|
19148
|
-
console.log(` ${
|
|
19149
|
-
console.log(` ${
|
|
19150
|
-
if (
|
|
20486
|
+
console.log(chalk27.bold(" Walk instructions"));
|
|
20487
|
+
console.log(` ${chalk27.dim("scenario:")} ${sfm.slug}`);
|
|
20488
|
+
console.log(` ${chalk27.dim("persona:")} ${pfm.name} (${pfm.role})`);
|
|
20489
|
+
console.log(` ${chalk27.dim("outline:")} ${ofm.title}`);
|
|
20490
|
+
if (sfm.state?.description || sfm.state?.approximation) {
|
|
20491
|
+
console.log(
|
|
20492
|
+
` ${chalk27.dim("state:")} ${sfm.state.description ? sfm.state.description : sfm.state.approximation ?? ""}`
|
|
20493
|
+
);
|
|
20494
|
+
}
|
|
20495
|
+
if (targetUrl) console.log(` ${chalk27.dim("target:")} ${targetUrl}`);
|
|
19151
20496
|
console.log(
|
|
19152
|
-
` ${
|
|
20497
|
+
` ${chalk27.dim("access:")} ${projectAccess || personaAccess ? `${projectAccess ? "project" : ""}${projectAccess && personaAccess ? " + " : ""}${personaAccess ? "persona" : ""}` : "not recorded (walker would run as whoever is signed in)"}`
|
|
19153
20498
|
);
|
|
19154
20499
|
console.log(
|
|
19155
|
-
` ${
|
|
20500
|
+
` ${chalk27.dim("dismissed:")} ${dismissedFacts.length} findings to skip`
|
|
19156
20501
|
);
|
|
19157
20502
|
console.log(
|
|
19158
|
-
` ${
|
|
20503
|
+
` ${chalk27.dim("guidance:")} ${guidance.length} tester guidance rule${guidance.length === 1 ? "" : "s"}`
|
|
19159
20504
|
);
|
|
19160
20505
|
if (revisit.length > 0) {
|
|
19161
20506
|
console.log(
|
|
19162
|
-
` ${
|
|
20507
|
+
` ${chalk27.dim("re-check:")} ${revisit.length} place${revisit.length === 1 ? "" : "s"} where fixes await a re-run`
|
|
19163
20508
|
);
|
|
19164
20509
|
}
|
|
19165
20510
|
console.log(
|
|
19166
|
-
` ${
|
|
20511
|
+
` ${chalk27.dim("methodology:")} ${methodology ? "loaded" : "missing (run iamu update)"}`
|
|
19167
20512
|
);
|
|
19168
20513
|
console.log();
|
|
19169
|
-
console.log(
|
|
20514
|
+
console.log(chalk27.dim(" Use --json to get the full instruction bundle."));
|
|
19170
20515
|
console.log();
|
|
19171
20516
|
}
|
|
19172
20517
|
var SCREEN;
|
|
@@ -19192,8 +20537,8 @@ __export(new_finding_exports, {
|
|
|
19192
20537
|
});
|
|
19193
20538
|
import { randomUUID as randomUUID10 } from "crypto";
|
|
19194
20539
|
import { access, readFile as readFile6 } from "fs/promises";
|
|
19195
|
-
import
|
|
19196
|
-
import
|
|
20540
|
+
import path26 from "path";
|
|
20541
|
+
import chalk28 from "chalk";
|
|
19197
20542
|
async function newFinding(opts) {
|
|
19198
20543
|
const ctx = await resolveProject(opts);
|
|
19199
20544
|
if (!ctx) return;
|
|
@@ -19259,7 +20604,7 @@ async function newFinding(opts) {
|
|
|
19259
20604
|
}
|
|
19260
20605
|
try {
|
|
19261
20606
|
await access(
|
|
19262
|
-
|
|
20607
|
+
path26.join(runDirPath, "steps", `${String(n).padStart(3, "0")}.json`)
|
|
19263
20608
|
);
|
|
19264
20609
|
} catch {
|
|
19265
20610
|
commandError(
|
|
@@ -19274,7 +20619,7 @@ async function newFinding(opts) {
|
|
|
19274
20619
|
if (!scenarioSlug) {
|
|
19275
20620
|
try {
|
|
19276
20621
|
const manifestRaw = await readFile6(
|
|
19277
|
-
|
|
20622
|
+
path26.join(runDirPath, "walkthrough.json"),
|
|
19278
20623
|
"utf8"
|
|
19279
20624
|
);
|
|
19280
20625
|
const manifest = JSON.parse(manifestRaw);
|
|
@@ -19363,10 +20708,10 @@ async function newFinding(opts) {
|
|
|
19363
20708
|
return;
|
|
19364
20709
|
}
|
|
19365
20710
|
console.log();
|
|
19366
|
-
console.log(` ${symbols.success} Created finding: ${
|
|
19367
|
-
console.log(` ${
|
|
19368
|
-
console.log(` ${
|
|
19369
|
-
for (const w of warnings) console.log(` ${
|
|
20711
|
+
console.log(` ${symbols.success} Created finding: ${chalk28.cyan(slug)}`);
|
|
20712
|
+
console.log(` ${chalk28.dim("path:")} ${record.path}`);
|
|
20713
|
+
console.log(` ${chalk28.dim("id:")} ${id}`);
|
|
20714
|
+
for (const w of warnings) console.log(` ${chalk28.yellow("!")} ${w}`);
|
|
19370
20715
|
console.log();
|
|
19371
20716
|
} catch (err) {
|
|
19372
20717
|
commandError(
|
|
@@ -19392,7 +20737,7 @@ __export(view_results_exports, {
|
|
|
19392
20737
|
promptViewResults: () => promptViewResults
|
|
19393
20738
|
});
|
|
19394
20739
|
import { isCancel as isCancel4, select as select3 } from "@clack/prompts";
|
|
19395
|
-
import
|
|
20740
|
+
import chalk29 from "chalk";
|
|
19396
20741
|
function launchCloudDashboard(config, runId) {
|
|
19397
20742
|
const cloudRunId = runId ? toCloudRunUuid(runId) : void 0;
|
|
19398
20743
|
const url = getCloudDashboardUrl(
|
|
@@ -19401,7 +20746,7 @@ function launchCloudDashboard(config, runId) {
|
|
|
19401
20746
|
cloudRunId
|
|
19402
20747
|
);
|
|
19403
20748
|
console.log();
|
|
19404
|
-
console.log(` ${symbols.success} Opening Iamu Cloud: ${
|
|
20749
|
+
console.log(` ${symbols.success} Opening Iamu Cloud: ${chalk29.cyan(url)}
|
|
19405
20750
|
`);
|
|
19406
20751
|
openBrowser(url);
|
|
19407
20752
|
}
|
|
@@ -19469,9 +20814,9 @@ __export(evaluate_exports, {
|
|
|
19469
20814
|
evaluate: () => evaluate,
|
|
19470
20815
|
evaluateRecord: () => evaluateRecord
|
|
19471
20816
|
});
|
|
19472
|
-
import { promises as
|
|
19473
|
-
import
|
|
19474
|
-
import
|
|
20817
|
+
import { promises as fs28 } from "fs";
|
|
20818
|
+
import path27 from "path";
|
|
20819
|
+
import chalk30 from "chalk";
|
|
19475
20820
|
import { z as z25 } from "zod";
|
|
19476
20821
|
function formatIssues(error) {
|
|
19477
20822
|
return error.issues.map(
|
|
@@ -19501,10 +20846,10 @@ async function evaluate(runId, opts) {
|
|
|
19501
20846
|
const resolved = await resolveProjectAndRunDir(runId, opts);
|
|
19502
20847
|
if (!resolved) return;
|
|
19503
20848
|
const { projectRoot, runDirPath } = resolved;
|
|
19504
|
-
const canonicalPath =
|
|
20849
|
+
const canonicalPath = path27.join(runDirPath, "canonical.json");
|
|
19505
20850
|
let canonical;
|
|
19506
20851
|
try {
|
|
19507
|
-
const raw = await
|
|
20852
|
+
const raw = await fs28.readFile(canonicalPath, "utf8");
|
|
19508
20853
|
canonical = JSON.parse(raw);
|
|
19509
20854
|
} catch {
|
|
19510
20855
|
commandError(
|
|
@@ -19560,15 +20905,15 @@ async function evaluate(runId, opts) {
|
|
|
19560
20905
|
)
|
|
19561
20906
|
};
|
|
19562
20907
|
const prompt = composeJudgePrompt(input);
|
|
19563
|
-
const promptFilePath = opts?.promptFile ??
|
|
19564
|
-
await
|
|
20908
|
+
const promptFilePath = opts?.promptFile ?? path27.join(runDirPath, "judge_prompt.md");
|
|
20909
|
+
await fs28.writeFile(promptFilePath, prompt, "utf8");
|
|
19565
20910
|
if (opts?.json) {
|
|
19566
20911
|
console.log(
|
|
19567
20912
|
JSON.stringify(
|
|
19568
20913
|
{
|
|
19569
20914
|
...input,
|
|
19570
|
-
prompt_file:
|
|
19571
|
-
prompt_file_abs:
|
|
20915
|
+
prompt_file: path27.relative(projectRoot, promptFilePath),
|
|
20916
|
+
prompt_file_abs: path27.resolve(promptFilePath),
|
|
19572
20917
|
judge_prompt: prompt
|
|
19573
20918
|
},
|
|
19574
20919
|
null,
|
|
@@ -19583,10 +20928,10 @@ async function evaluateRecord(runId, opts) {
|
|
|
19583
20928
|
const resolved = await resolveProjectAndRunDir(runId, opts);
|
|
19584
20929
|
if (!resolved) return;
|
|
19585
20930
|
const { projectRoot, runDirPath } = resolved;
|
|
19586
|
-
const canonicalPath =
|
|
20931
|
+
const canonicalPath = path27.join(runDirPath, "canonical.json");
|
|
19587
20932
|
let canonical;
|
|
19588
20933
|
try {
|
|
19589
|
-
const raw = await
|
|
20934
|
+
const raw = await fs28.readFile(canonicalPath, "utf8");
|
|
19590
20935
|
canonical = JSON.parse(raw);
|
|
19591
20936
|
} catch {
|
|
19592
20937
|
commandError(`No canonical.json for run: ${runId}`, opts);
|
|
@@ -19594,7 +20939,7 @@ async function evaluateRecord(runId, opts) {
|
|
|
19594
20939
|
}
|
|
19595
20940
|
let parsedFile;
|
|
19596
20941
|
try {
|
|
19597
|
-
const raw = await
|
|
20942
|
+
const raw = await fs28.readFile(opts.file, "utf8");
|
|
19598
20943
|
parsedFile = JSON.parse(raw);
|
|
19599
20944
|
} catch {
|
|
19600
20945
|
commandError(`Cannot read verdicts file: ${opts.file}`, opts);
|
|
@@ -19651,8 +20996,8 @@ ${formatIssues(fileResult.error)}`,
|
|
|
19651
20996
|
overall: inputVerdicts.overall
|
|
19652
20997
|
});
|
|
19653
20998
|
const reportContent = renderCanonicalMarkdown(canonicalForReport);
|
|
19654
|
-
const verdictsPath =
|
|
19655
|
-
const reportPath =
|
|
20999
|
+
const verdictsPath = path27.join(runDirPath, "verdicts.json");
|
|
21000
|
+
const reportPath = path27.join(runDirPath, "report.md");
|
|
19656
21001
|
await Promise.all([
|
|
19657
21002
|
atomicWriteFile(
|
|
19658
21003
|
verdictsPath,
|
|
@@ -19672,8 +21017,8 @@ ${formatIssues(fileResult.error)}`,
|
|
|
19672
21017
|
JSON.stringify(
|
|
19673
21018
|
{
|
|
19674
21019
|
runId,
|
|
19675
|
-
verdictsPath:
|
|
19676
|
-
reportPath:
|
|
21020
|
+
verdictsPath: path27.relative(projectRoot, verdictsPath),
|
|
21021
|
+
reportPath: path27.relative(projectRoot, reportPath),
|
|
19677
21022
|
verdictsCount: validatedVerdicts.length,
|
|
19678
21023
|
linkedCount: linked.length,
|
|
19679
21024
|
...cloudInfo ? { cloud: cloudInfo } : {}
|
|
@@ -19686,18 +21031,18 @@ ${formatIssues(fileResult.error)}`,
|
|
|
19686
21031
|
}
|
|
19687
21032
|
console.log();
|
|
19688
21033
|
console.log(
|
|
19689
|
-
` ${symbols.success} Recorded ${validatedVerdicts.length} verdicts for run ${
|
|
21034
|
+
` ${symbols.success} Recorded ${validatedVerdicts.length} verdicts for run ${chalk30.cyan(runId)}`
|
|
19690
21035
|
);
|
|
19691
21036
|
if (linked.length > 0) {
|
|
19692
21037
|
console.log(
|
|
19693
|
-
` ${
|
|
21038
|
+
` ${chalk30.dim("linked:")} ${linked.length} finding${linked.length === 1 ? "" : "s"} to the human test${linked.length === 1 ? "" : "s"} citing ${linked.length === 1 ? "it" : "them"}`
|
|
19694
21039
|
);
|
|
19695
21040
|
}
|
|
19696
21041
|
console.log(
|
|
19697
|
-
` ${
|
|
21042
|
+
` ${chalk30.dim("verdicts:")} ${path27.relative(projectRoot, verdictsPath)}`
|
|
19698
21043
|
);
|
|
19699
21044
|
console.log(
|
|
19700
|
-
` ${
|
|
21045
|
+
` ${chalk30.dim("report:")} ${path27.relative(projectRoot, reportPath)}`
|
|
19701
21046
|
);
|
|
19702
21047
|
printCloudRunInfo(cloudInfo);
|
|
19703
21048
|
await promptViewResults(projectRoot, { ...opts, runId });
|
|
@@ -19761,9 +21106,9 @@ __export(complete_exports, {
|
|
|
19761
21106
|
screenshotSizeWarning: () => screenshotSizeWarning,
|
|
19762
21107
|
validateCanonicalWalkthrough: () => validateCanonicalWalkthrough
|
|
19763
21108
|
});
|
|
19764
|
-
import { promises as
|
|
19765
|
-
import
|
|
19766
|
-
import
|
|
21109
|
+
import { promises as fs29 } from "fs";
|
|
21110
|
+
import path28 from "path";
|
|
21111
|
+
import chalk31 from "chalk";
|
|
19767
21112
|
async function complete(runId, opts) {
|
|
19768
21113
|
const run = await loadRun(runId, opts);
|
|
19769
21114
|
if (!run) return;
|
|
@@ -19773,7 +21118,7 @@ async function complete(runId, opts) {
|
|
|
19773
21118
|
}
|
|
19774
21119
|
if (!opts?.json) {
|
|
19775
21120
|
console.log();
|
|
19776
|
-
console.log(
|
|
21121
|
+
console.log(chalk31.bold(" Assembling canonical JSON\u2026"));
|
|
19777
21122
|
}
|
|
19778
21123
|
const provenanceAudit = auditStepProvenance(
|
|
19779
21124
|
await readStepsForRun(run.projectRoot, runId)
|
|
@@ -19786,8 +21131,8 @@ async function complete(runId, opts) {
|
|
|
19786
21131
|
const walkerSummary = await readWalkerSummary(run.runDirPath);
|
|
19787
21132
|
let judgeReported;
|
|
19788
21133
|
try {
|
|
19789
|
-
const verdictsRaw = await
|
|
19790
|
-
|
|
21134
|
+
const verdictsRaw = await fs29.readFile(
|
|
21135
|
+
path28.join(run.runDirPath, "verdicts.json"),
|
|
19791
21136
|
"utf8"
|
|
19792
21137
|
);
|
|
19793
21138
|
const parsed = JSON.parse(verdictsRaw);
|
|
@@ -19839,16 +21184,16 @@ async function loadRun(runId, opts) {
|
|
|
19839
21184
|
if (!resolved) return null;
|
|
19840
21185
|
const { projectRoot, runDirPath } = resolved;
|
|
19841
21186
|
try {
|
|
19842
|
-
await
|
|
21187
|
+
await fs29.access(runDirPath);
|
|
19843
21188
|
} catch {
|
|
19844
21189
|
commandError(`Run not found: ${runId}`, opts);
|
|
19845
21190
|
return null;
|
|
19846
21191
|
}
|
|
19847
|
-
const manifestPath =
|
|
21192
|
+
const manifestPath = path28.join(runDirPath, "walkthrough.json");
|
|
19848
21193
|
let manifest;
|
|
19849
21194
|
try {
|
|
19850
21195
|
manifest = JSON.parse(
|
|
19851
|
-
await
|
|
21196
|
+
await fs29.readFile(manifestPath, "utf8")
|
|
19852
21197
|
);
|
|
19853
21198
|
} catch {
|
|
19854
21199
|
commandError(`No walkthrough.json found for run: ${runId}`, opts);
|
|
@@ -19860,8 +21205,8 @@ async function reportAlreadyCompleted(run, opts) {
|
|
|
19860
21205
|
const { runId, manifest } = run;
|
|
19861
21206
|
if (opts?.json) {
|
|
19862
21207
|
try {
|
|
19863
|
-
const raw = await
|
|
19864
|
-
|
|
21208
|
+
const raw = await fs29.readFile(
|
|
21209
|
+
path28.join(run.runDirPath, "canonical.json"),
|
|
19865
21210
|
"utf8"
|
|
19866
21211
|
);
|
|
19867
21212
|
console.log(raw.trim());
|
|
@@ -19881,18 +21226,18 @@ async function reportAlreadyCompleted(run, opts) {
|
|
|
19881
21226
|
return;
|
|
19882
21227
|
}
|
|
19883
21228
|
console.log();
|
|
19884
|
-
console.log(
|
|
19885
|
-
console.log(` status ${
|
|
21229
|
+
console.log(chalk31.bold(" Run: ") + chalk31.cyan(runId));
|
|
21230
|
+
console.log(` status ${chalk31.green("completed")}`);
|
|
19886
21231
|
if (manifest.completed_at) {
|
|
19887
|
-
console.log(` completed ${
|
|
21232
|
+
console.log(` completed ${chalk31.dim(manifest.completed_at)}`);
|
|
19888
21233
|
}
|
|
19889
21234
|
const viewUrl = await pushedRunUrl(run.projectRoot, runId);
|
|
19890
21235
|
if (viewUrl) {
|
|
19891
|
-
console.log(` view ${
|
|
21236
|
+
console.log(` view ${chalk31.underline(viewUrl)}`);
|
|
19892
21237
|
}
|
|
19893
21238
|
console.log();
|
|
19894
21239
|
console.log(
|
|
19895
|
-
|
|
21240
|
+
chalk31.dim(
|
|
19896
21241
|
" Already completed \u2014 nothing to do. Use --force to reassemble the report."
|
|
19897
21242
|
)
|
|
19898
21243
|
);
|
|
@@ -19908,13 +21253,13 @@ function reportStrictProvenanceFailure(audit, opts) {
|
|
|
19908
21253
|
}
|
|
19909
21254
|
console.error();
|
|
19910
21255
|
console.error(
|
|
19911
|
-
|
|
21256
|
+
chalk31.red(` ${symbols.error} Strict provenance audit failed:`)
|
|
19912
21257
|
);
|
|
19913
21258
|
for (const w of audit.warnings) {
|
|
19914
|
-
console.error(
|
|
21259
|
+
console.error(chalk31.red(` \u2022 ${w}`));
|
|
19915
21260
|
}
|
|
19916
21261
|
console.error(
|
|
19917
|
-
|
|
21262
|
+
chalk31.dim(
|
|
19918
21263
|
" Run was flagged as synthetic / scripted. Remove --strict-provenance to complete anyway."
|
|
19919
21264
|
)
|
|
19920
21265
|
);
|
|
@@ -19955,41 +21300,41 @@ function completeJsonPayload(canonical, extras) {
|
|
|
19955
21300
|
function printCompletionSummary(run, completedAt, assembled, extras, provenanceAudit) {
|
|
19956
21301
|
const { runId, projectRoot, manifest } = run;
|
|
19957
21302
|
const { canonical } = assembled;
|
|
19958
|
-
const count = (n) => n === 0 ?
|
|
19959
|
-
console.log(
|
|
19960
|
-
console.log(` scenario ${
|
|
19961
|
-
console.log(` url ${
|
|
21303
|
+
const count = (n) => n === 0 ? chalk31.dim("0") : chalk31.cyan(String(n));
|
|
21304
|
+
console.log(chalk31.bold(" Run: ") + chalk31.cyan(runId));
|
|
21305
|
+
console.log(` scenario ${chalk31.dim(manifest.scenario_slug)}`);
|
|
21306
|
+
console.log(` url ${chalk31.dim(manifest.target_url)}`);
|
|
19962
21307
|
if (manifest.environment) {
|
|
19963
|
-
console.log(` env ${
|
|
21308
|
+
console.log(` env ${chalk31.dim(manifest.environment)}`);
|
|
19964
21309
|
}
|
|
19965
21310
|
if (manifest.build) {
|
|
19966
|
-
console.log(` build ${
|
|
21311
|
+
console.log(` build ${chalk31.dim(formatBuild(manifest.build))}`);
|
|
19967
21312
|
}
|
|
19968
21313
|
if (manifest.viewport) {
|
|
19969
|
-
console.log(` device ${
|
|
21314
|
+
console.log(` device ${chalk31.dim(formatViewport(manifest.viewport))}`);
|
|
19970
21315
|
}
|
|
19971
|
-
console.log(` status ${
|
|
21316
|
+
console.log(` status ${chalk31.green("completed")}`);
|
|
19972
21317
|
console.log(` findings ${count(canonical.findings.length)}`);
|
|
19973
21318
|
console.log(` steps ${count(canonical.steps.length)}`);
|
|
19974
|
-
console.log(` started ${
|
|
19975
|
-
console.log(` completed ${
|
|
21319
|
+
console.log(` started ${chalk31.dim(manifest.created_at)}`);
|
|
21320
|
+
console.log(` completed ${chalk31.dim(completedAt)}`);
|
|
19976
21321
|
const models = manifest.models;
|
|
19977
21322
|
if (models?.velocity_sec_per_step != null) {
|
|
19978
21323
|
console.log(
|
|
19979
|
-
` velocity ${
|
|
21324
|
+
` velocity ${chalk31.cyan(`~${models.velocity_sec_per_step}s/step`)} ${chalk31.dim(`(${models.total_duration_sec}s total)`)}`
|
|
19980
21325
|
);
|
|
19981
21326
|
}
|
|
19982
21327
|
if (models && (models.walker || models.judge || models.matcher)) {
|
|
19983
21328
|
const w = models.walker_reported ? `${models.walker} (${models.walker_reported})` : models.walker ?? "fast";
|
|
19984
21329
|
const j = models.judge_reported ? `${models.judge} (${models.judge_reported})` : models.judge ?? "thorough";
|
|
19985
|
-
console.log(` models ${
|
|
21330
|
+
console.log(` models ${chalk31.dim(`walker: ${w} \xB7 judge: ${j}`)}`);
|
|
19986
21331
|
}
|
|
19987
21332
|
console.log();
|
|
19988
21333
|
console.log(
|
|
19989
|
-
` ${
|
|
21334
|
+
` ${chalk31.dim("canonical:")} ${chalk31.dim(path28.relative(projectRoot, assembled.canonicalPath))}`
|
|
19990
21335
|
);
|
|
19991
21336
|
console.log(
|
|
19992
|
-
` ${
|
|
21337
|
+
` ${chalk31.dim("report:")} ${chalk31.dim(path28.relative(projectRoot, assembled.reportPath))}`
|
|
19993
21338
|
);
|
|
19994
21339
|
if (extras.viewportWarning) {
|
|
19995
21340
|
console.log();
|
|
@@ -19997,7 +21342,7 @@ function printCompletionSummary(run, completedAt, assembled, extras, provenanceA
|
|
|
19997
21342
|
}
|
|
19998
21343
|
if (extras.codeWarning) {
|
|
19999
21344
|
console.log();
|
|
20000
|
-
console.log(` ${
|
|
21345
|
+
console.log(` ${chalk31.yellow("!")} ${extras.codeWarning}`);
|
|
20001
21346
|
}
|
|
20002
21347
|
if (extras.revisit) printRevisit(extras.revisit);
|
|
20003
21348
|
if (assembled.verification) printVerification(assembled.verification);
|
|
@@ -20009,13 +21354,13 @@ function printProvenanceWarnings(audit) {
|
|
|
20009
21354
|
if (audit.warnings.length === 0) return;
|
|
20010
21355
|
console.log();
|
|
20011
21356
|
console.log(
|
|
20012
|
-
` ${symbols.warning} ${
|
|
21357
|
+
` ${symbols.warning} ${chalk31.yellow("Step provenance audit flagged suspicious timing:")}`
|
|
20013
21358
|
);
|
|
20014
21359
|
for (const w of audit.warnings) {
|
|
20015
|
-
console.log(` ${
|
|
21360
|
+
console.log(` ${chalk31.dim("\u2022")} ${w}`);
|
|
20016
21361
|
}
|
|
20017
21362
|
console.log(
|
|
20018
|
-
|
|
21363
|
+
chalk31.dim(
|
|
20019
21364
|
" Run appears to be programmatically batch-generated rather than an interactive walkthrough."
|
|
20020
21365
|
)
|
|
20021
21366
|
);
|
|
@@ -20047,17 +21392,17 @@ function screenshotFitsViewport(width, expected) {
|
|
|
20047
21392
|
}
|
|
20048
21393
|
async function screenshotSizeWarning(runDirPath, viewport) {
|
|
20049
21394
|
if (!viewport) return null;
|
|
20050
|
-
const dir =
|
|
21395
|
+
const dir = path28.join(runDirPath, "screenshots");
|
|
20051
21396
|
let first;
|
|
20052
21397
|
try {
|
|
20053
|
-
first = (await
|
|
21398
|
+
first = (await fs29.readdir(dir)).filter((f) => f.toLowerCase().endsWith(".png")).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }))[0];
|
|
20054
21399
|
} catch {
|
|
20055
21400
|
return null;
|
|
20056
21401
|
}
|
|
20057
21402
|
if (!first) return null;
|
|
20058
21403
|
let size = null;
|
|
20059
21404
|
try {
|
|
20060
|
-
const handle = await
|
|
21405
|
+
const handle = await fs29.open(path28.join(dir, first), "r");
|
|
20061
21406
|
try {
|
|
20062
21407
|
const head = Buffer.alloc(24);
|
|
20063
21408
|
const { bytesRead } = await handle.read(head, 0, 24, 0);
|
|
@@ -20074,7 +21419,7 @@ async function screenshotSizeWarning(runDirPath, viewport) {
|
|
|
20074
21419
|
async function readWalkerSummary(runDirPath) {
|
|
20075
21420
|
try {
|
|
20076
21421
|
const data = JSON.parse(
|
|
20077
|
-
await
|
|
21422
|
+
await fs29.readFile(path28.join(runDirPath, "summary.json"), "utf8")
|
|
20078
21423
|
);
|
|
20079
21424
|
const strings = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && x.trim() !== "") : void 0;
|
|
20080
21425
|
const model = typeof data.model === "string" && data.model.trim() !== "" ? data.model.trim() : void 0;
|
|
@@ -20183,7 +21528,7 @@ function validateCanonicalWalkthrough(canonical) {
|
|
|
20183
21528
|
async function applyRecordedVerdicts(canonical, runDirPath) {
|
|
20184
21529
|
try {
|
|
20185
21530
|
const recorded = JSON.parse(
|
|
20186
|
-
await
|
|
21531
|
+
await fs29.readFile(path28.join(runDirPath, "verdicts.json"), "utf8")
|
|
20187
21532
|
);
|
|
20188
21533
|
if (Array.isArray(recorded.verdicts)) {
|
|
20189
21534
|
return applyVerdicts(canonical, recorded);
|
|
@@ -20193,7 +21538,7 @@ async function applyRecordedVerdicts(canonical, runDirPath) {
|
|
|
20193
21538
|
return canonical;
|
|
20194
21539
|
}
|
|
20195
21540
|
async function persistCanonical(runDirPath, canonical) {
|
|
20196
|
-
const canonicalPath =
|
|
21541
|
+
const canonicalPath = path28.join(runDirPath, "canonical.json");
|
|
20197
21542
|
await atomicWriteFile(
|
|
20198
21543
|
canonicalPath,
|
|
20199
21544
|
`${JSON.stringify(canonical, null, 2)}
|
|
@@ -20202,7 +21547,7 @@ async function persistCanonical(runDirPath, canonical) {
|
|
|
20202
21547
|
return canonicalPath;
|
|
20203
21548
|
}
|
|
20204
21549
|
async function persistReport(runDirPath, canonical) {
|
|
20205
|
-
const reportPath =
|
|
21550
|
+
const reportPath = path28.join(runDirPath, "report.md");
|
|
20206
21551
|
await atomicWriteFile(reportPath, renderCanonicalMarkdown(canonical));
|
|
20207
21552
|
return reportPath;
|
|
20208
21553
|
}
|
|
@@ -20302,17 +21647,17 @@ function placeLabel(p) {
|
|
|
20302
21647
|
function printRevisit(r) {
|
|
20303
21648
|
const missed = r.places.filter((p) => !p.reached);
|
|
20304
21649
|
console.log();
|
|
20305
|
-
const head = ` ${
|
|
21650
|
+
const head = ` ${chalk31.bold("Re-check places:")} reached ${r.reached} of ${r.requested}`;
|
|
20306
21651
|
if (missed.length === 0) {
|
|
20307
|
-
console.log(`${head} ${
|
|
21652
|
+
console.log(`${head} ${chalk31.green("\u2713")}`);
|
|
20308
21653
|
return;
|
|
20309
21654
|
}
|
|
20310
21655
|
console.log(`${head} \u2014 not reached:`);
|
|
20311
21656
|
for (const p of missed) {
|
|
20312
|
-
console.log(` ${
|
|
21657
|
+
console.log(` ${chalk31.yellow("\u25CB")} ${placeLabel(p)}`);
|
|
20313
21658
|
}
|
|
20314
21659
|
console.log(
|
|
20315
|
-
|
|
21660
|
+
chalk31.dim(
|
|
20316
21661
|
" Fixes there stay awaiting a re-run until a walk gets there."
|
|
20317
21662
|
)
|
|
20318
21663
|
);
|
|
@@ -20320,7 +21665,7 @@ function printRevisit(r) {
|
|
|
20320
21665
|
function printVerification(verification) {
|
|
20321
21666
|
console.log();
|
|
20322
21667
|
console.log(
|
|
20323
|
-
|
|
21668
|
+
chalk31.bold(" Since this scenario's last run") + chalk31.dim(` (${verification.baseline_run_id.slice(0, 30)}\u2026)`) + chalk31.bold(":")
|
|
20324
21669
|
);
|
|
20325
21670
|
if (verification.resolved.length > 0) {
|
|
20326
21671
|
console.log(
|
|
@@ -20329,7 +21674,7 @@ function printVerification(verification) {
|
|
|
20329
21674
|
}
|
|
20330
21675
|
if (verification.not_observed.length > 0) {
|
|
20331
21676
|
console.log(
|
|
20332
|
-
` ${symbols.pending} ${verification.not_observed.length} not seen this run ${
|
|
21677
|
+
` ${symbols.pending} ${verification.not_observed.length} not seen this run ${chalk31.dim("(not evidence of a fix)")}`
|
|
20333
21678
|
);
|
|
20334
21679
|
}
|
|
20335
21680
|
if (verification.persists.length > 0) {
|
|
@@ -20370,14 +21715,14 @@ __export(match_exports, {
|
|
|
20370
21715
|
match: () => match,
|
|
20371
21716
|
matchRecord: () => matchRecord
|
|
20372
21717
|
});
|
|
20373
|
-
import { promises as
|
|
20374
|
-
import
|
|
20375
|
-
import
|
|
21718
|
+
import { promises as fs30 } from "fs";
|
|
21719
|
+
import path29 from "path";
|
|
21720
|
+
import chalk32 from "chalk";
|
|
20376
21721
|
async function completedManifest2(runDirPath, runId, opts) {
|
|
20377
21722
|
let manifest;
|
|
20378
21723
|
try {
|
|
20379
21724
|
manifest = JSON.parse(
|
|
20380
|
-
await
|
|
21725
|
+
await fs30.readFile(path29.join(runDirPath, "walkthrough.json"), "utf8")
|
|
20381
21726
|
);
|
|
20382
21727
|
} catch {
|
|
20383
21728
|
commandError(`Run not found: ${runId}`, opts);
|
|
@@ -20406,12 +21751,12 @@ async function match(runId, opts) {
|
|
|
20406
21751
|
);
|
|
20407
21752
|
const needed = input.current.length > 0 && (input.prior.length > 0 || (input.guidance ?? []).length > 0);
|
|
20408
21753
|
const prompt = needed ? composeMatchPrompt(input) : null;
|
|
20409
|
-
const promptFilePath = opts?.promptFile ??
|
|
21754
|
+
const promptFilePath = opts?.promptFile ?? path29.join(runDirPath, "match_prompt.md");
|
|
20410
21755
|
if (prompt) {
|
|
20411
|
-
await
|
|
21756
|
+
await fs30.writeFile(promptFilePath, prompt, "utf8");
|
|
20412
21757
|
} else {
|
|
20413
21758
|
try {
|
|
20414
|
-
await
|
|
21759
|
+
await fs30.unlink(promptFilePath);
|
|
20415
21760
|
} catch {
|
|
20416
21761
|
}
|
|
20417
21762
|
}
|
|
@@ -20421,8 +21766,8 @@ async function match(runId, opts) {
|
|
|
20421
21766
|
{
|
|
20422
21767
|
...input,
|
|
20423
21768
|
needed,
|
|
20424
|
-
prompt_file: prompt ?
|
|
20425
|
-
prompt_file_abs: prompt ?
|
|
21769
|
+
prompt_file: prompt ? path29.relative(ctx.projectRoot, promptFilePath) : null,
|
|
21770
|
+
prompt_file_abs: prompt ? path29.resolve(promptFilePath) : null,
|
|
20426
21771
|
match_prompt: prompt
|
|
20427
21772
|
},
|
|
20428
21773
|
null,
|
|
@@ -20433,7 +21778,7 @@ async function match(runId, opts) {
|
|
|
20433
21778
|
}
|
|
20434
21779
|
if (!prompt) {
|
|
20435
21780
|
console.log(
|
|
20436
|
-
|
|
21781
|
+
chalk32.dim(
|
|
20437
21782
|
" No earlier findings or tester guidance for this scenario to match against \u2014 nothing to do."
|
|
20438
21783
|
)
|
|
20439
21784
|
);
|
|
@@ -20449,7 +21794,7 @@ async function matchRecord(runId, opts) {
|
|
|
20449
21794
|
if (!manifest) return;
|
|
20450
21795
|
let answer;
|
|
20451
21796
|
try {
|
|
20452
|
-
answer = JSON.parse(await
|
|
21797
|
+
answer = JSON.parse(await fs30.readFile(opts.file, "utf8"));
|
|
20453
21798
|
} catch {
|
|
20454
21799
|
commandError(`Cannot read matches file: ${opts.file}`, opts);
|
|
20455
21800
|
return;
|
|
@@ -20490,17 +21835,17 @@ async function matchRecord(runId, opts) {
|
|
|
20490
21835
|
}
|
|
20491
21836
|
console.log();
|
|
20492
21837
|
console.log(
|
|
20493
|
-
` ${
|
|
21838
|
+
` ${chalk32.green("\u2713")} Matched ${result.linked} finding${result.linked === 1 ? "" : "s"} to earlier ones, ${result.new} first seen, ${result.rechecked_fixed} earlier re-checked fixed`
|
|
20494
21839
|
);
|
|
20495
21840
|
if (result.suppressed > 0) {
|
|
20496
21841
|
console.log(
|
|
20497
|
-
` ${
|
|
21842
|
+
` ${chalk32.yellow("!")} ${result.suppressed} finding${result.suppressed === 1 ? "" : "s"} match tester guidance (flagged with suppressed_by_guidance \u2014 dismiss them in triage if the rule holds)`
|
|
20498
21843
|
);
|
|
20499
21844
|
}
|
|
20500
21845
|
if (verification) printVerification(verification);
|
|
20501
21846
|
console.log();
|
|
20502
21847
|
console.log(
|
|
20503
|
-
|
|
21848
|
+
chalk32.dim(" Run `iamu sync` to push the linked findings to the cloud.")
|
|
20504
21849
|
);
|
|
20505
21850
|
console.log();
|
|
20506
21851
|
}
|
|
@@ -20521,7 +21866,7 @@ __export(prune_exports, {
|
|
|
20521
21866
|
prune: () => prune
|
|
20522
21867
|
});
|
|
20523
21868
|
import { confirm, isCancel as isCancel5 } from "@clack/prompts";
|
|
20524
|
-
import
|
|
21869
|
+
import chalk33 from "chalk";
|
|
20525
21870
|
function formatBytes(bytes) {
|
|
20526
21871
|
if (bytes <= 0) return "0 B";
|
|
20527
21872
|
if (bytes < 1024) return `${bytes} B`;
|
|
@@ -20567,7 +21912,7 @@ async function prune(opts = {}) {
|
|
|
20567
21912
|
});
|
|
20568
21913
|
if (preview.prunedRuns.length === 0) {
|
|
20569
21914
|
console.log(
|
|
20570
|
-
|
|
21915
|
+
chalk33.dim(" No runs match the pruning criteria. Nothing to prune.")
|
|
20571
21916
|
);
|
|
20572
21917
|
return preview;
|
|
20573
21918
|
}
|
|
@@ -20576,7 +21921,7 @@ async function prune(opts = {}) {
|
|
|
20576
21921
|
message: `Prune ${mode} ${preview.prunedRuns.length} run(s) and reclaim ${formatBytes(preview.totalBytesReclaimed)}?`
|
|
20577
21922
|
});
|
|
20578
21923
|
if (isCancel5(confirmed) || !confirmed) {
|
|
20579
|
-
console.log(
|
|
21924
|
+
console.log(chalk33.yellow(" Pruning cancelled."));
|
|
20580
21925
|
return;
|
|
20581
21926
|
}
|
|
20582
21927
|
}
|
|
@@ -20587,14 +21932,14 @@ async function prune(opts = {}) {
|
|
|
20587
21932
|
}
|
|
20588
21933
|
if (result.dryRun) {
|
|
20589
21934
|
console.log(
|
|
20590
|
-
|
|
21935
|
+
chalk33.cyan(
|
|
20591
21936
|
`
|
|
20592
21937
|
[DRY RUN] Would prune ${result.prunedRuns.length} of ${result.totalRunsScanned} run(s):`
|
|
20593
21938
|
)
|
|
20594
21939
|
);
|
|
20595
21940
|
} else {
|
|
20596
21941
|
console.log(
|
|
20597
|
-
|
|
21942
|
+
chalk33.bold(
|
|
20598
21943
|
`
|
|
20599
21944
|
${symbols.success} Pruned ${result.prunedRuns.length} of ${result.totalRunsScanned} run(s):`
|
|
20600
21945
|
)
|
|
@@ -20602,18 +21947,18 @@ async function prune(opts = {}) {
|
|
|
20602
21947
|
}
|
|
20603
21948
|
if (result.prunedRuns.length === 0) {
|
|
20604
21949
|
console.log(
|
|
20605
|
-
|
|
21950
|
+
chalk33.dim(" No runs matched the pruning criteria. Nothing pruned.")
|
|
20606
21951
|
);
|
|
20607
21952
|
} else {
|
|
20608
21953
|
for (const run of result.prunedRuns) {
|
|
20609
21954
|
const mode = run.fullyDeleted ? "removed" : "screenshots purged";
|
|
20610
21955
|
const count = run.screenshotsDeleted > 0 ? `${run.screenshotsDeleted} screenshots, ` : "";
|
|
20611
21956
|
console.log(
|
|
20612
|
-
` ${
|
|
21957
|
+
` ${chalk33.dim("\u2022")} ${chalk33.bold(run.runId)} ${chalk33.dim(`(${run.scenarioSlug ?? "unknown scenario"})`)}: ${chalk33.yellow(formatBytes(run.bytesReclaimed))} (${count}${mode})`
|
|
20613
21958
|
);
|
|
20614
21959
|
}
|
|
20615
21960
|
console.log(
|
|
20616
|
-
|
|
21961
|
+
chalk33.dim(
|
|
20617
21962
|
`
|
|
20618
21963
|
Total reclaimed: ${formatBytes(result.totalBytesReclaimed)} (${result.totalScreenshotsDeleted} screenshots deleted)
|
|
20619
21964
|
`
|
|
@@ -20764,10 +22109,13 @@ function registerProjectCommands(program) {
|
|
|
20764
22109
|
await status2(opts);
|
|
20765
22110
|
});
|
|
20766
22111
|
program.command("doctor").helpGroup("Project:").description(
|
|
20767
|
-
"Diagnose environment: runtime, browser automation, skills, cloud"
|
|
22112
|
+
"Diagnose environment: runtime, browser automation, skills, cloud, and target testability"
|
|
22113
|
+
).option(
|
|
22114
|
+
"-t, --target <url>",
|
|
22115
|
+
"Probe target URL health, reachability, and auth redirects"
|
|
20768
22116
|
).option("--json", "Output diagnostics as JSON").addHelpText(
|
|
20769
22117
|
"after",
|
|
20770
|
-
"\nExamples:\n $ iamu doctor\n $ iamu doctor --json"
|
|
22118
|
+
"\nExamples:\n $ iamu doctor\n $ iamu doctor --target http://localhost:3000\n $ iamu doctor --json"
|
|
20771
22119
|
).action(async (opts) => {
|
|
20772
22120
|
const { doctor: doctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
|
|
20773
22121
|
await doctor2(opts);
|
|
@@ -20799,9 +22147,9 @@ function registerProjectCommands(program) {
|
|
|
20799
22147
|
"after",
|
|
20800
22148
|
"\nExamples:\n $ iamu validate --all\n $ iamu validate --all --fix --dry-run\n $ iamu validate iamu/personas/maria.md"
|
|
20801
22149
|
).action(
|
|
20802
|
-
async (
|
|
22150
|
+
async (path30, opts) => {
|
|
20803
22151
|
const { validate: validate2 } = await Promise.resolve().then(() => (init_validate(), validate_exports));
|
|
20804
|
-
await validate2(
|
|
22152
|
+
await validate2(path30, opts);
|
|
20805
22153
|
}
|
|
20806
22154
|
);
|
|
20807
22155
|
program.command("update").helpGroup("Project:").description("Update and repair agent skills and prompts to latest version").option(
|
|
@@ -20822,6 +22170,12 @@ function registerProjectCommands(program) {
|
|
|
20822
22170
|
).option("--outline <slug>", "Outline slug (scenario only)").option("--persona <slug>", "Persona slug (scenario only)").option(
|
|
20823
22171
|
"--entry <path>",
|
|
20824
22172
|
"Where the situation starts, as a path on the product, e.g. /en/episodes/1 (scenario only)"
|
|
22173
|
+
).option(
|
|
22174
|
+
"--state-description <text>",
|
|
22175
|
+
"Assumed environment data/records state (scenario only)"
|
|
22176
|
+
).option(
|
|
22177
|
+
"--state-approximation <text>",
|
|
22178
|
+
"Guidance on how persona should approximate/squint if records are missing (scenario only)"
|
|
20825
22179
|
).option("--url <url>", "Document the source cites (source only, required)").option(
|
|
20826
22180
|
"--authority <level>",
|
|
20827
22181
|
"legislation | regulation | standard | company_policy | guideline | informational (source only, default informational)"
|
|
@@ -20834,6 +22188,54 @@ function registerProjectCommands(program) {
|
|
|
20834
22188
|
await newEntity2(type2, opts);
|
|
20835
22189
|
}
|
|
20836
22190
|
);
|
|
22191
|
+
const explore = program.command("explore").helpGroup("Project:").description(
|
|
22192
|
+
"Map the running product with a read-only crawl; compare the map with runs (coverage) or with another map (diff)"
|
|
22193
|
+
);
|
|
22194
|
+
explore.command("run [url]", { isDefault: true }).description(
|
|
22195
|
+
"Crawl from <url> (default: target_url in iamu/testability.md) and write iamu/.cache/explore/<id>/map.json"
|
|
22196
|
+
).option("--max-surfaces <n>", "Stop after this many surfaces (default 40)").option("--max-actions <n>", "Stop after this many clicks (default 300)").option(
|
|
22197
|
+
"--max-depth <n>",
|
|
22198
|
+
"Don't go more than this many clicks from the start (default 6)"
|
|
22199
|
+
).option(
|
|
22200
|
+
"--deny <pattern...>",
|
|
22201
|
+
"Never click a control whose name or link matches (regex, case-insensitive)"
|
|
22202
|
+
).option("--viewport <WxH>", "Page size, e.g. 390x844 (default 1280x800)").option(
|
|
22203
|
+
"--attach",
|
|
22204
|
+
"Crawl in a new tab of the `iamu browse` browser, keeping its sign-ins"
|
|
22205
|
+
).option("--json", "Output as JSON").addHelpText(
|
|
22206
|
+
"after",
|
|
22207
|
+
`
|
|
22208
|
+
Read-only: controls that read like writes (delete, pay, send, save, log out \u2026),
|
|
22209
|
+
form submits, downloads and other origins are never clicked, nothing is typed,
|
|
22210
|
+
and every request but GET/HEAD/OPTIONS is blocked before it leaves the browser.
|
|
22211
|
+
|
|
22212
|
+
Examples:
|
|
22213
|
+
$ iamu explore http://localhost:3000
|
|
22214
|
+
$ iamu explore --attach --deny "^Admin"
|
|
22215
|
+
$ iamu explore coverage
|
|
22216
|
+
$ iamu explore diff`
|
|
22217
|
+
).action(
|
|
22218
|
+
async (url, opts) => {
|
|
22219
|
+
const { exploreRun: exploreRun2 } = await Promise.resolve().then(() => (init_explore(), explore_exports));
|
|
22220
|
+
await exploreRun2(url, opts);
|
|
22221
|
+
}
|
|
22222
|
+
);
|
|
22223
|
+
explore.command("coverage [map]").description(
|
|
22224
|
+
"Surfaces of a map (default: latest) that no run reached, and places runs reached that the map lacks"
|
|
22225
|
+
).option("--json", "Output as JSON").action(async (map2, opts) => {
|
|
22226
|
+
const { exploreCoverageCommand: exploreCoverageCommand2 } = await Promise.resolve().then(() => (init_explore(), explore_exports));
|
|
22227
|
+
await exploreCoverageCommand2(map2, opts);
|
|
22228
|
+
});
|
|
22229
|
+
explore.command("diff [a] [b]").description("Compare two maps (default: the two latest)").option("--json", "Output as JSON").action(
|
|
22230
|
+
async (a, b, opts) => {
|
|
22231
|
+
const { exploreDiffCommand: exploreDiffCommand2 } = await Promise.resolve().then(() => (init_explore(), explore_exports));
|
|
22232
|
+
await exploreDiffCommand2(a, b, opts);
|
|
22233
|
+
}
|
|
22234
|
+
);
|
|
22235
|
+
explore.command("list").description("List maps").option("--json", "Output as JSON").action(async (opts) => {
|
|
22236
|
+
const { exploreList: exploreList2 } = await Promise.resolve().then(() => (init_explore(), explore_exports));
|
|
22237
|
+
await exploreList2(opts);
|
|
22238
|
+
});
|
|
20837
22239
|
program.command("completion [shell]").helpGroup("Project:").description("Output shell completion script (bash, zsh, fish)").action(async (shell) => {
|
|
20838
22240
|
const { completion: completion2 } = await Promise.resolve().then(() => (init_completion(), completion_exports));
|
|
20839
22241
|
completion2(program, shell);
|
|
@@ -21041,7 +22443,7 @@ function registerAllCommands(program) {
|
|
|
21041
22443
|
|
|
21042
22444
|
// src/ui/panic.ts
|
|
21043
22445
|
init_logger();
|
|
21044
|
-
import
|
|
22446
|
+
import chalk34 from "chalk";
|
|
21045
22447
|
var handlersRegistered = false;
|
|
21046
22448
|
var customSigintHandler = null;
|
|
21047
22449
|
function registerSigintCleanup(handler) {
|
|
@@ -21053,17 +22455,17 @@ function registerSigintCleanup(handler) {
|
|
|
21053
22455
|
function logFatalAndExit(title, errOrReason) {
|
|
21054
22456
|
process.stderr.write("\x1B[?25h");
|
|
21055
22457
|
if (isVerbose()) {
|
|
21056
|
-
console.error(
|
|
22458
|
+
console.error(chalk34.red(`
|
|
21057
22459
|
${title}:`));
|
|
21058
22460
|
console.error(
|
|
21059
22461
|
errOrReason instanceof Error ? errOrReason.stack || errOrReason : errOrReason
|
|
21060
22462
|
);
|
|
21061
22463
|
} else {
|
|
21062
22464
|
const msg = errOrReason instanceof Error ? errOrReason.message : String(errOrReason);
|
|
21063
|
-
console.error(
|
|
22465
|
+
console.error(chalk34.red(`
|
|
21064
22466
|
Fatal Error: ${msg}`));
|
|
21065
22467
|
console.error(
|
|
21066
|
-
|
|
22468
|
+
chalk34.dim(
|
|
21067
22469
|
" Run with --verbose or DEBUG=1 to view the full stack trace.\n"
|
|
21068
22470
|
)
|
|
21069
22471
|
);
|
|
@@ -21082,7 +22484,7 @@ function setupProcessHandlers() {
|
|
|
21082
22484
|
}
|
|
21083
22485
|
process.exit(130);
|
|
21084
22486
|
} else {
|
|
21085
|
-
console.error(
|
|
22487
|
+
console.error(chalk34.dim("\n Operation cancelled."));
|
|
21086
22488
|
process.exit(130);
|
|
21087
22489
|
}
|
|
21088
22490
|
});
|