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

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
@@ -248,7 +248,14 @@ var init_capability = __esm({
248
248
  capabilityFrontmatterSchema = entityBaseSchema.extend({
249
249
  /** Human-readable title; corresponds to cloud `name`. */
250
250
  title: z5.string().min(1),
251
- description: z5.string().nullish()
251
+ description: z5.string().nullish(),
252
+ /**
253
+ * Route patterns owned by this capability (e.g. `["/billing", "/invoices"]`).
254
+ * Evaluated with segment-boundary Longest Prefix Match against surface places.
255
+ */
256
+ paths: z5.array(z5.string()).default([]),
257
+ /** Optional visual grouping category (e.g. "Finance", "Administration"). */
258
+ area: z5.string().nullish()
252
259
  });
253
260
  }
254
261
  });
@@ -874,6 +881,58 @@ var init_source = __esm({
874
881
  }
875
882
  });
876
883
 
884
+ // ../../packages/protocol/dist/schemas/frontmatter/testability.js
885
+ import { z as z17 } from "zod";
886
+ function emptyTestability(targetUrl = "http://localhost:3000") {
887
+ return {
888
+ frontmatter: {
889
+ schema_version: SCHEMA_VERSION,
890
+ status: "draft",
891
+ target_url: targetUrl
892
+ },
893
+ body: `# Project Testability & Environment
894
+
895
+ Target URL and execution harness for walkthroughs.
896
+ Audited and populated during /iamu:init.
897
+
898
+ ## Target & Run Instructions
899
+ - Default URL: ${targetUrl}
900
+
901
+ ## Reachable Surfaces
902
+ (Run /iamu:init to discover testable routes)
903
+
904
+ ## Mocks & Data Fixtures
905
+ (Run /iamu:init to inspect existing harnesses)
906
+
907
+ ## Untestable Boundaries
908
+ (None documented yet)
909
+ `
910
+ };
911
+ }
912
+ var testabilityStatusEnum, authStrategyEnum, testabilityFrontmatterSchema;
913
+ var init_testability = __esm({
914
+ "../../packages/protocol/dist/schemas/frontmatter/testability.js"() {
915
+ "use strict";
916
+ init_version();
917
+ testabilityStatusEnum = z17.enum(["draft", "verified"]);
918
+ authStrategyEnum = z17.enum([
919
+ "dev_switcher",
920
+ "interactive",
921
+ "test_accounts",
922
+ "public"
923
+ ]);
924
+ testabilityFrontmatterSchema = z17.object({
925
+ schema_version: z17.number().int().optional(),
926
+ status: testabilityStatusEnum.default("draft"),
927
+ target_url: z17.string().url().describe("Target URL where the product is hosted or served locally."),
928
+ dev_command: z17.string().max(1e3).optional().describe("Dev script or startup command to boot the app locally."),
929
+ auth_strategy: authStrategyEnum.or(z17.string().min(1).max(100)).optional().describe("How personas authenticate in test environments."),
930
+ harnesses: z17.array(z17.string().min(1).max(500)).max(64).optional().describe("Mocking frameworks, seed scripts, or test harnesses present."),
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.")
932
+ });
933
+ }
934
+ });
935
+
877
936
  // ../../packages/protocol/dist/schemas/frontmatter/index.js
878
937
  var ENTITY_TYPES, entityFrontmatterSchemas, ENTITY_DIRS;
879
938
  var init_frontmatter = __esm({
@@ -891,6 +950,7 @@ var init_frontmatter = __esm({
891
950
  init_run_artifacts();
892
951
  init_scenario();
893
952
  init_source();
953
+ init_testability();
894
954
  init_capability();
895
955
  init_finding();
896
956
  init_guidance();
@@ -929,7 +989,7 @@ var init_frontmatter = __esm({
929
989
  });
930
990
 
931
991
  // ../../packages/protocol/dist/device.js
932
- import { z as z17 } from "zod";
992
+ import { z as z18 } from "zod";
933
993
  function deviceFromPersonaTags(tags) {
934
994
  const matches = [];
935
995
  for (const tag of tags ?? []) {
@@ -976,22 +1036,22 @@ var init_device = __esm({
976
1036
  desktop: "desktop",
977
1037
  laptop: "desktop"
978
1038
  };
979
- runViewportSchema = z17.object({
980
- device: z17.enum(DEVICE_CLASSES),
981
- width: z17.number().int().positive(),
982
- height: z17.number().int().positive(),
983
- source: z17.literal("persona-tags")
1039
+ runViewportSchema = z18.object({
1040
+ device: z18.enum(DEVICE_CLASSES),
1041
+ width: z18.number().int().positive(),
1042
+ height: z18.number().int().positive(),
1043
+ source: z18.literal("persona-tags")
984
1044
  });
985
1045
  }
986
1046
  });
987
1047
 
988
1048
  // ../../packages/protocol/dist/types/fingerprint.js
989
- import { z as z18 } from "zod";
1049
+ import { z as z19 } from "zod";
990
1050
  var stepActionTypeEnum, stepActionAnchorSchema, stepActionSchema, networkFingerprintEntrySchema, hex, stepFingerprintSchema;
991
1051
  var init_fingerprint = __esm({
992
1052
  "../../packages/protocol/dist/types/fingerprint.js"() {
993
1053
  "use strict";
994
- stepActionTypeEnum = z18.enum([
1054
+ stepActionTypeEnum = z19.enum([
995
1055
  "click",
996
1056
  "fill",
997
1057
  "hover",
@@ -1001,46 +1061,46 @@ var init_fingerprint = __esm({
1001
1061
  "done",
1002
1062
  "note"
1003
1063
  ]);
1004
- stepActionAnchorSchema = z18.object({
1064
+ stepActionAnchorSchema = z19.object({
1005
1065
  /** Max 512 chars to prevent bloating JSONB storage. */
1006
- role: z18.string().max(512).optional(),
1066
+ role: z19.string().max(512).optional(),
1007
1067
  /** Max 512 chars to prevent bloating JSONB storage. */
1008
- accessibleName: z18.string().max(512).optional(),
1068
+ accessibleName: z19.string().max(512).optional(),
1009
1069
  /** Max 512 chars to prevent bloating JSONB storage. */
1010
- text: z18.string().max(512).optional()
1070
+ text: z19.string().max(512).optional()
1011
1071
  });
1012
- stepActionSchema = z18.object({
1072
+ stepActionSchema = z19.object({
1013
1073
  type: stepActionTypeEnum,
1014
1074
  anchor: stepActionAnchorSchema.optional(),
1015
1075
  /** Max 10 fallback selectors, each capped at 1024 chars. */
1016
- selectorFallbacks: z18.array(z18.string().max(1024)).max(10).optional()
1076
+ selectorFallbacks: z19.array(z19.string().max(1024)).max(10).optional()
1017
1077
  });
1018
- networkFingerprintEntrySchema = z18.object({
1078
+ networkFingerprintEntrySchema = z19.object({
1019
1079
  /** HTTP method, max 16 chars (e.g. "GET", "POST", "OPTIONS"). */
1020
- method: z18.string().max(16),
1080
+ method: z19.string().max(16),
1021
1081
  /** Templated URL pattern, max 2048 chars. */
1022
- urlPattern: z18.string().max(2048),
1023
- status: z18.number().int().nonnegative()
1082
+ urlPattern: z19.string().max(2048),
1083
+ status: z19.number().int().nonnegative()
1024
1084
  });
1025
- hex = (len) => z18.string().regex(new RegExp(`^([0-9a-f]{${len}})?$`), `expected hex string of length ${len} or ""`);
1026
- stepFingerprintSchema = z18.object({
1085
+ hex = (len) => z19.string().regex(new RegExp(`^([0-9a-f]{${len}})?$`), `expected hex string of length ${len} or ""`);
1086
+ stepFingerprintSchema = z19.object({
1027
1087
  /** Full page URL at time of step, max 4096 chars. */
1028
- url: z18.string().max(4096),
1088
+ url: z19.string().max(4096),
1029
1089
  action: stepActionSchema,
1030
1090
  domHash: hex(64),
1031
1091
  axTreeHash: hex(64),
1032
1092
  screenshotPHash: hex(16),
1033
1093
  /** Max 500 network entries to prevent runner abuse bloating JSONB. */
1034
- networkFingerprint: z18.array(networkFingerprintEntrySchema).max(500),
1035
- consoleErrorCount: z18.number().int().nonnegative(),
1036
- jsExceptionCount: z18.number().int().nonnegative(),
1037
- settleDurationMs: z18.number().int().nonnegative()
1094
+ networkFingerprint: z19.array(networkFingerprintEntrySchema).max(500),
1095
+ consoleErrorCount: z19.number().int().nonnegative(),
1096
+ jsExceptionCount: z19.number().int().nonnegative(),
1097
+ settleDurationMs: z19.number().int().nonnegative()
1038
1098
  });
1039
1099
  }
1040
1100
  });
1041
1101
 
1042
1102
  // ../../packages/protocol/dist/schemas/walkthrough-bundle.js
1043
- import { z as z19 } from "zod";
1103
+ import { z as z20 } from "zod";
1044
1104
  function defaultEnvironment(targetUrl) {
1045
1105
  try {
1046
1106
  const origin = new URL(targetUrl).origin;
@@ -1059,147 +1119,147 @@ var init_walkthrough_bundle = __esm({
1059
1119
  init_finding();
1060
1120
  init_run_artifacts();
1061
1121
  init_fingerprint();
1062
- partNameSchema = z19.string().min(1).max(128).regex(/^[a-z][a-z0-9_-]*$/, {
1122
+ partNameSchema = z20.string().min(1).max(128).regex(/^[a-z][a-z0-9_-]*$/, {
1063
1123
  message: "part_name must be kebab-or-snake-case ASCII starting with a letter"
1064
1124
  }).describe("Key of the matching multipart form field in the bundle body. Must match a binary part exactly.");
1065
- artifactRefSchema = z19.object({
1066
- kind: z19.enum(["screenshot", "report", "other"]).describe("Artifact family. 'screenshot' streams to files/walkthroughs/<run_id>/step-<seq>.<ext>; 'report' / 'other' reserved for future kinds."),
1125
+ artifactRefSchema = z20.object({
1126
+ kind: z20.enum(["screenshot", "report", "other"]).describe("Artifact family. 'screenshot' streams to files/walkthroughs/<run_id>/step-<seq>.<ext>; 'report' / 'other' reserved for future kinds."),
1067
1127
  part_name: partNameSchema,
1068
- filename: z19.string().min(1).max(256).describe("Original filename as the client saw it. Used for logging and as a fallback display label."),
1069
- content_type: z19.string().min(1).max(128).describe("MIME type. Screenshots MUST use one of image/png | image/jpeg | image/webp \u2014 the cloud handler enforces that subset separately."),
1070
- bytes_hash: z19.string().length(64).regex(/^[0-9a-f]+$/).optional().describe("Lowercase hex sha256 of the binary bytes. Optional in v1; client populates when available so cloud can detect transport corruption.")
1128
+ filename: z20.string().min(1).max(256).describe("Original filename as the client saw it. Used for logging and as a fallback display label."),
1129
+ content_type: z20.string().min(1).max(128).describe("MIME type. Screenshots MUST use one of image/png | image/jpeg | image/webp \u2014 the cloud handler enforces that subset separately."),
1130
+ bytes_hash: z20.string().length(64).regex(/^[0-9a-f]+$/).optional().describe("Lowercase hex sha256 of the binary bytes. Optional in v1; client populates when available so cloud can detect transport corruption.")
1071
1131
  });
1072
- stepScreenshotRefSchema = z19.object({
1132
+ stepScreenshotRefSchema = z20.object({
1073
1133
  part_name: partNameSchema,
1074
- filename: z19.string().min(1).max(256),
1075
- content_type: z19.string().min(1).max(128)
1134
+ filename: z20.string().min(1).max(256),
1135
+ content_type: z20.string().min(1).max(128)
1076
1136
  });
1077
- bundleStepSchema = z19.object({
1078
- seq: z19.number().int().min(1).describe("1-based step sequence. Persisted to `walkthrough_steps.bundle_order` so render order survives DB insert-order drift."),
1079
- title: z19.string().min(1).max(2048).describe("Short first-person heading for the step \u2014 shows in the rendered report."),
1080
- persona_reasoning: z19.string().max(65536).nullable().optional().describe("First-person inner voice capture. Nullable for terse / action-only steps."),
1081
- grounding: z19.string().max(65536).nullable().optional().describe("Optional citations, domain-knowledge grounding, or verification notes for this step."),
1137
+ bundleStepSchema = z20.object({
1138
+ seq: z20.number().int().min(1).describe("1-based step sequence. Persisted to `walkthrough_steps.bundle_order` so render order survives DB insert-order drift."),
1139
+ title: z20.string().min(1).max(2048).describe("Short first-person heading for the step \u2014 shows in the rendered report."),
1140
+ persona_reasoning: z20.string().max(65536).nullable().optional().describe("First-person inner voice capture. Nullable for terse / action-only steps."),
1141
+ grounding: z20.string().max(65536).nullable().optional().describe("Optional citations, domain-knowledge grounding, or verification notes for this step."),
1082
1142
  created_at: isoTimestamp.describe("Client-side ISO timestamp. Trusted as-is by the cloud (runner writes cloud-side already trust the runner's clock \u2014 same invariant)."),
1083
1143
  fingerprint: stepFingerprintSchema.nullable().optional().describe("Full StepFingerprint object (dom/ax/screenshot hashes + network + console signals). Passed through verbatim to walkthrough_steps.fingerprint; runtime shape validated by the fingerprint package on write."),
1084
1144
  screenshot_ref: stepScreenshotRefSchema.nullable().optional().describe("When present, identifies the multipart binary part carrying this step's screenshot bytes."),
1085
- url: z19.string().min(1).max(STEP_URL_MAX_LENGTH).nullable().optional().describe("The page the persona was on when the step ended. Absent for runs recorded before steps carried a URL."),
1086
- surface: z19.string().min(1).max(STEP_SURFACE_MAX_LENGTH).nullable().optional().describe("The walker's short name for the screen (e.g. 'search results'). Optional.")
1145
+ url: z20.string().min(1).max(STEP_URL_MAX_LENGTH).nullable().optional().describe("The page the persona was on when the step ended. Absent for runs recorded before steps carried a URL."),
1146
+ surface: z20.string().min(1).max(STEP_SURFACE_MAX_LENGTH).nullable().optional().describe("The walker's short name for the screen (e.g. 'search results'). Optional.")
1087
1147
  });
1088
- bundleFindingSchema = z19.object({
1148
+ bundleFindingSchema = z20.object({
1089
1149
  slug: slugSchema,
1090
1150
  frontmatter: findingFrontmatterSchema,
1091
- body: z19.string().max(1048576).describe("Full markdown body of the finding file. 1 MiB cap matches the entity import envelope."),
1092
- screenshot_refs: z19.array(stepScreenshotRefSchema).max(32).optional().describe("Optional per-finding screenshot parts (distinct from step screenshots \u2014 e.g. annotated evidence shots attached to the finding write-up).")
1151
+ body: z20.string().max(1048576).describe("Full markdown body of the finding file. 1 MiB cap matches the entity import envelope."),
1152
+ screenshot_refs: z20.array(stepScreenshotRefSchema).max(32).optional().describe("Optional per-finding screenshot parts (distinct from step screenshots \u2014 e.g. annotated evidence shots attached to the finding write-up).")
1093
1153
  });
1094
- verificationItemSchema = z19.object({
1095
- canonical_id: z19.string().max(64).optional(),
1096
- id: z19.string().max(64).optional(),
1097
- title: z19.string().max(1024),
1098
- type: z19.string().max(32).optional()
1154
+ verificationItemSchema = z20.object({
1155
+ canonical_id: z20.string().max(64).optional(),
1156
+ id: z20.string().max(64).optional(),
1157
+ title: z20.string().max(1024),
1158
+ type: z20.string().max(32).optional()
1099
1159
  });
1100
- verificationGroup = z19.array(verificationItemSchema).max(500);
1101
- bundleVerificationSchema = z19.object({
1102
- baseline_run_id: z19.string().max(256),
1160
+ verificationGroup = z20.array(verificationItemSchema).max(500);
1161
+ bundleVerificationSchema = z20.object({
1162
+ baseline_run_id: z20.string().max(256),
1103
1163
  resolved: verificationGroup.default([]),
1104
1164
  persists: verificationGroup.default([]),
1105
1165
  not_observed: verificationGroup.default([]),
1106
1166
  uncertain: verificationGroup.default([]),
1107
1167
  new_findings: verificationGroup.default([])
1108
1168
  });
1109
- buildMetadataSchema = z19.object({
1110
- label: z19.string().min(1).max(200).optional().describe("Human label for the build, from `iamu start-run --build`."),
1111
- git_sha: z19.string().regex(/^[0-9a-f]{7,64}$/i, { message: "git_sha must be a hex commit id" }).optional().describe("Commit of the product under test."),
1112
- git_branch: z19.string().min(1).max(255).optional(),
1113
- base_sha: z19.string().regex(/^[0-9a-f]{7,64}$/i, {
1169
+ buildMetadataSchema = z20.object({
1170
+ label: z20.string().min(1).max(200).optional().describe("Human label for the build, from `iamu start-run --build`."),
1171
+ git_sha: z20.string().regex(/^[0-9a-f]{7,64}$/i, { message: "git_sha must be a hex commit id" }).optional().describe("Commit of the product under test."),
1172
+ git_branch: z20.string().min(1).max(255).optional(),
1173
+ base_sha: z20.string().regex(/^[0-9a-f]{7,64}$/i, {
1114
1174
  message: "base_sha must be a hex commit id"
1115
1175
  }).optional().describe("Commit the change is compared against (pull-request base)."),
1116
- git_dirty: z19.boolean().optional().describe("True when the working tree had changes outside `iamu/` (tracked edits vs HEAD, staged or not, or untracked non-ignored files). Absent outside git."),
1117
- worktree_hash: z19.string().regex(/^[0-9a-f]{12}$/, {
1176
+ git_dirty: z20.boolean().optional().describe("True when the working tree had changes outside `iamu/` (tracked edits vs HEAD, staged or not, or untracked non-ignored files). Absent outside git."),
1177
+ worktree_hash: z20.string().regex(/^[0-9a-f]{12}$/, {
1118
1178
  message: "worktree_hash must be 12 lowercase hex characters"
1119
1179
  }).optional().describe("Only when `git_dirty`: fingerprint of the uncommitted changes, so two runs on one commit with different edits are different builds."),
1120
- ci: z19.object({
1121
- provider: z19.string().min(1).max(64).describe("CI system that ran the walk, e.g. `github`, `azure`."),
1122
- run_url: z19.string().url().max(2048)
1180
+ ci: z20.object({
1181
+ provider: z20.string().min(1).max(64).describe("CI system that ran the walk, e.g. `github`, `azure`."),
1182
+ run_url: z20.string().url().max(2048)
1123
1183
  }).optional()
1124
1184
  });
1125
1185
  ENVIRONMENT_MAX_LENGTH = 255;
1126
- bundleManifestSchema = z19.object({
1127
- run_id: z19.string().uuid().describe("Client-minted UUID. Becomes `walkthroughs.id` on cloud. Idempotent key for the push endpoint."),
1128
- scenario_id: z19.string().min(1).describe("Scenario reference \u2014 slug in local-only mode, UUID in cloud-linked mode. Cloud handler resolves to the tenant's scenarios.id."),
1129
- target_url: z19.string().url().max(2048),
1130
- lenses: z19.array(z19.string().min(1).max(64)).min(1).max(16).describe("Analysis lenses enabled for this run. Defaults to ['persona'] in most flows."),
1131
- status: z19.literal("completed").describe("v1 only accepts completed bundles (bundle-on-complete). In-progress runs are not pushable."),
1132
- client_origin: z19.literal("local").describe("Discriminator written to walkthroughs.client_origin. Reserved for `local` in this endpoint; runner-origin rows take a different code path."),
1186
+ bundleManifestSchema = z20.object({
1187
+ run_id: z20.string().uuid().describe("Client-minted UUID. Becomes `walkthroughs.id` on cloud. Idempotent key for the push endpoint."),
1188
+ scenario_id: z20.string().min(1).describe("Scenario reference \u2014 slug in local-only mode, UUID in cloud-linked mode. Cloud handler resolves to the tenant's scenarios.id."),
1189
+ target_url: z20.string().url().max(2048),
1190
+ lenses: z20.array(z20.string().min(1).max(64)).min(1).max(16).describe("Analysis lenses enabled for this run. Defaults to ['persona'] in most flows."),
1191
+ status: z20.literal("completed").describe("v1 only accepts completed bundles (bundle-on-complete). In-progress runs are not pushable."),
1192
+ client_origin: z20.literal("local").describe("Discriminator written to walkthroughs.client_origin. Reserved for `local` in this endpoint; runner-origin rows take a different code path."),
1133
1193
  created_at: isoTimestamp,
1134
1194
  completed_at: isoTimestamp,
1135
- step_count: z19.number().int().min(0).max(1e4),
1136
- finding_count: z19.number().int().min(0).max(1e4),
1137
- report_markdown: z19.string().max(1048576).describe("report.md body verbatim. Persists to walkthroughs.report_markdown so dashboard can render without regenerating."),
1138
- what_worked: z19.array(z19.string().max(4096)).max(64).optional(),
1139
- open_questions: z19.array(z19.string().max(4096)).max(64).optional(),
1195
+ step_count: z20.number().int().min(0).max(1e4),
1196
+ finding_count: z20.number().int().min(0).max(1e4),
1197
+ report_markdown: z20.string().max(1048576).describe("report.md body verbatim. Persists to walkthroughs.report_markdown so dashboard can render without regenerating."),
1198
+ what_worked: z20.array(z20.string().max(4096)).max(64).optional(),
1199
+ open_questions: z20.array(z20.string().max(4096)).max(64).optional(),
1140
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."),
1141
1201
  build: buildMetadataSchema.optional().describe("Build of the product under test (git SHA, branch, CI run). Absent for runs started before `start-run` recorded it."),
1142
- environment: z19.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
+ 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`)."),
1143
1203
  /** Holistic UX quality score from UX review pass. Present only when `lenses` includes "ux". */
1144
- ux_quality_score: z19.number().min(1).max(5).nullish(),
1204
+ ux_quality_score: z20.number().min(1).max(5).nullish(),
1145
1205
  /** One-line rationale naming 1-2 structural factors that determined the score. */
1146
- ux_quality_rationale: z19.string().max(4096).nullish(),
1147
- verdict: z19.array(z19.object({
1148
- humanTestId: z19.string().optional(),
1149
- human_test_id: z19.string().optional(),
1150
- question: z19.string().default(""),
1151
- rating: z19.enum(["yes", "no", "partial", "not_applicable"]).optional(),
1152
- verdict: z19.enum(["yes", "no", "partial", "not_applicable"]).optional(),
1153
- reasoning: z19.string(),
1154
- groundingStepIds: z19.array(z19.string()).optional(),
1155
- grounding_step_ids: z19.array(z19.string()).optional(),
1156
- groundingFindingIds: z19.array(z19.string()).optional(),
1157
- grounding_finding_ids: z19.array(z19.string()).optional()
1206
+ ux_quality_rationale: z20.string().max(4096).nullish(),
1207
+ verdict: z20.array(z20.object({
1208
+ humanTestId: z20.string().optional(),
1209
+ human_test_id: z20.string().optional(),
1210
+ question: z20.string().default(""),
1211
+ rating: z20.enum(["yes", "no", "partial", "not_applicable"]).optional(),
1212
+ verdict: z20.enum(["yes", "no", "partial", "not_applicable"]).optional(),
1213
+ reasoning: z20.string(),
1214
+ groundingStepIds: z20.array(z20.string()).optional(),
1215
+ grounding_step_ids: z20.array(z20.string()).optional(),
1216
+ groundingFindingIds: z20.array(z20.string()).optional(),
1217
+ grounding_finding_ids: z20.array(z20.string()).optional()
1158
1218
  }).refine((v) => Boolean(v.humanTestId || v.human_test_id) && Boolean(v.rating || v.verdict), {
1159
1219
  message: "verdict item must include humanTestId or human_test_id, and rating or verdict"
1160
1220
  })).optional(),
1161
- judge_meta: z19.object({
1162
- overall: z19.object({
1163
- question: z19.string(),
1164
- answer: z19.string(),
1165
- reasoning: z19.string()
1221
+ judge_meta: z20.object({
1222
+ overall: z20.object({
1223
+ question: z20.string(),
1224
+ answer: z20.string(),
1225
+ reasoning: z20.string()
1166
1226
  }),
1167
- provenance: z19.object({
1168
- judge_prompt: z19.string().optional(),
1169
- judge_model: z19.string().optional()
1227
+ provenance: z20.object({
1228
+ judge_prompt: z20.string().optional(),
1229
+ judge_model: z20.string().optional()
1170
1230
  }).optional()
1171
1231
  }).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()
1232
+ models: z20.object({
1233
+ walker: z20.string().optional(),
1234
+ matcher: z20.string().optional(),
1235
+ judge: z20.string().optional(),
1236
+ walker_reported: z20.string().optional(),
1237
+ matcher_reported: z20.string().optional(),
1238
+ judge_reported: z20.string().optional(),
1239
+ walker_tier: z20.string().optional(),
1240
+ matcher_tier: z20.string().optional(),
1241
+ judge_tier: z20.string().optional(),
1242
+ execution_mode: z20.enum(["multi_agent", "single_agent"]).optional(),
1243
+ velocity_sec_per_step: z20.number().optional(),
1244
+ total_duration_sec: z20.number().optional()
1185
1245
  }).optional().describe("AI model configuration and reported runtime provenance for this walkthrough.")
1186
1246
  });
1187
- walkthroughBundleSchema = z19.object({
1247
+ walkthroughBundleSchema = z20.object({
1188
1248
  manifest: bundleManifestSchema,
1189
- steps: z19.array(bundleStepSchema).max(1e4),
1190
- findings: z19.array(bundleFindingSchema).max(1e4),
1191
- artifacts: z19.array(artifactRefSchema).max(1e4)
1249
+ steps: z20.array(bundleStepSchema).max(1e4),
1250
+ findings: z20.array(bundleFindingSchema).max(1e4),
1251
+ artifacts: z20.array(artifactRefSchema).max(1e4)
1192
1252
  }).superRefine((bundle, ctx) => {
1193
1253
  if (bundle.manifest.step_count !== bundle.steps.length) {
1194
1254
  ctx.addIssue({
1195
- code: z19.ZodIssueCode.custom,
1255
+ code: z20.ZodIssueCode.custom,
1196
1256
  path: ["manifest", "step_count"],
1197
1257
  message: `manifest.step_count (${bundle.manifest.step_count}) must equal steps.length (${bundle.steps.length})`
1198
1258
  });
1199
1259
  }
1200
1260
  if (bundle.manifest.finding_count !== bundle.findings.length) {
1201
1261
  ctx.addIssue({
1202
- code: z19.ZodIssueCode.custom,
1262
+ code: z20.ZodIssueCode.custom,
1203
1263
  path: ["manifest", "finding_count"],
1204
1264
  message: `manifest.finding_count (${bundle.manifest.finding_count}) must equal findings.length (${bundle.findings.length})`
1205
1265
  });
@@ -1208,14 +1268,14 @@ var init_walkthrough_bundle = __esm({
1208
1268
  const expected = Array.from({ length: bundle.steps.length }, (_, i) => i + 1);
1209
1269
  if (seqs.join(",") !== expected.join(",")) {
1210
1270
  ctx.addIssue({
1211
- code: z19.ZodIssueCode.custom,
1271
+ code: z20.ZodIssueCode.custom,
1212
1272
  path: ["steps"],
1213
1273
  message: `step seq values must be dense 1..${bundle.steps.length}; received [${seqs.join(",")}]`
1214
1274
  });
1215
1275
  }
1216
1276
  if (bundle.steps.length === 0 && bundle.artifacts.length > 0) {
1217
1277
  ctx.addIssue({
1218
- code: z19.ZodIssueCode.custom,
1278
+ code: z20.ZodIssueCode.custom,
1219
1279
  path: ["steps"],
1220
1280
  message: `bundle declares ${bundle.artifacts.length} artifact(s) but no steps \u2014 step records were not read`
1221
1281
  });
@@ -1225,7 +1285,7 @@ var init_walkthrough_bundle = __esm({
1225
1285
  for (const name of partNames) {
1226
1286
  if (seen.has(name)) {
1227
1287
  ctx.addIssue({
1228
- code: z19.ZodIssueCode.custom,
1288
+ code: z20.ZodIssueCode.custom,
1229
1289
  path: ["artifacts"],
1230
1290
  message: `duplicate artifact part_name: ${name}`
1231
1291
  });
@@ -1237,7 +1297,7 @@ var init_walkthrough_bundle = __esm({
1237
1297
  for (const [i, step] of bundle.steps.entries()) {
1238
1298
  if (step.screenshot_ref && !artifactPartNames.has(step.screenshot_ref.part_name)) {
1239
1299
  ctx.addIssue({
1240
- code: z19.ZodIssueCode.custom,
1300
+ code: z20.ZodIssueCode.custom,
1241
1301
  path: ["steps", i, "screenshot_ref", "part_name"],
1242
1302
  message: `step[${step.seq}].screenshot_ref.part_name "${step.screenshot_ref.part_name}" is not declared in artifacts[]`
1243
1303
  });
@@ -1247,7 +1307,7 @@ var init_walkthrough_bundle = __esm({
1247
1307
  for (const [j, ref] of (finding.screenshot_refs ?? []).entries()) {
1248
1308
  if (!artifactPartNames.has(ref.part_name)) {
1249
1309
  ctx.addIssue({
1250
- code: z19.ZodIssueCode.custom,
1310
+ code: z20.ZodIssueCode.custom,
1251
1311
  path: ["findings", i, "screenshot_refs", j, "part_name"],
1252
1312
  message: `finding[${finding.slug}].screenshot_refs[${j}].part_name "${ref.part_name}" is not declared in artifacts[]`
1253
1313
  });
@@ -1257,7 +1317,7 @@ var init_walkthrough_bundle = __esm({
1257
1317
  for (const [i, finding] of bundle.findings.entries()) {
1258
1318
  if (finding.frontmatter.scenario !== bundle.manifest.scenario_id) {
1259
1319
  ctx.addIssue({
1260
- code: z19.ZodIssueCode.custom,
1320
+ code: z20.ZodIssueCode.custom,
1261
1321
  path: ["findings", i, "frontmatter", "scenario"],
1262
1322
  message: `finding.frontmatter.scenario "${finding.frontmatter.scenario}" must match manifest.scenario_id "${bundle.manifest.scenario_id}"`
1263
1323
  });
@@ -1266,7 +1326,7 @@ var init_walkthrough_bundle = __esm({
1266
1326
  for (const [i, finding] of bundle.findings.entries()) {
1267
1327
  if (finding.frontmatter.run !== bundle.manifest.run_id) {
1268
1328
  ctx.addIssue({
1269
- code: z19.ZodIssueCode.custom,
1329
+ code: z20.ZodIssueCode.custom,
1270
1330
  path: ["findings", i, "frontmatter", "run"],
1271
1331
  message: `finding.frontmatter.run "${finding.frontmatter.run}" must match manifest.run_id "${bundle.manifest.run_id}"`
1272
1332
  });
@@ -1278,30 +1338,30 @@ var init_walkthrough_bundle = __esm({
1278
1338
  "updated",
1279
1339
  "unchanged"
1280
1340
  ];
1281
- walkthroughPushResponseSchema = z19.object({
1282
- run_id: z19.string().uuid(),
1283
- cloud_walkthrough_id: z19.string().uuid(),
1284
- status: z19.enum(WALKTHROUGH_PUSH_OUTCOMES),
1285
- step_count: z19.number().int().min(0),
1286
- finding_count: z19.number().int().min(0),
1287
- artifact_count: z19.number().int().min(0)
1341
+ walkthroughPushResponseSchema = z20.object({
1342
+ run_id: z20.string().uuid(),
1343
+ cloud_walkthrough_id: z20.string().uuid(),
1344
+ status: z20.enum(WALKTHROUGH_PUSH_OUTCOMES),
1345
+ step_count: z20.number().int().min(0),
1346
+ finding_count: z20.number().int().min(0),
1347
+ artifact_count: z20.number().int().min(0)
1288
1348
  });
1289
1349
  WALKTHROUGH_CONFLICT_REASONS = [
1290
1350
  "project_mismatch",
1291
1351
  "scenario_mismatch",
1292
1352
  "step_count_mismatch"
1293
1353
  ];
1294
- walkthroughPushConflictSchema = z19.object({
1295
- code: z19.literal("walkthrough_conflict"),
1296
- reason: z19.enum(WALKTHROUGH_CONFLICT_REASONS),
1297
- run_id: z19.string().uuid(),
1298
- message: z19.string()
1354
+ walkthroughPushConflictSchema = z20.object({
1355
+ code: z20.literal("walkthrough_conflict"),
1356
+ reason: z20.enum(WALKTHROUGH_CONFLICT_REASONS),
1357
+ run_id: z20.string().uuid(),
1358
+ message: z20.string()
1299
1359
  });
1300
1360
  }
1301
1361
  });
1302
1362
 
1303
1363
  // ../../packages/protocol/dist/types/walkthrough-canonical.js
1304
- import { z as z20 } from "zod";
1364
+ import { z as z21 } from "zod";
1305
1365
  var CANONICAL_WALKTHROUGH_VERSION, canonicalScenarioRefSchema, canonicalTargetSchema, canonicalProvenanceSchema, canonicalStepSchema, canonicalFindingSchema, canonicalHumanTestSchema, canonicalHumanTestOverallSchema, canonicalErrorSchema, cloudMirrorStateSchema, canonicalWalkthroughSchema;
1306
1366
  var init_walkthrough_canonical = __esm({
1307
1367
  "../../packages/protocol/dist/types/walkthrough-canonical.js"() {
@@ -1311,106 +1371,106 @@ var init_walkthrough_canonical = __esm({
1311
1371
  init_walkthrough_bundle();
1312
1372
  init_enums();
1313
1373
  CANONICAL_WALKTHROUGH_VERSION = "1.0.0";
1314
- canonicalScenarioRefSchema = z20.object({
1315
- id: z20.string(),
1316
- slug: z20.string(),
1317
- persona_slug: z20.string(),
1318
- outline_slug: z20.string(),
1319
- brief: z20.string()
1374
+ canonicalScenarioRefSchema = z21.object({
1375
+ id: z21.string(),
1376
+ slug: z21.string(),
1377
+ persona_slug: z21.string(),
1378
+ outline_slug: z21.string(),
1379
+ brief: z21.string()
1320
1380
  }).passthrough();
1321
- canonicalTargetSchema = z20.object({
1322
- url: z20.string(),
1381
+ canonicalTargetSchema = z21.object({
1382
+ url: z21.string(),
1323
1383
  /** Where the product runs. "live" = real backend; "mock" = stubbed. */
1324
- backend_mode: z20.enum(["live", "mock", "unknown"]),
1384
+ backend_mode: z21.enum(["live", "mock", "unknown"]),
1325
1385
  /** Browser / runner used for the walk. */
1326
- browser: z20.string()
1386
+ browser: z21.string()
1327
1387
  }).passthrough();
1328
- canonicalProvenanceSchema = z20.object({
1329
- walker_prompt: z20.object({ id: z20.string(), version: z20.string() }).passthrough(),
1330
- judge_prompt: z20.object({ id: z20.string(), version: z20.string() }).passthrough().optional(),
1331
- judge_mode: z20.enum(["subagent", "same_agent_second_pass", "failed"]).optional(),
1388
+ canonicalProvenanceSchema = z21.object({
1389
+ walker_prompt: z21.object({ id: z21.string(), version: z21.string() }).passthrough(),
1390
+ judge_prompt: z21.object({ id: z21.string(), version: z21.string() }).passthrough().optional(),
1391
+ judge_mode: z21.enum(["subagent", "same_agent_second_pass", "failed"]).optional(),
1332
1392
  /** Model identifier used by the walker. Judge model (when it runs) is
1333
1393
  * stamped inside `judge_meta.provenance.model` on the DB row. */
1334
- model: z20.string(),
1394
+ model: z21.string(),
1335
1395
  started_at: isoTimestamp,
1336
1396
  completed_at: isoTimestamp
1337
1397
  }).passthrough();
1338
- canonicalStepSchema = z20.object({
1339
- id: z20.string(),
1340
- step_number: z20.number().int(),
1341
- url: z20.string().optional(),
1398
+ canonicalStepSchema = z21.object({
1399
+ id: z21.string(),
1400
+ step_number: z21.number().int(),
1401
+ url: z21.string().optional(),
1342
1402
  /** The walker's short name for the screen. */
1343
- surface: z20.string().optional(),
1344
- screen_title: z20.string().optional(),
1403
+ surface: z21.string().optional(),
1404
+ screen_title: z21.string().optional(),
1345
1405
  /** Path reference to the screenshot file; never inline binary data. */
1346
- screenshot_ref: z20.string().nullable().optional(),
1347
- persona_reasoning: z20.string().nullable().optional(),
1348
- grounding: z20.string().nullable().optional(),
1349
- action: z20.string().optional()
1406
+ screenshot_ref: z21.string().nullable().optional(),
1407
+ persona_reasoning: z21.string().nullable().optional(),
1408
+ grounding: z21.string().nullable().optional(),
1409
+ action: z21.string().optional()
1350
1410
  }).passthrough();
1351
- canonicalFindingSchema = z20.object({
1352
- id: z20.string(),
1411
+ canonicalFindingSchema = z21.object({
1412
+ id: z21.string(),
1353
1413
  /** Stable across runs for pattern tracking — null until cross-run wiring lands. */
1354
- canonical_id: z20.string().nullable(),
1414
+ canonical_id: z21.string().nullable(),
1355
1415
  type: findingTypeEnum,
1356
- severity: z20.enum(["critical", "major", "minor", "observation"]).nullable(),
1416
+ severity: z21.enum(["critical", "major", "minor", "observation"]).nullable(),
1357
1417
  /** Depth-gate half 1: what the UI showed or did (observable without domain knowledge). */
1358
- ui_fact: z20.string().nullable(),
1418
+ ui_fact: z21.string().nullable(),
1359
1419
  /** Depth-gate half 2: what it means for the persona. Null on legacy rows (pre-migration). */
1360
- consequence: z20.string().nullable(),
1361
- recommendation: z20.string().nullable(),
1420
+ consequence: z21.string().nullable(),
1421
+ recommendation: z21.string().nullable(),
1362
1422
  /** Step ids this finding cites as evidence (references `steps[].id`). */
1363
- linked_step_ids: z20.array(z20.string()),
1423
+ linked_step_ids: z21.array(z21.string()),
1364
1424
  /** Parent scenario's human-test question id the finding bears on, if any. */
1365
- linked_human_test_id: z20.string().nullable(),
1366
- compliance_impact: z20.enum(["critical", "major", "minor", "informational"]).nullable().optional(),
1367
- task_impact: z20.enum(["blocks", "impairs", "slows", "neutral", "accelerates", "enables"]).nullable().optional(),
1368
- confidence: z20.enum(["high", "medium", "low"]).nullable().optional(),
1369
- regulatory_weight: z20.enum(["hard_requirement", "process_model", "best_practice"]).nullable().optional(),
1370
- citations: z20.array(z20.string()).optional()
1425
+ linked_human_test_id: z21.string().nullable(),
1426
+ compliance_impact: z21.enum(["critical", "major", "minor", "informational"]).nullable().optional(),
1427
+ task_impact: z21.enum(["blocks", "impairs", "slows", "neutral", "accelerates", "enables"]).nullable().optional(),
1428
+ confidence: z21.enum(["high", "medium", "low"]).nullable().optional(),
1429
+ regulatory_weight: z21.enum(["hard_requirement", "process_model", "best_practice"]).nullable().optional(),
1430
+ citations: z21.array(z21.string()).optional()
1371
1431
  }).passthrough();
1372
- canonicalHumanTestSchema = z20.object({
1373
- id: z20.string(),
1374
- question: z20.string(),
1432
+ canonicalHumanTestSchema = z21.object({
1433
+ id: z21.string(),
1434
+ question: z21.string(),
1375
1435
  /** Null until judge stage writes via `record_judge_result`. */
1376
- verdict: z20.enum(["yes", "partial", "no", "not_applicable"]).nullable(),
1377
- reasoning: z20.string().nullable(),
1378
- grounding_step_ids: z20.array(z20.string()),
1379
- grounding_finding_ids: z20.array(z20.string())
1436
+ verdict: z21.enum(["yes", "partial", "no", "not_applicable"]).nullable(),
1437
+ reasoning: z21.string().nullable(),
1438
+ grounding_step_ids: z21.array(z21.string()),
1439
+ grounding_finding_ids: z21.array(z21.string())
1380
1440
  }).passthrough();
1381
- canonicalHumanTestOverallSchema = z20.object({
1382
- question: z20.string(),
1441
+ canonicalHumanTestOverallSchema = z21.object({
1442
+ question: z21.string(),
1383
1443
  /** Null until judge stage answers. */
1384
- answer: z20.string().nullable(),
1385
- reasoning: z20.string().nullable()
1444
+ answer: z21.string().nullable(),
1445
+ reasoning: z21.string().nullable()
1386
1446
  }).passthrough();
1387
- canonicalErrorSchema = z20.object({
1388
- stage: z20.string(),
1389
- message: z20.string(),
1390
- at: z20.string()
1447
+ canonicalErrorSchema = z21.object({
1448
+ stage: z21.string(),
1449
+ message: z21.string(),
1450
+ at: z21.string()
1391
1451
  }).passthrough();
1392
- cloudMirrorStateSchema = z20.enum([
1452
+ cloudMirrorStateSchema = z21.enum([
1393
1453
  "pushed",
1394
1454
  "enqueued",
1395
1455
  "local-only",
1396
1456
  "already_pushed",
1397
1457
  "skipped"
1398
1458
  ]);
1399
- canonicalWalkthroughSchema = z20.object({
1400
- canonical_version: z20.literal(CANONICAL_WALKTHROUGH_VERSION),
1401
- run_id: z20.string(),
1459
+ canonicalWalkthroughSchema = z21.object({
1460
+ canonical_version: z21.literal(CANONICAL_WALKTHROUGH_VERSION),
1461
+ run_id: z21.string(),
1402
1462
  scenario: canonicalScenarioRefSchema,
1403
1463
  target: canonicalTargetSchema,
1404
1464
  provenance: canonicalProvenanceSchema,
1405
1465
  build: buildMetadataSchema.optional(),
1406
1466
  viewport: runViewportSchema.optional(),
1407
- environment: z20.string().optional(),
1408
- models: z20.record(z20.unknown()).optional(),
1409
- findings: z20.array(canonicalFindingSchema),
1410
- steps: z20.array(canonicalStepSchema),
1411
- human_tests: z20.array(canonicalHumanTestSchema),
1467
+ environment: z21.string().optional(),
1468
+ models: z21.record(z21.unknown()).optional(),
1469
+ findings: z21.array(canonicalFindingSchema),
1470
+ steps: z21.array(canonicalStepSchema),
1471
+ human_tests: z21.array(canonicalHumanTestSchema),
1412
1472
  human_test_overall: canonicalHumanTestOverallSchema,
1413
- errors: z20.array(canonicalErrorSchema),
1473
+ errors: z21.array(canonicalErrorSchema),
1414
1474
  cloud_mirror_state: cloudMirrorStateSchema
1415
1475
  }).passthrough();
1416
1476
  }
@@ -1821,7 +1881,7 @@ var init_prompt_registry = __esm({
1821
1881
  });
1822
1882
 
1823
1883
  // ../../packages/protocol/dist/mcp-schemas.js
1824
- import { z as z21 } from "zod";
1884
+ import { z as z22 } from "zod";
1825
1885
  var AuthorityLevelSchema, FindingTypeSchema, MirrorStatusSchema, FindingSummarySchema, ScenarioSummarySchema, RetrievalGapSchema, GetConstraintsDataSchema, GetConstraintsOutputSchema, QueryDataSchema, QueryOutputSchema, ListFindingsDataSchema, ListFindingsOutputSchema, ListScenariosDataSchema, ListScenariosOutputSchema, GetProjectDataSchema, GetProjectOutputSchema, CreateScenarioDataSchema, CreateScenarioOutputSchema, PrepareWalkthroughDataSchema, PrepareWalkthroughOutputSchema, PersonaSummarySchema, OutlineSummarySchema, CapabilitySummarySchema, ListPersonasDataSchema, ListPersonasOutputSchema, ListOutlinesDataSchema, ListOutlinesOutputSchema, ListCapabilitiesDataSchema, ListCapabilitiesOutputSchema, GetPersonaDataSchema, GetPersonaOutputSchema, GetOutlineDataSchema, GetOutlineOutputSchema, GetFindingDataSchema, GetFindingOutputSchema, CreatePersonaDataSchema, CreatePersonaOutputSchema, CreateOutlineDataSchema, CreateOutlineOutputSchema, CreateCapabilityDataSchema, CreateCapabilityOutputSchema, RecordStepDataSchema, RecordStepOutputSchema, RecordFindingDataSchema, RecordFindingOutputSchema, CompleteWalkthroughDataSchema, CompleteWalkthroughOutputSchema, GetJudgeInputDataSchema, GetJudgeInputOutputSchema, RecordJudgeResultDataSchema, RecordJudgeResultOutputSchema;
1826
1886
  var init_mcp_schemas = __esm({
1827
1887
  "../../packages/protocol/dist/mcp-schemas.js"() {
@@ -1833,352 +1893,352 @@ var init_mcp_schemas = __esm({
1833
1893
  init_fingerprint();
1834
1894
  AuthorityLevelSchema = authorityLevelEnum;
1835
1895
  FindingTypeSchema = findingTypeEnum;
1836
- MirrorStatusSchema = z21.enum([
1896
+ MirrorStatusSchema = z22.enum([
1837
1897
  "synced",
1838
1898
  "pending_retry",
1839
1899
  "local_only",
1840
1900
  "not_applicable"
1841
1901
  ]);
1842
- FindingSummarySchema = z21.object({
1843
- id: z21.string(),
1844
- slug: z21.string().nullable(),
1845
- title: z21.string(),
1902
+ FindingSummarySchema = z22.object({
1903
+ id: z22.string(),
1904
+ slug: z22.string().nullable(),
1905
+ title: z22.string(),
1846
1906
  type: FindingTypeSchema,
1847
1907
  /** Discriminator: "canonical" for deduplicated cloud findings,
1848
1908
  * "observation" for raw walkthrough findings (local or cloud). */
1849
- kind: z21.enum(["canonical", "observation"]),
1850
- severity: z21.enum(["critical", "major", "minor", "observation"]).nullable().describe("Effective severity. Null when not yet assigned."),
1851
- triage_status: z21.string().describe("open | addressed | dismissed"),
1909
+ kind: z22.enum(["canonical", "observation"]),
1910
+ severity: z22.enum(["critical", "major", "minor", "observation"]).nullable().describe("Effective severity. Null when not yet assigned."),
1911
+ triage_status: z22.string().describe("open | addressed | dismissed"),
1852
1912
  /** First 200 chars of the finding description. Fetch get_finding for the full text. */
1853
- description: z21.string(),
1854
- walkthrough_id: z21.string().nullable(),
1855
- scenario_id: z21.string(),
1913
+ description: z22.string(),
1914
+ walkthrough_id: z22.string().nullable(),
1915
+ scenario_id: z22.string(),
1856
1916
  created_at: isoTimestamp,
1857
- canonical_id: z21.string().nullable(),
1858
- cloud_mirrored: z21.boolean()
1917
+ canonical_id: z22.string().nullable(),
1918
+ cloud_mirrored: z22.boolean()
1859
1919
  });
1860
- ScenarioSummarySchema = z21.object({
1861
- id: z21.string(),
1862
- slug: z21.string().nullable(),
1863
- outline_id: z21.string(),
1864
- outline_slug: z21.string().nullable(),
1865
- persona_id: z21.string(),
1866
- persona_slug: z21.string().nullable(),
1920
+ ScenarioSummarySchema = z22.object({
1921
+ id: z22.string(),
1922
+ slug: z22.string().nullable(),
1923
+ outline_id: z22.string(),
1924
+ outline_slug: z22.string().nullable(),
1925
+ persona_id: z22.string(),
1926
+ persona_slug: z22.string().nullable(),
1867
1927
  created_at: isoTimestamp,
1868
- baseline_score: z21.number().nullable(),
1869
- current_score: z21.number().nullable(),
1870
- target_score: z21.number().nullable(),
1871
- walkthrough_count: z21.number().int().nonnegative(),
1872
- latest_walkthrough_id: z21.string().nullable(),
1873
- cloud_mirrored: z21.boolean()
1928
+ baseline_score: z22.number().nullable(),
1929
+ current_score: z22.number().nullable(),
1930
+ target_score: z22.number().nullable(),
1931
+ walkthrough_count: z22.number().int().nonnegative(),
1932
+ latest_walkthrough_id: z22.string().nullable(),
1933
+ cloud_mirrored: z22.boolean()
1874
1934
  });
1875
- RetrievalGapSchema = z21.object({
1876
- type: z21.enum(["no_results", "low_relevance", "unverified_only"]),
1877
- query: z21.string(),
1878
- suggestion: z21.string()
1935
+ RetrievalGapSchema = z22.object({
1936
+ type: z22.enum(["no_results", "low_relevance", "unverified_only"]),
1937
+ query: z22.string(),
1938
+ suggestion: z22.string()
1879
1939
  });
1880
- GetConstraintsDataSchema = z21.object({
1881
- domain: z21.string(),
1882
- constraint_count: z21.number().int().nonnegative(),
1883
- constraints: z21.array(z21.object({
1884
- rule: z21.string(),
1940
+ GetConstraintsDataSchema = z22.object({
1941
+ domain: z22.string(),
1942
+ constraint_count: z22.number().int().nonnegative(),
1943
+ constraints: z22.array(z22.object({
1944
+ rule: z22.string(),
1885
1945
  authority_level: AuthorityLevelSchema,
1886
- source_name: z21.string(),
1887
- source_section: z21.string(),
1888
- source_url: z21.string().nullable(),
1889
- source_version: z21.number(),
1890
- source_uri: z21.string().describe("iamu://source/<id>@v<version>")
1946
+ source_name: z22.string(),
1947
+ source_section: z22.string(),
1948
+ source_url: z22.string().nullable(),
1949
+ source_version: z22.number(),
1950
+ source_uri: z22.string().describe("iamu://source/<id>@v<version>")
1891
1951
  })),
1892
- gaps: z21.array(RetrievalGapSchema).optional(),
1893
- context_hint_needed: z21.boolean()
1952
+ gaps: z22.array(RetrievalGapSchema).optional(),
1953
+ context_hint_needed: z22.boolean()
1894
1954
  });
1895
1955
  GetConstraintsOutputSchema = mcpEnvelopeSchema(GetConstraintsDataSchema);
1896
- QueryDataSchema = z21.object({
1897
- results: z21.array(z21.object({
1898
- chunk_id: z21.string(),
1899
- content: z21.string(),
1900
- context_header: z21.string(),
1901
- context_text: z21.string().nullable(),
1956
+ QueryDataSchema = z22.object({
1957
+ results: z22.array(z22.object({
1958
+ chunk_id: z22.string(),
1959
+ content: z22.string(),
1960
+ context_header: z22.string(),
1961
+ context_text: z22.string().nullable(),
1902
1962
  authority_level: AuthorityLevelSchema,
1903
- source_name: z21.string(),
1904
- source_id: z21.string(),
1905
- source_uri: z21.string(),
1906
- source_version: z21.number(),
1907
- score: z21.number(),
1908
- verified: z21.boolean(),
1909
- trust: z21.enum(["hard", "soft", "unknown"])
1963
+ source_name: z22.string(),
1964
+ source_id: z22.string(),
1965
+ source_uri: z22.string(),
1966
+ source_version: z22.number(),
1967
+ score: z22.number(),
1968
+ verified: z22.boolean(),
1969
+ trust: z22.enum(["hard", "soft", "unknown"])
1910
1970
  })),
1911
- result_count: z21.number().int().nonnegative(),
1912
- query_time_ms: z21.number().int().nonnegative().optional(),
1913
- gaps: z21.array(RetrievalGapSchema).optional(),
1914
- context_hint_needed: z21.boolean()
1971
+ result_count: z22.number().int().nonnegative(),
1972
+ query_time_ms: z22.number().int().nonnegative().optional(),
1973
+ gaps: z22.array(RetrievalGapSchema).optional(),
1974
+ context_hint_needed: z22.boolean()
1915
1975
  });
1916
1976
  QueryOutputSchema = mcpEnvelopeSchema(QueryDataSchema);
1917
- ListFindingsDataSchema = z21.object({
1918
- findings: z21.array(FindingSummarySchema),
1919
- total_returned: z21.number().int().nonnegative()
1977
+ ListFindingsDataSchema = z22.object({
1978
+ findings: z22.array(FindingSummarySchema),
1979
+ total_returned: z22.number().int().nonnegative()
1920
1980
  });
1921
1981
  ListFindingsOutputSchema = mcpEnvelopeSchema(ListFindingsDataSchema);
1922
- ListScenariosDataSchema = z21.object({
1923
- scenarios: z21.array(ScenarioSummarySchema)
1982
+ ListScenariosDataSchema = z22.object({
1983
+ scenarios: z22.array(ScenarioSummarySchema)
1924
1984
  });
1925
1985
  ListScenariosOutputSchema = mcpEnvelopeSchema(ListScenariosDataSchema);
1926
- GetProjectDataSchema = z21.object({
1927
- id: z21.string(),
1928
- slug: z21.string().nullable(),
1929
- name: z21.string(),
1930
- cloud_linked: z21.boolean(),
1931
- cloud_project_id: z21.string().nullable(),
1986
+ GetProjectDataSchema = z22.object({
1987
+ id: z22.string(),
1988
+ slug: z22.string().nullable(),
1989
+ name: z22.string(),
1990
+ cloud_linked: z22.boolean(),
1991
+ cloud_project_id: z22.string().nullable(),
1932
1992
  created_at: isoTimestamp.nullable(),
1933
- capabilities_count: z21.number().int().nonnegative(),
1934
- outlines_count: z21.number().int().nonnegative(),
1935
- scenarios_count: z21.number().int().nonnegative(),
1936
- walkthroughs_count: z21.number().int().nonnegative(),
1937
- personas_count: z21.number().int().nonnegative()
1993
+ capabilities_count: z22.number().int().nonnegative(),
1994
+ outlines_count: z22.number().int().nonnegative(),
1995
+ scenarios_count: z22.number().int().nonnegative(),
1996
+ walkthroughs_count: z22.number().int().nonnegative(),
1997
+ personas_count: z22.number().int().nonnegative()
1938
1998
  });
1939
1999
  GetProjectOutputSchema = mcpEnvelopeSchema(GetProjectDataSchema);
1940
- CreateScenarioDataSchema = z21.object({
1941
- scenario_id: z21.string(),
1942
- outline_id: z21.string(),
1943
- persona_id: z21.string(),
1944
- was_existing: z21.boolean(),
2000
+ CreateScenarioDataSchema = z22.object({
2001
+ scenario_id: z22.string(),
2002
+ outline_id: z22.string(),
2003
+ persona_id: z22.string(),
2004
+ was_existing: z22.boolean(),
1945
2005
  created_at: isoTimestamp,
1946
2006
  brief_generated_at: isoTimestamp.nullable()
1947
2007
  });
1948
2008
  CreateScenarioOutputSchema = mcpEnvelopeSchema(CreateScenarioDataSchema);
1949
- PrepareWalkthroughDataSchema = z21.object({
1950
- walkthrough_id: z21.string(),
1951
- scenario_id: z21.string(),
1952
- target_url: z21.string(),
1953
- lenses: z21.array(z21.string()),
1954
- persona: z21.object({
1955
- id: z21.string(),
1956
- name: z21.string(),
1957
- slug: z21.string().nullable()
2009
+ PrepareWalkthroughDataSchema = z22.object({
2010
+ walkthrough_id: z22.string(),
2011
+ scenario_id: z22.string(),
2012
+ target_url: z22.string(),
2013
+ lenses: z22.array(z22.string()),
2014
+ persona: z22.object({
2015
+ id: z22.string(),
2016
+ name: z22.string(),
2017
+ slug: z22.string().nullable()
1958
2018
  }),
1959
- outline: z21.object({
1960
- id: z21.string(),
1961
- slug: z21.string().nullable(),
1962
- title: z21.string()
2019
+ outline: z22.object({
2020
+ id: z22.string(),
2021
+ slug: z22.string().nullable(),
2022
+ title: z22.string()
1963
2023
  }),
1964
- mode: z21.enum(["local", "cloud"]),
1965
- cloud_mirrored: z21.boolean(),
2024
+ mode: z22.enum(["local", "cloud"]),
2025
+ cloud_mirrored: z22.boolean(),
1966
2026
  mirror_status: MirrorStatusSchema,
1967
- run_dir: z21.string().nullable(),
2027
+ run_dir: z22.string().nullable(),
1968
2028
  /** Persona-specific first-person brief — markdown body of the scenario file. */
1969
- brief: z21.string(),
2029
+ brief: z22.string(),
1970
2030
  /** Full composed walking prompt: persona + outline + lenses +
1971
2031
  * evaluation philosophy. This is the text the walker should adopt
1972
2032
  * before driving the browser; `brief` alone lacks the philosophy
1973
2033
  * + lens guidance. */
1974
- prompt_text: z21.string(),
2034
+ prompt_text: z22.string(),
1975
2035
  /** Withheld during walker stage (always empty here). Populated by
1976
2036
  * `get_judge_input` once the walk completes. */
1977
- human_tests: z21.array(z21.string())
2037
+ human_tests: z22.array(z22.string())
1978
2038
  });
1979
2039
  PrepareWalkthroughOutputSchema = mcpEnvelopeSchema(PrepareWalkthroughDataSchema);
1980
- PersonaSummarySchema = z21.object({
1981
- id: z21.string(),
1982
- slug: z21.string(),
1983
- name: z21.string(),
1984
- role: z21.string(),
2040
+ PersonaSummarySchema = z22.object({
2041
+ id: z22.string(),
2042
+ slug: z22.string(),
2043
+ name: z22.string(),
2044
+ role: z22.string(),
1985
2045
  /** First 200 chars of context/background. */
1986
- context_preview: z21.string().nullable(),
1987
- status: z21.enum(["active", "archived"]),
2046
+ context_preview: z22.string().nullable(),
2047
+ status: z22.enum(["active", "archived"]),
1988
2048
  created_at: isoTimestamp,
1989
- cloud_mirrored: z21.boolean()
2049
+ cloud_mirrored: z22.boolean()
1990
2050
  });
1991
- OutlineSummarySchema = z21.object({
1992
- id: z21.string(),
1993
- slug: z21.string(),
1994
- name: z21.string(),
1995
- description: z21.string().nullable(),
1996
- primary_capability_id: z21.string().nullable(),
1997
- primary_capability_slug: z21.string().nullable(),
2051
+ OutlineSummarySchema = z22.object({
2052
+ id: z22.string(),
2053
+ slug: z22.string(),
2054
+ name: z22.string(),
2055
+ description: z22.string().nullable(),
2056
+ primary_capability_id: z22.string().nullable(),
2057
+ primary_capability_slug: z22.string().nullable(),
1998
2058
  /** Capability slugs (denormalized for agent display). */
1999
- additional_capabilities: z21.array(z21.string()),
2000
- task_criticality: z21.string().nullable(),
2059
+ additional_capabilities: z22.array(z22.string()),
2060
+ task_criticality: z22.string().nullable(),
2001
2061
  created_at: isoTimestamp,
2002
- cloud_mirrored: z21.boolean()
2062
+ cloud_mirrored: z22.boolean()
2003
2063
  });
2004
- CapabilitySummarySchema = z21.object({
2005
- id: z21.string(),
2006
- slug: z21.string(),
2007
- name: z21.string(),
2008
- description: z21.string().nullable(),
2064
+ CapabilitySummarySchema = z22.object({
2065
+ id: z22.string(),
2066
+ slug: z22.string(),
2067
+ name: z22.string(),
2068
+ description: z22.string().nullable(),
2009
2069
  /** Number of outlines with this capability as primary. Cloud-only;
2010
2070
  * null in local mode (filesystem walk would be too expensive). */
2011
- outline_count: z21.number().int().nonnegative().nullable(),
2071
+ outline_count: z22.number().int().nonnegative().nullable(),
2012
2072
  created_at: isoTimestamp,
2013
- cloud_mirrored: z21.boolean()
2073
+ cloud_mirrored: z22.boolean()
2014
2074
  });
2015
- ListPersonasDataSchema = z21.object({
2016
- personas: z21.array(PersonaSummarySchema)
2075
+ ListPersonasDataSchema = z22.object({
2076
+ personas: z22.array(PersonaSummarySchema)
2017
2077
  });
2018
2078
  ListPersonasOutputSchema = mcpEnvelopeSchema(ListPersonasDataSchema);
2019
- ListOutlinesDataSchema = z21.object({
2020
- outlines: z21.array(OutlineSummarySchema)
2079
+ ListOutlinesDataSchema = z22.object({
2080
+ outlines: z22.array(OutlineSummarySchema)
2021
2081
  });
2022
2082
  ListOutlinesOutputSchema = mcpEnvelopeSchema(ListOutlinesDataSchema);
2023
- ListCapabilitiesDataSchema = z21.object({
2024
- capabilities: z21.array(CapabilitySummarySchema)
2083
+ ListCapabilitiesDataSchema = z22.object({
2084
+ capabilities: z22.array(CapabilitySummarySchema)
2025
2085
  });
2026
2086
  ListCapabilitiesOutputSchema = mcpEnvelopeSchema(ListCapabilitiesDataSchema);
2027
- GetPersonaDataSchema = z21.object({
2028
- id: z21.string(),
2029
- slug: z21.string(),
2030
- name: z21.string(),
2031
- role: z21.string(),
2032
- context: z21.string().nullable(),
2033
- disposition: z21.string().nullable(),
2034
- brief: z21.string().nullable(),
2035
- status: z21.enum(["active", "archived"]),
2036
- body: z21.string(),
2087
+ GetPersonaDataSchema = z22.object({
2088
+ id: z22.string(),
2089
+ slug: z22.string(),
2090
+ name: z22.string(),
2091
+ role: z22.string(),
2092
+ context: z22.string().nullable(),
2093
+ disposition: z22.string().nullable(),
2094
+ brief: z22.string().nullable(),
2095
+ status: z22.enum(["active", "archived"]),
2096
+ body: z22.string(),
2037
2097
  created_at: isoTimestamp,
2038
2098
  updated_at: isoTimestamp.nullable(),
2039
- cloud_mirrored: z21.boolean(),
2040
- resource_uri: z21.string()
2099
+ cloud_mirrored: z22.boolean(),
2100
+ resource_uri: z22.string()
2041
2101
  });
2042
2102
  GetPersonaOutputSchema = mcpEnvelopeSchema(GetPersonaDataSchema);
2043
- GetOutlineDataSchema = z21.object({
2044
- id: z21.string(),
2045
- slug: z21.string(),
2046
- name: z21.string(),
2047
- description: z21.string().nullable(),
2048
- narrative: z21.string(),
2049
- primary_capability_id: z21.string().nullable(),
2050
- primary_capability_slug: z21.string().nullable(),
2051
- additional_capabilities: z21.array(z21.string()),
2052
- human_tests: z21.array(z21.object({ id: z21.string(), question: z21.string() })),
2053
- tension: z21.string().nullable(),
2054
- situation_roles: z21.array(z21.object({ role: z21.string(), stake: z21.string() })).nullable(),
2055
- task_criticality: z21.string().nullable(),
2056
- complexity_level: z21.string().nullable(),
2103
+ GetOutlineDataSchema = z22.object({
2104
+ id: z22.string(),
2105
+ slug: z22.string(),
2106
+ name: z22.string(),
2107
+ description: z22.string().nullable(),
2108
+ narrative: z22.string(),
2109
+ primary_capability_id: z22.string().nullable(),
2110
+ primary_capability_slug: z22.string().nullable(),
2111
+ additional_capabilities: z22.array(z22.string()),
2112
+ human_tests: z22.array(z22.object({ id: z22.string(), question: z22.string() })),
2113
+ tension: z22.string().nullable(),
2114
+ situation_roles: z22.array(z22.object({ role: z22.string(), stake: z22.string() })).nullable(),
2115
+ task_criticality: z22.string().nullable(),
2116
+ complexity_level: z22.string().nullable(),
2057
2117
  created_at: isoTimestamp,
2058
2118
  updated_at: isoTimestamp.nullable(),
2059
- cloud_mirrored: z21.boolean(),
2060
- resource_uri: z21.string()
2119
+ cloud_mirrored: z22.boolean(),
2120
+ resource_uri: z22.string()
2061
2121
  });
2062
2122
  GetOutlineOutputSchema = mcpEnvelopeSchema(GetOutlineDataSchema);
2063
- GetFindingDataSchema = z21.object({
2064
- id: z21.string(),
2065
- slug: z21.string().nullable(),
2066
- title: z21.string().nullable(),
2123
+ GetFindingDataSchema = z22.object({
2124
+ id: z22.string(),
2125
+ slug: z22.string().nullable(),
2126
+ title: z22.string().nullable(),
2067
2127
  type: FindingTypeSchema,
2068
- severity: z21.enum(["critical", "major", "minor", "observation"]).nullable(),
2069
- kind: z21.enum(["canonical", "observation"]),
2070
- ui_fact: z21.string().nullable(),
2071
- consequence: z21.string().nullable(),
2072
- description: z21.string().nullable(),
2073
- citations: z21.array(z21.string()),
2074
- walkthrough_id: z21.string().nullable(),
2075
- scenario_id: z21.string(),
2076
- persona_id: z21.string().nullable(),
2077
- run: z21.string().nullable(),
2078
- triage_status: z21.string(),
2079
- triage_note: z21.string().nullable(),
2080
- fix_status: z21.string(),
2081
- lens: z21.string().nullable(),
2082
- compliance_impact: z21.string().nullable(),
2083
- task_impact: z21.string().nullable(),
2084
- confidence: z21.string().nullable(),
2085
- regulatory_weight: z21.string().nullable(),
2086
- human_test_id: z21.string().nullable(),
2087
- walkthrough_step_id: z21.string().nullable(),
2088
- canonical_id: z21.string().nullable(),
2128
+ severity: z22.enum(["critical", "major", "minor", "observation"]).nullable(),
2129
+ kind: z22.enum(["canonical", "observation"]),
2130
+ ui_fact: z22.string().nullable(),
2131
+ consequence: z22.string().nullable(),
2132
+ description: z22.string().nullable(),
2133
+ citations: z22.array(z22.string()),
2134
+ walkthrough_id: z22.string().nullable(),
2135
+ scenario_id: z22.string(),
2136
+ persona_id: z22.string().nullable(),
2137
+ run: z22.string().nullable(),
2138
+ triage_status: z22.string(),
2139
+ triage_note: z22.string().nullable(),
2140
+ fix_status: z22.string(),
2141
+ lens: z22.string().nullable(),
2142
+ compliance_impact: z22.string().nullable(),
2143
+ task_impact: z22.string().nullable(),
2144
+ confidence: z22.string().nullable(),
2145
+ regulatory_weight: z22.string().nullable(),
2146
+ human_test_id: z22.string().nullable(),
2147
+ walkthrough_step_id: z22.string().nullable(),
2148
+ canonical_id: z22.string().nullable(),
2089
2149
  recorded_at: isoTimestamp.nullable(),
2090
2150
  updated_at: isoTimestamp.nullable(),
2091
- cloud_mirrored: z21.boolean(),
2151
+ cloud_mirrored: z22.boolean(),
2092
2152
  mirror_status: MirrorStatusSchema,
2093
- resource_uri: z21.string()
2153
+ resource_uri: z22.string()
2094
2154
  });
2095
2155
  GetFindingOutputSchema = mcpEnvelopeSchema(GetFindingDataSchema);
2096
- CreatePersonaDataSchema = z21.object({
2097
- persona_id: z21.string(),
2098
- slug: z21.string(),
2099
- was_existing: z21.boolean(),
2156
+ CreatePersonaDataSchema = z22.object({
2157
+ persona_id: z22.string(),
2158
+ slug: z22.string(),
2159
+ was_existing: z22.boolean(),
2100
2160
  created_at: isoTimestamp,
2101
2161
  brief_generated_at: isoTimestamp.nullable()
2102
2162
  });
2103
2163
  CreatePersonaOutputSchema = mcpEnvelopeSchema(CreatePersonaDataSchema);
2104
- CreateOutlineDataSchema = z21.object({
2105
- outline_id: z21.string(),
2106
- slug: z21.string(),
2107
- primary_capability_id: z21.string().nullable(),
2108
- primary_capability_slug: z21.string().nullable(),
2109
- was_existing: z21.boolean(),
2164
+ CreateOutlineDataSchema = z22.object({
2165
+ outline_id: z22.string(),
2166
+ slug: z22.string(),
2167
+ primary_capability_id: z22.string().nullable(),
2168
+ primary_capability_slug: z22.string().nullable(),
2169
+ was_existing: z22.boolean(),
2110
2170
  created_at: isoTimestamp
2111
2171
  });
2112
2172
  CreateOutlineOutputSchema = mcpEnvelopeSchema(CreateOutlineDataSchema);
2113
- CreateCapabilityDataSchema = z21.object({
2114
- capability_id: z21.string(),
2115
- slug: z21.string(),
2116
- was_existing: z21.boolean(),
2173
+ CreateCapabilityDataSchema = z22.object({
2174
+ capability_id: z22.string(),
2175
+ slug: z22.string(),
2176
+ was_existing: z22.boolean(),
2117
2177
  created_at: isoTimestamp
2118
2178
  });
2119
2179
  CreateCapabilityOutputSchema = mcpEnvelopeSchema(CreateCapabilityDataSchema);
2120
- RecordStepDataSchema = z21.object({
2121
- step_id: z21.string(),
2122
- step_index: z21.number().int().nonnegative(),
2123
- walkthrough_id: z21.string(),
2124
- persona_reasoning: z21.string(),
2180
+ RecordStepDataSchema = z22.object({
2181
+ step_id: z22.string(),
2182
+ step_index: z22.number().int().nonnegative(),
2183
+ walkthrough_id: z22.string(),
2184
+ persona_reasoning: z22.string(),
2125
2185
  recorded_at: isoTimestamp,
2126
2186
  /** Set when a screenshot_path was provided. */
2127
- screenshot_uri: z21.string().nullable(),
2187
+ screenshot_uri: z22.string().nullable(),
2128
2188
  /** Capture failure modes (per-field) — see fingerprint shape in
2129
2189
  * `./types/fingerprint.ts`. Optional; null
2130
2190
  * when fingerprint capture didn't run. */
2131
- fingerprint: stepFingerprintSchema.or(z21.object({
2132
- dom_hash: z21.string().nullable(),
2133
- ax_hash: z21.string().nullable(),
2134
- screenshot_hash: z21.string().nullable(),
2135
- url_template: z21.string().nullable()
2191
+ fingerprint: stepFingerprintSchema.or(z22.object({
2192
+ dom_hash: z22.string().nullable(),
2193
+ ax_hash: z22.string().nullable(),
2194
+ screenshot_hash: z22.string().nullable(),
2195
+ url_template: z22.string().nullable()
2136
2196
  })).nullable()
2137
2197
  });
2138
2198
  RecordStepOutputSchema = mcpEnvelopeSchema(RecordStepDataSchema);
2139
- RecordFindingDataSchema = z21.object({
2140
- finding_id: z21.string(),
2141
- slug: z21.string().nullable(),
2199
+ RecordFindingDataSchema = z22.object({
2200
+ finding_id: z22.string(),
2201
+ slug: z22.string().nullable(),
2142
2202
  type: FindingTypeSchema,
2143
- severity: z21.enum(["critical", "major", "minor", "observation"]).nullable(),
2144
- kind: z21.literal("observation"),
2145
- ui_fact: z21.string(),
2146
- consequence: z21.string(),
2147
- citations: z21.array(z21.string()),
2148
- scenario_id: z21.string(),
2149
- walkthrough_id: z21.string(),
2150
- persona_id: z21.string().nullable(),
2151
- triage_status: z21.literal("open"),
2152
- fix_status: z21.literal("open"),
2153
- lens: z21.string().nullable(),
2203
+ severity: z22.enum(["critical", "major", "minor", "observation"]).nullable(),
2204
+ kind: z22.literal("observation"),
2205
+ ui_fact: z22.string(),
2206
+ consequence: z22.string(),
2207
+ citations: z22.array(z22.string()),
2208
+ scenario_id: z22.string(),
2209
+ walkthrough_id: z22.string(),
2210
+ persona_id: z22.string().nullable(),
2211
+ triage_status: z22.literal("open"),
2212
+ fix_status: z22.literal("open"),
2213
+ lens: z22.string().nullable(),
2154
2214
  recorded_at: isoTimestamp,
2155
- resource_uri: z21.string()
2215
+ resource_uri: z22.string()
2156
2216
  });
2157
2217
  RecordFindingOutputSchema = mcpEnvelopeSchema(RecordFindingDataSchema);
2158
2218
  CompleteWalkthroughDataSchema = canonicalWalkthroughSchema;
2159
2219
  CompleteWalkthroughOutputSchema = mcpEnvelopeSchema(CompleteWalkthroughDataSchema);
2160
- GetJudgeInputDataSchema = z21.object({
2161
- walkthrough_id: z21.string(),
2162
- judge_prompt: z21.object({
2163
- id: z21.string(),
2164
- version: z21.string(),
2165
- text: z21.string()
2220
+ GetJudgeInputDataSchema = z22.object({
2221
+ walkthrough_id: z22.string(),
2222
+ judge_prompt: z22.object({
2223
+ id: z22.string(),
2224
+ version: z22.string(),
2225
+ text: z22.string()
2166
2226
  }),
2167
- scenario: z21.object({}).passthrough(),
2168
- outline_context: z21.object({}).passthrough(),
2169
- steps: z21.array(z21.unknown()),
2170
- findings: z21.array(z21.unknown()),
2171
- human_tests: z21.array(z21.object({
2172
- id: z21.string(),
2173
- question: z21.string()
2227
+ scenario: z22.object({}).passthrough(),
2228
+ outline_context: z22.object({}).passthrough(),
2229
+ steps: z22.array(z22.unknown()),
2230
+ findings: z22.array(z22.unknown()),
2231
+ human_tests: z22.array(z22.object({
2232
+ id: z22.string(),
2233
+ question: z22.string()
2174
2234
  }))
2175
2235
  });
2176
2236
  GetJudgeInputOutputSchema = mcpEnvelopeSchema(GetJudgeInputDataSchema);
2177
- RecordJudgeResultDataSchema = z21.object({
2178
- walkthrough_id: z21.string(),
2237
+ RecordJudgeResultDataSchema = z22.object({
2238
+ walkthrough_id: z22.string(),
2179
2239
  persisted_at: isoTimestamp,
2180
- canonical_path: z21.string(),
2181
- report_path: z21.string()
2240
+ canonical_path: z22.string(),
2241
+ report_path: z22.string()
2182
2242
  });
2183
2243
  RecordJudgeResultOutputSchema = mcpEnvelopeSchema(RecordJudgeResultDataSchema);
2184
2244
  }
@@ -2351,6 +2411,9 @@ function configPath(projectRoot) {
2351
2411
  function contextPath(projectRoot) {
2352
2412
  return path2.join(iamuDir(projectRoot), "context.yml");
2353
2413
  }
2414
+ function testabilityPath(projectRoot) {
2415
+ return path2.join(iamuDir(projectRoot), "testability.md");
2416
+ }
2354
2417
  function entityDirPath(projectRoot, type2) {
2355
2418
  return path2.join(iamuDir(projectRoot), ENTITY_DIRS[type2]);
2356
2419
  }
@@ -5294,6 +5357,37 @@ var init_context2 = __esm({
5294
5357
  }
5295
5358
  });
5296
5359
 
5360
+ // ../../packages/local-store/dist/testability.js
5361
+ import { promises as fs5 } from "fs";
5362
+ async function readTestability(projectRoot) {
5363
+ const p = testabilityPath(projectRoot);
5364
+ let raw;
5365
+ try {
5366
+ raw = await fs5.readFile(p, "utf8");
5367
+ } catch (err) {
5368
+ if (err.code === "ENOENT")
5369
+ return null;
5370
+ throw err;
5371
+ }
5372
+ const parsed = parseFrontmatter(raw);
5373
+ const result = testabilityFrontmatterSchema.safeParse(parsed.frontmatter);
5374
+ if (!result.success) {
5375
+ throw new Error(`testability: invalid frontmatter in ${p}: ${result.error.issues.map((i) => `${i.path.join(".") || "root"}: ${i.message}`).join("; ")}`);
5376
+ }
5377
+ return {
5378
+ frontmatter: result.data,
5379
+ body: parsed.body
5380
+ };
5381
+ }
5382
+ var init_testability2 = __esm({
5383
+ "../../packages/local-store/dist/testability.js"() {
5384
+ "use strict";
5385
+ init_dist();
5386
+ init_markdown();
5387
+ init_paths();
5388
+ }
5389
+ });
5390
+
5297
5391
  // ../../packages/local-store/dist/hash.js
5298
5392
  import { createHash } from "crypto";
5299
5393
  function hashNormalizedContent(raw) {
@@ -5327,7 +5421,7 @@ var init_server_reason = __esm({
5327
5421
 
5328
5422
  // ../../packages/local-store/dist/file-lock.js
5329
5423
  import { randomUUID as randomUUID3 } from "crypto";
5330
- import { promises as fs5 } from "fs";
5424
+ import { promises as fs6 } from "fs";
5331
5425
  import os from "os";
5332
5426
  import path4 from "path";
5333
5427
  function takeoverFilePathFor(lockPath) {
@@ -5364,7 +5458,7 @@ function pidIsDead(rec) {
5364
5458
  async function isStale(lockPath, rec, staleMs, now) {
5365
5459
  if (rec === null) {
5366
5460
  try {
5367
- const stat3 = await fs5.stat(lockPath);
5461
+ const stat3 = await fs6.stat(lockPath);
5368
5462
  if (stat3.size === 0) {
5369
5463
  const mtime = stat3.birthtimeMs && stat3.birthtimeMs > 0 ? stat3.birthtimeMs : stat3.mtimeMs;
5370
5464
  if (now - mtime <= staleMs) {
@@ -5389,7 +5483,7 @@ async function tryCreateMarker(markerPath, token) {
5389
5483
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
5390
5484
  };
5391
5485
  try {
5392
- const handle = await fs5.open(markerPath, "wx", 420);
5486
+ const handle = await fs6.open(markerPath, "wx", 420);
5393
5487
  try {
5394
5488
  await handle.writeFile(JSON.stringify(record));
5395
5489
  await handle.sync();
@@ -5408,7 +5502,7 @@ async function tryCreateLock(lockPath, token) {
5408
5502
  }
5409
5503
  async function readRecord(markerPath) {
5410
5504
  try {
5411
- return parseLockRecord(await fs5.readFile(markerPath, "utf8"));
5505
+ return parseLockRecord(await fs6.readFile(markerPath, "utf8"));
5412
5506
  } catch {
5413
5507
  return null;
5414
5508
  }
@@ -5418,7 +5512,7 @@ async function tryTakeover(lockPath, token, expected, opts) {
5418
5512
  if (!await tryCreateMarker(takeoverPath, token)) {
5419
5513
  const holder = await readRecord(takeoverPath);
5420
5514
  if (await isStale(takeoverPath, holder, opts.staleMs, opts.now())) {
5421
- await fs5.unlink(takeoverPath).catch(() => {
5515
+ await fs6.unlink(takeoverPath).catch(() => {
5422
5516
  });
5423
5517
  }
5424
5518
  return null;
@@ -5427,7 +5521,7 @@ async function tryTakeover(lockPath, token, expected, opts) {
5427
5521
  const current = await readRecord(lockPath);
5428
5522
  let present = true;
5429
5523
  try {
5430
- await fs5.access(lockPath);
5524
+ await fs6.access(lockPath);
5431
5525
  } catch {
5432
5526
  present = false;
5433
5527
  }
@@ -5438,11 +5532,11 @@ async function tryTakeover(lockPath, token, expected, opts) {
5438
5532
  return null;
5439
5533
  if (!await isStale(lockPath, current, opts.staleMs, opts.now()))
5440
5534
  return null;
5441
- await fs5.unlink(lockPath).catch(() => {
5535
+ await fs6.unlink(lockPath).catch(() => {
5442
5536
  });
5443
5537
  return await tryCreateLock(lockPath, token);
5444
5538
  } finally {
5445
- await fs5.unlink(takeoverPath).catch(() => {
5539
+ await fs6.unlink(takeoverPath).catch(() => {
5446
5540
  });
5447
5541
  }
5448
5542
  }
@@ -5459,13 +5553,13 @@ async function acquireLockFile(lockPath, opts) {
5459
5553
  const token = randomUUID3();
5460
5554
  const deadline = opts.now() + opts.timeoutMs;
5461
5555
  let lastHolderPid;
5462
- await fs5.mkdir(path4.dirname(lockPath), { recursive: true });
5556
+ await fs6.mkdir(path4.dirname(lockPath), { recursive: true });
5463
5557
  for (; ; ) {
5464
5558
  if (await tryCreateLock(lockPath, token))
5465
5559
  return token;
5466
5560
  let raw = null;
5467
5561
  try {
5468
- raw = await fs5.readFile(lockPath, "utf8");
5562
+ raw = await fs6.readFile(lockPath, "utf8");
5469
5563
  } catch (err) {
5470
5564
  if (err.code === "ENOENT")
5471
5565
  continue;
@@ -5492,13 +5586,13 @@ async function acquireLockFile(lockPath, opts) {
5492
5586
  }
5493
5587
  async function releaseLockFile(lockPath, token) {
5494
5588
  try {
5495
- const rec = parseLockRecord(await fs5.readFile(lockPath, "utf8"));
5589
+ const rec = parseLockRecord(await fs6.readFile(lockPath, "utf8"));
5496
5590
  if (rec?.token !== token)
5497
5591
  return;
5498
- const recheck = parseLockRecord(await fs5.readFile(lockPath, "utf8"));
5592
+ const recheck = parseLockRecord(await fs6.readFile(lockPath, "utf8"));
5499
5593
  if (recheck?.token !== token)
5500
5594
  return;
5501
- await fs5.unlink(lockPath);
5595
+ await fs6.unlink(lockPath);
5502
5596
  } catch {
5503
5597
  }
5504
5598
  }
@@ -5567,10 +5661,10 @@ var init_file_lock = __esm({
5567
5661
  });
5568
5662
 
5569
5663
  // ../../packages/local-store/dist/sync-tracking.js
5570
- import { promises as fs6 } from "fs";
5664
+ import { promises as fs7 } from "fs";
5571
5665
  async function readLastPush(projectRoot) {
5572
5666
  try {
5573
- const raw = await fs6.readFile(lastPushPath(projectRoot), "utf8");
5667
+ const raw = await fs7.readFile(lastPushPath(projectRoot), "utf8");
5574
5668
  const parsed = JSON.parse(raw);
5575
5669
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
5576
5670
  return parsed;
@@ -5617,7 +5711,7 @@ async function forgetLastPush(projectRoot, entityId) {
5617
5711
  }
5618
5712
  async function readCloudBase(projectRoot) {
5619
5713
  try {
5620
- const raw = await fs6.readFile(cloudBasePath(projectRoot), "utf8");
5714
+ const raw = await fs7.readFile(cloudBasePath(projectRoot), "utf8");
5621
5715
  const parsed = JSON.parse(raw);
5622
5716
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
5623
5717
  return parsed;
@@ -5718,7 +5812,7 @@ function isRetryQueueEntry(value) {
5718
5812
  async function readRetryQueueDetailed(projectRoot) {
5719
5813
  let raw;
5720
5814
  try {
5721
- raw = await fs6.readFile(retryQueuePath(projectRoot), "utf8");
5815
+ raw = await fs7.readFile(retryQueuePath(projectRoot), "utf8");
5722
5816
  } catch (err) {
5723
5817
  if (err.code === "ENOENT") {
5724
5818
  return { entries: [], warnings: [], corruptedLines: [] };
@@ -5774,7 +5868,7 @@ function canonicalJsonString(val) {
5774
5868
  async function readQueueAndPreserveCorrupted(projectRoot) {
5775
5869
  const { entries, corruptedLines } = await readRetryQueueDetailed(projectRoot);
5776
5870
  if (corruptedLines && corruptedLines.length > 0) {
5777
- await fs6.appendFile(retryQueueCorruptedPath(projectRoot), corruptedLines.map((l) => `${l}
5871
+ await fs7.appendFile(retryQueueCorruptedPath(projectRoot), corruptedLines.map((l) => `${l}
5778
5872
  `).join(""), "utf8");
5779
5873
  }
5780
5874
  return entries;
@@ -5824,7 +5918,7 @@ var init_sync_tracking = __esm({
5824
5918
  });
5825
5919
 
5826
5920
  // ../../packages/local-store/dist/entity.js
5827
- import { promises as fs7 } from "fs";
5921
+ import { promises as fs8 } from "fs";
5828
5922
  import path5 from "path";
5829
5923
  async function resolveEntityPath(projectRoot, type2, slugOrUuid) {
5830
5924
  const isSlug = slugSchema.safeParse(slugOrUuid).success;
@@ -5833,7 +5927,7 @@ async function resolveEntityPath(projectRoot, type2, slugOrUuid) {
5833
5927
  return null;
5834
5928
  const direct = entityFilePath(projectRoot, type2, slugOrUuid);
5835
5929
  try {
5836
- await fs7.access(direct);
5930
+ await fs8.access(direct);
5837
5931
  return direct;
5838
5932
  } catch {
5839
5933
  }
@@ -5842,7 +5936,7 @@ async function resolveEntityPath(projectRoot, type2, slugOrUuid) {
5842
5936
  const dir = entityDirPath(projectRoot, type2);
5843
5937
  let entries;
5844
5938
  try {
5845
- entries = await fs7.readdir(dir);
5939
+ entries = await fs8.readdir(dir);
5846
5940
  } catch {
5847
5941
  return null;
5848
5942
  }
@@ -5851,7 +5945,7 @@ async function resolveEntityPath(projectRoot, type2, slugOrUuid) {
5851
5945
  continue;
5852
5946
  const abs = path5.join(dir, name);
5853
5947
  try {
5854
- const raw = await fs7.readFile(abs, "utf8");
5948
+ const raw = await fs8.readFile(abs, "utf8");
5855
5949
  const { frontmatter } = parseFrontmatter(raw);
5856
5950
  if (typeof frontmatter.id === "string" && frontmatter.id.toLowerCase() === slugOrUuid.toLowerCase()) {
5857
5951
  return abs;
@@ -5868,7 +5962,7 @@ async function readEntity(projectRoot, type2, slugOrUuid) {
5868
5962
  const abs = await resolveEntityPath(projectRoot, type2, slugOrUuid);
5869
5963
  if (!abs)
5870
5964
  return null;
5871
- const raw = await fs7.readFile(abs, "utf8");
5965
+ const raw = await fs8.readFile(abs, "utf8");
5872
5966
  const { frontmatter, body } = parseFrontmatter(raw);
5873
5967
  const schema2 = entityFrontmatterSchemas[type2];
5874
5968
  const parsed = schema2.parse(frontmatter);
@@ -5896,7 +5990,7 @@ async function writeEntity(projectRoot, type2, frontmatter, body) {
5896
5990
  }
5897
5991
  async function entityExists(projectRoot, type2, slug) {
5898
5992
  try {
5899
- await fs7.access(entityFilePath(projectRoot, type2, slug));
5993
+ await fs8.access(entityFilePath(projectRoot, type2, slug));
5900
5994
  return true;
5901
5995
  } catch {
5902
5996
  return false;
@@ -5906,7 +6000,7 @@ async function listEntities(projectRoot, type2) {
5906
6000
  const dir = entityDirPath(projectRoot, type2);
5907
6001
  let names;
5908
6002
  try {
5909
- names = await fs7.readdir(dir);
6003
+ names = await fs8.readdir(dir);
5910
6004
  } catch (err) {
5911
6005
  if (err.code === "ENOENT") {
5912
6006
  return { entities: [], warnings: [] };
@@ -5921,7 +6015,7 @@ async function listEntities(projectRoot, type2) {
5921
6015
  continue;
5922
6016
  const abs = path5.join(dir, name);
5923
6017
  try {
5924
- const raw = await fs7.readFile(abs, "utf8");
6018
+ const raw = await fs8.readFile(abs, "utf8");
5925
6019
  const { frontmatter, body } = parseFrontmatter(raw);
5926
6020
  const parsed = schema2.parse(frontmatter);
5927
6021
  entities.push({
@@ -6036,14 +6130,14 @@ var init_guidance2 = __esm({
6036
6130
  });
6037
6131
 
6038
6132
  // ../../packages/local-store/dist/finding-match.js
6039
- import { promises as fs8 } from "fs";
6133
+ import { promises as fs9 } from "fs";
6040
6134
  import path6 from "path";
6041
- import { z as z22 } from "zod";
6135
+ import { z as z23 } from "zod";
6042
6136
  async function readScenarioFindings(projectRoot, scenarioSlug) {
6043
6137
  const dir = entityDirPath(projectRoot, "finding");
6044
6138
  let names;
6045
6139
  try {
6046
- names = await fs8.readdir(dir);
6140
+ names = await fs9.readdir(dir);
6047
6141
  } catch {
6048
6142
  return [];
6049
6143
  }
@@ -6053,7 +6147,7 @@ async function readScenarioFindings(projectRoot, scenarioSlug) {
6053
6147
  continue;
6054
6148
  const file = path6.join(dir, name);
6055
6149
  try {
6056
- const { frontmatter } = parseFrontmatter(await fs8.readFile(file, "utf8"));
6150
+ const { frontmatter } = parseFrontmatter(await fs9.readFile(file, "utf8"));
6057
6151
  const fm = frontmatter;
6058
6152
  if (fm.scenario === scenarioSlug)
6059
6153
  out.push({ file, frontmatter: fm });
@@ -6093,7 +6187,7 @@ async function suppressionRules(projectRoot, outlineSlug) {
6093
6187
  }
6094
6188
  async function scenarioOutline(projectRoot, scenarioSlug) {
6095
6189
  try {
6096
- const raw = await fs8.readFile(path6.join(entityDirPath(projectRoot, "scenario"), `${scenarioSlug}.md`), "utf8");
6190
+ const raw = await fs9.readFile(path6.join(entityDirPath(projectRoot, "scenario"), `${scenarioSlug}.md`), "utf8");
6097
6191
  const outline = parseFrontmatter(raw).frontmatter.outline;
6098
6192
  return typeof outline === "string" ? outline : null;
6099
6193
  } catch {
@@ -6206,7 +6300,7 @@ async function recordMatches(projectRoot, runId, scenarioSlug, answer) {
6206
6300
  const target = byId.get(m.finding_id);
6207
6301
  if (!target)
6208
6302
  continue;
6209
- const raw = await fs8.readFile(target, "utf8");
6303
+ const raw = await fs9.readFile(target, "utf8");
6210
6304
  const updated = setFrontmatterScalar(raw, "canonical_id", m.canonical_id);
6211
6305
  if (updated !== raw)
6212
6306
  await atomicWriteFile(target, updated);
@@ -6216,7 +6310,7 @@ async function recordMatches(projectRoot, runId, scenarioSlug, answer) {
6216
6310
  const target = byId.get(s.finding_id);
6217
6311
  if (!target)
6218
6312
  continue;
6219
- const raw = await fs8.readFile(target, "utf8");
6313
+ const raw = await fs9.readFile(target, "utf8");
6220
6314
  const updated = setFrontmatterScalar(raw, "suppressed_by_guidance", s.guidance_id);
6221
6315
  if (updated !== raw)
6222
6316
  await atomicWriteFile(target, updated);
@@ -6234,7 +6328,7 @@ async function setCanonicalFixStatus(projectRoot, scenarioSlug, canonicalId, sta
6234
6328
  for (const f of await readScenarioFindings(projectRoot, scenarioSlug)) {
6235
6329
  if (f.frontmatter.canonical_id !== canonicalId)
6236
6330
  continue;
6237
- const raw = await fs8.readFile(f.file, "utf8");
6331
+ const raw = await fs9.readFile(f.file, "utf8");
6238
6332
  const updated = setFrontmatterScalar(raw, "fix_status", status2);
6239
6333
  if (updated !== raw)
6240
6334
  await atomicWriteFile(f.file, updated);
@@ -6242,7 +6336,7 @@ async function setCanonicalFixStatus(projectRoot, scenarioSlug, canonicalId, sta
6242
6336
  }
6243
6337
  async function readMatches(projectRoot, runId) {
6244
6338
  try {
6245
- const raw = await fs8.readFile(path6.join(runDir(projectRoot, runId), MATCHES_FILE), "utf8");
6339
+ const raw = await fs9.readFile(path6.join(runDir(projectRoot, runId), MATCHES_FILE), "utf8");
6246
6340
  const parsed = matchesFileSchema.safeParse(JSON.parse(raw));
6247
6341
  return parsed.success ? parsed.data : null;
6248
6342
  } catch {
@@ -6259,32 +6353,32 @@ var init_finding_match = __esm({
6259
6353
  init_paths();
6260
6354
  init_walkthrough_canonical2();
6261
6355
  MATCHES_FILE = "matches.json";
6262
- matchesFileSchema = z22.object({
6263
- matches: z22.array(z22.object({
6264
- finding_id: z22.string().min(1),
6265
- canonical_id: z22.string().min(1).nullable(),
6266
- reasoning: z22.string().default("")
6356
+ matchesFileSchema = z23.object({
6357
+ matches: z23.array(z23.object({
6358
+ finding_id: z23.string().min(1),
6359
+ canonical_id: z23.string().min(1).nullable(),
6360
+ reasoning: z23.string().default("")
6267
6361
  })),
6268
- rechecked_fixed: z22.array(z22.object({
6269
- canonical_id: z22.string().min(1),
6270
- step_ids: z22.array(z22.string().min(1)).min(1),
6271
- reasoning: z22.string().default("")
6362
+ rechecked_fixed: z23.array(z23.object({
6363
+ canonical_id: z23.string().min(1),
6364
+ step_ids: z23.array(z23.string().min(1)).min(1),
6365
+ reasoning: z23.string().default("")
6272
6366
  })).default([]),
6273
6367
  /**
6274
6368
  * Findings of this run that an active `not_relevant` tester guidance
6275
6369
  * rule covers. Kept, flagged: `suppressed_by_guidance` on the file.
6276
6370
  */
6277
- suppressed: z22.array(z22.object({
6278
- finding_id: z22.string().min(1),
6279
- guidance_id: z22.string().min(1),
6280
- reasoning: z22.string().default("")
6371
+ suppressed: z23.array(z23.object({
6372
+ finding_id: z23.string().min(1),
6373
+ guidance_id: z23.string().min(1),
6374
+ reasoning: z23.string().default("")
6281
6375
  })).default([])
6282
6376
  });
6283
6377
  }
6284
6378
  });
6285
6379
 
6286
6380
  // ../../packages/local-store/dist/runs.js
6287
- import { promises as fs9 } from "fs";
6381
+ import { promises as fs10 } from "fs";
6288
6382
  import path7 from "path";
6289
6383
  async function openRun(projectRoot, scenarioSlug, options = {}) {
6290
6384
  const now = options.now ?? /* @__PURE__ */ new Date();
@@ -6293,9 +6387,9 @@ async function openRun(projectRoot, scenarioSlug, options = {}) {
6293
6387
  const stepsDir = path7.join(absPath, "steps");
6294
6388
  const fingerprintsDir = path7.join(absPath, "fingerprints");
6295
6389
  const screenshotsDir = path7.join(absPath, "screenshots");
6296
- await fs9.mkdir(stepsDir, { recursive: true });
6297
- await fs9.mkdir(fingerprintsDir, { recursive: true });
6298
- await fs9.mkdir(screenshotsDir, { recursive: true });
6390
+ await fs10.mkdir(stepsDir, { recursive: true });
6391
+ await fs10.mkdir(fingerprintsDir, { recursive: true });
6392
+ await fs10.mkdir(screenshotsDir, { recursive: true });
6299
6393
  return {
6300
6394
  runId,
6301
6395
  absPath,
@@ -6310,14 +6404,14 @@ function padStepNumber(n) {
6310
6404
  }
6311
6405
  async function writeStep(runDirAbs, step) {
6312
6406
  const stepsDir = path7.join(runDirAbs, "steps");
6313
- await fs9.mkdir(stepsDir, { recursive: true });
6407
+ await fs10.mkdir(stepsDir, { recursive: true });
6314
6408
  const filename = `${padStepNumber(step.step_number)}.json`;
6315
6409
  await atomicWriteFile(path7.join(stepsDir, filename), JSON.stringify(step, null, 2) + "\n");
6316
6410
  }
6317
6411
  async function listRuns(projectRoot) {
6318
6412
  const dir = runsDir(projectRoot);
6319
6413
  try {
6320
- const entries = await fs9.readdir(dir, { withFileTypes: true });
6414
+ const entries = await fs10.readdir(dir, { withFileTypes: true });
6321
6415
  return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort();
6322
6416
  } catch (err) {
6323
6417
  if (err.code === "ENOENT")
@@ -6329,7 +6423,7 @@ async function listIncompleteRuns(projectRoot) {
6329
6423
  const incomplete = [];
6330
6424
  for (const runId of await listRuns(projectRoot)) {
6331
6425
  try {
6332
- const manifest = JSON.parse(await fs9.readFile(path7.join(runsDir(projectRoot), runId, "walkthrough.json"), "utf8"));
6426
+ const manifest = JSON.parse(await fs10.readFile(path7.join(runsDir(projectRoot), runId, "walkthrough.json"), "utf8"));
6333
6427
  if (manifest.status !== "completed")
6334
6428
  incomplete.push(runId);
6335
6429
  } catch {
@@ -6347,7 +6441,7 @@ var init_runs = __esm({
6347
6441
  });
6348
6442
 
6349
6443
  // ../../packages/local-store/dist/fix-state.js
6350
- import { promises as fs10 } from "fs";
6444
+ import { promises as fs11 } from "fs";
6351
6445
  import path8 from "path";
6352
6446
  function toMillis(iso) {
6353
6447
  if (!iso)
@@ -6453,7 +6547,7 @@ function summarizeRerun(findings2, runs, states) {
6453
6547
  }
6454
6548
  async function readJson(file) {
6455
6549
  try {
6456
- return JSON.parse(await fs10.readFile(file, "utf8"));
6550
+ return JSON.parse(await fs11.readFile(file, "utf8"));
6457
6551
  } catch {
6458
6552
  return null;
6459
6553
  }
@@ -7026,19 +7120,19 @@ var init_journey_diff = __esm({
7026
7120
 
7027
7121
  // ../../packages/local-store/dist/walkthrough-canonical.js
7028
7122
  import { createHash as createHash2, randomUUID as randomUUID5 } from "crypto";
7029
- import { promises as fs11 } from "fs";
7123
+ import { promises as fs12 } from "fs";
7030
7124
  import path9 from "path";
7031
7125
  async function readStepsForRun(projectRoot, runId) {
7032
7126
  const runDirPath = runDir(projectRoot, runId);
7033
7127
  const stepsDir = path9.join(runDirPath, "steps");
7034
7128
  try {
7035
- const names = await fs11.readdir(stepsDir);
7129
+ const names = await fs12.readdir(stepsDir);
7036
7130
  const jsonFiles = names.filter((n) => n.endsWith(".json")).sort();
7037
7131
  if (jsonFiles.length > 0) {
7038
7132
  const out2 = [];
7039
7133
  for (const name of jsonFiles) {
7040
7134
  try {
7041
- const raw2 = await fs11.readFile(path9.join(stepsDir, name), "utf8");
7135
+ const raw2 = await fs12.readFile(path9.join(stepsDir, name), "utf8");
7042
7136
  const parsed = JSON.parse(raw2);
7043
7137
  if (parsed.step_number == null) {
7044
7138
  const numFromName = Number.parseInt(name.replace(/\.json$/, ""), 10);
@@ -7055,7 +7149,7 @@ async function readStepsForRun(projectRoot, runId) {
7055
7149
  const stepsPath = path9.join(runDirPath, "steps.jsonl");
7056
7150
  let raw;
7057
7151
  try {
7058
- raw = await fs11.readFile(stepsPath, "utf8");
7152
+ raw = await fs12.readFile(stepsPath, "utf8");
7059
7153
  } catch (err) {
7060
7154
  if (err.code === "ENOENT")
7061
7155
  return [];
@@ -7080,7 +7174,7 @@ async function readFindingsForRun(projectRoot, runId) {
7080
7174
  const findingsDir = entityDirPath(projectRoot, "finding");
7081
7175
  let names;
7082
7176
  try {
7083
- names = await fs11.readdir(findingsDir);
7177
+ names = await fs12.readdir(findingsDir);
7084
7178
  } catch (err) {
7085
7179
  if (err.code === "ENOENT")
7086
7180
  return [];
@@ -7091,7 +7185,7 @@ async function readFindingsForRun(projectRoot, runId) {
7091
7185
  if (!name.endsWith(".md") || name.startsWith("."))
7092
7186
  continue;
7093
7187
  const filePath = path9.join(findingsDir, name);
7094
- const raw = await fs11.readFile(filePath, "utf8");
7188
+ const raw = await fs12.readFile(filePath, "utf8");
7095
7189
  try {
7096
7190
  const { frontmatter, body } = parseFrontmatter(raw);
7097
7191
  const fm = frontmatter;
@@ -7122,7 +7216,7 @@ async function resolveCanonicalIds(projectRoot, scenarioSlug, currentRunId, curr
7122
7216
  const findingsDir = entityDirPath(projectRoot, "finding");
7123
7217
  let allNames;
7124
7218
  try {
7125
- allNames = await fs11.readdir(findingsDir);
7219
+ allNames = await fs12.readdir(findingsDir);
7126
7220
  } catch {
7127
7221
  allNames = [];
7128
7222
  }
@@ -7130,7 +7224,7 @@ async function resolveCanonicalIds(projectRoot, scenarioSlug, currentRunId, curr
7130
7224
  if (!name.endsWith(".md") || name.startsWith("."))
7131
7225
  continue;
7132
7226
  try {
7133
- const raw = await fs11.readFile(path9.join(findingsDir, name), "utf8");
7227
+ const raw = await fs12.readFile(path9.join(findingsDir, name), "utf8");
7134
7228
  const { frontmatter } = parseFrontmatter(raw);
7135
7229
  const fm = frontmatter;
7136
7230
  if (!fm.scenario || fm.scenario !== scenarioSlug)
@@ -7158,7 +7252,7 @@ async function resolveCanonicalIds(projectRoot, scenarioSlug, currentRunId, curr
7158
7252
  continue;
7159
7253
  const candidatePath = path9.join(findingsDir, name);
7160
7254
  try {
7161
- const candidateRaw = await fs11.readFile(candidatePath, "utf8");
7255
+ const candidateRaw = await fs12.readFile(candidatePath, "utf8");
7162
7256
  const { frontmatter: cFm } = parseFrontmatter(candidateRaw);
7163
7257
  if (cFm.id === f.frontmatter.id) {
7164
7258
  targetPath = candidatePath;
@@ -7169,7 +7263,7 @@ async function resolveCanonicalIds(projectRoot, scenarioSlug, currentRunId, curr
7169
7263
  }
7170
7264
  if (targetPath) {
7171
7265
  await withFileLock(targetPath, async () => {
7172
- const raw = await fs11.readFile(targetPath, "utf8");
7266
+ const raw = await fs12.readFile(targetPath, "utf8");
7173
7267
  const updated = raw.replace(/^(---\n[\s\S]*?)(---\n)/m, `$1canonical_id: ${canonicalId}
7174
7268
  $2`);
7175
7269
  if (updated !== raw) {
@@ -7305,7 +7399,7 @@ async function compareWithBaseline(projectRoot, scenarioSlug, currentRunId, curr
7305
7399
  continue;
7306
7400
  try {
7307
7401
  const mPath = path9.join(runDir(projectRoot, rid), "walkthrough.json");
7308
- const raw = await fs11.readFile(mPath, "utf8");
7402
+ const raw = await fs12.readFile(mPath, "utf8");
7309
7403
  const m = JSON.parse(raw);
7310
7404
  if (m.scenario_slug === scenarioSlug && m.status === "completed") {
7311
7405
  baselineRunId = rid;
@@ -7388,7 +7482,7 @@ async function updateFixStatus(projectRoot, slug, status2) {
7388
7482
  const findingsDir = entityDirPath(projectRoot, "finding");
7389
7483
  const filePath = path9.join(findingsDir, `${slug}.md`);
7390
7484
  await withFileLock(filePath, async () => {
7391
- const raw = await fs11.readFile(filePath, "utf8");
7485
+ const raw = await fs12.readFile(filePath, "utf8");
7392
7486
  const updated = raw.replace(/^(fix_status:\s*).*$/m, `$1${status2}`);
7393
7487
  if (updated !== raw) {
7394
7488
  await atomicWriteFile(filePath, updated);
@@ -7404,7 +7498,7 @@ fix_status: ${status2}`);
7404
7498
  async function listRunsInternal(projectRoot) {
7405
7499
  const dir = path9.join(projectRoot, "iamu", ".runs");
7406
7500
  try {
7407
- const names = await fs11.readdir(dir);
7501
+ const names = await fs12.readdir(dir);
7408
7502
  return names.filter((n) => !n.startsWith(".")).sort();
7409
7503
  } catch {
7410
7504
  return [];
@@ -7586,7 +7680,7 @@ __export(walkthrough_mirror_exports, {
7586
7680
  walkthroughBundleSchema: () => walkthroughBundleSchema
7587
7681
  });
7588
7682
  import { createHash as createHash3 } from "crypto";
7589
- import { promises as fs12 } from "fs";
7683
+ import { promises as fs13 } from "fs";
7590
7684
  import path10 from "path";
7591
7685
  function toCloudRunUuid(runId) {
7592
7686
  if (UUID_RE.test(runId))
@@ -7639,7 +7733,7 @@ function buildPushUrl(cloudApiUrl, cloudProjectId) {
7639
7733
  async function readLocalManifest(projectRoot, runId) {
7640
7734
  const p = path10.join(runDir(projectRoot, runId), "walkthrough.json");
7641
7735
  try {
7642
- const raw = await fs12.readFile(p, "utf8");
7736
+ const raw = await fs13.readFile(p, "utf8");
7643
7737
  return JSON.parse(raw);
7644
7738
  } catch {
7645
7739
  return null;
@@ -7648,7 +7742,7 @@ async function readLocalManifest(projectRoot, runId) {
7648
7742
  async function readLocalReport(projectRoot, runId) {
7649
7743
  const p = path10.join(runDir(projectRoot, runId), "report.md");
7650
7744
  try {
7651
- return await fs12.readFile(p, "utf8");
7745
+ return await fs13.readFile(p, "utf8");
7652
7746
  } catch (err) {
7653
7747
  if (err.code === "ENOENT")
7654
7748
  return "";
@@ -7659,7 +7753,7 @@ async function readScreenshotBytes(projectRoot, runId) {
7659
7753
  const dir = path10.join(runDir(projectRoot, runId), SCREENSHOTS_DIR);
7660
7754
  let names;
7661
7755
  try {
7662
- names = await fs12.readdir(dir);
7756
+ names = await fs13.readdir(dir);
7663
7757
  } catch (err) {
7664
7758
  if (err.code === "ENOENT")
7665
7759
  return /* @__PURE__ */ new Map();
@@ -7675,7 +7769,7 @@ async function readScreenshotBytes(projectRoot, runId) {
7675
7769
  const contentType = EXT_TO_CONTENT_TYPE[ext === ".jpeg" ? ".jpeg" : ext];
7676
7770
  if (!contentType)
7677
7771
  continue;
7678
- const bytes = await fs12.readFile(path10.join(dir, name));
7772
+ const bytes = await fs13.readFile(path10.join(dir, name));
7679
7773
  out.set(seq2, { bytes, filename: name, contentType });
7680
7774
  }
7681
7775
  return out;
@@ -7683,7 +7777,7 @@ async function readScreenshotBytes(projectRoot, runId) {
7683
7777
  async function readVerdictsJson(projectRoot, runId) {
7684
7778
  const filePath = path10.join(runDir(projectRoot, runId), "verdicts.json");
7685
7779
  try {
7686
- const raw = await fs12.readFile(filePath, "utf-8");
7780
+ const raw = await fs13.readFile(filePath, "utf-8");
7687
7781
  return JSON.parse(raw);
7688
7782
  } catch {
7689
7783
  return null;
@@ -7694,7 +7788,7 @@ async function readProvenance(projectRoot, runId, verdicts) {
7694
7788
  return verdicts.provenance;
7695
7789
  const filePath = path10.join(runDir(projectRoot, runId), "canonical.json");
7696
7790
  try {
7697
- const raw = await fs12.readFile(filePath, "utf-8");
7791
+ const raw = await fs13.readFile(filePath, "utf-8");
7698
7792
  const data = JSON.parse(raw);
7699
7793
  return data.provenance ?? null;
7700
7794
  } catch {
@@ -7703,7 +7797,7 @@ async function readProvenance(projectRoot, runId, verdicts) {
7703
7797
  }
7704
7798
  async function readCanonicalExtras(projectRoot, runId) {
7705
7799
  try {
7706
- const data = JSON.parse(await fs12.readFile(path10.join(runDir(projectRoot, runId), "canonical.json"), "utf-8"));
7800
+ const data = JSON.parse(await fs13.readFile(path10.join(runDir(projectRoot, runId), "canonical.json"), "utf-8"));
7707
7801
  const strings = (v) => Array.isArray(v) && v.every((x) => typeof x === "string") ? v : void 0;
7708
7802
  const verification = bundleVerificationSchema.safeParse(data.verification);
7709
7803
  return {
@@ -8442,7 +8536,7 @@ var init_refs = __esm({
8442
8536
  });
8443
8537
 
8444
8538
  // ../../packages/local-store/dist/sync/types.js
8445
- import { z as z23 } from "zod";
8539
+ import { z as z24 } from "zod";
8446
8540
  function toRef(d) {
8447
8541
  return { type: d.type, id: d.id, label: d.label };
8448
8542
  }
@@ -8507,17 +8601,17 @@ var init_types = __esm({
8507
8601
  DEFAULT_TIMEOUT_MS4 = 3e4;
8508
8602
  MAX_BATCH_BYTES = 6 * 1024 * 1024;
8509
8603
  BASE_HASH_BYTES = 96;
8510
- importConflictSchema = z23.object({
8511
- type: z23.string(),
8512
- id: z23.string(),
8513
- base_hash: z23.string().nullish(),
8514
- cloud_hash: z23.string(),
8515
- cloud_updated_at: z23.string().nullish(),
8516
- cloud_changed_by: z23.string().nullish()
8604
+ importConflictSchema = z24.object({
8605
+ type: z24.string(),
8606
+ id: z24.string(),
8607
+ base_hash: z24.string().nullish(),
8608
+ cloud_hash: z24.string(),
8609
+ cloud_updated_at: z24.string().nullish(),
8610
+ cloud_changed_by: z24.string().nullish()
8517
8611
  });
8518
- importResponseSchema = z23.object({
8519
- imported: z23.number().int().min(0),
8520
- conflicts: z23.array(importConflictSchema).optional()
8612
+ importResponseSchema = z24.object({
8613
+ imported: z24.number().int().min(0),
8614
+ conflicts: z24.array(importConflictSchema).optional()
8521
8615
  });
8522
8616
  }
8523
8617
  });
@@ -8768,7 +8862,7 @@ var init_push = __esm({
8768
8862
  });
8769
8863
 
8770
8864
  // ../../packages/local-store/dist/sync/context.js
8771
- import { promises as fs13 } from "fs";
8865
+ import { promises as fs14 } from "fs";
8772
8866
  async function syncContext(target) {
8773
8867
  const { projectRoot } = target;
8774
8868
  const summary = zeroContext();
@@ -8776,7 +8870,7 @@ async function syncContext(target) {
8776
8870
  const conflicts = [];
8777
8871
  let raw;
8778
8872
  try {
8779
- raw = await fs13.readFile(contextPath(projectRoot), "utf8");
8873
+ raw = await fs14.readFile(contextPath(projectRoot), "utf8");
8780
8874
  } catch (err) {
8781
8875
  if (err.code === "ENOENT")
8782
8876
  return { summary, errors, conflicts };
@@ -8986,7 +9080,7 @@ var init_git_branches = __esm({
8986
9080
  });
8987
9081
 
8988
9082
  // ../../packages/local-store/dist/deletions.js
8989
- import { promises as fs14 } from "fs";
9083
+ import { promises as fs15 } from "fs";
8990
9084
  import path12 from "path";
8991
9085
  function describeDeletionCount(type2, n) {
8992
9086
  const nouns = {
@@ -9014,7 +9108,7 @@ function summarizeDeletions(items) {
9014
9108
  }
9015
9109
  async function readPushedEntities(projectRoot) {
9016
9110
  try {
9017
- const raw = await fs14.readFile(pushedEntitiesPath(projectRoot), "utf8");
9111
+ const raw = await fs15.readFile(pushedEntitiesPath(projectRoot), "utf8");
9018
9112
  const parsed = JSON.parse(raw);
9019
9113
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
9020
9114
  return {};
@@ -9053,7 +9147,7 @@ async function rememberPushedEntities(projectRoot, known) {
9053
9147
  }
9054
9148
  async function readLenient(projectRoot, relPath) {
9055
9149
  try {
9056
- const raw = await fs14.readFile(path12.join(projectRoot, relPath), "utf8");
9150
+ const raw = await fs15.readFile(path12.join(projectRoot, relPath), "utf8");
9057
9151
  const fm = parseFrontmatter(raw).frontmatter;
9058
9152
  if (typeof fm.id !== "string" || fm.id.length === 0)
9059
9153
  return null;
@@ -9479,7 +9573,7 @@ var init_deletions2 = __esm({
9479
9573
  });
9480
9574
 
9481
9575
  // ../../packages/local-store/dist/sync/scan.js
9482
- import { promises as fs15 } from "fs";
9576
+ import { promises as fs16 } from "fs";
9483
9577
  function blockedRef(refs, blocked) {
9484
9578
  if (!refs)
9485
9579
  return null;
@@ -9575,7 +9669,7 @@ async function scanPending(projectRoot) {
9575
9669
  continue;
9576
9670
  let bytes;
9577
9671
  try {
9578
- bytes = await fs15.readFile(entity.absPath, "utf8");
9672
+ bytes = await fs16.readFile(entity.absPath, "utf8");
9579
9673
  } catch {
9580
9674
  continue;
9581
9675
  }
@@ -9636,7 +9730,7 @@ var init_scan = __esm({
9636
9730
  });
9637
9731
 
9638
9732
  // ../../packages/local-store/dist/sync/walkthroughs.js
9639
- import { promises as fs16 } from "fs";
9733
+ import { promises as fs17 } from "fs";
9640
9734
  import nodePath from "path";
9641
9735
  async function syncWalkthroughs(projectRoot, deps, unpushableScenarios = /* @__PURE__ */ new Set()) {
9642
9736
  const summary = zeroWalkthroughs();
@@ -9664,7 +9758,7 @@ async function syncWalkthroughs(projectRoot, deps, unpushableScenarios = /* @__P
9664
9758
  const manifestPath = nodePath.join(runDir(projectRoot, runId), "walkthrough.json");
9665
9759
  let manifestRaw;
9666
9760
  try {
9667
- manifestRaw = await fs16.readFile(manifestPath, "utf8");
9761
+ manifestRaw = await fs17.readFile(manifestPath, "utf8");
9668
9762
  } catch {
9669
9763
  continue;
9670
9764
  }
@@ -10003,7 +10097,7 @@ var init_disconnect = __esm({
10003
10097
  });
10004
10098
 
10005
10099
  // ../../packages/local-store/dist/status.js
10006
- import { promises as fs17 } from "fs";
10100
+ import { promises as fs18 } from "fs";
10007
10101
  import path13 from "path";
10008
10102
  async function countType(projectRoot, type2) {
10009
10103
  const result = await listEntities(projectRoot, type2);
@@ -10014,7 +10108,7 @@ async function countType(projectRoot, type2) {
10014
10108
  if (typeof id !== "string" || typeof slug !== "string")
10015
10109
  continue;
10016
10110
  try {
10017
- const raw = await fs17.readFile(entity.absPath, "utf8");
10111
+ const raw = await fs18.readFile(entity.absPath, "utf8");
10018
10112
  contentHashes.set(id, hashNormalizedContent(raw));
10019
10113
  } catch {
10020
10114
  }
@@ -10108,7 +10202,7 @@ async function countContext(projectRoot, opts = {
10108
10202
  return base;
10109
10203
  let pending = true;
10110
10204
  try {
10111
- const bytes = await fs17.readFile(contextPath(projectRoot), "utf8");
10205
+ const bytes = await fs18.readFile(contextPath(projectRoot), "utf8");
10112
10206
  const hash = hashNormalizedContent(bytes);
10113
10207
  pending = opts.lastPush["context:root"] !== hash;
10114
10208
  } catch {
@@ -10130,7 +10224,7 @@ async function countWalkthroughs(projectRoot, opts = { cloudLinked: false, lastP
10130
10224
  const manifestPath = path13.join(projectRoot, "iamu", ".runs", runId, "walkthrough.json");
10131
10225
  let completed = false;
10132
10226
  try {
10133
- const raw = await fs17.readFile(manifestPath, "utf8");
10227
+ const raw = await fs18.readFile(manifestPath, "utf8");
10134
10228
  const manifest = JSON.parse(raw);
10135
10229
  completed = manifest.status === "completed";
10136
10230
  } catch {
@@ -10150,7 +10244,7 @@ async function getProjectStatus(projectRoot, opts = {}) {
10150
10244
  const config = await readConfig(projectRoot);
10151
10245
  const iamuPath = iamuDir(projectRoot);
10152
10246
  try {
10153
- await fs17.access(iamuPath);
10247
+ await fs18.access(iamuPath);
10154
10248
  } catch {
10155
10249
  throw new Error(`status: iamu/ directory missing at ${iamuPath}`);
10156
10250
  }
@@ -10332,13 +10426,13 @@ var init_status = __esm({
10332
10426
  });
10333
10427
 
10334
10428
  // ../../packages/local-store/dist/prune.js
10335
- import { promises as fs18 } from "fs";
10429
+ import { promises as fs19 } from "fs";
10336
10430
  import path14 from "path";
10337
10431
  async function getDirectoryStats(dirPath) {
10338
10432
  let bytes = 0;
10339
10433
  let count = 0;
10340
10434
  try {
10341
- const entries = await fs18.readdir(dirPath, { withFileTypes: true });
10435
+ const entries = await fs19.readdir(dirPath, { withFileTypes: true });
10342
10436
  for (const entry of entries) {
10343
10437
  const fullPath = path14.join(dirPath, entry.name);
10344
10438
  if (entry.isDirectory()) {
@@ -10347,7 +10441,7 @@ async function getDirectoryStats(dirPath) {
10347
10441
  count += sub.count;
10348
10442
  } else if (entry.isFile()) {
10349
10443
  try {
10350
- const s = await fs18.stat(fullPath);
10444
+ const s = await fs19.stat(fullPath);
10351
10445
  bytes += s.size;
10352
10446
  count += 1;
10353
10447
  } catch {
@@ -10363,12 +10457,12 @@ async function getScreenshotsStats(screenshotsDir) {
10363
10457
  let count = 0;
10364
10458
  const paths = [];
10365
10459
  try {
10366
- const entries = await fs18.readdir(screenshotsDir, { withFileTypes: true });
10460
+ const entries = await fs19.readdir(screenshotsDir, { withFileTypes: true });
10367
10461
  for (const entry of entries) {
10368
10462
  if (entry.isFile() && (entry.name.endsWith(".png") || entry.name.endsWith(".jpg") || entry.name.endsWith(".jpeg") || entry.name.endsWith(".webp"))) {
10369
10463
  const fullPath = path14.join(screenshotsDir, entry.name);
10370
10464
  try {
10371
- const s = await fs18.stat(fullPath);
10465
+ const s = await fs19.stat(fullPath);
10372
10466
  bytes += s.size;
10373
10467
  count += 1;
10374
10468
  paths.push(fullPath);
@@ -10389,7 +10483,7 @@ async function pruneRuns(projectRoot, opts = {}) {
10389
10483
  let scenarioSlug;
10390
10484
  try {
10391
10485
  const manifestPath = path14.join(dirPath, "walkthrough.json");
10392
- const raw = await fs18.readFile(manifestPath, "utf-8");
10486
+ const raw = await fs19.readFile(manifestPath, "utf-8");
10393
10487
  const parsed = JSON.parse(raw);
10394
10488
  if (parsed.scenario_slug)
10395
10489
  scenarioSlug = parsed.scenario_slug;
@@ -10401,7 +10495,7 @@ async function pruneRuns(projectRoot, opts = {}) {
10401
10495
  }
10402
10496
  if (!timestamp2 || Number.isNaN(timestamp2.getTime())) {
10403
10497
  try {
10404
- const st = await fs18.stat(dirPath);
10498
+ const st = await fs19.stat(dirPath);
10405
10499
  timestamp2 = st.mtime;
10406
10500
  } catch {
10407
10501
  timestamp2 = /* @__PURE__ */ new Date();
@@ -10441,7 +10535,7 @@ async function pruneRuns(projectRoot, opts = {}) {
10441
10535
  if (opts.screenshotsOnly) {
10442
10536
  if (!opts.dryRun) {
10443
10537
  for (const shotPath of shotStats.paths) {
10444
- await fs18.unlink(shotPath).catch(() => {
10538
+ await fs19.unlink(shotPath).catch(() => {
10445
10539
  });
10446
10540
  }
10447
10541
  }
@@ -10458,7 +10552,7 @@ async function pruneRuns(projectRoot, opts = {}) {
10458
10552
  } else {
10459
10553
  const dirStats = await getDirectoryStats(c.dirPath);
10460
10554
  if (!opts.dryRun) {
10461
- await fs18.rm(c.dirPath, { recursive: true, force: true });
10555
+ await fs19.rm(c.dirPath, { recursive: true, force: true });
10462
10556
  }
10463
10557
  prunedRuns.push({
10464
10558
  runId: c.runId,
@@ -10490,7 +10584,7 @@ var init_prune = __esm({
10490
10584
 
10491
10585
  // ../../packages/local-store/dist/explore-map.js
10492
10586
  import { randomBytes as randomBytes2 } from "crypto";
10493
- import { promises as fs19 } from "fs";
10587
+ import { promises as fs20 } from "fs";
10494
10588
  import path15 from "path";
10495
10589
  function templateToken(token) {
10496
10590
  if (token === "")
@@ -10586,13 +10680,13 @@ function mapPath(projectRoot, id) {
10586
10680
  }
10587
10681
  async function writeExploreMap(projectRoot, map2) {
10588
10682
  const file = mapPath(projectRoot, map2.id);
10589
- await fs19.mkdir(path15.dirname(file), { recursive: true });
10683
+ await fs20.mkdir(path15.dirname(file), { recursive: true });
10590
10684
  await atomicWriteFile(file, `${JSON.stringify(map2, null, 2)}
10591
10685
  `);
10592
10686
  return file;
10593
10687
  }
10594
10688
  async function readExploreMap(projectRoot, id) {
10595
- const raw = await fs19.readFile(mapPath(projectRoot, id), "utf8");
10689
+ const raw = await fs20.readFile(mapPath(projectRoot, id), "utf8");
10596
10690
  const map2 = JSON.parse(raw);
10597
10691
  if (map2?.schema !== EXPLORE_MAP_SCHEMA || !Array.isArray(map2.surfaces)) {
10598
10692
  throw new Error(`Unreadable explore map: ${id}`);
@@ -10601,7 +10695,7 @@ async function readExploreMap(projectRoot, id) {
10601
10695
  }
10602
10696
  async function listExploreMaps(projectRoot) {
10603
10697
  try {
10604
- const entries = await fs19.readdir(exploreDir(projectRoot), {
10698
+ const entries = await fs20.readdir(exploreDir(projectRoot), {
10605
10699
  withFileTypes: true
10606
10700
  });
10607
10701
  return entries.filter((e) => e.isDirectory() && isValidExploreId(e.name)).map((e) => e.name).sort();
@@ -10699,6 +10793,127 @@ async function readAllRunSteps(projectRoot) {
10699
10793
  }
10700
10794
  return out;
10701
10795
  }
10796
+ function matchSurfaceCapabilities(place, capabilities) {
10797
+ const normPlace = place.length > 1 ? place.replace(/\/+$/, "") : place;
10798
+ const matchedCaps = /* @__PURE__ */ new Map();
10799
+ for (const cap of capabilities) {
10800
+ if (!cap.paths || cap.paths.length === 0)
10801
+ continue;
10802
+ for (const rawPath of cap.paths) {
10803
+ if (!rawPath)
10804
+ continue;
10805
+ const cleanPath = rawPath.length > 1 ? rawPath.replace(/\/+$/, "") : rawPath;
10806
+ let isMatch = false;
10807
+ if (cleanPath === "/") {
10808
+ isMatch = normPlace === "/" || normPlace.startsWith("/?") || normPlace.startsWith("/#");
10809
+ } else {
10810
+ isMatch = normPlace === cleanPath || normPlace.startsWith(`${cleanPath}/`) || normPlace.startsWith(`${cleanPath}?`) || normPlace.startsWith(`${cleanPath}#`);
10811
+ }
10812
+ if (isMatch) {
10813
+ const prevLen = matchedCaps.get(cap.slug) ?? -1;
10814
+ if (cleanPath.length > prevLen) {
10815
+ matchedCaps.set(cap.slug, cleanPath.length);
10816
+ }
10817
+ }
10818
+ }
10819
+ }
10820
+ if (matchedCaps.size > 0) {
10821
+ return Array.from(matchedCaps.entries()).sort((a, b) => b[1] - a[1]).map(([slug]) => slug);
10822
+ }
10823
+ const firstSegment = normPlace.replace(/^\/+/, "").split(/[/\\?#]/)[0];
10824
+ if (firstSegment) {
10825
+ const slugMatches = [];
10826
+ for (const c of capabilities) {
10827
+ if (c.slug === firstSegment || c.slug.toLowerCase() === firstSegment.toLowerCase()) {
10828
+ slugMatches.push(c.slug);
10829
+ }
10830
+ }
10831
+ if (slugMatches.length > 0)
10832
+ return slugMatches;
10833
+ }
10834
+ return [];
10835
+ }
10836
+ function summarizeCapabilityCoverage(surfaces, capabilities, outlines = []) {
10837
+ const capMap = /* @__PURE__ */ new Map();
10838
+ for (const c of capabilities) {
10839
+ capMap.set(c.slug, []);
10840
+ }
10841
+ const unassigned = [];
10842
+ for (const s of surfaces) {
10843
+ const matchedSlugs = matchSurfaceCapabilities(s.place, capabilities);
10844
+ if (matchedSlugs.length > 0) {
10845
+ for (const slug of matchedSlugs) {
10846
+ capMap.get(slug)?.push(s);
10847
+ }
10848
+ } else {
10849
+ unassigned.push(s);
10850
+ }
10851
+ }
10852
+ const primaryCounts = /* @__PURE__ */ new Map();
10853
+ const secondaryCounts = /* @__PURE__ */ new Map();
10854
+ for (const o of outlines) {
10855
+ if (o.primary_capability) {
10856
+ primaryCounts.set(o.primary_capability, (primaryCounts.get(o.primary_capability) ?? 0) + 1);
10857
+ }
10858
+ if (Array.isArray(o.additional_capabilities)) {
10859
+ for (const add of o.additional_capabilities) {
10860
+ if (add) {
10861
+ secondaryCounts.set(add, (secondaryCounts.get(add) ?? 0) + 1);
10862
+ }
10863
+ }
10864
+ }
10865
+ }
10866
+ const summaries = capabilities.map((c) => {
10867
+ const owned = capMap.get(c.slug) ?? [];
10868
+ const reached = owned.filter((s) => s.status === "reached").length;
10869
+ const unreached = owned.length - reached;
10870
+ const coverage_pct = owned.length > 0 ? Math.round(reached / owned.length * 100) : 0;
10871
+ const primary = primaryCounts.get(c.slug) ?? 0;
10872
+ const secondary = secondaryCounts.get(c.slug) ?? 0;
10873
+ return {
10874
+ slug: c.slug,
10875
+ title: c.title,
10876
+ area: c.area ?? null,
10877
+ total_surfaces: owned.length,
10878
+ reached_surfaces: reached,
10879
+ unreached_surfaces: unreached,
10880
+ coverage_pct,
10881
+ primary_scenarios: primary,
10882
+ secondary_scenarios: secondary,
10883
+ surfaces: owned
10884
+ };
10885
+ });
10886
+ summaries.sort((a, b) => {
10887
+ const aUntested = a.primary_scenarios === 0 && a.unreached_surfaces > 0;
10888
+ const bUntested = b.primary_scenarios === 0 && b.unreached_surfaces > 0;
10889
+ if (aUntested !== bUntested)
10890
+ return aUntested ? -1 : 1;
10891
+ if (a.coverage_pct !== b.coverage_pct)
10892
+ return a.coverage_pct - b.coverage_pct;
10893
+ return a.slug.localeCompare(b.slug);
10894
+ });
10895
+ return {
10896
+ capabilities: summaries,
10897
+ unassigned_surfaces: unassigned
10898
+ };
10899
+ }
10900
+ async function readCapabilityCoverage(projectRoot, coverage) {
10901
+ const [capsResult, outlinesResult] = await Promise.all([
10902
+ listEntities(projectRoot, "capability"),
10903
+ listEntities(projectRoot, "outline")
10904
+ ]);
10905
+ const caps = capsResult.entities.map((e) => ({
10906
+ slug: e.frontmatter.slug,
10907
+ title: e.frontmatter.title,
10908
+ area: e.frontmatter.area ?? null,
10909
+ paths: e.frontmatter.paths ?? []
10910
+ }));
10911
+ const outlines = outlinesResult.entities.map((e) => ({
10912
+ primary_capability: e.frontmatter.primary_capability,
10913
+ additional_capabilities: e.frontmatter.additional_capabilities ?? []
10914
+ }));
10915
+ return summarizeCapabilityCoverage(coverage.surfaces, caps, outlines);
10916
+ }
10702
10917
  function actionSignature(a) {
10703
10918
  const where = a.outcome.kind === "surface" ? a.outcome.surface : a.outcome.kind === "skipped" ? `skipped:${a.outcome.reason}` : a.outcome.kind === "external" ? `external:${a.outcome.url}` : a.outcome.kind;
10704
10919
  return `${a.role} "${a.name}" \u2192 ${where}`;
@@ -10743,6 +10958,7 @@ var init_explore_map = __esm({
10743
10958
  "../../packages/local-store/dist/explore-map.js"() {
10744
10959
  "use strict";
10745
10960
  init_atomic();
10961
+ init_entity();
10746
10962
  init_paths();
10747
10963
  init_revisit();
10748
10964
  init_runs();
@@ -10755,7 +10971,7 @@ var init_explore_map = __esm({
10755
10971
  });
10756
10972
 
10757
10973
  // ../../packages/local-store/dist/human-test-link.js
10758
- import { promises as fs20 } from "fs";
10974
+ import { promises as fs21 } from "fs";
10759
10975
  import path16 from "path";
10760
10976
  function humanTestLinks(verdicts) {
10761
10977
  const best = /* @__PURE__ */ new Map();
@@ -10779,7 +10995,7 @@ async function linkFindingsToHumanTests(projectRoot, runId, verdicts) {
10779
10995
  const dir = entityDirPath(projectRoot, "finding");
10780
10996
  let names;
10781
10997
  try {
10782
- names = await fs20.readdir(dir);
10998
+ names = await fs21.readdir(dir);
10783
10999
  } catch {
10784
11000
  return [];
10785
11001
  }
@@ -10791,7 +11007,7 @@ async function linkFindingsToHumanTests(projectRoot, runId, verdicts) {
10791
11007
  let raw;
10792
11008
  let fm;
10793
11009
  try {
10794
- raw = await fs20.readFile(file, "utf8");
11010
+ raw = await fs21.readFile(file, "utf8");
10795
11011
  fm = parseFrontmatter(raw).frontmatter;
10796
11012
  } catch {
10797
11013
  continue;
@@ -11022,14 +11238,14 @@ var init_cloud_client = __esm({
11022
11238
  });
11023
11239
 
11024
11240
  // ../../packages/local-store/dist/last-pull.js
11025
- import { promises as fs21 } from "fs";
11241
+ import { promises as fs22 } from "fs";
11026
11242
  import path17 from "path";
11027
11243
  function lastPullPath(projectRoot) {
11028
11244
  return path17.join(cacheDir(projectRoot), "last-pull.json");
11029
11245
  }
11030
11246
  async function readLastPull(projectRoot) {
11031
11247
  try {
11032
- const raw = await fs21.readFile(lastPullPath(projectRoot), "utf8");
11248
+ const raw = await fs22.readFile(lastPullPath(projectRoot), "utf8");
11033
11249
  const parsed = JSON.parse(raw);
11034
11250
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
11035
11251
  return parsed;
@@ -11315,7 +11531,7 @@ var init_frontmatter2 = __esm({
11315
11531
  });
11316
11532
 
11317
11533
  // ../../packages/local-store/dist/pull/write.js
11318
- import { promises as fs22 } from "fs";
11534
+ import { promises as fs23 } from "fs";
11319
11535
  async function loadLocalIndex(projectRoot, type2) {
11320
11536
  const byId = /* @__PURE__ */ new Map();
11321
11537
  const bySlug = /* @__PURE__ */ new Map();
@@ -11344,7 +11560,7 @@ async function classifyExisting(projectRoot, type2, cloudId, localSlug, targetSl
11344
11560
  if (localSlug !== targetSlug)
11345
11561
  return "untracked";
11346
11562
  const lastSynced = localIndex.lastPushById[cloudId];
11347
- const bytes = await fs22.readFile(entityFilePath(projectRoot, type2, localSlug), "utf8");
11563
+ const bytes = await fs23.readFile(entityFilePath(projectRoot, type2, localSlug), "utf8");
11348
11564
  return lastSynced && hashNormalizedContent(bytes) === lastSynced ? "fast_forward" : "diverged";
11349
11565
  }
11350
11566
  async function writeOrSkip(projectRoot, type2, cloudId, slug, frontmatter, body, localIndex, mode, cloudVersion) {
@@ -11405,13 +11621,13 @@ async function writeOrSkip(projectRoot, type2, cloudId, slug, frontmatter, body,
11405
11621
  }
11406
11622
  if (existingSlugForId && existingSlugForId !== slug) {
11407
11623
  try {
11408
- await fs22.unlink(entityFilePath(projectRoot, type2, existingSlugForId));
11624
+ await fs23.unlink(entityFilePath(projectRoot, type2, existingSlugForId));
11409
11625
  localIndex.bySlug.delete(existingSlugForId);
11410
11626
  } catch {
11411
11627
  }
11412
11628
  }
11413
11629
  await writeEntity(projectRoot, type2, frontmatter, text);
11414
- const bytes = await fs22.readFile(entityFilePath(projectRoot, type2, slug), "utf8");
11630
+ const bytes = await fs23.readFile(entityFilePath(projectRoot, type2, slug), "utf8");
11415
11631
  const hash = hashNormalizedContent(bytes);
11416
11632
  localIndex.byId.set(cloudId, slug);
11417
11633
  localIndex.bySlug.set(slug, cloudId);
@@ -11465,7 +11681,7 @@ var init_write = __esm({
11465
11681
  });
11466
11682
 
11467
11683
  // ../../packages/local-store/dist/pull/entities.js
11468
- import { promises as fs23 } from "fs";
11684
+ import { promises as fs24 } from "fs";
11469
11685
  function buildContextFrontmatter(payload) {
11470
11686
  const cloudCtx = payload.context ?? {};
11471
11687
  const glossary = (payload.glossary ?? []).map((g) => ({
@@ -11520,7 +11736,7 @@ async function pullContext(projectRoot, cloud, projectId, mode, result) {
11520
11736
  const frontmatter = buildContextFrontmatter(payload);
11521
11737
  let hasLocal = false;
11522
11738
  try {
11523
- await fs23.access(contextPath(projectRoot));
11739
+ await fs24.access(contextPath(projectRoot));
11524
11740
  hasLocal = true;
11525
11741
  } catch {
11526
11742
  hasLocal = false;
@@ -11550,7 +11766,7 @@ async function pullContext(projectRoot, cloud, projectId, mode, result) {
11550
11766
  frontmatter,
11551
11767
  notes_body: existingNotes
11552
11768
  });
11553
- const bytes = await fs23.readFile(contextPath(projectRoot), "utf8");
11769
+ const bytes = await fs24.readFile(contextPath(projectRoot), "utf8");
11554
11770
  const hash = hashNormalizedContent(bytes);
11555
11771
  await recordLastPull(projectRoot, "context:root", hash);
11556
11772
  await recordLastPush(projectRoot, "context:root", hash);
@@ -12070,6 +12286,7 @@ var init_dist2 = __esm({
12070
12286
  init_bootstrap();
12071
12287
  init_config2();
12072
12288
  init_context2();
12289
+ init_testability2();
12073
12290
  init_connect();
12074
12291
  init_disconnect();
12075
12292
  init_status();
@@ -12967,10 +13184,10 @@ import { existsSync as existsSync4 } from "fs";
12967
13184
  import { dirname as dirname2, resolve } from "path";
12968
13185
  import { fileURLToPath } from "url";
12969
13186
  function getTemplatesRoot() {
12970
- const thisDir2 = dirname2(fileURLToPath(import.meta.url));
12971
- const inThisDir = resolve(thisDir2, "templates");
13187
+ const thisDir = dirname2(fileURLToPath(import.meta.url));
13188
+ const inThisDir = resolve(thisDir, "templates");
12972
13189
  if (existsSync4(inThisDir)) return inThisDir;
12973
- const inParent = resolve(thisDir2, "..", "templates");
13190
+ const inParent = resolve(thisDir, "..", "templates");
12974
13191
  if (existsSync4(inParent)) return inParent;
12975
13192
  return inThisDir;
12976
13193
  }
@@ -12993,11 +13210,24 @@ import { dirname as dirname3, join as join4, resolve as resolve2 } from "path";
12993
13210
  import { fileURLToPath as fileURLToPath2 } from "url";
12994
13211
  import chalk6 from "chalk";
12995
13212
  function getVersion() {
13213
+ if ("0.2.0-beta.17") {
13214
+ return "0.2.0-beta.17";
13215
+ }
12996
13216
  try {
12997
- const thisDir2 = dirname3(fileURLToPath2(import.meta.url));
12998
- const pkgPath = resolve2(thisDir2, "..", "..", "package.json");
12999
- const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
13000
- return pkg.version ?? "0.0.0";
13217
+ const thisDir = dirname3(fileURLToPath2(import.meta.url));
13218
+ for (const rel of [".", "..", "../.."]) {
13219
+ const pkgPath = resolve2(thisDir, rel, "package.json");
13220
+ if (existsSync5(pkgPath)) {
13221
+ const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
13222
+ if (pkg.name === "@iamu-dev/iamu" && pkg.version) {
13223
+ return pkg.version;
13224
+ }
13225
+ if (pkg.version && pkg.version !== "0.0.0") {
13226
+ return pkg.version;
13227
+ }
13228
+ }
13229
+ }
13230
+ return "0.0.0";
13001
13231
  } catch {
13002
13232
  return "0.0.0";
13003
13233
  }
@@ -14887,17 +15117,12 @@ __export(start_run_exports, {
14887
15117
  startRun: () => startRun,
14888
15118
  testabilityTarget: () => testabilityTarget
14889
15119
  });
14890
- import fs24 from "fs/promises";
14891
15120
  import path20 from "path";
14892
15121
  import chalk12 from "chalk";
14893
15122
  async function testabilityTarget(projectRoot) {
14894
15123
  try {
14895
- const raw = await fs24.readFile(
14896
- path20.join(projectRoot, "iamu", "testability.md"),
14897
- "utf8"
14898
- );
14899
- const { frontmatter } = parseFrontmatter(raw);
14900
- return typeof frontmatter.target_url === "string" && frontmatter.target_url.trim().length > 0 ? frontmatter.target_url.trim() : null;
15124
+ const doc = await readTestability(projectRoot);
15125
+ return doc?.frontmatter.target_url ?? null;
14901
15126
  } catch {
14902
15127
  return null;
14903
15128
  }
@@ -15525,7 +15750,7 @@ var init_fatal = __esm({
15525
15750
 
15526
15751
  // src/sync-mcp/tools.ts
15527
15752
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
15528
- import { z as z24 } from "zod";
15753
+ import { z as z25 } from "zod";
15529
15754
  function getProjectRoot() {
15530
15755
  const root = findProjectRoot(process.cwd());
15531
15756
  if (!root)
@@ -15766,7 +15991,7 @@ function createSyncServer() {
15766
15991
  server.tool(
15767
15992
  "iamu_requests_claim",
15768
15993
  "Claim one work request before working on it. Takes an exclusive lease (2h, renewable by re-claiming) so no teammate's agent does the same work. Returns the full payload: the finding's ui_fact and consequence with its local finding ids, or the scenario and target URL, plus `next` \u2014 the skill step to run. Fails with lease_held when someone else is on it.",
15769
- { id: z24.string().uuid().describe("Request id from iamu_requests") },
15994
+ { id: z25.string().uuid().describe("Request id from iamu_requests") },
15770
15995
  async ({ id }) => {
15771
15996
  try {
15772
15997
  const linked = await resolveLinkedCloudClient();
@@ -16426,11 +16651,14 @@ var init_serve = __esm({
16426
16651
  // src/commands/init.ts
16427
16652
  var init_exports = {};
16428
16653
  __export(init_exports, {
16654
+ createDraftTestabilityContent: () => createDraftTestabilityContent,
16655
+ detectTargetPort: () => detectTargetPort,
16656
+ detectTargetUrl: () => detectTargetUrl,
16429
16657
  init: () => init,
16430
16658
  setupCommand: () => setupCommand
16431
16659
  });
16432
16660
  import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
16433
- import { mkdir as mkdir4, writeFile as writeFile3 } from "fs/promises";
16661
+ import { mkdir as mkdir4, readdir, writeFile as writeFile3 } from "fs/promises";
16434
16662
  import { basename as basename2, join as join5 } from "path";
16435
16663
  import { isCancel as isCancel3, multiselect, select as select3 } from "@clack/prompts";
16436
16664
  import chalk18 from "chalk";
@@ -16458,6 +16686,81 @@ function setupCommand(projectRoot, hosts) {
16458
16686
  if (hosts.every((h) => h.value === "claude")) return "/iamu:init";
16459
16687
  return "/iamu-setup (Claude Code: /iamu:init)";
16460
16688
  }
16689
+ function detectTargetPort(projectRoot) {
16690
+ if (process.env.PORT) {
16691
+ const p = Number.parseInt(process.env.PORT, 10);
16692
+ if (!Number.isNaN(p) && p > 0 && p <= 65535) return p;
16693
+ }
16694
+ for (const envFile of [".env.local", ".env.development", ".env"]) {
16695
+ const fullPath = join5(projectRoot, envFile);
16696
+ if (existsSync7(fullPath)) {
16697
+ try {
16698
+ const content = readFileSync5(fullPath, "utf-8");
16699
+ for (const line of content.split("\n")) {
16700
+ const trimmed = line.trim();
16701
+ if (trimmed.startsWith("#") || !trimmed.includes("=")) continue;
16702
+ const [key, ...rest] = trimmed.split("=");
16703
+ const k = key.trim();
16704
+ const val = rest.join("=").trim().replace(/^["']|["']$/g, "");
16705
+ if (k === "PORT" || k === "VITE_PORT") {
16706
+ const p = Number.parseInt(val, 10);
16707
+ if (!Number.isNaN(p) && p > 0 && p <= 65535) return p;
16708
+ }
16709
+ if (k === "NEXT_PUBLIC_APP_URL" || k === "APP_URL") {
16710
+ try {
16711
+ const url = new URL(val);
16712
+ if (url.port) return Number.parseInt(url.port, 10);
16713
+ } catch {
16714
+ }
16715
+ }
16716
+ }
16717
+ } catch {
16718
+ }
16719
+ }
16720
+ }
16721
+ const pkgPath = join5(projectRoot, "package.json");
16722
+ if (existsSync7(pkgPath)) {
16723
+ try {
16724
+ const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
16725
+ const scripts = pkg.scripts || {};
16726
+ for (const script of Object.values(scripts)) {
16727
+ if (typeof script !== "string") continue;
16728
+ const portMatch = script.match(/(?:-p|--port(?:=|\s+))(\d+)/);
16729
+ if (portMatch) {
16730
+ const p = Number.parseInt(portMatch[1], 10);
16731
+ if (!Number.isNaN(p) && p > 0 && p <= 65535) return p;
16732
+ }
16733
+ }
16734
+ const allDeps = {
16735
+ ...pkg.dependencies || {},
16736
+ ...pkg.devDependencies || {}
16737
+ };
16738
+ if ("astro" in allDeps) return 4321;
16739
+ if ("vite" in allDeps) return 5173;
16740
+ } catch {
16741
+ }
16742
+ }
16743
+ for (const f of ["astro.config.mjs", "astro.config.ts", "astro.config.js"]) {
16744
+ if (existsSync7(join5(projectRoot, f))) return 4321;
16745
+ }
16746
+ for (const f of [
16747
+ "vite.config.ts",
16748
+ "vite.config.js",
16749
+ "vite.config.mjs",
16750
+ "vite.config.cjs"
16751
+ ]) {
16752
+ if (existsSync7(join5(projectRoot, f))) return 5173;
16753
+ }
16754
+ return void 0;
16755
+ }
16756
+ function detectTargetUrl(projectRoot) {
16757
+ const port = detectTargetPort(projectRoot);
16758
+ return port ? `http://localhost:${port}` : "http://localhost:3000";
16759
+ }
16760
+ function createDraftTestabilityContent(targetUrl = "http://localhost:3000") {
16761
+ const doc = emptyTestability(targetUrl);
16762
+ return serializeFrontmatter(doc.frontmatter, doc.body);
16763
+ }
16461
16764
  async function init(opts) {
16462
16765
  const options = typeof opts === "string" ? { name: opts } : opts ?? {};
16463
16766
  const projectRoot = process.cwd();
@@ -16475,12 +16778,86 @@ async function init(opts) {
16475
16778
  console.log();
16476
16779
  }
16477
16780
  let freshInit = false;
16781
+ const backfilled = [];
16478
16782
  if (existsSync7(join5(iamuDir2, "config.json"))) {
16479
- const config2 = await readConfig(projectRoot);
16480
- if (!options.json) {
16481
- console.log(
16482
- chalk18.yellow(" iamu/ already exists") + chalk18.dim(` (${config2?.project_name ?? "unknown"})`)
16783
+ const testabilityPath2 = join5(iamuDir2, "testability.md");
16784
+ if (!existsSync7(testabilityPath2)) {
16785
+ const targetUrl = detectTargetUrl(projectRoot);
16786
+ await writeFile3(
16787
+ testabilityPath2,
16788
+ createDraftTestabilityContent(targetUrl)
16483
16789
  );
16790
+ backfilled.push("testability.md");
16791
+ }
16792
+ const contextPath2 = join5(iamuDir2, "context.yml");
16793
+ if (!existsSync7(contextPath2)) {
16794
+ await writeFile3(
16795
+ contextPath2,
16796
+ serializeFrontmatter(
16797
+ { status: "draft", schema_version: 1, name: projectName },
16798
+ ""
16799
+ )
16800
+ );
16801
+ backfilled.push("context.yml");
16802
+ }
16803
+ const gitignorePath = join5(iamuDir2, ".gitignore");
16804
+ if (!existsSync7(gitignorePath)) {
16805
+ await writeFile3(gitignorePath, GITIGNORE_CONTENT);
16806
+ backfilled.push(".gitignore");
16807
+ }
16808
+ const cacheDirPath = join5(iamuDir2, ".cache");
16809
+ if (!existsSync7(cacheDirPath)) {
16810
+ await mkdir4(cacheDirPath, { recursive: true });
16811
+ backfilled.push(".cache/");
16812
+ }
16813
+ const runsDirPath = join5(iamuDir2, ".runs");
16814
+ if (!existsSync7(runsDirPath)) {
16815
+ await mkdir4(runsDirPath, { recursive: true });
16816
+ }
16817
+ for (const type2 of ENTITY_TYPES) {
16818
+ const sub = join5(iamuDir2, ENTITY_DIRS[type2]);
16819
+ if (!existsSync7(sub)) {
16820
+ await mkdir4(sub, { recursive: true });
16821
+ }
16822
+ const keep = join5(sub, ".gitkeep");
16823
+ if (!existsSync7(keep)) {
16824
+ try {
16825
+ const files = await readdir(sub);
16826
+ if (files.length === 0) {
16827
+ await writeFile3(keep, "");
16828
+ }
16829
+ } catch {
16830
+ await writeFile3(keep, "");
16831
+ }
16832
+ }
16833
+ }
16834
+ const promptTemplates2 = loadPromptTemplates();
16835
+ const promptsDir2 = join5(iamuDir2, ".prompts");
16836
+ const missingPrompts = [];
16837
+ if (!existsSync7(promptsDir2)) {
16838
+ missingPrompts.push(".prompts/");
16839
+ } else {
16840
+ for (const [name] of promptTemplates2) {
16841
+ if (!existsSync7(join5(promptsDir2, name))) {
16842
+ missingPrompts.push(name);
16843
+ }
16844
+ }
16845
+ }
16846
+ if (missingPrompts.length > 0) {
16847
+ if (missingPrompts.includes(".prompts/")) {
16848
+ backfilled.push(".prompts/");
16849
+ } else {
16850
+ backfilled.push(...missingPrompts.map((p) => `.prompts/${p}`));
16851
+ }
16852
+ }
16853
+ if (!options.json) {
16854
+ if (backfilled.length > 0) {
16855
+ console.log(
16856
+ ` ${symbols.success} Scaffolding verified (backfilled missing ${backfilled.join(", ")})`
16857
+ );
16858
+ } else {
16859
+ console.log(` ${symbols.success} Scaffolding up to date`);
16860
+ }
16484
16861
  }
16485
16862
  } else {
16486
16863
  freshInit = true;
@@ -16496,33 +16873,12 @@ async function init(opts) {
16496
16873
  ""
16497
16874
  )
16498
16875
  );
16499
- const testabilityPath = join5(iamuDir2, "testability.md");
16500
- if (!existsSync7(testabilityPath)) {
16876
+ const testabilityPath2 = join5(iamuDir2, "testability.md");
16877
+ if (!existsSync7(testabilityPath2)) {
16878
+ const targetUrl = detectTargetUrl(projectRoot);
16501
16879
  await writeFile3(
16502
- testabilityPath,
16503
- serializeFrontmatter(
16504
- {
16505
- status: "draft",
16506
- target_url: "http://localhost:3000"
16507
- },
16508
- `# Project Testability & Environment
16509
-
16510
- Target URL and execution harness for walkthroughs.
16511
- Audited and populated during /iamu:init.
16512
-
16513
- ## Target & Run Instructions
16514
- - Default URL: http://localhost:3000
16515
-
16516
- ## Reachable Surfaces
16517
- (Run /iamu:init to discover testable routes)
16518
-
16519
- ## Mocks & Data Fixtures
16520
- (Run /iamu:init to inspect existing harnesses)
16521
-
16522
- ## Untestable Boundaries
16523
- (None documented yet)
16524
- `
16525
- )
16880
+ testabilityPath2,
16881
+ createDraftTestabilityContent(targetUrl)
16526
16882
  );
16527
16883
  }
16528
16884
  }
@@ -16532,7 +16888,13 @@ Audited and populated during /iamu:init.
16532
16888
  for (const [name, content] of promptTemplates) {
16533
16889
  await writeFile3(join5(promptsDir, name), content);
16534
16890
  }
16535
- const detected = detectHosts(projectRoot);
16891
+ const detectedInstalled = detectInstalledHosts(projectRoot);
16892
+ const detectedWorkspace = detectHosts(projectRoot);
16893
+ const detectedMap = /* @__PURE__ */ new Map();
16894
+ for (const h of [...detectedInstalled, ...detectedWorkspace]) {
16895
+ detectedMap.set(h.value, h);
16896
+ }
16897
+ const detected = Array.from(detectedMap.values());
16536
16898
  if (!options.json && detected.length > 0) {
16537
16899
  console.log(
16538
16900
  chalk18.dim(" Detected: ") + detected.map((h) => h.name).join(", ") + chalk18.dim(" (pre-selected)")
@@ -16641,6 +17003,7 @@ Audited and populated during /iamu:init.
16641
17003
  projectName,
16642
17004
  projectRoot,
16643
17005
  fresh: freshInit,
17006
+ backfilled,
16644
17007
  tools: selectedHosts.map((h) => h.name),
16645
17008
  skillsCount: totalSkills,
16646
17009
  commandsCount: totalCommands,
@@ -16742,6 +17105,7 @@ var init_init = __esm({
16742
17105
  "src/commands/init.ts"() {
16743
17106
  "use strict";
16744
17107
  init_dist2();
17108
+ init_dist();
16745
17109
  init_claude_plugin();
16746
17110
  init_registry();
16747
17111
  init_cloud_url();
@@ -16809,6 +17173,25 @@ async function status(opts) {
16809
17173
  const incompleteRuns = await listIncompleteRuns(projectRoot);
16810
17174
  const { rerun } = await computeFixStates(projectRoot);
16811
17175
  const staleSkills = await findStaleSkills(projectRoot);
17176
+ let exploreSummary = null;
17177
+ try {
17178
+ const maps = await listExploreMaps(projectRoot);
17179
+ if (maps.length > 0) {
17180
+ const latestMapId = maps.at(-1);
17181
+ const map2 = await readExploreMap(projectRoot, latestMapId);
17182
+ if (map2) {
17183
+ const steps = await readAllRunSteps(projectRoot);
17184
+ const cov = exploreCoverage(map2, steps);
17185
+ exploreSummary = {
17186
+ map_id: latestMapId,
17187
+ total_surfaces: cov.surfaces.length,
17188
+ reached_surfaces: cov.reached,
17189
+ unreached_surfaces: cov.unreached
17190
+ };
17191
+ }
17192
+ }
17193
+ } catch {
17194
+ }
16812
17195
  if (opts.json) {
16813
17196
  console.log(
16814
17197
  JSON.stringify(
@@ -16832,6 +17215,7 @@ async function status(opts) {
16832
17215
  last: runs.at(-1) ?? null,
16833
17216
  incomplete: incompleteRuns
16834
17217
  },
17218
+ explore: exploreSummary,
16835
17219
  rerun: rerun.map((r) => ({
16836
17220
  ...r,
16837
17221
  command: rerunCommand(projectRoot, r.scenario)
@@ -16919,6 +17303,11 @@ async function status(opts) {
16919
17303
  )
16920
17304
  );
16921
17305
  }
17306
+ if (exploreSummary) {
17307
+ console.log(
17308
+ ` ${padRight("product map", 14)} ${chalk19.cyan(String(exploreSummary.total_surfaces))} surfaces (${chalk19.green(`${exploreSummary.reached_surfaces} reached`)} \xB7 ${chalk19.yellow(`${exploreSummary.unreached_surfaces} unreached`)})`
17309
+ );
17310
+ }
16922
17311
  if (projectStatus.requests && projectStatus.requests.open > 0) {
16923
17312
  console.log(
16924
17313
  chalk19.cyan(
@@ -17221,7 +17610,7 @@ var init_browse_driver = __esm({
17221
17610
  });
17222
17611
 
17223
17612
  // src/commands/step-numbering.ts
17224
- import { open, readdir, unlink } from "fs/promises";
17613
+ import { open, readdir as readdir2, unlink } from "fs/promises";
17225
17614
  import path22 from "path";
17226
17615
  function stepFileName(stepNumber2) {
17227
17616
  return `${String(stepNumber2).padStart(3, "0")}.json`;
@@ -17230,7 +17619,7 @@ function stepLimitMessage(maxSteps, runId) {
17230
17619
  return `Step limit of ${maxSteps} reached for run "${runId}". To prevent runaway agent loops, step aborted. Complete the walk with \`iamu complete\` or increase the limit with --max-steps <n>.`;
17231
17620
  }
17232
17621
  async function peekNextStepNumber(stepsDir) {
17233
- const files = await readdir(stepsDir).catch(() => []);
17622
+ const files = await readdir2(stepsDir).catch(() => []);
17234
17623
  let max = 0;
17235
17624
  for (const f of files) {
17236
17625
  const m = /^(\d+)\.json$/.exec(f);
@@ -18299,7 +18688,7 @@ async function checkTargetHealth(targetUrl) {
18299
18688
  const res = await fetch(targetUrl, {
18300
18689
  method: "GET",
18301
18690
  redirect: "manual",
18302
- signal: AbortSignal.timeout(3e3)
18691
+ signal: AbortSignal.timeout(1e3)
18303
18692
  });
18304
18693
  if ([301, 302, 303, 307, 308].includes(res.status)) {
18305
18694
  const location = res.headers.get("location") || "";
@@ -18413,21 +18802,20 @@ async function checkProjectHarnesses(projectRoot) {
18413
18802
  }
18414
18803
  async function checkProjectTestabilityContract(projectRoot) {
18415
18804
  const checks = [];
18416
- const testabilityPath = join7(projectRoot, "iamu", "testability.md");
18417
- if (!existsSync10(testabilityPath)) {
18418
- checks.push({
18419
- category: "target",
18420
- name: "Testability Contract (testability.md)",
18421
- status: "warn",
18422
- message: "iamu/testability.md not found",
18423
- remedy: "Run `/iamu:init` to audit your codebase and record testable routes, auth strategies, and mock harnesses."
18424
- });
18425
- return { checks };
18426
- }
18427
18805
  try {
18428
- const raw = await fs26.readFile(testabilityPath, "utf8");
18429
- const { frontmatter } = parseFrontmatter(raw);
18430
- const targetUrl = typeof frontmatter.target_url === "string" ? frontmatter.target_url : void 0;
18806
+ const doc = await readTestability(projectRoot);
18807
+ if (!doc) {
18808
+ checks.push({
18809
+ category: "target",
18810
+ name: "Testability Contract (testability.md)",
18811
+ status: "warn",
18812
+ message: "iamu/testability.md not found",
18813
+ remedy: "Run `/iamu:init` to audit your codebase and record testable routes, auth strategies, and mock harnesses."
18814
+ });
18815
+ return { checks };
18816
+ }
18817
+ const { frontmatter } = doc;
18818
+ const targetUrl = frontmatter.target_url;
18431
18819
  const details = [];
18432
18820
  if (targetUrl) details.push(`Target: ${targetUrl}`);
18433
18821
  if (frontmatter.dev_command) {
@@ -19424,6 +19812,19 @@ async function update(opts) {
19424
19812
  console.log(` ${symbols.success} ${promptsUpdated} prompts updated`);
19425
19813
  }
19426
19814
  }
19815
+ if (existsSync11(iamuDir2)) {
19816
+ const testabilityPath2 = join8(iamuDir2, "testability.md");
19817
+ if (!existsSync11(testabilityPath2)) {
19818
+ const targetUrl = detectTargetUrl(projectRoot);
19819
+ await writeFile4(
19820
+ testabilityPath2,
19821
+ createDraftTestabilityContent(targetUrl)
19822
+ );
19823
+ console.log(
19824
+ ` ${symbols.success} Repaired testability contract: created iamu/testability.md (draft)`
19825
+ );
19826
+ }
19827
+ }
19427
19828
  const { hosts: targetHosts, error } = resolveTargetHosts(
19428
19829
  projectRoot,
19429
19830
  opts?.host
@@ -19479,6 +19880,7 @@ var init_update = __esm({
19479
19880
  init_mcp_config();
19480
19881
  init_context4();
19481
19882
  init_skills();
19883
+ init_init();
19482
19884
  init_templates();
19483
19885
  }
19484
19886
  });
@@ -19504,11 +19906,13 @@ function defaultFrontmatter(type2, id, slug, name, opts) {
19504
19906
  };
19505
19907
  case "outline": {
19506
19908
  const tests = Array.isArray(opts.humanTest) ? opts.humanTest : opts.humanTest ? [opts.humanTest] : [];
19909
+ const additionalCaps = Array.isArray(opts.additionalCapabilities) ? opts.additionalCapabilities : typeof opts.additionalCapabilities === "string" ? opts.additionalCapabilities.split(",").map((s) => s.trim()).filter(Boolean) : [];
19507
19910
  return {
19508
19911
  ...base,
19509
19912
  title: name,
19510
19913
  ...description,
19511
19914
  ...opts.capability ? { primary_capability: opts.capability } : {},
19915
+ ...additionalCaps.length > 0 ? { additional_capabilities: additionalCaps } : {},
19512
19916
  ...opts.tension ? { tension: opts.tension } : {},
19513
19917
  ...opts.narrative ? { narrative: opts.narrative } : {},
19514
19918
  ...tests.length > 0 ? {
@@ -19519,8 +19923,16 @@ function defaultFrontmatter(type2, id, slug, name, opts) {
19519
19923
  } : {}
19520
19924
  };
19521
19925
  }
19522
- case "capability":
19523
- return { ...base, title: name, ...description };
19926
+ case "capability": {
19927
+ const capPaths = Array.isArray(opts.paths) ? opts.paths : typeof opts.paths === "string" ? opts.paths.split(",").map((s) => s.trim()).filter(Boolean) : [];
19928
+ return {
19929
+ ...base,
19930
+ title: name,
19931
+ ...description,
19932
+ ...capPaths.length > 0 ? { paths: capPaths } : {},
19933
+ ...opts.area ? { area: opts.area.trim() } : {}
19934
+ };
19935
+ }
19524
19936
  case "source":
19525
19937
  return {
19526
19938
  ...base,
@@ -20115,7 +20527,6 @@ __export(explore_exports, {
20115
20527
  exploreList: () => exploreList,
20116
20528
  exploreRun: () => exploreRun
20117
20529
  });
20118
- import { promises as fs28 } from "fs";
20119
20530
  import path26 from "path";
20120
20531
  import chalk27 from "chalk";
20121
20532
  function fail2(message, json2) {
@@ -20133,12 +20544,8 @@ function positiveInt(raw, fallback, flag) {
20133
20544
  }
20134
20545
  async function testabilityTarget2(projectRoot) {
20135
20546
  try {
20136
- const raw = await fs28.readFile(
20137
- path26.join(projectRoot, "iamu", "testability.md"),
20138
- "utf8"
20139
- );
20140
- const { frontmatter } = parseFrontmatter(raw);
20141
- return typeof frontmatter.target_url === "string" ? frontmatter.target_url : null;
20547
+ const doc = await readTestability(projectRoot);
20548
+ return doc?.frontmatter.target_url ?? null;
20142
20549
  } catch {
20143
20550
  return null;
20144
20551
  }
@@ -20320,8 +20727,17 @@ async function exploreCoverageCommand(mapId, opts) {
20320
20727
  return;
20321
20728
  }
20322
20729
  const cov = exploreCoverage(map2, await readAllRunSteps(ctx.projectRoot));
20730
+ const capReport = await readCapabilityCoverage(ctx.projectRoot, cov);
20323
20731
  if (opts.json) {
20324
- console.log(JSON.stringify(cov));
20732
+ console.log(
20733
+ JSON.stringify({
20734
+ ...cov,
20735
+ ...capReport.capabilities.length > 0 ? {
20736
+ capabilities: capReport.capabilities,
20737
+ unassigned_surfaces: capReport.unassigned_surfaces
20738
+ } : {}
20739
+ })
20740
+ );
20325
20741
  return;
20326
20742
  }
20327
20743
  console.log(
@@ -20331,26 +20747,122 @@ async function exploreCoverageCommand(mapId, opts) {
20331
20747
  console.log(
20332
20748
  ` ${chalk27.green(`${cov.reached} reached`)} \xB7 ${chalk27.yellow(`${cov.place_only} URL reached, view unclear`)} \xB7 ${chalk27.red(`${cov.unreached} never reached`)}`
20333
20749
  );
20334
- const section = (title, rows, color) => {
20335
- if (rows.length === 0) return;
20336
- console.log(`
20337
- ${color(title)}`);
20338
- for (const r of rows) {
20750
+ if (capReport.capabilities.length > 0) {
20751
+ console.log(chalk27.bold("\n CAPABILITY COVERAGE"));
20752
+ console.log(
20753
+ chalk27.dim(
20754
+ " \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"
20755
+ )
20756
+ );
20757
+ console.log(
20758
+ chalk27.dim(
20759
+ ` ${"Capability".padEnd(24)} ${"Surfaces".padStart(8)} ${"Coverage".padStart(8)} ${"Scenarios (1\xB0 / 2\xB0)"}`
20760
+ )
20761
+ );
20762
+ console.log(
20763
+ chalk27.dim(
20764
+ " \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"
20765
+ )
20766
+ );
20767
+ for (const cap of capReport.capabilities) {
20768
+ const title = cap.slug.padEnd(24);
20769
+ const surfaces = `${cap.reached_surfaces}/${cap.total_surfaces}`.padStart(
20770
+ 8
20771
+ );
20772
+ const covStr = `${cap.coverage_pct}%`.padStart(8);
20773
+ const scenarios = `${cap.primary_scenarios} / ${cap.secondary_scenarios}`;
20774
+ const untestedFlag = cap.primary_scenarios === 0 && cap.unreached_surfaces > 0 ? chalk27.yellow(" (untested)") : "";
20775
+ const covColored = cap.coverage_pct === 100 ? chalk27.green(covStr) : cap.coverage_pct === 0 ? chalk27.red(covStr) : chalk27.yellow(covStr);
20339
20776
  console.log(
20340
- ` ${r.key.padEnd(32)} ${r.label}${chalk27.dim(` (depth ${r.depth})`)}`
20777
+ ` ${title} ${surfaces} ${covColored} ${scenarios}${untestedFlag}`
20341
20778
  );
20342
20779
  }
20343
- };
20344
- section(
20345
- "Never reached by a run",
20346
- cov.surfaces.filter((s) => s.status === "unreached"),
20347
- chalk27.red
20348
- );
20349
- section(
20350
- "URL reached, but no step names this view",
20351
- cov.surfaces.filter((s) => s.status === "place_only"),
20352
- chalk27.yellow
20353
- );
20780
+ if (capReport.unassigned_surfaces.length > 0) {
20781
+ const unassigned = capReport.unassigned_surfaces;
20782
+ const reached = unassigned.filter((s) => s.status === "reached").length;
20783
+ const pct = Math.round(reached / unassigned.length * 100);
20784
+ console.log(
20785
+ chalk27.dim(
20786
+ ` ${"(Unassigned)".padEnd(24)} ${`${reached}/${unassigned.length}`.padStart(8)} ${`${pct}%`.padStart(8)} -`
20787
+ )
20788
+ );
20789
+ }
20790
+ console.log(
20791
+ chalk27.dim(
20792
+ " \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"
20793
+ )
20794
+ );
20795
+ const hasGaps = capReport.capabilities.some((c) => c.unreached_surfaces > 0) || capReport.unassigned_surfaces.some((s) => s.status !== "reached");
20796
+ if (hasGaps) {
20797
+ console.log(chalk27.red("\n UNREACHED SURFACES (Gaps)"));
20798
+ for (const cap of capReport.capabilities) {
20799
+ const gaps = cap.surfaces.filter((s) => s.status !== "reached");
20800
+ if (gaps.length === 0) continue;
20801
+ console.log(` ${chalk27.bold(cap.slug)}:`);
20802
+ for (const s of gaps) {
20803
+ console.log(
20804
+ ` ${chalk27.red("\u25CB")} ${s.key.padEnd(32)} ${s.label}${chalk27.dim(` (depth ${s.depth})`)}`
20805
+ );
20806
+ }
20807
+ }
20808
+ const unassignedGaps = capReport.unassigned_surfaces.filter(
20809
+ (s) => s.status !== "reached"
20810
+ );
20811
+ if (unassignedGaps.length > 0) {
20812
+ console.log(chalk27.dim(" (Unassigned):"));
20813
+ for (const s of unassignedGaps) {
20814
+ console.log(
20815
+ ` ${chalk27.dim("\u25CB")} ${s.key.padEnd(32)} ${s.label}${chalk27.dim(` (depth ${s.depth})`)}`
20816
+ );
20817
+ }
20818
+ }
20819
+ }
20820
+ if (opts.all) {
20821
+ console.log(chalk27.green("\n REACHED SURFACES"));
20822
+ for (const cap of capReport.capabilities) {
20823
+ const reached = cap.surfaces.filter((s) => s.status === "reached");
20824
+ if (reached.length === 0) continue;
20825
+ console.log(` ${chalk27.bold(cap.slug)}:`);
20826
+ for (const s of reached) {
20827
+ console.log(
20828
+ ` ${chalk27.green("\u2713")} ${s.key.padEnd(32)} ${s.label}${chalk27.dim(` (depth ${s.depth})`)}`
20829
+ );
20830
+ }
20831
+ }
20832
+ const unassignedReached = capReport.unassigned_surfaces.filter(
20833
+ (s) => s.status === "reached"
20834
+ );
20835
+ if (unassignedReached.length > 0) {
20836
+ console.log(chalk27.dim(" (Unassigned):"));
20837
+ for (const s of unassignedReached) {
20838
+ console.log(
20839
+ ` ${chalk27.green("\u2713")} ${s.key.padEnd(32)} ${s.label}${chalk27.dim(` (depth ${s.depth})`)}`
20840
+ );
20841
+ }
20842
+ }
20843
+ }
20844
+ } else {
20845
+ const section = (title, rows, color) => {
20846
+ if (rows.length === 0) return;
20847
+ console.log(`
20848
+ ${color(title)}`);
20849
+ for (const r of rows) {
20850
+ console.log(
20851
+ ` ${r.key.padEnd(32)} ${r.label}${chalk27.dim(` (depth ${r.depth})`)}`
20852
+ );
20853
+ }
20854
+ };
20855
+ section(
20856
+ "Never reached by a run",
20857
+ cov.surfaces.filter((s) => s.status === "unreached"),
20858
+ chalk27.red
20859
+ );
20860
+ section(
20861
+ "URL reached, but no step names this view",
20862
+ cov.surfaces.filter((s) => s.status === "place_only"),
20863
+ chalk27.yellow
20864
+ );
20865
+ }
20354
20866
  if (cov.run_only_places.length > 0) {
20355
20867
  console.log(`
20356
20868
  ${chalk27.cyan("Walked by runs, not in the map")}`);
@@ -20910,17 +21422,13 @@ async function instructionsWalk(opts) {
20910
21422
  }
20911
21423
  const untestableBoundaries = [];
20912
21424
  try {
20913
- const testabilityPath = join9(projectRoot, "iamu", "testability.md");
20914
- if (existsSync12(testabilityPath)) {
20915
- const raw = readFileSync8(testabilityPath, "utf-8");
20916
- const { frontmatter, body } = parseFrontmatter(raw);
20917
- if (!targetUrl && typeof frontmatter.target_url === "string" && frontmatter.target_url.trim().length > 0) {
20918
- targetUrl = frontmatter.target_url.trim();
21425
+ const doc = await readTestability(projectRoot);
21426
+ if (doc) {
21427
+ if (!targetUrl && doc.frontmatter.target_url) {
21428
+ targetUrl = doc.frontmatter.target_url;
20919
21429
  }
20920
- const fromFm = Array.isArray(
20921
- frontmatter.untestable_boundaries
20922
- ) ? frontmatter.untestable_boundaries.filter(Boolean) : [];
20923
- const fromBody = extractUntestableBoundaries(body);
21430
+ const fromFm = doc.frontmatter.untestable_boundaries ?? [];
21431
+ const fromBody = extractUntestableBoundaries(doc.body);
20924
21432
  untestableBoundaries.push(
20925
21433
  ...Array.from(/* @__PURE__ */ new Set([...fromFm, ...fromBody]))
20926
21434
  );
@@ -21277,7 +21785,7 @@ var summary_exports = {};
21277
21785
  __export(summary_exports, {
21278
21786
  summaryCommand: () => summaryCommand
21279
21787
  });
21280
- import { promises as fs29 } from "fs";
21788
+ import { promises as fs28 } from "fs";
21281
21789
  import path28 from "path";
21282
21790
  import chalk30 from "chalk";
21283
21791
  async function summaryCommand(runIdArg, opts) {
@@ -21304,7 +21812,7 @@ async function summaryCommand(runIdArg, opts) {
21304
21812
  }
21305
21813
  raw = Buffer.concat(chunks).toString("utf8");
21306
21814
  } else {
21307
- raw = await fs29.readFile(path28.resolve(projectRoot, opts.file), "utf8");
21815
+ raw = await fs28.readFile(path28.resolve(projectRoot, opts.file), "utf8");
21308
21816
  }
21309
21817
  const parsed = JSON.parse(raw);
21310
21818
  if (Array.isArray(parsed.what_worked)) {
@@ -21472,10 +21980,10 @@ __export(evaluate_exports, {
21472
21980
  evaluate: () => evaluate,
21473
21981
  evaluateRecord: () => evaluateRecord
21474
21982
  });
21475
- import { promises as fs30 } from "fs";
21983
+ import { promises as fs29 } from "fs";
21476
21984
  import path29 from "path";
21477
21985
  import chalk32 from "chalk";
21478
- import { z as z25 } from "zod";
21986
+ import { z as z26 } from "zod";
21479
21987
  function formatIssues(error) {
21480
21988
  return error.issues.map(
21481
21989
  (i) => `${i.path.length > 0 ? i.path.join(".") : "(root)"}: ${i.message}`
@@ -21507,7 +22015,7 @@ async function evaluate(runId, opts) {
21507
22015
  const canonicalPath = path29.join(runDirPath, "canonical.json");
21508
22016
  let canonical;
21509
22017
  try {
21510
- const raw = await fs30.readFile(canonicalPath, "utf8");
22018
+ const raw = await fs29.readFile(canonicalPath, "utf8");
21511
22019
  canonical = JSON.parse(raw);
21512
22020
  } catch {
21513
22021
  commandError(
@@ -21564,7 +22072,7 @@ async function evaluate(runId, opts) {
21564
22072
  };
21565
22073
  const prompt = composeJudgePrompt(input);
21566
22074
  const promptFilePath = opts?.promptFile ?? path29.join(runDirPath, "judge_prompt.md");
21567
- await fs30.writeFile(promptFilePath, prompt, "utf8");
22075
+ await fs29.writeFile(promptFilePath, prompt, "utf8");
21568
22076
  if (opts?.json) {
21569
22077
  console.log(
21570
22078
  JSON.stringify(
@@ -21591,7 +22099,7 @@ async function evaluateRecord(runId, opts) {
21591
22099
  const canonicalPath = path29.join(runDirPath, "canonical.json");
21592
22100
  let canonical;
21593
22101
  try {
21594
- const raw = await fs30.readFile(canonicalPath, "utf8");
22102
+ const raw = await fs29.readFile(canonicalPath, "utf8");
21595
22103
  canonical = JSON.parse(raw);
21596
22104
  } catch {
21597
22105
  commandError(`No canonical.json for run: ${runId}`, opts);
@@ -21613,7 +22121,7 @@ async function evaluateRecord(runId, opts) {
21613
22121
  }
21614
22122
  raw = Buffer.concat(chunks).toString("utf8");
21615
22123
  } else {
21616
- raw = await fs30.readFile(opts.file, "utf8");
22124
+ raw = await fs29.readFile(opts.file, "utf8");
21617
22125
  }
21618
22126
  parsedFile = JSON.parse(raw);
21619
22127
  } catch (err) {
@@ -21736,28 +22244,28 @@ var init_evaluate = __esm({
21736
22244
  init_context4();
21737
22245
  init_live_run_client();
21738
22246
  init_view_results();
21739
- verdictValueSchema = z25.enum(["yes", "partial", "no", "not_applicable"]);
21740
- idList = z25.array(z25.string()).optional();
21741
- verdictEntrySchema = z25.object({
21742
- human_test_id: z25.string().min(1).optional(),
21743
- id: z25.string().min(1).optional(),
22247
+ verdictValueSchema = z26.enum(["yes", "partial", "no", "not_applicable"]);
22248
+ idList = z26.array(z26.string()).optional();
22249
+ verdictEntrySchema = z26.object({
22250
+ human_test_id: z26.string().min(1).optional(),
22251
+ id: z26.string().min(1).optional(),
21744
22252
  verdict: verdictValueSchema,
21745
- reasoning: z25.string(),
22253
+ reasoning: z26.string(),
21746
22254
  grounding_step_ids: idList,
21747
22255
  grounding_finding_ids: idList
21748
22256
  }).refine((v) => Boolean(v.human_test_id ?? v.id), {
21749
22257
  message: "must carry human_test_id (or id)",
21750
22258
  path: ["human_test_id"]
21751
22259
  });
21752
- verdictsFileSchema = z25.object({
21753
- verdicts: z25.array(verdictEntrySchema).optional(),
21754
- human_tests: z25.array(verdictEntrySchema).optional(),
21755
- overall: z25.object({ answer: z25.string(), reasoning: z25.string() }).passthrough().optional(),
21756
- provenance: z25.object({
21757
- judge_mode: z25.string().optional(),
21758
- prompt_id: z25.string().optional(),
21759
- prompt_version: z25.string().optional(),
21760
- model: z25.string().optional()
22260
+ verdictsFileSchema = z26.object({
22261
+ verdicts: z26.array(verdictEntrySchema).optional(),
22262
+ human_tests: z26.array(verdictEntrySchema).optional(),
22263
+ overall: z26.object({ answer: z26.string(), reasoning: z26.string() }).passthrough().optional(),
22264
+ provenance: z26.object({
22265
+ judge_mode: z26.string().optional(),
22266
+ prompt_id: z26.string().optional(),
22267
+ prompt_version: z26.string().optional(),
22268
+ model: z26.string().optional()
21761
22269
  }).passthrough().optional()
21762
22270
  });
21763
22271
  }
@@ -21785,7 +22293,7 @@ __export(complete_exports, {
21785
22293
  screenshotSizeWarning: () => screenshotSizeWarning,
21786
22294
  validateCanonicalWalkthrough: () => validateCanonicalWalkthrough
21787
22295
  });
21788
- import { promises as fs31 } from "fs";
22296
+ import { promises as fs30 } from "fs";
21789
22297
  import path30 from "path";
21790
22298
  import chalk33 from "chalk";
21791
22299
  async function complete(runId, opts) {
@@ -21812,7 +22320,7 @@ async function complete(runId, opts) {
21812
22320
  const walkerSummary = await readWalkerSummary(run.runDirPath);
21813
22321
  let judgeReported;
21814
22322
  try {
21815
- const verdictsRaw = await fs31.readFile(
22323
+ const verdictsRaw = await fs30.readFile(
21816
22324
  path30.join(run.runDirPath, "verdicts.json"),
21817
22325
  "utf8"
21818
22326
  );
@@ -21865,7 +22373,7 @@ async function loadRun(runId, opts) {
21865
22373
  if (!resolved) return null;
21866
22374
  const { projectRoot, runDirPath } = resolved;
21867
22375
  try {
21868
- await fs31.access(runDirPath);
22376
+ await fs30.access(runDirPath);
21869
22377
  } catch {
21870
22378
  commandError(`Run not found: ${runId}`, opts);
21871
22379
  return null;
@@ -21874,7 +22382,7 @@ async function loadRun(runId, opts) {
21874
22382
  let manifest;
21875
22383
  try {
21876
22384
  manifest = JSON.parse(
21877
- await fs31.readFile(manifestPath, "utf8")
22385
+ await fs30.readFile(manifestPath, "utf8")
21878
22386
  );
21879
22387
  } catch {
21880
22388
  commandError(`No walkthrough.json found for run: ${runId}`, opts);
@@ -21886,7 +22394,7 @@ async function reportAlreadyCompleted(run, opts) {
21886
22394
  const { runId, manifest } = run;
21887
22395
  if (opts?.json) {
21888
22396
  try {
21889
- const raw = await fs31.readFile(
22397
+ const raw = await fs30.readFile(
21890
22398
  path30.join(run.runDirPath, "canonical.json"),
21891
22399
  "utf8"
21892
22400
  );
@@ -22076,14 +22584,14 @@ async function screenshotSizeWarning(runDirPath, viewport) {
22076
22584
  const dir = path30.join(runDirPath, "screenshots");
22077
22585
  let first;
22078
22586
  try {
22079
- first = (await fs31.readdir(dir)).filter((f) => f.toLowerCase().endsWith(".png")).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }))[0];
22587
+ first = (await fs30.readdir(dir)).filter((f) => f.toLowerCase().endsWith(".png")).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }))[0];
22080
22588
  } catch {
22081
22589
  return null;
22082
22590
  }
22083
22591
  if (!first) return null;
22084
22592
  let size = null;
22085
22593
  try {
22086
- const handle = await fs31.open(path30.join(dir, first), "r");
22594
+ const handle = await fs30.open(path30.join(dir, first), "r");
22087
22595
  try {
22088
22596
  const head = Buffer.alloc(24);
22089
22597
  const { bytesRead } = await handle.read(head, 0, 24, 0);
@@ -22100,7 +22608,7 @@ async function screenshotSizeWarning(runDirPath, viewport) {
22100
22608
  async function readWalkerSummary(runDirPath) {
22101
22609
  try {
22102
22610
  const data = JSON.parse(
22103
- await fs31.readFile(path30.join(runDirPath, "summary.json"), "utf8")
22611
+ await fs30.readFile(path30.join(runDirPath, "summary.json"), "utf8")
22104
22612
  );
22105
22613
  const strings = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && x.trim() !== "") : void 0;
22106
22614
  const model = typeof data.model === "string" && data.model.trim() !== "" ? data.model.trim() : void 0;
@@ -22209,7 +22717,7 @@ function validateCanonicalWalkthrough(canonical) {
22209
22717
  async function applyRecordedVerdicts(canonical, runDirPath) {
22210
22718
  try {
22211
22719
  const recorded = JSON.parse(
22212
- await fs31.readFile(path30.join(runDirPath, "verdicts.json"), "utf8")
22720
+ await fs30.readFile(path30.join(runDirPath, "verdicts.json"), "utf8")
22213
22721
  );
22214
22722
  if (Array.isArray(recorded.verdicts)) {
22215
22723
  return applyVerdicts(canonical, recorded);
@@ -22397,14 +22905,14 @@ __export(match_exports, {
22397
22905
  match: () => match,
22398
22906
  matchRecord: () => matchRecord
22399
22907
  });
22400
- import { promises as fs32 } from "fs";
22908
+ import { promises as fs31 } from "fs";
22401
22909
  import path31 from "path";
22402
22910
  import chalk34 from "chalk";
22403
22911
  async function completedManifest2(runDirPath, runId, opts) {
22404
22912
  let manifest;
22405
22913
  try {
22406
22914
  manifest = JSON.parse(
22407
- await fs32.readFile(path31.join(runDirPath, "walkthrough.json"), "utf8")
22915
+ await fs31.readFile(path31.join(runDirPath, "walkthrough.json"), "utf8")
22408
22916
  );
22409
22917
  } catch {
22410
22918
  commandError(`Run not found: ${runId}`, opts);
@@ -22435,10 +22943,10 @@ async function match(runId, opts) {
22435
22943
  const prompt = needed ? composeMatchPrompt(input) : null;
22436
22944
  const promptFilePath = opts?.promptFile ?? path31.join(runDirPath, "match_prompt.md");
22437
22945
  if (prompt) {
22438
- await fs32.writeFile(promptFilePath, prompt, "utf8");
22946
+ await fs31.writeFile(promptFilePath, prompt, "utf8");
22439
22947
  } else {
22440
22948
  try {
22441
- await fs32.unlink(promptFilePath);
22949
+ await fs31.unlink(promptFilePath);
22442
22950
  } catch {
22443
22951
  }
22444
22952
  }
@@ -22492,7 +23000,7 @@ async function matchRecord(runId, opts) {
22492
23000
  }
22493
23001
  raw = Buffer.concat(chunks).toString("utf8");
22494
23002
  } else {
22495
- raw = await fs32.readFile(opts.file, "utf8");
23003
+ raw = await fs31.readFile(opts.file, "utf8");
22496
23004
  }
22497
23005
  answer = JSON.parse(raw);
22498
23006
  } catch (err) {
@@ -22682,9 +23190,8 @@ var init_prune2 = __esm({
22682
23190
 
22683
23191
  // src/bin.ts
22684
23192
  init_context4();
22685
- import { existsSync as existsSync13, readFileSync as readFileSync9 } from "fs";
22686
- import { dirname as dirname5, resolve as resolve3 } from "path";
22687
- import { fileURLToPath as fileURLToPath3 } from "url";
23193
+ import { existsSync as existsSync13 } from "fs";
23194
+ import { resolve as resolve3 } from "path";
22688
23195
  import chalk38 from "chalk";
22689
23196
  import { Command } from "commander";
22690
23197
 
@@ -22880,6 +23387,12 @@ function registerProjectCommands(program2) {
22880
23387
  "--description <text>",
22881
23388
  "One-line description (capability, outline)"
22882
23389
  ).option("--capability <slug>", "Primary capability slug (outline only)").option(
23390
+ "--additional-capabilities <slugs>",
23391
+ "Secondary capability slugs, comma-separated (outline only)"
23392
+ ).option(
23393
+ "--paths <paths>",
23394
+ "Route patterns owned by this capability, comma-separated (capability only)"
23395
+ ).option("--area <name>", "Visual grouping category (capability only)").option(
22883
23396
  "--tension <text>",
22884
23397
  "Structural tension driving this situation (outline only)"
22885
23398
  ).option(
@@ -22941,10 +23454,12 @@ Examples:
22941
23454
  );
22942
23455
  explore.command("coverage [map]").description(
22943
23456
  "Surfaces of a map (default: latest) that no run reached, and places runs reached that the map lacks"
22944
- ).option("--json", "Output as JSON").action(async (map2, opts) => {
22945
- const { exploreCoverageCommand: exploreCoverageCommand2 } = await Promise.resolve().then(() => (init_explore(), explore_exports));
22946
- await exploreCoverageCommand2(map2, opts);
22947
- });
23457
+ ).option("--json", "Output as JSON").option("--all", "List all surfaces, including reached ones").action(
23458
+ async (map2, opts) => {
23459
+ const { exploreCoverageCommand: exploreCoverageCommand2 } = await Promise.resolve().then(() => (init_explore(), explore_exports));
23460
+ await exploreCoverageCommand2(map2, opts);
23461
+ }
23462
+ );
22948
23463
  explore.command("diff [a] [b]").description("Compare two maps (default: the two latest)").option("--json", "Output as JSON").action(
22949
23464
  async (a, b, opts) => {
22950
23465
  const { exploreDiffCommand: exploreDiffCommand2 } = await Promise.resolve().then(() => (init_explore(), explore_exports));
@@ -23185,6 +23700,9 @@ function registerAllCommands(program2) {
23185
23700
  registerCloudCommands(program2);
23186
23701
  }
23187
23702
 
23703
+ // src/bin.ts
23704
+ init_skills();
23705
+
23188
23706
  // src/ui/panic.ts
23189
23707
  init_logger();
23190
23708
  import chalk36 from "chalk";
@@ -23307,15 +23825,7 @@ function printUpdateNotice(currentVersion, latestVersion) {
23307
23825
 
23308
23826
  // src/bin.ts
23309
23827
  setupProcessHandlers();
23310
- var thisDir = dirname5(fileURLToPath3(import.meta.url));
23311
- var version = "0.0.0";
23312
- try {
23313
- const pkg = JSON.parse(
23314
- readFileSync9(resolve3(thisDir, "..", "package.json"), "utf-8")
23315
- );
23316
- version = pkg.version ?? "0.0.0";
23317
- } catch {
23318
- }
23828
+ var version = getVersion();
23319
23829
  var program = new Command().name("iamu").description(
23320
23830
  "Scenario-driven development \u2014 test your product as realistic personas"
23321
23831
  ).version(version).option(