@iamu-dev/iamu 0.2.0-beta.1 → 0.2.0-beta.10

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 CHANGED
@@ -200,17 +200,24 @@ function defaultConfig(params) {
200
200
  cloud_api_url: null
201
201
  };
202
202
  }
203
- var baseProjectFields, localOnlyConfig, cloudLinkedConfig, projectConfigSchema;
203
+ var modelTierSchema, projectModelsConfigSchema, baseProjectFields, localOnlyConfig, cloudLinkedConfig, projectConfigSchema;
204
204
  var init_config = __esm({
205
205
  "../../packages/protocol/dist/schemas/frontmatter/config.js"() {
206
206
  "use strict";
207
207
  init_common();
208
208
  init_version();
209
+ modelTierSchema = z4.string().min(1).max(128).describe("Model role tier ('fast', 'thorough', 'inherit') or a specific model identifier.");
210
+ projectModelsConfigSchema = z4.object({
211
+ walker: modelTierSchema.optional(),
212
+ matcher: modelTierSchema.optional(),
213
+ judge: modelTierSchema.optional()
214
+ }).strict();
209
215
  baseProjectFields = {
210
216
  schema_version: z4.literal(SCHEMA_VERSION),
211
217
  id: uuidSchema,
212
218
  project_slug: slugSchema,
213
- project_name: z4.string().min(1)
219
+ project_name: z4.string().min(1),
220
+ models: projectModelsConfigSchema.optional()
214
221
  };
215
222
  localOnlyConfig = z4.object({
216
223
  ...baseProjectFields,
@@ -817,7 +824,14 @@ var init_scenario = __esm({
817
824
  reviews: z15.array(z15.string().min(1).max(64)).max(16).default([]),
818
825
  baseline_score: z15.number().min(0).max(100).nullish(),
819
826
  current_score: z15.number().min(0).max(100).nullish(),
820
- 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()
821
835
  });
822
836
  }
823
837
  });
@@ -1154,7 +1168,21 @@ var init_walkthrough_bundle = __esm({
1154
1168
  judge_prompt: z19.string().optional(),
1155
1169
  judge_model: z19.string().optional()
1156
1170
  }).optional()
1157
- }).optional()
1171
+ }).optional(),
1172
+ models: z19.object({
1173
+ walker: z19.string().optional(),
1174
+ matcher: z19.string().optional(),
1175
+ judge: z19.string().optional(),
1176
+ walker_reported: z19.string().optional(),
1177
+ matcher_reported: z19.string().optional(),
1178
+ judge_reported: z19.string().optional(),
1179
+ walker_tier: z19.string().optional(),
1180
+ matcher_tier: z19.string().optional(),
1181
+ judge_tier: z19.string().optional(),
1182
+ execution_mode: z19.enum(["multi_agent", "single_agent"]).optional(),
1183
+ velocity_sec_per_step: z19.number().optional(),
1184
+ total_duration_sec: z19.number().optional()
1185
+ }).optional().describe("AI model configuration and reported runtime provenance for this walkthrough.")
1158
1186
  });
1159
1187
  walkthroughBundleSchema = z19.object({
1160
1188
  manifest: bundleManifestSchema,
@@ -1377,6 +1405,7 @@ var init_walkthrough_canonical = __esm({
1377
1405
  build: buildMetadataSchema.optional(),
1378
1406
  viewport: runViewportSchema.optional(),
1379
1407
  environment: z20.string().optional(),
1408
+ models: z20.record(z20.unknown()).optional(),
1380
1409
  findings: z20.array(canonicalFindingSchema),
1381
1410
  steps: z20.array(canonicalStepSchema),
1382
1411
  human_tests: z20.array(canonicalHumanTestSchema),
@@ -7248,6 +7277,7 @@ async function buildCanonical(projectRoot, manifest, completedAt, inputs) {
7248
7277
  ...manifest.build ? { build: manifest.build } : {},
7249
7278
  ...manifest.viewport ? { viewport: manifest.viewport } : {},
7250
7279
  ...manifest.environment ? { environment: manifest.environment } : {},
7280
+ ...manifest.models ? { models: manifest.models } : {},
7251
7281
  findings: canonicalFindings,
7252
7282
  steps: canonicalSteps,
7253
7283
  human_tests: humanTests,
@@ -7408,6 +7438,20 @@ function renderCanonicalMarkdown(canonical) {
7408
7438
  lines.push(`- Viewport: ${canonical.viewport.device} (${canonical.viewport.width}\xD7${canonical.viewport.height})`);
7409
7439
  }
7410
7440
  lines.push(`- Started: ${canonical.provenance.started_at}`, `- Completed: ${canonical.provenance.completed_at}`, `- Walker prompt: ${canonical.provenance.walker_prompt.id}@${canonical.provenance.walker_prompt.version}`, `- Findings: ${canonical.findings.length}`, `- Steps: ${canonical.steps.length}`);
7441
+ const models = canonical.models;
7442
+ if (models) {
7443
+ const parts = [
7444
+ models.walker ? `Walker: ${models.walker}${models.walker_reported ? ` (${models.walker_reported})` : ""}` : "",
7445
+ models.judge ? `Judge: ${models.judge}${models.judge_reported ? ` (${models.judge_reported})` : ""}` : "",
7446
+ models.matcher ? `Matcher: ${models.matcher}` : ""
7447
+ ].filter(Boolean);
7448
+ if (parts.length > 0) {
7449
+ lines.push(`- Models: ${parts.join(" \xB7 ")}`);
7450
+ }
7451
+ if (typeof models.velocity_sec_per_step === "number") {
7452
+ lines.push(`- Velocity: ~${models.velocity_sec_per_step}s/step (${models.total_duration_sec}s total)`);
7453
+ }
7454
+ }
7411
7455
  const what = canonical.what_worked;
7412
7456
  if (what && what.length > 0) {
7413
7457
  lines.push("", "## What worked");
@@ -7777,6 +7821,7 @@ async function assembleWalkthroughBundle(projectRoot, runId) {
7777
7821
  report_markdown: report3,
7778
7822
  verdict: verdictArray,
7779
7823
  judge_meta: judgeMeta,
7824
+ ...manifest.models ? { models: manifest.models } : {},
7780
7825
  ...buildFields(manifest),
7781
7826
  ...await readCanonicalExtras(projectRoot, runId)
7782
7827
  },
@@ -10443,9 +10488,275 @@ var init_prune = __esm({
10443
10488
  }
10444
10489
  });
10445
10490
 
10446
- // ../../packages/local-store/dist/human-test-link.js
10491
+ // ../../packages/local-store/dist/explore-map.js
10492
+ import { randomBytes as randomBytes2 } from "crypto";
10447
10493
  import { promises as fs19 } from "fs";
10448
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";
10449
10760
  function humanTestLinks(verdicts) {
10450
10761
  const best = /* @__PURE__ */ new Map();
10451
10762
  for (const v of verdicts) {
@@ -10468,7 +10779,7 @@ async function linkFindingsToHumanTests(projectRoot, runId, verdicts) {
10468
10779
  const dir = entityDirPath(projectRoot, "finding");
10469
10780
  let names;
10470
10781
  try {
10471
- names = await fs19.readdir(dir);
10782
+ names = await fs20.readdir(dir);
10472
10783
  } catch {
10473
10784
  return [];
10474
10785
  }
@@ -10476,11 +10787,11 @@ async function linkFindingsToHumanTests(projectRoot, runId, verdicts) {
10476
10787
  for (const name of names) {
10477
10788
  if (!name.endsWith(".md") || name.startsWith("."))
10478
10789
  continue;
10479
- const file = path15.join(dir, name);
10790
+ const file = path16.join(dir, name);
10480
10791
  let raw;
10481
10792
  let fm;
10482
10793
  try {
10483
- raw = await fs19.readFile(file, "utf8");
10794
+ raw = await fs20.readFile(file, "utf8");
10484
10795
  fm = parseFrontmatter(raw).frontmatter;
10485
10796
  } catch {
10486
10797
  continue;
@@ -10711,14 +11022,14 @@ var init_cloud_client = __esm({
10711
11022
  });
10712
11023
 
10713
11024
  // ../../packages/local-store/dist/last-pull.js
10714
- import { promises as fs20 } from "fs";
10715
- import path16 from "path";
11025
+ import { promises as fs21 } from "fs";
11026
+ import path17 from "path";
10716
11027
  function lastPullPath(projectRoot) {
10717
- return path16.join(cacheDir(projectRoot), "last-pull.json");
11028
+ return path17.join(cacheDir(projectRoot), "last-pull.json");
10718
11029
  }
10719
11030
  async function readLastPull(projectRoot) {
10720
11031
  try {
10721
- const raw = await fs20.readFile(lastPullPath(projectRoot), "utf8");
11032
+ const raw = await fs21.readFile(lastPullPath(projectRoot), "utf8");
10722
11033
  const parsed = JSON.parse(raw);
10723
11034
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
10724
11035
  return parsed;
@@ -11004,7 +11315,7 @@ var init_frontmatter2 = __esm({
11004
11315
  });
11005
11316
 
11006
11317
  // ../../packages/local-store/dist/pull/write.js
11007
- import { promises as fs21 } from "fs";
11318
+ import { promises as fs22 } from "fs";
11008
11319
  async function loadLocalIndex(projectRoot, type2) {
11009
11320
  const byId = /* @__PURE__ */ new Map();
11010
11321
  const bySlug = /* @__PURE__ */ new Map();
@@ -11033,7 +11344,7 @@ async function classifyExisting(projectRoot, type2, cloudId, localSlug, targetSl
11033
11344
  if (localSlug !== targetSlug)
11034
11345
  return "untracked";
11035
11346
  const lastSynced = localIndex.lastPushById[cloudId];
11036
- const bytes = await fs21.readFile(entityFilePath(projectRoot, type2, localSlug), "utf8");
11347
+ const bytes = await fs22.readFile(entityFilePath(projectRoot, type2, localSlug), "utf8");
11037
11348
  return lastSynced && hashNormalizedContent(bytes) === lastSynced ? "fast_forward" : "diverged";
11038
11349
  }
11039
11350
  async function writeOrSkip(projectRoot, type2, cloudId, slug, frontmatter, body, localIndex, mode, cloudVersion) {
@@ -11094,13 +11405,13 @@ async function writeOrSkip(projectRoot, type2, cloudId, slug, frontmatter, body,
11094
11405
  }
11095
11406
  if (existingSlugForId && existingSlugForId !== slug) {
11096
11407
  try {
11097
- await fs21.unlink(entityFilePath(projectRoot, type2, existingSlugForId));
11408
+ await fs22.unlink(entityFilePath(projectRoot, type2, existingSlugForId));
11098
11409
  localIndex.bySlug.delete(existingSlugForId);
11099
11410
  } catch {
11100
11411
  }
11101
11412
  }
11102
11413
  await writeEntity(projectRoot, type2, frontmatter, text);
11103
- const bytes = await fs21.readFile(entityFilePath(projectRoot, type2, slug), "utf8");
11414
+ const bytes = await fs22.readFile(entityFilePath(projectRoot, type2, slug), "utf8");
11104
11415
  const hash = hashNormalizedContent(bytes);
11105
11416
  localIndex.byId.set(cloudId, slug);
11106
11417
  localIndex.bySlug.set(slug, cloudId);
@@ -11154,7 +11465,7 @@ var init_write = __esm({
11154
11465
  });
11155
11466
 
11156
11467
  // ../../packages/local-store/dist/pull/entities.js
11157
- import { promises as fs22 } from "fs";
11468
+ import { promises as fs23 } from "fs";
11158
11469
  function buildContextFrontmatter(payload) {
11159
11470
  const cloudCtx = payload.context ?? {};
11160
11471
  const glossary = (payload.glossary ?? []).map((g) => ({
@@ -11209,7 +11520,7 @@ async function pullContext(projectRoot, cloud, projectId, mode, result) {
11209
11520
  const frontmatter = buildContextFrontmatter(payload);
11210
11521
  let hasLocal = false;
11211
11522
  try {
11212
- await fs22.access(contextPath(projectRoot));
11523
+ await fs23.access(contextPath(projectRoot));
11213
11524
  hasLocal = true;
11214
11525
  } catch {
11215
11526
  hasLocal = false;
@@ -11239,7 +11550,7 @@ async function pullContext(projectRoot, cloud, projectId, mode, result) {
11239
11550
  frontmatter,
11240
11551
  notes_body: existingNotes
11241
11552
  });
11242
- const bytes = await fs22.readFile(contextPath(projectRoot), "utf8");
11553
+ const bytes = await fs23.readFile(contextPath(projectRoot), "utf8");
11243
11554
  const hash = hashNormalizedContent(bytes);
11244
11555
  await recordLastPull(projectRoot, "context:root", hash);
11245
11556
  await recordLastPush(projectRoot, "context:root", hash);
@@ -11769,6 +12080,7 @@ var init_dist2 = __esm({
11769
12080
  init_prune();
11770
12081
  init_fix_state();
11771
12082
  init_revisit();
12083
+ init_explore_map();
11772
12084
  init_walkthrough_canonical2();
11773
12085
  init_journey_diff();
11774
12086
  init_finding_match();
@@ -11800,15 +12112,15 @@ function resolveApiUrl(configured) {
11800
12112
  assertSecureApiUrl(url, configured ? "API URL" : "IAMU_API_URL");
11801
12113
  return url;
11802
12114
  }
11803
- function parseStore(raw, path28) {
12115
+ function parseStore(raw, path30) {
11804
12116
  let parsed;
11805
12117
  try {
11806
12118
  parsed = JSON.parse(raw);
11807
12119
  } catch (err) {
11808
- throw new CredentialsFileError(path28, "not valid JSON", err);
12120
+ throw new CredentialsFileError(path30, "not valid JSON", err);
11809
12121
  }
11810
12122
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
11811
- throw new CredentialsFileError(path28, "expected a JSON object");
12123
+ throw new CredentialsFileError(path30, "expected a JSON object");
11812
12124
  }
11813
12125
  return parsed;
11814
12126
  }
@@ -11816,26 +12128,26 @@ function isNotFound(err) {
11816
12128
  return typeof err === "object" && err !== null && err.code === "ENOENT";
11817
12129
  }
11818
12130
  async function readCredentialsStore() {
11819
- const path28 = getCredentialsPath();
12131
+ const path30 = getCredentialsPath();
11820
12132
  let raw;
11821
12133
  try {
11822
- raw = await readFile(path28, "utf-8");
12134
+ raw = await readFile(path30, "utf-8");
11823
12135
  } catch (err) {
11824
12136
  if (isNotFound(err)) return {};
11825
12137
  warnUnreadableStoreOnce(err);
11826
12138
  throw err;
11827
12139
  }
11828
12140
  try {
11829
- return parseStore(raw, path28);
12141
+ return parseStore(raw, path30);
11830
12142
  } catch (err) {
11831
12143
  warnUnreadableStoreOnce(err);
11832
12144
  throw err;
11833
12145
  }
11834
12146
  }
11835
12147
  async function writeCredentialsStore(store) {
11836
- const path28 = getCredentialsPath();
11837
- await mkdir(dirname(path28), { recursive: true, mode: 448 });
11838
- await atomicWriteFile(path28, `${JSON.stringify(store, null, 2)}
12148
+ const path30 = getCredentialsPath();
12149
+ await mkdir(dirname(path30), { recursive: true, mode: 448 });
12150
+ await atomicWriteFile(path30, `${JSON.stringify(store, null, 2)}
11839
12151
  `, {
11840
12152
  mode: 384
11841
12153
  });
@@ -11855,16 +12167,16 @@ function resolveApiKey(apiUrl) {
11855
12167
  } catch {
11856
12168
  return null;
11857
12169
  }
11858
- const path28 = getCredentialsPath();
12170
+ const path30 = getCredentialsPath();
11859
12171
  let raw;
11860
12172
  try {
11861
- raw = readFileSync(path28, "utf-8");
12173
+ raw = readFileSync(path30, "utf-8");
11862
12174
  } catch (err) {
11863
12175
  if (!isNotFound(err)) warnUnreadableStoreOnce(err);
11864
12176
  return null;
11865
12177
  }
11866
12178
  try {
11867
- const entry = parseStore(raw, path28)[url];
12179
+ const entry = parseStore(raw, path30)[url];
11868
12180
  if (typeof entry !== "object" || entry === null) return null;
11869
12181
  const token = entry.token;
11870
12182
  return typeof token === "string" && token ? token : null;
@@ -11896,19 +12208,19 @@ var init_credentials = __esm({
11896
12208
  "use strict";
11897
12209
  init_dist2();
11898
12210
  init_dist();
11899
- DEFAULT_API_URL = "https://api.iamu.dev";
12211
+ DEFAULT_API_URL = "https://app.iamu.dev";
11900
12212
  CredentialsFileError = class extends IamuConfigError {
11901
12213
  /**
11902
12214
  * @param path - The unreadable credentials file.
11903
12215
  * @param reason - What is wrong with it.
11904
12216
  * @param cause - Underlying parse error, if any.
11905
12217
  */
11906
- constructor(path28, reason, cause) {
12218
+ constructor(path30, reason, cause) {
11907
12219
  super(
11908
- `Credentials file ${path28} is corrupt (${reason}). Fix it by hand, or delete it and run \`iamu auth login\` again.`,
12220
+ `Credentials file ${path30} is corrupt (${reason}). Fix it by hand, or delete it and run \`iamu auth login\` again.`,
11909
12221
  {
11910
12222
  cause,
11911
- suggestedAction: `Delete ${path28} and run \`iamu auth login\`, or set IAMU_TOKEN.`
12223
+ suggestedAction: `Delete ${path30} and run \`iamu auth login\`, or set IAMU_TOKEN.`
11912
12224
  }
11913
12225
  );
11914
12226
  }
@@ -11993,7 +12305,7 @@ async function resolveCloudProject(opts) {
11993
12305
  commandError("Not cloud-linked. Run `iamu auth login` to connect.", opts);
11994
12306
  return null;
11995
12307
  }
11996
- const apiUrl = ctx.config.cloud_api_url ?? "https://api.iamu.dev";
12308
+ const apiUrl = ctx.config.cloud_api_url ?? DEFAULT_API_URL;
11997
12309
  try {
11998
12310
  assertSecureApiUrl(apiUrl, "cloud_api_url");
11999
12311
  } catch (err) {
@@ -12287,15 +12599,6 @@ function mcpJson(cmd) {
12287
12599
  2
12288
12600
  );
12289
12601
  }
12290
- function vscodeMcpJson(cmd) {
12291
- return JSON.stringify(
12292
- {
12293
- servers: { iamu: { type: "stdio", command: cmd[0], args: cmd.slice(1) } }
12294
- },
12295
- null,
12296
- 2
12297
- );
12298
- }
12299
12602
  function detectHosts(projectRoot) {
12300
12603
  return HOST_ADAPTERS.filter(
12301
12604
  (host) => host.detectionPaths.some((p) => existsSync3(join3(projectRoot, p)))
@@ -12344,13 +12647,17 @@ var init_registry = __esm({
12344
12647
  {
12345
12648
  name: "GitHub Copilot",
12346
12649
  value: "github-copilot",
12347
- detectionPaths: [".vscode", ".github/copilot-instructions.md"],
12348
- supportsSkills: false,
12349
- getSkillsDir: () => "",
12350
- getCommandPath: (root, id) => join3(root, ".github", "prompts", `iamu-${id}.prompt.md`),
12351
- formatCommand: descOnlyFormat,
12352
- getMcpConfigPath: (root) => join3(root, ".vscode", "mcp.json"),
12353
- formatMcpConfig: vscodeMcpJson
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
12354
12661
  },
12355
12662
  {
12356
12663
  name: "Cline",
@@ -12388,10 +12695,10 @@ var init_registry = __esm({
12388
12695
  {
12389
12696
  name: "Antigravity",
12390
12697
  value: "antigravity",
12391
- detectionPaths: [".gemini", ".agent", "GEMINI.md", "AGENTS.md"],
12698
+ detectionPaths: [".agent", ".agents", "AGENTS.md", ".gemini", "GEMINI.md"],
12392
12699
  supportsSkills: true,
12393
- getSkillsDir: (root) => existsSync3(join3(root, ".agent")) && !existsSync3(join3(root, ".gemini")) ? join3(root, ".agent", "skills") : join3(root, ".gemini", "skills"),
12394
- getCommandPath: (root, id) => existsSync3(join3(root, ".agent")) && !existsSync3(join3(root, ".gemini")) ? join3(root, ".agent", "skills", `iamu-${id}`, "SKILL.md") : join3(root, ".gemini", "skills", `iamu-${id}`, "SKILL.md"),
12700
+ getSkillsDir: (root) => existsSync3(join3(root, ".agents")) ? join3(root, ".agents", "skills") : join3(root, ".agent", "skills"),
12701
+ getCommandPath: (root, id) => existsSync3(join3(root, ".agents")) ? join3(root, ".agents", "skills", `iamu-${id}`, "SKILL.md") : join3(root, ".agent", "skills", `iamu-${id}`, "SKILL.md"),
12395
12702
  formatCommand: richFormat,
12396
12703
  getMcpConfigPath: (root) => join3(root, ".mcp.json"),
12397
12704
  formatMcpConfig: mcpJson
@@ -12525,9 +12832,17 @@ async function needsWrite(filePath, content) {
12525
12832
  return true;
12526
12833
  }
12527
12834
  }
12528
- function isCliWrittenSkill(path28) {
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) {
12529
12843
  try {
12530
- return /^generatedBy:/m.test(readFileSync3(path28, "utf-8"));
12844
+ const content = readFileSync3(path30, "utf-8");
12845
+ return content.includes("iamu") || /^generatedBy:/m.test(content);
12531
12846
  } catch {
12532
12847
  return false;
12533
12848
  }
@@ -12538,14 +12853,41 @@ function planSkillFiles(hosts, projectRoot, version2) {
12538
12853
  for (const host of hosts) {
12539
12854
  if (pluginProvidesSkills(host, projectRoot)) {
12540
12855
  for (const name of skillTemplates.keys()) {
12541
- const path28 = join4(host.getSkillsDir(projectRoot), name, "SKILL.md");
12542
- if (isCliWrittenSkill(path28)) files.push({ host, path: path28, content: null });
12856
+ const path30 = join4(host.getSkillsDir(projectRoot), name, "SKILL.md");
12857
+ if (isCliWrittenSkill(path30)) files.push({ host, path: path30, content: null });
12543
12858
  }
12544
12859
  } else if (host.supportsSkills) {
12545
12860
  const skillsDir = host.getSkillsDir(projectRoot);
12546
12861
  for (const [name, content] of skillTemplates) {
12547
12862
  files.push({ host, path: join4(skillsDir, name, "SKILL.md"), content });
12548
12863
  }
12864
+ if (host.value === "antigravity") {
12865
+ for (const name of skillTemplates.keys()) {
12866
+ const legacyPath = join4(
12867
+ projectRoot,
12868
+ ".gemini",
12869
+ "skills",
12870
+ name,
12871
+ "SKILL.md"
12872
+ );
12873
+ if (isCliWrittenSkill(legacyPath)) {
12874
+ files.push({ host, path: legacyPath, content: null });
12875
+ }
12876
+ }
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
+ }
12549
12891
  } else {
12550
12892
  for (const cmd of commandsFromSkills(skillTemplates)) {
12551
12893
  files.push({
@@ -12570,6 +12912,15 @@ function detectInstalledHosts(projectRoot) {
12570
12912
  const skillDir = join4(host.getSkillsDir(projectRoot), "iamu-setup");
12571
12913
  if (existsSync5(skillDir)) return true;
12572
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
+ }
12573
12924
  return false;
12574
12925
  });
12575
12926
  }
@@ -12636,7 +12987,13 @@ async function installSkillsForHosts(hosts, projectRoot, version2, opts) {
12636
12987
  let hostUpdated = 0;
12637
12988
  let hostTotal = 0;
12638
12989
  for (const f of planned) {
12639
- if (f.host !== host || f.content === null) continue;
12990
+ if (f.host !== host) continue;
12991
+ if (f.content === null) {
12992
+ await rm(f.path, { force: true });
12993
+ await rmdir(dirname3(f.path)).catch(() => {
12994
+ });
12995
+ continue;
12996
+ }
12640
12997
  hostTotal++;
12641
12998
  if (host.supportsSkills) totalSkills++;
12642
12999
  else totalCommands++;
@@ -13482,12 +13839,24 @@ async function browserLogin(apiUrl, opts, t) {
13482
13839
  chalk8.red(`
13483
13840
  Auth session expired or failed (HTTP ${result.status}).`)
13484
13841
  );
13842
+ console.error(
13843
+ chalk8.dim(" Re-run `iamu auth login` to start a new login session.\n")
13844
+ );
13485
13845
  process.exitCode = 1;
13486
13846
  return null;
13487
13847
  }
13488
13848
  if (result.kind === "timeout") {
13489
13849
  s.stop("Authentication timed out.");
13490
- console.error(chalk8.red("\n Authentication timed out. Try again."));
13850
+ console.error(
13851
+ chalk8.red(
13852
+ "\n Authentication timed out (no approval received within 2 minutes)."
13853
+ )
13854
+ );
13855
+ console.error(
13856
+ chalk8.dim(
13857
+ " \u2022 Complete browser authorization faster, or authenticate non-interactively with --token <key>.\n \u2022 Local setup is unaffected \u2014 re-run `iamu auth login` anytime.\n"
13858
+ )
13859
+ );
13491
13860
  process.exitCode = 1;
13492
13861
  return null;
13493
13862
  }
@@ -13513,7 +13882,12 @@ async function startAuthSession(apiUrl, timeoutMs) {
13513
13882
  if (!res.ok) {
13514
13883
  console.error(
13515
13884
  chalk8.red(
13516
- ` Failed to start auth session: ${res.text || `HTTP ${res.status}`}`
13885
+ ` Failed to start auth session at ${apiUrl}: ${res.text || `HTTP ${res.status}`}`
13886
+ )
13887
+ );
13888
+ console.error(
13889
+ chalk8.dim(
13890
+ " \u2022 The cloud service may be experiencing downtime or maintenance.\n \u2022 Local setup is unaffected; continue with local workflows or retry later with `iamu auth login`.\n"
13517
13891
  )
13518
13892
  );
13519
13893
  process.exitCode = 1;
@@ -13527,7 +13901,13 @@ async function startAuthSession(apiUrl, timeoutMs) {
13527
13901
  return { sessionId: data.sessionId, loginUrl: data.loginUrl };
13528
13902
  } catch (err) {
13529
13903
  console.error(
13530
- chalk8.red(" Cannot reach Iamu Cloud.") + chalk8.dim(` (${errorMessage(err)})`)
13904
+ chalk8.red(` Cannot reach Iamu Cloud (${apiUrl}).`) + chalk8.dim(`
13905
+ Reason: ${errorMessage(err)}`)
13906
+ );
13907
+ console.error(
13908
+ chalk8.dim(
13909
+ "\n \u2022 Check your internet connection or proxy settings.\n \u2022 If using a custom instance, pass --url <api-url> or set IAMU_API_URL.\n \u2022 Local setup is unaffected; continue with local workflows or retry later with `iamu auth login`.\n"
13910
+ )
13531
13911
  );
13532
13912
  process.exitCode = 1;
13533
13913
  return null;
@@ -13994,7 +14374,7 @@ import {
13994
14374
  readlinkSync,
13995
14375
  realpathSync as realpathSync2
13996
14376
  } from "fs";
13997
- import path17 from "path";
14377
+ import path18 from "path";
13998
14378
  function git(args, cwd) {
13999
14379
  return execFileSync("git", args, {
14000
14380
  cwd,
@@ -14047,8 +14427,8 @@ function detectWorktreeState(projectRoot, iamuDirName = IAMU_DIR) {
14047
14427
  const top = git(["rev-parse", "--show-toplevel"], projectRoot).toString("utf8").trim();
14048
14428
  if (!top) return void 0;
14049
14429
  const realTop = realpathSync2(top);
14050
- const realIamu = path17.join(realpathSync2(projectRoot), iamuDirName);
14051
- const iamuRel = path17.relative(realTop, realIamu).split(path17.sep).join("/");
14430
+ const realIamu = path18.join(realpathSync2(projectRoot), iamuDirName);
14431
+ const iamuRel = path18.relative(realTop, realIamu).split(path18.sep).join("/");
14052
14432
  const pathspec = iamuRel.startsWith("..") ? [":(top)"] : [":(top)", `:(top,exclude,literal)${iamuRel}`];
14053
14433
  const status2 = git(
14054
14434
  [
@@ -14078,7 +14458,7 @@ function detectWorktreeState(projectRoot, iamuDirName = IAMU_DIR) {
14078
14458
  );
14079
14459
  for (const file of sorted.slice(0, MAX_UNTRACKED_HASHED)) {
14080
14460
  const rel = file.replace(/\/$/, "");
14081
- hash.update(`${file}\0${hashFile(path17.join(realTop, rel))}
14461
+ hash.update(`${file}\0${hashFile(path18.join(realTop, rel))}
14082
14462
  `);
14083
14463
  }
14084
14464
  if (sorted.length > MAX_UNTRACKED_HASHED) {
@@ -14249,15 +14629,29 @@ __export(start_run_exports, {
14249
14629
  formatBuild: () => formatBuild,
14250
14630
  formatViewport: () => formatViewport,
14251
14631
  resolveStartUrl: () => resolveStartUrl,
14252
- startRun: () => startRun
14632
+ startRun: () => startRun,
14633
+ testabilityTarget: () => testabilityTarget
14253
14634
  });
14254
- import path18 from "path";
14635
+ import fs24 from "fs/promises";
14636
+ import path19 from "path";
14255
14637
  import chalk11 from "chalk";
14256
- function resolveStartUrl(target, scenario) {
14257
- const base = target ?? scenario.target_url ?? null;
14638
+ async function testabilityTarget(projectRoot) {
14639
+ try {
14640
+ const raw = await fs24.readFile(
14641
+ path19.join(projectRoot, "iamu", "testability.md"),
14642
+ "utf8"
14643
+ );
14644
+ const { frontmatter } = parseFrontmatter(raw);
14645
+ return typeof frontmatter.target_url === "string" && frontmatter.target_url.trim().length > 0 ? frontmatter.target_url.trim() : null;
14646
+ } catch {
14647
+ return null;
14648
+ }
14649
+ }
14650
+ function resolveStartUrl(target, scenario, fallbackTarget) {
14651
+ const base = target ?? scenario.target_url ?? fallbackTarget ?? null;
14258
14652
  if (!base) {
14259
14653
  return {
14260
- error: "No target \u2014 pass --target <url>, or set target_url on the scenario."
14654
+ error: "No target \u2014 pass --target <url>, set target_url on the scenario, or define target_url in iamu/testability.md."
14261
14655
  };
14262
14656
  }
14263
14657
  let parsed;
@@ -14357,7 +14751,8 @@ async function startRun(scenarioSlug, opts) {
14357
14751
  );
14358
14752
  return;
14359
14753
  }
14360
- const start2 = resolveStartUrl(opts.target, fm);
14754
+ const fallbackTarget = await testabilityTarget(projectRoot);
14755
+ const start2 = resolveStartUrl(opts.target, fm, fallbackTarget);
14361
14756
  if ("error" in start2) {
14362
14757
  commandError(start2.error, opts);
14363
14758
  return;
@@ -14391,6 +14786,18 @@ async function startRun(scenarioSlug, opts) {
14391
14786
  }
14392
14787
  const build = detectBuild({ cwd: projectRoot, label: opts.build });
14393
14788
  const environment = resolveEnvironment(opts.env, start2.url);
14789
+ const cfg = await readConfig(projectRoot).catch(() => null);
14790
+ const walker = opts.walker ?? process.env.IAMU_WALKER_MODEL ?? cfg?.models?.walker ?? "fast";
14791
+ const judge = opts.judge ?? process.env.IAMU_JUDGE_MODEL ?? cfg?.models?.judge ?? "thorough";
14792
+ const matcher = opts.matcher ?? process.env.IAMU_MATCHER_MODEL ?? cfg?.models?.matcher ?? "thorough";
14793
+ const models = {
14794
+ walker,
14795
+ judge,
14796
+ matcher,
14797
+ walker_tier: ["fast", "thorough", "inherit"].includes(walker) ? walker : "custom",
14798
+ judge_tier: ["fast", "thorough", "inherit"].includes(judge) ? judge : "custom",
14799
+ matcher_tier: ["fast", "thorough", "inherit"].includes(matcher) ? matcher : "custom"
14800
+ };
14394
14801
  let revisit = [];
14395
14802
  try {
14396
14803
  revisit = await computeRevisitPlaces(projectRoot, scenarioSlug, start2.url);
@@ -14408,13 +14815,14 @@ async function startRun(scenarioSlug, opts) {
14408
14815
  status: "running",
14409
14816
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
14410
14817
  completed_at: null,
14818
+ models,
14411
14819
  ...build ? { build } : {},
14412
14820
  ...environment ? { environment } : {},
14413
14821
  ...revisit.length > 0 ? { revisit } : {},
14414
14822
  ...viewport ? { viewport } : {}
14415
14823
  };
14416
14824
  await atomicWriteFile(
14417
- path18.join(run.absPath, "walkthrough.json"),
14825
+ path19.join(run.absPath, "walkthrough.json"),
14418
14826
  JSON.stringify(manifest, null, 2) + "\n"
14419
14827
  );
14420
14828
  if (opts.json) {
@@ -14422,13 +14830,14 @@ async function startRun(scenarioSlug, opts) {
14422
14830
  JSON.stringify(
14423
14831
  {
14424
14832
  run_id: run.runId,
14425
- path: path18.relative(projectRoot, run.absPath),
14833
+ path: path19.relative(projectRoot, run.absPath),
14426
14834
  scenario_slug: scenarioSlug,
14427
14835
  target_url: start2.url,
14428
14836
  target_reachable: targetReachable,
14429
14837
  ...targetWarning ? { target_warning: targetWarning } : {},
14430
14838
  persona_slug: fm.persona,
14431
14839
  outline_slug: fm.outline,
14840
+ models,
14432
14841
  build: build ?? null,
14433
14842
  environment: environment ?? null,
14434
14843
  revisit,
@@ -14447,6 +14856,9 @@ async function startRun(scenarioSlug, opts) {
14447
14856
  console.log(` ${chalk11.dim("persona:")} ${fm.persona}`);
14448
14857
  console.log(` ${chalk11.dim("outline:")} ${fm.outline}`);
14449
14858
  console.log(` ${chalk11.dim("target:")} ${start2.url}`);
14859
+ console.log(
14860
+ ` ${chalk11.dim("models:")} walker: ${chalk11.cyan(walker)} \xB7 judge: ${chalk11.cyan(judge)} \xB7 matcher: ${chalk11.cyan(matcher)}`
14861
+ );
14450
14862
  if (targetWarning) {
14451
14863
  console.log(` ${symbols.warning} ${chalk11.yellow(targetWarning)}`);
14452
14864
  }
@@ -14466,7 +14878,7 @@ async function startRun(scenarioSlug, opts) {
14466
14878
  );
14467
14879
  }
14468
14880
  console.log(
14469
- ` ${chalk11.dim("path:")} ${path18.relative(projectRoot, run.absPath)}`
14881
+ ` ${chalk11.dim("path:")} ${path19.relative(projectRoot, run.absPath)}`
14470
14882
  );
14471
14883
  console.log();
14472
14884
  console.log(` ${chalk11.bold("Next steps for blind execution:")}`);
@@ -14507,8 +14919,8 @@ var report_exports = {};
14507
14919
  __export(report_exports, {
14508
14920
  report: () => report
14509
14921
  });
14510
- import { promises as fs23 } from "fs";
14511
- import path19 from "path";
14922
+ import { promises as fs25 } from "fs";
14923
+ import path20 from "path";
14512
14924
  import chalk12 from "chalk";
14513
14925
  async function report(scenario, opts) {
14514
14926
  const ctx = await resolveProject(opts);
@@ -14527,8 +14939,8 @@ async function report(scenario, opts) {
14527
14939
  for (const rid of runs.reverse()) {
14528
14940
  try {
14529
14941
  const dir = runDir(projectRoot, rid);
14530
- const mRaw = await fs23.readFile(
14531
- path19.join(dir, "walkthrough.json"),
14942
+ const mRaw = await fs25.readFile(
14943
+ path20.join(dir, "walkthrough.json"),
14532
14944
  "utf8"
14533
14945
  );
14534
14946
  const manifest = JSON.parse(mRaw);
@@ -14536,8 +14948,8 @@ async function report(scenario, opts) {
14536
14948
  if (scenario && manifest.scenario_slug !== scenario) continue;
14537
14949
  let canonical = null;
14538
14950
  try {
14539
- const cRaw = await fs23.readFile(
14540
- path19.join(dir, "canonical.json"),
14951
+ const cRaw = await fs25.readFile(
14952
+ path20.join(dir, "canonical.json"),
14541
14953
  "utf8"
14542
14954
  );
14543
14955
  canonical = JSON.parse(cRaw);
@@ -15744,12 +16156,13 @@ var init_serve = __esm({
15744
16156
  // src/commands/init.ts
15745
16157
  var init_exports = {};
15746
16158
  __export(init_exports, {
15747
- init: () => init
16159
+ init: () => init,
16160
+ setupCommand: () => setupCommand
15748
16161
  });
15749
16162
  import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
15750
16163
  import { mkdir as mkdir4, writeFile as writeFile3 } from "fs/promises";
15751
16164
  import { basename as basename2, join as join5 } from "path";
15752
- import { isCancel as isCancel3, multiselect } from "@clack/prompts";
16165
+ import { isCancel as isCancel3, multiselect, select as select3 } from "@clack/prompts";
15753
16166
  import chalk17 from "chalk";
15754
16167
  function loadPromptTemplates() {
15755
16168
  const templates = /* @__PURE__ */ new Map();
@@ -15771,9 +16184,9 @@ function loadPromptTemplates() {
15771
16184
  return templates;
15772
16185
  }
15773
16186
  function setupCommand(projectRoot, hosts) {
15774
- if (!iamuPluginEnabled(projectRoot)) return "iamu-setup";
16187
+ if (!iamuPluginEnabled(projectRoot)) return "/iamu-setup";
15775
16188
  if (hosts.every((h) => h.value === "claude")) return "/iamu:init";
15776
- return "iamu-setup (Claude Code: /iamu:init)";
16189
+ return "/iamu-setup (Claude Code: /iamu:init)";
15777
16190
  }
15778
16191
  async function init(opts) {
15779
16192
  const options = typeof opts === "string" ? { name: opts } : opts ?? {};
@@ -15793,10 +16206,10 @@ async function init(opts) {
15793
16206
  }
15794
16207
  let freshInit = false;
15795
16208
  if (existsSync7(join5(iamuDir2, "config.json"))) {
15796
- const config = await readConfig(projectRoot);
16209
+ const config2 = await readConfig(projectRoot);
15797
16210
  if (!options.json) {
15798
16211
  console.log(
15799
- chalk17.yellow(" iamu/ already exists") + chalk17.dim(` (${config?.project_name ?? "unknown"})`)
16212
+ chalk17.yellow(" iamu/ already exists") + chalk17.dim(` (${config2?.project_name ?? "unknown"})`)
15800
16213
  );
15801
16214
  }
15802
16215
  } else {
@@ -15813,6 +16226,35 @@ async function init(opts) {
15813
16226
  ""
15814
16227
  )
15815
16228
  );
16229
+ const testabilityPath = join5(iamuDir2, "testability.md");
16230
+ if (!existsSync7(testabilityPath)) {
16231
+ await writeFile3(
16232
+ testabilityPath,
16233
+ serializeFrontmatter(
16234
+ {
16235
+ status: "draft",
16236
+ target_url: "http://localhost:3000"
16237
+ },
16238
+ `# Project Testability & Environment
16239
+
16240
+ Target URL and execution harness for walkthroughs.
16241
+ Audited and populated during /iamu:init.
16242
+
16243
+ ## Target & Run Instructions
16244
+ - Default URL: http://localhost:3000
16245
+
16246
+ ## Reachable Surfaces
16247
+ (Run /iamu:init to discover testable routes)
16248
+
16249
+ ## Mocks & Data Fixtures
16250
+ (Run /iamu:init to inspect existing harnesses)
16251
+
16252
+ ## Untestable Boundaries
16253
+ (None documented yet)
16254
+ `
16255
+ )
16256
+ );
16257
+ }
15816
16258
  }
15817
16259
  const promptTemplates = loadPromptTemplates();
15818
16260
  const promptsDir = join5(iamuDir2, ".prompts");
@@ -15869,20 +16311,57 @@ async function init(opts) {
15869
16311
  version2,
15870
16312
  { quiet: options.json }
15871
16313
  );
15872
- try {
15873
- const config = await readConfig(projectRoot);
15874
- if (config.cloud_linked) {
15875
- for (const host of selectedHosts) {
15876
- const mcpPath = host.getMcpConfigPath(projectRoot);
15877
- await writeSyncMcpEntry(mcpPath);
16314
+ let config = await readConfig(projectRoot);
16315
+ const shouldPromptCloud = isInteractive && freshInit && !config.cloud_linked && options.cloud !== false;
16316
+ if (options.cloud === true && !config.cloud_linked) {
16317
+ try {
16318
+ await authLogin({ yes: options.yes });
16319
+ config = await readConfig(projectRoot);
16320
+ } catch {
16321
+ }
16322
+ } else if (shouldPromptCloud) {
16323
+ console.log();
16324
+ const cloudChoice = await select3({
16325
+ message: "Connect this project to Iamu Cloud? (Free)",
16326
+ options: [
16327
+ {
16328
+ value: "connect",
16329
+ label: "Connect to Iamu Cloud (opens browser)",
16330
+ hint: "recommended \u2014 shareable run reports, step replays, team sync"
16331
+ },
16332
+ {
16333
+ value: "local",
16334
+ label: "Continue local-only",
16335
+ hint: "skip for now (link anytime with `iamu auth login`)"
16336
+ }
16337
+ ],
16338
+ initialValue: "connect"
16339
+ });
16340
+ if (!isCancel3(cloudChoice) && cloudChoice === "connect") {
16341
+ const prevExitCode = process.exitCode;
16342
+ try {
16343
+ await authLogin({ yes: options.yes });
16344
+ config = await readConfig(projectRoot);
16345
+ } catch {
16346
+ process.exitCode = prevExitCode;
15878
16347
  if (!options.json) {
15879
16348
  console.log(
15880
- ` ${symbols.success} ${host.name} \u2014 iamu-sync MCP server`
16349
+ chalk17.dim(
16350
+ " Cloud linking skipped. Your local setup is complete and fully functional."
16351
+ )
15881
16352
  );
15882
16353
  }
15883
16354
  }
15884
16355
  }
15885
- } catch {
16356
+ }
16357
+ if (config.cloud_linked) {
16358
+ for (const host of selectedHosts) {
16359
+ const mcpPath = host.getMcpConfigPath(projectRoot);
16360
+ await writeSyncMcpEntry(mcpPath);
16361
+ if (!options.json) {
16362
+ console.log(` ${symbols.success} ${host.name} \u2014 iamu-sync MCP server`);
16363
+ }
16364
+ }
15886
16365
  }
15887
16366
  if (options.json) {
15888
16367
  console.log(
@@ -15894,7 +16373,10 @@ async function init(opts) {
15894
16373
  fresh: freshInit,
15895
16374
  tools: selectedHosts.map((h) => h.name),
15896
16375
  skillsCount: totalSkills,
15897
- commandsCount: totalCommands
16376
+ commandsCount: totalCommands,
16377
+ cloudLinked: Boolean(config.cloud_linked),
16378
+ cloudProjectId: config.cloud_project_id ?? null,
16379
+ dashboardUrl: config.cloud_linked && config.cloud_project_id ? getCloudDashboardUrl(void 0, config.cloud_project_id) : null
15898
16380
  },
15899
16381
  null,
15900
16382
  2
@@ -15902,43 +16384,89 @@ async function init(opts) {
15902
16384
  );
15903
16385
  return;
15904
16386
  }
15905
- if (totalSkills > 0 || totalCommands > 0) {
16387
+ if (!options.json) {
15906
16388
  console.log();
15907
- const summaryParts = [];
15908
- if (totalSkills > 0) summaryParts.push(`${totalSkills} skills`);
15909
- if (totalCommands > 0) summaryParts.push(`${totalCommands} commands`);
15910
16389
  console.log(
15911
- chalk17.dim(
15912
- ` Total: ${summaryParts.join(" + ")} across ${selectedHosts.length} tool(s)`
16390
+ chalk17.bold(
16391
+ ` ${symbols.success} Project configured: ${chalk17.cyan(projectName)}`
15913
16392
  )
15914
16393
  );
15915
- }
15916
- console.log();
15917
- if (freshInit) {
15918
- console.log(chalk17.bold(" Next step:"));
15919
16394
  console.log();
15920
- console.log(chalk17.dim(" Open your editor and run:"));
16395
+ if (selectedHosts.length > 0) {
16396
+ const toolNames = selectedHosts.map((h) => h.name).join(", ");
16397
+ console.log(
16398
+ ` ${chalk17.dim("Tools:")} ${toolNames} ${chalk17.dim(`(${totalSkills} skills + ${totalCommands} commands installed)`)}`
16399
+ );
16400
+ }
16401
+ if (config.cloud_linked && config.cloud_project_id) {
16402
+ const dashboardUrl = getCloudDashboardUrl(
16403
+ void 0,
16404
+ config.cloud_project_id
16405
+ );
16406
+ console.log(
16407
+ ` ${chalk17.dim("Cloud:")} ${chalk17.green("Linked")} \xB7 ${chalk17.cyan(dashboardUrl)}`
16408
+ );
16409
+ } else {
16410
+ console.log(
16411
+ ` ${chalk17.dim("Cloud:")} Local-only ${chalk17.dim("(link anytime with `iamu auth login`)")}`
16412
+ );
16413
+ }
15921
16414
  console.log();
15922
- console.log(` ${chalk17.cyan(setupCommand(projectRoot, selectedHosts))}`);
16415
+ if (freshInit) {
16416
+ console.log(chalk17.bold(" Next steps:"));
16417
+ console.log();
16418
+ console.log(
16419
+ ` ${chalk17.bold("1.")} Start your application's dev server:`
16420
+ );
16421
+ console.log(
16422
+ ` ${chalk17.cyan("$ pnpm dev")} ${chalk17.dim("(or npm run dev)")}`
16423
+ );
16424
+ console.log();
16425
+ console.log(` ${chalk17.bold("2.")} In your AI agent chat, run:`);
16426
+ console.log(
16427
+ ` ${chalk17.cyan(setupCommand(projectRoot, selectedHosts))}`
16428
+ );
16429
+ console.log();
16430
+ console.log(
16431
+ chalk17.dim(
16432
+ " Your agent will analyze your codebase, map user roles, and generate your first walkthrough scenario."
16433
+ )
16434
+ );
16435
+ console.log();
16436
+ console.log(chalk17.bold(" Core workflow:"));
16437
+ console.log(
16438
+ chalk17.dim(" \u2022 ") + chalk17.cyan("/iamu:init") + chalk17.dim(" Map domain & scaffold inaugural scenario")
16439
+ );
16440
+ console.log(
16441
+ chalk17.dim(" \u2022 ") + chalk17.cyan("iamu-run <slug>") + chalk17.dim(" Walk scenario in browser & record findings")
16442
+ );
16443
+ console.log(
16444
+ chalk17.dim(" \u2022 ") + chalk17.cyan("iamu-findings") + chalk17.dim(" Triage persona feedback & apply code fixes")
16445
+ );
16446
+ console.log(
16447
+ chalk17.dim(" \u2022 ") + chalk17.cyan("iamu-propose") + chalk17.dim(" Brainstorm high-risk workflows & new personas")
16448
+ );
16449
+ } else {
16450
+ console.log(chalk17.bold(" Updated:"));
16451
+ console.log();
16452
+ console.log(
16453
+ chalk17.dim(" Skills and commands regenerated to latest version.")
16454
+ );
16455
+ }
15923
16456
  console.log();
15924
16457
  console.log(
15925
- chalk17.dim(" Your agent will analyze your codebase, ask a few")
16458
+ ` ${chalk17.dim("Docs:")} ${chalk17.underline("https://iamu.dev/docs")}`
15926
16459
  );
15927
16460
  console.log(
15928
- chalk17.dim(" questions, then you're ready for your first walkthrough.")
16461
+ ` ${chalk17.dim("Quickstart:")} ${chalk17.underline("https://iamu.dev/quickstart")}`
15929
16462
  );
15930
- } else {
15931
- console.log(chalk17.bold(" Updated:"));
16463
+ if (config.cloud_linked && config.cloud_project_id) {
16464
+ console.log(
16465
+ ` ${chalk17.dim("Dashboard:")} ${chalk17.underline(getCloudDashboardUrl(void 0, config.cloud_project_id))}`
16466
+ );
16467
+ }
15932
16468
  console.log();
15933
- console.log(
15934
- chalk17.dim(" Skills and commands regenerated to latest version.")
15935
- );
15936
16469
  }
15937
- console.log();
15938
- console.log(
15939
- chalk17.dim(" Docs: ") + chalk17.underline("https://iamu.dev/quickstart")
15940
- );
15941
- console.log();
15942
16470
  }
15943
16471
  var init_init = __esm({
15944
16472
  "src/commands/init.ts"() {
@@ -15946,7 +16474,9 @@ var init_init = __esm({
15946
16474
  init_dist2();
15947
16475
  init_claude_plugin();
15948
16476
  init_registry();
16477
+ init_cloud_url();
15949
16478
  init_symbols();
16479
+ init_auth();
15950
16480
  init_mcp_config();
15951
16481
  init_skills();
15952
16482
  init_templates();
@@ -16242,7 +16772,10 @@ var init_browse_actions = __esm({
16242
16772
  usage: "goto <url>",
16243
16773
  minArgs: 1,
16244
16774
  run: async (p, args) => {
16245
- await p.goto(args[0], { waitUntil: "domcontentloaded" });
16775
+ await p.goto(args[0], {
16776
+ waitUntil: "domcontentloaded",
16777
+ timeout: 1e4
16778
+ });
16246
16779
  }
16247
16780
  },
16248
16781
  click: {
@@ -16403,6 +16936,12 @@ var init_browse_driver = __esm({
16403
16936
  commandLine: (pid) => readProcessCommandLine(pid),
16404
16937
  kill(pid) {
16405
16938
  try {
16939
+ if (process.platform !== "win32") {
16940
+ try {
16941
+ process.kill(-pid, "SIGTERM");
16942
+ } catch {
16943
+ }
16944
+ }
16406
16945
  process.kill(pid, "SIGTERM");
16407
16946
  } catch {
16408
16947
  }
@@ -16413,7 +16952,7 @@ var init_browse_driver = __esm({
16413
16952
 
16414
16953
  // src/commands/step-numbering.ts
16415
16954
  import { open, readdir, unlink } from "fs/promises";
16416
- import path20 from "path";
16955
+ import path21 from "path";
16417
16956
  function stepFileName(stepNumber2) {
16418
16957
  return `${String(stepNumber2).padStart(3, "0")}.json`;
16419
16958
  }
@@ -16435,7 +16974,7 @@ async function reserveStepFile(stepsDir, limit) {
16435
16974
  if (candidate > limit.maxSteps) {
16436
16975
  throw new Error(stepLimitMessage(limit.maxSteps, limit.runId));
16437
16976
  }
16438
- const stepPath = path20.join(stepsDir, stepFileName(candidate));
16977
+ const stepPath = path21.join(stepsDir, stepFileName(candidate));
16439
16978
  try {
16440
16979
  const handle = await open(stepPath, "wx");
16441
16980
  await handle.close();
@@ -16467,7 +17006,7 @@ __export(step_exports, {
16467
17006
  stepCommand: () => stepCommand
16468
17007
  });
16469
17008
  import { copyFile, mkdir as mkdir5, stat } from "fs/promises";
16470
- import path21 from "path";
17009
+ import path22 from "path";
16471
17010
  import chalk19 from "chalk";
16472
17011
  function parseMaxSteps(val) {
16473
17012
  if (typeof val === "number" && Number.isFinite(val) && val > 0) return val;
@@ -16486,8 +17025,8 @@ async function recordStep(input) {
16486
17025
  if (!runDirPath) {
16487
17026
  throw new Error(`Run directory not found for run "${input.runId}"`);
16488
17027
  }
16489
- const stepsDir = path21.join(runDirPath, "steps");
16490
- const screenshotsDir = path21.join(runDirPath, "screenshots");
17028
+ const stepsDir = path22.join(runDirPath, "steps");
17029
+ const screenshotsDir = path22.join(runDirPath, "screenshots");
16491
17030
  await mkdir5(stepsDir, { recursive: true });
16492
17031
  await mkdir5(screenshotsDir, { recursive: true });
16493
17032
  const shotSource = input.shot ? await existingShot(input.shot) : null;
@@ -16501,11 +17040,11 @@ async function recordStep(input) {
16501
17040
  let finalScreenshotPath = null;
16502
17041
  if (shotSource) {
16503
17042
  const destName = `step-${stepNumber2}.png`;
16504
- finalScreenshotPath = path21.join(screenshotsDir, destName);
16505
- if (path21.resolve(finalScreenshotPath) !== shotSource) {
17043
+ finalScreenshotPath = path22.join(screenshotsDir, destName);
17044
+ if (path22.resolve(finalScreenshotPath) !== shotSource) {
16506
17045
  await copyFile(shotSource, finalScreenshotPath);
16507
17046
  }
16508
- screenshotRef = path21.join("screenshots", destName);
17047
+ screenshotRef = path22.join("screenshots", destName);
16509
17048
  }
16510
17049
  const parsedStep = runStepSchema.parse({
16511
17050
  step_number: stepNumber2,
@@ -16530,7 +17069,7 @@ async function recordStep(input) {
16530
17069
  }
16531
17070
  }
16532
17071
  async function existingShot(shot) {
16533
- const resolved = path21.resolve(shot);
17072
+ const resolved = path22.resolve(shot);
16534
17073
  const isFile = await stat(resolved).then((s) => s.isFile()).catch(() => false);
16535
17074
  if (!isFile) {
16536
17075
  throw new Error(
@@ -16572,7 +17111,7 @@ async function stepCommand(runArg, opts) {
16572
17111
  JSON.stringify(
16573
17112
  {
16574
17113
  step_number: res.step.step_number,
16575
- path: path21.relative(projectRoot, res.stepPath),
17114
+ path: path22.relative(projectRoot, res.stepPath),
16576
17115
  action: res.step.action,
16577
17116
  note: res.step.note,
16578
17117
  url: res.step.url,
@@ -16599,7 +17138,7 @@ async function stepCommand(runArg, opts) {
16599
17138
  console.log(` ${chalk19.dim("shot:")} ${res.step.screenshot}`);
16600
17139
  }
16601
17140
  console.log(
16602
- ` ${chalk19.dim("file:")} ${path21.relative(projectRoot, res.stepPath)}`
17141
+ ` ${chalk19.dim("file:")} ${path22.relative(projectRoot, res.stepPath)}`
16603
17142
  );
16604
17143
  console.log();
16605
17144
  } catch (err) {
@@ -16629,6 +17168,7 @@ __export(browse_exports, {
16629
17168
  readBrowseState: () => readBrowseState,
16630
17169
  shouldDisableSandbox: () => shouldDisableSandbox,
16631
17170
  start: () => start,
17171
+ waitForVisualStability: () => waitForVisualStability,
16632
17172
  withPage: () => withPage
16633
17173
  });
16634
17174
  import { randomUUID as randomUUID7 } from "crypto";
@@ -16641,21 +17181,21 @@ import {
16641
17181
  } from "fs";
16642
17182
  import { rm as rm2 } from "fs/promises";
16643
17183
  import { homedir as homedir3 } from "os";
16644
- import path22 from "path";
17184
+ import path23 from "path";
16645
17185
  import chalk20 from "chalk";
16646
17186
  function stateDir() {
16647
- return path22.join(
16648
- process.env.IAMU_HOME ?? path22.join(homedir3(), ".iamu"),
17187
+ return path23.join(
17188
+ process.env.IAMU_HOME ?? path23.join(homedir3(), ".iamu"),
16649
17189
  "browse"
16650
17190
  );
16651
17191
  }
16652
17192
  function profileDir() {
16653
- return path22.join(stateDir(), "profile");
17193
+ return path23.join(stateDir(), "profile");
16654
17194
  }
16655
17195
  function readBrowseState() {
16656
17196
  try {
16657
17197
  return JSON.parse(
16658
- readFileSync6(path22.join(stateDir(), "state.json"), "utf8")
17198
+ readFileSync6(path23.join(stateDir(), "state.json"), "utf8")
16659
17199
  );
16660
17200
  } catch {
16661
17201
  return null;
@@ -16663,7 +17203,7 @@ function readBrowseState() {
16663
17203
  }
16664
17204
  function writeState(state) {
16665
17205
  mkdirSync(stateDir(), { recursive: true });
16666
- writeFileSync(path22.join(stateDir(), "state.json"), JSON.stringify(state));
17206
+ writeFileSync(path23.join(stateDir(), "state.json"), JSON.stringify(state));
16667
17207
  }
16668
17208
  function fail(message, opts) {
16669
17209
  if (opts?.json) {
@@ -16726,7 +17266,7 @@ async function requestedViewport(opts) {
16726
17266
  let manifest;
16727
17267
  try {
16728
17268
  manifest = JSON.parse(
16729
- readFileSync6(path22.join(dir, "walkthrough.json"), "utf8")
17269
+ readFileSync6(path23.join(dir, "walkthrough.json"), "utf8")
16730
17270
  );
16731
17271
  } catch {
16732
17272
  fail(
@@ -16778,6 +17318,8 @@ function chromeArgs(opts, port, headless) {
16778
17318
  `--window-size=${opts.width ?? "1280"},${opts.height ?? "900"}`,
16779
17319
  "--no-first-run",
16780
17320
  "--no-default-browser-check",
17321
+ "--password-store=basic",
17322
+ "--use-mock-keychain",
16781
17323
  ...headless ? ["--headless=new", "--disable-dev-shm-usage", "--disable-gpu"] : [],
16782
17324
  ...shouldDisableSandbox() ? ["--no-sandbox"] : [],
16783
17325
  "about:blank"
@@ -16830,11 +17372,50 @@ async function stop(opts, driver) {
16830
17372
  await driver.closeBrowser(state.port);
16831
17373
  if (state.pid) await reapBrowser(state.pid, profileDir(), driver);
16832
17374
  try {
16833
- unlinkSync(path22.join(stateDir(), "state.json"));
17375
+ unlinkSync(path23.join(stateDir(), "state.json"));
16834
17376
  } catch {
16835
17377
  }
16836
17378
  report2(opts, { stopped: true }, [` ${chalk20.green("\u2713")} Browser stopped`]);
16837
17379
  }
17380
+ async function waitForVisualStability(page, maxWaitMs = 300) {
17381
+ let timer;
17382
+ try {
17383
+ const nodeTimeout = new Promise((resolve4) => {
17384
+ timer = setTimeout(resolve4, maxWaitMs + 100);
17385
+ });
17386
+ const browserWait = page.evaluate((maxWait) => {
17387
+ return new Promise((resolve4) => {
17388
+ const timeout = setTimeout(resolve4, maxWait);
17389
+ const done = () => {
17390
+ clearTimeout(timeout);
17391
+ resolve4();
17392
+ };
17393
+ const g = globalThis;
17394
+ if (typeof g.requestAnimationFrame !== "function") {
17395
+ done();
17396
+ return;
17397
+ }
17398
+ g.requestAnimationFrame(() => {
17399
+ g.requestAnimationFrame?.(() => {
17400
+ const animations = g.document?.getAnimations ? g.document.getAnimations().filter((a) => a.playState === "running" && a.finished) : [];
17401
+ if (animations.length === 0) {
17402
+ done();
17403
+ return;
17404
+ }
17405
+ Promise.allSettled(
17406
+ animations.map((a) => a.finished)
17407
+ ).finally(done);
17408
+ });
17409
+ });
17410
+ });
17411
+ }, maxWaitMs).catch(() => {
17412
+ });
17413
+ await Promise.race([browserWait, nodeTimeout]);
17414
+ } catch {
17415
+ } finally {
17416
+ if (timer) clearTimeout(timer);
17417
+ }
17418
+ }
16838
17419
  async function withPage(opts, driver, fn, target) {
16839
17420
  const state = readBrowseState();
16840
17421
  if (!state) {
@@ -16854,11 +17435,11 @@ async function withPage(opts, driver, fn, target) {
16854
17435
  await fn(page);
16855
17436
  await page.waitForLoadState("domcontentloaded").catch(() => {
16856
17437
  });
16857
- await page.waitForTimeout(400);
16858
17438
  let screenshot = null;
16859
17439
  if (opts.shot) {
16860
- screenshot = path22.resolve(opts.shot);
16861
- mkdirSync(path22.dirname(screenshot), { recursive: true });
17440
+ await waitForVisualStability(page);
17441
+ screenshot = path23.resolve(opts.shot);
17442
+ mkdirSync(path23.dirname(screenshot), { recursive: true });
16862
17443
  await page.screenshot({ path: screenshot, fullPage: Boolean(opts.full) });
16863
17444
  }
16864
17445
  return { url: page.url(), title: await page.title(), screenshot };
@@ -16937,7 +17518,7 @@ function printStep(opts, record, projectRoot) {
16937
17518
  if (opts.json) {
16938
17519
  const doc = {
16939
17520
  step_number: step.step_number,
16940
- path: path22.relative(projectRoot, record.stepPath),
17521
+ path: path23.relative(projectRoot, record.stepPath),
16941
17522
  action: step.action,
16942
17523
  note: step.note,
16943
17524
  url: step.url,
@@ -16957,6 +17538,35 @@ function printStep(opts, record, projectRoot) {
16957
17538
  }
16958
17539
  }
16959
17540
  async function browseStep(args, opts, driver) {
17541
+ if (opts.stepFile) {
17542
+ try {
17543
+ let raw;
17544
+ if (opts.stepFile === "-") {
17545
+ raw = readFileSync6(0, "utf8");
17546
+ } else {
17547
+ raw = readFileSync6(opts.stepFile, "utf8");
17548
+ }
17549
+ const data = JSON.parse(raw);
17550
+ if (data.run && typeof data.run === "string") opts.run = data.run;
17551
+ if (data.action && typeof data.action === "string")
17552
+ opts.action = data.action;
17553
+ if (data.note && typeof data.note === "string") opts.note = data.note;
17554
+ if (data.surface && typeof data.surface === "string")
17555
+ opts.surface = data.surface;
17556
+ if (args.length === 0 && data.cmd) {
17557
+ if (Array.isArray(data.cmd)) {
17558
+ args = data.cmd.map(String);
17559
+ } else if (typeof data.cmd === "string") {
17560
+ args = data.cmd.trim().split(/\s+/);
17561
+ }
17562
+ }
17563
+ } catch (err) {
17564
+ return fail(
17565
+ `Failed to read step payload from ${opts.stepFile}: ${err.message}`,
17566
+ opts
17567
+ );
17568
+ }
17569
+ }
16960
17570
  if (!opts.run)
16961
17571
  return fail("--run <run-id> is required for browse step.", opts);
16962
17572
  if (!opts.action) {
@@ -16982,10 +17592,10 @@ async function browseStep(args, opts, driver) {
16982
17592
  const runDirPath = resolveRunDir(ctx.projectRoot, opts.run);
16983
17593
  if (!runDirPath) return;
16984
17594
  const maxSteps = parseMaxSteps(opts.maxSteps);
16985
- if (await peekNextStepNumber(path22.join(runDirPath, "steps")) > maxSteps) {
17595
+ if (await peekNextStepNumber(path23.join(runDirPath, "steps")) > maxSteps) {
16986
17596
  return fail(stepLimitMessage(maxSteps, opts.run), opts);
16987
17597
  }
16988
- const pendingShot = path22.join(
17598
+ const pendingShot = path23.join(
16989
17599
  runDirPath,
16990
17600
  "screenshots",
16991
17601
  `.pending-${randomUUID7()}.png`
@@ -17056,11 +17666,14 @@ var doctor_exports = {};
17056
17666
  __export(doctor_exports, {
17057
17667
  checkCdpResponsive: () => checkCdpResponsive,
17058
17668
  checkPortAvailable: () => checkPortAvailable,
17669
+ checkProjectHarnesses: () => checkProjectHarnesses,
17670
+ checkProjectTestabilityContract: () => checkProjectTestabilityContract,
17671
+ checkTargetHealth: () => checkTargetHealth,
17059
17672
  doctor: () => doctor,
17060
17673
  runDoctorChecks: () => runDoctorChecks
17061
17674
  });
17062
17675
  import { createServer } from "net";
17063
- import { existsSync as existsSync10 } from "fs";
17676
+ import { existsSync as existsSync10, promises as fs26 } from "fs";
17064
17677
  import { join as join7 } from "path";
17065
17678
  import chalk21 from "chalk";
17066
17679
  function checkPortAvailable(port) {
@@ -17338,11 +17951,186 @@ async function checkAgentIntegrations(projectRoot) {
17338
17951
  }
17339
17952
  return checks;
17340
17953
  }
17341
- async function runDoctorChecks(projectRoot) {
17954
+ async function checkTargetHealth(targetUrl) {
17955
+ const checks = [];
17956
+ let parsedUrl;
17957
+ try {
17958
+ parsedUrl = new URL(targetUrl);
17959
+ } catch {
17960
+ checks.push({
17961
+ category: "target",
17962
+ name: "Target URL Format",
17963
+ status: "error",
17964
+ message: `Invalid target URL: "${targetUrl}"`,
17965
+ remedy: "Provide a valid URL with protocol (e.g. http://localhost:3000)."
17966
+ });
17967
+ return checks;
17968
+ }
17969
+ try {
17970
+ const res = await fetch(targetUrl, {
17971
+ method: "GET",
17972
+ redirect: "manual",
17973
+ signal: AbortSignal.timeout(3e3)
17974
+ });
17975
+ if ([301, 302, 303, 307, 308].includes(res.status)) {
17976
+ const location = res.headers.get("location") || "";
17977
+ const isExternalAuth = /auth0\.com|okta\.com|clerk\.com|accounts\.google\.com|cognito|login\.microsoftonline\.com/i.test(
17978
+ location
17979
+ ) || location.includes("/oauth");
17980
+ if (isExternalAuth) {
17981
+ let redirectHost = location;
17982
+ try {
17983
+ redirectHost = new URL(location, targetUrl).host;
17984
+ } catch {
17985
+ }
17986
+ checks.push({
17987
+ category: "target",
17988
+ name: "Authentication Boundary",
17989
+ status: "warn",
17990
+ message: `Target immediately redirects to external auth (${redirectHost})`,
17991
+ remedy: "Configure a dev auth bypass or prepare for interactive login handshake before walking."
17992
+ });
17993
+ return checks;
17994
+ }
17995
+ checks.push({
17996
+ category: "target",
17997
+ name: "Target Reachability",
17998
+ status: "ok",
17999
+ message: `HTTP ${res.status} redirect -> ${location}`
18000
+ });
18001
+ return checks;
18002
+ }
18003
+ if (res.status >= 500) {
18004
+ checks.push({
18005
+ category: "target",
18006
+ name: "Target Health",
18007
+ status: "error",
18008
+ message: `Target returned HTTP ${res.status} ${res.statusText || "Server Error"}`,
18009
+ remedy: "Check dev server logs for unhandled exceptions or missing environment variables."
18010
+ });
18011
+ return checks;
18012
+ }
18013
+ checks.push({
18014
+ category: "target",
18015
+ name: "Target Reachability",
18016
+ status: "ok",
18017
+ message: `HTTP ${res.status} OK (${parsedUrl.origin})`
18018
+ });
18019
+ } catch (err) {
18020
+ const message = err.message;
18021
+ checks.push({
18022
+ category: "target",
18023
+ name: "Target Reachability",
18024
+ status: "warn",
18025
+ message: `Cannot connect to ${targetUrl} (${message})`,
18026
+ remedy: "Ensure your dev server is running and listening on the specified port."
18027
+ });
18028
+ }
18029
+ return checks;
18030
+ }
18031
+ async function checkProjectHarnesses(projectRoot) {
18032
+ const checks = [];
18033
+ const pkgPath = join7(projectRoot, "package.json");
18034
+ if (!existsSync10(pkgPath)) return checks;
18035
+ try {
18036
+ const raw = await fs26.readFile(pkgPath, "utf8");
18037
+ const pkg = JSON.parse(raw);
18038
+ const scripts = pkg.scripts || {};
18039
+ const allDeps = {
18040
+ ...pkg.dependencies || {},
18041
+ ...pkg.devDependencies || {}
18042
+ };
18043
+ const devScripts = ["dev", "start", "dev:mock", "serve"].filter(
18044
+ (s) => s in scripts
18045
+ );
18046
+ if (devScripts.length > 0) {
18047
+ checks.push({
18048
+ category: "target",
18049
+ name: "Dev Server Script",
18050
+ status: "ok",
18051
+ message: `Available scripts: ${devScripts.map((s) => `pnpm ${s}`).join(", ")}`
18052
+ });
18053
+ } else {
18054
+ checks.push({
18055
+ category: "target",
18056
+ name: "Dev Server Script",
18057
+ status: "warn",
18058
+ message: "No standard dev script (dev, start, dev:mock) found in package.json",
18059
+ remedy: "Add a `dev` or `start` script to package.json to boot your app locally."
18060
+ });
18061
+ }
18062
+ if (allDeps.msw || existsSync10(join7(projectRoot, "src/mocks")) || existsSync10(join7(projectRoot, "mocks"))) {
18063
+ checks.push({
18064
+ category: "target",
18065
+ name: "Mock Harness (MSW)",
18066
+ status: "ok",
18067
+ message: "Mock Service Worker detected. API mocks can decouple walks from live backends."
18068
+ });
18069
+ }
18070
+ const seedScript = Object.keys(scripts).find(
18071
+ (s) => /seed|db:seed|fixtures/i.test(s)
18072
+ );
18073
+ if (seedScript) {
18074
+ checks.push({
18075
+ category: "target",
18076
+ name: "Database Seed",
18077
+ status: "ok",
18078
+ message: `Seed script detected (${seedScript}). Preconditions can be reset between runs.`
18079
+ });
18080
+ }
18081
+ } catch {
18082
+ }
18083
+ return checks;
18084
+ }
18085
+ async function checkProjectTestabilityContract(projectRoot) {
18086
+ const checks = [];
18087
+ const testabilityPath = join7(projectRoot, "iamu", "testability.md");
18088
+ if (!existsSync10(testabilityPath)) {
18089
+ checks.push({
18090
+ category: "target",
18091
+ name: "Testability Contract (testability.md)",
18092
+ status: "warn",
18093
+ message: "iamu/testability.md not found",
18094
+ remedy: "Run `/iamu:init` to audit your codebase and record testable routes, auth strategies, and mock harnesses."
18095
+ });
18096
+ return { checks };
18097
+ }
18098
+ try {
18099
+ const raw = await fs26.readFile(testabilityPath, "utf8");
18100
+ const { frontmatter } = parseFrontmatter(raw);
18101
+ const targetUrl = typeof frontmatter.target_url === "string" ? frontmatter.target_url : void 0;
18102
+ const details = [];
18103
+ if (targetUrl) details.push(`Target: ${targetUrl}`);
18104
+ if (frontmatter.dev_command) {
18105
+ details.push(`Command: ${frontmatter.dev_command}`);
18106
+ }
18107
+ if (frontmatter.auth_strategy) {
18108
+ details.push(`Auth: ${frontmatter.auth_strategy}`);
18109
+ }
18110
+ checks.push({
18111
+ category: "target",
18112
+ name: "Testability Contract (testability.md)",
18113
+ status: "ok",
18114
+ message: `Configured (${details.join(" \xB7 ") || "present"})`
18115
+ });
18116
+ return { checks, targetUrl };
18117
+ } catch (err) {
18118
+ checks.push({
18119
+ category: "target",
18120
+ name: "Testability Contract (testability.md)",
18121
+ status: "warn",
18122
+ message: `Unreadable testability contract: ${err.message}`,
18123
+ remedy: "Repair iamu/testability.md frontmatter."
18124
+ });
18125
+ return { checks };
18126
+ }
18127
+ }
18128
+ async function runDoctorChecks(projectRoot, opts) {
17342
18129
  const checks = [
17343
18130
  ...checkRuntimeChecks(),
17344
18131
  ...await checkBrowserChecks()
17345
18132
  ];
18133
+ let effectiveTarget = opts?.target;
17346
18134
  if (!projectRoot || !existsSync10(join7(projectRoot, "iamu"))) {
17347
18135
  checks.push({
17348
18136
  category: "project",
@@ -17362,6 +18150,15 @@ async function runDoctorChecks(projectRoot) {
17362
18150
  checks.push(...await checkProjectConfiguration(projectRoot));
17363
18151
  checks.push(...await checkProjectContextAndEntities(projectRoot));
17364
18152
  checks.push(...await checkAgentIntegrations(projectRoot));
18153
+ checks.push(...await checkProjectHarnesses(projectRoot));
18154
+ const testability = await checkProjectTestabilityContract(projectRoot);
18155
+ checks.push(...testability.checks);
18156
+ if (!effectiveTarget && testability.targetUrl) {
18157
+ effectiveTarget = testability.targetUrl;
18158
+ }
18159
+ }
18160
+ if (effectiveTarget) {
18161
+ checks.push(...await checkTargetHealth(effectiveTarget));
17365
18162
  }
17366
18163
  const okCount = checks.filter((c) => c.status === "ok").length;
17367
18164
  const warnCount = checks.filter((c) => c.status === "warn").length;
@@ -17378,7 +18175,7 @@ async function runDoctorChecks(projectRoot) {
17378
18175
  }
17379
18176
  async function doctor(opts) {
17380
18177
  const projectRoot = findProjectRoot(process.cwd()) ?? void 0;
17381
- const result = await runDoctorChecks(projectRoot);
18178
+ const result = await runDoctorChecks(projectRoot, { target: opts?.target });
17382
18179
  if (opts?.json) {
17383
18180
  console.log(JSON.stringify(result, null, 2));
17384
18181
  if (!result.ok) process.exitCode = 1;
@@ -17392,7 +18189,8 @@ async function doctor(opts) {
17392
18189
  { key: "browser", title: "Browser Automation" },
17393
18190
  { key: "project", title: "Project Configuration" },
17394
18191
  { key: "agent", title: "Agent Integrations" },
17395
- { key: "cloud", title: "Cloud & Authentication" }
18192
+ { key: "cloud", title: "Cloud & Authentication" },
18193
+ { key: "target", title: "Target & Testability" }
17396
18194
  ];
17397
18195
  for (const cat of categories) {
17398
18196
  const catChecks = result.checks.filter((c) => c.category === cat.key);
@@ -17870,13 +18668,13 @@ __export(validate_exports, {
17870
18668
  validate: () => validate
17871
18669
  });
17872
18670
  import { randomUUID as randomUUID8 } from "crypto";
17873
- import { promises as fs24 } from "fs";
17874
- import path23 from "path";
18671
+ import { promises as fs27 } from "fs";
18672
+ import path24 from "path";
17875
18673
  import chalk23 from "chalk";
17876
18674
  function inferTypeFromPath(filePath, projectRoot) {
17877
- const rel = path23.relative(path23.join(projectRoot, "iamu"), filePath);
17878
- if (rel.startsWith("..") || path23.isAbsolute(rel)) return null;
17879
- const segments = rel.split(path23.sep);
18675
+ const rel = path24.relative(path24.join(projectRoot, "iamu"), filePath);
18676
+ if (rel.startsWith("..") || path24.isAbsolute(rel)) return null;
18677
+ const segments = rel.split(path24.sep);
17880
18678
  if (segments.length < 2) return null;
17881
18679
  return TYPE_BY_ALIAS.get(segments[0]) ?? null;
17882
18680
  }
@@ -17906,7 +18704,7 @@ function validateFile(filePath, raw, projectRoot) {
17906
18704
  );
17907
18705
  return result;
17908
18706
  }
17909
- if (path23.basename(filePath) === "context.yml") {
18707
+ if (path24.basename(filePath) === "context.yml") {
17910
18708
  applyZodValidation(contextFrontmatterSchema, frontmatter, result);
17911
18709
  const warnings = [];
17912
18710
  const hasNotes = Boolean(
@@ -17966,17 +18764,17 @@ async function buildRepairLookups(projectRoot) {
17966
18764
  }
17967
18765
  }
17968
18766
  const runScenario = /* @__PURE__ */ new Map();
17969
- const runsDir2 = path23.join(projectRoot, "iamu", ".runs");
18767
+ const runsDir2 = path24.join(projectRoot, "iamu", ".runs");
17970
18768
  let runIds = [];
17971
18769
  try {
17972
- runIds = await fs24.readdir(runsDir2);
18770
+ runIds = await fs27.readdir(runsDir2);
17973
18771
  } catch {
17974
18772
  }
17975
18773
  for (const runId of runIds) {
17976
18774
  try {
17977
18775
  const manifest = JSON.parse(
17978
- await fs24.readFile(
17979
- path23.join(runsDir2, runId, "walkthrough.json"),
18776
+ await fs27.readFile(
18777
+ path24.join(runsDir2, runId, "walkthrough.json"),
17980
18778
  "utf8"
17981
18779
  )
17982
18780
  );
@@ -17998,7 +18796,7 @@ async function fixFindings(projectRoot, results, dryRun) {
17998
18796
  let raw;
17999
18797
  let parsed;
18000
18798
  try {
18001
- raw = await fs24.readFile(result.path, "utf8");
18799
+ raw = await fs27.readFile(result.path, "utf8");
18002
18800
  parsed = parseFrontmatter(raw);
18003
18801
  } catch {
18004
18802
  continue;
@@ -18007,7 +18805,7 @@ async function fixFindings(projectRoot, results, dryRun) {
18007
18805
  parsed.frontmatter,
18008
18806
  {
18009
18807
  ...lookups,
18010
- fileSlug: path23.basename(result.path, ".md"),
18808
+ fileSlug: path24.basename(result.path, ".md"),
18011
18809
  newId: randomUUID8,
18012
18810
  body: parsed.body
18013
18811
  }
@@ -18042,9 +18840,9 @@ async function validate(filePath, opts) {
18042
18840
  if (process.exitCode === 1) return;
18043
18841
  const results = [];
18044
18842
  if (filePath) {
18045
- const abs = path23.isAbsolute(filePath) ? filePath : path23.resolve(process.cwd(), filePath);
18843
+ const abs = path24.isAbsolute(filePath) ? filePath : path24.resolve(process.cwd(), filePath);
18046
18844
  try {
18047
- const raw = await fs24.readFile(abs, "utf8");
18845
+ const raw = await fs27.readFile(abs, "utf8");
18048
18846
  results.push(validateFile(abs, raw, projectRoot));
18049
18847
  } catch (err) {
18050
18848
  commandError(
@@ -18054,24 +18852,24 @@ async function validate(filePath, opts) {
18054
18852
  return;
18055
18853
  }
18056
18854
  } else if (opts.run) {
18057
- const findingsDir = path23.join(projectRoot, "iamu", ENTITY_DIRS.finding);
18855
+ const findingsDir = path24.join(projectRoot, "iamu", ENTITY_DIRS.finding);
18058
18856
  let names = [];
18059
18857
  try {
18060
- names = await fs24.readdir(findingsDir);
18858
+ names = await fs27.readdir(findingsDir);
18061
18859
  } catch {
18062
18860
  }
18063
18861
  for (const name of names) {
18064
18862
  if (!name.endsWith(".md")) continue;
18065
- const abs = path23.join(findingsDir, name);
18863
+ const abs = path24.join(findingsDir, name);
18066
18864
  try {
18067
- const raw = await fs24.readFile(abs, "utf8");
18865
+ const raw = await fs27.readFile(abs, "utf8");
18068
18866
  const { frontmatter } = parseFrontmatter(raw);
18069
18867
  if (frontmatter.run === opts.run) {
18070
18868
  results.push(validateFile(abs, raw, projectRoot));
18071
18869
  }
18072
18870
  } catch {
18073
18871
  results.push({
18074
- path: path23.relative(projectRoot, abs),
18872
+ path: path24.relative(projectRoot, abs),
18075
18873
  valid: false,
18076
18874
  errors: ["Cannot read file or parse frontmatter"]
18077
18875
  });
@@ -18104,31 +18902,31 @@ async function validate(filePath, opts) {
18104
18902
  } else {
18105
18903
  const typesToCheck = typeFilter ? ENTITY_TYPES.filter((t) => typeFilter.has(t)) : ENTITY_TYPES;
18106
18904
  for (const type2 of typesToCheck) {
18107
- const iamuTypeDir = path23.join(projectRoot, "iamu", ENTITY_DIRS[type2]);
18905
+ const iamuTypeDir = path24.join(projectRoot, "iamu", ENTITY_DIRS[type2]);
18108
18906
  let names;
18109
18907
  try {
18110
- names = await fs24.readdir(iamuTypeDir);
18908
+ names = await fs27.readdir(iamuTypeDir);
18111
18909
  } catch {
18112
18910
  continue;
18113
18911
  }
18114
18912
  for (const name of names) {
18115
18913
  if (!name.endsWith(".md")) continue;
18116
- const abs = path23.join(iamuTypeDir, name);
18914
+ const abs = path24.join(iamuTypeDir, name);
18117
18915
  try {
18118
- const raw = await fs24.readFile(abs, "utf8");
18916
+ const raw = await fs27.readFile(abs, "utf8");
18119
18917
  results.push(validateFile(abs, raw, projectRoot));
18120
18918
  } catch {
18121
18919
  results.push({
18122
- path: path23.relative(projectRoot, abs),
18920
+ path: path24.relative(projectRoot, abs),
18123
18921
  valid: false,
18124
18922
  errors: ["Cannot read file"]
18125
18923
  });
18126
18924
  }
18127
18925
  }
18128
18926
  }
18129
- const ctxPath = path23.join(projectRoot, "iamu", "context.yml");
18927
+ const ctxPath = path24.join(projectRoot, "iamu", "context.yml");
18130
18928
  try {
18131
- const raw = await fs24.readFile(ctxPath, "utf8");
18929
+ const raw = await fs27.readFile(ctxPath, "utf8");
18132
18930
  results.push(validateFile(ctxPath, raw, projectRoot));
18133
18931
  } catch {
18134
18932
  }
@@ -18147,19 +18945,19 @@ async function validate(filePath, opts) {
18147
18945
  valid,
18148
18946
  errors: results.filter((r) => !r.valid).flatMap(
18149
18947
  (r) => r.errors.map((e) => ({
18150
- path: path23.relative(projectRoot, r.path),
18948
+ path: path24.relative(projectRoot, r.path),
18151
18949
  message: e
18152
18950
  }))
18153
18951
  ),
18154
18952
  warnings: results.filter((r) => r.warnings && r.warnings.length > 0).flatMap(
18155
18953
  (r) => (r.warnings ?? []).map((w) => ({
18156
- path: path23.relative(projectRoot, r.path),
18954
+ path: path24.relative(projectRoot, r.path),
18157
18955
  message: w
18158
18956
  }))
18159
18957
  ),
18160
18958
  ...opts.fix ? {
18161
18959
  repaired: repaired.map((r) => ({
18162
- path: path23.relative(projectRoot, r.path),
18960
+ path: path24.relative(projectRoot, r.path),
18163
18961
  changes: r.repairs,
18164
18962
  valid: r.valid
18165
18963
  }))
@@ -18181,7 +18979,7 @@ async function validate(filePath, opts) {
18181
18979
  }
18182
18980
  let hasErrors = false;
18183
18981
  for (const r of results) {
18184
- const relPath = path23.relative(projectRoot, r.path);
18982
+ const relPath = path24.relative(projectRoot, r.path);
18185
18983
  if (r.valid) {
18186
18984
  if (r.warnings && r.warnings.length > 0) {
18187
18985
  console.log(` ${symbols.warning} ` + chalk23.yellow(relPath));
@@ -18372,10 +19170,26 @@ function defaultFrontmatter(type2, id, slug, name, opts) {
18372
19170
  ...base,
18373
19171
  name,
18374
19172
  role: opts.role ?? "TODO",
18375
- ...opts.disposition ? { disposition: opts.disposition } : {}
19173
+ ...opts.disposition ? { disposition: opts.disposition } : {},
19174
+ ...opts.access ? { access: opts.access } : {}
18376
19175
  };
18377
- case "outline":
18378
- return { ...base, title: name, ...description };
19176
+ case "outline": {
19177
+ const tests = Array.isArray(opts.humanTest) ? opts.humanTest : opts.humanTest ? [opts.humanTest] : [];
19178
+ return {
19179
+ ...base,
19180
+ title: name,
19181
+ ...description,
19182
+ ...opts.capability ? { primary_capability: opts.capability } : {},
19183
+ ...opts.tension ? { tension: opts.tension } : {},
19184
+ ...opts.narrative ? { narrative: opts.narrative } : {},
19185
+ ...tests.length > 0 ? {
19186
+ human_tests: tests.map((t, idx) => ({
19187
+ id: `test-${idx + 1}`,
19188
+ question: t
19189
+ }))
19190
+ } : {}
19191
+ };
19192
+ }
18379
19193
  case "capability":
18380
19194
  return { ...base, title: name, ...description };
18381
19195
  case "source":
@@ -18385,13 +19199,21 @@ function defaultFrontmatter(type2, id, slug, name, opts) {
18385
19199
  url: opts.url,
18386
19200
  authority_level: opts.authority ?? "informational"
18387
19201
  };
18388
- case "scenario":
19202
+ case "scenario": {
19203
+ const stateObj = opts.stateDescription || opts.stateApproximation ? {
19204
+ state: {
19205
+ ...opts.stateDescription ? { description: opts.stateDescription } : {},
19206
+ ...opts.stateApproximation ? { approximation: opts.stateApproximation } : {}
19207
+ }
19208
+ } : {};
18389
19209
  return {
18390
19210
  ...base,
18391
19211
  outline: opts.outline ?? null,
18392
19212
  persona: opts.persona ?? null,
18393
- ...opts.entry ? { entry_path: opts.entry } : {}
19213
+ ...opts.entry ? { entry_path: opts.entry } : {},
19214
+ ...stateObj
18394
19215
  };
19216
+ }
18395
19217
  default:
18396
19218
  return base;
18397
19219
  }
@@ -18431,7 +19253,14 @@ async function newEntity(type2, opts) {
18431
19253
  return;
18432
19254
  }
18433
19255
  }
18434
- const slug = opts.slug ? slugify(opts.slug) : slugify(opts.name);
19256
+ let slug;
19257
+ if (opts.slug) {
19258
+ slug = slugSchema.safeParse(opts.slug).success ? opts.slug : slugify(opts.slug);
19259
+ } else if (slugSchema.safeParse(opts.name).success) {
19260
+ slug = opts.name;
19261
+ } else {
19262
+ slug = slugify(opts.name);
19263
+ }
18435
19264
  if (await entityExists(projectRoot, entityType, slug)) {
18436
19265
  commandError(
18437
19266
  `Entity already exists: iamu/${ENTITY_DIRS[entityType]}/${slug}.md`,
@@ -18441,7 +19270,7 @@ async function newEntity(type2, opts) {
18441
19270
  }
18442
19271
  const id = randomUUID9();
18443
19272
  const frontmatter = defaultFrontmatter(entityType, id, slug, opts.name, opts);
18444
- const body = "";
19273
+ const body = opts.body ?? (entityType === "outline" ? "" : opts.narrative) ?? "";
18445
19274
  try {
18446
19275
  const record = await writeEntity(
18447
19276
  projectRoot,
@@ -18486,6 +19315,797 @@ var init_new_entity = __esm({
18486
19315
  }
18487
19316
  });
18488
19317
 
19318
+ // src/commands/explore-crawl.ts
19319
+ function candidateSig(c) {
19320
+ return `${c.role}
19321
+ ${c.name}
19322
+ ${c.occurrence}`;
19323
+ }
19324
+ function originOf(url) {
19325
+ try {
19326
+ return new URL(url).origin;
19327
+ } catch {
19328
+ return null;
19329
+ }
19330
+ }
19331
+ function skipReason(c, origin, deny) {
19332
+ if (deny.some((re) => re.test(c.name) || c.href && re.test(c.href))) {
19333
+ return "denied";
19334
+ }
19335
+ if (c.submit) return "submit";
19336
+ if (UNSAFE_NAME.test(c.name)) return "unsafe_name";
19337
+ if (c.href) {
19338
+ let u = null;
19339
+ try {
19340
+ u = new URL(c.href);
19341
+ } catch {
19342
+ }
19343
+ if (u && u.protocol !== "javascript:") {
19344
+ if (u.protocol !== "http:" && u.protocol !== "https:") return "external";
19345
+ if (u.origin !== origin) return "external";
19346
+ if (UNSAFE_PATH.test(u.pathname)) return "unsafe_name";
19347
+ }
19348
+ if (c.download) return "download";
19349
+ }
19350
+ return null;
19351
+ }
19352
+ async function settle(page, settleMs) {
19353
+ await page.waitForLoadState("domcontentloaded", { timeout: NAV_TIMEOUT_MS }).catch(() => {
19354
+ });
19355
+ await page.waitForLoadState("networkidle", { timeout: settleMs }).catch(() => {
19356
+ });
19357
+ }
19358
+ async function readPage(page) {
19359
+ return page.evaluate(READ_PAGE);
19360
+ }
19361
+ async function markDocument(page) {
19362
+ await page.evaluate("window.__iamuDoc = true").catch(() => {
19363
+ });
19364
+ }
19365
+ async function isSameDocument(page) {
19366
+ return page.evaluate("window.__iamuDoc === true").catch(() => false);
19367
+ }
19368
+ async function clickCandidate(page, want) {
19369
+ const state = await readPage(page);
19370
+ const c = state.candidates.find(
19371
+ (x) => candidateSig(x) === candidateSig(want)
19372
+ );
19373
+ if (!c) return false;
19374
+ await page.locator(`[data-iamu-x="${c.index}"]`).click({ timeout: CLICK_TIMEOUT_MS });
19375
+ return true;
19376
+ }
19377
+ async function crawl(page, opts) {
19378
+ const origin = originOf(opts.startUrl);
19379
+ if (!origin) throw new Error(`Not a URL: ${opts.startUrl}`);
19380
+ let blocked = [];
19381
+ await page.route("**/*", (route) => {
19382
+ const req = route.request();
19383
+ if (SAFE_METHODS.has(req.method())) return route.continue();
19384
+ let where = req.url();
19385
+ try {
19386
+ const u = new URL(where);
19387
+ where = u.origin === origin ? u.pathname : `${u.origin}${u.pathname}`;
19388
+ } catch {
19389
+ }
19390
+ blocked.push(`${req.method()} ${where}`);
19391
+ return route.abort("blockedbyclient");
19392
+ });
19393
+ const onDialog = (d) => {
19394
+ d.dismiss().catch(() => {
19395
+ });
19396
+ };
19397
+ const onPopup = (p) => {
19398
+ if (p !== page) p.close().catch(() => {
19399
+ });
19400
+ };
19401
+ page.on("dialog", onDialog);
19402
+ page.context().on("page", onPopup);
19403
+ try {
19404
+ return await crawlFrom(page, opts, origin, () => {
19405
+ const out = blocked;
19406
+ blocked = [];
19407
+ return out;
19408
+ });
19409
+ } finally {
19410
+ page.off("dialog", onDialog);
19411
+ page.context().off("page", onPopup);
19412
+ await page.unroute("**/*").catch(() => {
19413
+ });
19414
+ }
19415
+ }
19416
+ async function crawlFrom(page, opts, origin, takeBlocked) {
19417
+ const deny = opts.deny ?? [];
19418
+ const settleMs = opts.settleMs ?? 1500;
19419
+ const { budget } = opts;
19420
+ const stats = {
19421
+ surfaces: 0,
19422
+ actions_tried: 0,
19423
+ skipped: 0,
19424
+ blocked_requests: 0,
19425
+ errors: 0,
19426
+ truncated: false
19427
+ };
19428
+ const surfaces = [];
19429
+ const byKey = /* @__PURE__ */ new Map();
19430
+ const byPlaceView = /* @__PURE__ */ new Map();
19431
+ const signatureOf = (s) => exploreSignature(s.headings, s.title);
19432
+ function addSurface(state, place, view, path30, depth) {
19433
+ const key = exploreSurfaceKey(place, view);
19434
+ const surface = {
19435
+ key,
19436
+ url: state.url,
19437
+ place,
19438
+ view,
19439
+ signature: signatureOf(state),
19440
+ label: state.headings[0] ?? (state.title || place),
19441
+ headings: state.headings,
19442
+ fields: state.fields,
19443
+ depth,
19444
+ path: path30,
19445
+ actions: []
19446
+ };
19447
+ surfaces.push(surface);
19448
+ byKey.set(key, surface);
19449
+ byPlaceView.set(`${place}
19450
+ ${surface.signature}`, key);
19451
+ stats.surfaces++;
19452
+ opts.log?.(` ${" ".repeat(depth)}${key} ${surface.label}`);
19453
+ return surface;
19454
+ }
19455
+ async function goto(url) {
19456
+ await page.goto(url, {
19457
+ waitUntil: "domcontentloaded",
19458
+ timeout: NAV_TIMEOUT_MS
19459
+ });
19460
+ await settle(page, settleMs);
19461
+ }
19462
+ async function replay(path30) {
19463
+ if (!("goto" in (path30[0] ?? {}))) await goto(opts.startUrl);
19464
+ for (const step of path30) {
19465
+ if ("goto" in step) {
19466
+ await goto(step.goto);
19467
+ } else {
19468
+ if (!await clickCandidate(page, step.click)) return false;
19469
+ await settle(page, settleMs);
19470
+ }
19471
+ }
19472
+ return true;
19473
+ }
19474
+ await goto(opts.startUrl);
19475
+ if (await page.evaluate("Boolean(navigator.serviceWorker?.controller)").catch(() => false)) {
19476
+ throw new Error(
19477
+ "A service worker controls this page, and the write guard cannot see its requests. Explore without --attach (service workers are blocked there)."
19478
+ );
19479
+ }
19480
+ const first = await readPage(page);
19481
+ const firstPlace = explorePlaceKey(first.url);
19482
+ if (!firstPlace || originOf(first.url) !== origin) {
19483
+ throw new Error(`The start URL led off the product, to ${first.url}`);
19484
+ }
19485
+ addSurface(first, firstPlace, null, [], 0);
19486
+ for (let i = 0; i < surfaces.length; i++) {
19487
+ const surface = surfaces[i];
19488
+ if (!await replay(surface.path)) {
19489
+ stats.errors++;
19490
+ surface.actions.push({
19491
+ role: "",
19492
+ name: "",
19493
+ occurrence: 0,
19494
+ outcome: { kind: "error", message: LOST }
19495
+ });
19496
+ continue;
19497
+ }
19498
+ const home = await readPage(page);
19499
+ const seen = new Set(home.candidates.map(candidateSig));
19500
+ const work = home.candidates.map((c) => ({ c, via: [] }));
19501
+ const homeList = home.candidates.map(candidateSig).join("\n");
19502
+ let pristine = true;
19503
+ for (let w = 0; w < work.length; w++) {
19504
+ const { c, via } = work[w];
19505
+ const action = {
19506
+ role: c.role,
19507
+ name: c.name,
19508
+ occurrence: c.occurrence,
19509
+ ...c.href ? { href: c.href } : {},
19510
+ ...via.length > 0 ? { via } : {},
19511
+ outcome: { kind: "same" }
19512
+ };
19513
+ surface.actions.push(action);
19514
+ const reason = skipReason(c, origin, deny);
19515
+ if (reason) {
19516
+ action.outcome = { kind: "skipped", reason };
19517
+ stats.skipped++;
19518
+ continue;
19519
+ }
19520
+ if (surface.depth >= budget.max_depth || stats.actions_tried >= budget.max_actions) {
19521
+ action.outcome = { kind: "skipped", reason: "budget" };
19522
+ stats.skipped++;
19523
+ stats.truncated = true;
19524
+ continue;
19525
+ }
19526
+ stats.actions_tried++;
19527
+ takeBlocked();
19528
+ const click = {
19529
+ click: { role: c.role, name: c.name, occurrence: c.occurrence }
19530
+ };
19531
+ let outcome;
19532
+ let revealed = 0;
19533
+ let left = false;
19534
+ let lost = false;
19535
+ try {
19536
+ if (!pristine || via.length > 0) {
19537
+ if (!await replay([...surface.path, ...via])) {
19538
+ throw new Error(LOST);
19539
+ }
19540
+ }
19541
+ pristine = false;
19542
+ const before = await readPage(page);
19543
+ await markDocument(page);
19544
+ if (c.newTab && c.href) {
19545
+ await goto(c.href);
19546
+ } else {
19547
+ if (!await clickCandidate(page, c)) {
19548
+ throw new Error("control is gone");
19549
+ }
19550
+ await settle(page, settleMs);
19551
+ }
19552
+ const after = await readPage(page);
19553
+ if (originOf(after.url) !== origin) {
19554
+ outcome = { kind: "external", url: after.url };
19555
+ left = true;
19556
+ } else {
19557
+ const place = explorePlaceKey(after.url) ?? before.url;
19558
+ const sig = signatureOf(after);
19559
+ const sameDoc = !(c.newTab && c.href) && await isSameDocument(page);
19560
+ const beforePlace = explorePlaceKey(before.url);
19561
+ let key = byPlaceView.get(`${place}
19562
+ ${sig}`);
19563
+ let view = null;
19564
+ if (!key) {
19565
+ const inPage = sameDoc && place === beforePlace;
19566
+ view = inPage ? sig : null;
19567
+ key = exploreSurfaceKey(place, view);
19568
+ if (byKey.has(key)) byPlaceView.set(`${place}
19569
+ ${sig}`, key);
19570
+ }
19571
+ if (key === surface.key) {
19572
+ outcome = { kind: "same" };
19573
+ const list2 = after.candidates.map(candidateSig).join("\n");
19574
+ for (const n of after.candidates) {
19575
+ const s = candidateSig(n);
19576
+ if (seen.has(s)) continue;
19577
+ seen.add(s);
19578
+ work.push({ c: n, via: [...via, click] });
19579
+ revealed++;
19580
+ }
19581
+ if (list2 === homeList) pristine = true;
19582
+ } else {
19583
+ outcome = { kind: "surface", surface: key };
19584
+ if (!byKey.has(key)) {
19585
+ if (stats.surfaces >= budget.max_surfaces) {
19586
+ stats.truncated = true;
19587
+ } else {
19588
+ const path30 = !sameDoc && view === null ? [{ goto: after.url }] : [...surface.path, ...via, click];
19589
+ addSurface(after, place, view, path30, surface.depth + 1);
19590
+ }
19591
+ }
19592
+ }
19593
+ }
19594
+ } catch (err) {
19595
+ const msg = err instanceof Error ? err.message : String(err);
19596
+ outcome = { kind: "error", message: msg.split("\n")[0].slice(0, 200) };
19597
+ stats.errors++;
19598
+ lost = msg === LOST;
19599
+ }
19600
+ action.outcome = outcome;
19601
+ if (revealed > 0) action.revealed = revealed;
19602
+ const writes = takeBlocked();
19603
+ if (writes.length > 0) {
19604
+ action.blocked_requests = [...new Set(writes)];
19605
+ stats.blocked_requests += writes.length;
19606
+ }
19607
+ if (left) pristine = false;
19608
+ if (lost) break;
19609
+ }
19610
+ }
19611
+ return { surfaces, stats };
19612
+ }
19613
+ var UNSAFE_NAME, UNSAFE_PATH, SAFE_METHODS, CLICK_TIMEOUT_MS, LOST, NAV_TIMEOUT_MS, READ_PAGE;
19614
+ var init_explore_crawl = __esm({
19615
+ "src/commands/explore-crawl.ts"() {
19616
+ "use strict";
19617
+ init_dist2();
19618
+ UNSAFE_NAME = new RegExp(
19619
+ `(?<![\\p{L}\\p{N}])(${[
19620
+ // English
19621
+ "delete",
19622
+ "remove",
19623
+ "destroy",
19624
+ "erase",
19625
+ "pay",
19626
+ "pay now",
19627
+ "purchase",
19628
+ "buy",
19629
+ "checkout",
19630
+ "place order",
19631
+ "order now",
19632
+ "send",
19633
+ "submit",
19634
+ "save",
19635
+ "confirm",
19636
+ "approve",
19637
+ "reject",
19638
+ "decline",
19639
+ "publish",
19640
+ "archive",
19641
+ "unsubscribe",
19642
+ "log ?out",
19643
+ "sign ?out",
19644
+ "reset",
19645
+ "transfer",
19646
+ "invite",
19647
+ "upload",
19648
+ "import",
19649
+ "deactivate",
19650
+ "close account",
19651
+ // Swedish
19652
+ "ta bort",
19653
+ "radera",
19654
+ "betala",
19655
+ "k\xF6p",
19656
+ "best\xE4ll",
19657
+ "skicka",
19658
+ "skicka in",
19659
+ "spara",
19660
+ "bekr\xE4fta",
19661
+ "godk\xE4nn",
19662
+ "avsl\xE5",
19663
+ "publicera",
19664
+ "arkivera",
19665
+ "logga ut",
19666
+ "\xE5terst\xE4ll",
19667
+ "\xF6verf\xF6r",
19668
+ "bjud in",
19669
+ "ladda upp"
19670
+ ].join("|")})(?![\\p{L}\\p{N}])`,
19671
+ "iu"
19672
+ );
19673
+ UNSAFE_PATH = /(^|\/)(log-?out|sign-?out|logga-?ut|delete|remove|destroy|unsubscribe)(\/|$)/i;
19674
+ SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
19675
+ CLICK_TIMEOUT_MS = 3e3;
19676
+ LOST = "could not return to surface";
19677
+ NAV_TIMEOUT_MS = 15e3;
19678
+ READ_PAGE = String.raw`(() => {
19679
+ const SELECTOR = [
19680
+ "a[href]", "button", "summary",
19681
+ 'input[type="button"]', 'input[type="submit"]',
19682
+ 'input[type="reset"]', 'input[type="image"]',
19683
+ '[role="button"]', '[role="link"]', '[role="tab"]',
19684
+ '[role="menuitem"]', '[role="menuitemradio"]', '[role="menuitemcheckbox"]',
19685
+ ].join(",");
19686
+ const clean = (s) => (s ?? "").replace(/\s+/g, " ").trim();
19687
+ const visible = (el) => {
19688
+ if (typeof el.checkVisibility === "function" &&
19689
+ !el.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true })) {
19690
+ return false;
19691
+ }
19692
+ const r = el.getBoundingClientRect();
19693
+ return r.width > 0 && r.height > 0;
19694
+ };
19695
+ const textOf = (id) => clean(document.getElementById(id)?.innerText);
19696
+ const nameOf = (el) => {
19697
+ const label = clean(el.getAttribute("aria-label"));
19698
+ if (label) return label;
19699
+ const by = el.getAttribute("aria-labelledby");
19700
+ if (by) {
19701
+ const t = clean(by.split(/\s+/).map(textOf).join(" "));
19702
+ if (t) return t;
19703
+ }
19704
+ if (el instanceof HTMLInputElement) {
19705
+ return clean(el.value) || clean(el.alt) || clean(el.title);
19706
+ }
19707
+ const text = clean(el.innerText);
19708
+ if (text) return text.slice(0, 80);
19709
+ const img = el.querySelector("img[alt]");
19710
+ return clean(img?.getAttribute("alt")) || clean(el.title);
19711
+ };
19712
+ const roleOf = (el) =>
19713
+ el.getAttribute("role") || (el.tagName === "A" ? "link" : "button");
19714
+
19715
+ for (const el of document.querySelectorAll("[data-iamu-x]")) {
19716
+ el.removeAttribute("data-iamu-x");
19717
+ }
19718
+ const seen = new Map();
19719
+ const candidates = [];
19720
+ let index = 0;
19721
+ for (const el of document.querySelectorAll(SELECTOR)) {
19722
+ if (!visible(el)) continue;
19723
+ if (el.disabled || el.getAttribute("aria-disabled") === "true") continue;
19724
+ const role = roleOf(el);
19725
+ const name = nameOf(el);
19726
+ const sig = role + "\n" + name;
19727
+ const occurrence = seen.get(sig) ?? 0;
19728
+ seen.set(sig, occurrence + 1);
19729
+ let submit = false;
19730
+ if (el instanceof HTMLButtonElement) {
19731
+ // A button in a form submits unless it says type="button".
19732
+ submit = el.form
19733
+ ? el.type === "submit" || el.type === "reset"
19734
+ : el.getAttribute("type") === "submit";
19735
+ } else if (el instanceof HTMLInputElement) {
19736
+ submit = ["submit", "reset", "image"].includes(el.type);
19737
+ }
19738
+ const anchor = el instanceof HTMLAnchorElement ? el : null;
19739
+ el.setAttribute("data-iamu-x", String(index));
19740
+ candidates.push({
19741
+ index, role, name, occurrence,
19742
+ href: anchor ? anchor.href : null,
19743
+ download: anchor ? anchor.hasAttribute("download") : false,
19744
+ newTab: anchor ? anchor.target === "_blank" : false,
19745
+ submit,
19746
+ });
19747
+ index++;
19748
+ }
19749
+
19750
+ const headings = [];
19751
+ for (const el of document.querySelectorAll(
19752
+ 'h1, h2, [role="heading"][aria-level="1"], [role="heading"][aria-level="2"]',
19753
+ )) {
19754
+ if (!visible(el)) continue;
19755
+ const t = clean(el.innerText);
19756
+ if (t) headings.push(t.slice(0, 120));
19757
+ }
19758
+
19759
+ const fields = [];
19760
+ for (const f of document.querySelectorAll("input, select, textarea")) {
19761
+ if (["hidden", "submit", "button", "reset", "image"].includes(f.type)) continue;
19762
+ if (!visible(f)) continue;
19763
+ const forLabel = f.id
19764
+ ? document.querySelector('label[for="' + CSS.escape(f.id) + '"]')
19765
+ : null;
19766
+ const name =
19767
+ clean(f.getAttribute("aria-label")) ||
19768
+ clean(forLabel?.innerText) ||
19769
+ clean(f.closest("label")?.innerText) ||
19770
+ clean(f.placeholder) ||
19771
+ clean(f.name);
19772
+ if (name && !fields.includes(name)) fields.push(name.slice(0, 80));
19773
+ if (fields.length >= 40) break;
19774
+ }
19775
+
19776
+ return { url: location.href, title: clean(document.title), headings, fields, candidates };
19777
+ })()`;
19778
+ }
19779
+ });
19780
+
19781
+ // src/commands/explore.ts
19782
+ var explore_exports = {};
19783
+ __export(explore_exports, {
19784
+ exploreCoverageCommand: () => exploreCoverageCommand,
19785
+ exploreDiffCommand: () => exploreDiffCommand,
19786
+ exploreList: () => exploreList,
19787
+ exploreRun: () => exploreRun
19788
+ });
19789
+ import { promises as fs28 } from "fs";
19790
+ import path25 from "path";
19791
+ import chalk26 from "chalk";
19792
+ function fail2(message, json2) {
19793
+ if (json2) console.error(JSON.stringify({ error: message }));
19794
+ else console.error(chalk26.red(` ${message}`));
19795
+ process.exitCode = 1;
19796
+ }
19797
+ function positiveInt(raw, fallback, flag) {
19798
+ if (raw === void 0) return fallback;
19799
+ const n = Number(raw);
19800
+ if (!Number.isInteger(n) || n < 1) {
19801
+ throw new Error(`${flag} must be a positive whole number, got "${raw}"`);
19802
+ }
19803
+ return n;
19804
+ }
19805
+ async function testabilityTarget2(projectRoot) {
19806
+ try {
19807
+ const raw = await fs28.readFile(
19808
+ path25.join(projectRoot, "iamu", "testability.md"),
19809
+ "utf8"
19810
+ );
19811
+ const { frontmatter } = parseFrontmatter(raw);
19812
+ return typeof frontmatter.target_url === "string" ? frontmatter.target_url : null;
19813
+ } catch {
19814
+ return null;
19815
+ }
19816
+ }
19817
+ async function openPage(opts) {
19818
+ const { chromium } = await import("playwright-core");
19819
+ const viewport = parseViewport(opts.viewport) ?? {
19820
+ width: 1280,
19821
+ height: 800
19822
+ };
19823
+ if (opts.attach) {
19824
+ const state = readBrowseState();
19825
+ if (!state) {
19826
+ throw new Error(
19827
+ "No browser to attach to \u2014 start one with `iamu browse start` and sign in first."
19828
+ );
19829
+ }
19830
+ const browser2 = await chromium.connectOverCDP(
19831
+ `http://127.0.0.1:${state.port}`
19832
+ );
19833
+ const context = browser2.contexts()[0] ?? await browser2.newContext();
19834
+ const page2 = await context.newPage();
19835
+ await page2.setViewportSize(viewport);
19836
+ return {
19837
+ page: page2,
19838
+ close: async () => {
19839
+ await page2.close().catch(() => {
19840
+ });
19841
+ await browser2.close().catch(() => {
19842
+ });
19843
+ }
19844
+ };
19845
+ }
19846
+ const chrome = findChrome();
19847
+ if (!chrome) {
19848
+ throw new Error(
19849
+ "No Chrome or Chromium found \u2014 install one or set IAMU_BROWSER."
19850
+ );
19851
+ }
19852
+ const browser = await chromium.launch({
19853
+ executablePath: chrome,
19854
+ headless: true,
19855
+ chromiumSandbox: !shouldDisableSandbox()
19856
+ });
19857
+ const page = await browser.newPage({ viewport, serviceWorkers: "block" });
19858
+ return { page, close: () => browser.close().catch(() => {
19859
+ }) };
19860
+ }
19861
+ async function exploreRun(url, opts) {
19862
+ const ctx = await resolveProject(opts);
19863
+ if (!ctx) return;
19864
+ const target = url ?? await testabilityTarget2(ctx.projectRoot);
19865
+ if (!target) {
19866
+ fail2(
19867
+ "No URL to explore \u2014 pass one (iamu explore http://localhost:3000) or set target_url in iamu/testability.md.",
19868
+ opts.json
19869
+ );
19870
+ return;
19871
+ }
19872
+ let startUrl;
19873
+ let budget;
19874
+ let deny;
19875
+ try {
19876
+ startUrl = new URL(target).href;
19877
+ budget = {
19878
+ max_surfaces: positiveInt(
19879
+ opts.maxSurfaces,
19880
+ DEFAULTS.max_surfaces,
19881
+ "--max-surfaces"
19882
+ ),
19883
+ max_actions: positiveInt(
19884
+ opts.maxActions,
19885
+ DEFAULTS.max_actions,
19886
+ "--max-actions"
19887
+ ),
19888
+ max_depth: positiveInt(opts.maxDepth, DEFAULTS.max_depth, "--max-depth")
19889
+ };
19890
+ deny = (opts.deny ?? []).map((d) => new RegExp(d, "i"));
19891
+ } catch (err) {
19892
+ fail2(err.message, opts.json);
19893
+ return;
19894
+ }
19895
+ const quiet = opts.json || process.env.IAMU_QUIET === "1";
19896
+ if (!quiet) {
19897
+ console.error(chalk26.dim(` Exploring ${startUrl} (read-only)\u2026`));
19898
+ }
19899
+ const now = /* @__PURE__ */ new Date();
19900
+ let opened = null;
19901
+ let result;
19902
+ try {
19903
+ opened = await openPage(opts);
19904
+ result = await crawl(opened.page, {
19905
+ startUrl,
19906
+ budget,
19907
+ deny,
19908
+ log: quiet ? void 0 : (line) => console.error(chalk26.dim(line))
19909
+ });
19910
+ } catch (err) {
19911
+ fail2(err.message.split("\n")[0], opts.json);
19912
+ return;
19913
+ } finally {
19914
+ await opened?.close();
19915
+ }
19916
+ const build = detectBuild({ cwd: ctx.projectRoot });
19917
+ const map2 = {
19918
+ schema: 1,
19919
+ id: composeExploreId(now),
19920
+ created_at: now.toISOString(),
19921
+ target_url: startUrl,
19922
+ ...build ? { build: { ...build } } : {},
19923
+ budget,
19924
+ stats: result.stats,
19925
+ surfaces: result.surfaces
19926
+ };
19927
+ const file = await writeExploreMap(ctx.projectRoot, map2);
19928
+ const rel = path25.relative(process.cwd(), file);
19929
+ if (opts.json) {
19930
+ console.log(JSON.stringify({ id: map2.id, path: rel, stats: map2.stats }));
19931
+ return;
19932
+ }
19933
+ const s = map2.stats;
19934
+ console.log(
19935
+ `
19936
+ ${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` : ""}`
19937
+ );
19938
+ if (s.truncated) {
19939
+ console.log(
19940
+ chalk26.yellow(
19941
+ " Stopped by the budget before the product ran out \u2014 raise --max-surfaces / --max-actions / --max-depth to go further."
19942
+ )
19943
+ );
19944
+ }
19945
+ console.log(` Map: ${rel}`);
19946
+ console.log(chalk26.dim(" Next: iamu explore coverage"));
19947
+ }
19948
+ async function resolveMapId(projectRoot, id, offsetFromEnd = 1) {
19949
+ if (id && id !== "latest") return id;
19950
+ const ids = await listExploreMaps(projectRoot);
19951
+ return ids.at(-offsetFromEnd) ?? null;
19952
+ }
19953
+ async function exploreList(opts) {
19954
+ const ctx = await resolveProject(opts);
19955
+ if (!ctx) return;
19956
+ const rows = [];
19957
+ for (const id of await listExploreMaps(ctx.projectRoot)) {
19958
+ try {
19959
+ const m = await readExploreMap(ctx.projectRoot, id);
19960
+ rows.push({ id, target_url: m.target_url, surfaces: m.surfaces.length });
19961
+ } catch {
19962
+ }
19963
+ }
19964
+ if (opts.json) {
19965
+ console.log(JSON.stringify({ maps: rows }));
19966
+ return;
19967
+ }
19968
+ if (rows.length === 0) {
19969
+ console.log(" No maps yet. Run: iamu explore <url>");
19970
+ return;
19971
+ }
19972
+ for (const r of rows) {
19973
+ console.log(
19974
+ ` ${r.id} ${r.surfaces} surfaces ${chalk26.dim(r.target_url)}`
19975
+ );
19976
+ }
19977
+ }
19978
+ async function exploreCoverageCommand(mapId, opts) {
19979
+ const ctx = await resolveProject(opts);
19980
+ if (!ctx) return;
19981
+ const id = await resolveMapId(ctx.projectRoot, mapId);
19982
+ if (!id) {
19983
+ fail2("No map yet. Run: iamu explore <url>", opts.json);
19984
+ return;
19985
+ }
19986
+ let map2;
19987
+ try {
19988
+ map2 = await readExploreMap(ctx.projectRoot, id);
19989
+ } catch (err) {
19990
+ fail2(err.message, opts.json);
19991
+ return;
19992
+ }
19993
+ const cov = exploreCoverage(map2, await readAllRunSteps(ctx.projectRoot));
19994
+ if (opts.json) {
19995
+ console.log(JSON.stringify(cov));
19996
+ return;
19997
+ }
19998
+ console.log(
19999
+ `
20000
+ Map ${id} (${map2.surfaces.length} surfaces) against ${cov.runs} runs, ${cov.steps} steps`
20001
+ );
20002
+ console.log(
20003
+ ` ${chalk26.green(`${cov.reached} reached`)} \xB7 ${chalk26.yellow(`${cov.place_only} URL reached, view unclear`)} \xB7 ${chalk26.red(`${cov.unreached} never reached`)}`
20004
+ );
20005
+ const section = (title, rows, color) => {
20006
+ if (rows.length === 0) return;
20007
+ console.log(`
20008
+ ${color(title)}`);
20009
+ for (const r of rows) {
20010
+ console.log(
20011
+ ` ${r.key.padEnd(32)} ${r.label}${chalk26.dim(` (depth ${r.depth})`)}`
20012
+ );
20013
+ }
20014
+ };
20015
+ section(
20016
+ "Never reached by a run",
20017
+ cov.surfaces.filter((s) => s.status === "unreached"),
20018
+ chalk26.red
20019
+ );
20020
+ section(
20021
+ "URL reached, but no step names this view",
20022
+ cov.surfaces.filter((s) => s.status === "place_only"),
20023
+ chalk26.yellow
20024
+ );
20025
+ if (cov.run_only_places.length > 0) {
20026
+ console.log(`
20027
+ ${chalk26.cyan("Walked by runs, not in the map")}`);
20028
+ for (const p of cov.run_only_places) {
20029
+ console.log(
20030
+ ` ${p.place.padEnd(32)} ${p.steps} steps, ${p.runs.length} runs`
20031
+ );
20032
+ }
20033
+ }
20034
+ if (cov.runs === 0) {
20035
+ console.log(
20036
+ chalk26.dim(
20037
+ "\n No runs yet \u2014 every surface counts as never reached. Walk a scenario first (iamu-run)."
20038
+ )
20039
+ );
20040
+ }
20041
+ }
20042
+ async function exploreDiffCommand(a, b, opts) {
20043
+ const ctx = await resolveProject(opts);
20044
+ if (!ctx) return;
20045
+ const idA = a ?? await resolveMapId(ctx.projectRoot, void 0, 2);
20046
+ const idB = b ?? await resolveMapId(ctx.projectRoot, void 0, 1);
20047
+ if (!idA || !idB || idA === idB) {
20048
+ fail2(
20049
+ "Need two maps to compare \u2014 explore again (iamu explore <url>), or name both maps.",
20050
+ opts.json
20051
+ );
20052
+ return;
20053
+ }
20054
+ let mapA;
20055
+ let mapB;
20056
+ try {
20057
+ mapA = await readExploreMap(ctx.projectRoot, idA);
20058
+ mapB = await readExploreMap(ctx.projectRoot, idB);
20059
+ } catch (err) {
20060
+ fail2(err.message, opts.json);
20061
+ return;
20062
+ }
20063
+ const d = diffExploreMaps(mapA, mapB);
20064
+ if (opts.json) {
20065
+ console.log(JSON.stringify(d));
20066
+ return;
20067
+ }
20068
+ const pct = Math.round(d.surface_overlap * 100);
20069
+ console.log(`
20070
+ ${idA} \u2192 ${idB}`);
20071
+ console.log(
20072
+ ` ${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)`
20073
+ );
20074
+ if (d.only_a.length > 0) {
20075
+ console.log(`
20076
+ ${chalk26.red("Only in the first")}`);
20077
+ for (const s of d.only_a) console.log(` ${s.key.padEnd(32)} ${s.label}`);
20078
+ }
20079
+ if (d.only_b.length > 0) {
20080
+ console.log(`
20081
+ ${chalk26.green("Only in the second")}`);
20082
+ for (const s of d.only_b) console.log(` ${s.key.padEnd(32)} ${s.label}`);
20083
+ }
20084
+ if (d.changed_actions.length > 0) {
20085
+ console.log(
20086
+ `
20087
+ ${chalk26.yellow(`Controls differ on ${d.changed_actions.length} shared surfaces`)}`
20088
+ );
20089
+ for (const c of d.changed_actions) {
20090
+ console.log(` ${c.key} ${c.label}`);
20091
+ for (const x of c.only_a) console.log(chalk26.red(` - ${x}`));
20092
+ for (const x of c.only_b) console.log(chalk26.green(` + ${x}`));
20093
+ }
20094
+ }
20095
+ }
20096
+ var DEFAULTS;
20097
+ var init_explore = __esm({
20098
+ "src/commands/explore.ts"() {
20099
+ "use strict";
20100
+ init_dist2();
20101
+ init_build_info();
20102
+ init_browse();
20103
+ init_context4();
20104
+ init_explore_crawl();
20105
+ DEFAULTS = { max_surfaces: 40, max_actions: 300, max_depth: 6 };
20106
+ }
20107
+ });
20108
+
18489
20109
  // src/commands/completion.ts
18490
20110
  var completion_exports = {};
18491
20111
  __export(completion_exports, {
@@ -18676,13 +20296,14 @@ var init_completion = __esm({
18676
20296
  var instructions_walk_exports = {};
18677
20297
  __export(instructions_walk_exports, {
18678
20298
  composeWalkerPrompt: () => composeWalkerPrompt,
20299
+ extractUntestableBoundaries: () => extractUntestableBoundaries,
18679
20300
  instructionsWalk: () => instructionsWalk,
18680
20301
  revisitSection: () => revisitSection,
18681
20302
  viewportLine: () => viewportLine
18682
20303
  });
18683
20304
  import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
18684
20305
  import { join as join9 } from "path";
18685
- import chalk26 from "chalk";
20306
+ import chalk27 from "chalk";
18686
20307
  function selfCommand() {
18687
20308
  const q = (p) => /[\s"'$]/.test(p) ? JSON.stringify(p) : p;
18688
20309
  return `${q(process.execPath)} ${q(process.argv[1] ?? "iamu")}`;
@@ -18713,6 +20334,24 @@ function browseSurface(iamu, viewport) {
18713
20334
  ...viewport ? [viewportLine(viewport)] : []
18714
20335
  ].join("\n");
18715
20336
  }
20337
+ function extractUntestableBoundaries(body) {
20338
+ const match2 = body.match(
20339
+ /##\s+Untestable\s+Boundaries[^\n]*\n([\s\S]*?)(?=\n##|\s*$)/i
20340
+ );
20341
+ if (!match2) return [];
20342
+ const section = match2[1];
20343
+ const items = [];
20344
+ for (const line of section.split("\n")) {
20345
+ const trimmed = line.trim();
20346
+ if (trimmed.startsWith("- ") || trimmed.startsWith("\u2022 ") || trimmed.startsWith("* ")) {
20347
+ const item = trimmed.slice(2).trim();
20348
+ if (item && !item.toLowerCase().includes("none documented")) {
20349
+ items.push(item);
20350
+ }
20351
+ }
20352
+ }
20353
+ return items;
20354
+ }
18716
20355
  function guidanceLine(r) {
18717
20356
  const rule = r.rule.replace(/\s+/g, " ");
18718
20357
  return r.reason ? `- ${rule} (${r.reason.replace(/\s+/g, " ")})` : `- ${rule}`;
@@ -18785,6 +20424,23 @@ function composeWalkerPrompt(p) {
18785
20424
  "",
18786
20425
  "## First-person brief",
18787
20426
  p.brief ?? "",
20427
+ ...p.context ? ["", "## Product domain & regulatory context", p.context.trim()] : [],
20428
+ ...p.state?.description || p.state?.approximation ? [
20429
+ "",
20430
+ "## Expected state & semantic squinting",
20431
+ p.state.description ? `Expected state: ${p.state.description}` : "",
20432
+ p.state.approximation ? `Approximation strategy: ${p.state.approximation}` : "",
20433
+ "Squinting Guardrails:",
20434
+ "\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.",
20435
+ "\u2022 Squinting does NOT permit drifting to unrelated settings, account profiles, or off-target pages. Stay strictly within the scenario journey.",
20436
+ "\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."
20437
+ ].filter(Boolean) : [],
20438
+ ...p.untestableBoundaries && p.untestableBoundaries.length > 0 ? [
20439
+ "",
20440
+ "## Untestable Boundaries (DO NOT WALK)",
20441
+ "These areas are un-mocked or live external services. Do NOT attempt to navigate, submit, or interact with them:",
20442
+ ...p.untestableBoundaries.map((b) => `\u2022 ${b}`)
20443
+ ] : [],
18788
20444
  ...revisitSection(p.revisit ?? []),
18789
20445
  "",
18790
20446
  "## Access",
@@ -18813,7 +20469,7 @@ function composeWalkerPrompt(p) {
18813
20469
  `Screenshots to: ${rd}/screenshots/step-N.png \u2014 absolute paths.`,
18814
20470
  `Record findings with: ${iamu} new-finding --scenario ${p.scenarioSlug} --run ${p.runId} --step <N> --title "..." --type <helps|hinders|missing|irrelevant|incorrect> --severity <critical|major|minor|observation> --ui-fact "..." --consequence "..." --task-impact <blocks|impairs|slows|neutral|accelerates|enables> --confidence <high|medium|low> --json \u2014 then add the body (description + "## Recommendation") below the frontmatter of the file it returns.`,
18815
20471
  "`--step <N>` is the step whose screenshot shows the problem (or strength): it is the evidence people see next to the finding, so record that step and its screenshot first, and pick the screenshot where the thing is actually visible.",
18816
- `Before you finish, write ${rd}/summary.json: {"what_worked": [...], "open_questions": [...]} \u2014 one sentence each, in the persona's language.`,
20472
+ `Before you finish, write ${rd}/summary.json: {"what_worked": [...], "open_questions": [...], "model": "<your model identifier if known, e.g. claude-3-5-haiku, gemini-3.8-flash, or unspecified>"} \u2014 one sentence each, in the persona's language.`,
18817
20473
  "",
18818
20474
  "Do not read the product's source code, other iamu/ entity files, or earlier runs: you know only what the persona knows.",
18819
20475
  "",
@@ -18923,6 +20579,25 @@ async function instructionsWalk(opts) {
18923
20579
  if (!targetUrl) {
18924
20580
  targetUrl = sfm.target_url ?? null;
18925
20581
  }
20582
+ const untestableBoundaries = [];
20583
+ try {
20584
+ const testabilityPath = join9(projectRoot, "iamu", "testability.md");
20585
+ if (existsSync12(testabilityPath)) {
20586
+ const raw = readFileSync8(testabilityPath, "utf-8");
20587
+ const { frontmatter, body } = parseFrontmatter(raw);
20588
+ if (!targetUrl && typeof frontmatter.target_url === "string" && frontmatter.target_url.trim().length > 0) {
20589
+ targetUrl = frontmatter.target_url.trim();
20590
+ }
20591
+ const fromFm = Array.isArray(
20592
+ frontmatter.untestable_boundaries
20593
+ ) ? frontmatter.untestable_boundaries.filter(Boolean) : [];
20594
+ const fromBody = extractUntestableBoundaries(body);
20595
+ untestableBoundaries.push(
20596
+ ...Array.from(/* @__PURE__ */ new Set([...fromFm, ...fromBody]))
20597
+ );
20598
+ }
20599
+ } catch {
20600
+ }
18926
20601
  if (!revisit) {
18927
20602
  try {
18928
20603
  revisit = await computeRevisitPlaces(projectRoot, sfm.slug, targetUrl);
@@ -18933,7 +20608,8 @@ async function instructionsWalk(opts) {
18933
20608
  const result = {
18934
20609
  scenario: {
18935
20610
  slug: sfm.slug,
18936
- brief: scenario.body.trim() || null
20611
+ brief: scenario.body.trim() || null,
20612
+ state: sfm.state ?? null
18937
20613
  },
18938
20614
  persona: {
18939
20615
  slug: sfm.persona,
@@ -18954,6 +20630,7 @@ async function instructionsWalk(opts) {
18954
20630
  methodology,
18955
20631
  dismissed_findings: dismissedFacts,
18956
20632
  tester_guidance: guidance,
20633
+ untestable_boundaries: untestableBoundaries,
18957
20634
  // Where fixes awaiting a re-run were seen (for you; the walker gets
18958
20635
  // the places only, never which findings they belong to).
18959
20636
  revisit,
@@ -18987,7 +20664,10 @@ async function instructionsWalk(opts) {
18987
20664
  runDirPath,
18988
20665
  projectRoot,
18989
20666
  surface: opts.surface ?? "browse",
18990
- viewport
20667
+ viewport,
20668
+ state: sfm.state ?? null,
20669
+ context: contextYml,
20670
+ untestableBoundaries
18991
20671
  }) : null
18992
20672
  };
18993
20673
  if (opts.json) {
@@ -18995,30 +20675,40 @@ async function instructionsWalk(opts) {
18995
20675
  return;
18996
20676
  }
18997
20677
  console.log();
18998
- console.log(chalk26.bold(" Walk instructions"));
18999
- console.log(` ${chalk26.dim("scenario:")} ${sfm.slug}`);
19000
- console.log(` ${chalk26.dim("persona:")} ${pfm.name} (${pfm.role})`);
19001
- console.log(` ${chalk26.dim("outline:")} ${ofm.title}`);
19002
- if (targetUrl) console.log(` ${chalk26.dim("target:")} ${targetUrl}`);
20678
+ console.log(chalk27.bold(" Walk instructions"));
20679
+ console.log(` ${chalk27.dim("scenario:")} ${sfm.slug}`);
20680
+ console.log(` ${chalk27.dim("persona:")} ${pfm.name} (${pfm.role})`);
20681
+ console.log(` ${chalk27.dim("outline:")} ${ofm.title}`);
20682
+ if (sfm.state?.description || sfm.state?.approximation) {
20683
+ console.log(
20684
+ ` ${chalk27.dim("state:")} ${sfm.state.description ? sfm.state.description : sfm.state.approximation ?? ""}`
20685
+ );
20686
+ }
20687
+ if (targetUrl) console.log(` ${chalk27.dim("target:")} ${targetUrl}`);
19003
20688
  console.log(
19004
- ` ${chalk26.dim("access:")} ${projectAccess || personaAccess ? `${projectAccess ? "project" : ""}${projectAccess && personaAccess ? " + " : ""}${personaAccess ? "persona" : ""}` : "not recorded (walker would run as whoever is signed in)"}`
20689
+ ` ${chalk27.dim("access:")} ${projectAccess || personaAccess ? `${projectAccess ? "project" : ""}${projectAccess && personaAccess ? " + " : ""}${personaAccess ? "persona" : ""}` : "not recorded (walker would run as whoever is signed in)"}`
19005
20690
  );
19006
20691
  console.log(
19007
- ` ${chalk26.dim("dismissed:")} ${dismissedFacts.length} findings to skip`
20692
+ ` ${chalk27.dim("dismissed:")} ${dismissedFacts.length} findings to skip`
19008
20693
  );
19009
20694
  console.log(
19010
- ` ${chalk26.dim("guidance:")} ${guidance.length} tester guidance rule${guidance.length === 1 ? "" : "s"}`
20695
+ ` ${chalk27.dim("guidance:")} ${guidance.length} tester guidance rule${guidance.length === 1 ? "" : "s"}`
19011
20696
  );
20697
+ if (untestableBoundaries.length > 0) {
20698
+ console.log(
20699
+ ` ${chalk27.dim("boundaries:")} ${untestableBoundaries.length} untestable boundaries to avoid`
20700
+ );
20701
+ }
19012
20702
  if (revisit.length > 0) {
19013
20703
  console.log(
19014
- ` ${chalk26.dim("re-check:")} ${revisit.length} place${revisit.length === 1 ? "" : "s"} where fixes await a re-run`
20704
+ ` ${chalk27.dim("re-check:")} ${revisit.length} place${revisit.length === 1 ? "" : "s"} where fixes await a re-run`
19015
20705
  );
19016
20706
  }
19017
20707
  console.log(
19018
- ` ${chalk26.dim("methodology:")} ${methodology ? "loaded" : "missing (run iamu update)"}`
20708
+ ` ${chalk27.dim("methodology:")} ${methodology ? "loaded" : "missing (run iamu update)"}`
19019
20709
  );
19020
20710
  console.log();
19021
- console.log(chalk26.dim(" Use --json to get the full instruction bundle."));
20711
+ console.log(chalk27.dim(" Use --json to get the full instruction bundle."));
19022
20712
  console.log();
19023
20713
  }
19024
20714
  var SCREEN;
@@ -19044,8 +20734,8 @@ __export(new_finding_exports, {
19044
20734
  });
19045
20735
  import { randomUUID as randomUUID10 } from "crypto";
19046
20736
  import { access, readFile as readFile6 } from "fs/promises";
19047
- import path24 from "path";
19048
- import chalk27 from "chalk";
20737
+ import path26 from "path";
20738
+ import chalk28 from "chalk";
19049
20739
  async function newFinding(opts) {
19050
20740
  const ctx = await resolveProject(opts);
19051
20741
  if (!ctx) return;
@@ -19111,7 +20801,7 @@ async function newFinding(opts) {
19111
20801
  }
19112
20802
  try {
19113
20803
  await access(
19114
- path24.join(runDirPath, "steps", `${String(n).padStart(3, "0")}.json`)
20804
+ path26.join(runDirPath, "steps", `${String(n).padStart(3, "0")}.json`)
19115
20805
  );
19116
20806
  } catch {
19117
20807
  commandError(
@@ -19126,7 +20816,7 @@ async function newFinding(opts) {
19126
20816
  if (!scenarioSlug) {
19127
20817
  try {
19128
20818
  const manifestRaw = await readFile6(
19129
- path24.join(runDirPath, "walkthrough.json"),
20819
+ path26.join(runDirPath, "walkthrough.json"),
19130
20820
  "utf8"
19131
20821
  );
19132
20822
  const manifest = JSON.parse(manifestRaw);
@@ -19215,10 +20905,10 @@ async function newFinding(opts) {
19215
20905
  return;
19216
20906
  }
19217
20907
  console.log();
19218
- console.log(` ${symbols.success} Created finding: ${chalk27.cyan(slug)}`);
19219
- console.log(` ${chalk27.dim("path:")} ${record.path}`);
19220
- console.log(` ${chalk27.dim("id:")} ${id}`);
19221
- for (const w of warnings) console.log(` ${chalk27.yellow("!")} ${w}`);
20908
+ console.log(` ${symbols.success} Created finding: ${chalk28.cyan(slug)}`);
20909
+ console.log(` ${chalk28.dim("path:")} ${record.path}`);
20910
+ console.log(` ${chalk28.dim("id:")} ${id}`);
20911
+ for (const w of warnings) console.log(` ${chalk28.yellow("!")} ${w}`);
19222
20912
  console.log();
19223
20913
  } catch (err) {
19224
20914
  commandError(
@@ -19243,8 +20933,8 @@ __export(view_results_exports, {
19243
20933
  openBrowserResults: () => openBrowserResults,
19244
20934
  promptViewResults: () => promptViewResults
19245
20935
  });
19246
- import { isCancel as isCancel4, select as select3 } from "@clack/prompts";
19247
- import chalk28 from "chalk";
20936
+ import { isCancel as isCancel4, select as select4 } from "@clack/prompts";
20937
+ import chalk29 from "chalk";
19248
20938
  function launchCloudDashboard(config, runId) {
19249
20939
  const cloudRunId = runId ? toCloudRunUuid(runId) : void 0;
19250
20940
  const url = getCloudDashboardUrl(
@@ -19253,7 +20943,7 @@ function launchCloudDashboard(config, runId) {
19253
20943
  cloudRunId
19254
20944
  );
19255
20945
  console.log();
19256
- console.log(` ${symbols.success} Opening Iamu Cloud: ${chalk28.cyan(url)}
20946
+ console.log(` ${symbols.success} Opening Iamu Cloud: ${chalk29.cyan(url)}
19257
20947
  `);
19258
20948
  openBrowser(url);
19259
20949
  }
@@ -19289,7 +20979,7 @@ async function promptViewResults(projectRoot, opts) {
19289
20979
  },
19290
20980
  { label: "Skip", value: "skip" }
19291
20981
  ];
19292
- const choice = await select3({
20982
+ const choice = await select4({
19293
20983
  message: "View results?",
19294
20984
  options
19295
20985
  });
@@ -19321,9 +21011,9 @@ __export(evaluate_exports, {
19321
21011
  evaluate: () => evaluate,
19322
21012
  evaluateRecord: () => evaluateRecord
19323
21013
  });
19324
- import { promises as fs25 } from "fs";
19325
- import path25 from "path";
19326
- import chalk29 from "chalk";
21014
+ import { promises as fs29 } from "fs";
21015
+ import path27 from "path";
21016
+ import chalk30 from "chalk";
19327
21017
  import { z as z25 } from "zod";
19328
21018
  function formatIssues(error) {
19329
21019
  return error.issues.map(
@@ -19353,10 +21043,10 @@ async function evaluate(runId, opts) {
19353
21043
  const resolved = await resolveProjectAndRunDir(runId, opts);
19354
21044
  if (!resolved) return;
19355
21045
  const { projectRoot, runDirPath } = resolved;
19356
- const canonicalPath = path25.join(runDirPath, "canonical.json");
21046
+ const canonicalPath = path27.join(runDirPath, "canonical.json");
19357
21047
  let canonical;
19358
21048
  try {
19359
- const raw = await fs25.readFile(canonicalPath, "utf8");
21049
+ const raw = await fs29.readFile(canonicalPath, "utf8");
19360
21050
  canonical = JSON.parse(raw);
19361
21051
  } catch {
19362
21052
  commandError(
@@ -19412,8 +21102,21 @@ async function evaluate(runId, opts) {
19412
21102
  )
19413
21103
  };
19414
21104
  const prompt = composeJudgePrompt(input);
21105
+ const promptFilePath = opts?.promptFile ?? path27.join(runDirPath, "judge_prompt.md");
21106
+ await fs29.writeFile(promptFilePath, prompt, "utf8");
19415
21107
  if (opts?.json) {
19416
- console.log(JSON.stringify({ ...input, judge_prompt: prompt }, null, 2));
21108
+ console.log(
21109
+ JSON.stringify(
21110
+ {
21111
+ ...input,
21112
+ prompt_file: path27.relative(projectRoot, promptFilePath),
21113
+ prompt_file_abs: path27.resolve(promptFilePath),
21114
+ judge_prompt: prompt
21115
+ },
21116
+ null,
21117
+ 2
21118
+ )
21119
+ );
19417
21120
  return;
19418
21121
  }
19419
21122
  console.log(prompt);
@@ -19422,10 +21125,10 @@ async function evaluateRecord(runId, opts) {
19422
21125
  const resolved = await resolveProjectAndRunDir(runId, opts);
19423
21126
  if (!resolved) return;
19424
21127
  const { projectRoot, runDirPath } = resolved;
19425
- const canonicalPath = path25.join(runDirPath, "canonical.json");
21128
+ const canonicalPath = path27.join(runDirPath, "canonical.json");
19426
21129
  let canonical;
19427
21130
  try {
19428
- const raw = await fs25.readFile(canonicalPath, "utf8");
21131
+ const raw = await fs29.readFile(canonicalPath, "utf8");
19429
21132
  canonical = JSON.parse(raw);
19430
21133
  } catch {
19431
21134
  commandError(`No canonical.json for run: ${runId}`, opts);
@@ -19433,7 +21136,7 @@ async function evaluateRecord(runId, opts) {
19433
21136
  }
19434
21137
  let parsedFile;
19435
21138
  try {
19436
- const raw = await fs25.readFile(opts.file, "utf8");
21139
+ const raw = await fs29.readFile(opts.file, "utf8");
19437
21140
  parsedFile = JSON.parse(raw);
19438
21141
  } catch {
19439
21142
  commandError(`Cannot read verdicts file: ${opts.file}`, opts);
@@ -19490,8 +21193,8 @@ ${formatIssues(fileResult.error)}`,
19490
21193
  overall: inputVerdicts.overall
19491
21194
  });
19492
21195
  const reportContent = renderCanonicalMarkdown(canonicalForReport);
19493
- const verdictsPath = path25.join(runDirPath, "verdicts.json");
19494
- const reportPath = path25.join(runDirPath, "report.md");
21196
+ const verdictsPath = path27.join(runDirPath, "verdicts.json");
21197
+ const reportPath = path27.join(runDirPath, "report.md");
19495
21198
  await Promise.all([
19496
21199
  atomicWriteFile(
19497
21200
  verdictsPath,
@@ -19505,14 +21208,14 @@ ${formatIssues(fileResult.error)}`,
19505
21208
  runId,
19506
21209
  validatedVerdicts
19507
21210
  );
19508
- const cloudInfo = await pushRunToCloud(projectRoot, runId);
21211
+ const cloudInfo = opts?.push === false ? void 0 : await pushRunToCloud(projectRoot, runId);
19509
21212
  if (opts?.json) {
19510
21213
  console.log(
19511
21214
  JSON.stringify(
19512
21215
  {
19513
21216
  runId,
19514
- verdictsPath: path25.relative(projectRoot, verdictsPath),
19515
- reportPath: path25.relative(projectRoot, reportPath),
21217
+ verdictsPath: path27.relative(projectRoot, verdictsPath),
21218
+ reportPath: path27.relative(projectRoot, reportPath),
19516
21219
  verdictsCount: validatedVerdicts.length,
19517
21220
  linkedCount: linked.length,
19518
21221
  ...cloudInfo ? { cloud: cloudInfo } : {}
@@ -19525,18 +21228,18 @@ ${formatIssues(fileResult.error)}`,
19525
21228
  }
19526
21229
  console.log();
19527
21230
  console.log(
19528
- ` ${symbols.success} Recorded ${validatedVerdicts.length} verdicts for run ${chalk29.cyan(runId)}`
21231
+ ` ${symbols.success} Recorded ${validatedVerdicts.length} verdicts for run ${chalk30.cyan(runId)}`
19529
21232
  );
19530
21233
  if (linked.length > 0) {
19531
21234
  console.log(
19532
- ` ${chalk29.dim("linked:")} ${linked.length} finding${linked.length === 1 ? "" : "s"} to the human test${linked.length === 1 ? "" : "s"} citing ${linked.length === 1 ? "it" : "them"}`
21235
+ ` ${chalk30.dim("linked:")} ${linked.length} finding${linked.length === 1 ? "" : "s"} to the human test${linked.length === 1 ? "" : "s"} citing ${linked.length === 1 ? "it" : "them"}`
19533
21236
  );
19534
21237
  }
19535
21238
  console.log(
19536
- ` ${chalk29.dim("verdicts:")} ${path25.relative(projectRoot, verdictsPath)}`
21239
+ ` ${chalk30.dim("verdicts:")} ${path27.relative(projectRoot, verdictsPath)}`
19537
21240
  );
19538
21241
  console.log(
19539
- ` ${chalk29.dim("report:")} ${path25.relative(projectRoot, reportPath)}`
21242
+ ` ${chalk30.dim("report:")} ${path27.relative(projectRoot, reportPath)}`
19540
21243
  );
19541
21244
  printCloudRunInfo(cloudInfo);
19542
21245
  await promptViewResults(projectRoot, { ...opts, runId });
@@ -19600,9 +21303,9 @@ __export(complete_exports, {
19600
21303
  screenshotSizeWarning: () => screenshotSizeWarning,
19601
21304
  validateCanonicalWalkthrough: () => validateCanonicalWalkthrough
19602
21305
  });
19603
- import { promises as fs26 } from "fs";
19604
- import path26 from "path";
19605
- import chalk30 from "chalk";
21306
+ import { promises as fs30 } from "fs";
21307
+ import path28 from "path";
21308
+ import chalk31 from "chalk";
19606
21309
  async function complete(runId, opts) {
19607
21310
  const run = await loadRun(runId, opts);
19608
21311
  if (!run) return;
@@ -19612,7 +21315,7 @@ async function complete(runId, opts) {
19612
21315
  }
19613
21316
  if (!opts?.json) {
19614
21317
  console.log();
19615
- console.log(chalk30.bold(" Assembling canonical JSON\u2026"));
21318
+ console.log(chalk31.bold(" Assembling canonical JSON\u2026"));
19616
21319
  }
19617
21320
  const provenanceAudit = auditStepProvenance(
19618
21321
  await readStepsForRun(run.projectRoot, runId)
@@ -19622,6 +21325,36 @@ async function complete(runId, opts) {
19622
21325
  return;
19623
21326
  }
19624
21327
  const completedAt = pickCompletedAt(run.manifest);
21328
+ const walkerSummary = await readWalkerSummary(run.runDirPath);
21329
+ let judgeReported;
21330
+ try {
21331
+ const verdictsRaw = await fs30.readFile(
21332
+ path28.join(run.runDirPath, "verdicts.json"),
21333
+ "utf8"
21334
+ );
21335
+ const parsed = JSON.parse(verdictsRaw);
21336
+ if (parsed.provenance?.model) {
21337
+ judgeReported = parsed.provenance.model;
21338
+ }
21339
+ } catch {
21340
+ }
21341
+ const stepCount = provenanceAudit.step_count;
21342
+ const durationSec = provenanceAudit.duration_seconds ?? (run.manifest.created_at && Date.parse(completedAt) > Date.parse(run.manifest.created_at) ? Math.round(
21343
+ (Date.parse(completedAt) - Date.parse(run.manifest.created_at)) / 1e3 * 10
21344
+ ) / 10 : null);
21345
+ const velocitySec = provenanceAudit.avg_step_interval_seconds ?? (durationSec != null && stepCount > 1 ? Math.round(durationSec / (stepCount - 1) * 10) / 10 : null);
21346
+ const existingModels = run.manifest.models ?? {};
21347
+ const updatedModels = {
21348
+ ...existingModels,
21349
+ ...walkerSummary.model ? { walker_reported: walkerSummary.model } : {},
21350
+ ...judgeReported ? { judge_reported: judgeReported } : {},
21351
+ ...durationSec != null ? { total_duration_sec: durationSec } : {},
21352
+ ...velocitySec != null ? { velocity_sec_per_step: velocitySec } : {}
21353
+ };
21354
+ run.manifest = {
21355
+ ...run.manifest,
21356
+ models: updatedModels
21357
+ };
19625
21358
  const assembled = await assembleRun(
19626
21359
  run.projectRoot,
19627
21360
  runId,
@@ -19648,16 +21381,16 @@ async function loadRun(runId, opts) {
19648
21381
  if (!resolved) return null;
19649
21382
  const { projectRoot, runDirPath } = resolved;
19650
21383
  try {
19651
- await fs26.access(runDirPath);
21384
+ await fs30.access(runDirPath);
19652
21385
  } catch {
19653
21386
  commandError(`Run not found: ${runId}`, opts);
19654
21387
  return null;
19655
21388
  }
19656
- const manifestPath = path26.join(runDirPath, "walkthrough.json");
21389
+ const manifestPath = path28.join(runDirPath, "walkthrough.json");
19657
21390
  let manifest;
19658
21391
  try {
19659
21392
  manifest = JSON.parse(
19660
- await fs26.readFile(manifestPath, "utf8")
21393
+ await fs30.readFile(manifestPath, "utf8")
19661
21394
  );
19662
21395
  } catch {
19663
21396
  commandError(`No walkthrough.json found for run: ${runId}`, opts);
@@ -19669,8 +21402,8 @@ async function reportAlreadyCompleted(run, opts) {
19669
21402
  const { runId, manifest } = run;
19670
21403
  if (opts?.json) {
19671
21404
  try {
19672
- const raw = await fs26.readFile(
19673
- path26.join(run.runDirPath, "canonical.json"),
21405
+ const raw = await fs30.readFile(
21406
+ path28.join(run.runDirPath, "canonical.json"),
19674
21407
  "utf8"
19675
21408
  );
19676
21409
  console.log(raw.trim());
@@ -19690,18 +21423,18 @@ async function reportAlreadyCompleted(run, opts) {
19690
21423
  return;
19691
21424
  }
19692
21425
  console.log();
19693
- console.log(chalk30.bold(" Run: ") + chalk30.cyan(runId));
19694
- console.log(` status ${chalk30.green("completed")}`);
21426
+ console.log(chalk31.bold(" Run: ") + chalk31.cyan(runId));
21427
+ console.log(` status ${chalk31.green("completed")}`);
19695
21428
  if (manifest.completed_at) {
19696
- console.log(` completed ${chalk30.dim(manifest.completed_at)}`);
21429
+ console.log(` completed ${chalk31.dim(manifest.completed_at)}`);
19697
21430
  }
19698
21431
  const viewUrl = await pushedRunUrl(run.projectRoot, runId);
19699
21432
  if (viewUrl) {
19700
- console.log(` view ${chalk30.underline(viewUrl)}`);
21433
+ console.log(` view ${chalk31.underline(viewUrl)}`);
19701
21434
  }
19702
21435
  console.log();
19703
21436
  console.log(
19704
- chalk30.dim(
21437
+ chalk31.dim(
19705
21438
  " Already completed \u2014 nothing to do. Use --force to reassemble the report."
19706
21439
  )
19707
21440
  );
@@ -19717,13 +21450,13 @@ function reportStrictProvenanceFailure(audit, opts) {
19717
21450
  }
19718
21451
  console.error();
19719
21452
  console.error(
19720
- chalk30.red(` ${symbols.error} Strict provenance audit failed:`)
21453
+ chalk31.red(` ${symbols.error} Strict provenance audit failed:`)
19721
21454
  );
19722
21455
  for (const w of audit.warnings) {
19723
- console.error(chalk30.red(` \u2022 ${w}`));
21456
+ console.error(chalk31.red(` \u2022 ${w}`));
19724
21457
  }
19725
21458
  console.error(
19726
- chalk30.dim(
21459
+ chalk31.dim(
19727
21460
  " Run was flagged as synthetic / scripted. Remove --strict-provenance to complete anyway."
19728
21461
  )
19729
21462
  );
@@ -19749,7 +21482,7 @@ async function collectCompletionExtras(run, opts) {
19749
21482
  parseRunViewport(manifest.viewport)
19750
21483
  );
19751
21484
  const codeWarning = opts?.force ? null : codeChangedWarning(manifest.build, detectWorktreeState(projectRoot));
19752
- const cloudInfo = await pushRunToCloud(projectRoot, runId);
21485
+ const cloudInfo = opts?.push === false ? void 0 : await pushRunToCloud(projectRoot, runId);
19753
21486
  return { revisit, viewportWarning, codeWarning, cloudInfo };
19754
21487
  }
19755
21488
  function completeJsonPayload(canonical, extras) {
@@ -19764,30 +21497,41 @@ function completeJsonPayload(canonical, extras) {
19764
21497
  function printCompletionSummary(run, completedAt, assembled, extras, provenanceAudit) {
19765
21498
  const { runId, projectRoot, manifest } = run;
19766
21499
  const { canonical } = assembled;
19767
- const count = (n) => n === 0 ? chalk30.dim("0") : chalk30.cyan(String(n));
19768
- console.log(chalk30.bold(" Run: ") + chalk30.cyan(runId));
19769
- console.log(` scenario ${chalk30.dim(manifest.scenario_slug)}`);
19770
- console.log(` url ${chalk30.dim(manifest.target_url)}`);
21500
+ const count = (n) => n === 0 ? chalk31.dim("0") : chalk31.cyan(String(n));
21501
+ console.log(chalk31.bold(" Run: ") + chalk31.cyan(runId));
21502
+ console.log(` scenario ${chalk31.dim(manifest.scenario_slug)}`);
21503
+ console.log(` url ${chalk31.dim(manifest.target_url)}`);
19771
21504
  if (manifest.environment) {
19772
- console.log(` env ${chalk30.dim(manifest.environment)}`);
21505
+ console.log(` env ${chalk31.dim(manifest.environment)}`);
19773
21506
  }
19774
21507
  if (manifest.build) {
19775
- console.log(` build ${chalk30.dim(formatBuild(manifest.build))}`);
21508
+ console.log(` build ${chalk31.dim(formatBuild(manifest.build))}`);
19776
21509
  }
19777
21510
  if (manifest.viewport) {
19778
- console.log(` device ${chalk30.dim(formatViewport(manifest.viewport))}`);
21511
+ console.log(` device ${chalk31.dim(formatViewport(manifest.viewport))}`);
19779
21512
  }
19780
- console.log(` status ${chalk30.green("completed")}`);
21513
+ console.log(` status ${chalk31.green("completed")}`);
19781
21514
  console.log(` findings ${count(canonical.findings.length)}`);
19782
21515
  console.log(` steps ${count(canonical.steps.length)}`);
19783
- console.log(` started ${chalk30.dim(manifest.created_at)}`);
19784
- console.log(` completed ${chalk30.dim(completedAt)}`);
21516
+ console.log(` started ${chalk31.dim(manifest.created_at)}`);
21517
+ console.log(` completed ${chalk31.dim(completedAt)}`);
21518
+ const models = manifest.models;
21519
+ if (models?.velocity_sec_per_step != null) {
21520
+ console.log(
21521
+ ` velocity ${chalk31.cyan(`~${models.velocity_sec_per_step}s/step`)} ${chalk31.dim(`(${models.total_duration_sec}s total)`)}`
21522
+ );
21523
+ }
21524
+ if (models && (models.walker || models.judge || models.matcher)) {
21525
+ const w = models.walker_reported ? `${models.walker} (${models.walker_reported})` : models.walker ?? "fast";
21526
+ const j = models.judge_reported ? `${models.judge} (${models.judge_reported})` : models.judge ?? "thorough";
21527
+ console.log(` models ${chalk31.dim(`walker: ${w} \xB7 judge: ${j}`)}`);
21528
+ }
19785
21529
  console.log();
19786
21530
  console.log(
19787
- ` ${chalk30.dim("canonical:")} ${chalk30.dim(path26.relative(projectRoot, assembled.canonicalPath))}`
21531
+ ` ${chalk31.dim("canonical:")} ${chalk31.dim(path28.relative(projectRoot, assembled.canonicalPath))}`
19788
21532
  );
19789
21533
  console.log(
19790
- ` ${chalk30.dim("report:")} ${chalk30.dim(path26.relative(projectRoot, assembled.reportPath))}`
21534
+ ` ${chalk31.dim("report:")} ${chalk31.dim(path28.relative(projectRoot, assembled.reportPath))}`
19791
21535
  );
19792
21536
  if (extras.viewportWarning) {
19793
21537
  console.log();
@@ -19795,7 +21539,7 @@ function printCompletionSummary(run, completedAt, assembled, extras, provenanceA
19795
21539
  }
19796
21540
  if (extras.codeWarning) {
19797
21541
  console.log();
19798
- console.log(` ${chalk30.yellow("!")} ${extras.codeWarning}`);
21542
+ console.log(` ${chalk31.yellow("!")} ${extras.codeWarning}`);
19799
21543
  }
19800
21544
  if (extras.revisit) printRevisit(extras.revisit);
19801
21545
  if (assembled.verification) printVerification(assembled.verification);
@@ -19807,13 +21551,13 @@ function printProvenanceWarnings(audit) {
19807
21551
  if (audit.warnings.length === 0) return;
19808
21552
  console.log();
19809
21553
  console.log(
19810
- ` ${symbols.warning} ${chalk30.yellow("Step provenance audit flagged suspicious timing:")}`
21554
+ ` ${symbols.warning} ${chalk31.yellow("Step provenance audit flagged suspicious timing:")}`
19811
21555
  );
19812
21556
  for (const w of audit.warnings) {
19813
- console.log(` ${chalk30.dim("\u2022")} ${w}`);
21557
+ console.log(` ${chalk31.dim("\u2022")} ${w}`);
19814
21558
  }
19815
21559
  console.log(
19816
- chalk30.dim(
21560
+ chalk31.dim(
19817
21561
  " Run appears to be programmatically batch-generated rather than an interactive walkthrough."
19818
21562
  )
19819
21563
  );
@@ -19845,17 +21589,17 @@ function screenshotFitsViewport(width, expected) {
19845
21589
  }
19846
21590
  async function screenshotSizeWarning(runDirPath, viewport) {
19847
21591
  if (!viewport) return null;
19848
- const dir = path26.join(runDirPath, "screenshots");
21592
+ const dir = path28.join(runDirPath, "screenshots");
19849
21593
  let first;
19850
21594
  try {
19851
- first = (await fs26.readdir(dir)).filter((f) => f.toLowerCase().endsWith(".png")).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }))[0];
21595
+ first = (await fs30.readdir(dir)).filter((f) => f.toLowerCase().endsWith(".png")).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }))[0];
19852
21596
  } catch {
19853
21597
  return null;
19854
21598
  }
19855
21599
  if (!first) return null;
19856
21600
  let size = null;
19857
21601
  try {
19858
- const handle = await fs26.open(path26.join(dir, first), "r");
21602
+ const handle = await fs30.open(path28.join(dir, first), "r");
19859
21603
  try {
19860
21604
  const head = Buffer.alloc(24);
19861
21605
  const { bytesRead } = await handle.read(head, 0, 24, 0);
@@ -19872,12 +21616,14 @@ async function screenshotSizeWarning(runDirPath, viewport) {
19872
21616
  async function readWalkerSummary(runDirPath) {
19873
21617
  try {
19874
21618
  const data = JSON.parse(
19875
- await fs26.readFile(path26.join(runDirPath, "summary.json"), "utf8")
21619
+ await fs30.readFile(path28.join(runDirPath, "summary.json"), "utf8")
19876
21620
  );
19877
21621
  const strings = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && x.trim() !== "") : void 0;
21622
+ const model = typeof data.model === "string" && data.model.trim() !== "" ? data.model.trim() : void 0;
19878
21623
  return {
19879
21624
  what_worked: strings(data.what_worked),
19880
- open_questions: strings(data.open_questions)
21625
+ open_questions: strings(data.open_questions),
21626
+ model
19881
21627
  };
19882
21628
  } catch {
19883
21629
  return {};
@@ -19979,7 +21725,7 @@ function validateCanonicalWalkthrough(canonical) {
19979
21725
  async function applyRecordedVerdicts(canonical, runDirPath) {
19980
21726
  try {
19981
21727
  const recorded = JSON.parse(
19982
- await fs26.readFile(path26.join(runDirPath, "verdicts.json"), "utf8")
21728
+ await fs30.readFile(path28.join(runDirPath, "verdicts.json"), "utf8")
19983
21729
  );
19984
21730
  if (Array.isArray(recorded.verdicts)) {
19985
21731
  return applyVerdicts(canonical, recorded);
@@ -19989,7 +21735,7 @@ async function applyRecordedVerdicts(canonical, runDirPath) {
19989
21735
  return canonical;
19990
21736
  }
19991
21737
  async function persistCanonical(runDirPath, canonical) {
19992
- const canonicalPath = path26.join(runDirPath, "canonical.json");
21738
+ const canonicalPath = path28.join(runDirPath, "canonical.json");
19993
21739
  await atomicWriteFile(
19994
21740
  canonicalPath,
19995
21741
  `${JSON.stringify(canonical, null, 2)}
@@ -19998,7 +21744,7 @@ async function persistCanonical(runDirPath, canonical) {
19998
21744
  return canonicalPath;
19999
21745
  }
20000
21746
  async function persistReport(runDirPath, canonical) {
20001
- const reportPath = path26.join(runDirPath, "report.md");
21747
+ const reportPath = path28.join(runDirPath, "report.md");
20002
21748
  await atomicWriteFile(reportPath, renderCanonicalMarkdown(canonical));
20003
21749
  return reportPath;
20004
21750
  }
@@ -20098,17 +21844,17 @@ function placeLabel(p) {
20098
21844
  function printRevisit(r) {
20099
21845
  const missed = r.places.filter((p) => !p.reached);
20100
21846
  console.log();
20101
- const head = ` ${chalk30.bold("Re-check places:")} reached ${r.reached} of ${r.requested}`;
21847
+ const head = ` ${chalk31.bold("Re-check places:")} reached ${r.reached} of ${r.requested}`;
20102
21848
  if (missed.length === 0) {
20103
- console.log(`${head} ${chalk30.green("\u2713")}`);
21849
+ console.log(`${head} ${chalk31.green("\u2713")}`);
20104
21850
  return;
20105
21851
  }
20106
21852
  console.log(`${head} \u2014 not reached:`);
20107
21853
  for (const p of missed) {
20108
- console.log(` ${chalk30.yellow("\u25CB")} ${placeLabel(p)}`);
21854
+ console.log(` ${chalk31.yellow("\u25CB")} ${placeLabel(p)}`);
20109
21855
  }
20110
21856
  console.log(
20111
- chalk30.dim(
21857
+ chalk31.dim(
20112
21858
  " Fixes there stay awaiting a re-run until a walk gets there."
20113
21859
  )
20114
21860
  );
@@ -20116,7 +21862,7 @@ function printRevisit(r) {
20116
21862
  function printVerification(verification) {
20117
21863
  console.log();
20118
21864
  console.log(
20119
- chalk30.bold(" Since this scenario's last run") + chalk30.dim(` (${verification.baseline_run_id.slice(0, 30)}\u2026)`) + chalk30.bold(":")
21865
+ chalk31.bold(" Since this scenario's last run") + chalk31.dim(` (${verification.baseline_run_id.slice(0, 30)}\u2026)`) + chalk31.bold(":")
20120
21866
  );
20121
21867
  if (verification.resolved.length > 0) {
20122
21868
  console.log(
@@ -20125,7 +21871,7 @@ function printVerification(verification) {
20125
21871
  }
20126
21872
  if (verification.not_observed.length > 0) {
20127
21873
  console.log(
20128
- ` ${symbols.pending} ${verification.not_observed.length} not seen this run ${chalk30.dim("(not evidence of a fix)")}`
21874
+ ` ${symbols.pending} ${verification.not_observed.length} not seen this run ${chalk31.dim("(not evidence of a fix)")}`
20129
21875
  );
20130
21876
  }
20131
21877
  if (verification.persists.length > 0) {
@@ -20166,14 +21912,14 @@ __export(match_exports, {
20166
21912
  match: () => match,
20167
21913
  matchRecord: () => matchRecord
20168
21914
  });
20169
- import { promises as fs27 } from "fs";
20170
- import path27 from "path";
20171
- import chalk31 from "chalk";
21915
+ import { promises as fs31 } from "fs";
21916
+ import path29 from "path";
21917
+ import chalk32 from "chalk";
20172
21918
  async function completedManifest2(runDirPath, runId, opts) {
20173
21919
  let manifest;
20174
21920
  try {
20175
21921
  manifest = JSON.parse(
20176
- await fs27.readFile(path27.join(runDirPath, "walkthrough.json"), "utf8")
21922
+ await fs31.readFile(path29.join(runDirPath, "walkthrough.json"), "utf8")
20177
21923
  );
20178
21924
  } catch {
20179
21925
  commandError(`Run not found: ${runId}`, opts);
@@ -20202,15 +21948,34 @@ async function match(runId, opts) {
20202
21948
  );
20203
21949
  const needed = input.current.length > 0 && (input.prior.length > 0 || (input.guidance ?? []).length > 0);
20204
21950
  const prompt = needed ? composeMatchPrompt(input) : null;
21951
+ const promptFilePath = opts?.promptFile ?? path29.join(runDirPath, "match_prompt.md");
21952
+ if (prompt) {
21953
+ await fs31.writeFile(promptFilePath, prompt, "utf8");
21954
+ } else {
21955
+ try {
21956
+ await fs31.unlink(promptFilePath);
21957
+ } catch {
21958
+ }
21959
+ }
20205
21960
  if (opts?.json) {
20206
21961
  console.log(
20207
- JSON.stringify({ ...input, needed, match_prompt: prompt }, null, 2)
21962
+ JSON.stringify(
21963
+ {
21964
+ ...input,
21965
+ needed,
21966
+ prompt_file: prompt ? path29.relative(ctx.projectRoot, promptFilePath) : null,
21967
+ prompt_file_abs: prompt ? path29.resolve(promptFilePath) : null,
21968
+ match_prompt: prompt
21969
+ },
21970
+ null,
21971
+ 2
21972
+ )
20208
21973
  );
20209
21974
  return;
20210
21975
  }
20211
21976
  if (!prompt) {
20212
21977
  console.log(
20213
- chalk31.dim(
21978
+ chalk32.dim(
20214
21979
  " No earlier findings or tester guidance for this scenario to match against \u2014 nothing to do."
20215
21980
  )
20216
21981
  );
@@ -20226,7 +21991,7 @@ async function matchRecord(runId, opts) {
20226
21991
  if (!manifest) return;
20227
21992
  let answer;
20228
21993
  try {
20229
- answer = JSON.parse(await fs27.readFile(opts.file, "utf8"));
21994
+ answer = JSON.parse(await fs31.readFile(opts.file, "utf8"));
20230
21995
  } catch {
20231
21996
  commandError(`Cannot read matches file: ${opts.file}`, opts);
20232
21997
  return;
@@ -20267,17 +22032,17 @@ async function matchRecord(runId, opts) {
20267
22032
  }
20268
22033
  console.log();
20269
22034
  console.log(
20270
- ` ${chalk31.green("\u2713")} Matched ${result.linked} finding${result.linked === 1 ? "" : "s"} to earlier ones, ${result.new} first seen, ${result.rechecked_fixed} earlier re-checked fixed`
22035
+ ` ${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`
20271
22036
  );
20272
22037
  if (result.suppressed > 0) {
20273
22038
  console.log(
20274
- ` ${chalk31.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)`
22039
+ ` ${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)`
20275
22040
  );
20276
22041
  }
20277
22042
  if (verification) printVerification(verification);
20278
22043
  console.log();
20279
22044
  console.log(
20280
- chalk31.dim(" Run `iamu sync` to push the linked findings to the cloud.")
22045
+ chalk32.dim(" Run `iamu sync` to push the linked findings to the cloud.")
20281
22046
  );
20282
22047
  console.log();
20283
22048
  }
@@ -20298,7 +22063,7 @@ __export(prune_exports, {
20298
22063
  prune: () => prune
20299
22064
  });
20300
22065
  import { confirm, isCancel as isCancel5 } from "@clack/prompts";
20301
- import chalk32 from "chalk";
22066
+ import chalk33 from "chalk";
20302
22067
  function formatBytes(bytes) {
20303
22068
  if (bytes <= 0) return "0 B";
20304
22069
  if (bytes < 1024) return `${bytes} B`;
@@ -20344,7 +22109,7 @@ async function prune(opts = {}) {
20344
22109
  });
20345
22110
  if (preview.prunedRuns.length === 0) {
20346
22111
  console.log(
20347
- chalk32.dim(" No runs match the pruning criteria. Nothing to prune.")
22112
+ chalk33.dim(" No runs match the pruning criteria. Nothing to prune.")
20348
22113
  );
20349
22114
  return preview;
20350
22115
  }
@@ -20353,7 +22118,7 @@ async function prune(opts = {}) {
20353
22118
  message: `Prune ${mode} ${preview.prunedRuns.length} run(s) and reclaim ${formatBytes(preview.totalBytesReclaimed)}?`
20354
22119
  });
20355
22120
  if (isCancel5(confirmed) || !confirmed) {
20356
- console.log(chalk32.yellow(" Pruning cancelled."));
22121
+ console.log(chalk33.yellow(" Pruning cancelled."));
20357
22122
  return;
20358
22123
  }
20359
22124
  }
@@ -20364,14 +22129,14 @@ async function prune(opts = {}) {
20364
22129
  }
20365
22130
  if (result.dryRun) {
20366
22131
  console.log(
20367
- chalk32.cyan(
22132
+ chalk33.cyan(
20368
22133
  `
20369
22134
  [DRY RUN] Would prune ${result.prunedRuns.length} of ${result.totalRunsScanned} run(s):`
20370
22135
  )
20371
22136
  );
20372
22137
  } else {
20373
22138
  console.log(
20374
- chalk32.bold(
22139
+ chalk33.bold(
20375
22140
  `
20376
22141
  ${symbols.success} Pruned ${result.prunedRuns.length} of ${result.totalRunsScanned} run(s):`
20377
22142
  )
@@ -20379,18 +22144,18 @@ async function prune(opts = {}) {
20379
22144
  }
20380
22145
  if (result.prunedRuns.length === 0) {
20381
22146
  console.log(
20382
- chalk32.dim(" No runs matched the pruning criteria. Nothing pruned.")
22147
+ chalk33.dim(" No runs matched the pruning criteria. Nothing pruned.")
20383
22148
  );
20384
22149
  } else {
20385
22150
  for (const run of result.prunedRuns) {
20386
22151
  const mode = run.fullyDeleted ? "removed" : "screenshots purged";
20387
22152
  const count = run.screenshotsDeleted > 0 ? `${run.screenshotsDeleted} screenshots, ` : "";
20388
22153
  console.log(
20389
- ` ${chalk32.dim("\u2022")} ${chalk32.bold(run.runId)} ${chalk32.dim(`(${run.scenarioSlug ?? "unknown scenario"})`)}: ${chalk32.yellow(formatBytes(run.bytesReclaimed))} (${count}${mode})`
22154
+ ` ${chalk33.dim("\u2022")} ${chalk33.bold(run.runId)} ${chalk33.dim(`(${run.scenarioSlug ?? "unknown scenario"})`)}: ${chalk33.yellow(formatBytes(run.bytesReclaimed))} (${count}${mode})`
20390
22155
  );
20391
22156
  }
20392
22157
  console.log(
20393
- chalk32.dim(
22158
+ chalk33.dim(
20394
22159
  `
20395
22160
  Total reclaimed: ${formatBytes(result.totalBytesReclaimed)} (${result.totalScreenshotsDeleted} screenshots deleted)
20396
22161
  `
@@ -20413,13 +22178,13 @@ init_context4();
20413
22178
  import { existsSync as existsSync13, readFileSync as readFileSync9 } from "fs";
20414
22179
  import { dirname as dirname5, resolve as resolve3 } from "path";
20415
22180
  import { fileURLToPath as fileURLToPath3 } from "url";
20416
- import chalk35 from "chalk";
22181
+ import chalk36 from "chalk";
20417
22182
  import { Command } from "commander";
20418
22183
 
20419
22184
  // src/commands/register-cloud.ts
20420
22185
  import { Option } from "commander";
20421
22186
  function registerCloudCommands(program2) {
20422
- program2.command("sync").helpGroup("Cloud:").description("Push pending local changes to cloud").option("--status", "Show pending sync count without pushing").option(
22187
+ program2.command("sync").alias("push").helpGroup("Cloud:").description("Push pending local changes to cloud").option("--status", "Show pending sync count without pushing").option(
20423
22188
  "--keep-mine",
20424
22189
  "On conflict, overwrite the cloud version with the local one. CLI only \u2014 replacing a teammate's change is an explicit human action, so it is deliberately not exposed through the iamu_sync MCP tool."
20425
22190
  ).option(
@@ -20443,7 +22208,7 @@ function registerCloudCommands(program2) {
20443
22208
  "Connect to local cloud stack (default port: 3000)"
20444
22209
  ).addOption(
20445
22210
  new Option("-s, --staging", "Connect to staging cloud stack").hideHelp()
20446
- ).option("-t, --token <token>", "Authenticate directly with API token / key").option("--web-url <url>", "Custom web dashboard URL").option("--url <api-url>", "API URL (default: https://api.iamu.dev)").option("-y, --yes", "Accept default project without interactive selection").option(
22211
+ ).option("-t, --token <token>", "Authenticate directly with API token / key").option("--token-stdin", "Read API token from standard input").option("--web-url <url>", "Custom web dashboard URL").option("--url <api-url>", "API URL (default: https://app.iamu.dev)").option("-y, --yes", "Accept default project without interactive selection").option(
20447
22212
  "--project <project-id>",
20448
22213
  "Cloud project ID to link to, or `new` to create one. Required in a non-interactive shell when cloud projects exist"
20449
22214
  ).option("--no-browser", "Do not launch browser automatically").addHelpText(
@@ -20532,13 +22297,13 @@ function registerFindingsCommands(program2) {
20532
22297
 
20533
22298
  // src/commands/register-project.ts
20534
22299
  function registerProjectCommands(program2) {
20535
- program2.command("init").helpGroup("Project:").description("Scaffold iamu/ + install skills").argument("[name]", "Project name (defaults to directory name)").option("-y, --yes", "Skip prompts and accept all detected defaults").option("--json", "Output initialization metadata as JSON").addHelpText(
22300
+ program2.command("init").helpGroup("Project:").description("Scaffold iamu/ + install skills").argument("[name]", "Project name (defaults to directory name)").option("-y, --yes", "Skip prompts and accept all detected defaults").option("--cloud", "Connect to Iamu Cloud directly during init").option("--no-cloud", "Skip Iamu Cloud connection (local-only)").option("--json", "Output initialization metadata as JSON").addHelpText(
20536
22301
  "after",
20537
- "\nExamples:\n $ iamu init\n $ iamu init my-project -y\n $ iamu init --json"
22302
+ "\nExamples:\n $ iamu init\n $ iamu init my-project -y\n $ iamu init --cloud\n $ iamu init --json"
20538
22303
  ).action(
20539
22304
  async (name, opts) => {
20540
22305
  const { init: init2 } = await Promise.resolve().then(() => (init_init(), init_exports));
20541
- await init2({ name, yes: opts.yes, json: opts.json });
22306
+ await init2({ name, yes: opts.yes, json: opts.json, cloud: opts.cloud });
20542
22307
  }
20543
22308
  );
20544
22309
  program2.command("status").helpGroup("Project:").description("Show project state: entities, runs, context, cloud link").option("--json", "Output as JSON").addHelpText(
@@ -20549,10 +22314,13 @@ function registerProjectCommands(program2) {
20549
22314
  await status2(opts);
20550
22315
  });
20551
22316
  program2.command("doctor").helpGroup("Project:").description(
20552
- "Diagnose environment: runtime, browser automation, skills, cloud"
22317
+ "Diagnose environment: runtime, browser automation, skills, cloud, and target testability"
22318
+ ).option(
22319
+ "-t, --target <url>",
22320
+ "Probe target URL health, reachability, and auth redirects"
20553
22321
  ).option("--json", "Output diagnostics as JSON").addHelpText(
20554
22322
  "after",
20555
- "\nExamples:\n $ iamu doctor\n $ iamu doctor --json"
22323
+ "\nExamples:\n $ iamu doctor\n $ iamu doctor --target http://localhost:3000\n $ iamu doctor --json"
20556
22324
  ).action(async (opts) => {
20557
22325
  const { doctor: doctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
20558
22326
  await doctor2(opts);
@@ -20584,9 +22352,9 @@ function registerProjectCommands(program2) {
20584
22352
  "after",
20585
22353
  "\nExamples:\n $ iamu validate --all\n $ iamu validate --all --fix --dry-run\n $ iamu validate iamu/personas/maria.md"
20586
22354
  ).action(
20587
- async (path28, opts) => {
22355
+ async (path30, opts) => {
20588
22356
  const { validate: validate2 } = await Promise.resolve().then(() => (init_validate(), validate_exports));
20589
- await validate2(path28, opts);
22357
+ await validate2(path30, opts);
20590
22358
  }
20591
22359
  );
20592
22360
  program2.command("update").helpGroup("Project:").description("Update and repair agent skills and prompts to latest version").option(
@@ -20599,11 +22367,27 @@ function registerProjectCommands(program2) {
20599
22367
  program2.command("new <type>").helpGroup("Project:").description(
20600
22368
  "Create a new entity (persona, outline, capability, source, scenario)"
20601
22369
  ).requiredOption("--name <name>", "Entity name").option("--slug <slug>", "Custom entity slug (defaults to slugified name)").option("--role <role>", "Role description (persona only)").option("--disposition <text>", "Disposition / attitude (persona only)").option(
22370
+ "--access <role>",
22371
+ "Access role or testing account key (persona only)"
22372
+ ).option("--body <text>", "Markdown body / narrative content").option("--narrative <text>", "Alias for --body").option(
20602
22373
  "--description <text>",
20603
22374
  "One-line description (capability, outline)"
22375
+ ).option("--capability <slug>", "Primary capability slug (outline only)").option(
22376
+ "--tension <text>",
22377
+ "Structural tension driving this situation (outline only)"
22378
+ ).option(
22379
+ "--human-test <question>",
22380
+ "Human-test question evaluated at run completion (outline only, repeatable)",
22381
+ (val, prev = []) => [...prev, val]
20604
22382
  ).option("--outline <slug>", "Outline slug (scenario only)").option("--persona <slug>", "Persona slug (scenario only)").option(
20605
22383
  "--entry <path>",
20606
22384
  "Where the situation starts, as a path on the product, e.g. /en/episodes/1 (scenario only)"
22385
+ ).option(
22386
+ "--state-description <text>",
22387
+ "Assumed environment data/records state (scenario only)"
22388
+ ).option(
22389
+ "--state-approximation <text>",
22390
+ "Guidance on how persona should approximate/squint if records are missing (scenario only)"
20607
22391
  ).option("--url <url>", "Document the source cites (source only, required)").option(
20608
22392
  "--authority <level>",
20609
22393
  "legislation | regulation | standard | company_policy | guideline | informational (source only, default informational)"
@@ -20616,6 +22400,54 @@ function registerProjectCommands(program2) {
20616
22400
  await newEntity2(type2, opts);
20617
22401
  }
20618
22402
  );
22403
+ const explore = program2.command("explore").helpGroup("Project:").description(
22404
+ "Map the running product with a read-only crawl; compare the map with runs (coverage) or with another map (diff)"
22405
+ );
22406
+ explore.command("run [url]", { isDefault: true }).description(
22407
+ "Crawl from <url> (default: target_url in iamu/testability.md) and write iamu/.cache/explore/<id>/map.json"
22408
+ ).option("--max-surfaces <n>", "Stop after this many surfaces (default 40)").option("--max-actions <n>", "Stop after this many clicks (default 300)").option(
22409
+ "--max-depth <n>",
22410
+ "Don't go more than this many clicks from the start (default 6)"
22411
+ ).option(
22412
+ "--deny <pattern...>",
22413
+ "Never click a control whose name or link matches (regex, case-insensitive)"
22414
+ ).option("--viewport <WxH>", "Page size, e.g. 390x844 (default 1280x800)").option(
22415
+ "--attach",
22416
+ "Crawl in a new tab of the `iamu browse` browser, keeping its sign-ins"
22417
+ ).option("--json", "Output as JSON").addHelpText(
22418
+ "after",
22419
+ `
22420
+ Read-only: controls that read like writes (delete, pay, send, save, log out \u2026),
22421
+ form submits, downloads and other origins are never clicked, nothing is typed,
22422
+ and every request but GET/HEAD/OPTIONS is blocked before it leaves the browser.
22423
+
22424
+ Examples:
22425
+ $ iamu explore http://localhost:3000
22426
+ $ iamu explore --attach --deny "^Admin"
22427
+ $ iamu explore coverage
22428
+ $ iamu explore diff`
22429
+ ).action(
22430
+ async (url, opts) => {
22431
+ const { exploreRun: exploreRun2 } = await Promise.resolve().then(() => (init_explore(), explore_exports));
22432
+ await exploreRun2(url, opts);
22433
+ }
22434
+ );
22435
+ explore.command("coverage [map]").description(
22436
+ "Surfaces of a map (default: latest) that no run reached, and places runs reached that the map lacks"
22437
+ ).option("--json", "Output as JSON").action(async (map2, opts) => {
22438
+ const { exploreCoverageCommand: exploreCoverageCommand2 } = await Promise.resolve().then(() => (init_explore(), explore_exports));
22439
+ await exploreCoverageCommand2(map2, opts);
22440
+ });
22441
+ explore.command("diff [a] [b]").description("Compare two maps (default: the two latest)").option("--json", "Output as JSON").action(
22442
+ async (a, b, opts) => {
22443
+ const { exploreDiffCommand: exploreDiffCommand2 } = await Promise.resolve().then(() => (init_explore(), explore_exports));
22444
+ await exploreDiffCommand2(a, b, opts);
22445
+ }
22446
+ );
22447
+ explore.command("list").description("List maps").option("--json", "Output as JSON").action(async (opts) => {
22448
+ const { exploreList: exploreList2 } = await Promise.resolve().then(() => (init_explore(), explore_exports));
22449
+ await exploreList2(opts);
22450
+ });
20619
22451
  program2.command("completion [shell]").helpGroup("Project:").description("Output shell completion script (bash, zsh, fish)").action(async (shell) => {
20620
22452
  const { completion: completion2 } = await Promise.resolve().then(() => (init_completion(), completion_exports));
20621
22453
  completion2(program2, shell);
@@ -20645,6 +22477,15 @@ function registerWalkthroughCommands(program2) {
20645
22477
  ).option(
20646
22478
  "--env <name>",
20647
22479
  "Environment the product runs in (default: the target URL's origin)"
22480
+ ).option(
22481
+ "--walker <tierOrModel>",
22482
+ "Model tier ('fast', 'thorough', 'inherit') or model ID for driving browser navigation (default: fast)"
22483
+ ).option(
22484
+ "--judge <tierOrModel>",
22485
+ "Model tier ('fast', 'thorough', 'inherit') or model ID for human test evaluation (default: thorough)"
22486
+ ).option(
22487
+ "--matcher <tierOrModel>",
22488
+ "Model tier ('fast', 'thorough', 'inherit') or model ID for finding matching (default: thorough)"
20648
22489
  ).option(
20649
22490
  "--check",
20650
22491
  "Enforce target URL reachability (fail fast if server is offline)"
@@ -20675,7 +22516,10 @@ function registerWalkthroughCommands(program2) {
20675
22516
  ).option(
20676
22517
  "--run <run-id>",
20677
22518
  "Run ID to size browser (start) or record step into (step)"
20678
- ).option("--action <text>", "Action description (step only)").option("--note <text>", "Persona inner monologue / reasoning (step only)").option("--surface <name>", "Screen or surface name (step only)").option("--width <px>", "Window width (start)").option("--height <px>", "Window height (start)").option("--headless", "Run without a window (start)").option(
22519
+ ).option("--action <text>", "Action description (step only)").option("--note <text>", "Persona inner monologue / reasoning (step only)").option("--surface <name>", "Screen or surface name (step only)").option(
22520
+ "--step-file <path>",
22521
+ "Read step payload (action, note, cmd) from a JSON file or '-' for stdin (step only)"
22522
+ ).option("--width <px>", "Window width (start)").option("--height <px>", "Window height (start)").option("--headless", "Run without a window (start)").option(
20679
22523
  "--port <port>",
20680
22524
  "CDP port (start), default 9333",
20681
22525
  (value) => parsePortOption(value)
@@ -20751,9 +22595,9 @@ function registerWalkthroughCommands(program2) {
20751
22595
  ).option("-y, --yes", "Skip interactive prompts").option("--no-prompt", "Skip interactive prompt to view results").option("--view", "Open results in browser immediately without prompt").option(
20752
22596
  "--strict-provenance",
20753
22597
  "Fail if steps show synthetic or programmatic timing anomalies"
20754
- ).addHelpText(
22598
+ ).option("--no-push", "Skip pushing the run to Iamu Cloud").addHelpText(
20755
22599
  "after",
20756
- "\nExamples:\n $ iamu complete <run-id>\n $ iamu complete <run-id> --view\n $ iamu complete <run-id> --no-prompt\n $ iamu complete <run-id> --json\n $ iamu complete <run-id> --strict-provenance"
22600
+ "\nExamples:\n $ iamu complete <run-id>\n $ iamu complete <run-id> --view\n $ iamu complete <run-id> --no-prompt\n $ iamu complete <run-id> --no-push\n $ iamu complete <run-id> --json\n $ iamu complete <run-id> --strict-provenance"
20757
22601
  ).action(
20758
22602
  async (runId, opts) => {
20759
22603
  const { complete: complete2 } = await Promise.resolve().then(() => (init_complete(), complete_exports));
@@ -20762,23 +22606,27 @@ function registerWalkthroughCommands(program2) {
20762
22606
  );
20763
22607
  program2.command("match <run-id>").helpGroup("Walkthrough:").description(
20764
22608
  "Compose the prompt that links this run's findings to earlier ones"
20765
- ).option("--json", "Output the match input bundle as JSON").action(async (runId, opts) => {
20766
- const { match: match2 } = await Promise.resolve().then(() => (init_match(), match_exports));
20767
- await match2(runId, opts);
20768
- });
22609
+ ).option("--json", "Output the match input bundle as JSON").option("--prompt-file <path>", "Write the generated prompt to a file").action(
22610
+ async (runId, opts) => {
22611
+ const { match: match2 } = await Promise.resolve().then(() => (init_match(), match_exports));
22612
+ await match2(runId, opts);
22613
+ }
22614
+ );
20769
22615
  program2.command("match-record <run-id>").helpGroup("Walkthrough:").description(
20770
22616
  "Record finding matches and re-derive the run's since-last-run delta"
20771
22617
  ).requiredOption("--file <path>", "Path to matches JSON file").option("--json", "Output match results as JSON").action(async (runId, opts) => {
20772
22618
  const { matchRecord: matchRecord2 } = await Promise.resolve().then(() => (init_match(), match_exports));
20773
22619
  await matchRecord2(runId, opts);
20774
22620
  });
20775
- program2.command("evaluate <run-id>").helpGroup("Walkthrough:").description("Compose evaluation prompt for human-test questions").option("--json", "Output evaluation input bundle as JSON").action(async (runId, opts) => {
20776
- const { evaluate: evaluate2 } = await Promise.resolve().then(() => (init_evaluate(), evaluate_exports));
20777
- await evaluate2(runId, opts);
20778
- });
20779
- program2.command("evaluate-record <run-id>").helpGroup("Walkthrough:").description("Record evaluation verdicts into the run's verdicts.json").requiredOption("--file <path>", "Path to verdicts JSON file").option("--json", "Output summary as JSON").option("-y, --yes", "Skip interactive prompts").option("--no-prompt", "Skip interactive prompt to view results").option("--view", "Open results in browser immediately without prompt").addHelpText(
22621
+ program2.command("evaluate <run-id>").helpGroup("Walkthrough:").description("Compose evaluation prompt for human-test questions").option("--json", "Output evaluation input bundle as JSON").option("--prompt-file <path>", "Write the generated prompt to a file").action(
22622
+ async (runId, opts) => {
22623
+ const { evaluate: evaluate2 } = await Promise.resolve().then(() => (init_evaluate(), evaluate_exports));
22624
+ await evaluate2(runId, opts);
22625
+ }
22626
+ );
22627
+ program2.command("evaluate-record <run-id>").helpGroup("Walkthrough:").description("Record evaluation verdicts into the run's verdicts.json").requiredOption("--file <path>", "Path to verdicts JSON file").option("--json", "Output summary as JSON").option("-y, --yes", "Skip interactive prompts").option("--no-prompt", "Skip interactive prompt to view results").option("--no-push", "Skip pushing the updated run to Iamu Cloud").option("--view", "Open results in browser immediately without prompt").addHelpText(
20780
22628
  "after",
20781
- "\nExamples:\n $ iamu evaluate-record <run-id> --file verdicts.json\n $ iamu evaluate-record <run-id> --file verdicts.json --no-prompt\n $ iamu evaluate-record <run-id> --file verdicts.json --json"
22629
+ "\nExamples:\n $ iamu evaluate-record <run-id> --file verdicts.json\n $ iamu evaluate-record <run-id> --file verdicts.json --no-prompt\n $ iamu evaluate-record <run-id> --file verdicts.json --no-push\n $ iamu evaluate-record <run-id> --file verdicts.json --json"
20782
22630
  ).action(
20783
22631
  async (runId, opts) => {
20784
22632
  const { evaluateRecord: evaluateRecord2 } = await Promise.resolve().then(() => (init_evaluate(), evaluate_exports));
@@ -20807,23 +22655,23 @@ function registerAllCommands(program2) {
20807
22655
 
20808
22656
  // src/ui/panic.ts
20809
22657
  init_logger();
20810
- import chalk33 from "chalk";
22658
+ import chalk34 from "chalk";
20811
22659
  var handlersRegistered = false;
20812
22660
  var customSigintHandler = null;
20813
22661
  function logFatalAndExit(title, errOrReason) {
20814
22662
  process.stderr.write("\x1B[?25h");
20815
22663
  if (isVerbose()) {
20816
- console.error(chalk33.red(`
22664
+ console.error(chalk34.red(`
20817
22665
  ${title}:`));
20818
22666
  console.error(
20819
22667
  errOrReason instanceof Error ? errOrReason.stack || errOrReason : errOrReason
20820
22668
  );
20821
22669
  } else {
20822
22670
  const msg = errOrReason instanceof Error ? errOrReason.message : String(errOrReason);
20823
- console.error(chalk33.red(`
22671
+ console.error(chalk34.red(`
20824
22672
  Fatal Error: ${msg}`));
20825
22673
  console.error(
20826
- chalk33.dim(
22674
+ chalk34.dim(
20827
22675
  " Run with --verbose or DEBUG=1 to view the full stack trace.\n"
20828
22676
  )
20829
22677
  );
@@ -20842,7 +22690,7 @@ function setupProcessHandlers() {
20842
22690
  }
20843
22691
  process.exit(130);
20844
22692
  } else {
20845
- console.error(chalk33.dim("\n Operation cancelled."));
22693
+ console.error(chalk34.dim("\n Operation cancelled."));
20846
22694
  process.exit(130);
20847
22695
  }
20848
22696
  });
@@ -20868,7 +22716,7 @@ function setupProcessHandlers() {
20868
22716
  import { mkdir as mkdir7, readFile as readFile7, writeFile as writeFile5 } from "fs/promises";
20869
22717
  import { homedir as homedir4 } from "os";
20870
22718
  import { join as join10 } from "path";
20871
- import chalk34 from "chalk";
22719
+ import chalk35 from "chalk";
20872
22720
  var CACHE_FILE = join10(homedir4(), ".iamu", "update-check.json");
20873
22721
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
20874
22722
  function isSemverGreater(latest, current) {
@@ -20921,7 +22769,7 @@ async function checkForUpdate(currentVersion) {
20921
22769
  function printUpdateNotice(currentVersion, latestVersion) {
20922
22770
  console.error();
20923
22771
  console.error(
20924
- ` ${chalk34.yellow("Update available:")} ${chalk34.dim(currentVersion)} \u2192 ${chalk34.green(latestVersion)} ${chalk34.dim("(run: npm i -g @iamu-dev/iamu)")}`
22772
+ ` ${chalk35.yellow("Update available:")} ${chalk35.dim(currentVersion)} \u2192 ${chalk35.green(latestVersion)} ${chalk35.dim("(run: npm i -g @iamu-dev/iamu)")}`
20925
22773
  );
20926
22774
  }
20927
22775
 
@@ -20966,7 +22814,7 @@ var program = new Command().name("iamu").description(
20966
22814
  }
20967
22815
  }
20968
22816
  if (root.color === false || process.env.NO_COLOR) {
20969
- chalk35.level = 0;
22817
+ chalk36.level = 0;
20970
22818
  }
20971
22819
  if (root.verbose) {
20972
22820
  process.env.IAMU_VERBOSE = "1";