@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/index.js
CHANGED
|
@@ -385,7 +385,7 @@ var init_context = __esm({
|
|
|
385
385
|
jurisdiction: z7.string().max(1024).nullish().describe("Country / region the regulations and policies apply in. Example: 'Sweden', 'EU', 'United States'."),
|
|
386
386
|
architecture: z7.string().max(8192).nullish().describe("High-level architecture notes for the software. Brief is fine \u2014 this is orientation, not spec."),
|
|
387
387
|
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."),
|
|
388
|
-
access: z7.string().max(4096).nullish().describe(
|
|
388
|
+
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."),
|
|
389
389
|
governed_by: z7.array(contextGovernedBySchema).max(64).optional().describe("Regulations / standards / policies the domain is governed by, ordered roughly by authority."),
|
|
390
390
|
actors: z7.array(contextActorSchema).max(64).optional().describe("Key domain roles + their responsibilities."),
|
|
391
391
|
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.")
|
|
@@ -926,6 +926,7 @@ var init_testability = __esm({
|
|
|
926
926
|
target_url: z17.string().url().describe("Target URL where the product is hosted or served locally."),
|
|
927
927
|
dev_command: z17.string().max(1e3).optional().describe("Dev script or startup command to boot the app locally."),
|
|
928
928
|
auth_strategy: authStrategyEnum.or(z17.string().min(1).max(100)).optional().describe("How personas authenticate in test environments."),
|
|
929
|
+
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.`),
|
|
929
930
|
harnesses: z17.array(z17.string().min(1).max(500)).max(64).optional().describe("Mocking frameworks, seed scripts, or test harnesses present."),
|
|
930
931
|
untestable_boundaries: z17.array(z17.string().min(1).max(1024)).max(128).optional().describe("External services, third-party APIs, or surfaces forbidden to autonomous walkers.")
|
|
931
932
|
});
|
|
@@ -1197,6 +1198,9 @@ var init_walkthrough_bundle = __esm({
|
|
|
1197
1198
|
what_worked: z20.array(z20.string().max(4096)).max(64).optional(),
|
|
1198
1199
|
open_questions: z20.array(z20.string().max(4096)).max(64).optional(),
|
|
1199
1200
|
verification: bundleVerificationSchema.optional().describe("Since-last-run delta from canonical.json \u2014 which earlier findings persist, were re-checked fixed, were not seen, and which are new. Persists to walkthroughs.verification."),
|
|
1201
|
+
blocked: z20.object({
|
|
1202
|
+
reason: z20.string().min(1).max(2048)
|
|
1203
|
+
}).optional().describe("Set when the walk could not start as set up (missing preconditions)."),
|
|
1200
1204
|
build: buildMetadataSchema.optional().describe("Build of the product under test (git SHA, branch, CI run). Absent for runs started before `start-run` recorded it."),
|
|
1201
1205
|
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`)."),
|
|
1202
1206
|
/** Holistic UX quality score from UX review pass. Present only when `lenses` includes "ux". */
|
|
@@ -1470,7 +1474,12 @@ var init_walkthrough_canonical = __esm({
|
|
|
1470
1474
|
human_tests: z21.array(canonicalHumanTestSchema),
|
|
1471
1475
|
human_test_overall: canonicalHumanTestOverallSchema,
|
|
1472
1476
|
errors: z21.array(canonicalErrorSchema),
|
|
1473
|
-
cloud_mirror_state: cloudMirrorStateSchema
|
|
1477
|
+
cloud_mirror_state: cloudMirrorStateSchema,
|
|
1478
|
+
/**
|
|
1479
|
+
* The walk could not start as set up (the walker's `iamu summary
|
|
1480
|
+
* --blocked`). Its findings say nothing about earlier ones.
|
|
1481
|
+
*/
|
|
1482
|
+
blocked: z21.object({ reason: z21.string() }).passthrough().optional()
|
|
1474
1483
|
}).passthrough();
|
|
1475
1484
|
}
|
|
1476
1485
|
});
|
|
@@ -6570,6 +6579,8 @@ async function readRunEvidence(projectRoot) {
|
|
|
6570
6579
|
continue;
|
|
6571
6580
|
if (manifest.status !== "completed")
|
|
6572
6581
|
continue;
|
|
6582
|
+
if (manifest.blocked)
|
|
6583
|
+
continue;
|
|
6573
6584
|
const canonical = await readJson(path8.join(dir, "canonical.json"));
|
|
6574
6585
|
const matches = await readMatches(projectRoot, runId);
|
|
6575
6586
|
const declared = typeof manifest.started_at === "string" ? manifest.started_at : typeof manifest.created_at === "string" ? manifest.created_at : null;
|
|
@@ -7385,7 +7396,8 @@ async function buildCanonical(projectRoot, manifest, completedAt, inputs) {
|
|
|
7385
7396
|
// renderers + judge agents can surface them without losing signal.
|
|
7386
7397
|
// Consumers MUST preserve unknown keys (per design D3).
|
|
7387
7398
|
...inputs.what_worked && inputs.what_worked.length > 0 ? { what_worked: inputs.what_worked } : {},
|
|
7388
|
-
...inputs.open_questions && inputs.open_questions.length > 0 ? { open_questions: inputs.open_questions } : {}
|
|
7399
|
+
...inputs.open_questions && inputs.open_questions.length > 0 ? { open_questions: inputs.open_questions } : {},
|
|
7400
|
+
...manifest.blocked ? { blocked: manifest.blocked } : {}
|
|
7389
7401
|
};
|
|
7390
7402
|
}
|
|
7391
7403
|
async function compareWithBaseline(projectRoot, scenarioSlug, currentRunId, currentFindings) {
|
|
@@ -7400,7 +7412,7 @@ async function compareWithBaseline(projectRoot, scenarioSlug, currentRunId, curr
|
|
|
7400
7412
|
const mPath = path9.join(runDir(projectRoot, rid), "walkthrough.json");
|
|
7401
7413
|
const raw = await fs12.readFile(mPath, "utf8");
|
|
7402
7414
|
const m = JSON.parse(raw);
|
|
7403
|
-
if (m.scenario_slug === scenarioSlug && m.status === "completed") {
|
|
7415
|
+
if (m.scenario_slug === scenarioSlug && m.status === "completed" && !m.blocked) {
|
|
7404
7416
|
baselineRunId = rid;
|
|
7405
7417
|
baselineManifest = m;
|
|
7406
7418
|
break;
|
|
@@ -7509,6 +7521,13 @@ function renderCanonicalMarkdown(canonical) {
|
|
|
7509
7521
|
"",
|
|
7510
7522
|
`# Walkthrough report: ${canonical.scenario.slug}`,
|
|
7511
7523
|
"",
|
|
7524
|
+
...canonical.blocked ? [
|
|
7525
|
+
"> **Blocked \u2014 the walk could not start as it was set up.**",
|
|
7526
|
+
`> ${canonical.blocked.reason}`,
|
|
7527
|
+
">",
|
|
7528
|
+
"> 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.",
|
|
7529
|
+
""
|
|
7530
|
+
] : [],
|
|
7512
7531
|
`- Run ID: ${canonical.run_id}`,
|
|
7513
7532
|
`- Target URL: ${canonical.target.url}`
|
|
7514
7533
|
];
|
|
@@ -7618,7 +7637,11 @@ function renderCanonicalMarkdown(canonical) {
|
|
|
7618
7637
|
for (const ht of canonical.human_tests) {
|
|
7619
7638
|
lines.push("", `- **${ht.question}**`);
|
|
7620
7639
|
if (ht.verdict === null) {
|
|
7621
|
-
|
|
7640
|
+
if (canonical.blocked) {
|
|
7641
|
+
lines.push(` - Verdict: _skipped (walk blocked)_`);
|
|
7642
|
+
} else {
|
|
7643
|
+
lines.push(` - Verdict: _pending judge_ (walker does not self-grade)`);
|
|
7644
|
+
}
|
|
7622
7645
|
} else {
|
|
7623
7646
|
lines.push(` - Verdict: **${ht.verdict}**`);
|
|
7624
7647
|
if (ht.reasoning)
|
|
@@ -7628,7 +7651,11 @@ function renderCanonicalMarkdown(canonical) {
|
|
|
7628
7651
|
}
|
|
7629
7652
|
lines.push("", "## Human test overall", "", `> ${canonical.human_test_overall.question}`);
|
|
7630
7653
|
if (canonical.human_test_overall.answer === null) {
|
|
7631
|
-
|
|
7654
|
+
if (canonical.blocked) {
|
|
7655
|
+
lines.push("", "_Skipped (walk blocked)._");
|
|
7656
|
+
} else {
|
|
7657
|
+
lines.push("", "_Pending judge stage._");
|
|
7658
|
+
}
|
|
7632
7659
|
} else {
|
|
7633
7660
|
lines.push("", canonical.human_test_overall.answer);
|
|
7634
7661
|
if (canonical.human_test_overall.reasoning)
|
|
@@ -7799,10 +7826,15 @@ async function readCanonicalExtras(projectRoot, runId) {
|
|
|
7799
7826
|
const data = JSON.parse(await fs13.readFile(path10.join(runDir(projectRoot, runId), "canonical.json"), "utf-8"));
|
|
7800
7827
|
const strings = (v) => Array.isArray(v) && v.every((x) => typeof x === "string") ? v : void 0;
|
|
7801
7828
|
const verification = bundleVerificationSchema.safeParse(data.verification);
|
|
7829
|
+
let blocked;
|
|
7830
|
+
if (typeof data.blocked === "object" && data.blocked !== null && "reason" in data.blocked && typeof data.blocked.reason === "string" && data.blocked.reason.trim().length > 0) {
|
|
7831
|
+
blocked = { reason: data.blocked.reason.trim() };
|
|
7832
|
+
}
|
|
7802
7833
|
return {
|
|
7803
7834
|
what_worked: strings(data.what_worked),
|
|
7804
7835
|
open_questions: strings(data.open_questions),
|
|
7805
|
-
verification: verification.success ? verification.data : void 0
|
|
7836
|
+
verification: verification.success ? verification.data : void 0,
|
|
7837
|
+
...blocked ? { blocked } : {}
|
|
7806
7838
|
};
|
|
7807
7839
|
} catch {
|
|
7808
7840
|
return {};
|
|
@@ -7899,6 +7931,10 @@ async function assembleWalkthroughBundle(projectRoot, runId) {
|
|
|
7899
7931
|
}
|
|
7900
7932
|
} : {}
|
|
7901
7933
|
} : void 0;
|
|
7934
|
+
const canonicalExtras = await readCanonicalExtras(projectRoot, runId);
|
|
7935
|
+
const rawBlocked = manifest.blocked;
|
|
7936
|
+
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;
|
|
7937
|
+
const blocked = canonicalExtras.blocked ?? manifestBlocked;
|
|
7902
7938
|
const bundle = {
|
|
7903
7939
|
manifest: {
|
|
7904
7940
|
run_id: cloudRunUuid,
|
|
@@ -7916,7 +7952,8 @@ async function assembleWalkthroughBundle(projectRoot, runId) {
|
|
|
7916
7952
|
judge_meta: judgeMeta,
|
|
7917
7953
|
...manifest.models ? { models: manifest.models } : {},
|
|
7918
7954
|
...buildFields(manifest),
|
|
7919
|
-
...
|
|
7955
|
+
...canonicalExtras,
|
|
7956
|
+
...blocked ? { blocked } : {}
|
|
7920
7957
|
},
|
|
7921
7958
|
steps: bundleSteps,
|
|
7922
7959
|
findings: bundleFindings,
|
|
@@ -10896,6 +10933,26 @@ function summarizeCapabilityCoverage(surfaces, capabilities, outlines = []) {
|
|
|
10896
10933
|
unassigned_surfaces: unassigned
|
|
10897
10934
|
};
|
|
10898
10935
|
}
|
|
10936
|
+
function groupUnassignedByPrefix(surfaces) {
|
|
10937
|
+
const groups = /* @__PURE__ */ new Map();
|
|
10938
|
+
for (const s of surfaces) {
|
|
10939
|
+
const pathOnly = s.place.split(/[?#]/)[0] ?? "";
|
|
10940
|
+
const first = pathOnly.split("/").filter(Boolean)[0];
|
|
10941
|
+
const prefix = first ? `/${first}` : "/";
|
|
10942
|
+
let g = groups.get(prefix);
|
|
10943
|
+
if (!g) {
|
|
10944
|
+
g = { prefix, surfaces: 0, reached: 0, examples: [] };
|
|
10945
|
+
groups.set(prefix, g);
|
|
10946
|
+
}
|
|
10947
|
+
g.surfaces += 1;
|
|
10948
|
+
if (s.status === "reached")
|
|
10949
|
+
g.reached += 1;
|
|
10950
|
+
if (g.examples.length < 3 && !g.examples.includes(pathOnly)) {
|
|
10951
|
+
g.examples.push(pathOnly);
|
|
10952
|
+
}
|
|
10953
|
+
}
|
|
10954
|
+
return [...groups.values()].sort((a, b) => b.surfaces - a.surfaces || a.prefix.localeCompare(b.prefix));
|
|
10955
|
+
}
|
|
10899
10956
|
async function readCapabilityCoverage(projectRoot, coverage) {
|
|
10900
10957
|
const [capsResult, outlinesResult] = await Promise.all([
|
|
10901
10958
|
listEntities(projectRoot, "capability"),
|
|
@@ -11433,7 +11490,7 @@ function buildPersonaFrontmatter(row, slug) {
|
|
|
11433
11490
|
};
|
|
11434
11491
|
return { frontmatter, body: resolveBody(row, "") };
|
|
11435
11492
|
}
|
|
11436
|
-
function buildScenarioFrontmatter(row, personaSlug, outlineSlug) {
|
|
11493
|
+
function buildScenarioFrontmatter(row, personaSlug, outlineSlug, localFrontmatter) {
|
|
11437
11494
|
const slug = `${personaSlug}--${outlineSlug}`;
|
|
11438
11495
|
const frontmatter = {
|
|
11439
11496
|
id: row.id,
|
|
@@ -11444,6 +11501,8 @@ function buildScenarioFrontmatter(row, personaSlug, outlineSlug) {
|
|
|
11444
11501
|
// writing null wiped the local value on every pull.
|
|
11445
11502
|
..."targetUrl" in row ? { target_url: row.targetUrl ?? null } : {},
|
|
11446
11503
|
..."entryPath" in row ? { entry_path: row.entryPath ?? null } : {},
|
|
11504
|
+
..."reviews" in row && Array.isArray(row.reviews) ? { reviews: row.reviews } : localFrontmatter?.reviews !== void 0 ? { reviews: localFrontmatter.reviews } : {},
|
|
11505
|
+
..."state" in row && row.state !== void 0 ? { state: row.state ?? null } : localFrontmatter?.state !== void 0 ? { state: localFrontmatter.state } : {},
|
|
11447
11506
|
baseline_score: row.baselineScore ?? null,
|
|
11448
11507
|
current_score: row.currentScore ?? null,
|
|
11449
11508
|
target_score: row.targetScore ?? 95,
|
|
@@ -11535,6 +11594,7 @@ async function loadLocalIndex(projectRoot, type2) {
|
|
|
11535
11594
|
const byId = /* @__PURE__ */ new Map();
|
|
11536
11595
|
const bySlug = /* @__PURE__ */ new Map();
|
|
11537
11596
|
const bodyById = /* @__PURE__ */ new Map();
|
|
11597
|
+
const frontmatterById = /* @__PURE__ */ new Map();
|
|
11538
11598
|
const [result, baseById, lastPushById] = await Promise.all([
|
|
11539
11599
|
listEntities(projectRoot, type2),
|
|
11540
11600
|
readBaseHashes(projectRoot),
|
|
@@ -11546,9 +11606,10 @@ async function loadLocalIndex(projectRoot, type2) {
|
|
|
11546
11606
|
byId.set(fm.id, fm.slug);
|
|
11547
11607
|
bySlug.set(fm.slug, fm.id);
|
|
11548
11608
|
bodyById.set(fm.id, entity.body);
|
|
11609
|
+
frontmatterById.set(fm.id, entity.frontmatter);
|
|
11549
11610
|
}
|
|
11550
11611
|
}
|
|
11551
|
-
return { byId, bySlug, bodyById, baseById, lastPushById };
|
|
11612
|
+
return { byId, bySlug, bodyById, frontmatterById, baseById, lastPushById };
|
|
11552
11613
|
}
|
|
11553
11614
|
async function classifyExisting(projectRoot, type2, cloudId, localSlug, targetSlug, cloudVersion, localIndex) {
|
|
11554
11615
|
const base = localIndex.baseById[cloudId];
|
|
@@ -11889,7 +11950,8 @@ async function pullScenarios(projectRoot, cloud, projectId, outlineIdToSlug, per
|
|
|
11889
11950
|
});
|
|
11890
11951
|
continue;
|
|
11891
11952
|
}
|
|
11892
|
-
const
|
|
11953
|
+
const localFm = localIndex.frontmatterById.get(row.id);
|
|
11954
|
+
const { frontmatter, body, slug } = buildScenarioFrontmatter(row, personaSlug, outlineSlug, localFm);
|
|
11893
11955
|
await applyEntityPullOutcome(projectRoot, "scenario", row, slug, frontmatter, body, localIndex, mode, result.pulls.scenario, batch);
|
|
11894
11956
|
}
|
|
11895
11957
|
await finalizePullBatch(projectRoot, batch);
|
|
@@ -12401,15 +12463,15 @@ function resolveApiUrl(configured) {
|
|
|
12401
12463
|
assertSecureApiUrl(url, configured ? "API URL" : "IAMU_API_URL");
|
|
12402
12464
|
return url;
|
|
12403
12465
|
}
|
|
12404
|
-
function parseStore(raw,
|
|
12466
|
+
function parseStore(raw, path34) {
|
|
12405
12467
|
let parsed;
|
|
12406
12468
|
try {
|
|
12407
12469
|
parsed = JSON.parse(raw);
|
|
12408
12470
|
} catch (err) {
|
|
12409
|
-
throw new CredentialsFileError(
|
|
12471
|
+
throw new CredentialsFileError(path34, "not valid JSON", err);
|
|
12410
12472
|
}
|
|
12411
12473
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
12412
|
-
throw new CredentialsFileError(
|
|
12474
|
+
throw new CredentialsFileError(path34, "expected a JSON object");
|
|
12413
12475
|
}
|
|
12414
12476
|
return parsed;
|
|
12415
12477
|
}
|
|
@@ -12417,26 +12479,26 @@ function isNotFound(err) {
|
|
|
12417
12479
|
return typeof err === "object" && err !== null && err.code === "ENOENT";
|
|
12418
12480
|
}
|
|
12419
12481
|
async function readCredentialsStore() {
|
|
12420
|
-
const
|
|
12482
|
+
const path34 = getCredentialsPath();
|
|
12421
12483
|
let raw;
|
|
12422
12484
|
try {
|
|
12423
|
-
raw = await readFile(
|
|
12485
|
+
raw = await readFile(path34, "utf-8");
|
|
12424
12486
|
} catch (err) {
|
|
12425
12487
|
if (isNotFound(err)) return {};
|
|
12426
12488
|
warnUnreadableStoreOnce(err);
|
|
12427
12489
|
throw err;
|
|
12428
12490
|
}
|
|
12429
12491
|
try {
|
|
12430
|
-
return parseStore(raw,
|
|
12492
|
+
return parseStore(raw, path34);
|
|
12431
12493
|
} catch (err) {
|
|
12432
12494
|
warnUnreadableStoreOnce(err);
|
|
12433
12495
|
throw err;
|
|
12434
12496
|
}
|
|
12435
12497
|
}
|
|
12436
12498
|
async function writeCredentialsStore(store) {
|
|
12437
|
-
const
|
|
12438
|
-
await mkdir(dirname(
|
|
12439
|
-
await atomicWriteFile(
|
|
12499
|
+
const path34 = getCredentialsPath();
|
|
12500
|
+
await mkdir(dirname(path34), { recursive: true, mode: 448 });
|
|
12501
|
+
await atomicWriteFile(path34, `${JSON.stringify(store, null, 2)}
|
|
12440
12502
|
`, {
|
|
12441
12503
|
mode: 384
|
|
12442
12504
|
});
|
|
@@ -12456,16 +12518,16 @@ function resolveApiKey(apiUrl) {
|
|
|
12456
12518
|
} catch {
|
|
12457
12519
|
return null;
|
|
12458
12520
|
}
|
|
12459
|
-
const
|
|
12521
|
+
const path34 = getCredentialsPath();
|
|
12460
12522
|
let raw;
|
|
12461
12523
|
try {
|
|
12462
|
-
raw = readFileSync(
|
|
12524
|
+
raw = readFileSync(path34, "utf-8");
|
|
12463
12525
|
} catch (err) {
|
|
12464
12526
|
if (!isNotFound(err)) warnUnreadableStoreOnce(err);
|
|
12465
12527
|
return null;
|
|
12466
12528
|
}
|
|
12467
12529
|
try {
|
|
12468
|
-
const entry = parseStore(raw,
|
|
12530
|
+
const entry = parseStore(raw, path34)[url];
|
|
12469
12531
|
if (typeof entry !== "object" || entry === null) return null;
|
|
12470
12532
|
const token = entry.token;
|
|
12471
12533
|
return typeof token === "string" && token ? token : null;
|
|
@@ -12504,12 +12566,12 @@ var init_credentials = __esm({
|
|
|
12504
12566
|
* @param reason - What is wrong with it.
|
|
12505
12567
|
* @param cause - Underlying parse error, if any.
|
|
12506
12568
|
*/
|
|
12507
|
-
constructor(
|
|
12569
|
+
constructor(path34, reason, cause) {
|
|
12508
12570
|
super(
|
|
12509
|
-
`Credentials file ${
|
|
12571
|
+
`Credentials file ${path34} is corrupt (${reason}). Fix it by hand, or delete it and run \`iamu auth login\` again.`,
|
|
12510
12572
|
{
|
|
12511
12573
|
cause,
|
|
12512
|
-
suggestedAction: `Delete ${
|
|
12574
|
+
suggestedAction: `Delete ${path34} and run \`iamu auth login\`, or set IAMU_TOKEN.`
|
|
12513
12575
|
}
|
|
12514
12576
|
);
|
|
12515
12577
|
}
|
|
@@ -13189,8 +13251,8 @@ import { dirname as dirname3, join as join4, resolve as resolve2 } from "path";
|
|
|
13189
13251
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
13190
13252
|
import chalk6 from "chalk";
|
|
13191
13253
|
function getVersion() {
|
|
13192
|
-
if ("0.2.0-beta.
|
|
13193
|
-
return "0.2.0-beta.
|
|
13254
|
+
if ("0.2.0-beta.19") {
|
|
13255
|
+
return "0.2.0-beta.19";
|
|
13194
13256
|
}
|
|
13195
13257
|
try {
|
|
13196
13258
|
const thisDir = dirname3(fileURLToPath2(import.meta.url));
|
|
@@ -13219,6 +13281,7 @@ function loadSkillTemplates(version) {
|
|
|
13219
13281
|
"iamu-setup.md",
|
|
13220
13282
|
"iamu-propose.md",
|
|
13221
13283
|
"iamu-new.md",
|
|
13284
|
+
"iamu-stage.md",
|
|
13222
13285
|
"iamu-run.md",
|
|
13223
13286
|
"iamu-findings.md",
|
|
13224
13287
|
"iamu-status.md"
|
|
@@ -13296,16 +13359,16 @@ async function needsWrite(filePath, content) {
|
|
|
13296
13359
|
return true;
|
|
13297
13360
|
}
|
|
13298
13361
|
}
|
|
13299
|
-
function isCliWrittenSkill(
|
|
13362
|
+
function isCliWrittenSkill(path34) {
|
|
13300
13363
|
try {
|
|
13301
|
-
return /^generatedBy:/m.test(readFileSync3(
|
|
13364
|
+
return /^generatedBy:/m.test(readFileSync3(path34, "utf-8"));
|
|
13302
13365
|
} catch {
|
|
13303
13366
|
return false;
|
|
13304
13367
|
}
|
|
13305
13368
|
}
|
|
13306
|
-
function isCliWrittenPrompt(
|
|
13369
|
+
function isCliWrittenPrompt(path34) {
|
|
13307
13370
|
try {
|
|
13308
|
-
const content = readFileSync3(
|
|
13371
|
+
const content = readFileSync3(path34, "utf-8");
|
|
13309
13372
|
return content.includes("iamu") || /^generatedBy:/m.test(content);
|
|
13310
13373
|
} catch {
|
|
13311
13374
|
return false;
|
|
@@ -13317,8 +13380,8 @@ function planSkillFiles(hosts, projectRoot, version) {
|
|
|
13317
13380
|
for (const host of hosts) {
|
|
13318
13381
|
if (pluginProvidesSkills(host, projectRoot)) {
|
|
13319
13382
|
for (const name of skillTemplates.keys()) {
|
|
13320
|
-
const
|
|
13321
|
-
if (isCliWrittenSkill(
|
|
13383
|
+
const path34 = join4(host.getSkillsDir(projectRoot), name, "SKILL.md");
|
|
13384
|
+
if (isCliWrittenSkill(path34)) files.push({ host, path: path34, content: null });
|
|
13322
13385
|
}
|
|
13323
13386
|
} else if (host.supportsSkills) {
|
|
13324
13387
|
const skillsDir = host.getSkillsDir(projectRoot);
|
|
@@ -18809,6 +18872,22 @@ async function checkProjectTestabilityContract(projectRoot) {
|
|
|
18809
18872
|
status: "ok",
|
|
18810
18873
|
message: `Configured (${details.join(" \xB7 ") || "present"})`
|
|
18811
18874
|
});
|
|
18875
|
+
if (!frontmatter.access) {
|
|
18876
|
+
let contextAccess = null;
|
|
18877
|
+
try {
|
|
18878
|
+
contextAccess = (await readContext(projectRoot))?.frontmatter.access ?? null;
|
|
18879
|
+
} catch {
|
|
18880
|
+
}
|
|
18881
|
+
if (contextAccess) {
|
|
18882
|
+
checks.push({
|
|
18883
|
+
category: "target",
|
|
18884
|
+
name: "User switching (access)",
|
|
18885
|
+
status: "warn",
|
|
18886
|
+
message: "How to switch user is in iamu/context.yml, which holds the domain. Walks still read it there.",
|
|
18887
|
+
remedy: "Move the `access:` field from iamu/context.yml to the iamu/testability.md frontmatter."
|
|
18888
|
+
});
|
|
18889
|
+
}
|
|
18890
|
+
}
|
|
18812
18891
|
return { checks, targetUrl };
|
|
18813
18892
|
} catch (err) {
|
|
18814
18893
|
checks.push({
|
|
@@ -19361,6 +19440,7 @@ var init_list = __esm({
|
|
|
19361
19440
|
// src/commands/validate.ts
|
|
19362
19441
|
var validate_exports = {};
|
|
19363
19442
|
__export(validate_exports, {
|
|
19443
|
+
schemaByType: () => schemaByType,
|
|
19364
19444
|
validate: () => validate
|
|
19365
19445
|
});
|
|
19366
19446
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
@@ -20035,6 +20115,413 @@ var init_new_entity = __esm({
|
|
|
20035
20115
|
}
|
|
20036
20116
|
});
|
|
20037
20117
|
|
|
20118
|
+
// src/commands/set-entity.ts
|
|
20119
|
+
var set_entity_exports = {};
|
|
20120
|
+
__export(set_entity_exports, {
|
|
20121
|
+
setEntity: () => setEntity
|
|
20122
|
+
});
|
|
20123
|
+
import { promises as fs28 } from "fs";
|
|
20124
|
+
import path26 from "path";
|
|
20125
|
+
import chalk27 from "chalk";
|
|
20126
|
+
import { ZodError as ZodError2 } from "zod";
|
|
20127
|
+
function given(opts, key) {
|
|
20128
|
+
const v = opts[key];
|
|
20129
|
+
if (Array.isArray(v)) return v.length > 0;
|
|
20130
|
+
if (key === "state") return v === false;
|
|
20131
|
+
return v !== void 0;
|
|
20132
|
+
}
|
|
20133
|
+
function splitList(raw) {
|
|
20134
|
+
return raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
20135
|
+
}
|
|
20136
|
+
function applyChanges(type2, fm, opts) {
|
|
20137
|
+
const changed = [];
|
|
20138
|
+
const put = (key, value) => {
|
|
20139
|
+
if (value === null) delete fm[key];
|
|
20140
|
+
else fm[key] = value;
|
|
20141
|
+
changed.push(key);
|
|
20142
|
+
};
|
|
20143
|
+
if (type2 === "scenario") {
|
|
20144
|
+
if (opts.entry !== void 0) put("entry_path", opts.entry || null);
|
|
20145
|
+
if (opts.target !== void 0) put("target_url", opts.target || null);
|
|
20146
|
+
if (opts.state === false) {
|
|
20147
|
+
put("state", null);
|
|
20148
|
+
} else if (opts.stateDescription !== void 0 || opts.stateApproximation !== void 0) {
|
|
20149
|
+
const prev = fm.state ?? {};
|
|
20150
|
+
const next = { ...prev };
|
|
20151
|
+
if (opts.stateDescription !== void 0) {
|
|
20152
|
+
if (opts.stateDescription.trim() === "") delete next.description;
|
|
20153
|
+
else next.description = opts.stateDescription;
|
|
20154
|
+
}
|
|
20155
|
+
if (opts.stateApproximation !== void 0) {
|
|
20156
|
+
if (opts.stateApproximation.trim() === "") delete next.approximation;
|
|
20157
|
+
else next.approximation = opts.stateApproximation;
|
|
20158
|
+
}
|
|
20159
|
+
if (Object.keys(next).length === 0) {
|
|
20160
|
+
put("state", null);
|
|
20161
|
+
} else {
|
|
20162
|
+
put("state", next);
|
|
20163
|
+
}
|
|
20164
|
+
}
|
|
20165
|
+
} else if (type2 === "persona") {
|
|
20166
|
+
if (opts.access !== void 0) put("access", opts.access || null);
|
|
20167
|
+
} else if (type2 === "capability") {
|
|
20168
|
+
if (opts.paths === false) {
|
|
20169
|
+
put("paths", []);
|
|
20170
|
+
} else if (opts.paths !== void 0 || given(opts, "addPath") || given(opts, "removePath")) {
|
|
20171
|
+
let paths = typeof opts.paths === "string" ? splitList(opts.paths) : Array.isArray(fm.paths) ? fm.paths : [];
|
|
20172
|
+
for (const p of opts.addPath ?? []) {
|
|
20173
|
+
if (!paths.includes(p)) paths = [...paths, p];
|
|
20174
|
+
}
|
|
20175
|
+
const drop = new Set(opts.removePath ?? []);
|
|
20176
|
+
paths = paths.filter((p) => !drop.has(p));
|
|
20177
|
+
put("paths", paths);
|
|
20178
|
+
}
|
|
20179
|
+
if (opts.area !== void 0) put("area", opts.area || null);
|
|
20180
|
+
}
|
|
20181
|
+
return changed;
|
|
20182
|
+
}
|
|
20183
|
+
async function setEntity(type2, slug, opts) {
|
|
20184
|
+
const ctx = await resolveProject(opts);
|
|
20185
|
+
if (!ctx) return;
|
|
20186
|
+
const { projectRoot } = ctx;
|
|
20187
|
+
const allowed = SETTABLE[type2];
|
|
20188
|
+
if (!allowed) {
|
|
20189
|
+
commandError(
|
|
20190
|
+
`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.`,
|
|
20191
|
+
opts
|
|
20192
|
+
);
|
|
20193
|
+
return;
|
|
20194
|
+
}
|
|
20195
|
+
const foreign = Object.keys(FLAG_NAMES).filter(
|
|
20196
|
+
(k) => !allowed.includes(k) && given(opts, k)
|
|
20197
|
+
);
|
|
20198
|
+
if (foreign.length > 0) {
|
|
20199
|
+
commandError(
|
|
20200
|
+
`${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(", ")}.`,
|
|
20201
|
+
opts
|
|
20202
|
+
);
|
|
20203
|
+
return;
|
|
20204
|
+
}
|
|
20205
|
+
const effectiveOpts = { ...opts };
|
|
20206
|
+
if (opts.file) {
|
|
20207
|
+
let raw;
|
|
20208
|
+
try {
|
|
20209
|
+
if (opts.file === "-") {
|
|
20210
|
+
const chunks = [];
|
|
20211
|
+
for await (const chunk of process.stdin) {
|
|
20212
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
20213
|
+
}
|
|
20214
|
+
raw = Buffer.concat(chunks).toString("utf8");
|
|
20215
|
+
} else {
|
|
20216
|
+
raw = await fs28.readFile(path26.resolve(projectRoot, opts.file), "utf8");
|
|
20217
|
+
}
|
|
20218
|
+
} catch (err) {
|
|
20219
|
+
commandError(
|
|
20220
|
+
`Could not read patch file: ${err instanceof Error ? err.message : String(err)}`,
|
|
20221
|
+
opts
|
|
20222
|
+
);
|
|
20223
|
+
return;
|
|
20224
|
+
}
|
|
20225
|
+
let parsed;
|
|
20226
|
+
try {
|
|
20227
|
+
parsed = JSON.parse(raw);
|
|
20228
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
20229
|
+
throw new Error("Patch must be a JSON object");
|
|
20230
|
+
}
|
|
20231
|
+
} catch (err) {
|
|
20232
|
+
commandError(
|
|
20233
|
+
`Failed to parse patch JSON: ${err instanceof Error ? err.message : String(err)}`,
|
|
20234
|
+
opts
|
|
20235
|
+
);
|
|
20236
|
+
return;
|
|
20237
|
+
}
|
|
20238
|
+
const allowedPatchKeys = {
|
|
20239
|
+
scenario: ["entry", "entry_path", "target", "target_url", "state"],
|
|
20240
|
+
persona: ["access"],
|
|
20241
|
+
capability: ["paths", "addPath", "removePath", "area"]
|
|
20242
|
+
};
|
|
20243
|
+
const foreignPatchKeys = Object.keys(parsed).filter(
|
|
20244
|
+
(k) => !allowedPatchKeys[type2]?.includes(k)
|
|
20245
|
+
);
|
|
20246
|
+
if (foreignPatchKeys.length > 0) {
|
|
20247
|
+
commandError(
|
|
20248
|
+
`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.`,
|
|
20249
|
+
opts
|
|
20250
|
+
);
|
|
20251
|
+
return;
|
|
20252
|
+
}
|
|
20253
|
+
if (type2 === "scenario") {
|
|
20254
|
+
if (effectiveOpts.entry === void 0 && (parsed.entry !== void 0 || parsed.entry_path !== void 0)) {
|
|
20255
|
+
const v = parsed.entry ?? parsed.entry_path;
|
|
20256
|
+
effectiveOpts.entry = v === null || v === false ? false : String(v);
|
|
20257
|
+
}
|
|
20258
|
+
if (effectiveOpts.target === void 0 && (parsed.target !== void 0 || parsed.target_url !== void 0)) {
|
|
20259
|
+
const v = parsed.target ?? parsed.target_url;
|
|
20260
|
+
effectiveOpts.target = v === null || v === false ? false : String(v);
|
|
20261
|
+
}
|
|
20262
|
+
if (parsed.state !== void 0) {
|
|
20263
|
+
if (parsed.state === null || parsed.state === false) {
|
|
20264
|
+
if (effectiveOpts.state === void 0) effectiveOpts.state = false;
|
|
20265
|
+
} else if (typeof parsed.state === "object") {
|
|
20266
|
+
const s = parsed.state;
|
|
20267
|
+
if (effectiveOpts.stateDescription === void 0 && s.description !== void 0) {
|
|
20268
|
+
effectiveOpts.stateDescription = s.description === null ? "" : String(s.description);
|
|
20269
|
+
}
|
|
20270
|
+
if (effectiveOpts.stateApproximation === void 0 && s.approximation !== void 0) {
|
|
20271
|
+
effectiveOpts.stateApproximation = s.approximation === null ? "" : String(s.approximation);
|
|
20272
|
+
}
|
|
20273
|
+
}
|
|
20274
|
+
}
|
|
20275
|
+
} else if (type2 === "persona") {
|
|
20276
|
+
if (effectiveOpts.access === void 0 && parsed.access !== void 0) {
|
|
20277
|
+
effectiveOpts.access = parsed.access === null || parsed.access === false ? false : String(parsed.access);
|
|
20278
|
+
}
|
|
20279
|
+
} else if (type2 === "capability") {
|
|
20280
|
+
if (effectiveOpts.paths === void 0 && parsed.paths !== void 0) {
|
|
20281
|
+
if (parsed.paths === null || parsed.paths === false) {
|
|
20282
|
+
effectiveOpts.paths = false;
|
|
20283
|
+
} else if (Array.isArray(parsed.paths)) {
|
|
20284
|
+
effectiveOpts.paths = parsed.paths.length === 0 ? false : parsed.paths.join(",");
|
|
20285
|
+
}
|
|
20286
|
+
}
|
|
20287
|
+
if (effectiveOpts.addPath === void 0 && Array.isArray(parsed.addPath)) {
|
|
20288
|
+
effectiveOpts.addPath = parsed.addPath.map(String);
|
|
20289
|
+
}
|
|
20290
|
+
if (effectiveOpts.removePath === void 0 && Array.isArray(parsed.removePath)) {
|
|
20291
|
+
effectiveOpts.removePath = parsed.removePath.map(String);
|
|
20292
|
+
}
|
|
20293
|
+
if (effectiveOpts.area === void 0 && parsed.area !== void 0) {
|
|
20294
|
+
effectiveOpts.area = parsed.area === null || parsed.area === false ? false : String(parsed.area);
|
|
20295
|
+
}
|
|
20296
|
+
}
|
|
20297
|
+
}
|
|
20298
|
+
if (!allowed.some((k) => given(effectiveOpts, k))) {
|
|
20299
|
+
commandError(
|
|
20300
|
+
`Nothing to set. ${type2} takes: ${allowed.map((k) => FLAG_NAMES[k]).join(", ")}, or --file <path>.`,
|
|
20301
|
+
opts
|
|
20302
|
+
);
|
|
20303
|
+
return;
|
|
20304
|
+
}
|
|
20305
|
+
if (type2 === "capability") {
|
|
20306
|
+
const candidatePaths = [
|
|
20307
|
+
...typeof effectiveOpts.paths === "string" ? splitList(effectiveOpts.paths) : [],
|
|
20308
|
+
...effectiveOpts.addPath ?? []
|
|
20309
|
+
];
|
|
20310
|
+
const invalid = candidatePaths.filter((p) => !p.startsWith("/"));
|
|
20311
|
+
if (invalid.length > 0) {
|
|
20312
|
+
commandError(
|
|
20313
|
+
`Route prefixes must start with "/": ${invalid.join(", ")}`,
|
|
20314
|
+
opts
|
|
20315
|
+
);
|
|
20316
|
+
return;
|
|
20317
|
+
}
|
|
20318
|
+
}
|
|
20319
|
+
const entity = await safeReadEntity(
|
|
20320
|
+
projectRoot,
|
|
20321
|
+
type2,
|
|
20322
|
+
slug,
|
|
20323
|
+
opts
|
|
20324
|
+
);
|
|
20325
|
+
if (!entity) return;
|
|
20326
|
+
const fm = { ...entity.frontmatter };
|
|
20327
|
+
const changed = applyChanges(type2, fm, effectiveOpts);
|
|
20328
|
+
let writtenPath;
|
|
20329
|
+
try {
|
|
20330
|
+
const record = await writeEntity(
|
|
20331
|
+
projectRoot,
|
|
20332
|
+
type2,
|
|
20333
|
+
fm,
|
|
20334
|
+
entity.body
|
|
20335
|
+
);
|
|
20336
|
+
writtenPath = record.path;
|
|
20337
|
+
} catch (err) {
|
|
20338
|
+
const message = err instanceof ZodError2 ? err.issues.map((i) => `${i.path.join(".") || "root"}: ${i.message}`).join("; ") : err instanceof Error ? err.message : String(err);
|
|
20339
|
+
commandError(`Could not set ${type2} "${slug}": ${message}`, opts);
|
|
20340
|
+
return;
|
|
20341
|
+
}
|
|
20342
|
+
const entitySlug = String(fm.slug);
|
|
20343
|
+
if (opts.json) {
|
|
20344
|
+
console.log(
|
|
20345
|
+
JSON.stringify({ slug: entitySlug, type: type2, path: writtenPath, changed })
|
|
20346
|
+
);
|
|
20347
|
+
return;
|
|
20348
|
+
}
|
|
20349
|
+
console.log();
|
|
20350
|
+
console.log(
|
|
20351
|
+
` ${symbols.success} Updated ${type2} ${chalk27.cyan(entitySlug)}: ${changed.join(", ")}`
|
|
20352
|
+
);
|
|
20353
|
+
console.log(` ${chalk27.dim("path:")} ${writtenPath}`);
|
|
20354
|
+
console.log();
|
|
20355
|
+
}
|
|
20356
|
+
var SETTABLE, FLAG_NAMES;
|
|
20357
|
+
var init_set_entity = __esm({
|
|
20358
|
+
"src/commands/set-entity.ts"() {
|
|
20359
|
+
"use strict";
|
|
20360
|
+
init_dist2();
|
|
20361
|
+
init_symbols();
|
|
20362
|
+
init_context4();
|
|
20363
|
+
SETTABLE = {
|
|
20364
|
+
scenario: [
|
|
20365
|
+
"entry",
|
|
20366
|
+
"target",
|
|
20367
|
+
"stateDescription",
|
|
20368
|
+
"stateApproximation",
|
|
20369
|
+
"state"
|
|
20370
|
+
],
|
|
20371
|
+
persona: ["access"],
|
|
20372
|
+
capability: ["paths", "addPath", "removePath", "area"]
|
|
20373
|
+
};
|
|
20374
|
+
FLAG_NAMES = {
|
|
20375
|
+
entry: "--entry",
|
|
20376
|
+
target: "--target",
|
|
20377
|
+
stateDescription: "--state-description",
|
|
20378
|
+
stateApproximation: "--state-approximation",
|
|
20379
|
+
state: "--no-state",
|
|
20380
|
+
access: "--access",
|
|
20381
|
+
paths: "--paths",
|
|
20382
|
+
addPath: "--add-path",
|
|
20383
|
+
removePath: "--remove-path",
|
|
20384
|
+
area: "--area"
|
|
20385
|
+
};
|
|
20386
|
+
}
|
|
20387
|
+
});
|
|
20388
|
+
|
|
20389
|
+
// src/commands/edit-entity.ts
|
|
20390
|
+
var edit_entity_exports = {};
|
|
20391
|
+
__export(edit_entity_exports, {
|
|
20392
|
+
editEntity: () => editEntity
|
|
20393
|
+
});
|
|
20394
|
+
import { spawn as spawn2 } from "child_process";
|
|
20395
|
+
import { promises as fs29 } from "fs";
|
|
20396
|
+
import path27 from "path";
|
|
20397
|
+
import chalk28 from "chalk";
|
|
20398
|
+
async function editEntity(type2, slug, opts = {}) {
|
|
20399
|
+
const ctx = await resolveProject(opts);
|
|
20400
|
+
if (!ctx) return;
|
|
20401
|
+
const { projectRoot } = ctx;
|
|
20402
|
+
if (!ENTITY_TYPES.includes(type2)) {
|
|
20403
|
+
commandError(
|
|
20404
|
+
`Unknown entity type "${type2}". Supported: ${ENTITY_TYPES.join(", ")}.`,
|
|
20405
|
+
opts
|
|
20406
|
+
);
|
|
20407
|
+
return;
|
|
20408
|
+
}
|
|
20409
|
+
const dir = ENTITY_DIRS[type2];
|
|
20410
|
+
const filePath = path27.join(projectRoot, "iamu", dir, `${slug}.md`);
|
|
20411
|
+
try {
|
|
20412
|
+
await fs29.access(filePath);
|
|
20413
|
+
} catch {
|
|
20414
|
+
commandError(
|
|
20415
|
+
`${type2} "${slug}" not found at ${path27.relative(projectRoot, filePath)}.`,
|
|
20416
|
+
opts
|
|
20417
|
+
);
|
|
20418
|
+
return;
|
|
20419
|
+
}
|
|
20420
|
+
if (!process.stdin.isTTY || process.env.CI) {
|
|
20421
|
+
commandError(
|
|
20422
|
+
`Cannot open editor in a non-interactive environment. Edit the file directly: ${path27.relative(projectRoot, filePath)}`,
|
|
20423
|
+
opts
|
|
20424
|
+
);
|
|
20425
|
+
return;
|
|
20426
|
+
}
|
|
20427
|
+
const rawEditor = process.env.VISUAL || process.env.EDITOR || (process.platform === "win32" ? "notepad" : "vi");
|
|
20428
|
+
const parts = rawEditor.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) ?? [
|
|
20429
|
+
rawEditor
|
|
20430
|
+
];
|
|
20431
|
+
const editorCmd = parts[0].replace(/^["']|["']$/g, "");
|
|
20432
|
+
const editorArgs = parts.slice(1).map((a) => a.replace(/^["']|["']$/g, ""));
|
|
20433
|
+
await new Promise((resolve3) => {
|
|
20434
|
+
const child = spawn2(editorCmd, [...editorArgs, filePath], {
|
|
20435
|
+
stdio: "inherit"
|
|
20436
|
+
});
|
|
20437
|
+
child.on("exit", (code) => {
|
|
20438
|
+
if (code !== 0) {
|
|
20439
|
+
console.error(
|
|
20440
|
+
chalk28.red(
|
|
20441
|
+
`
|
|
20442
|
+
Editor exited with code ${code}. Changes not validated.
|
|
20443
|
+
`
|
|
20444
|
+
)
|
|
20445
|
+
);
|
|
20446
|
+
process.exitCode = code ?? 1;
|
|
20447
|
+
resolve3();
|
|
20448
|
+
} else {
|
|
20449
|
+
resolve3();
|
|
20450
|
+
}
|
|
20451
|
+
});
|
|
20452
|
+
child.on("error", (err) => {
|
|
20453
|
+
commandError(
|
|
20454
|
+
`Failed to launch editor (${rawEditor}): ${err.message}`,
|
|
20455
|
+
opts
|
|
20456
|
+
);
|
|
20457
|
+
resolve3();
|
|
20458
|
+
});
|
|
20459
|
+
});
|
|
20460
|
+
if (process.exitCode) return;
|
|
20461
|
+
try {
|
|
20462
|
+
const content = await fs29.readFile(filePath, "utf8");
|
|
20463
|
+
const { frontmatter } = parseFrontmatter(content);
|
|
20464
|
+
const schema2 = schemaByType[type2];
|
|
20465
|
+
const parsed = schema2.safeParse(frontmatter);
|
|
20466
|
+
if (!parsed.success) {
|
|
20467
|
+
if (opts.json) {
|
|
20468
|
+
console.log(
|
|
20469
|
+
JSON.stringify({
|
|
20470
|
+
valid: false,
|
|
20471
|
+
type: type2,
|
|
20472
|
+
slug,
|
|
20473
|
+
path: filePath,
|
|
20474
|
+
errors: parsed.error.issues.map((i) => ({
|
|
20475
|
+
field: i.path.join(".") || "root",
|
|
20476
|
+
message: i.message
|
|
20477
|
+
}))
|
|
20478
|
+
})
|
|
20479
|
+
);
|
|
20480
|
+
} else {
|
|
20481
|
+
console.log();
|
|
20482
|
+
console.log(
|
|
20483
|
+
` ${symbols.warning} ${chalk28.yellow(`Validation failed for ${type2} "${slug}":`)}`
|
|
20484
|
+
);
|
|
20485
|
+
for (const issue of parsed.error.issues) {
|
|
20486
|
+
const field2 = issue.path.join(".") || "root";
|
|
20487
|
+
console.log(` - ${field2}: ${issue.message}`);
|
|
20488
|
+
}
|
|
20489
|
+
console.log();
|
|
20490
|
+
console.log(
|
|
20491
|
+
chalk28.dim(
|
|
20492
|
+
` Fix the file and run: iamu validate ${path27.relative(projectRoot, filePath)}`
|
|
20493
|
+
)
|
|
20494
|
+
);
|
|
20495
|
+
console.log();
|
|
20496
|
+
}
|
|
20497
|
+
process.exitCode = 1;
|
|
20498
|
+
return;
|
|
20499
|
+
}
|
|
20500
|
+
if (opts.json) {
|
|
20501
|
+
console.log(JSON.stringify({ valid: true, type: type2, slug, path: filePath }));
|
|
20502
|
+
return;
|
|
20503
|
+
}
|
|
20504
|
+
console.log();
|
|
20505
|
+
console.log(` ${symbols.success} Validated ${type2} ${chalk28.cyan(slug)}`);
|
|
20506
|
+
console.log();
|
|
20507
|
+
} catch (err) {
|
|
20508
|
+
commandError(
|
|
20509
|
+
`Failed to validate ${type2} "${slug}" after edit: ${err instanceof Error ? err.message : String(err)}`,
|
|
20510
|
+
opts
|
|
20511
|
+
);
|
|
20512
|
+
}
|
|
20513
|
+
}
|
|
20514
|
+
var init_edit_entity = __esm({
|
|
20515
|
+
"src/commands/edit-entity.ts"() {
|
|
20516
|
+
"use strict";
|
|
20517
|
+
init_dist2();
|
|
20518
|
+
init_dist();
|
|
20519
|
+
init_symbols();
|
|
20520
|
+
init_context4();
|
|
20521
|
+
init_validate();
|
|
20522
|
+
}
|
|
20523
|
+
});
|
|
20524
|
+
|
|
20038
20525
|
// src/commands/explore-crawl.ts
|
|
20039
20526
|
function candidateSig(c) {
|
|
20040
20527
|
return `${c.role}
|
|
@@ -20074,6 +20561,9 @@ async function settle(page, settleMs) {
|
|
|
20074
20561
|
});
|
|
20075
20562
|
await page.waitForLoadState("networkidle", { timeout: settleMs }).catch(() => {
|
|
20076
20563
|
});
|
|
20564
|
+
const spaSettle = Math.min(Math.max(settleMs, 50), 200);
|
|
20565
|
+
await page.waitForTimeout(spaSettle).catch(() => {
|
|
20566
|
+
});
|
|
20077
20567
|
}
|
|
20078
20568
|
async function readPage(page) {
|
|
20079
20569
|
return page.evaluate(READ_PAGE);
|
|
@@ -20091,8 +20581,12 @@ async function clickCandidate(page, want) {
|
|
|
20091
20581
|
(x) => candidateSig(x) === candidateSig(want)
|
|
20092
20582
|
);
|
|
20093
20583
|
if (!c) return false;
|
|
20094
|
-
|
|
20095
|
-
|
|
20584
|
+
try {
|
|
20585
|
+
await page.locator(`[data-iamu-x="${c.index}"]`).click({ timeout: CLICK_TIMEOUT_MS });
|
|
20586
|
+
return true;
|
|
20587
|
+
} catch {
|
|
20588
|
+
return false;
|
|
20589
|
+
}
|
|
20096
20590
|
}
|
|
20097
20591
|
async function crawl(page, opts) {
|
|
20098
20592
|
const origin = originOf(opts.startUrl);
|
|
@@ -20149,7 +20643,7 @@ async function crawlFrom(page, opts, origin, takeBlocked) {
|
|
|
20149
20643
|
const byKey = /* @__PURE__ */ new Map();
|
|
20150
20644
|
const byPlaceView = /* @__PURE__ */ new Map();
|
|
20151
20645
|
const signatureOf = (s) => exploreSignature(s.headings, s.title);
|
|
20152
|
-
function addSurface(state, place, view,
|
|
20646
|
+
function addSurface(state, place, view, path34, depth) {
|
|
20153
20647
|
const key = exploreSurfaceKey(place, view);
|
|
20154
20648
|
const surface = {
|
|
20155
20649
|
key,
|
|
@@ -20161,7 +20655,7 @@ async function crawlFrom(page, opts, origin, takeBlocked) {
|
|
|
20161
20655
|
headings: state.headings,
|
|
20162
20656
|
fields: state.fields,
|
|
20163
20657
|
depth,
|
|
20164
|
-
path:
|
|
20658
|
+
path: path34,
|
|
20165
20659
|
actions: []
|
|
20166
20660
|
};
|
|
20167
20661
|
surfaces.push(surface);
|
|
@@ -20179,9 +20673,9 @@ ${surface.signature}`, key);
|
|
|
20179
20673
|
});
|
|
20180
20674
|
await settle(page, settleMs);
|
|
20181
20675
|
}
|
|
20182
|
-
async function replay(
|
|
20183
|
-
if (!("goto" in (
|
|
20184
|
-
for (const step of
|
|
20676
|
+
async function replay(path34) {
|
|
20677
|
+
if (!("goto" in (path34[0] ?? {}))) await goto(opts.startUrl);
|
|
20678
|
+
for (const step of path34) {
|
|
20185
20679
|
if ("goto" in step) {
|
|
20186
20680
|
await goto(step.goto);
|
|
20187
20681
|
} else {
|
|
@@ -20305,8 +20799,8 @@ ${sig}`, key);
|
|
|
20305
20799
|
if (stats.surfaces >= budget.max_surfaces) {
|
|
20306
20800
|
stats.truncated = true;
|
|
20307
20801
|
} else {
|
|
20308
|
-
const
|
|
20309
|
-
addSurface(after, place, view,
|
|
20802
|
+
const path34 = !sameDoc && view === null ? [{ goto: after.url }] : [...surface.path, ...via, click];
|
|
20803
|
+
addSurface(after, place, view, path34, surface.depth + 1);
|
|
20310
20804
|
}
|
|
20311
20805
|
}
|
|
20312
20806
|
}
|
|
@@ -20506,11 +21000,11 @@ __export(explore_exports, {
|
|
|
20506
21000
|
exploreList: () => exploreList,
|
|
20507
21001
|
exploreRun: () => exploreRun
|
|
20508
21002
|
});
|
|
20509
|
-
import
|
|
20510
|
-
import
|
|
21003
|
+
import path28 from "path";
|
|
21004
|
+
import chalk29 from "chalk";
|
|
20511
21005
|
function fail2(message, json2) {
|
|
20512
21006
|
if (json2) console.error(JSON.stringify({ error: message }));
|
|
20513
|
-
else console.error(
|
|
21007
|
+
else console.error(chalk29.red(` ${message}`));
|
|
20514
21008
|
process.exitCode = 1;
|
|
20515
21009
|
}
|
|
20516
21010
|
function positiveInt(raw, fallback, flag) {
|
|
@@ -20609,7 +21103,7 @@ async function exploreRun(url, opts) {
|
|
|
20609
21103
|
}
|
|
20610
21104
|
const quiet = opts.json || process.env.IAMU_QUIET === "1";
|
|
20611
21105
|
if (!quiet) {
|
|
20612
|
-
console.error(
|
|
21106
|
+
console.error(chalk29.dim(` Exploring ${startUrl} (read-only)\u2026`));
|
|
20613
21107
|
}
|
|
20614
21108
|
const now = /* @__PURE__ */ new Date();
|
|
20615
21109
|
let opened = null;
|
|
@@ -20620,7 +21114,7 @@ async function exploreRun(url, opts) {
|
|
|
20620
21114
|
startUrl,
|
|
20621
21115
|
budget,
|
|
20622
21116
|
deny,
|
|
20623
|
-
log: quiet ? void 0 : (line) => console.error(
|
|
21117
|
+
log: quiet ? void 0 : (line) => console.error(chalk29.dim(line))
|
|
20624
21118
|
});
|
|
20625
21119
|
} catch (err) {
|
|
20626
21120
|
fail2(err.message.split("\n")[0], opts.json);
|
|
@@ -20640,7 +21134,7 @@ async function exploreRun(url, opts) {
|
|
|
20640
21134
|
surfaces: result.surfaces
|
|
20641
21135
|
};
|
|
20642
21136
|
const file = await writeExploreMap(ctx.projectRoot, map2);
|
|
20643
|
-
const rel =
|
|
21137
|
+
const rel = path28.relative(process.cwd(), file);
|
|
20644
21138
|
if (opts.json) {
|
|
20645
21139
|
console.log(JSON.stringify({ id: map2.id, path: rel, stats: map2.stats }));
|
|
20646
21140
|
return;
|
|
@@ -20648,17 +21142,17 @@ async function exploreRun(url, opts) {
|
|
|
20648
21142
|
const s = map2.stats;
|
|
20649
21143
|
console.log(
|
|
20650
21144
|
`
|
|
20651
|
-
${
|
|
21145
|
+
${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` : ""}`
|
|
20652
21146
|
);
|
|
20653
21147
|
if (s.truncated) {
|
|
20654
21148
|
console.log(
|
|
20655
|
-
|
|
21149
|
+
chalk29.yellow(
|
|
20656
21150
|
" Stopped by the budget before the product ran out \u2014 raise --max-surfaces / --max-actions / --max-depth to go further."
|
|
20657
21151
|
)
|
|
20658
21152
|
);
|
|
20659
21153
|
}
|
|
20660
21154
|
console.log(` Map: ${rel}`);
|
|
20661
|
-
console.log(
|
|
21155
|
+
console.log(chalk29.dim(" Next: iamu explore coverage"));
|
|
20662
21156
|
}
|
|
20663
21157
|
async function resolveMapId(projectRoot, id, offsetFromEnd = 1) {
|
|
20664
21158
|
if (id && id !== "latest") return id;
|
|
@@ -20686,7 +21180,7 @@ async function exploreList(opts) {
|
|
|
20686
21180
|
}
|
|
20687
21181
|
for (const r of rows) {
|
|
20688
21182
|
console.log(
|
|
20689
|
-
` ${r.id} ${r.surfaces} surfaces ${
|
|
21183
|
+
` ${r.id} ${r.surfaces} surfaces ${chalk29.dim(r.target_url)}`
|
|
20690
21184
|
);
|
|
20691
21185
|
}
|
|
20692
21186
|
}
|
|
@@ -20713,7 +21207,10 @@ async function exploreCoverageCommand(mapId, opts) {
|
|
|
20713
21207
|
...cov,
|
|
20714
21208
|
...capReport.capabilities.length > 0 ? {
|
|
20715
21209
|
capabilities: capReport.capabilities,
|
|
20716
|
-
unassigned_surfaces: capReport.unassigned_surfaces
|
|
21210
|
+
unassigned_surfaces: capReport.unassigned_surfaces,
|
|
21211
|
+
unassigned_prefixes: groupUnassignedByPrefix(
|
|
21212
|
+
capReport.unassigned_surfaces
|
|
21213
|
+
)
|
|
20717
21214
|
} : {}
|
|
20718
21215
|
})
|
|
20719
21216
|
);
|
|
@@ -20724,22 +21221,22 @@ async function exploreCoverageCommand(mapId, opts) {
|
|
|
20724
21221
|
Map ${id} (${map2.surfaces.length} surfaces) against ${cov.runs} runs, ${cov.steps} steps`
|
|
20725
21222
|
);
|
|
20726
21223
|
console.log(
|
|
20727
|
-
` ${
|
|
21224
|
+
` ${chalk29.green(`${cov.reached} reached`)} \xB7 ${chalk29.yellow(`${cov.place_only} URL reached, view unclear`)} \xB7 ${chalk29.red(`${cov.unreached} never reached`)}`
|
|
20728
21225
|
);
|
|
20729
21226
|
if (capReport.capabilities.length > 0) {
|
|
20730
|
-
console.log(
|
|
21227
|
+
console.log(chalk29.bold("\n CAPABILITY COVERAGE"));
|
|
20731
21228
|
console.log(
|
|
20732
|
-
|
|
21229
|
+
chalk29.dim(
|
|
20733
21230
|
" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
|
|
20734
21231
|
)
|
|
20735
21232
|
);
|
|
20736
21233
|
console.log(
|
|
20737
|
-
|
|
21234
|
+
chalk29.dim(
|
|
20738
21235
|
` ${"Capability".padEnd(24)} ${"Surfaces".padStart(8)} ${"Coverage".padStart(8)} ${"Scenarios (1\xB0 / 2\xB0)"}`
|
|
20739
21236
|
)
|
|
20740
21237
|
);
|
|
20741
21238
|
console.log(
|
|
20742
|
-
|
|
21239
|
+
chalk29.dim(
|
|
20743
21240
|
" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
|
|
20744
21241
|
)
|
|
20745
21242
|
);
|
|
@@ -20750,8 +21247,8 @@ async function exploreCoverageCommand(mapId, opts) {
|
|
|
20750
21247
|
);
|
|
20751
21248
|
const covStr = `${cap.coverage_pct}%`.padStart(8);
|
|
20752
21249
|
const scenarios = `${cap.primary_scenarios} / ${cap.secondary_scenarios}`;
|
|
20753
|
-
const untestedFlag = cap.primary_scenarios === 0 && cap.unreached_surfaces > 0 ?
|
|
20754
|
-
const covColored = cap.coverage_pct === 100 ?
|
|
21250
|
+
const untestedFlag = cap.primary_scenarios === 0 && cap.unreached_surfaces > 0 ? chalk29.yellow(" (untested)") : "";
|
|
21251
|
+
const covColored = cap.coverage_pct === 100 ? chalk29.green(covStr) : cap.coverage_pct === 0 ? chalk29.red(covStr) : chalk29.yellow(covStr);
|
|
20755
21252
|
console.log(
|
|
20756
21253
|
` ${title} ${surfaces} ${covColored} ${scenarios}${untestedFlag}`
|
|
20757
21254
|
);
|
|
@@ -20761,26 +21258,41 @@ async function exploreCoverageCommand(mapId, opts) {
|
|
|
20761
21258
|
const reached = unassigned.filter((s) => s.status === "reached").length;
|
|
20762
21259
|
const pct = Math.round(reached / unassigned.length * 100);
|
|
20763
21260
|
console.log(
|
|
20764
|
-
|
|
21261
|
+
chalk29.dim(
|
|
20765
21262
|
` ${"(Unassigned)".padEnd(24)} ${`${reached}/${unassigned.length}`.padStart(8)} ${`${pct}%`.padStart(8)} -`
|
|
20766
21263
|
)
|
|
20767
21264
|
);
|
|
20768
21265
|
}
|
|
20769
21266
|
console.log(
|
|
20770
|
-
|
|
21267
|
+
chalk29.dim(
|
|
20771
21268
|
" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
|
|
20772
21269
|
)
|
|
20773
21270
|
);
|
|
21271
|
+
const prefixes = groupUnassignedByPrefix(capReport.unassigned_surfaces);
|
|
21272
|
+
if (prefixes.length > 0) {
|
|
21273
|
+
console.log(chalk29.bold("\n UNASSIGNED PAGES (no capability owns them)"));
|
|
21274
|
+
for (const g of prefixes) {
|
|
21275
|
+
const count = `${g.surfaces} page${g.surfaces === 1 ? "" : "s"} (${g.reached} reached)`;
|
|
21276
|
+
console.log(
|
|
21277
|
+
` ${g.prefix.padEnd(24)} ${count.padEnd(24)} ${chalk29.dim(`e.g. ${g.examples.join(", ")}`)}`
|
|
21278
|
+
);
|
|
21279
|
+
}
|
|
21280
|
+
console.log(
|
|
21281
|
+
chalk29.dim(
|
|
21282
|
+
` Give a prefix to the capability it belongs to: iamu set capability <slug> --add-path ${prefixes[0]?.prefix}`
|
|
21283
|
+
)
|
|
21284
|
+
);
|
|
21285
|
+
}
|
|
20774
21286
|
const hasGaps = capReport.capabilities.some((c) => c.unreached_surfaces > 0) || capReport.unassigned_surfaces.some((s) => s.status !== "reached");
|
|
20775
21287
|
if (hasGaps) {
|
|
20776
|
-
console.log(
|
|
21288
|
+
console.log(chalk29.red("\n UNREACHED SURFACES (Gaps)"));
|
|
20777
21289
|
for (const cap of capReport.capabilities) {
|
|
20778
21290
|
const gaps = cap.surfaces.filter((s) => s.status !== "reached");
|
|
20779
21291
|
if (gaps.length === 0) continue;
|
|
20780
|
-
console.log(` ${
|
|
21292
|
+
console.log(` ${chalk29.bold(cap.slug)}:`);
|
|
20781
21293
|
for (const s of gaps) {
|
|
20782
21294
|
console.log(
|
|
20783
|
-
` ${
|
|
21295
|
+
` ${chalk29.red("\u25CB")} ${s.key.padEnd(32)} ${s.label}${chalk29.dim(` (depth ${s.depth})`)}`
|
|
20784
21296
|
);
|
|
20785
21297
|
}
|
|
20786
21298
|
}
|
|
@@ -20788,23 +21300,23 @@ async function exploreCoverageCommand(mapId, opts) {
|
|
|
20788
21300
|
(s) => s.status !== "reached"
|
|
20789
21301
|
);
|
|
20790
21302
|
if (unassignedGaps.length > 0) {
|
|
20791
|
-
console.log(
|
|
21303
|
+
console.log(chalk29.dim(" (Unassigned):"));
|
|
20792
21304
|
for (const s of unassignedGaps) {
|
|
20793
21305
|
console.log(
|
|
20794
|
-
` ${
|
|
21306
|
+
` ${chalk29.dim("\u25CB")} ${s.key.padEnd(32)} ${s.label}${chalk29.dim(` (depth ${s.depth})`)}`
|
|
20795
21307
|
);
|
|
20796
21308
|
}
|
|
20797
21309
|
}
|
|
20798
21310
|
}
|
|
20799
21311
|
if (opts.all) {
|
|
20800
|
-
console.log(
|
|
21312
|
+
console.log(chalk29.green("\n REACHED SURFACES"));
|
|
20801
21313
|
for (const cap of capReport.capabilities) {
|
|
20802
21314
|
const reached = cap.surfaces.filter((s) => s.status === "reached");
|
|
20803
21315
|
if (reached.length === 0) continue;
|
|
20804
|
-
console.log(` ${
|
|
21316
|
+
console.log(` ${chalk29.bold(cap.slug)}:`);
|
|
20805
21317
|
for (const s of reached) {
|
|
20806
21318
|
console.log(
|
|
20807
|
-
` ${
|
|
21319
|
+
` ${chalk29.green("\u2713")} ${s.key.padEnd(32)} ${s.label}${chalk29.dim(` (depth ${s.depth})`)}`
|
|
20808
21320
|
);
|
|
20809
21321
|
}
|
|
20810
21322
|
}
|
|
@@ -20812,10 +21324,10 @@ async function exploreCoverageCommand(mapId, opts) {
|
|
|
20812
21324
|
(s) => s.status === "reached"
|
|
20813
21325
|
);
|
|
20814
21326
|
if (unassignedReached.length > 0) {
|
|
20815
|
-
console.log(
|
|
21327
|
+
console.log(chalk29.dim(" (Unassigned):"));
|
|
20816
21328
|
for (const s of unassignedReached) {
|
|
20817
21329
|
console.log(
|
|
20818
|
-
` ${
|
|
21330
|
+
` ${chalk29.green("\u2713")} ${s.key.padEnd(32)} ${s.label}${chalk29.dim(` (depth ${s.depth})`)}`
|
|
20819
21331
|
);
|
|
20820
21332
|
}
|
|
20821
21333
|
}
|
|
@@ -20827,24 +21339,24 @@ async function exploreCoverageCommand(mapId, opts) {
|
|
|
20827
21339
|
${color(title)}`);
|
|
20828
21340
|
for (const r of rows) {
|
|
20829
21341
|
console.log(
|
|
20830
|
-
` ${r.key.padEnd(32)} ${r.label}${
|
|
21342
|
+
` ${r.key.padEnd(32)} ${r.label}${chalk29.dim(` (depth ${r.depth})`)}`
|
|
20831
21343
|
);
|
|
20832
21344
|
}
|
|
20833
21345
|
};
|
|
20834
21346
|
section(
|
|
20835
21347
|
"Never reached by a run",
|
|
20836
21348
|
cov.surfaces.filter((s) => s.status === "unreached"),
|
|
20837
|
-
|
|
21349
|
+
chalk29.red
|
|
20838
21350
|
);
|
|
20839
21351
|
section(
|
|
20840
21352
|
"URL reached, but no step names this view",
|
|
20841
21353
|
cov.surfaces.filter((s) => s.status === "place_only"),
|
|
20842
|
-
|
|
21354
|
+
chalk29.yellow
|
|
20843
21355
|
);
|
|
20844
21356
|
}
|
|
20845
21357
|
if (cov.run_only_places.length > 0) {
|
|
20846
21358
|
console.log(`
|
|
20847
|
-
${
|
|
21359
|
+
${chalk29.cyan("Walked by runs, not in the map")}`);
|
|
20848
21360
|
for (const p of cov.run_only_places) {
|
|
20849
21361
|
console.log(
|
|
20850
21362
|
` ${p.place.padEnd(32)} ${p.steps} steps, ${p.runs.length} runs`
|
|
@@ -20853,7 +21365,7 @@ async function exploreCoverageCommand(mapId, opts) {
|
|
|
20853
21365
|
}
|
|
20854
21366
|
if (cov.runs === 0) {
|
|
20855
21367
|
console.log(
|
|
20856
|
-
|
|
21368
|
+
chalk29.dim(
|
|
20857
21369
|
"\n No runs yet \u2014 every surface counts as never reached. Walk a scenario first (iamu-run)."
|
|
20858
21370
|
)
|
|
20859
21371
|
);
|
|
@@ -20889,27 +21401,27 @@ async function exploreDiffCommand(a, b, opts) {
|
|
|
20889
21401
|
console.log(`
|
|
20890
21402
|
${idA} \u2192 ${idB}`);
|
|
20891
21403
|
console.log(
|
|
20892
|
-
` ${
|
|
21404
|
+
` ${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)`
|
|
20893
21405
|
);
|
|
20894
21406
|
if (d.only_a.length > 0) {
|
|
20895
21407
|
console.log(`
|
|
20896
|
-
${
|
|
21408
|
+
${chalk29.red("Only in the first")}`);
|
|
20897
21409
|
for (const s of d.only_a) console.log(` ${s.key.padEnd(32)} ${s.label}`);
|
|
20898
21410
|
}
|
|
20899
21411
|
if (d.only_b.length > 0) {
|
|
20900
21412
|
console.log(`
|
|
20901
|
-
${
|
|
21413
|
+
${chalk29.green("Only in the second")}`);
|
|
20902
21414
|
for (const s of d.only_b) console.log(` ${s.key.padEnd(32)} ${s.label}`);
|
|
20903
21415
|
}
|
|
20904
21416
|
if (d.changed_actions.length > 0) {
|
|
20905
21417
|
console.log(
|
|
20906
21418
|
`
|
|
20907
|
-
${
|
|
21419
|
+
${chalk29.yellow(`Controls differ on ${d.changed_actions.length} shared surfaces`)}`
|
|
20908
21420
|
);
|
|
20909
21421
|
for (const c of d.changed_actions) {
|
|
20910
21422
|
console.log(` ${c.key} ${c.label}`);
|
|
20911
|
-
for (const x of c.only_a) console.log(
|
|
20912
|
-
for (const x of c.only_b) console.log(
|
|
21423
|
+
for (const x of c.only_a) console.log(chalk29.red(` - ${x}`));
|
|
21424
|
+
for (const x of c.only_b) console.log(chalk29.green(` + ${x}`));
|
|
20913
21425
|
}
|
|
20914
21426
|
}
|
|
20915
21427
|
}
|
|
@@ -21123,7 +21635,7 @@ __export(instructions_walk_exports, {
|
|
|
21123
21635
|
});
|
|
21124
21636
|
import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
|
|
21125
21637
|
import { join as join9 } from "path";
|
|
21126
|
-
import
|
|
21638
|
+
import chalk30 from "chalk";
|
|
21127
21639
|
function selfCommand() {
|
|
21128
21640
|
const q = (p) => /[\s"'$]/.test(p) ? JSON.stringify(p) : p;
|
|
21129
21641
|
return `${q(process.execPath)} ${q(process.argv[1] ?? "iamu")}`;
|
|
@@ -21253,7 +21765,7 @@ function composeWalkerPrompt(p) {
|
|
|
21253
21765
|
"Squinting Guardrails:",
|
|
21254
21766
|
"\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.",
|
|
21255
21767
|
"\u2022 Squinting does NOT permit drifting to unrelated settings, account profiles, or off-target pages. Stay strictly within the scenario journey.",
|
|
21256
|
-
"\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.
|
|
21768
|
+
"\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)."
|
|
21257
21769
|
].filter(Boolean) : [],
|
|
21258
21770
|
...p.untestableBoundaries && p.untestableBoundaries.length > 0 ? [
|
|
21259
21771
|
"",
|
|
@@ -21289,6 +21801,7 @@ function composeWalkerPrompt(p) {
|
|
|
21289
21801
|
`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`,
|
|
21290
21802
|
"`--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.",
|
|
21291
21803
|
`Before you finish, record synthesis with: ${iamu} summary ${p.runId} --worked "..." --worked "..." --question "..."`,
|
|
21804
|
+
`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.`,
|
|
21292
21805
|
"Execute everything via CLI commands in the shell. Never invoke host Edit or Write tools on files in the workspace.",
|
|
21293
21806
|
"",
|
|
21294
21807
|
"Do not read the product's source code, other iamu/ entity files, or earlier runs: you know only what the persona knows.",
|
|
@@ -21406,6 +21919,7 @@ async function instructionsWalk(opts) {
|
|
|
21406
21919
|
if (!targetUrl && doc.frontmatter.target_url) {
|
|
21407
21920
|
targetUrl = doc.frontmatter.target_url;
|
|
21408
21921
|
}
|
|
21922
|
+
if (doc.frontmatter.access) projectAccess = doc.frontmatter.access;
|
|
21409
21923
|
const fromFm = doc.frontmatter.untestable_boundaries ?? [];
|
|
21410
21924
|
const fromBody = extractUntestableBoundaries(doc.body);
|
|
21411
21925
|
untestableBoundaries.push(
|
|
@@ -21491,40 +22005,40 @@ async function instructionsWalk(opts) {
|
|
|
21491
22005
|
return;
|
|
21492
22006
|
}
|
|
21493
22007
|
console.log();
|
|
21494
|
-
console.log(
|
|
21495
|
-
console.log(` ${
|
|
21496
|
-
console.log(` ${
|
|
21497
|
-
console.log(` ${
|
|
22008
|
+
console.log(chalk30.bold(" Walk instructions"));
|
|
22009
|
+
console.log(` ${chalk30.dim("scenario:")} ${sfm.slug}`);
|
|
22010
|
+
console.log(` ${chalk30.dim("persona:")} ${pfm.name} (${pfm.role})`);
|
|
22011
|
+
console.log(` ${chalk30.dim("outline:")} ${ofm.title}`);
|
|
21498
22012
|
if (sfm.state?.description || sfm.state?.approximation) {
|
|
21499
22013
|
console.log(
|
|
21500
|
-
` ${
|
|
22014
|
+
` ${chalk30.dim("state:")} ${sfm.state.description ? sfm.state.description : sfm.state.approximation ?? ""}`
|
|
21501
22015
|
);
|
|
21502
22016
|
}
|
|
21503
|
-
if (targetUrl) console.log(` ${
|
|
22017
|
+
if (targetUrl) console.log(` ${chalk30.dim("target:")} ${targetUrl}`);
|
|
21504
22018
|
console.log(
|
|
21505
|
-
` ${
|
|
22019
|
+
` ${chalk30.dim("access:")} ${projectAccess || personaAccess ? `${projectAccess ? "project" : ""}${projectAccess && personaAccess ? " + " : ""}${personaAccess ? "persona" : ""}` : "not recorded (walker would run as whoever is signed in)"}`
|
|
21506
22020
|
);
|
|
21507
22021
|
console.log(
|
|
21508
|
-
` ${
|
|
22022
|
+
` ${chalk30.dim("dismissed:")} ${dismissedFacts.length} findings to skip`
|
|
21509
22023
|
);
|
|
21510
22024
|
console.log(
|
|
21511
|
-
` ${
|
|
22025
|
+
` ${chalk30.dim("guidance:")} ${guidance.length} tester guidance rule${guidance.length === 1 ? "" : "s"}`
|
|
21512
22026
|
);
|
|
21513
22027
|
if (untestableBoundaries.length > 0) {
|
|
21514
22028
|
console.log(
|
|
21515
|
-
` ${
|
|
22029
|
+
` ${chalk30.dim("boundaries:")} ${untestableBoundaries.length} untestable boundaries to avoid`
|
|
21516
22030
|
);
|
|
21517
22031
|
}
|
|
21518
22032
|
if (revisit.length > 0) {
|
|
21519
22033
|
console.log(
|
|
21520
|
-
` ${
|
|
22034
|
+
` ${chalk30.dim("re-check:")} ${revisit.length} place${revisit.length === 1 ? "" : "s"} where fixes await a re-run`
|
|
21521
22035
|
);
|
|
21522
22036
|
}
|
|
21523
22037
|
console.log(
|
|
21524
|
-
` ${
|
|
22038
|
+
` ${chalk30.dim("methodology:")} ${methodology ? "loaded" : "missing (run iamu update)"}`
|
|
21525
22039
|
);
|
|
21526
22040
|
console.log();
|
|
21527
|
-
console.log(
|
|
22041
|
+
console.log(chalk30.dim(" Use --json to get the full instruction bundle."));
|
|
21528
22042
|
console.log();
|
|
21529
22043
|
}
|
|
21530
22044
|
var SCREEN;
|
|
@@ -21550,8 +22064,8 @@ __export(new_finding_exports, {
|
|
|
21550
22064
|
});
|
|
21551
22065
|
import { randomUUID as randomUUID10 } from "crypto";
|
|
21552
22066
|
import { access, readFile as readFile7 } from "fs/promises";
|
|
21553
|
-
import
|
|
21554
|
-
import
|
|
22067
|
+
import path29 from "path";
|
|
22068
|
+
import chalk31 from "chalk";
|
|
21555
22069
|
async function newFinding(opts) {
|
|
21556
22070
|
const ctx = await resolveProject(opts);
|
|
21557
22071
|
if (!ctx) return;
|
|
@@ -21617,7 +22131,7 @@ async function newFinding(opts) {
|
|
|
21617
22131
|
}
|
|
21618
22132
|
try {
|
|
21619
22133
|
await access(
|
|
21620
|
-
|
|
22134
|
+
path29.join(runDirPath, "steps", `${String(n).padStart(3, "0")}.json`)
|
|
21621
22135
|
);
|
|
21622
22136
|
} catch {
|
|
21623
22137
|
commandError(
|
|
@@ -21632,7 +22146,7 @@ async function newFinding(opts) {
|
|
|
21632
22146
|
if (!scenarioSlug) {
|
|
21633
22147
|
try {
|
|
21634
22148
|
const manifestRaw = await readFile7(
|
|
21635
|
-
|
|
22149
|
+
path29.join(runDirPath, "walkthrough.json"),
|
|
21636
22150
|
"utf8"
|
|
21637
22151
|
);
|
|
21638
22152
|
const manifest = JSON.parse(manifestRaw);
|
|
@@ -21736,10 +22250,10 @@ async function newFinding(opts) {
|
|
|
21736
22250
|
return;
|
|
21737
22251
|
}
|
|
21738
22252
|
console.log();
|
|
21739
|
-
console.log(` ${symbols.success} Created finding: ${
|
|
21740
|
-
console.log(` ${
|
|
21741
|
-
console.log(` ${
|
|
21742
|
-
for (const w of warnings) console.log(` ${
|
|
22253
|
+
console.log(` ${symbols.success} Created finding: ${chalk31.cyan(slug)}`);
|
|
22254
|
+
console.log(` ${chalk31.dim("path:")} ${record.path}`);
|
|
22255
|
+
console.log(` ${chalk31.dim("id:")} ${id}`);
|
|
22256
|
+
for (const w of warnings) console.log(` ${chalk31.yellow("!")} ${w}`);
|
|
21743
22257
|
console.log();
|
|
21744
22258
|
} catch (err) {
|
|
21745
22259
|
commandError(
|
|
@@ -21764,9 +22278,13 @@ var summary_exports = {};
|
|
|
21764
22278
|
__export(summary_exports, {
|
|
21765
22279
|
summaryCommand: () => summaryCommand
|
|
21766
22280
|
});
|
|
21767
|
-
import { promises as
|
|
21768
|
-
import
|
|
21769
|
-
import
|
|
22281
|
+
import { promises as fs30 } from "fs";
|
|
22282
|
+
import path30 from "path";
|
|
22283
|
+
import chalk32 from "chalk";
|
|
22284
|
+
function blockedFrom(value) {
|
|
22285
|
+
const reason = value && typeof value === "object" ? value.reason : value;
|
|
22286
|
+
return typeof reason === "string" && reason.trim() ? { reason: reason.trim() } : void 0;
|
|
22287
|
+
}
|
|
21770
22288
|
async function summaryCommand(runIdArg, opts) {
|
|
21771
22289
|
const ctx = await resolveProject(opts);
|
|
21772
22290
|
if (!ctx) return;
|
|
@@ -21781,6 +22299,13 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
21781
22299
|
let whatWorked = [];
|
|
21782
22300
|
let openQuestions = [];
|
|
21783
22301
|
let model = opts.model?.trim() || void 0;
|
|
22302
|
+
const summaryPath = path30.join(runDirPath, "summary.json");
|
|
22303
|
+
let blocked;
|
|
22304
|
+
try {
|
|
22305
|
+
const prev = JSON.parse(await fs30.readFile(summaryPath, "utf8"));
|
|
22306
|
+
blocked = blockedFrom(prev.blocked);
|
|
22307
|
+
} catch {
|
|
22308
|
+
}
|
|
21784
22309
|
if (opts.file) {
|
|
21785
22310
|
try {
|
|
21786
22311
|
let raw;
|
|
@@ -21791,7 +22316,7 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
21791
22316
|
}
|
|
21792
22317
|
raw = Buffer.concat(chunks).toString("utf8");
|
|
21793
22318
|
} else {
|
|
21794
|
-
raw = await
|
|
22319
|
+
raw = await fs30.readFile(path30.resolve(projectRoot, opts.file), "utf8");
|
|
21795
22320
|
}
|
|
21796
22321
|
const parsed = JSON.parse(raw);
|
|
21797
22322
|
if (Array.isArray(parsed.what_worked)) {
|
|
@@ -21807,6 +22332,7 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
21807
22332
|
if (typeof parsed.model === "string" && parsed.model.trim()) {
|
|
21808
22333
|
model = parsed.model.trim();
|
|
21809
22334
|
}
|
|
22335
|
+
blocked = blockedFrom(parsed.blocked) ?? blocked;
|
|
21810
22336
|
} catch (err) {
|
|
21811
22337
|
commandError(
|
|
21812
22338
|
`Failed to parse summary file: ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -21828,12 +22354,17 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
21828
22354
|
if (cliQuestions.length > 0) {
|
|
21829
22355
|
openQuestions = [...openQuestions, ...cliQuestions];
|
|
21830
22356
|
}
|
|
22357
|
+
if (opts.blocked === false) {
|
|
22358
|
+
blocked = void 0;
|
|
22359
|
+
} else {
|
|
22360
|
+
blocked = blockedFrom(opts.blocked) ?? blocked;
|
|
22361
|
+
}
|
|
21831
22362
|
const data = {
|
|
21832
22363
|
what_worked: whatWorked,
|
|
21833
22364
|
open_questions: openQuestions,
|
|
21834
|
-
...model ? { model } : {}
|
|
22365
|
+
...model ? { model } : {},
|
|
22366
|
+
...blocked ? { blocked } : {}
|
|
21835
22367
|
};
|
|
21836
|
-
const summaryPath = path28.join(runDirPath, "summary.json");
|
|
21837
22368
|
await atomicWriteFile(summaryPath, `${JSON.stringify(data, null, 2)}
|
|
21838
22369
|
`);
|
|
21839
22370
|
if (opts.json) {
|
|
@@ -21841,7 +22372,7 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
21841
22372
|
JSON.stringify(
|
|
21842
22373
|
{
|
|
21843
22374
|
run_id: runId,
|
|
21844
|
-
path:
|
|
22375
|
+
path: path30.relative(projectRoot, summaryPath),
|
|
21845
22376
|
summary: data
|
|
21846
22377
|
},
|
|
21847
22378
|
null,
|
|
@@ -21852,16 +22383,19 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
21852
22383
|
}
|
|
21853
22384
|
console.log();
|
|
21854
22385
|
console.log(
|
|
21855
|
-
` ${symbols.success} Summary recorded for run ${
|
|
22386
|
+
` ${symbols.success} Summary recorded for run ${chalk32.cyan(runId)}`
|
|
21856
22387
|
);
|
|
22388
|
+
if (data.blocked) {
|
|
22389
|
+
console.log(` ${chalk32.yellow("blocked:")} ${data.blocked.reason}`);
|
|
22390
|
+
}
|
|
21857
22391
|
if (data.what_worked.length > 0) {
|
|
21858
22392
|
console.log(
|
|
21859
|
-
` ${
|
|
22393
|
+
` ${chalk32.dim("what worked:")} ${data.what_worked.length} item(s)`
|
|
21860
22394
|
);
|
|
21861
22395
|
}
|
|
21862
22396
|
if (data.open_questions.length > 0) {
|
|
21863
22397
|
console.log(
|
|
21864
|
-
` ${
|
|
22398
|
+
` ${chalk32.dim("open questions:")} ${data.open_questions.length} item(s)`
|
|
21865
22399
|
);
|
|
21866
22400
|
}
|
|
21867
22401
|
console.log();
|
|
@@ -21882,7 +22416,7 @@ __export(view_results_exports, {
|
|
|
21882
22416
|
promptViewResults: () => promptViewResults
|
|
21883
22417
|
});
|
|
21884
22418
|
import { isCancel as isCancel4, select as select4 } from "@clack/prompts";
|
|
21885
|
-
import
|
|
22419
|
+
import chalk33 from "chalk";
|
|
21886
22420
|
function launchCloudDashboard(config, runId) {
|
|
21887
22421
|
const cloudRunId = runId ? toCloudRunUuid(runId) : void 0;
|
|
21888
22422
|
const url = getCloudDashboardUrl(
|
|
@@ -21891,7 +22425,7 @@ function launchCloudDashboard(config, runId) {
|
|
|
21891
22425
|
cloudRunId
|
|
21892
22426
|
);
|
|
21893
22427
|
console.log();
|
|
21894
|
-
console.log(` ${symbols.success} Opening Iamu Cloud: ${
|
|
22428
|
+
console.log(` ${symbols.success} Opening Iamu Cloud: ${chalk33.cyan(url)}
|
|
21895
22429
|
`);
|
|
21896
22430
|
openBrowser(url);
|
|
21897
22431
|
}
|
|
@@ -21959,9 +22493,9 @@ __export(evaluate_exports, {
|
|
|
21959
22493
|
evaluate: () => evaluate,
|
|
21960
22494
|
evaluateRecord: () => evaluateRecord
|
|
21961
22495
|
});
|
|
21962
|
-
import { promises as
|
|
21963
|
-
import
|
|
21964
|
-
import
|
|
22496
|
+
import { promises as fs31 } from "fs";
|
|
22497
|
+
import path31 from "path";
|
|
22498
|
+
import chalk34 from "chalk";
|
|
21965
22499
|
import { z as z26 } from "zod";
|
|
21966
22500
|
function formatIssues(error) {
|
|
21967
22501
|
return error.issues.map(
|
|
@@ -21991,10 +22525,10 @@ async function evaluate(runId, opts) {
|
|
|
21991
22525
|
const resolved = await resolveProjectAndRunDir(runId, opts);
|
|
21992
22526
|
if (!resolved) return;
|
|
21993
22527
|
const { projectRoot, runDirPath } = resolved;
|
|
21994
|
-
const canonicalPath =
|
|
22528
|
+
const canonicalPath = path31.join(runDirPath, "canonical.json");
|
|
21995
22529
|
let canonical;
|
|
21996
22530
|
try {
|
|
21997
|
-
const raw = await
|
|
22531
|
+
const raw = await fs31.readFile(canonicalPath, "utf8");
|
|
21998
22532
|
canonical = JSON.parse(raw);
|
|
21999
22533
|
} catch {
|
|
22000
22534
|
commandError(
|
|
@@ -22050,15 +22584,15 @@ async function evaluate(runId, opts) {
|
|
|
22050
22584
|
)
|
|
22051
22585
|
};
|
|
22052
22586
|
const prompt = composeJudgePrompt(input);
|
|
22053
|
-
const promptFilePath = opts?.promptFile ??
|
|
22054
|
-
await
|
|
22587
|
+
const promptFilePath = opts?.promptFile ?? path31.join(runDirPath, "judge_prompt.md");
|
|
22588
|
+
await fs31.writeFile(promptFilePath, prompt, "utf8");
|
|
22055
22589
|
if (opts?.json) {
|
|
22056
22590
|
console.log(
|
|
22057
22591
|
JSON.stringify(
|
|
22058
22592
|
{
|
|
22059
22593
|
...input,
|
|
22060
|
-
prompt_file:
|
|
22061
|
-
prompt_file_abs:
|
|
22594
|
+
prompt_file: path31.relative(projectRoot, promptFilePath),
|
|
22595
|
+
prompt_file_abs: path31.resolve(promptFilePath),
|
|
22062
22596
|
judge_prompt: prompt
|
|
22063
22597
|
},
|
|
22064
22598
|
null,
|
|
@@ -22075,10 +22609,10 @@ async function evaluateRecord(runId, opts) {
|
|
|
22075
22609
|
const { projectRoot, runDirPath } = resolved;
|
|
22076
22610
|
sendLivePhase(projectRoot, runId, "judging").catch(() => {
|
|
22077
22611
|
});
|
|
22078
|
-
const canonicalPath =
|
|
22612
|
+
const canonicalPath = path31.join(runDirPath, "canonical.json");
|
|
22079
22613
|
let canonical;
|
|
22080
22614
|
try {
|
|
22081
|
-
const raw = await
|
|
22615
|
+
const raw = await fs31.readFile(canonicalPath, "utf8");
|
|
22082
22616
|
canonical = JSON.parse(raw);
|
|
22083
22617
|
} catch {
|
|
22084
22618
|
commandError(`No canonical.json for run: ${runId}`, opts);
|
|
@@ -22100,7 +22634,7 @@ async function evaluateRecord(runId, opts) {
|
|
|
22100
22634
|
}
|
|
22101
22635
|
raw = Buffer.concat(chunks).toString("utf8");
|
|
22102
22636
|
} else {
|
|
22103
|
-
raw = await
|
|
22637
|
+
raw = await fs31.readFile(opts.file, "utf8");
|
|
22104
22638
|
}
|
|
22105
22639
|
parsedFile = JSON.parse(raw);
|
|
22106
22640
|
} catch (err) {
|
|
@@ -22161,8 +22695,8 @@ ${formatIssues(fileResult.error)}`,
|
|
|
22161
22695
|
overall: inputVerdicts.overall
|
|
22162
22696
|
});
|
|
22163
22697
|
const reportContent = renderCanonicalMarkdown(canonicalForReport);
|
|
22164
|
-
const verdictsPath =
|
|
22165
|
-
const reportPath =
|
|
22698
|
+
const verdictsPath = path31.join(runDirPath, "verdicts.json");
|
|
22699
|
+
const reportPath = path31.join(runDirPath, "report.md");
|
|
22166
22700
|
await Promise.all([
|
|
22167
22701
|
atomicWriteFile(
|
|
22168
22702
|
verdictsPath,
|
|
@@ -22182,8 +22716,8 @@ ${formatIssues(fileResult.error)}`,
|
|
|
22182
22716
|
JSON.stringify(
|
|
22183
22717
|
{
|
|
22184
22718
|
runId,
|
|
22185
|
-
verdictsPath:
|
|
22186
|
-
reportPath:
|
|
22719
|
+
verdictsPath: path31.relative(projectRoot, verdictsPath),
|
|
22720
|
+
reportPath: path31.relative(projectRoot, reportPath),
|
|
22187
22721
|
verdictsCount: validatedVerdicts.length,
|
|
22188
22722
|
linkedCount: linked.length,
|
|
22189
22723
|
...cloudInfo ? { cloud: cloudInfo } : {}
|
|
@@ -22196,18 +22730,18 @@ ${formatIssues(fileResult.error)}`,
|
|
|
22196
22730
|
}
|
|
22197
22731
|
console.log();
|
|
22198
22732
|
console.log(
|
|
22199
|
-
` ${symbols.success} Recorded ${validatedVerdicts.length} verdicts for run ${
|
|
22733
|
+
` ${symbols.success} Recorded ${validatedVerdicts.length} verdicts for run ${chalk34.cyan(runId)}`
|
|
22200
22734
|
);
|
|
22201
22735
|
if (linked.length > 0) {
|
|
22202
22736
|
console.log(
|
|
22203
|
-
` ${
|
|
22737
|
+
` ${chalk34.dim("linked:")} ${linked.length} finding${linked.length === 1 ? "" : "s"} to the human test${linked.length === 1 ? "" : "s"} citing ${linked.length === 1 ? "it" : "them"}`
|
|
22204
22738
|
);
|
|
22205
22739
|
}
|
|
22206
22740
|
console.log(
|
|
22207
|
-
` ${
|
|
22741
|
+
` ${chalk34.dim("verdicts:")} ${path31.relative(projectRoot, verdictsPath)}`
|
|
22208
22742
|
);
|
|
22209
22743
|
console.log(
|
|
22210
|
-
` ${
|
|
22744
|
+
` ${chalk34.dim("report:")} ${path31.relative(projectRoot, reportPath)}`
|
|
22211
22745
|
);
|
|
22212
22746
|
printCloudRunInfo(cloudInfo);
|
|
22213
22747
|
await promptViewResults(projectRoot, { ...opts, runId });
|
|
@@ -22272,9 +22806,9 @@ __export(complete_exports, {
|
|
|
22272
22806
|
screenshotSizeWarning: () => screenshotSizeWarning,
|
|
22273
22807
|
validateCanonicalWalkthrough: () => validateCanonicalWalkthrough
|
|
22274
22808
|
});
|
|
22275
|
-
import { promises as
|
|
22276
|
-
import
|
|
22277
|
-
import
|
|
22809
|
+
import { promises as fs32 } from "fs";
|
|
22810
|
+
import path32 from "path";
|
|
22811
|
+
import chalk35 from "chalk";
|
|
22278
22812
|
async function complete(runId, opts) {
|
|
22279
22813
|
const run = await loadRun(runId, opts);
|
|
22280
22814
|
if (!run) return;
|
|
@@ -22286,7 +22820,7 @@ async function complete(runId, opts) {
|
|
|
22286
22820
|
}
|
|
22287
22821
|
if (!opts?.json) {
|
|
22288
22822
|
console.log();
|
|
22289
|
-
console.log(
|
|
22823
|
+
console.log(chalk35.bold(" Assembling canonical JSON\u2026"));
|
|
22290
22824
|
}
|
|
22291
22825
|
const provenanceAudit = auditStepProvenance(
|
|
22292
22826
|
await readStepsForRun(run.projectRoot, runId)
|
|
@@ -22299,8 +22833,8 @@ async function complete(runId, opts) {
|
|
|
22299
22833
|
const walkerSummary = await readWalkerSummary(run.runDirPath);
|
|
22300
22834
|
let judgeReported;
|
|
22301
22835
|
try {
|
|
22302
|
-
const verdictsRaw = await
|
|
22303
|
-
|
|
22836
|
+
const verdictsRaw = await fs32.readFile(
|
|
22837
|
+
path32.join(run.runDirPath, "verdicts.json"),
|
|
22304
22838
|
"utf8"
|
|
22305
22839
|
);
|
|
22306
22840
|
const parsed = JSON.parse(verdictsRaw);
|
|
@@ -22322,9 +22856,11 @@ async function complete(runId, opts) {
|
|
|
22322
22856
|
...durationSec != null ? { total_duration_sec: durationSec } : {},
|
|
22323
22857
|
...velocitySec != null ? { velocity_sec_per_step: velocitySec } : {}
|
|
22324
22858
|
};
|
|
22859
|
+
const { blocked: _previousBlock, ...manifestRest } = run.manifest;
|
|
22325
22860
|
run.manifest = {
|
|
22326
|
-
...
|
|
22327
|
-
models: updatedModels
|
|
22861
|
+
...manifestRest,
|
|
22862
|
+
models: updatedModels,
|
|
22863
|
+
...walkerSummary.blocked ? { blocked: walkerSummary.blocked } : {}
|
|
22328
22864
|
};
|
|
22329
22865
|
const assembled = await assembleRun(
|
|
22330
22866
|
run.projectRoot,
|
|
@@ -22352,16 +22888,16 @@ async function loadRun(runId, opts) {
|
|
|
22352
22888
|
if (!resolved) return null;
|
|
22353
22889
|
const { projectRoot, runDirPath } = resolved;
|
|
22354
22890
|
try {
|
|
22355
|
-
await
|
|
22891
|
+
await fs32.access(runDirPath);
|
|
22356
22892
|
} catch {
|
|
22357
22893
|
commandError(`Run not found: ${runId}`, opts);
|
|
22358
22894
|
return null;
|
|
22359
22895
|
}
|
|
22360
|
-
const manifestPath =
|
|
22896
|
+
const manifestPath = path32.join(runDirPath, "walkthrough.json");
|
|
22361
22897
|
let manifest;
|
|
22362
22898
|
try {
|
|
22363
22899
|
manifest = JSON.parse(
|
|
22364
|
-
await
|
|
22900
|
+
await fs32.readFile(manifestPath, "utf8")
|
|
22365
22901
|
);
|
|
22366
22902
|
} catch {
|
|
22367
22903
|
commandError(`No walkthrough.json found for run: ${runId}`, opts);
|
|
@@ -22373,8 +22909,8 @@ async function reportAlreadyCompleted(run, opts) {
|
|
|
22373
22909
|
const { runId, manifest } = run;
|
|
22374
22910
|
if (opts?.json) {
|
|
22375
22911
|
try {
|
|
22376
|
-
const raw = await
|
|
22377
|
-
|
|
22912
|
+
const raw = await fs32.readFile(
|
|
22913
|
+
path32.join(run.runDirPath, "canonical.json"),
|
|
22378
22914
|
"utf8"
|
|
22379
22915
|
);
|
|
22380
22916
|
console.log(raw.trim());
|
|
@@ -22394,18 +22930,18 @@ async function reportAlreadyCompleted(run, opts) {
|
|
|
22394
22930
|
return;
|
|
22395
22931
|
}
|
|
22396
22932
|
console.log();
|
|
22397
|
-
console.log(
|
|
22398
|
-
console.log(` status ${
|
|
22933
|
+
console.log(chalk35.bold(" Run: ") + chalk35.cyan(runId));
|
|
22934
|
+
console.log(` status ${chalk35.green("completed")}`);
|
|
22399
22935
|
if (manifest.completed_at) {
|
|
22400
|
-
console.log(` completed ${
|
|
22936
|
+
console.log(` completed ${chalk35.dim(manifest.completed_at)}`);
|
|
22401
22937
|
}
|
|
22402
22938
|
const viewUrl = await pushedRunUrl(run.projectRoot, runId);
|
|
22403
22939
|
if (viewUrl) {
|
|
22404
|
-
console.log(` view ${
|
|
22940
|
+
console.log(` view ${chalk35.underline(viewUrl)}`);
|
|
22405
22941
|
}
|
|
22406
22942
|
console.log();
|
|
22407
22943
|
console.log(
|
|
22408
|
-
|
|
22944
|
+
chalk35.dim(
|
|
22409
22945
|
" Already completed \u2014 nothing to do. Use --force to reassemble the report."
|
|
22410
22946
|
)
|
|
22411
22947
|
);
|
|
@@ -22421,13 +22957,13 @@ function reportStrictProvenanceFailure(audit, opts) {
|
|
|
22421
22957
|
}
|
|
22422
22958
|
console.error();
|
|
22423
22959
|
console.error(
|
|
22424
|
-
|
|
22960
|
+
chalk35.red(` ${symbols.error} Strict provenance audit failed:`)
|
|
22425
22961
|
);
|
|
22426
22962
|
for (const w of audit.warnings) {
|
|
22427
|
-
console.error(
|
|
22963
|
+
console.error(chalk35.red(` \u2022 ${w}`));
|
|
22428
22964
|
}
|
|
22429
22965
|
console.error(
|
|
22430
|
-
|
|
22966
|
+
chalk35.dim(
|
|
22431
22967
|
" Run was flagged as synthetic / scripted. Remove --strict-provenance to complete anyway."
|
|
22432
22968
|
)
|
|
22433
22969
|
);
|
|
@@ -22468,41 +23004,48 @@ function completeJsonPayload(canonical, extras) {
|
|
|
22468
23004
|
function printCompletionSummary(run, completedAt, assembled, extras, provenanceAudit) {
|
|
22469
23005
|
const { runId, projectRoot, manifest } = run;
|
|
22470
23006
|
const { canonical } = assembled;
|
|
22471
|
-
const count = (n) => n === 0 ?
|
|
22472
|
-
console.log(
|
|
22473
|
-
console.log(` scenario ${
|
|
22474
|
-
console.log(` url ${
|
|
23007
|
+
const count = (n) => n === 0 ? chalk35.dim("0") : chalk35.cyan(String(n));
|
|
23008
|
+
console.log(chalk35.bold(" Run: ") + chalk35.cyan(runId));
|
|
23009
|
+
console.log(` scenario ${chalk35.dim(manifest.scenario_slug)}`);
|
|
23010
|
+
console.log(` url ${chalk35.dim(manifest.target_url)}`);
|
|
22475
23011
|
if (manifest.environment) {
|
|
22476
|
-
console.log(` env ${
|
|
23012
|
+
console.log(` env ${chalk35.dim(manifest.environment)}`);
|
|
22477
23013
|
}
|
|
22478
23014
|
if (manifest.build) {
|
|
22479
|
-
console.log(` build ${
|
|
23015
|
+
console.log(` build ${chalk35.dim(formatBuild(manifest.build))}`);
|
|
22480
23016
|
}
|
|
22481
23017
|
if (manifest.viewport) {
|
|
22482
|
-
console.log(` device ${
|
|
23018
|
+
console.log(` device ${chalk35.dim(formatViewport(manifest.viewport))}`);
|
|
23019
|
+
}
|
|
23020
|
+
if (manifest.blocked) {
|
|
23021
|
+
console.log(
|
|
23022
|
+
` status ${chalk35.yellow("blocked")} ${chalk35.dim("\u2014 the walk could not start as set up")}`
|
|
23023
|
+
);
|
|
23024
|
+
console.log(` reason ${manifest.blocked.reason}`);
|
|
23025
|
+
} else {
|
|
23026
|
+
console.log(` status ${chalk35.green("completed")}`);
|
|
22483
23027
|
}
|
|
22484
|
-
console.log(` status ${chalk33.green("completed")}`);
|
|
22485
23028
|
console.log(` findings ${count(canonical.findings.length)}`);
|
|
22486
23029
|
console.log(` steps ${count(canonical.steps.length)}`);
|
|
22487
|
-
console.log(` started ${
|
|
22488
|
-
console.log(` completed ${
|
|
23030
|
+
console.log(` started ${chalk35.dim(manifest.created_at)}`);
|
|
23031
|
+
console.log(` completed ${chalk35.dim(completedAt)}`);
|
|
22489
23032
|
const models = manifest.models;
|
|
22490
23033
|
if (models?.velocity_sec_per_step != null) {
|
|
22491
23034
|
console.log(
|
|
22492
|
-
` velocity ${
|
|
23035
|
+
` velocity ${chalk35.cyan(`~${models.velocity_sec_per_step}s/step`)} ${chalk35.dim(`(${models.total_duration_sec}s total)`)}`
|
|
22493
23036
|
);
|
|
22494
23037
|
}
|
|
22495
23038
|
if (models && (models.walker || models.judge || models.matcher)) {
|
|
22496
23039
|
const w = models.walker_reported ? `${models.walker} (${models.walker_reported})` : models.walker ?? "fast";
|
|
22497
23040
|
const j = models.judge_reported ? `${models.judge} (${models.judge_reported})` : models.judge ?? "thorough";
|
|
22498
|
-
console.log(` models ${
|
|
23041
|
+
console.log(` models ${chalk35.dim(`walker: ${w} \xB7 judge: ${j}`)}`);
|
|
22499
23042
|
}
|
|
22500
23043
|
console.log();
|
|
22501
23044
|
console.log(
|
|
22502
|
-
` ${
|
|
23045
|
+
` ${chalk35.dim("canonical:")} ${chalk35.dim(path32.relative(projectRoot, assembled.canonicalPath))}`
|
|
22503
23046
|
);
|
|
22504
23047
|
console.log(
|
|
22505
|
-
` ${
|
|
23048
|
+
` ${chalk35.dim("report:")} ${chalk35.dim(path32.relative(projectRoot, assembled.reportPath))}`
|
|
22506
23049
|
);
|
|
22507
23050
|
if (extras.viewportWarning) {
|
|
22508
23051
|
console.log();
|
|
@@ -22510,25 +23053,36 @@ function printCompletionSummary(run, completedAt, assembled, extras, provenanceA
|
|
|
22510
23053
|
}
|
|
22511
23054
|
if (extras.codeWarning) {
|
|
22512
23055
|
console.log();
|
|
22513
|
-
console.log(` ${
|
|
23056
|
+
console.log(` ${chalk35.yellow("!")} ${extras.codeWarning}`);
|
|
22514
23057
|
}
|
|
22515
23058
|
if (extras.revisit) printRevisit(extras.revisit);
|
|
22516
23059
|
if (assembled.verification) printVerification(assembled.verification);
|
|
22517
23060
|
if (assembled.journeyDiff) printJourneyDiff(assembled.journeyDiff);
|
|
22518
23061
|
printProvenanceWarnings(provenanceAudit);
|
|
22519
23062
|
printCloudRunInfo(extras.cloudInfo);
|
|
23063
|
+
if (manifest.blocked) {
|
|
23064
|
+
console.log();
|
|
23065
|
+
console.log(
|
|
23066
|
+
` ${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.`
|
|
23067
|
+
);
|
|
23068
|
+
if (canonical.findings.length > 0) {
|
|
23069
|
+
console.log(
|
|
23070
|
+
` ${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.`
|
|
23071
|
+
);
|
|
23072
|
+
}
|
|
23073
|
+
}
|
|
22520
23074
|
}
|
|
22521
23075
|
function printProvenanceWarnings(audit) {
|
|
22522
23076
|
if (audit.warnings.length === 0) return;
|
|
22523
23077
|
console.log();
|
|
22524
23078
|
console.log(
|
|
22525
|
-
` ${symbols.warning} ${
|
|
23079
|
+
` ${symbols.warning} ${chalk35.yellow("Step provenance audit flagged suspicious timing:")}`
|
|
22526
23080
|
);
|
|
22527
23081
|
for (const w of audit.warnings) {
|
|
22528
|
-
console.log(` ${
|
|
23082
|
+
console.log(` ${chalk35.dim("\u2022")} ${w}`);
|
|
22529
23083
|
}
|
|
22530
23084
|
console.log(
|
|
22531
|
-
|
|
23085
|
+
chalk35.dim(
|
|
22532
23086
|
" Run appears to be programmatically batch-generated rather than an interactive walkthrough."
|
|
22533
23087
|
)
|
|
22534
23088
|
);
|
|
@@ -22560,17 +23114,17 @@ function screenshotFitsViewport(width, expected) {
|
|
|
22560
23114
|
}
|
|
22561
23115
|
async function screenshotSizeWarning(runDirPath, viewport) {
|
|
22562
23116
|
if (!viewport) return null;
|
|
22563
|
-
const dir =
|
|
23117
|
+
const dir = path32.join(runDirPath, "screenshots");
|
|
22564
23118
|
let first;
|
|
22565
23119
|
try {
|
|
22566
|
-
first = (await
|
|
23120
|
+
first = (await fs32.readdir(dir)).filter((f) => f.toLowerCase().endsWith(".png")).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }))[0];
|
|
22567
23121
|
} catch {
|
|
22568
23122
|
return null;
|
|
22569
23123
|
}
|
|
22570
23124
|
if (!first) return null;
|
|
22571
23125
|
let size = null;
|
|
22572
23126
|
try {
|
|
22573
|
-
const handle = await
|
|
23127
|
+
const handle = await fs32.open(path32.join(dir, first), "r");
|
|
22574
23128
|
try {
|
|
22575
23129
|
const head = Buffer.alloc(24);
|
|
22576
23130
|
const { bytesRead } = await handle.read(head, 0, 24, 0);
|
|
@@ -22587,14 +23141,16 @@ async function screenshotSizeWarning(runDirPath, viewport) {
|
|
|
22587
23141
|
async function readWalkerSummary(runDirPath) {
|
|
22588
23142
|
try {
|
|
22589
23143
|
const data = JSON.parse(
|
|
22590
|
-
await
|
|
23144
|
+
await fs32.readFile(path32.join(runDirPath, "summary.json"), "utf8")
|
|
22591
23145
|
);
|
|
22592
23146
|
const strings = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && x.trim() !== "") : void 0;
|
|
22593
23147
|
const model = typeof data.model === "string" && data.model.trim() !== "" ? data.model.trim() : void 0;
|
|
23148
|
+
const reason = data.blocked?.reason;
|
|
22594
23149
|
return {
|
|
22595
23150
|
what_worked: strings(data.what_worked),
|
|
22596
23151
|
open_questions: strings(data.open_questions),
|
|
22597
|
-
model
|
|
23152
|
+
model,
|
|
23153
|
+
...typeof reason === "string" && reason.trim() !== "" ? { blocked: { reason: reason.trim() } } : {}
|
|
22598
23154
|
};
|
|
22599
23155
|
} catch {
|
|
22600
23156
|
return {};
|
|
@@ -22696,7 +23252,7 @@ function validateCanonicalWalkthrough(canonical) {
|
|
|
22696
23252
|
async function applyRecordedVerdicts(canonical, runDirPath) {
|
|
22697
23253
|
try {
|
|
22698
23254
|
const recorded = JSON.parse(
|
|
22699
|
-
await
|
|
23255
|
+
await fs32.readFile(path32.join(runDirPath, "verdicts.json"), "utf8")
|
|
22700
23256
|
);
|
|
22701
23257
|
if (Array.isArray(recorded.verdicts)) {
|
|
22702
23258
|
return applyVerdicts(canonical, recorded);
|
|
@@ -22706,7 +23262,7 @@ async function applyRecordedVerdicts(canonical, runDirPath) {
|
|
|
22706
23262
|
return canonical;
|
|
22707
23263
|
}
|
|
22708
23264
|
async function persistCanonical(runDirPath, canonical) {
|
|
22709
|
-
const canonicalPath =
|
|
23265
|
+
const canonicalPath = path32.join(runDirPath, "canonical.json");
|
|
22710
23266
|
await atomicWriteFile(
|
|
22711
23267
|
canonicalPath,
|
|
22712
23268
|
`${JSON.stringify(canonical, null, 2)}
|
|
@@ -22715,7 +23271,7 @@ async function persistCanonical(runDirPath, canonical) {
|
|
|
22715
23271
|
return canonicalPath;
|
|
22716
23272
|
}
|
|
22717
23273
|
async function persistReport(runDirPath, canonical) {
|
|
22718
|
-
const reportPath =
|
|
23274
|
+
const reportPath = path32.join(runDirPath, "report.md");
|
|
22719
23275
|
await atomicWriteFile(reportPath, renderCanonicalMarkdown(canonical));
|
|
22720
23276
|
return reportPath;
|
|
22721
23277
|
}
|
|
@@ -22727,7 +23283,7 @@ async function assembleRun(projectRoot, runId, manifest, completedAt) {
|
|
|
22727
23283
|
completedAt,
|
|
22728
23284
|
await readWalkerSummary(runDirPath)
|
|
22729
23285
|
);
|
|
22730
|
-
const verification = await compareWithBaseline(
|
|
23286
|
+
const verification = manifest.blocked ? null : await compareWithBaseline(
|
|
22731
23287
|
projectRoot,
|
|
22732
23288
|
manifest.scenario_slug,
|
|
22733
23289
|
runId,
|
|
@@ -22815,17 +23371,17 @@ function placeLabel(p) {
|
|
|
22815
23371
|
function printRevisit(r) {
|
|
22816
23372
|
const missed = r.places.filter((p) => !p.reached);
|
|
22817
23373
|
console.log();
|
|
22818
|
-
const head = ` ${
|
|
23374
|
+
const head = ` ${chalk35.bold("Re-check places:")} reached ${r.reached} of ${r.requested}`;
|
|
22819
23375
|
if (missed.length === 0) {
|
|
22820
|
-
console.log(`${head} ${
|
|
23376
|
+
console.log(`${head} ${chalk35.green("\u2713")}`);
|
|
22821
23377
|
return;
|
|
22822
23378
|
}
|
|
22823
23379
|
console.log(`${head} \u2014 not reached:`);
|
|
22824
23380
|
for (const p of missed) {
|
|
22825
|
-
console.log(` ${
|
|
23381
|
+
console.log(` ${chalk35.yellow("\u25CB")} ${placeLabel(p)}`);
|
|
22826
23382
|
}
|
|
22827
23383
|
console.log(
|
|
22828
|
-
|
|
23384
|
+
chalk35.dim(
|
|
22829
23385
|
" Fixes there stay awaiting a re-run until a walk gets there."
|
|
22830
23386
|
)
|
|
22831
23387
|
);
|
|
@@ -22833,7 +23389,7 @@ function printRevisit(r) {
|
|
|
22833
23389
|
function printVerification(verification) {
|
|
22834
23390
|
console.log();
|
|
22835
23391
|
console.log(
|
|
22836
|
-
|
|
23392
|
+
chalk35.bold(" Since this scenario's last run") + chalk35.dim(` (${verification.baseline_run_id.slice(0, 30)}\u2026)`) + chalk35.bold(":")
|
|
22837
23393
|
);
|
|
22838
23394
|
if (verification.resolved.length > 0) {
|
|
22839
23395
|
console.log(
|
|
@@ -22842,7 +23398,7 @@ function printVerification(verification) {
|
|
|
22842
23398
|
}
|
|
22843
23399
|
if (verification.not_observed.length > 0) {
|
|
22844
23400
|
console.log(
|
|
22845
|
-
` ${symbols.pending} ${verification.not_observed.length} not seen this run ${
|
|
23401
|
+
` ${symbols.pending} ${verification.not_observed.length} not seen this run ${chalk35.dim("(not evidence of a fix)")}`
|
|
22846
23402
|
);
|
|
22847
23403
|
}
|
|
22848
23404
|
if (verification.persists.length > 0) {
|
|
@@ -22884,14 +23440,14 @@ __export(match_exports, {
|
|
|
22884
23440
|
match: () => match,
|
|
22885
23441
|
matchRecord: () => matchRecord
|
|
22886
23442
|
});
|
|
22887
|
-
import { promises as
|
|
22888
|
-
import
|
|
22889
|
-
import
|
|
23443
|
+
import { promises as fs33 } from "fs";
|
|
23444
|
+
import path33 from "path";
|
|
23445
|
+
import chalk36 from "chalk";
|
|
22890
23446
|
async function completedManifest2(runDirPath, runId, opts) {
|
|
22891
23447
|
let manifest;
|
|
22892
23448
|
try {
|
|
22893
23449
|
manifest = JSON.parse(
|
|
22894
|
-
await
|
|
23450
|
+
await fs33.readFile(path33.join(runDirPath, "walkthrough.json"), "utf8")
|
|
22895
23451
|
);
|
|
22896
23452
|
} catch {
|
|
22897
23453
|
commandError(`Run not found: ${runId}`, opts);
|
|
@@ -22918,14 +23474,14 @@ async function match(runId, opts) {
|
|
|
22918
23474
|
runId,
|
|
22919
23475
|
manifest.scenario_slug
|
|
22920
23476
|
);
|
|
22921
|
-
const needed = input.current.length > 0 && (input.prior.length > 0 || (input.guidance ?? []).length > 0);
|
|
23477
|
+
const needed = !manifest.blocked && input.current.length > 0 && (input.prior.length > 0 || (input.guidance ?? []).length > 0);
|
|
22922
23478
|
const prompt = needed ? composeMatchPrompt(input) : null;
|
|
22923
|
-
const promptFilePath = opts?.promptFile ??
|
|
23479
|
+
const promptFilePath = opts?.promptFile ?? path33.join(runDirPath, "match_prompt.md");
|
|
22924
23480
|
if (prompt) {
|
|
22925
|
-
await
|
|
23481
|
+
await fs33.writeFile(promptFilePath, prompt, "utf8");
|
|
22926
23482
|
} else {
|
|
22927
23483
|
try {
|
|
22928
|
-
await
|
|
23484
|
+
await fs33.unlink(promptFilePath);
|
|
22929
23485
|
} catch {
|
|
22930
23486
|
}
|
|
22931
23487
|
}
|
|
@@ -22935,8 +23491,9 @@ async function match(runId, opts) {
|
|
|
22935
23491
|
{
|
|
22936
23492
|
...input,
|
|
22937
23493
|
needed,
|
|
22938
|
-
|
|
22939
|
-
|
|
23494
|
+
...manifest.blocked ? { blocked: manifest.blocked } : {},
|
|
23495
|
+
prompt_file: prompt ? path33.relative(ctx.projectRoot, promptFilePath) : null,
|
|
23496
|
+
prompt_file_abs: prompt ? path33.resolve(promptFilePath) : null,
|
|
22940
23497
|
match_prompt: prompt
|
|
22941
23498
|
},
|
|
22942
23499
|
null,
|
|
@@ -22947,7 +23504,7 @@ async function match(runId, opts) {
|
|
|
22947
23504
|
}
|
|
22948
23505
|
if (!prompt) {
|
|
22949
23506
|
console.log(
|
|
22950
|
-
|
|
23507
|
+
chalk36.dim(
|
|
22951
23508
|
" No earlier findings or tester guidance for this scenario to match against \u2014 nothing to do."
|
|
22952
23509
|
)
|
|
22953
23510
|
);
|
|
@@ -22979,7 +23536,7 @@ async function matchRecord(runId, opts) {
|
|
|
22979
23536
|
}
|
|
22980
23537
|
raw = Buffer.concat(chunks).toString("utf8");
|
|
22981
23538
|
} else {
|
|
22982
|
-
raw = await
|
|
23539
|
+
raw = await fs33.readFile(opts.file, "utf8");
|
|
22983
23540
|
}
|
|
22984
23541
|
answer = JSON.parse(raw);
|
|
22985
23542
|
} catch (err) {
|
|
@@ -23025,17 +23582,17 @@ async function matchRecord(runId, opts) {
|
|
|
23025
23582
|
}
|
|
23026
23583
|
console.log();
|
|
23027
23584
|
console.log(
|
|
23028
|
-
` ${
|
|
23585
|
+
` ${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`
|
|
23029
23586
|
);
|
|
23030
23587
|
if (result.suppressed > 0) {
|
|
23031
23588
|
console.log(
|
|
23032
|
-
` ${
|
|
23589
|
+
` ${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)`
|
|
23033
23590
|
);
|
|
23034
23591
|
}
|
|
23035
23592
|
if (verification) printVerification(verification);
|
|
23036
23593
|
console.log();
|
|
23037
23594
|
console.log(
|
|
23038
|
-
|
|
23595
|
+
chalk36.dim(" Run `iamu sync` to push the linked findings to the cloud.")
|
|
23039
23596
|
);
|
|
23040
23597
|
console.log();
|
|
23041
23598
|
}
|
|
@@ -23057,7 +23614,7 @@ __export(prune_exports, {
|
|
|
23057
23614
|
prune: () => prune
|
|
23058
23615
|
});
|
|
23059
23616
|
import { confirm, isCancel as isCancel5 } from "@clack/prompts";
|
|
23060
|
-
import
|
|
23617
|
+
import chalk37 from "chalk";
|
|
23061
23618
|
function formatBytes(bytes) {
|
|
23062
23619
|
if (bytes <= 0) return "0 B";
|
|
23063
23620
|
if (bytes < 1024) return `${bytes} B`;
|
|
@@ -23103,7 +23660,7 @@ async function prune(opts = {}) {
|
|
|
23103
23660
|
});
|
|
23104
23661
|
if (preview.prunedRuns.length === 0) {
|
|
23105
23662
|
console.log(
|
|
23106
|
-
|
|
23663
|
+
chalk37.dim(" No runs match the pruning criteria. Nothing to prune.")
|
|
23107
23664
|
);
|
|
23108
23665
|
return preview;
|
|
23109
23666
|
}
|
|
@@ -23112,7 +23669,7 @@ async function prune(opts = {}) {
|
|
|
23112
23669
|
message: `Prune ${mode} ${preview.prunedRuns.length} run(s) and reclaim ${formatBytes(preview.totalBytesReclaimed)}?`
|
|
23113
23670
|
});
|
|
23114
23671
|
if (isCancel5(confirmed) || !confirmed) {
|
|
23115
|
-
console.log(
|
|
23672
|
+
console.log(chalk37.yellow(" Pruning cancelled."));
|
|
23116
23673
|
return;
|
|
23117
23674
|
}
|
|
23118
23675
|
}
|
|
@@ -23123,14 +23680,14 @@ async function prune(opts = {}) {
|
|
|
23123
23680
|
}
|
|
23124
23681
|
if (result.dryRun) {
|
|
23125
23682
|
console.log(
|
|
23126
|
-
|
|
23683
|
+
chalk37.cyan(
|
|
23127
23684
|
`
|
|
23128
23685
|
[DRY RUN] Would prune ${result.prunedRuns.length} of ${result.totalRunsScanned} run(s):`
|
|
23129
23686
|
)
|
|
23130
23687
|
);
|
|
23131
23688
|
} else {
|
|
23132
23689
|
console.log(
|
|
23133
|
-
|
|
23690
|
+
chalk37.bold(
|
|
23134
23691
|
`
|
|
23135
23692
|
${symbols.success} Pruned ${result.prunedRuns.length} of ${result.totalRunsScanned} run(s):`
|
|
23136
23693
|
)
|
|
@@ -23138,18 +23695,18 @@ async function prune(opts = {}) {
|
|
|
23138
23695
|
}
|
|
23139
23696
|
if (result.prunedRuns.length === 0) {
|
|
23140
23697
|
console.log(
|
|
23141
|
-
|
|
23698
|
+
chalk37.dim(" No runs matched the pruning criteria. Nothing pruned.")
|
|
23142
23699
|
);
|
|
23143
23700
|
} else {
|
|
23144
23701
|
for (const run of result.prunedRuns) {
|
|
23145
23702
|
const mode = run.fullyDeleted ? "removed" : "screenshots purged";
|
|
23146
23703
|
const count = run.screenshotsDeleted > 0 ? `${run.screenshotsDeleted} screenshots, ` : "";
|
|
23147
23704
|
console.log(
|
|
23148
|
-
` ${
|
|
23705
|
+
` ${chalk37.dim("\u2022")} ${chalk37.bold(run.runId)} ${chalk37.dim(`(${run.scenarioSlug ?? "unknown scenario"})`)}: ${chalk37.yellow(formatBytes(run.bytesReclaimed))} (${count}${mode})`
|
|
23149
23706
|
);
|
|
23150
23707
|
}
|
|
23151
23708
|
console.log(
|
|
23152
|
-
|
|
23709
|
+
chalk37.dim(
|
|
23153
23710
|
`
|
|
23154
23711
|
Total reclaimed: ${formatBytes(result.totalBytesReclaimed)} (${result.totalScreenshotsDeleted} screenshots deleted)
|
|
23155
23712
|
`
|
|
@@ -23338,9 +23895,9 @@ function registerProjectCommands(program) {
|
|
|
23338
23895
|
"after",
|
|
23339
23896
|
"\nExamples:\n $ iamu validate --all\n $ iamu validate --all --fix --dry-run\n $ iamu validate iamu/personas/maria.md"
|
|
23340
23897
|
).action(
|
|
23341
|
-
async (
|
|
23898
|
+
async (path34, opts) => {
|
|
23342
23899
|
const { validate: validate2 } = await Promise.resolve().then(() => (init_validate(), validate_exports));
|
|
23343
|
-
await validate2(
|
|
23900
|
+
await validate2(path34, opts);
|
|
23344
23901
|
}
|
|
23345
23902
|
);
|
|
23346
23903
|
program.command("update").helpGroup("Project:").description("Update and repair agent skills and prompts to latest version").option(
|
|
@@ -23392,6 +23949,51 @@ function registerProjectCommands(program) {
|
|
|
23392
23949
|
await newEntity2(type2, opts);
|
|
23393
23950
|
}
|
|
23394
23951
|
);
|
|
23952
|
+
program.command("set <type> <slug>").helpGroup("Project:").description(
|
|
23953
|
+
"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)"
|
|
23954
|
+
).option(
|
|
23955
|
+
"--entry <path>",
|
|
23956
|
+
"Where the walk starts, as a path on the product (scenario)"
|
|
23957
|
+
).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(
|
|
23958
|
+
"--state-description <text>",
|
|
23959
|
+
"What the world must contain, in domain words (scenario)"
|
|
23960
|
+
).option(
|
|
23961
|
+
"--state-approximation <text>",
|
|
23962
|
+
"How the walker picks the closest record when the exact one is missing (scenario)"
|
|
23963
|
+
).option("--no-state", "Clear the starting state (scenario)").option(
|
|
23964
|
+
"--access <key>",
|
|
23965
|
+
"Account, role or switcher key this persona uses (persona)"
|
|
23966
|
+
).option("--no-access", "Clear the access key (persona)").option(
|
|
23967
|
+
"--paths <paths>",
|
|
23968
|
+
"Route prefixes, comma-separated; replaces the list (capability)"
|
|
23969
|
+
).option("--no-paths", "Clear route prefixes (capability)").option(
|
|
23970
|
+
"--add-path <path>",
|
|
23971
|
+
"Add a route prefix (capability, repeatable)",
|
|
23972
|
+
(val, prev = []) => [...prev, val]
|
|
23973
|
+
).option(
|
|
23974
|
+
"--remove-path <path>",
|
|
23975
|
+
"Remove a route prefix (capability, repeatable)",
|
|
23976
|
+
(val, prev = []) => [...prev, val]
|
|
23977
|
+
).option("--area <name>", "Visual grouping category (capability)").option("--no-area", "Clear the area (capability)").option(
|
|
23978
|
+
"--file <path>",
|
|
23979
|
+
"JSON patch file to apply staging fields (or - for stdin)"
|
|
23980
|
+
).option("--json", "Output as JSON").addHelpText(
|
|
23981
|
+
"after",
|
|
23982
|
+
'\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'
|
|
23983
|
+
).action(
|
|
23984
|
+
async (type2, slug, opts) => {
|
|
23985
|
+
const { setEntity: setEntity2 } = await Promise.resolve().then(() => (init_set_entity(), set_entity_exports));
|
|
23986
|
+
await setEntity2(type2, slug, opts);
|
|
23987
|
+
}
|
|
23988
|
+
);
|
|
23989
|
+
program.command("edit <type> <slug>").helpGroup("Project:").description(
|
|
23990
|
+
"Open an entity file in your editor ($VISUAL or $EDITOR) and validate on exit"
|
|
23991
|
+
).option("--json", "Output as JSON").action(
|
|
23992
|
+
async (type2, slug, opts = {}) => {
|
|
23993
|
+
const { editEntity: editEntity2 } = await Promise.resolve().then(() => (init_edit_entity(), edit_entity_exports));
|
|
23994
|
+
await editEntity2(type2, slug, opts);
|
|
23995
|
+
}
|
|
23996
|
+
);
|
|
23395
23997
|
const explore = program.command("explore").helpGroup("Project:").description(
|
|
23396
23998
|
"Map the running product with a read-only crawl; compare the map with runs (coverage) or with another map (diff)"
|
|
23397
23999
|
);
|
|
@@ -23593,14 +24195,17 @@ function registerWalkthroughCommands(program) {
|
|
|
23593
24195
|
}
|
|
23594
24196
|
);
|
|
23595
24197
|
program.command("summary [run-id]").helpGroup("Walkthrough:").description(
|
|
23596
|
-
"Record walker summary (what worked, open questions) atomically"
|
|
24198
|
+
"Record walker summary (what worked, open questions, or that the walk was blocked) atomically"
|
|
23597
24199
|
).option("--run <run-id>", "Run ID (or pass as positional argument)").option(
|
|
23598
24200
|
"--worked <note...>",
|
|
23599
24201
|
"Things that worked well (can be specified multiple times)"
|
|
23600
24202
|
).option(
|
|
23601
24203
|
"--question <note...>",
|
|
23602
24204
|
"Open questions that arose (can be specified multiple times)"
|
|
23603
|
-
).option("--model <name>", "Model identifier (e.g. flash, haiku)").option(
|
|
24205
|
+
).option("--model <name>", "Model identifier (e.g. flash, haiku)").option(
|
|
24206
|
+
"--blocked <reason>",
|
|
24207
|
+
"The walk could not start as set up (persona unreachable, world missing): what was missing"
|
|
24208
|
+
).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(
|
|
23604
24209
|
async (runIdArg, opts) => {
|
|
23605
24210
|
const { summaryCommand: summaryCommand2 } = await Promise.resolve().then(() => (init_summary(), summary_exports));
|
|
23606
24211
|
await summaryCommand2(runIdArg, opts);
|
|
@@ -23674,7 +24279,7 @@ function registerAllCommands(program) {
|
|
|
23674
24279
|
|
|
23675
24280
|
// src/ui/panic.ts
|
|
23676
24281
|
init_logger();
|
|
23677
|
-
import
|
|
24282
|
+
import chalk38 from "chalk";
|
|
23678
24283
|
var handlersRegistered = false;
|
|
23679
24284
|
var customSigintHandler = null;
|
|
23680
24285
|
function registerSigintCleanup(handler) {
|
|
@@ -23686,17 +24291,17 @@ function registerSigintCleanup(handler) {
|
|
|
23686
24291
|
function logFatalAndExit(title, errOrReason) {
|
|
23687
24292
|
process.stderr.write("\x1B[?25h");
|
|
23688
24293
|
if (isVerbose()) {
|
|
23689
|
-
console.error(
|
|
24294
|
+
console.error(chalk38.red(`
|
|
23690
24295
|
${title}:`));
|
|
23691
24296
|
console.error(
|
|
23692
24297
|
errOrReason instanceof Error ? errOrReason.stack || errOrReason : errOrReason
|
|
23693
24298
|
);
|
|
23694
24299
|
} else {
|
|
23695
24300
|
const msg = errOrReason instanceof Error ? errOrReason.message : String(errOrReason);
|
|
23696
|
-
console.error(
|
|
24301
|
+
console.error(chalk38.red(`
|
|
23697
24302
|
Fatal Error: ${msg}`));
|
|
23698
24303
|
console.error(
|
|
23699
|
-
|
|
24304
|
+
chalk38.dim(
|
|
23700
24305
|
" Run with --verbose or DEBUG=1 to view the full stack trace.\n"
|
|
23701
24306
|
)
|
|
23702
24307
|
);
|
|
@@ -23715,7 +24320,7 @@ function setupProcessHandlers() {
|
|
|
23715
24320
|
}
|
|
23716
24321
|
process.exit(130);
|
|
23717
24322
|
} else {
|
|
23718
|
-
console.error(
|
|
24323
|
+
console.error(chalk38.dim("\n Operation cancelled."));
|
|
23719
24324
|
process.exit(130);
|
|
23720
24325
|
}
|
|
23721
24326
|
});
|