@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/index.js CHANGED
@@ -199,17 +199,24 @@ function defaultConfig(params) {
199
199
  cloud_api_url: null
200
200
  };
201
201
  }
202
- var baseProjectFields, localOnlyConfig, cloudLinkedConfig, projectConfigSchema;
202
+ var modelTierSchema, projectModelsConfigSchema, baseProjectFields, localOnlyConfig, cloudLinkedConfig, projectConfigSchema;
203
203
  var init_config = __esm({
204
204
  "../../packages/protocol/dist/schemas/frontmatter/config.js"() {
205
205
  "use strict";
206
206
  init_common();
207
207
  init_version();
208
+ modelTierSchema = z4.string().min(1).max(128).describe("Model role tier ('fast', 'thorough', 'inherit') or a specific model identifier.");
209
+ projectModelsConfigSchema = z4.object({
210
+ walker: modelTierSchema.optional(),
211
+ matcher: modelTierSchema.optional(),
212
+ judge: modelTierSchema.optional()
213
+ }).strict();
208
214
  baseProjectFields = {
209
215
  schema_version: z4.literal(SCHEMA_VERSION),
210
216
  id: uuidSchema,
211
217
  project_slug: slugSchema,
212
- project_name: z4.string().min(1)
218
+ project_name: z4.string().min(1),
219
+ models: projectModelsConfigSchema.optional()
213
220
  };
214
221
  localOnlyConfig = z4.object({
215
222
  ...baseProjectFields,
@@ -816,7 +823,14 @@ var init_scenario = __esm({
816
823
  reviews: z15.array(z15.string().min(1).max(64)).max(16).default([]),
817
824
  baseline_score: z15.number().min(0).max(100).nullish(),
818
825
  current_score: z15.number().min(0).max(100).nullish(),
819
- target_score: z15.number().min(0).max(100).default(95)
826
+ target_score: z15.number().min(0).max(100).default(95),
827
+ /** Environment state preconditions and approximation guidance. */
828
+ state: z15.object({
829
+ /** Description of data/records expected to exist in the environment. */
830
+ description: z15.string().max(2048).nullish(),
831
+ /** Guidance on how to squint or approximate if exact records are absent. */
832
+ approximation: z15.string().max(2048).nullish()
833
+ }).nullish()
820
834
  });
821
835
  }
822
836
  });
@@ -1153,7 +1167,21 @@ var init_walkthrough_bundle = __esm({
1153
1167
  judge_prompt: z19.string().optional(),
1154
1168
  judge_model: z19.string().optional()
1155
1169
  }).optional()
1156
- }).optional()
1170
+ }).optional(),
1171
+ models: z19.object({
1172
+ walker: z19.string().optional(),
1173
+ matcher: z19.string().optional(),
1174
+ judge: z19.string().optional(),
1175
+ walker_reported: z19.string().optional(),
1176
+ matcher_reported: z19.string().optional(),
1177
+ judge_reported: z19.string().optional(),
1178
+ walker_tier: z19.string().optional(),
1179
+ matcher_tier: z19.string().optional(),
1180
+ judge_tier: z19.string().optional(),
1181
+ execution_mode: z19.enum(["multi_agent", "single_agent"]).optional(),
1182
+ velocity_sec_per_step: z19.number().optional(),
1183
+ total_duration_sec: z19.number().optional()
1184
+ }).optional().describe("AI model configuration and reported runtime provenance for this walkthrough.")
1157
1185
  });
1158
1186
  walkthroughBundleSchema = z19.object({
1159
1187
  manifest: bundleManifestSchema,
@@ -1376,6 +1404,7 @@ var init_walkthrough_canonical = __esm({
1376
1404
  build: buildMetadataSchema.optional(),
1377
1405
  viewport: runViewportSchema.optional(),
1378
1406
  environment: z20.string().optional(),
1407
+ models: z20.record(z20.unknown()).optional(),
1379
1408
  findings: z20.array(canonicalFindingSchema),
1380
1409
  steps: z20.array(canonicalStepSchema),
1381
1410
  human_tests: z20.array(canonicalHumanTestSchema),
@@ -7247,6 +7276,7 @@ async function buildCanonical(projectRoot, manifest, completedAt, inputs) {
7247
7276
  ...manifest.build ? { build: manifest.build } : {},
7248
7277
  ...manifest.viewport ? { viewport: manifest.viewport } : {},
7249
7278
  ...manifest.environment ? { environment: manifest.environment } : {},
7279
+ ...manifest.models ? { models: manifest.models } : {},
7250
7280
  findings: canonicalFindings,
7251
7281
  steps: canonicalSteps,
7252
7282
  human_tests: humanTests,
@@ -7407,6 +7437,20 @@ function renderCanonicalMarkdown(canonical) {
7407
7437
  lines.push(`- Viewport: ${canonical.viewport.device} (${canonical.viewport.width}\xD7${canonical.viewport.height})`);
7408
7438
  }
7409
7439
  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}`);
7440
+ const models = canonical.models;
7441
+ if (models) {
7442
+ const parts = [
7443
+ models.walker ? `Walker: ${models.walker}${models.walker_reported ? ` (${models.walker_reported})` : ""}` : "",
7444
+ models.judge ? `Judge: ${models.judge}${models.judge_reported ? ` (${models.judge_reported})` : ""}` : "",
7445
+ models.matcher ? `Matcher: ${models.matcher}` : ""
7446
+ ].filter(Boolean);
7447
+ if (parts.length > 0) {
7448
+ lines.push(`- Models: ${parts.join(" \xB7 ")}`);
7449
+ }
7450
+ if (typeof models.velocity_sec_per_step === "number") {
7451
+ lines.push(`- Velocity: ~${models.velocity_sec_per_step}s/step (${models.total_duration_sec}s total)`);
7452
+ }
7453
+ }
7410
7454
  const what = canonical.what_worked;
7411
7455
  if (what && what.length > 0) {
7412
7456
  lines.push("", "## What worked");
@@ -7776,6 +7820,7 @@ async function assembleWalkthroughBundle(projectRoot, runId) {
7776
7820
  report_markdown: report3,
7777
7821
  verdict: verdictArray,
7778
7822
  judge_meta: judgeMeta,
7823
+ ...manifest.models ? { models: manifest.models } : {},
7779
7824
  ...buildFields(manifest),
7780
7825
  ...await readCanonicalExtras(projectRoot, runId)
7781
7826
  },
@@ -10442,9 +10487,275 @@ var init_prune = __esm({
10442
10487
  }
10443
10488
  });
10444
10489
 
10445
- // ../../packages/local-store/dist/human-test-link.js
10490
+ // ../../packages/local-store/dist/explore-map.js
10491
+ import { randomBytes as randomBytes2 } from "crypto";
10446
10492
  import { promises as fs19 } from "fs";
10447
10493
  import path15 from "path";
10494
+ function templateToken(token) {
10495
+ if (token === "")
10496
+ return token;
10497
+ if (/^\d+$/.test(token))
10498
+ return ":id";
10499
+ if (UUID.test(token))
10500
+ return ":id";
10501
+ if (DATE.test(token))
10502
+ return ":date";
10503
+ if (/^[0-9a-f]{8,}$/i.test(token) && /\d/.test(token))
10504
+ return ":id";
10505
+ if (token.length >= 16 && /\d/.test(token) && /[a-z]/i.test(token)) {
10506
+ return ":id";
10507
+ }
10508
+ return token;
10509
+ }
10510
+ function templatePath(pathname) {
10511
+ const trimmed = pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname || "/";
10512
+ return trimmed.split("/").map((seg) => templateToken(decodeSegment(seg))).join("/");
10513
+ }
10514
+ function encodeQueryValue(v) {
10515
+ return v.startsWith(":") ? v : encodeURIComponent(v);
10516
+ }
10517
+ function decodeSegment(seg) {
10518
+ try {
10519
+ return decodeURIComponent(seg);
10520
+ } catch {
10521
+ return seg;
10522
+ }
10523
+ }
10524
+ function explorePlaceKey(raw) {
10525
+ if (!raw)
10526
+ return null;
10527
+ let u;
10528
+ try {
10529
+ u = new URL(raw);
10530
+ } catch {
10531
+ return null;
10532
+ }
10533
+ if (u.protocol !== "http:" && u.protocol !== "https:")
10534
+ return null;
10535
+ const params = [...u.searchParams.entries()].filter(([k]) => !k.toLowerCase().startsWith("utm_")).map(([k, v]) => [k, templateToken(v)]).sort(([a, av], [b, bv]) => a.localeCompare(b) || av.localeCompare(bv));
10536
+ const query = params.map(([k, v]) => `${encodeURIComponent(k)}=${encodeQueryValue(v)}`).join("&");
10537
+ let key = templatePath(u.pathname);
10538
+ if (query)
10539
+ key += `?${query}`;
10540
+ const route = /^#!?(\/.*)$/.exec(u.hash);
10541
+ if (route) {
10542
+ const [hashPath] = route[1].split("?");
10543
+ key += `#${templatePath(hashPath)}`;
10544
+ }
10545
+ return key;
10546
+ }
10547
+ function exploreViewSignature(headings) {
10548
+ return headings.map((h) => h.trim().replace(/\s+/g, " ").replace(/\d+/g, "#").toLowerCase()).filter(Boolean).join(" | ");
10549
+ }
10550
+ function exploreSignature(headings, title) {
10551
+ return exploreViewSignature(headings) || title.trim().toLowerCase();
10552
+ }
10553
+ function matchExploreSurface(map2, url, signature) {
10554
+ const place = explorePlaceKey(url);
10555
+ if (!place)
10556
+ return null;
10557
+ const atPlace = map2.surfaces.filter((s) => s.place === place);
10558
+ return atPlace.find((s) => s.signature === signature) ?? (atPlace.length === 1 ? atPlace[0] : null);
10559
+ }
10560
+ function exploreSurfaceKey(place, view) {
10561
+ return view === null ? place : `${place}~${fnv1a(view)}`;
10562
+ }
10563
+ function fnv1a(s) {
10564
+ let h = 2166136261;
10565
+ for (let i = 0; i < s.length; i++) {
10566
+ h ^= s.charCodeAt(i);
10567
+ h = Math.imul(h, 16777619) >>> 0;
10568
+ }
10569
+ return h.toString(16).padStart(8, "0");
10570
+ }
10571
+ function exploreDir(projectRoot) {
10572
+ return path15.join(cacheDir(projectRoot), "explore");
10573
+ }
10574
+ function composeExploreId(now, randomHex) {
10575
+ const ts = now.toISOString().replace(/[:.]/g, "-").replace(/Z$/, "");
10576
+ return `${ts}-${randomHex ?? randomBytes2(3).toString("hex")}`;
10577
+ }
10578
+ function isValidExploreId(id) {
10579
+ return MAP_ID.test(id);
10580
+ }
10581
+ function mapPath(projectRoot, id) {
10582
+ if (!isValidExploreId(id))
10583
+ throw new Error(`Not an explore map id: ${id}`);
10584
+ return path15.join(exploreDir(projectRoot), id, "map.json");
10585
+ }
10586
+ async function writeExploreMap(projectRoot, map2) {
10587
+ const file = mapPath(projectRoot, map2.id);
10588
+ await fs19.mkdir(path15.dirname(file), { recursive: true });
10589
+ await atomicWriteFile(file, `${JSON.stringify(map2, null, 2)}
10590
+ `);
10591
+ return file;
10592
+ }
10593
+ async function readExploreMap(projectRoot, id) {
10594
+ const raw = await fs19.readFile(mapPath(projectRoot, id), "utf8");
10595
+ const map2 = JSON.parse(raw);
10596
+ if (map2?.schema !== EXPLORE_MAP_SCHEMA || !Array.isArray(map2.surfaces)) {
10597
+ throw new Error(`Unreadable explore map: ${id}`);
10598
+ }
10599
+ return map2;
10600
+ }
10601
+ async function listExploreMaps(projectRoot) {
10602
+ try {
10603
+ const entries = await fs19.readdir(exploreDir(projectRoot), {
10604
+ withFileTypes: true
10605
+ });
10606
+ return entries.filter((e) => e.isDirectory() && isValidExploreId(e.name)).map((e) => e.name).sort();
10607
+ } catch (err) {
10608
+ if (err.code === "ENOENT")
10609
+ return [];
10610
+ throw err;
10611
+ }
10612
+ }
10613
+ function exploreCoverage(map2, steps) {
10614
+ const byPlace = /* @__PURE__ */ new Map();
10615
+ for (const s of map2.surfaces) {
10616
+ byPlace.set(s.place, [...byPlace.get(s.place) ?? [], s]);
10617
+ }
10618
+ const reachedBy = /* @__PURE__ */ new Map();
10619
+ const unresolvedBy = /* @__PURE__ */ new Map();
10620
+ const runOnlyBy = /* @__PURE__ */ new Map();
10621
+ const add = (m, k, s) => m.set(k, [...m.get(k) ?? [], s]);
10622
+ for (const step of steps) {
10623
+ const place = explorePlaceKey(step.url);
10624
+ if (!place)
10625
+ continue;
10626
+ const atPlace = byPlace.get(place);
10627
+ if (!atPlace) {
10628
+ add(runOnlyBy, place, step);
10629
+ continue;
10630
+ }
10631
+ const hit = tieStep(map2, atPlace, step);
10632
+ if (hit.length === 0)
10633
+ add(unresolvedBy, place, step);
10634
+ for (const s of hit)
10635
+ add(reachedBy, s.key, step);
10636
+ }
10637
+ const surfaces = map2.surfaces.map((s) => {
10638
+ const base = {
10639
+ key: s.key,
10640
+ label: s.label,
10641
+ place: s.place,
10642
+ url: s.url,
10643
+ depth: s.depth
10644
+ };
10645
+ const reached = reachedBy.get(s.key);
10646
+ if (reached)
10647
+ return { ...base, status: "reached", runs: distinctRuns(reached) };
10648
+ const unresolved = unresolvedBy.get(s.place);
10649
+ if (unresolved) {
10650
+ return { ...base, status: "place_only", runs: distinctRuns(unresolved) };
10651
+ }
10652
+ return { ...base, status: "unreached", runs: [] };
10653
+ });
10654
+ const runOnly = [...runOnlyBy].map(([place, list2]) => ({
10655
+ place,
10656
+ steps: list2.length,
10657
+ runs: distinctRuns(list2)
10658
+ }));
10659
+ runOnly.sort((a, b) => b.steps - a.steps || a.place.localeCompare(b.place));
10660
+ return {
10661
+ map_id: map2.id,
10662
+ runs: new Set(steps.map((s) => s.run_id)).size,
10663
+ steps: steps.length,
10664
+ reached: surfaces.filter((s) => s.status === "reached").length,
10665
+ place_only: surfaces.filter((s) => s.status === "place_only").length,
10666
+ unreached: surfaces.filter((s) => s.status === "unreached").length,
10667
+ surfaces,
10668
+ run_only_places: runOnly
10669
+ };
10670
+ }
10671
+ function tieStep(map2, atPlace, step) {
10672
+ if (typeof step.signature === "string") {
10673
+ const m = matchExploreSurface(map2, step.url, step.signature);
10674
+ return m ? [m] : [];
10675
+ }
10676
+ if (atPlace.length === 1)
10677
+ return [atPlace[0]];
10678
+ const name = step.surface?.trim();
10679
+ if (!name)
10680
+ return [];
10681
+ return atPlace.filter((s) => [s.label, ...s.headings].filter(Boolean).some((n) => surfaceMatches(n, name) || surfaceMatches(name, n)));
10682
+ }
10683
+ function distinctRuns(steps) {
10684
+ return [...new Set(steps.map((s) => s.run_id))].sort();
10685
+ }
10686
+ async function readAllRunSteps(projectRoot) {
10687
+ const out = [];
10688
+ for (const runId of await listRuns(projectRoot)) {
10689
+ let steps;
10690
+ try {
10691
+ steps = await readStepsForRun(projectRoot, runId);
10692
+ } catch {
10693
+ continue;
10694
+ }
10695
+ for (const s of steps) {
10696
+ out.push({ run_id: runId, url: s.url, surface: s.surface });
10697
+ }
10698
+ }
10699
+ return out;
10700
+ }
10701
+ function actionSignature(a) {
10702
+ const where = a.outcome.kind === "surface" ? a.outcome.surface : a.outcome.kind === "skipped" ? `skipped:${a.outcome.reason}` : a.outcome.kind === "external" ? `external:${a.outcome.url}` : a.outcome.kind;
10703
+ return `${a.role} "${a.name}" \u2192 ${where}`;
10704
+ }
10705
+ function diffExploreMaps(a, b) {
10706
+ const aByKey = new Map(a.surfaces.map((s) => [s.key, s]));
10707
+ const bByKey = new Map(b.surfaces.map((s) => [s.key, s]));
10708
+ const ref = (s) => ({
10709
+ key: s.key,
10710
+ label: s.label
10711
+ });
10712
+ const onlyA = a.surfaces.filter((s) => !bByKey.has(s.key)).map(ref);
10713
+ const onlyB = b.surfaces.filter((s) => !aByKey.has(s.key)).map(ref);
10714
+ const changed = [];
10715
+ let common2 = 0;
10716
+ for (const sa of a.surfaces) {
10717
+ const sb = bByKey.get(sa.key);
10718
+ if (!sb)
10719
+ continue;
10720
+ common2++;
10721
+ const setA = new Set(sa.actions.map(actionSignature));
10722
+ const setB = new Set(sb.actions.map(actionSignature));
10723
+ const inA = [...setA].filter((x) => !setB.has(x)).sort();
10724
+ const inB = [...setB].filter((x) => !setA.has(x)).sort();
10725
+ if (inA.length > 0 || inB.length > 0) {
10726
+ changed.push({ ...ref(sa), only_a: inA, only_b: inB });
10727
+ }
10728
+ }
10729
+ const union = common2 + onlyA.length + onlyB.length;
10730
+ return {
10731
+ a: a.id,
10732
+ b: b.id,
10733
+ common: common2,
10734
+ only_a: onlyA,
10735
+ only_b: onlyB,
10736
+ surface_overlap: union === 0 ? 1 : common2 / union,
10737
+ changed_actions: changed
10738
+ };
10739
+ }
10740
+ var EXPLORE_MAP_SCHEMA, UUID, DATE, MAP_ID;
10741
+ var init_explore_map = __esm({
10742
+ "../../packages/local-store/dist/explore-map.js"() {
10743
+ "use strict";
10744
+ init_atomic();
10745
+ init_paths();
10746
+ init_revisit();
10747
+ init_runs();
10748
+ init_walkthrough_canonical2();
10749
+ EXPLORE_MAP_SCHEMA = 1;
10750
+ UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
10751
+ DATE = /^\d{4}-\d{2}-\d{2}$/;
10752
+ MAP_ID = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}-[0-9a-f]{6}$/;
10753
+ }
10754
+ });
10755
+
10756
+ // ../../packages/local-store/dist/human-test-link.js
10757
+ import { promises as fs20 } from "fs";
10758
+ import path16 from "path";
10448
10759
  function humanTestLinks(verdicts) {
10449
10760
  const best = /* @__PURE__ */ new Map();
10450
10761
  for (const v of verdicts) {
@@ -10467,7 +10778,7 @@ async function linkFindingsToHumanTests(projectRoot, runId, verdicts) {
10467
10778
  const dir = entityDirPath(projectRoot, "finding");
10468
10779
  let names;
10469
10780
  try {
10470
- names = await fs19.readdir(dir);
10781
+ names = await fs20.readdir(dir);
10471
10782
  } catch {
10472
10783
  return [];
10473
10784
  }
@@ -10475,11 +10786,11 @@ async function linkFindingsToHumanTests(projectRoot, runId, verdicts) {
10475
10786
  for (const name of names) {
10476
10787
  if (!name.endsWith(".md") || name.startsWith("."))
10477
10788
  continue;
10478
- const file = path15.join(dir, name);
10789
+ const file = path16.join(dir, name);
10479
10790
  let raw;
10480
10791
  let fm;
10481
10792
  try {
10482
- raw = await fs19.readFile(file, "utf8");
10793
+ raw = await fs20.readFile(file, "utf8");
10483
10794
  fm = parseFrontmatter(raw).frontmatter;
10484
10795
  } catch {
10485
10796
  continue;
@@ -10710,14 +11021,14 @@ var init_cloud_client = __esm({
10710
11021
  });
10711
11022
 
10712
11023
  // ../../packages/local-store/dist/last-pull.js
10713
- import { promises as fs20 } from "fs";
10714
- import path16 from "path";
11024
+ import { promises as fs21 } from "fs";
11025
+ import path17 from "path";
10715
11026
  function lastPullPath(projectRoot) {
10716
- return path16.join(cacheDir(projectRoot), "last-pull.json");
11027
+ return path17.join(cacheDir(projectRoot), "last-pull.json");
10717
11028
  }
10718
11029
  async function readLastPull(projectRoot) {
10719
11030
  try {
10720
- const raw = await fs20.readFile(lastPullPath(projectRoot), "utf8");
11031
+ const raw = await fs21.readFile(lastPullPath(projectRoot), "utf8");
10721
11032
  const parsed = JSON.parse(raw);
10722
11033
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
10723
11034
  return parsed;
@@ -11003,7 +11314,7 @@ var init_frontmatter2 = __esm({
11003
11314
  });
11004
11315
 
11005
11316
  // ../../packages/local-store/dist/pull/write.js
11006
- import { promises as fs21 } from "fs";
11317
+ import { promises as fs22 } from "fs";
11007
11318
  async function loadLocalIndex(projectRoot, type2) {
11008
11319
  const byId = /* @__PURE__ */ new Map();
11009
11320
  const bySlug = /* @__PURE__ */ new Map();
@@ -11032,7 +11343,7 @@ async function classifyExisting(projectRoot, type2, cloudId, localSlug, targetSl
11032
11343
  if (localSlug !== targetSlug)
11033
11344
  return "untracked";
11034
11345
  const lastSynced = localIndex.lastPushById[cloudId];
11035
- const bytes = await fs21.readFile(entityFilePath(projectRoot, type2, localSlug), "utf8");
11346
+ const bytes = await fs22.readFile(entityFilePath(projectRoot, type2, localSlug), "utf8");
11036
11347
  return lastSynced && hashNormalizedContent(bytes) === lastSynced ? "fast_forward" : "diverged";
11037
11348
  }
11038
11349
  async function writeOrSkip(projectRoot, type2, cloudId, slug, frontmatter, body, localIndex, mode, cloudVersion) {
@@ -11093,13 +11404,13 @@ async function writeOrSkip(projectRoot, type2, cloudId, slug, frontmatter, body,
11093
11404
  }
11094
11405
  if (existingSlugForId && existingSlugForId !== slug) {
11095
11406
  try {
11096
- await fs21.unlink(entityFilePath(projectRoot, type2, existingSlugForId));
11407
+ await fs22.unlink(entityFilePath(projectRoot, type2, existingSlugForId));
11097
11408
  localIndex.bySlug.delete(existingSlugForId);
11098
11409
  } catch {
11099
11410
  }
11100
11411
  }
11101
11412
  await writeEntity(projectRoot, type2, frontmatter, text);
11102
- const bytes = await fs21.readFile(entityFilePath(projectRoot, type2, slug), "utf8");
11413
+ const bytes = await fs22.readFile(entityFilePath(projectRoot, type2, slug), "utf8");
11103
11414
  const hash = hashNormalizedContent(bytes);
11104
11415
  localIndex.byId.set(cloudId, slug);
11105
11416
  localIndex.bySlug.set(slug, cloudId);
@@ -11153,7 +11464,7 @@ var init_write = __esm({
11153
11464
  });
11154
11465
 
11155
11466
  // ../../packages/local-store/dist/pull/entities.js
11156
- import { promises as fs22 } from "fs";
11467
+ import { promises as fs23 } from "fs";
11157
11468
  function buildContextFrontmatter(payload) {
11158
11469
  const cloudCtx = payload.context ?? {};
11159
11470
  const glossary = (payload.glossary ?? []).map((g) => ({
@@ -11208,7 +11519,7 @@ async function pullContext(projectRoot, cloud, projectId, mode, result) {
11208
11519
  const frontmatter = buildContextFrontmatter(payload);
11209
11520
  let hasLocal = false;
11210
11521
  try {
11211
- await fs22.access(contextPath(projectRoot));
11522
+ await fs23.access(contextPath(projectRoot));
11212
11523
  hasLocal = true;
11213
11524
  } catch {
11214
11525
  hasLocal = false;
@@ -11238,7 +11549,7 @@ async function pullContext(projectRoot, cloud, projectId, mode, result) {
11238
11549
  frontmatter,
11239
11550
  notes_body: existingNotes
11240
11551
  });
11241
- const bytes = await fs22.readFile(contextPath(projectRoot), "utf8");
11552
+ const bytes = await fs23.readFile(contextPath(projectRoot), "utf8");
11242
11553
  const hash = hashNormalizedContent(bytes);
11243
11554
  await recordLastPull(projectRoot, "context:root", hash);
11244
11555
  await recordLastPush(projectRoot, "context:root", hash);
@@ -11768,6 +12079,7 @@ var init_dist2 = __esm({
11768
12079
  init_prune();
11769
12080
  init_fix_state();
11770
12081
  init_revisit();
12082
+ init_explore_map();
11771
12083
  init_walkthrough_canonical2();
11772
12084
  init_journey_diff();
11773
12085
  init_finding_match();
@@ -11872,15 +12184,15 @@ function resolveApiUrl(configured) {
11872
12184
  assertSecureApiUrl(url, configured ? "API URL" : "IAMU_API_URL");
11873
12185
  return url;
11874
12186
  }
11875
- function parseStore(raw, path28) {
12187
+ function parseStore(raw, path30) {
11876
12188
  let parsed;
11877
12189
  try {
11878
12190
  parsed = JSON.parse(raw);
11879
12191
  } catch (err) {
11880
- throw new CredentialsFileError(path28, "not valid JSON", err);
12192
+ throw new CredentialsFileError(path30, "not valid JSON", err);
11881
12193
  }
11882
12194
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
11883
- throw new CredentialsFileError(path28, "expected a JSON object");
12195
+ throw new CredentialsFileError(path30, "expected a JSON object");
11884
12196
  }
11885
12197
  return parsed;
11886
12198
  }
@@ -11888,26 +12200,26 @@ function isNotFound(err) {
11888
12200
  return typeof err === "object" && err !== null && err.code === "ENOENT";
11889
12201
  }
11890
12202
  async function readCredentialsStore() {
11891
- const path28 = getCredentialsPath();
12203
+ const path30 = getCredentialsPath();
11892
12204
  let raw;
11893
12205
  try {
11894
- raw = await readFile(path28, "utf-8");
12206
+ raw = await readFile(path30, "utf-8");
11895
12207
  } catch (err) {
11896
12208
  if (isNotFound(err)) return {};
11897
12209
  warnUnreadableStoreOnce(err);
11898
12210
  throw err;
11899
12211
  }
11900
12212
  try {
11901
- return parseStore(raw, path28);
12213
+ return parseStore(raw, path30);
11902
12214
  } catch (err) {
11903
12215
  warnUnreadableStoreOnce(err);
11904
12216
  throw err;
11905
12217
  }
11906
12218
  }
11907
12219
  async function writeCredentialsStore(store) {
11908
- const path28 = getCredentialsPath();
11909
- await mkdir(dirname(path28), { recursive: true, mode: 448 });
11910
- await atomicWriteFile(path28, `${JSON.stringify(store, null, 2)}
12220
+ const path30 = getCredentialsPath();
12221
+ await mkdir(dirname(path30), { recursive: true, mode: 448 });
12222
+ await atomicWriteFile(path30, `${JSON.stringify(store, null, 2)}
11911
12223
  `, {
11912
12224
  mode: 384
11913
12225
  });
@@ -11927,16 +12239,16 @@ function resolveApiKey(apiUrl) {
11927
12239
  } catch {
11928
12240
  return null;
11929
12241
  }
11930
- const path28 = getCredentialsPath();
12242
+ const path30 = getCredentialsPath();
11931
12243
  let raw;
11932
12244
  try {
11933
- raw = readFileSync(path28, "utf-8");
12245
+ raw = readFileSync(path30, "utf-8");
11934
12246
  } catch (err) {
11935
12247
  if (!isNotFound(err)) warnUnreadableStoreOnce(err);
11936
12248
  return null;
11937
12249
  }
11938
12250
  try {
11939
- const entry = parseStore(raw, path28)[url];
12251
+ const entry = parseStore(raw, path30)[url];
11940
12252
  if (typeof entry !== "object" || entry === null) return null;
11941
12253
  const token = entry.token;
11942
12254
  return typeof token === "string" && token ? token : null;
@@ -11968,19 +12280,19 @@ var init_credentials = __esm({
11968
12280
  "use strict";
11969
12281
  init_dist2();
11970
12282
  init_dist();
11971
- DEFAULT_API_URL = "https://api.iamu.dev";
12283
+ DEFAULT_API_URL = "https://app.iamu.dev";
11972
12284
  CredentialsFileError = class extends IamuConfigError {
11973
12285
  /**
11974
12286
  * @param path - The unreadable credentials file.
11975
12287
  * @param reason - What is wrong with it.
11976
12288
  * @param cause - Underlying parse error, if any.
11977
12289
  */
11978
- constructor(path28, reason, cause) {
12290
+ constructor(path30, reason, cause) {
11979
12291
  super(
11980
- `Credentials file ${path28} is corrupt (${reason}). Fix it by hand, or delete it and run \`iamu auth login\` again.`,
12292
+ `Credentials file ${path30} is corrupt (${reason}). Fix it by hand, or delete it and run \`iamu auth login\` again.`,
11981
12293
  {
11982
12294
  cause,
11983
- suggestedAction: `Delete ${path28} and run \`iamu auth login\`, or set IAMU_TOKEN.`
12295
+ suggestedAction: `Delete ${path30} and run \`iamu auth login\`, or set IAMU_TOKEN.`
11984
12296
  }
11985
12297
  );
11986
12298
  }
@@ -12118,7 +12430,7 @@ async function resolveCloudProject(opts) {
12118
12430
  commandError("Not cloud-linked. Run `iamu auth login` to connect.", opts);
12119
12431
  return null;
12120
12432
  }
12121
- const apiUrl = ctx.config.cloud_api_url ?? "https://api.iamu.dev";
12433
+ const apiUrl = ctx.config.cloud_api_url ?? DEFAULT_API_URL;
12122
12434
  try {
12123
12435
  assertSecureApiUrl(apiUrl, "cloud_api_url");
12124
12436
  } catch (err) {
@@ -12266,15 +12578,6 @@ function mcpJson(cmd) {
12266
12578
  2
12267
12579
  );
12268
12580
  }
12269
- function vscodeMcpJson(cmd) {
12270
- return JSON.stringify(
12271
- {
12272
- servers: { iamu: { type: "stdio", command: cmd[0], args: cmd.slice(1) } }
12273
- },
12274
- null,
12275
- 2
12276
- );
12277
- }
12278
12581
  function detectHosts(projectRoot) {
12279
12582
  return HOST_ADAPTERS.filter(
12280
12583
  (host) => host.detectionPaths.some((p) => existsSync3(join3(projectRoot, p)))
@@ -12323,13 +12626,17 @@ var init_registry = __esm({
12323
12626
  {
12324
12627
  name: "GitHub Copilot",
12325
12628
  value: "github-copilot",
12326
- detectionPaths: [".vscode", ".github/copilot-instructions.md"],
12327
- supportsSkills: false,
12328
- getSkillsDir: () => "",
12329
- getCommandPath: (root, id) => join3(root, ".github", "prompts", `iamu-${id}.prompt.md`),
12330
- formatCommand: descOnlyFormat,
12331
- getMcpConfigPath: (root) => join3(root, ".vscode", "mcp.json"),
12332
- formatMcpConfig: vscodeMcpJson
12629
+ detectionPaths: [
12630
+ ".github/copilot-instructions.md",
12631
+ ".github/skills",
12632
+ ".copilot"
12633
+ ],
12634
+ supportsSkills: true,
12635
+ getSkillsDir: (root) => join3(root, ".github", "skills"),
12636
+ getCommandPath: (root, id) => join3(root, ".github", "skills", `iamu-${id}`, "SKILL.md"),
12637
+ formatCommand: richFormat,
12638
+ getMcpConfigPath: (root) => join3(root, ".mcp.json"),
12639
+ formatMcpConfig: mcpJson
12333
12640
  },
12334
12641
  {
12335
12642
  name: "Cline",
@@ -12367,10 +12674,10 @@ var init_registry = __esm({
12367
12674
  {
12368
12675
  name: "Antigravity",
12369
12676
  value: "antigravity",
12370
- detectionPaths: [".gemini", ".agent", "GEMINI.md", "AGENTS.md"],
12677
+ detectionPaths: [".agent", ".agents", "AGENTS.md", ".gemini", "GEMINI.md"],
12371
12678
  supportsSkills: true,
12372
- getSkillsDir: (root) => existsSync3(join3(root, ".agent")) && !existsSync3(join3(root, ".gemini")) ? join3(root, ".agent", "skills") : join3(root, ".gemini", "skills"),
12373
- 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"),
12679
+ getSkillsDir: (root) => existsSync3(join3(root, ".agents")) ? join3(root, ".agents", "skills") : join3(root, ".agent", "skills"),
12680
+ getCommandPath: (root, id) => existsSync3(join3(root, ".agents")) ? join3(root, ".agents", "skills", `iamu-${id}`, "SKILL.md") : join3(root, ".agent", "skills", `iamu-${id}`, "SKILL.md"),
12374
12681
  formatCommand: richFormat,
12375
12682
  getMcpConfigPath: (root) => join3(root, ".mcp.json"),
12376
12683
  formatMcpConfig: mcpJson
@@ -12504,9 +12811,17 @@ async function needsWrite(filePath, content) {
12504
12811
  return true;
12505
12812
  }
12506
12813
  }
12507
- function isCliWrittenSkill(path28) {
12814
+ function isCliWrittenSkill(path30) {
12815
+ try {
12816
+ return /^generatedBy:/m.test(readFileSync3(path30, "utf-8"));
12817
+ } catch {
12818
+ return false;
12819
+ }
12820
+ }
12821
+ function isCliWrittenPrompt(path30) {
12508
12822
  try {
12509
- return /^generatedBy:/m.test(readFileSync3(path28, "utf-8"));
12823
+ const content = readFileSync3(path30, "utf-8");
12824
+ return content.includes("iamu") || /^generatedBy:/m.test(content);
12510
12825
  } catch {
12511
12826
  return false;
12512
12827
  }
@@ -12517,14 +12832,41 @@ function planSkillFiles(hosts, projectRoot, version) {
12517
12832
  for (const host of hosts) {
12518
12833
  if (pluginProvidesSkills(host, projectRoot)) {
12519
12834
  for (const name of skillTemplates.keys()) {
12520
- const path28 = join4(host.getSkillsDir(projectRoot), name, "SKILL.md");
12521
- if (isCliWrittenSkill(path28)) files.push({ host, path: path28, content: null });
12835
+ const path30 = join4(host.getSkillsDir(projectRoot), name, "SKILL.md");
12836
+ if (isCliWrittenSkill(path30)) files.push({ host, path: path30, content: null });
12522
12837
  }
12523
12838
  } else if (host.supportsSkills) {
12524
12839
  const skillsDir = host.getSkillsDir(projectRoot);
12525
12840
  for (const [name, content] of skillTemplates) {
12526
12841
  files.push({ host, path: join4(skillsDir, name, "SKILL.md"), content });
12527
12842
  }
12843
+ if (host.value === "antigravity") {
12844
+ for (const name of skillTemplates.keys()) {
12845
+ const legacyPath = join4(
12846
+ projectRoot,
12847
+ ".gemini",
12848
+ "skills",
12849
+ name,
12850
+ "SKILL.md"
12851
+ );
12852
+ if (isCliWrittenSkill(legacyPath)) {
12853
+ files.push({ host, path: legacyPath, content: null });
12854
+ }
12855
+ }
12856
+ }
12857
+ if (host.value === "github-copilot") {
12858
+ for (const cmd of commandsFromSkills(skillTemplates)) {
12859
+ const legacyPrompt = join4(
12860
+ projectRoot,
12861
+ ".github",
12862
+ "prompts",
12863
+ `iamu-${cmd.id}.prompt.md`
12864
+ );
12865
+ if (isCliWrittenPrompt(legacyPrompt)) {
12866
+ files.push({ host, path: legacyPrompt, content: null });
12867
+ }
12868
+ }
12869
+ }
12528
12870
  } else {
12529
12871
  for (const cmd of commandsFromSkills(skillTemplates)) {
12530
12872
  files.push({
@@ -12549,6 +12891,15 @@ function detectInstalledHosts(projectRoot) {
12549
12891
  const skillDir = join4(host.getSkillsDir(projectRoot), "iamu-setup");
12550
12892
  if (existsSync5(skillDir)) return true;
12551
12893
  }
12894
+ if (host.value === "github-copilot") {
12895
+ const legacyPrompt = join4(
12896
+ projectRoot,
12897
+ ".github",
12898
+ "prompts",
12899
+ "iamu-setup.prompt.md"
12900
+ );
12901
+ if (existsSync5(legacyPrompt)) return true;
12902
+ }
12552
12903
  return false;
12553
12904
  });
12554
12905
  }
@@ -12615,7 +12966,13 @@ async function installSkillsForHosts(hosts, projectRoot, version, opts) {
12615
12966
  let hostUpdated = 0;
12616
12967
  let hostTotal = 0;
12617
12968
  for (const f of planned) {
12618
- if (f.host !== host || f.content === null) continue;
12969
+ if (f.host !== host) continue;
12970
+ if (f.content === null) {
12971
+ await rm(f.path, { force: true });
12972
+ await rmdir(dirname3(f.path)).catch(() => {
12973
+ });
12974
+ continue;
12975
+ }
12619
12976
  hostTotal++;
12620
12977
  if (host.supportsSkills) totalSkills++;
12621
12978
  else totalCommands++;
@@ -13461,12 +13818,24 @@ async function browserLogin(apiUrl, opts, t) {
13461
13818
  chalk8.red(`
13462
13819
  Auth session expired or failed (HTTP ${result.status}).`)
13463
13820
  );
13821
+ console.error(
13822
+ chalk8.dim(" Re-run `iamu auth login` to start a new login session.\n")
13823
+ );
13464
13824
  process.exitCode = 1;
13465
13825
  return null;
13466
13826
  }
13467
13827
  if (result.kind === "timeout") {
13468
13828
  s.stop("Authentication timed out.");
13469
- console.error(chalk8.red("\n Authentication timed out. Try again."));
13829
+ console.error(
13830
+ chalk8.red(
13831
+ "\n Authentication timed out (no approval received within 2 minutes)."
13832
+ )
13833
+ );
13834
+ console.error(
13835
+ chalk8.dim(
13836
+ " \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"
13837
+ )
13838
+ );
13470
13839
  process.exitCode = 1;
13471
13840
  return null;
13472
13841
  }
@@ -13492,7 +13861,12 @@ async function startAuthSession(apiUrl, timeoutMs) {
13492
13861
  if (!res.ok) {
13493
13862
  console.error(
13494
13863
  chalk8.red(
13495
- ` Failed to start auth session: ${res.text || `HTTP ${res.status}`}`
13864
+ ` Failed to start auth session at ${apiUrl}: ${res.text || `HTTP ${res.status}`}`
13865
+ )
13866
+ );
13867
+ console.error(
13868
+ chalk8.dim(
13869
+ " \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"
13496
13870
  )
13497
13871
  );
13498
13872
  process.exitCode = 1;
@@ -13506,7 +13880,13 @@ async function startAuthSession(apiUrl, timeoutMs) {
13506
13880
  return { sessionId: data.sessionId, loginUrl: data.loginUrl };
13507
13881
  } catch (err) {
13508
13882
  console.error(
13509
- chalk8.red(" Cannot reach Iamu Cloud.") + chalk8.dim(` (${errorMessage(err)})`)
13883
+ chalk8.red(` Cannot reach Iamu Cloud (${apiUrl}).`) + chalk8.dim(`
13884
+ Reason: ${errorMessage(err)}`)
13885
+ );
13886
+ console.error(
13887
+ chalk8.dim(
13888
+ "\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"
13889
+ )
13510
13890
  );
13511
13891
  process.exitCode = 1;
13512
13892
  return null;
@@ -13973,7 +14353,7 @@ import {
13973
14353
  readlinkSync,
13974
14354
  realpathSync as realpathSync2
13975
14355
  } from "fs";
13976
- import path17 from "path";
14356
+ import path18 from "path";
13977
14357
  function git(args, cwd) {
13978
14358
  return execFileSync("git", args, {
13979
14359
  cwd,
@@ -14026,8 +14406,8 @@ function detectWorktreeState(projectRoot, iamuDirName = IAMU_DIR) {
14026
14406
  const top = git(["rev-parse", "--show-toplevel"], projectRoot).toString("utf8").trim();
14027
14407
  if (!top) return void 0;
14028
14408
  const realTop = realpathSync2(top);
14029
- const realIamu = path17.join(realpathSync2(projectRoot), iamuDirName);
14030
- const iamuRel = path17.relative(realTop, realIamu).split(path17.sep).join("/");
14409
+ const realIamu = path18.join(realpathSync2(projectRoot), iamuDirName);
14410
+ const iamuRel = path18.relative(realTop, realIamu).split(path18.sep).join("/");
14031
14411
  const pathspec = iamuRel.startsWith("..") ? [":(top)"] : [":(top)", `:(top,exclude,literal)${iamuRel}`];
14032
14412
  const status2 = git(
14033
14413
  [
@@ -14057,7 +14437,7 @@ function detectWorktreeState(projectRoot, iamuDirName = IAMU_DIR) {
14057
14437
  );
14058
14438
  for (const file of sorted.slice(0, MAX_UNTRACKED_HASHED)) {
14059
14439
  const rel = file.replace(/\/$/, "");
14060
- hash.update(`${file}\0${hashFile(path17.join(realTop, rel))}
14440
+ hash.update(`${file}\0${hashFile(path18.join(realTop, rel))}
14061
14441
  `);
14062
14442
  }
14063
14443
  if (sorted.length > MAX_UNTRACKED_HASHED) {
@@ -14228,15 +14608,29 @@ __export(start_run_exports, {
14228
14608
  formatBuild: () => formatBuild,
14229
14609
  formatViewport: () => formatViewport,
14230
14610
  resolveStartUrl: () => resolveStartUrl,
14231
- startRun: () => startRun
14611
+ startRun: () => startRun,
14612
+ testabilityTarget: () => testabilityTarget
14232
14613
  });
14233
- import path18 from "path";
14614
+ import fs24 from "fs/promises";
14615
+ import path19 from "path";
14234
14616
  import chalk11 from "chalk";
14235
- function resolveStartUrl(target, scenario) {
14236
- const base = target ?? scenario.target_url ?? null;
14617
+ async function testabilityTarget(projectRoot) {
14618
+ try {
14619
+ const raw = await fs24.readFile(
14620
+ path19.join(projectRoot, "iamu", "testability.md"),
14621
+ "utf8"
14622
+ );
14623
+ const { frontmatter } = parseFrontmatter(raw);
14624
+ return typeof frontmatter.target_url === "string" && frontmatter.target_url.trim().length > 0 ? frontmatter.target_url.trim() : null;
14625
+ } catch {
14626
+ return null;
14627
+ }
14628
+ }
14629
+ function resolveStartUrl(target, scenario, fallbackTarget) {
14630
+ const base = target ?? scenario.target_url ?? fallbackTarget ?? null;
14237
14631
  if (!base) {
14238
14632
  return {
14239
- error: "No target \u2014 pass --target <url>, or set target_url on the scenario."
14633
+ error: "No target \u2014 pass --target <url>, set target_url on the scenario, or define target_url in iamu/testability.md."
14240
14634
  };
14241
14635
  }
14242
14636
  let parsed;
@@ -14336,7 +14730,8 @@ async function startRun(scenarioSlug, opts) {
14336
14730
  );
14337
14731
  return;
14338
14732
  }
14339
- const start2 = resolveStartUrl(opts.target, fm);
14733
+ const fallbackTarget = await testabilityTarget(projectRoot);
14734
+ const start2 = resolveStartUrl(opts.target, fm, fallbackTarget);
14340
14735
  if ("error" in start2) {
14341
14736
  commandError(start2.error, opts);
14342
14737
  return;
@@ -14370,6 +14765,18 @@ async function startRun(scenarioSlug, opts) {
14370
14765
  }
14371
14766
  const build = detectBuild({ cwd: projectRoot, label: opts.build });
14372
14767
  const environment = resolveEnvironment(opts.env, start2.url);
14768
+ const cfg = await readConfig(projectRoot).catch(() => null);
14769
+ const walker = opts.walker ?? process.env.IAMU_WALKER_MODEL ?? cfg?.models?.walker ?? "fast";
14770
+ const judge = opts.judge ?? process.env.IAMU_JUDGE_MODEL ?? cfg?.models?.judge ?? "thorough";
14771
+ const matcher = opts.matcher ?? process.env.IAMU_MATCHER_MODEL ?? cfg?.models?.matcher ?? "thorough";
14772
+ const models = {
14773
+ walker,
14774
+ judge,
14775
+ matcher,
14776
+ walker_tier: ["fast", "thorough", "inherit"].includes(walker) ? walker : "custom",
14777
+ judge_tier: ["fast", "thorough", "inherit"].includes(judge) ? judge : "custom",
14778
+ matcher_tier: ["fast", "thorough", "inherit"].includes(matcher) ? matcher : "custom"
14779
+ };
14373
14780
  let revisit = [];
14374
14781
  try {
14375
14782
  revisit = await computeRevisitPlaces(projectRoot, scenarioSlug, start2.url);
@@ -14387,13 +14794,14 @@ async function startRun(scenarioSlug, opts) {
14387
14794
  status: "running",
14388
14795
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
14389
14796
  completed_at: null,
14797
+ models,
14390
14798
  ...build ? { build } : {},
14391
14799
  ...environment ? { environment } : {},
14392
14800
  ...revisit.length > 0 ? { revisit } : {},
14393
14801
  ...viewport ? { viewport } : {}
14394
14802
  };
14395
14803
  await atomicWriteFile(
14396
- path18.join(run.absPath, "walkthrough.json"),
14804
+ path19.join(run.absPath, "walkthrough.json"),
14397
14805
  JSON.stringify(manifest, null, 2) + "\n"
14398
14806
  );
14399
14807
  if (opts.json) {
@@ -14401,13 +14809,14 @@ async function startRun(scenarioSlug, opts) {
14401
14809
  JSON.stringify(
14402
14810
  {
14403
14811
  run_id: run.runId,
14404
- path: path18.relative(projectRoot, run.absPath),
14812
+ path: path19.relative(projectRoot, run.absPath),
14405
14813
  scenario_slug: scenarioSlug,
14406
14814
  target_url: start2.url,
14407
14815
  target_reachable: targetReachable,
14408
14816
  ...targetWarning ? { target_warning: targetWarning } : {},
14409
14817
  persona_slug: fm.persona,
14410
14818
  outline_slug: fm.outline,
14819
+ models,
14411
14820
  build: build ?? null,
14412
14821
  environment: environment ?? null,
14413
14822
  revisit,
@@ -14426,6 +14835,9 @@ async function startRun(scenarioSlug, opts) {
14426
14835
  console.log(` ${chalk11.dim("persona:")} ${fm.persona}`);
14427
14836
  console.log(` ${chalk11.dim("outline:")} ${fm.outline}`);
14428
14837
  console.log(` ${chalk11.dim("target:")} ${start2.url}`);
14838
+ console.log(
14839
+ ` ${chalk11.dim("models:")} walker: ${chalk11.cyan(walker)} \xB7 judge: ${chalk11.cyan(judge)} \xB7 matcher: ${chalk11.cyan(matcher)}`
14840
+ );
14429
14841
  if (targetWarning) {
14430
14842
  console.log(` ${symbols.warning} ${chalk11.yellow(targetWarning)}`);
14431
14843
  }
@@ -14445,7 +14857,7 @@ async function startRun(scenarioSlug, opts) {
14445
14857
  );
14446
14858
  }
14447
14859
  console.log(
14448
- ` ${chalk11.dim("path:")} ${path18.relative(projectRoot, run.absPath)}`
14860
+ ` ${chalk11.dim("path:")} ${path19.relative(projectRoot, run.absPath)}`
14449
14861
  );
14450
14862
  console.log();
14451
14863
  console.log(` ${chalk11.bold("Next steps for blind execution:")}`);
@@ -14486,8 +14898,8 @@ var report_exports = {};
14486
14898
  __export(report_exports, {
14487
14899
  report: () => report
14488
14900
  });
14489
- import { promises as fs23 } from "fs";
14490
- import path19 from "path";
14901
+ import { promises as fs25 } from "fs";
14902
+ import path20 from "path";
14491
14903
  import chalk12 from "chalk";
14492
14904
  async function report(scenario, opts) {
14493
14905
  const ctx = await resolveProject(opts);
@@ -14506,8 +14918,8 @@ async function report(scenario, opts) {
14506
14918
  for (const rid of runs.reverse()) {
14507
14919
  try {
14508
14920
  const dir = runDir(projectRoot, rid);
14509
- const mRaw = await fs23.readFile(
14510
- path19.join(dir, "walkthrough.json"),
14921
+ const mRaw = await fs25.readFile(
14922
+ path20.join(dir, "walkthrough.json"),
14511
14923
  "utf8"
14512
14924
  );
14513
14925
  const manifest = JSON.parse(mRaw);
@@ -14515,8 +14927,8 @@ async function report(scenario, opts) {
14515
14927
  if (scenario && manifest.scenario_slug !== scenario) continue;
14516
14928
  let canonical = null;
14517
14929
  try {
14518
- const cRaw = await fs23.readFile(
14519
- path19.join(dir, "canonical.json"),
14930
+ const cRaw = await fs25.readFile(
14931
+ path20.join(dir, "canonical.json"),
14520
14932
  "utf8"
14521
14933
  );
14522
14934
  canonical = JSON.parse(cRaw);
@@ -15723,12 +16135,13 @@ var init_serve = __esm({
15723
16135
  // src/commands/init.ts
15724
16136
  var init_exports = {};
15725
16137
  __export(init_exports, {
15726
- init: () => init
16138
+ init: () => init,
16139
+ setupCommand: () => setupCommand
15727
16140
  });
15728
16141
  import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
15729
16142
  import { mkdir as mkdir4, writeFile as writeFile3 } from "fs/promises";
15730
16143
  import { basename as basename2, join as join5 } from "path";
15731
- import { isCancel as isCancel3, multiselect } from "@clack/prompts";
16144
+ import { isCancel as isCancel3, multiselect, select as select3 } from "@clack/prompts";
15732
16145
  import chalk17 from "chalk";
15733
16146
  function loadPromptTemplates() {
15734
16147
  const templates = /* @__PURE__ */ new Map();
@@ -15750,9 +16163,9 @@ function loadPromptTemplates() {
15750
16163
  return templates;
15751
16164
  }
15752
16165
  function setupCommand(projectRoot, hosts) {
15753
- if (!iamuPluginEnabled(projectRoot)) return "iamu-setup";
16166
+ if (!iamuPluginEnabled(projectRoot)) return "/iamu-setup";
15754
16167
  if (hosts.every((h) => h.value === "claude")) return "/iamu:init";
15755
- return "iamu-setup (Claude Code: /iamu:init)";
16168
+ return "/iamu-setup (Claude Code: /iamu:init)";
15756
16169
  }
15757
16170
  async function init(opts) {
15758
16171
  const options = typeof opts === "string" ? { name: opts } : opts ?? {};
@@ -15772,10 +16185,10 @@ async function init(opts) {
15772
16185
  }
15773
16186
  let freshInit = false;
15774
16187
  if (existsSync7(join5(iamuDir2, "config.json"))) {
15775
- const config = await readConfig(projectRoot);
16188
+ const config2 = await readConfig(projectRoot);
15776
16189
  if (!options.json) {
15777
16190
  console.log(
15778
- chalk17.yellow(" iamu/ already exists") + chalk17.dim(` (${config?.project_name ?? "unknown"})`)
16191
+ chalk17.yellow(" iamu/ already exists") + chalk17.dim(` (${config2?.project_name ?? "unknown"})`)
15779
16192
  );
15780
16193
  }
15781
16194
  } else {
@@ -15792,6 +16205,35 @@ async function init(opts) {
15792
16205
  ""
15793
16206
  )
15794
16207
  );
16208
+ const testabilityPath = join5(iamuDir2, "testability.md");
16209
+ if (!existsSync7(testabilityPath)) {
16210
+ await writeFile3(
16211
+ testabilityPath,
16212
+ serializeFrontmatter(
16213
+ {
16214
+ status: "draft",
16215
+ target_url: "http://localhost:3000"
16216
+ },
16217
+ `# Project Testability & Environment
16218
+
16219
+ Target URL and execution harness for walkthroughs.
16220
+ Audited and populated during /iamu:init.
16221
+
16222
+ ## Target & Run Instructions
16223
+ - Default URL: http://localhost:3000
16224
+
16225
+ ## Reachable Surfaces
16226
+ (Run /iamu:init to discover testable routes)
16227
+
16228
+ ## Mocks & Data Fixtures
16229
+ (Run /iamu:init to inspect existing harnesses)
16230
+
16231
+ ## Untestable Boundaries
16232
+ (None documented yet)
16233
+ `
16234
+ )
16235
+ );
16236
+ }
15795
16237
  }
15796
16238
  const promptTemplates = loadPromptTemplates();
15797
16239
  const promptsDir = join5(iamuDir2, ".prompts");
@@ -15848,20 +16290,57 @@ async function init(opts) {
15848
16290
  version,
15849
16291
  { quiet: options.json }
15850
16292
  );
15851
- try {
15852
- const config = await readConfig(projectRoot);
15853
- if (config.cloud_linked) {
15854
- for (const host of selectedHosts) {
15855
- const mcpPath = host.getMcpConfigPath(projectRoot);
15856
- await writeSyncMcpEntry(mcpPath);
16293
+ let config = await readConfig(projectRoot);
16294
+ const shouldPromptCloud = isInteractive && freshInit && !config.cloud_linked && options.cloud !== false;
16295
+ if (options.cloud === true && !config.cloud_linked) {
16296
+ try {
16297
+ await authLogin({ yes: options.yes });
16298
+ config = await readConfig(projectRoot);
16299
+ } catch {
16300
+ }
16301
+ } else if (shouldPromptCloud) {
16302
+ console.log();
16303
+ const cloudChoice = await select3({
16304
+ message: "Connect this project to Iamu Cloud? (Free)",
16305
+ options: [
16306
+ {
16307
+ value: "connect",
16308
+ label: "Connect to Iamu Cloud (opens browser)",
16309
+ hint: "recommended \u2014 shareable run reports, step replays, team sync"
16310
+ },
16311
+ {
16312
+ value: "local",
16313
+ label: "Continue local-only",
16314
+ hint: "skip for now (link anytime with `iamu auth login`)"
16315
+ }
16316
+ ],
16317
+ initialValue: "connect"
16318
+ });
16319
+ if (!isCancel3(cloudChoice) && cloudChoice === "connect") {
16320
+ const prevExitCode = process.exitCode;
16321
+ try {
16322
+ await authLogin({ yes: options.yes });
16323
+ config = await readConfig(projectRoot);
16324
+ } catch {
16325
+ process.exitCode = prevExitCode;
15857
16326
  if (!options.json) {
15858
16327
  console.log(
15859
- ` ${symbols.success} ${host.name} \u2014 iamu-sync MCP server`
16328
+ chalk17.dim(
16329
+ " Cloud linking skipped. Your local setup is complete and fully functional."
16330
+ )
15860
16331
  );
15861
16332
  }
15862
16333
  }
15863
16334
  }
15864
- } catch {
16335
+ }
16336
+ if (config.cloud_linked) {
16337
+ for (const host of selectedHosts) {
16338
+ const mcpPath = host.getMcpConfigPath(projectRoot);
16339
+ await writeSyncMcpEntry(mcpPath);
16340
+ if (!options.json) {
16341
+ console.log(` ${symbols.success} ${host.name} \u2014 iamu-sync MCP server`);
16342
+ }
16343
+ }
15865
16344
  }
15866
16345
  if (options.json) {
15867
16346
  console.log(
@@ -15873,7 +16352,10 @@ async function init(opts) {
15873
16352
  fresh: freshInit,
15874
16353
  tools: selectedHosts.map((h) => h.name),
15875
16354
  skillsCount: totalSkills,
15876
- commandsCount: totalCommands
16355
+ commandsCount: totalCommands,
16356
+ cloudLinked: Boolean(config.cloud_linked),
16357
+ cloudProjectId: config.cloud_project_id ?? null,
16358
+ dashboardUrl: config.cloud_linked && config.cloud_project_id ? getCloudDashboardUrl(void 0, config.cloud_project_id) : null
15877
16359
  },
15878
16360
  null,
15879
16361
  2
@@ -15881,43 +16363,89 @@ async function init(opts) {
15881
16363
  );
15882
16364
  return;
15883
16365
  }
15884
- if (totalSkills > 0 || totalCommands > 0) {
16366
+ if (!options.json) {
15885
16367
  console.log();
15886
- const summaryParts = [];
15887
- if (totalSkills > 0) summaryParts.push(`${totalSkills} skills`);
15888
- if (totalCommands > 0) summaryParts.push(`${totalCommands} commands`);
15889
16368
  console.log(
15890
- chalk17.dim(
15891
- ` Total: ${summaryParts.join(" + ")} across ${selectedHosts.length} tool(s)`
16369
+ chalk17.bold(
16370
+ ` ${symbols.success} Project configured: ${chalk17.cyan(projectName)}`
15892
16371
  )
15893
16372
  );
15894
- }
15895
- console.log();
15896
- if (freshInit) {
15897
- console.log(chalk17.bold(" Next step:"));
15898
16373
  console.log();
15899
- console.log(chalk17.dim(" Open your editor and run:"));
16374
+ if (selectedHosts.length > 0) {
16375
+ const toolNames = selectedHosts.map((h) => h.name).join(", ");
16376
+ console.log(
16377
+ ` ${chalk17.dim("Tools:")} ${toolNames} ${chalk17.dim(`(${totalSkills} skills + ${totalCommands} commands installed)`)}`
16378
+ );
16379
+ }
16380
+ if (config.cloud_linked && config.cloud_project_id) {
16381
+ const dashboardUrl = getCloudDashboardUrl(
16382
+ void 0,
16383
+ config.cloud_project_id
16384
+ );
16385
+ console.log(
16386
+ ` ${chalk17.dim("Cloud:")} ${chalk17.green("Linked")} \xB7 ${chalk17.cyan(dashboardUrl)}`
16387
+ );
16388
+ } else {
16389
+ console.log(
16390
+ ` ${chalk17.dim("Cloud:")} Local-only ${chalk17.dim("(link anytime with `iamu auth login`)")}`
16391
+ );
16392
+ }
15900
16393
  console.log();
15901
- console.log(` ${chalk17.cyan(setupCommand(projectRoot, selectedHosts))}`);
16394
+ if (freshInit) {
16395
+ console.log(chalk17.bold(" Next steps:"));
16396
+ console.log();
16397
+ console.log(
16398
+ ` ${chalk17.bold("1.")} Start your application's dev server:`
16399
+ );
16400
+ console.log(
16401
+ ` ${chalk17.cyan("$ pnpm dev")} ${chalk17.dim("(or npm run dev)")}`
16402
+ );
16403
+ console.log();
16404
+ console.log(` ${chalk17.bold("2.")} In your AI agent chat, run:`);
16405
+ console.log(
16406
+ ` ${chalk17.cyan(setupCommand(projectRoot, selectedHosts))}`
16407
+ );
16408
+ console.log();
16409
+ console.log(
16410
+ chalk17.dim(
16411
+ " Your agent will analyze your codebase, map user roles, and generate your first walkthrough scenario."
16412
+ )
16413
+ );
16414
+ console.log();
16415
+ console.log(chalk17.bold(" Core workflow:"));
16416
+ console.log(
16417
+ chalk17.dim(" \u2022 ") + chalk17.cyan("/iamu:init") + chalk17.dim(" Map domain & scaffold inaugural scenario")
16418
+ );
16419
+ console.log(
16420
+ chalk17.dim(" \u2022 ") + chalk17.cyan("iamu-run <slug>") + chalk17.dim(" Walk scenario in browser & record findings")
16421
+ );
16422
+ console.log(
16423
+ chalk17.dim(" \u2022 ") + chalk17.cyan("iamu-findings") + chalk17.dim(" Triage persona feedback & apply code fixes")
16424
+ );
16425
+ console.log(
16426
+ chalk17.dim(" \u2022 ") + chalk17.cyan("iamu-propose") + chalk17.dim(" Brainstorm high-risk workflows & new personas")
16427
+ );
16428
+ } else {
16429
+ console.log(chalk17.bold(" Updated:"));
16430
+ console.log();
16431
+ console.log(
16432
+ chalk17.dim(" Skills and commands regenerated to latest version.")
16433
+ );
16434
+ }
15902
16435
  console.log();
15903
16436
  console.log(
15904
- chalk17.dim(" Your agent will analyze your codebase, ask a few")
16437
+ ` ${chalk17.dim("Docs:")} ${chalk17.underline("https://iamu.dev/docs")}`
15905
16438
  );
15906
16439
  console.log(
15907
- chalk17.dim(" questions, then you're ready for your first walkthrough.")
16440
+ ` ${chalk17.dim("Quickstart:")} ${chalk17.underline("https://iamu.dev/quickstart")}`
15908
16441
  );
15909
- } else {
15910
- console.log(chalk17.bold(" Updated:"));
16442
+ if (config.cloud_linked && config.cloud_project_id) {
16443
+ console.log(
16444
+ ` ${chalk17.dim("Dashboard:")} ${chalk17.underline(getCloudDashboardUrl(void 0, config.cloud_project_id))}`
16445
+ );
16446
+ }
15911
16447
  console.log();
15912
- console.log(
15913
- chalk17.dim(" Skills and commands regenerated to latest version.")
15914
- );
15915
16448
  }
15916
- console.log();
15917
- console.log(
15918
- chalk17.dim(" Docs: ") + chalk17.underline("https://iamu.dev/quickstart")
15919
- );
15920
- console.log();
15921
16449
  }
15922
16450
  var init_init = __esm({
15923
16451
  "src/commands/init.ts"() {
@@ -15925,7 +16453,9 @@ var init_init = __esm({
15925
16453
  init_dist2();
15926
16454
  init_claude_plugin();
15927
16455
  init_registry();
16456
+ init_cloud_url();
15928
16457
  init_symbols();
16458
+ init_auth();
15929
16459
  init_mcp_config();
15930
16460
  init_skills();
15931
16461
  init_templates();
@@ -16221,7 +16751,10 @@ var init_browse_actions = __esm({
16221
16751
  usage: "goto <url>",
16222
16752
  minArgs: 1,
16223
16753
  run: async (p, args) => {
16224
- await p.goto(args[0], { waitUntil: "domcontentloaded" });
16754
+ await p.goto(args[0], {
16755
+ waitUntil: "domcontentloaded",
16756
+ timeout: 1e4
16757
+ });
16225
16758
  }
16226
16759
  },
16227
16760
  click: {
@@ -16382,6 +16915,12 @@ var init_browse_driver = __esm({
16382
16915
  commandLine: (pid) => readProcessCommandLine(pid),
16383
16916
  kill(pid) {
16384
16917
  try {
16918
+ if (process.platform !== "win32") {
16919
+ try {
16920
+ process.kill(-pid, "SIGTERM");
16921
+ } catch {
16922
+ }
16923
+ }
16385
16924
  process.kill(pid, "SIGTERM");
16386
16925
  } catch {
16387
16926
  }
@@ -16392,7 +16931,7 @@ var init_browse_driver = __esm({
16392
16931
 
16393
16932
  // src/commands/step-numbering.ts
16394
16933
  import { open, readdir, unlink } from "fs/promises";
16395
- import path20 from "path";
16934
+ import path21 from "path";
16396
16935
  function stepFileName(stepNumber2) {
16397
16936
  return `${String(stepNumber2).padStart(3, "0")}.json`;
16398
16937
  }
@@ -16414,7 +16953,7 @@ async function reserveStepFile(stepsDir, limit) {
16414
16953
  if (candidate > limit.maxSteps) {
16415
16954
  throw new Error(stepLimitMessage(limit.maxSteps, limit.runId));
16416
16955
  }
16417
- const stepPath = path20.join(stepsDir, stepFileName(candidate));
16956
+ const stepPath = path21.join(stepsDir, stepFileName(candidate));
16418
16957
  try {
16419
16958
  const handle = await open(stepPath, "wx");
16420
16959
  await handle.close();
@@ -16446,7 +16985,7 @@ __export(step_exports, {
16446
16985
  stepCommand: () => stepCommand
16447
16986
  });
16448
16987
  import { copyFile, mkdir as mkdir5, stat } from "fs/promises";
16449
- import path21 from "path";
16988
+ import path22 from "path";
16450
16989
  import chalk19 from "chalk";
16451
16990
  function parseMaxSteps(val) {
16452
16991
  if (typeof val === "number" && Number.isFinite(val) && val > 0) return val;
@@ -16465,8 +17004,8 @@ async function recordStep(input) {
16465
17004
  if (!runDirPath) {
16466
17005
  throw new Error(`Run directory not found for run "${input.runId}"`);
16467
17006
  }
16468
- const stepsDir = path21.join(runDirPath, "steps");
16469
- const screenshotsDir = path21.join(runDirPath, "screenshots");
17007
+ const stepsDir = path22.join(runDirPath, "steps");
17008
+ const screenshotsDir = path22.join(runDirPath, "screenshots");
16470
17009
  await mkdir5(stepsDir, { recursive: true });
16471
17010
  await mkdir5(screenshotsDir, { recursive: true });
16472
17011
  const shotSource = input.shot ? await existingShot(input.shot) : null;
@@ -16480,11 +17019,11 @@ async function recordStep(input) {
16480
17019
  let finalScreenshotPath = null;
16481
17020
  if (shotSource) {
16482
17021
  const destName = `step-${stepNumber2}.png`;
16483
- finalScreenshotPath = path21.join(screenshotsDir, destName);
16484
- if (path21.resolve(finalScreenshotPath) !== shotSource) {
17022
+ finalScreenshotPath = path22.join(screenshotsDir, destName);
17023
+ if (path22.resolve(finalScreenshotPath) !== shotSource) {
16485
17024
  await copyFile(shotSource, finalScreenshotPath);
16486
17025
  }
16487
- screenshotRef = path21.join("screenshots", destName);
17026
+ screenshotRef = path22.join("screenshots", destName);
16488
17027
  }
16489
17028
  const parsedStep = runStepSchema.parse({
16490
17029
  step_number: stepNumber2,
@@ -16509,7 +17048,7 @@ async function recordStep(input) {
16509
17048
  }
16510
17049
  }
16511
17050
  async function existingShot(shot) {
16512
- const resolved = path21.resolve(shot);
17051
+ const resolved = path22.resolve(shot);
16513
17052
  const isFile = await stat(resolved).then((s) => s.isFile()).catch(() => false);
16514
17053
  if (!isFile) {
16515
17054
  throw new Error(
@@ -16551,7 +17090,7 @@ async function stepCommand(runArg, opts) {
16551
17090
  JSON.stringify(
16552
17091
  {
16553
17092
  step_number: res.step.step_number,
16554
- path: path21.relative(projectRoot, res.stepPath),
17093
+ path: path22.relative(projectRoot, res.stepPath),
16555
17094
  action: res.step.action,
16556
17095
  note: res.step.note,
16557
17096
  url: res.step.url,
@@ -16578,7 +17117,7 @@ async function stepCommand(runArg, opts) {
16578
17117
  console.log(` ${chalk19.dim("shot:")} ${res.step.screenshot}`);
16579
17118
  }
16580
17119
  console.log(
16581
- ` ${chalk19.dim("file:")} ${path21.relative(projectRoot, res.stepPath)}`
17120
+ ` ${chalk19.dim("file:")} ${path22.relative(projectRoot, res.stepPath)}`
16582
17121
  );
16583
17122
  console.log();
16584
17123
  } catch (err) {
@@ -16608,6 +17147,7 @@ __export(browse_exports, {
16608
17147
  readBrowseState: () => readBrowseState,
16609
17148
  shouldDisableSandbox: () => shouldDisableSandbox,
16610
17149
  start: () => start,
17150
+ waitForVisualStability: () => waitForVisualStability,
16611
17151
  withPage: () => withPage
16612
17152
  });
16613
17153
  import { randomUUID as randomUUID7 } from "crypto";
@@ -16620,21 +17160,21 @@ import {
16620
17160
  } from "fs";
16621
17161
  import { rm as rm2 } from "fs/promises";
16622
17162
  import { homedir as homedir3 } from "os";
16623
- import path22 from "path";
17163
+ import path23 from "path";
16624
17164
  import chalk20 from "chalk";
16625
17165
  function stateDir() {
16626
- return path22.join(
16627
- process.env.IAMU_HOME ?? path22.join(homedir3(), ".iamu"),
17166
+ return path23.join(
17167
+ process.env.IAMU_HOME ?? path23.join(homedir3(), ".iamu"),
16628
17168
  "browse"
16629
17169
  );
16630
17170
  }
16631
17171
  function profileDir() {
16632
- return path22.join(stateDir(), "profile");
17172
+ return path23.join(stateDir(), "profile");
16633
17173
  }
16634
17174
  function readBrowseState() {
16635
17175
  try {
16636
17176
  return JSON.parse(
16637
- readFileSync6(path22.join(stateDir(), "state.json"), "utf8")
17177
+ readFileSync6(path23.join(stateDir(), "state.json"), "utf8")
16638
17178
  );
16639
17179
  } catch {
16640
17180
  return null;
@@ -16642,7 +17182,7 @@ function readBrowseState() {
16642
17182
  }
16643
17183
  function writeState(state) {
16644
17184
  mkdirSync(stateDir(), { recursive: true });
16645
- writeFileSync(path22.join(stateDir(), "state.json"), JSON.stringify(state));
17185
+ writeFileSync(path23.join(stateDir(), "state.json"), JSON.stringify(state));
16646
17186
  }
16647
17187
  function fail(message, opts) {
16648
17188
  if (opts?.json) {
@@ -16705,7 +17245,7 @@ async function requestedViewport(opts) {
16705
17245
  let manifest;
16706
17246
  try {
16707
17247
  manifest = JSON.parse(
16708
- readFileSync6(path22.join(dir, "walkthrough.json"), "utf8")
17248
+ readFileSync6(path23.join(dir, "walkthrough.json"), "utf8")
16709
17249
  );
16710
17250
  } catch {
16711
17251
  fail(
@@ -16757,6 +17297,8 @@ function chromeArgs(opts, port, headless) {
16757
17297
  `--window-size=${opts.width ?? "1280"},${opts.height ?? "900"}`,
16758
17298
  "--no-first-run",
16759
17299
  "--no-default-browser-check",
17300
+ "--password-store=basic",
17301
+ "--use-mock-keychain",
16760
17302
  ...headless ? ["--headless=new", "--disable-dev-shm-usage", "--disable-gpu"] : [],
16761
17303
  ...shouldDisableSandbox() ? ["--no-sandbox"] : [],
16762
17304
  "about:blank"
@@ -16809,11 +17351,50 @@ async function stop(opts, driver) {
16809
17351
  await driver.closeBrowser(state.port);
16810
17352
  if (state.pid) await reapBrowser(state.pid, profileDir(), driver);
16811
17353
  try {
16812
- unlinkSync(path22.join(stateDir(), "state.json"));
17354
+ unlinkSync(path23.join(stateDir(), "state.json"));
16813
17355
  } catch {
16814
17356
  }
16815
17357
  report2(opts, { stopped: true }, [` ${chalk20.green("\u2713")} Browser stopped`]);
16816
17358
  }
17359
+ async function waitForVisualStability(page, maxWaitMs = 300) {
17360
+ let timer;
17361
+ try {
17362
+ const nodeTimeout = new Promise((resolve3) => {
17363
+ timer = setTimeout(resolve3, maxWaitMs + 100);
17364
+ });
17365
+ const browserWait = page.evaluate((maxWait) => {
17366
+ return new Promise((resolve3) => {
17367
+ const timeout = setTimeout(resolve3, maxWait);
17368
+ const done = () => {
17369
+ clearTimeout(timeout);
17370
+ resolve3();
17371
+ };
17372
+ const g = globalThis;
17373
+ if (typeof g.requestAnimationFrame !== "function") {
17374
+ done();
17375
+ return;
17376
+ }
17377
+ g.requestAnimationFrame(() => {
17378
+ g.requestAnimationFrame?.(() => {
17379
+ const animations = g.document?.getAnimations ? g.document.getAnimations().filter((a) => a.playState === "running" && a.finished) : [];
17380
+ if (animations.length === 0) {
17381
+ done();
17382
+ return;
17383
+ }
17384
+ Promise.allSettled(
17385
+ animations.map((a) => a.finished)
17386
+ ).finally(done);
17387
+ });
17388
+ });
17389
+ });
17390
+ }, maxWaitMs).catch(() => {
17391
+ });
17392
+ await Promise.race([browserWait, nodeTimeout]);
17393
+ } catch {
17394
+ } finally {
17395
+ if (timer) clearTimeout(timer);
17396
+ }
17397
+ }
16817
17398
  async function withPage(opts, driver, fn, target) {
16818
17399
  const state = readBrowseState();
16819
17400
  if (!state) {
@@ -16833,11 +17414,11 @@ async function withPage(opts, driver, fn, target) {
16833
17414
  await fn(page);
16834
17415
  await page.waitForLoadState("domcontentloaded").catch(() => {
16835
17416
  });
16836
- await page.waitForTimeout(400);
16837
17417
  let screenshot = null;
16838
17418
  if (opts.shot) {
16839
- screenshot = path22.resolve(opts.shot);
16840
- mkdirSync(path22.dirname(screenshot), { recursive: true });
17419
+ await waitForVisualStability(page);
17420
+ screenshot = path23.resolve(opts.shot);
17421
+ mkdirSync(path23.dirname(screenshot), { recursive: true });
16841
17422
  await page.screenshot({ path: screenshot, fullPage: Boolean(opts.full) });
16842
17423
  }
16843
17424
  return { url: page.url(), title: await page.title(), screenshot };
@@ -16916,7 +17497,7 @@ function printStep(opts, record, projectRoot) {
16916
17497
  if (opts.json) {
16917
17498
  const doc = {
16918
17499
  step_number: step.step_number,
16919
- path: path22.relative(projectRoot, record.stepPath),
17500
+ path: path23.relative(projectRoot, record.stepPath),
16920
17501
  action: step.action,
16921
17502
  note: step.note,
16922
17503
  url: step.url,
@@ -16936,6 +17517,35 @@ function printStep(opts, record, projectRoot) {
16936
17517
  }
16937
17518
  }
16938
17519
  async function browseStep(args, opts, driver) {
17520
+ if (opts.stepFile) {
17521
+ try {
17522
+ let raw;
17523
+ if (opts.stepFile === "-") {
17524
+ raw = readFileSync6(0, "utf8");
17525
+ } else {
17526
+ raw = readFileSync6(opts.stepFile, "utf8");
17527
+ }
17528
+ const data = JSON.parse(raw);
17529
+ if (data.run && typeof data.run === "string") opts.run = data.run;
17530
+ if (data.action && typeof data.action === "string")
17531
+ opts.action = data.action;
17532
+ if (data.note && typeof data.note === "string") opts.note = data.note;
17533
+ if (data.surface && typeof data.surface === "string")
17534
+ opts.surface = data.surface;
17535
+ if (args.length === 0 && data.cmd) {
17536
+ if (Array.isArray(data.cmd)) {
17537
+ args = data.cmd.map(String);
17538
+ } else if (typeof data.cmd === "string") {
17539
+ args = data.cmd.trim().split(/\s+/);
17540
+ }
17541
+ }
17542
+ } catch (err) {
17543
+ return fail(
17544
+ `Failed to read step payload from ${opts.stepFile}: ${err.message}`,
17545
+ opts
17546
+ );
17547
+ }
17548
+ }
16939
17549
  if (!opts.run)
16940
17550
  return fail("--run <run-id> is required for browse step.", opts);
16941
17551
  if (!opts.action) {
@@ -16961,10 +17571,10 @@ async function browseStep(args, opts, driver) {
16961
17571
  const runDirPath = resolveRunDir(ctx.projectRoot, opts.run);
16962
17572
  if (!runDirPath) return;
16963
17573
  const maxSteps = parseMaxSteps(opts.maxSteps);
16964
- if (await peekNextStepNumber(path22.join(runDirPath, "steps")) > maxSteps) {
17574
+ if (await peekNextStepNumber(path23.join(runDirPath, "steps")) > maxSteps) {
16965
17575
  return fail(stepLimitMessage(maxSteps, opts.run), opts);
16966
17576
  }
16967
- const pendingShot = path22.join(
17577
+ const pendingShot = path23.join(
16968
17578
  runDirPath,
16969
17579
  "screenshots",
16970
17580
  `.pending-${randomUUID7()}.png`
@@ -17035,11 +17645,14 @@ var doctor_exports = {};
17035
17645
  __export(doctor_exports, {
17036
17646
  checkCdpResponsive: () => checkCdpResponsive,
17037
17647
  checkPortAvailable: () => checkPortAvailable,
17648
+ checkProjectHarnesses: () => checkProjectHarnesses,
17649
+ checkProjectTestabilityContract: () => checkProjectTestabilityContract,
17650
+ checkTargetHealth: () => checkTargetHealth,
17038
17651
  doctor: () => doctor,
17039
17652
  runDoctorChecks: () => runDoctorChecks
17040
17653
  });
17041
17654
  import { createServer } from "net";
17042
- import { existsSync as existsSync10 } from "fs";
17655
+ import { existsSync as existsSync10, promises as fs26 } from "fs";
17043
17656
  import { join as join7 } from "path";
17044
17657
  import chalk21 from "chalk";
17045
17658
  function checkPortAvailable(port) {
@@ -17317,11 +17930,186 @@ async function checkAgentIntegrations(projectRoot) {
17317
17930
  }
17318
17931
  return checks;
17319
17932
  }
17320
- async function runDoctorChecks(projectRoot) {
17933
+ async function checkTargetHealth(targetUrl) {
17934
+ const checks = [];
17935
+ let parsedUrl;
17936
+ try {
17937
+ parsedUrl = new URL(targetUrl);
17938
+ } catch {
17939
+ checks.push({
17940
+ category: "target",
17941
+ name: "Target URL Format",
17942
+ status: "error",
17943
+ message: `Invalid target URL: "${targetUrl}"`,
17944
+ remedy: "Provide a valid URL with protocol (e.g. http://localhost:3000)."
17945
+ });
17946
+ return checks;
17947
+ }
17948
+ try {
17949
+ const res = await fetch(targetUrl, {
17950
+ method: "GET",
17951
+ redirect: "manual",
17952
+ signal: AbortSignal.timeout(3e3)
17953
+ });
17954
+ if ([301, 302, 303, 307, 308].includes(res.status)) {
17955
+ const location = res.headers.get("location") || "";
17956
+ const isExternalAuth = /auth0\.com|okta\.com|clerk\.com|accounts\.google\.com|cognito|login\.microsoftonline\.com/i.test(
17957
+ location
17958
+ ) || location.includes("/oauth");
17959
+ if (isExternalAuth) {
17960
+ let redirectHost = location;
17961
+ try {
17962
+ redirectHost = new URL(location, targetUrl).host;
17963
+ } catch {
17964
+ }
17965
+ checks.push({
17966
+ category: "target",
17967
+ name: "Authentication Boundary",
17968
+ status: "warn",
17969
+ message: `Target immediately redirects to external auth (${redirectHost})`,
17970
+ remedy: "Configure a dev auth bypass or prepare for interactive login handshake before walking."
17971
+ });
17972
+ return checks;
17973
+ }
17974
+ checks.push({
17975
+ category: "target",
17976
+ name: "Target Reachability",
17977
+ status: "ok",
17978
+ message: `HTTP ${res.status} redirect -> ${location}`
17979
+ });
17980
+ return checks;
17981
+ }
17982
+ if (res.status >= 500) {
17983
+ checks.push({
17984
+ category: "target",
17985
+ name: "Target Health",
17986
+ status: "error",
17987
+ message: `Target returned HTTP ${res.status} ${res.statusText || "Server Error"}`,
17988
+ remedy: "Check dev server logs for unhandled exceptions or missing environment variables."
17989
+ });
17990
+ return checks;
17991
+ }
17992
+ checks.push({
17993
+ category: "target",
17994
+ name: "Target Reachability",
17995
+ status: "ok",
17996
+ message: `HTTP ${res.status} OK (${parsedUrl.origin})`
17997
+ });
17998
+ } catch (err) {
17999
+ const message = err.message;
18000
+ checks.push({
18001
+ category: "target",
18002
+ name: "Target Reachability",
18003
+ status: "warn",
18004
+ message: `Cannot connect to ${targetUrl} (${message})`,
18005
+ remedy: "Ensure your dev server is running and listening on the specified port."
18006
+ });
18007
+ }
18008
+ return checks;
18009
+ }
18010
+ async function checkProjectHarnesses(projectRoot) {
18011
+ const checks = [];
18012
+ const pkgPath = join7(projectRoot, "package.json");
18013
+ if (!existsSync10(pkgPath)) return checks;
18014
+ try {
18015
+ const raw = await fs26.readFile(pkgPath, "utf8");
18016
+ const pkg = JSON.parse(raw);
18017
+ const scripts = pkg.scripts || {};
18018
+ const allDeps = {
18019
+ ...pkg.dependencies || {},
18020
+ ...pkg.devDependencies || {}
18021
+ };
18022
+ const devScripts = ["dev", "start", "dev:mock", "serve"].filter(
18023
+ (s) => s in scripts
18024
+ );
18025
+ if (devScripts.length > 0) {
18026
+ checks.push({
18027
+ category: "target",
18028
+ name: "Dev Server Script",
18029
+ status: "ok",
18030
+ message: `Available scripts: ${devScripts.map((s) => `pnpm ${s}`).join(", ")}`
18031
+ });
18032
+ } else {
18033
+ checks.push({
18034
+ category: "target",
18035
+ name: "Dev Server Script",
18036
+ status: "warn",
18037
+ message: "No standard dev script (dev, start, dev:mock) found in package.json",
18038
+ remedy: "Add a `dev` or `start` script to package.json to boot your app locally."
18039
+ });
18040
+ }
18041
+ if (allDeps.msw || existsSync10(join7(projectRoot, "src/mocks")) || existsSync10(join7(projectRoot, "mocks"))) {
18042
+ checks.push({
18043
+ category: "target",
18044
+ name: "Mock Harness (MSW)",
18045
+ status: "ok",
18046
+ message: "Mock Service Worker detected. API mocks can decouple walks from live backends."
18047
+ });
18048
+ }
18049
+ const seedScript = Object.keys(scripts).find(
18050
+ (s) => /seed|db:seed|fixtures/i.test(s)
18051
+ );
18052
+ if (seedScript) {
18053
+ checks.push({
18054
+ category: "target",
18055
+ name: "Database Seed",
18056
+ status: "ok",
18057
+ message: `Seed script detected (${seedScript}). Preconditions can be reset between runs.`
18058
+ });
18059
+ }
18060
+ } catch {
18061
+ }
18062
+ return checks;
18063
+ }
18064
+ async function checkProjectTestabilityContract(projectRoot) {
18065
+ const checks = [];
18066
+ const testabilityPath = join7(projectRoot, "iamu", "testability.md");
18067
+ if (!existsSync10(testabilityPath)) {
18068
+ checks.push({
18069
+ category: "target",
18070
+ name: "Testability Contract (testability.md)",
18071
+ status: "warn",
18072
+ message: "iamu/testability.md not found",
18073
+ remedy: "Run `/iamu:init` to audit your codebase and record testable routes, auth strategies, and mock harnesses."
18074
+ });
18075
+ return { checks };
18076
+ }
18077
+ try {
18078
+ const raw = await fs26.readFile(testabilityPath, "utf8");
18079
+ const { frontmatter } = parseFrontmatter(raw);
18080
+ const targetUrl = typeof frontmatter.target_url === "string" ? frontmatter.target_url : void 0;
18081
+ const details = [];
18082
+ if (targetUrl) details.push(`Target: ${targetUrl}`);
18083
+ if (frontmatter.dev_command) {
18084
+ details.push(`Command: ${frontmatter.dev_command}`);
18085
+ }
18086
+ if (frontmatter.auth_strategy) {
18087
+ details.push(`Auth: ${frontmatter.auth_strategy}`);
18088
+ }
18089
+ checks.push({
18090
+ category: "target",
18091
+ name: "Testability Contract (testability.md)",
18092
+ status: "ok",
18093
+ message: `Configured (${details.join(" \xB7 ") || "present"})`
18094
+ });
18095
+ return { checks, targetUrl };
18096
+ } catch (err) {
18097
+ checks.push({
18098
+ category: "target",
18099
+ name: "Testability Contract (testability.md)",
18100
+ status: "warn",
18101
+ message: `Unreadable testability contract: ${err.message}`,
18102
+ remedy: "Repair iamu/testability.md frontmatter."
18103
+ });
18104
+ return { checks };
18105
+ }
18106
+ }
18107
+ async function runDoctorChecks(projectRoot, opts) {
17321
18108
  const checks = [
17322
18109
  ...checkRuntimeChecks(),
17323
18110
  ...await checkBrowserChecks()
17324
18111
  ];
18112
+ let effectiveTarget = opts?.target;
17325
18113
  if (!projectRoot || !existsSync10(join7(projectRoot, "iamu"))) {
17326
18114
  checks.push({
17327
18115
  category: "project",
@@ -17341,6 +18129,15 @@ async function runDoctorChecks(projectRoot) {
17341
18129
  checks.push(...await checkProjectConfiguration(projectRoot));
17342
18130
  checks.push(...await checkProjectContextAndEntities(projectRoot));
17343
18131
  checks.push(...await checkAgentIntegrations(projectRoot));
18132
+ checks.push(...await checkProjectHarnesses(projectRoot));
18133
+ const testability = await checkProjectTestabilityContract(projectRoot);
18134
+ checks.push(...testability.checks);
18135
+ if (!effectiveTarget && testability.targetUrl) {
18136
+ effectiveTarget = testability.targetUrl;
18137
+ }
18138
+ }
18139
+ if (effectiveTarget) {
18140
+ checks.push(...await checkTargetHealth(effectiveTarget));
17344
18141
  }
17345
18142
  const okCount = checks.filter((c) => c.status === "ok").length;
17346
18143
  const warnCount = checks.filter((c) => c.status === "warn").length;
@@ -17357,7 +18154,7 @@ async function runDoctorChecks(projectRoot) {
17357
18154
  }
17358
18155
  async function doctor(opts) {
17359
18156
  const projectRoot = findProjectRoot(process.cwd()) ?? void 0;
17360
- const result = await runDoctorChecks(projectRoot);
18157
+ const result = await runDoctorChecks(projectRoot, { target: opts?.target });
17361
18158
  if (opts?.json) {
17362
18159
  console.log(JSON.stringify(result, null, 2));
17363
18160
  if (!result.ok) process.exitCode = 1;
@@ -17371,7 +18168,8 @@ async function doctor(opts) {
17371
18168
  { key: "browser", title: "Browser Automation" },
17372
18169
  { key: "project", title: "Project Configuration" },
17373
18170
  { key: "agent", title: "Agent Integrations" },
17374
- { key: "cloud", title: "Cloud & Authentication" }
18171
+ { key: "cloud", title: "Cloud & Authentication" },
18172
+ { key: "target", title: "Target & Testability" }
17375
18173
  ];
17376
18174
  for (const cat of categories) {
17377
18175
  const catChecks = result.checks.filter((c) => c.category === cat.key);
@@ -17849,13 +18647,13 @@ __export(validate_exports, {
17849
18647
  validate: () => validate
17850
18648
  });
17851
18649
  import { randomUUID as randomUUID8 } from "crypto";
17852
- import { promises as fs24 } from "fs";
17853
- import path23 from "path";
18650
+ import { promises as fs27 } from "fs";
18651
+ import path24 from "path";
17854
18652
  import chalk23 from "chalk";
17855
18653
  function inferTypeFromPath(filePath, projectRoot) {
17856
- const rel = path23.relative(path23.join(projectRoot, "iamu"), filePath);
17857
- if (rel.startsWith("..") || path23.isAbsolute(rel)) return null;
17858
- const segments = rel.split(path23.sep);
18654
+ const rel = path24.relative(path24.join(projectRoot, "iamu"), filePath);
18655
+ if (rel.startsWith("..") || path24.isAbsolute(rel)) return null;
18656
+ const segments = rel.split(path24.sep);
17859
18657
  if (segments.length < 2) return null;
17860
18658
  return TYPE_BY_ALIAS.get(segments[0]) ?? null;
17861
18659
  }
@@ -17885,7 +18683,7 @@ function validateFile(filePath, raw, projectRoot) {
17885
18683
  );
17886
18684
  return result;
17887
18685
  }
17888
- if (path23.basename(filePath) === "context.yml") {
18686
+ if (path24.basename(filePath) === "context.yml") {
17889
18687
  applyZodValidation(contextFrontmatterSchema, frontmatter, result);
17890
18688
  const warnings = [];
17891
18689
  const hasNotes = Boolean(
@@ -17945,17 +18743,17 @@ async function buildRepairLookups(projectRoot) {
17945
18743
  }
17946
18744
  }
17947
18745
  const runScenario = /* @__PURE__ */ new Map();
17948
- const runsDir2 = path23.join(projectRoot, "iamu", ".runs");
18746
+ const runsDir2 = path24.join(projectRoot, "iamu", ".runs");
17949
18747
  let runIds = [];
17950
18748
  try {
17951
- runIds = await fs24.readdir(runsDir2);
18749
+ runIds = await fs27.readdir(runsDir2);
17952
18750
  } catch {
17953
18751
  }
17954
18752
  for (const runId of runIds) {
17955
18753
  try {
17956
18754
  const manifest = JSON.parse(
17957
- await fs24.readFile(
17958
- path23.join(runsDir2, runId, "walkthrough.json"),
18755
+ await fs27.readFile(
18756
+ path24.join(runsDir2, runId, "walkthrough.json"),
17959
18757
  "utf8"
17960
18758
  )
17961
18759
  );
@@ -17977,7 +18775,7 @@ async function fixFindings(projectRoot, results, dryRun) {
17977
18775
  let raw;
17978
18776
  let parsed;
17979
18777
  try {
17980
- raw = await fs24.readFile(result.path, "utf8");
18778
+ raw = await fs27.readFile(result.path, "utf8");
17981
18779
  parsed = parseFrontmatter(raw);
17982
18780
  } catch {
17983
18781
  continue;
@@ -17986,7 +18784,7 @@ async function fixFindings(projectRoot, results, dryRun) {
17986
18784
  parsed.frontmatter,
17987
18785
  {
17988
18786
  ...lookups,
17989
- fileSlug: path23.basename(result.path, ".md"),
18787
+ fileSlug: path24.basename(result.path, ".md"),
17990
18788
  newId: randomUUID8,
17991
18789
  body: parsed.body
17992
18790
  }
@@ -18021,9 +18819,9 @@ async function validate(filePath, opts) {
18021
18819
  if (process.exitCode === 1) return;
18022
18820
  const results = [];
18023
18821
  if (filePath) {
18024
- const abs = path23.isAbsolute(filePath) ? filePath : path23.resolve(process.cwd(), filePath);
18822
+ const abs = path24.isAbsolute(filePath) ? filePath : path24.resolve(process.cwd(), filePath);
18025
18823
  try {
18026
- const raw = await fs24.readFile(abs, "utf8");
18824
+ const raw = await fs27.readFile(abs, "utf8");
18027
18825
  results.push(validateFile(abs, raw, projectRoot));
18028
18826
  } catch (err) {
18029
18827
  commandError(
@@ -18033,24 +18831,24 @@ async function validate(filePath, opts) {
18033
18831
  return;
18034
18832
  }
18035
18833
  } else if (opts.run) {
18036
- const findingsDir = path23.join(projectRoot, "iamu", ENTITY_DIRS.finding);
18834
+ const findingsDir = path24.join(projectRoot, "iamu", ENTITY_DIRS.finding);
18037
18835
  let names = [];
18038
18836
  try {
18039
- names = await fs24.readdir(findingsDir);
18837
+ names = await fs27.readdir(findingsDir);
18040
18838
  } catch {
18041
18839
  }
18042
18840
  for (const name of names) {
18043
18841
  if (!name.endsWith(".md")) continue;
18044
- const abs = path23.join(findingsDir, name);
18842
+ const abs = path24.join(findingsDir, name);
18045
18843
  try {
18046
- const raw = await fs24.readFile(abs, "utf8");
18844
+ const raw = await fs27.readFile(abs, "utf8");
18047
18845
  const { frontmatter } = parseFrontmatter(raw);
18048
18846
  if (frontmatter.run === opts.run) {
18049
18847
  results.push(validateFile(abs, raw, projectRoot));
18050
18848
  }
18051
18849
  } catch {
18052
18850
  results.push({
18053
- path: path23.relative(projectRoot, abs),
18851
+ path: path24.relative(projectRoot, abs),
18054
18852
  valid: false,
18055
18853
  errors: ["Cannot read file or parse frontmatter"]
18056
18854
  });
@@ -18083,31 +18881,31 @@ async function validate(filePath, opts) {
18083
18881
  } else {
18084
18882
  const typesToCheck = typeFilter ? ENTITY_TYPES.filter((t) => typeFilter.has(t)) : ENTITY_TYPES;
18085
18883
  for (const type2 of typesToCheck) {
18086
- const iamuTypeDir = path23.join(projectRoot, "iamu", ENTITY_DIRS[type2]);
18884
+ const iamuTypeDir = path24.join(projectRoot, "iamu", ENTITY_DIRS[type2]);
18087
18885
  let names;
18088
18886
  try {
18089
- names = await fs24.readdir(iamuTypeDir);
18887
+ names = await fs27.readdir(iamuTypeDir);
18090
18888
  } catch {
18091
18889
  continue;
18092
18890
  }
18093
18891
  for (const name of names) {
18094
18892
  if (!name.endsWith(".md")) continue;
18095
- const abs = path23.join(iamuTypeDir, name);
18893
+ const abs = path24.join(iamuTypeDir, name);
18096
18894
  try {
18097
- const raw = await fs24.readFile(abs, "utf8");
18895
+ const raw = await fs27.readFile(abs, "utf8");
18098
18896
  results.push(validateFile(abs, raw, projectRoot));
18099
18897
  } catch {
18100
18898
  results.push({
18101
- path: path23.relative(projectRoot, abs),
18899
+ path: path24.relative(projectRoot, abs),
18102
18900
  valid: false,
18103
18901
  errors: ["Cannot read file"]
18104
18902
  });
18105
18903
  }
18106
18904
  }
18107
18905
  }
18108
- const ctxPath = path23.join(projectRoot, "iamu", "context.yml");
18906
+ const ctxPath = path24.join(projectRoot, "iamu", "context.yml");
18109
18907
  try {
18110
- const raw = await fs24.readFile(ctxPath, "utf8");
18908
+ const raw = await fs27.readFile(ctxPath, "utf8");
18111
18909
  results.push(validateFile(ctxPath, raw, projectRoot));
18112
18910
  } catch {
18113
18911
  }
@@ -18126,19 +18924,19 @@ async function validate(filePath, opts) {
18126
18924
  valid,
18127
18925
  errors: results.filter((r) => !r.valid).flatMap(
18128
18926
  (r) => r.errors.map((e) => ({
18129
- path: path23.relative(projectRoot, r.path),
18927
+ path: path24.relative(projectRoot, r.path),
18130
18928
  message: e
18131
18929
  }))
18132
18930
  ),
18133
18931
  warnings: results.filter((r) => r.warnings && r.warnings.length > 0).flatMap(
18134
18932
  (r) => (r.warnings ?? []).map((w) => ({
18135
- path: path23.relative(projectRoot, r.path),
18933
+ path: path24.relative(projectRoot, r.path),
18136
18934
  message: w
18137
18935
  }))
18138
18936
  ),
18139
18937
  ...opts.fix ? {
18140
18938
  repaired: repaired.map((r) => ({
18141
- path: path23.relative(projectRoot, r.path),
18939
+ path: path24.relative(projectRoot, r.path),
18142
18940
  changes: r.repairs,
18143
18941
  valid: r.valid
18144
18942
  }))
@@ -18160,7 +18958,7 @@ async function validate(filePath, opts) {
18160
18958
  }
18161
18959
  let hasErrors = false;
18162
18960
  for (const r of results) {
18163
- const relPath = path23.relative(projectRoot, r.path);
18961
+ const relPath = path24.relative(projectRoot, r.path);
18164
18962
  if (r.valid) {
18165
18963
  if (r.warnings && r.warnings.length > 0) {
18166
18964
  console.log(` ${symbols.warning} ` + chalk23.yellow(relPath));
@@ -18351,10 +19149,26 @@ function defaultFrontmatter(type2, id, slug, name, opts) {
18351
19149
  ...base,
18352
19150
  name,
18353
19151
  role: opts.role ?? "TODO",
18354
- ...opts.disposition ? { disposition: opts.disposition } : {}
19152
+ ...opts.disposition ? { disposition: opts.disposition } : {},
19153
+ ...opts.access ? { access: opts.access } : {}
18355
19154
  };
18356
- case "outline":
18357
- return { ...base, title: name, ...description };
19155
+ case "outline": {
19156
+ const tests = Array.isArray(opts.humanTest) ? opts.humanTest : opts.humanTest ? [opts.humanTest] : [];
19157
+ return {
19158
+ ...base,
19159
+ title: name,
19160
+ ...description,
19161
+ ...opts.capability ? { primary_capability: opts.capability } : {},
19162
+ ...opts.tension ? { tension: opts.tension } : {},
19163
+ ...opts.narrative ? { narrative: opts.narrative } : {},
19164
+ ...tests.length > 0 ? {
19165
+ human_tests: tests.map((t, idx) => ({
19166
+ id: `test-${idx + 1}`,
19167
+ question: t
19168
+ }))
19169
+ } : {}
19170
+ };
19171
+ }
18358
19172
  case "capability":
18359
19173
  return { ...base, title: name, ...description };
18360
19174
  case "source":
@@ -18364,13 +19178,21 @@ function defaultFrontmatter(type2, id, slug, name, opts) {
18364
19178
  url: opts.url,
18365
19179
  authority_level: opts.authority ?? "informational"
18366
19180
  };
18367
- case "scenario":
19181
+ case "scenario": {
19182
+ const stateObj = opts.stateDescription || opts.stateApproximation ? {
19183
+ state: {
19184
+ ...opts.stateDescription ? { description: opts.stateDescription } : {},
19185
+ ...opts.stateApproximation ? { approximation: opts.stateApproximation } : {}
19186
+ }
19187
+ } : {};
18368
19188
  return {
18369
19189
  ...base,
18370
19190
  outline: opts.outline ?? null,
18371
19191
  persona: opts.persona ?? null,
18372
- ...opts.entry ? { entry_path: opts.entry } : {}
19192
+ ...opts.entry ? { entry_path: opts.entry } : {},
19193
+ ...stateObj
18373
19194
  };
19195
+ }
18374
19196
  default:
18375
19197
  return base;
18376
19198
  }
@@ -18410,7 +19232,14 @@ async function newEntity(type2, opts) {
18410
19232
  return;
18411
19233
  }
18412
19234
  }
18413
- const slug = opts.slug ? slugify(opts.slug) : slugify(opts.name);
19235
+ let slug;
19236
+ if (opts.slug) {
19237
+ slug = slugSchema.safeParse(opts.slug).success ? opts.slug : slugify(opts.slug);
19238
+ } else if (slugSchema.safeParse(opts.name).success) {
19239
+ slug = opts.name;
19240
+ } else {
19241
+ slug = slugify(opts.name);
19242
+ }
18414
19243
  if (await entityExists(projectRoot, entityType, slug)) {
18415
19244
  commandError(
18416
19245
  `Entity already exists: iamu/${ENTITY_DIRS[entityType]}/${slug}.md`,
@@ -18420,7 +19249,7 @@ async function newEntity(type2, opts) {
18420
19249
  }
18421
19250
  const id = randomUUID9();
18422
19251
  const frontmatter = defaultFrontmatter(entityType, id, slug, opts.name, opts);
18423
- const body = "";
19252
+ const body = opts.body ?? (entityType === "outline" ? "" : opts.narrative) ?? "";
18424
19253
  try {
18425
19254
  const record = await writeEntity(
18426
19255
  projectRoot,
@@ -18465,6 +19294,797 @@ var init_new_entity = __esm({
18465
19294
  }
18466
19295
  });
18467
19296
 
19297
+ // src/commands/explore-crawl.ts
19298
+ function candidateSig(c) {
19299
+ return `${c.role}
19300
+ ${c.name}
19301
+ ${c.occurrence}`;
19302
+ }
19303
+ function originOf(url) {
19304
+ try {
19305
+ return new URL(url).origin;
19306
+ } catch {
19307
+ return null;
19308
+ }
19309
+ }
19310
+ function skipReason(c, origin, deny) {
19311
+ if (deny.some((re) => re.test(c.name) || c.href && re.test(c.href))) {
19312
+ return "denied";
19313
+ }
19314
+ if (c.submit) return "submit";
19315
+ if (UNSAFE_NAME.test(c.name)) return "unsafe_name";
19316
+ if (c.href) {
19317
+ let u = null;
19318
+ try {
19319
+ u = new URL(c.href);
19320
+ } catch {
19321
+ }
19322
+ if (u && u.protocol !== "javascript:") {
19323
+ if (u.protocol !== "http:" && u.protocol !== "https:") return "external";
19324
+ if (u.origin !== origin) return "external";
19325
+ if (UNSAFE_PATH.test(u.pathname)) return "unsafe_name";
19326
+ }
19327
+ if (c.download) return "download";
19328
+ }
19329
+ return null;
19330
+ }
19331
+ async function settle(page, settleMs) {
19332
+ await page.waitForLoadState("domcontentloaded", { timeout: NAV_TIMEOUT_MS }).catch(() => {
19333
+ });
19334
+ await page.waitForLoadState("networkidle", { timeout: settleMs }).catch(() => {
19335
+ });
19336
+ }
19337
+ async function readPage(page) {
19338
+ return page.evaluate(READ_PAGE);
19339
+ }
19340
+ async function markDocument(page) {
19341
+ await page.evaluate("window.__iamuDoc = true").catch(() => {
19342
+ });
19343
+ }
19344
+ async function isSameDocument(page) {
19345
+ return page.evaluate("window.__iamuDoc === true").catch(() => false);
19346
+ }
19347
+ async function clickCandidate(page, want) {
19348
+ const state = await readPage(page);
19349
+ const c = state.candidates.find(
19350
+ (x) => candidateSig(x) === candidateSig(want)
19351
+ );
19352
+ if (!c) return false;
19353
+ await page.locator(`[data-iamu-x="${c.index}"]`).click({ timeout: CLICK_TIMEOUT_MS });
19354
+ return true;
19355
+ }
19356
+ async function crawl(page, opts) {
19357
+ const origin = originOf(opts.startUrl);
19358
+ if (!origin) throw new Error(`Not a URL: ${opts.startUrl}`);
19359
+ let blocked = [];
19360
+ await page.route("**/*", (route) => {
19361
+ const req = route.request();
19362
+ if (SAFE_METHODS.has(req.method())) return route.continue();
19363
+ let where = req.url();
19364
+ try {
19365
+ const u = new URL(where);
19366
+ where = u.origin === origin ? u.pathname : `${u.origin}${u.pathname}`;
19367
+ } catch {
19368
+ }
19369
+ blocked.push(`${req.method()} ${where}`);
19370
+ return route.abort("blockedbyclient");
19371
+ });
19372
+ const onDialog = (d) => {
19373
+ d.dismiss().catch(() => {
19374
+ });
19375
+ };
19376
+ const onPopup = (p) => {
19377
+ if (p !== page) p.close().catch(() => {
19378
+ });
19379
+ };
19380
+ page.on("dialog", onDialog);
19381
+ page.context().on("page", onPopup);
19382
+ try {
19383
+ return await crawlFrom(page, opts, origin, () => {
19384
+ const out = blocked;
19385
+ blocked = [];
19386
+ return out;
19387
+ });
19388
+ } finally {
19389
+ page.off("dialog", onDialog);
19390
+ page.context().off("page", onPopup);
19391
+ await page.unroute("**/*").catch(() => {
19392
+ });
19393
+ }
19394
+ }
19395
+ async function crawlFrom(page, opts, origin, takeBlocked) {
19396
+ const deny = opts.deny ?? [];
19397
+ const settleMs = opts.settleMs ?? 1500;
19398
+ const { budget } = opts;
19399
+ const stats = {
19400
+ surfaces: 0,
19401
+ actions_tried: 0,
19402
+ skipped: 0,
19403
+ blocked_requests: 0,
19404
+ errors: 0,
19405
+ truncated: false
19406
+ };
19407
+ const surfaces = [];
19408
+ const byKey = /* @__PURE__ */ new Map();
19409
+ const byPlaceView = /* @__PURE__ */ new Map();
19410
+ const signatureOf = (s) => exploreSignature(s.headings, s.title);
19411
+ function addSurface(state, place, view, path30, depth) {
19412
+ const key = exploreSurfaceKey(place, view);
19413
+ const surface = {
19414
+ key,
19415
+ url: state.url,
19416
+ place,
19417
+ view,
19418
+ signature: signatureOf(state),
19419
+ label: state.headings[0] ?? (state.title || place),
19420
+ headings: state.headings,
19421
+ fields: state.fields,
19422
+ depth,
19423
+ path: path30,
19424
+ actions: []
19425
+ };
19426
+ surfaces.push(surface);
19427
+ byKey.set(key, surface);
19428
+ byPlaceView.set(`${place}
19429
+ ${surface.signature}`, key);
19430
+ stats.surfaces++;
19431
+ opts.log?.(` ${" ".repeat(depth)}${key} ${surface.label}`);
19432
+ return surface;
19433
+ }
19434
+ async function goto(url) {
19435
+ await page.goto(url, {
19436
+ waitUntil: "domcontentloaded",
19437
+ timeout: NAV_TIMEOUT_MS
19438
+ });
19439
+ await settle(page, settleMs);
19440
+ }
19441
+ async function replay(path30) {
19442
+ if (!("goto" in (path30[0] ?? {}))) await goto(opts.startUrl);
19443
+ for (const step of path30) {
19444
+ if ("goto" in step) {
19445
+ await goto(step.goto);
19446
+ } else {
19447
+ if (!await clickCandidate(page, step.click)) return false;
19448
+ await settle(page, settleMs);
19449
+ }
19450
+ }
19451
+ return true;
19452
+ }
19453
+ await goto(opts.startUrl);
19454
+ if (await page.evaluate("Boolean(navigator.serviceWorker?.controller)").catch(() => false)) {
19455
+ throw new Error(
19456
+ "A service worker controls this page, and the write guard cannot see its requests. Explore without --attach (service workers are blocked there)."
19457
+ );
19458
+ }
19459
+ const first = await readPage(page);
19460
+ const firstPlace = explorePlaceKey(first.url);
19461
+ if (!firstPlace || originOf(first.url) !== origin) {
19462
+ throw new Error(`The start URL led off the product, to ${first.url}`);
19463
+ }
19464
+ addSurface(first, firstPlace, null, [], 0);
19465
+ for (let i = 0; i < surfaces.length; i++) {
19466
+ const surface = surfaces[i];
19467
+ if (!await replay(surface.path)) {
19468
+ stats.errors++;
19469
+ surface.actions.push({
19470
+ role: "",
19471
+ name: "",
19472
+ occurrence: 0,
19473
+ outcome: { kind: "error", message: LOST }
19474
+ });
19475
+ continue;
19476
+ }
19477
+ const home = await readPage(page);
19478
+ const seen = new Set(home.candidates.map(candidateSig));
19479
+ const work = home.candidates.map((c) => ({ c, via: [] }));
19480
+ const homeList = home.candidates.map(candidateSig).join("\n");
19481
+ let pristine = true;
19482
+ for (let w = 0; w < work.length; w++) {
19483
+ const { c, via } = work[w];
19484
+ const action = {
19485
+ role: c.role,
19486
+ name: c.name,
19487
+ occurrence: c.occurrence,
19488
+ ...c.href ? { href: c.href } : {},
19489
+ ...via.length > 0 ? { via } : {},
19490
+ outcome: { kind: "same" }
19491
+ };
19492
+ surface.actions.push(action);
19493
+ const reason = skipReason(c, origin, deny);
19494
+ if (reason) {
19495
+ action.outcome = { kind: "skipped", reason };
19496
+ stats.skipped++;
19497
+ continue;
19498
+ }
19499
+ if (surface.depth >= budget.max_depth || stats.actions_tried >= budget.max_actions) {
19500
+ action.outcome = { kind: "skipped", reason: "budget" };
19501
+ stats.skipped++;
19502
+ stats.truncated = true;
19503
+ continue;
19504
+ }
19505
+ stats.actions_tried++;
19506
+ takeBlocked();
19507
+ const click = {
19508
+ click: { role: c.role, name: c.name, occurrence: c.occurrence }
19509
+ };
19510
+ let outcome;
19511
+ let revealed = 0;
19512
+ let left = false;
19513
+ let lost = false;
19514
+ try {
19515
+ if (!pristine || via.length > 0) {
19516
+ if (!await replay([...surface.path, ...via])) {
19517
+ throw new Error(LOST);
19518
+ }
19519
+ }
19520
+ pristine = false;
19521
+ const before = await readPage(page);
19522
+ await markDocument(page);
19523
+ if (c.newTab && c.href) {
19524
+ await goto(c.href);
19525
+ } else {
19526
+ if (!await clickCandidate(page, c)) {
19527
+ throw new Error("control is gone");
19528
+ }
19529
+ await settle(page, settleMs);
19530
+ }
19531
+ const after = await readPage(page);
19532
+ if (originOf(after.url) !== origin) {
19533
+ outcome = { kind: "external", url: after.url };
19534
+ left = true;
19535
+ } else {
19536
+ const place = explorePlaceKey(after.url) ?? before.url;
19537
+ const sig = signatureOf(after);
19538
+ const sameDoc = !(c.newTab && c.href) && await isSameDocument(page);
19539
+ const beforePlace = explorePlaceKey(before.url);
19540
+ let key = byPlaceView.get(`${place}
19541
+ ${sig}`);
19542
+ let view = null;
19543
+ if (!key) {
19544
+ const inPage = sameDoc && place === beforePlace;
19545
+ view = inPage ? sig : null;
19546
+ key = exploreSurfaceKey(place, view);
19547
+ if (byKey.has(key)) byPlaceView.set(`${place}
19548
+ ${sig}`, key);
19549
+ }
19550
+ if (key === surface.key) {
19551
+ outcome = { kind: "same" };
19552
+ const list2 = after.candidates.map(candidateSig).join("\n");
19553
+ for (const n of after.candidates) {
19554
+ const s = candidateSig(n);
19555
+ if (seen.has(s)) continue;
19556
+ seen.add(s);
19557
+ work.push({ c: n, via: [...via, click] });
19558
+ revealed++;
19559
+ }
19560
+ if (list2 === homeList) pristine = true;
19561
+ } else {
19562
+ outcome = { kind: "surface", surface: key };
19563
+ if (!byKey.has(key)) {
19564
+ if (stats.surfaces >= budget.max_surfaces) {
19565
+ stats.truncated = true;
19566
+ } else {
19567
+ const path30 = !sameDoc && view === null ? [{ goto: after.url }] : [...surface.path, ...via, click];
19568
+ addSurface(after, place, view, path30, surface.depth + 1);
19569
+ }
19570
+ }
19571
+ }
19572
+ }
19573
+ } catch (err) {
19574
+ const msg = err instanceof Error ? err.message : String(err);
19575
+ outcome = { kind: "error", message: msg.split("\n")[0].slice(0, 200) };
19576
+ stats.errors++;
19577
+ lost = msg === LOST;
19578
+ }
19579
+ action.outcome = outcome;
19580
+ if (revealed > 0) action.revealed = revealed;
19581
+ const writes = takeBlocked();
19582
+ if (writes.length > 0) {
19583
+ action.blocked_requests = [...new Set(writes)];
19584
+ stats.blocked_requests += writes.length;
19585
+ }
19586
+ if (left) pristine = false;
19587
+ if (lost) break;
19588
+ }
19589
+ }
19590
+ return { surfaces, stats };
19591
+ }
19592
+ var UNSAFE_NAME, UNSAFE_PATH, SAFE_METHODS, CLICK_TIMEOUT_MS, LOST, NAV_TIMEOUT_MS, READ_PAGE;
19593
+ var init_explore_crawl = __esm({
19594
+ "src/commands/explore-crawl.ts"() {
19595
+ "use strict";
19596
+ init_dist2();
19597
+ UNSAFE_NAME = new RegExp(
19598
+ `(?<![\\p{L}\\p{N}])(${[
19599
+ // English
19600
+ "delete",
19601
+ "remove",
19602
+ "destroy",
19603
+ "erase",
19604
+ "pay",
19605
+ "pay now",
19606
+ "purchase",
19607
+ "buy",
19608
+ "checkout",
19609
+ "place order",
19610
+ "order now",
19611
+ "send",
19612
+ "submit",
19613
+ "save",
19614
+ "confirm",
19615
+ "approve",
19616
+ "reject",
19617
+ "decline",
19618
+ "publish",
19619
+ "archive",
19620
+ "unsubscribe",
19621
+ "log ?out",
19622
+ "sign ?out",
19623
+ "reset",
19624
+ "transfer",
19625
+ "invite",
19626
+ "upload",
19627
+ "import",
19628
+ "deactivate",
19629
+ "close account",
19630
+ // Swedish
19631
+ "ta bort",
19632
+ "radera",
19633
+ "betala",
19634
+ "k\xF6p",
19635
+ "best\xE4ll",
19636
+ "skicka",
19637
+ "skicka in",
19638
+ "spara",
19639
+ "bekr\xE4fta",
19640
+ "godk\xE4nn",
19641
+ "avsl\xE5",
19642
+ "publicera",
19643
+ "arkivera",
19644
+ "logga ut",
19645
+ "\xE5terst\xE4ll",
19646
+ "\xF6verf\xF6r",
19647
+ "bjud in",
19648
+ "ladda upp"
19649
+ ].join("|")})(?![\\p{L}\\p{N}])`,
19650
+ "iu"
19651
+ );
19652
+ UNSAFE_PATH = /(^|\/)(log-?out|sign-?out|logga-?ut|delete|remove|destroy|unsubscribe)(\/|$)/i;
19653
+ SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
19654
+ CLICK_TIMEOUT_MS = 3e3;
19655
+ LOST = "could not return to surface";
19656
+ NAV_TIMEOUT_MS = 15e3;
19657
+ READ_PAGE = String.raw`(() => {
19658
+ const SELECTOR = [
19659
+ "a[href]", "button", "summary",
19660
+ 'input[type="button"]', 'input[type="submit"]',
19661
+ 'input[type="reset"]', 'input[type="image"]',
19662
+ '[role="button"]', '[role="link"]', '[role="tab"]',
19663
+ '[role="menuitem"]', '[role="menuitemradio"]', '[role="menuitemcheckbox"]',
19664
+ ].join(",");
19665
+ const clean = (s) => (s ?? "").replace(/\s+/g, " ").trim();
19666
+ const visible = (el) => {
19667
+ if (typeof el.checkVisibility === "function" &&
19668
+ !el.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true })) {
19669
+ return false;
19670
+ }
19671
+ const r = el.getBoundingClientRect();
19672
+ return r.width > 0 && r.height > 0;
19673
+ };
19674
+ const textOf = (id) => clean(document.getElementById(id)?.innerText);
19675
+ const nameOf = (el) => {
19676
+ const label = clean(el.getAttribute("aria-label"));
19677
+ if (label) return label;
19678
+ const by = el.getAttribute("aria-labelledby");
19679
+ if (by) {
19680
+ const t = clean(by.split(/\s+/).map(textOf).join(" "));
19681
+ if (t) return t;
19682
+ }
19683
+ if (el instanceof HTMLInputElement) {
19684
+ return clean(el.value) || clean(el.alt) || clean(el.title);
19685
+ }
19686
+ const text = clean(el.innerText);
19687
+ if (text) return text.slice(0, 80);
19688
+ const img = el.querySelector("img[alt]");
19689
+ return clean(img?.getAttribute("alt")) || clean(el.title);
19690
+ };
19691
+ const roleOf = (el) =>
19692
+ el.getAttribute("role") || (el.tagName === "A" ? "link" : "button");
19693
+
19694
+ for (const el of document.querySelectorAll("[data-iamu-x]")) {
19695
+ el.removeAttribute("data-iamu-x");
19696
+ }
19697
+ const seen = new Map();
19698
+ const candidates = [];
19699
+ let index = 0;
19700
+ for (const el of document.querySelectorAll(SELECTOR)) {
19701
+ if (!visible(el)) continue;
19702
+ if (el.disabled || el.getAttribute("aria-disabled") === "true") continue;
19703
+ const role = roleOf(el);
19704
+ const name = nameOf(el);
19705
+ const sig = role + "\n" + name;
19706
+ const occurrence = seen.get(sig) ?? 0;
19707
+ seen.set(sig, occurrence + 1);
19708
+ let submit = false;
19709
+ if (el instanceof HTMLButtonElement) {
19710
+ // A button in a form submits unless it says type="button".
19711
+ submit = el.form
19712
+ ? el.type === "submit" || el.type === "reset"
19713
+ : el.getAttribute("type") === "submit";
19714
+ } else if (el instanceof HTMLInputElement) {
19715
+ submit = ["submit", "reset", "image"].includes(el.type);
19716
+ }
19717
+ const anchor = el instanceof HTMLAnchorElement ? el : null;
19718
+ el.setAttribute("data-iamu-x", String(index));
19719
+ candidates.push({
19720
+ index, role, name, occurrence,
19721
+ href: anchor ? anchor.href : null,
19722
+ download: anchor ? anchor.hasAttribute("download") : false,
19723
+ newTab: anchor ? anchor.target === "_blank" : false,
19724
+ submit,
19725
+ });
19726
+ index++;
19727
+ }
19728
+
19729
+ const headings = [];
19730
+ for (const el of document.querySelectorAll(
19731
+ 'h1, h2, [role="heading"][aria-level="1"], [role="heading"][aria-level="2"]',
19732
+ )) {
19733
+ if (!visible(el)) continue;
19734
+ const t = clean(el.innerText);
19735
+ if (t) headings.push(t.slice(0, 120));
19736
+ }
19737
+
19738
+ const fields = [];
19739
+ for (const f of document.querySelectorAll("input, select, textarea")) {
19740
+ if (["hidden", "submit", "button", "reset", "image"].includes(f.type)) continue;
19741
+ if (!visible(f)) continue;
19742
+ const forLabel = f.id
19743
+ ? document.querySelector('label[for="' + CSS.escape(f.id) + '"]')
19744
+ : null;
19745
+ const name =
19746
+ clean(f.getAttribute("aria-label")) ||
19747
+ clean(forLabel?.innerText) ||
19748
+ clean(f.closest("label")?.innerText) ||
19749
+ clean(f.placeholder) ||
19750
+ clean(f.name);
19751
+ if (name && !fields.includes(name)) fields.push(name.slice(0, 80));
19752
+ if (fields.length >= 40) break;
19753
+ }
19754
+
19755
+ return { url: location.href, title: clean(document.title), headings, fields, candidates };
19756
+ })()`;
19757
+ }
19758
+ });
19759
+
19760
+ // src/commands/explore.ts
19761
+ var explore_exports = {};
19762
+ __export(explore_exports, {
19763
+ exploreCoverageCommand: () => exploreCoverageCommand,
19764
+ exploreDiffCommand: () => exploreDiffCommand,
19765
+ exploreList: () => exploreList,
19766
+ exploreRun: () => exploreRun
19767
+ });
19768
+ import { promises as fs28 } from "fs";
19769
+ import path25 from "path";
19770
+ import chalk26 from "chalk";
19771
+ function fail2(message, json2) {
19772
+ if (json2) console.error(JSON.stringify({ error: message }));
19773
+ else console.error(chalk26.red(` ${message}`));
19774
+ process.exitCode = 1;
19775
+ }
19776
+ function positiveInt(raw, fallback, flag) {
19777
+ if (raw === void 0) return fallback;
19778
+ const n = Number(raw);
19779
+ if (!Number.isInteger(n) || n < 1) {
19780
+ throw new Error(`${flag} must be a positive whole number, got "${raw}"`);
19781
+ }
19782
+ return n;
19783
+ }
19784
+ async function testabilityTarget2(projectRoot) {
19785
+ try {
19786
+ const raw = await fs28.readFile(
19787
+ path25.join(projectRoot, "iamu", "testability.md"),
19788
+ "utf8"
19789
+ );
19790
+ const { frontmatter } = parseFrontmatter(raw);
19791
+ return typeof frontmatter.target_url === "string" ? frontmatter.target_url : null;
19792
+ } catch {
19793
+ return null;
19794
+ }
19795
+ }
19796
+ async function openPage(opts) {
19797
+ const { chromium } = await import("playwright-core");
19798
+ const viewport = parseViewport(opts.viewport) ?? {
19799
+ width: 1280,
19800
+ height: 800
19801
+ };
19802
+ if (opts.attach) {
19803
+ const state = readBrowseState();
19804
+ if (!state) {
19805
+ throw new Error(
19806
+ "No browser to attach to \u2014 start one with `iamu browse start` and sign in first."
19807
+ );
19808
+ }
19809
+ const browser2 = await chromium.connectOverCDP(
19810
+ `http://127.0.0.1:${state.port}`
19811
+ );
19812
+ const context = browser2.contexts()[0] ?? await browser2.newContext();
19813
+ const page2 = await context.newPage();
19814
+ await page2.setViewportSize(viewport);
19815
+ return {
19816
+ page: page2,
19817
+ close: async () => {
19818
+ await page2.close().catch(() => {
19819
+ });
19820
+ await browser2.close().catch(() => {
19821
+ });
19822
+ }
19823
+ };
19824
+ }
19825
+ const chrome = findChrome();
19826
+ if (!chrome) {
19827
+ throw new Error(
19828
+ "No Chrome or Chromium found \u2014 install one or set IAMU_BROWSER."
19829
+ );
19830
+ }
19831
+ const browser = await chromium.launch({
19832
+ executablePath: chrome,
19833
+ headless: true,
19834
+ chromiumSandbox: !shouldDisableSandbox()
19835
+ });
19836
+ const page = await browser.newPage({ viewport, serviceWorkers: "block" });
19837
+ return { page, close: () => browser.close().catch(() => {
19838
+ }) };
19839
+ }
19840
+ async function exploreRun(url, opts) {
19841
+ const ctx = await resolveProject(opts);
19842
+ if (!ctx) return;
19843
+ const target = url ?? await testabilityTarget2(ctx.projectRoot);
19844
+ if (!target) {
19845
+ fail2(
19846
+ "No URL to explore \u2014 pass one (iamu explore http://localhost:3000) or set target_url in iamu/testability.md.",
19847
+ opts.json
19848
+ );
19849
+ return;
19850
+ }
19851
+ let startUrl;
19852
+ let budget;
19853
+ let deny;
19854
+ try {
19855
+ startUrl = new URL(target).href;
19856
+ budget = {
19857
+ max_surfaces: positiveInt(
19858
+ opts.maxSurfaces,
19859
+ DEFAULTS.max_surfaces,
19860
+ "--max-surfaces"
19861
+ ),
19862
+ max_actions: positiveInt(
19863
+ opts.maxActions,
19864
+ DEFAULTS.max_actions,
19865
+ "--max-actions"
19866
+ ),
19867
+ max_depth: positiveInt(opts.maxDepth, DEFAULTS.max_depth, "--max-depth")
19868
+ };
19869
+ deny = (opts.deny ?? []).map((d) => new RegExp(d, "i"));
19870
+ } catch (err) {
19871
+ fail2(err.message, opts.json);
19872
+ return;
19873
+ }
19874
+ const quiet = opts.json || process.env.IAMU_QUIET === "1";
19875
+ if (!quiet) {
19876
+ console.error(chalk26.dim(` Exploring ${startUrl} (read-only)\u2026`));
19877
+ }
19878
+ const now = /* @__PURE__ */ new Date();
19879
+ let opened = null;
19880
+ let result;
19881
+ try {
19882
+ opened = await openPage(opts);
19883
+ result = await crawl(opened.page, {
19884
+ startUrl,
19885
+ budget,
19886
+ deny,
19887
+ log: quiet ? void 0 : (line) => console.error(chalk26.dim(line))
19888
+ });
19889
+ } catch (err) {
19890
+ fail2(err.message.split("\n")[0], opts.json);
19891
+ return;
19892
+ } finally {
19893
+ await opened?.close();
19894
+ }
19895
+ const build = detectBuild({ cwd: ctx.projectRoot });
19896
+ const map2 = {
19897
+ schema: 1,
19898
+ id: composeExploreId(now),
19899
+ created_at: now.toISOString(),
19900
+ target_url: startUrl,
19901
+ ...build ? { build: { ...build } } : {},
19902
+ budget,
19903
+ stats: result.stats,
19904
+ surfaces: result.surfaces
19905
+ };
19906
+ const file = await writeExploreMap(ctx.projectRoot, map2);
19907
+ const rel = path25.relative(process.cwd(), file);
19908
+ if (opts.json) {
19909
+ console.log(JSON.stringify({ id: map2.id, path: rel, stats: map2.stats }));
19910
+ return;
19911
+ }
19912
+ const s = map2.stats;
19913
+ console.log(
19914
+ `
19915
+ ${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` : ""}`
19916
+ );
19917
+ if (s.truncated) {
19918
+ console.log(
19919
+ chalk26.yellow(
19920
+ " Stopped by the budget before the product ran out \u2014 raise --max-surfaces / --max-actions / --max-depth to go further."
19921
+ )
19922
+ );
19923
+ }
19924
+ console.log(` Map: ${rel}`);
19925
+ console.log(chalk26.dim(" Next: iamu explore coverage"));
19926
+ }
19927
+ async function resolveMapId(projectRoot, id, offsetFromEnd = 1) {
19928
+ if (id && id !== "latest") return id;
19929
+ const ids = await listExploreMaps(projectRoot);
19930
+ return ids.at(-offsetFromEnd) ?? null;
19931
+ }
19932
+ async function exploreList(opts) {
19933
+ const ctx = await resolveProject(opts);
19934
+ if (!ctx) return;
19935
+ const rows = [];
19936
+ for (const id of await listExploreMaps(ctx.projectRoot)) {
19937
+ try {
19938
+ const m = await readExploreMap(ctx.projectRoot, id);
19939
+ rows.push({ id, target_url: m.target_url, surfaces: m.surfaces.length });
19940
+ } catch {
19941
+ }
19942
+ }
19943
+ if (opts.json) {
19944
+ console.log(JSON.stringify({ maps: rows }));
19945
+ return;
19946
+ }
19947
+ if (rows.length === 0) {
19948
+ console.log(" No maps yet. Run: iamu explore <url>");
19949
+ return;
19950
+ }
19951
+ for (const r of rows) {
19952
+ console.log(
19953
+ ` ${r.id} ${r.surfaces} surfaces ${chalk26.dim(r.target_url)}`
19954
+ );
19955
+ }
19956
+ }
19957
+ async function exploreCoverageCommand(mapId, opts) {
19958
+ const ctx = await resolveProject(opts);
19959
+ if (!ctx) return;
19960
+ const id = await resolveMapId(ctx.projectRoot, mapId);
19961
+ if (!id) {
19962
+ fail2("No map yet. Run: iamu explore <url>", opts.json);
19963
+ return;
19964
+ }
19965
+ let map2;
19966
+ try {
19967
+ map2 = await readExploreMap(ctx.projectRoot, id);
19968
+ } catch (err) {
19969
+ fail2(err.message, opts.json);
19970
+ return;
19971
+ }
19972
+ const cov = exploreCoverage(map2, await readAllRunSteps(ctx.projectRoot));
19973
+ if (opts.json) {
19974
+ console.log(JSON.stringify(cov));
19975
+ return;
19976
+ }
19977
+ console.log(
19978
+ `
19979
+ Map ${id} (${map2.surfaces.length} surfaces) against ${cov.runs} runs, ${cov.steps} steps`
19980
+ );
19981
+ console.log(
19982
+ ` ${chalk26.green(`${cov.reached} reached`)} \xB7 ${chalk26.yellow(`${cov.place_only} URL reached, view unclear`)} \xB7 ${chalk26.red(`${cov.unreached} never reached`)}`
19983
+ );
19984
+ const section = (title, rows, color) => {
19985
+ if (rows.length === 0) return;
19986
+ console.log(`
19987
+ ${color(title)}`);
19988
+ for (const r of rows) {
19989
+ console.log(
19990
+ ` ${r.key.padEnd(32)} ${r.label}${chalk26.dim(` (depth ${r.depth})`)}`
19991
+ );
19992
+ }
19993
+ };
19994
+ section(
19995
+ "Never reached by a run",
19996
+ cov.surfaces.filter((s) => s.status === "unreached"),
19997
+ chalk26.red
19998
+ );
19999
+ section(
20000
+ "URL reached, but no step names this view",
20001
+ cov.surfaces.filter((s) => s.status === "place_only"),
20002
+ chalk26.yellow
20003
+ );
20004
+ if (cov.run_only_places.length > 0) {
20005
+ console.log(`
20006
+ ${chalk26.cyan("Walked by runs, not in the map")}`);
20007
+ for (const p of cov.run_only_places) {
20008
+ console.log(
20009
+ ` ${p.place.padEnd(32)} ${p.steps} steps, ${p.runs.length} runs`
20010
+ );
20011
+ }
20012
+ }
20013
+ if (cov.runs === 0) {
20014
+ console.log(
20015
+ chalk26.dim(
20016
+ "\n No runs yet \u2014 every surface counts as never reached. Walk a scenario first (iamu-run)."
20017
+ )
20018
+ );
20019
+ }
20020
+ }
20021
+ async function exploreDiffCommand(a, b, opts) {
20022
+ const ctx = await resolveProject(opts);
20023
+ if (!ctx) return;
20024
+ const idA = a ?? await resolveMapId(ctx.projectRoot, void 0, 2);
20025
+ const idB = b ?? await resolveMapId(ctx.projectRoot, void 0, 1);
20026
+ if (!idA || !idB || idA === idB) {
20027
+ fail2(
20028
+ "Need two maps to compare \u2014 explore again (iamu explore <url>), or name both maps.",
20029
+ opts.json
20030
+ );
20031
+ return;
20032
+ }
20033
+ let mapA;
20034
+ let mapB;
20035
+ try {
20036
+ mapA = await readExploreMap(ctx.projectRoot, idA);
20037
+ mapB = await readExploreMap(ctx.projectRoot, idB);
20038
+ } catch (err) {
20039
+ fail2(err.message, opts.json);
20040
+ return;
20041
+ }
20042
+ const d = diffExploreMaps(mapA, mapB);
20043
+ if (opts.json) {
20044
+ console.log(JSON.stringify(d));
20045
+ return;
20046
+ }
20047
+ const pct = Math.round(d.surface_overlap * 100);
20048
+ console.log(`
20049
+ ${idA} \u2192 ${idB}`);
20050
+ console.log(
20051
+ ` ${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)`
20052
+ );
20053
+ if (d.only_a.length > 0) {
20054
+ console.log(`
20055
+ ${chalk26.red("Only in the first")}`);
20056
+ for (const s of d.only_a) console.log(` ${s.key.padEnd(32)} ${s.label}`);
20057
+ }
20058
+ if (d.only_b.length > 0) {
20059
+ console.log(`
20060
+ ${chalk26.green("Only in the second")}`);
20061
+ for (const s of d.only_b) console.log(` ${s.key.padEnd(32)} ${s.label}`);
20062
+ }
20063
+ if (d.changed_actions.length > 0) {
20064
+ console.log(
20065
+ `
20066
+ ${chalk26.yellow(`Controls differ on ${d.changed_actions.length} shared surfaces`)}`
20067
+ );
20068
+ for (const c of d.changed_actions) {
20069
+ console.log(` ${c.key} ${c.label}`);
20070
+ for (const x of c.only_a) console.log(chalk26.red(` - ${x}`));
20071
+ for (const x of c.only_b) console.log(chalk26.green(` + ${x}`));
20072
+ }
20073
+ }
20074
+ }
20075
+ var DEFAULTS;
20076
+ var init_explore = __esm({
20077
+ "src/commands/explore.ts"() {
20078
+ "use strict";
20079
+ init_dist2();
20080
+ init_build_info();
20081
+ init_browse();
20082
+ init_context4();
20083
+ init_explore_crawl();
20084
+ DEFAULTS = { max_surfaces: 40, max_actions: 300, max_depth: 6 };
20085
+ }
20086
+ });
20087
+
18468
20088
  // src/commands/completion.ts
18469
20089
  var completion_exports = {};
18470
20090
  __export(completion_exports, {
@@ -18655,13 +20275,14 @@ var init_completion = __esm({
18655
20275
  var instructions_walk_exports = {};
18656
20276
  __export(instructions_walk_exports, {
18657
20277
  composeWalkerPrompt: () => composeWalkerPrompt,
20278
+ extractUntestableBoundaries: () => extractUntestableBoundaries,
18658
20279
  instructionsWalk: () => instructionsWalk,
18659
20280
  revisitSection: () => revisitSection,
18660
20281
  viewportLine: () => viewportLine
18661
20282
  });
18662
20283
  import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
18663
20284
  import { join as join9 } from "path";
18664
- import chalk26 from "chalk";
20285
+ import chalk27 from "chalk";
18665
20286
  function selfCommand() {
18666
20287
  const q = (p) => /[\s"'$]/.test(p) ? JSON.stringify(p) : p;
18667
20288
  return `${q(process.execPath)} ${q(process.argv[1] ?? "iamu")}`;
@@ -18692,6 +20313,24 @@ function browseSurface(iamu, viewport) {
18692
20313
  ...viewport ? [viewportLine(viewport)] : []
18693
20314
  ].join("\n");
18694
20315
  }
20316
+ function extractUntestableBoundaries(body) {
20317
+ const match2 = body.match(
20318
+ /##\s+Untestable\s+Boundaries[^\n]*\n([\s\S]*?)(?=\n##|\s*$)/i
20319
+ );
20320
+ if (!match2) return [];
20321
+ const section = match2[1];
20322
+ const items = [];
20323
+ for (const line of section.split("\n")) {
20324
+ const trimmed = line.trim();
20325
+ if (trimmed.startsWith("- ") || trimmed.startsWith("\u2022 ") || trimmed.startsWith("* ")) {
20326
+ const item = trimmed.slice(2).trim();
20327
+ if (item && !item.toLowerCase().includes("none documented")) {
20328
+ items.push(item);
20329
+ }
20330
+ }
20331
+ }
20332
+ return items;
20333
+ }
18695
20334
  function guidanceLine(r) {
18696
20335
  const rule = r.rule.replace(/\s+/g, " ");
18697
20336
  return r.reason ? `- ${rule} (${r.reason.replace(/\s+/g, " ")})` : `- ${rule}`;
@@ -18764,6 +20403,23 @@ function composeWalkerPrompt(p) {
18764
20403
  "",
18765
20404
  "## First-person brief",
18766
20405
  p.brief ?? "",
20406
+ ...p.context ? ["", "## Product domain & regulatory context", p.context.trim()] : [],
20407
+ ...p.state?.description || p.state?.approximation ? [
20408
+ "",
20409
+ "## Expected state & semantic squinting",
20410
+ p.state.description ? `Expected state: ${p.state.description}` : "",
20411
+ p.state.approximation ? `Approximation strategy: ${p.state.approximation}` : "",
20412
+ "Squinting Guardrails:",
20413
+ "\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.",
20414
+ "\u2022 Squinting does NOT permit drifting to unrelated settings, account profiles, or off-target pages. Stay strictly within the scenario journey.",
20415
+ "\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."
20416
+ ].filter(Boolean) : [],
20417
+ ...p.untestableBoundaries && p.untestableBoundaries.length > 0 ? [
20418
+ "",
20419
+ "## Untestable Boundaries (DO NOT WALK)",
20420
+ "These areas are un-mocked or live external services. Do NOT attempt to navigate, submit, or interact with them:",
20421
+ ...p.untestableBoundaries.map((b) => `\u2022 ${b}`)
20422
+ ] : [],
18767
20423
  ...revisitSection(p.revisit ?? []),
18768
20424
  "",
18769
20425
  "## Access",
@@ -18792,7 +20448,7 @@ function composeWalkerPrompt(p) {
18792
20448
  `Screenshots to: ${rd}/screenshots/step-N.png \u2014 absolute paths.`,
18793
20449
  `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.`,
18794
20450
  "`--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.",
18795
- `Before you finish, write ${rd}/summary.json: {"what_worked": [...], "open_questions": [...]} \u2014 one sentence each, in the persona's language.`,
20451
+ `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.`,
18796
20452
  "",
18797
20453
  "Do not read the product's source code, other iamu/ entity files, or earlier runs: you know only what the persona knows.",
18798
20454
  "",
@@ -18902,6 +20558,25 @@ async function instructionsWalk(opts) {
18902
20558
  if (!targetUrl) {
18903
20559
  targetUrl = sfm.target_url ?? null;
18904
20560
  }
20561
+ const untestableBoundaries = [];
20562
+ try {
20563
+ const testabilityPath = join9(projectRoot, "iamu", "testability.md");
20564
+ if (existsSync12(testabilityPath)) {
20565
+ const raw = readFileSync8(testabilityPath, "utf-8");
20566
+ const { frontmatter, body } = parseFrontmatter(raw);
20567
+ if (!targetUrl && typeof frontmatter.target_url === "string" && frontmatter.target_url.trim().length > 0) {
20568
+ targetUrl = frontmatter.target_url.trim();
20569
+ }
20570
+ const fromFm = Array.isArray(
20571
+ frontmatter.untestable_boundaries
20572
+ ) ? frontmatter.untestable_boundaries.filter(Boolean) : [];
20573
+ const fromBody = extractUntestableBoundaries(body);
20574
+ untestableBoundaries.push(
20575
+ ...Array.from(/* @__PURE__ */ new Set([...fromFm, ...fromBody]))
20576
+ );
20577
+ }
20578
+ } catch {
20579
+ }
18905
20580
  if (!revisit) {
18906
20581
  try {
18907
20582
  revisit = await computeRevisitPlaces(projectRoot, sfm.slug, targetUrl);
@@ -18912,7 +20587,8 @@ async function instructionsWalk(opts) {
18912
20587
  const result = {
18913
20588
  scenario: {
18914
20589
  slug: sfm.slug,
18915
- brief: scenario.body.trim() || null
20590
+ brief: scenario.body.trim() || null,
20591
+ state: sfm.state ?? null
18916
20592
  },
18917
20593
  persona: {
18918
20594
  slug: sfm.persona,
@@ -18933,6 +20609,7 @@ async function instructionsWalk(opts) {
18933
20609
  methodology,
18934
20610
  dismissed_findings: dismissedFacts,
18935
20611
  tester_guidance: guidance,
20612
+ untestable_boundaries: untestableBoundaries,
18936
20613
  // Where fixes awaiting a re-run were seen (for you; the walker gets
18937
20614
  // the places only, never which findings they belong to).
18938
20615
  revisit,
@@ -18966,7 +20643,10 @@ async function instructionsWalk(opts) {
18966
20643
  runDirPath,
18967
20644
  projectRoot,
18968
20645
  surface: opts.surface ?? "browse",
18969
- viewport
20646
+ viewport,
20647
+ state: sfm.state ?? null,
20648
+ context: contextYml,
20649
+ untestableBoundaries
18970
20650
  }) : null
18971
20651
  };
18972
20652
  if (opts.json) {
@@ -18974,30 +20654,40 @@ async function instructionsWalk(opts) {
18974
20654
  return;
18975
20655
  }
18976
20656
  console.log();
18977
- console.log(chalk26.bold(" Walk instructions"));
18978
- console.log(` ${chalk26.dim("scenario:")} ${sfm.slug}`);
18979
- console.log(` ${chalk26.dim("persona:")} ${pfm.name} (${pfm.role})`);
18980
- console.log(` ${chalk26.dim("outline:")} ${ofm.title}`);
18981
- if (targetUrl) console.log(` ${chalk26.dim("target:")} ${targetUrl}`);
20657
+ console.log(chalk27.bold(" Walk instructions"));
20658
+ console.log(` ${chalk27.dim("scenario:")} ${sfm.slug}`);
20659
+ console.log(` ${chalk27.dim("persona:")} ${pfm.name} (${pfm.role})`);
20660
+ console.log(` ${chalk27.dim("outline:")} ${ofm.title}`);
20661
+ if (sfm.state?.description || sfm.state?.approximation) {
20662
+ console.log(
20663
+ ` ${chalk27.dim("state:")} ${sfm.state.description ? sfm.state.description : sfm.state.approximation ?? ""}`
20664
+ );
20665
+ }
20666
+ if (targetUrl) console.log(` ${chalk27.dim("target:")} ${targetUrl}`);
18982
20667
  console.log(
18983
- ` ${chalk26.dim("access:")} ${projectAccess || personaAccess ? `${projectAccess ? "project" : ""}${projectAccess && personaAccess ? " + " : ""}${personaAccess ? "persona" : ""}` : "not recorded (walker would run as whoever is signed in)"}`
20668
+ ` ${chalk27.dim("access:")} ${projectAccess || personaAccess ? `${projectAccess ? "project" : ""}${projectAccess && personaAccess ? " + " : ""}${personaAccess ? "persona" : ""}` : "not recorded (walker would run as whoever is signed in)"}`
18984
20669
  );
18985
20670
  console.log(
18986
- ` ${chalk26.dim("dismissed:")} ${dismissedFacts.length} findings to skip`
20671
+ ` ${chalk27.dim("dismissed:")} ${dismissedFacts.length} findings to skip`
18987
20672
  );
18988
20673
  console.log(
18989
- ` ${chalk26.dim("guidance:")} ${guidance.length} tester guidance rule${guidance.length === 1 ? "" : "s"}`
20674
+ ` ${chalk27.dim("guidance:")} ${guidance.length} tester guidance rule${guidance.length === 1 ? "" : "s"}`
18990
20675
  );
20676
+ if (untestableBoundaries.length > 0) {
20677
+ console.log(
20678
+ ` ${chalk27.dim("boundaries:")} ${untestableBoundaries.length} untestable boundaries to avoid`
20679
+ );
20680
+ }
18991
20681
  if (revisit.length > 0) {
18992
20682
  console.log(
18993
- ` ${chalk26.dim("re-check:")} ${revisit.length} place${revisit.length === 1 ? "" : "s"} where fixes await a re-run`
20683
+ ` ${chalk27.dim("re-check:")} ${revisit.length} place${revisit.length === 1 ? "" : "s"} where fixes await a re-run`
18994
20684
  );
18995
20685
  }
18996
20686
  console.log(
18997
- ` ${chalk26.dim("methodology:")} ${methodology ? "loaded" : "missing (run iamu update)"}`
20687
+ ` ${chalk27.dim("methodology:")} ${methodology ? "loaded" : "missing (run iamu update)"}`
18998
20688
  );
18999
20689
  console.log();
19000
- console.log(chalk26.dim(" Use --json to get the full instruction bundle."));
20690
+ console.log(chalk27.dim(" Use --json to get the full instruction bundle."));
19001
20691
  console.log();
19002
20692
  }
19003
20693
  var SCREEN;
@@ -19023,8 +20713,8 @@ __export(new_finding_exports, {
19023
20713
  });
19024
20714
  import { randomUUID as randomUUID10 } from "crypto";
19025
20715
  import { access, readFile as readFile6 } from "fs/promises";
19026
- import path24 from "path";
19027
- import chalk27 from "chalk";
20716
+ import path26 from "path";
20717
+ import chalk28 from "chalk";
19028
20718
  async function newFinding(opts) {
19029
20719
  const ctx = await resolveProject(opts);
19030
20720
  if (!ctx) return;
@@ -19090,7 +20780,7 @@ async function newFinding(opts) {
19090
20780
  }
19091
20781
  try {
19092
20782
  await access(
19093
- path24.join(runDirPath, "steps", `${String(n).padStart(3, "0")}.json`)
20783
+ path26.join(runDirPath, "steps", `${String(n).padStart(3, "0")}.json`)
19094
20784
  );
19095
20785
  } catch {
19096
20786
  commandError(
@@ -19105,7 +20795,7 @@ async function newFinding(opts) {
19105
20795
  if (!scenarioSlug) {
19106
20796
  try {
19107
20797
  const manifestRaw = await readFile6(
19108
- path24.join(runDirPath, "walkthrough.json"),
20798
+ path26.join(runDirPath, "walkthrough.json"),
19109
20799
  "utf8"
19110
20800
  );
19111
20801
  const manifest = JSON.parse(manifestRaw);
@@ -19194,10 +20884,10 @@ async function newFinding(opts) {
19194
20884
  return;
19195
20885
  }
19196
20886
  console.log();
19197
- console.log(` ${symbols.success} Created finding: ${chalk27.cyan(slug)}`);
19198
- console.log(` ${chalk27.dim("path:")} ${record.path}`);
19199
- console.log(` ${chalk27.dim("id:")} ${id}`);
19200
- for (const w of warnings) console.log(` ${chalk27.yellow("!")} ${w}`);
20887
+ console.log(` ${symbols.success} Created finding: ${chalk28.cyan(slug)}`);
20888
+ console.log(` ${chalk28.dim("path:")} ${record.path}`);
20889
+ console.log(` ${chalk28.dim("id:")} ${id}`);
20890
+ for (const w of warnings) console.log(` ${chalk28.yellow("!")} ${w}`);
19201
20891
  console.log();
19202
20892
  } catch (err) {
19203
20893
  commandError(
@@ -19222,8 +20912,8 @@ __export(view_results_exports, {
19222
20912
  openBrowserResults: () => openBrowserResults,
19223
20913
  promptViewResults: () => promptViewResults
19224
20914
  });
19225
- import { isCancel as isCancel4, select as select3 } from "@clack/prompts";
19226
- import chalk28 from "chalk";
20915
+ import { isCancel as isCancel4, select as select4 } from "@clack/prompts";
20916
+ import chalk29 from "chalk";
19227
20917
  function launchCloudDashboard(config, runId) {
19228
20918
  const cloudRunId = runId ? toCloudRunUuid(runId) : void 0;
19229
20919
  const url = getCloudDashboardUrl(
@@ -19232,7 +20922,7 @@ function launchCloudDashboard(config, runId) {
19232
20922
  cloudRunId
19233
20923
  );
19234
20924
  console.log();
19235
- console.log(` ${symbols.success} Opening Iamu Cloud: ${chalk28.cyan(url)}
20925
+ console.log(` ${symbols.success} Opening Iamu Cloud: ${chalk29.cyan(url)}
19236
20926
  `);
19237
20927
  openBrowser(url);
19238
20928
  }
@@ -19268,7 +20958,7 @@ async function promptViewResults(projectRoot, opts) {
19268
20958
  },
19269
20959
  { label: "Skip", value: "skip" }
19270
20960
  ];
19271
- const choice = await select3({
20961
+ const choice = await select4({
19272
20962
  message: "View results?",
19273
20963
  options
19274
20964
  });
@@ -19300,9 +20990,9 @@ __export(evaluate_exports, {
19300
20990
  evaluate: () => evaluate,
19301
20991
  evaluateRecord: () => evaluateRecord
19302
20992
  });
19303
- import { promises as fs25 } from "fs";
19304
- import path25 from "path";
19305
- import chalk29 from "chalk";
20993
+ import { promises as fs29 } from "fs";
20994
+ import path27 from "path";
20995
+ import chalk30 from "chalk";
19306
20996
  import { z as z25 } from "zod";
19307
20997
  function formatIssues(error) {
19308
20998
  return error.issues.map(
@@ -19332,10 +21022,10 @@ async function evaluate(runId, opts) {
19332
21022
  const resolved = await resolveProjectAndRunDir(runId, opts);
19333
21023
  if (!resolved) return;
19334
21024
  const { projectRoot, runDirPath } = resolved;
19335
- const canonicalPath = path25.join(runDirPath, "canonical.json");
21025
+ const canonicalPath = path27.join(runDirPath, "canonical.json");
19336
21026
  let canonical;
19337
21027
  try {
19338
- const raw = await fs25.readFile(canonicalPath, "utf8");
21028
+ const raw = await fs29.readFile(canonicalPath, "utf8");
19339
21029
  canonical = JSON.parse(raw);
19340
21030
  } catch {
19341
21031
  commandError(
@@ -19391,8 +21081,21 @@ async function evaluate(runId, opts) {
19391
21081
  )
19392
21082
  };
19393
21083
  const prompt = composeJudgePrompt(input);
21084
+ const promptFilePath = opts?.promptFile ?? path27.join(runDirPath, "judge_prompt.md");
21085
+ await fs29.writeFile(promptFilePath, prompt, "utf8");
19394
21086
  if (opts?.json) {
19395
- console.log(JSON.stringify({ ...input, judge_prompt: prompt }, null, 2));
21087
+ console.log(
21088
+ JSON.stringify(
21089
+ {
21090
+ ...input,
21091
+ prompt_file: path27.relative(projectRoot, promptFilePath),
21092
+ prompt_file_abs: path27.resolve(promptFilePath),
21093
+ judge_prompt: prompt
21094
+ },
21095
+ null,
21096
+ 2
21097
+ )
21098
+ );
19396
21099
  return;
19397
21100
  }
19398
21101
  console.log(prompt);
@@ -19401,10 +21104,10 @@ async function evaluateRecord(runId, opts) {
19401
21104
  const resolved = await resolveProjectAndRunDir(runId, opts);
19402
21105
  if (!resolved) return;
19403
21106
  const { projectRoot, runDirPath } = resolved;
19404
- const canonicalPath = path25.join(runDirPath, "canonical.json");
21107
+ const canonicalPath = path27.join(runDirPath, "canonical.json");
19405
21108
  let canonical;
19406
21109
  try {
19407
- const raw = await fs25.readFile(canonicalPath, "utf8");
21110
+ const raw = await fs29.readFile(canonicalPath, "utf8");
19408
21111
  canonical = JSON.parse(raw);
19409
21112
  } catch {
19410
21113
  commandError(`No canonical.json for run: ${runId}`, opts);
@@ -19412,7 +21115,7 @@ async function evaluateRecord(runId, opts) {
19412
21115
  }
19413
21116
  let parsedFile;
19414
21117
  try {
19415
- const raw = await fs25.readFile(opts.file, "utf8");
21118
+ const raw = await fs29.readFile(opts.file, "utf8");
19416
21119
  parsedFile = JSON.parse(raw);
19417
21120
  } catch {
19418
21121
  commandError(`Cannot read verdicts file: ${opts.file}`, opts);
@@ -19469,8 +21172,8 @@ ${formatIssues(fileResult.error)}`,
19469
21172
  overall: inputVerdicts.overall
19470
21173
  });
19471
21174
  const reportContent = renderCanonicalMarkdown(canonicalForReport);
19472
- const verdictsPath = path25.join(runDirPath, "verdicts.json");
19473
- const reportPath = path25.join(runDirPath, "report.md");
21175
+ const verdictsPath = path27.join(runDirPath, "verdicts.json");
21176
+ const reportPath = path27.join(runDirPath, "report.md");
19474
21177
  await Promise.all([
19475
21178
  atomicWriteFile(
19476
21179
  verdictsPath,
@@ -19484,14 +21187,14 @@ ${formatIssues(fileResult.error)}`,
19484
21187
  runId,
19485
21188
  validatedVerdicts
19486
21189
  );
19487
- const cloudInfo = await pushRunToCloud(projectRoot, runId);
21190
+ const cloudInfo = opts?.push === false ? void 0 : await pushRunToCloud(projectRoot, runId);
19488
21191
  if (opts?.json) {
19489
21192
  console.log(
19490
21193
  JSON.stringify(
19491
21194
  {
19492
21195
  runId,
19493
- verdictsPath: path25.relative(projectRoot, verdictsPath),
19494
- reportPath: path25.relative(projectRoot, reportPath),
21196
+ verdictsPath: path27.relative(projectRoot, verdictsPath),
21197
+ reportPath: path27.relative(projectRoot, reportPath),
19495
21198
  verdictsCount: validatedVerdicts.length,
19496
21199
  linkedCount: linked.length,
19497
21200
  ...cloudInfo ? { cloud: cloudInfo } : {}
@@ -19504,18 +21207,18 @@ ${formatIssues(fileResult.error)}`,
19504
21207
  }
19505
21208
  console.log();
19506
21209
  console.log(
19507
- ` ${symbols.success} Recorded ${validatedVerdicts.length} verdicts for run ${chalk29.cyan(runId)}`
21210
+ ` ${symbols.success} Recorded ${validatedVerdicts.length} verdicts for run ${chalk30.cyan(runId)}`
19508
21211
  );
19509
21212
  if (linked.length > 0) {
19510
21213
  console.log(
19511
- ` ${chalk29.dim("linked:")} ${linked.length} finding${linked.length === 1 ? "" : "s"} to the human test${linked.length === 1 ? "" : "s"} citing ${linked.length === 1 ? "it" : "them"}`
21214
+ ` ${chalk30.dim("linked:")} ${linked.length} finding${linked.length === 1 ? "" : "s"} to the human test${linked.length === 1 ? "" : "s"} citing ${linked.length === 1 ? "it" : "them"}`
19512
21215
  );
19513
21216
  }
19514
21217
  console.log(
19515
- ` ${chalk29.dim("verdicts:")} ${path25.relative(projectRoot, verdictsPath)}`
21218
+ ` ${chalk30.dim("verdicts:")} ${path27.relative(projectRoot, verdictsPath)}`
19516
21219
  );
19517
21220
  console.log(
19518
- ` ${chalk29.dim("report:")} ${path25.relative(projectRoot, reportPath)}`
21221
+ ` ${chalk30.dim("report:")} ${path27.relative(projectRoot, reportPath)}`
19519
21222
  );
19520
21223
  printCloudRunInfo(cloudInfo);
19521
21224
  await promptViewResults(projectRoot, { ...opts, runId });
@@ -19579,9 +21282,9 @@ __export(complete_exports, {
19579
21282
  screenshotSizeWarning: () => screenshotSizeWarning,
19580
21283
  validateCanonicalWalkthrough: () => validateCanonicalWalkthrough
19581
21284
  });
19582
- import { promises as fs26 } from "fs";
19583
- import path26 from "path";
19584
- import chalk30 from "chalk";
21285
+ import { promises as fs30 } from "fs";
21286
+ import path28 from "path";
21287
+ import chalk31 from "chalk";
19585
21288
  async function complete(runId, opts) {
19586
21289
  const run = await loadRun(runId, opts);
19587
21290
  if (!run) return;
@@ -19591,7 +21294,7 @@ async function complete(runId, opts) {
19591
21294
  }
19592
21295
  if (!opts?.json) {
19593
21296
  console.log();
19594
- console.log(chalk30.bold(" Assembling canonical JSON\u2026"));
21297
+ console.log(chalk31.bold(" Assembling canonical JSON\u2026"));
19595
21298
  }
19596
21299
  const provenanceAudit = auditStepProvenance(
19597
21300
  await readStepsForRun(run.projectRoot, runId)
@@ -19601,6 +21304,36 @@ async function complete(runId, opts) {
19601
21304
  return;
19602
21305
  }
19603
21306
  const completedAt = pickCompletedAt(run.manifest);
21307
+ const walkerSummary = await readWalkerSummary(run.runDirPath);
21308
+ let judgeReported;
21309
+ try {
21310
+ const verdictsRaw = await fs30.readFile(
21311
+ path28.join(run.runDirPath, "verdicts.json"),
21312
+ "utf8"
21313
+ );
21314
+ const parsed = JSON.parse(verdictsRaw);
21315
+ if (parsed.provenance?.model) {
21316
+ judgeReported = parsed.provenance.model;
21317
+ }
21318
+ } catch {
21319
+ }
21320
+ const stepCount = provenanceAudit.step_count;
21321
+ const durationSec = provenanceAudit.duration_seconds ?? (run.manifest.created_at && Date.parse(completedAt) > Date.parse(run.manifest.created_at) ? Math.round(
21322
+ (Date.parse(completedAt) - Date.parse(run.manifest.created_at)) / 1e3 * 10
21323
+ ) / 10 : null);
21324
+ const velocitySec = provenanceAudit.avg_step_interval_seconds ?? (durationSec != null && stepCount > 1 ? Math.round(durationSec / (stepCount - 1) * 10) / 10 : null);
21325
+ const existingModels = run.manifest.models ?? {};
21326
+ const updatedModels = {
21327
+ ...existingModels,
21328
+ ...walkerSummary.model ? { walker_reported: walkerSummary.model } : {},
21329
+ ...judgeReported ? { judge_reported: judgeReported } : {},
21330
+ ...durationSec != null ? { total_duration_sec: durationSec } : {},
21331
+ ...velocitySec != null ? { velocity_sec_per_step: velocitySec } : {}
21332
+ };
21333
+ run.manifest = {
21334
+ ...run.manifest,
21335
+ models: updatedModels
21336
+ };
19604
21337
  const assembled = await assembleRun(
19605
21338
  run.projectRoot,
19606
21339
  runId,
@@ -19627,16 +21360,16 @@ async function loadRun(runId, opts) {
19627
21360
  if (!resolved) return null;
19628
21361
  const { projectRoot, runDirPath } = resolved;
19629
21362
  try {
19630
- await fs26.access(runDirPath);
21363
+ await fs30.access(runDirPath);
19631
21364
  } catch {
19632
21365
  commandError(`Run not found: ${runId}`, opts);
19633
21366
  return null;
19634
21367
  }
19635
- const manifestPath = path26.join(runDirPath, "walkthrough.json");
21368
+ const manifestPath = path28.join(runDirPath, "walkthrough.json");
19636
21369
  let manifest;
19637
21370
  try {
19638
21371
  manifest = JSON.parse(
19639
- await fs26.readFile(manifestPath, "utf8")
21372
+ await fs30.readFile(manifestPath, "utf8")
19640
21373
  );
19641
21374
  } catch {
19642
21375
  commandError(`No walkthrough.json found for run: ${runId}`, opts);
@@ -19648,8 +21381,8 @@ async function reportAlreadyCompleted(run, opts) {
19648
21381
  const { runId, manifest } = run;
19649
21382
  if (opts?.json) {
19650
21383
  try {
19651
- const raw = await fs26.readFile(
19652
- path26.join(run.runDirPath, "canonical.json"),
21384
+ const raw = await fs30.readFile(
21385
+ path28.join(run.runDirPath, "canonical.json"),
19653
21386
  "utf8"
19654
21387
  );
19655
21388
  console.log(raw.trim());
@@ -19669,18 +21402,18 @@ async function reportAlreadyCompleted(run, opts) {
19669
21402
  return;
19670
21403
  }
19671
21404
  console.log();
19672
- console.log(chalk30.bold(" Run: ") + chalk30.cyan(runId));
19673
- console.log(` status ${chalk30.green("completed")}`);
21405
+ console.log(chalk31.bold(" Run: ") + chalk31.cyan(runId));
21406
+ console.log(` status ${chalk31.green("completed")}`);
19674
21407
  if (manifest.completed_at) {
19675
- console.log(` completed ${chalk30.dim(manifest.completed_at)}`);
21408
+ console.log(` completed ${chalk31.dim(manifest.completed_at)}`);
19676
21409
  }
19677
21410
  const viewUrl = await pushedRunUrl(run.projectRoot, runId);
19678
21411
  if (viewUrl) {
19679
- console.log(` view ${chalk30.underline(viewUrl)}`);
21412
+ console.log(` view ${chalk31.underline(viewUrl)}`);
19680
21413
  }
19681
21414
  console.log();
19682
21415
  console.log(
19683
- chalk30.dim(
21416
+ chalk31.dim(
19684
21417
  " Already completed \u2014 nothing to do. Use --force to reassemble the report."
19685
21418
  )
19686
21419
  );
@@ -19696,13 +21429,13 @@ function reportStrictProvenanceFailure(audit, opts) {
19696
21429
  }
19697
21430
  console.error();
19698
21431
  console.error(
19699
- chalk30.red(` ${symbols.error} Strict provenance audit failed:`)
21432
+ chalk31.red(` ${symbols.error} Strict provenance audit failed:`)
19700
21433
  );
19701
21434
  for (const w of audit.warnings) {
19702
- console.error(chalk30.red(` \u2022 ${w}`));
21435
+ console.error(chalk31.red(` \u2022 ${w}`));
19703
21436
  }
19704
21437
  console.error(
19705
- chalk30.dim(
21438
+ chalk31.dim(
19706
21439
  " Run was flagged as synthetic / scripted. Remove --strict-provenance to complete anyway."
19707
21440
  )
19708
21441
  );
@@ -19728,7 +21461,7 @@ async function collectCompletionExtras(run, opts) {
19728
21461
  parseRunViewport(manifest.viewport)
19729
21462
  );
19730
21463
  const codeWarning = opts?.force ? null : codeChangedWarning(manifest.build, detectWorktreeState(projectRoot));
19731
- const cloudInfo = await pushRunToCloud(projectRoot, runId);
21464
+ const cloudInfo = opts?.push === false ? void 0 : await pushRunToCloud(projectRoot, runId);
19732
21465
  return { revisit, viewportWarning, codeWarning, cloudInfo };
19733
21466
  }
19734
21467
  function completeJsonPayload(canonical, extras) {
@@ -19743,30 +21476,41 @@ function completeJsonPayload(canonical, extras) {
19743
21476
  function printCompletionSummary(run, completedAt, assembled, extras, provenanceAudit) {
19744
21477
  const { runId, projectRoot, manifest } = run;
19745
21478
  const { canonical } = assembled;
19746
- const count = (n) => n === 0 ? chalk30.dim("0") : chalk30.cyan(String(n));
19747
- console.log(chalk30.bold(" Run: ") + chalk30.cyan(runId));
19748
- console.log(` scenario ${chalk30.dim(manifest.scenario_slug)}`);
19749
- console.log(` url ${chalk30.dim(manifest.target_url)}`);
21479
+ const count = (n) => n === 0 ? chalk31.dim("0") : chalk31.cyan(String(n));
21480
+ console.log(chalk31.bold(" Run: ") + chalk31.cyan(runId));
21481
+ console.log(` scenario ${chalk31.dim(manifest.scenario_slug)}`);
21482
+ console.log(` url ${chalk31.dim(manifest.target_url)}`);
19750
21483
  if (manifest.environment) {
19751
- console.log(` env ${chalk30.dim(manifest.environment)}`);
21484
+ console.log(` env ${chalk31.dim(manifest.environment)}`);
19752
21485
  }
19753
21486
  if (manifest.build) {
19754
- console.log(` build ${chalk30.dim(formatBuild(manifest.build))}`);
21487
+ console.log(` build ${chalk31.dim(formatBuild(manifest.build))}`);
19755
21488
  }
19756
21489
  if (manifest.viewport) {
19757
- console.log(` device ${chalk30.dim(formatViewport(manifest.viewport))}`);
21490
+ console.log(` device ${chalk31.dim(formatViewport(manifest.viewport))}`);
19758
21491
  }
19759
- console.log(` status ${chalk30.green("completed")}`);
21492
+ console.log(` status ${chalk31.green("completed")}`);
19760
21493
  console.log(` findings ${count(canonical.findings.length)}`);
19761
21494
  console.log(` steps ${count(canonical.steps.length)}`);
19762
- console.log(` started ${chalk30.dim(manifest.created_at)}`);
19763
- console.log(` completed ${chalk30.dim(completedAt)}`);
21495
+ console.log(` started ${chalk31.dim(manifest.created_at)}`);
21496
+ console.log(` completed ${chalk31.dim(completedAt)}`);
21497
+ const models = manifest.models;
21498
+ if (models?.velocity_sec_per_step != null) {
21499
+ console.log(
21500
+ ` velocity ${chalk31.cyan(`~${models.velocity_sec_per_step}s/step`)} ${chalk31.dim(`(${models.total_duration_sec}s total)`)}`
21501
+ );
21502
+ }
21503
+ if (models && (models.walker || models.judge || models.matcher)) {
21504
+ const w = models.walker_reported ? `${models.walker} (${models.walker_reported})` : models.walker ?? "fast";
21505
+ const j = models.judge_reported ? `${models.judge} (${models.judge_reported})` : models.judge ?? "thorough";
21506
+ console.log(` models ${chalk31.dim(`walker: ${w} \xB7 judge: ${j}`)}`);
21507
+ }
19764
21508
  console.log();
19765
21509
  console.log(
19766
- ` ${chalk30.dim("canonical:")} ${chalk30.dim(path26.relative(projectRoot, assembled.canonicalPath))}`
21510
+ ` ${chalk31.dim("canonical:")} ${chalk31.dim(path28.relative(projectRoot, assembled.canonicalPath))}`
19767
21511
  );
19768
21512
  console.log(
19769
- ` ${chalk30.dim("report:")} ${chalk30.dim(path26.relative(projectRoot, assembled.reportPath))}`
21513
+ ` ${chalk31.dim("report:")} ${chalk31.dim(path28.relative(projectRoot, assembled.reportPath))}`
19770
21514
  );
19771
21515
  if (extras.viewportWarning) {
19772
21516
  console.log();
@@ -19774,7 +21518,7 @@ function printCompletionSummary(run, completedAt, assembled, extras, provenanceA
19774
21518
  }
19775
21519
  if (extras.codeWarning) {
19776
21520
  console.log();
19777
- console.log(` ${chalk30.yellow("!")} ${extras.codeWarning}`);
21521
+ console.log(` ${chalk31.yellow("!")} ${extras.codeWarning}`);
19778
21522
  }
19779
21523
  if (extras.revisit) printRevisit(extras.revisit);
19780
21524
  if (assembled.verification) printVerification(assembled.verification);
@@ -19786,13 +21530,13 @@ function printProvenanceWarnings(audit) {
19786
21530
  if (audit.warnings.length === 0) return;
19787
21531
  console.log();
19788
21532
  console.log(
19789
- ` ${symbols.warning} ${chalk30.yellow("Step provenance audit flagged suspicious timing:")}`
21533
+ ` ${symbols.warning} ${chalk31.yellow("Step provenance audit flagged suspicious timing:")}`
19790
21534
  );
19791
21535
  for (const w of audit.warnings) {
19792
- console.log(` ${chalk30.dim("\u2022")} ${w}`);
21536
+ console.log(` ${chalk31.dim("\u2022")} ${w}`);
19793
21537
  }
19794
21538
  console.log(
19795
- chalk30.dim(
21539
+ chalk31.dim(
19796
21540
  " Run appears to be programmatically batch-generated rather than an interactive walkthrough."
19797
21541
  )
19798
21542
  );
@@ -19824,17 +21568,17 @@ function screenshotFitsViewport(width, expected) {
19824
21568
  }
19825
21569
  async function screenshotSizeWarning(runDirPath, viewport) {
19826
21570
  if (!viewport) return null;
19827
- const dir = path26.join(runDirPath, "screenshots");
21571
+ const dir = path28.join(runDirPath, "screenshots");
19828
21572
  let first;
19829
21573
  try {
19830
- first = (await fs26.readdir(dir)).filter((f) => f.toLowerCase().endsWith(".png")).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }))[0];
21574
+ first = (await fs30.readdir(dir)).filter((f) => f.toLowerCase().endsWith(".png")).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }))[0];
19831
21575
  } catch {
19832
21576
  return null;
19833
21577
  }
19834
21578
  if (!first) return null;
19835
21579
  let size = null;
19836
21580
  try {
19837
- const handle = await fs26.open(path26.join(dir, first), "r");
21581
+ const handle = await fs30.open(path28.join(dir, first), "r");
19838
21582
  try {
19839
21583
  const head = Buffer.alloc(24);
19840
21584
  const { bytesRead } = await handle.read(head, 0, 24, 0);
@@ -19851,12 +21595,14 @@ async function screenshotSizeWarning(runDirPath, viewport) {
19851
21595
  async function readWalkerSummary(runDirPath) {
19852
21596
  try {
19853
21597
  const data = JSON.parse(
19854
- await fs26.readFile(path26.join(runDirPath, "summary.json"), "utf8")
21598
+ await fs30.readFile(path28.join(runDirPath, "summary.json"), "utf8")
19855
21599
  );
19856
21600
  const strings = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && x.trim() !== "") : void 0;
21601
+ const model = typeof data.model === "string" && data.model.trim() !== "" ? data.model.trim() : void 0;
19857
21602
  return {
19858
21603
  what_worked: strings(data.what_worked),
19859
- open_questions: strings(data.open_questions)
21604
+ open_questions: strings(data.open_questions),
21605
+ model
19860
21606
  };
19861
21607
  } catch {
19862
21608
  return {};
@@ -19958,7 +21704,7 @@ function validateCanonicalWalkthrough(canonical) {
19958
21704
  async function applyRecordedVerdicts(canonical, runDirPath) {
19959
21705
  try {
19960
21706
  const recorded = JSON.parse(
19961
- await fs26.readFile(path26.join(runDirPath, "verdicts.json"), "utf8")
21707
+ await fs30.readFile(path28.join(runDirPath, "verdicts.json"), "utf8")
19962
21708
  );
19963
21709
  if (Array.isArray(recorded.verdicts)) {
19964
21710
  return applyVerdicts(canonical, recorded);
@@ -19968,7 +21714,7 @@ async function applyRecordedVerdicts(canonical, runDirPath) {
19968
21714
  return canonical;
19969
21715
  }
19970
21716
  async function persistCanonical(runDirPath, canonical) {
19971
- const canonicalPath = path26.join(runDirPath, "canonical.json");
21717
+ const canonicalPath = path28.join(runDirPath, "canonical.json");
19972
21718
  await atomicWriteFile(
19973
21719
  canonicalPath,
19974
21720
  `${JSON.stringify(canonical, null, 2)}
@@ -19977,7 +21723,7 @@ async function persistCanonical(runDirPath, canonical) {
19977
21723
  return canonicalPath;
19978
21724
  }
19979
21725
  async function persistReport(runDirPath, canonical) {
19980
- const reportPath = path26.join(runDirPath, "report.md");
21726
+ const reportPath = path28.join(runDirPath, "report.md");
19981
21727
  await atomicWriteFile(reportPath, renderCanonicalMarkdown(canonical));
19982
21728
  return reportPath;
19983
21729
  }
@@ -20077,17 +21823,17 @@ function placeLabel(p) {
20077
21823
  function printRevisit(r) {
20078
21824
  const missed = r.places.filter((p) => !p.reached);
20079
21825
  console.log();
20080
- const head = ` ${chalk30.bold("Re-check places:")} reached ${r.reached} of ${r.requested}`;
21826
+ const head = ` ${chalk31.bold("Re-check places:")} reached ${r.reached} of ${r.requested}`;
20081
21827
  if (missed.length === 0) {
20082
- console.log(`${head} ${chalk30.green("\u2713")}`);
21828
+ console.log(`${head} ${chalk31.green("\u2713")}`);
20083
21829
  return;
20084
21830
  }
20085
21831
  console.log(`${head} \u2014 not reached:`);
20086
21832
  for (const p of missed) {
20087
- console.log(` ${chalk30.yellow("\u25CB")} ${placeLabel(p)}`);
21833
+ console.log(` ${chalk31.yellow("\u25CB")} ${placeLabel(p)}`);
20088
21834
  }
20089
21835
  console.log(
20090
- chalk30.dim(
21836
+ chalk31.dim(
20091
21837
  " Fixes there stay awaiting a re-run until a walk gets there."
20092
21838
  )
20093
21839
  );
@@ -20095,7 +21841,7 @@ function printRevisit(r) {
20095
21841
  function printVerification(verification) {
20096
21842
  console.log();
20097
21843
  console.log(
20098
- chalk30.bold(" Since this scenario's last run") + chalk30.dim(` (${verification.baseline_run_id.slice(0, 30)}\u2026)`) + chalk30.bold(":")
21844
+ chalk31.bold(" Since this scenario's last run") + chalk31.dim(` (${verification.baseline_run_id.slice(0, 30)}\u2026)`) + chalk31.bold(":")
20099
21845
  );
20100
21846
  if (verification.resolved.length > 0) {
20101
21847
  console.log(
@@ -20104,7 +21850,7 @@ function printVerification(verification) {
20104
21850
  }
20105
21851
  if (verification.not_observed.length > 0) {
20106
21852
  console.log(
20107
- ` ${symbols.pending} ${verification.not_observed.length} not seen this run ${chalk30.dim("(not evidence of a fix)")}`
21853
+ ` ${symbols.pending} ${verification.not_observed.length} not seen this run ${chalk31.dim("(not evidence of a fix)")}`
20108
21854
  );
20109
21855
  }
20110
21856
  if (verification.persists.length > 0) {
@@ -20145,14 +21891,14 @@ __export(match_exports, {
20145
21891
  match: () => match,
20146
21892
  matchRecord: () => matchRecord
20147
21893
  });
20148
- import { promises as fs27 } from "fs";
20149
- import path27 from "path";
20150
- import chalk31 from "chalk";
21894
+ import { promises as fs31 } from "fs";
21895
+ import path29 from "path";
21896
+ import chalk32 from "chalk";
20151
21897
  async function completedManifest2(runDirPath, runId, opts) {
20152
21898
  let manifest;
20153
21899
  try {
20154
21900
  manifest = JSON.parse(
20155
- await fs27.readFile(path27.join(runDirPath, "walkthrough.json"), "utf8")
21901
+ await fs31.readFile(path29.join(runDirPath, "walkthrough.json"), "utf8")
20156
21902
  );
20157
21903
  } catch {
20158
21904
  commandError(`Run not found: ${runId}`, opts);
@@ -20181,15 +21927,34 @@ async function match(runId, opts) {
20181
21927
  );
20182
21928
  const needed = input.current.length > 0 && (input.prior.length > 0 || (input.guidance ?? []).length > 0);
20183
21929
  const prompt = needed ? composeMatchPrompt(input) : null;
21930
+ const promptFilePath = opts?.promptFile ?? path29.join(runDirPath, "match_prompt.md");
21931
+ if (prompt) {
21932
+ await fs31.writeFile(promptFilePath, prompt, "utf8");
21933
+ } else {
21934
+ try {
21935
+ await fs31.unlink(promptFilePath);
21936
+ } catch {
21937
+ }
21938
+ }
20184
21939
  if (opts?.json) {
20185
21940
  console.log(
20186
- JSON.stringify({ ...input, needed, match_prompt: prompt }, null, 2)
21941
+ JSON.stringify(
21942
+ {
21943
+ ...input,
21944
+ needed,
21945
+ prompt_file: prompt ? path29.relative(ctx.projectRoot, promptFilePath) : null,
21946
+ prompt_file_abs: prompt ? path29.resolve(promptFilePath) : null,
21947
+ match_prompt: prompt
21948
+ },
21949
+ null,
21950
+ 2
21951
+ )
20187
21952
  );
20188
21953
  return;
20189
21954
  }
20190
21955
  if (!prompt) {
20191
21956
  console.log(
20192
- chalk31.dim(
21957
+ chalk32.dim(
20193
21958
  " No earlier findings or tester guidance for this scenario to match against \u2014 nothing to do."
20194
21959
  )
20195
21960
  );
@@ -20205,7 +21970,7 @@ async function matchRecord(runId, opts) {
20205
21970
  if (!manifest) return;
20206
21971
  let answer;
20207
21972
  try {
20208
- answer = JSON.parse(await fs27.readFile(opts.file, "utf8"));
21973
+ answer = JSON.parse(await fs31.readFile(opts.file, "utf8"));
20209
21974
  } catch {
20210
21975
  commandError(`Cannot read matches file: ${opts.file}`, opts);
20211
21976
  return;
@@ -20246,17 +22011,17 @@ async function matchRecord(runId, opts) {
20246
22011
  }
20247
22012
  console.log();
20248
22013
  console.log(
20249
- ` ${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`
22014
+ ` ${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`
20250
22015
  );
20251
22016
  if (result.suppressed > 0) {
20252
22017
  console.log(
20253
- ` ${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)`
22018
+ ` ${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)`
20254
22019
  );
20255
22020
  }
20256
22021
  if (verification) printVerification(verification);
20257
22022
  console.log();
20258
22023
  console.log(
20259
- chalk31.dim(" Run `iamu sync` to push the linked findings to the cloud.")
22024
+ chalk32.dim(" Run `iamu sync` to push the linked findings to the cloud.")
20260
22025
  );
20261
22026
  console.log();
20262
22027
  }
@@ -20277,7 +22042,7 @@ __export(prune_exports, {
20277
22042
  prune: () => prune
20278
22043
  });
20279
22044
  import { confirm, isCancel as isCancel5 } from "@clack/prompts";
20280
- import chalk32 from "chalk";
22045
+ import chalk33 from "chalk";
20281
22046
  function formatBytes(bytes) {
20282
22047
  if (bytes <= 0) return "0 B";
20283
22048
  if (bytes < 1024) return `${bytes} B`;
@@ -20323,7 +22088,7 @@ async function prune(opts = {}) {
20323
22088
  });
20324
22089
  if (preview.prunedRuns.length === 0) {
20325
22090
  console.log(
20326
- chalk32.dim(" No runs match the pruning criteria. Nothing to prune.")
22091
+ chalk33.dim(" No runs match the pruning criteria. Nothing to prune.")
20327
22092
  );
20328
22093
  return preview;
20329
22094
  }
@@ -20332,7 +22097,7 @@ async function prune(opts = {}) {
20332
22097
  message: `Prune ${mode} ${preview.prunedRuns.length} run(s) and reclaim ${formatBytes(preview.totalBytesReclaimed)}?`
20333
22098
  });
20334
22099
  if (isCancel5(confirmed) || !confirmed) {
20335
- console.log(chalk32.yellow(" Pruning cancelled."));
22100
+ console.log(chalk33.yellow(" Pruning cancelled."));
20336
22101
  return;
20337
22102
  }
20338
22103
  }
@@ -20343,14 +22108,14 @@ async function prune(opts = {}) {
20343
22108
  }
20344
22109
  if (result.dryRun) {
20345
22110
  console.log(
20346
- chalk32.cyan(
22111
+ chalk33.cyan(
20347
22112
  `
20348
22113
  [DRY RUN] Would prune ${result.prunedRuns.length} of ${result.totalRunsScanned} run(s):`
20349
22114
  )
20350
22115
  );
20351
22116
  } else {
20352
22117
  console.log(
20353
- chalk32.bold(
22118
+ chalk33.bold(
20354
22119
  `
20355
22120
  ${symbols.success} Pruned ${result.prunedRuns.length} of ${result.totalRunsScanned} run(s):`
20356
22121
  )
@@ -20358,18 +22123,18 @@ async function prune(opts = {}) {
20358
22123
  }
20359
22124
  if (result.prunedRuns.length === 0) {
20360
22125
  console.log(
20361
- chalk32.dim(" No runs matched the pruning criteria. Nothing pruned.")
22126
+ chalk33.dim(" No runs matched the pruning criteria. Nothing pruned.")
20362
22127
  );
20363
22128
  } else {
20364
22129
  for (const run of result.prunedRuns) {
20365
22130
  const mode = run.fullyDeleted ? "removed" : "screenshots purged";
20366
22131
  const count = run.screenshotsDeleted > 0 ? `${run.screenshotsDeleted} screenshots, ` : "";
20367
22132
  console.log(
20368
- ` ${chalk32.dim("\u2022")} ${chalk32.bold(run.runId)} ${chalk32.dim(`(${run.scenarioSlug ?? "unknown scenario"})`)}: ${chalk32.yellow(formatBytes(run.bytesReclaimed))} (${count}${mode})`
22133
+ ` ${chalk33.dim("\u2022")} ${chalk33.bold(run.runId)} ${chalk33.dim(`(${run.scenarioSlug ?? "unknown scenario"})`)}: ${chalk33.yellow(formatBytes(run.bytesReclaimed))} (${count}${mode})`
20369
22134
  );
20370
22135
  }
20371
22136
  console.log(
20372
- chalk32.dim(
22137
+ chalk33.dim(
20373
22138
  `
20374
22139
  Total reclaimed: ${formatBytes(result.totalBytesReclaimed)} (${result.totalScreenshotsDeleted} screenshots deleted)
20375
22140
  `
@@ -20390,7 +22155,7 @@ var init_prune2 = __esm({
20390
22155
  // src/commands/register-cloud.ts
20391
22156
  import { Option } from "commander";
20392
22157
  function registerCloudCommands(program) {
20393
- program.command("sync").helpGroup("Cloud:").description("Push pending local changes to cloud").option("--status", "Show pending sync count without pushing").option(
22158
+ program.command("sync").alias("push").helpGroup("Cloud:").description("Push pending local changes to cloud").option("--status", "Show pending sync count without pushing").option(
20394
22159
  "--keep-mine",
20395
22160
  "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."
20396
22161
  ).option(
@@ -20414,7 +22179,7 @@ function registerCloudCommands(program) {
20414
22179
  "Connect to local cloud stack (default port: 3000)"
20415
22180
  ).addOption(
20416
22181
  new Option("-s, --staging", "Connect to staging cloud stack").hideHelp()
20417
- ).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(
22182
+ ).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(
20418
22183
  "--project <project-id>",
20419
22184
  "Cloud project ID to link to, or `new` to create one. Required in a non-interactive shell when cloud projects exist"
20420
22185
  ).option("--no-browser", "Do not launch browser automatically").addHelpText(
@@ -20503,13 +22268,13 @@ function registerFindingsCommands(program) {
20503
22268
 
20504
22269
  // src/commands/register-project.ts
20505
22270
  function registerProjectCommands(program) {
20506
- program.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(
22271
+ program.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(
20507
22272
  "after",
20508
- "\nExamples:\n $ iamu init\n $ iamu init my-project -y\n $ iamu init --json"
22273
+ "\nExamples:\n $ iamu init\n $ iamu init my-project -y\n $ iamu init --cloud\n $ iamu init --json"
20509
22274
  ).action(
20510
22275
  async (name, opts) => {
20511
22276
  const { init: init2 } = await Promise.resolve().then(() => (init_init(), init_exports));
20512
- await init2({ name, yes: opts.yes, json: opts.json });
22277
+ await init2({ name, yes: opts.yes, json: opts.json, cloud: opts.cloud });
20513
22278
  }
20514
22279
  );
20515
22280
  program.command("status").helpGroup("Project:").description("Show project state: entities, runs, context, cloud link").option("--json", "Output as JSON").addHelpText(
@@ -20520,10 +22285,13 @@ function registerProjectCommands(program) {
20520
22285
  await status2(opts);
20521
22286
  });
20522
22287
  program.command("doctor").helpGroup("Project:").description(
20523
- "Diagnose environment: runtime, browser automation, skills, cloud"
22288
+ "Diagnose environment: runtime, browser automation, skills, cloud, and target testability"
22289
+ ).option(
22290
+ "-t, --target <url>",
22291
+ "Probe target URL health, reachability, and auth redirects"
20524
22292
  ).option("--json", "Output diagnostics as JSON").addHelpText(
20525
22293
  "after",
20526
- "\nExamples:\n $ iamu doctor\n $ iamu doctor --json"
22294
+ "\nExamples:\n $ iamu doctor\n $ iamu doctor --target http://localhost:3000\n $ iamu doctor --json"
20527
22295
  ).action(async (opts) => {
20528
22296
  const { doctor: doctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
20529
22297
  await doctor2(opts);
@@ -20555,9 +22323,9 @@ function registerProjectCommands(program) {
20555
22323
  "after",
20556
22324
  "\nExamples:\n $ iamu validate --all\n $ iamu validate --all --fix --dry-run\n $ iamu validate iamu/personas/maria.md"
20557
22325
  ).action(
20558
- async (path28, opts) => {
22326
+ async (path30, opts) => {
20559
22327
  const { validate: validate2 } = await Promise.resolve().then(() => (init_validate(), validate_exports));
20560
- await validate2(path28, opts);
22328
+ await validate2(path30, opts);
20561
22329
  }
20562
22330
  );
20563
22331
  program.command("update").helpGroup("Project:").description("Update and repair agent skills and prompts to latest version").option(
@@ -20570,11 +22338,27 @@ function registerProjectCommands(program) {
20570
22338
  program.command("new <type>").helpGroup("Project:").description(
20571
22339
  "Create a new entity (persona, outline, capability, source, scenario)"
20572
22340
  ).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(
22341
+ "--access <role>",
22342
+ "Access role or testing account key (persona only)"
22343
+ ).option("--body <text>", "Markdown body / narrative content").option("--narrative <text>", "Alias for --body").option(
20573
22344
  "--description <text>",
20574
22345
  "One-line description (capability, outline)"
22346
+ ).option("--capability <slug>", "Primary capability slug (outline only)").option(
22347
+ "--tension <text>",
22348
+ "Structural tension driving this situation (outline only)"
22349
+ ).option(
22350
+ "--human-test <question>",
22351
+ "Human-test question evaluated at run completion (outline only, repeatable)",
22352
+ (val, prev = []) => [...prev, val]
20575
22353
  ).option("--outline <slug>", "Outline slug (scenario only)").option("--persona <slug>", "Persona slug (scenario only)").option(
20576
22354
  "--entry <path>",
20577
22355
  "Where the situation starts, as a path on the product, e.g. /en/episodes/1 (scenario only)"
22356
+ ).option(
22357
+ "--state-description <text>",
22358
+ "Assumed environment data/records state (scenario only)"
22359
+ ).option(
22360
+ "--state-approximation <text>",
22361
+ "Guidance on how persona should approximate/squint if records are missing (scenario only)"
20578
22362
  ).option("--url <url>", "Document the source cites (source only, required)").option(
20579
22363
  "--authority <level>",
20580
22364
  "legislation | regulation | standard | company_policy | guideline | informational (source only, default informational)"
@@ -20587,6 +22371,54 @@ function registerProjectCommands(program) {
20587
22371
  await newEntity2(type2, opts);
20588
22372
  }
20589
22373
  );
22374
+ const explore = program.command("explore").helpGroup("Project:").description(
22375
+ "Map the running product with a read-only crawl; compare the map with runs (coverage) or with another map (diff)"
22376
+ );
22377
+ explore.command("run [url]", { isDefault: true }).description(
22378
+ "Crawl from <url> (default: target_url in iamu/testability.md) and write iamu/.cache/explore/<id>/map.json"
22379
+ ).option("--max-surfaces <n>", "Stop after this many surfaces (default 40)").option("--max-actions <n>", "Stop after this many clicks (default 300)").option(
22380
+ "--max-depth <n>",
22381
+ "Don't go more than this many clicks from the start (default 6)"
22382
+ ).option(
22383
+ "--deny <pattern...>",
22384
+ "Never click a control whose name or link matches (regex, case-insensitive)"
22385
+ ).option("--viewport <WxH>", "Page size, e.g. 390x844 (default 1280x800)").option(
22386
+ "--attach",
22387
+ "Crawl in a new tab of the `iamu browse` browser, keeping its sign-ins"
22388
+ ).option("--json", "Output as JSON").addHelpText(
22389
+ "after",
22390
+ `
22391
+ Read-only: controls that read like writes (delete, pay, send, save, log out \u2026),
22392
+ form submits, downloads and other origins are never clicked, nothing is typed,
22393
+ and every request but GET/HEAD/OPTIONS is blocked before it leaves the browser.
22394
+
22395
+ Examples:
22396
+ $ iamu explore http://localhost:3000
22397
+ $ iamu explore --attach --deny "^Admin"
22398
+ $ iamu explore coverage
22399
+ $ iamu explore diff`
22400
+ ).action(
22401
+ async (url, opts) => {
22402
+ const { exploreRun: exploreRun2 } = await Promise.resolve().then(() => (init_explore(), explore_exports));
22403
+ await exploreRun2(url, opts);
22404
+ }
22405
+ );
22406
+ explore.command("coverage [map]").description(
22407
+ "Surfaces of a map (default: latest) that no run reached, and places runs reached that the map lacks"
22408
+ ).option("--json", "Output as JSON").action(async (map2, opts) => {
22409
+ const { exploreCoverageCommand: exploreCoverageCommand2 } = await Promise.resolve().then(() => (init_explore(), explore_exports));
22410
+ await exploreCoverageCommand2(map2, opts);
22411
+ });
22412
+ explore.command("diff [a] [b]").description("Compare two maps (default: the two latest)").option("--json", "Output as JSON").action(
22413
+ async (a, b, opts) => {
22414
+ const { exploreDiffCommand: exploreDiffCommand2 } = await Promise.resolve().then(() => (init_explore(), explore_exports));
22415
+ await exploreDiffCommand2(a, b, opts);
22416
+ }
22417
+ );
22418
+ explore.command("list").description("List maps").option("--json", "Output as JSON").action(async (opts) => {
22419
+ const { exploreList: exploreList2 } = await Promise.resolve().then(() => (init_explore(), explore_exports));
22420
+ await exploreList2(opts);
22421
+ });
20590
22422
  program.command("completion [shell]").helpGroup("Project:").description("Output shell completion script (bash, zsh, fish)").action(async (shell) => {
20591
22423
  const { completion: completion2 } = await Promise.resolve().then(() => (init_completion(), completion_exports));
20592
22424
  completion2(program, shell);
@@ -20616,6 +22448,15 @@ function registerWalkthroughCommands(program) {
20616
22448
  ).option(
20617
22449
  "--env <name>",
20618
22450
  "Environment the product runs in (default: the target URL's origin)"
22451
+ ).option(
22452
+ "--walker <tierOrModel>",
22453
+ "Model tier ('fast', 'thorough', 'inherit') or model ID for driving browser navigation (default: fast)"
22454
+ ).option(
22455
+ "--judge <tierOrModel>",
22456
+ "Model tier ('fast', 'thorough', 'inherit') or model ID for human test evaluation (default: thorough)"
22457
+ ).option(
22458
+ "--matcher <tierOrModel>",
22459
+ "Model tier ('fast', 'thorough', 'inherit') or model ID for finding matching (default: thorough)"
20619
22460
  ).option(
20620
22461
  "--check",
20621
22462
  "Enforce target URL reachability (fail fast if server is offline)"
@@ -20646,7 +22487,10 @@ function registerWalkthroughCommands(program) {
20646
22487
  ).option(
20647
22488
  "--run <run-id>",
20648
22489
  "Run ID to size browser (start) or record step into (step)"
20649
- ).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(
22490
+ ).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(
22491
+ "--step-file <path>",
22492
+ "Read step payload (action, note, cmd) from a JSON file or '-' for stdin (step only)"
22493
+ ).option("--width <px>", "Window width (start)").option("--height <px>", "Window height (start)").option("--headless", "Run without a window (start)").option(
20650
22494
  "--port <port>",
20651
22495
  "CDP port (start), default 9333",
20652
22496
  (value) => parsePortOption(value)
@@ -20722,9 +22566,9 @@ function registerWalkthroughCommands(program) {
20722
22566
  ).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(
20723
22567
  "--strict-provenance",
20724
22568
  "Fail if steps show synthetic or programmatic timing anomalies"
20725
- ).addHelpText(
22569
+ ).option("--no-push", "Skip pushing the run to Iamu Cloud").addHelpText(
20726
22570
  "after",
20727
- "\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"
22571
+ "\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"
20728
22572
  ).action(
20729
22573
  async (runId, opts) => {
20730
22574
  const { complete: complete2 } = await Promise.resolve().then(() => (init_complete(), complete_exports));
@@ -20733,23 +22577,27 @@ function registerWalkthroughCommands(program) {
20733
22577
  );
20734
22578
  program.command("match <run-id>").helpGroup("Walkthrough:").description(
20735
22579
  "Compose the prompt that links this run's findings to earlier ones"
20736
- ).option("--json", "Output the match input bundle as JSON").action(async (runId, opts) => {
20737
- const { match: match2 } = await Promise.resolve().then(() => (init_match(), match_exports));
20738
- await match2(runId, opts);
20739
- });
22580
+ ).option("--json", "Output the match input bundle as JSON").option("--prompt-file <path>", "Write the generated prompt to a file").action(
22581
+ async (runId, opts) => {
22582
+ const { match: match2 } = await Promise.resolve().then(() => (init_match(), match_exports));
22583
+ await match2(runId, opts);
22584
+ }
22585
+ );
20740
22586
  program.command("match-record <run-id>").helpGroup("Walkthrough:").description(
20741
22587
  "Record finding matches and re-derive the run's since-last-run delta"
20742
22588
  ).requiredOption("--file <path>", "Path to matches JSON file").option("--json", "Output match results as JSON").action(async (runId, opts) => {
20743
22589
  const { matchRecord: matchRecord2 } = await Promise.resolve().then(() => (init_match(), match_exports));
20744
22590
  await matchRecord2(runId, opts);
20745
22591
  });
20746
- program.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) => {
20747
- const { evaluate: evaluate2 } = await Promise.resolve().then(() => (init_evaluate(), evaluate_exports));
20748
- await evaluate2(runId, opts);
20749
- });
20750
- program.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(
22592
+ program.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(
22593
+ async (runId, opts) => {
22594
+ const { evaluate: evaluate2 } = await Promise.resolve().then(() => (init_evaluate(), evaluate_exports));
22595
+ await evaluate2(runId, opts);
22596
+ }
22597
+ );
22598
+ program.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(
20751
22599
  "after",
20752
- "\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"
22600
+ "\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"
20753
22601
  ).action(
20754
22602
  async (runId, opts) => {
20755
22603
  const { evaluateRecord: evaluateRecord2 } = await Promise.resolve().then(() => (init_evaluate(), evaluate_exports));
@@ -20778,7 +22626,7 @@ function registerAllCommands(program) {
20778
22626
 
20779
22627
  // src/ui/panic.ts
20780
22628
  init_logger();
20781
- import chalk33 from "chalk";
22629
+ import chalk34 from "chalk";
20782
22630
  var handlersRegistered = false;
20783
22631
  var customSigintHandler = null;
20784
22632
  function registerSigintCleanup(handler) {
@@ -20790,17 +22638,17 @@ function registerSigintCleanup(handler) {
20790
22638
  function logFatalAndExit(title, errOrReason) {
20791
22639
  process.stderr.write("\x1B[?25h");
20792
22640
  if (isVerbose()) {
20793
- console.error(chalk33.red(`
22641
+ console.error(chalk34.red(`
20794
22642
  ${title}:`));
20795
22643
  console.error(
20796
22644
  errOrReason instanceof Error ? errOrReason.stack || errOrReason : errOrReason
20797
22645
  );
20798
22646
  } else {
20799
22647
  const msg = errOrReason instanceof Error ? errOrReason.message : String(errOrReason);
20800
- console.error(chalk33.red(`
22648
+ console.error(chalk34.red(`
20801
22649
  Fatal Error: ${msg}`));
20802
22650
  console.error(
20803
- chalk33.dim(
22651
+ chalk34.dim(
20804
22652
  " Run with --verbose or DEBUG=1 to view the full stack trace.\n"
20805
22653
  )
20806
22654
  );
@@ -20819,7 +22667,7 @@ function setupProcessHandlers() {
20819
22667
  }
20820
22668
  process.exit(130);
20821
22669
  } else {
20822
- console.error(chalk33.dim("\n Operation cancelled."));
22670
+ console.error(chalk34.dim("\n Operation cancelled."));
20823
22671
  process.exit(130);
20824
22672
  }
20825
22673
  });