@iamu-dev/iamu 0.2.0-beta.5 → 0.2.0-beta.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -200,17 +200,24 @@ function defaultConfig(params) {
200
200
  cloud_api_url: null
201
201
  };
202
202
  }
203
- var baseProjectFields, localOnlyConfig, cloudLinkedConfig, projectConfigSchema;
203
+ var modelTierSchema, projectModelsConfigSchema, baseProjectFields, localOnlyConfig, cloudLinkedConfig, projectConfigSchema;
204
204
  var init_config = __esm({
205
205
  "../../packages/protocol/dist/schemas/frontmatter/config.js"() {
206
206
  "use strict";
207
207
  init_common();
208
208
  init_version();
209
+ modelTierSchema = z4.string().min(1).max(128).describe("Model role tier ('fast', 'thorough', 'inherit') or a specific model identifier.");
210
+ projectModelsConfigSchema = z4.object({
211
+ walker: modelTierSchema.optional(),
212
+ matcher: modelTierSchema.optional(),
213
+ judge: modelTierSchema.optional()
214
+ }).strict();
209
215
  baseProjectFields = {
210
216
  schema_version: z4.literal(SCHEMA_VERSION),
211
217
  id: uuidSchema,
212
218
  project_slug: slugSchema,
213
- project_name: z4.string().min(1)
219
+ project_name: z4.string().min(1),
220
+ models: projectModelsConfigSchema.optional()
214
221
  };
215
222
  localOnlyConfig = z4.object({
216
223
  ...baseProjectFields,
@@ -817,7 +824,14 @@ var init_scenario = __esm({
817
824
  reviews: z15.array(z15.string().min(1).max(64)).max(16).default([]),
818
825
  baseline_score: z15.number().min(0).max(100).nullish(),
819
826
  current_score: z15.number().min(0).max(100).nullish(),
820
- target_score: z15.number().min(0).max(100).default(95)
827
+ target_score: z15.number().min(0).max(100).default(95),
828
+ /** Environment state preconditions and approximation guidance. */
829
+ state: z15.object({
830
+ /** Description of data/records expected to exist in the environment. */
831
+ description: z15.string().max(2048).nullish(),
832
+ /** Guidance on how to squint or approximate if exact records are absent. */
833
+ approximation: z15.string().max(2048).nullish()
834
+ }).nullish()
821
835
  });
822
836
  }
823
837
  });
@@ -1154,7 +1168,21 @@ var init_walkthrough_bundle = __esm({
1154
1168
  judge_prompt: z19.string().optional(),
1155
1169
  judge_model: z19.string().optional()
1156
1170
  }).optional()
1157
- }).optional()
1171
+ }).optional(),
1172
+ models: z19.object({
1173
+ walker: z19.string().optional(),
1174
+ matcher: z19.string().optional(),
1175
+ judge: z19.string().optional(),
1176
+ walker_reported: z19.string().optional(),
1177
+ matcher_reported: z19.string().optional(),
1178
+ judge_reported: z19.string().optional(),
1179
+ walker_tier: z19.string().optional(),
1180
+ matcher_tier: z19.string().optional(),
1181
+ judge_tier: z19.string().optional(),
1182
+ execution_mode: z19.enum(["multi_agent", "single_agent"]).optional(),
1183
+ velocity_sec_per_step: z19.number().optional(),
1184
+ total_duration_sec: z19.number().optional()
1185
+ }).optional().describe("AI model configuration and reported runtime provenance for this walkthrough.")
1158
1186
  });
1159
1187
  walkthroughBundleSchema = z19.object({
1160
1188
  manifest: bundleManifestSchema,
@@ -1377,6 +1405,7 @@ var init_walkthrough_canonical = __esm({
1377
1405
  build: buildMetadataSchema.optional(),
1378
1406
  viewport: runViewportSchema.optional(),
1379
1407
  environment: z20.string().optional(),
1408
+ models: z20.record(z20.unknown()).optional(),
1380
1409
  findings: z20.array(canonicalFindingSchema),
1381
1410
  steps: z20.array(canonicalStepSchema),
1382
1411
  human_tests: z20.array(canonicalHumanTestSchema),
@@ -7248,6 +7277,7 @@ async function buildCanonical(projectRoot, manifest, completedAt, inputs) {
7248
7277
  ...manifest.build ? { build: manifest.build } : {},
7249
7278
  ...manifest.viewport ? { viewport: manifest.viewport } : {},
7250
7279
  ...manifest.environment ? { environment: manifest.environment } : {},
7280
+ ...manifest.models ? { models: manifest.models } : {},
7251
7281
  findings: canonicalFindings,
7252
7282
  steps: canonicalSteps,
7253
7283
  human_tests: humanTests,
@@ -7408,6 +7438,20 @@ function renderCanonicalMarkdown(canonical) {
7408
7438
  lines.push(`- Viewport: ${canonical.viewport.device} (${canonical.viewport.width}\xD7${canonical.viewport.height})`);
7409
7439
  }
7410
7440
  lines.push(`- Started: ${canonical.provenance.started_at}`, `- Completed: ${canonical.provenance.completed_at}`, `- Walker prompt: ${canonical.provenance.walker_prompt.id}@${canonical.provenance.walker_prompt.version}`, `- Findings: ${canonical.findings.length}`, `- Steps: ${canonical.steps.length}`);
7441
+ const models = canonical.models;
7442
+ if (models) {
7443
+ const parts = [
7444
+ models.walker ? `Walker: ${models.walker}${models.walker_reported ? ` (${models.walker_reported})` : ""}` : "",
7445
+ models.judge ? `Judge: ${models.judge}${models.judge_reported ? ` (${models.judge_reported})` : ""}` : "",
7446
+ models.matcher ? `Matcher: ${models.matcher}` : ""
7447
+ ].filter(Boolean);
7448
+ if (parts.length > 0) {
7449
+ lines.push(`- Models: ${parts.join(" \xB7 ")}`);
7450
+ }
7451
+ if (typeof models.velocity_sec_per_step === "number") {
7452
+ lines.push(`- Velocity: ~${models.velocity_sec_per_step}s/step (${models.total_duration_sec}s total)`);
7453
+ }
7454
+ }
7411
7455
  const what = canonical.what_worked;
7412
7456
  if (what && what.length > 0) {
7413
7457
  lines.push("", "## What worked");
@@ -7777,6 +7821,7 @@ async function assembleWalkthroughBundle(projectRoot, runId) {
7777
7821
  report_markdown: report3,
7778
7822
  verdict: verdictArray,
7779
7823
  judge_meta: judgeMeta,
7824
+ ...manifest.models ? { models: manifest.models } : {},
7780
7825
  ...buildFields(manifest),
7781
7826
  ...await readCanonicalExtras(projectRoot, runId)
7782
7827
  },
@@ -12287,15 +12332,6 @@ function mcpJson(cmd) {
12287
12332
  2
12288
12333
  );
12289
12334
  }
12290
- function vscodeMcpJson(cmd) {
12291
- return JSON.stringify(
12292
- {
12293
- servers: { iamu: { type: "stdio", command: cmd[0], args: cmd.slice(1) } }
12294
- },
12295
- null,
12296
- 2
12297
- );
12298
- }
12299
12335
  function detectHosts(projectRoot) {
12300
12336
  return HOST_ADAPTERS.filter(
12301
12337
  (host) => host.detectionPaths.some((p) => existsSync3(join3(projectRoot, p)))
@@ -12344,13 +12380,17 @@ var init_registry = __esm({
12344
12380
  {
12345
12381
  name: "GitHub Copilot",
12346
12382
  value: "github-copilot",
12347
- detectionPaths: [".vscode", ".github/copilot-instructions.md"],
12348
- supportsSkills: false,
12349
- getSkillsDir: () => "",
12350
- getCommandPath: (root, id) => join3(root, ".github", "prompts", `iamu-${id}.prompt.md`),
12351
- formatCommand: descOnlyFormat,
12352
- getMcpConfigPath: (root) => join3(root, ".vscode", "mcp.json"),
12353
- formatMcpConfig: vscodeMcpJson
12383
+ detectionPaths: [
12384
+ ".github/copilot-instructions.md",
12385
+ ".github/skills",
12386
+ ".copilot"
12387
+ ],
12388
+ supportsSkills: true,
12389
+ getSkillsDir: (root) => join3(root, ".github", "skills"),
12390
+ getCommandPath: (root, id) => join3(root, ".github", "skills", `iamu-${id}`, "SKILL.md"),
12391
+ formatCommand: richFormat,
12392
+ getMcpConfigPath: (root) => join3(root, ".mcp.json"),
12393
+ formatMcpConfig: mcpJson
12354
12394
  },
12355
12395
  {
12356
12396
  name: "Cline",
@@ -12532,6 +12572,14 @@ function isCliWrittenSkill(path28) {
12532
12572
  return false;
12533
12573
  }
12534
12574
  }
12575
+ function isCliWrittenPrompt(path28) {
12576
+ try {
12577
+ const content = readFileSync3(path28, "utf-8");
12578
+ return content.includes("iamu") || /^generatedBy:/m.test(content);
12579
+ } catch {
12580
+ return false;
12581
+ }
12582
+ }
12535
12583
  function planSkillFiles(hosts, projectRoot, version2) {
12536
12584
  const skillTemplates = loadSkillTemplates(version2);
12537
12585
  const files = [];
@@ -12560,6 +12608,19 @@ function planSkillFiles(hosts, projectRoot, version2) {
12560
12608
  }
12561
12609
  }
12562
12610
  }
12611
+ if (host.value === "github-copilot") {
12612
+ for (const cmd of commandsFromSkills(skillTemplates)) {
12613
+ const legacyPrompt = join4(
12614
+ projectRoot,
12615
+ ".github",
12616
+ "prompts",
12617
+ `iamu-${cmd.id}.prompt.md`
12618
+ );
12619
+ if (isCliWrittenPrompt(legacyPrompt)) {
12620
+ files.push({ host, path: legacyPrompt, content: null });
12621
+ }
12622
+ }
12623
+ }
12563
12624
  } else {
12564
12625
  for (const cmd of commandsFromSkills(skillTemplates)) {
12565
12626
  files.push({
@@ -12584,6 +12645,15 @@ function detectInstalledHosts(projectRoot) {
12584
12645
  const skillDir = join4(host.getSkillsDir(projectRoot), "iamu-setup");
12585
12646
  if (existsSync5(skillDir)) return true;
12586
12647
  }
12648
+ if (host.value === "github-copilot") {
12649
+ const legacyPrompt = join4(
12650
+ projectRoot,
12651
+ ".github",
12652
+ "prompts",
12653
+ "iamu-setup.prompt.md"
12654
+ );
12655
+ if (existsSync5(legacyPrompt)) return true;
12656
+ }
12587
12657
  return false;
12588
12658
  });
12589
12659
  }
@@ -14434,6 +14504,18 @@ async function startRun(scenarioSlug, opts) {
14434
14504
  }
14435
14505
  const build = detectBuild({ cwd: projectRoot, label: opts.build });
14436
14506
  const environment = resolveEnvironment(opts.env, start2.url);
14507
+ const cfg = await readConfig(projectRoot).catch(() => null);
14508
+ const walker = opts.walker ?? process.env.IAMU_WALKER_MODEL ?? cfg?.models?.walker ?? "fast";
14509
+ const judge = opts.judge ?? process.env.IAMU_JUDGE_MODEL ?? cfg?.models?.judge ?? "thorough";
14510
+ const matcher = opts.matcher ?? process.env.IAMU_MATCHER_MODEL ?? cfg?.models?.matcher ?? "thorough";
14511
+ const models = {
14512
+ walker,
14513
+ judge,
14514
+ matcher,
14515
+ walker_tier: ["fast", "thorough", "inherit"].includes(walker) ? walker : "custom",
14516
+ judge_tier: ["fast", "thorough", "inherit"].includes(judge) ? judge : "custom",
14517
+ matcher_tier: ["fast", "thorough", "inherit"].includes(matcher) ? matcher : "custom"
14518
+ };
14437
14519
  let revisit = [];
14438
14520
  try {
14439
14521
  revisit = await computeRevisitPlaces(projectRoot, scenarioSlug, start2.url);
@@ -14451,6 +14533,7 @@ async function startRun(scenarioSlug, opts) {
14451
14533
  status: "running",
14452
14534
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
14453
14535
  completed_at: null,
14536
+ models,
14454
14537
  ...build ? { build } : {},
14455
14538
  ...environment ? { environment } : {},
14456
14539
  ...revisit.length > 0 ? { revisit } : {},
@@ -14472,6 +14555,7 @@ async function startRun(scenarioSlug, opts) {
14472
14555
  ...targetWarning ? { target_warning: targetWarning } : {},
14473
14556
  persona_slug: fm.persona,
14474
14557
  outline_slug: fm.outline,
14558
+ models,
14475
14559
  build: build ?? null,
14476
14560
  environment: environment ?? null,
14477
14561
  revisit,
@@ -14490,6 +14574,9 @@ async function startRun(scenarioSlug, opts) {
14490
14574
  console.log(` ${chalk11.dim("persona:")} ${fm.persona}`);
14491
14575
  console.log(` ${chalk11.dim("outline:")} ${fm.outline}`);
14492
14576
  console.log(` ${chalk11.dim("target:")} ${start2.url}`);
14577
+ console.log(
14578
+ ` ${chalk11.dim("models:")} walker: ${chalk11.cyan(walker)} \xB7 judge: ${chalk11.cyan(judge)} \xB7 matcher: ${chalk11.cyan(matcher)}`
14579
+ );
14493
14580
  if (targetWarning) {
14494
14581
  console.log(` ${symbols.warning} ${chalk11.yellow(targetWarning)}`);
14495
14582
  }
@@ -16286,7 +16373,10 @@ var init_browse_actions = __esm({
16286
16373
  usage: "goto <url>",
16287
16374
  minArgs: 1,
16288
16375
  run: async (p, args) => {
16289
- await p.goto(args[0], { waitUntil: "domcontentloaded" });
16376
+ await p.goto(args[0], {
16377
+ waitUntil: "domcontentloaded",
16378
+ timeout: 1e4
16379
+ });
16290
16380
  }
16291
16381
  },
16292
16382
  click: {
@@ -16447,6 +16537,12 @@ var init_browse_driver = __esm({
16447
16537
  commandLine: (pid) => readProcessCommandLine(pid),
16448
16538
  kill(pid) {
16449
16539
  try {
16540
+ if (process.platform !== "win32") {
16541
+ try {
16542
+ process.kill(-pid, "SIGTERM");
16543
+ } catch {
16544
+ }
16545
+ }
16450
16546
  process.kill(pid, "SIGTERM");
16451
16547
  } catch {
16452
16548
  }
@@ -16673,6 +16769,7 @@ __export(browse_exports, {
16673
16769
  readBrowseState: () => readBrowseState,
16674
16770
  shouldDisableSandbox: () => shouldDisableSandbox,
16675
16771
  start: () => start,
16772
+ waitForVisualStability: () => waitForVisualStability,
16676
16773
  withPage: () => withPage
16677
16774
  });
16678
16775
  import { randomUUID as randomUUID7 } from "crypto";
@@ -16822,6 +16919,8 @@ function chromeArgs(opts, port, headless) {
16822
16919
  `--window-size=${opts.width ?? "1280"},${opts.height ?? "900"}`,
16823
16920
  "--no-first-run",
16824
16921
  "--no-default-browser-check",
16922
+ "--password-store=basic",
16923
+ "--use-mock-keychain",
16825
16924
  ...headless ? ["--headless=new", "--disable-dev-shm-usage", "--disable-gpu"] : [],
16826
16925
  ...shouldDisableSandbox() ? ["--no-sandbox"] : [],
16827
16926
  "about:blank"
@@ -16879,6 +16978,45 @@ async function stop(opts, driver) {
16879
16978
  }
16880
16979
  report2(opts, { stopped: true }, [` ${chalk20.green("\u2713")} Browser stopped`]);
16881
16980
  }
16981
+ async function waitForVisualStability(page, maxWaitMs = 300) {
16982
+ let timer;
16983
+ try {
16984
+ const nodeTimeout = new Promise((resolve4) => {
16985
+ timer = setTimeout(resolve4, maxWaitMs + 100);
16986
+ });
16987
+ const browserWait = page.evaluate((maxWait) => {
16988
+ return new Promise((resolve4) => {
16989
+ const timeout = setTimeout(resolve4, maxWait);
16990
+ const done = () => {
16991
+ clearTimeout(timeout);
16992
+ resolve4();
16993
+ };
16994
+ const g = globalThis;
16995
+ if (typeof g.requestAnimationFrame !== "function") {
16996
+ done();
16997
+ return;
16998
+ }
16999
+ g.requestAnimationFrame(() => {
17000
+ g.requestAnimationFrame?.(() => {
17001
+ const animations = g.document?.getAnimations ? g.document.getAnimations().filter((a) => a.playState === "running" && a.finished) : [];
17002
+ if (animations.length === 0) {
17003
+ done();
17004
+ return;
17005
+ }
17006
+ Promise.allSettled(
17007
+ animations.map((a) => a.finished)
17008
+ ).finally(done);
17009
+ });
17010
+ });
17011
+ });
17012
+ }, maxWaitMs).catch(() => {
17013
+ });
17014
+ await Promise.race([browserWait, nodeTimeout]);
17015
+ } catch {
17016
+ } finally {
17017
+ if (timer) clearTimeout(timer);
17018
+ }
17019
+ }
16882
17020
  async function withPage(opts, driver, fn, target) {
16883
17021
  const state = readBrowseState();
16884
17022
  if (!state) {
@@ -16898,9 +17036,9 @@ async function withPage(opts, driver, fn, target) {
16898
17036
  await fn(page);
16899
17037
  await page.waitForLoadState("domcontentloaded").catch(() => {
16900
17038
  });
16901
- await page.waitForTimeout(400);
16902
17039
  let screenshot = null;
16903
17040
  if (opts.shot) {
17041
+ await waitForVisualStability(page);
16904
17042
  screenshot = path22.resolve(opts.shot);
16905
17043
  mkdirSync(path22.dirname(screenshot), { recursive: true });
16906
17044
  await page.screenshot({ path: screenshot, fullPage: Boolean(opts.full) });
@@ -18459,13 +18597,21 @@ function defaultFrontmatter(type2, id, slug, name, opts) {
18459
18597
  url: opts.url,
18460
18598
  authority_level: opts.authority ?? "informational"
18461
18599
  };
18462
- case "scenario":
18600
+ case "scenario": {
18601
+ const stateObj = opts.stateDescription || opts.stateApproximation ? {
18602
+ state: {
18603
+ ...opts.stateDescription ? { description: opts.stateDescription } : {},
18604
+ ...opts.stateApproximation ? { approximation: opts.stateApproximation } : {}
18605
+ }
18606
+ } : {};
18463
18607
  return {
18464
18608
  ...base,
18465
18609
  outline: opts.outline ?? null,
18466
18610
  persona: opts.persona ?? null,
18467
- ...opts.entry ? { entry_path: opts.entry } : {}
18611
+ ...opts.entry ? { entry_path: opts.entry } : {},
18612
+ ...stateObj
18468
18613
  };
18614
+ }
18469
18615
  default:
18470
18616
  return base;
18471
18617
  }
@@ -18859,6 +19005,17 @@ function composeWalkerPrompt(p) {
18859
19005
  "",
18860
19006
  "## First-person brief",
18861
19007
  p.brief ?? "",
19008
+ ...p.context ? ["", "## Product domain & regulatory context", p.context.trim()] : [],
19009
+ ...p.state?.description || p.state?.approximation ? [
19010
+ "",
19011
+ "## Expected state & semantic squinting",
19012
+ p.state.description ? `Expected state: ${p.state.description}` : "",
19013
+ p.state.approximation ? `Approximation strategy: ${p.state.approximation}` : "",
19014
+ "Squinting Guardrails:",
19015
+ "\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.",
19016
+ "\u2022 Squinting does NOT permit drifting to unrelated settings, account profiles, or off-target pages. Stay strictly within the scenario journey.",
19017
+ "\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."
19018
+ ].filter(Boolean) : [],
18862
19019
  ...revisitSection(p.revisit ?? []),
18863
19020
  "",
18864
19021
  "## Access",
@@ -18887,7 +19044,7 @@ function composeWalkerPrompt(p) {
18887
19044
  `Screenshots to: ${rd}/screenshots/step-N.png \u2014 absolute paths.`,
18888
19045
  `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.`,
18889
19046
  "`--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.",
18890
- `Before you finish, write ${rd}/summary.json: {"what_worked": [...], "open_questions": [...]} \u2014 one sentence each, in the persona's language.`,
19047
+ `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.`,
18891
19048
  "",
18892
19049
  "Do not read the product's source code, other iamu/ entity files, or earlier runs: you know only what the persona knows.",
18893
19050
  "",
@@ -19007,7 +19164,8 @@ async function instructionsWalk(opts) {
19007
19164
  const result = {
19008
19165
  scenario: {
19009
19166
  slug: sfm.slug,
19010
- brief: scenario.body.trim() || null
19167
+ brief: scenario.body.trim() || null,
19168
+ state: sfm.state ?? null
19011
19169
  },
19012
19170
  persona: {
19013
19171
  slug: sfm.persona,
@@ -19061,7 +19219,9 @@ async function instructionsWalk(opts) {
19061
19219
  runDirPath,
19062
19220
  projectRoot,
19063
19221
  surface: opts.surface ?? "browse",
19064
- viewport
19222
+ viewport,
19223
+ state: sfm.state ?? null,
19224
+ context: contextYml
19065
19225
  }) : null
19066
19226
  };
19067
19227
  if (opts.json) {
@@ -19073,6 +19233,11 @@ async function instructionsWalk(opts) {
19073
19233
  console.log(` ${chalk26.dim("scenario:")} ${sfm.slug}`);
19074
19234
  console.log(` ${chalk26.dim("persona:")} ${pfm.name} (${pfm.role})`);
19075
19235
  console.log(` ${chalk26.dim("outline:")} ${ofm.title}`);
19236
+ if (sfm.state?.description || sfm.state?.approximation) {
19237
+ console.log(
19238
+ ` ${chalk26.dim("state:")} ${sfm.state.description ? sfm.state.description : sfm.state.approximation ?? ""}`
19239
+ );
19240
+ }
19076
19241
  if (targetUrl) console.log(` ${chalk26.dim("target:")} ${targetUrl}`);
19077
19242
  console.log(
19078
19243
  ` ${chalk26.dim("access:")} ${projectAccess || personaAccess ? `${projectAccess ? "project" : ""}${projectAccess && personaAccess ? " + " : ""}${personaAccess ? "persona" : ""}` : "not recorded (walker would run as whoever is signed in)"}`
@@ -19709,6 +19874,36 @@ async function complete(runId, opts) {
19709
19874
  return;
19710
19875
  }
19711
19876
  const completedAt = pickCompletedAt(run.manifest);
19877
+ const walkerSummary = await readWalkerSummary(run.runDirPath);
19878
+ let judgeReported;
19879
+ try {
19880
+ const verdictsRaw = await fs26.readFile(
19881
+ path26.join(run.runDirPath, "verdicts.json"),
19882
+ "utf8"
19883
+ );
19884
+ const parsed = JSON.parse(verdictsRaw);
19885
+ if (parsed.provenance?.model) {
19886
+ judgeReported = parsed.provenance.model;
19887
+ }
19888
+ } catch {
19889
+ }
19890
+ const stepCount = provenanceAudit.step_count;
19891
+ const durationSec = provenanceAudit.duration_seconds ?? (run.manifest.created_at && Date.parse(completedAt) > Date.parse(run.manifest.created_at) ? Math.round(
19892
+ (Date.parse(completedAt) - Date.parse(run.manifest.created_at)) / 1e3 * 10
19893
+ ) / 10 : null);
19894
+ const velocitySec = provenanceAudit.avg_step_interval_seconds ?? (durationSec != null && stepCount > 1 ? Math.round(durationSec / (stepCount - 1) * 10) / 10 : null);
19895
+ const existingModels = run.manifest.models ?? {};
19896
+ const updatedModels = {
19897
+ ...existingModels,
19898
+ ...walkerSummary.model ? { walker_reported: walkerSummary.model } : {},
19899
+ ...judgeReported ? { judge_reported: judgeReported } : {},
19900
+ ...durationSec != null ? { total_duration_sec: durationSec } : {},
19901
+ ...velocitySec != null ? { velocity_sec_per_step: velocitySec } : {}
19902
+ };
19903
+ run.manifest = {
19904
+ ...run.manifest,
19905
+ models: updatedModels
19906
+ };
19712
19907
  const assembled = await assembleRun(
19713
19908
  run.projectRoot,
19714
19909
  runId,
@@ -19869,6 +20064,17 @@ function printCompletionSummary(run, completedAt, assembled, extras, provenanceA
19869
20064
  console.log(` steps ${count(canonical.steps.length)}`);
19870
20065
  console.log(` started ${chalk30.dim(manifest.created_at)}`);
19871
20066
  console.log(` completed ${chalk30.dim(completedAt)}`);
20067
+ const models = manifest.models;
20068
+ if (models?.velocity_sec_per_step != null) {
20069
+ console.log(
20070
+ ` velocity ${chalk30.cyan(`~${models.velocity_sec_per_step}s/step`)} ${chalk30.dim(`(${models.total_duration_sec}s total)`)}`
20071
+ );
20072
+ }
20073
+ if (models && (models.walker || models.judge || models.matcher)) {
20074
+ const w = models.walker_reported ? `${models.walker} (${models.walker_reported})` : models.walker ?? "fast";
20075
+ const j = models.judge_reported ? `${models.judge} (${models.judge_reported})` : models.judge ?? "thorough";
20076
+ console.log(` models ${chalk30.dim(`walker: ${w} \xB7 judge: ${j}`)}`);
20077
+ }
19872
20078
  console.log();
19873
20079
  console.log(
19874
20080
  ` ${chalk30.dim("canonical:")} ${chalk30.dim(path26.relative(projectRoot, assembled.canonicalPath))}`
@@ -19962,9 +20168,11 @@ async function readWalkerSummary(runDirPath) {
19962
20168
  await fs26.readFile(path26.join(runDirPath, "summary.json"), "utf8")
19963
20169
  );
19964
20170
  const strings = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && x.trim() !== "") : void 0;
20171
+ const model = typeof data.model === "string" && data.model.trim() !== "" ? data.model.trim() : void 0;
19965
20172
  return {
19966
20173
  what_worked: strings(data.what_worked),
19967
- open_questions: strings(data.open_questions)
20174
+ open_questions: strings(data.open_questions),
20175
+ model
19968
20176
  };
19969
20177
  } catch {
19970
20178
  return {};
@@ -20713,6 +20921,12 @@ function registerProjectCommands(program2) {
20713
20921
  ).option("--outline <slug>", "Outline slug (scenario only)").option("--persona <slug>", "Persona slug (scenario only)").option(
20714
20922
  "--entry <path>",
20715
20923
  "Where the situation starts, as a path on the product, e.g. /en/episodes/1 (scenario only)"
20924
+ ).option(
20925
+ "--state-description <text>",
20926
+ "Assumed environment data/records state (scenario only)"
20927
+ ).option(
20928
+ "--state-approximation <text>",
20929
+ "Guidance on how persona should approximate/squint if records are missing (scenario only)"
20716
20930
  ).option("--url <url>", "Document the source cites (source only, required)").option(
20717
20931
  "--authority <level>",
20718
20932
  "legislation | regulation | standard | company_policy | guideline | informational (source only, default informational)"
@@ -20754,6 +20968,15 @@ function registerWalkthroughCommands(program2) {
20754
20968
  ).option(
20755
20969
  "--env <name>",
20756
20970
  "Environment the product runs in (default: the target URL's origin)"
20971
+ ).option(
20972
+ "--walker <tierOrModel>",
20973
+ "Model tier ('fast', 'thorough', 'inherit') or model ID for driving browser navigation (default: fast)"
20974
+ ).option(
20975
+ "--judge <tierOrModel>",
20976
+ "Model tier ('fast', 'thorough', 'inherit') or model ID for human test evaluation (default: thorough)"
20977
+ ).option(
20978
+ "--matcher <tierOrModel>",
20979
+ "Model tier ('fast', 'thorough', 'inherit') or model ID for finding matching (default: thorough)"
20757
20980
  ).option(
20758
20981
  "--check",
20759
20982
  "Enforce target URL reachability (fail fast if server is offline)"