@iamu-dev/iamu 0.2.0-beta.4 → 0.2.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -199,17 +199,24 @@ function defaultConfig(params) {
199
199
  cloud_api_url: null
200
200
  };
201
201
  }
202
- var baseProjectFields, localOnlyConfig, cloudLinkedConfig, projectConfigSchema;
202
+ var modelTierSchema, projectModelsConfigSchema, baseProjectFields, localOnlyConfig, cloudLinkedConfig, projectConfigSchema;
203
203
  var init_config = __esm({
204
204
  "../../packages/protocol/dist/schemas/frontmatter/config.js"() {
205
205
  "use strict";
206
206
  init_common();
207
207
  init_version();
208
+ modelTierSchema = z4.string().min(1).max(128).describe("Model role tier ('fast', 'thorough', 'inherit') or a specific model identifier.");
209
+ projectModelsConfigSchema = z4.object({
210
+ walker: modelTierSchema.optional(),
211
+ matcher: modelTierSchema.optional(),
212
+ judge: modelTierSchema.optional()
213
+ }).strict();
208
214
  baseProjectFields = {
209
215
  schema_version: z4.literal(SCHEMA_VERSION),
210
216
  id: uuidSchema,
211
217
  project_slug: slugSchema,
212
- project_name: z4.string().min(1)
218
+ project_name: z4.string().min(1),
219
+ models: projectModelsConfigSchema.optional()
213
220
  };
214
221
  localOnlyConfig = z4.object({
215
222
  ...baseProjectFields,
@@ -1153,7 +1160,21 @@ var init_walkthrough_bundle = __esm({
1153
1160
  judge_prompt: z19.string().optional(),
1154
1161
  judge_model: z19.string().optional()
1155
1162
  }).optional()
1156
- }).optional()
1163
+ }).optional(),
1164
+ models: z19.object({
1165
+ walker: z19.string().optional(),
1166
+ matcher: z19.string().optional(),
1167
+ judge: z19.string().optional(),
1168
+ walker_reported: z19.string().optional(),
1169
+ matcher_reported: z19.string().optional(),
1170
+ judge_reported: z19.string().optional(),
1171
+ walker_tier: z19.string().optional(),
1172
+ matcher_tier: z19.string().optional(),
1173
+ judge_tier: z19.string().optional(),
1174
+ execution_mode: z19.enum(["multi_agent", "single_agent"]).optional(),
1175
+ velocity_sec_per_step: z19.number().optional(),
1176
+ total_duration_sec: z19.number().optional()
1177
+ }).optional().describe("AI model configuration and reported runtime provenance for this walkthrough.")
1157
1178
  });
1158
1179
  walkthroughBundleSchema = z19.object({
1159
1180
  manifest: bundleManifestSchema,
@@ -1376,6 +1397,7 @@ var init_walkthrough_canonical = __esm({
1376
1397
  build: buildMetadataSchema.optional(),
1377
1398
  viewport: runViewportSchema.optional(),
1378
1399
  environment: z20.string().optional(),
1400
+ models: z20.record(z20.unknown()).optional(),
1379
1401
  findings: z20.array(canonicalFindingSchema),
1380
1402
  steps: z20.array(canonicalStepSchema),
1381
1403
  human_tests: z20.array(canonicalHumanTestSchema),
@@ -7247,6 +7269,7 @@ async function buildCanonical(projectRoot, manifest, completedAt, inputs) {
7247
7269
  ...manifest.build ? { build: manifest.build } : {},
7248
7270
  ...manifest.viewport ? { viewport: manifest.viewport } : {},
7249
7271
  ...manifest.environment ? { environment: manifest.environment } : {},
7272
+ ...manifest.models ? { models: manifest.models } : {},
7250
7273
  findings: canonicalFindings,
7251
7274
  steps: canonicalSteps,
7252
7275
  human_tests: humanTests,
@@ -7407,6 +7430,20 @@ function renderCanonicalMarkdown(canonical) {
7407
7430
  lines.push(`- Viewport: ${canonical.viewport.device} (${canonical.viewport.width}\xD7${canonical.viewport.height})`);
7408
7431
  }
7409
7432
  lines.push(`- Started: ${canonical.provenance.started_at}`, `- Completed: ${canonical.provenance.completed_at}`, `- Walker prompt: ${canonical.provenance.walker_prompt.id}@${canonical.provenance.walker_prompt.version}`, `- Findings: ${canonical.findings.length}`, `- Steps: ${canonical.steps.length}`);
7433
+ const models = canonical.models;
7434
+ if (models) {
7435
+ const parts = [
7436
+ models.walker ? `Walker: ${models.walker}${models.walker_reported ? ` (${models.walker_reported})` : ""}` : "",
7437
+ models.judge ? `Judge: ${models.judge}${models.judge_reported ? ` (${models.judge_reported})` : ""}` : "",
7438
+ models.matcher ? `Matcher: ${models.matcher}` : ""
7439
+ ].filter(Boolean);
7440
+ if (parts.length > 0) {
7441
+ lines.push(`- Models: ${parts.join(" \xB7 ")}`);
7442
+ }
7443
+ if (typeof models.velocity_sec_per_step === "number") {
7444
+ lines.push(`- Velocity: ~${models.velocity_sec_per_step}s/step (${models.total_duration_sec}s total)`);
7445
+ }
7446
+ }
7410
7447
  const what = canonical.what_worked;
7411
7448
  if (what && what.length > 0) {
7412
7449
  lines.push("", "## What worked");
@@ -7776,6 +7813,7 @@ async function assembleWalkthroughBundle(projectRoot, runId) {
7776
7813
  report_markdown: report3,
7777
7814
  verdict: verdictArray,
7778
7815
  judge_meta: judgeMeta,
7816
+ ...manifest.models ? { models: manifest.models } : {},
7779
7817
  ...buildFields(manifest),
7780
7818
  ...await readCanonicalExtras(projectRoot, runId)
7781
7819
  },
@@ -14413,6 +14451,18 @@ async function startRun(scenarioSlug, opts) {
14413
14451
  }
14414
14452
  const build = detectBuild({ cwd: projectRoot, label: opts.build });
14415
14453
  const environment = resolveEnvironment(opts.env, start2.url);
14454
+ const cfg = await readConfig(projectRoot).catch(() => null);
14455
+ const walker = opts.walker ?? process.env.IAMU_WALKER_MODEL ?? cfg?.models?.walker ?? "fast";
14456
+ const judge = opts.judge ?? process.env.IAMU_JUDGE_MODEL ?? cfg?.models?.judge ?? "thorough";
14457
+ const matcher = opts.matcher ?? process.env.IAMU_MATCHER_MODEL ?? cfg?.models?.matcher ?? "thorough";
14458
+ const models = {
14459
+ walker,
14460
+ judge,
14461
+ matcher,
14462
+ walker_tier: ["fast", "thorough", "inherit"].includes(walker) ? walker : "custom",
14463
+ judge_tier: ["fast", "thorough", "inherit"].includes(judge) ? judge : "custom",
14464
+ matcher_tier: ["fast", "thorough", "inherit"].includes(matcher) ? matcher : "custom"
14465
+ };
14416
14466
  let revisit = [];
14417
14467
  try {
14418
14468
  revisit = await computeRevisitPlaces(projectRoot, scenarioSlug, start2.url);
@@ -14430,6 +14480,7 @@ async function startRun(scenarioSlug, opts) {
14430
14480
  status: "running",
14431
14481
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
14432
14482
  completed_at: null,
14483
+ models,
14433
14484
  ...build ? { build } : {},
14434
14485
  ...environment ? { environment } : {},
14435
14486
  ...revisit.length > 0 ? { revisit } : {},
@@ -14451,6 +14502,7 @@ async function startRun(scenarioSlug, opts) {
14451
14502
  ...targetWarning ? { target_warning: targetWarning } : {},
14452
14503
  persona_slug: fm.persona,
14453
14504
  outline_slug: fm.outline,
14505
+ models,
14454
14506
  build: build ?? null,
14455
14507
  environment: environment ?? null,
14456
14508
  revisit,
@@ -14469,6 +14521,9 @@ async function startRun(scenarioSlug, opts) {
14469
14521
  console.log(` ${chalk11.dim("persona:")} ${fm.persona}`);
14470
14522
  console.log(` ${chalk11.dim("outline:")} ${fm.outline}`);
14471
14523
  console.log(` ${chalk11.dim("target:")} ${start2.url}`);
14524
+ console.log(
14525
+ ` ${chalk11.dim("models:")} walker: ${chalk11.cyan(walker)} \xB7 judge: ${chalk11.cyan(judge)} \xB7 matcher: ${chalk11.cyan(matcher)}`
14526
+ );
14472
14527
  if (targetWarning) {
14473
14528
  console.log(` ${symbols.warning} ${chalk11.yellow(targetWarning)}`);
14474
14529
  }
@@ -16652,6 +16707,7 @@ __export(browse_exports, {
16652
16707
  readBrowseState: () => readBrowseState,
16653
16708
  shouldDisableSandbox: () => shouldDisableSandbox,
16654
16709
  start: () => start,
16710
+ waitForVisualStability: () => waitForVisualStability,
16655
16711
  withPage: () => withPage
16656
16712
  });
16657
16713
  import { randomUUID as randomUUID7 } from "crypto";
@@ -16858,6 +16914,45 @@ async function stop(opts, driver) {
16858
16914
  }
16859
16915
  report2(opts, { stopped: true }, [` ${chalk20.green("\u2713")} Browser stopped`]);
16860
16916
  }
16917
+ async function waitForVisualStability(page, maxWaitMs = 300) {
16918
+ let timer;
16919
+ try {
16920
+ const nodeTimeout = new Promise((resolve3) => {
16921
+ timer = setTimeout(resolve3, maxWaitMs + 100);
16922
+ });
16923
+ const browserWait = page.evaluate((maxWait) => {
16924
+ return new Promise((resolve3) => {
16925
+ const timeout = setTimeout(resolve3, maxWait);
16926
+ const done = () => {
16927
+ clearTimeout(timeout);
16928
+ resolve3();
16929
+ };
16930
+ const g = globalThis;
16931
+ if (typeof g.requestAnimationFrame !== "function") {
16932
+ done();
16933
+ return;
16934
+ }
16935
+ g.requestAnimationFrame(() => {
16936
+ g.requestAnimationFrame?.(() => {
16937
+ const animations = g.document?.getAnimations ? g.document.getAnimations().filter((a) => a.playState === "running" && a.finished) : [];
16938
+ if (animations.length === 0) {
16939
+ done();
16940
+ return;
16941
+ }
16942
+ Promise.allSettled(
16943
+ animations.map((a) => a.finished)
16944
+ ).finally(done);
16945
+ });
16946
+ });
16947
+ });
16948
+ }, maxWaitMs).catch(() => {
16949
+ });
16950
+ await Promise.race([browserWait, nodeTimeout]);
16951
+ } catch {
16952
+ } finally {
16953
+ if (timer) clearTimeout(timer);
16954
+ }
16955
+ }
16861
16956
  async function withPage(opts, driver, fn, target) {
16862
16957
  const state = readBrowseState();
16863
16958
  if (!state) {
@@ -16877,9 +16972,9 @@ async function withPage(opts, driver, fn, target) {
16877
16972
  await fn(page);
16878
16973
  await page.waitForLoadState("domcontentloaded").catch(() => {
16879
16974
  });
16880
- await page.waitForTimeout(400);
16881
16975
  let screenshot = null;
16882
16976
  if (opts.shot) {
16977
+ await waitForVisualStability(page);
16883
16978
  screenshot = path22.resolve(opts.shot);
16884
16979
  mkdirSync(path22.dirname(screenshot), { recursive: true });
16885
16980
  await page.screenshot({ path: screenshot, fullPage: Boolean(opts.full) });
@@ -16980,6 +17075,35 @@ function printStep(opts, record, projectRoot) {
16980
17075
  }
16981
17076
  }
16982
17077
  async function browseStep(args, opts, driver) {
17078
+ if (opts.stepFile) {
17079
+ try {
17080
+ let raw;
17081
+ if (opts.stepFile === "-") {
17082
+ raw = readFileSync6(0, "utf8");
17083
+ } else {
17084
+ raw = readFileSync6(opts.stepFile, "utf8");
17085
+ }
17086
+ const data = JSON.parse(raw);
17087
+ if (data.run && typeof data.run === "string") opts.run = data.run;
17088
+ if (data.action && typeof data.action === "string")
17089
+ opts.action = data.action;
17090
+ if (data.note && typeof data.note === "string") opts.note = data.note;
17091
+ if (data.surface && typeof data.surface === "string")
17092
+ opts.surface = data.surface;
17093
+ if (args.length === 0 && data.cmd) {
17094
+ if (Array.isArray(data.cmd)) {
17095
+ args = data.cmd.map(String);
17096
+ } else if (typeof data.cmd === "string") {
17097
+ args = data.cmd.trim().split(/\s+/);
17098
+ }
17099
+ }
17100
+ } catch (err) {
17101
+ return fail(
17102
+ `Failed to read step payload from ${opts.stepFile}: ${err.message}`,
17103
+ opts
17104
+ );
17105
+ }
17106
+ }
16983
17107
  if (!opts.run)
16984
17108
  return fail("--run <run-id> is required for browse step.", opts);
16985
17109
  if (!opts.action) {
@@ -18837,7 +18961,7 @@ function composeWalkerPrompt(p) {
18837
18961
  `Screenshots to: ${rd}/screenshots/step-N.png \u2014 absolute paths.`,
18838
18962
  `Record findings with: ${iamu} new-finding --scenario ${p.scenarioSlug} --run ${p.runId} --step <N> --title "..." --type <helps|hinders|missing|irrelevant|incorrect> --severity <critical|major|minor|observation> --ui-fact "..." --consequence "..." --task-impact <blocks|impairs|slows|neutral|accelerates|enables> --confidence <high|medium|low> --json \u2014 then add the body (description + "## Recommendation") below the frontmatter of the file it returns.`,
18839
18963
  "`--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.",
18840
- `Before you finish, write ${rd}/summary.json: {"what_worked": [...], "open_questions": [...]} \u2014 one sentence each, in the persona's language.`,
18964
+ `Before you finish, write ${rd}/summary.json: {"what_worked": [...], "open_questions": [...], "model": "<your model identifier if known, e.g. claude-3-5-haiku, gemini-3.8-flash, or unspecified>"} \u2014 one sentence each, in the persona's language.`,
18841
18965
  "",
18842
18966
  "Do not read the product's source code, other iamu/ entity files, or earlier runs: you know only what the persona knows.",
18843
18967
  "",
@@ -19436,8 +19560,21 @@ async function evaluate(runId, opts) {
19436
19560
  )
19437
19561
  };
19438
19562
  const prompt = composeJudgePrompt(input);
19563
+ const promptFilePath = opts?.promptFile ?? path25.join(runDirPath, "judge_prompt.md");
19564
+ await fs25.writeFile(promptFilePath, prompt, "utf8");
19439
19565
  if (opts?.json) {
19440
- console.log(JSON.stringify({ ...input, judge_prompt: prompt }, null, 2));
19566
+ console.log(
19567
+ JSON.stringify(
19568
+ {
19569
+ ...input,
19570
+ prompt_file: path25.relative(projectRoot, promptFilePath),
19571
+ prompt_file_abs: path25.resolve(promptFilePath),
19572
+ judge_prompt: prompt
19573
+ },
19574
+ null,
19575
+ 2
19576
+ )
19577
+ );
19441
19578
  return;
19442
19579
  }
19443
19580
  console.log(prompt);
@@ -19529,7 +19666,7 @@ ${formatIssues(fileResult.error)}`,
19529
19666
  runId,
19530
19667
  validatedVerdicts
19531
19668
  );
19532
- const cloudInfo = await pushRunToCloud(projectRoot, runId);
19669
+ const cloudInfo = opts?.push === false ? void 0 : await pushRunToCloud(projectRoot, runId);
19533
19670
  if (opts?.json) {
19534
19671
  console.log(
19535
19672
  JSON.stringify(
@@ -19646,6 +19783,36 @@ async function complete(runId, opts) {
19646
19783
  return;
19647
19784
  }
19648
19785
  const completedAt = pickCompletedAt(run.manifest);
19786
+ const walkerSummary = await readWalkerSummary(run.runDirPath);
19787
+ let judgeReported;
19788
+ try {
19789
+ const verdictsRaw = await fs26.readFile(
19790
+ path26.join(run.runDirPath, "verdicts.json"),
19791
+ "utf8"
19792
+ );
19793
+ const parsed = JSON.parse(verdictsRaw);
19794
+ if (parsed.provenance?.model) {
19795
+ judgeReported = parsed.provenance.model;
19796
+ }
19797
+ } catch {
19798
+ }
19799
+ const stepCount = provenanceAudit.step_count;
19800
+ const durationSec = provenanceAudit.duration_seconds ?? (run.manifest.created_at && Date.parse(completedAt) > Date.parse(run.manifest.created_at) ? Math.round(
19801
+ (Date.parse(completedAt) - Date.parse(run.manifest.created_at)) / 1e3 * 10
19802
+ ) / 10 : null);
19803
+ const velocitySec = provenanceAudit.avg_step_interval_seconds ?? (durationSec != null && stepCount > 1 ? Math.round(durationSec / (stepCount - 1) * 10) / 10 : null);
19804
+ const existingModels = run.manifest.models ?? {};
19805
+ const updatedModels = {
19806
+ ...existingModels,
19807
+ ...walkerSummary.model ? { walker_reported: walkerSummary.model } : {},
19808
+ ...judgeReported ? { judge_reported: judgeReported } : {},
19809
+ ...durationSec != null ? { total_duration_sec: durationSec } : {},
19810
+ ...velocitySec != null ? { velocity_sec_per_step: velocitySec } : {}
19811
+ };
19812
+ run.manifest = {
19813
+ ...run.manifest,
19814
+ models: updatedModels
19815
+ };
19649
19816
  const assembled = await assembleRun(
19650
19817
  run.projectRoot,
19651
19818
  runId,
@@ -19773,7 +19940,7 @@ async function collectCompletionExtras(run, opts) {
19773
19940
  parseRunViewport(manifest.viewport)
19774
19941
  );
19775
19942
  const codeWarning = opts?.force ? null : codeChangedWarning(manifest.build, detectWorktreeState(projectRoot));
19776
- const cloudInfo = await pushRunToCloud(projectRoot, runId);
19943
+ const cloudInfo = opts?.push === false ? void 0 : await pushRunToCloud(projectRoot, runId);
19777
19944
  return { revisit, viewportWarning, codeWarning, cloudInfo };
19778
19945
  }
19779
19946
  function completeJsonPayload(canonical, extras) {
@@ -19806,6 +19973,17 @@ function printCompletionSummary(run, completedAt, assembled, extras, provenanceA
19806
19973
  console.log(` steps ${count(canonical.steps.length)}`);
19807
19974
  console.log(` started ${chalk30.dim(manifest.created_at)}`);
19808
19975
  console.log(` completed ${chalk30.dim(completedAt)}`);
19976
+ const models = manifest.models;
19977
+ if (models?.velocity_sec_per_step != null) {
19978
+ console.log(
19979
+ ` velocity ${chalk30.cyan(`~${models.velocity_sec_per_step}s/step`)} ${chalk30.dim(`(${models.total_duration_sec}s total)`)}`
19980
+ );
19981
+ }
19982
+ if (models && (models.walker || models.judge || models.matcher)) {
19983
+ const w = models.walker_reported ? `${models.walker} (${models.walker_reported})` : models.walker ?? "fast";
19984
+ const j = models.judge_reported ? `${models.judge} (${models.judge_reported})` : models.judge ?? "thorough";
19985
+ console.log(` models ${chalk30.dim(`walker: ${w} \xB7 judge: ${j}`)}`);
19986
+ }
19809
19987
  console.log();
19810
19988
  console.log(
19811
19989
  ` ${chalk30.dim("canonical:")} ${chalk30.dim(path26.relative(projectRoot, assembled.canonicalPath))}`
@@ -19899,9 +20077,11 @@ async function readWalkerSummary(runDirPath) {
19899
20077
  await fs26.readFile(path26.join(runDirPath, "summary.json"), "utf8")
19900
20078
  );
19901
20079
  const strings = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && x.trim() !== "") : void 0;
20080
+ const model = typeof data.model === "string" && data.model.trim() !== "" ? data.model.trim() : void 0;
19902
20081
  return {
19903
20082
  what_worked: strings(data.what_worked),
19904
- open_questions: strings(data.open_questions)
20083
+ open_questions: strings(data.open_questions),
20084
+ model
19905
20085
  };
19906
20086
  } catch {
19907
20087
  return {};
@@ -20226,9 +20406,28 @@ async function match(runId, opts) {
20226
20406
  );
20227
20407
  const needed = input.current.length > 0 && (input.prior.length > 0 || (input.guidance ?? []).length > 0);
20228
20408
  const prompt = needed ? composeMatchPrompt(input) : null;
20409
+ const promptFilePath = opts?.promptFile ?? path27.join(runDirPath, "match_prompt.md");
20410
+ if (prompt) {
20411
+ await fs27.writeFile(promptFilePath, prompt, "utf8");
20412
+ } else {
20413
+ try {
20414
+ await fs27.unlink(promptFilePath);
20415
+ } catch {
20416
+ }
20417
+ }
20229
20418
  if (opts?.json) {
20230
20419
  console.log(
20231
- JSON.stringify({ ...input, needed, match_prompt: prompt }, null, 2)
20420
+ JSON.stringify(
20421
+ {
20422
+ ...input,
20423
+ needed,
20424
+ prompt_file: prompt ? path27.relative(ctx.projectRoot, promptFilePath) : null,
20425
+ prompt_file_abs: prompt ? path27.resolve(promptFilePath) : null,
20426
+ match_prompt: prompt
20427
+ },
20428
+ null,
20429
+ 2
20430
+ )
20232
20431
  );
20233
20432
  return;
20234
20433
  }
@@ -20664,6 +20863,15 @@ function registerWalkthroughCommands(program) {
20664
20863
  ).option(
20665
20864
  "--env <name>",
20666
20865
  "Environment the product runs in (default: the target URL's origin)"
20866
+ ).option(
20867
+ "--walker <tierOrModel>",
20868
+ "Model tier ('fast', 'thorough', 'inherit') or model ID for driving browser navigation (default: fast)"
20869
+ ).option(
20870
+ "--judge <tierOrModel>",
20871
+ "Model tier ('fast', 'thorough', 'inherit') or model ID for human test evaluation (default: thorough)"
20872
+ ).option(
20873
+ "--matcher <tierOrModel>",
20874
+ "Model tier ('fast', 'thorough', 'inherit') or model ID for finding matching (default: thorough)"
20667
20875
  ).option(
20668
20876
  "--check",
20669
20877
  "Enforce target URL reachability (fail fast if server is offline)"
@@ -20694,7 +20902,10 @@ function registerWalkthroughCommands(program) {
20694
20902
  ).option(
20695
20903
  "--run <run-id>",
20696
20904
  "Run ID to size browser (start) or record step into (step)"
20697
- ).option("--action <text>", "Action description (step only)").option("--note <text>", "Persona inner monologue / reasoning (step only)").option("--surface <name>", "Screen or surface name (step only)").option("--width <px>", "Window width (start)").option("--height <px>", "Window height (start)").option("--headless", "Run without a window (start)").option(
20905
+ ).option("--action <text>", "Action description (step only)").option("--note <text>", "Persona inner monologue / reasoning (step only)").option("--surface <name>", "Screen or surface name (step only)").option(
20906
+ "--step-file <path>",
20907
+ "Read step payload (action, note, cmd) from a JSON file or '-' for stdin (step only)"
20908
+ ).option("--width <px>", "Window width (start)").option("--height <px>", "Window height (start)").option("--headless", "Run without a window (start)").option(
20698
20909
  "--port <port>",
20699
20910
  "CDP port (start), default 9333",
20700
20911
  (value) => parsePortOption(value)
@@ -20770,9 +20981,9 @@ function registerWalkthroughCommands(program) {
20770
20981
  ).option("-y, --yes", "Skip interactive prompts").option("--no-prompt", "Skip interactive prompt to view results").option("--view", "Open results in browser immediately without prompt").option(
20771
20982
  "--strict-provenance",
20772
20983
  "Fail if steps show synthetic or programmatic timing anomalies"
20773
- ).addHelpText(
20984
+ ).option("--no-push", "Skip pushing the run to Iamu Cloud").addHelpText(
20774
20985
  "after",
20775
- "\nExamples:\n $ iamu complete <run-id>\n $ iamu complete <run-id> --view\n $ iamu complete <run-id> --no-prompt\n $ iamu complete <run-id> --json\n $ iamu complete <run-id> --strict-provenance"
20986
+ "\nExamples:\n $ iamu complete <run-id>\n $ iamu complete <run-id> --view\n $ iamu complete <run-id> --no-prompt\n $ iamu complete <run-id> --no-push\n $ iamu complete <run-id> --json\n $ iamu complete <run-id> --strict-provenance"
20776
20987
  ).action(
20777
20988
  async (runId, opts) => {
20778
20989
  const { complete: complete2 } = await Promise.resolve().then(() => (init_complete(), complete_exports));
@@ -20781,23 +20992,27 @@ function registerWalkthroughCommands(program) {
20781
20992
  );
20782
20993
  program.command("match <run-id>").helpGroup("Walkthrough:").description(
20783
20994
  "Compose the prompt that links this run's findings to earlier ones"
20784
- ).option("--json", "Output the match input bundle as JSON").action(async (runId, opts) => {
20785
- const { match: match2 } = await Promise.resolve().then(() => (init_match(), match_exports));
20786
- await match2(runId, opts);
20787
- });
20995
+ ).option("--json", "Output the match input bundle as JSON").option("--prompt-file <path>", "Write the generated prompt to a file").action(
20996
+ async (runId, opts) => {
20997
+ const { match: match2 } = await Promise.resolve().then(() => (init_match(), match_exports));
20998
+ await match2(runId, opts);
20999
+ }
21000
+ );
20788
21001
  program.command("match-record <run-id>").helpGroup("Walkthrough:").description(
20789
21002
  "Record finding matches and re-derive the run's since-last-run delta"
20790
21003
  ).requiredOption("--file <path>", "Path to matches JSON file").option("--json", "Output match results as JSON").action(async (runId, opts) => {
20791
21004
  const { matchRecord: matchRecord2 } = await Promise.resolve().then(() => (init_match(), match_exports));
20792
21005
  await matchRecord2(runId, opts);
20793
21006
  });
20794
- program.command("evaluate <run-id>").helpGroup("Walkthrough:").description("Compose evaluation prompt for human-test questions").option("--json", "Output evaluation input bundle as JSON").action(async (runId, opts) => {
20795
- const { evaluate: evaluate2 } = await Promise.resolve().then(() => (init_evaluate(), evaluate_exports));
20796
- await evaluate2(runId, opts);
20797
- });
20798
- program.command("evaluate-record <run-id>").helpGroup("Walkthrough:").description("Record evaluation verdicts into the run's verdicts.json").requiredOption("--file <path>", "Path to verdicts JSON file").option("--json", "Output summary as JSON").option("-y, --yes", "Skip interactive prompts").option("--no-prompt", "Skip interactive prompt to view results").option("--view", "Open results in browser immediately without prompt").addHelpText(
21007
+ program.command("evaluate <run-id>").helpGroup("Walkthrough:").description("Compose evaluation prompt for human-test questions").option("--json", "Output evaluation input bundle as JSON").option("--prompt-file <path>", "Write the generated prompt to a file").action(
21008
+ async (runId, opts) => {
21009
+ const { evaluate: evaluate2 } = await Promise.resolve().then(() => (init_evaluate(), evaluate_exports));
21010
+ await evaluate2(runId, opts);
21011
+ }
21012
+ );
21013
+ program.command("evaluate-record <run-id>").helpGroup("Walkthrough:").description("Record evaluation verdicts into the run's verdicts.json").requiredOption("--file <path>", "Path to verdicts JSON file").option("--json", "Output summary as JSON").option("-y, --yes", "Skip interactive prompts").option("--no-prompt", "Skip interactive prompt to view results").option("--no-push", "Skip pushing the updated run to Iamu Cloud").option("--view", "Open results in browser immediately without prompt").addHelpText(
20799
21014
  "after",
20800
- "\nExamples:\n $ iamu evaluate-record <run-id> --file verdicts.json\n $ iamu evaluate-record <run-id> --file verdicts.json --no-prompt\n $ iamu evaluate-record <run-id> --file verdicts.json --json"
21015
+ "\nExamples:\n $ iamu evaluate-record <run-id> --file verdicts.json\n $ iamu evaluate-record <run-id> --file verdicts.json --no-prompt\n $ iamu evaluate-record <run-id> --file verdicts.json --no-push\n $ iamu evaluate-record <run-id> --file verdicts.json --json"
20801
21016
  ).action(
20802
21017
  async (runId, opts) => {
20803
21018
  const { evaluateRecord: evaluateRecord2 } = await Promise.resolve().then(() => (init_evaluate(), evaluate_exports));
@@ -67,47 +67,45 @@ Filter to `triage_status: open`.
67
67
 
68
68
  A run that simply did not come across a finding proves nothing — never call a fix verified because it was not seen. After the list, if any fixes await a re-run, say how many and in which scenarios (`iamu status --json` → `rerun`, with `requests.open` for team requests), and offer `iamu-run <scenario>` for the first one.
69
69
 
70
- Group by `scenario` field and render as a severity-sorted table per scenario group (CRITICAL → MAJOR → MINOR → OBS; hinders and missing first, helps last):
70
+ Group by `scenario` field and render with an executive tally header, followed by a severity-sorted list per scenario group (CRITICAL → MAJOR → MINOR → OBS; hinders and missing first, helps last), including persona quotes, evidence, and clickable file links:
71
71
 
72
72
  ```
73
- Open findings (N total)
74
-
75
- ▼ maria-gomez--busy-shift (4 findings)
76
-
77
- | # | Severity | Type | Finding | Slug |
78
- |---|----------|---------|--------------------------------------------------|------------------------------------------------------------|
79
- | 1 | CRITICAL | hinders | Handoff list is cluttered | maria-gomez--busy-shift--handoff-list-cluttered-a1b2c3 |
80
- | 2 | MAJOR | missing | No visible confirmation after shift submit | maria-gomez--busy-shift--no-submit-confirmation-d4e5f6 |
81
- | 3 | MINOR | hinders | Timeout warning fires mid-input | maria-gomez--busy-shift--timeout-mid-input-g7h8i9 |
82
- | 4 | OBS | helps | Gesture hint at top of screen | maria-gomez--busy-shift--gesture-hint-j1k2l3 |
83
-
84
- ▼ dev-patel--checkout-abandon (3 findings)
85
-
86
- | # | Severity | Type | Finding | Slug |
87
- |---|----------|-----------|--------------------------------------------------|------------------------------------------------------------|
88
- | 5 | CRITICAL | incorrect | Cart total recalculates to wrong amount | dev-patel--checkout-abandon--cart-total-wrong-... |
89
- | 6 | MAJOR | missing | No save-for-later affordance | dev-patel--checkout-abandon--no-save-for-later-... |
90
- | 7 | MINOR | hinders | Auto-scroll on form error | dev-patel--checkout-abandon--auto-scroll-error-... |
73
+ Findings Tally: <N> Open (<C> Critical · <M> Major · <m> Minor) across <S> Scenarios
74
+ Awaiting verification: <K> addressed findings pending re-run
75
+
76
+ ▼ [<scenario>](iamu/scenarios/<scenario>.md) (4 findings)
77
+ 1. [CRITICAL · hinders] Handoff list is cluttered ([file](iamu/findings/<slug>.md))
78
+ • Persona: <Persona Name>: "<Persona reaction or consequence quote>"
79
+ • Evidence: Step <N> at <url>
80
+ • Recommendation: Add status filter chips
81
+
82
+ 2. [MAJOR · missing] No visible confirmation after shift submit ([file](iamu/findings/<slug>.md))
83
+ • Persona: <Persona Name>: "<Persona reaction or consequence quote>"
84
+ • Evidence: Step <N> at <url>
85
+ • Recommendation: Show inline success toast
91
86
  ```
92
87
 
93
- Ask the user which finding to triage (by number, slug, or "skip").
88
+ Ask the user how they would like to triage:
94
89
 
90
+ - **"walk through them"** → iterate through all open findings one-by-one in order with full context, code location, and triage choices (`[Fix now] [Skip / Dismiss] [Discuss] [Next]`).
95
91
  - **"all critical"** → iterate through all critical findings in order.
96
92
  - **Scenario group name** (e.g. "group 1") → iterate through that scenario's findings.
97
- - **Single number or slug** → single finding.
93
+ - **Single number or slug** → single finding (skip to step 3).
94
+ - **"fix 1 and 2"** → make code fixes and mark addressed immediately.
95
+ - **"skip 3" / "dismiss 3"** → dismiss with reason (`out_of_scope`, `not_relevant`, `walker_error`, `duplicate`).
98
96
 
99
97
  ### 3. Direct triage: review one finding
100
98
 
101
- Read the finding file at `iamu/findings/<slug>.md`. Parse the YAML frontmatter and markdown body. Render full detail:
99
+ Read the finding file at `iamu/findings/<slug>.md`. Parse the YAML frontmatter and markdown body. Render full detail with clickable links and evidence:
102
100
 
103
101
  ```
104
- Finding: <title>
105
- Slug: <slug>
106
- Scenario: <scenario>
107
- Type: <type> Severity: <severity> Triage: <triage_status>
102
+ Finding: <title> ([file](iamu/findings/<slug>.md))
103
+ Scenario: [<scenario>](iamu/scenarios/<scenario>.md)
104
+ Severity: <severity> · Type: <type> · Status: <triage_status>
108
105
 
109
- UI fact: <ui_fact>
110
- Consequence: <consequence>
106
+ Persona Quote: "<persona_reaction_or_consequence>"
107
+ UI Fact: <ui_fact>
108
+ Evidence: Step <N> at <url> (Screenshot: [view](iamu/.runs/<run>/screenshots/<NNN>.png))
111
109
 
112
110
  Description:
113
111
  <markdown body above ## Recommendation>
@@ -115,7 +113,7 @@ Description:
115
113
  Recommendation:
116
114
  <## Recommendation section, or "none">
117
115
 
118
- Linked run: iamu/.runs/<run>/report.md
116
+ Report: [report.md](iamu/.runs/<run>/report.md)
119
117
  ```
120
118
 
121
119
  If the finding references a run directory, mention the report path for context. If the user wants to see the screenshot, read from `iamu/.runs/<run>/screenshots/`.