@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/bin.js
CHANGED
|
@@ -824,7 +824,14 @@ var init_scenario = __esm({
|
|
|
824
824
|
reviews: z15.array(z15.string().min(1).max(64)).max(16).default([]),
|
|
825
825
|
baseline_score: z15.number().min(0).max(100).nullish(),
|
|
826
826
|
current_score: z15.number().min(0).max(100).nullish(),
|
|
827
|
-
target_score: z15.number().min(0).max(100).default(95)
|
|
827
|
+
target_score: z15.number().min(0).max(100).default(95),
|
|
828
|
+
/** Environment state preconditions and approximation guidance. */
|
|
829
|
+
state: z15.object({
|
|
830
|
+
/** Description of data/records expected to exist in the environment. */
|
|
831
|
+
description: z15.string().max(2048).nullish(),
|
|
832
|
+
/** Guidance on how to squint or approximate if exact records are absent. */
|
|
833
|
+
approximation: z15.string().max(2048).nullish()
|
|
834
|
+
}).nullish()
|
|
828
835
|
});
|
|
829
836
|
}
|
|
830
837
|
});
|
|
@@ -10481,9 +10488,275 @@ var init_prune = __esm({
|
|
|
10481
10488
|
}
|
|
10482
10489
|
});
|
|
10483
10490
|
|
|
10484
|
-
// ../../packages/local-store/dist/
|
|
10491
|
+
// ../../packages/local-store/dist/explore-map.js
|
|
10492
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
10485
10493
|
import { promises as fs19 } from "fs";
|
|
10486
10494
|
import path15 from "path";
|
|
10495
|
+
function templateToken(token) {
|
|
10496
|
+
if (token === "")
|
|
10497
|
+
return token;
|
|
10498
|
+
if (/^\d+$/.test(token))
|
|
10499
|
+
return ":id";
|
|
10500
|
+
if (UUID.test(token))
|
|
10501
|
+
return ":id";
|
|
10502
|
+
if (DATE.test(token))
|
|
10503
|
+
return ":date";
|
|
10504
|
+
if (/^[0-9a-f]{8,}$/i.test(token) && /\d/.test(token))
|
|
10505
|
+
return ":id";
|
|
10506
|
+
if (token.length >= 16 && /\d/.test(token) && /[a-z]/i.test(token)) {
|
|
10507
|
+
return ":id";
|
|
10508
|
+
}
|
|
10509
|
+
return token;
|
|
10510
|
+
}
|
|
10511
|
+
function templatePath(pathname) {
|
|
10512
|
+
const trimmed = pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname || "/";
|
|
10513
|
+
return trimmed.split("/").map((seg) => templateToken(decodeSegment(seg))).join("/");
|
|
10514
|
+
}
|
|
10515
|
+
function encodeQueryValue(v) {
|
|
10516
|
+
return v.startsWith(":") ? v : encodeURIComponent(v);
|
|
10517
|
+
}
|
|
10518
|
+
function decodeSegment(seg) {
|
|
10519
|
+
try {
|
|
10520
|
+
return decodeURIComponent(seg);
|
|
10521
|
+
} catch {
|
|
10522
|
+
return seg;
|
|
10523
|
+
}
|
|
10524
|
+
}
|
|
10525
|
+
function explorePlaceKey(raw) {
|
|
10526
|
+
if (!raw)
|
|
10527
|
+
return null;
|
|
10528
|
+
let u;
|
|
10529
|
+
try {
|
|
10530
|
+
u = new URL(raw);
|
|
10531
|
+
} catch {
|
|
10532
|
+
return null;
|
|
10533
|
+
}
|
|
10534
|
+
if (u.protocol !== "http:" && u.protocol !== "https:")
|
|
10535
|
+
return null;
|
|
10536
|
+
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));
|
|
10537
|
+
const query = params.map(([k, v]) => `${encodeURIComponent(k)}=${encodeQueryValue(v)}`).join("&");
|
|
10538
|
+
let key = templatePath(u.pathname);
|
|
10539
|
+
if (query)
|
|
10540
|
+
key += `?${query}`;
|
|
10541
|
+
const route = /^#!?(\/.*)$/.exec(u.hash);
|
|
10542
|
+
if (route) {
|
|
10543
|
+
const [hashPath] = route[1].split("?");
|
|
10544
|
+
key += `#${templatePath(hashPath)}`;
|
|
10545
|
+
}
|
|
10546
|
+
return key;
|
|
10547
|
+
}
|
|
10548
|
+
function exploreViewSignature(headings) {
|
|
10549
|
+
return headings.map((h) => h.trim().replace(/\s+/g, " ").replace(/\d+/g, "#").toLowerCase()).filter(Boolean).join(" | ");
|
|
10550
|
+
}
|
|
10551
|
+
function exploreSignature(headings, title) {
|
|
10552
|
+
return exploreViewSignature(headings) || title.trim().toLowerCase();
|
|
10553
|
+
}
|
|
10554
|
+
function matchExploreSurface(map2, url, signature) {
|
|
10555
|
+
const place = explorePlaceKey(url);
|
|
10556
|
+
if (!place)
|
|
10557
|
+
return null;
|
|
10558
|
+
const atPlace = map2.surfaces.filter((s) => s.place === place);
|
|
10559
|
+
return atPlace.find((s) => s.signature === signature) ?? (atPlace.length === 1 ? atPlace[0] : null);
|
|
10560
|
+
}
|
|
10561
|
+
function exploreSurfaceKey(place, view) {
|
|
10562
|
+
return view === null ? place : `${place}~${fnv1a(view)}`;
|
|
10563
|
+
}
|
|
10564
|
+
function fnv1a(s) {
|
|
10565
|
+
let h = 2166136261;
|
|
10566
|
+
for (let i = 0; i < s.length; i++) {
|
|
10567
|
+
h ^= s.charCodeAt(i);
|
|
10568
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
10569
|
+
}
|
|
10570
|
+
return h.toString(16).padStart(8, "0");
|
|
10571
|
+
}
|
|
10572
|
+
function exploreDir(projectRoot) {
|
|
10573
|
+
return path15.join(cacheDir(projectRoot), "explore");
|
|
10574
|
+
}
|
|
10575
|
+
function composeExploreId(now, randomHex) {
|
|
10576
|
+
const ts = now.toISOString().replace(/[:.]/g, "-").replace(/Z$/, "");
|
|
10577
|
+
return `${ts}-${randomHex ?? randomBytes2(3).toString("hex")}`;
|
|
10578
|
+
}
|
|
10579
|
+
function isValidExploreId(id) {
|
|
10580
|
+
return MAP_ID.test(id);
|
|
10581
|
+
}
|
|
10582
|
+
function mapPath(projectRoot, id) {
|
|
10583
|
+
if (!isValidExploreId(id))
|
|
10584
|
+
throw new Error(`Not an explore map id: ${id}`);
|
|
10585
|
+
return path15.join(exploreDir(projectRoot), id, "map.json");
|
|
10586
|
+
}
|
|
10587
|
+
async function writeExploreMap(projectRoot, map2) {
|
|
10588
|
+
const file = mapPath(projectRoot, map2.id);
|
|
10589
|
+
await fs19.mkdir(path15.dirname(file), { recursive: true });
|
|
10590
|
+
await atomicWriteFile(file, `${JSON.stringify(map2, null, 2)}
|
|
10591
|
+
`);
|
|
10592
|
+
return file;
|
|
10593
|
+
}
|
|
10594
|
+
async function readExploreMap(projectRoot, id) {
|
|
10595
|
+
const raw = await fs19.readFile(mapPath(projectRoot, id), "utf8");
|
|
10596
|
+
const map2 = JSON.parse(raw);
|
|
10597
|
+
if (map2?.schema !== EXPLORE_MAP_SCHEMA || !Array.isArray(map2.surfaces)) {
|
|
10598
|
+
throw new Error(`Unreadable explore map: ${id}`);
|
|
10599
|
+
}
|
|
10600
|
+
return map2;
|
|
10601
|
+
}
|
|
10602
|
+
async function listExploreMaps(projectRoot) {
|
|
10603
|
+
try {
|
|
10604
|
+
const entries = await fs19.readdir(exploreDir(projectRoot), {
|
|
10605
|
+
withFileTypes: true
|
|
10606
|
+
});
|
|
10607
|
+
return entries.filter((e) => e.isDirectory() && isValidExploreId(e.name)).map((e) => e.name).sort();
|
|
10608
|
+
} catch (err) {
|
|
10609
|
+
if (err.code === "ENOENT")
|
|
10610
|
+
return [];
|
|
10611
|
+
throw err;
|
|
10612
|
+
}
|
|
10613
|
+
}
|
|
10614
|
+
function exploreCoverage(map2, steps) {
|
|
10615
|
+
const byPlace = /* @__PURE__ */ new Map();
|
|
10616
|
+
for (const s of map2.surfaces) {
|
|
10617
|
+
byPlace.set(s.place, [...byPlace.get(s.place) ?? [], s]);
|
|
10618
|
+
}
|
|
10619
|
+
const reachedBy = /* @__PURE__ */ new Map();
|
|
10620
|
+
const unresolvedBy = /* @__PURE__ */ new Map();
|
|
10621
|
+
const runOnlyBy = /* @__PURE__ */ new Map();
|
|
10622
|
+
const add = (m, k, s) => m.set(k, [...m.get(k) ?? [], s]);
|
|
10623
|
+
for (const step of steps) {
|
|
10624
|
+
const place = explorePlaceKey(step.url);
|
|
10625
|
+
if (!place)
|
|
10626
|
+
continue;
|
|
10627
|
+
const atPlace = byPlace.get(place);
|
|
10628
|
+
if (!atPlace) {
|
|
10629
|
+
add(runOnlyBy, place, step);
|
|
10630
|
+
continue;
|
|
10631
|
+
}
|
|
10632
|
+
const hit = tieStep(map2, atPlace, step);
|
|
10633
|
+
if (hit.length === 0)
|
|
10634
|
+
add(unresolvedBy, place, step);
|
|
10635
|
+
for (const s of hit)
|
|
10636
|
+
add(reachedBy, s.key, step);
|
|
10637
|
+
}
|
|
10638
|
+
const surfaces = map2.surfaces.map((s) => {
|
|
10639
|
+
const base = {
|
|
10640
|
+
key: s.key,
|
|
10641
|
+
label: s.label,
|
|
10642
|
+
place: s.place,
|
|
10643
|
+
url: s.url,
|
|
10644
|
+
depth: s.depth
|
|
10645
|
+
};
|
|
10646
|
+
const reached = reachedBy.get(s.key);
|
|
10647
|
+
if (reached)
|
|
10648
|
+
return { ...base, status: "reached", runs: distinctRuns(reached) };
|
|
10649
|
+
const unresolved = unresolvedBy.get(s.place);
|
|
10650
|
+
if (unresolved) {
|
|
10651
|
+
return { ...base, status: "place_only", runs: distinctRuns(unresolved) };
|
|
10652
|
+
}
|
|
10653
|
+
return { ...base, status: "unreached", runs: [] };
|
|
10654
|
+
});
|
|
10655
|
+
const runOnly = [...runOnlyBy].map(([place, list2]) => ({
|
|
10656
|
+
place,
|
|
10657
|
+
steps: list2.length,
|
|
10658
|
+
runs: distinctRuns(list2)
|
|
10659
|
+
}));
|
|
10660
|
+
runOnly.sort((a, b) => b.steps - a.steps || a.place.localeCompare(b.place));
|
|
10661
|
+
return {
|
|
10662
|
+
map_id: map2.id,
|
|
10663
|
+
runs: new Set(steps.map((s) => s.run_id)).size,
|
|
10664
|
+
steps: steps.length,
|
|
10665
|
+
reached: surfaces.filter((s) => s.status === "reached").length,
|
|
10666
|
+
place_only: surfaces.filter((s) => s.status === "place_only").length,
|
|
10667
|
+
unreached: surfaces.filter((s) => s.status === "unreached").length,
|
|
10668
|
+
surfaces,
|
|
10669
|
+
run_only_places: runOnly
|
|
10670
|
+
};
|
|
10671
|
+
}
|
|
10672
|
+
function tieStep(map2, atPlace, step) {
|
|
10673
|
+
if (typeof step.signature === "string") {
|
|
10674
|
+
const m = matchExploreSurface(map2, step.url, step.signature);
|
|
10675
|
+
return m ? [m] : [];
|
|
10676
|
+
}
|
|
10677
|
+
if (atPlace.length === 1)
|
|
10678
|
+
return [atPlace[0]];
|
|
10679
|
+
const name = step.surface?.trim();
|
|
10680
|
+
if (!name)
|
|
10681
|
+
return [];
|
|
10682
|
+
return atPlace.filter((s) => [s.label, ...s.headings].filter(Boolean).some((n) => surfaceMatches(n, name) || surfaceMatches(name, n)));
|
|
10683
|
+
}
|
|
10684
|
+
function distinctRuns(steps) {
|
|
10685
|
+
return [...new Set(steps.map((s) => s.run_id))].sort();
|
|
10686
|
+
}
|
|
10687
|
+
async function readAllRunSteps(projectRoot) {
|
|
10688
|
+
const out = [];
|
|
10689
|
+
for (const runId of await listRuns(projectRoot)) {
|
|
10690
|
+
let steps;
|
|
10691
|
+
try {
|
|
10692
|
+
steps = await readStepsForRun(projectRoot, runId);
|
|
10693
|
+
} catch {
|
|
10694
|
+
continue;
|
|
10695
|
+
}
|
|
10696
|
+
for (const s of steps) {
|
|
10697
|
+
out.push({ run_id: runId, url: s.url, surface: s.surface });
|
|
10698
|
+
}
|
|
10699
|
+
}
|
|
10700
|
+
return out;
|
|
10701
|
+
}
|
|
10702
|
+
function actionSignature(a) {
|
|
10703
|
+
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;
|
|
10704
|
+
return `${a.role} "${a.name}" \u2192 ${where}`;
|
|
10705
|
+
}
|
|
10706
|
+
function diffExploreMaps(a, b) {
|
|
10707
|
+
const aByKey = new Map(a.surfaces.map((s) => [s.key, s]));
|
|
10708
|
+
const bByKey = new Map(b.surfaces.map((s) => [s.key, s]));
|
|
10709
|
+
const ref = (s) => ({
|
|
10710
|
+
key: s.key,
|
|
10711
|
+
label: s.label
|
|
10712
|
+
});
|
|
10713
|
+
const onlyA = a.surfaces.filter((s) => !bByKey.has(s.key)).map(ref);
|
|
10714
|
+
const onlyB = b.surfaces.filter((s) => !aByKey.has(s.key)).map(ref);
|
|
10715
|
+
const changed = [];
|
|
10716
|
+
let common2 = 0;
|
|
10717
|
+
for (const sa of a.surfaces) {
|
|
10718
|
+
const sb = bByKey.get(sa.key);
|
|
10719
|
+
if (!sb)
|
|
10720
|
+
continue;
|
|
10721
|
+
common2++;
|
|
10722
|
+
const setA = new Set(sa.actions.map(actionSignature));
|
|
10723
|
+
const setB = new Set(sb.actions.map(actionSignature));
|
|
10724
|
+
const inA = [...setA].filter((x) => !setB.has(x)).sort();
|
|
10725
|
+
const inB = [...setB].filter((x) => !setA.has(x)).sort();
|
|
10726
|
+
if (inA.length > 0 || inB.length > 0) {
|
|
10727
|
+
changed.push({ ...ref(sa), only_a: inA, only_b: inB });
|
|
10728
|
+
}
|
|
10729
|
+
}
|
|
10730
|
+
const union = common2 + onlyA.length + onlyB.length;
|
|
10731
|
+
return {
|
|
10732
|
+
a: a.id,
|
|
10733
|
+
b: b.id,
|
|
10734
|
+
common: common2,
|
|
10735
|
+
only_a: onlyA,
|
|
10736
|
+
only_b: onlyB,
|
|
10737
|
+
surface_overlap: union === 0 ? 1 : common2 / union,
|
|
10738
|
+
changed_actions: changed
|
|
10739
|
+
};
|
|
10740
|
+
}
|
|
10741
|
+
var EXPLORE_MAP_SCHEMA, UUID, DATE, MAP_ID;
|
|
10742
|
+
var init_explore_map = __esm({
|
|
10743
|
+
"../../packages/local-store/dist/explore-map.js"() {
|
|
10744
|
+
"use strict";
|
|
10745
|
+
init_atomic();
|
|
10746
|
+
init_paths();
|
|
10747
|
+
init_revisit();
|
|
10748
|
+
init_runs();
|
|
10749
|
+
init_walkthrough_canonical2();
|
|
10750
|
+
EXPLORE_MAP_SCHEMA = 1;
|
|
10751
|
+
UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
10752
|
+
DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
10753
|
+
MAP_ID = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}-[0-9a-f]{6}$/;
|
|
10754
|
+
}
|
|
10755
|
+
});
|
|
10756
|
+
|
|
10757
|
+
// ../../packages/local-store/dist/human-test-link.js
|
|
10758
|
+
import { promises as fs20 } from "fs";
|
|
10759
|
+
import path16 from "path";
|
|
10487
10760
|
function humanTestLinks(verdicts) {
|
|
10488
10761
|
const best = /* @__PURE__ */ new Map();
|
|
10489
10762
|
for (const v of verdicts) {
|
|
@@ -10506,7 +10779,7 @@ async function linkFindingsToHumanTests(projectRoot, runId, verdicts) {
|
|
|
10506
10779
|
const dir = entityDirPath(projectRoot, "finding");
|
|
10507
10780
|
let names;
|
|
10508
10781
|
try {
|
|
10509
|
-
names = await
|
|
10782
|
+
names = await fs20.readdir(dir);
|
|
10510
10783
|
} catch {
|
|
10511
10784
|
return [];
|
|
10512
10785
|
}
|
|
@@ -10514,11 +10787,11 @@ async function linkFindingsToHumanTests(projectRoot, runId, verdicts) {
|
|
|
10514
10787
|
for (const name of names) {
|
|
10515
10788
|
if (!name.endsWith(".md") || name.startsWith("."))
|
|
10516
10789
|
continue;
|
|
10517
|
-
const file =
|
|
10790
|
+
const file = path16.join(dir, name);
|
|
10518
10791
|
let raw;
|
|
10519
10792
|
let fm;
|
|
10520
10793
|
try {
|
|
10521
|
-
raw = await
|
|
10794
|
+
raw = await fs20.readFile(file, "utf8");
|
|
10522
10795
|
fm = parseFrontmatter(raw).frontmatter;
|
|
10523
10796
|
} catch {
|
|
10524
10797
|
continue;
|
|
@@ -10749,14 +11022,14 @@ var init_cloud_client = __esm({
|
|
|
10749
11022
|
});
|
|
10750
11023
|
|
|
10751
11024
|
// ../../packages/local-store/dist/last-pull.js
|
|
10752
|
-
import { promises as
|
|
10753
|
-
import
|
|
11025
|
+
import { promises as fs21 } from "fs";
|
|
11026
|
+
import path17 from "path";
|
|
10754
11027
|
function lastPullPath(projectRoot) {
|
|
10755
|
-
return
|
|
11028
|
+
return path17.join(cacheDir(projectRoot), "last-pull.json");
|
|
10756
11029
|
}
|
|
10757
11030
|
async function readLastPull(projectRoot) {
|
|
10758
11031
|
try {
|
|
10759
|
-
const raw = await
|
|
11032
|
+
const raw = await fs21.readFile(lastPullPath(projectRoot), "utf8");
|
|
10760
11033
|
const parsed = JSON.parse(raw);
|
|
10761
11034
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
10762
11035
|
return parsed;
|
|
@@ -11042,7 +11315,7 @@ var init_frontmatter2 = __esm({
|
|
|
11042
11315
|
});
|
|
11043
11316
|
|
|
11044
11317
|
// ../../packages/local-store/dist/pull/write.js
|
|
11045
|
-
import { promises as
|
|
11318
|
+
import { promises as fs22 } from "fs";
|
|
11046
11319
|
async function loadLocalIndex(projectRoot, type2) {
|
|
11047
11320
|
const byId = /* @__PURE__ */ new Map();
|
|
11048
11321
|
const bySlug = /* @__PURE__ */ new Map();
|
|
@@ -11071,7 +11344,7 @@ async function classifyExisting(projectRoot, type2, cloudId, localSlug, targetSl
|
|
|
11071
11344
|
if (localSlug !== targetSlug)
|
|
11072
11345
|
return "untracked";
|
|
11073
11346
|
const lastSynced = localIndex.lastPushById[cloudId];
|
|
11074
|
-
const bytes = await
|
|
11347
|
+
const bytes = await fs22.readFile(entityFilePath(projectRoot, type2, localSlug), "utf8");
|
|
11075
11348
|
return lastSynced && hashNormalizedContent(bytes) === lastSynced ? "fast_forward" : "diverged";
|
|
11076
11349
|
}
|
|
11077
11350
|
async function writeOrSkip(projectRoot, type2, cloudId, slug, frontmatter, body, localIndex, mode, cloudVersion) {
|
|
@@ -11132,13 +11405,13 @@ async function writeOrSkip(projectRoot, type2, cloudId, slug, frontmatter, body,
|
|
|
11132
11405
|
}
|
|
11133
11406
|
if (existingSlugForId && existingSlugForId !== slug) {
|
|
11134
11407
|
try {
|
|
11135
|
-
await
|
|
11408
|
+
await fs22.unlink(entityFilePath(projectRoot, type2, existingSlugForId));
|
|
11136
11409
|
localIndex.bySlug.delete(existingSlugForId);
|
|
11137
11410
|
} catch {
|
|
11138
11411
|
}
|
|
11139
11412
|
}
|
|
11140
11413
|
await writeEntity(projectRoot, type2, frontmatter, text);
|
|
11141
|
-
const bytes = await
|
|
11414
|
+
const bytes = await fs22.readFile(entityFilePath(projectRoot, type2, slug), "utf8");
|
|
11142
11415
|
const hash = hashNormalizedContent(bytes);
|
|
11143
11416
|
localIndex.byId.set(cloudId, slug);
|
|
11144
11417
|
localIndex.bySlug.set(slug, cloudId);
|
|
@@ -11192,7 +11465,7 @@ var init_write = __esm({
|
|
|
11192
11465
|
});
|
|
11193
11466
|
|
|
11194
11467
|
// ../../packages/local-store/dist/pull/entities.js
|
|
11195
|
-
import { promises as
|
|
11468
|
+
import { promises as fs23 } from "fs";
|
|
11196
11469
|
function buildContextFrontmatter(payload) {
|
|
11197
11470
|
const cloudCtx = payload.context ?? {};
|
|
11198
11471
|
const glossary = (payload.glossary ?? []).map((g) => ({
|
|
@@ -11247,7 +11520,7 @@ async function pullContext(projectRoot, cloud, projectId, mode, result) {
|
|
|
11247
11520
|
const frontmatter = buildContextFrontmatter(payload);
|
|
11248
11521
|
let hasLocal = false;
|
|
11249
11522
|
try {
|
|
11250
|
-
await
|
|
11523
|
+
await fs23.access(contextPath(projectRoot));
|
|
11251
11524
|
hasLocal = true;
|
|
11252
11525
|
} catch {
|
|
11253
11526
|
hasLocal = false;
|
|
@@ -11277,7 +11550,7 @@ async function pullContext(projectRoot, cloud, projectId, mode, result) {
|
|
|
11277
11550
|
frontmatter,
|
|
11278
11551
|
notes_body: existingNotes
|
|
11279
11552
|
});
|
|
11280
|
-
const bytes = await
|
|
11553
|
+
const bytes = await fs23.readFile(contextPath(projectRoot), "utf8");
|
|
11281
11554
|
const hash = hashNormalizedContent(bytes);
|
|
11282
11555
|
await recordLastPull(projectRoot, "context:root", hash);
|
|
11283
11556
|
await recordLastPush(projectRoot, "context:root", hash);
|
|
@@ -11807,6 +12080,7 @@ var init_dist2 = __esm({
|
|
|
11807
12080
|
init_prune();
|
|
11808
12081
|
init_fix_state();
|
|
11809
12082
|
init_revisit();
|
|
12083
|
+
init_explore_map();
|
|
11810
12084
|
init_walkthrough_canonical2();
|
|
11811
12085
|
init_journey_diff();
|
|
11812
12086
|
init_finding_match();
|
|
@@ -11838,15 +12112,15 @@ function resolveApiUrl(configured) {
|
|
|
11838
12112
|
assertSecureApiUrl(url, configured ? "API URL" : "IAMU_API_URL");
|
|
11839
12113
|
return url;
|
|
11840
12114
|
}
|
|
11841
|
-
function parseStore(raw,
|
|
12115
|
+
function parseStore(raw, path30) {
|
|
11842
12116
|
let parsed;
|
|
11843
12117
|
try {
|
|
11844
12118
|
parsed = JSON.parse(raw);
|
|
11845
12119
|
} catch (err) {
|
|
11846
|
-
throw new CredentialsFileError(
|
|
12120
|
+
throw new CredentialsFileError(path30, "not valid JSON", err);
|
|
11847
12121
|
}
|
|
11848
12122
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
11849
|
-
throw new CredentialsFileError(
|
|
12123
|
+
throw new CredentialsFileError(path30, "expected a JSON object");
|
|
11850
12124
|
}
|
|
11851
12125
|
return parsed;
|
|
11852
12126
|
}
|
|
@@ -11854,26 +12128,26 @@ function isNotFound(err) {
|
|
|
11854
12128
|
return typeof err === "object" && err !== null && err.code === "ENOENT";
|
|
11855
12129
|
}
|
|
11856
12130
|
async function readCredentialsStore() {
|
|
11857
|
-
const
|
|
12131
|
+
const path30 = getCredentialsPath();
|
|
11858
12132
|
let raw;
|
|
11859
12133
|
try {
|
|
11860
|
-
raw = await readFile(
|
|
12134
|
+
raw = await readFile(path30, "utf-8");
|
|
11861
12135
|
} catch (err) {
|
|
11862
12136
|
if (isNotFound(err)) return {};
|
|
11863
12137
|
warnUnreadableStoreOnce(err);
|
|
11864
12138
|
throw err;
|
|
11865
12139
|
}
|
|
11866
12140
|
try {
|
|
11867
|
-
return parseStore(raw,
|
|
12141
|
+
return parseStore(raw, path30);
|
|
11868
12142
|
} catch (err) {
|
|
11869
12143
|
warnUnreadableStoreOnce(err);
|
|
11870
12144
|
throw err;
|
|
11871
12145
|
}
|
|
11872
12146
|
}
|
|
11873
12147
|
async function writeCredentialsStore(store) {
|
|
11874
|
-
const
|
|
11875
|
-
await mkdir(dirname(
|
|
11876
|
-
await atomicWriteFile(
|
|
12148
|
+
const path30 = getCredentialsPath();
|
|
12149
|
+
await mkdir(dirname(path30), { recursive: true, mode: 448 });
|
|
12150
|
+
await atomicWriteFile(path30, `${JSON.stringify(store, null, 2)}
|
|
11877
12151
|
`, {
|
|
11878
12152
|
mode: 384
|
|
11879
12153
|
});
|
|
@@ -11893,16 +12167,16 @@ function resolveApiKey(apiUrl) {
|
|
|
11893
12167
|
} catch {
|
|
11894
12168
|
return null;
|
|
11895
12169
|
}
|
|
11896
|
-
const
|
|
12170
|
+
const path30 = getCredentialsPath();
|
|
11897
12171
|
let raw;
|
|
11898
12172
|
try {
|
|
11899
|
-
raw = readFileSync(
|
|
12173
|
+
raw = readFileSync(path30, "utf-8");
|
|
11900
12174
|
} catch (err) {
|
|
11901
12175
|
if (!isNotFound(err)) warnUnreadableStoreOnce(err);
|
|
11902
12176
|
return null;
|
|
11903
12177
|
}
|
|
11904
12178
|
try {
|
|
11905
|
-
const entry = parseStore(raw,
|
|
12179
|
+
const entry = parseStore(raw, path30)[url];
|
|
11906
12180
|
if (typeof entry !== "object" || entry === null) return null;
|
|
11907
12181
|
const token = entry.token;
|
|
11908
12182
|
return typeof token === "string" && token ? token : null;
|
|
@@ -11941,12 +12215,12 @@ var init_credentials = __esm({
|
|
|
11941
12215
|
* @param reason - What is wrong with it.
|
|
11942
12216
|
* @param cause - Underlying parse error, if any.
|
|
11943
12217
|
*/
|
|
11944
|
-
constructor(
|
|
12218
|
+
constructor(path30, reason, cause) {
|
|
11945
12219
|
super(
|
|
11946
|
-
`Credentials file ${
|
|
12220
|
+
`Credentials file ${path30} is corrupt (${reason}). Fix it by hand, or delete it and run \`iamu auth login\` again.`,
|
|
11947
12221
|
{
|
|
11948
12222
|
cause,
|
|
11949
|
-
suggestedAction: `Delete ${
|
|
12223
|
+
suggestedAction: `Delete ${path30} and run \`iamu auth login\`, or set IAMU_TOKEN.`
|
|
11950
12224
|
}
|
|
11951
12225
|
);
|
|
11952
12226
|
}
|
|
@@ -12325,15 +12599,6 @@ function mcpJson(cmd) {
|
|
|
12325
12599
|
2
|
|
12326
12600
|
);
|
|
12327
12601
|
}
|
|
12328
|
-
function vscodeMcpJson(cmd) {
|
|
12329
|
-
return JSON.stringify(
|
|
12330
|
-
{
|
|
12331
|
-
servers: { iamu: { type: "stdio", command: cmd[0], args: cmd.slice(1) } }
|
|
12332
|
-
},
|
|
12333
|
-
null,
|
|
12334
|
-
2
|
|
12335
|
-
);
|
|
12336
|
-
}
|
|
12337
12602
|
function detectHosts(projectRoot) {
|
|
12338
12603
|
return HOST_ADAPTERS.filter(
|
|
12339
12604
|
(host) => host.detectionPaths.some((p) => existsSync3(join3(projectRoot, p)))
|
|
@@ -12382,13 +12647,17 @@ var init_registry = __esm({
|
|
|
12382
12647
|
{
|
|
12383
12648
|
name: "GitHub Copilot",
|
|
12384
12649
|
value: "github-copilot",
|
|
12385
|
-
detectionPaths: [
|
|
12386
|
-
|
|
12387
|
-
|
|
12388
|
-
|
|
12389
|
-
|
|
12390
|
-
|
|
12391
|
-
|
|
12650
|
+
detectionPaths: [
|
|
12651
|
+
".github/copilot-instructions.md",
|
|
12652
|
+
".github/skills",
|
|
12653
|
+
".copilot"
|
|
12654
|
+
],
|
|
12655
|
+
supportsSkills: true,
|
|
12656
|
+
getSkillsDir: (root) => join3(root, ".github", "skills"),
|
|
12657
|
+
getCommandPath: (root, id) => join3(root, ".github", "skills", `iamu-${id}`, "SKILL.md"),
|
|
12658
|
+
formatCommand: richFormat,
|
|
12659
|
+
getMcpConfigPath: (root) => join3(root, ".mcp.json"),
|
|
12660
|
+
formatMcpConfig: mcpJson
|
|
12392
12661
|
},
|
|
12393
12662
|
{
|
|
12394
12663
|
name: "Cline",
|
|
@@ -12563,9 +12832,17 @@ async function needsWrite(filePath, content) {
|
|
|
12563
12832
|
return true;
|
|
12564
12833
|
}
|
|
12565
12834
|
}
|
|
12566
|
-
function isCliWrittenSkill(
|
|
12835
|
+
function isCliWrittenSkill(path30) {
|
|
12836
|
+
try {
|
|
12837
|
+
return /^generatedBy:/m.test(readFileSync3(path30, "utf-8"));
|
|
12838
|
+
} catch {
|
|
12839
|
+
return false;
|
|
12840
|
+
}
|
|
12841
|
+
}
|
|
12842
|
+
function isCliWrittenPrompt(path30) {
|
|
12567
12843
|
try {
|
|
12568
|
-
|
|
12844
|
+
const content = readFileSync3(path30, "utf-8");
|
|
12845
|
+
return content.includes("iamu") || /^generatedBy:/m.test(content);
|
|
12569
12846
|
} catch {
|
|
12570
12847
|
return false;
|
|
12571
12848
|
}
|
|
@@ -12576,8 +12853,8 @@ function planSkillFiles(hosts, projectRoot, version2) {
|
|
|
12576
12853
|
for (const host of hosts) {
|
|
12577
12854
|
if (pluginProvidesSkills(host, projectRoot)) {
|
|
12578
12855
|
for (const name of skillTemplates.keys()) {
|
|
12579
|
-
const
|
|
12580
|
-
if (isCliWrittenSkill(
|
|
12856
|
+
const path30 = join4(host.getSkillsDir(projectRoot), name, "SKILL.md");
|
|
12857
|
+
if (isCliWrittenSkill(path30)) files.push({ host, path: path30, content: null });
|
|
12581
12858
|
}
|
|
12582
12859
|
} else if (host.supportsSkills) {
|
|
12583
12860
|
const skillsDir = host.getSkillsDir(projectRoot);
|
|
@@ -12598,6 +12875,19 @@ function planSkillFiles(hosts, projectRoot, version2) {
|
|
|
12598
12875
|
}
|
|
12599
12876
|
}
|
|
12600
12877
|
}
|
|
12878
|
+
if (host.value === "github-copilot") {
|
|
12879
|
+
for (const cmd of commandsFromSkills(skillTemplates)) {
|
|
12880
|
+
const legacyPrompt = join4(
|
|
12881
|
+
projectRoot,
|
|
12882
|
+
".github",
|
|
12883
|
+
"prompts",
|
|
12884
|
+
`iamu-${cmd.id}.prompt.md`
|
|
12885
|
+
);
|
|
12886
|
+
if (isCliWrittenPrompt(legacyPrompt)) {
|
|
12887
|
+
files.push({ host, path: legacyPrompt, content: null });
|
|
12888
|
+
}
|
|
12889
|
+
}
|
|
12890
|
+
}
|
|
12601
12891
|
} else {
|
|
12602
12892
|
for (const cmd of commandsFromSkills(skillTemplates)) {
|
|
12603
12893
|
files.push({
|
|
@@ -12622,6 +12912,15 @@ function detectInstalledHosts(projectRoot) {
|
|
|
12622
12912
|
const skillDir = join4(host.getSkillsDir(projectRoot), "iamu-setup");
|
|
12623
12913
|
if (existsSync5(skillDir)) return true;
|
|
12624
12914
|
}
|
|
12915
|
+
if (host.value === "github-copilot") {
|
|
12916
|
+
const legacyPrompt = join4(
|
|
12917
|
+
projectRoot,
|
|
12918
|
+
".github",
|
|
12919
|
+
"prompts",
|
|
12920
|
+
"iamu-setup.prompt.md"
|
|
12921
|
+
);
|
|
12922
|
+
if (existsSync5(legacyPrompt)) return true;
|
|
12923
|
+
}
|
|
12625
12924
|
return false;
|
|
12626
12925
|
});
|
|
12627
12926
|
}
|
|
@@ -14075,7 +14374,7 @@ import {
|
|
|
14075
14374
|
readlinkSync,
|
|
14076
14375
|
realpathSync as realpathSync2
|
|
14077
14376
|
} from "fs";
|
|
14078
|
-
import
|
|
14377
|
+
import path18 from "path";
|
|
14079
14378
|
function git(args, cwd) {
|
|
14080
14379
|
return execFileSync("git", args, {
|
|
14081
14380
|
cwd,
|
|
@@ -14128,8 +14427,8 @@ function detectWorktreeState(projectRoot, iamuDirName = IAMU_DIR) {
|
|
|
14128
14427
|
const top = git(["rev-parse", "--show-toplevel"], projectRoot).toString("utf8").trim();
|
|
14129
14428
|
if (!top) return void 0;
|
|
14130
14429
|
const realTop = realpathSync2(top);
|
|
14131
|
-
const realIamu =
|
|
14132
|
-
const iamuRel =
|
|
14430
|
+
const realIamu = path18.join(realpathSync2(projectRoot), iamuDirName);
|
|
14431
|
+
const iamuRel = path18.relative(realTop, realIamu).split(path18.sep).join("/");
|
|
14133
14432
|
const pathspec = iamuRel.startsWith("..") ? [":(top)"] : [":(top)", `:(top,exclude,literal)${iamuRel}`];
|
|
14134
14433
|
const status2 = git(
|
|
14135
14434
|
[
|
|
@@ -14159,7 +14458,7 @@ function detectWorktreeState(projectRoot, iamuDirName = IAMU_DIR) {
|
|
|
14159
14458
|
);
|
|
14160
14459
|
for (const file of sorted.slice(0, MAX_UNTRACKED_HASHED)) {
|
|
14161
14460
|
const rel = file.replace(/\/$/, "");
|
|
14162
|
-
hash.update(`${file}\0${hashFile(
|
|
14461
|
+
hash.update(`${file}\0${hashFile(path18.join(realTop, rel))}
|
|
14163
14462
|
`);
|
|
14164
14463
|
}
|
|
14165
14464
|
if (sorted.length > MAX_UNTRACKED_HASHED) {
|
|
@@ -14332,7 +14631,7 @@ __export(start_run_exports, {
|
|
|
14332
14631
|
resolveStartUrl: () => resolveStartUrl,
|
|
14333
14632
|
startRun: () => startRun
|
|
14334
14633
|
});
|
|
14335
|
-
import
|
|
14634
|
+
import path19 from "path";
|
|
14336
14635
|
import chalk11 from "chalk";
|
|
14337
14636
|
function resolveStartUrl(target, scenario) {
|
|
14338
14637
|
const base = target ?? scenario.target_url ?? null;
|
|
@@ -14508,7 +14807,7 @@ async function startRun(scenarioSlug, opts) {
|
|
|
14508
14807
|
...viewport ? { viewport } : {}
|
|
14509
14808
|
};
|
|
14510
14809
|
await atomicWriteFile(
|
|
14511
|
-
|
|
14810
|
+
path19.join(run.absPath, "walkthrough.json"),
|
|
14512
14811
|
JSON.stringify(manifest, null, 2) + "\n"
|
|
14513
14812
|
);
|
|
14514
14813
|
if (opts.json) {
|
|
@@ -14516,7 +14815,7 @@ async function startRun(scenarioSlug, opts) {
|
|
|
14516
14815
|
JSON.stringify(
|
|
14517
14816
|
{
|
|
14518
14817
|
run_id: run.runId,
|
|
14519
|
-
path:
|
|
14818
|
+
path: path19.relative(projectRoot, run.absPath),
|
|
14520
14819
|
scenario_slug: scenarioSlug,
|
|
14521
14820
|
target_url: start2.url,
|
|
14522
14821
|
target_reachable: targetReachable,
|
|
@@ -14564,7 +14863,7 @@ async function startRun(scenarioSlug, opts) {
|
|
|
14564
14863
|
);
|
|
14565
14864
|
}
|
|
14566
14865
|
console.log(
|
|
14567
|
-
` ${chalk11.dim("path:")} ${
|
|
14866
|
+
` ${chalk11.dim("path:")} ${path19.relative(projectRoot, run.absPath)}`
|
|
14568
14867
|
);
|
|
14569
14868
|
console.log();
|
|
14570
14869
|
console.log(` ${chalk11.bold("Next steps for blind execution:")}`);
|
|
@@ -14605,8 +14904,8 @@ var report_exports = {};
|
|
|
14605
14904
|
__export(report_exports, {
|
|
14606
14905
|
report: () => report
|
|
14607
14906
|
});
|
|
14608
|
-
import { promises as
|
|
14609
|
-
import
|
|
14907
|
+
import { promises as fs24 } from "fs";
|
|
14908
|
+
import path20 from "path";
|
|
14610
14909
|
import chalk12 from "chalk";
|
|
14611
14910
|
async function report(scenario, opts) {
|
|
14612
14911
|
const ctx = await resolveProject(opts);
|
|
@@ -14625,8 +14924,8 @@ async function report(scenario, opts) {
|
|
|
14625
14924
|
for (const rid of runs.reverse()) {
|
|
14626
14925
|
try {
|
|
14627
14926
|
const dir = runDir(projectRoot, rid);
|
|
14628
|
-
const mRaw = await
|
|
14629
|
-
|
|
14927
|
+
const mRaw = await fs24.readFile(
|
|
14928
|
+
path20.join(dir, "walkthrough.json"),
|
|
14630
14929
|
"utf8"
|
|
14631
14930
|
);
|
|
14632
14931
|
const manifest = JSON.parse(mRaw);
|
|
@@ -14634,8 +14933,8 @@ async function report(scenario, opts) {
|
|
|
14634
14933
|
if (scenario && manifest.scenario_slug !== scenario) continue;
|
|
14635
14934
|
let canonical = null;
|
|
14636
14935
|
try {
|
|
14637
|
-
const cRaw = await
|
|
14638
|
-
|
|
14936
|
+
const cRaw = await fs24.readFile(
|
|
14937
|
+
path20.join(dir, "canonical.json"),
|
|
14639
14938
|
"utf8"
|
|
14640
14939
|
);
|
|
14641
14940
|
canonical = JSON.parse(cRaw);
|
|
@@ -15912,6 +16211,35 @@ async function init(opts) {
|
|
|
15912
16211
|
""
|
|
15913
16212
|
)
|
|
15914
16213
|
);
|
|
16214
|
+
const testabilityPath = join5(iamuDir2, "testability.md");
|
|
16215
|
+
if (!existsSync7(testabilityPath)) {
|
|
16216
|
+
await writeFile3(
|
|
16217
|
+
testabilityPath,
|
|
16218
|
+
serializeFrontmatter(
|
|
16219
|
+
{
|
|
16220
|
+
status: "draft",
|
|
16221
|
+
target_url: "http://localhost:3000"
|
|
16222
|
+
},
|
|
16223
|
+
`# Project Testability & Environment
|
|
16224
|
+
|
|
16225
|
+
Target URL and execution harness for walkthroughs.
|
|
16226
|
+
Audited and populated during /iamu:init.
|
|
16227
|
+
|
|
16228
|
+
## Target & Run Instructions
|
|
16229
|
+
- Default URL: http://localhost:3000
|
|
16230
|
+
|
|
16231
|
+
## Reachable Surfaces
|
|
16232
|
+
(Run /iamu:init to discover testable routes)
|
|
16233
|
+
|
|
16234
|
+
## Mocks & Data Fixtures
|
|
16235
|
+
(Run /iamu:init to inspect existing harnesses)
|
|
16236
|
+
|
|
16237
|
+
## Untestable Boundaries
|
|
16238
|
+
(None documented yet)
|
|
16239
|
+
`
|
|
16240
|
+
)
|
|
16241
|
+
);
|
|
16242
|
+
}
|
|
15915
16243
|
}
|
|
15916
16244
|
const promptTemplates = loadPromptTemplates();
|
|
15917
16245
|
const promptsDir = join5(iamuDir2, ".prompts");
|
|
@@ -16341,7 +16669,10 @@ var init_browse_actions = __esm({
|
|
|
16341
16669
|
usage: "goto <url>",
|
|
16342
16670
|
minArgs: 1,
|
|
16343
16671
|
run: async (p, args) => {
|
|
16344
|
-
await p.goto(args[0], {
|
|
16672
|
+
await p.goto(args[0], {
|
|
16673
|
+
waitUntil: "domcontentloaded",
|
|
16674
|
+
timeout: 1e4
|
|
16675
|
+
});
|
|
16345
16676
|
}
|
|
16346
16677
|
},
|
|
16347
16678
|
click: {
|
|
@@ -16502,6 +16833,12 @@ var init_browse_driver = __esm({
|
|
|
16502
16833
|
commandLine: (pid) => readProcessCommandLine(pid),
|
|
16503
16834
|
kill(pid) {
|
|
16504
16835
|
try {
|
|
16836
|
+
if (process.platform !== "win32") {
|
|
16837
|
+
try {
|
|
16838
|
+
process.kill(-pid, "SIGTERM");
|
|
16839
|
+
} catch {
|
|
16840
|
+
}
|
|
16841
|
+
}
|
|
16505
16842
|
process.kill(pid, "SIGTERM");
|
|
16506
16843
|
} catch {
|
|
16507
16844
|
}
|
|
@@ -16512,7 +16849,7 @@ var init_browse_driver = __esm({
|
|
|
16512
16849
|
|
|
16513
16850
|
// src/commands/step-numbering.ts
|
|
16514
16851
|
import { open, readdir, unlink } from "fs/promises";
|
|
16515
|
-
import
|
|
16852
|
+
import path21 from "path";
|
|
16516
16853
|
function stepFileName(stepNumber2) {
|
|
16517
16854
|
return `${String(stepNumber2).padStart(3, "0")}.json`;
|
|
16518
16855
|
}
|
|
@@ -16534,7 +16871,7 @@ async function reserveStepFile(stepsDir, limit) {
|
|
|
16534
16871
|
if (candidate > limit.maxSteps) {
|
|
16535
16872
|
throw new Error(stepLimitMessage(limit.maxSteps, limit.runId));
|
|
16536
16873
|
}
|
|
16537
|
-
const stepPath =
|
|
16874
|
+
const stepPath = path21.join(stepsDir, stepFileName(candidate));
|
|
16538
16875
|
try {
|
|
16539
16876
|
const handle = await open(stepPath, "wx");
|
|
16540
16877
|
await handle.close();
|
|
@@ -16566,7 +16903,7 @@ __export(step_exports, {
|
|
|
16566
16903
|
stepCommand: () => stepCommand
|
|
16567
16904
|
});
|
|
16568
16905
|
import { copyFile, mkdir as mkdir5, stat } from "fs/promises";
|
|
16569
|
-
import
|
|
16906
|
+
import path22 from "path";
|
|
16570
16907
|
import chalk19 from "chalk";
|
|
16571
16908
|
function parseMaxSteps(val) {
|
|
16572
16909
|
if (typeof val === "number" && Number.isFinite(val) && val > 0) return val;
|
|
@@ -16585,8 +16922,8 @@ async function recordStep(input) {
|
|
|
16585
16922
|
if (!runDirPath) {
|
|
16586
16923
|
throw new Error(`Run directory not found for run "${input.runId}"`);
|
|
16587
16924
|
}
|
|
16588
|
-
const stepsDir =
|
|
16589
|
-
const screenshotsDir =
|
|
16925
|
+
const stepsDir = path22.join(runDirPath, "steps");
|
|
16926
|
+
const screenshotsDir = path22.join(runDirPath, "screenshots");
|
|
16590
16927
|
await mkdir5(stepsDir, { recursive: true });
|
|
16591
16928
|
await mkdir5(screenshotsDir, { recursive: true });
|
|
16592
16929
|
const shotSource = input.shot ? await existingShot(input.shot) : null;
|
|
@@ -16600,11 +16937,11 @@ async function recordStep(input) {
|
|
|
16600
16937
|
let finalScreenshotPath = null;
|
|
16601
16938
|
if (shotSource) {
|
|
16602
16939
|
const destName = `step-${stepNumber2}.png`;
|
|
16603
|
-
finalScreenshotPath =
|
|
16604
|
-
if (
|
|
16940
|
+
finalScreenshotPath = path22.join(screenshotsDir, destName);
|
|
16941
|
+
if (path22.resolve(finalScreenshotPath) !== shotSource) {
|
|
16605
16942
|
await copyFile(shotSource, finalScreenshotPath);
|
|
16606
16943
|
}
|
|
16607
|
-
screenshotRef =
|
|
16944
|
+
screenshotRef = path22.join("screenshots", destName);
|
|
16608
16945
|
}
|
|
16609
16946
|
const parsedStep = runStepSchema.parse({
|
|
16610
16947
|
step_number: stepNumber2,
|
|
@@ -16629,7 +16966,7 @@ async function recordStep(input) {
|
|
|
16629
16966
|
}
|
|
16630
16967
|
}
|
|
16631
16968
|
async function existingShot(shot) {
|
|
16632
|
-
const resolved =
|
|
16969
|
+
const resolved = path22.resolve(shot);
|
|
16633
16970
|
const isFile = await stat(resolved).then((s) => s.isFile()).catch(() => false);
|
|
16634
16971
|
if (!isFile) {
|
|
16635
16972
|
throw new Error(
|
|
@@ -16671,7 +17008,7 @@ async function stepCommand(runArg, opts) {
|
|
|
16671
17008
|
JSON.stringify(
|
|
16672
17009
|
{
|
|
16673
17010
|
step_number: res.step.step_number,
|
|
16674
|
-
path:
|
|
17011
|
+
path: path22.relative(projectRoot, res.stepPath),
|
|
16675
17012
|
action: res.step.action,
|
|
16676
17013
|
note: res.step.note,
|
|
16677
17014
|
url: res.step.url,
|
|
@@ -16698,7 +17035,7 @@ async function stepCommand(runArg, opts) {
|
|
|
16698
17035
|
console.log(` ${chalk19.dim("shot:")} ${res.step.screenshot}`);
|
|
16699
17036
|
}
|
|
16700
17037
|
console.log(
|
|
16701
|
-
` ${chalk19.dim("file:")} ${
|
|
17038
|
+
` ${chalk19.dim("file:")} ${path22.relative(projectRoot, res.stepPath)}`
|
|
16702
17039
|
);
|
|
16703
17040
|
console.log();
|
|
16704
17041
|
} catch (err) {
|
|
@@ -16741,21 +17078,21 @@ import {
|
|
|
16741
17078
|
} from "fs";
|
|
16742
17079
|
import { rm as rm2 } from "fs/promises";
|
|
16743
17080
|
import { homedir as homedir3 } from "os";
|
|
16744
|
-
import
|
|
17081
|
+
import path23 from "path";
|
|
16745
17082
|
import chalk20 from "chalk";
|
|
16746
17083
|
function stateDir() {
|
|
16747
|
-
return
|
|
16748
|
-
process.env.IAMU_HOME ??
|
|
17084
|
+
return path23.join(
|
|
17085
|
+
process.env.IAMU_HOME ?? path23.join(homedir3(), ".iamu"),
|
|
16749
17086
|
"browse"
|
|
16750
17087
|
);
|
|
16751
17088
|
}
|
|
16752
17089
|
function profileDir() {
|
|
16753
|
-
return
|
|
17090
|
+
return path23.join(stateDir(), "profile");
|
|
16754
17091
|
}
|
|
16755
17092
|
function readBrowseState() {
|
|
16756
17093
|
try {
|
|
16757
17094
|
return JSON.parse(
|
|
16758
|
-
readFileSync6(
|
|
17095
|
+
readFileSync6(path23.join(stateDir(), "state.json"), "utf8")
|
|
16759
17096
|
);
|
|
16760
17097
|
} catch {
|
|
16761
17098
|
return null;
|
|
@@ -16763,7 +17100,7 @@ function readBrowseState() {
|
|
|
16763
17100
|
}
|
|
16764
17101
|
function writeState(state) {
|
|
16765
17102
|
mkdirSync(stateDir(), { recursive: true });
|
|
16766
|
-
writeFileSync(
|
|
17103
|
+
writeFileSync(path23.join(stateDir(), "state.json"), JSON.stringify(state));
|
|
16767
17104
|
}
|
|
16768
17105
|
function fail(message, opts) {
|
|
16769
17106
|
if (opts?.json) {
|
|
@@ -16826,7 +17163,7 @@ async function requestedViewport(opts) {
|
|
|
16826
17163
|
let manifest;
|
|
16827
17164
|
try {
|
|
16828
17165
|
manifest = JSON.parse(
|
|
16829
|
-
readFileSync6(
|
|
17166
|
+
readFileSync6(path23.join(dir, "walkthrough.json"), "utf8")
|
|
16830
17167
|
);
|
|
16831
17168
|
} catch {
|
|
16832
17169
|
fail(
|
|
@@ -16878,6 +17215,8 @@ function chromeArgs(opts, port, headless) {
|
|
|
16878
17215
|
`--window-size=${opts.width ?? "1280"},${opts.height ?? "900"}`,
|
|
16879
17216
|
"--no-first-run",
|
|
16880
17217
|
"--no-default-browser-check",
|
|
17218
|
+
"--password-store=basic",
|
|
17219
|
+
"--use-mock-keychain",
|
|
16881
17220
|
...headless ? ["--headless=new", "--disable-dev-shm-usage", "--disable-gpu"] : [],
|
|
16882
17221
|
...shouldDisableSandbox() ? ["--no-sandbox"] : [],
|
|
16883
17222
|
"about:blank"
|
|
@@ -16930,7 +17269,7 @@ async function stop(opts, driver) {
|
|
|
16930
17269
|
await driver.closeBrowser(state.port);
|
|
16931
17270
|
if (state.pid) await reapBrowser(state.pid, profileDir(), driver);
|
|
16932
17271
|
try {
|
|
16933
|
-
unlinkSync(
|
|
17272
|
+
unlinkSync(path23.join(stateDir(), "state.json"));
|
|
16934
17273
|
} catch {
|
|
16935
17274
|
}
|
|
16936
17275
|
report2(opts, { stopped: true }, [` ${chalk20.green("\u2713")} Browser stopped`]);
|
|
@@ -16996,8 +17335,8 @@ async function withPage(opts, driver, fn, target) {
|
|
|
16996
17335
|
let screenshot = null;
|
|
16997
17336
|
if (opts.shot) {
|
|
16998
17337
|
await waitForVisualStability(page);
|
|
16999
|
-
screenshot =
|
|
17000
|
-
mkdirSync(
|
|
17338
|
+
screenshot = path23.resolve(opts.shot);
|
|
17339
|
+
mkdirSync(path23.dirname(screenshot), { recursive: true });
|
|
17001
17340
|
await page.screenshot({ path: screenshot, fullPage: Boolean(opts.full) });
|
|
17002
17341
|
}
|
|
17003
17342
|
return { url: page.url(), title: await page.title(), screenshot };
|
|
@@ -17076,7 +17415,7 @@ function printStep(opts, record, projectRoot) {
|
|
|
17076
17415
|
if (opts.json) {
|
|
17077
17416
|
const doc = {
|
|
17078
17417
|
step_number: step.step_number,
|
|
17079
|
-
path:
|
|
17418
|
+
path: path23.relative(projectRoot, record.stepPath),
|
|
17080
17419
|
action: step.action,
|
|
17081
17420
|
note: step.note,
|
|
17082
17421
|
url: step.url,
|
|
@@ -17150,10 +17489,10 @@ async function browseStep(args, opts, driver) {
|
|
|
17150
17489
|
const runDirPath = resolveRunDir(ctx.projectRoot, opts.run);
|
|
17151
17490
|
if (!runDirPath) return;
|
|
17152
17491
|
const maxSteps = parseMaxSteps(opts.maxSteps);
|
|
17153
|
-
if (await peekNextStepNumber(
|
|
17492
|
+
if (await peekNextStepNumber(path23.join(runDirPath, "steps")) > maxSteps) {
|
|
17154
17493
|
return fail(stepLimitMessage(maxSteps, opts.run), opts);
|
|
17155
17494
|
}
|
|
17156
|
-
const pendingShot =
|
|
17495
|
+
const pendingShot = path23.join(
|
|
17157
17496
|
runDirPath,
|
|
17158
17497
|
"screenshots",
|
|
17159
17498
|
`.pending-${randomUUID7()}.png`
|
|
@@ -17224,11 +17563,14 @@ var doctor_exports = {};
|
|
|
17224
17563
|
__export(doctor_exports, {
|
|
17225
17564
|
checkCdpResponsive: () => checkCdpResponsive,
|
|
17226
17565
|
checkPortAvailable: () => checkPortAvailable,
|
|
17566
|
+
checkProjectHarnesses: () => checkProjectHarnesses,
|
|
17567
|
+
checkProjectTestabilityContract: () => checkProjectTestabilityContract,
|
|
17568
|
+
checkTargetHealth: () => checkTargetHealth,
|
|
17227
17569
|
doctor: () => doctor,
|
|
17228
17570
|
runDoctorChecks: () => runDoctorChecks
|
|
17229
17571
|
});
|
|
17230
17572
|
import { createServer } from "net";
|
|
17231
|
-
import { existsSync as existsSync10 } from "fs";
|
|
17573
|
+
import { existsSync as existsSync10, promises as fs25 } from "fs";
|
|
17232
17574
|
import { join as join7 } from "path";
|
|
17233
17575
|
import chalk21 from "chalk";
|
|
17234
17576
|
function checkPortAvailable(port) {
|
|
@@ -17506,11 +17848,186 @@ async function checkAgentIntegrations(projectRoot) {
|
|
|
17506
17848
|
}
|
|
17507
17849
|
return checks;
|
|
17508
17850
|
}
|
|
17509
|
-
async function
|
|
17851
|
+
async function checkTargetHealth(targetUrl) {
|
|
17852
|
+
const checks = [];
|
|
17853
|
+
let parsedUrl;
|
|
17854
|
+
try {
|
|
17855
|
+
parsedUrl = new URL(targetUrl);
|
|
17856
|
+
} catch {
|
|
17857
|
+
checks.push({
|
|
17858
|
+
category: "target",
|
|
17859
|
+
name: "Target URL Format",
|
|
17860
|
+
status: "error",
|
|
17861
|
+
message: `Invalid target URL: "${targetUrl}"`,
|
|
17862
|
+
remedy: "Provide a valid URL with protocol (e.g. http://localhost:3000)."
|
|
17863
|
+
});
|
|
17864
|
+
return checks;
|
|
17865
|
+
}
|
|
17866
|
+
try {
|
|
17867
|
+
const res = await fetch(targetUrl, {
|
|
17868
|
+
method: "GET",
|
|
17869
|
+
redirect: "manual",
|
|
17870
|
+
signal: AbortSignal.timeout(3e3)
|
|
17871
|
+
});
|
|
17872
|
+
if ([301, 302, 303, 307, 308].includes(res.status)) {
|
|
17873
|
+
const location = res.headers.get("location") || "";
|
|
17874
|
+
const isExternalAuth = /auth0\.com|okta\.com|clerk\.com|accounts\.google\.com|cognito|login\.microsoftonline\.com/i.test(
|
|
17875
|
+
location
|
|
17876
|
+
) || location.includes("/oauth");
|
|
17877
|
+
if (isExternalAuth) {
|
|
17878
|
+
let redirectHost = location;
|
|
17879
|
+
try {
|
|
17880
|
+
redirectHost = new URL(location, targetUrl).host;
|
|
17881
|
+
} catch {
|
|
17882
|
+
}
|
|
17883
|
+
checks.push({
|
|
17884
|
+
category: "target",
|
|
17885
|
+
name: "Authentication Boundary",
|
|
17886
|
+
status: "warn",
|
|
17887
|
+
message: `Target immediately redirects to external auth (${redirectHost})`,
|
|
17888
|
+
remedy: "Configure a dev auth bypass or prepare for interactive login handshake before walking."
|
|
17889
|
+
});
|
|
17890
|
+
return checks;
|
|
17891
|
+
}
|
|
17892
|
+
checks.push({
|
|
17893
|
+
category: "target",
|
|
17894
|
+
name: "Target Reachability",
|
|
17895
|
+
status: "ok",
|
|
17896
|
+
message: `HTTP ${res.status} redirect -> ${location}`
|
|
17897
|
+
});
|
|
17898
|
+
return checks;
|
|
17899
|
+
}
|
|
17900
|
+
if (res.status >= 500) {
|
|
17901
|
+
checks.push({
|
|
17902
|
+
category: "target",
|
|
17903
|
+
name: "Target Health",
|
|
17904
|
+
status: "error",
|
|
17905
|
+
message: `Target returned HTTP ${res.status} ${res.statusText || "Server Error"}`,
|
|
17906
|
+
remedy: "Check dev server logs for unhandled exceptions or missing environment variables."
|
|
17907
|
+
});
|
|
17908
|
+
return checks;
|
|
17909
|
+
}
|
|
17910
|
+
checks.push({
|
|
17911
|
+
category: "target",
|
|
17912
|
+
name: "Target Reachability",
|
|
17913
|
+
status: "ok",
|
|
17914
|
+
message: `HTTP ${res.status} OK (${parsedUrl.origin})`
|
|
17915
|
+
});
|
|
17916
|
+
} catch (err) {
|
|
17917
|
+
const message = err.message;
|
|
17918
|
+
checks.push({
|
|
17919
|
+
category: "target",
|
|
17920
|
+
name: "Target Reachability",
|
|
17921
|
+
status: "warn",
|
|
17922
|
+
message: `Cannot connect to ${targetUrl} (${message})`,
|
|
17923
|
+
remedy: "Ensure your dev server is running and listening on the specified port."
|
|
17924
|
+
});
|
|
17925
|
+
}
|
|
17926
|
+
return checks;
|
|
17927
|
+
}
|
|
17928
|
+
async function checkProjectHarnesses(projectRoot) {
|
|
17929
|
+
const checks = [];
|
|
17930
|
+
const pkgPath = join7(projectRoot, "package.json");
|
|
17931
|
+
if (!existsSync10(pkgPath)) return checks;
|
|
17932
|
+
try {
|
|
17933
|
+
const raw = await fs25.readFile(pkgPath, "utf8");
|
|
17934
|
+
const pkg = JSON.parse(raw);
|
|
17935
|
+
const scripts = pkg.scripts || {};
|
|
17936
|
+
const allDeps = {
|
|
17937
|
+
...pkg.dependencies || {},
|
|
17938
|
+
...pkg.devDependencies || {}
|
|
17939
|
+
};
|
|
17940
|
+
const devScripts = ["dev", "start", "dev:mock", "serve"].filter(
|
|
17941
|
+
(s) => s in scripts
|
|
17942
|
+
);
|
|
17943
|
+
if (devScripts.length > 0) {
|
|
17944
|
+
checks.push({
|
|
17945
|
+
category: "target",
|
|
17946
|
+
name: "Dev Server Script",
|
|
17947
|
+
status: "ok",
|
|
17948
|
+
message: `Available scripts: ${devScripts.map((s) => `pnpm ${s}`).join(", ")}`
|
|
17949
|
+
});
|
|
17950
|
+
} else {
|
|
17951
|
+
checks.push({
|
|
17952
|
+
category: "target",
|
|
17953
|
+
name: "Dev Server Script",
|
|
17954
|
+
status: "warn",
|
|
17955
|
+
message: "No standard dev script (dev, start, dev:mock) found in package.json",
|
|
17956
|
+
remedy: "Add a `dev` or `start` script to package.json to boot your app locally."
|
|
17957
|
+
});
|
|
17958
|
+
}
|
|
17959
|
+
if (allDeps.msw || existsSync10(join7(projectRoot, "src/mocks")) || existsSync10(join7(projectRoot, "mocks"))) {
|
|
17960
|
+
checks.push({
|
|
17961
|
+
category: "target",
|
|
17962
|
+
name: "Mock Harness (MSW)",
|
|
17963
|
+
status: "ok",
|
|
17964
|
+
message: "Mock Service Worker detected. API mocks can decouple walks from live backends."
|
|
17965
|
+
});
|
|
17966
|
+
}
|
|
17967
|
+
const seedScript = Object.keys(scripts).find(
|
|
17968
|
+
(s) => /seed|db:seed|fixtures/i.test(s)
|
|
17969
|
+
);
|
|
17970
|
+
if (seedScript) {
|
|
17971
|
+
checks.push({
|
|
17972
|
+
category: "target",
|
|
17973
|
+
name: "Database Seed",
|
|
17974
|
+
status: "ok",
|
|
17975
|
+
message: `Seed script detected (${seedScript}). Preconditions can be reset between runs.`
|
|
17976
|
+
});
|
|
17977
|
+
}
|
|
17978
|
+
} catch {
|
|
17979
|
+
}
|
|
17980
|
+
return checks;
|
|
17981
|
+
}
|
|
17982
|
+
async function checkProjectTestabilityContract(projectRoot) {
|
|
17983
|
+
const checks = [];
|
|
17984
|
+
const testabilityPath = join7(projectRoot, "iamu", "testability.md");
|
|
17985
|
+
if (!existsSync10(testabilityPath)) {
|
|
17986
|
+
checks.push({
|
|
17987
|
+
category: "target",
|
|
17988
|
+
name: "Testability Contract (testability.md)",
|
|
17989
|
+
status: "warn",
|
|
17990
|
+
message: "iamu/testability.md not found",
|
|
17991
|
+
remedy: "Run `/iamu:init` to audit your codebase and record testable routes, auth strategies, and mock harnesses."
|
|
17992
|
+
});
|
|
17993
|
+
return { checks };
|
|
17994
|
+
}
|
|
17995
|
+
try {
|
|
17996
|
+
const raw = await fs25.readFile(testabilityPath, "utf8");
|
|
17997
|
+
const { frontmatter } = parseFrontmatter(raw);
|
|
17998
|
+
const targetUrl = typeof frontmatter.target_url === "string" ? frontmatter.target_url : void 0;
|
|
17999
|
+
const details = [];
|
|
18000
|
+
if (targetUrl) details.push(`Target: ${targetUrl}`);
|
|
18001
|
+
if (frontmatter.dev_command) {
|
|
18002
|
+
details.push(`Command: ${frontmatter.dev_command}`);
|
|
18003
|
+
}
|
|
18004
|
+
if (frontmatter.auth_strategy) {
|
|
18005
|
+
details.push(`Auth: ${frontmatter.auth_strategy}`);
|
|
18006
|
+
}
|
|
18007
|
+
checks.push({
|
|
18008
|
+
category: "target",
|
|
18009
|
+
name: "Testability Contract (testability.md)",
|
|
18010
|
+
status: "ok",
|
|
18011
|
+
message: `Configured (${details.join(" \xB7 ") || "present"})`
|
|
18012
|
+
});
|
|
18013
|
+
return { checks, targetUrl };
|
|
18014
|
+
} catch (err) {
|
|
18015
|
+
checks.push({
|
|
18016
|
+
category: "target",
|
|
18017
|
+
name: "Testability Contract (testability.md)",
|
|
18018
|
+
status: "warn",
|
|
18019
|
+
message: `Unreadable testability contract: ${err.message}`,
|
|
18020
|
+
remedy: "Repair iamu/testability.md frontmatter."
|
|
18021
|
+
});
|
|
18022
|
+
return { checks };
|
|
18023
|
+
}
|
|
18024
|
+
}
|
|
18025
|
+
async function runDoctorChecks(projectRoot, opts) {
|
|
17510
18026
|
const checks = [
|
|
17511
18027
|
...checkRuntimeChecks(),
|
|
17512
18028
|
...await checkBrowserChecks()
|
|
17513
18029
|
];
|
|
18030
|
+
let effectiveTarget = opts?.target;
|
|
17514
18031
|
if (!projectRoot || !existsSync10(join7(projectRoot, "iamu"))) {
|
|
17515
18032
|
checks.push({
|
|
17516
18033
|
category: "project",
|
|
@@ -17530,6 +18047,15 @@ async function runDoctorChecks(projectRoot) {
|
|
|
17530
18047
|
checks.push(...await checkProjectConfiguration(projectRoot));
|
|
17531
18048
|
checks.push(...await checkProjectContextAndEntities(projectRoot));
|
|
17532
18049
|
checks.push(...await checkAgentIntegrations(projectRoot));
|
|
18050
|
+
checks.push(...await checkProjectHarnesses(projectRoot));
|
|
18051
|
+
const testability = await checkProjectTestabilityContract(projectRoot);
|
|
18052
|
+
checks.push(...testability.checks);
|
|
18053
|
+
if (!effectiveTarget && testability.targetUrl) {
|
|
18054
|
+
effectiveTarget = testability.targetUrl;
|
|
18055
|
+
}
|
|
18056
|
+
}
|
|
18057
|
+
if (effectiveTarget) {
|
|
18058
|
+
checks.push(...await checkTargetHealth(effectiveTarget));
|
|
17533
18059
|
}
|
|
17534
18060
|
const okCount = checks.filter((c) => c.status === "ok").length;
|
|
17535
18061
|
const warnCount = checks.filter((c) => c.status === "warn").length;
|
|
@@ -17546,7 +18072,7 @@ async function runDoctorChecks(projectRoot) {
|
|
|
17546
18072
|
}
|
|
17547
18073
|
async function doctor(opts) {
|
|
17548
18074
|
const projectRoot = findProjectRoot(process.cwd()) ?? void 0;
|
|
17549
|
-
const result = await runDoctorChecks(projectRoot);
|
|
18075
|
+
const result = await runDoctorChecks(projectRoot, { target: opts?.target });
|
|
17550
18076
|
if (opts?.json) {
|
|
17551
18077
|
console.log(JSON.stringify(result, null, 2));
|
|
17552
18078
|
if (!result.ok) process.exitCode = 1;
|
|
@@ -17560,7 +18086,8 @@ async function doctor(opts) {
|
|
|
17560
18086
|
{ key: "browser", title: "Browser Automation" },
|
|
17561
18087
|
{ key: "project", title: "Project Configuration" },
|
|
17562
18088
|
{ key: "agent", title: "Agent Integrations" },
|
|
17563
|
-
{ key: "cloud", title: "Cloud & Authentication" }
|
|
18089
|
+
{ key: "cloud", title: "Cloud & Authentication" },
|
|
18090
|
+
{ key: "target", title: "Target & Testability" }
|
|
17564
18091
|
];
|
|
17565
18092
|
for (const cat of categories) {
|
|
17566
18093
|
const catChecks = result.checks.filter((c) => c.category === cat.key);
|
|
@@ -18038,13 +18565,13 @@ __export(validate_exports, {
|
|
|
18038
18565
|
validate: () => validate
|
|
18039
18566
|
});
|
|
18040
18567
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
18041
|
-
import { promises as
|
|
18042
|
-
import
|
|
18568
|
+
import { promises as fs26 } from "fs";
|
|
18569
|
+
import path24 from "path";
|
|
18043
18570
|
import chalk23 from "chalk";
|
|
18044
18571
|
function inferTypeFromPath(filePath, projectRoot) {
|
|
18045
|
-
const rel =
|
|
18046
|
-
if (rel.startsWith("..") ||
|
|
18047
|
-
const segments = rel.split(
|
|
18572
|
+
const rel = path24.relative(path24.join(projectRoot, "iamu"), filePath);
|
|
18573
|
+
if (rel.startsWith("..") || path24.isAbsolute(rel)) return null;
|
|
18574
|
+
const segments = rel.split(path24.sep);
|
|
18048
18575
|
if (segments.length < 2) return null;
|
|
18049
18576
|
return TYPE_BY_ALIAS.get(segments[0]) ?? null;
|
|
18050
18577
|
}
|
|
@@ -18074,7 +18601,7 @@ function validateFile(filePath, raw, projectRoot) {
|
|
|
18074
18601
|
);
|
|
18075
18602
|
return result;
|
|
18076
18603
|
}
|
|
18077
|
-
if (
|
|
18604
|
+
if (path24.basename(filePath) === "context.yml") {
|
|
18078
18605
|
applyZodValidation(contextFrontmatterSchema, frontmatter, result);
|
|
18079
18606
|
const warnings = [];
|
|
18080
18607
|
const hasNotes = Boolean(
|
|
@@ -18134,17 +18661,17 @@ async function buildRepairLookups(projectRoot) {
|
|
|
18134
18661
|
}
|
|
18135
18662
|
}
|
|
18136
18663
|
const runScenario = /* @__PURE__ */ new Map();
|
|
18137
|
-
const runsDir2 =
|
|
18664
|
+
const runsDir2 = path24.join(projectRoot, "iamu", ".runs");
|
|
18138
18665
|
let runIds = [];
|
|
18139
18666
|
try {
|
|
18140
|
-
runIds = await
|
|
18667
|
+
runIds = await fs26.readdir(runsDir2);
|
|
18141
18668
|
} catch {
|
|
18142
18669
|
}
|
|
18143
18670
|
for (const runId of runIds) {
|
|
18144
18671
|
try {
|
|
18145
18672
|
const manifest = JSON.parse(
|
|
18146
|
-
await
|
|
18147
|
-
|
|
18673
|
+
await fs26.readFile(
|
|
18674
|
+
path24.join(runsDir2, runId, "walkthrough.json"),
|
|
18148
18675
|
"utf8"
|
|
18149
18676
|
)
|
|
18150
18677
|
);
|
|
@@ -18166,7 +18693,7 @@ async function fixFindings(projectRoot, results, dryRun) {
|
|
|
18166
18693
|
let raw;
|
|
18167
18694
|
let parsed;
|
|
18168
18695
|
try {
|
|
18169
|
-
raw = await
|
|
18696
|
+
raw = await fs26.readFile(result.path, "utf8");
|
|
18170
18697
|
parsed = parseFrontmatter(raw);
|
|
18171
18698
|
} catch {
|
|
18172
18699
|
continue;
|
|
@@ -18175,7 +18702,7 @@ async function fixFindings(projectRoot, results, dryRun) {
|
|
|
18175
18702
|
parsed.frontmatter,
|
|
18176
18703
|
{
|
|
18177
18704
|
...lookups,
|
|
18178
|
-
fileSlug:
|
|
18705
|
+
fileSlug: path24.basename(result.path, ".md"),
|
|
18179
18706
|
newId: randomUUID8,
|
|
18180
18707
|
body: parsed.body
|
|
18181
18708
|
}
|
|
@@ -18210,9 +18737,9 @@ async function validate(filePath, opts) {
|
|
|
18210
18737
|
if (process.exitCode === 1) return;
|
|
18211
18738
|
const results = [];
|
|
18212
18739
|
if (filePath) {
|
|
18213
|
-
const abs =
|
|
18740
|
+
const abs = path24.isAbsolute(filePath) ? filePath : path24.resolve(process.cwd(), filePath);
|
|
18214
18741
|
try {
|
|
18215
|
-
const raw = await
|
|
18742
|
+
const raw = await fs26.readFile(abs, "utf8");
|
|
18216
18743
|
results.push(validateFile(abs, raw, projectRoot));
|
|
18217
18744
|
} catch (err) {
|
|
18218
18745
|
commandError(
|
|
@@ -18222,24 +18749,24 @@ async function validate(filePath, opts) {
|
|
|
18222
18749
|
return;
|
|
18223
18750
|
}
|
|
18224
18751
|
} else if (opts.run) {
|
|
18225
|
-
const findingsDir =
|
|
18752
|
+
const findingsDir = path24.join(projectRoot, "iamu", ENTITY_DIRS.finding);
|
|
18226
18753
|
let names = [];
|
|
18227
18754
|
try {
|
|
18228
|
-
names = await
|
|
18755
|
+
names = await fs26.readdir(findingsDir);
|
|
18229
18756
|
} catch {
|
|
18230
18757
|
}
|
|
18231
18758
|
for (const name of names) {
|
|
18232
18759
|
if (!name.endsWith(".md")) continue;
|
|
18233
|
-
const abs =
|
|
18760
|
+
const abs = path24.join(findingsDir, name);
|
|
18234
18761
|
try {
|
|
18235
|
-
const raw = await
|
|
18762
|
+
const raw = await fs26.readFile(abs, "utf8");
|
|
18236
18763
|
const { frontmatter } = parseFrontmatter(raw);
|
|
18237
18764
|
if (frontmatter.run === opts.run) {
|
|
18238
18765
|
results.push(validateFile(abs, raw, projectRoot));
|
|
18239
18766
|
}
|
|
18240
18767
|
} catch {
|
|
18241
18768
|
results.push({
|
|
18242
|
-
path:
|
|
18769
|
+
path: path24.relative(projectRoot, abs),
|
|
18243
18770
|
valid: false,
|
|
18244
18771
|
errors: ["Cannot read file or parse frontmatter"]
|
|
18245
18772
|
});
|
|
@@ -18272,31 +18799,31 @@ async function validate(filePath, opts) {
|
|
|
18272
18799
|
} else {
|
|
18273
18800
|
const typesToCheck = typeFilter ? ENTITY_TYPES.filter((t) => typeFilter.has(t)) : ENTITY_TYPES;
|
|
18274
18801
|
for (const type2 of typesToCheck) {
|
|
18275
|
-
const iamuTypeDir =
|
|
18802
|
+
const iamuTypeDir = path24.join(projectRoot, "iamu", ENTITY_DIRS[type2]);
|
|
18276
18803
|
let names;
|
|
18277
18804
|
try {
|
|
18278
|
-
names = await
|
|
18805
|
+
names = await fs26.readdir(iamuTypeDir);
|
|
18279
18806
|
} catch {
|
|
18280
18807
|
continue;
|
|
18281
18808
|
}
|
|
18282
18809
|
for (const name of names) {
|
|
18283
18810
|
if (!name.endsWith(".md")) continue;
|
|
18284
|
-
const abs =
|
|
18811
|
+
const abs = path24.join(iamuTypeDir, name);
|
|
18285
18812
|
try {
|
|
18286
|
-
const raw = await
|
|
18813
|
+
const raw = await fs26.readFile(abs, "utf8");
|
|
18287
18814
|
results.push(validateFile(abs, raw, projectRoot));
|
|
18288
18815
|
} catch {
|
|
18289
18816
|
results.push({
|
|
18290
|
-
path:
|
|
18817
|
+
path: path24.relative(projectRoot, abs),
|
|
18291
18818
|
valid: false,
|
|
18292
18819
|
errors: ["Cannot read file"]
|
|
18293
18820
|
});
|
|
18294
18821
|
}
|
|
18295
18822
|
}
|
|
18296
18823
|
}
|
|
18297
|
-
const ctxPath =
|
|
18824
|
+
const ctxPath = path24.join(projectRoot, "iamu", "context.yml");
|
|
18298
18825
|
try {
|
|
18299
|
-
const raw = await
|
|
18826
|
+
const raw = await fs26.readFile(ctxPath, "utf8");
|
|
18300
18827
|
results.push(validateFile(ctxPath, raw, projectRoot));
|
|
18301
18828
|
} catch {
|
|
18302
18829
|
}
|
|
@@ -18315,19 +18842,19 @@ async function validate(filePath, opts) {
|
|
|
18315
18842
|
valid,
|
|
18316
18843
|
errors: results.filter((r) => !r.valid).flatMap(
|
|
18317
18844
|
(r) => r.errors.map((e) => ({
|
|
18318
|
-
path:
|
|
18845
|
+
path: path24.relative(projectRoot, r.path),
|
|
18319
18846
|
message: e
|
|
18320
18847
|
}))
|
|
18321
18848
|
),
|
|
18322
18849
|
warnings: results.filter((r) => r.warnings && r.warnings.length > 0).flatMap(
|
|
18323
18850
|
(r) => (r.warnings ?? []).map((w) => ({
|
|
18324
|
-
path:
|
|
18851
|
+
path: path24.relative(projectRoot, r.path),
|
|
18325
18852
|
message: w
|
|
18326
18853
|
}))
|
|
18327
18854
|
),
|
|
18328
18855
|
...opts.fix ? {
|
|
18329
18856
|
repaired: repaired.map((r) => ({
|
|
18330
|
-
path:
|
|
18857
|
+
path: path24.relative(projectRoot, r.path),
|
|
18331
18858
|
changes: r.repairs,
|
|
18332
18859
|
valid: r.valid
|
|
18333
18860
|
}))
|
|
@@ -18349,7 +18876,7 @@ async function validate(filePath, opts) {
|
|
|
18349
18876
|
}
|
|
18350
18877
|
let hasErrors = false;
|
|
18351
18878
|
for (const r of results) {
|
|
18352
|
-
const relPath =
|
|
18879
|
+
const relPath = path24.relative(projectRoot, r.path);
|
|
18353
18880
|
if (r.valid) {
|
|
18354
18881
|
if (r.warnings && r.warnings.length > 0) {
|
|
18355
18882
|
console.log(` ${symbols.warning} ` + chalk23.yellow(relPath));
|
|
@@ -18554,13 +19081,21 @@ function defaultFrontmatter(type2, id, slug, name, opts) {
|
|
|
18554
19081
|
url: opts.url,
|
|
18555
19082
|
authority_level: opts.authority ?? "informational"
|
|
18556
19083
|
};
|
|
18557
|
-
case "scenario":
|
|
19084
|
+
case "scenario": {
|
|
19085
|
+
const stateObj = opts.stateDescription || opts.stateApproximation ? {
|
|
19086
|
+
state: {
|
|
19087
|
+
...opts.stateDescription ? { description: opts.stateDescription } : {},
|
|
19088
|
+
...opts.stateApproximation ? { approximation: opts.stateApproximation } : {}
|
|
19089
|
+
}
|
|
19090
|
+
} : {};
|
|
18558
19091
|
return {
|
|
18559
19092
|
...base,
|
|
18560
19093
|
outline: opts.outline ?? null,
|
|
18561
19094
|
persona: opts.persona ?? null,
|
|
18562
|
-
...opts.entry ? { entry_path: opts.entry } : {}
|
|
19095
|
+
...opts.entry ? { entry_path: opts.entry } : {},
|
|
19096
|
+
...stateObj
|
|
18563
19097
|
};
|
|
19098
|
+
}
|
|
18564
19099
|
default:
|
|
18565
19100
|
return base;
|
|
18566
19101
|
}
|
|
@@ -18655,34 +19190,825 @@ var init_new_entity = __esm({
|
|
|
18655
19190
|
}
|
|
18656
19191
|
});
|
|
18657
19192
|
|
|
18658
|
-
// src/commands/
|
|
18659
|
-
|
|
18660
|
-
|
|
18661
|
-
|
|
18662
|
-
}
|
|
18663
|
-
|
|
18664
|
-
|
|
18665
|
-
|
|
18666
|
-
|
|
18667
|
-
|
|
18668
|
-
|
|
18669
|
-
|
|
18670
|
-
|
|
18671
|
-
|
|
18672
|
-
|
|
18673
|
-
|
|
18674
|
-
|
|
18675
|
-
|
|
18676
|
-
|
|
18677
|
-
|
|
18678
|
-
|
|
18679
|
-
|
|
18680
|
-
|
|
18681
|
-
|
|
18682
|
-
}
|
|
18683
|
-
|
|
18684
|
-
|
|
18685
|
-
|
|
19193
|
+
// src/commands/explore-crawl.ts
|
|
19194
|
+
function candidateSig(c) {
|
|
19195
|
+
return `${c.role}
|
|
19196
|
+
${c.name}
|
|
19197
|
+
${c.occurrence}`;
|
|
19198
|
+
}
|
|
19199
|
+
function originOf(url) {
|
|
19200
|
+
try {
|
|
19201
|
+
return new URL(url).origin;
|
|
19202
|
+
} catch {
|
|
19203
|
+
return null;
|
|
19204
|
+
}
|
|
19205
|
+
}
|
|
19206
|
+
function skipReason(c, origin, deny) {
|
|
19207
|
+
if (deny.some((re) => re.test(c.name) || c.href && re.test(c.href))) {
|
|
19208
|
+
return "denied";
|
|
19209
|
+
}
|
|
19210
|
+
if (c.submit) return "submit";
|
|
19211
|
+
if (UNSAFE_NAME.test(c.name)) return "unsafe_name";
|
|
19212
|
+
if (c.href) {
|
|
19213
|
+
let u = null;
|
|
19214
|
+
try {
|
|
19215
|
+
u = new URL(c.href);
|
|
19216
|
+
} catch {
|
|
19217
|
+
}
|
|
19218
|
+
if (u && u.protocol !== "javascript:") {
|
|
19219
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") return "external";
|
|
19220
|
+
if (u.origin !== origin) return "external";
|
|
19221
|
+
if (UNSAFE_PATH.test(u.pathname)) return "unsafe_name";
|
|
19222
|
+
}
|
|
19223
|
+
if (c.download) return "download";
|
|
19224
|
+
}
|
|
19225
|
+
return null;
|
|
19226
|
+
}
|
|
19227
|
+
async function settle(page, settleMs) {
|
|
19228
|
+
await page.waitForLoadState("domcontentloaded", { timeout: NAV_TIMEOUT_MS }).catch(() => {
|
|
19229
|
+
});
|
|
19230
|
+
await page.waitForLoadState("networkidle", { timeout: settleMs }).catch(() => {
|
|
19231
|
+
});
|
|
19232
|
+
}
|
|
19233
|
+
async function readPage(page) {
|
|
19234
|
+
return page.evaluate(READ_PAGE);
|
|
19235
|
+
}
|
|
19236
|
+
async function markDocument(page) {
|
|
19237
|
+
await page.evaluate("window.__iamuDoc = true").catch(() => {
|
|
19238
|
+
});
|
|
19239
|
+
}
|
|
19240
|
+
async function isSameDocument(page) {
|
|
19241
|
+
return page.evaluate("window.__iamuDoc === true").catch(() => false);
|
|
19242
|
+
}
|
|
19243
|
+
async function clickCandidate(page, want) {
|
|
19244
|
+
const state = await readPage(page);
|
|
19245
|
+
const c = state.candidates.find(
|
|
19246
|
+
(x) => candidateSig(x) === candidateSig(want)
|
|
19247
|
+
);
|
|
19248
|
+
if (!c) return false;
|
|
19249
|
+
await page.locator(`[data-iamu-x="${c.index}"]`).click({ timeout: CLICK_TIMEOUT_MS });
|
|
19250
|
+
return true;
|
|
19251
|
+
}
|
|
19252
|
+
async function crawl(page, opts) {
|
|
19253
|
+
const origin = originOf(opts.startUrl);
|
|
19254
|
+
if (!origin) throw new Error(`Not a URL: ${opts.startUrl}`);
|
|
19255
|
+
let blocked = [];
|
|
19256
|
+
await page.route("**/*", (route) => {
|
|
19257
|
+
const req = route.request();
|
|
19258
|
+
if (SAFE_METHODS.has(req.method())) return route.continue();
|
|
19259
|
+
let where = req.url();
|
|
19260
|
+
try {
|
|
19261
|
+
const u = new URL(where);
|
|
19262
|
+
where = u.origin === origin ? u.pathname : `${u.origin}${u.pathname}`;
|
|
19263
|
+
} catch {
|
|
19264
|
+
}
|
|
19265
|
+
blocked.push(`${req.method()} ${where}`);
|
|
19266
|
+
return route.abort("blockedbyclient");
|
|
19267
|
+
});
|
|
19268
|
+
const onDialog = (d) => {
|
|
19269
|
+
d.dismiss().catch(() => {
|
|
19270
|
+
});
|
|
19271
|
+
};
|
|
19272
|
+
const onPopup = (p) => {
|
|
19273
|
+
if (p !== page) p.close().catch(() => {
|
|
19274
|
+
});
|
|
19275
|
+
};
|
|
19276
|
+
page.on("dialog", onDialog);
|
|
19277
|
+
page.context().on("page", onPopup);
|
|
19278
|
+
try {
|
|
19279
|
+
return await crawlFrom(page, opts, origin, () => {
|
|
19280
|
+
const out = blocked;
|
|
19281
|
+
blocked = [];
|
|
19282
|
+
return out;
|
|
19283
|
+
});
|
|
19284
|
+
} finally {
|
|
19285
|
+
page.off("dialog", onDialog);
|
|
19286
|
+
page.context().off("page", onPopup);
|
|
19287
|
+
await page.unroute("**/*").catch(() => {
|
|
19288
|
+
});
|
|
19289
|
+
}
|
|
19290
|
+
}
|
|
19291
|
+
async function crawlFrom(page, opts, origin, takeBlocked) {
|
|
19292
|
+
const deny = opts.deny ?? [];
|
|
19293
|
+
const settleMs = opts.settleMs ?? 1500;
|
|
19294
|
+
const { budget } = opts;
|
|
19295
|
+
const stats = {
|
|
19296
|
+
surfaces: 0,
|
|
19297
|
+
actions_tried: 0,
|
|
19298
|
+
skipped: 0,
|
|
19299
|
+
blocked_requests: 0,
|
|
19300
|
+
errors: 0,
|
|
19301
|
+
truncated: false
|
|
19302
|
+
};
|
|
19303
|
+
const surfaces = [];
|
|
19304
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
19305
|
+
const byPlaceView = /* @__PURE__ */ new Map();
|
|
19306
|
+
const signatureOf = (s) => exploreSignature(s.headings, s.title);
|
|
19307
|
+
function addSurface(state, place, view, path30, depth) {
|
|
19308
|
+
const key = exploreSurfaceKey(place, view);
|
|
19309
|
+
const surface = {
|
|
19310
|
+
key,
|
|
19311
|
+
url: state.url,
|
|
19312
|
+
place,
|
|
19313
|
+
view,
|
|
19314
|
+
signature: signatureOf(state),
|
|
19315
|
+
label: state.headings[0] ?? (state.title || place),
|
|
19316
|
+
headings: state.headings,
|
|
19317
|
+
fields: state.fields,
|
|
19318
|
+
depth,
|
|
19319
|
+
path: path30,
|
|
19320
|
+
actions: []
|
|
19321
|
+
};
|
|
19322
|
+
surfaces.push(surface);
|
|
19323
|
+
byKey.set(key, surface);
|
|
19324
|
+
byPlaceView.set(`${place}
|
|
19325
|
+
${surface.signature}`, key);
|
|
19326
|
+
stats.surfaces++;
|
|
19327
|
+
opts.log?.(` ${" ".repeat(depth)}${key} ${surface.label}`);
|
|
19328
|
+
return surface;
|
|
19329
|
+
}
|
|
19330
|
+
async function goto(url) {
|
|
19331
|
+
await page.goto(url, {
|
|
19332
|
+
waitUntil: "domcontentloaded",
|
|
19333
|
+
timeout: NAV_TIMEOUT_MS
|
|
19334
|
+
});
|
|
19335
|
+
await settle(page, settleMs);
|
|
19336
|
+
}
|
|
19337
|
+
async function replay(path30) {
|
|
19338
|
+
if (!("goto" in (path30[0] ?? {}))) await goto(opts.startUrl);
|
|
19339
|
+
for (const step of path30) {
|
|
19340
|
+
if ("goto" in step) {
|
|
19341
|
+
await goto(step.goto);
|
|
19342
|
+
} else {
|
|
19343
|
+
if (!await clickCandidate(page, step.click)) return false;
|
|
19344
|
+
await settle(page, settleMs);
|
|
19345
|
+
}
|
|
19346
|
+
}
|
|
19347
|
+
return true;
|
|
19348
|
+
}
|
|
19349
|
+
await goto(opts.startUrl);
|
|
19350
|
+
if (await page.evaluate("Boolean(navigator.serviceWorker?.controller)").catch(() => false)) {
|
|
19351
|
+
throw new Error(
|
|
19352
|
+
"A service worker controls this page, and the write guard cannot see its requests. Explore without --attach (service workers are blocked there)."
|
|
19353
|
+
);
|
|
19354
|
+
}
|
|
19355
|
+
const first = await readPage(page);
|
|
19356
|
+
const firstPlace = explorePlaceKey(first.url);
|
|
19357
|
+
if (!firstPlace || originOf(first.url) !== origin) {
|
|
19358
|
+
throw new Error(`The start URL led off the product, to ${first.url}`);
|
|
19359
|
+
}
|
|
19360
|
+
addSurface(first, firstPlace, null, [], 0);
|
|
19361
|
+
for (let i = 0; i < surfaces.length; i++) {
|
|
19362
|
+
const surface = surfaces[i];
|
|
19363
|
+
if (!await replay(surface.path)) {
|
|
19364
|
+
stats.errors++;
|
|
19365
|
+
surface.actions.push({
|
|
19366
|
+
role: "",
|
|
19367
|
+
name: "",
|
|
19368
|
+
occurrence: 0,
|
|
19369
|
+
outcome: { kind: "error", message: LOST }
|
|
19370
|
+
});
|
|
19371
|
+
continue;
|
|
19372
|
+
}
|
|
19373
|
+
const home = await readPage(page);
|
|
19374
|
+
const seen = new Set(home.candidates.map(candidateSig));
|
|
19375
|
+
const work = home.candidates.map((c) => ({ c, via: [] }));
|
|
19376
|
+
const homeList = home.candidates.map(candidateSig).join("\n");
|
|
19377
|
+
let pristine = true;
|
|
19378
|
+
for (let w = 0; w < work.length; w++) {
|
|
19379
|
+
const { c, via } = work[w];
|
|
19380
|
+
const action = {
|
|
19381
|
+
role: c.role,
|
|
19382
|
+
name: c.name,
|
|
19383
|
+
occurrence: c.occurrence,
|
|
19384
|
+
...c.href ? { href: c.href } : {},
|
|
19385
|
+
...via.length > 0 ? { via } : {},
|
|
19386
|
+
outcome: { kind: "same" }
|
|
19387
|
+
};
|
|
19388
|
+
surface.actions.push(action);
|
|
19389
|
+
const reason = skipReason(c, origin, deny);
|
|
19390
|
+
if (reason) {
|
|
19391
|
+
action.outcome = { kind: "skipped", reason };
|
|
19392
|
+
stats.skipped++;
|
|
19393
|
+
continue;
|
|
19394
|
+
}
|
|
19395
|
+
if (surface.depth >= budget.max_depth || stats.actions_tried >= budget.max_actions) {
|
|
19396
|
+
action.outcome = { kind: "skipped", reason: "budget" };
|
|
19397
|
+
stats.skipped++;
|
|
19398
|
+
stats.truncated = true;
|
|
19399
|
+
continue;
|
|
19400
|
+
}
|
|
19401
|
+
stats.actions_tried++;
|
|
19402
|
+
takeBlocked();
|
|
19403
|
+
const click = {
|
|
19404
|
+
click: { role: c.role, name: c.name, occurrence: c.occurrence }
|
|
19405
|
+
};
|
|
19406
|
+
let outcome;
|
|
19407
|
+
let revealed = 0;
|
|
19408
|
+
let left = false;
|
|
19409
|
+
let lost = false;
|
|
19410
|
+
try {
|
|
19411
|
+
if (!pristine || via.length > 0) {
|
|
19412
|
+
if (!await replay([...surface.path, ...via])) {
|
|
19413
|
+
throw new Error(LOST);
|
|
19414
|
+
}
|
|
19415
|
+
}
|
|
19416
|
+
pristine = false;
|
|
19417
|
+
const before = await readPage(page);
|
|
19418
|
+
await markDocument(page);
|
|
19419
|
+
if (c.newTab && c.href) {
|
|
19420
|
+
await goto(c.href);
|
|
19421
|
+
} else {
|
|
19422
|
+
if (!await clickCandidate(page, c)) {
|
|
19423
|
+
throw new Error("control is gone");
|
|
19424
|
+
}
|
|
19425
|
+
await settle(page, settleMs);
|
|
19426
|
+
}
|
|
19427
|
+
const after = await readPage(page);
|
|
19428
|
+
if (originOf(after.url) !== origin) {
|
|
19429
|
+
outcome = { kind: "external", url: after.url };
|
|
19430
|
+
left = true;
|
|
19431
|
+
} else {
|
|
19432
|
+
const place = explorePlaceKey(after.url) ?? before.url;
|
|
19433
|
+
const sig = signatureOf(after);
|
|
19434
|
+
const sameDoc = !(c.newTab && c.href) && await isSameDocument(page);
|
|
19435
|
+
const beforePlace = explorePlaceKey(before.url);
|
|
19436
|
+
let key = byPlaceView.get(`${place}
|
|
19437
|
+
${sig}`);
|
|
19438
|
+
let view = null;
|
|
19439
|
+
if (!key) {
|
|
19440
|
+
const inPage = sameDoc && place === beforePlace;
|
|
19441
|
+
view = inPage ? sig : null;
|
|
19442
|
+
key = exploreSurfaceKey(place, view);
|
|
19443
|
+
if (byKey.has(key)) byPlaceView.set(`${place}
|
|
19444
|
+
${sig}`, key);
|
|
19445
|
+
}
|
|
19446
|
+
if (key === surface.key) {
|
|
19447
|
+
outcome = { kind: "same" };
|
|
19448
|
+
const list2 = after.candidates.map(candidateSig).join("\n");
|
|
19449
|
+
for (const n of after.candidates) {
|
|
19450
|
+
const s = candidateSig(n);
|
|
19451
|
+
if (seen.has(s)) continue;
|
|
19452
|
+
seen.add(s);
|
|
19453
|
+
work.push({ c: n, via: [...via, click] });
|
|
19454
|
+
revealed++;
|
|
19455
|
+
}
|
|
19456
|
+
if (list2 === homeList) pristine = true;
|
|
19457
|
+
} else {
|
|
19458
|
+
outcome = { kind: "surface", surface: key };
|
|
19459
|
+
if (!byKey.has(key)) {
|
|
19460
|
+
if (stats.surfaces >= budget.max_surfaces) {
|
|
19461
|
+
stats.truncated = true;
|
|
19462
|
+
} else {
|
|
19463
|
+
const path30 = !sameDoc && view === null ? [{ goto: after.url }] : [...surface.path, ...via, click];
|
|
19464
|
+
addSurface(after, place, view, path30, surface.depth + 1);
|
|
19465
|
+
}
|
|
19466
|
+
}
|
|
19467
|
+
}
|
|
19468
|
+
}
|
|
19469
|
+
} catch (err) {
|
|
19470
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
19471
|
+
outcome = { kind: "error", message: msg.split("\n")[0].slice(0, 200) };
|
|
19472
|
+
stats.errors++;
|
|
19473
|
+
lost = msg === LOST;
|
|
19474
|
+
}
|
|
19475
|
+
action.outcome = outcome;
|
|
19476
|
+
if (revealed > 0) action.revealed = revealed;
|
|
19477
|
+
const writes = takeBlocked();
|
|
19478
|
+
if (writes.length > 0) {
|
|
19479
|
+
action.blocked_requests = [...new Set(writes)];
|
|
19480
|
+
stats.blocked_requests += writes.length;
|
|
19481
|
+
}
|
|
19482
|
+
if (left) pristine = false;
|
|
19483
|
+
if (lost) break;
|
|
19484
|
+
}
|
|
19485
|
+
}
|
|
19486
|
+
return { surfaces, stats };
|
|
19487
|
+
}
|
|
19488
|
+
var UNSAFE_NAME, UNSAFE_PATH, SAFE_METHODS, CLICK_TIMEOUT_MS, LOST, NAV_TIMEOUT_MS, READ_PAGE;
|
|
19489
|
+
var init_explore_crawl = __esm({
|
|
19490
|
+
"src/commands/explore-crawl.ts"() {
|
|
19491
|
+
"use strict";
|
|
19492
|
+
init_dist2();
|
|
19493
|
+
UNSAFE_NAME = new RegExp(
|
|
19494
|
+
`(?<![\\p{L}\\p{N}])(${[
|
|
19495
|
+
// English
|
|
19496
|
+
"delete",
|
|
19497
|
+
"remove",
|
|
19498
|
+
"destroy",
|
|
19499
|
+
"erase",
|
|
19500
|
+
"pay",
|
|
19501
|
+
"pay now",
|
|
19502
|
+
"purchase",
|
|
19503
|
+
"buy",
|
|
19504
|
+
"checkout",
|
|
19505
|
+
"place order",
|
|
19506
|
+
"order now",
|
|
19507
|
+
"send",
|
|
19508
|
+
"submit",
|
|
19509
|
+
"save",
|
|
19510
|
+
"confirm",
|
|
19511
|
+
"approve",
|
|
19512
|
+
"reject",
|
|
19513
|
+
"decline",
|
|
19514
|
+
"publish",
|
|
19515
|
+
"archive",
|
|
19516
|
+
"unsubscribe",
|
|
19517
|
+
"log ?out",
|
|
19518
|
+
"sign ?out",
|
|
19519
|
+
"reset",
|
|
19520
|
+
"transfer",
|
|
19521
|
+
"invite",
|
|
19522
|
+
"upload",
|
|
19523
|
+
"import",
|
|
19524
|
+
"deactivate",
|
|
19525
|
+
"close account",
|
|
19526
|
+
// Swedish
|
|
19527
|
+
"ta bort",
|
|
19528
|
+
"radera",
|
|
19529
|
+
"betala",
|
|
19530
|
+
"k\xF6p",
|
|
19531
|
+
"best\xE4ll",
|
|
19532
|
+
"skicka",
|
|
19533
|
+
"skicka in",
|
|
19534
|
+
"spara",
|
|
19535
|
+
"bekr\xE4fta",
|
|
19536
|
+
"godk\xE4nn",
|
|
19537
|
+
"avsl\xE5",
|
|
19538
|
+
"publicera",
|
|
19539
|
+
"arkivera",
|
|
19540
|
+
"logga ut",
|
|
19541
|
+
"\xE5terst\xE4ll",
|
|
19542
|
+
"\xF6verf\xF6r",
|
|
19543
|
+
"bjud in",
|
|
19544
|
+
"ladda upp"
|
|
19545
|
+
].join("|")})(?![\\p{L}\\p{N}])`,
|
|
19546
|
+
"iu"
|
|
19547
|
+
);
|
|
19548
|
+
UNSAFE_PATH = /(^|\/)(log-?out|sign-?out|logga-?ut|delete|remove|destroy|unsubscribe)(\/|$)/i;
|
|
19549
|
+
SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
19550
|
+
CLICK_TIMEOUT_MS = 3e3;
|
|
19551
|
+
LOST = "could not return to surface";
|
|
19552
|
+
NAV_TIMEOUT_MS = 15e3;
|
|
19553
|
+
READ_PAGE = String.raw`(() => {
|
|
19554
|
+
const SELECTOR = [
|
|
19555
|
+
"a[href]", "button", "summary",
|
|
19556
|
+
'input[type="button"]', 'input[type="submit"]',
|
|
19557
|
+
'input[type="reset"]', 'input[type="image"]',
|
|
19558
|
+
'[role="button"]', '[role="link"]', '[role="tab"]',
|
|
19559
|
+
'[role="menuitem"]', '[role="menuitemradio"]', '[role="menuitemcheckbox"]',
|
|
19560
|
+
].join(",");
|
|
19561
|
+
const clean = (s) => (s ?? "").replace(/\s+/g, " ").trim();
|
|
19562
|
+
const visible = (el) => {
|
|
19563
|
+
if (typeof el.checkVisibility === "function" &&
|
|
19564
|
+
!el.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true })) {
|
|
19565
|
+
return false;
|
|
19566
|
+
}
|
|
19567
|
+
const r = el.getBoundingClientRect();
|
|
19568
|
+
return r.width > 0 && r.height > 0;
|
|
19569
|
+
};
|
|
19570
|
+
const textOf = (id) => clean(document.getElementById(id)?.innerText);
|
|
19571
|
+
const nameOf = (el) => {
|
|
19572
|
+
const label = clean(el.getAttribute("aria-label"));
|
|
19573
|
+
if (label) return label;
|
|
19574
|
+
const by = el.getAttribute("aria-labelledby");
|
|
19575
|
+
if (by) {
|
|
19576
|
+
const t = clean(by.split(/\s+/).map(textOf).join(" "));
|
|
19577
|
+
if (t) return t;
|
|
19578
|
+
}
|
|
19579
|
+
if (el instanceof HTMLInputElement) {
|
|
19580
|
+
return clean(el.value) || clean(el.alt) || clean(el.title);
|
|
19581
|
+
}
|
|
19582
|
+
const text = clean(el.innerText);
|
|
19583
|
+
if (text) return text.slice(0, 80);
|
|
19584
|
+
const img = el.querySelector("img[alt]");
|
|
19585
|
+
return clean(img?.getAttribute("alt")) || clean(el.title);
|
|
19586
|
+
};
|
|
19587
|
+
const roleOf = (el) =>
|
|
19588
|
+
el.getAttribute("role") || (el.tagName === "A" ? "link" : "button");
|
|
19589
|
+
|
|
19590
|
+
for (const el of document.querySelectorAll("[data-iamu-x]")) {
|
|
19591
|
+
el.removeAttribute("data-iamu-x");
|
|
19592
|
+
}
|
|
19593
|
+
const seen = new Map();
|
|
19594
|
+
const candidates = [];
|
|
19595
|
+
let index = 0;
|
|
19596
|
+
for (const el of document.querySelectorAll(SELECTOR)) {
|
|
19597
|
+
if (!visible(el)) continue;
|
|
19598
|
+
if (el.disabled || el.getAttribute("aria-disabled") === "true") continue;
|
|
19599
|
+
const role = roleOf(el);
|
|
19600
|
+
const name = nameOf(el);
|
|
19601
|
+
const sig = role + "\n" + name;
|
|
19602
|
+
const occurrence = seen.get(sig) ?? 0;
|
|
19603
|
+
seen.set(sig, occurrence + 1);
|
|
19604
|
+
let submit = false;
|
|
19605
|
+
if (el instanceof HTMLButtonElement) {
|
|
19606
|
+
// A button in a form submits unless it says type="button".
|
|
19607
|
+
submit = el.form
|
|
19608
|
+
? el.type === "submit" || el.type === "reset"
|
|
19609
|
+
: el.getAttribute("type") === "submit";
|
|
19610
|
+
} else if (el instanceof HTMLInputElement) {
|
|
19611
|
+
submit = ["submit", "reset", "image"].includes(el.type);
|
|
19612
|
+
}
|
|
19613
|
+
const anchor = el instanceof HTMLAnchorElement ? el : null;
|
|
19614
|
+
el.setAttribute("data-iamu-x", String(index));
|
|
19615
|
+
candidates.push({
|
|
19616
|
+
index, role, name, occurrence,
|
|
19617
|
+
href: anchor ? anchor.href : null,
|
|
19618
|
+
download: anchor ? anchor.hasAttribute("download") : false,
|
|
19619
|
+
newTab: anchor ? anchor.target === "_blank" : false,
|
|
19620
|
+
submit,
|
|
19621
|
+
});
|
|
19622
|
+
index++;
|
|
19623
|
+
}
|
|
19624
|
+
|
|
19625
|
+
const headings = [];
|
|
19626
|
+
for (const el of document.querySelectorAll(
|
|
19627
|
+
'h1, h2, [role="heading"][aria-level="1"], [role="heading"][aria-level="2"]',
|
|
19628
|
+
)) {
|
|
19629
|
+
if (!visible(el)) continue;
|
|
19630
|
+
const t = clean(el.innerText);
|
|
19631
|
+
if (t) headings.push(t.slice(0, 120));
|
|
19632
|
+
}
|
|
19633
|
+
|
|
19634
|
+
const fields = [];
|
|
19635
|
+
for (const f of document.querySelectorAll("input, select, textarea")) {
|
|
19636
|
+
if (["hidden", "submit", "button", "reset", "image"].includes(f.type)) continue;
|
|
19637
|
+
if (!visible(f)) continue;
|
|
19638
|
+
const forLabel = f.id
|
|
19639
|
+
? document.querySelector('label[for="' + CSS.escape(f.id) + '"]')
|
|
19640
|
+
: null;
|
|
19641
|
+
const name =
|
|
19642
|
+
clean(f.getAttribute("aria-label")) ||
|
|
19643
|
+
clean(forLabel?.innerText) ||
|
|
19644
|
+
clean(f.closest("label")?.innerText) ||
|
|
19645
|
+
clean(f.placeholder) ||
|
|
19646
|
+
clean(f.name);
|
|
19647
|
+
if (name && !fields.includes(name)) fields.push(name.slice(0, 80));
|
|
19648
|
+
if (fields.length >= 40) break;
|
|
19649
|
+
}
|
|
19650
|
+
|
|
19651
|
+
return { url: location.href, title: clean(document.title), headings, fields, candidates };
|
|
19652
|
+
})()`;
|
|
19653
|
+
}
|
|
19654
|
+
});
|
|
19655
|
+
|
|
19656
|
+
// src/commands/explore.ts
|
|
19657
|
+
var explore_exports = {};
|
|
19658
|
+
__export(explore_exports, {
|
|
19659
|
+
exploreCoverageCommand: () => exploreCoverageCommand,
|
|
19660
|
+
exploreDiffCommand: () => exploreDiffCommand,
|
|
19661
|
+
exploreList: () => exploreList,
|
|
19662
|
+
exploreRun: () => exploreRun
|
|
19663
|
+
});
|
|
19664
|
+
import { promises as fs27 } from "fs";
|
|
19665
|
+
import path25 from "path";
|
|
19666
|
+
import chalk26 from "chalk";
|
|
19667
|
+
function fail2(message, json2) {
|
|
19668
|
+
if (json2) console.error(JSON.stringify({ error: message }));
|
|
19669
|
+
else console.error(chalk26.red(` ${message}`));
|
|
19670
|
+
process.exitCode = 1;
|
|
19671
|
+
}
|
|
19672
|
+
function positiveInt(raw, fallback, flag) {
|
|
19673
|
+
if (raw === void 0) return fallback;
|
|
19674
|
+
const n = Number(raw);
|
|
19675
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
19676
|
+
throw new Error(`${flag} must be a positive whole number, got "${raw}"`);
|
|
19677
|
+
}
|
|
19678
|
+
return n;
|
|
19679
|
+
}
|
|
19680
|
+
async function testabilityTarget(projectRoot) {
|
|
19681
|
+
try {
|
|
19682
|
+
const raw = await fs27.readFile(
|
|
19683
|
+
path25.join(projectRoot, "iamu", "testability.md"),
|
|
19684
|
+
"utf8"
|
|
19685
|
+
);
|
|
19686
|
+
const { frontmatter } = parseFrontmatter(raw);
|
|
19687
|
+
return typeof frontmatter.target_url === "string" ? frontmatter.target_url : null;
|
|
19688
|
+
} catch {
|
|
19689
|
+
return null;
|
|
19690
|
+
}
|
|
19691
|
+
}
|
|
19692
|
+
async function openPage(opts) {
|
|
19693
|
+
const { chromium } = await import("playwright-core");
|
|
19694
|
+
const viewport = parseViewport(opts.viewport) ?? {
|
|
19695
|
+
width: 1280,
|
|
19696
|
+
height: 800
|
|
19697
|
+
};
|
|
19698
|
+
if (opts.attach) {
|
|
19699
|
+
const state = readBrowseState();
|
|
19700
|
+
if (!state) {
|
|
19701
|
+
throw new Error(
|
|
19702
|
+
"No browser to attach to \u2014 start one with `iamu browse start` and sign in first."
|
|
19703
|
+
);
|
|
19704
|
+
}
|
|
19705
|
+
const browser2 = await chromium.connectOverCDP(
|
|
19706
|
+
`http://127.0.0.1:${state.port}`
|
|
19707
|
+
);
|
|
19708
|
+
const context = browser2.contexts()[0] ?? await browser2.newContext();
|
|
19709
|
+
const page2 = await context.newPage();
|
|
19710
|
+
await page2.setViewportSize(viewport);
|
|
19711
|
+
return {
|
|
19712
|
+
page: page2,
|
|
19713
|
+
close: async () => {
|
|
19714
|
+
await page2.close().catch(() => {
|
|
19715
|
+
});
|
|
19716
|
+
await browser2.close().catch(() => {
|
|
19717
|
+
});
|
|
19718
|
+
}
|
|
19719
|
+
};
|
|
19720
|
+
}
|
|
19721
|
+
const chrome = findChrome();
|
|
19722
|
+
if (!chrome) {
|
|
19723
|
+
throw new Error(
|
|
19724
|
+
"No Chrome or Chromium found \u2014 install one or set IAMU_BROWSER."
|
|
19725
|
+
);
|
|
19726
|
+
}
|
|
19727
|
+
const browser = await chromium.launch({
|
|
19728
|
+
executablePath: chrome,
|
|
19729
|
+
headless: true,
|
|
19730
|
+
chromiumSandbox: !shouldDisableSandbox()
|
|
19731
|
+
});
|
|
19732
|
+
const page = await browser.newPage({ viewport, serviceWorkers: "block" });
|
|
19733
|
+
return { page, close: () => browser.close().catch(() => {
|
|
19734
|
+
}) };
|
|
19735
|
+
}
|
|
19736
|
+
async function exploreRun(url, opts) {
|
|
19737
|
+
const ctx = await resolveProject(opts);
|
|
19738
|
+
if (!ctx) return;
|
|
19739
|
+
const target = url ?? await testabilityTarget(ctx.projectRoot);
|
|
19740
|
+
if (!target) {
|
|
19741
|
+
fail2(
|
|
19742
|
+
"No URL to explore \u2014 pass one (iamu explore http://localhost:3000) or set target_url in iamu/testability.md.",
|
|
19743
|
+
opts.json
|
|
19744
|
+
);
|
|
19745
|
+
return;
|
|
19746
|
+
}
|
|
19747
|
+
let startUrl;
|
|
19748
|
+
let budget;
|
|
19749
|
+
let deny;
|
|
19750
|
+
try {
|
|
19751
|
+
startUrl = new URL(target).href;
|
|
19752
|
+
budget = {
|
|
19753
|
+
max_surfaces: positiveInt(
|
|
19754
|
+
opts.maxSurfaces,
|
|
19755
|
+
DEFAULTS.max_surfaces,
|
|
19756
|
+
"--max-surfaces"
|
|
19757
|
+
),
|
|
19758
|
+
max_actions: positiveInt(
|
|
19759
|
+
opts.maxActions,
|
|
19760
|
+
DEFAULTS.max_actions,
|
|
19761
|
+
"--max-actions"
|
|
19762
|
+
),
|
|
19763
|
+
max_depth: positiveInt(opts.maxDepth, DEFAULTS.max_depth, "--max-depth")
|
|
19764
|
+
};
|
|
19765
|
+
deny = (opts.deny ?? []).map((d) => new RegExp(d, "i"));
|
|
19766
|
+
} catch (err) {
|
|
19767
|
+
fail2(err.message, opts.json);
|
|
19768
|
+
return;
|
|
19769
|
+
}
|
|
19770
|
+
const quiet = opts.json || process.env.IAMU_QUIET === "1";
|
|
19771
|
+
if (!quiet) {
|
|
19772
|
+
console.error(chalk26.dim(` Exploring ${startUrl} (read-only)\u2026`));
|
|
19773
|
+
}
|
|
19774
|
+
const now = /* @__PURE__ */ new Date();
|
|
19775
|
+
let opened = null;
|
|
19776
|
+
let result;
|
|
19777
|
+
try {
|
|
19778
|
+
opened = await openPage(opts);
|
|
19779
|
+
result = await crawl(opened.page, {
|
|
19780
|
+
startUrl,
|
|
19781
|
+
budget,
|
|
19782
|
+
deny,
|
|
19783
|
+
log: quiet ? void 0 : (line) => console.error(chalk26.dim(line))
|
|
19784
|
+
});
|
|
19785
|
+
} catch (err) {
|
|
19786
|
+
fail2(err.message.split("\n")[0], opts.json);
|
|
19787
|
+
return;
|
|
19788
|
+
} finally {
|
|
19789
|
+
await opened?.close();
|
|
19790
|
+
}
|
|
19791
|
+
const build = detectBuild({ cwd: ctx.projectRoot });
|
|
19792
|
+
const map2 = {
|
|
19793
|
+
schema: 1,
|
|
19794
|
+
id: composeExploreId(now),
|
|
19795
|
+
created_at: now.toISOString(),
|
|
19796
|
+
target_url: startUrl,
|
|
19797
|
+
...build ? { build: { ...build } } : {},
|
|
19798
|
+
budget,
|
|
19799
|
+
stats: result.stats,
|
|
19800
|
+
surfaces: result.surfaces
|
|
19801
|
+
};
|
|
19802
|
+
const file = await writeExploreMap(ctx.projectRoot, map2);
|
|
19803
|
+
const rel = path25.relative(process.cwd(), file);
|
|
19804
|
+
if (opts.json) {
|
|
19805
|
+
console.log(JSON.stringify({ id: map2.id, path: rel, stats: map2.stats }));
|
|
19806
|
+
return;
|
|
19807
|
+
}
|
|
19808
|
+
const s = map2.stats;
|
|
19809
|
+
console.log(
|
|
19810
|
+
`
|
|
19811
|
+
${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` : ""}`
|
|
19812
|
+
);
|
|
19813
|
+
if (s.truncated) {
|
|
19814
|
+
console.log(
|
|
19815
|
+
chalk26.yellow(
|
|
19816
|
+
" Stopped by the budget before the product ran out \u2014 raise --max-surfaces / --max-actions / --max-depth to go further."
|
|
19817
|
+
)
|
|
19818
|
+
);
|
|
19819
|
+
}
|
|
19820
|
+
console.log(` Map: ${rel}`);
|
|
19821
|
+
console.log(chalk26.dim(" Next: iamu explore coverage"));
|
|
19822
|
+
}
|
|
19823
|
+
async function resolveMapId(projectRoot, id, offsetFromEnd = 1) {
|
|
19824
|
+
if (id && id !== "latest") return id;
|
|
19825
|
+
const ids = await listExploreMaps(projectRoot);
|
|
19826
|
+
return ids.at(-offsetFromEnd) ?? null;
|
|
19827
|
+
}
|
|
19828
|
+
async function exploreList(opts) {
|
|
19829
|
+
const ctx = await resolveProject(opts);
|
|
19830
|
+
if (!ctx) return;
|
|
19831
|
+
const rows = [];
|
|
19832
|
+
for (const id of await listExploreMaps(ctx.projectRoot)) {
|
|
19833
|
+
try {
|
|
19834
|
+
const m = await readExploreMap(ctx.projectRoot, id);
|
|
19835
|
+
rows.push({ id, target_url: m.target_url, surfaces: m.surfaces.length });
|
|
19836
|
+
} catch {
|
|
19837
|
+
}
|
|
19838
|
+
}
|
|
19839
|
+
if (opts.json) {
|
|
19840
|
+
console.log(JSON.stringify({ maps: rows }));
|
|
19841
|
+
return;
|
|
19842
|
+
}
|
|
19843
|
+
if (rows.length === 0) {
|
|
19844
|
+
console.log(" No maps yet. Run: iamu explore <url>");
|
|
19845
|
+
return;
|
|
19846
|
+
}
|
|
19847
|
+
for (const r of rows) {
|
|
19848
|
+
console.log(
|
|
19849
|
+
` ${r.id} ${r.surfaces} surfaces ${chalk26.dim(r.target_url)}`
|
|
19850
|
+
);
|
|
19851
|
+
}
|
|
19852
|
+
}
|
|
19853
|
+
async function exploreCoverageCommand(mapId, opts) {
|
|
19854
|
+
const ctx = await resolveProject(opts);
|
|
19855
|
+
if (!ctx) return;
|
|
19856
|
+
const id = await resolveMapId(ctx.projectRoot, mapId);
|
|
19857
|
+
if (!id) {
|
|
19858
|
+
fail2("No map yet. Run: iamu explore <url>", opts.json);
|
|
19859
|
+
return;
|
|
19860
|
+
}
|
|
19861
|
+
let map2;
|
|
19862
|
+
try {
|
|
19863
|
+
map2 = await readExploreMap(ctx.projectRoot, id);
|
|
19864
|
+
} catch (err) {
|
|
19865
|
+
fail2(err.message, opts.json);
|
|
19866
|
+
return;
|
|
19867
|
+
}
|
|
19868
|
+
const cov = exploreCoverage(map2, await readAllRunSteps(ctx.projectRoot));
|
|
19869
|
+
if (opts.json) {
|
|
19870
|
+
console.log(JSON.stringify(cov));
|
|
19871
|
+
return;
|
|
19872
|
+
}
|
|
19873
|
+
console.log(
|
|
19874
|
+
`
|
|
19875
|
+
Map ${id} (${map2.surfaces.length} surfaces) against ${cov.runs} runs, ${cov.steps} steps`
|
|
19876
|
+
);
|
|
19877
|
+
console.log(
|
|
19878
|
+
` ${chalk26.green(`${cov.reached} reached`)} \xB7 ${chalk26.yellow(`${cov.place_only} URL reached, view unclear`)} \xB7 ${chalk26.red(`${cov.unreached} never reached`)}`
|
|
19879
|
+
);
|
|
19880
|
+
const section = (title, rows, color) => {
|
|
19881
|
+
if (rows.length === 0) return;
|
|
19882
|
+
console.log(`
|
|
19883
|
+
${color(title)}`);
|
|
19884
|
+
for (const r of rows) {
|
|
19885
|
+
console.log(
|
|
19886
|
+
` ${r.key.padEnd(32)} ${r.label}${chalk26.dim(` (depth ${r.depth})`)}`
|
|
19887
|
+
);
|
|
19888
|
+
}
|
|
19889
|
+
};
|
|
19890
|
+
section(
|
|
19891
|
+
"Never reached by a run",
|
|
19892
|
+
cov.surfaces.filter((s) => s.status === "unreached"),
|
|
19893
|
+
chalk26.red
|
|
19894
|
+
);
|
|
19895
|
+
section(
|
|
19896
|
+
"URL reached, but no step names this view",
|
|
19897
|
+
cov.surfaces.filter((s) => s.status === "place_only"),
|
|
19898
|
+
chalk26.yellow
|
|
19899
|
+
);
|
|
19900
|
+
if (cov.run_only_places.length > 0) {
|
|
19901
|
+
console.log(`
|
|
19902
|
+
${chalk26.cyan("Walked by runs, not in the map")}`);
|
|
19903
|
+
for (const p of cov.run_only_places) {
|
|
19904
|
+
console.log(
|
|
19905
|
+
` ${p.place.padEnd(32)} ${p.steps} steps, ${p.runs.length} runs`
|
|
19906
|
+
);
|
|
19907
|
+
}
|
|
19908
|
+
}
|
|
19909
|
+
if (cov.runs === 0) {
|
|
19910
|
+
console.log(
|
|
19911
|
+
chalk26.dim(
|
|
19912
|
+
"\n No runs yet \u2014 every surface counts as never reached. Walk a scenario first (iamu-run)."
|
|
19913
|
+
)
|
|
19914
|
+
);
|
|
19915
|
+
}
|
|
19916
|
+
}
|
|
19917
|
+
async function exploreDiffCommand(a, b, opts) {
|
|
19918
|
+
const ctx = await resolveProject(opts);
|
|
19919
|
+
if (!ctx) return;
|
|
19920
|
+
const idA = a ?? await resolveMapId(ctx.projectRoot, void 0, 2);
|
|
19921
|
+
const idB = b ?? await resolveMapId(ctx.projectRoot, void 0, 1);
|
|
19922
|
+
if (!idA || !idB || idA === idB) {
|
|
19923
|
+
fail2(
|
|
19924
|
+
"Need two maps to compare \u2014 explore again (iamu explore <url>), or name both maps.",
|
|
19925
|
+
opts.json
|
|
19926
|
+
);
|
|
19927
|
+
return;
|
|
19928
|
+
}
|
|
19929
|
+
let mapA;
|
|
19930
|
+
let mapB;
|
|
19931
|
+
try {
|
|
19932
|
+
mapA = await readExploreMap(ctx.projectRoot, idA);
|
|
19933
|
+
mapB = await readExploreMap(ctx.projectRoot, idB);
|
|
19934
|
+
} catch (err) {
|
|
19935
|
+
fail2(err.message, opts.json);
|
|
19936
|
+
return;
|
|
19937
|
+
}
|
|
19938
|
+
const d = diffExploreMaps(mapA, mapB);
|
|
19939
|
+
if (opts.json) {
|
|
19940
|
+
console.log(JSON.stringify(d));
|
|
19941
|
+
return;
|
|
19942
|
+
}
|
|
19943
|
+
const pct = Math.round(d.surface_overlap * 100);
|
|
19944
|
+
console.log(`
|
|
19945
|
+
${idA} \u2192 ${idB}`);
|
|
19946
|
+
console.log(
|
|
19947
|
+
` ${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)`
|
|
19948
|
+
);
|
|
19949
|
+
if (d.only_a.length > 0) {
|
|
19950
|
+
console.log(`
|
|
19951
|
+
${chalk26.red("Only in the first")}`);
|
|
19952
|
+
for (const s of d.only_a) console.log(` ${s.key.padEnd(32)} ${s.label}`);
|
|
19953
|
+
}
|
|
19954
|
+
if (d.only_b.length > 0) {
|
|
19955
|
+
console.log(`
|
|
19956
|
+
${chalk26.green("Only in the second")}`);
|
|
19957
|
+
for (const s of d.only_b) console.log(` ${s.key.padEnd(32)} ${s.label}`);
|
|
19958
|
+
}
|
|
19959
|
+
if (d.changed_actions.length > 0) {
|
|
19960
|
+
console.log(
|
|
19961
|
+
`
|
|
19962
|
+
${chalk26.yellow(`Controls differ on ${d.changed_actions.length} shared surfaces`)}`
|
|
19963
|
+
);
|
|
19964
|
+
for (const c of d.changed_actions) {
|
|
19965
|
+
console.log(` ${c.key} ${c.label}`);
|
|
19966
|
+
for (const x of c.only_a) console.log(chalk26.red(` - ${x}`));
|
|
19967
|
+
for (const x of c.only_b) console.log(chalk26.green(` + ${x}`));
|
|
19968
|
+
}
|
|
19969
|
+
}
|
|
19970
|
+
}
|
|
19971
|
+
var DEFAULTS;
|
|
19972
|
+
var init_explore = __esm({
|
|
19973
|
+
"src/commands/explore.ts"() {
|
|
19974
|
+
"use strict";
|
|
19975
|
+
init_dist2();
|
|
19976
|
+
init_build_info();
|
|
19977
|
+
init_browse();
|
|
19978
|
+
init_context4();
|
|
19979
|
+
init_explore_crawl();
|
|
19980
|
+
DEFAULTS = { max_surfaces: 40, max_actions: 300, max_depth: 6 };
|
|
19981
|
+
}
|
|
19982
|
+
});
|
|
19983
|
+
|
|
19984
|
+
// src/commands/completion.ts
|
|
19985
|
+
var completion_exports = {};
|
|
19986
|
+
__export(completion_exports, {
|
|
19987
|
+
completion: () => completion
|
|
19988
|
+
});
|
|
19989
|
+
function extractCommandMeta(program2) {
|
|
19990
|
+
const result = [];
|
|
19991
|
+
for (const cmd of program2.commands) {
|
|
19992
|
+
if (cmd._hidden) continue;
|
|
19993
|
+
const name = cmd.name();
|
|
19994
|
+
const description = cmd.description() || "";
|
|
19995
|
+
const options = cmd.options.map((opt) => {
|
|
19996
|
+
return opt.long ?? opt.short ?? opt.flags;
|
|
19997
|
+
});
|
|
19998
|
+
const subcommands = [];
|
|
19999
|
+
for (const sub of cmd.commands) {
|
|
20000
|
+
if (sub._hidden) continue;
|
|
20001
|
+
subcommands.push({
|
|
20002
|
+
name: sub.name(),
|
|
20003
|
+
description: sub.description() || "",
|
|
20004
|
+
options: sub.options.map(
|
|
20005
|
+
(opt) => opt.long ?? opt.short ?? opt.flags
|
|
20006
|
+
)
|
|
20007
|
+
});
|
|
20008
|
+
}
|
|
20009
|
+
result.push({ name, description, options, subcommands });
|
|
20010
|
+
}
|
|
20011
|
+
return result;
|
|
18686
20012
|
}
|
|
18687
20013
|
function generateBash(program2) {
|
|
18688
20014
|
const metas = extractCommandMeta(program2);
|
|
@@ -18851,7 +20177,7 @@ __export(instructions_walk_exports, {
|
|
|
18851
20177
|
});
|
|
18852
20178
|
import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
|
|
18853
20179
|
import { join as join9 } from "path";
|
|
18854
|
-
import
|
|
20180
|
+
import chalk27 from "chalk";
|
|
18855
20181
|
function selfCommand() {
|
|
18856
20182
|
const q = (p) => /[\s"'$]/.test(p) ? JSON.stringify(p) : p;
|
|
18857
20183
|
return `${q(process.execPath)} ${q(process.argv[1] ?? "iamu")}`;
|
|
@@ -18954,6 +20280,17 @@ function composeWalkerPrompt(p) {
|
|
|
18954
20280
|
"",
|
|
18955
20281
|
"## First-person brief",
|
|
18956
20282
|
p.brief ?? "",
|
|
20283
|
+
...p.context ? ["", "## Product domain & regulatory context", p.context.trim()] : [],
|
|
20284
|
+
...p.state?.description || p.state?.approximation ? [
|
|
20285
|
+
"",
|
|
20286
|
+
"## Expected state & semantic squinting",
|
|
20287
|
+
p.state.description ? `Expected state: ${p.state.description}` : "",
|
|
20288
|
+
p.state.approximation ? `Approximation strategy: ${p.state.approximation}` : "",
|
|
20289
|
+
"Squinting Guardrails:",
|
|
20290
|
+
"\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.",
|
|
20291
|
+
"\u2022 Squinting does NOT permit drifting to unrelated settings, account profiles, or off-target pages. Stay strictly within the scenario journey.",
|
|
20292
|
+
"\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."
|
|
20293
|
+
].filter(Boolean) : [],
|
|
18957
20294
|
...revisitSection(p.revisit ?? []),
|
|
18958
20295
|
"",
|
|
18959
20296
|
"## Access",
|
|
@@ -19102,7 +20439,8 @@ async function instructionsWalk(opts) {
|
|
|
19102
20439
|
const result = {
|
|
19103
20440
|
scenario: {
|
|
19104
20441
|
slug: sfm.slug,
|
|
19105
|
-
brief: scenario.body.trim() || null
|
|
20442
|
+
brief: scenario.body.trim() || null,
|
|
20443
|
+
state: sfm.state ?? null
|
|
19106
20444
|
},
|
|
19107
20445
|
persona: {
|
|
19108
20446
|
slug: sfm.persona,
|
|
@@ -19156,7 +20494,9 @@ async function instructionsWalk(opts) {
|
|
|
19156
20494
|
runDirPath,
|
|
19157
20495
|
projectRoot,
|
|
19158
20496
|
surface: opts.surface ?? "browse",
|
|
19159
|
-
viewport
|
|
20497
|
+
viewport,
|
|
20498
|
+
state: sfm.state ?? null,
|
|
20499
|
+
context: contextYml
|
|
19160
20500
|
}) : null
|
|
19161
20501
|
};
|
|
19162
20502
|
if (opts.json) {
|
|
@@ -19164,30 +20504,35 @@ async function instructionsWalk(opts) {
|
|
|
19164
20504
|
return;
|
|
19165
20505
|
}
|
|
19166
20506
|
console.log();
|
|
19167
|
-
console.log(
|
|
19168
|
-
console.log(` ${
|
|
19169
|
-
console.log(` ${
|
|
19170
|
-
console.log(` ${
|
|
19171
|
-
if (
|
|
20507
|
+
console.log(chalk27.bold(" Walk instructions"));
|
|
20508
|
+
console.log(` ${chalk27.dim("scenario:")} ${sfm.slug}`);
|
|
20509
|
+
console.log(` ${chalk27.dim("persona:")} ${pfm.name} (${pfm.role})`);
|
|
20510
|
+
console.log(` ${chalk27.dim("outline:")} ${ofm.title}`);
|
|
20511
|
+
if (sfm.state?.description || sfm.state?.approximation) {
|
|
20512
|
+
console.log(
|
|
20513
|
+
` ${chalk27.dim("state:")} ${sfm.state.description ? sfm.state.description : sfm.state.approximation ?? ""}`
|
|
20514
|
+
);
|
|
20515
|
+
}
|
|
20516
|
+
if (targetUrl) console.log(` ${chalk27.dim("target:")} ${targetUrl}`);
|
|
19172
20517
|
console.log(
|
|
19173
|
-
` ${
|
|
20518
|
+
` ${chalk27.dim("access:")} ${projectAccess || personaAccess ? `${projectAccess ? "project" : ""}${projectAccess && personaAccess ? " + " : ""}${personaAccess ? "persona" : ""}` : "not recorded (walker would run as whoever is signed in)"}`
|
|
19174
20519
|
);
|
|
19175
20520
|
console.log(
|
|
19176
|
-
` ${
|
|
20521
|
+
` ${chalk27.dim("dismissed:")} ${dismissedFacts.length} findings to skip`
|
|
19177
20522
|
);
|
|
19178
20523
|
console.log(
|
|
19179
|
-
` ${
|
|
20524
|
+
` ${chalk27.dim("guidance:")} ${guidance.length} tester guidance rule${guidance.length === 1 ? "" : "s"}`
|
|
19180
20525
|
);
|
|
19181
20526
|
if (revisit.length > 0) {
|
|
19182
20527
|
console.log(
|
|
19183
|
-
` ${
|
|
20528
|
+
` ${chalk27.dim("re-check:")} ${revisit.length} place${revisit.length === 1 ? "" : "s"} where fixes await a re-run`
|
|
19184
20529
|
);
|
|
19185
20530
|
}
|
|
19186
20531
|
console.log(
|
|
19187
|
-
` ${
|
|
20532
|
+
` ${chalk27.dim("methodology:")} ${methodology ? "loaded" : "missing (run iamu update)"}`
|
|
19188
20533
|
);
|
|
19189
20534
|
console.log();
|
|
19190
|
-
console.log(
|
|
20535
|
+
console.log(chalk27.dim(" Use --json to get the full instruction bundle."));
|
|
19191
20536
|
console.log();
|
|
19192
20537
|
}
|
|
19193
20538
|
var SCREEN;
|
|
@@ -19213,8 +20558,8 @@ __export(new_finding_exports, {
|
|
|
19213
20558
|
});
|
|
19214
20559
|
import { randomUUID as randomUUID10 } from "crypto";
|
|
19215
20560
|
import { access, readFile as readFile6 } from "fs/promises";
|
|
19216
|
-
import
|
|
19217
|
-
import
|
|
20561
|
+
import path26 from "path";
|
|
20562
|
+
import chalk28 from "chalk";
|
|
19218
20563
|
async function newFinding(opts) {
|
|
19219
20564
|
const ctx = await resolveProject(opts);
|
|
19220
20565
|
if (!ctx) return;
|
|
@@ -19280,7 +20625,7 @@ async function newFinding(opts) {
|
|
|
19280
20625
|
}
|
|
19281
20626
|
try {
|
|
19282
20627
|
await access(
|
|
19283
|
-
|
|
20628
|
+
path26.join(runDirPath, "steps", `${String(n).padStart(3, "0")}.json`)
|
|
19284
20629
|
);
|
|
19285
20630
|
} catch {
|
|
19286
20631
|
commandError(
|
|
@@ -19295,7 +20640,7 @@ async function newFinding(opts) {
|
|
|
19295
20640
|
if (!scenarioSlug) {
|
|
19296
20641
|
try {
|
|
19297
20642
|
const manifestRaw = await readFile6(
|
|
19298
|
-
|
|
20643
|
+
path26.join(runDirPath, "walkthrough.json"),
|
|
19299
20644
|
"utf8"
|
|
19300
20645
|
);
|
|
19301
20646
|
const manifest = JSON.parse(manifestRaw);
|
|
@@ -19384,10 +20729,10 @@ async function newFinding(opts) {
|
|
|
19384
20729
|
return;
|
|
19385
20730
|
}
|
|
19386
20731
|
console.log();
|
|
19387
|
-
console.log(` ${symbols.success} Created finding: ${
|
|
19388
|
-
console.log(` ${
|
|
19389
|
-
console.log(` ${
|
|
19390
|
-
for (const w of warnings) console.log(` ${
|
|
20732
|
+
console.log(` ${symbols.success} Created finding: ${chalk28.cyan(slug)}`);
|
|
20733
|
+
console.log(` ${chalk28.dim("path:")} ${record.path}`);
|
|
20734
|
+
console.log(` ${chalk28.dim("id:")} ${id}`);
|
|
20735
|
+
for (const w of warnings) console.log(` ${chalk28.yellow("!")} ${w}`);
|
|
19391
20736
|
console.log();
|
|
19392
20737
|
} catch (err) {
|
|
19393
20738
|
commandError(
|
|
@@ -19413,7 +20758,7 @@ __export(view_results_exports, {
|
|
|
19413
20758
|
promptViewResults: () => promptViewResults
|
|
19414
20759
|
});
|
|
19415
20760
|
import { isCancel as isCancel4, select as select3 } from "@clack/prompts";
|
|
19416
|
-
import
|
|
20761
|
+
import chalk29 from "chalk";
|
|
19417
20762
|
function launchCloudDashboard(config, runId) {
|
|
19418
20763
|
const cloudRunId = runId ? toCloudRunUuid(runId) : void 0;
|
|
19419
20764
|
const url = getCloudDashboardUrl(
|
|
@@ -19422,7 +20767,7 @@ function launchCloudDashboard(config, runId) {
|
|
|
19422
20767
|
cloudRunId
|
|
19423
20768
|
);
|
|
19424
20769
|
console.log();
|
|
19425
|
-
console.log(` ${symbols.success} Opening Iamu Cloud: ${
|
|
20770
|
+
console.log(` ${symbols.success} Opening Iamu Cloud: ${chalk29.cyan(url)}
|
|
19426
20771
|
`);
|
|
19427
20772
|
openBrowser(url);
|
|
19428
20773
|
}
|
|
@@ -19490,9 +20835,9 @@ __export(evaluate_exports, {
|
|
|
19490
20835
|
evaluate: () => evaluate,
|
|
19491
20836
|
evaluateRecord: () => evaluateRecord
|
|
19492
20837
|
});
|
|
19493
|
-
import { promises as
|
|
19494
|
-
import
|
|
19495
|
-
import
|
|
20838
|
+
import { promises as fs28 } from "fs";
|
|
20839
|
+
import path27 from "path";
|
|
20840
|
+
import chalk30 from "chalk";
|
|
19496
20841
|
import { z as z25 } from "zod";
|
|
19497
20842
|
function formatIssues(error) {
|
|
19498
20843
|
return error.issues.map(
|
|
@@ -19522,10 +20867,10 @@ async function evaluate(runId, opts) {
|
|
|
19522
20867
|
const resolved = await resolveProjectAndRunDir(runId, opts);
|
|
19523
20868
|
if (!resolved) return;
|
|
19524
20869
|
const { projectRoot, runDirPath } = resolved;
|
|
19525
|
-
const canonicalPath =
|
|
20870
|
+
const canonicalPath = path27.join(runDirPath, "canonical.json");
|
|
19526
20871
|
let canonical;
|
|
19527
20872
|
try {
|
|
19528
|
-
const raw = await
|
|
20873
|
+
const raw = await fs28.readFile(canonicalPath, "utf8");
|
|
19529
20874
|
canonical = JSON.parse(raw);
|
|
19530
20875
|
} catch {
|
|
19531
20876
|
commandError(
|
|
@@ -19581,15 +20926,15 @@ async function evaluate(runId, opts) {
|
|
|
19581
20926
|
)
|
|
19582
20927
|
};
|
|
19583
20928
|
const prompt = composeJudgePrompt(input);
|
|
19584
|
-
const promptFilePath = opts?.promptFile ??
|
|
19585
|
-
await
|
|
20929
|
+
const promptFilePath = opts?.promptFile ?? path27.join(runDirPath, "judge_prompt.md");
|
|
20930
|
+
await fs28.writeFile(promptFilePath, prompt, "utf8");
|
|
19586
20931
|
if (opts?.json) {
|
|
19587
20932
|
console.log(
|
|
19588
20933
|
JSON.stringify(
|
|
19589
20934
|
{
|
|
19590
20935
|
...input,
|
|
19591
|
-
prompt_file:
|
|
19592
|
-
prompt_file_abs:
|
|
20936
|
+
prompt_file: path27.relative(projectRoot, promptFilePath),
|
|
20937
|
+
prompt_file_abs: path27.resolve(promptFilePath),
|
|
19593
20938
|
judge_prompt: prompt
|
|
19594
20939
|
},
|
|
19595
20940
|
null,
|
|
@@ -19604,10 +20949,10 @@ async function evaluateRecord(runId, opts) {
|
|
|
19604
20949
|
const resolved = await resolveProjectAndRunDir(runId, opts);
|
|
19605
20950
|
if (!resolved) return;
|
|
19606
20951
|
const { projectRoot, runDirPath } = resolved;
|
|
19607
|
-
const canonicalPath =
|
|
20952
|
+
const canonicalPath = path27.join(runDirPath, "canonical.json");
|
|
19608
20953
|
let canonical;
|
|
19609
20954
|
try {
|
|
19610
|
-
const raw = await
|
|
20955
|
+
const raw = await fs28.readFile(canonicalPath, "utf8");
|
|
19611
20956
|
canonical = JSON.parse(raw);
|
|
19612
20957
|
} catch {
|
|
19613
20958
|
commandError(`No canonical.json for run: ${runId}`, opts);
|
|
@@ -19615,7 +20960,7 @@ async function evaluateRecord(runId, opts) {
|
|
|
19615
20960
|
}
|
|
19616
20961
|
let parsedFile;
|
|
19617
20962
|
try {
|
|
19618
|
-
const raw = await
|
|
20963
|
+
const raw = await fs28.readFile(opts.file, "utf8");
|
|
19619
20964
|
parsedFile = JSON.parse(raw);
|
|
19620
20965
|
} catch {
|
|
19621
20966
|
commandError(`Cannot read verdicts file: ${opts.file}`, opts);
|
|
@@ -19672,8 +21017,8 @@ ${formatIssues(fileResult.error)}`,
|
|
|
19672
21017
|
overall: inputVerdicts.overall
|
|
19673
21018
|
});
|
|
19674
21019
|
const reportContent = renderCanonicalMarkdown(canonicalForReport);
|
|
19675
|
-
const verdictsPath =
|
|
19676
|
-
const reportPath =
|
|
21020
|
+
const verdictsPath = path27.join(runDirPath, "verdicts.json");
|
|
21021
|
+
const reportPath = path27.join(runDirPath, "report.md");
|
|
19677
21022
|
await Promise.all([
|
|
19678
21023
|
atomicWriteFile(
|
|
19679
21024
|
verdictsPath,
|
|
@@ -19693,8 +21038,8 @@ ${formatIssues(fileResult.error)}`,
|
|
|
19693
21038
|
JSON.stringify(
|
|
19694
21039
|
{
|
|
19695
21040
|
runId,
|
|
19696
|
-
verdictsPath:
|
|
19697
|
-
reportPath:
|
|
21041
|
+
verdictsPath: path27.relative(projectRoot, verdictsPath),
|
|
21042
|
+
reportPath: path27.relative(projectRoot, reportPath),
|
|
19698
21043
|
verdictsCount: validatedVerdicts.length,
|
|
19699
21044
|
linkedCount: linked.length,
|
|
19700
21045
|
...cloudInfo ? { cloud: cloudInfo } : {}
|
|
@@ -19707,18 +21052,18 @@ ${formatIssues(fileResult.error)}`,
|
|
|
19707
21052
|
}
|
|
19708
21053
|
console.log();
|
|
19709
21054
|
console.log(
|
|
19710
|
-
` ${symbols.success} Recorded ${validatedVerdicts.length} verdicts for run ${
|
|
21055
|
+
` ${symbols.success} Recorded ${validatedVerdicts.length} verdicts for run ${chalk30.cyan(runId)}`
|
|
19711
21056
|
);
|
|
19712
21057
|
if (linked.length > 0) {
|
|
19713
21058
|
console.log(
|
|
19714
|
-
` ${
|
|
21059
|
+
` ${chalk30.dim("linked:")} ${linked.length} finding${linked.length === 1 ? "" : "s"} to the human test${linked.length === 1 ? "" : "s"} citing ${linked.length === 1 ? "it" : "them"}`
|
|
19715
21060
|
);
|
|
19716
21061
|
}
|
|
19717
21062
|
console.log(
|
|
19718
|
-
` ${
|
|
21063
|
+
` ${chalk30.dim("verdicts:")} ${path27.relative(projectRoot, verdictsPath)}`
|
|
19719
21064
|
);
|
|
19720
21065
|
console.log(
|
|
19721
|
-
` ${
|
|
21066
|
+
` ${chalk30.dim("report:")} ${path27.relative(projectRoot, reportPath)}`
|
|
19722
21067
|
);
|
|
19723
21068
|
printCloudRunInfo(cloudInfo);
|
|
19724
21069
|
await promptViewResults(projectRoot, { ...opts, runId });
|
|
@@ -19782,9 +21127,9 @@ __export(complete_exports, {
|
|
|
19782
21127
|
screenshotSizeWarning: () => screenshotSizeWarning,
|
|
19783
21128
|
validateCanonicalWalkthrough: () => validateCanonicalWalkthrough
|
|
19784
21129
|
});
|
|
19785
|
-
import { promises as
|
|
19786
|
-
import
|
|
19787
|
-
import
|
|
21130
|
+
import { promises as fs29 } from "fs";
|
|
21131
|
+
import path28 from "path";
|
|
21132
|
+
import chalk31 from "chalk";
|
|
19788
21133
|
async function complete(runId, opts) {
|
|
19789
21134
|
const run = await loadRun(runId, opts);
|
|
19790
21135
|
if (!run) return;
|
|
@@ -19794,7 +21139,7 @@ async function complete(runId, opts) {
|
|
|
19794
21139
|
}
|
|
19795
21140
|
if (!opts?.json) {
|
|
19796
21141
|
console.log();
|
|
19797
|
-
console.log(
|
|
21142
|
+
console.log(chalk31.bold(" Assembling canonical JSON\u2026"));
|
|
19798
21143
|
}
|
|
19799
21144
|
const provenanceAudit = auditStepProvenance(
|
|
19800
21145
|
await readStepsForRun(run.projectRoot, runId)
|
|
@@ -19807,8 +21152,8 @@ async function complete(runId, opts) {
|
|
|
19807
21152
|
const walkerSummary = await readWalkerSummary(run.runDirPath);
|
|
19808
21153
|
let judgeReported;
|
|
19809
21154
|
try {
|
|
19810
|
-
const verdictsRaw = await
|
|
19811
|
-
|
|
21155
|
+
const verdictsRaw = await fs29.readFile(
|
|
21156
|
+
path28.join(run.runDirPath, "verdicts.json"),
|
|
19812
21157
|
"utf8"
|
|
19813
21158
|
);
|
|
19814
21159
|
const parsed = JSON.parse(verdictsRaw);
|
|
@@ -19860,16 +21205,16 @@ async function loadRun(runId, opts) {
|
|
|
19860
21205
|
if (!resolved) return null;
|
|
19861
21206
|
const { projectRoot, runDirPath } = resolved;
|
|
19862
21207
|
try {
|
|
19863
|
-
await
|
|
21208
|
+
await fs29.access(runDirPath);
|
|
19864
21209
|
} catch {
|
|
19865
21210
|
commandError(`Run not found: ${runId}`, opts);
|
|
19866
21211
|
return null;
|
|
19867
21212
|
}
|
|
19868
|
-
const manifestPath =
|
|
21213
|
+
const manifestPath = path28.join(runDirPath, "walkthrough.json");
|
|
19869
21214
|
let manifest;
|
|
19870
21215
|
try {
|
|
19871
21216
|
manifest = JSON.parse(
|
|
19872
|
-
await
|
|
21217
|
+
await fs29.readFile(manifestPath, "utf8")
|
|
19873
21218
|
);
|
|
19874
21219
|
} catch {
|
|
19875
21220
|
commandError(`No walkthrough.json found for run: ${runId}`, opts);
|
|
@@ -19881,8 +21226,8 @@ async function reportAlreadyCompleted(run, opts) {
|
|
|
19881
21226
|
const { runId, manifest } = run;
|
|
19882
21227
|
if (opts?.json) {
|
|
19883
21228
|
try {
|
|
19884
|
-
const raw = await
|
|
19885
|
-
|
|
21229
|
+
const raw = await fs29.readFile(
|
|
21230
|
+
path28.join(run.runDirPath, "canonical.json"),
|
|
19886
21231
|
"utf8"
|
|
19887
21232
|
);
|
|
19888
21233
|
console.log(raw.trim());
|
|
@@ -19902,18 +21247,18 @@ async function reportAlreadyCompleted(run, opts) {
|
|
|
19902
21247
|
return;
|
|
19903
21248
|
}
|
|
19904
21249
|
console.log();
|
|
19905
|
-
console.log(
|
|
19906
|
-
console.log(` status ${
|
|
21250
|
+
console.log(chalk31.bold(" Run: ") + chalk31.cyan(runId));
|
|
21251
|
+
console.log(` status ${chalk31.green("completed")}`);
|
|
19907
21252
|
if (manifest.completed_at) {
|
|
19908
|
-
console.log(` completed ${
|
|
21253
|
+
console.log(` completed ${chalk31.dim(manifest.completed_at)}`);
|
|
19909
21254
|
}
|
|
19910
21255
|
const viewUrl = await pushedRunUrl(run.projectRoot, runId);
|
|
19911
21256
|
if (viewUrl) {
|
|
19912
|
-
console.log(` view ${
|
|
21257
|
+
console.log(` view ${chalk31.underline(viewUrl)}`);
|
|
19913
21258
|
}
|
|
19914
21259
|
console.log();
|
|
19915
21260
|
console.log(
|
|
19916
|
-
|
|
21261
|
+
chalk31.dim(
|
|
19917
21262
|
" Already completed \u2014 nothing to do. Use --force to reassemble the report."
|
|
19918
21263
|
)
|
|
19919
21264
|
);
|
|
@@ -19929,13 +21274,13 @@ function reportStrictProvenanceFailure(audit, opts) {
|
|
|
19929
21274
|
}
|
|
19930
21275
|
console.error();
|
|
19931
21276
|
console.error(
|
|
19932
|
-
|
|
21277
|
+
chalk31.red(` ${symbols.error} Strict provenance audit failed:`)
|
|
19933
21278
|
);
|
|
19934
21279
|
for (const w of audit.warnings) {
|
|
19935
|
-
console.error(
|
|
21280
|
+
console.error(chalk31.red(` \u2022 ${w}`));
|
|
19936
21281
|
}
|
|
19937
21282
|
console.error(
|
|
19938
|
-
|
|
21283
|
+
chalk31.dim(
|
|
19939
21284
|
" Run was flagged as synthetic / scripted. Remove --strict-provenance to complete anyway."
|
|
19940
21285
|
)
|
|
19941
21286
|
);
|
|
@@ -19976,41 +21321,41 @@ function completeJsonPayload(canonical, extras) {
|
|
|
19976
21321
|
function printCompletionSummary(run, completedAt, assembled, extras, provenanceAudit) {
|
|
19977
21322
|
const { runId, projectRoot, manifest } = run;
|
|
19978
21323
|
const { canonical } = assembled;
|
|
19979
|
-
const count = (n) => n === 0 ?
|
|
19980
|
-
console.log(
|
|
19981
|
-
console.log(` scenario ${
|
|
19982
|
-
console.log(` url ${
|
|
21324
|
+
const count = (n) => n === 0 ? chalk31.dim("0") : chalk31.cyan(String(n));
|
|
21325
|
+
console.log(chalk31.bold(" Run: ") + chalk31.cyan(runId));
|
|
21326
|
+
console.log(` scenario ${chalk31.dim(manifest.scenario_slug)}`);
|
|
21327
|
+
console.log(` url ${chalk31.dim(manifest.target_url)}`);
|
|
19983
21328
|
if (manifest.environment) {
|
|
19984
|
-
console.log(` env ${
|
|
21329
|
+
console.log(` env ${chalk31.dim(manifest.environment)}`);
|
|
19985
21330
|
}
|
|
19986
21331
|
if (manifest.build) {
|
|
19987
|
-
console.log(` build ${
|
|
21332
|
+
console.log(` build ${chalk31.dim(formatBuild(manifest.build))}`);
|
|
19988
21333
|
}
|
|
19989
21334
|
if (manifest.viewport) {
|
|
19990
|
-
console.log(` device ${
|
|
21335
|
+
console.log(` device ${chalk31.dim(formatViewport(manifest.viewport))}`);
|
|
19991
21336
|
}
|
|
19992
|
-
console.log(` status ${
|
|
21337
|
+
console.log(` status ${chalk31.green("completed")}`);
|
|
19993
21338
|
console.log(` findings ${count(canonical.findings.length)}`);
|
|
19994
21339
|
console.log(` steps ${count(canonical.steps.length)}`);
|
|
19995
|
-
console.log(` started ${
|
|
19996
|
-
console.log(` completed ${
|
|
21340
|
+
console.log(` started ${chalk31.dim(manifest.created_at)}`);
|
|
21341
|
+
console.log(` completed ${chalk31.dim(completedAt)}`);
|
|
19997
21342
|
const models = manifest.models;
|
|
19998
21343
|
if (models?.velocity_sec_per_step != null) {
|
|
19999
21344
|
console.log(
|
|
20000
|
-
` velocity ${
|
|
21345
|
+
` velocity ${chalk31.cyan(`~${models.velocity_sec_per_step}s/step`)} ${chalk31.dim(`(${models.total_duration_sec}s total)`)}`
|
|
20001
21346
|
);
|
|
20002
21347
|
}
|
|
20003
21348
|
if (models && (models.walker || models.judge || models.matcher)) {
|
|
20004
21349
|
const w = models.walker_reported ? `${models.walker} (${models.walker_reported})` : models.walker ?? "fast";
|
|
20005
21350
|
const j = models.judge_reported ? `${models.judge} (${models.judge_reported})` : models.judge ?? "thorough";
|
|
20006
|
-
console.log(` models ${
|
|
21351
|
+
console.log(` models ${chalk31.dim(`walker: ${w} \xB7 judge: ${j}`)}`);
|
|
20007
21352
|
}
|
|
20008
21353
|
console.log();
|
|
20009
21354
|
console.log(
|
|
20010
|
-
` ${
|
|
21355
|
+
` ${chalk31.dim("canonical:")} ${chalk31.dim(path28.relative(projectRoot, assembled.canonicalPath))}`
|
|
20011
21356
|
);
|
|
20012
21357
|
console.log(
|
|
20013
|
-
` ${
|
|
21358
|
+
` ${chalk31.dim("report:")} ${chalk31.dim(path28.relative(projectRoot, assembled.reportPath))}`
|
|
20014
21359
|
);
|
|
20015
21360
|
if (extras.viewportWarning) {
|
|
20016
21361
|
console.log();
|
|
@@ -20018,7 +21363,7 @@ function printCompletionSummary(run, completedAt, assembled, extras, provenanceA
|
|
|
20018
21363
|
}
|
|
20019
21364
|
if (extras.codeWarning) {
|
|
20020
21365
|
console.log();
|
|
20021
|
-
console.log(` ${
|
|
21366
|
+
console.log(` ${chalk31.yellow("!")} ${extras.codeWarning}`);
|
|
20022
21367
|
}
|
|
20023
21368
|
if (extras.revisit) printRevisit(extras.revisit);
|
|
20024
21369
|
if (assembled.verification) printVerification(assembled.verification);
|
|
@@ -20030,13 +21375,13 @@ function printProvenanceWarnings(audit) {
|
|
|
20030
21375
|
if (audit.warnings.length === 0) return;
|
|
20031
21376
|
console.log();
|
|
20032
21377
|
console.log(
|
|
20033
|
-
` ${symbols.warning} ${
|
|
21378
|
+
` ${symbols.warning} ${chalk31.yellow("Step provenance audit flagged suspicious timing:")}`
|
|
20034
21379
|
);
|
|
20035
21380
|
for (const w of audit.warnings) {
|
|
20036
|
-
console.log(` ${
|
|
21381
|
+
console.log(` ${chalk31.dim("\u2022")} ${w}`);
|
|
20037
21382
|
}
|
|
20038
21383
|
console.log(
|
|
20039
|
-
|
|
21384
|
+
chalk31.dim(
|
|
20040
21385
|
" Run appears to be programmatically batch-generated rather than an interactive walkthrough."
|
|
20041
21386
|
)
|
|
20042
21387
|
);
|
|
@@ -20068,17 +21413,17 @@ function screenshotFitsViewport(width, expected) {
|
|
|
20068
21413
|
}
|
|
20069
21414
|
async function screenshotSizeWarning(runDirPath, viewport) {
|
|
20070
21415
|
if (!viewport) return null;
|
|
20071
|
-
const dir =
|
|
21416
|
+
const dir = path28.join(runDirPath, "screenshots");
|
|
20072
21417
|
let first;
|
|
20073
21418
|
try {
|
|
20074
|
-
first = (await
|
|
21419
|
+
first = (await fs29.readdir(dir)).filter((f) => f.toLowerCase().endsWith(".png")).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }))[0];
|
|
20075
21420
|
} catch {
|
|
20076
21421
|
return null;
|
|
20077
21422
|
}
|
|
20078
21423
|
if (!first) return null;
|
|
20079
21424
|
let size = null;
|
|
20080
21425
|
try {
|
|
20081
|
-
const handle = await
|
|
21426
|
+
const handle = await fs29.open(path28.join(dir, first), "r");
|
|
20082
21427
|
try {
|
|
20083
21428
|
const head = Buffer.alloc(24);
|
|
20084
21429
|
const { bytesRead } = await handle.read(head, 0, 24, 0);
|
|
@@ -20095,7 +21440,7 @@ async function screenshotSizeWarning(runDirPath, viewport) {
|
|
|
20095
21440
|
async function readWalkerSummary(runDirPath) {
|
|
20096
21441
|
try {
|
|
20097
21442
|
const data = JSON.parse(
|
|
20098
|
-
await
|
|
21443
|
+
await fs29.readFile(path28.join(runDirPath, "summary.json"), "utf8")
|
|
20099
21444
|
);
|
|
20100
21445
|
const strings = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && x.trim() !== "") : void 0;
|
|
20101
21446
|
const model = typeof data.model === "string" && data.model.trim() !== "" ? data.model.trim() : void 0;
|
|
@@ -20204,7 +21549,7 @@ function validateCanonicalWalkthrough(canonical) {
|
|
|
20204
21549
|
async function applyRecordedVerdicts(canonical, runDirPath) {
|
|
20205
21550
|
try {
|
|
20206
21551
|
const recorded = JSON.parse(
|
|
20207
|
-
await
|
|
21552
|
+
await fs29.readFile(path28.join(runDirPath, "verdicts.json"), "utf8")
|
|
20208
21553
|
);
|
|
20209
21554
|
if (Array.isArray(recorded.verdicts)) {
|
|
20210
21555
|
return applyVerdicts(canonical, recorded);
|
|
@@ -20214,7 +21559,7 @@ async function applyRecordedVerdicts(canonical, runDirPath) {
|
|
|
20214
21559
|
return canonical;
|
|
20215
21560
|
}
|
|
20216
21561
|
async function persistCanonical(runDirPath, canonical) {
|
|
20217
|
-
const canonicalPath =
|
|
21562
|
+
const canonicalPath = path28.join(runDirPath, "canonical.json");
|
|
20218
21563
|
await atomicWriteFile(
|
|
20219
21564
|
canonicalPath,
|
|
20220
21565
|
`${JSON.stringify(canonical, null, 2)}
|
|
@@ -20223,7 +21568,7 @@ async function persistCanonical(runDirPath, canonical) {
|
|
|
20223
21568
|
return canonicalPath;
|
|
20224
21569
|
}
|
|
20225
21570
|
async function persistReport(runDirPath, canonical) {
|
|
20226
|
-
const reportPath =
|
|
21571
|
+
const reportPath = path28.join(runDirPath, "report.md");
|
|
20227
21572
|
await atomicWriteFile(reportPath, renderCanonicalMarkdown(canonical));
|
|
20228
21573
|
return reportPath;
|
|
20229
21574
|
}
|
|
@@ -20323,17 +21668,17 @@ function placeLabel(p) {
|
|
|
20323
21668
|
function printRevisit(r) {
|
|
20324
21669
|
const missed = r.places.filter((p) => !p.reached);
|
|
20325
21670
|
console.log();
|
|
20326
|
-
const head = ` ${
|
|
21671
|
+
const head = ` ${chalk31.bold("Re-check places:")} reached ${r.reached} of ${r.requested}`;
|
|
20327
21672
|
if (missed.length === 0) {
|
|
20328
|
-
console.log(`${head} ${
|
|
21673
|
+
console.log(`${head} ${chalk31.green("\u2713")}`);
|
|
20329
21674
|
return;
|
|
20330
21675
|
}
|
|
20331
21676
|
console.log(`${head} \u2014 not reached:`);
|
|
20332
21677
|
for (const p of missed) {
|
|
20333
|
-
console.log(` ${
|
|
21678
|
+
console.log(` ${chalk31.yellow("\u25CB")} ${placeLabel(p)}`);
|
|
20334
21679
|
}
|
|
20335
21680
|
console.log(
|
|
20336
|
-
|
|
21681
|
+
chalk31.dim(
|
|
20337
21682
|
" Fixes there stay awaiting a re-run until a walk gets there."
|
|
20338
21683
|
)
|
|
20339
21684
|
);
|
|
@@ -20341,7 +21686,7 @@ function printRevisit(r) {
|
|
|
20341
21686
|
function printVerification(verification) {
|
|
20342
21687
|
console.log();
|
|
20343
21688
|
console.log(
|
|
20344
|
-
|
|
21689
|
+
chalk31.bold(" Since this scenario's last run") + chalk31.dim(` (${verification.baseline_run_id.slice(0, 30)}\u2026)`) + chalk31.bold(":")
|
|
20345
21690
|
);
|
|
20346
21691
|
if (verification.resolved.length > 0) {
|
|
20347
21692
|
console.log(
|
|
@@ -20350,7 +21695,7 @@ function printVerification(verification) {
|
|
|
20350
21695
|
}
|
|
20351
21696
|
if (verification.not_observed.length > 0) {
|
|
20352
21697
|
console.log(
|
|
20353
|
-
` ${symbols.pending} ${verification.not_observed.length} not seen this run ${
|
|
21698
|
+
` ${symbols.pending} ${verification.not_observed.length} not seen this run ${chalk31.dim("(not evidence of a fix)")}`
|
|
20354
21699
|
);
|
|
20355
21700
|
}
|
|
20356
21701
|
if (verification.persists.length > 0) {
|
|
@@ -20391,14 +21736,14 @@ __export(match_exports, {
|
|
|
20391
21736
|
match: () => match,
|
|
20392
21737
|
matchRecord: () => matchRecord
|
|
20393
21738
|
});
|
|
20394
|
-
import { promises as
|
|
20395
|
-
import
|
|
20396
|
-
import
|
|
21739
|
+
import { promises as fs30 } from "fs";
|
|
21740
|
+
import path29 from "path";
|
|
21741
|
+
import chalk32 from "chalk";
|
|
20397
21742
|
async function completedManifest2(runDirPath, runId, opts) {
|
|
20398
21743
|
let manifest;
|
|
20399
21744
|
try {
|
|
20400
21745
|
manifest = JSON.parse(
|
|
20401
|
-
await
|
|
21746
|
+
await fs30.readFile(path29.join(runDirPath, "walkthrough.json"), "utf8")
|
|
20402
21747
|
);
|
|
20403
21748
|
} catch {
|
|
20404
21749
|
commandError(`Run not found: ${runId}`, opts);
|
|
@@ -20427,12 +21772,12 @@ async function match(runId, opts) {
|
|
|
20427
21772
|
);
|
|
20428
21773
|
const needed = input.current.length > 0 && (input.prior.length > 0 || (input.guidance ?? []).length > 0);
|
|
20429
21774
|
const prompt = needed ? composeMatchPrompt(input) : null;
|
|
20430
|
-
const promptFilePath = opts?.promptFile ??
|
|
21775
|
+
const promptFilePath = opts?.promptFile ?? path29.join(runDirPath, "match_prompt.md");
|
|
20431
21776
|
if (prompt) {
|
|
20432
|
-
await
|
|
21777
|
+
await fs30.writeFile(promptFilePath, prompt, "utf8");
|
|
20433
21778
|
} else {
|
|
20434
21779
|
try {
|
|
20435
|
-
await
|
|
21780
|
+
await fs30.unlink(promptFilePath);
|
|
20436
21781
|
} catch {
|
|
20437
21782
|
}
|
|
20438
21783
|
}
|
|
@@ -20442,8 +21787,8 @@ async function match(runId, opts) {
|
|
|
20442
21787
|
{
|
|
20443
21788
|
...input,
|
|
20444
21789
|
needed,
|
|
20445
|
-
prompt_file: prompt ?
|
|
20446
|
-
prompt_file_abs: prompt ?
|
|
21790
|
+
prompt_file: prompt ? path29.relative(ctx.projectRoot, promptFilePath) : null,
|
|
21791
|
+
prompt_file_abs: prompt ? path29.resolve(promptFilePath) : null,
|
|
20447
21792
|
match_prompt: prompt
|
|
20448
21793
|
},
|
|
20449
21794
|
null,
|
|
@@ -20454,7 +21799,7 @@ async function match(runId, opts) {
|
|
|
20454
21799
|
}
|
|
20455
21800
|
if (!prompt) {
|
|
20456
21801
|
console.log(
|
|
20457
|
-
|
|
21802
|
+
chalk32.dim(
|
|
20458
21803
|
" No earlier findings or tester guidance for this scenario to match against \u2014 nothing to do."
|
|
20459
21804
|
)
|
|
20460
21805
|
);
|
|
@@ -20470,7 +21815,7 @@ async function matchRecord(runId, opts) {
|
|
|
20470
21815
|
if (!manifest) return;
|
|
20471
21816
|
let answer;
|
|
20472
21817
|
try {
|
|
20473
|
-
answer = JSON.parse(await
|
|
21818
|
+
answer = JSON.parse(await fs30.readFile(opts.file, "utf8"));
|
|
20474
21819
|
} catch {
|
|
20475
21820
|
commandError(`Cannot read matches file: ${opts.file}`, opts);
|
|
20476
21821
|
return;
|
|
@@ -20511,17 +21856,17 @@ async function matchRecord(runId, opts) {
|
|
|
20511
21856
|
}
|
|
20512
21857
|
console.log();
|
|
20513
21858
|
console.log(
|
|
20514
|
-
` ${
|
|
21859
|
+
` ${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`
|
|
20515
21860
|
);
|
|
20516
21861
|
if (result.suppressed > 0) {
|
|
20517
21862
|
console.log(
|
|
20518
|
-
` ${
|
|
21863
|
+
` ${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)`
|
|
20519
21864
|
);
|
|
20520
21865
|
}
|
|
20521
21866
|
if (verification) printVerification(verification);
|
|
20522
21867
|
console.log();
|
|
20523
21868
|
console.log(
|
|
20524
|
-
|
|
21869
|
+
chalk32.dim(" Run `iamu sync` to push the linked findings to the cloud.")
|
|
20525
21870
|
);
|
|
20526
21871
|
console.log();
|
|
20527
21872
|
}
|
|
@@ -20542,7 +21887,7 @@ __export(prune_exports, {
|
|
|
20542
21887
|
prune: () => prune
|
|
20543
21888
|
});
|
|
20544
21889
|
import { confirm, isCancel as isCancel5 } from "@clack/prompts";
|
|
20545
|
-
import
|
|
21890
|
+
import chalk33 from "chalk";
|
|
20546
21891
|
function formatBytes(bytes) {
|
|
20547
21892
|
if (bytes <= 0) return "0 B";
|
|
20548
21893
|
if (bytes < 1024) return `${bytes} B`;
|
|
@@ -20588,7 +21933,7 @@ async function prune(opts = {}) {
|
|
|
20588
21933
|
});
|
|
20589
21934
|
if (preview.prunedRuns.length === 0) {
|
|
20590
21935
|
console.log(
|
|
20591
|
-
|
|
21936
|
+
chalk33.dim(" No runs match the pruning criteria. Nothing to prune.")
|
|
20592
21937
|
);
|
|
20593
21938
|
return preview;
|
|
20594
21939
|
}
|
|
@@ -20597,7 +21942,7 @@ async function prune(opts = {}) {
|
|
|
20597
21942
|
message: `Prune ${mode} ${preview.prunedRuns.length} run(s) and reclaim ${formatBytes(preview.totalBytesReclaimed)}?`
|
|
20598
21943
|
});
|
|
20599
21944
|
if (isCancel5(confirmed) || !confirmed) {
|
|
20600
|
-
console.log(
|
|
21945
|
+
console.log(chalk33.yellow(" Pruning cancelled."));
|
|
20601
21946
|
return;
|
|
20602
21947
|
}
|
|
20603
21948
|
}
|
|
@@ -20608,14 +21953,14 @@ async function prune(opts = {}) {
|
|
|
20608
21953
|
}
|
|
20609
21954
|
if (result.dryRun) {
|
|
20610
21955
|
console.log(
|
|
20611
|
-
|
|
21956
|
+
chalk33.cyan(
|
|
20612
21957
|
`
|
|
20613
21958
|
[DRY RUN] Would prune ${result.prunedRuns.length} of ${result.totalRunsScanned} run(s):`
|
|
20614
21959
|
)
|
|
20615
21960
|
);
|
|
20616
21961
|
} else {
|
|
20617
21962
|
console.log(
|
|
20618
|
-
|
|
21963
|
+
chalk33.bold(
|
|
20619
21964
|
`
|
|
20620
21965
|
${symbols.success} Pruned ${result.prunedRuns.length} of ${result.totalRunsScanned} run(s):`
|
|
20621
21966
|
)
|
|
@@ -20623,18 +21968,18 @@ async function prune(opts = {}) {
|
|
|
20623
21968
|
}
|
|
20624
21969
|
if (result.prunedRuns.length === 0) {
|
|
20625
21970
|
console.log(
|
|
20626
|
-
|
|
21971
|
+
chalk33.dim(" No runs matched the pruning criteria. Nothing pruned.")
|
|
20627
21972
|
);
|
|
20628
21973
|
} else {
|
|
20629
21974
|
for (const run of result.prunedRuns) {
|
|
20630
21975
|
const mode = run.fullyDeleted ? "removed" : "screenshots purged";
|
|
20631
21976
|
const count = run.screenshotsDeleted > 0 ? `${run.screenshotsDeleted} screenshots, ` : "";
|
|
20632
21977
|
console.log(
|
|
20633
|
-
` ${
|
|
21978
|
+
` ${chalk33.dim("\u2022")} ${chalk33.bold(run.runId)} ${chalk33.dim(`(${run.scenarioSlug ?? "unknown scenario"})`)}: ${chalk33.yellow(formatBytes(run.bytesReclaimed))} (${count}${mode})`
|
|
20634
21979
|
);
|
|
20635
21980
|
}
|
|
20636
21981
|
console.log(
|
|
20637
|
-
|
|
21982
|
+
chalk33.dim(
|
|
20638
21983
|
`
|
|
20639
21984
|
Total reclaimed: ${formatBytes(result.totalBytesReclaimed)} (${result.totalScreenshotsDeleted} screenshots deleted)
|
|
20640
21985
|
`
|
|
@@ -20657,7 +22002,7 @@ init_context4();
|
|
|
20657
22002
|
import { existsSync as existsSync13, readFileSync as readFileSync9 } from "fs";
|
|
20658
22003
|
import { dirname as dirname5, resolve as resolve3 } from "path";
|
|
20659
22004
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
20660
|
-
import
|
|
22005
|
+
import chalk36 from "chalk";
|
|
20661
22006
|
import { Command } from "commander";
|
|
20662
22007
|
|
|
20663
22008
|
// src/commands/register-cloud.ts
|
|
@@ -20793,10 +22138,13 @@ function registerProjectCommands(program2) {
|
|
|
20793
22138
|
await status2(opts);
|
|
20794
22139
|
});
|
|
20795
22140
|
program2.command("doctor").helpGroup("Project:").description(
|
|
20796
|
-
"Diagnose environment: runtime, browser automation, skills, cloud"
|
|
22141
|
+
"Diagnose environment: runtime, browser automation, skills, cloud, and target testability"
|
|
22142
|
+
).option(
|
|
22143
|
+
"-t, --target <url>",
|
|
22144
|
+
"Probe target URL health, reachability, and auth redirects"
|
|
20797
22145
|
).option("--json", "Output diagnostics as JSON").addHelpText(
|
|
20798
22146
|
"after",
|
|
20799
|
-
"\nExamples:\n $ iamu doctor\n $ iamu doctor --json"
|
|
22147
|
+
"\nExamples:\n $ iamu doctor\n $ iamu doctor --target http://localhost:3000\n $ iamu doctor --json"
|
|
20800
22148
|
).action(async (opts) => {
|
|
20801
22149
|
const { doctor: doctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
|
|
20802
22150
|
await doctor2(opts);
|
|
@@ -20828,9 +22176,9 @@ function registerProjectCommands(program2) {
|
|
|
20828
22176
|
"after",
|
|
20829
22177
|
"\nExamples:\n $ iamu validate --all\n $ iamu validate --all --fix --dry-run\n $ iamu validate iamu/personas/maria.md"
|
|
20830
22178
|
).action(
|
|
20831
|
-
async (
|
|
22179
|
+
async (path30, opts) => {
|
|
20832
22180
|
const { validate: validate2 } = await Promise.resolve().then(() => (init_validate(), validate_exports));
|
|
20833
|
-
await validate2(
|
|
22181
|
+
await validate2(path30, opts);
|
|
20834
22182
|
}
|
|
20835
22183
|
);
|
|
20836
22184
|
program2.command("update").helpGroup("Project:").description("Update and repair agent skills and prompts to latest version").option(
|
|
@@ -20851,6 +22199,12 @@ function registerProjectCommands(program2) {
|
|
|
20851
22199
|
).option("--outline <slug>", "Outline slug (scenario only)").option("--persona <slug>", "Persona slug (scenario only)").option(
|
|
20852
22200
|
"--entry <path>",
|
|
20853
22201
|
"Where the situation starts, as a path on the product, e.g. /en/episodes/1 (scenario only)"
|
|
22202
|
+
).option(
|
|
22203
|
+
"--state-description <text>",
|
|
22204
|
+
"Assumed environment data/records state (scenario only)"
|
|
22205
|
+
).option(
|
|
22206
|
+
"--state-approximation <text>",
|
|
22207
|
+
"Guidance on how persona should approximate/squint if records are missing (scenario only)"
|
|
20854
22208
|
).option("--url <url>", "Document the source cites (source only, required)").option(
|
|
20855
22209
|
"--authority <level>",
|
|
20856
22210
|
"legislation | regulation | standard | company_policy | guideline | informational (source only, default informational)"
|
|
@@ -20863,6 +22217,54 @@ function registerProjectCommands(program2) {
|
|
|
20863
22217
|
await newEntity2(type2, opts);
|
|
20864
22218
|
}
|
|
20865
22219
|
);
|
|
22220
|
+
const explore = program2.command("explore").helpGroup("Project:").description(
|
|
22221
|
+
"Map the running product with a read-only crawl; compare the map with runs (coverage) or with another map (diff)"
|
|
22222
|
+
);
|
|
22223
|
+
explore.command("run [url]", { isDefault: true }).description(
|
|
22224
|
+
"Crawl from <url> (default: target_url in iamu/testability.md) and write iamu/.cache/explore/<id>/map.json"
|
|
22225
|
+
).option("--max-surfaces <n>", "Stop after this many surfaces (default 40)").option("--max-actions <n>", "Stop after this many clicks (default 300)").option(
|
|
22226
|
+
"--max-depth <n>",
|
|
22227
|
+
"Don't go more than this many clicks from the start (default 6)"
|
|
22228
|
+
).option(
|
|
22229
|
+
"--deny <pattern...>",
|
|
22230
|
+
"Never click a control whose name or link matches (regex, case-insensitive)"
|
|
22231
|
+
).option("--viewport <WxH>", "Page size, e.g. 390x844 (default 1280x800)").option(
|
|
22232
|
+
"--attach",
|
|
22233
|
+
"Crawl in a new tab of the `iamu browse` browser, keeping its sign-ins"
|
|
22234
|
+
).option("--json", "Output as JSON").addHelpText(
|
|
22235
|
+
"after",
|
|
22236
|
+
`
|
|
22237
|
+
Read-only: controls that read like writes (delete, pay, send, save, log out \u2026),
|
|
22238
|
+
form submits, downloads and other origins are never clicked, nothing is typed,
|
|
22239
|
+
and every request but GET/HEAD/OPTIONS is blocked before it leaves the browser.
|
|
22240
|
+
|
|
22241
|
+
Examples:
|
|
22242
|
+
$ iamu explore http://localhost:3000
|
|
22243
|
+
$ iamu explore --attach --deny "^Admin"
|
|
22244
|
+
$ iamu explore coverage
|
|
22245
|
+
$ iamu explore diff`
|
|
22246
|
+
).action(
|
|
22247
|
+
async (url, opts) => {
|
|
22248
|
+
const { exploreRun: exploreRun2 } = await Promise.resolve().then(() => (init_explore(), explore_exports));
|
|
22249
|
+
await exploreRun2(url, opts);
|
|
22250
|
+
}
|
|
22251
|
+
);
|
|
22252
|
+
explore.command("coverage [map]").description(
|
|
22253
|
+
"Surfaces of a map (default: latest) that no run reached, and places runs reached that the map lacks"
|
|
22254
|
+
).option("--json", "Output as JSON").action(async (map2, opts) => {
|
|
22255
|
+
const { exploreCoverageCommand: exploreCoverageCommand2 } = await Promise.resolve().then(() => (init_explore(), explore_exports));
|
|
22256
|
+
await exploreCoverageCommand2(map2, opts);
|
|
22257
|
+
});
|
|
22258
|
+
explore.command("diff [a] [b]").description("Compare two maps (default: the two latest)").option("--json", "Output as JSON").action(
|
|
22259
|
+
async (a, b, opts) => {
|
|
22260
|
+
const { exploreDiffCommand: exploreDiffCommand2 } = await Promise.resolve().then(() => (init_explore(), explore_exports));
|
|
22261
|
+
await exploreDiffCommand2(a, b, opts);
|
|
22262
|
+
}
|
|
22263
|
+
);
|
|
22264
|
+
explore.command("list").description("List maps").option("--json", "Output as JSON").action(async (opts) => {
|
|
22265
|
+
const { exploreList: exploreList2 } = await Promise.resolve().then(() => (init_explore(), explore_exports));
|
|
22266
|
+
await exploreList2(opts);
|
|
22267
|
+
});
|
|
20866
22268
|
program2.command("completion [shell]").helpGroup("Project:").description("Output shell completion script (bash, zsh, fish)").action(async (shell) => {
|
|
20867
22269
|
const { completion: completion2 } = await Promise.resolve().then(() => (init_completion(), completion_exports));
|
|
20868
22270
|
completion2(program2, shell);
|
|
@@ -21070,23 +22472,23 @@ function registerAllCommands(program2) {
|
|
|
21070
22472
|
|
|
21071
22473
|
// src/ui/panic.ts
|
|
21072
22474
|
init_logger();
|
|
21073
|
-
import
|
|
22475
|
+
import chalk34 from "chalk";
|
|
21074
22476
|
var handlersRegistered = false;
|
|
21075
22477
|
var customSigintHandler = null;
|
|
21076
22478
|
function logFatalAndExit(title, errOrReason) {
|
|
21077
22479
|
process.stderr.write("\x1B[?25h");
|
|
21078
22480
|
if (isVerbose()) {
|
|
21079
|
-
console.error(
|
|
22481
|
+
console.error(chalk34.red(`
|
|
21080
22482
|
${title}:`));
|
|
21081
22483
|
console.error(
|
|
21082
22484
|
errOrReason instanceof Error ? errOrReason.stack || errOrReason : errOrReason
|
|
21083
22485
|
);
|
|
21084
22486
|
} else {
|
|
21085
22487
|
const msg = errOrReason instanceof Error ? errOrReason.message : String(errOrReason);
|
|
21086
|
-
console.error(
|
|
22488
|
+
console.error(chalk34.red(`
|
|
21087
22489
|
Fatal Error: ${msg}`));
|
|
21088
22490
|
console.error(
|
|
21089
|
-
|
|
22491
|
+
chalk34.dim(
|
|
21090
22492
|
" Run with --verbose or DEBUG=1 to view the full stack trace.\n"
|
|
21091
22493
|
)
|
|
21092
22494
|
);
|
|
@@ -21105,7 +22507,7 @@ function setupProcessHandlers() {
|
|
|
21105
22507
|
}
|
|
21106
22508
|
process.exit(130);
|
|
21107
22509
|
} else {
|
|
21108
|
-
console.error(
|
|
22510
|
+
console.error(chalk34.dim("\n Operation cancelled."));
|
|
21109
22511
|
process.exit(130);
|
|
21110
22512
|
}
|
|
21111
22513
|
});
|
|
@@ -21131,7 +22533,7 @@ function setupProcessHandlers() {
|
|
|
21131
22533
|
import { mkdir as mkdir7, readFile as readFile7, writeFile as writeFile5 } from "fs/promises";
|
|
21132
22534
|
import { homedir as homedir4 } from "os";
|
|
21133
22535
|
import { join as join10 } from "path";
|
|
21134
|
-
import
|
|
22536
|
+
import chalk35 from "chalk";
|
|
21135
22537
|
var CACHE_FILE = join10(homedir4(), ".iamu", "update-check.json");
|
|
21136
22538
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
21137
22539
|
function isSemverGreater(latest, current) {
|
|
@@ -21184,7 +22586,7 @@ async function checkForUpdate(currentVersion) {
|
|
|
21184
22586
|
function printUpdateNotice(currentVersion, latestVersion) {
|
|
21185
22587
|
console.error();
|
|
21186
22588
|
console.error(
|
|
21187
|
-
` ${
|
|
22589
|
+
` ${chalk35.yellow("Update available:")} ${chalk35.dim(currentVersion)} \u2192 ${chalk35.green(latestVersion)} ${chalk35.dim("(run: npm i -g @iamu-dev/iamu)")}`
|
|
21188
22590
|
);
|
|
21189
22591
|
}
|
|
21190
22592
|
|
|
@@ -21229,7 +22631,7 @@ var program = new Command().name("iamu").description(
|
|
|
21229
22631
|
}
|
|
21230
22632
|
}
|
|
21231
22633
|
if (root.color === false || process.env.NO_COLOR) {
|
|
21232
|
-
|
|
22634
|
+
chalk36.level = 0;
|
|
21233
22635
|
}
|
|
21234
22636
|
if (root.verbose) {
|
|
21235
22637
|
process.env.IAMU_VERBOSE = "1";
|