@iamu-dev/iamu 0.2.0-beta.17 → 0.2.0-beta.19

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
@@ -385,7 +385,7 @@ var init_context = __esm({
385
385
  jurisdiction: z7.string().max(1024).nullish().describe("Country / region the regulations and policies apply in. Example: 'Sweden', 'EU', 'United States'."),
386
386
  architecture: z7.string().max(8192).nullish().describe("High-level architecture notes for the software. Brief is fine \u2014 this is orientation, not spec."),
387
387
  notes: z7.string().max(16384).nullish().describe("Additional orientation context that doesn't fit the structured fields. Extended multi-paragraph notes belong in the file body BELOW the frontmatter, not here."),
388
- access: z7.string().max(4096).nullish().describe(`How a walker becomes a given user in this product: the switching mechanism, written once for the project. Example: 'No login in dev. In the page, run localStorage.setItem("dev_persona", "<persona access>") and reload.' Each persona's own \`access\` names which user they are. Never put passwords or tokens here \u2014 name where they live instead.`),
388
+ access: z7.string().max(4096).nullish().describe("Deprecated: belongs in `iamu/testability.md` `access` \u2014 how to switch user is test setup, not domain. Still read as a fallback when testability.md has none."),
389
389
  governed_by: z7.array(contextGovernedBySchema).max(64).optional().describe("Regulations / standards / policies the domain is governed by, ordered roughly by authority."),
390
390
  actors: z7.array(contextActorSchema).max(64).optional().describe("Key domain roles + their responsibilities."),
391
391
  glossary: z7.array(contextGlossaryEntrySchema).max(512).optional().describe("Domain-specific terms the project uses. Flattened into one file locally; the cloud handler splits into the terminology table on push.")
@@ -926,6 +926,7 @@ var init_testability = __esm({
926
926
  target_url: z17.string().url().describe("Target URL where the product is hosted or served locally."),
927
927
  dev_command: z17.string().max(1e3).optional().describe("Dev script or startup command to boot the app locally."),
928
928
  auth_strategy: authStrategyEnum.or(z17.string().min(1).max(100)).optional().describe("How personas authenticate in test environments."),
929
+ access: z17.string().max(4096).optional().describe(`How a walker becomes a given user in this product: the switching mechanism, written once for the project. Example: 'No login in dev. In the page, run localStorage.setItem("dev_persona", "<persona access>") and reload.' Each persona's own \`access\` names which user they are. Never put passwords or tokens here \u2014 name where they live instead.`),
929
930
  harnesses: z17.array(z17.string().min(1).max(500)).max(64).optional().describe("Mocking frameworks, seed scripts, or test harnesses present."),
930
931
  untestable_boundaries: z17.array(z17.string().min(1).max(1024)).max(128).optional().describe("External services, third-party APIs, or surfaces forbidden to autonomous walkers.")
931
932
  });
@@ -1197,6 +1198,9 @@ var init_walkthrough_bundle = __esm({
1197
1198
  what_worked: z20.array(z20.string().max(4096)).max(64).optional(),
1198
1199
  open_questions: z20.array(z20.string().max(4096)).max(64).optional(),
1199
1200
  verification: bundleVerificationSchema.optional().describe("Since-last-run delta from canonical.json \u2014 which earlier findings persist, were re-checked fixed, were not seen, and which are new. Persists to walkthroughs.verification."),
1201
+ blocked: z20.object({
1202
+ reason: z20.string().min(1).max(2048)
1203
+ }).optional().describe("Set when the walk could not start as set up (missing preconditions)."),
1200
1204
  build: buildMetadataSchema.optional().describe("Build of the product under test (git SHA, branch, CI run). Absent for runs started before `start-run` recorded it."),
1201
1205
  environment: z20.string().min(1).max(ENVIRONMENT_MAX_LENGTH).optional().describe("Where the product ran: `--env <name>` or, by default, the target URL's origin (e.g. `http://localhost:3000`)."),
1202
1206
  /** Holistic UX quality score from UX review pass. Present only when `lenses` includes "ux". */
@@ -1470,7 +1474,12 @@ var init_walkthrough_canonical = __esm({
1470
1474
  human_tests: z21.array(canonicalHumanTestSchema),
1471
1475
  human_test_overall: canonicalHumanTestOverallSchema,
1472
1476
  errors: z21.array(canonicalErrorSchema),
1473
- cloud_mirror_state: cloudMirrorStateSchema
1477
+ cloud_mirror_state: cloudMirrorStateSchema,
1478
+ /**
1479
+ * The walk could not start as set up (the walker's `iamu summary
1480
+ * --blocked`). Its findings say nothing about earlier ones.
1481
+ */
1482
+ blocked: z21.object({ reason: z21.string() }).passthrough().optional()
1474
1483
  }).passthrough();
1475
1484
  }
1476
1485
  });
@@ -6570,6 +6579,8 @@ async function readRunEvidence(projectRoot) {
6570
6579
  continue;
6571
6580
  if (manifest.status !== "completed")
6572
6581
  continue;
6582
+ if (manifest.blocked)
6583
+ continue;
6573
6584
  const canonical = await readJson(path8.join(dir, "canonical.json"));
6574
6585
  const matches = await readMatches(projectRoot, runId);
6575
6586
  const declared = typeof manifest.started_at === "string" ? manifest.started_at : typeof manifest.created_at === "string" ? manifest.created_at : null;
@@ -7385,7 +7396,8 @@ async function buildCanonical(projectRoot, manifest, completedAt, inputs) {
7385
7396
  // renderers + judge agents can surface them without losing signal.
7386
7397
  // Consumers MUST preserve unknown keys (per design D3).
7387
7398
  ...inputs.what_worked && inputs.what_worked.length > 0 ? { what_worked: inputs.what_worked } : {},
7388
- ...inputs.open_questions && inputs.open_questions.length > 0 ? { open_questions: inputs.open_questions } : {}
7399
+ ...inputs.open_questions && inputs.open_questions.length > 0 ? { open_questions: inputs.open_questions } : {},
7400
+ ...manifest.blocked ? { blocked: manifest.blocked } : {}
7389
7401
  };
7390
7402
  }
7391
7403
  async function compareWithBaseline(projectRoot, scenarioSlug, currentRunId, currentFindings) {
@@ -7400,7 +7412,7 @@ async function compareWithBaseline(projectRoot, scenarioSlug, currentRunId, curr
7400
7412
  const mPath = path9.join(runDir(projectRoot, rid), "walkthrough.json");
7401
7413
  const raw = await fs12.readFile(mPath, "utf8");
7402
7414
  const m = JSON.parse(raw);
7403
- if (m.scenario_slug === scenarioSlug && m.status === "completed") {
7415
+ if (m.scenario_slug === scenarioSlug && m.status === "completed" && !m.blocked) {
7404
7416
  baselineRunId = rid;
7405
7417
  baselineManifest = m;
7406
7418
  break;
@@ -7509,6 +7521,13 @@ function renderCanonicalMarkdown(canonical) {
7509
7521
  "",
7510
7522
  `# Walkthrough report: ${canonical.scenario.slug}`,
7511
7523
  "",
7524
+ ...canonical.blocked ? [
7525
+ "> **Blocked \u2014 the walk could not start as it was set up.**",
7526
+ `> ${canonical.blocked.reason}`,
7527
+ ">",
7528
+ "> This run is not compared with earlier runs and proves nothing about earlier findings. Fix the setup (the scenario's starting state or the persona's access) and run it again.",
7529
+ ""
7530
+ ] : [],
7512
7531
  `- Run ID: ${canonical.run_id}`,
7513
7532
  `- Target URL: ${canonical.target.url}`
7514
7533
  ];
@@ -7618,7 +7637,11 @@ function renderCanonicalMarkdown(canonical) {
7618
7637
  for (const ht of canonical.human_tests) {
7619
7638
  lines.push("", `- **${ht.question}**`);
7620
7639
  if (ht.verdict === null) {
7621
- lines.push(` - Verdict: _pending judge_ (walker does not self-grade)`);
7640
+ if (canonical.blocked) {
7641
+ lines.push(` - Verdict: _skipped (walk blocked)_`);
7642
+ } else {
7643
+ lines.push(` - Verdict: _pending judge_ (walker does not self-grade)`);
7644
+ }
7622
7645
  } else {
7623
7646
  lines.push(` - Verdict: **${ht.verdict}**`);
7624
7647
  if (ht.reasoning)
@@ -7628,7 +7651,11 @@ function renderCanonicalMarkdown(canonical) {
7628
7651
  }
7629
7652
  lines.push("", "## Human test overall", "", `> ${canonical.human_test_overall.question}`);
7630
7653
  if (canonical.human_test_overall.answer === null) {
7631
- lines.push("", "_Pending judge stage._");
7654
+ if (canonical.blocked) {
7655
+ lines.push("", "_Skipped (walk blocked)._");
7656
+ } else {
7657
+ lines.push("", "_Pending judge stage._");
7658
+ }
7632
7659
  } else {
7633
7660
  lines.push("", canonical.human_test_overall.answer);
7634
7661
  if (canonical.human_test_overall.reasoning)
@@ -7799,10 +7826,15 @@ async function readCanonicalExtras(projectRoot, runId) {
7799
7826
  const data = JSON.parse(await fs13.readFile(path10.join(runDir(projectRoot, runId), "canonical.json"), "utf-8"));
7800
7827
  const strings = (v) => Array.isArray(v) && v.every((x) => typeof x === "string") ? v : void 0;
7801
7828
  const verification = bundleVerificationSchema.safeParse(data.verification);
7829
+ let blocked;
7830
+ if (typeof data.blocked === "object" && data.blocked !== null && "reason" in data.blocked && typeof data.blocked.reason === "string" && data.blocked.reason.trim().length > 0) {
7831
+ blocked = { reason: data.blocked.reason.trim() };
7832
+ }
7802
7833
  return {
7803
7834
  what_worked: strings(data.what_worked),
7804
7835
  open_questions: strings(data.open_questions),
7805
- verification: verification.success ? verification.data : void 0
7836
+ verification: verification.success ? verification.data : void 0,
7837
+ ...blocked ? { blocked } : {}
7806
7838
  };
7807
7839
  } catch {
7808
7840
  return {};
@@ -7899,6 +7931,10 @@ async function assembleWalkthroughBundle(projectRoot, runId) {
7899
7931
  }
7900
7932
  } : {}
7901
7933
  } : void 0;
7934
+ const canonicalExtras = await readCanonicalExtras(projectRoot, runId);
7935
+ const rawBlocked = manifest.blocked;
7936
+ const manifestBlocked = typeof rawBlocked === "object" && rawBlocked !== null && "reason" in rawBlocked && typeof rawBlocked.reason === "string" && rawBlocked.reason.trim().length > 0 ? { reason: rawBlocked.reason.trim() } : void 0;
7937
+ const blocked = canonicalExtras.blocked ?? manifestBlocked;
7902
7938
  const bundle = {
7903
7939
  manifest: {
7904
7940
  run_id: cloudRunUuid,
@@ -7916,7 +7952,8 @@ async function assembleWalkthroughBundle(projectRoot, runId) {
7916
7952
  judge_meta: judgeMeta,
7917
7953
  ...manifest.models ? { models: manifest.models } : {},
7918
7954
  ...buildFields(manifest),
7919
- ...await readCanonicalExtras(projectRoot, runId)
7955
+ ...canonicalExtras,
7956
+ ...blocked ? { blocked } : {}
7920
7957
  },
7921
7958
  steps: bundleSteps,
7922
7959
  findings: bundleFindings,
@@ -10896,6 +10933,26 @@ function summarizeCapabilityCoverage(surfaces, capabilities, outlines = []) {
10896
10933
  unassigned_surfaces: unassigned
10897
10934
  };
10898
10935
  }
10936
+ function groupUnassignedByPrefix(surfaces) {
10937
+ const groups = /* @__PURE__ */ new Map();
10938
+ for (const s of surfaces) {
10939
+ const pathOnly = s.place.split(/[?#]/)[0] ?? "";
10940
+ const first = pathOnly.split("/").filter(Boolean)[0];
10941
+ const prefix = first ? `/${first}` : "/";
10942
+ let g = groups.get(prefix);
10943
+ if (!g) {
10944
+ g = { prefix, surfaces: 0, reached: 0, examples: [] };
10945
+ groups.set(prefix, g);
10946
+ }
10947
+ g.surfaces += 1;
10948
+ if (s.status === "reached")
10949
+ g.reached += 1;
10950
+ if (g.examples.length < 3 && !g.examples.includes(pathOnly)) {
10951
+ g.examples.push(pathOnly);
10952
+ }
10953
+ }
10954
+ return [...groups.values()].sort((a, b) => b.surfaces - a.surfaces || a.prefix.localeCompare(b.prefix));
10955
+ }
10899
10956
  async function readCapabilityCoverage(projectRoot, coverage) {
10900
10957
  const [capsResult, outlinesResult] = await Promise.all([
10901
10958
  listEntities(projectRoot, "capability"),
@@ -11433,7 +11490,7 @@ function buildPersonaFrontmatter(row, slug) {
11433
11490
  };
11434
11491
  return { frontmatter, body: resolveBody(row, "") };
11435
11492
  }
11436
- function buildScenarioFrontmatter(row, personaSlug, outlineSlug) {
11493
+ function buildScenarioFrontmatter(row, personaSlug, outlineSlug, localFrontmatter) {
11437
11494
  const slug = `${personaSlug}--${outlineSlug}`;
11438
11495
  const frontmatter = {
11439
11496
  id: row.id,
@@ -11444,6 +11501,8 @@ function buildScenarioFrontmatter(row, personaSlug, outlineSlug) {
11444
11501
  // writing null wiped the local value on every pull.
11445
11502
  ..."targetUrl" in row ? { target_url: row.targetUrl ?? null } : {},
11446
11503
  ..."entryPath" in row ? { entry_path: row.entryPath ?? null } : {},
11504
+ ..."reviews" in row && Array.isArray(row.reviews) ? { reviews: row.reviews } : localFrontmatter?.reviews !== void 0 ? { reviews: localFrontmatter.reviews } : {},
11505
+ ..."state" in row && row.state !== void 0 ? { state: row.state ?? null } : localFrontmatter?.state !== void 0 ? { state: localFrontmatter.state } : {},
11447
11506
  baseline_score: row.baselineScore ?? null,
11448
11507
  current_score: row.currentScore ?? null,
11449
11508
  target_score: row.targetScore ?? 95,
@@ -11535,6 +11594,7 @@ async function loadLocalIndex(projectRoot, type2) {
11535
11594
  const byId = /* @__PURE__ */ new Map();
11536
11595
  const bySlug = /* @__PURE__ */ new Map();
11537
11596
  const bodyById = /* @__PURE__ */ new Map();
11597
+ const frontmatterById = /* @__PURE__ */ new Map();
11538
11598
  const [result, baseById, lastPushById] = await Promise.all([
11539
11599
  listEntities(projectRoot, type2),
11540
11600
  readBaseHashes(projectRoot),
@@ -11546,9 +11606,10 @@ async function loadLocalIndex(projectRoot, type2) {
11546
11606
  byId.set(fm.id, fm.slug);
11547
11607
  bySlug.set(fm.slug, fm.id);
11548
11608
  bodyById.set(fm.id, entity.body);
11609
+ frontmatterById.set(fm.id, entity.frontmatter);
11549
11610
  }
11550
11611
  }
11551
- return { byId, bySlug, bodyById, baseById, lastPushById };
11612
+ return { byId, bySlug, bodyById, frontmatterById, baseById, lastPushById };
11552
11613
  }
11553
11614
  async function classifyExisting(projectRoot, type2, cloudId, localSlug, targetSlug, cloudVersion, localIndex) {
11554
11615
  const base = localIndex.baseById[cloudId];
@@ -11889,7 +11950,8 @@ async function pullScenarios(projectRoot, cloud, projectId, outlineIdToSlug, per
11889
11950
  });
11890
11951
  continue;
11891
11952
  }
11892
- const { frontmatter, body, slug } = buildScenarioFrontmatter(row, personaSlug, outlineSlug);
11953
+ const localFm = localIndex.frontmatterById.get(row.id);
11954
+ const { frontmatter, body, slug } = buildScenarioFrontmatter(row, personaSlug, outlineSlug, localFm);
11893
11955
  await applyEntityPullOutcome(projectRoot, "scenario", row, slug, frontmatter, body, localIndex, mode, result.pulls.scenario, batch);
11894
11956
  }
11895
11957
  await finalizePullBatch(projectRoot, batch);
@@ -12401,15 +12463,15 @@ function resolveApiUrl(configured) {
12401
12463
  assertSecureApiUrl(url, configured ? "API URL" : "IAMU_API_URL");
12402
12464
  return url;
12403
12465
  }
12404
- function parseStore(raw, path32) {
12466
+ function parseStore(raw, path34) {
12405
12467
  let parsed;
12406
12468
  try {
12407
12469
  parsed = JSON.parse(raw);
12408
12470
  } catch (err) {
12409
- throw new CredentialsFileError(path32, "not valid JSON", err);
12471
+ throw new CredentialsFileError(path34, "not valid JSON", err);
12410
12472
  }
12411
12473
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
12412
- throw new CredentialsFileError(path32, "expected a JSON object");
12474
+ throw new CredentialsFileError(path34, "expected a JSON object");
12413
12475
  }
12414
12476
  return parsed;
12415
12477
  }
@@ -12417,26 +12479,26 @@ function isNotFound(err) {
12417
12479
  return typeof err === "object" && err !== null && err.code === "ENOENT";
12418
12480
  }
12419
12481
  async function readCredentialsStore() {
12420
- const path32 = getCredentialsPath();
12482
+ const path34 = getCredentialsPath();
12421
12483
  let raw;
12422
12484
  try {
12423
- raw = await readFile(path32, "utf-8");
12485
+ raw = await readFile(path34, "utf-8");
12424
12486
  } catch (err) {
12425
12487
  if (isNotFound(err)) return {};
12426
12488
  warnUnreadableStoreOnce(err);
12427
12489
  throw err;
12428
12490
  }
12429
12491
  try {
12430
- return parseStore(raw, path32);
12492
+ return parseStore(raw, path34);
12431
12493
  } catch (err) {
12432
12494
  warnUnreadableStoreOnce(err);
12433
12495
  throw err;
12434
12496
  }
12435
12497
  }
12436
12498
  async function writeCredentialsStore(store) {
12437
- const path32 = getCredentialsPath();
12438
- await mkdir(dirname(path32), { recursive: true, mode: 448 });
12439
- await atomicWriteFile(path32, `${JSON.stringify(store, null, 2)}
12499
+ const path34 = getCredentialsPath();
12500
+ await mkdir(dirname(path34), { recursive: true, mode: 448 });
12501
+ await atomicWriteFile(path34, `${JSON.stringify(store, null, 2)}
12440
12502
  `, {
12441
12503
  mode: 384
12442
12504
  });
@@ -12456,16 +12518,16 @@ function resolveApiKey(apiUrl) {
12456
12518
  } catch {
12457
12519
  return null;
12458
12520
  }
12459
- const path32 = getCredentialsPath();
12521
+ const path34 = getCredentialsPath();
12460
12522
  let raw;
12461
12523
  try {
12462
- raw = readFileSync(path32, "utf-8");
12524
+ raw = readFileSync(path34, "utf-8");
12463
12525
  } catch (err) {
12464
12526
  if (!isNotFound(err)) warnUnreadableStoreOnce(err);
12465
12527
  return null;
12466
12528
  }
12467
12529
  try {
12468
- const entry = parseStore(raw, path32)[url];
12530
+ const entry = parseStore(raw, path34)[url];
12469
12531
  if (typeof entry !== "object" || entry === null) return null;
12470
12532
  const token = entry.token;
12471
12533
  return typeof token === "string" && token ? token : null;
@@ -12504,12 +12566,12 @@ var init_credentials = __esm({
12504
12566
  * @param reason - What is wrong with it.
12505
12567
  * @param cause - Underlying parse error, if any.
12506
12568
  */
12507
- constructor(path32, reason, cause) {
12569
+ constructor(path34, reason, cause) {
12508
12570
  super(
12509
- `Credentials file ${path32} is corrupt (${reason}). Fix it by hand, or delete it and run \`iamu auth login\` again.`,
12571
+ `Credentials file ${path34} is corrupt (${reason}). Fix it by hand, or delete it and run \`iamu auth login\` again.`,
12510
12572
  {
12511
12573
  cause,
12512
- suggestedAction: `Delete ${path32} and run \`iamu auth login\`, or set IAMU_TOKEN.`
12574
+ suggestedAction: `Delete ${path34} and run \`iamu auth login\`, or set IAMU_TOKEN.`
12513
12575
  }
12514
12576
  );
12515
12577
  }
@@ -13189,8 +13251,8 @@ import { dirname as dirname3, join as join4, resolve as resolve2 } from "path";
13189
13251
  import { fileURLToPath as fileURLToPath2 } from "url";
13190
13252
  import chalk6 from "chalk";
13191
13253
  function getVersion() {
13192
- if ("0.2.0-beta.17") {
13193
- return "0.2.0-beta.17";
13254
+ if ("0.2.0-beta.19") {
13255
+ return "0.2.0-beta.19";
13194
13256
  }
13195
13257
  try {
13196
13258
  const thisDir = dirname3(fileURLToPath2(import.meta.url));
@@ -13219,6 +13281,7 @@ function loadSkillTemplates(version) {
13219
13281
  "iamu-setup.md",
13220
13282
  "iamu-propose.md",
13221
13283
  "iamu-new.md",
13284
+ "iamu-stage.md",
13222
13285
  "iamu-run.md",
13223
13286
  "iamu-findings.md",
13224
13287
  "iamu-status.md"
@@ -13296,16 +13359,16 @@ async function needsWrite(filePath, content) {
13296
13359
  return true;
13297
13360
  }
13298
13361
  }
13299
- function isCliWrittenSkill(path32) {
13362
+ function isCliWrittenSkill(path34) {
13300
13363
  try {
13301
- return /^generatedBy:/m.test(readFileSync3(path32, "utf-8"));
13364
+ return /^generatedBy:/m.test(readFileSync3(path34, "utf-8"));
13302
13365
  } catch {
13303
13366
  return false;
13304
13367
  }
13305
13368
  }
13306
- function isCliWrittenPrompt(path32) {
13369
+ function isCliWrittenPrompt(path34) {
13307
13370
  try {
13308
- const content = readFileSync3(path32, "utf-8");
13371
+ const content = readFileSync3(path34, "utf-8");
13309
13372
  return content.includes("iamu") || /^generatedBy:/m.test(content);
13310
13373
  } catch {
13311
13374
  return false;
@@ -13317,8 +13380,8 @@ function planSkillFiles(hosts, projectRoot, version) {
13317
13380
  for (const host of hosts) {
13318
13381
  if (pluginProvidesSkills(host, projectRoot)) {
13319
13382
  for (const name of skillTemplates.keys()) {
13320
- const path32 = join4(host.getSkillsDir(projectRoot), name, "SKILL.md");
13321
- if (isCliWrittenSkill(path32)) files.push({ host, path: path32, content: null });
13383
+ const path34 = join4(host.getSkillsDir(projectRoot), name, "SKILL.md");
13384
+ if (isCliWrittenSkill(path34)) files.push({ host, path: path34, content: null });
13322
13385
  }
13323
13386
  } else if (host.supportsSkills) {
13324
13387
  const skillsDir = host.getSkillsDir(projectRoot);
@@ -18809,6 +18872,22 @@ async function checkProjectTestabilityContract(projectRoot) {
18809
18872
  status: "ok",
18810
18873
  message: `Configured (${details.join(" \xB7 ") || "present"})`
18811
18874
  });
18875
+ if (!frontmatter.access) {
18876
+ let contextAccess = null;
18877
+ try {
18878
+ contextAccess = (await readContext(projectRoot))?.frontmatter.access ?? null;
18879
+ } catch {
18880
+ }
18881
+ if (contextAccess) {
18882
+ checks.push({
18883
+ category: "target",
18884
+ name: "User switching (access)",
18885
+ status: "warn",
18886
+ message: "How to switch user is in iamu/context.yml, which holds the domain. Walks still read it there.",
18887
+ remedy: "Move the `access:` field from iamu/context.yml to the iamu/testability.md frontmatter."
18888
+ });
18889
+ }
18890
+ }
18812
18891
  return { checks, targetUrl };
18813
18892
  } catch (err) {
18814
18893
  checks.push({
@@ -19361,6 +19440,7 @@ var init_list = __esm({
19361
19440
  // src/commands/validate.ts
19362
19441
  var validate_exports = {};
19363
19442
  __export(validate_exports, {
19443
+ schemaByType: () => schemaByType,
19364
19444
  validate: () => validate
19365
19445
  });
19366
19446
  import { randomUUID as randomUUID8 } from "crypto";
@@ -20035,6 +20115,413 @@ var init_new_entity = __esm({
20035
20115
  }
20036
20116
  });
20037
20117
 
20118
+ // src/commands/set-entity.ts
20119
+ var set_entity_exports = {};
20120
+ __export(set_entity_exports, {
20121
+ setEntity: () => setEntity
20122
+ });
20123
+ import { promises as fs28 } from "fs";
20124
+ import path26 from "path";
20125
+ import chalk27 from "chalk";
20126
+ import { ZodError as ZodError2 } from "zod";
20127
+ function given(opts, key) {
20128
+ const v = opts[key];
20129
+ if (Array.isArray(v)) return v.length > 0;
20130
+ if (key === "state") return v === false;
20131
+ return v !== void 0;
20132
+ }
20133
+ function splitList(raw) {
20134
+ return raw.split(",").map((s) => s.trim()).filter(Boolean);
20135
+ }
20136
+ function applyChanges(type2, fm, opts) {
20137
+ const changed = [];
20138
+ const put = (key, value) => {
20139
+ if (value === null) delete fm[key];
20140
+ else fm[key] = value;
20141
+ changed.push(key);
20142
+ };
20143
+ if (type2 === "scenario") {
20144
+ if (opts.entry !== void 0) put("entry_path", opts.entry || null);
20145
+ if (opts.target !== void 0) put("target_url", opts.target || null);
20146
+ if (opts.state === false) {
20147
+ put("state", null);
20148
+ } else if (opts.stateDescription !== void 0 || opts.stateApproximation !== void 0) {
20149
+ const prev = fm.state ?? {};
20150
+ const next = { ...prev };
20151
+ if (opts.stateDescription !== void 0) {
20152
+ if (opts.stateDescription.trim() === "") delete next.description;
20153
+ else next.description = opts.stateDescription;
20154
+ }
20155
+ if (opts.stateApproximation !== void 0) {
20156
+ if (opts.stateApproximation.trim() === "") delete next.approximation;
20157
+ else next.approximation = opts.stateApproximation;
20158
+ }
20159
+ if (Object.keys(next).length === 0) {
20160
+ put("state", null);
20161
+ } else {
20162
+ put("state", next);
20163
+ }
20164
+ }
20165
+ } else if (type2 === "persona") {
20166
+ if (opts.access !== void 0) put("access", opts.access || null);
20167
+ } else if (type2 === "capability") {
20168
+ if (opts.paths === false) {
20169
+ put("paths", []);
20170
+ } else if (opts.paths !== void 0 || given(opts, "addPath") || given(opts, "removePath")) {
20171
+ let paths = typeof opts.paths === "string" ? splitList(opts.paths) : Array.isArray(fm.paths) ? fm.paths : [];
20172
+ for (const p of opts.addPath ?? []) {
20173
+ if (!paths.includes(p)) paths = [...paths, p];
20174
+ }
20175
+ const drop = new Set(opts.removePath ?? []);
20176
+ paths = paths.filter((p) => !drop.has(p));
20177
+ put("paths", paths);
20178
+ }
20179
+ if (opts.area !== void 0) put("area", opts.area || null);
20180
+ }
20181
+ return changed;
20182
+ }
20183
+ async function setEntity(type2, slug, opts) {
20184
+ const ctx = await resolveProject(opts);
20185
+ if (!ctx) return;
20186
+ const { projectRoot } = ctx;
20187
+ const allowed = SETTABLE[type2];
20188
+ if (!allowed) {
20189
+ commandError(
20190
+ `Cannot set fields on "${type2}". Supported: ${Object.keys(SETTABLE).join(", ")}. What a scenario is about (narrative, tension, human tests, brief) is edited in its file, not here.`,
20191
+ opts
20192
+ );
20193
+ return;
20194
+ }
20195
+ const foreign = Object.keys(FLAG_NAMES).filter(
20196
+ (k) => !allowed.includes(k) && given(opts, k)
20197
+ );
20198
+ if (foreign.length > 0) {
20199
+ commandError(
20200
+ `${foreign.map((k) => FLAG_NAMES[k]).join(", ")} ${foreign.length === 1 ? "does" : "do"} not apply to a ${type2}. ${type2} takes: ${allowed.map((k) => FLAG_NAMES[k]).join(", ")}.`,
20201
+ opts
20202
+ );
20203
+ return;
20204
+ }
20205
+ const effectiveOpts = { ...opts };
20206
+ if (opts.file) {
20207
+ let raw;
20208
+ try {
20209
+ if (opts.file === "-") {
20210
+ const chunks = [];
20211
+ for await (const chunk of process.stdin) {
20212
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
20213
+ }
20214
+ raw = Buffer.concat(chunks).toString("utf8");
20215
+ } else {
20216
+ raw = await fs28.readFile(path26.resolve(projectRoot, opts.file), "utf8");
20217
+ }
20218
+ } catch (err) {
20219
+ commandError(
20220
+ `Could not read patch file: ${err instanceof Error ? err.message : String(err)}`,
20221
+ opts
20222
+ );
20223
+ return;
20224
+ }
20225
+ let parsed;
20226
+ try {
20227
+ parsed = JSON.parse(raw);
20228
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
20229
+ throw new Error("Patch must be a JSON object");
20230
+ }
20231
+ } catch (err) {
20232
+ commandError(
20233
+ `Failed to parse patch JSON: ${err instanceof Error ? err.message : String(err)}`,
20234
+ opts
20235
+ );
20236
+ return;
20237
+ }
20238
+ const allowedPatchKeys = {
20239
+ scenario: ["entry", "entry_path", "target", "target_url", "state"],
20240
+ persona: ["access"],
20241
+ capability: ["paths", "addPath", "removePath", "area"]
20242
+ };
20243
+ const foreignPatchKeys = Object.keys(parsed).filter(
20244
+ (k) => !allowedPatchKeys[type2]?.includes(k)
20245
+ );
20246
+ if (foreignPatchKeys.length > 0) {
20247
+ commandError(
20248
+ `Cannot set authoring fields on ${type2} via staging patch: ${foreignPatchKeys.join(", ")}. What an entity is about (narrative, tension, human tests, brief) is edited in its file, not through staging.`,
20249
+ opts
20250
+ );
20251
+ return;
20252
+ }
20253
+ if (type2 === "scenario") {
20254
+ if (effectiveOpts.entry === void 0 && (parsed.entry !== void 0 || parsed.entry_path !== void 0)) {
20255
+ const v = parsed.entry ?? parsed.entry_path;
20256
+ effectiveOpts.entry = v === null || v === false ? false : String(v);
20257
+ }
20258
+ if (effectiveOpts.target === void 0 && (parsed.target !== void 0 || parsed.target_url !== void 0)) {
20259
+ const v = parsed.target ?? parsed.target_url;
20260
+ effectiveOpts.target = v === null || v === false ? false : String(v);
20261
+ }
20262
+ if (parsed.state !== void 0) {
20263
+ if (parsed.state === null || parsed.state === false) {
20264
+ if (effectiveOpts.state === void 0) effectiveOpts.state = false;
20265
+ } else if (typeof parsed.state === "object") {
20266
+ const s = parsed.state;
20267
+ if (effectiveOpts.stateDescription === void 0 && s.description !== void 0) {
20268
+ effectiveOpts.stateDescription = s.description === null ? "" : String(s.description);
20269
+ }
20270
+ if (effectiveOpts.stateApproximation === void 0 && s.approximation !== void 0) {
20271
+ effectiveOpts.stateApproximation = s.approximation === null ? "" : String(s.approximation);
20272
+ }
20273
+ }
20274
+ }
20275
+ } else if (type2 === "persona") {
20276
+ if (effectiveOpts.access === void 0 && parsed.access !== void 0) {
20277
+ effectiveOpts.access = parsed.access === null || parsed.access === false ? false : String(parsed.access);
20278
+ }
20279
+ } else if (type2 === "capability") {
20280
+ if (effectiveOpts.paths === void 0 && parsed.paths !== void 0) {
20281
+ if (parsed.paths === null || parsed.paths === false) {
20282
+ effectiveOpts.paths = false;
20283
+ } else if (Array.isArray(parsed.paths)) {
20284
+ effectiveOpts.paths = parsed.paths.length === 0 ? false : parsed.paths.join(",");
20285
+ }
20286
+ }
20287
+ if (effectiveOpts.addPath === void 0 && Array.isArray(parsed.addPath)) {
20288
+ effectiveOpts.addPath = parsed.addPath.map(String);
20289
+ }
20290
+ if (effectiveOpts.removePath === void 0 && Array.isArray(parsed.removePath)) {
20291
+ effectiveOpts.removePath = parsed.removePath.map(String);
20292
+ }
20293
+ if (effectiveOpts.area === void 0 && parsed.area !== void 0) {
20294
+ effectiveOpts.area = parsed.area === null || parsed.area === false ? false : String(parsed.area);
20295
+ }
20296
+ }
20297
+ }
20298
+ if (!allowed.some((k) => given(effectiveOpts, k))) {
20299
+ commandError(
20300
+ `Nothing to set. ${type2} takes: ${allowed.map((k) => FLAG_NAMES[k]).join(", ")}, or --file <path>.`,
20301
+ opts
20302
+ );
20303
+ return;
20304
+ }
20305
+ if (type2 === "capability") {
20306
+ const candidatePaths = [
20307
+ ...typeof effectiveOpts.paths === "string" ? splitList(effectiveOpts.paths) : [],
20308
+ ...effectiveOpts.addPath ?? []
20309
+ ];
20310
+ const invalid = candidatePaths.filter((p) => !p.startsWith("/"));
20311
+ if (invalid.length > 0) {
20312
+ commandError(
20313
+ `Route prefixes must start with "/": ${invalid.join(", ")}`,
20314
+ opts
20315
+ );
20316
+ return;
20317
+ }
20318
+ }
20319
+ const entity = await safeReadEntity(
20320
+ projectRoot,
20321
+ type2,
20322
+ slug,
20323
+ opts
20324
+ );
20325
+ if (!entity) return;
20326
+ const fm = { ...entity.frontmatter };
20327
+ const changed = applyChanges(type2, fm, effectiveOpts);
20328
+ let writtenPath;
20329
+ try {
20330
+ const record = await writeEntity(
20331
+ projectRoot,
20332
+ type2,
20333
+ fm,
20334
+ entity.body
20335
+ );
20336
+ writtenPath = record.path;
20337
+ } catch (err) {
20338
+ const message = err instanceof ZodError2 ? err.issues.map((i) => `${i.path.join(".") || "root"}: ${i.message}`).join("; ") : err instanceof Error ? err.message : String(err);
20339
+ commandError(`Could not set ${type2} "${slug}": ${message}`, opts);
20340
+ return;
20341
+ }
20342
+ const entitySlug = String(fm.slug);
20343
+ if (opts.json) {
20344
+ console.log(
20345
+ JSON.stringify({ slug: entitySlug, type: type2, path: writtenPath, changed })
20346
+ );
20347
+ return;
20348
+ }
20349
+ console.log();
20350
+ console.log(
20351
+ ` ${symbols.success} Updated ${type2} ${chalk27.cyan(entitySlug)}: ${changed.join(", ")}`
20352
+ );
20353
+ console.log(` ${chalk27.dim("path:")} ${writtenPath}`);
20354
+ console.log();
20355
+ }
20356
+ var SETTABLE, FLAG_NAMES;
20357
+ var init_set_entity = __esm({
20358
+ "src/commands/set-entity.ts"() {
20359
+ "use strict";
20360
+ init_dist2();
20361
+ init_symbols();
20362
+ init_context4();
20363
+ SETTABLE = {
20364
+ scenario: [
20365
+ "entry",
20366
+ "target",
20367
+ "stateDescription",
20368
+ "stateApproximation",
20369
+ "state"
20370
+ ],
20371
+ persona: ["access"],
20372
+ capability: ["paths", "addPath", "removePath", "area"]
20373
+ };
20374
+ FLAG_NAMES = {
20375
+ entry: "--entry",
20376
+ target: "--target",
20377
+ stateDescription: "--state-description",
20378
+ stateApproximation: "--state-approximation",
20379
+ state: "--no-state",
20380
+ access: "--access",
20381
+ paths: "--paths",
20382
+ addPath: "--add-path",
20383
+ removePath: "--remove-path",
20384
+ area: "--area"
20385
+ };
20386
+ }
20387
+ });
20388
+
20389
+ // src/commands/edit-entity.ts
20390
+ var edit_entity_exports = {};
20391
+ __export(edit_entity_exports, {
20392
+ editEntity: () => editEntity
20393
+ });
20394
+ import { spawn as spawn2 } from "child_process";
20395
+ import { promises as fs29 } from "fs";
20396
+ import path27 from "path";
20397
+ import chalk28 from "chalk";
20398
+ async function editEntity(type2, slug, opts = {}) {
20399
+ const ctx = await resolveProject(opts);
20400
+ if (!ctx) return;
20401
+ const { projectRoot } = ctx;
20402
+ if (!ENTITY_TYPES.includes(type2)) {
20403
+ commandError(
20404
+ `Unknown entity type "${type2}". Supported: ${ENTITY_TYPES.join(", ")}.`,
20405
+ opts
20406
+ );
20407
+ return;
20408
+ }
20409
+ const dir = ENTITY_DIRS[type2];
20410
+ const filePath = path27.join(projectRoot, "iamu", dir, `${slug}.md`);
20411
+ try {
20412
+ await fs29.access(filePath);
20413
+ } catch {
20414
+ commandError(
20415
+ `${type2} "${slug}" not found at ${path27.relative(projectRoot, filePath)}.`,
20416
+ opts
20417
+ );
20418
+ return;
20419
+ }
20420
+ if (!process.stdin.isTTY || process.env.CI) {
20421
+ commandError(
20422
+ `Cannot open editor in a non-interactive environment. Edit the file directly: ${path27.relative(projectRoot, filePath)}`,
20423
+ opts
20424
+ );
20425
+ return;
20426
+ }
20427
+ const rawEditor = process.env.VISUAL || process.env.EDITOR || (process.platform === "win32" ? "notepad" : "vi");
20428
+ const parts = rawEditor.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) ?? [
20429
+ rawEditor
20430
+ ];
20431
+ const editorCmd = parts[0].replace(/^["']|["']$/g, "");
20432
+ const editorArgs = parts.slice(1).map((a) => a.replace(/^["']|["']$/g, ""));
20433
+ await new Promise((resolve3) => {
20434
+ const child = spawn2(editorCmd, [...editorArgs, filePath], {
20435
+ stdio: "inherit"
20436
+ });
20437
+ child.on("exit", (code) => {
20438
+ if (code !== 0) {
20439
+ console.error(
20440
+ chalk28.red(
20441
+ `
20442
+ Editor exited with code ${code}. Changes not validated.
20443
+ `
20444
+ )
20445
+ );
20446
+ process.exitCode = code ?? 1;
20447
+ resolve3();
20448
+ } else {
20449
+ resolve3();
20450
+ }
20451
+ });
20452
+ child.on("error", (err) => {
20453
+ commandError(
20454
+ `Failed to launch editor (${rawEditor}): ${err.message}`,
20455
+ opts
20456
+ );
20457
+ resolve3();
20458
+ });
20459
+ });
20460
+ if (process.exitCode) return;
20461
+ try {
20462
+ const content = await fs29.readFile(filePath, "utf8");
20463
+ const { frontmatter } = parseFrontmatter(content);
20464
+ const schema2 = schemaByType[type2];
20465
+ const parsed = schema2.safeParse(frontmatter);
20466
+ if (!parsed.success) {
20467
+ if (opts.json) {
20468
+ console.log(
20469
+ JSON.stringify({
20470
+ valid: false,
20471
+ type: type2,
20472
+ slug,
20473
+ path: filePath,
20474
+ errors: parsed.error.issues.map((i) => ({
20475
+ field: i.path.join(".") || "root",
20476
+ message: i.message
20477
+ }))
20478
+ })
20479
+ );
20480
+ } else {
20481
+ console.log();
20482
+ console.log(
20483
+ ` ${symbols.warning} ${chalk28.yellow(`Validation failed for ${type2} "${slug}":`)}`
20484
+ );
20485
+ for (const issue of parsed.error.issues) {
20486
+ const field2 = issue.path.join(".") || "root";
20487
+ console.log(` - ${field2}: ${issue.message}`);
20488
+ }
20489
+ console.log();
20490
+ console.log(
20491
+ chalk28.dim(
20492
+ ` Fix the file and run: iamu validate ${path27.relative(projectRoot, filePath)}`
20493
+ )
20494
+ );
20495
+ console.log();
20496
+ }
20497
+ process.exitCode = 1;
20498
+ return;
20499
+ }
20500
+ if (opts.json) {
20501
+ console.log(JSON.stringify({ valid: true, type: type2, slug, path: filePath }));
20502
+ return;
20503
+ }
20504
+ console.log();
20505
+ console.log(` ${symbols.success} Validated ${type2} ${chalk28.cyan(slug)}`);
20506
+ console.log();
20507
+ } catch (err) {
20508
+ commandError(
20509
+ `Failed to validate ${type2} "${slug}" after edit: ${err instanceof Error ? err.message : String(err)}`,
20510
+ opts
20511
+ );
20512
+ }
20513
+ }
20514
+ var init_edit_entity = __esm({
20515
+ "src/commands/edit-entity.ts"() {
20516
+ "use strict";
20517
+ init_dist2();
20518
+ init_dist();
20519
+ init_symbols();
20520
+ init_context4();
20521
+ init_validate();
20522
+ }
20523
+ });
20524
+
20038
20525
  // src/commands/explore-crawl.ts
20039
20526
  function candidateSig(c) {
20040
20527
  return `${c.role}
@@ -20074,6 +20561,9 @@ async function settle(page, settleMs) {
20074
20561
  });
20075
20562
  await page.waitForLoadState("networkidle", { timeout: settleMs }).catch(() => {
20076
20563
  });
20564
+ const spaSettle = Math.min(Math.max(settleMs, 50), 200);
20565
+ await page.waitForTimeout(spaSettle).catch(() => {
20566
+ });
20077
20567
  }
20078
20568
  async function readPage(page) {
20079
20569
  return page.evaluate(READ_PAGE);
@@ -20091,8 +20581,12 @@ async function clickCandidate(page, want) {
20091
20581
  (x) => candidateSig(x) === candidateSig(want)
20092
20582
  );
20093
20583
  if (!c) return false;
20094
- await page.locator(`[data-iamu-x="${c.index}"]`).click({ timeout: CLICK_TIMEOUT_MS });
20095
- return true;
20584
+ try {
20585
+ await page.locator(`[data-iamu-x="${c.index}"]`).click({ timeout: CLICK_TIMEOUT_MS });
20586
+ return true;
20587
+ } catch {
20588
+ return false;
20589
+ }
20096
20590
  }
20097
20591
  async function crawl(page, opts) {
20098
20592
  const origin = originOf(opts.startUrl);
@@ -20149,7 +20643,7 @@ async function crawlFrom(page, opts, origin, takeBlocked) {
20149
20643
  const byKey = /* @__PURE__ */ new Map();
20150
20644
  const byPlaceView = /* @__PURE__ */ new Map();
20151
20645
  const signatureOf = (s) => exploreSignature(s.headings, s.title);
20152
- function addSurface(state, place, view, path32, depth) {
20646
+ function addSurface(state, place, view, path34, depth) {
20153
20647
  const key = exploreSurfaceKey(place, view);
20154
20648
  const surface = {
20155
20649
  key,
@@ -20161,7 +20655,7 @@ async function crawlFrom(page, opts, origin, takeBlocked) {
20161
20655
  headings: state.headings,
20162
20656
  fields: state.fields,
20163
20657
  depth,
20164
- path: path32,
20658
+ path: path34,
20165
20659
  actions: []
20166
20660
  };
20167
20661
  surfaces.push(surface);
@@ -20179,9 +20673,9 @@ ${surface.signature}`, key);
20179
20673
  });
20180
20674
  await settle(page, settleMs);
20181
20675
  }
20182
- async function replay(path32) {
20183
- if (!("goto" in (path32[0] ?? {}))) await goto(opts.startUrl);
20184
- for (const step of path32) {
20676
+ async function replay(path34) {
20677
+ if (!("goto" in (path34[0] ?? {}))) await goto(opts.startUrl);
20678
+ for (const step of path34) {
20185
20679
  if ("goto" in step) {
20186
20680
  await goto(step.goto);
20187
20681
  } else {
@@ -20305,8 +20799,8 @@ ${sig}`, key);
20305
20799
  if (stats.surfaces >= budget.max_surfaces) {
20306
20800
  stats.truncated = true;
20307
20801
  } else {
20308
- const path32 = !sameDoc && view === null ? [{ goto: after.url }] : [...surface.path, ...via, click];
20309
- addSurface(after, place, view, path32, surface.depth + 1);
20802
+ const path34 = !sameDoc && view === null ? [{ goto: after.url }] : [...surface.path, ...via, click];
20803
+ addSurface(after, place, view, path34, surface.depth + 1);
20310
20804
  }
20311
20805
  }
20312
20806
  }
@@ -20506,11 +21000,11 @@ __export(explore_exports, {
20506
21000
  exploreList: () => exploreList,
20507
21001
  exploreRun: () => exploreRun
20508
21002
  });
20509
- import path26 from "path";
20510
- import chalk27 from "chalk";
21003
+ import path28 from "path";
21004
+ import chalk29 from "chalk";
20511
21005
  function fail2(message, json2) {
20512
21006
  if (json2) console.error(JSON.stringify({ error: message }));
20513
- else console.error(chalk27.red(` ${message}`));
21007
+ else console.error(chalk29.red(` ${message}`));
20514
21008
  process.exitCode = 1;
20515
21009
  }
20516
21010
  function positiveInt(raw, fallback, flag) {
@@ -20609,7 +21103,7 @@ async function exploreRun(url, opts) {
20609
21103
  }
20610
21104
  const quiet = opts.json || process.env.IAMU_QUIET === "1";
20611
21105
  if (!quiet) {
20612
- console.error(chalk27.dim(` Exploring ${startUrl} (read-only)\u2026`));
21106
+ console.error(chalk29.dim(` Exploring ${startUrl} (read-only)\u2026`));
20613
21107
  }
20614
21108
  const now = /* @__PURE__ */ new Date();
20615
21109
  let opened = null;
@@ -20620,7 +21114,7 @@ async function exploreRun(url, opts) {
20620
21114
  startUrl,
20621
21115
  budget,
20622
21116
  deny,
20623
- log: quiet ? void 0 : (line) => console.error(chalk27.dim(line))
21117
+ log: quiet ? void 0 : (line) => console.error(chalk29.dim(line))
20624
21118
  });
20625
21119
  } catch (err) {
20626
21120
  fail2(err.message.split("\n")[0], opts.json);
@@ -20640,7 +21134,7 @@ async function exploreRun(url, opts) {
20640
21134
  surfaces: result.surfaces
20641
21135
  };
20642
21136
  const file = await writeExploreMap(ctx.projectRoot, map2);
20643
- const rel = path26.relative(process.cwd(), file);
21137
+ const rel = path28.relative(process.cwd(), file);
20644
21138
  if (opts.json) {
20645
21139
  console.log(JSON.stringify({ id: map2.id, path: rel, stats: map2.stats }));
20646
21140
  return;
@@ -20648,17 +21142,17 @@ async function exploreRun(url, opts) {
20648
21142
  const s = map2.stats;
20649
21143
  console.log(
20650
21144
  `
20651
- ${chalk27.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` : ""}`
21145
+ ${chalk29.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` : ""}`
20652
21146
  );
20653
21147
  if (s.truncated) {
20654
21148
  console.log(
20655
- chalk27.yellow(
21149
+ chalk29.yellow(
20656
21150
  " Stopped by the budget before the product ran out \u2014 raise --max-surfaces / --max-actions / --max-depth to go further."
20657
21151
  )
20658
21152
  );
20659
21153
  }
20660
21154
  console.log(` Map: ${rel}`);
20661
- console.log(chalk27.dim(" Next: iamu explore coverage"));
21155
+ console.log(chalk29.dim(" Next: iamu explore coverage"));
20662
21156
  }
20663
21157
  async function resolveMapId(projectRoot, id, offsetFromEnd = 1) {
20664
21158
  if (id && id !== "latest") return id;
@@ -20686,7 +21180,7 @@ async function exploreList(opts) {
20686
21180
  }
20687
21181
  for (const r of rows) {
20688
21182
  console.log(
20689
- ` ${r.id} ${r.surfaces} surfaces ${chalk27.dim(r.target_url)}`
21183
+ ` ${r.id} ${r.surfaces} surfaces ${chalk29.dim(r.target_url)}`
20690
21184
  );
20691
21185
  }
20692
21186
  }
@@ -20713,7 +21207,10 @@ async function exploreCoverageCommand(mapId, opts) {
20713
21207
  ...cov,
20714
21208
  ...capReport.capabilities.length > 0 ? {
20715
21209
  capabilities: capReport.capabilities,
20716
- unassigned_surfaces: capReport.unassigned_surfaces
21210
+ unassigned_surfaces: capReport.unassigned_surfaces,
21211
+ unassigned_prefixes: groupUnassignedByPrefix(
21212
+ capReport.unassigned_surfaces
21213
+ )
20717
21214
  } : {}
20718
21215
  })
20719
21216
  );
@@ -20724,22 +21221,22 @@ async function exploreCoverageCommand(mapId, opts) {
20724
21221
  Map ${id} (${map2.surfaces.length} surfaces) against ${cov.runs} runs, ${cov.steps} steps`
20725
21222
  );
20726
21223
  console.log(
20727
- ` ${chalk27.green(`${cov.reached} reached`)} \xB7 ${chalk27.yellow(`${cov.place_only} URL reached, view unclear`)} \xB7 ${chalk27.red(`${cov.unreached} never reached`)}`
21224
+ ` ${chalk29.green(`${cov.reached} reached`)} \xB7 ${chalk29.yellow(`${cov.place_only} URL reached, view unclear`)} \xB7 ${chalk29.red(`${cov.unreached} never reached`)}`
20728
21225
  );
20729
21226
  if (capReport.capabilities.length > 0) {
20730
- console.log(chalk27.bold("\n CAPABILITY COVERAGE"));
21227
+ console.log(chalk29.bold("\n CAPABILITY COVERAGE"));
20731
21228
  console.log(
20732
- chalk27.dim(
21229
+ chalk29.dim(
20733
21230
  " \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
20734
21231
  )
20735
21232
  );
20736
21233
  console.log(
20737
- chalk27.dim(
21234
+ chalk29.dim(
20738
21235
  ` ${"Capability".padEnd(24)} ${"Surfaces".padStart(8)} ${"Coverage".padStart(8)} ${"Scenarios (1\xB0 / 2\xB0)"}`
20739
21236
  )
20740
21237
  );
20741
21238
  console.log(
20742
- chalk27.dim(
21239
+ chalk29.dim(
20743
21240
  " \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
20744
21241
  )
20745
21242
  );
@@ -20750,8 +21247,8 @@ async function exploreCoverageCommand(mapId, opts) {
20750
21247
  );
20751
21248
  const covStr = `${cap.coverage_pct}%`.padStart(8);
20752
21249
  const scenarios = `${cap.primary_scenarios} / ${cap.secondary_scenarios}`;
20753
- const untestedFlag = cap.primary_scenarios === 0 && cap.unreached_surfaces > 0 ? chalk27.yellow(" (untested)") : "";
20754
- const covColored = cap.coverage_pct === 100 ? chalk27.green(covStr) : cap.coverage_pct === 0 ? chalk27.red(covStr) : chalk27.yellow(covStr);
21250
+ const untestedFlag = cap.primary_scenarios === 0 && cap.unreached_surfaces > 0 ? chalk29.yellow(" (untested)") : "";
21251
+ const covColored = cap.coverage_pct === 100 ? chalk29.green(covStr) : cap.coverage_pct === 0 ? chalk29.red(covStr) : chalk29.yellow(covStr);
20755
21252
  console.log(
20756
21253
  ` ${title} ${surfaces} ${covColored} ${scenarios}${untestedFlag}`
20757
21254
  );
@@ -20761,26 +21258,41 @@ async function exploreCoverageCommand(mapId, opts) {
20761
21258
  const reached = unassigned.filter((s) => s.status === "reached").length;
20762
21259
  const pct = Math.round(reached / unassigned.length * 100);
20763
21260
  console.log(
20764
- chalk27.dim(
21261
+ chalk29.dim(
20765
21262
  ` ${"(Unassigned)".padEnd(24)} ${`${reached}/${unassigned.length}`.padStart(8)} ${`${pct}%`.padStart(8)} -`
20766
21263
  )
20767
21264
  );
20768
21265
  }
20769
21266
  console.log(
20770
- chalk27.dim(
21267
+ chalk29.dim(
20771
21268
  " \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
20772
21269
  )
20773
21270
  );
21271
+ const prefixes = groupUnassignedByPrefix(capReport.unassigned_surfaces);
21272
+ if (prefixes.length > 0) {
21273
+ console.log(chalk29.bold("\n UNASSIGNED PAGES (no capability owns them)"));
21274
+ for (const g of prefixes) {
21275
+ const count = `${g.surfaces} page${g.surfaces === 1 ? "" : "s"} (${g.reached} reached)`;
21276
+ console.log(
21277
+ ` ${g.prefix.padEnd(24)} ${count.padEnd(24)} ${chalk29.dim(`e.g. ${g.examples.join(", ")}`)}`
21278
+ );
21279
+ }
21280
+ console.log(
21281
+ chalk29.dim(
21282
+ ` Give a prefix to the capability it belongs to: iamu set capability <slug> --add-path ${prefixes[0]?.prefix}`
21283
+ )
21284
+ );
21285
+ }
20774
21286
  const hasGaps = capReport.capabilities.some((c) => c.unreached_surfaces > 0) || capReport.unassigned_surfaces.some((s) => s.status !== "reached");
20775
21287
  if (hasGaps) {
20776
- console.log(chalk27.red("\n UNREACHED SURFACES (Gaps)"));
21288
+ console.log(chalk29.red("\n UNREACHED SURFACES (Gaps)"));
20777
21289
  for (const cap of capReport.capabilities) {
20778
21290
  const gaps = cap.surfaces.filter((s) => s.status !== "reached");
20779
21291
  if (gaps.length === 0) continue;
20780
- console.log(` ${chalk27.bold(cap.slug)}:`);
21292
+ console.log(` ${chalk29.bold(cap.slug)}:`);
20781
21293
  for (const s of gaps) {
20782
21294
  console.log(
20783
- ` ${chalk27.red("\u25CB")} ${s.key.padEnd(32)} ${s.label}${chalk27.dim(` (depth ${s.depth})`)}`
21295
+ ` ${chalk29.red("\u25CB")} ${s.key.padEnd(32)} ${s.label}${chalk29.dim(` (depth ${s.depth})`)}`
20784
21296
  );
20785
21297
  }
20786
21298
  }
@@ -20788,23 +21300,23 @@ async function exploreCoverageCommand(mapId, opts) {
20788
21300
  (s) => s.status !== "reached"
20789
21301
  );
20790
21302
  if (unassignedGaps.length > 0) {
20791
- console.log(chalk27.dim(" (Unassigned):"));
21303
+ console.log(chalk29.dim(" (Unassigned):"));
20792
21304
  for (const s of unassignedGaps) {
20793
21305
  console.log(
20794
- ` ${chalk27.dim("\u25CB")} ${s.key.padEnd(32)} ${s.label}${chalk27.dim(` (depth ${s.depth})`)}`
21306
+ ` ${chalk29.dim("\u25CB")} ${s.key.padEnd(32)} ${s.label}${chalk29.dim(` (depth ${s.depth})`)}`
20795
21307
  );
20796
21308
  }
20797
21309
  }
20798
21310
  }
20799
21311
  if (opts.all) {
20800
- console.log(chalk27.green("\n REACHED SURFACES"));
21312
+ console.log(chalk29.green("\n REACHED SURFACES"));
20801
21313
  for (const cap of capReport.capabilities) {
20802
21314
  const reached = cap.surfaces.filter((s) => s.status === "reached");
20803
21315
  if (reached.length === 0) continue;
20804
- console.log(` ${chalk27.bold(cap.slug)}:`);
21316
+ console.log(` ${chalk29.bold(cap.slug)}:`);
20805
21317
  for (const s of reached) {
20806
21318
  console.log(
20807
- ` ${chalk27.green("\u2713")} ${s.key.padEnd(32)} ${s.label}${chalk27.dim(` (depth ${s.depth})`)}`
21319
+ ` ${chalk29.green("\u2713")} ${s.key.padEnd(32)} ${s.label}${chalk29.dim(` (depth ${s.depth})`)}`
20808
21320
  );
20809
21321
  }
20810
21322
  }
@@ -20812,10 +21324,10 @@ async function exploreCoverageCommand(mapId, opts) {
20812
21324
  (s) => s.status === "reached"
20813
21325
  );
20814
21326
  if (unassignedReached.length > 0) {
20815
- console.log(chalk27.dim(" (Unassigned):"));
21327
+ console.log(chalk29.dim(" (Unassigned):"));
20816
21328
  for (const s of unassignedReached) {
20817
21329
  console.log(
20818
- ` ${chalk27.green("\u2713")} ${s.key.padEnd(32)} ${s.label}${chalk27.dim(` (depth ${s.depth})`)}`
21330
+ ` ${chalk29.green("\u2713")} ${s.key.padEnd(32)} ${s.label}${chalk29.dim(` (depth ${s.depth})`)}`
20819
21331
  );
20820
21332
  }
20821
21333
  }
@@ -20827,24 +21339,24 @@ async function exploreCoverageCommand(mapId, opts) {
20827
21339
  ${color(title)}`);
20828
21340
  for (const r of rows) {
20829
21341
  console.log(
20830
- ` ${r.key.padEnd(32)} ${r.label}${chalk27.dim(` (depth ${r.depth})`)}`
21342
+ ` ${r.key.padEnd(32)} ${r.label}${chalk29.dim(` (depth ${r.depth})`)}`
20831
21343
  );
20832
21344
  }
20833
21345
  };
20834
21346
  section(
20835
21347
  "Never reached by a run",
20836
21348
  cov.surfaces.filter((s) => s.status === "unreached"),
20837
- chalk27.red
21349
+ chalk29.red
20838
21350
  );
20839
21351
  section(
20840
21352
  "URL reached, but no step names this view",
20841
21353
  cov.surfaces.filter((s) => s.status === "place_only"),
20842
- chalk27.yellow
21354
+ chalk29.yellow
20843
21355
  );
20844
21356
  }
20845
21357
  if (cov.run_only_places.length > 0) {
20846
21358
  console.log(`
20847
- ${chalk27.cyan("Walked by runs, not in the map")}`);
21359
+ ${chalk29.cyan("Walked by runs, not in the map")}`);
20848
21360
  for (const p of cov.run_only_places) {
20849
21361
  console.log(
20850
21362
  ` ${p.place.padEnd(32)} ${p.steps} steps, ${p.runs.length} runs`
@@ -20853,7 +21365,7 @@ async function exploreCoverageCommand(mapId, opts) {
20853
21365
  }
20854
21366
  if (cov.runs === 0) {
20855
21367
  console.log(
20856
- chalk27.dim(
21368
+ chalk29.dim(
20857
21369
  "\n No runs yet \u2014 every surface counts as never reached. Walk a scenario first (iamu-run)."
20858
21370
  )
20859
21371
  );
@@ -20889,27 +21401,27 @@ async function exploreDiffCommand(a, b, opts) {
20889
21401
  console.log(`
20890
21402
  ${idA} \u2192 ${idB}`);
20891
21403
  console.log(
20892
- ` ${chalk27.bold(`${pct}% of surfaces match`)} (${d.common} shared, ${d.only_a.length} only in the first, ${d.only_b.length} only in the second)`
21404
+ ` ${chalk29.bold(`${pct}% of surfaces match`)} (${d.common} shared, ${d.only_a.length} only in the first, ${d.only_b.length} only in the second)`
20893
21405
  );
20894
21406
  if (d.only_a.length > 0) {
20895
21407
  console.log(`
20896
- ${chalk27.red("Only in the first")}`);
21408
+ ${chalk29.red("Only in the first")}`);
20897
21409
  for (const s of d.only_a) console.log(` ${s.key.padEnd(32)} ${s.label}`);
20898
21410
  }
20899
21411
  if (d.only_b.length > 0) {
20900
21412
  console.log(`
20901
- ${chalk27.green("Only in the second")}`);
21413
+ ${chalk29.green("Only in the second")}`);
20902
21414
  for (const s of d.only_b) console.log(` ${s.key.padEnd(32)} ${s.label}`);
20903
21415
  }
20904
21416
  if (d.changed_actions.length > 0) {
20905
21417
  console.log(
20906
21418
  `
20907
- ${chalk27.yellow(`Controls differ on ${d.changed_actions.length} shared surfaces`)}`
21419
+ ${chalk29.yellow(`Controls differ on ${d.changed_actions.length} shared surfaces`)}`
20908
21420
  );
20909
21421
  for (const c of d.changed_actions) {
20910
21422
  console.log(` ${c.key} ${c.label}`);
20911
- for (const x of c.only_a) console.log(chalk27.red(` - ${x}`));
20912
- for (const x of c.only_b) console.log(chalk27.green(` + ${x}`));
21423
+ for (const x of c.only_a) console.log(chalk29.red(` - ${x}`));
21424
+ for (const x of c.only_b) console.log(chalk29.green(` + ${x}`));
20913
21425
  }
20914
21426
  }
20915
21427
  }
@@ -21123,7 +21635,7 @@ __export(instructions_walk_exports, {
21123
21635
  });
21124
21636
  import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
21125
21637
  import { join as join9 } from "path";
21126
- import chalk28 from "chalk";
21638
+ import chalk30 from "chalk";
21127
21639
  function selfCommand() {
21128
21640
  const q = (p) => /[\s"'$]/.test(p) ? JSON.stringify(p) : p;
21129
21641
  return `${q(process.execPath)} ${q(process.argv[1] ?? "iamu")}`;
@@ -21253,7 +21765,7 @@ function composeWalkerPrompt(p) {
21253
21765
  "Squinting Guardrails:",
21254
21766
  "\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.",
21255
21767
  "\u2022 Squinting does NOT permit drifting to unrelated settings, account profiles, or off-target pages. Stay strictly within the scenario journey.",
21256
- "\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."
21768
+ "\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. End the walk as blocked (below)."
21257
21769
  ].filter(Boolean) : [],
21258
21770
  ...p.untestableBoundaries && p.untestableBoundaries.length > 0 ? [
21259
21771
  "",
@@ -21289,6 +21801,7 @@ function composeWalkerPrompt(p) {
21289
21801
  `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> --description "..." --recommendation "..." --json`,
21290
21802
  "`--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.",
21291
21803
  `Before you finish, record synthesis with: ${iamu} summary ${p.runId} --worked "..." --worked "..." --question "..."`,
21804
+ `If the walk cannot start as it was set up \u2014 you cannot become this persona (the switch or login fails, or you are someone else), the world the situation needs is not there and you cannot create it, or the starting page does not load \u2014 stop immediately and end the walk with: ${iamu} summary ${p.runId} --blocked "<what was missing, as you saw it>". Do NOT record preliminary findings if preconditions are not met. That is about the setup, not the product: when the product has no way to do what you need, that is a finding (--type missing), not a block. Do not record findings about the missing setup itself.`,
21292
21805
  "Execute everything via CLI commands in the shell. Never invoke host Edit or Write tools on files in the workspace.",
21293
21806
  "",
21294
21807
  "Do not read the product's source code, other iamu/ entity files, or earlier runs: you know only what the persona knows.",
@@ -21406,6 +21919,7 @@ async function instructionsWalk(opts) {
21406
21919
  if (!targetUrl && doc.frontmatter.target_url) {
21407
21920
  targetUrl = doc.frontmatter.target_url;
21408
21921
  }
21922
+ if (doc.frontmatter.access) projectAccess = doc.frontmatter.access;
21409
21923
  const fromFm = doc.frontmatter.untestable_boundaries ?? [];
21410
21924
  const fromBody = extractUntestableBoundaries(doc.body);
21411
21925
  untestableBoundaries.push(
@@ -21491,40 +22005,40 @@ async function instructionsWalk(opts) {
21491
22005
  return;
21492
22006
  }
21493
22007
  console.log();
21494
- console.log(chalk28.bold(" Walk instructions"));
21495
- console.log(` ${chalk28.dim("scenario:")} ${sfm.slug}`);
21496
- console.log(` ${chalk28.dim("persona:")} ${pfm.name} (${pfm.role})`);
21497
- console.log(` ${chalk28.dim("outline:")} ${ofm.title}`);
22008
+ console.log(chalk30.bold(" Walk instructions"));
22009
+ console.log(` ${chalk30.dim("scenario:")} ${sfm.slug}`);
22010
+ console.log(` ${chalk30.dim("persona:")} ${pfm.name} (${pfm.role})`);
22011
+ console.log(` ${chalk30.dim("outline:")} ${ofm.title}`);
21498
22012
  if (sfm.state?.description || sfm.state?.approximation) {
21499
22013
  console.log(
21500
- ` ${chalk28.dim("state:")} ${sfm.state.description ? sfm.state.description : sfm.state.approximation ?? ""}`
22014
+ ` ${chalk30.dim("state:")} ${sfm.state.description ? sfm.state.description : sfm.state.approximation ?? ""}`
21501
22015
  );
21502
22016
  }
21503
- if (targetUrl) console.log(` ${chalk28.dim("target:")} ${targetUrl}`);
22017
+ if (targetUrl) console.log(` ${chalk30.dim("target:")} ${targetUrl}`);
21504
22018
  console.log(
21505
- ` ${chalk28.dim("access:")} ${projectAccess || personaAccess ? `${projectAccess ? "project" : ""}${projectAccess && personaAccess ? " + " : ""}${personaAccess ? "persona" : ""}` : "not recorded (walker would run as whoever is signed in)"}`
22019
+ ` ${chalk30.dim("access:")} ${projectAccess || personaAccess ? `${projectAccess ? "project" : ""}${projectAccess && personaAccess ? " + " : ""}${personaAccess ? "persona" : ""}` : "not recorded (walker would run as whoever is signed in)"}`
21506
22020
  );
21507
22021
  console.log(
21508
- ` ${chalk28.dim("dismissed:")} ${dismissedFacts.length} findings to skip`
22022
+ ` ${chalk30.dim("dismissed:")} ${dismissedFacts.length} findings to skip`
21509
22023
  );
21510
22024
  console.log(
21511
- ` ${chalk28.dim("guidance:")} ${guidance.length} tester guidance rule${guidance.length === 1 ? "" : "s"}`
22025
+ ` ${chalk30.dim("guidance:")} ${guidance.length} tester guidance rule${guidance.length === 1 ? "" : "s"}`
21512
22026
  );
21513
22027
  if (untestableBoundaries.length > 0) {
21514
22028
  console.log(
21515
- ` ${chalk28.dim("boundaries:")} ${untestableBoundaries.length} untestable boundaries to avoid`
22029
+ ` ${chalk30.dim("boundaries:")} ${untestableBoundaries.length} untestable boundaries to avoid`
21516
22030
  );
21517
22031
  }
21518
22032
  if (revisit.length > 0) {
21519
22033
  console.log(
21520
- ` ${chalk28.dim("re-check:")} ${revisit.length} place${revisit.length === 1 ? "" : "s"} where fixes await a re-run`
22034
+ ` ${chalk30.dim("re-check:")} ${revisit.length} place${revisit.length === 1 ? "" : "s"} where fixes await a re-run`
21521
22035
  );
21522
22036
  }
21523
22037
  console.log(
21524
- ` ${chalk28.dim("methodology:")} ${methodology ? "loaded" : "missing (run iamu update)"}`
22038
+ ` ${chalk30.dim("methodology:")} ${methodology ? "loaded" : "missing (run iamu update)"}`
21525
22039
  );
21526
22040
  console.log();
21527
- console.log(chalk28.dim(" Use --json to get the full instruction bundle."));
22041
+ console.log(chalk30.dim(" Use --json to get the full instruction bundle."));
21528
22042
  console.log();
21529
22043
  }
21530
22044
  var SCREEN;
@@ -21550,8 +22064,8 @@ __export(new_finding_exports, {
21550
22064
  });
21551
22065
  import { randomUUID as randomUUID10 } from "crypto";
21552
22066
  import { access, readFile as readFile7 } from "fs/promises";
21553
- import path27 from "path";
21554
- import chalk29 from "chalk";
22067
+ import path29 from "path";
22068
+ import chalk31 from "chalk";
21555
22069
  async function newFinding(opts) {
21556
22070
  const ctx = await resolveProject(opts);
21557
22071
  if (!ctx) return;
@@ -21617,7 +22131,7 @@ async function newFinding(opts) {
21617
22131
  }
21618
22132
  try {
21619
22133
  await access(
21620
- path27.join(runDirPath, "steps", `${String(n).padStart(3, "0")}.json`)
22134
+ path29.join(runDirPath, "steps", `${String(n).padStart(3, "0")}.json`)
21621
22135
  );
21622
22136
  } catch {
21623
22137
  commandError(
@@ -21632,7 +22146,7 @@ async function newFinding(opts) {
21632
22146
  if (!scenarioSlug) {
21633
22147
  try {
21634
22148
  const manifestRaw = await readFile7(
21635
- path27.join(runDirPath, "walkthrough.json"),
22149
+ path29.join(runDirPath, "walkthrough.json"),
21636
22150
  "utf8"
21637
22151
  );
21638
22152
  const manifest = JSON.parse(manifestRaw);
@@ -21736,10 +22250,10 @@ async function newFinding(opts) {
21736
22250
  return;
21737
22251
  }
21738
22252
  console.log();
21739
- console.log(` ${symbols.success} Created finding: ${chalk29.cyan(slug)}`);
21740
- console.log(` ${chalk29.dim("path:")} ${record.path}`);
21741
- console.log(` ${chalk29.dim("id:")} ${id}`);
21742
- for (const w of warnings) console.log(` ${chalk29.yellow("!")} ${w}`);
22253
+ console.log(` ${symbols.success} Created finding: ${chalk31.cyan(slug)}`);
22254
+ console.log(` ${chalk31.dim("path:")} ${record.path}`);
22255
+ console.log(` ${chalk31.dim("id:")} ${id}`);
22256
+ for (const w of warnings) console.log(` ${chalk31.yellow("!")} ${w}`);
21743
22257
  console.log();
21744
22258
  } catch (err) {
21745
22259
  commandError(
@@ -21764,9 +22278,13 @@ var summary_exports = {};
21764
22278
  __export(summary_exports, {
21765
22279
  summaryCommand: () => summaryCommand
21766
22280
  });
21767
- import { promises as fs28 } from "fs";
21768
- import path28 from "path";
21769
- import chalk30 from "chalk";
22281
+ import { promises as fs30 } from "fs";
22282
+ import path30 from "path";
22283
+ import chalk32 from "chalk";
22284
+ function blockedFrom(value) {
22285
+ const reason = value && typeof value === "object" ? value.reason : value;
22286
+ return typeof reason === "string" && reason.trim() ? { reason: reason.trim() } : void 0;
22287
+ }
21770
22288
  async function summaryCommand(runIdArg, opts) {
21771
22289
  const ctx = await resolveProject(opts);
21772
22290
  if (!ctx) return;
@@ -21781,6 +22299,13 @@ async function summaryCommand(runIdArg, opts) {
21781
22299
  let whatWorked = [];
21782
22300
  let openQuestions = [];
21783
22301
  let model = opts.model?.trim() || void 0;
22302
+ const summaryPath = path30.join(runDirPath, "summary.json");
22303
+ let blocked;
22304
+ try {
22305
+ const prev = JSON.parse(await fs30.readFile(summaryPath, "utf8"));
22306
+ blocked = blockedFrom(prev.blocked);
22307
+ } catch {
22308
+ }
21784
22309
  if (opts.file) {
21785
22310
  try {
21786
22311
  let raw;
@@ -21791,7 +22316,7 @@ async function summaryCommand(runIdArg, opts) {
21791
22316
  }
21792
22317
  raw = Buffer.concat(chunks).toString("utf8");
21793
22318
  } else {
21794
- raw = await fs28.readFile(path28.resolve(projectRoot, opts.file), "utf8");
22319
+ raw = await fs30.readFile(path30.resolve(projectRoot, opts.file), "utf8");
21795
22320
  }
21796
22321
  const parsed = JSON.parse(raw);
21797
22322
  if (Array.isArray(parsed.what_worked)) {
@@ -21807,6 +22332,7 @@ async function summaryCommand(runIdArg, opts) {
21807
22332
  if (typeof parsed.model === "string" && parsed.model.trim()) {
21808
22333
  model = parsed.model.trim();
21809
22334
  }
22335
+ blocked = blockedFrom(parsed.blocked) ?? blocked;
21810
22336
  } catch (err) {
21811
22337
  commandError(
21812
22338
  `Failed to parse summary file: ${err instanceof Error ? err.message : String(err)}`,
@@ -21828,12 +22354,17 @@ async function summaryCommand(runIdArg, opts) {
21828
22354
  if (cliQuestions.length > 0) {
21829
22355
  openQuestions = [...openQuestions, ...cliQuestions];
21830
22356
  }
22357
+ if (opts.blocked === false) {
22358
+ blocked = void 0;
22359
+ } else {
22360
+ blocked = blockedFrom(opts.blocked) ?? blocked;
22361
+ }
21831
22362
  const data = {
21832
22363
  what_worked: whatWorked,
21833
22364
  open_questions: openQuestions,
21834
- ...model ? { model } : {}
22365
+ ...model ? { model } : {},
22366
+ ...blocked ? { blocked } : {}
21835
22367
  };
21836
- const summaryPath = path28.join(runDirPath, "summary.json");
21837
22368
  await atomicWriteFile(summaryPath, `${JSON.stringify(data, null, 2)}
21838
22369
  `);
21839
22370
  if (opts.json) {
@@ -21841,7 +22372,7 @@ async function summaryCommand(runIdArg, opts) {
21841
22372
  JSON.stringify(
21842
22373
  {
21843
22374
  run_id: runId,
21844
- path: path28.relative(projectRoot, summaryPath),
22375
+ path: path30.relative(projectRoot, summaryPath),
21845
22376
  summary: data
21846
22377
  },
21847
22378
  null,
@@ -21852,16 +22383,19 @@ async function summaryCommand(runIdArg, opts) {
21852
22383
  }
21853
22384
  console.log();
21854
22385
  console.log(
21855
- ` ${symbols.success} Summary recorded for run ${chalk30.cyan(runId)}`
22386
+ ` ${symbols.success} Summary recorded for run ${chalk32.cyan(runId)}`
21856
22387
  );
22388
+ if (data.blocked) {
22389
+ console.log(` ${chalk32.yellow("blocked:")} ${data.blocked.reason}`);
22390
+ }
21857
22391
  if (data.what_worked.length > 0) {
21858
22392
  console.log(
21859
- ` ${chalk30.dim("what worked:")} ${data.what_worked.length} item(s)`
22393
+ ` ${chalk32.dim("what worked:")} ${data.what_worked.length} item(s)`
21860
22394
  );
21861
22395
  }
21862
22396
  if (data.open_questions.length > 0) {
21863
22397
  console.log(
21864
- ` ${chalk30.dim("open questions:")} ${data.open_questions.length} item(s)`
22398
+ ` ${chalk32.dim("open questions:")} ${data.open_questions.length} item(s)`
21865
22399
  );
21866
22400
  }
21867
22401
  console.log();
@@ -21882,7 +22416,7 @@ __export(view_results_exports, {
21882
22416
  promptViewResults: () => promptViewResults
21883
22417
  });
21884
22418
  import { isCancel as isCancel4, select as select4 } from "@clack/prompts";
21885
- import chalk31 from "chalk";
22419
+ import chalk33 from "chalk";
21886
22420
  function launchCloudDashboard(config, runId) {
21887
22421
  const cloudRunId = runId ? toCloudRunUuid(runId) : void 0;
21888
22422
  const url = getCloudDashboardUrl(
@@ -21891,7 +22425,7 @@ function launchCloudDashboard(config, runId) {
21891
22425
  cloudRunId
21892
22426
  );
21893
22427
  console.log();
21894
- console.log(` ${symbols.success} Opening Iamu Cloud: ${chalk31.cyan(url)}
22428
+ console.log(` ${symbols.success} Opening Iamu Cloud: ${chalk33.cyan(url)}
21895
22429
  `);
21896
22430
  openBrowser(url);
21897
22431
  }
@@ -21959,9 +22493,9 @@ __export(evaluate_exports, {
21959
22493
  evaluate: () => evaluate,
21960
22494
  evaluateRecord: () => evaluateRecord
21961
22495
  });
21962
- import { promises as fs29 } from "fs";
21963
- import path29 from "path";
21964
- import chalk32 from "chalk";
22496
+ import { promises as fs31 } from "fs";
22497
+ import path31 from "path";
22498
+ import chalk34 from "chalk";
21965
22499
  import { z as z26 } from "zod";
21966
22500
  function formatIssues(error) {
21967
22501
  return error.issues.map(
@@ -21991,10 +22525,10 @@ async function evaluate(runId, opts) {
21991
22525
  const resolved = await resolveProjectAndRunDir(runId, opts);
21992
22526
  if (!resolved) return;
21993
22527
  const { projectRoot, runDirPath } = resolved;
21994
- const canonicalPath = path29.join(runDirPath, "canonical.json");
22528
+ const canonicalPath = path31.join(runDirPath, "canonical.json");
21995
22529
  let canonical;
21996
22530
  try {
21997
- const raw = await fs29.readFile(canonicalPath, "utf8");
22531
+ const raw = await fs31.readFile(canonicalPath, "utf8");
21998
22532
  canonical = JSON.parse(raw);
21999
22533
  } catch {
22000
22534
  commandError(
@@ -22050,15 +22584,15 @@ async function evaluate(runId, opts) {
22050
22584
  )
22051
22585
  };
22052
22586
  const prompt = composeJudgePrompt(input);
22053
- const promptFilePath = opts?.promptFile ?? path29.join(runDirPath, "judge_prompt.md");
22054
- await fs29.writeFile(promptFilePath, prompt, "utf8");
22587
+ const promptFilePath = opts?.promptFile ?? path31.join(runDirPath, "judge_prompt.md");
22588
+ await fs31.writeFile(promptFilePath, prompt, "utf8");
22055
22589
  if (opts?.json) {
22056
22590
  console.log(
22057
22591
  JSON.stringify(
22058
22592
  {
22059
22593
  ...input,
22060
- prompt_file: path29.relative(projectRoot, promptFilePath),
22061
- prompt_file_abs: path29.resolve(promptFilePath),
22594
+ prompt_file: path31.relative(projectRoot, promptFilePath),
22595
+ prompt_file_abs: path31.resolve(promptFilePath),
22062
22596
  judge_prompt: prompt
22063
22597
  },
22064
22598
  null,
@@ -22075,10 +22609,10 @@ async function evaluateRecord(runId, opts) {
22075
22609
  const { projectRoot, runDirPath } = resolved;
22076
22610
  sendLivePhase(projectRoot, runId, "judging").catch(() => {
22077
22611
  });
22078
- const canonicalPath = path29.join(runDirPath, "canonical.json");
22612
+ const canonicalPath = path31.join(runDirPath, "canonical.json");
22079
22613
  let canonical;
22080
22614
  try {
22081
- const raw = await fs29.readFile(canonicalPath, "utf8");
22615
+ const raw = await fs31.readFile(canonicalPath, "utf8");
22082
22616
  canonical = JSON.parse(raw);
22083
22617
  } catch {
22084
22618
  commandError(`No canonical.json for run: ${runId}`, opts);
@@ -22100,7 +22634,7 @@ async function evaluateRecord(runId, opts) {
22100
22634
  }
22101
22635
  raw = Buffer.concat(chunks).toString("utf8");
22102
22636
  } else {
22103
- raw = await fs29.readFile(opts.file, "utf8");
22637
+ raw = await fs31.readFile(opts.file, "utf8");
22104
22638
  }
22105
22639
  parsedFile = JSON.parse(raw);
22106
22640
  } catch (err) {
@@ -22161,8 +22695,8 @@ ${formatIssues(fileResult.error)}`,
22161
22695
  overall: inputVerdicts.overall
22162
22696
  });
22163
22697
  const reportContent = renderCanonicalMarkdown(canonicalForReport);
22164
- const verdictsPath = path29.join(runDirPath, "verdicts.json");
22165
- const reportPath = path29.join(runDirPath, "report.md");
22698
+ const verdictsPath = path31.join(runDirPath, "verdicts.json");
22699
+ const reportPath = path31.join(runDirPath, "report.md");
22166
22700
  await Promise.all([
22167
22701
  atomicWriteFile(
22168
22702
  verdictsPath,
@@ -22182,8 +22716,8 @@ ${formatIssues(fileResult.error)}`,
22182
22716
  JSON.stringify(
22183
22717
  {
22184
22718
  runId,
22185
- verdictsPath: path29.relative(projectRoot, verdictsPath),
22186
- reportPath: path29.relative(projectRoot, reportPath),
22719
+ verdictsPath: path31.relative(projectRoot, verdictsPath),
22720
+ reportPath: path31.relative(projectRoot, reportPath),
22187
22721
  verdictsCount: validatedVerdicts.length,
22188
22722
  linkedCount: linked.length,
22189
22723
  ...cloudInfo ? { cloud: cloudInfo } : {}
@@ -22196,18 +22730,18 @@ ${formatIssues(fileResult.error)}`,
22196
22730
  }
22197
22731
  console.log();
22198
22732
  console.log(
22199
- ` ${symbols.success} Recorded ${validatedVerdicts.length} verdicts for run ${chalk32.cyan(runId)}`
22733
+ ` ${symbols.success} Recorded ${validatedVerdicts.length} verdicts for run ${chalk34.cyan(runId)}`
22200
22734
  );
22201
22735
  if (linked.length > 0) {
22202
22736
  console.log(
22203
- ` ${chalk32.dim("linked:")} ${linked.length} finding${linked.length === 1 ? "" : "s"} to the human test${linked.length === 1 ? "" : "s"} citing ${linked.length === 1 ? "it" : "them"}`
22737
+ ` ${chalk34.dim("linked:")} ${linked.length} finding${linked.length === 1 ? "" : "s"} to the human test${linked.length === 1 ? "" : "s"} citing ${linked.length === 1 ? "it" : "them"}`
22204
22738
  );
22205
22739
  }
22206
22740
  console.log(
22207
- ` ${chalk32.dim("verdicts:")} ${path29.relative(projectRoot, verdictsPath)}`
22741
+ ` ${chalk34.dim("verdicts:")} ${path31.relative(projectRoot, verdictsPath)}`
22208
22742
  );
22209
22743
  console.log(
22210
- ` ${chalk32.dim("report:")} ${path29.relative(projectRoot, reportPath)}`
22744
+ ` ${chalk34.dim("report:")} ${path31.relative(projectRoot, reportPath)}`
22211
22745
  );
22212
22746
  printCloudRunInfo(cloudInfo);
22213
22747
  await promptViewResults(projectRoot, { ...opts, runId });
@@ -22272,9 +22806,9 @@ __export(complete_exports, {
22272
22806
  screenshotSizeWarning: () => screenshotSizeWarning,
22273
22807
  validateCanonicalWalkthrough: () => validateCanonicalWalkthrough
22274
22808
  });
22275
- import { promises as fs30 } from "fs";
22276
- import path30 from "path";
22277
- import chalk33 from "chalk";
22809
+ import { promises as fs32 } from "fs";
22810
+ import path32 from "path";
22811
+ import chalk35 from "chalk";
22278
22812
  async function complete(runId, opts) {
22279
22813
  const run = await loadRun(runId, opts);
22280
22814
  if (!run) return;
@@ -22286,7 +22820,7 @@ async function complete(runId, opts) {
22286
22820
  }
22287
22821
  if (!opts?.json) {
22288
22822
  console.log();
22289
- console.log(chalk33.bold(" Assembling canonical JSON\u2026"));
22823
+ console.log(chalk35.bold(" Assembling canonical JSON\u2026"));
22290
22824
  }
22291
22825
  const provenanceAudit = auditStepProvenance(
22292
22826
  await readStepsForRun(run.projectRoot, runId)
@@ -22299,8 +22833,8 @@ async function complete(runId, opts) {
22299
22833
  const walkerSummary = await readWalkerSummary(run.runDirPath);
22300
22834
  let judgeReported;
22301
22835
  try {
22302
- const verdictsRaw = await fs30.readFile(
22303
- path30.join(run.runDirPath, "verdicts.json"),
22836
+ const verdictsRaw = await fs32.readFile(
22837
+ path32.join(run.runDirPath, "verdicts.json"),
22304
22838
  "utf8"
22305
22839
  );
22306
22840
  const parsed = JSON.parse(verdictsRaw);
@@ -22322,9 +22856,11 @@ async function complete(runId, opts) {
22322
22856
  ...durationSec != null ? { total_duration_sec: durationSec } : {},
22323
22857
  ...velocitySec != null ? { velocity_sec_per_step: velocitySec } : {}
22324
22858
  };
22859
+ const { blocked: _previousBlock, ...manifestRest } = run.manifest;
22325
22860
  run.manifest = {
22326
- ...run.manifest,
22327
- models: updatedModels
22861
+ ...manifestRest,
22862
+ models: updatedModels,
22863
+ ...walkerSummary.blocked ? { blocked: walkerSummary.blocked } : {}
22328
22864
  };
22329
22865
  const assembled = await assembleRun(
22330
22866
  run.projectRoot,
@@ -22352,16 +22888,16 @@ async function loadRun(runId, opts) {
22352
22888
  if (!resolved) return null;
22353
22889
  const { projectRoot, runDirPath } = resolved;
22354
22890
  try {
22355
- await fs30.access(runDirPath);
22891
+ await fs32.access(runDirPath);
22356
22892
  } catch {
22357
22893
  commandError(`Run not found: ${runId}`, opts);
22358
22894
  return null;
22359
22895
  }
22360
- const manifestPath = path30.join(runDirPath, "walkthrough.json");
22896
+ const manifestPath = path32.join(runDirPath, "walkthrough.json");
22361
22897
  let manifest;
22362
22898
  try {
22363
22899
  manifest = JSON.parse(
22364
- await fs30.readFile(manifestPath, "utf8")
22900
+ await fs32.readFile(manifestPath, "utf8")
22365
22901
  );
22366
22902
  } catch {
22367
22903
  commandError(`No walkthrough.json found for run: ${runId}`, opts);
@@ -22373,8 +22909,8 @@ async function reportAlreadyCompleted(run, opts) {
22373
22909
  const { runId, manifest } = run;
22374
22910
  if (opts?.json) {
22375
22911
  try {
22376
- const raw = await fs30.readFile(
22377
- path30.join(run.runDirPath, "canonical.json"),
22912
+ const raw = await fs32.readFile(
22913
+ path32.join(run.runDirPath, "canonical.json"),
22378
22914
  "utf8"
22379
22915
  );
22380
22916
  console.log(raw.trim());
@@ -22394,18 +22930,18 @@ async function reportAlreadyCompleted(run, opts) {
22394
22930
  return;
22395
22931
  }
22396
22932
  console.log();
22397
- console.log(chalk33.bold(" Run: ") + chalk33.cyan(runId));
22398
- console.log(` status ${chalk33.green("completed")}`);
22933
+ console.log(chalk35.bold(" Run: ") + chalk35.cyan(runId));
22934
+ console.log(` status ${chalk35.green("completed")}`);
22399
22935
  if (manifest.completed_at) {
22400
- console.log(` completed ${chalk33.dim(manifest.completed_at)}`);
22936
+ console.log(` completed ${chalk35.dim(manifest.completed_at)}`);
22401
22937
  }
22402
22938
  const viewUrl = await pushedRunUrl(run.projectRoot, runId);
22403
22939
  if (viewUrl) {
22404
- console.log(` view ${chalk33.underline(viewUrl)}`);
22940
+ console.log(` view ${chalk35.underline(viewUrl)}`);
22405
22941
  }
22406
22942
  console.log();
22407
22943
  console.log(
22408
- chalk33.dim(
22944
+ chalk35.dim(
22409
22945
  " Already completed \u2014 nothing to do. Use --force to reassemble the report."
22410
22946
  )
22411
22947
  );
@@ -22421,13 +22957,13 @@ function reportStrictProvenanceFailure(audit, opts) {
22421
22957
  }
22422
22958
  console.error();
22423
22959
  console.error(
22424
- chalk33.red(` ${symbols.error} Strict provenance audit failed:`)
22960
+ chalk35.red(` ${symbols.error} Strict provenance audit failed:`)
22425
22961
  );
22426
22962
  for (const w of audit.warnings) {
22427
- console.error(chalk33.red(` \u2022 ${w}`));
22963
+ console.error(chalk35.red(` \u2022 ${w}`));
22428
22964
  }
22429
22965
  console.error(
22430
- chalk33.dim(
22966
+ chalk35.dim(
22431
22967
  " Run was flagged as synthetic / scripted. Remove --strict-provenance to complete anyway."
22432
22968
  )
22433
22969
  );
@@ -22468,41 +23004,48 @@ function completeJsonPayload(canonical, extras) {
22468
23004
  function printCompletionSummary(run, completedAt, assembled, extras, provenanceAudit) {
22469
23005
  const { runId, projectRoot, manifest } = run;
22470
23006
  const { canonical } = assembled;
22471
- const count = (n) => n === 0 ? chalk33.dim("0") : chalk33.cyan(String(n));
22472
- console.log(chalk33.bold(" Run: ") + chalk33.cyan(runId));
22473
- console.log(` scenario ${chalk33.dim(manifest.scenario_slug)}`);
22474
- console.log(` url ${chalk33.dim(manifest.target_url)}`);
23007
+ const count = (n) => n === 0 ? chalk35.dim("0") : chalk35.cyan(String(n));
23008
+ console.log(chalk35.bold(" Run: ") + chalk35.cyan(runId));
23009
+ console.log(` scenario ${chalk35.dim(manifest.scenario_slug)}`);
23010
+ console.log(` url ${chalk35.dim(manifest.target_url)}`);
22475
23011
  if (manifest.environment) {
22476
- console.log(` env ${chalk33.dim(manifest.environment)}`);
23012
+ console.log(` env ${chalk35.dim(manifest.environment)}`);
22477
23013
  }
22478
23014
  if (manifest.build) {
22479
- console.log(` build ${chalk33.dim(formatBuild(manifest.build))}`);
23015
+ console.log(` build ${chalk35.dim(formatBuild(manifest.build))}`);
22480
23016
  }
22481
23017
  if (manifest.viewport) {
22482
- console.log(` device ${chalk33.dim(formatViewport(manifest.viewport))}`);
23018
+ console.log(` device ${chalk35.dim(formatViewport(manifest.viewport))}`);
23019
+ }
23020
+ if (manifest.blocked) {
23021
+ console.log(
23022
+ ` status ${chalk35.yellow("blocked")} ${chalk35.dim("\u2014 the walk could not start as set up")}`
23023
+ );
23024
+ console.log(` reason ${manifest.blocked.reason}`);
23025
+ } else {
23026
+ console.log(` status ${chalk35.green("completed")}`);
22483
23027
  }
22484
- console.log(` status ${chalk33.green("completed")}`);
22485
23028
  console.log(` findings ${count(canonical.findings.length)}`);
22486
23029
  console.log(` steps ${count(canonical.steps.length)}`);
22487
- console.log(` started ${chalk33.dim(manifest.created_at)}`);
22488
- console.log(` completed ${chalk33.dim(completedAt)}`);
23030
+ console.log(` started ${chalk35.dim(manifest.created_at)}`);
23031
+ console.log(` completed ${chalk35.dim(completedAt)}`);
22489
23032
  const models = manifest.models;
22490
23033
  if (models?.velocity_sec_per_step != null) {
22491
23034
  console.log(
22492
- ` velocity ${chalk33.cyan(`~${models.velocity_sec_per_step}s/step`)} ${chalk33.dim(`(${models.total_duration_sec}s total)`)}`
23035
+ ` velocity ${chalk35.cyan(`~${models.velocity_sec_per_step}s/step`)} ${chalk35.dim(`(${models.total_duration_sec}s total)`)}`
22493
23036
  );
22494
23037
  }
22495
23038
  if (models && (models.walker || models.judge || models.matcher)) {
22496
23039
  const w = models.walker_reported ? `${models.walker} (${models.walker_reported})` : models.walker ?? "fast";
22497
23040
  const j = models.judge_reported ? `${models.judge} (${models.judge_reported})` : models.judge ?? "thorough";
22498
- console.log(` models ${chalk33.dim(`walker: ${w} \xB7 judge: ${j}`)}`);
23041
+ console.log(` models ${chalk35.dim(`walker: ${w} \xB7 judge: ${j}`)}`);
22499
23042
  }
22500
23043
  console.log();
22501
23044
  console.log(
22502
- ` ${chalk33.dim("canonical:")} ${chalk33.dim(path30.relative(projectRoot, assembled.canonicalPath))}`
23045
+ ` ${chalk35.dim("canonical:")} ${chalk35.dim(path32.relative(projectRoot, assembled.canonicalPath))}`
22503
23046
  );
22504
23047
  console.log(
22505
- ` ${chalk33.dim("report:")} ${chalk33.dim(path30.relative(projectRoot, assembled.reportPath))}`
23048
+ ` ${chalk35.dim("report:")} ${chalk35.dim(path32.relative(projectRoot, assembled.reportPath))}`
22506
23049
  );
22507
23050
  if (extras.viewportWarning) {
22508
23051
  console.log();
@@ -22510,25 +23053,36 @@ function printCompletionSummary(run, completedAt, assembled, extras, provenanceA
22510
23053
  }
22511
23054
  if (extras.codeWarning) {
22512
23055
  console.log();
22513
- console.log(` ${chalk33.yellow("!")} ${extras.codeWarning}`);
23056
+ console.log(` ${chalk35.yellow("!")} ${extras.codeWarning}`);
22514
23057
  }
22515
23058
  if (extras.revisit) printRevisit(extras.revisit);
22516
23059
  if (assembled.verification) printVerification(assembled.verification);
22517
23060
  if (assembled.journeyDiff) printJourneyDiff(assembled.journeyDiff);
22518
23061
  printProvenanceWarnings(provenanceAudit);
22519
23062
  printCloudRunInfo(extras.cloudInfo);
23063
+ if (manifest.blocked) {
23064
+ console.log();
23065
+ console.log(
23066
+ ` ${symbols.warning} ${chalk35.yellow("Not compared with earlier runs.")} Fix the setup (iamu set scenario ${manifest.scenario_slug} \u2026, or the persona's access) and run the scenario again.`
23067
+ );
23068
+ if (canonical.findings.length > 0) {
23069
+ console.log(
23070
+ ` ${symbols.warning} ${chalk35.yellow(`Walk recorded ${canonical.findings.length} finding(s) before being blocked.`)} Because setup was incomplete, these findings will not be verified against future runs.`
23071
+ );
23072
+ }
23073
+ }
22520
23074
  }
22521
23075
  function printProvenanceWarnings(audit) {
22522
23076
  if (audit.warnings.length === 0) return;
22523
23077
  console.log();
22524
23078
  console.log(
22525
- ` ${symbols.warning} ${chalk33.yellow("Step provenance audit flagged suspicious timing:")}`
23079
+ ` ${symbols.warning} ${chalk35.yellow("Step provenance audit flagged suspicious timing:")}`
22526
23080
  );
22527
23081
  for (const w of audit.warnings) {
22528
- console.log(` ${chalk33.dim("\u2022")} ${w}`);
23082
+ console.log(` ${chalk35.dim("\u2022")} ${w}`);
22529
23083
  }
22530
23084
  console.log(
22531
- chalk33.dim(
23085
+ chalk35.dim(
22532
23086
  " Run appears to be programmatically batch-generated rather than an interactive walkthrough."
22533
23087
  )
22534
23088
  );
@@ -22560,17 +23114,17 @@ function screenshotFitsViewport(width, expected) {
22560
23114
  }
22561
23115
  async function screenshotSizeWarning(runDirPath, viewport) {
22562
23116
  if (!viewport) return null;
22563
- const dir = path30.join(runDirPath, "screenshots");
23117
+ const dir = path32.join(runDirPath, "screenshots");
22564
23118
  let first;
22565
23119
  try {
22566
- first = (await fs30.readdir(dir)).filter((f) => f.toLowerCase().endsWith(".png")).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }))[0];
23120
+ first = (await fs32.readdir(dir)).filter((f) => f.toLowerCase().endsWith(".png")).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }))[0];
22567
23121
  } catch {
22568
23122
  return null;
22569
23123
  }
22570
23124
  if (!first) return null;
22571
23125
  let size = null;
22572
23126
  try {
22573
- const handle = await fs30.open(path30.join(dir, first), "r");
23127
+ const handle = await fs32.open(path32.join(dir, first), "r");
22574
23128
  try {
22575
23129
  const head = Buffer.alloc(24);
22576
23130
  const { bytesRead } = await handle.read(head, 0, 24, 0);
@@ -22587,14 +23141,16 @@ async function screenshotSizeWarning(runDirPath, viewport) {
22587
23141
  async function readWalkerSummary(runDirPath) {
22588
23142
  try {
22589
23143
  const data = JSON.parse(
22590
- await fs30.readFile(path30.join(runDirPath, "summary.json"), "utf8")
23144
+ await fs32.readFile(path32.join(runDirPath, "summary.json"), "utf8")
22591
23145
  );
22592
23146
  const strings = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && x.trim() !== "") : void 0;
22593
23147
  const model = typeof data.model === "string" && data.model.trim() !== "" ? data.model.trim() : void 0;
23148
+ const reason = data.blocked?.reason;
22594
23149
  return {
22595
23150
  what_worked: strings(data.what_worked),
22596
23151
  open_questions: strings(data.open_questions),
22597
- model
23152
+ model,
23153
+ ...typeof reason === "string" && reason.trim() !== "" ? { blocked: { reason: reason.trim() } } : {}
22598
23154
  };
22599
23155
  } catch {
22600
23156
  return {};
@@ -22696,7 +23252,7 @@ function validateCanonicalWalkthrough(canonical) {
22696
23252
  async function applyRecordedVerdicts(canonical, runDirPath) {
22697
23253
  try {
22698
23254
  const recorded = JSON.parse(
22699
- await fs30.readFile(path30.join(runDirPath, "verdicts.json"), "utf8")
23255
+ await fs32.readFile(path32.join(runDirPath, "verdicts.json"), "utf8")
22700
23256
  );
22701
23257
  if (Array.isArray(recorded.verdicts)) {
22702
23258
  return applyVerdicts(canonical, recorded);
@@ -22706,7 +23262,7 @@ async function applyRecordedVerdicts(canonical, runDirPath) {
22706
23262
  return canonical;
22707
23263
  }
22708
23264
  async function persistCanonical(runDirPath, canonical) {
22709
- const canonicalPath = path30.join(runDirPath, "canonical.json");
23265
+ const canonicalPath = path32.join(runDirPath, "canonical.json");
22710
23266
  await atomicWriteFile(
22711
23267
  canonicalPath,
22712
23268
  `${JSON.stringify(canonical, null, 2)}
@@ -22715,7 +23271,7 @@ async function persistCanonical(runDirPath, canonical) {
22715
23271
  return canonicalPath;
22716
23272
  }
22717
23273
  async function persistReport(runDirPath, canonical) {
22718
- const reportPath = path30.join(runDirPath, "report.md");
23274
+ const reportPath = path32.join(runDirPath, "report.md");
22719
23275
  await atomicWriteFile(reportPath, renderCanonicalMarkdown(canonical));
22720
23276
  return reportPath;
22721
23277
  }
@@ -22727,7 +23283,7 @@ async function assembleRun(projectRoot, runId, manifest, completedAt) {
22727
23283
  completedAt,
22728
23284
  await readWalkerSummary(runDirPath)
22729
23285
  );
22730
- const verification = await compareWithBaseline(
23286
+ const verification = manifest.blocked ? null : await compareWithBaseline(
22731
23287
  projectRoot,
22732
23288
  manifest.scenario_slug,
22733
23289
  runId,
@@ -22815,17 +23371,17 @@ function placeLabel(p) {
22815
23371
  function printRevisit(r) {
22816
23372
  const missed = r.places.filter((p) => !p.reached);
22817
23373
  console.log();
22818
- const head = ` ${chalk33.bold("Re-check places:")} reached ${r.reached} of ${r.requested}`;
23374
+ const head = ` ${chalk35.bold("Re-check places:")} reached ${r.reached} of ${r.requested}`;
22819
23375
  if (missed.length === 0) {
22820
- console.log(`${head} ${chalk33.green("\u2713")}`);
23376
+ console.log(`${head} ${chalk35.green("\u2713")}`);
22821
23377
  return;
22822
23378
  }
22823
23379
  console.log(`${head} \u2014 not reached:`);
22824
23380
  for (const p of missed) {
22825
- console.log(` ${chalk33.yellow("\u25CB")} ${placeLabel(p)}`);
23381
+ console.log(` ${chalk35.yellow("\u25CB")} ${placeLabel(p)}`);
22826
23382
  }
22827
23383
  console.log(
22828
- chalk33.dim(
23384
+ chalk35.dim(
22829
23385
  " Fixes there stay awaiting a re-run until a walk gets there."
22830
23386
  )
22831
23387
  );
@@ -22833,7 +23389,7 @@ function printRevisit(r) {
22833
23389
  function printVerification(verification) {
22834
23390
  console.log();
22835
23391
  console.log(
22836
- chalk33.bold(" Since this scenario's last run") + chalk33.dim(` (${verification.baseline_run_id.slice(0, 30)}\u2026)`) + chalk33.bold(":")
23392
+ chalk35.bold(" Since this scenario's last run") + chalk35.dim(` (${verification.baseline_run_id.slice(0, 30)}\u2026)`) + chalk35.bold(":")
22837
23393
  );
22838
23394
  if (verification.resolved.length > 0) {
22839
23395
  console.log(
@@ -22842,7 +23398,7 @@ function printVerification(verification) {
22842
23398
  }
22843
23399
  if (verification.not_observed.length > 0) {
22844
23400
  console.log(
22845
- ` ${symbols.pending} ${verification.not_observed.length} not seen this run ${chalk33.dim("(not evidence of a fix)")}`
23401
+ ` ${symbols.pending} ${verification.not_observed.length} not seen this run ${chalk35.dim("(not evidence of a fix)")}`
22846
23402
  );
22847
23403
  }
22848
23404
  if (verification.persists.length > 0) {
@@ -22884,14 +23440,14 @@ __export(match_exports, {
22884
23440
  match: () => match,
22885
23441
  matchRecord: () => matchRecord
22886
23442
  });
22887
- import { promises as fs31 } from "fs";
22888
- import path31 from "path";
22889
- import chalk34 from "chalk";
23443
+ import { promises as fs33 } from "fs";
23444
+ import path33 from "path";
23445
+ import chalk36 from "chalk";
22890
23446
  async function completedManifest2(runDirPath, runId, opts) {
22891
23447
  let manifest;
22892
23448
  try {
22893
23449
  manifest = JSON.parse(
22894
- await fs31.readFile(path31.join(runDirPath, "walkthrough.json"), "utf8")
23450
+ await fs33.readFile(path33.join(runDirPath, "walkthrough.json"), "utf8")
22895
23451
  );
22896
23452
  } catch {
22897
23453
  commandError(`Run not found: ${runId}`, opts);
@@ -22918,14 +23474,14 @@ async function match(runId, opts) {
22918
23474
  runId,
22919
23475
  manifest.scenario_slug
22920
23476
  );
22921
- const needed = input.current.length > 0 && (input.prior.length > 0 || (input.guidance ?? []).length > 0);
23477
+ const needed = !manifest.blocked && input.current.length > 0 && (input.prior.length > 0 || (input.guidance ?? []).length > 0);
22922
23478
  const prompt = needed ? composeMatchPrompt(input) : null;
22923
- const promptFilePath = opts?.promptFile ?? path31.join(runDirPath, "match_prompt.md");
23479
+ const promptFilePath = opts?.promptFile ?? path33.join(runDirPath, "match_prompt.md");
22924
23480
  if (prompt) {
22925
- await fs31.writeFile(promptFilePath, prompt, "utf8");
23481
+ await fs33.writeFile(promptFilePath, prompt, "utf8");
22926
23482
  } else {
22927
23483
  try {
22928
- await fs31.unlink(promptFilePath);
23484
+ await fs33.unlink(promptFilePath);
22929
23485
  } catch {
22930
23486
  }
22931
23487
  }
@@ -22935,8 +23491,9 @@ async function match(runId, opts) {
22935
23491
  {
22936
23492
  ...input,
22937
23493
  needed,
22938
- prompt_file: prompt ? path31.relative(ctx.projectRoot, promptFilePath) : null,
22939
- prompt_file_abs: prompt ? path31.resolve(promptFilePath) : null,
23494
+ ...manifest.blocked ? { blocked: manifest.blocked } : {},
23495
+ prompt_file: prompt ? path33.relative(ctx.projectRoot, promptFilePath) : null,
23496
+ prompt_file_abs: prompt ? path33.resolve(promptFilePath) : null,
22940
23497
  match_prompt: prompt
22941
23498
  },
22942
23499
  null,
@@ -22947,7 +23504,7 @@ async function match(runId, opts) {
22947
23504
  }
22948
23505
  if (!prompt) {
22949
23506
  console.log(
22950
- chalk34.dim(
23507
+ chalk36.dim(
22951
23508
  " No earlier findings or tester guidance for this scenario to match against \u2014 nothing to do."
22952
23509
  )
22953
23510
  );
@@ -22979,7 +23536,7 @@ async function matchRecord(runId, opts) {
22979
23536
  }
22980
23537
  raw = Buffer.concat(chunks).toString("utf8");
22981
23538
  } else {
22982
- raw = await fs31.readFile(opts.file, "utf8");
23539
+ raw = await fs33.readFile(opts.file, "utf8");
22983
23540
  }
22984
23541
  answer = JSON.parse(raw);
22985
23542
  } catch (err) {
@@ -23025,17 +23582,17 @@ async function matchRecord(runId, opts) {
23025
23582
  }
23026
23583
  console.log();
23027
23584
  console.log(
23028
- ` ${chalk34.green("\u2713")} Matched ${result.linked} finding${result.linked === 1 ? "" : "s"} to earlier ones, ${result.new} first seen, ${result.rechecked_fixed} earlier re-checked fixed`
23585
+ ` ${chalk36.green("\u2713")} Matched ${result.linked} finding${result.linked === 1 ? "" : "s"} to earlier ones, ${result.new} first seen, ${result.rechecked_fixed} earlier re-checked fixed`
23029
23586
  );
23030
23587
  if (result.suppressed > 0) {
23031
23588
  console.log(
23032
- ` ${chalk34.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)`
23589
+ ` ${chalk36.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)`
23033
23590
  );
23034
23591
  }
23035
23592
  if (verification) printVerification(verification);
23036
23593
  console.log();
23037
23594
  console.log(
23038
- chalk34.dim(" Run `iamu sync` to push the linked findings to the cloud.")
23595
+ chalk36.dim(" Run `iamu sync` to push the linked findings to the cloud.")
23039
23596
  );
23040
23597
  console.log();
23041
23598
  }
@@ -23057,7 +23614,7 @@ __export(prune_exports, {
23057
23614
  prune: () => prune
23058
23615
  });
23059
23616
  import { confirm, isCancel as isCancel5 } from "@clack/prompts";
23060
- import chalk35 from "chalk";
23617
+ import chalk37 from "chalk";
23061
23618
  function formatBytes(bytes) {
23062
23619
  if (bytes <= 0) return "0 B";
23063
23620
  if (bytes < 1024) return `${bytes} B`;
@@ -23103,7 +23660,7 @@ async function prune(opts = {}) {
23103
23660
  });
23104
23661
  if (preview.prunedRuns.length === 0) {
23105
23662
  console.log(
23106
- chalk35.dim(" No runs match the pruning criteria. Nothing to prune.")
23663
+ chalk37.dim(" No runs match the pruning criteria. Nothing to prune.")
23107
23664
  );
23108
23665
  return preview;
23109
23666
  }
@@ -23112,7 +23669,7 @@ async function prune(opts = {}) {
23112
23669
  message: `Prune ${mode} ${preview.prunedRuns.length} run(s) and reclaim ${formatBytes(preview.totalBytesReclaimed)}?`
23113
23670
  });
23114
23671
  if (isCancel5(confirmed) || !confirmed) {
23115
- console.log(chalk35.yellow(" Pruning cancelled."));
23672
+ console.log(chalk37.yellow(" Pruning cancelled."));
23116
23673
  return;
23117
23674
  }
23118
23675
  }
@@ -23123,14 +23680,14 @@ async function prune(opts = {}) {
23123
23680
  }
23124
23681
  if (result.dryRun) {
23125
23682
  console.log(
23126
- chalk35.cyan(
23683
+ chalk37.cyan(
23127
23684
  `
23128
23685
  [DRY RUN] Would prune ${result.prunedRuns.length} of ${result.totalRunsScanned} run(s):`
23129
23686
  )
23130
23687
  );
23131
23688
  } else {
23132
23689
  console.log(
23133
- chalk35.bold(
23690
+ chalk37.bold(
23134
23691
  `
23135
23692
  ${symbols.success} Pruned ${result.prunedRuns.length} of ${result.totalRunsScanned} run(s):`
23136
23693
  )
@@ -23138,18 +23695,18 @@ async function prune(opts = {}) {
23138
23695
  }
23139
23696
  if (result.prunedRuns.length === 0) {
23140
23697
  console.log(
23141
- chalk35.dim(" No runs matched the pruning criteria. Nothing pruned.")
23698
+ chalk37.dim(" No runs matched the pruning criteria. Nothing pruned.")
23142
23699
  );
23143
23700
  } else {
23144
23701
  for (const run of result.prunedRuns) {
23145
23702
  const mode = run.fullyDeleted ? "removed" : "screenshots purged";
23146
23703
  const count = run.screenshotsDeleted > 0 ? `${run.screenshotsDeleted} screenshots, ` : "";
23147
23704
  console.log(
23148
- ` ${chalk35.dim("\u2022")} ${chalk35.bold(run.runId)} ${chalk35.dim(`(${run.scenarioSlug ?? "unknown scenario"})`)}: ${chalk35.yellow(formatBytes(run.bytesReclaimed))} (${count}${mode})`
23705
+ ` ${chalk37.dim("\u2022")} ${chalk37.bold(run.runId)} ${chalk37.dim(`(${run.scenarioSlug ?? "unknown scenario"})`)}: ${chalk37.yellow(formatBytes(run.bytesReclaimed))} (${count}${mode})`
23149
23706
  );
23150
23707
  }
23151
23708
  console.log(
23152
- chalk35.dim(
23709
+ chalk37.dim(
23153
23710
  `
23154
23711
  Total reclaimed: ${formatBytes(result.totalBytesReclaimed)} (${result.totalScreenshotsDeleted} screenshots deleted)
23155
23712
  `
@@ -23338,9 +23895,9 @@ function registerProjectCommands(program) {
23338
23895
  "after",
23339
23896
  "\nExamples:\n $ iamu validate --all\n $ iamu validate --all --fix --dry-run\n $ iamu validate iamu/personas/maria.md"
23340
23897
  ).action(
23341
- async (path32, opts) => {
23898
+ async (path34, opts) => {
23342
23899
  const { validate: validate2 } = await Promise.resolve().then(() => (init_validate(), validate_exports));
23343
- await validate2(path32, opts);
23900
+ await validate2(path34, opts);
23344
23901
  }
23345
23902
  );
23346
23903
  program.command("update").helpGroup("Project:").description("Update and repair agent skills and prompts to latest version").option(
@@ -23392,6 +23949,51 @@ function registerProjectCommands(program) {
23392
23949
  await newEntity2(type2, opts);
23393
23950
  }
23394
23951
  );
23952
+ program.command("set <type> <slug>").helpGroup("Project:").description(
23953
+ "Change how an entity is staged: a scenario's start path, target and starting state, a persona's access key, a capability's routes (scenario, persona, capability)"
23954
+ ).option(
23955
+ "--entry <path>",
23956
+ "Where the walk starts, as a path on the product (scenario)"
23957
+ ).option("--no-entry", "Clear the start path (scenario)").option("--target <url>", "Default target URL (scenario)").option("--no-target", "Clear the default target URL (scenario)").option(
23958
+ "--state-description <text>",
23959
+ "What the world must contain, in domain words (scenario)"
23960
+ ).option(
23961
+ "--state-approximation <text>",
23962
+ "How the walker picks the closest record when the exact one is missing (scenario)"
23963
+ ).option("--no-state", "Clear the starting state (scenario)").option(
23964
+ "--access <key>",
23965
+ "Account, role or switcher key this persona uses (persona)"
23966
+ ).option("--no-access", "Clear the access key (persona)").option(
23967
+ "--paths <paths>",
23968
+ "Route prefixes, comma-separated; replaces the list (capability)"
23969
+ ).option("--no-paths", "Clear route prefixes (capability)").option(
23970
+ "--add-path <path>",
23971
+ "Add a route prefix (capability, repeatable)",
23972
+ (val, prev = []) => [...prev, val]
23973
+ ).option(
23974
+ "--remove-path <path>",
23975
+ "Remove a route prefix (capability, repeatable)",
23976
+ (val, prev = []) => [...prev, val]
23977
+ ).option("--area <name>", "Visual grouping category (capability)").option("--no-area", "Clear the area (capability)").option(
23978
+ "--file <path>",
23979
+ "JSON patch file to apply staging fields (or - for stdin)"
23980
+ ).option("--json", "Output as JSON").addHelpText(
23981
+ "after",
23982
+ '\nWhat a scenario is about (narrative, tension, human tests, brief) is not set here.\n\nExamples:\n $ iamu set scenario maria--handoff --entry /incidents --state-description "Five open incidents at one site"\n $ iamu set persona maria --access coordinator@site-a.test\n $ iamu set capability team-access --add-path /team\n $ iamu set scenario maria--handoff --file patch.json'
23983
+ ).action(
23984
+ async (type2, slug, opts) => {
23985
+ const { setEntity: setEntity2 } = await Promise.resolve().then(() => (init_set_entity(), set_entity_exports));
23986
+ await setEntity2(type2, slug, opts);
23987
+ }
23988
+ );
23989
+ program.command("edit <type> <slug>").helpGroup("Project:").description(
23990
+ "Open an entity file in your editor ($VISUAL or $EDITOR) and validate on exit"
23991
+ ).option("--json", "Output as JSON").action(
23992
+ async (type2, slug, opts = {}) => {
23993
+ const { editEntity: editEntity2 } = await Promise.resolve().then(() => (init_edit_entity(), edit_entity_exports));
23994
+ await editEntity2(type2, slug, opts);
23995
+ }
23996
+ );
23395
23997
  const explore = program.command("explore").helpGroup("Project:").description(
23396
23998
  "Map the running product with a read-only crawl; compare the map with runs (coverage) or with another map (diff)"
23397
23999
  );
@@ -23593,14 +24195,17 @@ function registerWalkthroughCommands(program) {
23593
24195
  }
23594
24196
  );
23595
24197
  program.command("summary [run-id]").helpGroup("Walkthrough:").description(
23596
- "Record walker summary (what worked, open questions) atomically"
24198
+ "Record walker summary (what worked, open questions, or that the walk was blocked) atomically"
23597
24199
  ).option("--run <run-id>", "Run ID (or pass as positional argument)").option(
23598
24200
  "--worked <note...>",
23599
24201
  "Things that worked well (can be specified multiple times)"
23600
24202
  ).option(
23601
24203
  "--question <note...>",
23602
24204
  "Open questions that arose (can be specified multiple times)"
23603
- ).option("--model <name>", "Model identifier (e.g. flash, haiku)").option("--file <path>", "Path to summary JSON file (or - for stdin)").option("--json", "Output as JSON").action(
24205
+ ).option("--model <name>", "Model identifier (e.g. flash, haiku)").option(
24206
+ "--blocked <reason>",
24207
+ "The walk could not start as set up (persona unreachable, world missing): what was missing"
24208
+ ).option("--no-blocked", "Clear the blocked status (if previously set)").option("--file <path>", "Path to summary JSON file (or - for stdin)").option("--json", "Output as JSON").action(
23604
24209
  async (runIdArg, opts) => {
23605
24210
  const { summaryCommand: summaryCommand2 } = await Promise.resolve().then(() => (init_summary(), summary_exports));
23606
24211
  await summaryCommand2(runIdArg, opts);
@@ -23674,7 +24279,7 @@ function registerAllCommands(program) {
23674
24279
 
23675
24280
  // src/ui/panic.ts
23676
24281
  init_logger();
23677
- import chalk36 from "chalk";
24282
+ import chalk38 from "chalk";
23678
24283
  var handlersRegistered = false;
23679
24284
  var customSigintHandler = null;
23680
24285
  function registerSigintCleanup(handler) {
@@ -23686,17 +24291,17 @@ function registerSigintCleanup(handler) {
23686
24291
  function logFatalAndExit(title, errOrReason) {
23687
24292
  process.stderr.write("\x1B[?25h");
23688
24293
  if (isVerbose()) {
23689
- console.error(chalk36.red(`
24294
+ console.error(chalk38.red(`
23690
24295
  ${title}:`));
23691
24296
  console.error(
23692
24297
  errOrReason instanceof Error ? errOrReason.stack || errOrReason : errOrReason
23693
24298
  );
23694
24299
  } else {
23695
24300
  const msg = errOrReason instanceof Error ? errOrReason.message : String(errOrReason);
23696
- console.error(chalk36.red(`
24301
+ console.error(chalk38.red(`
23697
24302
  Fatal Error: ${msg}`));
23698
24303
  console.error(
23699
- chalk36.dim(
24304
+ chalk38.dim(
23700
24305
  " Run with --verbose or DEBUG=1 to view the full stack trace.\n"
23701
24306
  )
23702
24307
  );
@@ -23715,7 +24320,7 @@ function setupProcessHandlers() {
23715
24320
  }
23716
24321
  process.exit(130);
23717
24322
  } else {
23718
- console.error(chalk36.dim("\n Operation cancelled."));
24323
+ console.error(chalk38.dim("\n Operation cancelled."));
23719
24324
  process.exit(130);
23720
24325
  }
23721
24326
  });