@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/index.js CHANGED
@@ -247,7 +247,14 @@ var init_capability = __esm({
247
247
  capabilityFrontmatterSchema = entityBaseSchema.extend({
248
248
  /** Human-readable title; corresponds to cloud `name`. */
249
249
  title: z5.string().min(1),
250
- description: z5.string().nullish()
250
+ description: z5.string().nullish(),
251
+ /**
252
+ * Route patterns owned by this capability (e.g. `["/billing", "/invoices"]`).
253
+ * Evaluated with segment-boundary Longest Prefix Match against surface places.
254
+ */
255
+ paths: z5.array(z5.string()).default([]),
256
+ /** Optional visual grouping category (e.g. "Finance", "Administration"). */
257
+ area: z5.string().nullish()
251
258
  });
252
259
  }
253
260
  });
@@ -873,6 +880,58 @@ var init_source = __esm({
873
880
  }
874
881
  });
875
882
 
883
+ // ../../packages/protocol/dist/schemas/frontmatter/testability.js
884
+ import { z as z17 } from "zod";
885
+ function emptyTestability(targetUrl = "http://localhost:3000") {
886
+ return {
887
+ frontmatter: {
888
+ schema_version: SCHEMA_VERSION,
889
+ status: "draft",
890
+ target_url: targetUrl
891
+ },
892
+ body: `# Project Testability & Environment
893
+
894
+ Target URL and execution harness for walkthroughs.
895
+ Audited and populated during /iamu:init.
896
+
897
+ ## Target & Run Instructions
898
+ - Default URL: ${targetUrl}
899
+
900
+ ## Reachable Surfaces
901
+ (Run /iamu:init to discover testable routes)
902
+
903
+ ## Mocks & Data Fixtures
904
+ (Run /iamu:init to inspect existing harnesses)
905
+
906
+ ## Untestable Boundaries
907
+ (None documented yet)
908
+ `
909
+ };
910
+ }
911
+ var testabilityStatusEnum, authStrategyEnum, testabilityFrontmatterSchema;
912
+ var init_testability = __esm({
913
+ "../../packages/protocol/dist/schemas/frontmatter/testability.js"() {
914
+ "use strict";
915
+ init_version();
916
+ testabilityStatusEnum = z17.enum(["draft", "verified"]);
917
+ authStrategyEnum = z17.enum([
918
+ "dev_switcher",
919
+ "interactive",
920
+ "test_accounts",
921
+ "public"
922
+ ]);
923
+ testabilityFrontmatterSchema = z17.object({
924
+ schema_version: z17.number().int().optional(),
925
+ status: testabilityStatusEnum.default("draft"),
926
+ target_url: z17.string().url().describe("Target URL where the product is hosted or served locally."),
927
+ dev_command: z17.string().max(1e3).optional().describe("Dev script or startup command to boot the app locally."),
928
+ auth_strategy: authStrategyEnum.or(z17.string().min(1).max(100)).optional().describe("How personas authenticate in test environments."),
929
+ harnesses: z17.array(z17.string().min(1).max(500)).max(64).optional().describe("Mocking frameworks, seed scripts, or test harnesses present."),
930
+ untestable_boundaries: z17.array(z17.string().min(1).max(1024)).max(128).optional().describe("External services, third-party APIs, or surfaces forbidden to autonomous walkers.")
931
+ });
932
+ }
933
+ });
934
+
876
935
  // ../../packages/protocol/dist/schemas/frontmatter/index.js
877
936
  var ENTITY_TYPES, entityFrontmatterSchemas, ENTITY_DIRS;
878
937
  var init_frontmatter = __esm({
@@ -890,6 +949,7 @@ var init_frontmatter = __esm({
890
949
  init_run_artifacts();
891
950
  init_scenario();
892
951
  init_source();
952
+ init_testability();
893
953
  init_capability();
894
954
  init_finding();
895
955
  init_guidance();
@@ -928,7 +988,7 @@ var init_frontmatter = __esm({
928
988
  });
929
989
 
930
990
  // ../../packages/protocol/dist/device.js
931
- import { z as z17 } from "zod";
991
+ import { z as z18 } from "zod";
932
992
  function deviceFromPersonaTags(tags) {
933
993
  const matches = [];
934
994
  for (const tag of tags ?? []) {
@@ -975,22 +1035,22 @@ var init_device = __esm({
975
1035
  desktop: "desktop",
976
1036
  laptop: "desktop"
977
1037
  };
978
- runViewportSchema = z17.object({
979
- device: z17.enum(DEVICE_CLASSES),
980
- width: z17.number().int().positive(),
981
- height: z17.number().int().positive(),
982
- source: z17.literal("persona-tags")
1038
+ runViewportSchema = z18.object({
1039
+ device: z18.enum(DEVICE_CLASSES),
1040
+ width: z18.number().int().positive(),
1041
+ height: z18.number().int().positive(),
1042
+ source: z18.literal("persona-tags")
983
1043
  });
984
1044
  }
985
1045
  });
986
1046
 
987
1047
  // ../../packages/protocol/dist/types/fingerprint.js
988
- import { z as z18 } from "zod";
1048
+ import { z as z19 } from "zod";
989
1049
  var stepActionTypeEnum, stepActionAnchorSchema, stepActionSchema, networkFingerprintEntrySchema, hex, stepFingerprintSchema;
990
1050
  var init_fingerprint = __esm({
991
1051
  "../../packages/protocol/dist/types/fingerprint.js"() {
992
1052
  "use strict";
993
- stepActionTypeEnum = z18.enum([
1053
+ stepActionTypeEnum = z19.enum([
994
1054
  "click",
995
1055
  "fill",
996
1056
  "hover",
@@ -1000,46 +1060,46 @@ var init_fingerprint = __esm({
1000
1060
  "done",
1001
1061
  "note"
1002
1062
  ]);
1003
- stepActionAnchorSchema = z18.object({
1063
+ stepActionAnchorSchema = z19.object({
1004
1064
  /** Max 512 chars to prevent bloating JSONB storage. */
1005
- role: z18.string().max(512).optional(),
1065
+ role: z19.string().max(512).optional(),
1006
1066
  /** Max 512 chars to prevent bloating JSONB storage. */
1007
- accessibleName: z18.string().max(512).optional(),
1067
+ accessibleName: z19.string().max(512).optional(),
1008
1068
  /** Max 512 chars to prevent bloating JSONB storage. */
1009
- text: z18.string().max(512).optional()
1069
+ text: z19.string().max(512).optional()
1010
1070
  });
1011
- stepActionSchema = z18.object({
1071
+ stepActionSchema = z19.object({
1012
1072
  type: stepActionTypeEnum,
1013
1073
  anchor: stepActionAnchorSchema.optional(),
1014
1074
  /** Max 10 fallback selectors, each capped at 1024 chars. */
1015
- selectorFallbacks: z18.array(z18.string().max(1024)).max(10).optional()
1075
+ selectorFallbacks: z19.array(z19.string().max(1024)).max(10).optional()
1016
1076
  });
1017
- networkFingerprintEntrySchema = z18.object({
1077
+ networkFingerprintEntrySchema = z19.object({
1018
1078
  /** HTTP method, max 16 chars (e.g. "GET", "POST", "OPTIONS"). */
1019
- method: z18.string().max(16),
1079
+ method: z19.string().max(16),
1020
1080
  /** Templated URL pattern, max 2048 chars. */
1021
- urlPattern: z18.string().max(2048),
1022
- status: z18.number().int().nonnegative()
1081
+ urlPattern: z19.string().max(2048),
1082
+ status: z19.number().int().nonnegative()
1023
1083
  });
1024
- hex = (len) => z18.string().regex(new RegExp(`^([0-9a-f]{${len}})?$`), `expected hex string of length ${len} or ""`);
1025
- stepFingerprintSchema = z18.object({
1084
+ hex = (len) => z19.string().regex(new RegExp(`^([0-9a-f]{${len}})?$`), `expected hex string of length ${len} or ""`);
1085
+ stepFingerprintSchema = z19.object({
1026
1086
  /** Full page URL at time of step, max 4096 chars. */
1027
- url: z18.string().max(4096),
1087
+ url: z19.string().max(4096),
1028
1088
  action: stepActionSchema,
1029
1089
  domHash: hex(64),
1030
1090
  axTreeHash: hex(64),
1031
1091
  screenshotPHash: hex(16),
1032
1092
  /** Max 500 network entries to prevent runner abuse bloating JSONB. */
1033
- networkFingerprint: z18.array(networkFingerprintEntrySchema).max(500),
1034
- consoleErrorCount: z18.number().int().nonnegative(),
1035
- jsExceptionCount: z18.number().int().nonnegative(),
1036
- settleDurationMs: z18.number().int().nonnegative()
1093
+ networkFingerprint: z19.array(networkFingerprintEntrySchema).max(500),
1094
+ consoleErrorCount: z19.number().int().nonnegative(),
1095
+ jsExceptionCount: z19.number().int().nonnegative(),
1096
+ settleDurationMs: z19.number().int().nonnegative()
1037
1097
  });
1038
1098
  }
1039
1099
  });
1040
1100
 
1041
1101
  // ../../packages/protocol/dist/schemas/walkthrough-bundle.js
1042
- import { z as z19 } from "zod";
1102
+ import { z as z20 } from "zod";
1043
1103
  function defaultEnvironment(targetUrl) {
1044
1104
  try {
1045
1105
  const origin = new URL(targetUrl).origin;
@@ -1058,147 +1118,147 @@ var init_walkthrough_bundle = __esm({
1058
1118
  init_finding();
1059
1119
  init_run_artifacts();
1060
1120
  init_fingerprint();
1061
- partNameSchema = z19.string().min(1).max(128).regex(/^[a-z][a-z0-9_-]*$/, {
1121
+ partNameSchema = z20.string().min(1).max(128).regex(/^[a-z][a-z0-9_-]*$/, {
1062
1122
  message: "part_name must be kebab-or-snake-case ASCII starting with a letter"
1063
1123
  }).describe("Key of the matching multipart form field in the bundle body. Must match a binary part exactly.");
1064
- artifactRefSchema = z19.object({
1065
- 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."),
1124
+ artifactRefSchema = z20.object({
1125
+ 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."),
1066
1126
  part_name: partNameSchema,
1067
- filename: z19.string().min(1).max(256).describe("Original filename as the client saw it. Used for logging and as a fallback display label."),
1068
- 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."),
1069
- 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.")
1127
+ filename: z20.string().min(1).max(256).describe("Original filename as the client saw it. Used for logging and as a fallback display label."),
1128
+ 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."),
1129
+ 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.")
1070
1130
  });
1071
- stepScreenshotRefSchema = z19.object({
1131
+ stepScreenshotRefSchema = z20.object({
1072
1132
  part_name: partNameSchema,
1073
- filename: z19.string().min(1).max(256),
1074
- content_type: z19.string().min(1).max(128)
1133
+ filename: z20.string().min(1).max(256),
1134
+ content_type: z20.string().min(1).max(128)
1075
1135
  });
1076
- bundleStepSchema = z19.object({
1077
- 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."),
1078
- title: z19.string().min(1).max(2048).describe("Short first-person heading for the step \u2014 shows in the rendered report."),
1079
- persona_reasoning: z19.string().max(65536).nullable().optional().describe("First-person inner voice capture. Nullable for terse / action-only steps."),
1080
- grounding: z19.string().max(65536).nullable().optional().describe("Optional citations, domain-knowledge grounding, or verification notes for this step."),
1136
+ bundleStepSchema = z20.object({
1137
+ 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."),
1138
+ title: z20.string().min(1).max(2048).describe("Short first-person heading for the step \u2014 shows in the rendered report."),
1139
+ persona_reasoning: z20.string().max(65536).nullable().optional().describe("First-person inner voice capture. Nullable for terse / action-only steps."),
1140
+ grounding: z20.string().max(65536).nullable().optional().describe("Optional citations, domain-knowledge grounding, or verification notes for this step."),
1081
1141
  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)."),
1082
1142
  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."),
1083
1143
  screenshot_ref: stepScreenshotRefSchema.nullable().optional().describe("When present, identifies the multipart binary part carrying this step's screenshot bytes."),
1084
- 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."),
1085
- 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.")
1144
+ 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."),
1145
+ 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.")
1086
1146
  });
1087
- bundleFindingSchema = z19.object({
1147
+ bundleFindingSchema = z20.object({
1088
1148
  slug: slugSchema,
1089
1149
  frontmatter: findingFrontmatterSchema,
1090
- body: z19.string().max(1048576).describe("Full markdown body of the finding file. 1 MiB cap matches the entity import envelope."),
1091
- 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).")
1150
+ body: z20.string().max(1048576).describe("Full markdown body of the finding file. 1 MiB cap matches the entity import envelope."),
1151
+ 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).")
1092
1152
  });
1093
- verificationItemSchema = z19.object({
1094
- canonical_id: z19.string().max(64).optional(),
1095
- id: z19.string().max(64).optional(),
1096
- title: z19.string().max(1024),
1097
- type: z19.string().max(32).optional()
1153
+ verificationItemSchema = z20.object({
1154
+ canonical_id: z20.string().max(64).optional(),
1155
+ id: z20.string().max(64).optional(),
1156
+ title: z20.string().max(1024),
1157
+ type: z20.string().max(32).optional()
1098
1158
  });
1099
- verificationGroup = z19.array(verificationItemSchema).max(500);
1100
- bundleVerificationSchema = z19.object({
1101
- baseline_run_id: z19.string().max(256),
1159
+ verificationGroup = z20.array(verificationItemSchema).max(500);
1160
+ bundleVerificationSchema = z20.object({
1161
+ baseline_run_id: z20.string().max(256),
1102
1162
  resolved: verificationGroup.default([]),
1103
1163
  persists: verificationGroup.default([]),
1104
1164
  not_observed: verificationGroup.default([]),
1105
1165
  uncertain: verificationGroup.default([]),
1106
1166
  new_findings: verificationGroup.default([])
1107
1167
  });
1108
- buildMetadataSchema = z19.object({
1109
- label: z19.string().min(1).max(200).optional().describe("Human label for the build, from `iamu start-run --build`."),
1110
- 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."),
1111
- git_branch: z19.string().min(1).max(255).optional(),
1112
- base_sha: z19.string().regex(/^[0-9a-f]{7,64}$/i, {
1168
+ buildMetadataSchema = z20.object({
1169
+ label: z20.string().min(1).max(200).optional().describe("Human label for the build, from `iamu start-run --build`."),
1170
+ 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."),
1171
+ git_branch: z20.string().min(1).max(255).optional(),
1172
+ base_sha: z20.string().regex(/^[0-9a-f]{7,64}$/i, {
1113
1173
  message: "base_sha must be a hex commit id"
1114
1174
  }).optional().describe("Commit the change is compared against (pull-request base)."),
1115
- 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."),
1116
- worktree_hash: z19.string().regex(/^[0-9a-f]{12}$/, {
1175
+ 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."),
1176
+ worktree_hash: z20.string().regex(/^[0-9a-f]{12}$/, {
1117
1177
  message: "worktree_hash must be 12 lowercase hex characters"
1118
1178
  }).optional().describe("Only when `git_dirty`: fingerprint of the uncommitted changes, so two runs on one commit with different edits are different builds."),
1119
- ci: z19.object({
1120
- provider: z19.string().min(1).max(64).describe("CI system that ran the walk, e.g. `github`, `azure`."),
1121
- run_url: z19.string().url().max(2048)
1179
+ ci: z20.object({
1180
+ provider: z20.string().min(1).max(64).describe("CI system that ran the walk, e.g. `github`, `azure`."),
1181
+ run_url: z20.string().url().max(2048)
1122
1182
  }).optional()
1123
1183
  });
1124
1184
  ENVIRONMENT_MAX_LENGTH = 255;
1125
- bundleManifestSchema = z19.object({
1126
- run_id: z19.string().uuid().describe("Client-minted UUID. Becomes `walkthroughs.id` on cloud. Idempotent key for the push endpoint."),
1127
- 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."),
1128
- target_url: z19.string().url().max(2048),
1129
- 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."),
1130
- status: z19.literal("completed").describe("v1 only accepts completed bundles (bundle-on-complete). In-progress runs are not pushable."),
1131
- 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."),
1185
+ bundleManifestSchema = z20.object({
1186
+ run_id: z20.string().uuid().describe("Client-minted UUID. Becomes `walkthroughs.id` on cloud. Idempotent key for the push endpoint."),
1187
+ 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."),
1188
+ target_url: z20.string().url().max(2048),
1189
+ 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."),
1190
+ status: z20.literal("completed").describe("v1 only accepts completed bundles (bundle-on-complete). In-progress runs are not pushable."),
1191
+ 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."),
1132
1192
  created_at: isoTimestamp,
1133
1193
  completed_at: isoTimestamp,
1134
- step_count: z19.number().int().min(0).max(1e4),
1135
- finding_count: z19.number().int().min(0).max(1e4),
1136
- report_markdown: z19.string().max(1048576).describe("report.md body verbatim. Persists to walkthroughs.report_markdown so dashboard can render without regenerating."),
1137
- what_worked: z19.array(z19.string().max(4096)).max(64).optional(),
1138
- open_questions: z19.array(z19.string().max(4096)).max(64).optional(),
1194
+ step_count: z20.number().int().min(0).max(1e4),
1195
+ finding_count: z20.number().int().min(0).max(1e4),
1196
+ report_markdown: z20.string().max(1048576).describe("report.md body verbatim. Persists to walkthroughs.report_markdown so dashboard can render without regenerating."),
1197
+ what_worked: z20.array(z20.string().max(4096)).max(64).optional(),
1198
+ open_questions: z20.array(z20.string().max(4096)).max(64).optional(),
1139
1199
  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."),
1140
1200
  build: buildMetadataSchema.optional().describe("Build of the product under test (git SHA, branch, CI run). Absent for runs started before `start-run` recorded it."),
1141
- 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`)."),
1201
+ 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`)."),
1142
1202
  /** Holistic UX quality score from UX review pass. Present only when `lenses` includes "ux". */
1143
- ux_quality_score: z19.number().min(1).max(5).nullish(),
1203
+ ux_quality_score: z20.number().min(1).max(5).nullish(),
1144
1204
  /** One-line rationale naming 1-2 structural factors that determined the score. */
1145
- ux_quality_rationale: z19.string().max(4096).nullish(),
1146
- verdict: z19.array(z19.object({
1147
- humanTestId: z19.string().optional(),
1148
- human_test_id: z19.string().optional(),
1149
- question: z19.string().default(""),
1150
- rating: z19.enum(["yes", "no", "partial", "not_applicable"]).optional(),
1151
- verdict: z19.enum(["yes", "no", "partial", "not_applicable"]).optional(),
1152
- reasoning: z19.string(),
1153
- groundingStepIds: z19.array(z19.string()).optional(),
1154
- grounding_step_ids: z19.array(z19.string()).optional(),
1155
- groundingFindingIds: z19.array(z19.string()).optional(),
1156
- grounding_finding_ids: z19.array(z19.string()).optional()
1205
+ ux_quality_rationale: z20.string().max(4096).nullish(),
1206
+ verdict: z20.array(z20.object({
1207
+ humanTestId: z20.string().optional(),
1208
+ human_test_id: z20.string().optional(),
1209
+ question: z20.string().default(""),
1210
+ rating: z20.enum(["yes", "no", "partial", "not_applicable"]).optional(),
1211
+ verdict: z20.enum(["yes", "no", "partial", "not_applicable"]).optional(),
1212
+ reasoning: z20.string(),
1213
+ groundingStepIds: z20.array(z20.string()).optional(),
1214
+ grounding_step_ids: z20.array(z20.string()).optional(),
1215
+ groundingFindingIds: z20.array(z20.string()).optional(),
1216
+ grounding_finding_ids: z20.array(z20.string()).optional()
1157
1217
  }).refine((v) => Boolean(v.humanTestId || v.human_test_id) && Boolean(v.rating || v.verdict), {
1158
1218
  message: "verdict item must include humanTestId or human_test_id, and rating or verdict"
1159
1219
  })).optional(),
1160
- judge_meta: z19.object({
1161
- overall: z19.object({
1162
- question: z19.string(),
1163
- answer: z19.string(),
1164
- reasoning: z19.string()
1220
+ judge_meta: z20.object({
1221
+ overall: z20.object({
1222
+ question: z20.string(),
1223
+ answer: z20.string(),
1224
+ reasoning: z20.string()
1165
1225
  }),
1166
- provenance: z19.object({
1167
- judge_prompt: z19.string().optional(),
1168
- judge_model: z19.string().optional()
1226
+ provenance: z20.object({
1227
+ judge_prompt: z20.string().optional(),
1228
+ judge_model: z20.string().optional()
1169
1229
  }).optional()
1170
1230
  }).optional(),
1171
- models: z19.object({
1172
- walker: z19.string().optional(),
1173
- matcher: z19.string().optional(),
1174
- judge: z19.string().optional(),
1175
- walker_reported: z19.string().optional(),
1176
- matcher_reported: z19.string().optional(),
1177
- judge_reported: z19.string().optional(),
1178
- walker_tier: z19.string().optional(),
1179
- matcher_tier: z19.string().optional(),
1180
- judge_tier: z19.string().optional(),
1181
- execution_mode: z19.enum(["multi_agent", "single_agent"]).optional(),
1182
- velocity_sec_per_step: z19.number().optional(),
1183
- total_duration_sec: z19.number().optional()
1231
+ models: z20.object({
1232
+ walker: z20.string().optional(),
1233
+ matcher: z20.string().optional(),
1234
+ judge: z20.string().optional(),
1235
+ walker_reported: z20.string().optional(),
1236
+ matcher_reported: z20.string().optional(),
1237
+ judge_reported: z20.string().optional(),
1238
+ walker_tier: z20.string().optional(),
1239
+ matcher_tier: z20.string().optional(),
1240
+ judge_tier: z20.string().optional(),
1241
+ execution_mode: z20.enum(["multi_agent", "single_agent"]).optional(),
1242
+ velocity_sec_per_step: z20.number().optional(),
1243
+ total_duration_sec: z20.number().optional()
1184
1244
  }).optional().describe("AI model configuration and reported runtime provenance for this walkthrough.")
1185
1245
  });
1186
- walkthroughBundleSchema = z19.object({
1246
+ walkthroughBundleSchema = z20.object({
1187
1247
  manifest: bundleManifestSchema,
1188
- steps: z19.array(bundleStepSchema).max(1e4),
1189
- findings: z19.array(bundleFindingSchema).max(1e4),
1190
- artifacts: z19.array(artifactRefSchema).max(1e4)
1248
+ steps: z20.array(bundleStepSchema).max(1e4),
1249
+ findings: z20.array(bundleFindingSchema).max(1e4),
1250
+ artifacts: z20.array(artifactRefSchema).max(1e4)
1191
1251
  }).superRefine((bundle, ctx) => {
1192
1252
  if (bundle.manifest.step_count !== bundle.steps.length) {
1193
1253
  ctx.addIssue({
1194
- code: z19.ZodIssueCode.custom,
1254
+ code: z20.ZodIssueCode.custom,
1195
1255
  path: ["manifest", "step_count"],
1196
1256
  message: `manifest.step_count (${bundle.manifest.step_count}) must equal steps.length (${bundle.steps.length})`
1197
1257
  });
1198
1258
  }
1199
1259
  if (bundle.manifest.finding_count !== bundle.findings.length) {
1200
1260
  ctx.addIssue({
1201
- code: z19.ZodIssueCode.custom,
1261
+ code: z20.ZodIssueCode.custom,
1202
1262
  path: ["manifest", "finding_count"],
1203
1263
  message: `manifest.finding_count (${bundle.manifest.finding_count}) must equal findings.length (${bundle.findings.length})`
1204
1264
  });
@@ -1207,14 +1267,14 @@ var init_walkthrough_bundle = __esm({
1207
1267
  const expected = Array.from({ length: bundle.steps.length }, (_, i) => i + 1);
1208
1268
  if (seqs.join(",") !== expected.join(",")) {
1209
1269
  ctx.addIssue({
1210
- code: z19.ZodIssueCode.custom,
1270
+ code: z20.ZodIssueCode.custom,
1211
1271
  path: ["steps"],
1212
1272
  message: `step seq values must be dense 1..${bundle.steps.length}; received [${seqs.join(",")}]`
1213
1273
  });
1214
1274
  }
1215
1275
  if (bundle.steps.length === 0 && bundle.artifacts.length > 0) {
1216
1276
  ctx.addIssue({
1217
- code: z19.ZodIssueCode.custom,
1277
+ code: z20.ZodIssueCode.custom,
1218
1278
  path: ["steps"],
1219
1279
  message: `bundle declares ${bundle.artifacts.length} artifact(s) but no steps \u2014 step records were not read`
1220
1280
  });
@@ -1224,7 +1284,7 @@ var init_walkthrough_bundle = __esm({
1224
1284
  for (const name of partNames) {
1225
1285
  if (seen.has(name)) {
1226
1286
  ctx.addIssue({
1227
- code: z19.ZodIssueCode.custom,
1287
+ code: z20.ZodIssueCode.custom,
1228
1288
  path: ["artifacts"],
1229
1289
  message: `duplicate artifact part_name: ${name}`
1230
1290
  });
@@ -1236,7 +1296,7 @@ var init_walkthrough_bundle = __esm({
1236
1296
  for (const [i, step] of bundle.steps.entries()) {
1237
1297
  if (step.screenshot_ref && !artifactPartNames.has(step.screenshot_ref.part_name)) {
1238
1298
  ctx.addIssue({
1239
- code: z19.ZodIssueCode.custom,
1299
+ code: z20.ZodIssueCode.custom,
1240
1300
  path: ["steps", i, "screenshot_ref", "part_name"],
1241
1301
  message: `step[${step.seq}].screenshot_ref.part_name "${step.screenshot_ref.part_name}" is not declared in artifacts[]`
1242
1302
  });
@@ -1246,7 +1306,7 @@ var init_walkthrough_bundle = __esm({
1246
1306
  for (const [j, ref] of (finding.screenshot_refs ?? []).entries()) {
1247
1307
  if (!artifactPartNames.has(ref.part_name)) {
1248
1308
  ctx.addIssue({
1249
- code: z19.ZodIssueCode.custom,
1309
+ code: z20.ZodIssueCode.custom,
1250
1310
  path: ["findings", i, "screenshot_refs", j, "part_name"],
1251
1311
  message: `finding[${finding.slug}].screenshot_refs[${j}].part_name "${ref.part_name}" is not declared in artifacts[]`
1252
1312
  });
@@ -1256,7 +1316,7 @@ var init_walkthrough_bundle = __esm({
1256
1316
  for (const [i, finding] of bundle.findings.entries()) {
1257
1317
  if (finding.frontmatter.scenario !== bundle.manifest.scenario_id) {
1258
1318
  ctx.addIssue({
1259
- code: z19.ZodIssueCode.custom,
1319
+ code: z20.ZodIssueCode.custom,
1260
1320
  path: ["findings", i, "frontmatter", "scenario"],
1261
1321
  message: `finding.frontmatter.scenario "${finding.frontmatter.scenario}" must match manifest.scenario_id "${bundle.manifest.scenario_id}"`
1262
1322
  });
@@ -1265,7 +1325,7 @@ var init_walkthrough_bundle = __esm({
1265
1325
  for (const [i, finding] of bundle.findings.entries()) {
1266
1326
  if (finding.frontmatter.run !== bundle.manifest.run_id) {
1267
1327
  ctx.addIssue({
1268
- code: z19.ZodIssueCode.custom,
1328
+ code: z20.ZodIssueCode.custom,
1269
1329
  path: ["findings", i, "frontmatter", "run"],
1270
1330
  message: `finding.frontmatter.run "${finding.frontmatter.run}" must match manifest.run_id "${bundle.manifest.run_id}"`
1271
1331
  });
@@ -1277,30 +1337,30 @@ var init_walkthrough_bundle = __esm({
1277
1337
  "updated",
1278
1338
  "unchanged"
1279
1339
  ];
1280
- walkthroughPushResponseSchema = z19.object({
1281
- run_id: z19.string().uuid(),
1282
- cloud_walkthrough_id: z19.string().uuid(),
1283
- status: z19.enum(WALKTHROUGH_PUSH_OUTCOMES),
1284
- step_count: z19.number().int().min(0),
1285
- finding_count: z19.number().int().min(0),
1286
- artifact_count: z19.number().int().min(0)
1340
+ walkthroughPushResponseSchema = z20.object({
1341
+ run_id: z20.string().uuid(),
1342
+ cloud_walkthrough_id: z20.string().uuid(),
1343
+ status: z20.enum(WALKTHROUGH_PUSH_OUTCOMES),
1344
+ step_count: z20.number().int().min(0),
1345
+ finding_count: z20.number().int().min(0),
1346
+ artifact_count: z20.number().int().min(0)
1287
1347
  });
1288
1348
  WALKTHROUGH_CONFLICT_REASONS = [
1289
1349
  "project_mismatch",
1290
1350
  "scenario_mismatch",
1291
1351
  "step_count_mismatch"
1292
1352
  ];
1293
- walkthroughPushConflictSchema = z19.object({
1294
- code: z19.literal("walkthrough_conflict"),
1295
- reason: z19.enum(WALKTHROUGH_CONFLICT_REASONS),
1296
- run_id: z19.string().uuid(),
1297
- message: z19.string()
1353
+ walkthroughPushConflictSchema = z20.object({
1354
+ code: z20.literal("walkthrough_conflict"),
1355
+ reason: z20.enum(WALKTHROUGH_CONFLICT_REASONS),
1356
+ run_id: z20.string().uuid(),
1357
+ message: z20.string()
1298
1358
  });
1299
1359
  }
1300
1360
  });
1301
1361
 
1302
1362
  // ../../packages/protocol/dist/types/walkthrough-canonical.js
1303
- import { z as z20 } from "zod";
1363
+ import { z as z21 } from "zod";
1304
1364
  var CANONICAL_WALKTHROUGH_VERSION, canonicalScenarioRefSchema, canonicalTargetSchema, canonicalProvenanceSchema, canonicalStepSchema, canonicalFindingSchema, canonicalHumanTestSchema, canonicalHumanTestOverallSchema, canonicalErrorSchema, cloudMirrorStateSchema, canonicalWalkthroughSchema;
1305
1365
  var init_walkthrough_canonical = __esm({
1306
1366
  "../../packages/protocol/dist/types/walkthrough-canonical.js"() {
@@ -1310,106 +1370,106 @@ var init_walkthrough_canonical = __esm({
1310
1370
  init_walkthrough_bundle();
1311
1371
  init_enums();
1312
1372
  CANONICAL_WALKTHROUGH_VERSION = "1.0.0";
1313
- canonicalScenarioRefSchema = z20.object({
1314
- id: z20.string(),
1315
- slug: z20.string(),
1316
- persona_slug: z20.string(),
1317
- outline_slug: z20.string(),
1318
- brief: z20.string()
1373
+ canonicalScenarioRefSchema = z21.object({
1374
+ id: z21.string(),
1375
+ slug: z21.string(),
1376
+ persona_slug: z21.string(),
1377
+ outline_slug: z21.string(),
1378
+ brief: z21.string()
1319
1379
  }).passthrough();
1320
- canonicalTargetSchema = z20.object({
1321
- url: z20.string(),
1380
+ canonicalTargetSchema = z21.object({
1381
+ url: z21.string(),
1322
1382
  /** Where the product runs. "live" = real backend; "mock" = stubbed. */
1323
- backend_mode: z20.enum(["live", "mock", "unknown"]),
1383
+ backend_mode: z21.enum(["live", "mock", "unknown"]),
1324
1384
  /** Browser / runner used for the walk. */
1325
- browser: z20.string()
1385
+ browser: z21.string()
1326
1386
  }).passthrough();
1327
- canonicalProvenanceSchema = z20.object({
1328
- walker_prompt: z20.object({ id: z20.string(), version: z20.string() }).passthrough(),
1329
- judge_prompt: z20.object({ id: z20.string(), version: z20.string() }).passthrough().optional(),
1330
- judge_mode: z20.enum(["subagent", "same_agent_second_pass", "failed"]).optional(),
1387
+ canonicalProvenanceSchema = z21.object({
1388
+ walker_prompt: z21.object({ id: z21.string(), version: z21.string() }).passthrough(),
1389
+ judge_prompt: z21.object({ id: z21.string(), version: z21.string() }).passthrough().optional(),
1390
+ judge_mode: z21.enum(["subagent", "same_agent_second_pass", "failed"]).optional(),
1331
1391
  /** Model identifier used by the walker. Judge model (when it runs) is
1332
1392
  * stamped inside `judge_meta.provenance.model` on the DB row. */
1333
- model: z20.string(),
1393
+ model: z21.string(),
1334
1394
  started_at: isoTimestamp,
1335
1395
  completed_at: isoTimestamp
1336
1396
  }).passthrough();
1337
- canonicalStepSchema = z20.object({
1338
- id: z20.string(),
1339
- step_number: z20.number().int(),
1340
- url: z20.string().optional(),
1397
+ canonicalStepSchema = z21.object({
1398
+ id: z21.string(),
1399
+ step_number: z21.number().int(),
1400
+ url: z21.string().optional(),
1341
1401
  /** The walker's short name for the screen. */
1342
- surface: z20.string().optional(),
1343
- screen_title: z20.string().optional(),
1402
+ surface: z21.string().optional(),
1403
+ screen_title: z21.string().optional(),
1344
1404
  /** Path reference to the screenshot file; never inline binary data. */
1345
- screenshot_ref: z20.string().nullable().optional(),
1346
- persona_reasoning: z20.string().nullable().optional(),
1347
- grounding: z20.string().nullable().optional(),
1348
- action: z20.string().optional()
1405
+ screenshot_ref: z21.string().nullable().optional(),
1406
+ persona_reasoning: z21.string().nullable().optional(),
1407
+ grounding: z21.string().nullable().optional(),
1408
+ action: z21.string().optional()
1349
1409
  }).passthrough();
1350
- canonicalFindingSchema = z20.object({
1351
- id: z20.string(),
1410
+ canonicalFindingSchema = z21.object({
1411
+ id: z21.string(),
1352
1412
  /** Stable across runs for pattern tracking — null until cross-run wiring lands. */
1353
- canonical_id: z20.string().nullable(),
1413
+ canonical_id: z21.string().nullable(),
1354
1414
  type: findingTypeEnum,
1355
- severity: z20.enum(["critical", "major", "minor", "observation"]).nullable(),
1415
+ severity: z21.enum(["critical", "major", "minor", "observation"]).nullable(),
1356
1416
  /** Depth-gate half 1: what the UI showed or did (observable without domain knowledge). */
1357
- ui_fact: z20.string().nullable(),
1417
+ ui_fact: z21.string().nullable(),
1358
1418
  /** Depth-gate half 2: what it means for the persona. Null on legacy rows (pre-migration). */
1359
- consequence: z20.string().nullable(),
1360
- recommendation: z20.string().nullable(),
1419
+ consequence: z21.string().nullable(),
1420
+ recommendation: z21.string().nullable(),
1361
1421
  /** Step ids this finding cites as evidence (references `steps[].id`). */
1362
- linked_step_ids: z20.array(z20.string()),
1422
+ linked_step_ids: z21.array(z21.string()),
1363
1423
  /** Parent scenario's human-test question id the finding bears on, if any. */
1364
- linked_human_test_id: z20.string().nullable(),
1365
- compliance_impact: z20.enum(["critical", "major", "minor", "informational"]).nullable().optional(),
1366
- task_impact: z20.enum(["blocks", "impairs", "slows", "neutral", "accelerates", "enables"]).nullable().optional(),
1367
- confidence: z20.enum(["high", "medium", "low"]).nullable().optional(),
1368
- regulatory_weight: z20.enum(["hard_requirement", "process_model", "best_practice"]).nullable().optional(),
1369
- citations: z20.array(z20.string()).optional()
1424
+ linked_human_test_id: z21.string().nullable(),
1425
+ compliance_impact: z21.enum(["critical", "major", "minor", "informational"]).nullable().optional(),
1426
+ task_impact: z21.enum(["blocks", "impairs", "slows", "neutral", "accelerates", "enables"]).nullable().optional(),
1427
+ confidence: z21.enum(["high", "medium", "low"]).nullable().optional(),
1428
+ regulatory_weight: z21.enum(["hard_requirement", "process_model", "best_practice"]).nullable().optional(),
1429
+ citations: z21.array(z21.string()).optional()
1370
1430
  }).passthrough();
1371
- canonicalHumanTestSchema = z20.object({
1372
- id: z20.string(),
1373
- question: z20.string(),
1431
+ canonicalHumanTestSchema = z21.object({
1432
+ id: z21.string(),
1433
+ question: z21.string(),
1374
1434
  /** Null until judge stage writes via `record_judge_result`. */
1375
- verdict: z20.enum(["yes", "partial", "no", "not_applicable"]).nullable(),
1376
- reasoning: z20.string().nullable(),
1377
- grounding_step_ids: z20.array(z20.string()),
1378
- grounding_finding_ids: z20.array(z20.string())
1435
+ verdict: z21.enum(["yes", "partial", "no", "not_applicable"]).nullable(),
1436
+ reasoning: z21.string().nullable(),
1437
+ grounding_step_ids: z21.array(z21.string()),
1438
+ grounding_finding_ids: z21.array(z21.string())
1379
1439
  }).passthrough();
1380
- canonicalHumanTestOverallSchema = z20.object({
1381
- question: z20.string(),
1440
+ canonicalHumanTestOverallSchema = z21.object({
1441
+ question: z21.string(),
1382
1442
  /** Null until judge stage answers. */
1383
- answer: z20.string().nullable(),
1384
- reasoning: z20.string().nullable()
1443
+ answer: z21.string().nullable(),
1444
+ reasoning: z21.string().nullable()
1385
1445
  }).passthrough();
1386
- canonicalErrorSchema = z20.object({
1387
- stage: z20.string(),
1388
- message: z20.string(),
1389
- at: z20.string()
1446
+ canonicalErrorSchema = z21.object({
1447
+ stage: z21.string(),
1448
+ message: z21.string(),
1449
+ at: z21.string()
1390
1450
  }).passthrough();
1391
- cloudMirrorStateSchema = z20.enum([
1451
+ cloudMirrorStateSchema = z21.enum([
1392
1452
  "pushed",
1393
1453
  "enqueued",
1394
1454
  "local-only",
1395
1455
  "already_pushed",
1396
1456
  "skipped"
1397
1457
  ]);
1398
- canonicalWalkthroughSchema = z20.object({
1399
- canonical_version: z20.literal(CANONICAL_WALKTHROUGH_VERSION),
1400
- run_id: z20.string(),
1458
+ canonicalWalkthroughSchema = z21.object({
1459
+ canonical_version: z21.literal(CANONICAL_WALKTHROUGH_VERSION),
1460
+ run_id: z21.string(),
1401
1461
  scenario: canonicalScenarioRefSchema,
1402
1462
  target: canonicalTargetSchema,
1403
1463
  provenance: canonicalProvenanceSchema,
1404
1464
  build: buildMetadataSchema.optional(),
1405
1465
  viewport: runViewportSchema.optional(),
1406
- environment: z20.string().optional(),
1407
- models: z20.record(z20.unknown()).optional(),
1408
- findings: z20.array(canonicalFindingSchema),
1409
- steps: z20.array(canonicalStepSchema),
1410
- human_tests: z20.array(canonicalHumanTestSchema),
1466
+ environment: z21.string().optional(),
1467
+ models: z21.record(z21.unknown()).optional(),
1468
+ findings: z21.array(canonicalFindingSchema),
1469
+ steps: z21.array(canonicalStepSchema),
1470
+ human_tests: z21.array(canonicalHumanTestSchema),
1411
1471
  human_test_overall: canonicalHumanTestOverallSchema,
1412
- errors: z20.array(canonicalErrorSchema),
1472
+ errors: z21.array(canonicalErrorSchema),
1413
1473
  cloud_mirror_state: cloudMirrorStateSchema
1414
1474
  }).passthrough();
1415
1475
  }
@@ -1820,7 +1880,7 @@ var init_prompt_registry = __esm({
1820
1880
  });
1821
1881
 
1822
1882
  // ../../packages/protocol/dist/mcp-schemas.js
1823
- import { z as z21 } from "zod";
1883
+ import { z as z22 } from "zod";
1824
1884
  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;
1825
1885
  var init_mcp_schemas = __esm({
1826
1886
  "../../packages/protocol/dist/mcp-schemas.js"() {
@@ -1832,352 +1892,352 @@ var init_mcp_schemas = __esm({
1832
1892
  init_fingerprint();
1833
1893
  AuthorityLevelSchema = authorityLevelEnum;
1834
1894
  FindingTypeSchema = findingTypeEnum;
1835
- MirrorStatusSchema = z21.enum([
1895
+ MirrorStatusSchema = z22.enum([
1836
1896
  "synced",
1837
1897
  "pending_retry",
1838
1898
  "local_only",
1839
1899
  "not_applicable"
1840
1900
  ]);
1841
- FindingSummarySchema = z21.object({
1842
- id: z21.string(),
1843
- slug: z21.string().nullable(),
1844
- title: z21.string(),
1901
+ FindingSummarySchema = z22.object({
1902
+ id: z22.string(),
1903
+ slug: z22.string().nullable(),
1904
+ title: z22.string(),
1845
1905
  type: FindingTypeSchema,
1846
1906
  /** Discriminator: "canonical" for deduplicated cloud findings,
1847
1907
  * "observation" for raw walkthrough findings (local or cloud). */
1848
- kind: z21.enum(["canonical", "observation"]),
1849
- severity: z21.enum(["critical", "major", "minor", "observation"]).nullable().describe("Effective severity. Null when not yet assigned."),
1850
- triage_status: z21.string().describe("open | addressed | dismissed"),
1908
+ kind: z22.enum(["canonical", "observation"]),
1909
+ severity: z22.enum(["critical", "major", "minor", "observation"]).nullable().describe("Effective severity. Null when not yet assigned."),
1910
+ triage_status: z22.string().describe("open | addressed | dismissed"),
1851
1911
  /** First 200 chars of the finding description. Fetch get_finding for the full text. */
1852
- description: z21.string(),
1853
- walkthrough_id: z21.string().nullable(),
1854
- scenario_id: z21.string(),
1912
+ description: z22.string(),
1913
+ walkthrough_id: z22.string().nullable(),
1914
+ scenario_id: z22.string(),
1855
1915
  created_at: isoTimestamp,
1856
- canonical_id: z21.string().nullable(),
1857
- cloud_mirrored: z21.boolean()
1916
+ canonical_id: z22.string().nullable(),
1917
+ cloud_mirrored: z22.boolean()
1858
1918
  });
1859
- ScenarioSummarySchema = z21.object({
1860
- id: z21.string(),
1861
- slug: z21.string().nullable(),
1862
- outline_id: z21.string(),
1863
- outline_slug: z21.string().nullable(),
1864
- persona_id: z21.string(),
1865
- persona_slug: z21.string().nullable(),
1919
+ ScenarioSummarySchema = z22.object({
1920
+ id: z22.string(),
1921
+ slug: z22.string().nullable(),
1922
+ outline_id: z22.string(),
1923
+ outline_slug: z22.string().nullable(),
1924
+ persona_id: z22.string(),
1925
+ persona_slug: z22.string().nullable(),
1866
1926
  created_at: isoTimestamp,
1867
- baseline_score: z21.number().nullable(),
1868
- current_score: z21.number().nullable(),
1869
- target_score: z21.number().nullable(),
1870
- walkthrough_count: z21.number().int().nonnegative(),
1871
- latest_walkthrough_id: z21.string().nullable(),
1872
- cloud_mirrored: z21.boolean()
1927
+ baseline_score: z22.number().nullable(),
1928
+ current_score: z22.number().nullable(),
1929
+ target_score: z22.number().nullable(),
1930
+ walkthrough_count: z22.number().int().nonnegative(),
1931
+ latest_walkthrough_id: z22.string().nullable(),
1932
+ cloud_mirrored: z22.boolean()
1873
1933
  });
1874
- RetrievalGapSchema = z21.object({
1875
- type: z21.enum(["no_results", "low_relevance", "unverified_only"]),
1876
- query: z21.string(),
1877
- suggestion: z21.string()
1934
+ RetrievalGapSchema = z22.object({
1935
+ type: z22.enum(["no_results", "low_relevance", "unverified_only"]),
1936
+ query: z22.string(),
1937
+ suggestion: z22.string()
1878
1938
  });
1879
- GetConstraintsDataSchema = z21.object({
1880
- domain: z21.string(),
1881
- constraint_count: z21.number().int().nonnegative(),
1882
- constraints: z21.array(z21.object({
1883
- rule: z21.string(),
1939
+ GetConstraintsDataSchema = z22.object({
1940
+ domain: z22.string(),
1941
+ constraint_count: z22.number().int().nonnegative(),
1942
+ constraints: z22.array(z22.object({
1943
+ rule: z22.string(),
1884
1944
  authority_level: AuthorityLevelSchema,
1885
- source_name: z21.string(),
1886
- source_section: z21.string(),
1887
- source_url: z21.string().nullable(),
1888
- source_version: z21.number(),
1889
- source_uri: z21.string().describe("iamu://source/<id>@v<version>")
1945
+ source_name: z22.string(),
1946
+ source_section: z22.string(),
1947
+ source_url: z22.string().nullable(),
1948
+ source_version: z22.number(),
1949
+ source_uri: z22.string().describe("iamu://source/<id>@v<version>")
1890
1950
  })),
1891
- gaps: z21.array(RetrievalGapSchema).optional(),
1892
- context_hint_needed: z21.boolean()
1951
+ gaps: z22.array(RetrievalGapSchema).optional(),
1952
+ context_hint_needed: z22.boolean()
1893
1953
  });
1894
1954
  GetConstraintsOutputSchema = mcpEnvelopeSchema(GetConstraintsDataSchema);
1895
- QueryDataSchema = z21.object({
1896
- results: z21.array(z21.object({
1897
- chunk_id: z21.string(),
1898
- content: z21.string(),
1899
- context_header: z21.string(),
1900
- context_text: z21.string().nullable(),
1955
+ QueryDataSchema = z22.object({
1956
+ results: z22.array(z22.object({
1957
+ chunk_id: z22.string(),
1958
+ content: z22.string(),
1959
+ context_header: z22.string(),
1960
+ context_text: z22.string().nullable(),
1901
1961
  authority_level: AuthorityLevelSchema,
1902
- source_name: z21.string(),
1903
- source_id: z21.string(),
1904
- source_uri: z21.string(),
1905
- source_version: z21.number(),
1906
- score: z21.number(),
1907
- verified: z21.boolean(),
1908
- trust: z21.enum(["hard", "soft", "unknown"])
1962
+ source_name: z22.string(),
1963
+ source_id: z22.string(),
1964
+ source_uri: z22.string(),
1965
+ source_version: z22.number(),
1966
+ score: z22.number(),
1967
+ verified: z22.boolean(),
1968
+ trust: z22.enum(["hard", "soft", "unknown"])
1909
1969
  })),
1910
- result_count: z21.number().int().nonnegative(),
1911
- query_time_ms: z21.number().int().nonnegative().optional(),
1912
- gaps: z21.array(RetrievalGapSchema).optional(),
1913
- context_hint_needed: z21.boolean()
1970
+ result_count: z22.number().int().nonnegative(),
1971
+ query_time_ms: z22.number().int().nonnegative().optional(),
1972
+ gaps: z22.array(RetrievalGapSchema).optional(),
1973
+ context_hint_needed: z22.boolean()
1914
1974
  });
1915
1975
  QueryOutputSchema = mcpEnvelopeSchema(QueryDataSchema);
1916
- ListFindingsDataSchema = z21.object({
1917
- findings: z21.array(FindingSummarySchema),
1918
- total_returned: z21.number().int().nonnegative()
1976
+ ListFindingsDataSchema = z22.object({
1977
+ findings: z22.array(FindingSummarySchema),
1978
+ total_returned: z22.number().int().nonnegative()
1919
1979
  });
1920
1980
  ListFindingsOutputSchema = mcpEnvelopeSchema(ListFindingsDataSchema);
1921
- ListScenariosDataSchema = z21.object({
1922
- scenarios: z21.array(ScenarioSummarySchema)
1981
+ ListScenariosDataSchema = z22.object({
1982
+ scenarios: z22.array(ScenarioSummarySchema)
1923
1983
  });
1924
1984
  ListScenariosOutputSchema = mcpEnvelopeSchema(ListScenariosDataSchema);
1925
- GetProjectDataSchema = z21.object({
1926
- id: z21.string(),
1927
- slug: z21.string().nullable(),
1928
- name: z21.string(),
1929
- cloud_linked: z21.boolean(),
1930
- cloud_project_id: z21.string().nullable(),
1985
+ GetProjectDataSchema = z22.object({
1986
+ id: z22.string(),
1987
+ slug: z22.string().nullable(),
1988
+ name: z22.string(),
1989
+ cloud_linked: z22.boolean(),
1990
+ cloud_project_id: z22.string().nullable(),
1931
1991
  created_at: isoTimestamp.nullable(),
1932
- capabilities_count: z21.number().int().nonnegative(),
1933
- outlines_count: z21.number().int().nonnegative(),
1934
- scenarios_count: z21.number().int().nonnegative(),
1935
- walkthroughs_count: z21.number().int().nonnegative(),
1936
- personas_count: z21.number().int().nonnegative()
1992
+ capabilities_count: z22.number().int().nonnegative(),
1993
+ outlines_count: z22.number().int().nonnegative(),
1994
+ scenarios_count: z22.number().int().nonnegative(),
1995
+ walkthroughs_count: z22.number().int().nonnegative(),
1996
+ personas_count: z22.number().int().nonnegative()
1937
1997
  });
1938
1998
  GetProjectOutputSchema = mcpEnvelopeSchema(GetProjectDataSchema);
1939
- CreateScenarioDataSchema = z21.object({
1940
- scenario_id: z21.string(),
1941
- outline_id: z21.string(),
1942
- persona_id: z21.string(),
1943
- was_existing: z21.boolean(),
1999
+ CreateScenarioDataSchema = z22.object({
2000
+ scenario_id: z22.string(),
2001
+ outline_id: z22.string(),
2002
+ persona_id: z22.string(),
2003
+ was_existing: z22.boolean(),
1944
2004
  created_at: isoTimestamp,
1945
2005
  brief_generated_at: isoTimestamp.nullable()
1946
2006
  });
1947
2007
  CreateScenarioOutputSchema = mcpEnvelopeSchema(CreateScenarioDataSchema);
1948
- PrepareWalkthroughDataSchema = z21.object({
1949
- walkthrough_id: z21.string(),
1950
- scenario_id: z21.string(),
1951
- target_url: z21.string(),
1952
- lenses: z21.array(z21.string()),
1953
- persona: z21.object({
1954
- id: z21.string(),
1955
- name: z21.string(),
1956
- slug: z21.string().nullable()
2008
+ PrepareWalkthroughDataSchema = z22.object({
2009
+ walkthrough_id: z22.string(),
2010
+ scenario_id: z22.string(),
2011
+ target_url: z22.string(),
2012
+ lenses: z22.array(z22.string()),
2013
+ persona: z22.object({
2014
+ id: z22.string(),
2015
+ name: z22.string(),
2016
+ slug: z22.string().nullable()
1957
2017
  }),
1958
- outline: z21.object({
1959
- id: z21.string(),
1960
- slug: z21.string().nullable(),
1961
- title: z21.string()
2018
+ outline: z22.object({
2019
+ id: z22.string(),
2020
+ slug: z22.string().nullable(),
2021
+ title: z22.string()
1962
2022
  }),
1963
- mode: z21.enum(["local", "cloud"]),
1964
- cloud_mirrored: z21.boolean(),
2023
+ mode: z22.enum(["local", "cloud"]),
2024
+ cloud_mirrored: z22.boolean(),
1965
2025
  mirror_status: MirrorStatusSchema,
1966
- run_dir: z21.string().nullable(),
2026
+ run_dir: z22.string().nullable(),
1967
2027
  /** Persona-specific first-person brief — markdown body of the scenario file. */
1968
- brief: z21.string(),
2028
+ brief: z22.string(),
1969
2029
  /** Full composed walking prompt: persona + outline + lenses +
1970
2030
  * evaluation philosophy. This is the text the walker should adopt
1971
2031
  * before driving the browser; `brief` alone lacks the philosophy
1972
2032
  * + lens guidance. */
1973
- prompt_text: z21.string(),
2033
+ prompt_text: z22.string(),
1974
2034
  /** Withheld during walker stage (always empty here). Populated by
1975
2035
  * `get_judge_input` once the walk completes. */
1976
- human_tests: z21.array(z21.string())
2036
+ human_tests: z22.array(z22.string())
1977
2037
  });
1978
2038
  PrepareWalkthroughOutputSchema = mcpEnvelopeSchema(PrepareWalkthroughDataSchema);
1979
- PersonaSummarySchema = z21.object({
1980
- id: z21.string(),
1981
- slug: z21.string(),
1982
- name: z21.string(),
1983
- role: z21.string(),
2039
+ PersonaSummarySchema = z22.object({
2040
+ id: z22.string(),
2041
+ slug: z22.string(),
2042
+ name: z22.string(),
2043
+ role: z22.string(),
1984
2044
  /** First 200 chars of context/background. */
1985
- context_preview: z21.string().nullable(),
1986
- status: z21.enum(["active", "archived"]),
2045
+ context_preview: z22.string().nullable(),
2046
+ status: z22.enum(["active", "archived"]),
1987
2047
  created_at: isoTimestamp,
1988
- cloud_mirrored: z21.boolean()
2048
+ cloud_mirrored: z22.boolean()
1989
2049
  });
1990
- OutlineSummarySchema = z21.object({
1991
- id: z21.string(),
1992
- slug: z21.string(),
1993
- name: z21.string(),
1994
- description: z21.string().nullable(),
1995
- primary_capability_id: z21.string().nullable(),
1996
- primary_capability_slug: z21.string().nullable(),
2050
+ OutlineSummarySchema = z22.object({
2051
+ id: z22.string(),
2052
+ slug: z22.string(),
2053
+ name: z22.string(),
2054
+ description: z22.string().nullable(),
2055
+ primary_capability_id: z22.string().nullable(),
2056
+ primary_capability_slug: z22.string().nullable(),
1997
2057
  /** Capability slugs (denormalized for agent display). */
1998
- additional_capabilities: z21.array(z21.string()),
1999
- task_criticality: z21.string().nullable(),
2058
+ additional_capabilities: z22.array(z22.string()),
2059
+ task_criticality: z22.string().nullable(),
2000
2060
  created_at: isoTimestamp,
2001
- cloud_mirrored: z21.boolean()
2061
+ cloud_mirrored: z22.boolean()
2002
2062
  });
2003
- CapabilitySummarySchema = z21.object({
2004
- id: z21.string(),
2005
- slug: z21.string(),
2006
- name: z21.string(),
2007
- description: z21.string().nullable(),
2063
+ CapabilitySummarySchema = z22.object({
2064
+ id: z22.string(),
2065
+ slug: z22.string(),
2066
+ name: z22.string(),
2067
+ description: z22.string().nullable(),
2008
2068
  /** Number of outlines with this capability as primary. Cloud-only;
2009
2069
  * null in local mode (filesystem walk would be too expensive). */
2010
- outline_count: z21.number().int().nonnegative().nullable(),
2070
+ outline_count: z22.number().int().nonnegative().nullable(),
2011
2071
  created_at: isoTimestamp,
2012
- cloud_mirrored: z21.boolean()
2072
+ cloud_mirrored: z22.boolean()
2013
2073
  });
2014
- ListPersonasDataSchema = z21.object({
2015
- personas: z21.array(PersonaSummarySchema)
2074
+ ListPersonasDataSchema = z22.object({
2075
+ personas: z22.array(PersonaSummarySchema)
2016
2076
  });
2017
2077
  ListPersonasOutputSchema = mcpEnvelopeSchema(ListPersonasDataSchema);
2018
- ListOutlinesDataSchema = z21.object({
2019
- outlines: z21.array(OutlineSummarySchema)
2078
+ ListOutlinesDataSchema = z22.object({
2079
+ outlines: z22.array(OutlineSummarySchema)
2020
2080
  });
2021
2081
  ListOutlinesOutputSchema = mcpEnvelopeSchema(ListOutlinesDataSchema);
2022
- ListCapabilitiesDataSchema = z21.object({
2023
- capabilities: z21.array(CapabilitySummarySchema)
2082
+ ListCapabilitiesDataSchema = z22.object({
2083
+ capabilities: z22.array(CapabilitySummarySchema)
2024
2084
  });
2025
2085
  ListCapabilitiesOutputSchema = mcpEnvelopeSchema(ListCapabilitiesDataSchema);
2026
- GetPersonaDataSchema = z21.object({
2027
- id: z21.string(),
2028
- slug: z21.string(),
2029
- name: z21.string(),
2030
- role: z21.string(),
2031
- context: z21.string().nullable(),
2032
- disposition: z21.string().nullable(),
2033
- brief: z21.string().nullable(),
2034
- status: z21.enum(["active", "archived"]),
2035
- body: z21.string(),
2086
+ GetPersonaDataSchema = z22.object({
2087
+ id: z22.string(),
2088
+ slug: z22.string(),
2089
+ name: z22.string(),
2090
+ role: z22.string(),
2091
+ context: z22.string().nullable(),
2092
+ disposition: z22.string().nullable(),
2093
+ brief: z22.string().nullable(),
2094
+ status: z22.enum(["active", "archived"]),
2095
+ body: z22.string(),
2036
2096
  created_at: isoTimestamp,
2037
2097
  updated_at: isoTimestamp.nullable(),
2038
- cloud_mirrored: z21.boolean(),
2039
- resource_uri: z21.string()
2098
+ cloud_mirrored: z22.boolean(),
2099
+ resource_uri: z22.string()
2040
2100
  });
2041
2101
  GetPersonaOutputSchema = mcpEnvelopeSchema(GetPersonaDataSchema);
2042
- GetOutlineDataSchema = z21.object({
2043
- id: z21.string(),
2044
- slug: z21.string(),
2045
- name: z21.string(),
2046
- description: z21.string().nullable(),
2047
- narrative: z21.string(),
2048
- primary_capability_id: z21.string().nullable(),
2049
- primary_capability_slug: z21.string().nullable(),
2050
- additional_capabilities: z21.array(z21.string()),
2051
- human_tests: z21.array(z21.object({ id: z21.string(), question: z21.string() })),
2052
- tension: z21.string().nullable(),
2053
- situation_roles: z21.array(z21.object({ role: z21.string(), stake: z21.string() })).nullable(),
2054
- task_criticality: z21.string().nullable(),
2055
- complexity_level: z21.string().nullable(),
2102
+ GetOutlineDataSchema = z22.object({
2103
+ id: z22.string(),
2104
+ slug: z22.string(),
2105
+ name: z22.string(),
2106
+ description: z22.string().nullable(),
2107
+ narrative: z22.string(),
2108
+ primary_capability_id: z22.string().nullable(),
2109
+ primary_capability_slug: z22.string().nullable(),
2110
+ additional_capabilities: z22.array(z22.string()),
2111
+ human_tests: z22.array(z22.object({ id: z22.string(), question: z22.string() })),
2112
+ tension: z22.string().nullable(),
2113
+ situation_roles: z22.array(z22.object({ role: z22.string(), stake: z22.string() })).nullable(),
2114
+ task_criticality: z22.string().nullable(),
2115
+ complexity_level: z22.string().nullable(),
2056
2116
  created_at: isoTimestamp,
2057
2117
  updated_at: isoTimestamp.nullable(),
2058
- cloud_mirrored: z21.boolean(),
2059
- resource_uri: z21.string()
2118
+ cloud_mirrored: z22.boolean(),
2119
+ resource_uri: z22.string()
2060
2120
  });
2061
2121
  GetOutlineOutputSchema = mcpEnvelopeSchema(GetOutlineDataSchema);
2062
- GetFindingDataSchema = z21.object({
2063
- id: z21.string(),
2064
- slug: z21.string().nullable(),
2065
- title: z21.string().nullable(),
2122
+ GetFindingDataSchema = z22.object({
2123
+ id: z22.string(),
2124
+ slug: z22.string().nullable(),
2125
+ title: z22.string().nullable(),
2066
2126
  type: FindingTypeSchema,
2067
- severity: z21.enum(["critical", "major", "minor", "observation"]).nullable(),
2068
- kind: z21.enum(["canonical", "observation"]),
2069
- ui_fact: z21.string().nullable(),
2070
- consequence: z21.string().nullable(),
2071
- description: z21.string().nullable(),
2072
- citations: z21.array(z21.string()),
2073
- walkthrough_id: z21.string().nullable(),
2074
- scenario_id: z21.string(),
2075
- persona_id: z21.string().nullable(),
2076
- run: z21.string().nullable(),
2077
- triage_status: z21.string(),
2078
- triage_note: z21.string().nullable(),
2079
- fix_status: z21.string(),
2080
- lens: z21.string().nullable(),
2081
- compliance_impact: z21.string().nullable(),
2082
- task_impact: z21.string().nullable(),
2083
- confidence: z21.string().nullable(),
2084
- regulatory_weight: z21.string().nullable(),
2085
- human_test_id: z21.string().nullable(),
2086
- walkthrough_step_id: z21.string().nullable(),
2087
- canonical_id: z21.string().nullable(),
2127
+ severity: z22.enum(["critical", "major", "minor", "observation"]).nullable(),
2128
+ kind: z22.enum(["canonical", "observation"]),
2129
+ ui_fact: z22.string().nullable(),
2130
+ consequence: z22.string().nullable(),
2131
+ description: z22.string().nullable(),
2132
+ citations: z22.array(z22.string()),
2133
+ walkthrough_id: z22.string().nullable(),
2134
+ scenario_id: z22.string(),
2135
+ persona_id: z22.string().nullable(),
2136
+ run: z22.string().nullable(),
2137
+ triage_status: z22.string(),
2138
+ triage_note: z22.string().nullable(),
2139
+ fix_status: z22.string(),
2140
+ lens: z22.string().nullable(),
2141
+ compliance_impact: z22.string().nullable(),
2142
+ task_impact: z22.string().nullable(),
2143
+ confidence: z22.string().nullable(),
2144
+ regulatory_weight: z22.string().nullable(),
2145
+ human_test_id: z22.string().nullable(),
2146
+ walkthrough_step_id: z22.string().nullable(),
2147
+ canonical_id: z22.string().nullable(),
2088
2148
  recorded_at: isoTimestamp.nullable(),
2089
2149
  updated_at: isoTimestamp.nullable(),
2090
- cloud_mirrored: z21.boolean(),
2150
+ cloud_mirrored: z22.boolean(),
2091
2151
  mirror_status: MirrorStatusSchema,
2092
- resource_uri: z21.string()
2152
+ resource_uri: z22.string()
2093
2153
  });
2094
2154
  GetFindingOutputSchema = mcpEnvelopeSchema(GetFindingDataSchema);
2095
- CreatePersonaDataSchema = z21.object({
2096
- persona_id: z21.string(),
2097
- slug: z21.string(),
2098
- was_existing: z21.boolean(),
2155
+ CreatePersonaDataSchema = z22.object({
2156
+ persona_id: z22.string(),
2157
+ slug: z22.string(),
2158
+ was_existing: z22.boolean(),
2099
2159
  created_at: isoTimestamp,
2100
2160
  brief_generated_at: isoTimestamp.nullable()
2101
2161
  });
2102
2162
  CreatePersonaOutputSchema = mcpEnvelopeSchema(CreatePersonaDataSchema);
2103
- CreateOutlineDataSchema = z21.object({
2104
- outline_id: z21.string(),
2105
- slug: z21.string(),
2106
- primary_capability_id: z21.string().nullable(),
2107
- primary_capability_slug: z21.string().nullable(),
2108
- was_existing: z21.boolean(),
2163
+ CreateOutlineDataSchema = z22.object({
2164
+ outline_id: z22.string(),
2165
+ slug: z22.string(),
2166
+ primary_capability_id: z22.string().nullable(),
2167
+ primary_capability_slug: z22.string().nullable(),
2168
+ was_existing: z22.boolean(),
2109
2169
  created_at: isoTimestamp
2110
2170
  });
2111
2171
  CreateOutlineOutputSchema = mcpEnvelopeSchema(CreateOutlineDataSchema);
2112
- CreateCapabilityDataSchema = z21.object({
2113
- capability_id: z21.string(),
2114
- slug: z21.string(),
2115
- was_existing: z21.boolean(),
2172
+ CreateCapabilityDataSchema = z22.object({
2173
+ capability_id: z22.string(),
2174
+ slug: z22.string(),
2175
+ was_existing: z22.boolean(),
2116
2176
  created_at: isoTimestamp
2117
2177
  });
2118
2178
  CreateCapabilityOutputSchema = mcpEnvelopeSchema(CreateCapabilityDataSchema);
2119
- RecordStepDataSchema = z21.object({
2120
- step_id: z21.string(),
2121
- step_index: z21.number().int().nonnegative(),
2122
- walkthrough_id: z21.string(),
2123
- persona_reasoning: z21.string(),
2179
+ RecordStepDataSchema = z22.object({
2180
+ step_id: z22.string(),
2181
+ step_index: z22.number().int().nonnegative(),
2182
+ walkthrough_id: z22.string(),
2183
+ persona_reasoning: z22.string(),
2124
2184
  recorded_at: isoTimestamp,
2125
2185
  /** Set when a screenshot_path was provided. */
2126
- screenshot_uri: z21.string().nullable(),
2186
+ screenshot_uri: z22.string().nullable(),
2127
2187
  /** Capture failure modes (per-field) — see fingerprint shape in
2128
2188
  * `./types/fingerprint.ts`. Optional; null
2129
2189
  * when fingerprint capture didn't run. */
2130
- fingerprint: stepFingerprintSchema.or(z21.object({
2131
- dom_hash: z21.string().nullable(),
2132
- ax_hash: z21.string().nullable(),
2133
- screenshot_hash: z21.string().nullable(),
2134
- url_template: z21.string().nullable()
2190
+ fingerprint: stepFingerprintSchema.or(z22.object({
2191
+ dom_hash: z22.string().nullable(),
2192
+ ax_hash: z22.string().nullable(),
2193
+ screenshot_hash: z22.string().nullable(),
2194
+ url_template: z22.string().nullable()
2135
2195
  })).nullable()
2136
2196
  });
2137
2197
  RecordStepOutputSchema = mcpEnvelopeSchema(RecordStepDataSchema);
2138
- RecordFindingDataSchema = z21.object({
2139
- finding_id: z21.string(),
2140
- slug: z21.string().nullable(),
2198
+ RecordFindingDataSchema = z22.object({
2199
+ finding_id: z22.string(),
2200
+ slug: z22.string().nullable(),
2141
2201
  type: FindingTypeSchema,
2142
- severity: z21.enum(["critical", "major", "minor", "observation"]).nullable(),
2143
- kind: z21.literal("observation"),
2144
- ui_fact: z21.string(),
2145
- consequence: z21.string(),
2146
- citations: z21.array(z21.string()),
2147
- scenario_id: z21.string(),
2148
- walkthrough_id: z21.string(),
2149
- persona_id: z21.string().nullable(),
2150
- triage_status: z21.literal("open"),
2151
- fix_status: z21.literal("open"),
2152
- lens: z21.string().nullable(),
2202
+ severity: z22.enum(["critical", "major", "minor", "observation"]).nullable(),
2203
+ kind: z22.literal("observation"),
2204
+ ui_fact: z22.string(),
2205
+ consequence: z22.string(),
2206
+ citations: z22.array(z22.string()),
2207
+ scenario_id: z22.string(),
2208
+ walkthrough_id: z22.string(),
2209
+ persona_id: z22.string().nullable(),
2210
+ triage_status: z22.literal("open"),
2211
+ fix_status: z22.literal("open"),
2212
+ lens: z22.string().nullable(),
2153
2213
  recorded_at: isoTimestamp,
2154
- resource_uri: z21.string()
2214
+ resource_uri: z22.string()
2155
2215
  });
2156
2216
  RecordFindingOutputSchema = mcpEnvelopeSchema(RecordFindingDataSchema);
2157
2217
  CompleteWalkthroughDataSchema = canonicalWalkthroughSchema;
2158
2218
  CompleteWalkthroughOutputSchema = mcpEnvelopeSchema(CompleteWalkthroughDataSchema);
2159
- GetJudgeInputDataSchema = z21.object({
2160
- walkthrough_id: z21.string(),
2161
- judge_prompt: z21.object({
2162
- id: z21.string(),
2163
- version: z21.string(),
2164
- text: z21.string()
2219
+ GetJudgeInputDataSchema = z22.object({
2220
+ walkthrough_id: z22.string(),
2221
+ judge_prompt: z22.object({
2222
+ id: z22.string(),
2223
+ version: z22.string(),
2224
+ text: z22.string()
2165
2225
  }),
2166
- scenario: z21.object({}).passthrough(),
2167
- outline_context: z21.object({}).passthrough(),
2168
- steps: z21.array(z21.unknown()),
2169
- findings: z21.array(z21.unknown()),
2170
- human_tests: z21.array(z21.object({
2171
- id: z21.string(),
2172
- question: z21.string()
2226
+ scenario: z22.object({}).passthrough(),
2227
+ outline_context: z22.object({}).passthrough(),
2228
+ steps: z22.array(z22.unknown()),
2229
+ findings: z22.array(z22.unknown()),
2230
+ human_tests: z22.array(z22.object({
2231
+ id: z22.string(),
2232
+ question: z22.string()
2173
2233
  }))
2174
2234
  });
2175
2235
  GetJudgeInputOutputSchema = mcpEnvelopeSchema(GetJudgeInputDataSchema);
2176
- RecordJudgeResultDataSchema = z21.object({
2177
- walkthrough_id: z21.string(),
2236
+ RecordJudgeResultDataSchema = z22.object({
2237
+ walkthrough_id: z22.string(),
2178
2238
  persisted_at: isoTimestamp,
2179
- canonical_path: z21.string(),
2180
- report_path: z21.string()
2239
+ canonical_path: z22.string(),
2240
+ report_path: z22.string()
2181
2241
  });
2182
2242
  RecordJudgeResultOutputSchema = mcpEnvelopeSchema(RecordJudgeResultDataSchema);
2183
2243
  }
@@ -2350,6 +2410,9 @@ function configPath(projectRoot) {
2350
2410
  function contextPath(projectRoot) {
2351
2411
  return path2.join(iamuDir(projectRoot), "context.yml");
2352
2412
  }
2413
+ function testabilityPath(projectRoot) {
2414
+ return path2.join(iamuDir(projectRoot), "testability.md");
2415
+ }
2353
2416
  function entityDirPath(projectRoot, type2) {
2354
2417
  return path2.join(iamuDir(projectRoot), ENTITY_DIRS[type2]);
2355
2418
  }
@@ -5293,6 +5356,37 @@ var init_context2 = __esm({
5293
5356
  }
5294
5357
  });
5295
5358
 
5359
+ // ../../packages/local-store/dist/testability.js
5360
+ import { promises as fs5 } from "fs";
5361
+ async function readTestability(projectRoot) {
5362
+ const p = testabilityPath(projectRoot);
5363
+ let raw;
5364
+ try {
5365
+ raw = await fs5.readFile(p, "utf8");
5366
+ } catch (err) {
5367
+ if (err.code === "ENOENT")
5368
+ return null;
5369
+ throw err;
5370
+ }
5371
+ const parsed = parseFrontmatter(raw);
5372
+ const result = testabilityFrontmatterSchema.safeParse(parsed.frontmatter);
5373
+ if (!result.success) {
5374
+ throw new Error(`testability: invalid frontmatter in ${p}: ${result.error.issues.map((i) => `${i.path.join(".") || "root"}: ${i.message}`).join("; ")}`);
5375
+ }
5376
+ return {
5377
+ frontmatter: result.data,
5378
+ body: parsed.body
5379
+ };
5380
+ }
5381
+ var init_testability2 = __esm({
5382
+ "../../packages/local-store/dist/testability.js"() {
5383
+ "use strict";
5384
+ init_dist();
5385
+ init_markdown();
5386
+ init_paths();
5387
+ }
5388
+ });
5389
+
5296
5390
  // ../../packages/local-store/dist/hash.js
5297
5391
  import { createHash } from "crypto";
5298
5392
  function hashNormalizedContent(raw) {
@@ -5326,7 +5420,7 @@ var init_server_reason = __esm({
5326
5420
 
5327
5421
  // ../../packages/local-store/dist/file-lock.js
5328
5422
  import { randomUUID as randomUUID3 } from "crypto";
5329
- import { promises as fs5 } from "fs";
5423
+ import { promises as fs6 } from "fs";
5330
5424
  import os from "os";
5331
5425
  import path4 from "path";
5332
5426
  function takeoverFilePathFor(lockPath) {
@@ -5363,7 +5457,7 @@ function pidIsDead(rec) {
5363
5457
  async function isStale(lockPath, rec, staleMs, now) {
5364
5458
  if (rec === null) {
5365
5459
  try {
5366
- const stat3 = await fs5.stat(lockPath);
5460
+ const stat3 = await fs6.stat(lockPath);
5367
5461
  if (stat3.size === 0) {
5368
5462
  const mtime = stat3.birthtimeMs && stat3.birthtimeMs > 0 ? stat3.birthtimeMs : stat3.mtimeMs;
5369
5463
  if (now - mtime <= staleMs) {
@@ -5388,7 +5482,7 @@ async function tryCreateMarker(markerPath, token) {
5388
5482
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
5389
5483
  };
5390
5484
  try {
5391
- const handle = await fs5.open(markerPath, "wx", 420);
5485
+ const handle = await fs6.open(markerPath, "wx", 420);
5392
5486
  try {
5393
5487
  await handle.writeFile(JSON.stringify(record));
5394
5488
  await handle.sync();
@@ -5407,7 +5501,7 @@ async function tryCreateLock(lockPath, token) {
5407
5501
  }
5408
5502
  async function readRecord(markerPath) {
5409
5503
  try {
5410
- return parseLockRecord(await fs5.readFile(markerPath, "utf8"));
5504
+ return parseLockRecord(await fs6.readFile(markerPath, "utf8"));
5411
5505
  } catch {
5412
5506
  return null;
5413
5507
  }
@@ -5417,7 +5511,7 @@ async function tryTakeover(lockPath, token, expected, opts) {
5417
5511
  if (!await tryCreateMarker(takeoverPath, token)) {
5418
5512
  const holder = await readRecord(takeoverPath);
5419
5513
  if (await isStale(takeoverPath, holder, opts.staleMs, opts.now())) {
5420
- await fs5.unlink(takeoverPath).catch(() => {
5514
+ await fs6.unlink(takeoverPath).catch(() => {
5421
5515
  });
5422
5516
  }
5423
5517
  return null;
@@ -5426,7 +5520,7 @@ async function tryTakeover(lockPath, token, expected, opts) {
5426
5520
  const current = await readRecord(lockPath);
5427
5521
  let present = true;
5428
5522
  try {
5429
- await fs5.access(lockPath);
5523
+ await fs6.access(lockPath);
5430
5524
  } catch {
5431
5525
  present = false;
5432
5526
  }
@@ -5437,11 +5531,11 @@ async function tryTakeover(lockPath, token, expected, opts) {
5437
5531
  return null;
5438
5532
  if (!await isStale(lockPath, current, opts.staleMs, opts.now()))
5439
5533
  return null;
5440
- await fs5.unlink(lockPath).catch(() => {
5534
+ await fs6.unlink(lockPath).catch(() => {
5441
5535
  });
5442
5536
  return await tryCreateLock(lockPath, token);
5443
5537
  } finally {
5444
- await fs5.unlink(takeoverPath).catch(() => {
5538
+ await fs6.unlink(takeoverPath).catch(() => {
5445
5539
  });
5446
5540
  }
5447
5541
  }
@@ -5458,13 +5552,13 @@ async function acquireLockFile(lockPath, opts) {
5458
5552
  const token = randomUUID3();
5459
5553
  const deadline = opts.now() + opts.timeoutMs;
5460
5554
  let lastHolderPid;
5461
- await fs5.mkdir(path4.dirname(lockPath), { recursive: true });
5555
+ await fs6.mkdir(path4.dirname(lockPath), { recursive: true });
5462
5556
  for (; ; ) {
5463
5557
  if (await tryCreateLock(lockPath, token))
5464
5558
  return token;
5465
5559
  let raw = null;
5466
5560
  try {
5467
- raw = await fs5.readFile(lockPath, "utf8");
5561
+ raw = await fs6.readFile(lockPath, "utf8");
5468
5562
  } catch (err) {
5469
5563
  if (err.code === "ENOENT")
5470
5564
  continue;
@@ -5491,13 +5585,13 @@ async function acquireLockFile(lockPath, opts) {
5491
5585
  }
5492
5586
  async function releaseLockFile(lockPath, token) {
5493
5587
  try {
5494
- const rec = parseLockRecord(await fs5.readFile(lockPath, "utf8"));
5588
+ const rec = parseLockRecord(await fs6.readFile(lockPath, "utf8"));
5495
5589
  if (rec?.token !== token)
5496
5590
  return;
5497
- const recheck = parseLockRecord(await fs5.readFile(lockPath, "utf8"));
5591
+ const recheck = parseLockRecord(await fs6.readFile(lockPath, "utf8"));
5498
5592
  if (recheck?.token !== token)
5499
5593
  return;
5500
- await fs5.unlink(lockPath);
5594
+ await fs6.unlink(lockPath);
5501
5595
  } catch {
5502
5596
  }
5503
5597
  }
@@ -5566,10 +5660,10 @@ var init_file_lock = __esm({
5566
5660
  });
5567
5661
 
5568
5662
  // ../../packages/local-store/dist/sync-tracking.js
5569
- import { promises as fs6 } from "fs";
5663
+ import { promises as fs7 } from "fs";
5570
5664
  async function readLastPush(projectRoot) {
5571
5665
  try {
5572
- const raw = await fs6.readFile(lastPushPath(projectRoot), "utf8");
5666
+ const raw = await fs7.readFile(lastPushPath(projectRoot), "utf8");
5573
5667
  const parsed = JSON.parse(raw);
5574
5668
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
5575
5669
  return parsed;
@@ -5616,7 +5710,7 @@ async function forgetLastPush(projectRoot, entityId) {
5616
5710
  }
5617
5711
  async function readCloudBase(projectRoot) {
5618
5712
  try {
5619
- const raw = await fs6.readFile(cloudBasePath(projectRoot), "utf8");
5713
+ const raw = await fs7.readFile(cloudBasePath(projectRoot), "utf8");
5620
5714
  const parsed = JSON.parse(raw);
5621
5715
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
5622
5716
  return parsed;
@@ -5717,7 +5811,7 @@ function isRetryQueueEntry(value) {
5717
5811
  async function readRetryQueueDetailed(projectRoot) {
5718
5812
  let raw;
5719
5813
  try {
5720
- raw = await fs6.readFile(retryQueuePath(projectRoot), "utf8");
5814
+ raw = await fs7.readFile(retryQueuePath(projectRoot), "utf8");
5721
5815
  } catch (err) {
5722
5816
  if (err.code === "ENOENT") {
5723
5817
  return { entries: [], warnings: [], corruptedLines: [] };
@@ -5773,7 +5867,7 @@ function canonicalJsonString(val) {
5773
5867
  async function readQueueAndPreserveCorrupted(projectRoot) {
5774
5868
  const { entries, corruptedLines } = await readRetryQueueDetailed(projectRoot);
5775
5869
  if (corruptedLines && corruptedLines.length > 0) {
5776
- await fs6.appendFile(retryQueueCorruptedPath(projectRoot), corruptedLines.map((l) => `${l}
5870
+ await fs7.appendFile(retryQueueCorruptedPath(projectRoot), corruptedLines.map((l) => `${l}
5777
5871
  `).join(""), "utf8");
5778
5872
  }
5779
5873
  return entries;
@@ -5823,7 +5917,7 @@ var init_sync_tracking = __esm({
5823
5917
  });
5824
5918
 
5825
5919
  // ../../packages/local-store/dist/entity.js
5826
- import { promises as fs7 } from "fs";
5920
+ import { promises as fs8 } from "fs";
5827
5921
  import path5 from "path";
5828
5922
  async function resolveEntityPath(projectRoot, type2, slugOrUuid) {
5829
5923
  const isSlug = slugSchema.safeParse(slugOrUuid).success;
@@ -5832,7 +5926,7 @@ async function resolveEntityPath(projectRoot, type2, slugOrUuid) {
5832
5926
  return null;
5833
5927
  const direct = entityFilePath(projectRoot, type2, slugOrUuid);
5834
5928
  try {
5835
- await fs7.access(direct);
5929
+ await fs8.access(direct);
5836
5930
  return direct;
5837
5931
  } catch {
5838
5932
  }
@@ -5841,7 +5935,7 @@ async function resolveEntityPath(projectRoot, type2, slugOrUuid) {
5841
5935
  const dir = entityDirPath(projectRoot, type2);
5842
5936
  let entries;
5843
5937
  try {
5844
- entries = await fs7.readdir(dir);
5938
+ entries = await fs8.readdir(dir);
5845
5939
  } catch {
5846
5940
  return null;
5847
5941
  }
@@ -5850,7 +5944,7 @@ async function resolveEntityPath(projectRoot, type2, slugOrUuid) {
5850
5944
  continue;
5851
5945
  const abs = path5.join(dir, name);
5852
5946
  try {
5853
- const raw = await fs7.readFile(abs, "utf8");
5947
+ const raw = await fs8.readFile(abs, "utf8");
5854
5948
  const { frontmatter } = parseFrontmatter(raw);
5855
5949
  if (typeof frontmatter.id === "string" && frontmatter.id.toLowerCase() === slugOrUuid.toLowerCase()) {
5856
5950
  return abs;
@@ -5867,7 +5961,7 @@ async function readEntity(projectRoot, type2, slugOrUuid) {
5867
5961
  const abs = await resolveEntityPath(projectRoot, type2, slugOrUuid);
5868
5962
  if (!abs)
5869
5963
  return null;
5870
- const raw = await fs7.readFile(abs, "utf8");
5964
+ const raw = await fs8.readFile(abs, "utf8");
5871
5965
  const { frontmatter, body } = parseFrontmatter(raw);
5872
5966
  const schema2 = entityFrontmatterSchemas[type2];
5873
5967
  const parsed = schema2.parse(frontmatter);
@@ -5895,7 +5989,7 @@ async function writeEntity(projectRoot, type2, frontmatter, body) {
5895
5989
  }
5896
5990
  async function entityExists(projectRoot, type2, slug) {
5897
5991
  try {
5898
- await fs7.access(entityFilePath(projectRoot, type2, slug));
5992
+ await fs8.access(entityFilePath(projectRoot, type2, slug));
5899
5993
  return true;
5900
5994
  } catch {
5901
5995
  return false;
@@ -5905,7 +5999,7 @@ async function listEntities(projectRoot, type2) {
5905
5999
  const dir = entityDirPath(projectRoot, type2);
5906
6000
  let names;
5907
6001
  try {
5908
- names = await fs7.readdir(dir);
6002
+ names = await fs8.readdir(dir);
5909
6003
  } catch (err) {
5910
6004
  if (err.code === "ENOENT") {
5911
6005
  return { entities: [], warnings: [] };
@@ -5920,7 +6014,7 @@ async function listEntities(projectRoot, type2) {
5920
6014
  continue;
5921
6015
  const abs = path5.join(dir, name);
5922
6016
  try {
5923
- const raw = await fs7.readFile(abs, "utf8");
6017
+ const raw = await fs8.readFile(abs, "utf8");
5924
6018
  const { frontmatter, body } = parseFrontmatter(raw);
5925
6019
  const parsed = schema2.parse(frontmatter);
5926
6020
  entities.push({
@@ -6035,14 +6129,14 @@ var init_guidance2 = __esm({
6035
6129
  });
6036
6130
 
6037
6131
  // ../../packages/local-store/dist/finding-match.js
6038
- import { promises as fs8 } from "fs";
6132
+ import { promises as fs9 } from "fs";
6039
6133
  import path6 from "path";
6040
- import { z as z22 } from "zod";
6134
+ import { z as z23 } from "zod";
6041
6135
  async function readScenarioFindings(projectRoot, scenarioSlug) {
6042
6136
  const dir = entityDirPath(projectRoot, "finding");
6043
6137
  let names;
6044
6138
  try {
6045
- names = await fs8.readdir(dir);
6139
+ names = await fs9.readdir(dir);
6046
6140
  } catch {
6047
6141
  return [];
6048
6142
  }
@@ -6052,7 +6146,7 @@ async function readScenarioFindings(projectRoot, scenarioSlug) {
6052
6146
  continue;
6053
6147
  const file = path6.join(dir, name);
6054
6148
  try {
6055
- const { frontmatter } = parseFrontmatter(await fs8.readFile(file, "utf8"));
6149
+ const { frontmatter } = parseFrontmatter(await fs9.readFile(file, "utf8"));
6056
6150
  const fm = frontmatter;
6057
6151
  if (fm.scenario === scenarioSlug)
6058
6152
  out.push({ file, frontmatter: fm });
@@ -6092,7 +6186,7 @@ async function suppressionRules(projectRoot, outlineSlug) {
6092
6186
  }
6093
6187
  async function scenarioOutline(projectRoot, scenarioSlug) {
6094
6188
  try {
6095
- const raw = await fs8.readFile(path6.join(entityDirPath(projectRoot, "scenario"), `${scenarioSlug}.md`), "utf8");
6189
+ const raw = await fs9.readFile(path6.join(entityDirPath(projectRoot, "scenario"), `${scenarioSlug}.md`), "utf8");
6096
6190
  const outline = parseFrontmatter(raw).frontmatter.outline;
6097
6191
  return typeof outline === "string" ? outline : null;
6098
6192
  } catch {
@@ -6205,7 +6299,7 @@ async function recordMatches(projectRoot, runId, scenarioSlug, answer) {
6205
6299
  const target = byId.get(m.finding_id);
6206
6300
  if (!target)
6207
6301
  continue;
6208
- const raw = await fs8.readFile(target, "utf8");
6302
+ const raw = await fs9.readFile(target, "utf8");
6209
6303
  const updated = setFrontmatterScalar(raw, "canonical_id", m.canonical_id);
6210
6304
  if (updated !== raw)
6211
6305
  await atomicWriteFile(target, updated);
@@ -6215,7 +6309,7 @@ async function recordMatches(projectRoot, runId, scenarioSlug, answer) {
6215
6309
  const target = byId.get(s.finding_id);
6216
6310
  if (!target)
6217
6311
  continue;
6218
- const raw = await fs8.readFile(target, "utf8");
6312
+ const raw = await fs9.readFile(target, "utf8");
6219
6313
  const updated = setFrontmatterScalar(raw, "suppressed_by_guidance", s.guidance_id);
6220
6314
  if (updated !== raw)
6221
6315
  await atomicWriteFile(target, updated);
@@ -6233,7 +6327,7 @@ async function setCanonicalFixStatus(projectRoot, scenarioSlug, canonicalId, sta
6233
6327
  for (const f of await readScenarioFindings(projectRoot, scenarioSlug)) {
6234
6328
  if (f.frontmatter.canonical_id !== canonicalId)
6235
6329
  continue;
6236
- const raw = await fs8.readFile(f.file, "utf8");
6330
+ const raw = await fs9.readFile(f.file, "utf8");
6237
6331
  const updated = setFrontmatterScalar(raw, "fix_status", status2);
6238
6332
  if (updated !== raw)
6239
6333
  await atomicWriteFile(f.file, updated);
@@ -6241,7 +6335,7 @@ async function setCanonicalFixStatus(projectRoot, scenarioSlug, canonicalId, sta
6241
6335
  }
6242
6336
  async function readMatches(projectRoot, runId) {
6243
6337
  try {
6244
- const raw = await fs8.readFile(path6.join(runDir(projectRoot, runId), MATCHES_FILE), "utf8");
6338
+ const raw = await fs9.readFile(path6.join(runDir(projectRoot, runId), MATCHES_FILE), "utf8");
6245
6339
  const parsed = matchesFileSchema.safeParse(JSON.parse(raw));
6246
6340
  return parsed.success ? parsed.data : null;
6247
6341
  } catch {
@@ -6258,32 +6352,32 @@ var init_finding_match = __esm({
6258
6352
  init_paths();
6259
6353
  init_walkthrough_canonical2();
6260
6354
  MATCHES_FILE = "matches.json";
6261
- matchesFileSchema = z22.object({
6262
- matches: z22.array(z22.object({
6263
- finding_id: z22.string().min(1),
6264
- canonical_id: z22.string().min(1).nullable(),
6265
- reasoning: z22.string().default("")
6355
+ matchesFileSchema = z23.object({
6356
+ matches: z23.array(z23.object({
6357
+ finding_id: z23.string().min(1),
6358
+ canonical_id: z23.string().min(1).nullable(),
6359
+ reasoning: z23.string().default("")
6266
6360
  })),
6267
- rechecked_fixed: z22.array(z22.object({
6268
- canonical_id: z22.string().min(1),
6269
- step_ids: z22.array(z22.string().min(1)).min(1),
6270
- reasoning: z22.string().default("")
6361
+ rechecked_fixed: z23.array(z23.object({
6362
+ canonical_id: z23.string().min(1),
6363
+ step_ids: z23.array(z23.string().min(1)).min(1),
6364
+ reasoning: z23.string().default("")
6271
6365
  })).default([]),
6272
6366
  /**
6273
6367
  * Findings of this run that an active `not_relevant` tester guidance
6274
6368
  * rule covers. Kept, flagged: `suppressed_by_guidance` on the file.
6275
6369
  */
6276
- suppressed: z22.array(z22.object({
6277
- finding_id: z22.string().min(1),
6278
- guidance_id: z22.string().min(1),
6279
- reasoning: z22.string().default("")
6370
+ suppressed: z23.array(z23.object({
6371
+ finding_id: z23.string().min(1),
6372
+ guidance_id: z23.string().min(1),
6373
+ reasoning: z23.string().default("")
6280
6374
  })).default([])
6281
6375
  });
6282
6376
  }
6283
6377
  });
6284
6378
 
6285
6379
  // ../../packages/local-store/dist/runs.js
6286
- import { promises as fs9 } from "fs";
6380
+ import { promises as fs10 } from "fs";
6287
6381
  import path7 from "path";
6288
6382
  async function openRun(projectRoot, scenarioSlug, options = {}) {
6289
6383
  const now = options.now ?? /* @__PURE__ */ new Date();
@@ -6292,9 +6386,9 @@ async function openRun(projectRoot, scenarioSlug, options = {}) {
6292
6386
  const stepsDir = path7.join(absPath, "steps");
6293
6387
  const fingerprintsDir = path7.join(absPath, "fingerprints");
6294
6388
  const screenshotsDir = path7.join(absPath, "screenshots");
6295
- await fs9.mkdir(stepsDir, { recursive: true });
6296
- await fs9.mkdir(fingerprintsDir, { recursive: true });
6297
- await fs9.mkdir(screenshotsDir, { recursive: true });
6389
+ await fs10.mkdir(stepsDir, { recursive: true });
6390
+ await fs10.mkdir(fingerprintsDir, { recursive: true });
6391
+ await fs10.mkdir(screenshotsDir, { recursive: true });
6298
6392
  return {
6299
6393
  runId,
6300
6394
  absPath,
@@ -6309,14 +6403,14 @@ function padStepNumber(n) {
6309
6403
  }
6310
6404
  async function writeStep(runDirAbs, step) {
6311
6405
  const stepsDir = path7.join(runDirAbs, "steps");
6312
- await fs9.mkdir(stepsDir, { recursive: true });
6406
+ await fs10.mkdir(stepsDir, { recursive: true });
6313
6407
  const filename = `${padStepNumber(step.step_number)}.json`;
6314
6408
  await atomicWriteFile(path7.join(stepsDir, filename), JSON.stringify(step, null, 2) + "\n");
6315
6409
  }
6316
6410
  async function listRuns(projectRoot) {
6317
6411
  const dir = runsDir(projectRoot);
6318
6412
  try {
6319
- const entries = await fs9.readdir(dir, { withFileTypes: true });
6413
+ const entries = await fs10.readdir(dir, { withFileTypes: true });
6320
6414
  return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort();
6321
6415
  } catch (err) {
6322
6416
  if (err.code === "ENOENT")
@@ -6328,7 +6422,7 @@ async function listIncompleteRuns(projectRoot) {
6328
6422
  const incomplete = [];
6329
6423
  for (const runId of await listRuns(projectRoot)) {
6330
6424
  try {
6331
- const manifest = JSON.parse(await fs9.readFile(path7.join(runsDir(projectRoot), runId, "walkthrough.json"), "utf8"));
6425
+ const manifest = JSON.parse(await fs10.readFile(path7.join(runsDir(projectRoot), runId, "walkthrough.json"), "utf8"));
6332
6426
  if (manifest.status !== "completed")
6333
6427
  incomplete.push(runId);
6334
6428
  } catch {
@@ -6346,7 +6440,7 @@ var init_runs = __esm({
6346
6440
  });
6347
6441
 
6348
6442
  // ../../packages/local-store/dist/fix-state.js
6349
- import { promises as fs10 } from "fs";
6443
+ import { promises as fs11 } from "fs";
6350
6444
  import path8 from "path";
6351
6445
  function toMillis(iso) {
6352
6446
  if (!iso)
@@ -6452,7 +6546,7 @@ function summarizeRerun(findings2, runs, states) {
6452
6546
  }
6453
6547
  async function readJson(file) {
6454
6548
  try {
6455
- return JSON.parse(await fs10.readFile(file, "utf8"));
6549
+ return JSON.parse(await fs11.readFile(file, "utf8"));
6456
6550
  } catch {
6457
6551
  return null;
6458
6552
  }
@@ -7025,19 +7119,19 @@ var init_journey_diff = __esm({
7025
7119
 
7026
7120
  // ../../packages/local-store/dist/walkthrough-canonical.js
7027
7121
  import { createHash as createHash2, randomUUID as randomUUID5 } from "crypto";
7028
- import { promises as fs11 } from "fs";
7122
+ import { promises as fs12 } from "fs";
7029
7123
  import path9 from "path";
7030
7124
  async function readStepsForRun(projectRoot, runId) {
7031
7125
  const runDirPath = runDir(projectRoot, runId);
7032
7126
  const stepsDir = path9.join(runDirPath, "steps");
7033
7127
  try {
7034
- const names = await fs11.readdir(stepsDir);
7128
+ const names = await fs12.readdir(stepsDir);
7035
7129
  const jsonFiles = names.filter((n) => n.endsWith(".json")).sort();
7036
7130
  if (jsonFiles.length > 0) {
7037
7131
  const out2 = [];
7038
7132
  for (const name of jsonFiles) {
7039
7133
  try {
7040
- const raw2 = await fs11.readFile(path9.join(stepsDir, name), "utf8");
7134
+ const raw2 = await fs12.readFile(path9.join(stepsDir, name), "utf8");
7041
7135
  const parsed = JSON.parse(raw2);
7042
7136
  if (parsed.step_number == null) {
7043
7137
  const numFromName = Number.parseInt(name.replace(/\.json$/, ""), 10);
@@ -7054,7 +7148,7 @@ async function readStepsForRun(projectRoot, runId) {
7054
7148
  const stepsPath = path9.join(runDirPath, "steps.jsonl");
7055
7149
  let raw;
7056
7150
  try {
7057
- raw = await fs11.readFile(stepsPath, "utf8");
7151
+ raw = await fs12.readFile(stepsPath, "utf8");
7058
7152
  } catch (err) {
7059
7153
  if (err.code === "ENOENT")
7060
7154
  return [];
@@ -7079,7 +7173,7 @@ async function readFindingsForRun(projectRoot, runId) {
7079
7173
  const findingsDir = entityDirPath(projectRoot, "finding");
7080
7174
  let names;
7081
7175
  try {
7082
- names = await fs11.readdir(findingsDir);
7176
+ names = await fs12.readdir(findingsDir);
7083
7177
  } catch (err) {
7084
7178
  if (err.code === "ENOENT")
7085
7179
  return [];
@@ -7090,7 +7184,7 @@ async function readFindingsForRun(projectRoot, runId) {
7090
7184
  if (!name.endsWith(".md") || name.startsWith("."))
7091
7185
  continue;
7092
7186
  const filePath = path9.join(findingsDir, name);
7093
- const raw = await fs11.readFile(filePath, "utf8");
7187
+ const raw = await fs12.readFile(filePath, "utf8");
7094
7188
  try {
7095
7189
  const { frontmatter, body } = parseFrontmatter(raw);
7096
7190
  const fm = frontmatter;
@@ -7121,7 +7215,7 @@ async function resolveCanonicalIds(projectRoot, scenarioSlug, currentRunId, curr
7121
7215
  const findingsDir = entityDirPath(projectRoot, "finding");
7122
7216
  let allNames;
7123
7217
  try {
7124
- allNames = await fs11.readdir(findingsDir);
7218
+ allNames = await fs12.readdir(findingsDir);
7125
7219
  } catch {
7126
7220
  allNames = [];
7127
7221
  }
@@ -7129,7 +7223,7 @@ async function resolveCanonicalIds(projectRoot, scenarioSlug, currentRunId, curr
7129
7223
  if (!name.endsWith(".md") || name.startsWith("."))
7130
7224
  continue;
7131
7225
  try {
7132
- const raw = await fs11.readFile(path9.join(findingsDir, name), "utf8");
7226
+ const raw = await fs12.readFile(path9.join(findingsDir, name), "utf8");
7133
7227
  const { frontmatter } = parseFrontmatter(raw);
7134
7228
  const fm = frontmatter;
7135
7229
  if (!fm.scenario || fm.scenario !== scenarioSlug)
@@ -7157,7 +7251,7 @@ async function resolveCanonicalIds(projectRoot, scenarioSlug, currentRunId, curr
7157
7251
  continue;
7158
7252
  const candidatePath = path9.join(findingsDir, name);
7159
7253
  try {
7160
- const candidateRaw = await fs11.readFile(candidatePath, "utf8");
7254
+ const candidateRaw = await fs12.readFile(candidatePath, "utf8");
7161
7255
  const { frontmatter: cFm } = parseFrontmatter(candidateRaw);
7162
7256
  if (cFm.id === f.frontmatter.id) {
7163
7257
  targetPath = candidatePath;
@@ -7168,7 +7262,7 @@ async function resolveCanonicalIds(projectRoot, scenarioSlug, currentRunId, curr
7168
7262
  }
7169
7263
  if (targetPath) {
7170
7264
  await withFileLock(targetPath, async () => {
7171
- const raw = await fs11.readFile(targetPath, "utf8");
7265
+ const raw = await fs12.readFile(targetPath, "utf8");
7172
7266
  const updated = raw.replace(/^(---\n[\s\S]*?)(---\n)/m, `$1canonical_id: ${canonicalId}
7173
7267
  $2`);
7174
7268
  if (updated !== raw) {
@@ -7304,7 +7398,7 @@ async function compareWithBaseline(projectRoot, scenarioSlug, currentRunId, curr
7304
7398
  continue;
7305
7399
  try {
7306
7400
  const mPath = path9.join(runDir(projectRoot, rid), "walkthrough.json");
7307
- const raw = await fs11.readFile(mPath, "utf8");
7401
+ const raw = await fs12.readFile(mPath, "utf8");
7308
7402
  const m = JSON.parse(raw);
7309
7403
  if (m.scenario_slug === scenarioSlug && m.status === "completed") {
7310
7404
  baselineRunId = rid;
@@ -7387,7 +7481,7 @@ async function updateFixStatus(projectRoot, slug, status2) {
7387
7481
  const findingsDir = entityDirPath(projectRoot, "finding");
7388
7482
  const filePath = path9.join(findingsDir, `${slug}.md`);
7389
7483
  await withFileLock(filePath, async () => {
7390
- const raw = await fs11.readFile(filePath, "utf8");
7484
+ const raw = await fs12.readFile(filePath, "utf8");
7391
7485
  const updated = raw.replace(/^(fix_status:\s*).*$/m, `$1${status2}`);
7392
7486
  if (updated !== raw) {
7393
7487
  await atomicWriteFile(filePath, updated);
@@ -7403,7 +7497,7 @@ fix_status: ${status2}`);
7403
7497
  async function listRunsInternal(projectRoot) {
7404
7498
  const dir = path9.join(projectRoot, "iamu", ".runs");
7405
7499
  try {
7406
- const names = await fs11.readdir(dir);
7500
+ const names = await fs12.readdir(dir);
7407
7501
  return names.filter((n) => !n.startsWith(".")).sort();
7408
7502
  } catch {
7409
7503
  return [];
@@ -7585,7 +7679,7 @@ __export(walkthrough_mirror_exports, {
7585
7679
  walkthroughBundleSchema: () => walkthroughBundleSchema
7586
7680
  });
7587
7681
  import { createHash as createHash3 } from "crypto";
7588
- import { promises as fs12 } from "fs";
7682
+ import { promises as fs13 } from "fs";
7589
7683
  import path10 from "path";
7590
7684
  function toCloudRunUuid(runId) {
7591
7685
  if (UUID_RE.test(runId))
@@ -7638,7 +7732,7 @@ function buildPushUrl(cloudApiUrl, cloudProjectId) {
7638
7732
  async function readLocalManifest(projectRoot, runId) {
7639
7733
  const p = path10.join(runDir(projectRoot, runId), "walkthrough.json");
7640
7734
  try {
7641
- const raw = await fs12.readFile(p, "utf8");
7735
+ const raw = await fs13.readFile(p, "utf8");
7642
7736
  return JSON.parse(raw);
7643
7737
  } catch {
7644
7738
  return null;
@@ -7647,7 +7741,7 @@ async function readLocalManifest(projectRoot, runId) {
7647
7741
  async function readLocalReport(projectRoot, runId) {
7648
7742
  const p = path10.join(runDir(projectRoot, runId), "report.md");
7649
7743
  try {
7650
- return await fs12.readFile(p, "utf8");
7744
+ return await fs13.readFile(p, "utf8");
7651
7745
  } catch (err) {
7652
7746
  if (err.code === "ENOENT")
7653
7747
  return "";
@@ -7658,7 +7752,7 @@ async function readScreenshotBytes(projectRoot, runId) {
7658
7752
  const dir = path10.join(runDir(projectRoot, runId), SCREENSHOTS_DIR);
7659
7753
  let names;
7660
7754
  try {
7661
- names = await fs12.readdir(dir);
7755
+ names = await fs13.readdir(dir);
7662
7756
  } catch (err) {
7663
7757
  if (err.code === "ENOENT")
7664
7758
  return /* @__PURE__ */ new Map();
@@ -7674,7 +7768,7 @@ async function readScreenshotBytes(projectRoot, runId) {
7674
7768
  const contentType = EXT_TO_CONTENT_TYPE[ext === ".jpeg" ? ".jpeg" : ext];
7675
7769
  if (!contentType)
7676
7770
  continue;
7677
- const bytes = await fs12.readFile(path10.join(dir, name));
7771
+ const bytes = await fs13.readFile(path10.join(dir, name));
7678
7772
  out.set(seq2, { bytes, filename: name, contentType });
7679
7773
  }
7680
7774
  return out;
@@ -7682,7 +7776,7 @@ async function readScreenshotBytes(projectRoot, runId) {
7682
7776
  async function readVerdictsJson(projectRoot, runId) {
7683
7777
  const filePath = path10.join(runDir(projectRoot, runId), "verdicts.json");
7684
7778
  try {
7685
- const raw = await fs12.readFile(filePath, "utf-8");
7779
+ const raw = await fs13.readFile(filePath, "utf-8");
7686
7780
  return JSON.parse(raw);
7687
7781
  } catch {
7688
7782
  return null;
@@ -7693,7 +7787,7 @@ async function readProvenance(projectRoot, runId, verdicts) {
7693
7787
  return verdicts.provenance;
7694
7788
  const filePath = path10.join(runDir(projectRoot, runId), "canonical.json");
7695
7789
  try {
7696
- const raw = await fs12.readFile(filePath, "utf-8");
7790
+ const raw = await fs13.readFile(filePath, "utf-8");
7697
7791
  const data = JSON.parse(raw);
7698
7792
  return data.provenance ?? null;
7699
7793
  } catch {
@@ -7702,7 +7796,7 @@ async function readProvenance(projectRoot, runId, verdicts) {
7702
7796
  }
7703
7797
  async function readCanonicalExtras(projectRoot, runId) {
7704
7798
  try {
7705
- const data = JSON.parse(await fs12.readFile(path10.join(runDir(projectRoot, runId), "canonical.json"), "utf-8"));
7799
+ const data = JSON.parse(await fs13.readFile(path10.join(runDir(projectRoot, runId), "canonical.json"), "utf-8"));
7706
7800
  const strings = (v) => Array.isArray(v) && v.every((x) => typeof x === "string") ? v : void 0;
7707
7801
  const verification = bundleVerificationSchema.safeParse(data.verification);
7708
7802
  return {
@@ -8441,7 +8535,7 @@ var init_refs = __esm({
8441
8535
  });
8442
8536
 
8443
8537
  // ../../packages/local-store/dist/sync/types.js
8444
- import { z as z23 } from "zod";
8538
+ import { z as z24 } from "zod";
8445
8539
  function toRef(d) {
8446
8540
  return { type: d.type, id: d.id, label: d.label };
8447
8541
  }
@@ -8506,17 +8600,17 @@ var init_types = __esm({
8506
8600
  DEFAULT_TIMEOUT_MS4 = 3e4;
8507
8601
  MAX_BATCH_BYTES = 6 * 1024 * 1024;
8508
8602
  BASE_HASH_BYTES = 96;
8509
- importConflictSchema = z23.object({
8510
- type: z23.string(),
8511
- id: z23.string(),
8512
- base_hash: z23.string().nullish(),
8513
- cloud_hash: z23.string(),
8514
- cloud_updated_at: z23.string().nullish(),
8515
- cloud_changed_by: z23.string().nullish()
8603
+ importConflictSchema = z24.object({
8604
+ type: z24.string(),
8605
+ id: z24.string(),
8606
+ base_hash: z24.string().nullish(),
8607
+ cloud_hash: z24.string(),
8608
+ cloud_updated_at: z24.string().nullish(),
8609
+ cloud_changed_by: z24.string().nullish()
8516
8610
  });
8517
- importResponseSchema = z23.object({
8518
- imported: z23.number().int().min(0),
8519
- conflicts: z23.array(importConflictSchema).optional()
8611
+ importResponseSchema = z24.object({
8612
+ imported: z24.number().int().min(0),
8613
+ conflicts: z24.array(importConflictSchema).optional()
8520
8614
  });
8521
8615
  }
8522
8616
  });
@@ -8767,7 +8861,7 @@ var init_push = __esm({
8767
8861
  });
8768
8862
 
8769
8863
  // ../../packages/local-store/dist/sync/context.js
8770
- import { promises as fs13 } from "fs";
8864
+ import { promises as fs14 } from "fs";
8771
8865
  async function syncContext(target) {
8772
8866
  const { projectRoot } = target;
8773
8867
  const summary = zeroContext();
@@ -8775,7 +8869,7 @@ async function syncContext(target) {
8775
8869
  const conflicts = [];
8776
8870
  let raw;
8777
8871
  try {
8778
- raw = await fs13.readFile(contextPath(projectRoot), "utf8");
8872
+ raw = await fs14.readFile(contextPath(projectRoot), "utf8");
8779
8873
  } catch (err) {
8780
8874
  if (err.code === "ENOENT")
8781
8875
  return { summary, errors, conflicts };
@@ -8985,7 +9079,7 @@ var init_git_branches = __esm({
8985
9079
  });
8986
9080
 
8987
9081
  // ../../packages/local-store/dist/deletions.js
8988
- import { promises as fs14 } from "fs";
9082
+ import { promises as fs15 } from "fs";
8989
9083
  import path12 from "path";
8990
9084
  function describeDeletionCount(type2, n) {
8991
9085
  const nouns = {
@@ -9013,7 +9107,7 @@ function summarizeDeletions(items) {
9013
9107
  }
9014
9108
  async function readPushedEntities(projectRoot) {
9015
9109
  try {
9016
- const raw = await fs14.readFile(pushedEntitiesPath(projectRoot), "utf8");
9110
+ const raw = await fs15.readFile(pushedEntitiesPath(projectRoot), "utf8");
9017
9111
  const parsed = JSON.parse(raw);
9018
9112
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
9019
9113
  return {};
@@ -9052,7 +9146,7 @@ async function rememberPushedEntities(projectRoot, known) {
9052
9146
  }
9053
9147
  async function readLenient(projectRoot, relPath) {
9054
9148
  try {
9055
- const raw = await fs14.readFile(path12.join(projectRoot, relPath), "utf8");
9149
+ const raw = await fs15.readFile(path12.join(projectRoot, relPath), "utf8");
9056
9150
  const fm = parseFrontmatter(raw).frontmatter;
9057
9151
  if (typeof fm.id !== "string" || fm.id.length === 0)
9058
9152
  return null;
@@ -9478,7 +9572,7 @@ var init_deletions2 = __esm({
9478
9572
  });
9479
9573
 
9480
9574
  // ../../packages/local-store/dist/sync/scan.js
9481
- import { promises as fs15 } from "fs";
9575
+ import { promises as fs16 } from "fs";
9482
9576
  function blockedRef(refs, blocked) {
9483
9577
  if (!refs)
9484
9578
  return null;
@@ -9574,7 +9668,7 @@ async function scanPending(projectRoot) {
9574
9668
  continue;
9575
9669
  let bytes;
9576
9670
  try {
9577
- bytes = await fs15.readFile(entity.absPath, "utf8");
9671
+ bytes = await fs16.readFile(entity.absPath, "utf8");
9578
9672
  } catch {
9579
9673
  continue;
9580
9674
  }
@@ -9635,7 +9729,7 @@ var init_scan = __esm({
9635
9729
  });
9636
9730
 
9637
9731
  // ../../packages/local-store/dist/sync/walkthroughs.js
9638
- import { promises as fs16 } from "fs";
9732
+ import { promises as fs17 } from "fs";
9639
9733
  import nodePath from "path";
9640
9734
  async function syncWalkthroughs(projectRoot, deps, unpushableScenarios = /* @__PURE__ */ new Set()) {
9641
9735
  const summary = zeroWalkthroughs();
@@ -9663,7 +9757,7 @@ async function syncWalkthroughs(projectRoot, deps, unpushableScenarios = /* @__P
9663
9757
  const manifestPath = nodePath.join(runDir(projectRoot, runId), "walkthrough.json");
9664
9758
  let manifestRaw;
9665
9759
  try {
9666
- manifestRaw = await fs16.readFile(manifestPath, "utf8");
9760
+ manifestRaw = await fs17.readFile(manifestPath, "utf8");
9667
9761
  } catch {
9668
9762
  continue;
9669
9763
  }
@@ -10002,7 +10096,7 @@ var init_disconnect = __esm({
10002
10096
  });
10003
10097
 
10004
10098
  // ../../packages/local-store/dist/status.js
10005
- import { promises as fs17 } from "fs";
10099
+ import { promises as fs18 } from "fs";
10006
10100
  import path13 from "path";
10007
10101
  async function countType(projectRoot, type2) {
10008
10102
  const result = await listEntities(projectRoot, type2);
@@ -10013,7 +10107,7 @@ async function countType(projectRoot, type2) {
10013
10107
  if (typeof id !== "string" || typeof slug !== "string")
10014
10108
  continue;
10015
10109
  try {
10016
- const raw = await fs17.readFile(entity.absPath, "utf8");
10110
+ const raw = await fs18.readFile(entity.absPath, "utf8");
10017
10111
  contentHashes.set(id, hashNormalizedContent(raw));
10018
10112
  } catch {
10019
10113
  }
@@ -10107,7 +10201,7 @@ async function countContext(projectRoot, opts = {
10107
10201
  return base;
10108
10202
  let pending = true;
10109
10203
  try {
10110
- const bytes = await fs17.readFile(contextPath(projectRoot), "utf8");
10204
+ const bytes = await fs18.readFile(contextPath(projectRoot), "utf8");
10111
10205
  const hash = hashNormalizedContent(bytes);
10112
10206
  pending = opts.lastPush["context:root"] !== hash;
10113
10207
  } catch {
@@ -10129,7 +10223,7 @@ async function countWalkthroughs(projectRoot, opts = { cloudLinked: false, lastP
10129
10223
  const manifestPath = path13.join(projectRoot, "iamu", ".runs", runId, "walkthrough.json");
10130
10224
  let completed = false;
10131
10225
  try {
10132
- const raw = await fs17.readFile(manifestPath, "utf8");
10226
+ const raw = await fs18.readFile(manifestPath, "utf8");
10133
10227
  const manifest = JSON.parse(raw);
10134
10228
  completed = manifest.status === "completed";
10135
10229
  } catch {
@@ -10149,7 +10243,7 @@ async function getProjectStatus(projectRoot, opts = {}) {
10149
10243
  const config = await readConfig(projectRoot);
10150
10244
  const iamuPath = iamuDir(projectRoot);
10151
10245
  try {
10152
- await fs17.access(iamuPath);
10246
+ await fs18.access(iamuPath);
10153
10247
  } catch {
10154
10248
  throw new Error(`status: iamu/ directory missing at ${iamuPath}`);
10155
10249
  }
@@ -10331,13 +10425,13 @@ var init_status = __esm({
10331
10425
  });
10332
10426
 
10333
10427
  // ../../packages/local-store/dist/prune.js
10334
- import { promises as fs18 } from "fs";
10428
+ import { promises as fs19 } from "fs";
10335
10429
  import path14 from "path";
10336
10430
  async function getDirectoryStats(dirPath) {
10337
10431
  let bytes = 0;
10338
10432
  let count = 0;
10339
10433
  try {
10340
- const entries = await fs18.readdir(dirPath, { withFileTypes: true });
10434
+ const entries = await fs19.readdir(dirPath, { withFileTypes: true });
10341
10435
  for (const entry of entries) {
10342
10436
  const fullPath = path14.join(dirPath, entry.name);
10343
10437
  if (entry.isDirectory()) {
@@ -10346,7 +10440,7 @@ async function getDirectoryStats(dirPath) {
10346
10440
  count += sub.count;
10347
10441
  } else if (entry.isFile()) {
10348
10442
  try {
10349
- const s = await fs18.stat(fullPath);
10443
+ const s = await fs19.stat(fullPath);
10350
10444
  bytes += s.size;
10351
10445
  count += 1;
10352
10446
  } catch {
@@ -10362,12 +10456,12 @@ async function getScreenshotsStats(screenshotsDir) {
10362
10456
  let count = 0;
10363
10457
  const paths = [];
10364
10458
  try {
10365
- const entries = await fs18.readdir(screenshotsDir, { withFileTypes: true });
10459
+ const entries = await fs19.readdir(screenshotsDir, { withFileTypes: true });
10366
10460
  for (const entry of entries) {
10367
10461
  if (entry.isFile() && (entry.name.endsWith(".png") || entry.name.endsWith(".jpg") || entry.name.endsWith(".jpeg") || entry.name.endsWith(".webp"))) {
10368
10462
  const fullPath = path14.join(screenshotsDir, entry.name);
10369
10463
  try {
10370
- const s = await fs18.stat(fullPath);
10464
+ const s = await fs19.stat(fullPath);
10371
10465
  bytes += s.size;
10372
10466
  count += 1;
10373
10467
  paths.push(fullPath);
@@ -10388,7 +10482,7 @@ async function pruneRuns(projectRoot, opts = {}) {
10388
10482
  let scenarioSlug;
10389
10483
  try {
10390
10484
  const manifestPath = path14.join(dirPath, "walkthrough.json");
10391
- const raw = await fs18.readFile(manifestPath, "utf-8");
10485
+ const raw = await fs19.readFile(manifestPath, "utf-8");
10392
10486
  const parsed = JSON.parse(raw);
10393
10487
  if (parsed.scenario_slug)
10394
10488
  scenarioSlug = parsed.scenario_slug;
@@ -10400,7 +10494,7 @@ async function pruneRuns(projectRoot, opts = {}) {
10400
10494
  }
10401
10495
  if (!timestamp2 || Number.isNaN(timestamp2.getTime())) {
10402
10496
  try {
10403
- const st = await fs18.stat(dirPath);
10497
+ const st = await fs19.stat(dirPath);
10404
10498
  timestamp2 = st.mtime;
10405
10499
  } catch {
10406
10500
  timestamp2 = /* @__PURE__ */ new Date();
@@ -10440,7 +10534,7 @@ async function pruneRuns(projectRoot, opts = {}) {
10440
10534
  if (opts.screenshotsOnly) {
10441
10535
  if (!opts.dryRun) {
10442
10536
  for (const shotPath of shotStats.paths) {
10443
- await fs18.unlink(shotPath).catch(() => {
10537
+ await fs19.unlink(shotPath).catch(() => {
10444
10538
  });
10445
10539
  }
10446
10540
  }
@@ -10457,7 +10551,7 @@ async function pruneRuns(projectRoot, opts = {}) {
10457
10551
  } else {
10458
10552
  const dirStats = await getDirectoryStats(c.dirPath);
10459
10553
  if (!opts.dryRun) {
10460
- await fs18.rm(c.dirPath, { recursive: true, force: true });
10554
+ await fs19.rm(c.dirPath, { recursive: true, force: true });
10461
10555
  }
10462
10556
  prunedRuns.push({
10463
10557
  runId: c.runId,
@@ -10489,7 +10583,7 @@ var init_prune = __esm({
10489
10583
 
10490
10584
  // ../../packages/local-store/dist/explore-map.js
10491
10585
  import { randomBytes as randomBytes2 } from "crypto";
10492
- import { promises as fs19 } from "fs";
10586
+ import { promises as fs20 } from "fs";
10493
10587
  import path15 from "path";
10494
10588
  function templateToken(token) {
10495
10589
  if (token === "")
@@ -10585,13 +10679,13 @@ function mapPath(projectRoot, id) {
10585
10679
  }
10586
10680
  async function writeExploreMap(projectRoot, map2) {
10587
10681
  const file = mapPath(projectRoot, map2.id);
10588
- await fs19.mkdir(path15.dirname(file), { recursive: true });
10682
+ await fs20.mkdir(path15.dirname(file), { recursive: true });
10589
10683
  await atomicWriteFile(file, `${JSON.stringify(map2, null, 2)}
10590
10684
  `);
10591
10685
  return file;
10592
10686
  }
10593
10687
  async function readExploreMap(projectRoot, id) {
10594
- const raw = await fs19.readFile(mapPath(projectRoot, id), "utf8");
10688
+ const raw = await fs20.readFile(mapPath(projectRoot, id), "utf8");
10595
10689
  const map2 = JSON.parse(raw);
10596
10690
  if (map2?.schema !== EXPLORE_MAP_SCHEMA || !Array.isArray(map2.surfaces)) {
10597
10691
  throw new Error(`Unreadable explore map: ${id}`);
@@ -10600,7 +10694,7 @@ async function readExploreMap(projectRoot, id) {
10600
10694
  }
10601
10695
  async function listExploreMaps(projectRoot) {
10602
10696
  try {
10603
- const entries = await fs19.readdir(exploreDir(projectRoot), {
10697
+ const entries = await fs20.readdir(exploreDir(projectRoot), {
10604
10698
  withFileTypes: true
10605
10699
  });
10606
10700
  return entries.filter((e) => e.isDirectory() && isValidExploreId(e.name)).map((e) => e.name).sort();
@@ -10698,6 +10792,127 @@ async function readAllRunSteps(projectRoot) {
10698
10792
  }
10699
10793
  return out;
10700
10794
  }
10795
+ function matchSurfaceCapabilities(place, capabilities) {
10796
+ const normPlace = place.length > 1 ? place.replace(/\/+$/, "") : place;
10797
+ const matchedCaps = /* @__PURE__ */ new Map();
10798
+ for (const cap of capabilities) {
10799
+ if (!cap.paths || cap.paths.length === 0)
10800
+ continue;
10801
+ for (const rawPath of cap.paths) {
10802
+ if (!rawPath)
10803
+ continue;
10804
+ const cleanPath = rawPath.length > 1 ? rawPath.replace(/\/+$/, "") : rawPath;
10805
+ let isMatch = false;
10806
+ if (cleanPath === "/") {
10807
+ isMatch = normPlace === "/" || normPlace.startsWith("/?") || normPlace.startsWith("/#");
10808
+ } else {
10809
+ isMatch = normPlace === cleanPath || normPlace.startsWith(`${cleanPath}/`) || normPlace.startsWith(`${cleanPath}?`) || normPlace.startsWith(`${cleanPath}#`);
10810
+ }
10811
+ if (isMatch) {
10812
+ const prevLen = matchedCaps.get(cap.slug) ?? -1;
10813
+ if (cleanPath.length > prevLen) {
10814
+ matchedCaps.set(cap.slug, cleanPath.length);
10815
+ }
10816
+ }
10817
+ }
10818
+ }
10819
+ if (matchedCaps.size > 0) {
10820
+ return Array.from(matchedCaps.entries()).sort((a, b) => b[1] - a[1]).map(([slug]) => slug);
10821
+ }
10822
+ const firstSegment = normPlace.replace(/^\/+/, "").split(/[/\\?#]/)[0];
10823
+ if (firstSegment) {
10824
+ const slugMatches = [];
10825
+ for (const c of capabilities) {
10826
+ if (c.slug === firstSegment || c.slug.toLowerCase() === firstSegment.toLowerCase()) {
10827
+ slugMatches.push(c.slug);
10828
+ }
10829
+ }
10830
+ if (slugMatches.length > 0)
10831
+ return slugMatches;
10832
+ }
10833
+ return [];
10834
+ }
10835
+ function summarizeCapabilityCoverage(surfaces, capabilities, outlines = []) {
10836
+ const capMap = /* @__PURE__ */ new Map();
10837
+ for (const c of capabilities) {
10838
+ capMap.set(c.slug, []);
10839
+ }
10840
+ const unassigned = [];
10841
+ for (const s of surfaces) {
10842
+ const matchedSlugs = matchSurfaceCapabilities(s.place, capabilities);
10843
+ if (matchedSlugs.length > 0) {
10844
+ for (const slug of matchedSlugs) {
10845
+ capMap.get(slug)?.push(s);
10846
+ }
10847
+ } else {
10848
+ unassigned.push(s);
10849
+ }
10850
+ }
10851
+ const primaryCounts = /* @__PURE__ */ new Map();
10852
+ const secondaryCounts = /* @__PURE__ */ new Map();
10853
+ for (const o of outlines) {
10854
+ if (o.primary_capability) {
10855
+ primaryCounts.set(o.primary_capability, (primaryCounts.get(o.primary_capability) ?? 0) + 1);
10856
+ }
10857
+ if (Array.isArray(o.additional_capabilities)) {
10858
+ for (const add of o.additional_capabilities) {
10859
+ if (add) {
10860
+ secondaryCounts.set(add, (secondaryCounts.get(add) ?? 0) + 1);
10861
+ }
10862
+ }
10863
+ }
10864
+ }
10865
+ const summaries = capabilities.map((c) => {
10866
+ const owned = capMap.get(c.slug) ?? [];
10867
+ const reached = owned.filter((s) => s.status === "reached").length;
10868
+ const unreached = owned.length - reached;
10869
+ const coverage_pct = owned.length > 0 ? Math.round(reached / owned.length * 100) : 0;
10870
+ const primary = primaryCounts.get(c.slug) ?? 0;
10871
+ const secondary = secondaryCounts.get(c.slug) ?? 0;
10872
+ return {
10873
+ slug: c.slug,
10874
+ title: c.title,
10875
+ area: c.area ?? null,
10876
+ total_surfaces: owned.length,
10877
+ reached_surfaces: reached,
10878
+ unreached_surfaces: unreached,
10879
+ coverage_pct,
10880
+ primary_scenarios: primary,
10881
+ secondary_scenarios: secondary,
10882
+ surfaces: owned
10883
+ };
10884
+ });
10885
+ summaries.sort((a, b) => {
10886
+ const aUntested = a.primary_scenarios === 0 && a.unreached_surfaces > 0;
10887
+ const bUntested = b.primary_scenarios === 0 && b.unreached_surfaces > 0;
10888
+ if (aUntested !== bUntested)
10889
+ return aUntested ? -1 : 1;
10890
+ if (a.coverage_pct !== b.coverage_pct)
10891
+ return a.coverage_pct - b.coverage_pct;
10892
+ return a.slug.localeCompare(b.slug);
10893
+ });
10894
+ return {
10895
+ capabilities: summaries,
10896
+ unassigned_surfaces: unassigned
10897
+ };
10898
+ }
10899
+ async function readCapabilityCoverage(projectRoot, coverage) {
10900
+ const [capsResult, outlinesResult] = await Promise.all([
10901
+ listEntities(projectRoot, "capability"),
10902
+ listEntities(projectRoot, "outline")
10903
+ ]);
10904
+ const caps = capsResult.entities.map((e) => ({
10905
+ slug: e.frontmatter.slug,
10906
+ title: e.frontmatter.title,
10907
+ area: e.frontmatter.area ?? null,
10908
+ paths: e.frontmatter.paths ?? []
10909
+ }));
10910
+ const outlines = outlinesResult.entities.map((e) => ({
10911
+ primary_capability: e.frontmatter.primary_capability,
10912
+ additional_capabilities: e.frontmatter.additional_capabilities ?? []
10913
+ }));
10914
+ return summarizeCapabilityCoverage(coverage.surfaces, caps, outlines);
10915
+ }
10701
10916
  function actionSignature(a) {
10702
10917
  const where = a.outcome.kind === "surface" ? a.outcome.surface : a.outcome.kind === "skipped" ? `skipped:${a.outcome.reason}` : a.outcome.kind === "external" ? `external:${a.outcome.url}` : a.outcome.kind;
10703
10918
  return `${a.role} "${a.name}" \u2192 ${where}`;
@@ -10742,6 +10957,7 @@ var init_explore_map = __esm({
10742
10957
  "../../packages/local-store/dist/explore-map.js"() {
10743
10958
  "use strict";
10744
10959
  init_atomic();
10960
+ init_entity();
10745
10961
  init_paths();
10746
10962
  init_revisit();
10747
10963
  init_runs();
@@ -10754,7 +10970,7 @@ var init_explore_map = __esm({
10754
10970
  });
10755
10971
 
10756
10972
  // ../../packages/local-store/dist/human-test-link.js
10757
- import { promises as fs20 } from "fs";
10973
+ import { promises as fs21 } from "fs";
10758
10974
  import path16 from "path";
10759
10975
  function humanTestLinks(verdicts) {
10760
10976
  const best = /* @__PURE__ */ new Map();
@@ -10778,7 +10994,7 @@ async function linkFindingsToHumanTests(projectRoot, runId, verdicts) {
10778
10994
  const dir = entityDirPath(projectRoot, "finding");
10779
10995
  let names;
10780
10996
  try {
10781
- names = await fs20.readdir(dir);
10997
+ names = await fs21.readdir(dir);
10782
10998
  } catch {
10783
10999
  return [];
10784
11000
  }
@@ -10790,7 +11006,7 @@ async function linkFindingsToHumanTests(projectRoot, runId, verdicts) {
10790
11006
  let raw;
10791
11007
  let fm;
10792
11008
  try {
10793
- raw = await fs20.readFile(file, "utf8");
11009
+ raw = await fs21.readFile(file, "utf8");
10794
11010
  fm = parseFrontmatter(raw).frontmatter;
10795
11011
  } catch {
10796
11012
  continue;
@@ -11021,14 +11237,14 @@ var init_cloud_client = __esm({
11021
11237
  });
11022
11238
 
11023
11239
  // ../../packages/local-store/dist/last-pull.js
11024
- import { promises as fs21 } from "fs";
11240
+ import { promises as fs22 } from "fs";
11025
11241
  import path17 from "path";
11026
11242
  function lastPullPath(projectRoot) {
11027
11243
  return path17.join(cacheDir(projectRoot), "last-pull.json");
11028
11244
  }
11029
11245
  async function readLastPull(projectRoot) {
11030
11246
  try {
11031
- const raw = await fs21.readFile(lastPullPath(projectRoot), "utf8");
11247
+ const raw = await fs22.readFile(lastPullPath(projectRoot), "utf8");
11032
11248
  const parsed = JSON.parse(raw);
11033
11249
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
11034
11250
  return parsed;
@@ -11314,7 +11530,7 @@ var init_frontmatter2 = __esm({
11314
11530
  });
11315
11531
 
11316
11532
  // ../../packages/local-store/dist/pull/write.js
11317
- import { promises as fs22 } from "fs";
11533
+ import { promises as fs23 } from "fs";
11318
11534
  async function loadLocalIndex(projectRoot, type2) {
11319
11535
  const byId = /* @__PURE__ */ new Map();
11320
11536
  const bySlug = /* @__PURE__ */ new Map();
@@ -11343,7 +11559,7 @@ async function classifyExisting(projectRoot, type2, cloudId, localSlug, targetSl
11343
11559
  if (localSlug !== targetSlug)
11344
11560
  return "untracked";
11345
11561
  const lastSynced = localIndex.lastPushById[cloudId];
11346
- const bytes = await fs22.readFile(entityFilePath(projectRoot, type2, localSlug), "utf8");
11562
+ const bytes = await fs23.readFile(entityFilePath(projectRoot, type2, localSlug), "utf8");
11347
11563
  return lastSynced && hashNormalizedContent(bytes) === lastSynced ? "fast_forward" : "diverged";
11348
11564
  }
11349
11565
  async function writeOrSkip(projectRoot, type2, cloudId, slug, frontmatter, body, localIndex, mode, cloudVersion) {
@@ -11404,13 +11620,13 @@ async function writeOrSkip(projectRoot, type2, cloudId, slug, frontmatter, body,
11404
11620
  }
11405
11621
  if (existingSlugForId && existingSlugForId !== slug) {
11406
11622
  try {
11407
- await fs22.unlink(entityFilePath(projectRoot, type2, existingSlugForId));
11623
+ await fs23.unlink(entityFilePath(projectRoot, type2, existingSlugForId));
11408
11624
  localIndex.bySlug.delete(existingSlugForId);
11409
11625
  } catch {
11410
11626
  }
11411
11627
  }
11412
11628
  await writeEntity(projectRoot, type2, frontmatter, text);
11413
- const bytes = await fs22.readFile(entityFilePath(projectRoot, type2, slug), "utf8");
11629
+ const bytes = await fs23.readFile(entityFilePath(projectRoot, type2, slug), "utf8");
11414
11630
  const hash = hashNormalizedContent(bytes);
11415
11631
  localIndex.byId.set(cloudId, slug);
11416
11632
  localIndex.bySlug.set(slug, cloudId);
@@ -11464,7 +11680,7 @@ var init_write = __esm({
11464
11680
  });
11465
11681
 
11466
11682
  // ../../packages/local-store/dist/pull/entities.js
11467
- import { promises as fs23 } from "fs";
11683
+ import { promises as fs24 } from "fs";
11468
11684
  function buildContextFrontmatter(payload) {
11469
11685
  const cloudCtx = payload.context ?? {};
11470
11686
  const glossary = (payload.glossary ?? []).map((g) => ({
@@ -11519,7 +11735,7 @@ async function pullContext(projectRoot, cloud, projectId, mode, result) {
11519
11735
  const frontmatter = buildContextFrontmatter(payload);
11520
11736
  let hasLocal = false;
11521
11737
  try {
11522
- await fs23.access(contextPath(projectRoot));
11738
+ await fs24.access(contextPath(projectRoot));
11523
11739
  hasLocal = true;
11524
11740
  } catch {
11525
11741
  hasLocal = false;
@@ -11549,7 +11765,7 @@ async function pullContext(projectRoot, cloud, projectId, mode, result) {
11549
11765
  frontmatter,
11550
11766
  notes_body: existingNotes
11551
11767
  });
11552
- const bytes = await fs23.readFile(contextPath(projectRoot), "utf8");
11768
+ const bytes = await fs24.readFile(contextPath(projectRoot), "utf8");
11553
11769
  const hash = hashNormalizedContent(bytes);
11554
11770
  await recordLastPull(projectRoot, "context:root", hash);
11555
11771
  await recordLastPush(projectRoot, "context:root", hash);
@@ -12069,6 +12285,7 @@ var init_dist2 = __esm({
12069
12285
  init_bootstrap();
12070
12286
  init_config2();
12071
12287
  init_context2();
12288
+ init_testability2();
12072
12289
  init_connect();
12073
12290
  init_disconnect();
12074
12291
  init_status();
@@ -12972,11 +13189,24 @@ import { dirname as dirname3, join as join4, resolve as resolve2 } from "path";
12972
13189
  import { fileURLToPath as fileURLToPath2 } from "url";
12973
13190
  import chalk6 from "chalk";
12974
13191
  function getVersion() {
13192
+ if ("0.2.0-beta.17") {
13193
+ return "0.2.0-beta.17";
13194
+ }
12975
13195
  try {
12976
13196
  const thisDir = dirname3(fileURLToPath2(import.meta.url));
12977
- const pkgPath = resolve2(thisDir, "..", "..", "package.json");
12978
- const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
12979
- return pkg.version ?? "0.0.0";
13197
+ for (const rel of [".", "..", "../.."]) {
13198
+ const pkgPath = resolve2(thisDir, rel, "package.json");
13199
+ if (existsSync5(pkgPath)) {
13200
+ const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
13201
+ if (pkg.name === "@iamu-dev/iamu" && pkg.version) {
13202
+ return pkg.version;
13203
+ }
13204
+ if (pkg.version && pkg.version !== "0.0.0") {
13205
+ return pkg.version;
13206
+ }
13207
+ }
13208
+ }
13209
+ return "0.0.0";
12980
13210
  } catch {
12981
13211
  return "0.0.0";
12982
13212
  }
@@ -14866,17 +15096,12 @@ __export(start_run_exports, {
14866
15096
  startRun: () => startRun,
14867
15097
  testabilityTarget: () => testabilityTarget
14868
15098
  });
14869
- import fs24 from "fs/promises";
14870
15099
  import path20 from "path";
14871
15100
  import chalk12 from "chalk";
14872
15101
  async function testabilityTarget(projectRoot) {
14873
15102
  try {
14874
- const raw = await fs24.readFile(
14875
- path20.join(projectRoot, "iamu", "testability.md"),
14876
- "utf8"
14877
- );
14878
- const { frontmatter } = parseFrontmatter(raw);
14879
- return typeof frontmatter.target_url === "string" && frontmatter.target_url.trim().length > 0 ? frontmatter.target_url.trim() : null;
15103
+ const doc = await readTestability(projectRoot);
15104
+ return doc?.frontmatter.target_url ?? null;
14880
15105
  } catch {
14881
15106
  return null;
14882
15107
  }
@@ -15504,7 +15729,7 @@ var init_fatal = __esm({
15504
15729
 
15505
15730
  // src/sync-mcp/tools.ts
15506
15731
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
15507
- import { z as z24 } from "zod";
15732
+ import { z as z25 } from "zod";
15508
15733
  function getProjectRoot() {
15509
15734
  const root = findProjectRoot(process.cwd());
15510
15735
  if (!root)
@@ -15745,7 +15970,7 @@ function createSyncServer() {
15745
15970
  server.tool(
15746
15971
  "iamu_requests_claim",
15747
15972
  "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.",
15748
- { id: z24.string().uuid().describe("Request id from iamu_requests") },
15973
+ { id: z25.string().uuid().describe("Request id from iamu_requests") },
15749
15974
  async ({ id }) => {
15750
15975
  try {
15751
15976
  const linked = await resolveLinkedCloudClient();
@@ -16405,11 +16630,14 @@ var init_serve = __esm({
16405
16630
  // src/commands/init.ts
16406
16631
  var init_exports = {};
16407
16632
  __export(init_exports, {
16633
+ createDraftTestabilityContent: () => createDraftTestabilityContent,
16634
+ detectTargetPort: () => detectTargetPort,
16635
+ detectTargetUrl: () => detectTargetUrl,
16408
16636
  init: () => init,
16409
16637
  setupCommand: () => setupCommand
16410
16638
  });
16411
16639
  import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
16412
- import { mkdir as mkdir4, writeFile as writeFile3 } from "fs/promises";
16640
+ import { mkdir as mkdir4, readdir, writeFile as writeFile3 } from "fs/promises";
16413
16641
  import { basename as basename2, join as join5 } from "path";
16414
16642
  import { isCancel as isCancel3, multiselect, select as select3 } from "@clack/prompts";
16415
16643
  import chalk18 from "chalk";
@@ -16437,6 +16665,81 @@ function setupCommand(projectRoot, hosts) {
16437
16665
  if (hosts.every((h) => h.value === "claude")) return "/iamu:init";
16438
16666
  return "/iamu-setup (Claude Code: /iamu:init)";
16439
16667
  }
16668
+ function detectTargetPort(projectRoot) {
16669
+ if (process.env.PORT) {
16670
+ const p = Number.parseInt(process.env.PORT, 10);
16671
+ if (!Number.isNaN(p) && p > 0 && p <= 65535) return p;
16672
+ }
16673
+ for (const envFile of [".env.local", ".env.development", ".env"]) {
16674
+ const fullPath = join5(projectRoot, envFile);
16675
+ if (existsSync7(fullPath)) {
16676
+ try {
16677
+ const content = readFileSync5(fullPath, "utf-8");
16678
+ for (const line of content.split("\n")) {
16679
+ const trimmed = line.trim();
16680
+ if (trimmed.startsWith("#") || !trimmed.includes("=")) continue;
16681
+ const [key, ...rest] = trimmed.split("=");
16682
+ const k = key.trim();
16683
+ const val = rest.join("=").trim().replace(/^["']|["']$/g, "");
16684
+ if (k === "PORT" || k === "VITE_PORT") {
16685
+ const p = Number.parseInt(val, 10);
16686
+ if (!Number.isNaN(p) && p > 0 && p <= 65535) return p;
16687
+ }
16688
+ if (k === "NEXT_PUBLIC_APP_URL" || k === "APP_URL") {
16689
+ try {
16690
+ const url = new URL(val);
16691
+ if (url.port) return Number.parseInt(url.port, 10);
16692
+ } catch {
16693
+ }
16694
+ }
16695
+ }
16696
+ } catch {
16697
+ }
16698
+ }
16699
+ }
16700
+ const pkgPath = join5(projectRoot, "package.json");
16701
+ if (existsSync7(pkgPath)) {
16702
+ try {
16703
+ const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
16704
+ const scripts = pkg.scripts || {};
16705
+ for (const script of Object.values(scripts)) {
16706
+ if (typeof script !== "string") continue;
16707
+ const portMatch = script.match(/(?:-p|--port(?:=|\s+))(\d+)/);
16708
+ if (portMatch) {
16709
+ const p = Number.parseInt(portMatch[1], 10);
16710
+ if (!Number.isNaN(p) && p > 0 && p <= 65535) return p;
16711
+ }
16712
+ }
16713
+ const allDeps = {
16714
+ ...pkg.dependencies || {},
16715
+ ...pkg.devDependencies || {}
16716
+ };
16717
+ if ("astro" in allDeps) return 4321;
16718
+ if ("vite" in allDeps) return 5173;
16719
+ } catch {
16720
+ }
16721
+ }
16722
+ for (const f of ["astro.config.mjs", "astro.config.ts", "astro.config.js"]) {
16723
+ if (existsSync7(join5(projectRoot, f))) return 4321;
16724
+ }
16725
+ for (const f of [
16726
+ "vite.config.ts",
16727
+ "vite.config.js",
16728
+ "vite.config.mjs",
16729
+ "vite.config.cjs"
16730
+ ]) {
16731
+ if (existsSync7(join5(projectRoot, f))) return 5173;
16732
+ }
16733
+ return void 0;
16734
+ }
16735
+ function detectTargetUrl(projectRoot) {
16736
+ const port = detectTargetPort(projectRoot);
16737
+ return port ? `http://localhost:${port}` : "http://localhost:3000";
16738
+ }
16739
+ function createDraftTestabilityContent(targetUrl = "http://localhost:3000") {
16740
+ const doc = emptyTestability(targetUrl);
16741
+ return serializeFrontmatter(doc.frontmatter, doc.body);
16742
+ }
16440
16743
  async function init(opts) {
16441
16744
  const options = typeof opts === "string" ? { name: opts } : opts ?? {};
16442
16745
  const projectRoot = process.cwd();
@@ -16454,12 +16757,86 @@ async function init(opts) {
16454
16757
  console.log();
16455
16758
  }
16456
16759
  let freshInit = false;
16760
+ const backfilled = [];
16457
16761
  if (existsSync7(join5(iamuDir2, "config.json"))) {
16458
- const config2 = await readConfig(projectRoot);
16459
- if (!options.json) {
16460
- console.log(
16461
- chalk18.yellow(" iamu/ already exists") + chalk18.dim(` (${config2?.project_name ?? "unknown"})`)
16762
+ const testabilityPath2 = join5(iamuDir2, "testability.md");
16763
+ if (!existsSync7(testabilityPath2)) {
16764
+ const targetUrl = detectTargetUrl(projectRoot);
16765
+ await writeFile3(
16766
+ testabilityPath2,
16767
+ createDraftTestabilityContent(targetUrl)
16768
+ );
16769
+ backfilled.push("testability.md");
16770
+ }
16771
+ const contextPath2 = join5(iamuDir2, "context.yml");
16772
+ if (!existsSync7(contextPath2)) {
16773
+ await writeFile3(
16774
+ contextPath2,
16775
+ serializeFrontmatter(
16776
+ { status: "draft", schema_version: 1, name: projectName },
16777
+ ""
16778
+ )
16462
16779
  );
16780
+ backfilled.push("context.yml");
16781
+ }
16782
+ const gitignorePath = join5(iamuDir2, ".gitignore");
16783
+ if (!existsSync7(gitignorePath)) {
16784
+ await writeFile3(gitignorePath, GITIGNORE_CONTENT);
16785
+ backfilled.push(".gitignore");
16786
+ }
16787
+ const cacheDirPath = join5(iamuDir2, ".cache");
16788
+ if (!existsSync7(cacheDirPath)) {
16789
+ await mkdir4(cacheDirPath, { recursive: true });
16790
+ backfilled.push(".cache/");
16791
+ }
16792
+ const runsDirPath = join5(iamuDir2, ".runs");
16793
+ if (!existsSync7(runsDirPath)) {
16794
+ await mkdir4(runsDirPath, { recursive: true });
16795
+ }
16796
+ for (const type2 of ENTITY_TYPES) {
16797
+ const sub = join5(iamuDir2, ENTITY_DIRS[type2]);
16798
+ if (!existsSync7(sub)) {
16799
+ await mkdir4(sub, { recursive: true });
16800
+ }
16801
+ const keep = join5(sub, ".gitkeep");
16802
+ if (!existsSync7(keep)) {
16803
+ try {
16804
+ const files = await readdir(sub);
16805
+ if (files.length === 0) {
16806
+ await writeFile3(keep, "");
16807
+ }
16808
+ } catch {
16809
+ await writeFile3(keep, "");
16810
+ }
16811
+ }
16812
+ }
16813
+ const promptTemplates2 = loadPromptTemplates();
16814
+ const promptsDir2 = join5(iamuDir2, ".prompts");
16815
+ const missingPrompts = [];
16816
+ if (!existsSync7(promptsDir2)) {
16817
+ missingPrompts.push(".prompts/");
16818
+ } else {
16819
+ for (const [name] of promptTemplates2) {
16820
+ if (!existsSync7(join5(promptsDir2, name))) {
16821
+ missingPrompts.push(name);
16822
+ }
16823
+ }
16824
+ }
16825
+ if (missingPrompts.length > 0) {
16826
+ if (missingPrompts.includes(".prompts/")) {
16827
+ backfilled.push(".prompts/");
16828
+ } else {
16829
+ backfilled.push(...missingPrompts.map((p) => `.prompts/${p}`));
16830
+ }
16831
+ }
16832
+ if (!options.json) {
16833
+ if (backfilled.length > 0) {
16834
+ console.log(
16835
+ ` ${symbols.success} Scaffolding verified (backfilled missing ${backfilled.join(", ")})`
16836
+ );
16837
+ } else {
16838
+ console.log(` ${symbols.success} Scaffolding up to date`);
16839
+ }
16463
16840
  }
16464
16841
  } else {
16465
16842
  freshInit = true;
@@ -16475,33 +16852,12 @@ async function init(opts) {
16475
16852
  ""
16476
16853
  )
16477
16854
  );
16478
- const testabilityPath = join5(iamuDir2, "testability.md");
16479
- if (!existsSync7(testabilityPath)) {
16855
+ const testabilityPath2 = join5(iamuDir2, "testability.md");
16856
+ if (!existsSync7(testabilityPath2)) {
16857
+ const targetUrl = detectTargetUrl(projectRoot);
16480
16858
  await writeFile3(
16481
- testabilityPath,
16482
- serializeFrontmatter(
16483
- {
16484
- status: "draft",
16485
- target_url: "http://localhost:3000"
16486
- },
16487
- `# Project Testability & Environment
16488
-
16489
- Target URL and execution harness for walkthroughs.
16490
- Audited and populated during /iamu:init.
16491
-
16492
- ## Target & Run Instructions
16493
- - Default URL: http://localhost:3000
16494
-
16495
- ## Reachable Surfaces
16496
- (Run /iamu:init to discover testable routes)
16497
-
16498
- ## Mocks & Data Fixtures
16499
- (Run /iamu:init to inspect existing harnesses)
16500
-
16501
- ## Untestable Boundaries
16502
- (None documented yet)
16503
- `
16504
- )
16859
+ testabilityPath2,
16860
+ createDraftTestabilityContent(targetUrl)
16505
16861
  );
16506
16862
  }
16507
16863
  }
@@ -16511,7 +16867,13 @@ Audited and populated during /iamu:init.
16511
16867
  for (const [name, content] of promptTemplates) {
16512
16868
  await writeFile3(join5(promptsDir, name), content);
16513
16869
  }
16514
- const detected = detectHosts(projectRoot);
16870
+ const detectedInstalled = detectInstalledHosts(projectRoot);
16871
+ const detectedWorkspace = detectHosts(projectRoot);
16872
+ const detectedMap = /* @__PURE__ */ new Map();
16873
+ for (const h of [...detectedInstalled, ...detectedWorkspace]) {
16874
+ detectedMap.set(h.value, h);
16875
+ }
16876
+ const detected = Array.from(detectedMap.values());
16515
16877
  if (!options.json && detected.length > 0) {
16516
16878
  console.log(
16517
16879
  chalk18.dim(" Detected: ") + detected.map((h) => h.name).join(", ") + chalk18.dim(" (pre-selected)")
@@ -16620,6 +16982,7 @@ Audited and populated during /iamu:init.
16620
16982
  projectName,
16621
16983
  projectRoot,
16622
16984
  fresh: freshInit,
16985
+ backfilled,
16623
16986
  tools: selectedHosts.map((h) => h.name),
16624
16987
  skillsCount: totalSkills,
16625
16988
  commandsCount: totalCommands,
@@ -16721,6 +17084,7 @@ var init_init = __esm({
16721
17084
  "src/commands/init.ts"() {
16722
17085
  "use strict";
16723
17086
  init_dist2();
17087
+ init_dist();
16724
17088
  init_claude_plugin();
16725
17089
  init_registry();
16726
17090
  init_cloud_url();
@@ -16788,6 +17152,25 @@ async function status(opts) {
16788
17152
  const incompleteRuns = await listIncompleteRuns(projectRoot);
16789
17153
  const { rerun } = await computeFixStates(projectRoot);
16790
17154
  const staleSkills = await findStaleSkills(projectRoot);
17155
+ let exploreSummary = null;
17156
+ try {
17157
+ const maps = await listExploreMaps(projectRoot);
17158
+ if (maps.length > 0) {
17159
+ const latestMapId = maps.at(-1);
17160
+ const map2 = await readExploreMap(projectRoot, latestMapId);
17161
+ if (map2) {
17162
+ const steps = await readAllRunSteps(projectRoot);
17163
+ const cov = exploreCoverage(map2, steps);
17164
+ exploreSummary = {
17165
+ map_id: latestMapId,
17166
+ total_surfaces: cov.surfaces.length,
17167
+ reached_surfaces: cov.reached,
17168
+ unreached_surfaces: cov.unreached
17169
+ };
17170
+ }
17171
+ }
17172
+ } catch {
17173
+ }
16791
17174
  if (opts.json) {
16792
17175
  console.log(
16793
17176
  JSON.stringify(
@@ -16811,6 +17194,7 @@ async function status(opts) {
16811
17194
  last: runs.at(-1) ?? null,
16812
17195
  incomplete: incompleteRuns
16813
17196
  },
17197
+ explore: exploreSummary,
16814
17198
  rerun: rerun.map((r) => ({
16815
17199
  ...r,
16816
17200
  command: rerunCommand(projectRoot, r.scenario)
@@ -16898,6 +17282,11 @@ async function status(opts) {
16898
17282
  )
16899
17283
  );
16900
17284
  }
17285
+ if (exploreSummary) {
17286
+ console.log(
17287
+ ` ${padRight("product map", 14)} ${chalk19.cyan(String(exploreSummary.total_surfaces))} surfaces (${chalk19.green(`${exploreSummary.reached_surfaces} reached`)} \xB7 ${chalk19.yellow(`${exploreSummary.unreached_surfaces} unreached`)})`
17288
+ );
17289
+ }
16901
17290
  if (projectStatus.requests && projectStatus.requests.open > 0) {
16902
17291
  console.log(
16903
17292
  chalk19.cyan(
@@ -17200,7 +17589,7 @@ var init_browse_driver = __esm({
17200
17589
  });
17201
17590
 
17202
17591
  // src/commands/step-numbering.ts
17203
- import { open, readdir, unlink } from "fs/promises";
17592
+ import { open, readdir as readdir2, unlink } from "fs/promises";
17204
17593
  import path22 from "path";
17205
17594
  function stepFileName(stepNumber2) {
17206
17595
  return `${String(stepNumber2).padStart(3, "0")}.json`;
@@ -17209,7 +17598,7 @@ function stepLimitMessage(maxSteps, runId) {
17209
17598
  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>.`;
17210
17599
  }
17211
17600
  async function peekNextStepNumber(stepsDir) {
17212
- const files = await readdir(stepsDir).catch(() => []);
17601
+ const files = await readdir2(stepsDir).catch(() => []);
17213
17602
  let max = 0;
17214
17603
  for (const f of files) {
17215
17604
  const m = /^(\d+)\.json$/.exec(f);
@@ -18278,7 +18667,7 @@ async function checkTargetHealth(targetUrl) {
18278
18667
  const res = await fetch(targetUrl, {
18279
18668
  method: "GET",
18280
18669
  redirect: "manual",
18281
- signal: AbortSignal.timeout(3e3)
18670
+ signal: AbortSignal.timeout(1e3)
18282
18671
  });
18283
18672
  if ([301, 302, 303, 307, 308].includes(res.status)) {
18284
18673
  const location = res.headers.get("location") || "";
@@ -18392,21 +18781,20 @@ async function checkProjectHarnesses(projectRoot) {
18392
18781
  }
18393
18782
  async function checkProjectTestabilityContract(projectRoot) {
18394
18783
  const checks = [];
18395
- const testabilityPath = join7(projectRoot, "iamu", "testability.md");
18396
- if (!existsSync10(testabilityPath)) {
18397
- checks.push({
18398
- category: "target",
18399
- name: "Testability Contract (testability.md)",
18400
- status: "warn",
18401
- message: "iamu/testability.md not found",
18402
- remedy: "Run `/iamu:init` to audit your codebase and record testable routes, auth strategies, and mock harnesses."
18403
- });
18404
- return { checks };
18405
- }
18406
18784
  try {
18407
- const raw = await fs26.readFile(testabilityPath, "utf8");
18408
- const { frontmatter } = parseFrontmatter(raw);
18409
- const targetUrl = typeof frontmatter.target_url === "string" ? frontmatter.target_url : void 0;
18785
+ const doc = await readTestability(projectRoot);
18786
+ if (!doc) {
18787
+ checks.push({
18788
+ category: "target",
18789
+ name: "Testability Contract (testability.md)",
18790
+ status: "warn",
18791
+ message: "iamu/testability.md not found",
18792
+ remedy: "Run `/iamu:init` to audit your codebase and record testable routes, auth strategies, and mock harnesses."
18793
+ });
18794
+ return { checks };
18795
+ }
18796
+ const { frontmatter } = doc;
18797
+ const targetUrl = frontmatter.target_url;
18410
18798
  const details = [];
18411
18799
  if (targetUrl) details.push(`Target: ${targetUrl}`);
18412
18800
  if (frontmatter.dev_command) {
@@ -19403,6 +19791,19 @@ async function update(opts) {
19403
19791
  console.log(` ${symbols.success} ${promptsUpdated} prompts updated`);
19404
19792
  }
19405
19793
  }
19794
+ if (existsSync11(iamuDir2)) {
19795
+ const testabilityPath2 = join8(iamuDir2, "testability.md");
19796
+ if (!existsSync11(testabilityPath2)) {
19797
+ const targetUrl = detectTargetUrl(projectRoot);
19798
+ await writeFile4(
19799
+ testabilityPath2,
19800
+ createDraftTestabilityContent(targetUrl)
19801
+ );
19802
+ console.log(
19803
+ ` ${symbols.success} Repaired testability contract: created iamu/testability.md (draft)`
19804
+ );
19805
+ }
19806
+ }
19406
19807
  const { hosts: targetHosts, error } = resolveTargetHosts(
19407
19808
  projectRoot,
19408
19809
  opts?.host
@@ -19458,6 +19859,7 @@ var init_update = __esm({
19458
19859
  init_mcp_config();
19459
19860
  init_context4();
19460
19861
  init_skills();
19862
+ init_init();
19461
19863
  init_templates();
19462
19864
  }
19463
19865
  });
@@ -19483,11 +19885,13 @@ function defaultFrontmatter(type2, id, slug, name, opts) {
19483
19885
  };
19484
19886
  case "outline": {
19485
19887
  const tests = Array.isArray(opts.humanTest) ? opts.humanTest : opts.humanTest ? [opts.humanTest] : [];
19888
+ const additionalCaps = Array.isArray(opts.additionalCapabilities) ? opts.additionalCapabilities : typeof opts.additionalCapabilities === "string" ? opts.additionalCapabilities.split(",").map((s) => s.trim()).filter(Boolean) : [];
19486
19889
  return {
19487
19890
  ...base,
19488
19891
  title: name,
19489
19892
  ...description,
19490
19893
  ...opts.capability ? { primary_capability: opts.capability } : {},
19894
+ ...additionalCaps.length > 0 ? { additional_capabilities: additionalCaps } : {},
19491
19895
  ...opts.tension ? { tension: opts.tension } : {},
19492
19896
  ...opts.narrative ? { narrative: opts.narrative } : {},
19493
19897
  ...tests.length > 0 ? {
@@ -19498,8 +19902,16 @@ function defaultFrontmatter(type2, id, slug, name, opts) {
19498
19902
  } : {}
19499
19903
  };
19500
19904
  }
19501
- case "capability":
19502
- return { ...base, title: name, ...description };
19905
+ case "capability": {
19906
+ const capPaths = Array.isArray(opts.paths) ? opts.paths : typeof opts.paths === "string" ? opts.paths.split(",").map((s) => s.trim()).filter(Boolean) : [];
19907
+ return {
19908
+ ...base,
19909
+ title: name,
19910
+ ...description,
19911
+ ...capPaths.length > 0 ? { paths: capPaths } : {},
19912
+ ...opts.area ? { area: opts.area.trim() } : {}
19913
+ };
19914
+ }
19503
19915
  case "source":
19504
19916
  return {
19505
19917
  ...base,
@@ -20094,7 +20506,6 @@ __export(explore_exports, {
20094
20506
  exploreList: () => exploreList,
20095
20507
  exploreRun: () => exploreRun
20096
20508
  });
20097
- import { promises as fs28 } from "fs";
20098
20509
  import path26 from "path";
20099
20510
  import chalk27 from "chalk";
20100
20511
  function fail2(message, json2) {
@@ -20112,12 +20523,8 @@ function positiveInt(raw, fallback, flag) {
20112
20523
  }
20113
20524
  async function testabilityTarget2(projectRoot) {
20114
20525
  try {
20115
- const raw = await fs28.readFile(
20116
- path26.join(projectRoot, "iamu", "testability.md"),
20117
- "utf8"
20118
- );
20119
- const { frontmatter } = parseFrontmatter(raw);
20120
- return typeof frontmatter.target_url === "string" ? frontmatter.target_url : null;
20526
+ const doc = await readTestability(projectRoot);
20527
+ return doc?.frontmatter.target_url ?? null;
20121
20528
  } catch {
20122
20529
  return null;
20123
20530
  }
@@ -20299,8 +20706,17 @@ async function exploreCoverageCommand(mapId, opts) {
20299
20706
  return;
20300
20707
  }
20301
20708
  const cov = exploreCoverage(map2, await readAllRunSteps(ctx.projectRoot));
20709
+ const capReport = await readCapabilityCoverage(ctx.projectRoot, cov);
20302
20710
  if (opts.json) {
20303
- console.log(JSON.stringify(cov));
20711
+ console.log(
20712
+ JSON.stringify({
20713
+ ...cov,
20714
+ ...capReport.capabilities.length > 0 ? {
20715
+ capabilities: capReport.capabilities,
20716
+ unassigned_surfaces: capReport.unassigned_surfaces
20717
+ } : {}
20718
+ })
20719
+ );
20304
20720
  return;
20305
20721
  }
20306
20722
  console.log(
@@ -20310,26 +20726,122 @@ async function exploreCoverageCommand(mapId, opts) {
20310
20726
  console.log(
20311
20727
  ` ${chalk27.green(`${cov.reached} reached`)} \xB7 ${chalk27.yellow(`${cov.place_only} URL reached, view unclear`)} \xB7 ${chalk27.red(`${cov.unreached} never reached`)}`
20312
20728
  );
20313
- const section = (title, rows, color) => {
20314
- if (rows.length === 0) return;
20315
- console.log(`
20316
- ${color(title)}`);
20317
- for (const r of rows) {
20729
+ if (capReport.capabilities.length > 0) {
20730
+ console.log(chalk27.bold("\n CAPABILITY COVERAGE"));
20731
+ console.log(
20732
+ chalk27.dim(
20733
+ " \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
20734
+ )
20735
+ );
20736
+ console.log(
20737
+ chalk27.dim(
20738
+ ` ${"Capability".padEnd(24)} ${"Surfaces".padStart(8)} ${"Coverage".padStart(8)} ${"Scenarios (1\xB0 / 2\xB0)"}`
20739
+ )
20740
+ );
20741
+ console.log(
20742
+ chalk27.dim(
20743
+ " \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
20744
+ )
20745
+ );
20746
+ for (const cap of capReport.capabilities) {
20747
+ const title = cap.slug.padEnd(24);
20748
+ const surfaces = `${cap.reached_surfaces}/${cap.total_surfaces}`.padStart(
20749
+ 8
20750
+ );
20751
+ const covStr = `${cap.coverage_pct}%`.padStart(8);
20752
+ const scenarios = `${cap.primary_scenarios} / ${cap.secondary_scenarios}`;
20753
+ const untestedFlag = cap.primary_scenarios === 0 && cap.unreached_surfaces > 0 ? chalk27.yellow(" (untested)") : "";
20754
+ const covColored = cap.coverage_pct === 100 ? chalk27.green(covStr) : cap.coverage_pct === 0 ? chalk27.red(covStr) : chalk27.yellow(covStr);
20318
20755
  console.log(
20319
- ` ${r.key.padEnd(32)} ${r.label}${chalk27.dim(` (depth ${r.depth})`)}`
20756
+ ` ${title} ${surfaces} ${covColored} ${scenarios}${untestedFlag}`
20320
20757
  );
20321
20758
  }
20322
- };
20323
- section(
20324
- "Never reached by a run",
20325
- cov.surfaces.filter((s) => s.status === "unreached"),
20326
- chalk27.red
20327
- );
20328
- section(
20329
- "URL reached, but no step names this view",
20330
- cov.surfaces.filter((s) => s.status === "place_only"),
20331
- chalk27.yellow
20332
- );
20759
+ if (capReport.unassigned_surfaces.length > 0) {
20760
+ const unassigned = capReport.unassigned_surfaces;
20761
+ const reached = unassigned.filter((s) => s.status === "reached").length;
20762
+ const pct = Math.round(reached / unassigned.length * 100);
20763
+ console.log(
20764
+ chalk27.dim(
20765
+ ` ${"(Unassigned)".padEnd(24)} ${`${reached}/${unassigned.length}`.padStart(8)} ${`${pct}%`.padStart(8)} -`
20766
+ )
20767
+ );
20768
+ }
20769
+ console.log(
20770
+ chalk27.dim(
20771
+ " \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
20772
+ )
20773
+ );
20774
+ const hasGaps = capReport.capabilities.some((c) => c.unreached_surfaces > 0) || capReport.unassigned_surfaces.some((s) => s.status !== "reached");
20775
+ if (hasGaps) {
20776
+ console.log(chalk27.red("\n UNREACHED SURFACES (Gaps)"));
20777
+ for (const cap of capReport.capabilities) {
20778
+ const gaps = cap.surfaces.filter((s) => s.status !== "reached");
20779
+ if (gaps.length === 0) continue;
20780
+ console.log(` ${chalk27.bold(cap.slug)}:`);
20781
+ for (const s of gaps) {
20782
+ console.log(
20783
+ ` ${chalk27.red("\u25CB")} ${s.key.padEnd(32)} ${s.label}${chalk27.dim(` (depth ${s.depth})`)}`
20784
+ );
20785
+ }
20786
+ }
20787
+ const unassignedGaps = capReport.unassigned_surfaces.filter(
20788
+ (s) => s.status !== "reached"
20789
+ );
20790
+ if (unassignedGaps.length > 0) {
20791
+ console.log(chalk27.dim(" (Unassigned):"));
20792
+ for (const s of unassignedGaps) {
20793
+ console.log(
20794
+ ` ${chalk27.dim("\u25CB")} ${s.key.padEnd(32)} ${s.label}${chalk27.dim(` (depth ${s.depth})`)}`
20795
+ );
20796
+ }
20797
+ }
20798
+ }
20799
+ if (opts.all) {
20800
+ console.log(chalk27.green("\n REACHED SURFACES"));
20801
+ for (const cap of capReport.capabilities) {
20802
+ const reached = cap.surfaces.filter((s) => s.status === "reached");
20803
+ if (reached.length === 0) continue;
20804
+ console.log(` ${chalk27.bold(cap.slug)}:`);
20805
+ for (const s of reached) {
20806
+ console.log(
20807
+ ` ${chalk27.green("\u2713")} ${s.key.padEnd(32)} ${s.label}${chalk27.dim(` (depth ${s.depth})`)}`
20808
+ );
20809
+ }
20810
+ }
20811
+ const unassignedReached = capReport.unassigned_surfaces.filter(
20812
+ (s) => s.status === "reached"
20813
+ );
20814
+ if (unassignedReached.length > 0) {
20815
+ console.log(chalk27.dim(" (Unassigned):"));
20816
+ for (const s of unassignedReached) {
20817
+ console.log(
20818
+ ` ${chalk27.green("\u2713")} ${s.key.padEnd(32)} ${s.label}${chalk27.dim(` (depth ${s.depth})`)}`
20819
+ );
20820
+ }
20821
+ }
20822
+ }
20823
+ } else {
20824
+ const section = (title, rows, color) => {
20825
+ if (rows.length === 0) return;
20826
+ console.log(`
20827
+ ${color(title)}`);
20828
+ for (const r of rows) {
20829
+ console.log(
20830
+ ` ${r.key.padEnd(32)} ${r.label}${chalk27.dim(` (depth ${r.depth})`)}`
20831
+ );
20832
+ }
20833
+ };
20834
+ section(
20835
+ "Never reached by a run",
20836
+ cov.surfaces.filter((s) => s.status === "unreached"),
20837
+ chalk27.red
20838
+ );
20839
+ section(
20840
+ "URL reached, but no step names this view",
20841
+ cov.surfaces.filter((s) => s.status === "place_only"),
20842
+ chalk27.yellow
20843
+ );
20844
+ }
20333
20845
  if (cov.run_only_places.length > 0) {
20334
20846
  console.log(`
20335
20847
  ${chalk27.cyan("Walked by runs, not in the map")}`);
@@ -20889,17 +21401,13 @@ async function instructionsWalk(opts) {
20889
21401
  }
20890
21402
  const untestableBoundaries = [];
20891
21403
  try {
20892
- const testabilityPath = join9(projectRoot, "iamu", "testability.md");
20893
- if (existsSync12(testabilityPath)) {
20894
- const raw = readFileSync8(testabilityPath, "utf-8");
20895
- const { frontmatter, body } = parseFrontmatter(raw);
20896
- if (!targetUrl && typeof frontmatter.target_url === "string" && frontmatter.target_url.trim().length > 0) {
20897
- targetUrl = frontmatter.target_url.trim();
21404
+ const doc = await readTestability(projectRoot);
21405
+ if (doc) {
21406
+ if (!targetUrl && doc.frontmatter.target_url) {
21407
+ targetUrl = doc.frontmatter.target_url;
20898
21408
  }
20899
- const fromFm = Array.isArray(
20900
- frontmatter.untestable_boundaries
20901
- ) ? frontmatter.untestable_boundaries.filter(Boolean) : [];
20902
- const fromBody = extractUntestableBoundaries(body);
21409
+ const fromFm = doc.frontmatter.untestable_boundaries ?? [];
21410
+ const fromBody = extractUntestableBoundaries(doc.body);
20903
21411
  untestableBoundaries.push(
20904
21412
  ...Array.from(/* @__PURE__ */ new Set([...fromFm, ...fromBody]))
20905
21413
  );
@@ -21256,7 +21764,7 @@ var summary_exports = {};
21256
21764
  __export(summary_exports, {
21257
21765
  summaryCommand: () => summaryCommand
21258
21766
  });
21259
- import { promises as fs29 } from "fs";
21767
+ import { promises as fs28 } from "fs";
21260
21768
  import path28 from "path";
21261
21769
  import chalk30 from "chalk";
21262
21770
  async function summaryCommand(runIdArg, opts) {
@@ -21283,7 +21791,7 @@ async function summaryCommand(runIdArg, opts) {
21283
21791
  }
21284
21792
  raw = Buffer.concat(chunks).toString("utf8");
21285
21793
  } else {
21286
- raw = await fs29.readFile(path28.resolve(projectRoot, opts.file), "utf8");
21794
+ raw = await fs28.readFile(path28.resolve(projectRoot, opts.file), "utf8");
21287
21795
  }
21288
21796
  const parsed = JSON.parse(raw);
21289
21797
  if (Array.isArray(parsed.what_worked)) {
@@ -21451,10 +21959,10 @@ __export(evaluate_exports, {
21451
21959
  evaluate: () => evaluate,
21452
21960
  evaluateRecord: () => evaluateRecord
21453
21961
  });
21454
- import { promises as fs30 } from "fs";
21962
+ import { promises as fs29 } from "fs";
21455
21963
  import path29 from "path";
21456
21964
  import chalk32 from "chalk";
21457
- import { z as z25 } from "zod";
21965
+ import { z as z26 } from "zod";
21458
21966
  function formatIssues(error) {
21459
21967
  return error.issues.map(
21460
21968
  (i) => `${i.path.length > 0 ? i.path.join(".") : "(root)"}: ${i.message}`
@@ -21486,7 +21994,7 @@ async function evaluate(runId, opts) {
21486
21994
  const canonicalPath = path29.join(runDirPath, "canonical.json");
21487
21995
  let canonical;
21488
21996
  try {
21489
- const raw = await fs30.readFile(canonicalPath, "utf8");
21997
+ const raw = await fs29.readFile(canonicalPath, "utf8");
21490
21998
  canonical = JSON.parse(raw);
21491
21999
  } catch {
21492
22000
  commandError(
@@ -21543,7 +22051,7 @@ async function evaluate(runId, opts) {
21543
22051
  };
21544
22052
  const prompt = composeJudgePrompt(input);
21545
22053
  const promptFilePath = opts?.promptFile ?? path29.join(runDirPath, "judge_prompt.md");
21546
- await fs30.writeFile(promptFilePath, prompt, "utf8");
22054
+ await fs29.writeFile(promptFilePath, prompt, "utf8");
21547
22055
  if (opts?.json) {
21548
22056
  console.log(
21549
22057
  JSON.stringify(
@@ -21570,7 +22078,7 @@ async function evaluateRecord(runId, opts) {
21570
22078
  const canonicalPath = path29.join(runDirPath, "canonical.json");
21571
22079
  let canonical;
21572
22080
  try {
21573
- const raw = await fs30.readFile(canonicalPath, "utf8");
22081
+ const raw = await fs29.readFile(canonicalPath, "utf8");
21574
22082
  canonical = JSON.parse(raw);
21575
22083
  } catch {
21576
22084
  commandError(`No canonical.json for run: ${runId}`, opts);
@@ -21592,7 +22100,7 @@ async function evaluateRecord(runId, opts) {
21592
22100
  }
21593
22101
  raw = Buffer.concat(chunks).toString("utf8");
21594
22102
  } else {
21595
- raw = await fs30.readFile(opts.file, "utf8");
22103
+ raw = await fs29.readFile(opts.file, "utf8");
21596
22104
  }
21597
22105
  parsedFile = JSON.parse(raw);
21598
22106
  } catch (err) {
@@ -21715,28 +22223,28 @@ var init_evaluate = __esm({
21715
22223
  init_context4();
21716
22224
  init_live_run_client();
21717
22225
  init_view_results();
21718
- verdictValueSchema = z25.enum(["yes", "partial", "no", "not_applicable"]);
21719
- idList = z25.array(z25.string()).optional();
21720
- verdictEntrySchema = z25.object({
21721
- human_test_id: z25.string().min(1).optional(),
21722
- id: z25.string().min(1).optional(),
22226
+ verdictValueSchema = z26.enum(["yes", "partial", "no", "not_applicable"]);
22227
+ idList = z26.array(z26.string()).optional();
22228
+ verdictEntrySchema = z26.object({
22229
+ human_test_id: z26.string().min(1).optional(),
22230
+ id: z26.string().min(1).optional(),
21723
22231
  verdict: verdictValueSchema,
21724
- reasoning: z25.string(),
22232
+ reasoning: z26.string(),
21725
22233
  grounding_step_ids: idList,
21726
22234
  grounding_finding_ids: idList
21727
22235
  }).refine((v) => Boolean(v.human_test_id ?? v.id), {
21728
22236
  message: "must carry human_test_id (or id)",
21729
22237
  path: ["human_test_id"]
21730
22238
  });
21731
- verdictsFileSchema = z25.object({
21732
- verdicts: z25.array(verdictEntrySchema).optional(),
21733
- human_tests: z25.array(verdictEntrySchema).optional(),
21734
- overall: z25.object({ answer: z25.string(), reasoning: z25.string() }).passthrough().optional(),
21735
- provenance: z25.object({
21736
- judge_mode: z25.string().optional(),
21737
- prompt_id: z25.string().optional(),
21738
- prompt_version: z25.string().optional(),
21739
- model: z25.string().optional()
22239
+ verdictsFileSchema = z26.object({
22240
+ verdicts: z26.array(verdictEntrySchema).optional(),
22241
+ human_tests: z26.array(verdictEntrySchema).optional(),
22242
+ overall: z26.object({ answer: z26.string(), reasoning: z26.string() }).passthrough().optional(),
22243
+ provenance: z26.object({
22244
+ judge_mode: z26.string().optional(),
22245
+ prompt_id: z26.string().optional(),
22246
+ prompt_version: z26.string().optional(),
22247
+ model: z26.string().optional()
21740
22248
  }).passthrough().optional()
21741
22249
  });
21742
22250
  }
@@ -21764,7 +22272,7 @@ __export(complete_exports, {
21764
22272
  screenshotSizeWarning: () => screenshotSizeWarning,
21765
22273
  validateCanonicalWalkthrough: () => validateCanonicalWalkthrough
21766
22274
  });
21767
- import { promises as fs31 } from "fs";
22275
+ import { promises as fs30 } from "fs";
21768
22276
  import path30 from "path";
21769
22277
  import chalk33 from "chalk";
21770
22278
  async function complete(runId, opts) {
@@ -21791,7 +22299,7 @@ async function complete(runId, opts) {
21791
22299
  const walkerSummary = await readWalkerSummary(run.runDirPath);
21792
22300
  let judgeReported;
21793
22301
  try {
21794
- const verdictsRaw = await fs31.readFile(
22302
+ const verdictsRaw = await fs30.readFile(
21795
22303
  path30.join(run.runDirPath, "verdicts.json"),
21796
22304
  "utf8"
21797
22305
  );
@@ -21844,7 +22352,7 @@ async function loadRun(runId, opts) {
21844
22352
  if (!resolved) return null;
21845
22353
  const { projectRoot, runDirPath } = resolved;
21846
22354
  try {
21847
- await fs31.access(runDirPath);
22355
+ await fs30.access(runDirPath);
21848
22356
  } catch {
21849
22357
  commandError(`Run not found: ${runId}`, opts);
21850
22358
  return null;
@@ -21853,7 +22361,7 @@ async function loadRun(runId, opts) {
21853
22361
  let manifest;
21854
22362
  try {
21855
22363
  manifest = JSON.parse(
21856
- await fs31.readFile(manifestPath, "utf8")
22364
+ await fs30.readFile(manifestPath, "utf8")
21857
22365
  );
21858
22366
  } catch {
21859
22367
  commandError(`No walkthrough.json found for run: ${runId}`, opts);
@@ -21865,7 +22373,7 @@ async function reportAlreadyCompleted(run, opts) {
21865
22373
  const { runId, manifest } = run;
21866
22374
  if (opts?.json) {
21867
22375
  try {
21868
- const raw = await fs31.readFile(
22376
+ const raw = await fs30.readFile(
21869
22377
  path30.join(run.runDirPath, "canonical.json"),
21870
22378
  "utf8"
21871
22379
  );
@@ -22055,14 +22563,14 @@ async function screenshotSizeWarning(runDirPath, viewport) {
22055
22563
  const dir = path30.join(runDirPath, "screenshots");
22056
22564
  let first;
22057
22565
  try {
22058
- first = (await fs31.readdir(dir)).filter((f) => f.toLowerCase().endsWith(".png")).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }))[0];
22566
+ first = (await fs30.readdir(dir)).filter((f) => f.toLowerCase().endsWith(".png")).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }))[0];
22059
22567
  } catch {
22060
22568
  return null;
22061
22569
  }
22062
22570
  if (!first) return null;
22063
22571
  let size = null;
22064
22572
  try {
22065
- const handle = await fs31.open(path30.join(dir, first), "r");
22573
+ const handle = await fs30.open(path30.join(dir, first), "r");
22066
22574
  try {
22067
22575
  const head = Buffer.alloc(24);
22068
22576
  const { bytesRead } = await handle.read(head, 0, 24, 0);
@@ -22079,7 +22587,7 @@ async function screenshotSizeWarning(runDirPath, viewport) {
22079
22587
  async function readWalkerSummary(runDirPath) {
22080
22588
  try {
22081
22589
  const data = JSON.parse(
22082
- await fs31.readFile(path30.join(runDirPath, "summary.json"), "utf8")
22590
+ await fs30.readFile(path30.join(runDirPath, "summary.json"), "utf8")
22083
22591
  );
22084
22592
  const strings = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && x.trim() !== "") : void 0;
22085
22593
  const model = typeof data.model === "string" && data.model.trim() !== "" ? data.model.trim() : void 0;
@@ -22188,7 +22696,7 @@ function validateCanonicalWalkthrough(canonical) {
22188
22696
  async function applyRecordedVerdicts(canonical, runDirPath) {
22189
22697
  try {
22190
22698
  const recorded = JSON.parse(
22191
- await fs31.readFile(path30.join(runDirPath, "verdicts.json"), "utf8")
22699
+ await fs30.readFile(path30.join(runDirPath, "verdicts.json"), "utf8")
22192
22700
  );
22193
22701
  if (Array.isArray(recorded.verdicts)) {
22194
22702
  return applyVerdicts(canonical, recorded);
@@ -22376,14 +22884,14 @@ __export(match_exports, {
22376
22884
  match: () => match,
22377
22885
  matchRecord: () => matchRecord
22378
22886
  });
22379
- import { promises as fs32 } from "fs";
22887
+ import { promises as fs31 } from "fs";
22380
22888
  import path31 from "path";
22381
22889
  import chalk34 from "chalk";
22382
22890
  async function completedManifest2(runDirPath, runId, opts) {
22383
22891
  let manifest;
22384
22892
  try {
22385
22893
  manifest = JSON.parse(
22386
- await fs32.readFile(path31.join(runDirPath, "walkthrough.json"), "utf8")
22894
+ await fs31.readFile(path31.join(runDirPath, "walkthrough.json"), "utf8")
22387
22895
  );
22388
22896
  } catch {
22389
22897
  commandError(`Run not found: ${runId}`, opts);
@@ -22414,10 +22922,10 @@ async function match(runId, opts) {
22414
22922
  const prompt = needed ? composeMatchPrompt(input) : null;
22415
22923
  const promptFilePath = opts?.promptFile ?? path31.join(runDirPath, "match_prompt.md");
22416
22924
  if (prompt) {
22417
- await fs32.writeFile(promptFilePath, prompt, "utf8");
22925
+ await fs31.writeFile(promptFilePath, prompt, "utf8");
22418
22926
  } else {
22419
22927
  try {
22420
- await fs32.unlink(promptFilePath);
22928
+ await fs31.unlink(promptFilePath);
22421
22929
  } catch {
22422
22930
  }
22423
22931
  }
@@ -22471,7 +22979,7 @@ async function matchRecord(runId, opts) {
22471
22979
  }
22472
22980
  raw = Buffer.concat(chunks).toString("utf8");
22473
22981
  } else {
22474
- raw = await fs32.readFile(opts.file, "utf8");
22982
+ raw = await fs31.readFile(opts.file, "utf8");
22475
22983
  }
22476
22984
  answer = JSON.parse(raw);
22477
22985
  } catch (err) {
@@ -22851,6 +23359,12 @@ function registerProjectCommands(program) {
22851
23359
  "--description <text>",
22852
23360
  "One-line description (capability, outline)"
22853
23361
  ).option("--capability <slug>", "Primary capability slug (outline only)").option(
23362
+ "--additional-capabilities <slugs>",
23363
+ "Secondary capability slugs, comma-separated (outline only)"
23364
+ ).option(
23365
+ "--paths <paths>",
23366
+ "Route patterns owned by this capability, comma-separated (capability only)"
23367
+ ).option("--area <name>", "Visual grouping category (capability only)").option(
22854
23368
  "--tension <text>",
22855
23369
  "Structural tension driving this situation (outline only)"
22856
23370
  ).option(
@@ -22912,10 +23426,12 @@ Examples:
22912
23426
  );
22913
23427
  explore.command("coverage [map]").description(
22914
23428
  "Surfaces of a map (default: latest) that no run reached, and places runs reached that the map lacks"
22915
- ).option("--json", "Output as JSON").action(async (map2, opts) => {
22916
- const { exploreCoverageCommand: exploreCoverageCommand2 } = await Promise.resolve().then(() => (init_explore(), explore_exports));
22917
- await exploreCoverageCommand2(map2, opts);
22918
- });
23429
+ ).option("--json", "Output as JSON").option("--all", "List all surfaces, including reached ones").action(
23430
+ async (map2, opts) => {
23431
+ const { exploreCoverageCommand: exploreCoverageCommand2 } = await Promise.resolve().then(() => (init_explore(), explore_exports));
23432
+ await exploreCoverageCommand2(map2, opts);
23433
+ }
23434
+ );
22919
23435
  explore.command("diff [a] [b]").description("Compare two maps (default: the two latest)").option("--json", "Output as JSON").action(
22920
23436
  async (a, b, opts) => {
22921
23437
  const { exploreDiffCommand: exploreDiffCommand2 } = await Promise.resolve().then(() => (init_explore(), explore_exports));