@iamu-dev/iamu 0.2.0-beta.17 → 0.2.0-beta.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +834 -229
- package/dist/commands/edit-entity.d.ts +8 -0
- package/dist/commands/set-entity.d.ts +46 -0
- package/dist/commands/summary.d.ts +6 -0
- package/dist/commands/validate.d.ts +3 -0
- package/dist/index.js +830 -225
- package/dist/templates/skills/iamu-new.md +19 -28
- package/dist/templates/skills/iamu-propose.md +9 -23
- package/dist/templates/skills/iamu-run.md +15 -4
- package/dist/templates/skills/iamu-setup.md +1 -0
- package/dist/templates/skills/iamu-stage.md +155 -0
- package/dist/templates/skills/iamu-status.md +1 -1
- package/package.json +3 -3
package/dist/bin.js
CHANGED
|
@@ -386,7 +386,7 @@ var init_context = __esm({
|
|
|
386
386
|
jurisdiction: z7.string().max(1024).nullish().describe("Country / region the regulations and policies apply in. Example: 'Sweden', 'EU', 'United States'."),
|
|
387
387
|
architecture: z7.string().max(8192).nullish().describe("High-level architecture notes for the software. Brief is fine \u2014 this is orientation, not spec."),
|
|
388
388
|
notes: z7.string().max(16384).nullish().describe("Additional orientation context that doesn't fit the structured fields. Extended multi-paragraph notes belong in the file body BELOW the frontmatter, not here."),
|
|
389
|
-
access: z7.string().max(4096).nullish().describe(
|
|
389
|
+
access: z7.string().max(4096).nullish().describe("Deprecated: belongs in `iamu/testability.md` `access` \u2014 how to switch user is test setup, not domain. Still read as a fallback when testability.md has none."),
|
|
390
390
|
governed_by: z7.array(contextGovernedBySchema).max(64).optional().describe("Regulations / standards / policies the domain is governed by, ordered roughly by authority."),
|
|
391
391
|
actors: z7.array(contextActorSchema).max(64).optional().describe("Key domain roles + their responsibilities."),
|
|
392
392
|
glossary: z7.array(contextGlossaryEntrySchema).max(512).optional().describe("Domain-specific terms the project uses. Flattened into one file locally; the cloud handler splits into the terminology table on push.")
|
|
@@ -927,6 +927,7 @@ var init_testability = __esm({
|
|
|
927
927
|
target_url: z17.string().url().describe("Target URL where the product is hosted or served locally."),
|
|
928
928
|
dev_command: z17.string().max(1e3).optional().describe("Dev script or startup command to boot the app locally."),
|
|
929
929
|
auth_strategy: authStrategyEnum.or(z17.string().min(1).max(100)).optional().describe("How personas authenticate in test environments."),
|
|
930
|
+
access: z17.string().max(4096).optional().describe(`How a walker becomes a given user in this product: the switching mechanism, written once for the project. Example: 'No login in dev. In the page, run localStorage.setItem("dev_persona", "<persona access>") and reload.' Each persona's own \`access\` names which user they are. Never put passwords or tokens here \u2014 name where they live instead.`),
|
|
930
931
|
harnesses: z17.array(z17.string().min(1).max(500)).max(64).optional().describe("Mocking frameworks, seed scripts, or test harnesses present."),
|
|
931
932
|
untestable_boundaries: z17.array(z17.string().min(1).max(1024)).max(128).optional().describe("External services, third-party APIs, or surfaces forbidden to autonomous walkers.")
|
|
932
933
|
});
|
|
@@ -1198,6 +1199,9 @@ var init_walkthrough_bundle = __esm({
|
|
|
1198
1199
|
what_worked: z20.array(z20.string().max(4096)).max(64).optional(),
|
|
1199
1200
|
open_questions: z20.array(z20.string().max(4096)).max(64).optional(),
|
|
1200
1201
|
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."),
|
|
1202
|
+
blocked: z20.object({
|
|
1203
|
+
reason: z20.string().min(1).max(2048)
|
|
1204
|
+
}).optional().describe("Set when the walk could not start as set up (missing preconditions)."),
|
|
1201
1205
|
build: buildMetadataSchema.optional().describe("Build of the product under test (git SHA, branch, CI run). Absent for runs started before `start-run` recorded it."),
|
|
1202
1206
|
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`)."),
|
|
1203
1207
|
/** Holistic UX quality score from UX review pass. Present only when `lenses` includes "ux". */
|
|
@@ -1471,7 +1475,12 @@ var init_walkthrough_canonical = __esm({
|
|
|
1471
1475
|
human_tests: z21.array(canonicalHumanTestSchema),
|
|
1472
1476
|
human_test_overall: canonicalHumanTestOverallSchema,
|
|
1473
1477
|
errors: z21.array(canonicalErrorSchema),
|
|
1474
|
-
cloud_mirror_state: cloudMirrorStateSchema
|
|
1478
|
+
cloud_mirror_state: cloudMirrorStateSchema,
|
|
1479
|
+
/**
|
|
1480
|
+
* The walk could not start as set up (the walker's `iamu summary
|
|
1481
|
+
* --blocked`). Its findings say nothing about earlier ones.
|
|
1482
|
+
*/
|
|
1483
|
+
blocked: z21.object({ reason: z21.string() }).passthrough().optional()
|
|
1475
1484
|
}).passthrough();
|
|
1476
1485
|
}
|
|
1477
1486
|
});
|
|
@@ -6571,6 +6580,8 @@ async function readRunEvidence(projectRoot) {
|
|
|
6571
6580
|
continue;
|
|
6572
6581
|
if (manifest.status !== "completed")
|
|
6573
6582
|
continue;
|
|
6583
|
+
if (manifest.blocked)
|
|
6584
|
+
continue;
|
|
6574
6585
|
const canonical = await readJson(path8.join(dir, "canonical.json"));
|
|
6575
6586
|
const matches = await readMatches(projectRoot, runId);
|
|
6576
6587
|
const declared = typeof manifest.started_at === "string" ? manifest.started_at : typeof manifest.created_at === "string" ? manifest.created_at : null;
|
|
@@ -7386,7 +7397,8 @@ async function buildCanonical(projectRoot, manifest, completedAt, inputs) {
|
|
|
7386
7397
|
// renderers + judge agents can surface them without losing signal.
|
|
7387
7398
|
// Consumers MUST preserve unknown keys (per design D3).
|
|
7388
7399
|
...inputs.what_worked && inputs.what_worked.length > 0 ? { what_worked: inputs.what_worked } : {},
|
|
7389
|
-
...inputs.open_questions && inputs.open_questions.length > 0 ? { open_questions: inputs.open_questions } : {}
|
|
7400
|
+
...inputs.open_questions && inputs.open_questions.length > 0 ? { open_questions: inputs.open_questions } : {},
|
|
7401
|
+
...manifest.blocked ? { blocked: manifest.blocked } : {}
|
|
7390
7402
|
};
|
|
7391
7403
|
}
|
|
7392
7404
|
async function compareWithBaseline(projectRoot, scenarioSlug, currentRunId, currentFindings) {
|
|
@@ -7401,7 +7413,7 @@ async function compareWithBaseline(projectRoot, scenarioSlug, currentRunId, curr
|
|
|
7401
7413
|
const mPath = path9.join(runDir(projectRoot, rid), "walkthrough.json");
|
|
7402
7414
|
const raw = await fs12.readFile(mPath, "utf8");
|
|
7403
7415
|
const m = JSON.parse(raw);
|
|
7404
|
-
if (m.scenario_slug === scenarioSlug && m.status === "completed") {
|
|
7416
|
+
if (m.scenario_slug === scenarioSlug && m.status === "completed" && !m.blocked) {
|
|
7405
7417
|
baselineRunId = rid;
|
|
7406
7418
|
baselineManifest = m;
|
|
7407
7419
|
break;
|
|
@@ -7510,6 +7522,13 @@ function renderCanonicalMarkdown(canonical) {
|
|
|
7510
7522
|
"",
|
|
7511
7523
|
`# Walkthrough report: ${canonical.scenario.slug}`,
|
|
7512
7524
|
"",
|
|
7525
|
+
...canonical.blocked ? [
|
|
7526
|
+
"> **Blocked \u2014 the walk could not start as it was set up.**",
|
|
7527
|
+
`> ${canonical.blocked.reason}`,
|
|
7528
|
+
">",
|
|
7529
|
+
"> This run is not compared with earlier runs and proves nothing about earlier findings. Fix the setup (the scenario's starting state or the persona's access) and run it again.",
|
|
7530
|
+
""
|
|
7531
|
+
] : [],
|
|
7513
7532
|
`- Run ID: ${canonical.run_id}`,
|
|
7514
7533
|
`- Target URL: ${canonical.target.url}`
|
|
7515
7534
|
];
|
|
@@ -7619,7 +7638,11 @@ function renderCanonicalMarkdown(canonical) {
|
|
|
7619
7638
|
for (const ht of canonical.human_tests) {
|
|
7620
7639
|
lines.push("", `- **${ht.question}**`);
|
|
7621
7640
|
if (ht.verdict === null) {
|
|
7622
|
-
|
|
7641
|
+
if (canonical.blocked) {
|
|
7642
|
+
lines.push(` - Verdict: _skipped (walk blocked)_`);
|
|
7643
|
+
} else {
|
|
7644
|
+
lines.push(` - Verdict: _pending judge_ (walker does not self-grade)`);
|
|
7645
|
+
}
|
|
7623
7646
|
} else {
|
|
7624
7647
|
lines.push(` - Verdict: **${ht.verdict}**`);
|
|
7625
7648
|
if (ht.reasoning)
|
|
@@ -7629,7 +7652,11 @@ function renderCanonicalMarkdown(canonical) {
|
|
|
7629
7652
|
}
|
|
7630
7653
|
lines.push("", "## Human test overall", "", `> ${canonical.human_test_overall.question}`);
|
|
7631
7654
|
if (canonical.human_test_overall.answer === null) {
|
|
7632
|
-
|
|
7655
|
+
if (canonical.blocked) {
|
|
7656
|
+
lines.push("", "_Skipped (walk blocked)._");
|
|
7657
|
+
} else {
|
|
7658
|
+
lines.push("", "_Pending judge stage._");
|
|
7659
|
+
}
|
|
7633
7660
|
} else {
|
|
7634
7661
|
lines.push("", canonical.human_test_overall.answer);
|
|
7635
7662
|
if (canonical.human_test_overall.reasoning)
|
|
@@ -7800,10 +7827,15 @@ async function readCanonicalExtras(projectRoot, runId) {
|
|
|
7800
7827
|
const data = JSON.parse(await fs13.readFile(path10.join(runDir(projectRoot, runId), "canonical.json"), "utf-8"));
|
|
7801
7828
|
const strings = (v) => Array.isArray(v) && v.every((x) => typeof x === "string") ? v : void 0;
|
|
7802
7829
|
const verification = bundleVerificationSchema.safeParse(data.verification);
|
|
7830
|
+
let blocked;
|
|
7831
|
+
if (typeof data.blocked === "object" && data.blocked !== null && "reason" in data.blocked && typeof data.blocked.reason === "string" && data.blocked.reason.trim().length > 0) {
|
|
7832
|
+
blocked = { reason: data.blocked.reason.trim() };
|
|
7833
|
+
}
|
|
7803
7834
|
return {
|
|
7804
7835
|
what_worked: strings(data.what_worked),
|
|
7805
7836
|
open_questions: strings(data.open_questions),
|
|
7806
|
-
verification: verification.success ? verification.data : void 0
|
|
7837
|
+
verification: verification.success ? verification.data : void 0,
|
|
7838
|
+
...blocked ? { blocked } : {}
|
|
7807
7839
|
};
|
|
7808
7840
|
} catch {
|
|
7809
7841
|
return {};
|
|
@@ -7900,6 +7932,10 @@ async function assembleWalkthroughBundle(projectRoot, runId) {
|
|
|
7900
7932
|
}
|
|
7901
7933
|
} : {}
|
|
7902
7934
|
} : void 0;
|
|
7935
|
+
const canonicalExtras = await readCanonicalExtras(projectRoot, runId);
|
|
7936
|
+
const rawBlocked = manifest.blocked;
|
|
7937
|
+
const manifestBlocked = typeof rawBlocked === "object" && rawBlocked !== null && "reason" in rawBlocked && typeof rawBlocked.reason === "string" && rawBlocked.reason.trim().length > 0 ? { reason: rawBlocked.reason.trim() } : void 0;
|
|
7938
|
+
const blocked = canonicalExtras.blocked ?? manifestBlocked;
|
|
7903
7939
|
const bundle = {
|
|
7904
7940
|
manifest: {
|
|
7905
7941
|
run_id: cloudRunUuid,
|
|
@@ -7917,7 +7953,8 @@ async function assembleWalkthroughBundle(projectRoot, runId) {
|
|
|
7917
7953
|
judge_meta: judgeMeta,
|
|
7918
7954
|
...manifest.models ? { models: manifest.models } : {},
|
|
7919
7955
|
...buildFields(manifest),
|
|
7920
|
-
...
|
|
7956
|
+
...canonicalExtras,
|
|
7957
|
+
...blocked ? { blocked } : {}
|
|
7921
7958
|
},
|
|
7922
7959
|
steps: bundleSteps,
|
|
7923
7960
|
findings: bundleFindings,
|
|
@@ -10897,6 +10934,26 @@ function summarizeCapabilityCoverage(surfaces, capabilities, outlines = []) {
|
|
|
10897
10934
|
unassigned_surfaces: unassigned
|
|
10898
10935
|
};
|
|
10899
10936
|
}
|
|
10937
|
+
function groupUnassignedByPrefix(surfaces) {
|
|
10938
|
+
const groups = /* @__PURE__ */ new Map();
|
|
10939
|
+
for (const s of surfaces) {
|
|
10940
|
+
const pathOnly = s.place.split(/[?#]/)[0] ?? "";
|
|
10941
|
+
const first = pathOnly.split("/").filter(Boolean)[0];
|
|
10942
|
+
const prefix = first ? `/${first}` : "/";
|
|
10943
|
+
let g = groups.get(prefix);
|
|
10944
|
+
if (!g) {
|
|
10945
|
+
g = { prefix, surfaces: 0, reached: 0, examples: [] };
|
|
10946
|
+
groups.set(prefix, g);
|
|
10947
|
+
}
|
|
10948
|
+
g.surfaces += 1;
|
|
10949
|
+
if (s.status === "reached")
|
|
10950
|
+
g.reached += 1;
|
|
10951
|
+
if (g.examples.length < 3 && !g.examples.includes(pathOnly)) {
|
|
10952
|
+
g.examples.push(pathOnly);
|
|
10953
|
+
}
|
|
10954
|
+
}
|
|
10955
|
+
return [...groups.values()].sort((a, b) => b.surfaces - a.surfaces || a.prefix.localeCompare(b.prefix));
|
|
10956
|
+
}
|
|
10900
10957
|
async function readCapabilityCoverage(projectRoot, coverage) {
|
|
10901
10958
|
const [capsResult, outlinesResult] = await Promise.all([
|
|
10902
10959
|
listEntities(projectRoot, "capability"),
|
|
@@ -11434,7 +11491,7 @@ function buildPersonaFrontmatter(row, slug) {
|
|
|
11434
11491
|
};
|
|
11435
11492
|
return { frontmatter, body: resolveBody(row, "") };
|
|
11436
11493
|
}
|
|
11437
|
-
function buildScenarioFrontmatter(row, personaSlug, outlineSlug) {
|
|
11494
|
+
function buildScenarioFrontmatter(row, personaSlug, outlineSlug, localFrontmatter) {
|
|
11438
11495
|
const slug = `${personaSlug}--${outlineSlug}`;
|
|
11439
11496
|
const frontmatter = {
|
|
11440
11497
|
id: row.id,
|
|
@@ -11445,6 +11502,8 @@ function buildScenarioFrontmatter(row, personaSlug, outlineSlug) {
|
|
|
11445
11502
|
// writing null wiped the local value on every pull.
|
|
11446
11503
|
..."targetUrl" in row ? { target_url: row.targetUrl ?? null } : {},
|
|
11447
11504
|
..."entryPath" in row ? { entry_path: row.entryPath ?? null } : {},
|
|
11505
|
+
..."reviews" in row && Array.isArray(row.reviews) ? { reviews: row.reviews } : localFrontmatter?.reviews !== void 0 ? { reviews: localFrontmatter.reviews } : {},
|
|
11506
|
+
..."state" in row && row.state !== void 0 ? { state: row.state ?? null } : localFrontmatter?.state !== void 0 ? { state: localFrontmatter.state } : {},
|
|
11448
11507
|
baseline_score: row.baselineScore ?? null,
|
|
11449
11508
|
current_score: row.currentScore ?? null,
|
|
11450
11509
|
target_score: row.targetScore ?? 95,
|
|
@@ -11536,6 +11595,7 @@ async function loadLocalIndex(projectRoot, type2) {
|
|
|
11536
11595
|
const byId = /* @__PURE__ */ new Map();
|
|
11537
11596
|
const bySlug = /* @__PURE__ */ new Map();
|
|
11538
11597
|
const bodyById = /* @__PURE__ */ new Map();
|
|
11598
|
+
const frontmatterById = /* @__PURE__ */ new Map();
|
|
11539
11599
|
const [result, baseById, lastPushById] = await Promise.all([
|
|
11540
11600
|
listEntities(projectRoot, type2),
|
|
11541
11601
|
readBaseHashes(projectRoot),
|
|
@@ -11547,9 +11607,10 @@ async function loadLocalIndex(projectRoot, type2) {
|
|
|
11547
11607
|
byId.set(fm.id, fm.slug);
|
|
11548
11608
|
bySlug.set(fm.slug, fm.id);
|
|
11549
11609
|
bodyById.set(fm.id, entity.body);
|
|
11610
|
+
frontmatterById.set(fm.id, entity.frontmatter);
|
|
11550
11611
|
}
|
|
11551
11612
|
}
|
|
11552
|
-
return { byId, bySlug, bodyById, baseById, lastPushById };
|
|
11613
|
+
return { byId, bySlug, bodyById, frontmatterById, baseById, lastPushById };
|
|
11553
11614
|
}
|
|
11554
11615
|
async function classifyExisting(projectRoot, type2, cloudId, localSlug, targetSlug, cloudVersion, localIndex) {
|
|
11555
11616
|
const base = localIndex.baseById[cloudId];
|
|
@@ -11890,7 +11951,8 @@ async function pullScenarios(projectRoot, cloud, projectId, outlineIdToSlug, per
|
|
|
11890
11951
|
});
|
|
11891
11952
|
continue;
|
|
11892
11953
|
}
|
|
11893
|
-
const
|
|
11954
|
+
const localFm = localIndex.frontmatterById.get(row.id);
|
|
11955
|
+
const { frontmatter, body, slug } = buildScenarioFrontmatter(row, personaSlug, outlineSlug, localFm);
|
|
11894
11956
|
await applyEntityPullOutcome(projectRoot, "scenario", row, slug, frontmatter, body, localIndex, mode, result.pulls.scenario, batch);
|
|
11895
11957
|
}
|
|
11896
11958
|
await finalizePullBatch(projectRoot, batch);
|
|
@@ -12329,15 +12391,15 @@ function resolveApiUrl(configured) {
|
|
|
12329
12391
|
assertSecureApiUrl(url, configured ? "API URL" : "IAMU_API_URL");
|
|
12330
12392
|
return url;
|
|
12331
12393
|
}
|
|
12332
|
-
function parseStore(raw,
|
|
12394
|
+
function parseStore(raw, path34) {
|
|
12333
12395
|
let parsed;
|
|
12334
12396
|
try {
|
|
12335
12397
|
parsed = JSON.parse(raw);
|
|
12336
12398
|
} catch (err) {
|
|
12337
|
-
throw new CredentialsFileError(
|
|
12399
|
+
throw new CredentialsFileError(path34, "not valid JSON", err);
|
|
12338
12400
|
}
|
|
12339
12401
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
12340
|
-
throw new CredentialsFileError(
|
|
12402
|
+
throw new CredentialsFileError(path34, "expected a JSON object");
|
|
12341
12403
|
}
|
|
12342
12404
|
return parsed;
|
|
12343
12405
|
}
|
|
@@ -12345,26 +12407,26 @@ function isNotFound(err) {
|
|
|
12345
12407
|
return typeof err === "object" && err !== null && err.code === "ENOENT";
|
|
12346
12408
|
}
|
|
12347
12409
|
async function readCredentialsStore() {
|
|
12348
|
-
const
|
|
12410
|
+
const path34 = getCredentialsPath();
|
|
12349
12411
|
let raw;
|
|
12350
12412
|
try {
|
|
12351
|
-
raw = await readFile(
|
|
12413
|
+
raw = await readFile(path34, "utf-8");
|
|
12352
12414
|
} catch (err) {
|
|
12353
12415
|
if (isNotFound(err)) return {};
|
|
12354
12416
|
warnUnreadableStoreOnce(err);
|
|
12355
12417
|
throw err;
|
|
12356
12418
|
}
|
|
12357
12419
|
try {
|
|
12358
|
-
return parseStore(raw,
|
|
12420
|
+
return parseStore(raw, path34);
|
|
12359
12421
|
} catch (err) {
|
|
12360
12422
|
warnUnreadableStoreOnce(err);
|
|
12361
12423
|
throw err;
|
|
12362
12424
|
}
|
|
12363
12425
|
}
|
|
12364
12426
|
async function writeCredentialsStore(store) {
|
|
12365
|
-
const
|
|
12366
|
-
await mkdir(dirname(
|
|
12367
|
-
await atomicWriteFile(
|
|
12427
|
+
const path34 = getCredentialsPath();
|
|
12428
|
+
await mkdir(dirname(path34), { recursive: true, mode: 448 });
|
|
12429
|
+
await atomicWriteFile(path34, `${JSON.stringify(store, null, 2)}
|
|
12368
12430
|
`, {
|
|
12369
12431
|
mode: 384
|
|
12370
12432
|
});
|
|
@@ -12384,16 +12446,16 @@ function resolveApiKey(apiUrl) {
|
|
|
12384
12446
|
} catch {
|
|
12385
12447
|
return null;
|
|
12386
12448
|
}
|
|
12387
|
-
const
|
|
12449
|
+
const path34 = getCredentialsPath();
|
|
12388
12450
|
let raw;
|
|
12389
12451
|
try {
|
|
12390
|
-
raw = readFileSync(
|
|
12452
|
+
raw = readFileSync(path34, "utf-8");
|
|
12391
12453
|
} catch (err) {
|
|
12392
12454
|
if (!isNotFound(err)) warnUnreadableStoreOnce(err);
|
|
12393
12455
|
return null;
|
|
12394
12456
|
}
|
|
12395
12457
|
try {
|
|
12396
|
-
const entry = parseStore(raw,
|
|
12458
|
+
const entry = parseStore(raw, path34)[url];
|
|
12397
12459
|
if (typeof entry !== "object" || entry === null) return null;
|
|
12398
12460
|
const token = entry.token;
|
|
12399
12461
|
return typeof token === "string" && token ? token : null;
|
|
@@ -12432,12 +12494,12 @@ var init_credentials = __esm({
|
|
|
12432
12494
|
* @param reason - What is wrong with it.
|
|
12433
12495
|
* @param cause - Underlying parse error, if any.
|
|
12434
12496
|
*/
|
|
12435
|
-
constructor(
|
|
12497
|
+
constructor(path34, reason, cause) {
|
|
12436
12498
|
super(
|
|
12437
|
-
`Credentials file ${
|
|
12499
|
+
`Credentials file ${path34} is corrupt (${reason}). Fix it by hand, or delete it and run \`iamu auth login\` again.`,
|
|
12438
12500
|
{
|
|
12439
12501
|
cause,
|
|
12440
|
-
suggestedAction: `Delete ${
|
|
12502
|
+
suggestedAction: `Delete ${path34} and run \`iamu auth login\`, or set IAMU_TOKEN.`
|
|
12441
12503
|
}
|
|
12442
12504
|
);
|
|
12443
12505
|
}
|
|
@@ -13210,8 +13272,8 @@ import { dirname as dirname3, join as join4, resolve as resolve2 } from "path";
|
|
|
13210
13272
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
13211
13273
|
import chalk6 from "chalk";
|
|
13212
13274
|
function getVersion() {
|
|
13213
|
-
if ("0.2.0-beta.
|
|
13214
|
-
return "0.2.0-beta.
|
|
13275
|
+
if ("0.2.0-beta.19") {
|
|
13276
|
+
return "0.2.0-beta.19";
|
|
13215
13277
|
}
|
|
13216
13278
|
try {
|
|
13217
13279
|
const thisDir = dirname3(fileURLToPath2(import.meta.url));
|
|
@@ -13240,6 +13302,7 @@ function loadSkillTemplates(version2) {
|
|
|
13240
13302
|
"iamu-setup.md",
|
|
13241
13303
|
"iamu-propose.md",
|
|
13242
13304
|
"iamu-new.md",
|
|
13305
|
+
"iamu-stage.md",
|
|
13243
13306
|
"iamu-run.md",
|
|
13244
13307
|
"iamu-findings.md",
|
|
13245
13308
|
"iamu-status.md"
|
|
@@ -13317,16 +13380,16 @@ async function needsWrite(filePath, content) {
|
|
|
13317
13380
|
return true;
|
|
13318
13381
|
}
|
|
13319
13382
|
}
|
|
13320
|
-
function isCliWrittenSkill(
|
|
13383
|
+
function isCliWrittenSkill(path34) {
|
|
13321
13384
|
try {
|
|
13322
|
-
return /^generatedBy:/m.test(readFileSync3(
|
|
13385
|
+
return /^generatedBy:/m.test(readFileSync3(path34, "utf-8"));
|
|
13323
13386
|
} catch {
|
|
13324
13387
|
return false;
|
|
13325
13388
|
}
|
|
13326
13389
|
}
|
|
13327
|
-
function isCliWrittenPrompt(
|
|
13390
|
+
function isCliWrittenPrompt(path34) {
|
|
13328
13391
|
try {
|
|
13329
|
-
const content = readFileSync3(
|
|
13392
|
+
const content = readFileSync3(path34, "utf-8");
|
|
13330
13393
|
return content.includes("iamu") || /^generatedBy:/m.test(content);
|
|
13331
13394
|
} catch {
|
|
13332
13395
|
return false;
|
|
@@ -13338,8 +13401,8 @@ function planSkillFiles(hosts, projectRoot, version2) {
|
|
|
13338
13401
|
for (const host of hosts) {
|
|
13339
13402
|
if (pluginProvidesSkills(host, projectRoot)) {
|
|
13340
13403
|
for (const name of skillTemplates.keys()) {
|
|
13341
|
-
const
|
|
13342
|
-
if (isCliWrittenSkill(
|
|
13404
|
+
const path34 = join4(host.getSkillsDir(projectRoot), name, "SKILL.md");
|
|
13405
|
+
if (isCliWrittenSkill(path34)) files.push({ host, path: path34, content: null });
|
|
13343
13406
|
}
|
|
13344
13407
|
} else if (host.supportsSkills) {
|
|
13345
13408
|
const skillsDir = host.getSkillsDir(projectRoot);
|
|
@@ -18830,6 +18893,22 @@ async function checkProjectTestabilityContract(projectRoot) {
|
|
|
18830
18893
|
status: "ok",
|
|
18831
18894
|
message: `Configured (${details.join(" \xB7 ") || "present"})`
|
|
18832
18895
|
});
|
|
18896
|
+
if (!frontmatter.access) {
|
|
18897
|
+
let contextAccess = null;
|
|
18898
|
+
try {
|
|
18899
|
+
contextAccess = (await readContext(projectRoot))?.frontmatter.access ?? null;
|
|
18900
|
+
} catch {
|
|
18901
|
+
}
|
|
18902
|
+
if (contextAccess) {
|
|
18903
|
+
checks.push({
|
|
18904
|
+
category: "target",
|
|
18905
|
+
name: "User switching (access)",
|
|
18906
|
+
status: "warn",
|
|
18907
|
+
message: "How to switch user is in iamu/context.yml, which holds the domain. Walks still read it there.",
|
|
18908
|
+
remedy: "Move the `access:` field from iamu/context.yml to the iamu/testability.md frontmatter."
|
|
18909
|
+
});
|
|
18910
|
+
}
|
|
18911
|
+
}
|
|
18833
18912
|
return { checks, targetUrl };
|
|
18834
18913
|
} catch (err) {
|
|
18835
18914
|
checks.push({
|
|
@@ -19382,6 +19461,7 @@ var init_list = __esm({
|
|
|
19382
19461
|
// src/commands/validate.ts
|
|
19383
19462
|
var validate_exports = {};
|
|
19384
19463
|
__export(validate_exports, {
|
|
19464
|
+
schemaByType: () => schemaByType,
|
|
19385
19465
|
validate: () => validate
|
|
19386
19466
|
});
|
|
19387
19467
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
@@ -20056,6 +20136,413 @@ var init_new_entity = __esm({
|
|
|
20056
20136
|
}
|
|
20057
20137
|
});
|
|
20058
20138
|
|
|
20139
|
+
// src/commands/set-entity.ts
|
|
20140
|
+
var set_entity_exports = {};
|
|
20141
|
+
__export(set_entity_exports, {
|
|
20142
|
+
setEntity: () => setEntity
|
|
20143
|
+
});
|
|
20144
|
+
import { promises as fs28 } from "fs";
|
|
20145
|
+
import path26 from "path";
|
|
20146
|
+
import chalk27 from "chalk";
|
|
20147
|
+
import { ZodError as ZodError2 } from "zod";
|
|
20148
|
+
function given(opts, key) {
|
|
20149
|
+
const v = opts[key];
|
|
20150
|
+
if (Array.isArray(v)) return v.length > 0;
|
|
20151
|
+
if (key === "state") return v === false;
|
|
20152
|
+
return v !== void 0;
|
|
20153
|
+
}
|
|
20154
|
+
function splitList(raw) {
|
|
20155
|
+
return raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
20156
|
+
}
|
|
20157
|
+
function applyChanges(type2, fm, opts) {
|
|
20158
|
+
const changed = [];
|
|
20159
|
+
const put = (key, value) => {
|
|
20160
|
+
if (value === null) delete fm[key];
|
|
20161
|
+
else fm[key] = value;
|
|
20162
|
+
changed.push(key);
|
|
20163
|
+
};
|
|
20164
|
+
if (type2 === "scenario") {
|
|
20165
|
+
if (opts.entry !== void 0) put("entry_path", opts.entry || null);
|
|
20166
|
+
if (opts.target !== void 0) put("target_url", opts.target || null);
|
|
20167
|
+
if (opts.state === false) {
|
|
20168
|
+
put("state", null);
|
|
20169
|
+
} else if (opts.stateDescription !== void 0 || opts.stateApproximation !== void 0) {
|
|
20170
|
+
const prev = fm.state ?? {};
|
|
20171
|
+
const next = { ...prev };
|
|
20172
|
+
if (opts.stateDescription !== void 0) {
|
|
20173
|
+
if (opts.stateDescription.trim() === "") delete next.description;
|
|
20174
|
+
else next.description = opts.stateDescription;
|
|
20175
|
+
}
|
|
20176
|
+
if (opts.stateApproximation !== void 0) {
|
|
20177
|
+
if (opts.stateApproximation.trim() === "") delete next.approximation;
|
|
20178
|
+
else next.approximation = opts.stateApproximation;
|
|
20179
|
+
}
|
|
20180
|
+
if (Object.keys(next).length === 0) {
|
|
20181
|
+
put("state", null);
|
|
20182
|
+
} else {
|
|
20183
|
+
put("state", next);
|
|
20184
|
+
}
|
|
20185
|
+
}
|
|
20186
|
+
} else if (type2 === "persona") {
|
|
20187
|
+
if (opts.access !== void 0) put("access", opts.access || null);
|
|
20188
|
+
} else if (type2 === "capability") {
|
|
20189
|
+
if (opts.paths === false) {
|
|
20190
|
+
put("paths", []);
|
|
20191
|
+
} else if (opts.paths !== void 0 || given(opts, "addPath") || given(opts, "removePath")) {
|
|
20192
|
+
let paths = typeof opts.paths === "string" ? splitList(opts.paths) : Array.isArray(fm.paths) ? fm.paths : [];
|
|
20193
|
+
for (const p of opts.addPath ?? []) {
|
|
20194
|
+
if (!paths.includes(p)) paths = [...paths, p];
|
|
20195
|
+
}
|
|
20196
|
+
const drop = new Set(opts.removePath ?? []);
|
|
20197
|
+
paths = paths.filter((p) => !drop.has(p));
|
|
20198
|
+
put("paths", paths);
|
|
20199
|
+
}
|
|
20200
|
+
if (opts.area !== void 0) put("area", opts.area || null);
|
|
20201
|
+
}
|
|
20202
|
+
return changed;
|
|
20203
|
+
}
|
|
20204
|
+
async function setEntity(type2, slug, opts) {
|
|
20205
|
+
const ctx = await resolveProject(opts);
|
|
20206
|
+
if (!ctx) return;
|
|
20207
|
+
const { projectRoot } = ctx;
|
|
20208
|
+
const allowed = SETTABLE[type2];
|
|
20209
|
+
if (!allowed) {
|
|
20210
|
+
commandError(
|
|
20211
|
+
`Cannot set fields on "${type2}". Supported: ${Object.keys(SETTABLE).join(", ")}. What a scenario is about (narrative, tension, human tests, brief) is edited in its file, not here.`,
|
|
20212
|
+
opts
|
|
20213
|
+
);
|
|
20214
|
+
return;
|
|
20215
|
+
}
|
|
20216
|
+
const foreign = Object.keys(FLAG_NAMES).filter(
|
|
20217
|
+
(k) => !allowed.includes(k) && given(opts, k)
|
|
20218
|
+
);
|
|
20219
|
+
if (foreign.length > 0) {
|
|
20220
|
+
commandError(
|
|
20221
|
+
`${foreign.map((k) => FLAG_NAMES[k]).join(", ")} ${foreign.length === 1 ? "does" : "do"} not apply to a ${type2}. ${type2} takes: ${allowed.map((k) => FLAG_NAMES[k]).join(", ")}.`,
|
|
20222
|
+
opts
|
|
20223
|
+
);
|
|
20224
|
+
return;
|
|
20225
|
+
}
|
|
20226
|
+
const effectiveOpts = { ...opts };
|
|
20227
|
+
if (opts.file) {
|
|
20228
|
+
let raw;
|
|
20229
|
+
try {
|
|
20230
|
+
if (opts.file === "-") {
|
|
20231
|
+
const chunks = [];
|
|
20232
|
+
for await (const chunk of process.stdin) {
|
|
20233
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
20234
|
+
}
|
|
20235
|
+
raw = Buffer.concat(chunks).toString("utf8");
|
|
20236
|
+
} else {
|
|
20237
|
+
raw = await fs28.readFile(path26.resolve(projectRoot, opts.file), "utf8");
|
|
20238
|
+
}
|
|
20239
|
+
} catch (err) {
|
|
20240
|
+
commandError(
|
|
20241
|
+
`Could not read patch file: ${err instanceof Error ? err.message : String(err)}`,
|
|
20242
|
+
opts
|
|
20243
|
+
);
|
|
20244
|
+
return;
|
|
20245
|
+
}
|
|
20246
|
+
let parsed;
|
|
20247
|
+
try {
|
|
20248
|
+
parsed = JSON.parse(raw);
|
|
20249
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
20250
|
+
throw new Error("Patch must be a JSON object");
|
|
20251
|
+
}
|
|
20252
|
+
} catch (err) {
|
|
20253
|
+
commandError(
|
|
20254
|
+
`Failed to parse patch JSON: ${err instanceof Error ? err.message : String(err)}`,
|
|
20255
|
+
opts
|
|
20256
|
+
);
|
|
20257
|
+
return;
|
|
20258
|
+
}
|
|
20259
|
+
const allowedPatchKeys = {
|
|
20260
|
+
scenario: ["entry", "entry_path", "target", "target_url", "state"],
|
|
20261
|
+
persona: ["access"],
|
|
20262
|
+
capability: ["paths", "addPath", "removePath", "area"]
|
|
20263
|
+
};
|
|
20264
|
+
const foreignPatchKeys = Object.keys(parsed).filter(
|
|
20265
|
+
(k) => !allowedPatchKeys[type2]?.includes(k)
|
|
20266
|
+
);
|
|
20267
|
+
if (foreignPatchKeys.length > 0) {
|
|
20268
|
+
commandError(
|
|
20269
|
+
`Cannot set authoring fields on ${type2} via staging patch: ${foreignPatchKeys.join(", ")}. What an entity is about (narrative, tension, human tests, brief) is edited in its file, not through staging.`,
|
|
20270
|
+
opts
|
|
20271
|
+
);
|
|
20272
|
+
return;
|
|
20273
|
+
}
|
|
20274
|
+
if (type2 === "scenario") {
|
|
20275
|
+
if (effectiveOpts.entry === void 0 && (parsed.entry !== void 0 || parsed.entry_path !== void 0)) {
|
|
20276
|
+
const v = parsed.entry ?? parsed.entry_path;
|
|
20277
|
+
effectiveOpts.entry = v === null || v === false ? false : String(v);
|
|
20278
|
+
}
|
|
20279
|
+
if (effectiveOpts.target === void 0 && (parsed.target !== void 0 || parsed.target_url !== void 0)) {
|
|
20280
|
+
const v = parsed.target ?? parsed.target_url;
|
|
20281
|
+
effectiveOpts.target = v === null || v === false ? false : String(v);
|
|
20282
|
+
}
|
|
20283
|
+
if (parsed.state !== void 0) {
|
|
20284
|
+
if (parsed.state === null || parsed.state === false) {
|
|
20285
|
+
if (effectiveOpts.state === void 0) effectiveOpts.state = false;
|
|
20286
|
+
} else if (typeof parsed.state === "object") {
|
|
20287
|
+
const s = parsed.state;
|
|
20288
|
+
if (effectiveOpts.stateDescription === void 0 && s.description !== void 0) {
|
|
20289
|
+
effectiveOpts.stateDescription = s.description === null ? "" : String(s.description);
|
|
20290
|
+
}
|
|
20291
|
+
if (effectiveOpts.stateApproximation === void 0 && s.approximation !== void 0) {
|
|
20292
|
+
effectiveOpts.stateApproximation = s.approximation === null ? "" : String(s.approximation);
|
|
20293
|
+
}
|
|
20294
|
+
}
|
|
20295
|
+
}
|
|
20296
|
+
} else if (type2 === "persona") {
|
|
20297
|
+
if (effectiveOpts.access === void 0 && parsed.access !== void 0) {
|
|
20298
|
+
effectiveOpts.access = parsed.access === null || parsed.access === false ? false : String(parsed.access);
|
|
20299
|
+
}
|
|
20300
|
+
} else if (type2 === "capability") {
|
|
20301
|
+
if (effectiveOpts.paths === void 0 && parsed.paths !== void 0) {
|
|
20302
|
+
if (parsed.paths === null || parsed.paths === false) {
|
|
20303
|
+
effectiveOpts.paths = false;
|
|
20304
|
+
} else if (Array.isArray(parsed.paths)) {
|
|
20305
|
+
effectiveOpts.paths = parsed.paths.length === 0 ? false : parsed.paths.join(",");
|
|
20306
|
+
}
|
|
20307
|
+
}
|
|
20308
|
+
if (effectiveOpts.addPath === void 0 && Array.isArray(parsed.addPath)) {
|
|
20309
|
+
effectiveOpts.addPath = parsed.addPath.map(String);
|
|
20310
|
+
}
|
|
20311
|
+
if (effectiveOpts.removePath === void 0 && Array.isArray(parsed.removePath)) {
|
|
20312
|
+
effectiveOpts.removePath = parsed.removePath.map(String);
|
|
20313
|
+
}
|
|
20314
|
+
if (effectiveOpts.area === void 0 && parsed.area !== void 0) {
|
|
20315
|
+
effectiveOpts.area = parsed.area === null || parsed.area === false ? false : String(parsed.area);
|
|
20316
|
+
}
|
|
20317
|
+
}
|
|
20318
|
+
}
|
|
20319
|
+
if (!allowed.some((k) => given(effectiveOpts, k))) {
|
|
20320
|
+
commandError(
|
|
20321
|
+
`Nothing to set. ${type2} takes: ${allowed.map((k) => FLAG_NAMES[k]).join(", ")}, or --file <path>.`,
|
|
20322
|
+
opts
|
|
20323
|
+
);
|
|
20324
|
+
return;
|
|
20325
|
+
}
|
|
20326
|
+
if (type2 === "capability") {
|
|
20327
|
+
const candidatePaths = [
|
|
20328
|
+
...typeof effectiveOpts.paths === "string" ? splitList(effectiveOpts.paths) : [],
|
|
20329
|
+
...effectiveOpts.addPath ?? []
|
|
20330
|
+
];
|
|
20331
|
+
const invalid = candidatePaths.filter((p) => !p.startsWith("/"));
|
|
20332
|
+
if (invalid.length > 0) {
|
|
20333
|
+
commandError(
|
|
20334
|
+
`Route prefixes must start with "/": ${invalid.join(", ")}`,
|
|
20335
|
+
opts
|
|
20336
|
+
);
|
|
20337
|
+
return;
|
|
20338
|
+
}
|
|
20339
|
+
}
|
|
20340
|
+
const entity = await safeReadEntity(
|
|
20341
|
+
projectRoot,
|
|
20342
|
+
type2,
|
|
20343
|
+
slug,
|
|
20344
|
+
opts
|
|
20345
|
+
);
|
|
20346
|
+
if (!entity) return;
|
|
20347
|
+
const fm = { ...entity.frontmatter };
|
|
20348
|
+
const changed = applyChanges(type2, fm, effectiveOpts);
|
|
20349
|
+
let writtenPath;
|
|
20350
|
+
try {
|
|
20351
|
+
const record = await writeEntity(
|
|
20352
|
+
projectRoot,
|
|
20353
|
+
type2,
|
|
20354
|
+
fm,
|
|
20355
|
+
entity.body
|
|
20356
|
+
);
|
|
20357
|
+
writtenPath = record.path;
|
|
20358
|
+
} catch (err) {
|
|
20359
|
+
const message = err instanceof ZodError2 ? err.issues.map((i) => `${i.path.join(".") || "root"}: ${i.message}`).join("; ") : err instanceof Error ? err.message : String(err);
|
|
20360
|
+
commandError(`Could not set ${type2} "${slug}": ${message}`, opts);
|
|
20361
|
+
return;
|
|
20362
|
+
}
|
|
20363
|
+
const entitySlug = String(fm.slug);
|
|
20364
|
+
if (opts.json) {
|
|
20365
|
+
console.log(
|
|
20366
|
+
JSON.stringify({ slug: entitySlug, type: type2, path: writtenPath, changed })
|
|
20367
|
+
);
|
|
20368
|
+
return;
|
|
20369
|
+
}
|
|
20370
|
+
console.log();
|
|
20371
|
+
console.log(
|
|
20372
|
+
` ${symbols.success} Updated ${type2} ${chalk27.cyan(entitySlug)}: ${changed.join(", ")}`
|
|
20373
|
+
);
|
|
20374
|
+
console.log(` ${chalk27.dim("path:")} ${writtenPath}`);
|
|
20375
|
+
console.log();
|
|
20376
|
+
}
|
|
20377
|
+
var SETTABLE, FLAG_NAMES;
|
|
20378
|
+
var init_set_entity = __esm({
|
|
20379
|
+
"src/commands/set-entity.ts"() {
|
|
20380
|
+
"use strict";
|
|
20381
|
+
init_dist2();
|
|
20382
|
+
init_symbols();
|
|
20383
|
+
init_context4();
|
|
20384
|
+
SETTABLE = {
|
|
20385
|
+
scenario: [
|
|
20386
|
+
"entry",
|
|
20387
|
+
"target",
|
|
20388
|
+
"stateDescription",
|
|
20389
|
+
"stateApproximation",
|
|
20390
|
+
"state"
|
|
20391
|
+
],
|
|
20392
|
+
persona: ["access"],
|
|
20393
|
+
capability: ["paths", "addPath", "removePath", "area"]
|
|
20394
|
+
};
|
|
20395
|
+
FLAG_NAMES = {
|
|
20396
|
+
entry: "--entry",
|
|
20397
|
+
target: "--target",
|
|
20398
|
+
stateDescription: "--state-description",
|
|
20399
|
+
stateApproximation: "--state-approximation",
|
|
20400
|
+
state: "--no-state",
|
|
20401
|
+
access: "--access",
|
|
20402
|
+
paths: "--paths",
|
|
20403
|
+
addPath: "--add-path",
|
|
20404
|
+
removePath: "--remove-path",
|
|
20405
|
+
area: "--area"
|
|
20406
|
+
};
|
|
20407
|
+
}
|
|
20408
|
+
});
|
|
20409
|
+
|
|
20410
|
+
// src/commands/edit-entity.ts
|
|
20411
|
+
var edit_entity_exports = {};
|
|
20412
|
+
__export(edit_entity_exports, {
|
|
20413
|
+
editEntity: () => editEntity
|
|
20414
|
+
});
|
|
20415
|
+
import { spawn as spawn2 } from "child_process";
|
|
20416
|
+
import { promises as fs29 } from "fs";
|
|
20417
|
+
import path27 from "path";
|
|
20418
|
+
import chalk28 from "chalk";
|
|
20419
|
+
async function editEntity(type2, slug, opts = {}) {
|
|
20420
|
+
const ctx = await resolveProject(opts);
|
|
20421
|
+
if (!ctx) return;
|
|
20422
|
+
const { projectRoot } = ctx;
|
|
20423
|
+
if (!ENTITY_TYPES.includes(type2)) {
|
|
20424
|
+
commandError(
|
|
20425
|
+
`Unknown entity type "${type2}". Supported: ${ENTITY_TYPES.join(", ")}.`,
|
|
20426
|
+
opts
|
|
20427
|
+
);
|
|
20428
|
+
return;
|
|
20429
|
+
}
|
|
20430
|
+
const dir = ENTITY_DIRS[type2];
|
|
20431
|
+
const filePath = path27.join(projectRoot, "iamu", dir, `${slug}.md`);
|
|
20432
|
+
try {
|
|
20433
|
+
await fs29.access(filePath);
|
|
20434
|
+
} catch {
|
|
20435
|
+
commandError(
|
|
20436
|
+
`${type2} "${slug}" not found at ${path27.relative(projectRoot, filePath)}.`,
|
|
20437
|
+
opts
|
|
20438
|
+
);
|
|
20439
|
+
return;
|
|
20440
|
+
}
|
|
20441
|
+
if (!process.stdin.isTTY || process.env.CI) {
|
|
20442
|
+
commandError(
|
|
20443
|
+
`Cannot open editor in a non-interactive environment. Edit the file directly: ${path27.relative(projectRoot, filePath)}`,
|
|
20444
|
+
opts
|
|
20445
|
+
);
|
|
20446
|
+
return;
|
|
20447
|
+
}
|
|
20448
|
+
const rawEditor = process.env.VISUAL || process.env.EDITOR || (process.platform === "win32" ? "notepad" : "vi");
|
|
20449
|
+
const parts = rawEditor.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) ?? [
|
|
20450
|
+
rawEditor
|
|
20451
|
+
];
|
|
20452
|
+
const editorCmd = parts[0].replace(/^["']|["']$/g, "");
|
|
20453
|
+
const editorArgs = parts.slice(1).map((a) => a.replace(/^["']|["']$/g, ""));
|
|
20454
|
+
await new Promise((resolve4) => {
|
|
20455
|
+
const child = spawn2(editorCmd, [...editorArgs, filePath], {
|
|
20456
|
+
stdio: "inherit"
|
|
20457
|
+
});
|
|
20458
|
+
child.on("exit", (code) => {
|
|
20459
|
+
if (code !== 0) {
|
|
20460
|
+
console.error(
|
|
20461
|
+
chalk28.red(
|
|
20462
|
+
`
|
|
20463
|
+
Editor exited with code ${code}. Changes not validated.
|
|
20464
|
+
`
|
|
20465
|
+
)
|
|
20466
|
+
);
|
|
20467
|
+
process.exitCode = code ?? 1;
|
|
20468
|
+
resolve4();
|
|
20469
|
+
} else {
|
|
20470
|
+
resolve4();
|
|
20471
|
+
}
|
|
20472
|
+
});
|
|
20473
|
+
child.on("error", (err) => {
|
|
20474
|
+
commandError(
|
|
20475
|
+
`Failed to launch editor (${rawEditor}): ${err.message}`,
|
|
20476
|
+
opts
|
|
20477
|
+
);
|
|
20478
|
+
resolve4();
|
|
20479
|
+
});
|
|
20480
|
+
});
|
|
20481
|
+
if (process.exitCode) return;
|
|
20482
|
+
try {
|
|
20483
|
+
const content = await fs29.readFile(filePath, "utf8");
|
|
20484
|
+
const { frontmatter } = parseFrontmatter(content);
|
|
20485
|
+
const schema2 = schemaByType[type2];
|
|
20486
|
+
const parsed = schema2.safeParse(frontmatter);
|
|
20487
|
+
if (!parsed.success) {
|
|
20488
|
+
if (opts.json) {
|
|
20489
|
+
console.log(
|
|
20490
|
+
JSON.stringify({
|
|
20491
|
+
valid: false,
|
|
20492
|
+
type: type2,
|
|
20493
|
+
slug,
|
|
20494
|
+
path: filePath,
|
|
20495
|
+
errors: parsed.error.issues.map((i) => ({
|
|
20496
|
+
field: i.path.join(".") || "root",
|
|
20497
|
+
message: i.message
|
|
20498
|
+
}))
|
|
20499
|
+
})
|
|
20500
|
+
);
|
|
20501
|
+
} else {
|
|
20502
|
+
console.log();
|
|
20503
|
+
console.log(
|
|
20504
|
+
` ${symbols.warning} ${chalk28.yellow(`Validation failed for ${type2} "${slug}":`)}`
|
|
20505
|
+
);
|
|
20506
|
+
for (const issue of parsed.error.issues) {
|
|
20507
|
+
const field2 = issue.path.join(".") || "root";
|
|
20508
|
+
console.log(` - ${field2}: ${issue.message}`);
|
|
20509
|
+
}
|
|
20510
|
+
console.log();
|
|
20511
|
+
console.log(
|
|
20512
|
+
chalk28.dim(
|
|
20513
|
+
` Fix the file and run: iamu validate ${path27.relative(projectRoot, filePath)}`
|
|
20514
|
+
)
|
|
20515
|
+
);
|
|
20516
|
+
console.log();
|
|
20517
|
+
}
|
|
20518
|
+
process.exitCode = 1;
|
|
20519
|
+
return;
|
|
20520
|
+
}
|
|
20521
|
+
if (opts.json) {
|
|
20522
|
+
console.log(JSON.stringify({ valid: true, type: type2, slug, path: filePath }));
|
|
20523
|
+
return;
|
|
20524
|
+
}
|
|
20525
|
+
console.log();
|
|
20526
|
+
console.log(` ${symbols.success} Validated ${type2} ${chalk28.cyan(slug)}`);
|
|
20527
|
+
console.log();
|
|
20528
|
+
} catch (err) {
|
|
20529
|
+
commandError(
|
|
20530
|
+
`Failed to validate ${type2} "${slug}" after edit: ${err instanceof Error ? err.message : String(err)}`,
|
|
20531
|
+
opts
|
|
20532
|
+
);
|
|
20533
|
+
}
|
|
20534
|
+
}
|
|
20535
|
+
var init_edit_entity = __esm({
|
|
20536
|
+
"src/commands/edit-entity.ts"() {
|
|
20537
|
+
"use strict";
|
|
20538
|
+
init_dist2();
|
|
20539
|
+
init_dist();
|
|
20540
|
+
init_symbols();
|
|
20541
|
+
init_context4();
|
|
20542
|
+
init_validate();
|
|
20543
|
+
}
|
|
20544
|
+
});
|
|
20545
|
+
|
|
20059
20546
|
// src/commands/explore-crawl.ts
|
|
20060
20547
|
function candidateSig(c) {
|
|
20061
20548
|
return `${c.role}
|
|
@@ -20095,6 +20582,9 @@ async function settle(page, settleMs) {
|
|
|
20095
20582
|
});
|
|
20096
20583
|
await page.waitForLoadState("networkidle", { timeout: settleMs }).catch(() => {
|
|
20097
20584
|
});
|
|
20585
|
+
const spaSettle = Math.min(Math.max(settleMs, 50), 200);
|
|
20586
|
+
await page.waitForTimeout(spaSettle).catch(() => {
|
|
20587
|
+
});
|
|
20098
20588
|
}
|
|
20099
20589
|
async function readPage(page) {
|
|
20100
20590
|
return page.evaluate(READ_PAGE);
|
|
@@ -20112,8 +20602,12 @@ async function clickCandidate(page, want) {
|
|
|
20112
20602
|
(x) => candidateSig(x) === candidateSig(want)
|
|
20113
20603
|
);
|
|
20114
20604
|
if (!c) return false;
|
|
20115
|
-
|
|
20116
|
-
|
|
20605
|
+
try {
|
|
20606
|
+
await page.locator(`[data-iamu-x="${c.index}"]`).click({ timeout: CLICK_TIMEOUT_MS });
|
|
20607
|
+
return true;
|
|
20608
|
+
} catch {
|
|
20609
|
+
return false;
|
|
20610
|
+
}
|
|
20117
20611
|
}
|
|
20118
20612
|
async function crawl(page, opts) {
|
|
20119
20613
|
const origin = originOf(opts.startUrl);
|
|
@@ -20170,7 +20664,7 @@ async function crawlFrom(page, opts, origin, takeBlocked) {
|
|
|
20170
20664
|
const byKey = /* @__PURE__ */ new Map();
|
|
20171
20665
|
const byPlaceView = /* @__PURE__ */ new Map();
|
|
20172
20666
|
const signatureOf = (s) => exploreSignature(s.headings, s.title);
|
|
20173
|
-
function addSurface(state, place, view,
|
|
20667
|
+
function addSurface(state, place, view, path34, depth) {
|
|
20174
20668
|
const key = exploreSurfaceKey(place, view);
|
|
20175
20669
|
const surface = {
|
|
20176
20670
|
key,
|
|
@@ -20182,7 +20676,7 @@ async function crawlFrom(page, opts, origin, takeBlocked) {
|
|
|
20182
20676
|
headings: state.headings,
|
|
20183
20677
|
fields: state.fields,
|
|
20184
20678
|
depth,
|
|
20185
|
-
path:
|
|
20679
|
+
path: path34,
|
|
20186
20680
|
actions: []
|
|
20187
20681
|
};
|
|
20188
20682
|
surfaces.push(surface);
|
|
@@ -20200,9 +20694,9 @@ ${surface.signature}`, key);
|
|
|
20200
20694
|
});
|
|
20201
20695
|
await settle(page, settleMs);
|
|
20202
20696
|
}
|
|
20203
|
-
async function replay(
|
|
20204
|
-
if (!("goto" in (
|
|
20205
|
-
for (const step of
|
|
20697
|
+
async function replay(path34) {
|
|
20698
|
+
if (!("goto" in (path34[0] ?? {}))) await goto(opts.startUrl);
|
|
20699
|
+
for (const step of path34) {
|
|
20206
20700
|
if ("goto" in step) {
|
|
20207
20701
|
await goto(step.goto);
|
|
20208
20702
|
} else {
|
|
@@ -20326,8 +20820,8 @@ ${sig}`, key);
|
|
|
20326
20820
|
if (stats.surfaces >= budget.max_surfaces) {
|
|
20327
20821
|
stats.truncated = true;
|
|
20328
20822
|
} else {
|
|
20329
|
-
const
|
|
20330
|
-
addSurface(after, place, view,
|
|
20823
|
+
const path34 = !sameDoc && view === null ? [{ goto: after.url }] : [...surface.path, ...via, click];
|
|
20824
|
+
addSurface(after, place, view, path34, surface.depth + 1);
|
|
20331
20825
|
}
|
|
20332
20826
|
}
|
|
20333
20827
|
}
|
|
@@ -20527,11 +21021,11 @@ __export(explore_exports, {
|
|
|
20527
21021
|
exploreList: () => exploreList,
|
|
20528
21022
|
exploreRun: () => exploreRun
|
|
20529
21023
|
});
|
|
20530
|
-
import
|
|
20531
|
-
import
|
|
21024
|
+
import path28 from "path";
|
|
21025
|
+
import chalk29 from "chalk";
|
|
20532
21026
|
function fail2(message, json2) {
|
|
20533
21027
|
if (json2) console.error(JSON.stringify({ error: message }));
|
|
20534
|
-
else console.error(
|
|
21028
|
+
else console.error(chalk29.red(` ${message}`));
|
|
20535
21029
|
process.exitCode = 1;
|
|
20536
21030
|
}
|
|
20537
21031
|
function positiveInt(raw, fallback, flag) {
|
|
@@ -20630,7 +21124,7 @@ async function exploreRun(url, opts) {
|
|
|
20630
21124
|
}
|
|
20631
21125
|
const quiet = opts.json || process.env.IAMU_QUIET === "1";
|
|
20632
21126
|
if (!quiet) {
|
|
20633
|
-
console.error(
|
|
21127
|
+
console.error(chalk29.dim(` Exploring ${startUrl} (read-only)\u2026`));
|
|
20634
21128
|
}
|
|
20635
21129
|
const now = /* @__PURE__ */ new Date();
|
|
20636
21130
|
let opened = null;
|
|
@@ -20641,7 +21135,7 @@ async function exploreRun(url, opts) {
|
|
|
20641
21135
|
startUrl,
|
|
20642
21136
|
budget,
|
|
20643
21137
|
deny,
|
|
20644
|
-
log: quiet ? void 0 : (line) => console.error(
|
|
21138
|
+
log: quiet ? void 0 : (line) => console.error(chalk29.dim(line))
|
|
20645
21139
|
});
|
|
20646
21140
|
} catch (err) {
|
|
20647
21141
|
fail2(err.message.split("\n")[0], opts.json);
|
|
@@ -20661,7 +21155,7 @@ async function exploreRun(url, opts) {
|
|
|
20661
21155
|
surfaces: result.surfaces
|
|
20662
21156
|
};
|
|
20663
21157
|
const file = await writeExploreMap(ctx.projectRoot, map2);
|
|
20664
|
-
const rel =
|
|
21158
|
+
const rel = path28.relative(process.cwd(), file);
|
|
20665
21159
|
if (opts.json) {
|
|
20666
21160
|
console.log(JSON.stringify({ id: map2.id, path: rel, stats: map2.stats }));
|
|
20667
21161
|
return;
|
|
@@ -20669,17 +21163,17 @@ async function exploreRun(url, opts) {
|
|
|
20669
21163
|
const s = map2.stats;
|
|
20670
21164
|
console.log(
|
|
20671
21165
|
`
|
|
20672
|
-
${
|
|
21166
|
+
${chalk29.bold(`${s.surfaces} surfaces`)} \xB7 ${s.actions_tried} controls tried \xB7 ${s.skipped} skipped \xB7 ${s.blocked_requests} writes blocked${s.errors ? ` \xB7 ${s.errors} errors` : ""}`
|
|
20673
21167
|
);
|
|
20674
21168
|
if (s.truncated) {
|
|
20675
21169
|
console.log(
|
|
20676
|
-
|
|
21170
|
+
chalk29.yellow(
|
|
20677
21171
|
" Stopped by the budget before the product ran out \u2014 raise --max-surfaces / --max-actions / --max-depth to go further."
|
|
20678
21172
|
)
|
|
20679
21173
|
);
|
|
20680
21174
|
}
|
|
20681
21175
|
console.log(` Map: ${rel}`);
|
|
20682
|
-
console.log(
|
|
21176
|
+
console.log(chalk29.dim(" Next: iamu explore coverage"));
|
|
20683
21177
|
}
|
|
20684
21178
|
async function resolveMapId(projectRoot, id, offsetFromEnd = 1) {
|
|
20685
21179
|
if (id && id !== "latest") return id;
|
|
@@ -20707,7 +21201,7 @@ async function exploreList(opts) {
|
|
|
20707
21201
|
}
|
|
20708
21202
|
for (const r of rows) {
|
|
20709
21203
|
console.log(
|
|
20710
|
-
` ${r.id} ${r.surfaces} surfaces ${
|
|
21204
|
+
` ${r.id} ${r.surfaces} surfaces ${chalk29.dim(r.target_url)}`
|
|
20711
21205
|
);
|
|
20712
21206
|
}
|
|
20713
21207
|
}
|
|
@@ -20734,7 +21228,10 @@ async function exploreCoverageCommand(mapId, opts) {
|
|
|
20734
21228
|
...cov,
|
|
20735
21229
|
...capReport.capabilities.length > 0 ? {
|
|
20736
21230
|
capabilities: capReport.capabilities,
|
|
20737
|
-
unassigned_surfaces: capReport.unassigned_surfaces
|
|
21231
|
+
unassigned_surfaces: capReport.unassigned_surfaces,
|
|
21232
|
+
unassigned_prefixes: groupUnassignedByPrefix(
|
|
21233
|
+
capReport.unassigned_surfaces
|
|
21234
|
+
)
|
|
20738
21235
|
} : {}
|
|
20739
21236
|
})
|
|
20740
21237
|
);
|
|
@@ -20745,22 +21242,22 @@ async function exploreCoverageCommand(mapId, opts) {
|
|
|
20745
21242
|
Map ${id} (${map2.surfaces.length} surfaces) against ${cov.runs} runs, ${cov.steps} steps`
|
|
20746
21243
|
);
|
|
20747
21244
|
console.log(
|
|
20748
|
-
` ${
|
|
21245
|
+
` ${chalk29.green(`${cov.reached} reached`)} \xB7 ${chalk29.yellow(`${cov.place_only} URL reached, view unclear`)} \xB7 ${chalk29.red(`${cov.unreached} never reached`)}`
|
|
20749
21246
|
);
|
|
20750
21247
|
if (capReport.capabilities.length > 0) {
|
|
20751
|
-
console.log(
|
|
21248
|
+
console.log(chalk29.bold("\n CAPABILITY COVERAGE"));
|
|
20752
21249
|
console.log(
|
|
20753
|
-
|
|
21250
|
+
chalk29.dim(
|
|
20754
21251
|
" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
|
|
20755
21252
|
)
|
|
20756
21253
|
);
|
|
20757
21254
|
console.log(
|
|
20758
|
-
|
|
21255
|
+
chalk29.dim(
|
|
20759
21256
|
` ${"Capability".padEnd(24)} ${"Surfaces".padStart(8)} ${"Coverage".padStart(8)} ${"Scenarios (1\xB0 / 2\xB0)"}`
|
|
20760
21257
|
)
|
|
20761
21258
|
);
|
|
20762
21259
|
console.log(
|
|
20763
|
-
|
|
21260
|
+
chalk29.dim(
|
|
20764
21261
|
" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
|
|
20765
21262
|
)
|
|
20766
21263
|
);
|
|
@@ -20771,8 +21268,8 @@ async function exploreCoverageCommand(mapId, opts) {
|
|
|
20771
21268
|
);
|
|
20772
21269
|
const covStr = `${cap.coverage_pct}%`.padStart(8);
|
|
20773
21270
|
const scenarios = `${cap.primary_scenarios} / ${cap.secondary_scenarios}`;
|
|
20774
|
-
const untestedFlag = cap.primary_scenarios === 0 && cap.unreached_surfaces > 0 ?
|
|
20775
|
-
const covColored = cap.coverage_pct === 100 ?
|
|
21271
|
+
const untestedFlag = cap.primary_scenarios === 0 && cap.unreached_surfaces > 0 ? chalk29.yellow(" (untested)") : "";
|
|
21272
|
+
const covColored = cap.coverage_pct === 100 ? chalk29.green(covStr) : cap.coverage_pct === 0 ? chalk29.red(covStr) : chalk29.yellow(covStr);
|
|
20776
21273
|
console.log(
|
|
20777
21274
|
` ${title} ${surfaces} ${covColored} ${scenarios}${untestedFlag}`
|
|
20778
21275
|
);
|
|
@@ -20782,26 +21279,41 @@ async function exploreCoverageCommand(mapId, opts) {
|
|
|
20782
21279
|
const reached = unassigned.filter((s) => s.status === "reached").length;
|
|
20783
21280
|
const pct = Math.round(reached / unassigned.length * 100);
|
|
20784
21281
|
console.log(
|
|
20785
|
-
|
|
21282
|
+
chalk29.dim(
|
|
20786
21283
|
` ${"(Unassigned)".padEnd(24)} ${`${reached}/${unassigned.length}`.padStart(8)} ${`${pct}%`.padStart(8)} -`
|
|
20787
21284
|
)
|
|
20788
21285
|
);
|
|
20789
21286
|
}
|
|
20790
21287
|
console.log(
|
|
20791
|
-
|
|
21288
|
+
chalk29.dim(
|
|
20792
21289
|
" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
|
|
20793
21290
|
)
|
|
20794
21291
|
);
|
|
21292
|
+
const prefixes = groupUnassignedByPrefix(capReport.unassigned_surfaces);
|
|
21293
|
+
if (prefixes.length > 0) {
|
|
21294
|
+
console.log(chalk29.bold("\n UNASSIGNED PAGES (no capability owns them)"));
|
|
21295
|
+
for (const g of prefixes) {
|
|
21296
|
+
const count = `${g.surfaces} page${g.surfaces === 1 ? "" : "s"} (${g.reached} reached)`;
|
|
21297
|
+
console.log(
|
|
21298
|
+
` ${g.prefix.padEnd(24)} ${count.padEnd(24)} ${chalk29.dim(`e.g. ${g.examples.join(", ")}`)}`
|
|
21299
|
+
);
|
|
21300
|
+
}
|
|
21301
|
+
console.log(
|
|
21302
|
+
chalk29.dim(
|
|
21303
|
+
` Give a prefix to the capability it belongs to: iamu set capability <slug> --add-path ${prefixes[0]?.prefix}`
|
|
21304
|
+
)
|
|
21305
|
+
);
|
|
21306
|
+
}
|
|
20795
21307
|
const hasGaps = capReport.capabilities.some((c) => c.unreached_surfaces > 0) || capReport.unassigned_surfaces.some((s) => s.status !== "reached");
|
|
20796
21308
|
if (hasGaps) {
|
|
20797
|
-
console.log(
|
|
21309
|
+
console.log(chalk29.red("\n UNREACHED SURFACES (Gaps)"));
|
|
20798
21310
|
for (const cap of capReport.capabilities) {
|
|
20799
21311
|
const gaps = cap.surfaces.filter((s) => s.status !== "reached");
|
|
20800
21312
|
if (gaps.length === 0) continue;
|
|
20801
|
-
console.log(` ${
|
|
21313
|
+
console.log(` ${chalk29.bold(cap.slug)}:`);
|
|
20802
21314
|
for (const s of gaps) {
|
|
20803
21315
|
console.log(
|
|
20804
|
-
` ${
|
|
21316
|
+
` ${chalk29.red("\u25CB")} ${s.key.padEnd(32)} ${s.label}${chalk29.dim(` (depth ${s.depth})`)}`
|
|
20805
21317
|
);
|
|
20806
21318
|
}
|
|
20807
21319
|
}
|
|
@@ -20809,23 +21321,23 @@ async function exploreCoverageCommand(mapId, opts) {
|
|
|
20809
21321
|
(s) => s.status !== "reached"
|
|
20810
21322
|
);
|
|
20811
21323
|
if (unassignedGaps.length > 0) {
|
|
20812
|
-
console.log(
|
|
21324
|
+
console.log(chalk29.dim(" (Unassigned):"));
|
|
20813
21325
|
for (const s of unassignedGaps) {
|
|
20814
21326
|
console.log(
|
|
20815
|
-
` ${
|
|
21327
|
+
` ${chalk29.dim("\u25CB")} ${s.key.padEnd(32)} ${s.label}${chalk29.dim(` (depth ${s.depth})`)}`
|
|
20816
21328
|
);
|
|
20817
21329
|
}
|
|
20818
21330
|
}
|
|
20819
21331
|
}
|
|
20820
21332
|
if (opts.all) {
|
|
20821
|
-
console.log(
|
|
21333
|
+
console.log(chalk29.green("\n REACHED SURFACES"));
|
|
20822
21334
|
for (const cap of capReport.capabilities) {
|
|
20823
21335
|
const reached = cap.surfaces.filter((s) => s.status === "reached");
|
|
20824
21336
|
if (reached.length === 0) continue;
|
|
20825
|
-
console.log(` ${
|
|
21337
|
+
console.log(` ${chalk29.bold(cap.slug)}:`);
|
|
20826
21338
|
for (const s of reached) {
|
|
20827
21339
|
console.log(
|
|
20828
|
-
` ${
|
|
21340
|
+
` ${chalk29.green("\u2713")} ${s.key.padEnd(32)} ${s.label}${chalk29.dim(` (depth ${s.depth})`)}`
|
|
20829
21341
|
);
|
|
20830
21342
|
}
|
|
20831
21343
|
}
|
|
@@ -20833,10 +21345,10 @@ async function exploreCoverageCommand(mapId, opts) {
|
|
|
20833
21345
|
(s) => s.status === "reached"
|
|
20834
21346
|
);
|
|
20835
21347
|
if (unassignedReached.length > 0) {
|
|
20836
|
-
console.log(
|
|
21348
|
+
console.log(chalk29.dim(" (Unassigned):"));
|
|
20837
21349
|
for (const s of unassignedReached) {
|
|
20838
21350
|
console.log(
|
|
20839
|
-
` ${
|
|
21351
|
+
` ${chalk29.green("\u2713")} ${s.key.padEnd(32)} ${s.label}${chalk29.dim(` (depth ${s.depth})`)}`
|
|
20840
21352
|
);
|
|
20841
21353
|
}
|
|
20842
21354
|
}
|
|
@@ -20848,24 +21360,24 @@ async function exploreCoverageCommand(mapId, opts) {
|
|
|
20848
21360
|
${color(title)}`);
|
|
20849
21361
|
for (const r of rows) {
|
|
20850
21362
|
console.log(
|
|
20851
|
-
` ${r.key.padEnd(32)} ${r.label}${
|
|
21363
|
+
` ${r.key.padEnd(32)} ${r.label}${chalk29.dim(` (depth ${r.depth})`)}`
|
|
20852
21364
|
);
|
|
20853
21365
|
}
|
|
20854
21366
|
};
|
|
20855
21367
|
section(
|
|
20856
21368
|
"Never reached by a run",
|
|
20857
21369
|
cov.surfaces.filter((s) => s.status === "unreached"),
|
|
20858
|
-
|
|
21370
|
+
chalk29.red
|
|
20859
21371
|
);
|
|
20860
21372
|
section(
|
|
20861
21373
|
"URL reached, but no step names this view",
|
|
20862
21374
|
cov.surfaces.filter((s) => s.status === "place_only"),
|
|
20863
|
-
|
|
21375
|
+
chalk29.yellow
|
|
20864
21376
|
);
|
|
20865
21377
|
}
|
|
20866
21378
|
if (cov.run_only_places.length > 0) {
|
|
20867
21379
|
console.log(`
|
|
20868
|
-
${
|
|
21380
|
+
${chalk29.cyan("Walked by runs, not in the map")}`);
|
|
20869
21381
|
for (const p of cov.run_only_places) {
|
|
20870
21382
|
console.log(
|
|
20871
21383
|
` ${p.place.padEnd(32)} ${p.steps} steps, ${p.runs.length} runs`
|
|
@@ -20874,7 +21386,7 @@ async function exploreCoverageCommand(mapId, opts) {
|
|
|
20874
21386
|
}
|
|
20875
21387
|
if (cov.runs === 0) {
|
|
20876
21388
|
console.log(
|
|
20877
|
-
|
|
21389
|
+
chalk29.dim(
|
|
20878
21390
|
"\n No runs yet \u2014 every surface counts as never reached. Walk a scenario first (iamu-run)."
|
|
20879
21391
|
)
|
|
20880
21392
|
);
|
|
@@ -20910,27 +21422,27 @@ async function exploreDiffCommand(a, b, opts) {
|
|
|
20910
21422
|
console.log(`
|
|
20911
21423
|
${idA} \u2192 ${idB}`);
|
|
20912
21424
|
console.log(
|
|
20913
|
-
` ${
|
|
21425
|
+
` ${chalk29.bold(`${pct}% of surfaces match`)} (${d.common} shared, ${d.only_a.length} only in the first, ${d.only_b.length} only in the second)`
|
|
20914
21426
|
);
|
|
20915
21427
|
if (d.only_a.length > 0) {
|
|
20916
21428
|
console.log(`
|
|
20917
|
-
${
|
|
21429
|
+
${chalk29.red("Only in the first")}`);
|
|
20918
21430
|
for (const s of d.only_a) console.log(` ${s.key.padEnd(32)} ${s.label}`);
|
|
20919
21431
|
}
|
|
20920
21432
|
if (d.only_b.length > 0) {
|
|
20921
21433
|
console.log(`
|
|
20922
|
-
${
|
|
21434
|
+
${chalk29.green("Only in the second")}`);
|
|
20923
21435
|
for (const s of d.only_b) console.log(` ${s.key.padEnd(32)} ${s.label}`);
|
|
20924
21436
|
}
|
|
20925
21437
|
if (d.changed_actions.length > 0) {
|
|
20926
21438
|
console.log(
|
|
20927
21439
|
`
|
|
20928
|
-
${
|
|
21440
|
+
${chalk29.yellow(`Controls differ on ${d.changed_actions.length} shared surfaces`)}`
|
|
20929
21441
|
);
|
|
20930
21442
|
for (const c of d.changed_actions) {
|
|
20931
21443
|
console.log(` ${c.key} ${c.label}`);
|
|
20932
|
-
for (const x of c.only_a) console.log(
|
|
20933
|
-
for (const x of c.only_b) console.log(
|
|
21444
|
+
for (const x of c.only_a) console.log(chalk29.red(` - ${x}`));
|
|
21445
|
+
for (const x of c.only_b) console.log(chalk29.green(` + ${x}`));
|
|
20934
21446
|
}
|
|
20935
21447
|
}
|
|
20936
21448
|
}
|
|
@@ -21144,7 +21656,7 @@ __export(instructions_walk_exports, {
|
|
|
21144
21656
|
});
|
|
21145
21657
|
import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
|
|
21146
21658
|
import { join as join9 } from "path";
|
|
21147
|
-
import
|
|
21659
|
+
import chalk30 from "chalk";
|
|
21148
21660
|
function selfCommand() {
|
|
21149
21661
|
const q = (p) => /[\s"'$]/.test(p) ? JSON.stringify(p) : p;
|
|
21150
21662
|
return `${q(process.execPath)} ${q(process.argv[1] ?? "iamu")}`;
|
|
@@ -21274,7 +21786,7 @@ function composeWalkerPrompt(p) {
|
|
|
21274
21786
|
"Squinting Guardrails:",
|
|
21275
21787
|
"\u2022 If the exact target record/option is not available, apply the approximation strategy above to pick the closest plausible entity within the domain flow.",
|
|
21276
21788
|
"\u2022 Squinting does NOT permit drifting to unrelated settings, account profiles, or off-target pages. Stay strictly within the scenario journey.",
|
|
21277
|
-
"\u2022 If the system is completely empty or unseeded, and your persona role cannot create data, do NOT report false product bugs about the empty state.
|
|
21789
|
+
"\u2022 If the system is completely empty or unseeded, and your persona role cannot create data, do NOT report false product bugs about the empty state. End the walk as blocked (below)."
|
|
21278
21790
|
].filter(Boolean) : [],
|
|
21279
21791
|
...p.untestableBoundaries && p.untestableBoundaries.length > 0 ? [
|
|
21280
21792
|
"",
|
|
@@ -21310,6 +21822,7 @@ function composeWalkerPrompt(p) {
|
|
|
21310
21822
|
`Record findings with: ${iamu} new-finding --scenario ${p.scenarioSlug} --run ${p.runId} --step <N> --title "..." --type <helps|hinders|missing|irrelevant|incorrect> --severity <critical|major|minor|observation> --ui-fact "..." --consequence "..." --task-impact <blocks|impairs|slows|neutral|accelerates|enables> --confidence <high|medium|low> --description "..." --recommendation "..." --json`,
|
|
21311
21823
|
"`--step <N>` is the step whose screenshot shows the problem (or strength): it is the evidence people see next to the finding, so record that step and its screenshot first, and pick the screenshot where the thing is actually visible.",
|
|
21312
21824
|
`Before you finish, record synthesis with: ${iamu} summary ${p.runId} --worked "..." --worked "..." --question "..."`,
|
|
21825
|
+
`If the walk cannot start as it was set up \u2014 you cannot become this persona (the switch or login fails, or you are someone else), the world the situation needs is not there and you cannot create it, or the starting page does not load \u2014 stop immediately and end the walk with: ${iamu} summary ${p.runId} --blocked "<what was missing, as you saw it>". Do NOT record preliminary findings if preconditions are not met. That is about the setup, not the product: when the product has no way to do what you need, that is a finding (--type missing), not a block. Do not record findings about the missing setup itself.`,
|
|
21313
21826
|
"Execute everything via CLI commands in the shell. Never invoke host Edit or Write tools on files in the workspace.",
|
|
21314
21827
|
"",
|
|
21315
21828
|
"Do not read the product's source code, other iamu/ entity files, or earlier runs: you know only what the persona knows.",
|
|
@@ -21427,6 +21940,7 @@ async function instructionsWalk(opts) {
|
|
|
21427
21940
|
if (!targetUrl && doc.frontmatter.target_url) {
|
|
21428
21941
|
targetUrl = doc.frontmatter.target_url;
|
|
21429
21942
|
}
|
|
21943
|
+
if (doc.frontmatter.access) projectAccess = doc.frontmatter.access;
|
|
21430
21944
|
const fromFm = doc.frontmatter.untestable_boundaries ?? [];
|
|
21431
21945
|
const fromBody = extractUntestableBoundaries(doc.body);
|
|
21432
21946
|
untestableBoundaries.push(
|
|
@@ -21512,40 +22026,40 @@ async function instructionsWalk(opts) {
|
|
|
21512
22026
|
return;
|
|
21513
22027
|
}
|
|
21514
22028
|
console.log();
|
|
21515
|
-
console.log(
|
|
21516
|
-
console.log(` ${
|
|
21517
|
-
console.log(` ${
|
|
21518
|
-
console.log(` ${
|
|
22029
|
+
console.log(chalk30.bold(" Walk instructions"));
|
|
22030
|
+
console.log(` ${chalk30.dim("scenario:")} ${sfm.slug}`);
|
|
22031
|
+
console.log(` ${chalk30.dim("persona:")} ${pfm.name} (${pfm.role})`);
|
|
22032
|
+
console.log(` ${chalk30.dim("outline:")} ${ofm.title}`);
|
|
21519
22033
|
if (sfm.state?.description || sfm.state?.approximation) {
|
|
21520
22034
|
console.log(
|
|
21521
|
-
` ${
|
|
22035
|
+
` ${chalk30.dim("state:")} ${sfm.state.description ? sfm.state.description : sfm.state.approximation ?? ""}`
|
|
21522
22036
|
);
|
|
21523
22037
|
}
|
|
21524
|
-
if (targetUrl) console.log(` ${
|
|
22038
|
+
if (targetUrl) console.log(` ${chalk30.dim("target:")} ${targetUrl}`);
|
|
21525
22039
|
console.log(
|
|
21526
|
-
` ${
|
|
22040
|
+
` ${chalk30.dim("access:")} ${projectAccess || personaAccess ? `${projectAccess ? "project" : ""}${projectAccess && personaAccess ? " + " : ""}${personaAccess ? "persona" : ""}` : "not recorded (walker would run as whoever is signed in)"}`
|
|
21527
22041
|
);
|
|
21528
22042
|
console.log(
|
|
21529
|
-
` ${
|
|
22043
|
+
` ${chalk30.dim("dismissed:")} ${dismissedFacts.length} findings to skip`
|
|
21530
22044
|
);
|
|
21531
22045
|
console.log(
|
|
21532
|
-
` ${
|
|
22046
|
+
` ${chalk30.dim("guidance:")} ${guidance.length} tester guidance rule${guidance.length === 1 ? "" : "s"}`
|
|
21533
22047
|
);
|
|
21534
22048
|
if (untestableBoundaries.length > 0) {
|
|
21535
22049
|
console.log(
|
|
21536
|
-
` ${
|
|
22050
|
+
` ${chalk30.dim("boundaries:")} ${untestableBoundaries.length} untestable boundaries to avoid`
|
|
21537
22051
|
);
|
|
21538
22052
|
}
|
|
21539
22053
|
if (revisit.length > 0) {
|
|
21540
22054
|
console.log(
|
|
21541
|
-
` ${
|
|
22055
|
+
` ${chalk30.dim("re-check:")} ${revisit.length} place${revisit.length === 1 ? "" : "s"} where fixes await a re-run`
|
|
21542
22056
|
);
|
|
21543
22057
|
}
|
|
21544
22058
|
console.log(
|
|
21545
|
-
` ${
|
|
22059
|
+
` ${chalk30.dim("methodology:")} ${methodology ? "loaded" : "missing (run iamu update)"}`
|
|
21546
22060
|
);
|
|
21547
22061
|
console.log();
|
|
21548
|
-
console.log(
|
|
22062
|
+
console.log(chalk30.dim(" Use --json to get the full instruction bundle."));
|
|
21549
22063
|
console.log();
|
|
21550
22064
|
}
|
|
21551
22065
|
var SCREEN;
|
|
@@ -21571,8 +22085,8 @@ __export(new_finding_exports, {
|
|
|
21571
22085
|
});
|
|
21572
22086
|
import { randomUUID as randomUUID10 } from "crypto";
|
|
21573
22087
|
import { access, readFile as readFile7 } from "fs/promises";
|
|
21574
|
-
import
|
|
21575
|
-
import
|
|
22088
|
+
import path29 from "path";
|
|
22089
|
+
import chalk31 from "chalk";
|
|
21576
22090
|
async function newFinding(opts) {
|
|
21577
22091
|
const ctx = await resolveProject(opts);
|
|
21578
22092
|
if (!ctx) return;
|
|
@@ -21638,7 +22152,7 @@ async function newFinding(opts) {
|
|
|
21638
22152
|
}
|
|
21639
22153
|
try {
|
|
21640
22154
|
await access(
|
|
21641
|
-
|
|
22155
|
+
path29.join(runDirPath, "steps", `${String(n).padStart(3, "0")}.json`)
|
|
21642
22156
|
);
|
|
21643
22157
|
} catch {
|
|
21644
22158
|
commandError(
|
|
@@ -21653,7 +22167,7 @@ async function newFinding(opts) {
|
|
|
21653
22167
|
if (!scenarioSlug) {
|
|
21654
22168
|
try {
|
|
21655
22169
|
const manifestRaw = await readFile7(
|
|
21656
|
-
|
|
22170
|
+
path29.join(runDirPath, "walkthrough.json"),
|
|
21657
22171
|
"utf8"
|
|
21658
22172
|
);
|
|
21659
22173
|
const manifest = JSON.parse(manifestRaw);
|
|
@@ -21757,10 +22271,10 @@ async function newFinding(opts) {
|
|
|
21757
22271
|
return;
|
|
21758
22272
|
}
|
|
21759
22273
|
console.log();
|
|
21760
|
-
console.log(` ${symbols.success} Created finding: ${
|
|
21761
|
-
console.log(` ${
|
|
21762
|
-
console.log(` ${
|
|
21763
|
-
for (const w of warnings) console.log(` ${
|
|
22274
|
+
console.log(` ${symbols.success} Created finding: ${chalk31.cyan(slug)}`);
|
|
22275
|
+
console.log(` ${chalk31.dim("path:")} ${record.path}`);
|
|
22276
|
+
console.log(` ${chalk31.dim("id:")} ${id}`);
|
|
22277
|
+
for (const w of warnings) console.log(` ${chalk31.yellow("!")} ${w}`);
|
|
21764
22278
|
console.log();
|
|
21765
22279
|
} catch (err) {
|
|
21766
22280
|
commandError(
|
|
@@ -21785,9 +22299,13 @@ var summary_exports = {};
|
|
|
21785
22299
|
__export(summary_exports, {
|
|
21786
22300
|
summaryCommand: () => summaryCommand
|
|
21787
22301
|
});
|
|
21788
|
-
import { promises as
|
|
21789
|
-
import
|
|
21790
|
-
import
|
|
22302
|
+
import { promises as fs30 } from "fs";
|
|
22303
|
+
import path30 from "path";
|
|
22304
|
+
import chalk32 from "chalk";
|
|
22305
|
+
function blockedFrom(value) {
|
|
22306
|
+
const reason = value && typeof value === "object" ? value.reason : value;
|
|
22307
|
+
return typeof reason === "string" && reason.trim() ? { reason: reason.trim() } : void 0;
|
|
22308
|
+
}
|
|
21791
22309
|
async function summaryCommand(runIdArg, opts) {
|
|
21792
22310
|
const ctx = await resolveProject(opts);
|
|
21793
22311
|
if (!ctx) return;
|
|
@@ -21802,6 +22320,13 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
21802
22320
|
let whatWorked = [];
|
|
21803
22321
|
let openQuestions = [];
|
|
21804
22322
|
let model = opts.model?.trim() || void 0;
|
|
22323
|
+
const summaryPath = path30.join(runDirPath, "summary.json");
|
|
22324
|
+
let blocked;
|
|
22325
|
+
try {
|
|
22326
|
+
const prev = JSON.parse(await fs30.readFile(summaryPath, "utf8"));
|
|
22327
|
+
blocked = blockedFrom(prev.blocked);
|
|
22328
|
+
} catch {
|
|
22329
|
+
}
|
|
21805
22330
|
if (opts.file) {
|
|
21806
22331
|
try {
|
|
21807
22332
|
let raw;
|
|
@@ -21812,7 +22337,7 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
21812
22337
|
}
|
|
21813
22338
|
raw = Buffer.concat(chunks).toString("utf8");
|
|
21814
22339
|
} else {
|
|
21815
|
-
raw = await
|
|
22340
|
+
raw = await fs30.readFile(path30.resolve(projectRoot, opts.file), "utf8");
|
|
21816
22341
|
}
|
|
21817
22342
|
const parsed = JSON.parse(raw);
|
|
21818
22343
|
if (Array.isArray(parsed.what_worked)) {
|
|
@@ -21828,6 +22353,7 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
21828
22353
|
if (typeof parsed.model === "string" && parsed.model.trim()) {
|
|
21829
22354
|
model = parsed.model.trim();
|
|
21830
22355
|
}
|
|
22356
|
+
blocked = blockedFrom(parsed.blocked) ?? blocked;
|
|
21831
22357
|
} catch (err) {
|
|
21832
22358
|
commandError(
|
|
21833
22359
|
`Failed to parse summary file: ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -21849,12 +22375,17 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
21849
22375
|
if (cliQuestions.length > 0) {
|
|
21850
22376
|
openQuestions = [...openQuestions, ...cliQuestions];
|
|
21851
22377
|
}
|
|
22378
|
+
if (opts.blocked === false) {
|
|
22379
|
+
blocked = void 0;
|
|
22380
|
+
} else {
|
|
22381
|
+
blocked = blockedFrom(opts.blocked) ?? blocked;
|
|
22382
|
+
}
|
|
21852
22383
|
const data = {
|
|
21853
22384
|
what_worked: whatWorked,
|
|
21854
22385
|
open_questions: openQuestions,
|
|
21855
|
-
...model ? { model } : {}
|
|
22386
|
+
...model ? { model } : {},
|
|
22387
|
+
...blocked ? { blocked } : {}
|
|
21856
22388
|
};
|
|
21857
|
-
const summaryPath = path28.join(runDirPath, "summary.json");
|
|
21858
22389
|
await atomicWriteFile(summaryPath, `${JSON.stringify(data, null, 2)}
|
|
21859
22390
|
`);
|
|
21860
22391
|
if (opts.json) {
|
|
@@ -21862,7 +22393,7 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
21862
22393
|
JSON.stringify(
|
|
21863
22394
|
{
|
|
21864
22395
|
run_id: runId,
|
|
21865
|
-
path:
|
|
22396
|
+
path: path30.relative(projectRoot, summaryPath),
|
|
21866
22397
|
summary: data
|
|
21867
22398
|
},
|
|
21868
22399
|
null,
|
|
@@ -21873,16 +22404,19 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
21873
22404
|
}
|
|
21874
22405
|
console.log();
|
|
21875
22406
|
console.log(
|
|
21876
|
-
` ${symbols.success} Summary recorded for run ${
|
|
22407
|
+
` ${symbols.success} Summary recorded for run ${chalk32.cyan(runId)}`
|
|
21877
22408
|
);
|
|
22409
|
+
if (data.blocked) {
|
|
22410
|
+
console.log(` ${chalk32.yellow("blocked:")} ${data.blocked.reason}`);
|
|
22411
|
+
}
|
|
21878
22412
|
if (data.what_worked.length > 0) {
|
|
21879
22413
|
console.log(
|
|
21880
|
-
` ${
|
|
22414
|
+
` ${chalk32.dim("what worked:")} ${data.what_worked.length} item(s)`
|
|
21881
22415
|
);
|
|
21882
22416
|
}
|
|
21883
22417
|
if (data.open_questions.length > 0) {
|
|
21884
22418
|
console.log(
|
|
21885
|
-
` ${
|
|
22419
|
+
` ${chalk32.dim("open questions:")} ${data.open_questions.length} item(s)`
|
|
21886
22420
|
);
|
|
21887
22421
|
}
|
|
21888
22422
|
console.log();
|
|
@@ -21903,7 +22437,7 @@ __export(view_results_exports, {
|
|
|
21903
22437
|
promptViewResults: () => promptViewResults
|
|
21904
22438
|
});
|
|
21905
22439
|
import { isCancel as isCancel4, select as select4 } from "@clack/prompts";
|
|
21906
|
-
import
|
|
22440
|
+
import chalk33 from "chalk";
|
|
21907
22441
|
function launchCloudDashboard(config, runId) {
|
|
21908
22442
|
const cloudRunId = runId ? toCloudRunUuid(runId) : void 0;
|
|
21909
22443
|
const url = getCloudDashboardUrl(
|
|
@@ -21912,7 +22446,7 @@ function launchCloudDashboard(config, runId) {
|
|
|
21912
22446
|
cloudRunId
|
|
21913
22447
|
);
|
|
21914
22448
|
console.log();
|
|
21915
|
-
console.log(` ${symbols.success} Opening Iamu Cloud: ${
|
|
22449
|
+
console.log(` ${symbols.success} Opening Iamu Cloud: ${chalk33.cyan(url)}
|
|
21916
22450
|
`);
|
|
21917
22451
|
openBrowser(url);
|
|
21918
22452
|
}
|
|
@@ -21980,9 +22514,9 @@ __export(evaluate_exports, {
|
|
|
21980
22514
|
evaluate: () => evaluate,
|
|
21981
22515
|
evaluateRecord: () => evaluateRecord
|
|
21982
22516
|
});
|
|
21983
|
-
import { promises as
|
|
21984
|
-
import
|
|
21985
|
-
import
|
|
22517
|
+
import { promises as fs31 } from "fs";
|
|
22518
|
+
import path31 from "path";
|
|
22519
|
+
import chalk34 from "chalk";
|
|
21986
22520
|
import { z as z26 } from "zod";
|
|
21987
22521
|
function formatIssues(error) {
|
|
21988
22522
|
return error.issues.map(
|
|
@@ -22012,10 +22546,10 @@ async function evaluate(runId, opts) {
|
|
|
22012
22546
|
const resolved = await resolveProjectAndRunDir(runId, opts);
|
|
22013
22547
|
if (!resolved) return;
|
|
22014
22548
|
const { projectRoot, runDirPath } = resolved;
|
|
22015
|
-
const canonicalPath =
|
|
22549
|
+
const canonicalPath = path31.join(runDirPath, "canonical.json");
|
|
22016
22550
|
let canonical;
|
|
22017
22551
|
try {
|
|
22018
|
-
const raw = await
|
|
22552
|
+
const raw = await fs31.readFile(canonicalPath, "utf8");
|
|
22019
22553
|
canonical = JSON.parse(raw);
|
|
22020
22554
|
} catch {
|
|
22021
22555
|
commandError(
|
|
@@ -22071,15 +22605,15 @@ async function evaluate(runId, opts) {
|
|
|
22071
22605
|
)
|
|
22072
22606
|
};
|
|
22073
22607
|
const prompt = composeJudgePrompt(input);
|
|
22074
|
-
const promptFilePath = opts?.promptFile ??
|
|
22075
|
-
await
|
|
22608
|
+
const promptFilePath = opts?.promptFile ?? path31.join(runDirPath, "judge_prompt.md");
|
|
22609
|
+
await fs31.writeFile(promptFilePath, prompt, "utf8");
|
|
22076
22610
|
if (opts?.json) {
|
|
22077
22611
|
console.log(
|
|
22078
22612
|
JSON.stringify(
|
|
22079
22613
|
{
|
|
22080
22614
|
...input,
|
|
22081
|
-
prompt_file:
|
|
22082
|
-
prompt_file_abs:
|
|
22615
|
+
prompt_file: path31.relative(projectRoot, promptFilePath),
|
|
22616
|
+
prompt_file_abs: path31.resolve(promptFilePath),
|
|
22083
22617
|
judge_prompt: prompt
|
|
22084
22618
|
},
|
|
22085
22619
|
null,
|
|
@@ -22096,10 +22630,10 @@ async function evaluateRecord(runId, opts) {
|
|
|
22096
22630
|
const { projectRoot, runDirPath } = resolved;
|
|
22097
22631
|
sendLivePhase(projectRoot, runId, "judging").catch(() => {
|
|
22098
22632
|
});
|
|
22099
|
-
const canonicalPath =
|
|
22633
|
+
const canonicalPath = path31.join(runDirPath, "canonical.json");
|
|
22100
22634
|
let canonical;
|
|
22101
22635
|
try {
|
|
22102
|
-
const raw = await
|
|
22636
|
+
const raw = await fs31.readFile(canonicalPath, "utf8");
|
|
22103
22637
|
canonical = JSON.parse(raw);
|
|
22104
22638
|
} catch {
|
|
22105
22639
|
commandError(`No canonical.json for run: ${runId}`, opts);
|
|
@@ -22121,7 +22655,7 @@ async function evaluateRecord(runId, opts) {
|
|
|
22121
22655
|
}
|
|
22122
22656
|
raw = Buffer.concat(chunks).toString("utf8");
|
|
22123
22657
|
} else {
|
|
22124
|
-
raw = await
|
|
22658
|
+
raw = await fs31.readFile(opts.file, "utf8");
|
|
22125
22659
|
}
|
|
22126
22660
|
parsedFile = JSON.parse(raw);
|
|
22127
22661
|
} catch (err) {
|
|
@@ -22182,8 +22716,8 @@ ${formatIssues(fileResult.error)}`,
|
|
|
22182
22716
|
overall: inputVerdicts.overall
|
|
22183
22717
|
});
|
|
22184
22718
|
const reportContent = renderCanonicalMarkdown(canonicalForReport);
|
|
22185
|
-
const verdictsPath =
|
|
22186
|
-
const reportPath =
|
|
22719
|
+
const verdictsPath = path31.join(runDirPath, "verdicts.json");
|
|
22720
|
+
const reportPath = path31.join(runDirPath, "report.md");
|
|
22187
22721
|
await Promise.all([
|
|
22188
22722
|
atomicWriteFile(
|
|
22189
22723
|
verdictsPath,
|
|
@@ -22203,8 +22737,8 @@ ${formatIssues(fileResult.error)}`,
|
|
|
22203
22737
|
JSON.stringify(
|
|
22204
22738
|
{
|
|
22205
22739
|
runId,
|
|
22206
|
-
verdictsPath:
|
|
22207
|
-
reportPath:
|
|
22740
|
+
verdictsPath: path31.relative(projectRoot, verdictsPath),
|
|
22741
|
+
reportPath: path31.relative(projectRoot, reportPath),
|
|
22208
22742
|
verdictsCount: validatedVerdicts.length,
|
|
22209
22743
|
linkedCount: linked.length,
|
|
22210
22744
|
...cloudInfo ? { cloud: cloudInfo } : {}
|
|
@@ -22217,18 +22751,18 @@ ${formatIssues(fileResult.error)}`,
|
|
|
22217
22751
|
}
|
|
22218
22752
|
console.log();
|
|
22219
22753
|
console.log(
|
|
22220
|
-
` ${symbols.success} Recorded ${validatedVerdicts.length} verdicts for run ${
|
|
22754
|
+
` ${symbols.success} Recorded ${validatedVerdicts.length} verdicts for run ${chalk34.cyan(runId)}`
|
|
22221
22755
|
);
|
|
22222
22756
|
if (linked.length > 0) {
|
|
22223
22757
|
console.log(
|
|
22224
|
-
` ${
|
|
22758
|
+
` ${chalk34.dim("linked:")} ${linked.length} finding${linked.length === 1 ? "" : "s"} to the human test${linked.length === 1 ? "" : "s"} citing ${linked.length === 1 ? "it" : "them"}`
|
|
22225
22759
|
);
|
|
22226
22760
|
}
|
|
22227
22761
|
console.log(
|
|
22228
|
-
` ${
|
|
22762
|
+
` ${chalk34.dim("verdicts:")} ${path31.relative(projectRoot, verdictsPath)}`
|
|
22229
22763
|
);
|
|
22230
22764
|
console.log(
|
|
22231
|
-
` ${
|
|
22765
|
+
` ${chalk34.dim("report:")} ${path31.relative(projectRoot, reportPath)}`
|
|
22232
22766
|
);
|
|
22233
22767
|
printCloudRunInfo(cloudInfo);
|
|
22234
22768
|
await promptViewResults(projectRoot, { ...opts, runId });
|
|
@@ -22293,9 +22827,9 @@ __export(complete_exports, {
|
|
|
22293
22827
|
screenshotSizeWarning: () => screenshotSizeWarning,
|
|
22294
22828
|
validateCanonicalWalkthrough: () => validateCanonicalWalkthrough
|
|
22295
22829
|
});
|
|
22296
|
-
import { promises as
|
|
22297
|
-
import
|
|
22298
|
-
import
|
|
22830
|
+
import { promises as fs32 } from "fs";
|
|
22831
|
+
import path32 from "path";
|
|
22832
|
+
import chalk35 from "chalk";
|
|
22299
22833
|
async function complete(runId, opts) {
|
|
22300
22834
|
const run = await loadRun(runId, opts);
|
|
22301
22835
|
if (!run) return;
|
|
@@ -22307,7 +22841,7 @@ async function complete(runId, opts) {
|
|
|
22307
22841
|
}
|
|
22308
22842
|
if (!opts?.json) {
|
|
22309
22843
|
console.log();
|
|
22310
|
-
console.log(
|
|
22844
|
+
console.log(chalk35.bold(" Assembling canonical JSON\u2026"));
|
|
22311
22845
|
}
|
|
22312
22846
|
const provenanceAudit = auditStepProvenance(
|
|
22313
22847
|
await readStepsForRun(run.projectRoot, runId)
|
|
@@ -22320,8 +22854,8 @@ async function complete(runId, opts) {
|
|
|
22320
22854
|
const walkerSummary = await readWalkerSummary(run.runDirPath);
|
|
22321
22855
|
let judgeReported;
|
|
22322
22856
|
try {
|
|
22323
|
-
const verdictsRaw = await
|
|
22324
|
-
|
|
22857
|
+
const verdictsRaw = await fs32.readFile(
|
|
22858
|
+
path32.join(run.runDirPath, "verdicts.json"),
|
|
22325
22859
|
"utf8"
|
|
22326
22860
|
);
|
|
22327
22861
|
const parsed = JSON.parse(verdictsRaw);
|
|
@@ -22343,9 +22877,11 @@ async function complete(runId, opts) {
|
|
|
22343
22877
|
...durationSec != null ? { total_duration_sec: durationSec } : {},
|
|
22344
22878
|
...velocitySec != null ? { velocity_sec_per_step: velocitySec } : {}
|
|
22345
22879
|
};
|
|
22880
|
+
const { blocked: _previousBlock, ...manifestRest } = run.manifest;
|
|
22346
22881
|
run.manifest = {
|
|
22347
|
-
...
|
|
22348
|
-
models: updatedModels
|
|
22882
|
+
...manifestRest,
|
|
22883
|
+
models: updatedModels,
|
|
22884
|
+
...walkerSummary.blocked ? { blocked: walkerSummary.blocked } : {}
|
|
22349
22885
|
};
|
|
22350
22886
|
const assembled = await assembleRun(
|
|
22351
22887
|
run.projectRoot,
|
|
@@ -22373,16 +22909,16 @@ async function loadRun(runId, opts) {
|
|
|
22373
22909
|
if (!resolved) return null;
|
|
22374
22910
|
const { projectRoot, runDirPath } = resolved;
|
|
22375
22911
|
try {
|
|
22376
|
-
await
|
|
22912
|
+
await fs32.access(runDirPath);
|
|
22377
22913
|
} catch {
|
|
22378
22914
|
commandError(`Run not found: ${runId}`, opts);
|
|
22379
22915
|
return null;
|
|
22380
22916
|
}
|
|
22381
|
-
const manifestPath =
|
|
22917
|
+
const manifestPath = path32.join(runDirPath, "walkthrough.json");
|
|
22382
22918
|
let manifest;
|
|
22383
22919
|
try {
|
|
22384
22920
|
manifest = JSON.parse(
|
|
22385
|
-
await
|
|
22921
|
+
await fs32.readFile(manifestPath, "utf8")
|
|
22386
22922
|
);
|
|
22387
22923
|
} catch {
|
|
22388
22924
|
commandError(`No walkthrough.json found for run: ${runId}`, opts);
|
|
@@ -22394,8 +22930,8 @@ async function reportAlreadyCompleted(run, opts) {
|
|
|
22394
22930
|
const { runId, manifest } = run;
|
|
22395
22931
|
if (opts?.json) {
|
|
22396
22932
|
try {
|
|
22397
|
-
const raw = await
|
|
22398
|
-
|
|
22933
|
+
const raw = await fs32.readFile(
|
|
22934
|
+
path32.join(run.runDirPath, "canonical.json"),
|
|
22399
22935
|
"utf8"
|
|
22400
22936
|
);
|
|
22401
22937
|
console.log(raw.trim());
|
|
@@ -22415,18 +22951,18 @@ async function reportAlreadyCompleted(run, opts) {
|
|
|
22415
22951
|
return;
|
|
22416
22952
|
}
|
|
22417
22953
|
console.log();
|
|
22418
|
-
console.log(
|
|
22419
|
-
console.log(` status ${
|
|
22954
|
+
console.log(chalk35.bold(" Run: ") + chalk35.cyan(runId));
|
|
22955
|
+
console.log(` status ${chalk35.green("completed")}`);
|
|
22420
22956
|
if (manifest.completed_at) {
|
|
22421
|
-
console.log(` completed ${
|
|
22957
|
+
console.log(` completed ${chalk35.dim(manifest.completed_at)}`);
|
|
22422
22958
|
}
|
|
22423
22959
|
const viewUrl = await pushedRunUrl(run.projectRoot, runId);
|
|
22424
22960
|
if (viewUrl) {
|
|
22425
|
-
console.log(` view ${
|
|
22961
|
+
console.log(` view ${chalk35.underline(viewUrl)}`);
|
|
22426
22962
|
}
|
|
22427
22963
|
console.log();
|
|
22428
22964
|
console.log(
|
|
22429
|
-
|
|
22965
|
+
chalk35.dim(
|
|
22430
22966
|
" Already completed \u2014 nothing to do. Use --force to reassemble the report."
|
|
22431
22967
|
)
|
|
22432
22968
|
);
|
|
@@ -22442,13 +22978,13 @@ function reportStrictProvenanceFailure(audit, opts) {
|
|
|
22442
22978
|
}
|
|
22443
22979
|
console.error();
|
|
22444
22980
|
console.error(
|
|
22445
|
-
|
|
22981
|
+
chalk35.red(` ${symbols.error} Strict provenance audit failed:`)
|
|
22446
22982
|
);
|
|
22447
22983
|
for (const w of audit.warnings) {
|
|
22448
|
-
console.error(
|
|
22984
|
+
console.error(chalk35.red(` \u2022 ${w}`));
|
|
22449
22985
|
}
|
|
22450
22986
|
console.error(
|
|
22451
|
-
|
|
22987
|
+
chalk35.dim(
|
|
22452
22988
|
" Run was flagged as synthetic / scripted. Remove --strict-provenance to complete anyway."
|
|
22453
22989
|
)
|
|
22454
22990
|
);
|
|
@@ -22489,41 +23025,48 @@ function completeJsonPayload(canonical, extras) {
|
|
|
22489
23025
|
function printCompletionSummary(run, completedAt, assembled, extras, provenanceAudit) {
|
|
22490
23026
|
const { runId, projectRoot, manifest } = run;
|
|
22491
23027
|
const { canonical } = assembled;
|
|
22492
|
-
const count = (n) => n === 0 ?
|
|
22493
|
-
console.log(
|
|
22494
|
-
console.log(` scenario ${
|
|
22495
|
-
console.log(` url ${
|
|
23028
|
+
const count = (n) => n === 0 ? chalk35.dim("0") : chalk35.cyan(String(n));
|
|
23029
|
+
console.log(chalk35.bold(" Run: ") + chalk35.cyan(runId));
|
|
23030
|
+
console.log(` scenario ${chalk35.dim(manifest.scenario_slug)}`);
|
|
23031
|
+
console.log(` url ${chalk35.dim(manifest.target_url)}`);
|
|
22496
23032
|
if (manifest.environment) {
|
|
22497
|
-
console.log(` env ${
|
|
23033
|
+
console.log(` env ${chalk35.dim(manifest.environment)}`);
|
|
22498
23034
|
}
|
|
22499
23035
|
if (manifest.build) {
|
|
22500
|
-
console.log(` build ${
|
|
23036
|
+
console.log(` build ${chalk35.dim(formatBuild(manifest.build))}`);
|
|
22501
23037
|
}
|
|
22502
23038
|
if (manifest.viewport) {
|
|
22503
|
-
console.log(` device ${
|
|
23039
|
+
console.log(` device ${chalk35.dim(formatViewport(manifest.viewport))}`);
|
|
23040
|
+
}
|
|
23041
|
+
if (manifest.blocked) {
|
|
23042
|
+
console.log(
|
|
23043
|
+
` status ${chalk35.yellow("blocked")} ${chalk35.dim("\u2014 the walk could not start as set up")}`
|
|
23044
|
+
);
|
|
23045
|
+
console.log(` reason ${manifest.blocked.reason}`);
|
|
23046
|
+
} else {
|
|
23047
|
+
console.log(` status ${chalk35.green("completed")}`);
|
|
22504
23048
|
}
|
|
22505
|
-
console.log(` status ${chalk33.green("completed")}`);
|
|
22506
23049
|
console.log(` findings ${count(canonical.findings.length)}`);
|
|
22507
23050
|
console.log(` steps ${count(canonical.steps.length)}`);
|
|
22508
|
-
console.log(` started ${
|
|
22509
|
-
console.log(` completed ${
|
|
23051
|
+
console.log(` started ${chalk35.dim(manifest.created_at)}`);
|
|
23052
|
+
console.log(` completed ${chalk35.dim(completedAt)}`);
|
|
22510
23053
|
const models = manifest.models;
|
|
22511
23054
|
if (models?.velocity_sec_per_step != null) {
|
|
22512
23055
|
console.log(
|
|
22513
|
-
` velocity ${
|
|
23056
|
+
` velocity ${chalk35.cyan(`~${models.velocity_sec_per_step}s/step`)} ${chalk35.dim(`(${models.total_duration_sec}s total)`)}`
|
|
22514
23057
|
);
|
|
22515
23058
|
}
|
|
22516
23059
|
if (models && (models.walker || models.judge || models.matcher)) {
|
|
22517
23060
|
const w = models.walker_reported ? `${models.walker} (${models.walker_reported})` : models.walker ?? "fast";
|
|
22518
23061
|
const j = models.judge_reported ? `${models.judge} (${models.judge_reported})` : models.judge ?? "thorough";
|
|
22519
|
-
console.log(` models ${
|
|
23062
|
+
console.log(` models ${chalk35.dim(`walker: ${w} \xB7 judge: ${j}`)}`);
|
|
22520
23063
|
}
|
|
22521
23064
|
console.log();
|
|
22522
23065
|
console.log(
|
|
22523
|
-
` ${
|
|
23066
|
+
` ${chalk35.dim("canonical:")} ${chalk35.dim(path32.relative(projectRoot, assembled.canonicalPath))}`
|
|
22524
23067
|
);
|
|
22525
23068
|
console.log(
|
|
22526
|
-
` ${
|
|
23069
|
+
` ${chalk35.dim("report:")} ${chalk35.dim(path32.relative(projectRoot, assembled.reportPath))}`
|
|
22527
23070
|
);
|
|
22528
23071
|
if (extras.viewportWarning) {
|
|
22529
23072
|
console.log();
|
|
@@ -22531,25 +23074,36 @@ function printCompletionSummary(run, completedAt, assembled, extras, provenanceA
|
|
|
22531
23074
|
}
|
|
22532
23075
|
if (extras.codeWarning) {
|
|
22533
23076
|
console.log();
|
|
22534
|
-
console.log(` ${
|
|
23077
|
+
console.log(` ${chalk35.yellow("!")} ${extras.codeWarning}`);
|
|
22535
23078
|
}
|
|
22536
23079
|
if (extras.revisit) printRevisit(extras.revisit);
|
|
22537
23080
|
if (assembled.verification) printVerification(assembled.verification);
|
|
22538
23081
|
if (assembled.journeyDiff) printJourneyDiff(assembled.journeyDiff);
|
|
22539
23082
|
printProvenanceWarnings(provenanceAudit);
|
|
22540
23083
|
printCloudRunInfo(extras.cloudInfo);
|
|
23084
|
+
if (manifest.blocked) {
|
|
23085
|
+
console.log();
|
|
23086
|
+
console.log(
|
|
23087
|
+
` ${symbols.warning} ${chalk35.yellow("Not compared with earlier runs.")} Fix the setup (iamu set scenario ${manifest.scenario_slug} \u2026, or the persona's access) and run the scenario again.`
|
|
23088
|
+
);
|
|
23089
|
+
if (canonical.findings.length > 0) {
|
|
23090
|
+
console.log(
|
|
23091
|
+
` ${symbols.warning} ${chalk35.yellow(`Walk recorded ${canonical.findings.length} finding(s) before being blocked.`)} Because setup was incomplete, these findings will not be verified against future runs.`
|
|
23092
|
+
);
|
|
23093
|
+
}
|
|
23094
|
+
}
|
|
22541
23095
|
}
|
|
22542
23096
|
function printProvenanceWarnings(audit) {
|
|
22543
23097
|
if (audit.warnings.length === 0) return;
|
|
22544
23098
|
console.log();
|
|
22545
23099
|
console.log(
|
|
22546
|
-
` ${symbols.warning} ${
|
|
23100
|
+
` ${symbols.warning} ${chalk35.yellow("Step provenance audit flagged suspicious timing:")}`
|
|
22547
23101
|
);
|
|
22548
23102
|
for (const w of audit.warnings) {
|
|
22549
|
-
console.log(` ${
|
|
23103
|
+
console.log(` ${chalk35.dim("\u2022")} ${w}`);
|
|
22550
23104
|
}
|
|
22551
23105
|
console.log(
|
|
22552
|
-
|
|
23106
|
+
chalk35.dim(
|
|
22553
23107
|
" Run appears to be programmatically batch-generated rather than an interactive walkthrough."
|
|
22554
23108
|
)
|
|
22555
23109
|
);
|
|
@@ -22581,17 +23135,17 @@ function screenshotFitsViewport(width, expected) {
|
|
|
22581
23135
|
}
|
|
22582
23136
|
async function screenshotSizeWarning(runDirPath, viewport) {
|
|
22583
23137
|
if (!viewport) return null;
|
|
22584
|
-
const dir =
|
|
23138
|
+
const dir = path32.join(runDirPath, "screenshots");
|
|
22585
23139
|
let first;
|
|
22586
23140
|
try {
|
|
22587
|
-
first = (await
|
|
23141
|
+
first = (await fs32.readdir(dir)).filter((f) => f.toLowerCase().endsWith(".png")).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }))[0];
|
|
22588
23142
|
} catch {
|
|
22589
23143
|
return null;
|
|
22590
23144
|
}
|
|
22591
23145
|
if (!first) return null;
|
|
22592
23146
|
let size = null;
|
|
22593
23147
|
try {
|
|
22594
|
-
const handle = await
|
|
23148
|
+
const handle = await fs32.open(path32.join(dir, first), "r");
|
|
22595
23149
|
try {
|
|
22596
23150
|
const head = Buffer.alloc(24);
|
|
22597
23151
|
const { bytesRead } = await handle.read(head, 0, 24, 0);
|
|
@@ -22608,14 +23162,16 @@ async function screenshotSizeWarning(runDirPath, viewport) {
|
|
|
22608
23162
|
async function readWalkerSummary(runDirPath) {
|
|
22609
23163
|
try {
|
|
22610
23164
|
const data = JSON.parse(
|
|
22611
|
-
await
|
|
23165
|
+
await fs32.readFile(path32.join(runDirPath, "summary.json"), "utf8")
|
|
22612
23166
|
);
|
|
22613
23167
|
const strings = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && x.trim() !== "") : void 0;
|
|
22614
23168
|
const model = typeof data.model === "string" && data.model.trim() !== "" ? data.model.trim() : void 0;
|
|
23169
|
+
const reason = data.blocked?.reason;
|
|
22615
23170
|
return {
|
|
22616
23171
|
what_worked: strings(data.what_worked),
|
|
22617
23172
|
open_questions: strings(data.open_questions),
|
|
22618
|
-
model
|
|
23173
|
+
model,
|
|
23174
|
+
...typeof reason === "string" && reason.trim() !== "" ? { blocked: { reason: reason.trim() } } : {}
|
|
22619
23175
|
};
|
|
22620
23176
|
} catch {
|
|
22621
23177
|
return {};
|
|
@@ -22717,7 +23273,7 @@ function validateCanonicalWalkthrough(canonical) {
|
|
|
22717
23273
|
async function applyRecordedVerdicts(canonical, runDirPath) {
|
|
22718
23274
|
try {
|
|
22719
23275
|
const recorded = JSON.parse(
|
|
22720
|
-
await
|
|
23276
|
+
await fs32.readFile(path32.join(runDirPath, "verdicts.json"), "utf8")
|
|
22721
23277
|
);
|
|
22722
23278
|
if (Array.isArray(recorded.verdicts)) {
|
|
22723
23279
|
return applyVerdicts(canonical, recorded);
|
|
@@ -22727,7 +23283,7 @@ async function applyRecordedVerdicts(canonical, runDirPath) {
|
|
|
22727
23283
|
return canonical;
|
|
22728
23284
|
}
|
|
22729
23285
|
async function persistCanonical(runDirPath, canonical) {
|
|
22730
|
-
const canonicalPath =
|
|
23286
|
+
const canonicalPath = path32.join(runDirPath, "canonical.json");
|
|
22731
23287
|
await atomicWriteFile(
|
|
22732
23288
|
canonicalPath,
|
|
22733
23289
|
`${JSON.stringify(canonical, null, 2)}
|
|
@@ -22736,7 +23292,7 @@ async function persistCanonical(runDirPath, canonical) {
|
|
|
22736
23292
|
return canonicalPath;
|
|
22737
23293
|
}
|
|
22738
23294
|
async function persistReport(runDirPath, canonical) {
|
|
22739
|
-
const reportPath =
|
|
23295
|
+
const reportPath = path32.join(runDirPath, "report.md");
|
|
22740
23296
|
await atomicWriteFile(reportPath, renderCanonicalMarkdown(canonical));
|
|
22741
23297
|
return reportPath;
|
|
22742
23298
|
}
|
|
@@ -22748,7 +23304,7 @@ async function assembleRun(projectRoot, runId, manifest, completedAt) {
|
|
|
22748
23304
|
completedAt,
|
|
22749
23305
|
await readWalkerSummary(runDirPath)
|
|
22750
23306
|
);
|
|
22751
|
-
const verification = await compareWithBaseline(
|
|
23307
|
+
const verification = manifest.blocked ? null : await compareWithBaseline(
|
|
22752
23308
|
projectRoot,
|
|
22753
23309
|
manifest.scenario_slug,
|
|
22754
23310
|
runId,
|
|
@@ -22836,17 +23392,17 @@ function placeLabel(p) {
|
|
|
22836
23392
|
function printRevisit(r) {
|
|
22837
23393
|
const missed = r.places.filter((p) => !p.reached);
|
|
22838
23394
|
console.log();
|
|
22839
|
-
const head = ` ${
|
|
23395
|
+
const head = ` ${chalk35.bold("Re-check places:")} reached ${r.reached} of ${r.requested}`;
|
|
22840
23396
|
if (missed.length === 0) {
|
|
22841
|
-
console.log(`${head} ${
|
|
23397
|
+
console.log(`${head} ${chalk35.green("\u2713")}`);
|
|
22842
23398
|
return;
|
|
22843
23399
|
}
|
|
22844
23400
|
console.log(`${head} \u2014 not reached:`);
|
|
22845
23401
|
for (const p of missed) {
|
|
22846
|
-
console.log(` ${
|
|
23402
|
+
console.log(` ${chalk35.yellow("\u25CB")} ${placeLabel(p)}`);
|
|
22847
23403
|
}
|
|
22848
23404
|
console.log(
|
|
22849
|
-
|
|
23405
|
+
chalk35.dim(
|
|
22850
23406
|
" Fixes there stay awaiting a re-run until a walk gets there."
|
|
22851
23407
|
)
|
|
22852
23408
|
);
|
|
@@ -22854,7 +23410,7 @@ function printRevisit(r) {
|
|
|
22854
23410
|
function printVerification(verification) {
|
|
22855
23411
|
console.log();
|
|
22856
23412
|
console.log(
|
|
22857
|
-
|
|
23413
|
+
chalk35.bold(" Since this scenario's last run") + chalk35.dim(` (${verification.baseline_run_id.slice(0, 30)}\u2026)`) + chalk35.bold(":")
|
|
22858
23414
|
);
|
|
22859
23415
|
if (verification.resolved.length > 0) {
|
|
22860
23416
|
console.log(
|
|
@@ -22863,7 +23419,7 @@ function printVerification(verification) {
|
|
|
22863
23419
|
}
|
|
22864
23420
|
if (verification.not_observed.length > 0) {
|
|
22865
23421
|
console.log(
|
|
22866
|
-
` ${symbols.pending} ${verification.not_observed.length} not seen this run ${
|
|
23422
|
+
` ${symbols.pending} ${verification.not_observed.length} not seen this run ${chalk35.dim("(not evidence of a fix)")}`
|
|
22867
23423
|
);
|
|
22868
23424
|
}
|
|
22869
23425
|
if (verification.persists.length > 0) {
|
|
@@ -22905,14 +23461,14 @@ __export(match_exports, {
|
|
|
22905
23461
|
match: () => match,
|
|
22906
23462
|
matchRecord: () => matchRecord
|
|
22907
23463
|
});
|
|
22908
|
-
import { promises as
|
|
22909
|
-
import
|
|
22910
|
-
import
|
|
23464
|
+
import { promises as fs33 } from "fs";
|
|
23465
|
+
import path33 from "path";
|
|
23466
|
+
import chalk36 from "chalk";
|
|
22911
23467
|
async function completedManifest2(runDirPath, runId, opts) {
|
|
22912
23468
|
let manifest;
|
|
22913
23469
|
try {
|
|
22914
23470
|
manifest = JSON.parse(
|
|
22915
|
-
await
|
|
23471
|
+
await fs33.readFile(path33.join(runDirPath, "walkthrough.json"), "utf8")
|
|
22916
23472
|
);
|
|
22917
23473
|
} catch {
|
|
22918
23474
|
commandError(`Run not found: ${runId}`, opts);
|
|
@@ -22939,14 +23495,14 @@ async function match(runId, opts) {
|
|
|
22939
23495
|
runId,
|
|
22940
23496
|
manifest.scenario_slug
|
|
22941
23497
|
);
|
|
22942
|
-
const needed = input.current.length > 0 && (input.prior.length > 0 || (input.guidance ?? []).length > 0);
|
|
23498
|
+
const needed = !manifest.blocked && input.current.length > 0 && (input.prior.length > 0 || (input.guidance ?? []).length > 0);
|
|
22943
23499
|
const prompt = needed ? composeMatchPrompt(input) : null;
|
|
22944
|
-
const promptFilePath = opts?.promptFile ??
|
|
23500
|
+
const promptFilePath = opts?.promptFile ?? path33.join(runDirPath, "match_prompt.md");
|
|
22945
23501
|
if (prompt) {
|
|
22946
|
-
await
|
|
23502
|
+
await fs33.writeFile(promptFilePath, prompt, "utf8");
|
|
22947
23503
|
} else {
|
|
22948
23504
|
try {
|
|
22949
|
-
await
|
|
23505
|
+
await fs33.unlink(promptFilePath);
|
|
22950
23506
|
} catch {
|
|
22951
23507
|
}
|
|
22952
23508
|
}
|
|
@@ -22956,8 +23512,9 @@ async function match(runId, opts) {
|
|
|
22956
23512
|
{
|
|
22957
23513
|
...input,
|
|
22958
23514
|
needed,
|
|
22959
|
-
|
|
22960
|
-
|
|
23515
|
+
...manifest.blocked ? { blocked: manifest.blocked } : {},
|
|
23516
|
+
prompt_file: prompt ? path33.relative(ctx.projectRoot, promptFilePath) : null,
|
|
23517
|
+
prompt_file_abs: prompt ? path33.resolve(promptFilePath) : null,
|
|
22961
23518
|
match_prompt: prompt
|
|
22962
23519
|
},
|
|
22963
23520
|
null,
|
|
@@ -22968,7 +23525,7 @@ async function match(runId, opts) {
|
|
|
22968
23525
|
}
|
|
22969
23526
|
if (!prompt) {
|
|
22970
23527
|
console.log(
|
|
22971
|
-
|
|
23528
|
+
chalk36.dim(
|
|
22972
23529
|
" No earlier findings or tester guidance for this scenario to match against \u2014 nothing to do."
|
|
22973
23530
|
)
|
|
22974
23531
|
);
|
|
@@ -23000,7 +23557,7 @@ async function matchRecord(runId, opts) {
|
|
|
23000
23557
|
}
|
|
23001
23558
|
raw = Buffer.concat(chunks).toString("utf8");
|
|
23002
23559
|
} else {
|
|
23003
|
-
raw = await
|
|
23560
|
+
raw = await fs33.readFile(opts.file, "utf8");
|
|
23004
23561
|
}
|
|
23005
23562
|
answer = JSON.parse(raw);
|
|
23006
23563
|
} catch (err) {
|
|
@@ -23046,17 +23603,17 @@ async function matchRecord(runId, opts) {
|
|
|
23046
23603
|
}
|
|
23047
23604
|
console.log();
|
|
23048
23605
|
console.log(
|
|
23049
|
-
` ${
|
|
23606
|
+
` ${chalk36.green("\u2713")} Matched ${result.linked} finding${result.linked === 1 ? "" : "s"} to earlier ones, ${result.new} first seen, ${result.rechecked_fixed} earlier re-checked fixed`
|
|
23050
23607
|
);
|
|
23051
23608
|
if (result.suppressed > 0) {
|
|
23052
23609
|
console.log(
|
|
23053
|
-
` ${
|
|
23610
|
+
` ${chalk36.yellow("!")} ${result.suppressed} finding${result.suppressed === 1 ? "" : "s"} match tester guidance (flagged with suppressed_by_guidance \u2014 dismiss them in triage if the rule holds)`
|
|
23054
23611
|
);
|
|
23055
23612
|
}
|
|
23056
23613
|
if (verification) printVerification(verification);
|
|
23057
23614
|
console.log();
|
|
23058
23615
|
console.log(
|
|
23059
|
-
|
|
23616
|
+
chalk36.dim(" Run `iamu sync` to push the linked findings to the cloud.")
|
|
23060
23617
|
);
|
|
23061
23618
|
console.log();
|
|
23062
23619
|
}
|
|
@@ -23078,7 +23635,7 @@ __export(prune_exports, {
|
|
|
23078
23635
|
prune: () => prune
|
|
23079
23636
|
});
|
|
23080
23637
|
import { confirm, isCancel as isCancel5 } from "@clack/prompts";
|
|
23081
|
-
import
|
|
23638
|
+
import chalk37 from "chalk";
|
|
23082
23639
|
function formatBytes(bytes) {
|
|
23083
23640
|
if (bytes <= 0) return "0 B";
|
|
23084
23641
|
if (bytes < 1024) return `${bytes} B`;
|
|
@@ -23124,7 +23681,7 @@ async function prune(opts = {}) {
|
|
|
23124
23681
|
});
|
|
23125
23682
|
if (preview.prunedRuns.length === 0) {
|
|
23126
23683
|
console.log(
|
|
23127
|
-
|
|
23684
|
+
chalk37.dim(" No runs match the pruning criteria. Nothing to prune.")
|
|
23128
23685
|
);
|
|
23129
23686
|
return preview;
|
|
23130
23687
|
}
|
|
@@ -23133,7 +23690,7 @@ async function prune(opts = {}) {
|
|
|
23133
23690
|
message: `Prune ${mode} ${preview.prunedRuns.length} run(s) and reclaim ${formatBytes(preview.totalBytesReclaimed)}?`
|
|
23134
23691
|
});
|
|
23135
23692
|
if (isCancel5(confirmed) || !confirmed) {
|
|
23136
|
-
console.log(
|
|
23693
|
+
console.log(chalk37.yellow(" Pruning cancelled."));
|
|
23137
23694
|
return;
|
|
23138
23695
|
}
|
|
23139
23696
|
}
|
|
@@ -23144,14 +23701,14 @@ async function prune(opts = {}) {
|
|
|
23144
23701
|
}
|
|
23145
23702
|
if (result.dryRun) {
|
|
23146
23703
|
console.log(
|
|
23147
|
-
|
|
23704
|
+
chalk37.cyan(
|
|
23148
23705
|
`
|
|
23149
23706
|
[DRY RUN] Would prune ${result.prunedRuns.length} of ${result.totalRunsScanned} run(s):`
|
|
23150
23707
|
)
|
|
23151
23708
|
);
|
|
23152
23709
|
} else {
|
|
23153
23710
|
console.log(
|
|
23154
|
-
|
|
23711
|
+
chalk37.bold(
|
|
23155
23712
|
`
|
|
23156
23713
|
${symbols.success} Pruned ${result.prunedRuns.length} of ${result.totalRunsScanned} run(s):`
|
|
23157
23714
|
)
|
|
@@ -23159,18 +23716,18 @@ async function prune(opts = {}) {
|
|
|
23159
23716
|
}
|
|
23160
23717
|
if (result.prunedRuns.length === 0) {
|
|
23161
23718
|
console.log(
|
|
23162
|
-
|
|
23719
|
+
chalk37.dim(" No runs matched the pruning criteria. Nothing pruned.")
|
|
23163
23720
|
);
|
|
23164
23721
|
} else {
|
|
23165
23722
|
for (const run of result.prunedRuns) {
|
|
23166
23723
|
const mode = run.fullyDeleted ? "removed" : "screenshots purged";
|
|
23167
23724
|
const count = run.screenshotsDeleted > 0 ? `${run.screenshotsDeleted} screenshots, ` : "";
|
|
23168
23725
|
console.log(
|
|
23169
|
-
` ${
|
|
23726
|
+
` ${chalk37.dim("\u2022")} ${chalk37.bold(run.runId)} ${chalk37.dim(`(${run.scenarioSlug ?? "unknown scenario"})`)}: ${chalk37.yellow(formatBytes(run.bytesReclaimed))} (${count}${mode})`
|
|
23170
23727
|
);
|
|
23171
23728
|
}
|
|
23172
23729
|
console.log(
|
|
23173
|
-
|
|
23730
|
+
chalk37.dim(
|
|
23174
23731
|
`
|
|
23175
23732
|
Total reclaimed: ${formatBytes(result.totalBytesReclaimed)} (${result.totalScreenshotsDeleted} screenshots deleted)
|
|
23176
23733
|
`
|
|
@@ -23192,7 +23749,7 @@ var init_prune2 = __esm({
|
|
|
23192
23749
|
init_context4();
|
|
23193
23750
|
import { existsSync as existsSync13 } from "fs";
|
|
23194
23751
|
import { resolve as resolve3 } from "path";
|
|
23195
|
-
import
|
|
23752
|
+
import chalk40 from "chalk";
|
|
23196
23753
|
import { Command } from "commander";
|
|
23197
23754
|
|
|
23198
23755
|
// src/commands/register-cloud.ts
|
|
@@ -23366,9 +23923,9 @@ function registerProjectCommands(program2) {
|
|
|
23366
23923
|
"after",
|
|
23367
23924
|
"\nExamples:\n $ iamu validate --all\n $ iamu validate --all --fix --dry-run\n $ iamu validate iamu/personas/maria.md"
|
|
23368
23925
|
).action(
|
|
23369
|
-
async (
|
|
23926
|
+
async (path34, opts) => {
|
|
23370
23927
|
const { validate: validate2 } = await Promise.resolve().then(() => (init_validate(), validate_exports));
|
|
23371
|
-
await validate2(
|
|
23928
|
+
await validate2(path34, opts);
|
|
23372
23929
|
}
|
|
23373
23930
|
);
|
|
23374
23931
|
program2.command("update").helpGroup("Project:").description("Update and repair agent skills and prompts to latest version").option(
|
|
@@ -23420,6 +23977,51 @@ function registerProjectCommands(program2) {
|
|
|
23420
23977
|
await newEntity2(type2, opts);
|
|
23421
23978
|
}
|
|
23422
23979
|
);
|
|
23980
|
+
program2.command("set <type> <slug>").helpGroup("Project:").description(
|
|
23981
|
+
"Change how an entity is staged: a scenario's start path, target and starting state, a persona's access key, a capability's routes (scenario, persona, capability)"
|
|
23982
|
+
).option(
|
|
23983
|
+
"--entry <path>",
|
|
23984
|
+
"Where the walk starts, as a path on the product (scenario)"
|
|
23985
|
+
).option("--no-entry", "Clear the start path (scenario)").option("--target <url>", "Default target URL (scenario)").option("--no-target", "Clear the default target URL (scenario)").option(
|
|
23986
|
+
"--state-description <text>",
|
|
23987
|
+
"What the world must contain, in domain words (scenario)"
|
|
23988
|
+
).option(
|
|
23989
|
+
"--state-approximation <text>",
|
|
23990
|
+
"How the walker picks the closest record when the exact one is missing (scenario)"
|
|
23991
|
+
).option("--no-state", "Clear the starting state (scenario)").option(
|
|
23992
|
+
"--access <key>",
|
|
23993
|
+
"Account, role or switcher key this persona uses (persona)"
|
|
23994
|
+
).option("--no-access", "Clear the access key (persona)").option(
|
|
23995
|
+
"--paths <paths>",
|
|
23996
|
+
"Route prefixes, comma-separated; replaces the list (capability)"
|
|
23997
|
+
).option("--no-paths", "Clear route prefixes (capability)").option(
|
|
23998
|
+
"--add-path <path>",
|
|
23999
|
+
"Add a route prefix (capability, repeatable)",
|
|
24000
|
+
(val, prev = []) => [...prev, val]
|
|
24001
|
+
).option(
|
|
24002
|
+
"--remove-path <path>",
|
|
24003
|
+
"Remove a route prefix (capability, repeatable)",
|
|
24004
|
+
(val, prev = []) => [...prev, val]
|
|
24005
|
+
).option("--area <name>", "Visual grouping category (capability)").option("--no-area", "Clear the area (capability)").option(
|
|
24006
|
+
"--file <path>",
|
|
24007
|
+
"JSON patch file to apply staging fields (or - for stdin)"
|
|
24008
|
+
).option("--json", "Output as JSON").addHelpText(
|
|
24009
|
+
"after",
|
|
24010
|
+
'\nWhat a scenario is about (narrative, tension, human tests, brief) is not set here.\n\nExamples:\n $ iamu set scenario maria--handoff --entry /incidents --state-description "Five open incidents at one site"\n $ iamu set persona maria --access coordinator@site-a.test\n $ iamu set capability team-access --add-path /team\n $ iamu set scenario maria--handoff --file patch.json'
|
|
24011
|
+
).action(
|
|
24012
|
+
async (type2, slug, opts) => {
|
|
24013
|
+
const { setEntity: setEntity2 } = await Promise.resolve().then(() => (init_set_entity(), set_entity_exports));
|
|
24014
|
+
await setEntity2(type2, slug, opts);
|
|
24015
|
+
}
|
|
24016
|
+
);
|
|
24017
|
+
program2.command("edit <type> <slug>").helpGroup("Project:").description(
|
|
24018
|
+
"Open an entity file in your editor ($VISUAL or $EDITOR) and validate on exit"
|
|
24019
|
+
).option("--json", "Output as JSON").action(
|
|
24020
|
+
async (type2, slug, opts = {}) => {
|
|
24021
|
+
const { editEntity: editEntity2 } = await Promise.resolve().then(() => (init_edit_entity(), edit_entity_exports));
|
|
24022
|
+
await editEntity2(type2, slug, opts);
|
|
24023
|
+
}
|
|
24024
|
+
);
|
|
23423
24025
|
const explore = program2.command("explore").helpGroup("Project:").description(
|
|
23424
24026
|
"Map the running product with a read-only crawl; compare the map with runs (coverage) or with another map (diff)"
|
|
23425
24027
|
);
|
|
@@ -23621,14 +24223,17 @@ function registerWalkthroughCommands(program2) {
|
|
|
23621
24223
|
}
|
|
23622
24224
|
);
|
|
23623
24225
|
program2.command("summary [run-id]").helpGroup("Walkthrough:").description(
|
|
23624
|
-
"Record walker summary (what worked, open questions) atomically"
|
|
24226
|
+
"Record walker summary (what worked, open questions, or that the walk was blocked) atomically"
|
|
23625
24227
|
).option("--run <run-id>", "Run ID (or pass as positional argument)").option(
|
|
23626
24228
|
"--worked <note...>",
|
|
23627
24229
|
"Things that worked well (can be specified multiple times)"
|
|
23628
24230
|
).option(
|
|
23629
24231
|
"--question <note...>",
|
|
23630
24232
|
"Open questions that arose (can be specified multiple times)"
|
|
23631
|
-
).option("--model <name>", "Model identifier (e.g. flash, haiku)").option(
|
|
24233
|
+
).option("--model <name>", "Model identifier (e.g. flash, haiku)").option(
|
|
24234
|
+
"--blocked <reason>",
|
|
24235
|
+
"The walk could not start as set up (persona unreachable, world missing): what was missing"
|
|
24236
|
+
).option("--no-blocked", "Clear the blocked status (if previously set)").option("--file <path>", "Path to summary JSON file (or - for stdin)").option("--json", "Output as JSON").action(
|
|
23632
24237
|
async (runIdArg, opts) => {
|
|
23633
24238
|
const { summaryCommand: summaryCommand2 } = await Promise.resolve().then(() => (init_summary(), summary_exports));
|
|
23634
24239
|
await summaryCommand2(runIdArg, opts);
|
|
@@ -23705,23 +24310,23 @@ init_skills();
|
|
|
23705
24310
|
|
|
23706
24311
|
// src/ui/panic.ts
|
|
23707
24312
|
init_logger();
|
|
23708
|
-
import
|
|
24313
|
+
import chalk38 from "chalk";
|
|
23709
24314
|
var handlersRegistered = false;
|
|
23710
24315
|
var customSigintHandler = null;
|
|
23711
24316
|
function logFatalAndExit(title, errOrReason) {
|
|
23712
24317
|
process.stderr.write("\x1B[?25h");
|
|
23713
24318
|
if (isVerbose()) {
|
|
23714
|
-
console.error(
|
|
24319
|
+
console.error(chalk38.red(`
|
|
23715
24320
|
${title}:`));
|
|
23716
24321
|
console.error(
|
|
23717
24322
|
errOrReason instanceof Error ? errOrReason.stack || errOrReason : errOrReason
|
|
23718
24323
|
);
|
|
23719
24324
|
} else {
|
|
23720
24325
|
const msg = errOrReason instanceof Error ? errOrReason.message : String(errOrReason);
|
|
23721
|
-
console.error(
|
|
24326
|
+
console.error(chalk38.red(`
|
|
23722
24327
|
Fatal Error: ${msg}`));
|
|
23723
24328
|
console.error(
|
|
23724
|
-
|
|
24329
|
+
chalk38.dim(
|
|
23725
24330
|
" Run with --verbose or DEBUG=1 to view the full stack trace.\n"
|
|
23726
24331
|
)
|
|
23727
24332
|
);
|
|
@@ -23740,7 +24345,7 @@ function setupProcessHandlers() {
|
|
|
23740
24345
|
}
|
|
23741
24346
|
process.exit(130);
|
|
23742
24347
|
} else {
|
|
23743
|
-
console.error(
|
|
24348
|
+
console.error(chalk38.dim("\n Operation cancelled."));
|
|
23744
24349
|
process.exit(130);
|
|
23745
24350
|
}
|
|
23746
24351
|
});
|
|
@@ -23766,7 +24371,7 @@ function setupProcessHandlers() {
|
|
|
23766
24371
|
import { mkdir as mkdir7, readFile as readFile8, writeFile as writeFile5 } from "fs/promises";
|
|
23767
24372
|
import { homedir as homedir4 } from "os";
|
|
23768
24373
|
import { join as join10 } from "path";
|
|
23769
|
-
import
|
|
24374
|
+
import chalk39 from "chalk";
|
|
23770
24375
|
var CACHE_FILE = join10(homedir4(), ".iamu", "update-check.json");
|
|
23771
24376
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
23772
24377
|
function isSemverGreater(latest, current) {
|
|
@@ -23819,7 +24424,7 @@ async function checkForUpdate(currentVersion) {
|
|
|
23819
24424
|
function printUpdateNotice(currentVersion, latestVersion) {
|
|
23820
24425
|
console.error();
|
|
23821
24426
|
console.error(
|
|
23822
|
-
` ${
|
|
24427
|
+
` ${chalk39.yellow("Update available:")} ${chalk39.dim(currentVersion)} \u2192 ${chalk39.green(latestVersion)} ${chalk39.dim("(run: npm i -g @iamu-dev/iamu)")}`
|
|
23823
24428
|
);
|
|
23824
24429
|
}
|
|
23825
24430
|
|
|
@@ -23856,7 +24461,7 @@ var program = new Command().name("iamu").description(
|
|
|
23856
24461
|
}
|
|
23857
24462
|
}
|
|
23858
24463
|
if (root.color === false || process.env.NO_COLOR) {
|
|
23859
|
-
|
|
24464
|
+
chalk40.level = 0;
|
|
23860
24465
|
}
|
|
23861
24466
|
if (root.verbose) {
|
|
23862
24467
|
process.env.IAMU_VERBOSE = "1";
|