@iamu-dev/iamu 0.2.0-beta.1 → 0.2.0-beta.10
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 +2178 -330
- package/dist/commands/browse.d.ts +14 -0
- package/dist/commands/complete.d.ts +1 -0
- package/dist/commands/credentials.d.ts +1 -1
- package/dist/commands/doctor.d.ts +11 -2
- package/dist/commands/evaluate.d.ts +2 -0
- package/dist/commands/explore-crawl.d.ts +67 -0
- package/dist/commands/explore.d.ts +52 -0
- package/dist/commands/init.d.ts +7 -0
- package/dist/commands/instructions-walk.d.ts +11 -1
- package/dist/commands/match.d.ts +1 -0
- package/dist/commands/new-entity.d.ts +13 -0
- package/dist/commands/start-run.d.ts +7 -2
- package/dist/eval/gemini-eval-cli.d.ts +10 -0
- package/dist/eval/gemini-eval-cli.js +976 -0
- package/dist/eval/gemini-walker.d.ts +46 -0
- package/dist/eval/walkthrough-evaluator.d.ts +61 -0
- package/dist/index.js +2174 -326
- package/dist/templates/prompts/walk.md +16 -4
- package/dist/templates/skills/iamu-findings.md +70 -32
- package/dist/templates/skills/iamu-new.md +123 -66
- package/dist/templates/skills/iamu-propose.md +60 -27
- package/dist/templates/skills/iamu-run.md +180 -60
- package/dist/templates/skills/iamu-setup.md +195 -117
- package/dist/templates/skills/iamu-status.md +21 -14
- package/package.json +6 -3
package/dist/index.js
CHANGED
|
@@ -199,17 +199,24 @@ function defaultConfig(params) {
|
|
|
199
199
|
cloud_api_url: null
|
|
200
200
|
};
|
|
201
201
|
}
|
|
202
|
-
var baseProjectFields, localOnlyConfig, cloudLinkedConfig, projectConfigSchema;
|
|
202
|
+
var modelTierSchema, projectModelsConfigSchema, baseProjectFields, localOnlyConfig, cloudLinkedConfig, projectConfigSchema;
|
|
203
203
|
var init_config = __esm({
|
|
204
204
|
"../../packages/protocol/dist/schemas/frontmatter/config.js"() {
|
|
205
205
|
"use strict";
|
|
206
206
|
init_common();
|
|
207
207
|
init_version();
|
|
208
|
+
modelTierSchema = z4.string().min(1).max(128).describe("Model role tier ('fast', 'thorough', 'inherit') or a specific model identifier.");
|
|
209
|
+
projectModelsConfigSchema = z4.object({
|
|
210
|
+
walker: modelTierSchema.optional(),
|
|
211
|
+
matcher: modelTierSchema.optional(),
|
|
212
|
+
judge: modelTierSchema.optional()
|
|
213
|
+
}).strict();
|
|
208
214
|
baseProjectFields = {
|
|
209
215
|
schema_version: z4.literal(SCHEMA_VERSION),
|
|
210
216
|
id: uuidSchema,
|
|
211
217
|
project_slug: slugSchema,
|
|
212
|
-
project_name: z4.string().min(1)
|
|
218
|
+
project_name: z4.string().min(1),
|
|
219
|
+
models: projectModelsConfigSchema.optional()
|
|
213
220
|
};
|
|
214
221
|
localOnlyConfig = z4.object({
|
|
215
222
|
...baseProjectFields,
|
|
@@ -816,7 +823,14 @@ var init_scenario = __esm({
|
|
|
816
823
|
reviews: z15.array(z15.string().min(1).max(64)).max(16).default([]),
|
|
817
824
|
baseline_score: z15.number().min(0).max(100).nullish(),
|
|
818
825
|
current_score: z15.number().min(0).max(100).nullish(),
|
|
819
|
-
target_score: z15.number().min(0).max(100).default(95)
|
|
826
|
+
target_score: z15.number().min(0).max(100).default(95),
|
|
827
|
+
/** Environment state preconditions and approximation guidance. */
|
|
828
|
+
state: z15.object({
|
|
829
|
+
/** Description of data/records expected to exist in the environment. */
|
|
830
|
+
description: z15.string().max(2048).nullish(),
|
|
831
|
+
/** Guidance on how to squint or approximate if exact records are absent. */
|
|
832
|
+
approximation: z15.string().max(2048).nullish()
|
|
833
|
+
}).nullish()
|
|
820
834
|
});
|
|
821
835
|
}
|
|
822
836
|
});
|
|
@@ -1153,7 +1167,21 @@ var init_walkthrough_bundle = __esm({
|
|
|
1153
1167
|
judge_prompt: z19.string().optional(),
|
|
1154
1168
|
judge_model: z19.string().optional()
|
|
1155
1169
|
}).optional()
|
|
1156
|
-
}).optional()
|
|
1170
|
+
}).optional(),
|
|
1171
|
+
models: z19.object({
|
|
1172
|
+
walker: z19.string().optional(),
|
|
1173
|
+
matcher: z19.string().optional(),
|
|
1174
|
+
judge: z19.string().optional(),
|
|
1175
|
+
walker_reported: z19.string().optional(),
|
|
1176
|
+
matcher_reported: z19.string().optional(),
|
|
1177
|
+
judge_reported: z19.string().optional(),
|
|
1178
|
+
walker_tier: z19.string().optional(),
|
|
1179
|
+
matcher_tier: z19.string().optional(),
|
|
1180
|
+
judge_tier: z19.string().optional(),
|
|
1181
|
+
execution_mode: z19.enum(["multi_agent", "single_agent"]).optional(),
|
|
1182
|
+
velocity_sec_per_step: z19.number().optional(),
|
|
1183
|
+
total_duration_sec: z19.number().optional()
|
|
1184
|
+
}).optional().describe("AI model configuration and reported runtime provenance for this walkthrough.")
|
|
1157
1185
|
});
|
|
1158
1186
|
walkthroughBundleSchema = z19.object({
|
|
1159
1187
|
manifest: bundleManifestSchema,
|
|
@@ -1376,6 +1404,7 @@ var init_walkthrough_canonical = __esm({
|
|
|
1376
1404
|
build: buildMetadataSchema.optional(),
|
|
1377
1405
|
viewport: runViewportSchema.optional(),
|
|
1378
1406
|
environment: z20.string().optional(),
|
|
1407
|
+
models: z20.record(z20.unknown()).optional(),
|
|
1379
1408
|
findings: z20.array(canonicalFindingSchema),
|
|
1380
1409
|
steps: z20.array(canonicalStepSchema),
|
|
1381
1410
|
human_tests: z20.array(canonicalHumanTestSchema),
|
|
@@ -7247,6 +7276,7 @@ async function buildCanonical(projectRoot, manifest, completedAt, inputs) {
|
|
|
7247
7276
|
...manifest.build ? { build: manifest.build } : {},
|
|
7248
7277
|
...manifest.viewport ? { viewport: manifest.viewport } : {},
|
|
7249
7278
|
...manifest.environment ? { environment: manifest.environment } : {},
|
|
7279
|
+
...manifest.models ? { models: manifest.models } : {},
|
|
7250
7280
|
findings: canonicalFindings,
|
|
7251
7281
|
steps: canonicalSteps,
|
|
7252
7282
|
human_tests: humanTests,
|
|
@@ -7407,6 +7437,20 @@ function renderCanonicalMarkdown(canonical) {
|
|
|
7407
7437
|
lines.push(`- Viewport: ${canonical.viewport.device} (${canonical.viewport.width}\xD7${canonical.viewport.height})`);
|
|
7408
7438
|
}
|
|
7409
7439
|
lines.push(`- Started: ${canonical.provenance.started_at}`, `- Completed: ${canonical.provenance.completed_at}`, `- Walker prompt: ${canonical.provenance.walker_prompt.id}@${canonical.provenance.walker_prompt.version}`, `- Findings: ${canonical.findings.length}`, `- Steps: ${canonical.steps.length}`);
|
|
7440
|
+
const models = canonical.models;
|
|
7441
|
+
if (models) {
|
|
7442
|
+
const parts = [
|
|
7443
|
+
models.walker ? `Walker: ${models.walker}${models.walker_reported ? ` (${models.walker_reported})` : ""}` : "",
|
|
7444
|
+
models.judge ? `Judge: ${models.judge}${models.judge_reported ? ` (${models.judge_reported})` : ""}` : "",
|
|
7445
|
+
models.matcher ? `Matcher: ${models.matcher}` : ""
|
|
7446
|
+
].filter(Boolean);
|
|
7447
|
+
if (parts.length > 0) {
|
|
7448
|
+
lines.push(`- Models: ${parts.join(" \xB7 ")}`);
|
|
7449
|
+
}
|
|
7450
|
+
if (typeof models.velocity_sec_per_step === "number") {
|
|
7451
|
+
lines.push(`- Velocity: ~${models.velocity_sec_per_step}s/step (${models.total_duration_sec}s total)`);
|
|
7452
|
+
}
|
|
7453
|
+
}
|
|
7410
7454
|
const what = canonical.what_worked;
|
|
7411
7455
|
if (what && what.length > 0) {
|
|
7412
7456
|
lines.push("", "## What worked");
|
|
@@ -7776,6 +7820,7 @@ async function assembleWalkthroughBundle(projectRoot, runId) {
|
|
|
7776
7820
|
report_markdown: report3,
|
|
7777
7821
|
verdict: verdictArray,
|
|
7778
7822
|
judge_meta: judgeMeta,
|
|
7823
|
+
...manifest.models ? { models: manifest.models } : {},
|
|
7779
7824
|
...buildFields(manifest),
|
|
7780
7825
|
...await readCanonicalExtras(projectRoot, runId)
|
|
7781
7826
|
},
|
|
@@ -10442,9 +10487,275 @@ var init_prune = __esm({
|
|
|
10442
10487
|
}
|
|
10443
10488
|
});
|
|
10444
10489
|
|
|
10445
|
-
// ../../packages/local-store/dist/
|
|
10490
|
+
// ../../packages/local-store/dist/explore-map.js
|
|
10491
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
10446
10492
|
import { promises as fs19 } from "fs";
|
|
10447
10493
|
import path15 from "path";
|
|
10494
|
+
function templateToken(token) {
|
|
10495
|
+
if (token === "")
|
|
10496
|
+
return token;
|
|
10497
|
+
if (/^\d+$/.test(token))
|
|
10498
|
+
return ":id";
|
|
10499
|
+
if (UUID.test(token))
|
|
10500
|
+
return ":id";
|
|
10501
|
+
if (DATE.test(token))
|
|
10502
|
+
return ":date";
|
|
10503
|
+
if (/^[0-9a-f]{8,}$/i.test(token) && /\d/.test(token))
|
|
10504
|
+
return ":id";
|
|
10505
|
+
if (token.length >= 16 && /\d/.test(token) && /[a-z]/i.test(token)) {
|
|
10506
|
+
return ":id";
|
|
10507
|
+
}
|
|
10508
|
+
return token;
|
|
10509
|
+
}
|
|
10510
|
+
function templatePath(pathname) {
|
|
10511
|
+
const trimmed = pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname || "/";
|
|
10512
|
+
return trimmed.split("/").map((seg) => templateToken(decodeSegment(seg))).join("/");
|
|
10513
|
+
}
|
|
10514
|
+
function encodeQueryValue(v) {
|
|
10515
|
+
return v.startsWith(":") ? v : encodeURIComponent(v);
|
|
10516
|
+
}
|
|
10517
|
+
function decodeSegment(seg) {
|
|
10518
|
+
try {
|
|
10519
|
+
return decodeURIComponent(seg);
|
|
10520
|
+
} catch {
|
|
10521
|
+
return seg;
|
|
10522
|
+
}
|
|
10523
|
+
}
|
|
10524
|
+
function explorePlaceKey(raw) {
|
|
10525
|
+
if (!raw)
|
|
10526
|
+
return null;
|
|
10527
|
+
let u;
|
|
10528
|
+
try {
|
|
10529
|
+
u = new URL(raw);
|
|
10530
|
+
} catch {
|
|
10531
|
+
return null;
|
|
10532
|
+
}
|
|
10533
|
+
if (u.protocol !== "http:" && u.protocol !== "https:")
|
|
10534
|
+
return null;
|
|
10535
|
+
const params = [...u.searchParams.entries()].filter(([k]) => !k.toLowerCase().startsWith("utm_")).map(([k, v]) => [k, templateToken(v)]).sort(([a, av], [b, bv]) => a.localeCompare(b) || av.localeCompare(bv));
|
|
10536
|
+
const query = params.map(([k, v]) => `${encodeURIComponent(k)}=${encodeQueryValue(v)}`).join("&");
|
|
10537
|
+
let key = templatePath(u.pathname);
|
|
10538
|
+
if (query)
|
|
10539
|
+
key += `?${query}`;
|
|
10540
|
+
const route = /^#!?(\/.*)$/.exec(u.hash);
|
|
10541
|
+
if (route) {
|
|
10542
|
+
const [hashPath] = route[1].split("?");
|
|
10543
|
+
key += `#${templatePath(hashPath)}`;
|
|
10544
|
+
}
|
|
10545
|
+
return key;
|
|
10546
|
+
}
|
|
10547
|
+
function exploreViewSignature(headings) {
|
|
10548
|
+
return headings.map((h) => h.trim().replace(/\s+/g, " ").replace(/\d+/g, "#").toLowerCase()).filter(Boolean).join(" | ");
|
|
10549
|
+
}
|
|
10550
|
+
function exploreSignature(headings, title) {
|
|
10551
|
+
return exploreViewSignature(headings) || title.trim().toLowerCase();
|
|
10552
|
+
}
|
|
10553
|
+
function matchExploreSurface(map2, url, signature) {
|
|
10554
|
+
const place = explorePlaceKey(url);
|
|
10555
|
+
if (!place)
|
|
10556
|
+
return null;
|
|
10557
|
+
const atPlace = map2.surfaces.filter((s) => s.place === place);
|
|
10558
|
+
return atPlace.find((s) => s.signature === signature) ?? (atPlace.length === 1 ? atPlace[0] : null);
|
|
10559
|
+
}
|
|
10560
|
+
function exploreSurfaceKey(place, view) {
|
|
10561
|
+
return view === null ? place : `${place}~${fnv1a(view)}`;
|
|
10562
|
+
}
|
|
10563
|
+
function fnv1a(s) {
|
|
10564
|
+
let h = 2166136261;
|
|
10565
|
+
for (let i = 0; i < s.length; i++) {
|
|
10566
|
+
h ^= s.charCodeAt(i);
|
|
10567
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
10568
|
+
}
|
|
10569
|
+
return h.toString(16).padStart(8, "0");
|
|
10570
|
+
}
|
|
10571
|
+
function exploreDir(projectRoot) {
|
|
10572
|
+
return path15.join(cacheDir(projectRoot), "explore");
|
|
10573
|
+
}
|
|
10574
|
+
function composeExploreId(now, randomHex) {
|
|
10575
|
+
const ts = now.toISOString().replace(/[:.]/g, "-").replace(/Z$/, "");
|
|
10576
|
+
return `${ts}-${randomHex ?? randomBytes2(3).toString("hex")}`;
|
|
10577
|
+
}
|
|
10578
|
+
function isValidExploreId(id) {
|
|
10579
|
+
return MAP_ID.test(id);
|
|
10580
|
+
}
|
|
10581
|
+
function mapPath(projectRoot, id) {
|
|
10582
|
+
if (!isValidExploreId(id))
|
|
10583
|
+
throw new Error(`Not an explore map id: ${id}`);
|
|
10584
|
+
return path15.join(exploreDir(projectRoot), id, "map.json");
|
|
10585
|
+
}
|
|
10586
|
+
async function writeExploreMap(projectRoot, map2) {
|
|
10587
|
+
const file = mapPath(projectRoot, map2.id);
|
|
10588
|
+
await fs19.mkdir(path15.dirname(file), { recursive: true });
|
|
10589
|
+
await atomicWriteFile(file, `${JSON.stringify(map2, null, 2)}
|
|
10590
|
+
`);
|
|
10591
|
+
return file;
|
|
10592
|
+
}
|
|
10593
|
+
async function readExploreMap(projectRoot, id) {
|
|
10594
|
+
const raw = await fs19.readFile(mapPath(projectRoot, id), "utf8");
|
|
10595
|
+
const map2 = JSON.parse(raw);
|
|
10596
|
+
if (map2?.schema !== EXPLORE_MAP_SCHEMA || !Array.isArray(map2.surfaces)) {
|
|
10597
|
+
throw new Error(`Unreadable explore map: ${id}`);
|
|
10598
|
+
}
|
|
10599
|
+
return map2;
|
|
10600
|
+
}
|
|
10601
|
+
async function listExploreMaps(projectRoot) {
|
|
10602
|
+
try {
|
|
10603
|
+
const entries = await fs19.readdir(exploreDir(projectRoot), {
|
|
10604
|
+
withFileTypes: true
|
|
10605
|
+
});
|
|
10606
|
+
return entries.filter((e) => e.isDirectory() && isValidExploreId(e.name)).map((e) => e.name).sort();
|
|
10607
|
+
} catch (err) {
|
|
10608
|
+
if (err.code === "ENOENT")
|
|
10609
|
+
return [];
|
|
10610
|
+
throw err;
|
|
10611
|
+
}
|
|
10612
|
+
}
|
|
10613
|
+
function exploreCoverage(map2, steps) {
|
|
10614
|
+
const byPlace = /* @__PURE__ */ new Map();
|
|
10615
|
+
for (const s of map2.surfaces) {
|
|
10616
|
+
byPlace.set(s.place, [...byPlace.get(s.place) ?? [], s]);
|
|
10617
|
+
}
|
|
10618
|
+
const reachedBy = /* @__PURE__ */ new Map();
|
|
10619
|
+
const unresolvedBy = /* @__PURE__ */ new Map();
|
|
10620
|
+
const runOnlyBy = /* @__PURE__ */ new Map();
|
|
10621
|
+
const add = (m, k, s) => m.set(k, [...m.get(k) ?? [], s]);
|
|
10622
|
+
for (const step of steps) {
|
|
10623
|
+
const place = explorePlaceKey(step.url);
|
|
10624
|
+
if (!place)
|
|
10625
|
+
continue;
|
|
10626
|
+
const atPlace = byPlace.get(place);
|
|
10627
|
+
if (!atPlace) {
|
|
10628
|
+
add(runOnlyBy, place, step);
|
|
10629
|
+
continue;
|
|
10630
|
+
}
|
|
10631
|
+
const hit = tieStep(map2, atPlace, step);
|
|
10632
|
+
if (hit.length === 0)
|
|
10633
|
+
add(unresolvedBy, place, step);
|
|
10634
|
+
for (const s of hit)
|
|
10635
|
+
add(reachedBy, s.key, step);
|
|
10636
|
+
}
|
|
10637
|
+
const surfaces = map2.surfaces.map((s) => {
|
|
10638
|
+
const base = {
|
|
10639
|
+
key: s.key,
|
|
10640
|
+
label: s.label,
|
|
10641
|
+
place: s.place,
|
|
10642
|
+
url: s.url,
|
|
10643
|
+
depth: s.depth
|
|
10644
|
+
};
|
|
10645
|
+
const reached = reachedBy.get(s.key);
|
|
10646
|
+
if (reached)
|
|
10647
|
+
return { ...base, status: "reached", runs: distinctRuns(reached) };
|
|
10648
|
+
const unresolved = unresolvedBy.get(s.place);
|
|
10649
|
+
if (unresolved) {
|
|
10650
|
+
return { ...base, status: "place_only", runs: distinctRuns(unresolved) };
|
|
10651
|
+
}
|
|
10652
|
+
return { ...base, status: "unreached", runs: [] };
|
|
10653
|
+
});
|
|
10654
|
+
const runOnly = [...runOnlyBy].map(([place, list2]) => ({
|
|
10655
|
+
place,
|
|
10656
|
+
steps: list2.length,
|
|
10657
|
+
runs: distinctRuns(list2)
|
|
10658
|
+
}));
|
|
10659
|
+
runOnly.sort((a, b) => b.steps - a.steps || a.place.localeCompare(b.place));
|
|
10660
|
+
return {
|
|
10661
|
+
map_id: map2.id,
|
|
10662
|
+
runs: new Set(steps.map((s) => s.run_id)).size,
|
|
10663
|
+
steps: steps.length,
|
|
10664
|
+
reached: surfaces.filter((s) => s.status === "reached").length,
|
|
10665
|
+
place_only: surfaces.filter((s) => s.status === "place_only").length,
|
|
10666
|
+
unreached: surfaces.filter((s) => s.status === "unreached").length,
|
|
10667
|
+
surfaces,
|
|
10668
|
+
run_only_places: runOnly
|
|
10669
|
+
};
|
|
10670
|
+
}
|
|
10671
|
+
function tieStep(map2, atPlace, step) {
|
|
10672
|
+
if (typeof step.signature === "string") {
|
|
10673
|
+
const m = matchExploreSurface(map2, step.url, step.signature);
|
|
10674
|
+
return m ? [m] : [];
|
|
10675
|
+
}
|
|
10676
|
+
if (atPlace.length === 1)
|
|
10677
|
+
return [atPlace[0]];
|
|
10678
|
+
const name = step.surface?.trim();
|
|
10679
|
+
if (!name)
|
|
10680
|
+
return [];
|
|
10681
|
+
return atPlace.filter((s) => [s.label, ...s.headings].filter(Boolean).some((n) => surfaceMatches(n, name) || surfaceMatches(name, n)));
|
|
10682
|
+
}
|
|
10683
|
+
function distinctRuns(steps) {
|
|
10684
|
+
return [...new Set(steps.map((s) => s.run_id))].sort();
|
|
10685
|
+
}
|
|
10686
|
+
async function readAllRunSteps(projectRoot) {
|
|
10687
|
+
const out = [];
|
|
10688
|
+
for (const runId of await listRuns(projectRoot)) {
|
|
10689
|
+
let steps;
|
|
10690
|
+
try {
|
|
10691
|
+
steps = await readStepsForRun(projectRoot, runId);
|
|
10692
|
+
} catch {
|
|
10693
|
+
continue;
|
|
10694
|
+
}
|
|
10695
|
+
for (const s of steps) {
|
|
10696
|
+
out.push({ run_id: runId, url: s.url, surface: s.surface });
|
|
10697
|
+
}
|
|
10698
|
+
}
|
|
10699
|
+
return out;
|
|
10700
|
+
}
|
|
10701
|
+
function actionSignature(a) {
|
|
10702
|
+
const where = a.outcome.kind === "surface" ? a.outcome.surface : a.outcome.kind === "skipped" ? `skipped:${a.outcome.reason}` : a.outcome.kind === "external" ? `external:${a.outcome.url}` : a.outcome.kind;
|
|
10703
|
+
return `${a.role} "${a.name}" \u2192 ${where}`;
|
|
10704
|
+
}
|
|
10705
|
+
function diffExploreMaps(a, b) {
|
|
10706
|
+
const aByKey = new Map(a.surfaces.map((s) => [s.key, s]));
|
|
10707
|
+
const bByKey = new Map(b.surfaces.map((s) => [s.key, s]));
|
|
10708
|
+
const ref = (s) => ({
|
|
10709
|
+
key: s.key,
|
|
10710
|
+
label: s.label
|
|
10711
|
+
});
|
|
10712
|
+
const onlyA = a.surfaces.filter((s) => !bByKey.has(s.key)).map(ref);
|
|
10713
|
+
const onlyB = b.surfaces.filter((s) => !aByKey.has(s.key)).map(ref);
|
|
10714
|
+
const changed = [];
|
|
10715
|
+
let common2 = 0;
|
|
10716
|
+
for (const sa of a.surfaces) {
|
|
10717
|
+
const sb = bByKey.get(sa.key);
|
|
10718
|
+
if (!sb)
|
|
10719
|
+
continue;
|
|
10720
|
+
common2++;
|
|
10721
|
+
const setA = new Set(sa.actions.map(actionSignature));
|
|
10722
|
+
const setB = new Set(sb.actions.map(actionSignature));
|
|
10723
|
+
const inA = [...setA].filter((x) => !setB.has(x)).sort();
|
|
10724
|
+
const inB = [...setB].filter((x) => !setA.has(x)).sort();
|
|
10725
|
+
if (inA.length > 0 || inB.length > 0) {
|
|
10726
|
+
changed.push({ ...ref(sa), only_a: inA, only_b: inB });
|
|
10727
|
+
}
|
|
10728
|
+
}
|
|
10729
|
+
const union = common2 + onlyA.length + onlyB.length;
|
|
10730
|
+
return {
|
|
10731
|
+
a: a.id,
|
|
10732
|
+
b: b.id,
|
|
10733
|
+
common: common2,
|
|
10734
|
+
only_a: onlyA,
|
|
10735
|
+
only_b: onlyB,
|
|
10736
|
+
surface_overlap: union === 0 ? 1 : common2 / union,
|
|
10737
|
+
changed_actions: changed
|
|
10738
|
+
};
|
|
10739
|
+
}
|
|
10740
|
+
var EXPLORE_MAP_SCHEMA, UUID, DATE, MAP_ID;
|
|
10741
|
+
var init_explore_map = __esm({
|
|
10742
|
+
"../../packages/local-store/dist/explore-map.js"() {
|
|
10743
|
+
"use strict";
|
|
10744
|
+
init_atomic();
|
|
10745
|
+
init_paths();
|
|
10746
|
+
init_revisit();
|
|
10747
|
+
init_runs();
|
|
10748
|
+
init_walkthrough_canonical2();
|
|
10749
|
+
EXPLORE_MAP_SCHEMA = 1;
|
|
10750
|
+
UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
10751
|
+
DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
10752
|
+
MAP_ID = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}-[0-9a-f]{6}$/;
|
|
10753
|
+
}
|
|
10754
|
+
});
|
|
10755
|
+
|
|
10756
|
+
// ../../packages/local-store/dist/human-test-link.js
|
|
10757
|
+
import { promises as fs20 } from "fs";
|
|
10758
|
+
import path16 from "path";
|
|
10448
10759
|
function humanTestLinks(verdicts) {
|
|
10449
10760
|
const best = /* @__PURE__ */ new Map();
|
|
10450
10761
|
for (const v of verdicts) {
|
|
@@ -10467,7 +10778,7 @@ async function linkFindingsToHumanTests(projectRoot, runId, verdicts) {
|
|
|
10467
10778
|
const dir = entityDirPath(projectRoot, "finding");
|
|
10468
10779
|
let names;
|
|
10469
10780
|
try {
|
|
10470
|
-
names = await
|
|
10781
|
+
names = await fs20.readdir(dir);
|
|
10471
10782
|
} catch {
|
|
10472
10783
|
return [];
|
|
10473
10784
|
}
|
|
@@ -10475,11 +10786,11 @@ async function linkFindingsToHumanTests(projectRoot, runId, verdicts) {
|
|
|
10475
10786
|
for (const name of names) {
|
|
10476
10787
|
if (!name.endsWith(".md") || name.startsWith("."))
|
|
10477
10788
|
continue;
|
|
10478
|
-
const file =
|
|
10789
|
+
const file = path16.join(dir, name);
|
|
10479
10790
|
let raw;
|
|
10480
10791
|
let fm;
|
|
10481
10792
|
try {
|
|
10482
|
-
raw = await
|
|
10793
|
+
raw = await fs20.readFile(file, "utf8");
|
|
10483
10794
|
fm = parseFrontmatter(raw).frontmatter;
|
|
10484
10795
|
} catch {
|
|
10485
10796
|
continue;
|
|
@@ -10710,14 +11021,14 @@ var init_cloud_client = __esm({
|
|
|
10710
11021
|
});
|
|
10711
11022
|
|
|
10712
11023
|
// ../../packages/local-store/dist/last-pull.js
|
|
10713
|
-
import { promises as
|
|
10714
|
-
import
|
|
11024
|
+
import { promises as fs21 } from "fs";
|
|
11025
|
+
import path17 from "path";
|
|
10715
11026
|
function lastPullPath(projectRoot) {
|
|
10716
|
-
return
|
|
11027
|
+
return path17.join(cacheDir(projectRoot), "last-pull.json");
|
|
10717
11028
|
}
|
|
10718
11029
|
async function readLastPull(projectRoot) {
|
|
10719
11030
|
try {
|
|
10720
|
-
const raw = await
|
|
11031
|
+
const raw = await fs21.readFile(lastPullPath(projectRoot), "utf8");
|
|
10721
11032
|
const parsed = JSON.parse(raw);
|
|
10722
11033
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
10723
11034
|
return parsed;
|
|
@@ -11003,7 +11314,7 @@ var init_frontmatter2 = __esm({
|
|
|
11003
11314
|
});
|
|
11004
11315
|
|
|
11005
11316
|
// ../../packages/local-store/dist/pull/write.js
|
|
11006
|
-
import { promises as
|
|
11317
|
+
import { promises as fs22 } from "fs";
|
|
11007
11318
|
async function loadLocalIndex(projectRoot, type2) {
|
|
11008
11319
|
const byId = /* @__PURE__ */ new Map();
|
|
11009
11320
|
const bySlug = /* @__PURE__ */ new Map();
|
|
@@ -11032,7 +11343,7 @@ async function classifyExisting(projectRoot, type2, cloudId, localSlug, targetSl
|
|
|
11032
11343
|
if (localSlug !== targetSlug)
|
|
11033
11344
|
return "untracked";
|
|
11034
11345
|
const lastSynced = localIndex.lastPushById[cloudId];
|
|
11035
|
-
const bytes = await
|
|
11346
|
+
const bytes = await fs22.readFile(entityFilePath(projectRoot, type2, localSlug), "utf8");
|
|
11036
11347
|
return lastSynced && hashNormalizedContent(bytes) === lastSynced ? "fast_forward" : "diverged";
|
|
11037
11348
|
}
|
|
11038
11349
|
async function writeOrSkip(projectRoot, type2, cloudId, slug, frontmatter, body, localIndex, mode, cloudVersion) {
|
|
@@ -11093,13 +11404,13 @@ async function writeOrSkip(projectRoot, type2, cloudId, slug, frontmatter, body,
|
|
|
11093
11404
|
}
|
|
11094
11405
|
if (existingSlugForId && existingSlugForId !== slug) {
|
|
11095
11406
|
try {
|
|
11096
|
-
await
|
|
11407
|
+
await fs22.unlink(entityFilePath(projectRoot, type2, existingSlugForId));
|
|
11097
11408
|
localIndex.bySlug.delete(existingSlugForId);
|
|
11098
11409
|
} catch {
|
|
11099
11410
|
}
|
|
11100
11411
|
}
|
|
11101
11412
|
await writeEntity(projectRoot, type2, frontmatter, text);
|
|
11102
|
-
const bytes = await
|
|
11413
|
+
const bytes = await fs22.readFile(entityFilePath(projectRoot, type2, slug), "utf8");
|
|
11103
11414
|
const hash = hashNormalizedContent(bytes);
|
|
11104
11415
|
localIndex.byId.set(cloudId, slug);
|
|
11105
11416
|
localIndex.bySlug.set(slug, cloudId);
|
|
@@ -11153,7 +11464,7 @@ var init_write = __esm({
|
|
|
11153
11464
|
});
|
|
11154
11465
|
|
|
11155
11466
|
// ../../packages/local-store/dist/pull/entities.js
|
|
11156
|
-
import { promises as
|
|
11467
|
+
import { promises as fs23 } from "fs";
|
|
11157
11468
|
function buildContextFrontmatter(payload) {
|
|
11158
11469
|
const cloudCtx = payload.context ?? {};
|
|
11159
11470
|
const glossary = (payload.glossary ?? []).map((g) => ({
|
|
@@ -11208,7 +11519,7 @@ async function pullContext(projectRoot, cloud, projectId, mode, result) {
|
|
|
11208
11519
|
const frontmatter = buildContextFrontmatter(payload);
|
|
11209
11520
|
let hasLocal = false;
|
|
11210
11521
|
try {
|
|
11211
|
-
await
|
|
11522
|
+
await fs23.access(contextPath(projectRoot));
|
|
11212
11523
|
hasLocal = true;
|
|
11213
11524
|
} catch {
|
|
11214
11525
|
hasLocal = false;
|
|
@@ -11238,7 +11549,7 @@ async function pullContext(projectRoot, cloud, projectId, mode, result) {
|
|
|
11238
11549
|
frontmatter,
|
|
11239
11550
|
notes_body: existingNotes
|
|
11240
11551
|
});
|
|
11241
|
-
const bytes = await
|
|
11552
|
+
const bytes = await fs23.readFile(contextPath(projectRoot), "utf8");
|
|
11242
11553
|
const hash = hashNormalizedContent(bytes);
|
|
11243
11554
|
await recordLastPull(projectRoot, "context:root", hash);
|
|
11244
11555
|
await recordLastPush(projectRoot, "context:root", hash);
|
|
@@ -11768,6 +12079,7 @@ var init_dist2 = __esm({
|
|
|
11768
12079
|
init_prune();
|
|
11769
12080
|
init_fix_state();
|
|
11770
12081
|
init_revisit();
|
|
12082
|
+
init_explore_map();
|
|
11771
12083
|
init_walkthrough_canonical2();
|
|
11772
12084
|
init_journey_diff();
|
|
11773
12085
|
init_finding_match();
|
|
@@ -11872,15 +12184,15 @@ function resolveApiUrl(configured) {
|
|
|
11872
12184
|
assertSecureApiUrl(url, configured ? "API URL" : "IAMU_API_URL");
|
|
11873
12185
|
return url;
|
|
11874
12186
|
}
|
|
11875
|
-
function parseStore(raw,
|
|
12187
|
+
function parseStore(raw, path30) {
|
|
11876
12188
|
let parsed;
|
|
11877
12189
|
try {
|
|
11878
12190
|
parsed = JSON.parse(raw);
|
|
11879
12191
|
} catch (err) {
|
|
11880
|
-
throw new CredentialsFileError(
|
|
12192
|
+
throw new CredentialsFileError(path30, "not valid JSON", err);
|
|
11881
12193
|
}
|
|
11882
12194
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
11883
|
-
throw new CredentialsFileError(
|
|
12195
|
+
throw new CredentialsFileError(path30, "expected a JSON object");
|
|
11884
12196
|
}
|
|
11885
12197
|
return parsed;
|
|
11886
12198
|
}
|
|
@@ -11888,26 +12200,26 @@ function isNotFound(err) {
|
|
|
11888
12200
|
return typeof err === "object" && err !== null && err.code === "ENOENT";
|
|
11889
12201
|
}
|
|
11890
12202
|
async function readCredentialsStore() {
|
|
11891
|
-
const
|
|
12203
|
+
const path30 = getCredentialsPath();
|
|
11892
12204
|
let raw;
|
|
11893
12205
|
try {
|
|
11894
|
-
raw = await readFile(
|
|
12206
|
+
raw = await readFile(path30, "utf-8");
|
|
11895
12207
|
} catch (err) {
|
|
11896
12208
|
if (isNotFound(err)) return {};
|
|
11897
12209
|
warnUnreadableStoreOnce(err);
|
|
11898
12210
|
throw err;
|
|
11899
12211
|
}
|
|
11900
12212
|
try {
|
|
11901
|
-
return parseStore(raw,
|
|
12213
|
+
return parseStore(raw, path30);
|
|
11902
12214
|
} catch (err) {
|
|
11903
12215
|
warnUnreadableStoreOnce(err);
|
|
11904
12216
|
throw err;
|
|
11905
12217
|
}
|
|
11906
12218
|
}
|
|
11907
12219
|
async function writeCredentialsStore(store) {
|
|
11908
|
-
const
|
|
11909
|
-
await mkdir(dirname(
|
|
11910
|
-
await atomicWriteFile(
|
|
12220
|
+
const path30 = getCredentialsPath();
|
|
12221
|
+
await mkdir(dirname(path30), { recursive: true, mode: 448 });
|
|
12222
|
+
await atomicWriteFile(path30, `${JSON.stringify(store, null, 2)}
|
|
11911
12223
|
`, {
|
|
11912
12224
|
mode: 384
|
|
11913
12225
|
});
|
|
@@ -11927,16 +12239,16 @@ function resolveApiKey(apiUrl) {
|
|
|
11927
12239
|
} catch {
|
|
11928
12240
|
return null;
|
|
11929
12241
|
}
|
|
11930
|
-
const
|
|
12242
|
+
const path30 = getCredentialsPath();
|
|
11931
12243
|
let raw;
|
|
11932
12244
|
try {
|
|
11933
|
-
raw = readFileSync(
|
|
12245
|
+
raw = readFileSync(path30, "utf-8");
|
|
11934
12246
|
} catch (err) {
|
|
11935
12247
|
if (!isNotFound(err)) warnUnreadableStoreOnce(err);
|
|
11936
12248
|
return null;
|
|
11937
12249
|
}
|
|
11938
12250
|
try {
|
|
11939
|
-
const entry = parseStore(raw,
|
|
12251
|
+
const entry = parseStore(raw, path30)[url];
|
|
11940
12252
|
if (typeof entry !== "object" || entry === null) return null;
|
|
11941
12253
|
const token = entry.token;
|
|
11942
12254
|
return typeof token === "string" && token ? token : null;
|
|
@@ -11968,19 +12280,19 @@ var init_credentials = __esm({
|
|
|
11968
12280
|
"use strict";
|
|
11969
12281
|
init_dist2();
|
|
11970
12282
|
init_dist();
|
|
11971
|
-
DEFAULT_API_URL = "https://
|
|
12283
|
+
DEFAULT_API_URL = "https://app.iamu.dev";
|
|
11972
12284
|
CredentialsFileError = class extends IamuConfigError {
|
|
11973
12285
|
/**
|
|
11974
12286
|
* @param path - The unreadable credentials file.
|
|
11975
12287
|
* @param reason - What is wrong with it.
|
|
11976
12288
|
* @param cause - Underlying parse error, if any.
|
|
11977
12289
|
*/
|
|
11978
|
-
constructor(
|
|
12290
|
+
constructor(path30, reason, cause) {
|
|
11979
12291
|
super(
|
|
11980
|
-
`Credentials file ${
|
|
12292
|
+
`Credentials file ${path30} is corrupt (${reason}). Fix it by hand, or delete it and run \`iamu auth login\` again.`,
|
|
11981
12293
|
{
|
|
11982
12294
|
cause,
|
|
11983
|
-
suggestedAction: `Delete ${
|
|
12295
|
+
suggestedAction: `Delete ${path30} and run \`iamu auth login\`, or set IAMU_TOKEN.`
|
|
11984
12296
|
}
|
|
11985
12297
|
);
|
|
11986
12298
|
}
|
|
@@ -12118,7 +12430,7 @@ async function resolveCloudProject(opts) {
|
|
|
12118
12430
|
commandError("Not cloud-linked. Run `iamu auth login` to connect.", opts);
|
|
12119
12431
|
return null;
|
|
12120
12432
|
}
|
|
12121
|
-
const apiUrl = ctx.config.cloud_api_url ??
|
|
12433
|
+
const apiUrl = ctx.config.cloud_api_url ?? DEFAULT_API_URL;
|
|
12122
12434
|
try {
|
|
12123
12435
|
assertSecureApiUrl(apiUrl, "cloud_api_url");
|
|
12124
12436
|
} catch (err) {
|
|
@@ -12266,15 +12578,6 @@ function mcpJson(cmd) {
|
|
|
12266
12578
|
2
|
|
12267
12579
|
);
|
|
12268
12580
|
}
|
|
12269
|
-
function vscodeMcpJson(cmd) {
|
|
12270
|
-
return JSON.stringify(
|
|
12271
|
-
{
|
|
12272
|
-
servers: { iamu: { type: "stdio", command: cmd[0], args: cmd.slice(1) } }
|
|
12273
|
-
},
|
|
12274
|
-
null,
|
|
12275
|
-
2
|
|
12276
|
-
);
|
|
12277
|
-
}
|
|
12278
12581
|
function detectHosts(projectRoot) {
|
|
12279
12582
|
return HOST_ADAPTERS.filter(
|
|
12280
12583
|
(host) => host.detectionPaths.some((p) => existsSync3(join3(projectRoot, p)))
|
|
@@ -12323,13 +12626,17 @@ var init_registry = __esm({
|
|
|
12323
12626
|
{
|
|
12324
12627
|
name: "GitHub Copilot",
|
|
12325
12628
|
value: "github-copilot",
|
|
12326
|
-
detectionPaths: [
|
|
12327
|
-
|
|
12328
|
-
|
|
12329
|
-
|
|
12330
|
-
|
|
12331
|
-
|
|
12332
|
-
|
|
12629
|
+
detectionPaths: [
|
|
12630
|
+
".github/copilot-instructions.md",
|
|
12631
|
+
".github/skills",
|
|
12632
|
+
".copilot"
|
|
12633
|
+
],
|
|
12634
|
+
supportsSkills: true,
|
|
12635
|
+
getSkillsDir: (root) => join3(root, ".github", "skills"),
|
|
12636
|
+
getCommandPath: (root, id) => join3(root, ".github", "skills", `iamu-${id}`, "SKILL.md"),
|
|
12637
|
+
formatCommand: richFormat,
|
|
12638
|
+
getMcpConfigPath: (root) => join3(root, ".mcp.json"),
|
|
12639
|
+
formatMcpConfig: mcpJson
|
|
12333
12640
|
},
|
|
12334
12641
|
{
|
|
12335
12642
|
name: "Cline",
|
|
@@ -12367,10 +12674,10 @@ var init_registry = __esm({
|
|
|
12367
12674
|
{
|
|
12368
12675
|
name: "Antigravity",
|
|
12369
12676
|
value: "antigravity",
|
|
12370
|
-
detectionPaths: [".
|
|
12677
|
+
detectionPaths: [".agent", ".agents", "AGENTS.md", ".gemini", "GEMINI.md"],
|
|
12371
12678
|
supportsSkills: true,
|
|
12372
|
-
getSkillsDir: (root) => existsSync3(join3(root, ".
|
|
12373
|
-
getCommandPath: (root, id) => existsSync3(join3(root, ".
|
|
12679
|
+
getSkillsDir: (root) => existsSync3(join3(root, ".agents")) ? join3(root, ".agents", "skills") : join3(root, ".agent", "skills"),
|
|
12680
|
+
getCommandPath: (root, id) => existsSync3(join3(root, ".agents")) ? join3(root, ".agents", "skills", `iamu-${id}`, "SKILL.md") : join3(root, ".agent", "skills", `iamu-${id}`, "SKILL.md"),
|
|
12374
12681
|
formatCommand: richFormat,
|
|
12375
12682
|
getMcpConfigPath: (root) => join3(root, ".mcp.json"),
|
|
12376
12683
|
formatMcpConfig: mcpJson
|
|
@@ -12504,9 +12811,17 @@ async function needsWrite(filePath, content) {
|
|
|
12504
12811
|
return true;
|
|
12505
12812
|
}
|
|
12506
12813
|
}
|
|
12507
|
-
function isCliWrittenSkill(
|
|
12814
|
+
function isCliWrittenSkill(path30) {
|
|
12815
|
+
try {
|
|
12816
|
+
return /^generatedBy:/m.test(readFileSync3(path30, "utf-8"));
|
|
12817
|
+
} catch {
|
|
12818
|
+
return false;
|
|
12819
|
+
}
|
|
12820
|
+
}
|
|
12821
|
+
function isCliWrittenPrompt(path30) {
|
|
12508
12822
|
try {
|
|
12509
|
-
|
|
12823
|
+
const content = readFileSync3(path30, "utf-8");
|
|
12824
|
+
return content.includes("iamu") || /^generatedBy:/m.test(content);
|
|
12510
12825
|
} catch {
|
|
12511
12826
|
return false;
|
|
12512
12827
|
}
|
|
@@ -12517,14 +12832,41 @@ function planSkillFiles(hosts, projectRoot, version) {
|
|
|
12517
12832
|
for (const host of hosts) {
|
|
12518
12833
|
if (pluginProvidesSkills(host, projectRoot)) {
|
|
12519
12834
|
for (const name of skillTemplates.keys()) {
|
|
12520
|
-
const
|
|
12521
|
-
if (isCliWrittenSkill(
|
|
12835
|
+
const path30 = join4(host.getSkillsDir(projectRoot), name, "SKILL.md");
|
|
12836
|
+
if (isCliWrittenSkill(path30)) files.push({ host, path: path30, content: null });
|
|
12522
12837
|
}
|
|
12523
12838
|
} else if (host.supportsSkills) {
|
|
12524
12839
|
const skillsDir = host.getSkillsDir(projectRoot);
|
|
12525
12840
|
for (const [name, content] of skillTemplates) {
|
|
12526
12841
|
files.push({ host, path: join4(skillsDir, name, "SKILL.md"), content });
|
|
12527
12842
|
}
|
|
12843
|
+
if (host.value === "antigravity") {
|
|
12844
|
+
for (const name of skillTemplates.keys()) {
|
|
12845
|
+
const legacyPath = join4(
|
|
12846
|
+
projectRoot,
|
|
12847
|
+
".gemini",
|
|
12848
|
+
"skills",
|
|
12849
|
+
name,
|
|
12850
|
+
"SKILL.md"
|
|
12851
|
+
);
|
|
12852
|
+
if (isCliWrittenSkill(legacyPath)) {
|
|
12853
|
+
files.push({ host, path: legacyPath, content: null });
|
|
12854
|
+
}
|
|
12855
|
+
}
|
|
12856
|
+
}
|
|
12857
|
+
if (host.value === "github-copilot") {
|
|
12858
|
+
for (const cmd of commandsFromSkills(skillTemplates)) {
|
|
12859
|
+
const legacyPrompt = join4(
|
|
12860
|
+
projectRoot,
|
|
12861
|
+
".github",
|
|
12862
|
+
"prompts",
|
|
12863
|
+
`iamu-${cmd.id}.prompt.md`
|
|
12864
|
+
);
|
|
12865
|
+
if (isCliWrittenPrompt(legacyPrompt)) {
|
|
12866
|
+
files.push({ host, path: legacyPrompt, content: null });
|
|
12867
|
+
}
|
|
12868
|
+
}
|
|
12869
|
+
}
|
|
12528
12870
|
} else {
|
|
12529
12871
|
for (const cmd of commandsFromSkills(skillTemplates)) {
|
|
12530
12872
|
files.push({
|
|
@@ -12549,6 +12891,15 @@ function detectInstalledHosts(projectRoot) {
|
|
|
12549
12891
|
const skillDir = join4(host.getSkillsDir(projectRoot), "iamu-setup");
|
|
12550
12892
|
if (existsSync5(skillDir)) return true;
|
|
12551
12893
|
}
|
|
12894
|
+
if (host.value === "github-copilot") {
|
|
12895
|
+
const legacyPrompt = join4(
|
|
12896
|
+
projectRoot,
|
|
12897
|
+
".github",
|
|
12898
|
+
"prompts",
|
|
12899
|
+
"iamu-setup.prompt.md"
|
|
12900
|
+
);
|
|
12901
|
+
if (existsSync5(legacyPrompt)) return true;
|
|
12902
|
+
}
|
|
12552
12903
|
return false;
|
|
12553
12904
|
});
|
|
12554
12905
|
}
|
|
@@ -12615,7 +12966,13 @@ async function installSkillsForHosts(hosts, projectRoot, version, opts) {
|
|
|
12615
12966
|
let hostUpdated = 0;
|
|
12616
12967
|
let hostTotal = 0;
|
|
12617
12968
|
for (const f of planned) {
|
|
12618
|
-
if (f.host !== host
|
|
12969
|
+
if (f.host !== host) continue;
|
|
12970
|
+
if (f.content === null) {
|
|
12971
|
+
await rm(f.path, { force: true });
|
|
12972
|
+
await rmdir(dirname3(f.path)).catch(() => {
|
|
12973
|
+
});
|
|
12974
|
+
continue;
|
|
12975
|
+
}
|
|
12619
12976
|
hostTotal++;
|
|
12620
12977
|
if (host.supportsSkills) totalSkills++;
|
|
12621
12978
|
else totalCommands++;
|
|
@@ -13461,12 +13818,24 @@ async function browserLogin(apiUrl, opts, t) {
|
|
|
13461
13818
|
chalk8.red(`
|
|
13462
13819
|
Auth session expired or failed (HTTP ${result.status}).`)
|
|
13463
13820
|
);
|
|
13821
|
+
console.error(
|
|
13822
|
+
chalk8.dim(" Re-run `iamu auth login` to start a new login session.\n")
|
|
13823
|
+
);
|
|
13464
13824
|
process.exitCode = 1;
|
|
13465
13825
|
return null;
|
|
13466
13826
|
}
|
|
13467
13827
|
if (result.kind === "timeout") {
|
|
13468
13828
|
s.stop("Authentication timed out.");
|
|
13469
|
-
console.error(
|
|
13829
|
+
console.error(
|
|
13830
|
+
chalk8.red(
|
|
13831
|
+
"\n Authentication timed out (no approval received within 2 minutes)."
|
|
13832
|
+
)
|
|
13833
|
+
);
|
|
13834
|
+
console.error(
|
|
13835
|
+
chalk8.dim(
|
|
13836
|
+
" \u2022 Complete browser authorization faster, or authenticate non-interactively with --token <key>.\n \u2022 Local setup is unaffected \u2014 re-run `iamu auth login` anytime.\n"
|
|
13837
|
+
)
|
|
13838
|
+
);
|
|
13470
13839
|
process.exitCode = 1;
|
|
13471
13840
|
return null;
|
|
13472
13841
|
}
|
|
@@ -13492,7 +13861,12 @@ async function startAuthSession(apiUrl, timeoutMs) {
|
|
|
13492
13861
|
if (!res.ok) {
|
|
13493
13862
|
console.error(
|
|
13494
13863
|
chalk8.red(
|
|
13495
|
-
` Failed to start auth session: ${res.text || `HTTP ${res.status}`}`
|
|
13864
|
+
` Failed to start auth session at ${apiUrl}: ${res.text || `HTTP ${res.status}`}`
|
|
13865
|
+
)
|
|
13866
|
+
);
|
|
13867
|
+
console.error(
|
|
13868
|
+
chalk8.dim(
|
|
13869
|
+
" \u2022 The cloud service may be experiencing downtime or maintenance.\n \u2022 Local setup is unaffected; continue with local workflows or retry later with `iamu auth login`.\n"
|
|
13496
13870
|
)
|
|
13497
13871
|
);
|
|
13498
13872
|
process.exitCode = 1;
|
|
@@ -13506,7 +13880,13 @@ async function startAuthSession(apiUrl, timeoutMs) {
|
|
|
13506
13880
|
return { sessionId: data.sessionId, loginUrl: data.loginUrl };
|
|
13507
13881
|
} catch (err) {
|
|
13508
13882
|
console.error(
|
|
13509
|
-
chalk8.red(
|
|
13883
|
+
chalk8.red(` Cannot reach Iamu Cloud (${apiUrl}).`) + chalk8.dim(`
|
|
13884
|
+
Reason: ${errorMessage(err)}`)
|
|
13885
|
+
);
|
|
13886
|
+
console.error(
|
|
13887
|
+
chalk8.dim(
|
|
13888
|
+
"\n \u2022 Check your internet connection or proxy settings.\n \u2022 If using a custom instance, pass --url <api-url> or set IAMU_API_URL.\n \u2022 Local setup is unaffected; continue with local workflows or retry later with `iamu auth login`.\n"
|
|
13889
|
+
)
|
|
13510
13890
|
);
|
|
13511
13891
|
process.exitCode = 1;
|
|
13512
13892
|
return null;
|
|
@@ -13973,7 +14353,7 @@ import {
|
|
|
13973
14353
|
readlinkSync,
|
|
13974
14354
|
realpathSync as realpathSync2
|
|
13975
14355
|
} from "fs";
|
|
13976
|
-
import
|
|
14356
|
+
import path18 from "path";
|
|
13977
14357
|
function git(args, cwd) {
|
|
13978
14358
|
return execFileSync("git", args, {
|
|
13979
14359
|
cwd,
|
|
@@ -14026,8 +14406,8 @@ function detectWorktreeState(projectRoot, iamuDirName = IAMU_DIR) {
|
|
|
14026
14406
|
const top = git(["rev-parse", "--show-toplevel"], projectRoot).toString("utf8").trim();
|
|
14027
14407
|
if (!top) return void 0;
|
|
14028
14408
|
const realTop = realpathSync2(top);
|
|
14029
|
-
const realIamu =
|
|
14030
|
-
const iamuRel =
|
|
14409
|
+
const realIamu = path18.join(realpathSync2(projectRoot), iamuDirName);
|
|
14410
|
+
const iamuRel = path18.relative(realTop, realIamu).split(path18.sep).join("/");
|
|
14031
14411
|
const pathspec = iamuRel.startsWith("..") ? [":(top)"] : [":(top)", `:(top,exclude,literal)${iamuRel}`];
|
|
14032
14412
|
const status2 = git(
|
|
14033
14413
|
[
|
|
@@ -14057,7 +14437,7 @@ function detectWorktreeState(projectRoot, iamuDirName = IAMU_DIR) {
|
|
|
14057
14437
|
);
|
|
14058
14438
|
for (const file of sorted.slice(0, MAX_UNTRACKED_HASHED)) {
|
|
14059
14439
|
const rel = file.replace(/\/$/, "");
|
|
14060
|
-
hash.update(`${file}\0${hashFile(
|
|
14440
|
+
hash.update(`${file}\0${hashFile(path18.join(realTop, rel))}
|
|
14061
14441
|
`);
|
|
14062
14442
|
}
|
|
14063
14443
|
if (sorted.length > MAX_UNTRACKED_HASHED) {
|
|
@@ -14228,15 +14608,29 @@ __export(start_run_exports, {
|
|
|
14228
14608
|
formatBuild: () => formatBuild,
|
|
14229
14609
|
formatViewport: () => formatViewport,
|
|
14230
14610
|
resolveStartUrl: () => resolveStartUrl,
|
|
14231
|
-
startRun: () => startRun
|
|
14611
|
+
startRun: () => startRun,
|
|
14612
|
+
testabilityTarget: () => testabilityTarget
|
|
14232
14613
|
});
|
|
14233
|
-
import
|
|
14614
|
+
import fs24 from "fs/promises";
|
|
14615
|
+
import path19 from "path";
|
|
14234
14616
|
import chalk11 from "chalk";
|
|
14235
|
-
function
|
|
14236
|
-
|
|
14617
|
+
async function testabilityTarget(projectRoot) {
|
|
14618
|
+
try {
|
|
14619
|
+
const raw = await fs24.readFile(
|
|
14620
|
+
path19.join(projectRoot, "iamu", "testability.md"),
|
|
14621
|
+
"utf8"
|
|
14622
|
+
);
|
|
14623
|
+
const { frontmatter } = parseFrontmatter(raw);
|
|
14624
|
+
return typeof frontmatter.target_url === "string" && frontmatter.target_url.trim().length > 0 ? frontmatter.target_url.trim() : null;
|
|
14625
|
+
} catch {
|
|
14626
|
+
return null;
|
|
14627
|
+
}
|
|
14628
|
+
}
|
|
14629
|
+
function resolveStartUrl(target, scenario, fallbackTarget) {
|
|
14630
|
+
const base = target ?? scenario.target_url ?? fallbackTarget ?? null;
|
|
14237
14631
|
if (!base) {
|
|
14238
14632
|
return {
|
|
14239
|
-
error: "No target \u2014 pass --target <url>,
|
|
14633
|
+
error: "No target \u2014 pass --target <url>, set target_url on the scenario, or define target_url in iamu/testability.md."
|
|
14240
14634
|
};
|
|
14241
14635
|
}
|
|
14242
14636
|
let parsed;
|
|
@@ -14336,7 +14730,8 @@ async function startRun(scenarioSlug, opts) {
|
|
|
14336
14730
|
);
|
|
14337
14731
|
return;
|
|
14338
14732
|
}
|
|
14339
|
-
const
|
|
14733
|
+
const fallbackTarget = await testabilityTarget(projectRoot);
|
|
14734
|
+
const start2 = resolveStartUrl(opts.target, fm, fallbackTarget);
|
|
14340
14735
|
if ("error" in start2) {
|
|
14341
14736
|
commandError(start2.error, opts);
|
|
14342
14737
|
return;
|
|
@@ -14370,6 +14765,18 @@ async function startRun(scenarioSlug, opts) {
|
|
|
14370
14765
|
}
|
|
14371
14766
|
const build = detectBuild({ cwd: projectRoot, label: opts.build });
|
|
14372
14767
|
const environment = resolveEnvironment(opts.env, start2.url);
|
|
14768
|
+
const cfg = await readConfig(projectRoot).catch(() => null);
|
|
14769
|
+
const walker = opts.walker ?? process.env.IAMU_WALKER_MODEL ?? cfg?.models?.walker ?? "fast";
|
|
14770
|
+
const judge = opts.judge ?? process.env.IAMU_JUDGE_MODEL ?? cfg?.models?.judge ?? "thorough";
|
|
14771
|
+
const matcher = opts.matcher ?? process.env.IAMU_MATCHER_MODEL ?? cfg?.models?.matcher ?? "thorough";
|
|
14772
|
+
const models = {
|
|
14773
|
+
walker,
|
|
14774
|
+
judge,
|
|
14775
|
+
matcher,
|
|
14776
|
+
walker_tier: ["fast", "thorough", "inherit"].includes(walker) ? walker : "custom",
|
|
14777
|
+
judge_tier: ["fast", "thorough", "inherit"].includes(judge) ? judge : "custom",
|
|
14778
|
+
matcher_tier: ["fast", "thorough", "inherit"].includes(matcher) ? matcher : "custom"
|
|
14779
|
+
};
|
|
14373
14780
|
let revisit = [];
|
|
14374
14781
|
try {
|
|
14375
14782
|
revisit = await computeRevisitPlaces(projectRoot, scenarioSlug, start2.url);
|
|
@@ -14387,13 +14794,14 @@ async function startRun(scenarioSlug, opts) {
|
|
|
14387
14794
|
status: "running",
|
|
14388
14795
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14389
14796
|
completed_at: null,
|
|
14797
|
+
models,
|
|
14390
14798
|
...build ? { build } : {},
|
|
14391
14799
|
...environment ? { environment } : {},
|
|
14392
14800
|
...revisit.length > 0 ? { revisit } : {},
|
|
14393
14801
|
...viewport ? { viewport } : {}
|
|
14394
14802
|
};
|
|
14395
14803
|
await atomicWriteFile(
|
|
14396
|
-
|
|
14804
|
+
path19.join(run.absPath, "walkthrough.json"),
|
|
14397
14805
|
JSON.stringify(manifest, null, 2) + "\n"
|
|
14398
14806
|
);
|
|
14399
14807
|
if (opts.json) {
|
|
@@ -14401,13 +14809,14 @@ async function startRun(scenarioSlug, opts) {
|
|
|
14401
14809
|
JSON.stringify(
|
|
14402
14810
|
{
|
|
14403
14811
|
run_id: run.runId,
|
|
14404
|
-
path:
|
|
14812
|
+
path: path19.relative(projectRoot, run.absPath),
|
|
14405
14813
|
scenario_slug: scenarioSlug,
|
|
14406
14814
|
target_url: start2.url,
|
|
14407
14815
|
target_reachable: targetReachable,
|
|
14408
14816
|
...targetWarning ? { target_warning: targetWarning } : {},
|
|
14409
14817
|
persona_slug: fm.persona,
|
|
14410
14818
|
outline_slug: fm.outline,
|
|
14819
|
+
models,
|
|
14411
14820
|
build: build ?? null,
|
|
14412
14821
|
environment: environment ?? null,
|
|
14413
14822
|
revisit,
|
|
@@ -14426,6 +14835,9 @@ async function startRun(scenarioSlug, opts) {
|
|
|
14426
14835
|
console.log(` ${chalk11.dim("persona:")} ${fm.persona}`);
|
|
14427
14836
|
console.log(` ${chalk11.dim("outline:")} ${fm.outline}`);
|
|
14428
14837
|
console.log(` ${chalk11.dim("target:")} ${start2.url}`);
|
|
14838
|
+
console.log(
|
|
14839
|
+
` ${chalk11.dim("models:")} walker: ${chalk11.cyan(walker)} \xB7 judge: ${chalk11.cyan(judge)} \xB7 matcher: ${chalk11.cyan(matcher)}`
|
|
14840
|
+
);
|
|
14429
14841
|
if (targetWarning) {
|
|
14430
14842
|
console.log(` ${symbols.warning} ${chalk11.yellow(targetWarning)}`);
|
|
14431
14843
|
}
|
|
@@ -14445,7 +14857,7 @@ async function startRun(scenarioSlug, opts) {
|
|
|
14445
14857
|
);
|
|
14446
14858
|
}
|
|
14447
14859
|
console.log(
|
|
14448
|
-
` ${chalk11.dim("path:")} ${
|
|
14860
|
+
` ${chalk11.dim("path:")} ${path19.relative(projectRoot, run.absPath)}`
|
|
14449
14861
|
);
|
|
14450
14862
|
console.log();
|
|
14451
14863
|
console.log(` ${chalk11.bold("Next steps for blind execution:")}`);
|
|
@@ -14486,8 +14898,8 @@ var report_exports = {};
|
|
|
14486
14898
|
__export(report_exports, {
|
|
14487
14899
|
report: () => report
|
|
14488
14900
|
});
|
|
14489
|
-
import { promises as
|
|
14490
|
-
import
|
|
14901
|
+
import { promises as fs25 } from "fs";
|
|
14902
|
+
import path20 from "path";
|
|
14491
14903
|
import chalk12 from "chalk";
|
|
14492
14904
|
async function report(scenario, opts) {
|
|
14493
14905
|
const ctx = await resolveProject(opts);
|
|
@@ -14506,8 +14918,8 @@ async function report(scenario, opts) {
|
|
|
14506
14918
|
for (const rid of runs.reverse()) {
|
|
14507
14919
|
try {
|
|
14508
14920
|
const dir = runDir(projectRoot, rid);
|
|
14509
|
-
const mRaw = await
|
|
14510
|
-
|
|
14921
|
+
const mRaw = await fs25.readFile(
|
|
14922
|
+
path20.join(dir, "walkthrough.json"),
|
|
14511
14923
|
"utf8"
|
|
14512
14924
|
);
|
|
14513
14925
|
const manifest = JSON.parse(mRaw);
|
|
@@ -14515,8 +14927,8 @@ async function report(scenario, opts) {
|
|
|
14515
14927
|
if (scenario && manifest.scenario_slug !== scenario) continue;
|
|
14516
14928
|
let canonical = null;
|
|
14517
14929
|
try {
|
|
14518
|
-
const cRaw = await
|
|
14519
|
-
|
|
14930
|
+
const cRaw = await fs25.readFile(
|
|
14931
|
+
path20.join(dir, "canonical.json"),
|
|
14520
14932
|
"utf8"
|
|
14521
14933
|
);
|
|
14522
14934
|
canonical = JSON.parse(cRaw);
|
|
@@ -15723,12 +16135,13 @@ var init_serve = __esm({
|
|
|
15723
16135
|
// src/commands/init.ts
|
|
15724
16136
|
var init_exports = {};
|
|
15725
16137
|
__export(init_exports, {
|
|
15726
|
-
init: () => init
|
|
16138
|
+
init: () => init,
|
|
16139
|
+
setupCommand: () => setupCommand
|
|
15727
16140
|
});
|
|
15728
16141
|
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
|
|
15729
16142
|
import { mkdir as mkdir4, writeFile as writeFile3 } from "fs/promises";
|
|
15730
16143
|
import { basename as basename2, join as join5 } from "path";
|
|
15731
|
-
import { isCancel as isCancel3, multiselect } from "@clack/prompts";
|
|
16144
|
+
import { isCancel as isCancel3, multiselect, select as select3 } from "@clack/prompts";
|
|
15732
16145
|
import chalk17 from "chalk";
|
|
15733
16146
|
function loadPromptTemplates() {
|
|
15734
16147
|
const templates = /* @__PURE__ */ new Map();
|
|
@@ -15750,9 +16163,9 @@ function loadPromptTemplates() {
|
|
|
15750
16163
|
return templates;
|
|
15751
16164
|
}
|
|
15752
16165
|
function setupCommand(projectRoot, hosts) {
|
|
15753
|
-
if (!iamuPluginEnabled(projectRoot)) return "iamu-setup";
|
|
16166
|
+
if (!iamuPluginEnabled(projectRoot)) return "/iamu-setup";
|
|
15754
16167
|
if (hosts.every((h) => h.value === "claude")) return "/iamu:init";
|
|
15755
|
-
return "iamu-setup (Claude Code: /iamu:init)";
|
|
16168
|
+
return "/iamu-setup (Claude Code: /iamu:init)";
|
|
15756
16169
|
}
|
|
15757
16170
|
async function init(opts) {
|
|
15758
16171
|
const options = typeof opts === "string" ? { name: opts } : opts ?? {};
|
|
@@ -15772,10 +16185,10 @@ async function init(opts) {
|
|
|
15772
16185
|
}
|
|
15773
16186
|
let freshInit = false;
|
|
15774
16187
|
if (existsSync7(join5(iamuDir2, "config.json"))) {
|
|
15775
|
-
const
|
|
16188
|
+
const config2 = await readConfig(projectRoot);
|
|
15776
16189
|
if (!options.json) {
|
|
15777
16190
|
console.log(
|
|
15778
|
-
chalk17.yellow(" iamu/ already exists") + chalk17.dim(` (${
|
|
16191
|
+
chalk17.yellow(" iamu/ already exists") + chalk17.dim(` (${config2?.project_name ?? "unknown"})`)
|
|
15779
16192
|
);
|
|
15780
16193
|
}
|
|
15781
16194
|
} else {
|
|
@@ -15792,6 +16205,35 @@ async function init(opts) {
|
|
|
15792
16205
|
""
|
|
15793
16206
|
)
|
|
15794
16207
|
);
|
|
16208
|
+
const testabilityPath = join5(iamuDir2, "testability.md");
|
|
16209
|
+
if (!existsSync7(testabilityPath)) {
|
|
16210
|
+
await writeFile3(
|
|
16211
|
+
testabilityPath,
|
|
16212
|
+
serializeFrontmatter(
|
|
16213
|
+
{
|
|
16214
|
+
status: "draft",
|
|
16215
|
+
target_url: "http://localhost:3000"
|
|
16216
|
+
},
|
|
16217
|
+
`# Project Testability & Environment
|
|
16218
|
+
|
|
16219
|
+
Target URL and execution harness for walkthroughs.
|
|
16220
|
+
Audited and populated during /iamu:init.
|
|
16221
|
+
|
|
16222
|
+
## Target & Run Instructions
|
|
16223
|
+
- Default URL: http://localhost:3000
|
|
16224
|
+
|
|
16225
|
+
## Reachable Surfaces
|
|
16226
|
+
(Run /iamu:init to discover testable routes)
|
|
16227
|
+
|
|
16228
|
+
## Mocks & Data Fixtures
|
|
16229
|
+
(Run /iamu:init to inspect existing harnesses)
|
|
16230
|
+
|
|
16231
|
+
## Untestable Boundaries
|
|
16232
|
+
(None documented yet)
|
|
16233
|
+
`
|
|
16234
|
+
)
|
|
16235
|
+
);
|
|
16236
|
+
}
|
|
15795
16237
|
}
|
|
15796
16238
|
const promptTemplates = loadPromptTemplates();
|
|
15797
16239
|
const promptsDir = join5(iamuDir2, ".prompts");
|
|
@@ -15848,20 +16290,57 @@ async function init(opts) {
|
|
|
15848
16290
|
version,
|
|
15849
16291
|
{ quiet: options.json }
|
|
15850
16292
|
);
|
|
15851
|
-
|
|
15852
|
-
|
|
15853
|
-
|
|
15854
|
-
|
|
15855
|
-
|
|
15856
|
-
|
|
16293
|
+
let config = await readConfig(projectRoot);
|
|
16294
|
+
const shouldPromptCloud = isInteractive && freshInit && !config.cloud_linked && options.cloud !== false;
|
|
16295
|
+
if (options.cloud === true && !config.cloud_linked) {
|
|
16296
|
+
try {
|
|
16297
|
+
await authLogin({ yes: options.yes });
|
|
16298
|
+
config = await readConfig(projectRoot);
|
|
16299
|
+
} catch {
|
|
16300
|
+
}
|
|
16301
|
+
} else if (shouldPromptCloud) {
|
|
16302
|
+
console.log();
|
|
16303
|
+
const cloudChoice = await select3({
|
|
16304
|
+
message: "Connect this project to Iamu Cloud? (Free)",
|
|
16305
|
+
options: [
|
|
16306
|
+
{
|
|
16307
|
+
value: "connect",
|
|
16308
|
+
label: "Connect to Iamu Cloud (opens browser)",
|
|
16309
|
+
hint: "recommended \u2014 shareable run reports, step replays, team sync"
|
|
16310
|
+
},
|
|
16311
|
+
{
|
|
16312
|
+
value: "local",
|
|
16313
|
+
label: "Continue local-only",
|
|
16314
|
+
hint: "skip for now (link anytime with `iamu auth login`)"
|
|
16315
|
+
}
|
|
16316
|
+
],
|
|
16317
|
+
initialValue: "connect"
|
|
16318
|
+
});
|
|
16319
|
+
if (!isCancel3(cloudChoice) && cloudChoice === "connect") {
|
|
16320
|
+
const prevExitCode = process.exitCode;
|
|
16321
|
+
try {
|
|
16322
|
+
await authLogin({ yes: options.yes });
|
|
16323
|
+
config = await readConfig(projectRoot);
|
|
16324
|
+
} catch {
|
|
16325
|
+
process.exitCode = prevExitCode;
|
|
15857
16326
|
if (!options.json) {
|
|
15858
16327
|
console.log(
|
|
15859
|
-
|
|
16328
|
+
chalk17.dim(
|
|
16329
|
+
" Cloud linking skipped. Your local setup is complete and fully functional."
|
|
16330
|
+
)
|
|
15860
16331
|
);
|
|
15861
16332
|
}
|
|
15862
16333
|
}
|
|
15863
16334
|
}
|
|
15864
|
-
}
|
|
16335
|
+
}
|
|
16336
|
+
if (config.cloud_linked) {
|
|
16337
|
+
for (const host of selectedHosts) {
|
|
16338
|
+
const mcpPath = host.getMcpConfigPath(projectRoot);
|
|
16339
|
+
await writeSyncMcpEntry(mcpPath);
|
|
16340
|
+
if (!options.json) {
|
|
16341
|
+
console.log(` ${symbols.success} ${host.name} \u2014 iamu-sync MCP server`);
|
|
16342
|
+
}
|
|
16343
|
+
}
|
|
15865
16344
|
}
|
|
15866
16345
|
if (options.json) {
|
|
15867
16346
|
console.log(
|
|
@@ -15873,7 +16352,10 @@ async function init(opts) {
|
|
|
15873
16352
|
fresh: freshInit,
|
|
15874
16353
|
tools: selectedHosts.map((h) => h.name),
|
|
15875
16354
|
skillsCount: totalSkills,
|
|
15876
|
-
commandsCount: totalCommands
|
|
16355
|
+
commandsCount: totalCommands,
|
|
16356
|
+
cloudLinked: Boolean(config.cloud_linked),
|
|
16357
|
+
cloudProjectId: config.cloud_project_id ?? null,
|
|
16358
|
+
dashboardUrl: config.cloud_linked && config.cloud_project_id ? getCloudDashboardUrl(void 0, config.cloud_project_id) : null
|
|
15877
16359
|
},
|
|
15878
16360
|
null,
|
|
15879
16361
|
2
|
|
@@ -15881,43 +16363,89 @@ async function init(opts) {
|
|
|
15881
16363
|
);
|
|
15882
16364
|
return;
|
|
15883
16365
|
}
|
|
15884
|
-
if (
|
|
16366
|
+
if (!options.json) {
|
|
15885
16367
|
console.log();
|
|
15886
|
-
const summaryParts = [];
|
|
15887
|
-
if (totalSkills > 0) summaryParts.push(`${totalSkills} skills`);
|
|
15888
|
-
if (totalCommands > 0) summaryParts.push(`${totalCommands} commands`);
|
|
15889
16368
|
console.log(
|
|
15890
|
-
chalk17.
|
|
15891
|
-
`
|
|
16369
|
+
chalk17.bold(
|
|
16370
|
+
` ${symbols.success} Project configured: ${chalk17.cyan(projectName)}`
|
|
15892
16371
|
)
|
|
15893
16372
|
);
|
|
15894
|
-
}
|
|
15895
|
-
console.log();
|
|
15896
|
-
if (freshInit) {
|
|
15897
|
-
console.log(chalk17.bold(" Next step:"));
|
|
15898
16373
|
console.log();
|
|
15899
|
-
|
|
16374
|
+
if (selectedHosts.length > 0) {
|
|
16375
|
+
const toolNames = selectedHosts.map((h) => h.name).join(", ");
|
|
16376
|
+
console.log(
|
|
16377
|
+
` ${chalk17.dim("Tools:")} ${toolNames} ${chalk17.dim(`(${totalSkills} skills + ${totalCommands} commands installed)`)}`
|
|
16378
|
+
);
|
|
16379
|
+
}
|
|
16380
|
+
if (config.cloud_linked && config.cloud_project_id) {
|
|
16381
|
+
const dashboardUrl = getCloudDashboardUrl(
|
|
16382
|
+
void 0,
|
|
16383
|
+
config.cloud_project_id
|
|
16384
|
+
);
|
|
16385
|
+
console.log(
|
|
16386
|
+
` ${chalk17.dim("Cloud:")} ${chalk17.green("Linked")} \xB7 ${chalk17.cyan(dashboardUrl)}`
|
|
16387
|
+
);
|
|
16388
|
+
} else {
|
|
16389
|
+
console.log(
|
|
16390
|
+
` ${chalk17.dim("Cloud:")} Local-only ${chalk17.dim("(link anytime with `iamu auth login`)")}`
|
|
16391
|
+
);
|
|
16392
|
+
}
|
|
15900
16393
|
console.log();
|
|
15901
|
-
|
|
16394
|
+
if (freshInit) {
|
|
16395
|
+
console.log(chalk17.bold(" Next steps:"));
|
|
16396
|
+
console.log();
|
|
16397
|
+
console.log(
|
|
16398
|
+
` ${chalk17.bold("1.")} Start your application's dev server:`
|
|
16399
|
+
);
|
|
16400
|
+
console.log(
|
|
16401
|
+
` ${chalk17.cyan("$ pnpm dev")} ${chalk17.dim("(or npm run dev)")}`
|
|
16402
|
+
);
|
|
16403
|
+
console.log();
|
|
16404
|
+
console.log(` ${chalk17.bold("2.")} In your AI agent chat, run:`);
|
|
16405
|
+
console.log(
|
|
16406
|
+
` ${chalk17.cyan(setupCommand(projectRoot, selectedHosts))}`
|
|
16407
|
+
);
|
|
16408
|
+
console.log();
|
|
16409
|
+
console.log(
|
|
16410
|
+
chalk17.dim(
|
|
16411
|
+
" Your agent will analyze your codebase, map user roles, and generate your first walkthrough scenario."
|
|
16412
|
+
)
|
|
16413
|
+
);
|
|
16414
|
+
console.log();
|
|
16415
|
+
console.log(chalk17.bold(" Core workflow:"));
|
|
16416
|
+
console.log(
|
|
16417
|
+
chalk17.dim(" \u2022 ") + chalk17.cyan("/iamu:init") + chalk17.dim(" Map domain & scaffold inaugural scenario")
|
|
16418
|
+
);
|
|
16419
|
+
console.log(
|
|
16420
|
+
chalk17.dim(" \u2022 ") + chalk17.cyan("iamu-run <slug>") + chalk17.dim(" Walk scenario in browser & record findings")
|
|
16421
|
+
);
|
|
16422
|
+
console.log(
|
|
16423
|
+
chalk17.dim(" \u2022 ") + chalk17.cyan("iamu-findings") + chalk17.dim(" Triage persona feedback & apply code fixes")
|
|
16424
|
+
);
|
|
16425
|
+
console.log(
|
|
16426
|
+
chalk17.dim(" \u2022 ") + chalk17.cyan("iamu-propose") + chalk17.dim(" Brainstorm high-risk workflows & new personas")
|
|
16427
|
+
);
|
|
16428
|
+
} else {
|
|
16429
|
+
console.log(chalk17.bold(" Updated:"));
|
|
16430
|
+
console.log();
|
|
16431
|
+
console.log(
|
|
16432
|
+
chalk17.dim(" Skills and commands regenerated to latest version.")
|
|
16433
|
+
);
|
|
16434
|
+
}
|
|
15902
16435
|
console.log();
|
|
15903
16436
|
console.log(
|
|
15904
|
-
chalk17.dim("
|
|
16437
|
+
` ${chalk17.dim("Docs:")} ${chalk17.underline("https://iamu.dev/docs")}`
|
|
15905
16438
|
);
|
|
15906
16439
|
console.log(
|
|
15907
|
-
chalk17.dim("
|
|
16440
|
+
` ${chalk17.dim("Quickstart:")} ${chalk17.underline("https://iamu.dev/quickstart")}`
|
|
15908
16441
|
);
|
|
15909
|
-
|
|
15910
|
-
|
|
16442
|
+
if (config.cloud_linked && config.cloud_project_id) {
|
|
16443
|
+
console.log(
|
|
16444
|
+
` ${chalk17.dim("Dashboard:")} ${chalk17.underline(getCloudDashboardUrl(void 0, config.cloud_project_id))}`
|
|
16445
|
+
);
|
|
16446
|
+
}
|
|
15911
16447
|
console.log();
|
|
15912
|
-
console.log(
|
|
15913
|
-
chalk17.dim(" Skills and commands regenerated to latest version.")
|
|
15914
|
-
);
|
|
15915
16448
|
}
|
|
15916
|
-
console.log();
|
|
15917
|
-
console.log(
|
|
15918
|
-
chalk17.dim(" Docs: ") + chalk17.underline("https://iamu.dev/quickstart")
|
|
15919
|
-
);
|
|
15920
|
-
console.log();
|
|
15921
16449
|
}
|
|
15922
16450
|
var init_init = __esm({
|
|
15923
16451
|
"src/commands/init.ts"() {
|
|
@@ -15925,7 +16453,9 @@ var init_init = __esm({
|
|
|
15925
16453
|
init_dist2();
|
|
15926
16454
|
init_claude_plugin();
|
|
15927
16455
|
init_registry();
|
|
16456
|
+
init_cloud_url();
|
|
15928
16457
|
init_symbols();
|
|
16458
|
+
init_auth();
|
|
15929
16459
|
init_mcp_config();
|
|
15930
16460
|
init_skills();
|
|
15931
16461
|
init_templates();
|
|
@@ -16221,7 +16751,10 @@ var init_browse_actions = __esm({
|
|
|
16221
16751
|
usage: "goto <url>",
|
|
16222
16752
|
minArgs: 1,
|
|
16223
16753
|
run: async (p, args) => {
|
|
16224
|
-
await p.goto(args[0], {
|
|
16754
|
+
await p.goto(args[0], {
|
|
16755
|
+
waitUntil: "domcontentloaded",
|
|
16756
|
+
timeout: 1e4
|
|
16757
|
+
});
|
|
16225
16758
|
}
|
|
16226
16759
|
},
|
|
16227
16760
|
click: {
|
|
@@ -16382,6 +16915,12 @@ var init_browse_driver = __esm({
|
|
|
16382
16915
|
commandLine: (pid) => readProcessCommandLine(pid),
|
|
16383
16916
|
kill(pid) {
|
|
16384
16917
|
try {
|
|
16918
|
+
if (process.platform !== "win32") {
|
|
16919
|
+
try {
|
|
16920
|
+
process.kill(-pid, "SIGTERM");
|
|
16921
|
+
} catch {
|
|
16922
|
+
}
|
|
16923
|
+
}
|
|
16385
16924
|
process.kill(pid, "SIGTERM");
|
|
16386
16925
|
} catch {
|
|
16387
16926
|
}
|
|
@@ -16392,7 +16931,7 @@ var init_browse_driver = __esm({
|
|
|
16392
16931
|
|
|
16393
16932
|
// src/commands/step-numbering.ts
|
|
16394
16933
|
import { open, readdir, unlink } from "fs/promises";
|
|
16395
|
-
import
|
|
16934
|
+
import path21 from "path";
|
|
16396
16935
|
function stepFileName(stepNumber2) {
|
|
16397
16936
|
return `${String(stepNumber2).padStart(3, "0")}.json`;
|
|
16398
16937
|
}
|
|
@@ -16414,7 +16953,7 @@ async function reserveStepFile(stepsDir, limit) {
|
|
|
16414
16953
|
if (candidate > limit.maxSteps) {
|
|
16415
16954
|
throw new Error(stepLimitMessage(limit.maxSteps, limit.runId));
|
|
16416
16955
|
}
|
|
16417
|
-
const stepPath =
|
|
16956
|
+
const stepPath = path21.join(stepsDir, stepFileName(candidate));
|
|
16418
16957
|
try {
|
|
16419
16958
|
const handle = await open(stepPath, "wx");
|
|
16420
16959
|
await handle.close();
|
|
@@ -16446,7 +16985,7 @@ __export(step_exports, {
|
|
|
16446
16985
|
stepCommand: () => stepCommand
|
|
16447
16986
|
});
|
|
16448
16987
|
import { copyFile, mkdir as mkdir5, stat } from "fs/promises";
|
|
16449
|
-
import
|
|
16988
|
+
import path22 from "path";
|
|
16450
16989
|
import chalk19 from "chalk";
|
|
16451
16990
|
function parseMaxSteps(val) {
|
|
16452
16991
|
if (typeof val === "number" && Number.isFinite(val) && val > 0) return val;
|
|
@@ -16465,8 +17004,8 @@ async function recordStep(input) {
|
|
|
16465
17004
|
if (!runDirPath) {
|
|
16466
17005
|
throw new Error(`Run directory not found for run "${input.runId}"`);
|
|
16467
17006
|
}
|
|
16468
|
-
const stepsDir =
|
|
16469
|
-
const screenshotsDir =
|
|
17007
|
+
const stepsDir = path22.join(runDirPath, "steps");
|
|
17008
|
+
const screenshotsDir = path22.join(runDirPath, "screenshots");
|
|
16470
17009
|
await mkdir5(stepsDir, { recursive: true });
|
|
16471
17010
|
await mkdir5(screenshotsDir, { recursive: true });
|
|
16472
17011
|
const shotSource = input.shot ? await existingShot(input.shot) : null;
|
|
@@ -16480,11 +17019,11 @@ async function recordStep(input) {
|
|
|
16480
17019
|
let finalScreenshotPath = null;
|
|
16481
17020
|
if (shotSource) {
|
|
16482
17021
|
const destName = `step-${stepNumber2}.png`;
|
|
16483
|
-
finalScreenshotPath =
|
|
16484
|
-
if (
|
|
17022
|
+
finalScreenshotPath = path22.join(screenshotsDir, destName);
|
|
17023
|
+
if (path22.resolve(finalScreenshotPath) !== shotSource) {
|
|
16485
17024
|
await copyFile(shotSource, finalScreenshotPath);
|
|
16486
17025
|
}
|
|
16487
|
-
screenshotRef =
|
|
17026
|
+
screenshotRef = path22.join("screenshots", destName);
|
|
16488
17027
|
}
|
|
16489
17028
|
const parsedStep = runStepSchema.parse({
|
|
16490
17029
|
step_number: stepNumber2,
|
|
@@ -16509,7 +17048,7 @@ async function recordStep(input) {
|
|
|
16509
17048
|
}
|
|
16510
17049
|
}
|
|
16511
17050
|
async function existingShot(shot) {
|
|
16512
|
-
const resolved =
|
|
17051
|
+
const resolved = path22.resolve(shot);
|
|
16513
17052
|
const isFile = await stat(resolved).then((s) => s.isFile()).catch(() => false);
|
|
16514
17053
|
if (!isFile) {
|
|
16515
17054
|
throw new Error(
|
|
@@ -16551,7 +17090,7 @@ async function stepCommand(runArg, opts) {
|
|
|
16551
17090
|
JSON.stringify(
|
|
16552
17091
|
{
|
|
16553
17092
|
step_number: res.step.step_number,
|
|
16554
|
-
path:
|
|
17093
|
+
path: path22.relative(projectRoot, res.stepPath),
|
|
16555
17094
|
action: res.step.action,
|
|
16556
17095
|
note: res.step.note,
|
|
16557
17096
|
url: res.step.url,
|
|
@@ -16578,7 +17117,7 @@ async function stepCommand(runArg, opts) {
|
|
|
16578
17117
|
console.log(` ${chalk19.dim("shot:")} ${res.step.screenshot}`);
|
|
16579
17118
|
}
|
|
16580
17119
|
console.log(
|
|
16581
|
-
` ${chalk19.dim("file:")} ${
|
|
17120
|
+
` ${chalk19.dim("file:")} ${path22.relative(projectRoot, res.stepPath)}`
|
|
16582
17121
|
);
|
|
16583
17122
|
console.log();
|
|
16584
17123
|
} catch (err) {
|
|
@@ -16608,6 +17147,7 @@ __export(browse_exports, {
|
|
|
16608
17147
|
readBrowseState: () => readBrowseState,
|
|
16609
17148
|
shouldDisableSandbox: () => shouldDisableSandbox,
|
|
16610
17149
|
start: () => start,
|
|
17150
|
+
waitForVisualStability: () => waitForVisualStability,
|
|
16611
17151
|
withPage: () => withPage
|
|
16612
17152
|
});
|
|
16613
17153
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
@@ -16620,21 +17160,21 @@ import {
|
|
|
16620
17160
|
} from "fs";
|
|
16621
17161
|
import { rm as rm2 } from "fs/promises";
|
|
16622
17162
|
import { homedir as homedir3 } from "os";
|
|
16623
|
-
import
|
|
17163
|
+
import path23 from "path";
|
|
16624
17164
|
import chalk20 from "chalk";
|
|
16625
17165
|
function stateDir() {
|
|
16626
|
-
return
|
|
16627
|
-
process.env.IAMU_HOME ??
|
|
17166
|
+
return path23.join(
|
|
17167
|
+
process.env.IAMU_HOME ?? path23.join(homedir3(), ".iamu"),
|
|
16628
17168
|
"browse"
|
|
16629
17169
|
);
|
|
16630
17170
|
}
|
|
16631
17171
|
function profileDir() {
|
|
16632
|
-
return
|
|
17172
|
+
return path23.join(stateDir(), "profile");
|
|
16633
17173
|
}
|
|
16634
17174
|
function readBrowseState() {
|
|
16635
17175
|
try {
|
|
16636
17176
|
return JSON.parse(
|
|
16637
|
-
readFileSync6(
|
|
17177
|
+
readFileSync6(path23.join(stateDir(), "state.json"), "utf8")
|
|
16638
17178
|
);
|
|
16639
17179
|
} catch {
|
|
16640
17180
|
return null;
|
|
@@ -16642,7 +17182,7 @@ function readBrowseState() {
|
|
|
16642
17182
|
}
|
|
16643
17183
|
function writeState(state) {
|
|
16644
17184
|
mkdirSync(stateDir(), { recursive: true });
|
|
16645
|
-
writeFileSync(
|
|
17185
|
+
writeFileSync(path23.join(stateDir(), "state.json"), JSON.stringify(state));
|
|
16646
17186
|
}
|
|
16647
17187
|
function fail(message, opts) {
|
|
16648
17188
|
if (opts?.json) {
|
|
@@ -16705,7 +17245,7 @@ async function requestedViewport(opts) {
|
|
|
16705
17245
|
let manifest;
|
|
16706
17246
|
try {
|
|
16707
17247
|
manifest = JSON.parse(
|
|
16708
|
-
readFileSync6(
|
|
17248
|
+
readFileSync6(path23.join(dir, "walkthrough.json"), "utf8")
|
|
16709
17249
|
);
|
|
16710
17250
|
} catch {
|
|
16711
17251
|
fail(
|
|
@@ -16757,6 +17297,8 @@ function chromeArgs(opts, port, headless) {
|
|
|
16757
17297
|
`--window-size=${opts.width ?? "1280"},${opts.height ?? "900"}`,
|
|
16758
17298
|
"--no-first-run",
|
|
16759
17299
|
"--no-default-browser-check",
|
|
17300
|
+
"--password-store=basic",
|
|
17301
|
+
"--use-mock-keychain",
|
|
16760
17302
|
...headless ? ["--headless=new", "--disable-dev-shm-usage", "--disable-gpu"] : [],
|
|
16761
17303
|
...shouldDisableSandbox() ? ["--no-sandbox"] : [],
|
|
16762
17304
|
"about:blank"
|
|
@@ -16809,11 +17351,50 @@ async function stop(opts, driver) {
|
|
|
16809
17351
|
await driver.closeBrowser(state.port);
|
|
16810
17352
|
if (state.pid) await reapBrowser(state.pid, profileDir(), driver);
|
|
16811
17353
|
try {
|
|
16812
|
-
unlinkSync(
|
|
17354
|
+
unlinkSync(path23.join(stateDir(), "state.json"));
|
|
16813
17355
|
} catch {
|
|
16814
17356
|
}
|
|
16815
17357
|
report2(opts, { stopped: true }, [` ${chalk20.green("\u2713")} Browser stopped`]);
|
|
16816
17358
|
}
|
|
17359
|
+
async function waitForVisualStability(page, maxWaitMs = 300) {
|
|
17360
|
+
let timer;
|
|
17361
|
+
try {
|
|
17362
|
+
const nodeTimeout = new Promise((resolve3) => {
|
|
17363
|
+
timer = setTimeout(resolve3, maxWaitMs + 100);
|
|
17364
|
+
});
|
|
17365
|
+
const browserWait = page.evaluate((maxWait) => {
|
|
17366
|
+
return new Promise((resolve3) => {
|
|
17367
|
+
const timeout = setTimeout(resolve3, maxWait);
|
|
17368
|
+
const done = () => {
|
|
17369
|
+
clearTimeout(timeout);
|
|
17370
|
+
resolve3();
|
|
17371
|
+
};
|
|
17372
|
+
const g = globalThis;
|
|
17373
|
+
if (typeof g.requestAnimationFrame !== "function") {
|
|
17374
|
+
done();
|
|
17375
|
+
return;
|
|
17376
|
+
}
|
|
17377
|
+
g.requestAnimationFrame(() => {
|
|
17378
|
+
g.requestAnimationFrame?.(() => {
|
|
17379
|
+
const animations = g.document?.getAnimations ? g.document.getAnimations().filter((a) => a.playState === "running" && a.finished) : [];
|
|
17380
|
+
if (animations.length === 0) {
|
|
17381
|
+
done();
|
|
17382
|
+
return;
|
|
17383
|
+
}
|
|
17384
|
+
Promise.allSettled(
|
|
17385
|
+
animations.map((a) => a.finished)
|
|
17386
|
+
).finally(done);
|
|
17387
|
+
});
|
|
17388
|
+
});
|
|
17389
|
+
});
|
|
17390
|
+
}, maxWaitMs).catch(() => {
|
|
17391
|
+
});
|
|
17392
|
+
await Promise.race([browserWait, nodeTimeout]);
|
|
17393
|
+
} catch {
|
|
17394
|
+
} finally {
|
|
17395
|
+
if (timer) clearTimeout(timer);
|
|
17396
|
+
}
|
|
17397
|
+
}
|
|
16817
17398
|
async function withPage(opts, driver, fn, target) {
|
|
16818
17399
|
const state = readBrowseState();
|
|
16819
17400
|
if (!state) {
|
|
@@ -16833,11 +17414,11 @@ async function withPage(opts, driver, fn, target) {
|
|
|
16833
17414
|
await fn(page);
|
|
16834
17415
|
await page.waitForLoadState("domcontentloaded").catch(() => {
|
|
16835
17416
|
});
|
|
16836
|
-
await page.waitForTimeout(400);
|
|
16837
17417
|
let screenshot = null;
|
|
16838
17418
|
if (opts.shot) {
|
|
16839
|
-
|
|
16840
|
-
|
|
17419
|
+
await waitForVisualStability(page);
|
|
17420
|
+
screenshot = path23.resolve(opts.shot);
|
|
17421
|
+
mkdirSync(path23.dirname(screenshot), { recursive: true });
|
|
16841
17422
|
await page.screenshot({ path: screenshot, fullPage: Boolean(opts.full) });
|
|
16842
17423
|
}
|
|
16843
17424
|
return { url: page.url(), title: await page.title(), screenshot };
|
|
@@ -16916,7 +17497,7 @@ function printStep(opts, record, projectRoot) {
|
|
|
16916
17497
|
if (opts.json) {
|
|
16917
17498
|
const doc = {
|
|
16918
17499
|
step_number: step.step_number,
|
|
16919
|
-
path:
|
|
17500
|
+
path: path23.relative(projectRoot, record.stepPath),
|
|
16920
17501
|
action: step.action,
|
|
16921
17502
|
note: step.note,
|
|
16922
17503
|
url: step.url,
|
|
@@ -16936,6 +17517,35 @@ function printStep(opts, record, projectRoot) {
|
|
|
16936
17517
|
}
|
|
16937
17518
|
}
|
|
16938
17519
|
async function browseStep(args, opts, driver) {
|
|
17520
|
+
if (opts.stepFile) {
|
|
17521
|
+
try {
|
|
17522
|
+
let raw;
|
|
17523
|
+
if (opts.stepFile === "-") {
|
|
17524
|
+
raw = readFileSync6(0, "utf8");
|
|
17525
|
+
} else {
|
|
17526
|
+
raw = readFileSync6(opts.stepFile, "utf8");
|
|
17527
|
+
}
|
|
17528
|
+
const data = JSON.parse(raw);
|
|
17529
|
+
if (data.run && typeof data.run === "string") opts.run = data.run;
|
|
17530
|
+
if (data.action && typeof data.action === "string")
|
|
17531
|
+
opts.action = data.action;
|
|
17532
|
+
if (data.note && typeof data.note === "string") opts.note = data.note;
|
|
17533
|
+
if (data.surface && typeof data.surface === "string")
|
|
17534
|
+
opts.surface = data.surface;
|
|
17535
|
+
if (args.length === 0 && data.cmd) {
|
|
17536
|
+
if (Array.isArray(data.cmd)) {
|
|
17537
|
+
args = data.cmd.map(String);
|
|
17538
|
+
} else if (typeof data.cmd === "string") {
|
|
17539
|
+
args = data.cmd.trim().split(/\s+/);
|
|
17540
|
+
}
|
|
17541
|
+
}
|
|
17542
|
+
} catch (err) {
|
|
17543
|
+
return fail(
|
|
17544
|
+
`Failed to read step payload from ${opts.stepFile}: ${err.message}`,
|
|
17545
|
+
opts
|
|
17546
|
+
);
|
|
17547
|
+
}
|
|
17548
|
+
}
|
|
16939
17549
|
if (!opts.run)
|
|
16940
17550
|
return fail("--run <run-id> is required for browse step.", opts);
|
|
16941
17551
|
if (!opts.action) {
|
|
@@ -16961,10 +17571,10 @@ async function browseStep(args, opts, driver) {
|
|
|
16961
17571
|
const runDirPath = resolveRunDir(ctx.projectRoot, opts.run);
|
|
16962
17572
|
if (!runDirPath) return;
|
|
16963
17573
|
const maxSteps = parseMaxSteps(opts.maxSteps);
|
|
16964
|
-
if (await peekNextStepNumber(
|
|
17574
|
+
if (await peekNextStepNumber(path23.join(runDirPath, "steps")) > maxSteps) {
|
|
16965
17575
|
return fail(stepLimitMessage(maxSteps, opts.run), opts);
|
|
16966
17576
|
}
|
|
16967
|
-
const pendingShot =
|
|
17577
|
+
const pendingShot = path23.join(
|
|
16968
17578
|
runDirPath,
|
|
16969
17579
|
"screenshots",
|
|
16970
17580
|
`.pending-${randomUUID7()}.png`
|
|
@@ -17035,11 +17645,14 @@ var doctor_exports = {};
|
|
|
17035
17645
|
__export(doctor_exports, {
|
|
17036
17646
|
checkCdpResponsive: () => checkCdpResponsive,
|
|
17037
17647
|
checkPortAvailable: () => checkPortAvailable,
|
|
17648
|
+
checkProjectHarnesses: () => checkProjectHarnesses,
|
|
17649
|
+
checkProjectTestabilityContract: () => checkProjectTestabilityContract,
|
|
17650
|
+
checkTargetHealth: () => checkTargetHealth,
|
|
17038
17651
|
doctor: () => doctor,
|
|
17039
17652
|
runDoctorChecks: () => runDoctorChecks
|
|
17040
17653
|
});
|
|
17041
17654
|
import { createServer } from "net";
|
|
17042
|
-
import { existsSync as existsSync10 } from "fs";
|
|
17655
|
+
import { existsSync as existsSync10, promises as fs26 } from "fs";
|
|
17043
17656
|
import { join as join7 } from "path";
|
|
17044
17657
|
import chalk21 from "chalk";
|
|
17045
17658
|
function checkPortAvailable(port) {
|
|
@@ -17317,11 +17930,186 @@ async function checkAgentIntegrations(projectRoot) {
|
|
|
17317
17930
|
}
|
|
17318
17931
|
return checks;
|
|
17319
17932
|
}
|
|
17320
|
-
async function
|
|
17933
|
+
async function checkTargetHealth(targetUrl) {
|
|
17934
|
+
const checks = [];
|
|
17935
|
+
let parsedUrl;
|
|
17936
|
+
try {
|
|
17937
|
+
parsedUrl = new URL(targetUrl);
|
|
17938
|
+
} catch {
|
|
17939
|
+
checks.push({
|
|
17940
|
+
category: "target",
|
|
17941
|
+
name: "Target URL Format",
|
|
17942
|
+
status: "error",
|
|
17943
|
+
message: `Invalid target URL: "${targetUrl}"`,
|
|
17944
|
+
remedy: "Provide a valid URL with protocol (e.g. http://localhost:3000)."
|
|
17945
|
+
});
|
|
17946
|
+
return checks;
|
|
17947
|
+
}
|
|
17948
|
+
try {
|
|
17949
|
+
const res = await fetch(targetUrl, {
|
|
17950
|
+
method: "GET",
|
|
17951
|
+
redirect: "manual",
|
|
17952
|
+
signal: AbortSignal.timeout(3e3)
|
|
17953
|
+
});
|
|
17954
|
+
if ([301, 302, 303, 307, 308].includes(res.status)) {
|
|
17955
|
+
const location = res.headers.get("location") || "";
|
|
17956
|
+
const isExternalAuth = /auth0\.com|okta\.com|clerk\.com|accounts\.google\.com|cognito|login\.microsoftonline\.com/i.test(
|
|
17957
|
+
location
|
|
17958
|
+
) || location.includes("/oauth");
|
|
17959
|
+
if (isExternalAuth) {
|
|
17960
|
+
let redirectHost = location;
|
|
17961
|
+
try {
|
|
17962
|
+
redirectHost = new URL(location, targetUrl).host;
|
|
17963
|
+
} catch {
|
|
17964
|
+
}
|
|
17965
|
+
checks.push({
|
|
17966
|
+
category: "target",
|
|
17967
|
+
name: "Authentication Boundary",
|
|
17968
|
+
status: "warn",
|
|
17969
|
+
message: `Target immediately redirects to external auth (${redirectHost})`,
|
|
17970
|
+
remedy: "Configure a dev auth bypass or prepare for interactive login handshake before walking."
|
|
17971
|
+
});
|
|
17972
|
+
return checks;
|
|
17973
|
+
}
|
|
17974
|
+
checks.push({
|
|
17975
|
+
category: "target",
|
|
17976
|
+
name: "Target Reachability",
|
|
17977
|
+
status: "ok",
|
|
17978
|
+
message: `HTTP ${res.status} redirect -> ${location}`
|
|
17979
|
+
});
|
|
17980
|
+
return checks;
|
|
17981
|
+
}
|
|
17982
|
+
if (res.status >= 500) {
|
|
17983
|
+
checks.push({
|
|
17984
|
+
category: "target",
|
|
17985
|
+
name: "Target Health",
|
|
17986
|
+
status: "error",
|
|
17987
|
+
message: `Target returned HTTP ${res.status} ${res.statusText || "Server Error"}`,
|
|
17988
|
+
remedy: "Check dev server logs for unhandled exceptions or missing environment variables."
|
|
17989
|
+
});
|
|
17990
|
+
return checks;
|
|
17991
|
+
}
|
|
17992
|
+
checks.push({
|
|
17993
|
+
category: "target",
|
|
17994
|
+
name: "Target Reachability",
|
|
17995
|
+
status: "ok",
|
|
17996
|
+
message: `HTTP ${res.status} OK (${parsedUrl.origin})`
|
|
17997
|
+
});
|
|
17998
|
+
} catch (err) {
|
|
17999
|
+
const message = err.message;
|
|
18000
|
+
checks.push({
|
|
18001
|
+
category: "target",
|
|
18002
|
+
name: "Target Reachability",
|
|
18003
|
+
status: "warn",
|
|
18004
|
+
message: `Cannot connect to ${targetUrl} (${message})`,
|
|
18005
|
+
remedy: "Ensure your dev server is running and listening on the specified port."
|
|
18006
|
+
});
|
|
18007
|
+
}
|
|
18008
|
+
return checks;
|
|
18009
|
+
}
|
|
18010
|
+
async function checkProjectHarnesses(projectRoot) {
|
|
18011
|
+
const checks = [];
|
|
18012
|
+
const pkgPath = join7(projectRoot, "package.json");
|
|
18013
|
+
if (!existsSync10(pkgPath)) return checks;
|
|
18014
|
+
try {
|
|
18015
|
+
const raw = await fs26.readFile(pkgPath, "utf8");
|
|
18016
|
+
const pkg = JSON.parse(raw);
|
|
18017
|
+
const scripts = pkg.scripts || {};
|
|
18018
|
+
const allDeps = {
|
|
18019
|
+
...pkg.dependencies || {},
|
|
18020
|
+
...pkg.devDependencies || {}
|
|
18021
|
+
};
|
|
18022
|
+
const devScripts = ["dev", "start", "dev:mock", "serve"].filter(
|
|
18023
|
+
(s) => s in scripts
|
|
18024
|
+
);
|
|
18025
|
+
if (devScripts.length > 0) {
|
|
18026
|
+
checks.push({
|
|
18027
|
+
category: "target",
|
|
18028
|
+
name: "Dev Server Script",
|
|
18029
|
+
status: "ok",
|
|
18030
|
+
message: `Available scripts: ${devScripts.map((s) => `pnpm ${s}`).join(", ")}`
|
|
18031
|
+
});
|
|
18032
|
+
} else {
|
|
18033
|
+
checks.push({
|
|
18034
|
+
category: "target",
|
|
18035
|
+
name: "Dev Server Script",
|
|
18036
|
+
status: "warn",
|
|
18037
|
+
message: "No standard dev script (dev, start, dev:mock) found in package.json",
|
|
18038
|
+
remedy: "Add a `dev` or `start` script to package.json to boot your app locally."
|
|
18039
|
+
});
|
|
18040
|
+
}
|
|
18041
|
+
if (allDeps.msw || existsSync10(join7(projectRoot, "src/mocks")) || existsSync10(join7(projectRoot, "mocks"))) {
|
|
18042
|
+
checks.push({
|
|
18043
|
+
category: "target",
|
|
18044
|
+
name: "Mock Harness (MSW)",
|
|
18045
|
+
status: "ok",
|
|
18046
|
+
message: "Mock Service Worker detected. API mocks can decouple walks from live backends."
|
|
18047
|
+
});
|
|
18048
|
+
}
|
|
18049
|
+
const seedScript = Object.keys(scripts).find(
|
|
18050
|
+
(s) => /seed|db:seed|fixtures/i.test(s)
|
|
18051
|
+
);
|
|
18052
|
+
if (seedScript) {
|
|
18053
|
+
checks.push({
|
|
18054
|
+
category: "target",
|
|
18055
|
+
name: "Database Seed",
|
|
18056
|
+
status: "ok",
|
|
18057
|
+
message: `Seed script detected (${seedScript}). Preconditions can be reset between runs.`
|
|
18058
|
+
});
|
|
18059
|
+
}
|
|
18060
|
+
} catch {
|
|
18061
|
+
}
|
|
18062
|
+
return checks;
|
|
18063
|
+
}
|
|
18064
|
+
async function checkProjectTestabilityContract(projectRoot) {
|
|
18065
|
+
const checks = [];
|
|
18066
|
+
const testabilityPath = join7(projectRoot, "iamu", "testability.md");
|
|
18067
|
+
if (!existsSync10(testabilityPath)) {
|
|
18068
|
+
checks.push({
|
|
18069
|
+
category: "target",
|
|
18070
|
+
name: "Testability Contract (testability.md)",
|
|
18071
|
+
status: "warn",
|
|
18072
|
+
message: "iamu/testability.md not found",
|
|
18073
|
+
remedy: "Run `/iamu:init` to audit your codebase and record testable routes, auth strategies, and mock harnesses."
|
|
18074
|
+
});
|
|
18075
|
+
return { checks };
|
|
18076
|
+
}
|
|
18077
|
+
try {
|
|
18078
|
+
const raw = await fs26.readFile(testabilityPath, "utf8");
|
|
18079
|
+
const { frontmatter } = parseFrontmatter(raw);
|
|
18080
|
+
const targetUrl = typeof frontmatter.target_url === "string" ? frontmatter.target_url : void 0;
|
|
18081
|
+
const details = [];
|
|
18082
|
+
if (targetUrl) details.push(`Target: ${targetUrl}`);
|
|
18083
|
+
if (frontmatter.dev_command) {
|
|
18084
|
+
details.push(`Command: ${frontmatter.dev_command}`);
|
|
18085
|
+
}
|
|
18086
|
+
if (frontmatter.auth_strategy) {
|
|
18087
|
+
details.push(`Auth: ${frontmatter.auth_strategy}`);
|
|
18088
|
+
}
|
|
18089
|
+
checks.push({
|
|
18090
|
+
category: "target",
|
|
18091
|
+
name: "Testability Contract (testability.md)",
|
|
18092
|
+
status: "ok",
|
|
18093
|
+
message: `Configured (${details.join(" \xB7 ") || "present"})`
|
|
18094
|
+
});
|
|
18095
|
+
return { checks, targetUrl };
|
|
18096
|
+
} catch (err) {
|
|
18097
|
+
checks.push({
|
|
18098
|
+
category: "target",
|
|
18099
|
+
name: "Testability Contract (testability.md)",
|
|
18100
|
+
status: "warn",
|
|
18101
|
+
message: `Unreadable testability contract: ${err.message}`,
|
|
18102
|
+
remedy: "Repair iamu/testability.md frontmatter."
|
|
18103
|
+
});
|
|
18104
|
+
return { checks };
|
|
18105
|
+
}
|
|
18106
|
+
}
|
|
18107
|
+
async function runDoctorChecks(projectRoot, opts) {
|
|
17321
18108
|
const checks = [
|
|
17322
18109
|
...checkRuntimeChecks(),
|
|
17323
18110
|
...await checkBrowserChecks()
|
|
17324
18111
|
];
|
|
18112
|
+
let effectiveTarget = opts?.target;
|
|
17325
18113
|
if (!projectRoot || !existsSync10(join7(projectRoot, "iamu"))) {
|
|
17326
18114
|
checks.push({
|
|
17327
18115
|
category: "project",
|
|
@@ -17341,6 +18129,15 @@ async function runDoctorChecks(projectRoot) {
|
|
|
17341
18129
|
checks.push(...await checkProjectConfiguration(projectRoot));
|
|
17342
18130
|
checks.push(...await checkProjectContextAndEntities(projectRoot));
|
|
17343
18131
|
checks.push(...await checkAgentIntegrations(projectRoot));
|
|
18132
|
+
checks.push(...await checkProjectHarnesses(projectRoot));
|
|
18133
|
+
const testability = await checkProjectTestabilityContract(projectRoot);
|
|
18134
|
+
checks.push(...testability.checks);
|
|
18135
|
+
if (!effectiveTarget && testability.targetUrl) {
|
|
18136
|
+
effectiveTarget = testability.targetUrl;
|
|
18137
|
+
}
|
|
18138
|
+
}
|
|
18139
|
+
if (effectiveTarget) {
|
|
18140
|
+
checks.push(...await checkTargetHealth(effectiveTarget));
|
|
17344
18141
|
}
|
|
17345
18142
|
const okCount = checks.filter((c) => c.status === "ok").length;
|
|
17346
18143
|
const warnCount = checks.filter((c) => c.status === "warn").length;
|
|
@@ -17357,7 +18154,7 @@ async function runDoctorChecks(projectRoot) {
|
|
|
17357
18154
|
}
|
|
17358
18155
|
async function doctor(opts) {
|
|
17359
18156
|
const projectRoot = findProjectRoot(process.cwd()) ?? void 0;
|
|
17360
|
-
const result = await runDoctorChecks(projectRoot);
|
|
18157
|
+
const result = await runDoctorChecks(projectRoot, { target: opts?.target });
|
|
17361
18158
|
if (opts?.json) {
|
|
17362
18159
|
console.log(JSON.stringify(result, null, 2));
|
|
17363
18160
|
if (!result.ok) process.exitCode = 1;
|
|
@@ -17371,7 +18168,8 @@ async function doctor(opts) {
|
|
|
17371
18168
|
{ key: "browser", title: "Browser Automation" },
|
|
17372
18169
|
{ key: "project", title: "Project Configuration" },
|
|
17373
18170
|
{ key: "agent", title: "Agent Integrations" },
|
|
17374
|
-
{ key: "cloud", title: "Cloud & Authentication" }
|
|
18171
|
+
{ key: "cloud", title: "Cloud & Authentication" },
|
|
18172
|
+
{ key: "target", title: "Target & Testability" }
|
|
17375
18173
|
];
|
|
17376
18174
|
for (const cat of categories) {
|
|
17377
18175
|
const catChecks = result.checks.filter((c) => c.category === cat.key);
|
|
@@ -17849,13 +18647,13 @@ __export(validate_exports, {
|
|
|
17849
18647
|
validate: () => validate
|
|
17850
18648
|
});
|
|
17851
18649
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
17852
|
-
import { promises as
|
|
17853
|
-
import
|
|
18650
|
+
import { promises as fs27 } from "fs";
|
|
18651
|
+
import path24 from "path";
|
|
17854
18652
|
import chalk23 from "chalk";
|
|
17855
18653
|
function inferTypeFromPath(filePath, projectRoot) {
|
|
17856
|
-
const rel =
|
|
17857
|
-
if (rel.startsWith("..") ||
|
|
17858
|
-
const segments = rel.split(
|
|
18654
|
+
const rel = path24.relative(path24.join(projectRoot, "iamu"), filePath);
|
|
18655
|
+
if (rel.startsWith("..") || path24.isAbsolute(rel)) return null;
|
|
18656
|
+
const segments = rel.split(path24.sep);
|
|
17859
18657
|
if (segments.length < 2) return null;
|
|
17860
18658
|
return TYPE_BY_ALIAS.get(segments[0]) ?? null;
|
|
17861
18659
|
}
|
|
@@ -17885,7 +18683,7 @@ function validateFile(filePath, raw, projectRoot) {
|
|
|
17885
18683
|
);
|
|
17886
18684
|
return result;
|
|
17887
18685
|
}
|
|
17888
|
-
if (
|
|
18686
|
+
if (path24.basename(filePath) === "context.yml") {
|
|
17889
18687
|
applyZodValidation(contextFrontmatterSchema, frontmatter, result);
|
|
17890
18688
|
const warnings = [];
|
|
17891
18689
|
const hasNotes = Boolean(
|
|
@@ -17945,17 +18743,17 @@ async function buildRepairLookups(projectRoot) {
|
|
|
17945
18743
|
}
|
|
17946
18744
|
}
|
|
17947
18745
|
const runScenario = /* @__PURE__ */ new Map();
|
|
17948
|
-
const runsDir2 =
|
|
18746
|
+
const runsDir2 = path24.join(projectRoot, "iamu", ".runs");
|
|
17949
18747
|
let runIds = [];
|
|
17950
18748
|
try {
|
|
17951
|
-
runIds = await
|
|
18749
|
+
runIds = await fs27.readdir(runsDir2);
|
|
17952
18750
|
} catch {
|
|
17953
18751
|
}
|
|
17954
18752
|
for (const runId of runIds) {
|
|
17955
18753
|
try {
|
|
17956
18754
|
const manifest = JSON.parse(
|
|
17957
|
-
await
|
|
17958
|
-
|
|
18755
|
+
await fs27.readFile(
|
|
18756
|
+
path24.join(runsDir2, runId, "walkthrough.json"),
|
|
17959
18757
|
"utf8"
|
|
17960
18758
|
)
|
|
17961
18759
|
);
|
|
@@ -17977,7 +18775,7 @@ async function fixFindings(projectRoot, results, dryRun) {
|
|
|
17977
18775
|
let raw;
|
|
17978
18776
|
let parsed;
|
|
17979
18777
|
try {
|
|
17980
|
-
raw = await
|
|
18778
|
+
raw = await fs27.readFile(result.path, "utf8");
|
|
17981
18779
|
parsed = parseFrontmatter(raw);
|
|
17982
18780
|
} catch {
|
|
17983
18781
|
continue;
|
|
@@ -17986,7 +18784,7 @@ async function fixFindings(projectRoot, results, dryRun) {
|
|
|
17986
18784
|
parsed.frontmatter,
|
|
17987
18785
|
{
|
|
17988
18786
|
...lookups,
|
|
17989
|
-
fileSlug:
|
|
18787
|
+
fileSlug: path24.basename(result.path, ".md"),
|
|
17990
18788
|
newId: randomUUID8,
|
|
17991
18789
|
body: parsed.body
|
|
17992
18790
|
}
|
|
@@ -18021,9 +18819,9 @@ async function validate(filePath, opts) {
|
|
|
18021
18819
|
if (process.exitCode === 1) return;
|
|
18022
18820
|
const results = [];
|
|
18023
18821
|
if (filePath) {
|
|
18024
|
-
const abs =
|
|
18822
|
+
const abs = path24.isAbsolute(filePath) ? filePath : path24.resolve(process.cwd(), filePath);
|
|
18025
18823
|
try {
|
|
18026
|
-
const raw = await
|
|
18824
|
+
const raw = await fs27.readFile(abs, "utf8");
|
|
18027
18825
|
results.push(validateFile(abs, raw, projectRoot));
|
|
18028
18826
|
} catch (err) {
|
|
18029
18827
|
commandError(
|
|
@@ -18033,24 +18831,24 @@ async function validate(filePath, opts) {
|
|
|
18033
18831
|
return;
|
|
18034
18832
|
}
|
|
18035
18833
|
} else if (opts.run) {
|
|
18036
|
-
const findingsDir =
|
|
18834
|
+
const findingsDir = path24.join(projectRoot, "iamu", ENTITY_DIRS.finding);
|
|
18037
18835
|
let names = [];
|
|
18038
18836
|
try {
|
|
18039
|
-
names = await
|
|
18837
|
+
names = await fs27.readdir(findingsDir);
|
|
18040
18838
|
} catch {
|
|
18041
18839
|
}
|
|
18042
18840
|
for (const name of names) {
|
|
18043
18841
|
if (!name.endsWith(".md")) continue;
|
|
18044
|
-
const abs =
|
|
18842
|
+
const abs = path24.join(findingsDir, name);
|
|
18045
18843
|
try {
|
|
18046
|
-
const raw = await
|
|
18844
|
+
const raw = await fs27.readFile(abs, "utf8");
|
|
18047
18845
|
const { frontmatter } = parseFrontmatter(raw);
|
|
18048
18846
|
if (frontmatter.run === opts.run) {
|
|
18049
18847
|
results.push(validateFile(abs, raw, projectRoot));
|
|
18050
18848
|
}
|
|
18051
18849
|
} catch {
|
|
18052
18850
|
results.push({
|
|
18053
|
-
path:
|
|
18851
|
+
path: path24.relative(projectRoot, abs),
|
|
18054
18852
|
valid: false,
|
|
18055
18853
|
errors: ["Cannot read file or parse frontmatter"]
|
|
18056
18854
|
});
|
|
@@ -18083,31 +18881,31 @@ async function validate(filePath, opts) {
|
|
|
18083
18881
|
} else {
|
|
18084
18882
|
const typesToCheck = typeFilter ? ENTITY_TYPES.filter((t) => typeFilter.has(t)) : ENTITY_TYPES;
|
|
18085
18883
|
for (const type2 of typesToCheck) {
|
|
18086
|
-
const iamuTypeDir =
|
|
18884
|
+
const iamuTypeDir = path24.join(projectRoot, "iamu", ENTITY_DIRS[type2]);
|
|
18087
18885
|
let names;
|
|
18088
18886
|
try {
|
|
18089
|
-
names = await
|
|
18887
|
+
names = await fs27.readdir(iamuTypeDir);
|
|
18090
18888
|
} catch {
|
|
18091
18889
|
continue;
|
|
18092
18890
|
}
|
|
18093
18891
|
for (const name of names) {
|
|
18094
18892
|
if (!name.endsWith(".md")) continue;
|
|
18095
|
-
const abs =
|
|
18893
|
+
const abs = path24.join(iamuTypeDir, name);
|
|
18096
18894
|
try {
|
|
18097
|
-
const raw = await
|
|
18895
|
+
const raw = await fs27.readFile(abs, "utf8");
|
|
18098
18896
|
results.push(validateFile(abs, raw, projectRoot));
|
|
18099
18897
|
} catch {
|
|
18100
18898
|
results.push({
|
|
18101
|
-
path:
|
|
18899
|
+
path: path24.relative(projectRoot, abs),
|
|
18102
18900
|
valid: false,
|
|
18103
18901
|
errors: ["Cannot read file"]
|
|
18104
18902
|
});
|
|
18105
18903
|
}
|
|
18106
18904
|
}
|
|
18107
18905
|
}
|
|
18108
|
-
const ctxPath =
|
|
18906
|
+
const ctxPath = path24.join(projectRoot, "iamu", "context.yml");
|
|
18109
18907
|
try {
|
|
18110
|
-
const raw = await
|
|
18908
|
+
const raw = await fs27.readFile(ctxPath, "utf8");
|
|
18111
18909
|
results.push(validateFile(ctxPath, raw, projectRoot));
|
|
18112
18910
|
} catch {
|
|
18113
18911
|
}
|
|
@@ -18126,19 +18924,19 @@ async function validate(filePath, opts) {
|
|
|
18126
18924
|
valid,
|
|
18127
18925
|
errors: results.filter((r) => !r.valid).flatMap(
|
|
18128
18926
|
(r) => r.errors.map((e) => ({
|
|
18129
|
-
path:
|
|
18927
|
+
path: path24.relative(projectRoot, r.path),
|
|
18130
18928
|
message: e
|
|
18131
18929
|
}))
|
|
18132
18930
|
),
|
|
18133
18931
|
warnings: results.filter((r) => r.warnings && r.warnings.length > 0).flatMap(
|
|
18134
18932
|
(r) => (r.warnings ?? []).map((w) => ({
|
|
18135
|
-
path:
|
|
18933
|
+
path: path24.relative(projectRoot, r.path),
|
|
18136
18934
|
message: w
|
|
18137
18935
|
}))
|
|
18138
18936
|
),
|
|
18139
18937
|
...opts.fix ? {
|
|
18140
18938
|
repaired: repaired.map((r) => ({
|
|
18141
|
-
path:
|
|
18939
|
+
path: path24.relative(projectRoot, r.path),
|
|
18142
18940
|
changes: r.repairs,
|
|
18143
18941
|
valid: r.valid
|
|
18144
18942
|
}))
|
|
@@ -18160,7 +18958,7 @@ async function validate(filePath, opts) {
|
|
|
18160
18958
|
}
|
|
18161
18959
|
let hasErrors = false;
|
|
18162
18960
|
for (const r of results) {
|
|
18163
|
-
const relPath =
|
|
18961
|
+
const relPath = path24.relative(projectRoot, r.path);
|
|
18164
18962
|
if (r.valid) {
|
|
18165
18963
|
if (r.warnings && r.warnings.length > 0) {
|
|
18166
18964
|
console.log(` ${symbols.warning} ` + chalk23.yellow(relPath));
|
|
@@ -18351,10 +19149,26 @@ function defaultFrontmatter(type2, id, slug, name, opts) {
|
|
|
18351
19149
|
...base,
|
|
18352
19150
|
name,
|
|
18353
19151
|
role: opts.role ?? "TODO",
|
|
18354
|
-
...opts.disposition ? { disposition: opts.disposition } : {}
|
|
19152
|
+
...opts.disposition ? { disposition: opts.disposition } : {},
|
|
19153
|
+
...opts.access ? { access: opts.access } : {}
|
|
18355
19154
|
};
|
|
18356
|
-
case "outline":
|
|
18357
|
-
|
|
19155
|
+
case "outline": {
|
|
19156
|
+
const tests = Array.isArray(opts.humanTest) ? opts.humanTest : opts.humanTest ? [opts.humanTest] : [];
|
|
19157
|
+
return {
|
|
19158
|
+
...base,
|
|
19159
|
+
title: name,
|
|
19160
|
+
...description,
|
|
19161
|
+
...opts.capability ? { primary_capability: opts.capability } : {},
|
|
19162
|
+
...opts.tension ? { tension: opts.tension } : {},
|
|
19163
|
+
...opts.narrative ? { narrative: opts.narrative } : {},
|
|
19164
|
+
...tests.length > 0 ? {
|
|
19165
|
+
human_tests: tests.map((t, idx) => ({
|
|
19166
|
+
id: `test-${idx + 1}`,
|
|
19167
|
+
question: t
|
|
19168
|
+
}))
|
|
19169
|
+
} : {}
|
|
19170
|
+
};
|
|
19171
|
+
}
|
|
18358
19172
|
case "capability":
|
|
18359
19173
|
return { ...base, title: name, ...description };
|
|
18360
19174
|
case "source":
|
|
@@ -18364,13 +19178,21 @@ function defaultFrontmatter(type2, id, slug, name, opts) {
|
|
|
18364
19178
|
url: opts.url,
|
|
18365
19179
|
authority_level: opts.authority ?? "informational"
|
|
18366
19180
|
};
|
|
18367
|
-
case "scenario":
|
|
19181
|
+
case "scenario": {
|
|
19182
|
+
const stateObj = opts.stateDescription || opts.stateApproximation ? {
|
|
19183
|
+
state: {
|
|
19184
|
+
...opts.stateDescription ? { description: opts.stateDescription } : {},
|
|
19185
|
+
...opts.stateApproximation ? { approximation: opts.stateApproximation } : {}
|
|
19186
|
+
}
|
|
19187
|
+
} : {};
|
|
18368
19188
|
return {
|
|
18369
19189
|
...base,
|
|
18370
19190
|
outline: opts.outline ?? null,
|
|
18371
19191
|
persona: opts.persona ?? null,
|
|
18372
|
-
...opts.entry ? { entry_path: opts.entry } : {}
|
|
19192
|
+
...opts.entry ? { entry_path: opts.entry } : {},
|
|
19193
|
+
...stateObj
|
|
18373
19194
|
};
|
|
19195
|
+
}
|
|
18374
19196
|
default:
|
|
18375
19197
|
return base;
|
|
18376
19198
|
}
|
|
@@ -18410,7 +19232,14 @@ async function newEntity(type2, opts) {
|
|
|
18410
19232
|
return;
|
|
18411
19233
|
}
|
|
18412
19234
|
}
|
|
18413
|
-
|
|
19235
|
+
let slug;
|
|
19236
|
+
if (opts.slug) {
|
|
19237
|
+
slug = slugSchema.safeParse(opts.slug).success ? opts.slug : slugify(opts.slug);
|
|
19238
|
+
} else if (slugSchema.safeParse(opts.name).success) {
|
|
19239
|
+
slug = opts.name;
|
|
19240
|
+
} else {
|
|
19241
|
+
slug = slugify(opts.name);
|
|
19242
|
+
}
|
|
18414
19243
|
if (await entityExists(projectRoot, entityType, slug)) {
|
|
18415
19244
|
commandError(
|
|
18416
19245
|
`Entity already exists: iamu/${ENTITY_DIRS[entityType]}/${slug}.md`,
|
|
@@ -18420,7 +19249,7 @@ async function newEntity(type2, opts) {
|
|
|
18420
19249
|
}
|
|
18421
19250
|
const id = randomUUID9();
|
|
18422
19251
|
const frontmatter = defaultFrontmatter(entityType, id, slug, opts.name, opts);
|
|
18423
|
-
const body = "";
|
|
19252
|
+
const body = opts.body ?? (entityType === "outline" ? "" : opts.narrative) ?? "";
|
|
18424
19253
|
try {
|
|
18425
19254
|
const record = await writeEntity(
|
|
18426
19255
|
projectRoot,
|
|
@@ -18465,6 +19294,797 @@ var init_new_entity = __esm({
|
|
|
18465
19294
|
}
|
|
18466
19295
|
});
|
|
18467
19296
|
|
|
19297
|
+
// src/commands/explore-crawl.ts
|
|
19298
|
+
function candidateSig(c) {
|
|
19299
|
+
return `${c.role}
|
|
19300
|
+
${c.name}
|
|
19301
|
+
${c.occurrence}`;
|
|
19302
|
+
}
|
|
19303
|
+
function originOf(url) {
|
|
19304
|
+
try {
|
|
19305
|
+
return new URL(url).origin;
|
|
19306
|
+
} catch {
|
|
19307
|
+
return null;
|
|
19308
|
+
}
|
|
19309
|
+
}
|
|
19310
|
+
function skipReason(c, origin, deny) {
|
|
19311
|
+
if (deny.some((re) => re.test(c.name) || c.href && re.test(c.href))) {
|
|
19312
|
+
return "denied";
|
|
19313
|
+
}
|
|
19314
|
+
if (c.submit) return "submit";
|
|
19315
|
+
if (UNSAFE_NAME.test(c.name)) return "unsafe_name";
|
|
19316
|
+
if (c.href) {
|
|
19317
|
+
let u = null;
|
|
19318
|
+
try {
|
|
19319
|
+
u = new URL(c.href);
|
|
19320
|
+
} catch {
|
|
19321
|
+
}
|
|
19322
|
+
if (u && u.protocol !== "javascript:") {
|
|
19323
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") return "external";
|
|
19324
|
+
if (u.origin !== origin) return "external";
|
|
19325
|
+
if (UNSAFE_PATH.test(u.pathname)) return "unsafe_name";
|
|
19326
|
+
}
|
|
19327
|
+
if (c.download) return "download";
|
|
19328
|
+
}
|
|
19329
|
+
return null;
|
|
19330
|
+
}
|
|
19331
|
+
async function settle(page, settleMs) {
|
|
19332
|
+
await page.waitForLoadState("domcontentloaded", { timeout: NAV_TIMEOUT_MS }).catch(() => {
|
|
19333
|
+
});
|
|
19334
|
+
await page.waitForLoadState("networkidle", { timeout: settleMs }).catch(() => {
|
|
19335
|
+
});
|
|
19336
|
+
}
|
|
19337
|
+
async function readPage(page) {
|
|
19338
|
+
return page.evaluate(READ_PAGE);
|
|
19339
|
+
}
|
|
19340
|
+
async function markDocument(page) {
|
|
19341
|
+
await page.evaluate("window.__iamuDoc = true").catch(() => {
|
|
19342
|
+
});
|
|
19343
|
+
}
|
|
19344
|
+
async function isSameDocument(page) {
|
|
19345
|
+
return page.evaluate("window.__iamuDoc === true").catch(() => false);
|
|
19346
|
+
}
|
|
19347
|
+
async function clickCandidate(page, want) {
|
|
19348
|
+
const state = await readPage(page);
|
|
19349
|
+
const c = state.candidates.find(
|
|
19350
|
+
(x) => candidateSig(x) === candidateSig(want)
|
|
19351
|
+
);
|
|
19352
|
+
if (!c) return false;
|
|
19353
|
+
await page.locator(`[data-iamu-x="${c.index}"]`).click({ timeout: CLICK_TIMEOUT_MS });
|
|
19354
|
+
return true;
|
|
19355
|
+
}
|
|
19356
|
+
async function crawl(page, opts) {
|
|
19357
|
+
const origin = originOf(opts.startUrl);
|
|
19358
|
+
if (!origin) throw new Error(`Not a URL: ${opts.startUrl}`);
|
|
19359
|
+
let blocked = [];
|
|
19360
|
+
await page.route("**/*", (route) => {
|
|
19361
|
+
const req = route.request();
|
|
19362
|
+
if (SAFE_METHODS.has(req.method())) return route.continue();
|
|
19363
|
+
let where = req.url();
|
|
19364
|
+
try {
|
|
19365
|
+
const u = new URL(where);
|
|
19366
|
+
where = u.origin === origin ? u.pathname : `${u.origin}${u.pathname}`;
|
|
19367
|
+
} catch {
|
|
19368
|
+
}
|
|
19369
|
+
blocked.push(`${req.method()} ${where}`);
|
|
19370
|
+
return route.abort("blockedbyclient");
|
|
19371
|
+
});
|
|
19372
|
+
const onDialog = (d) => {
|
|
19373
|
+
d.dismiss().catch(() => {
|
|
19374
|
+
});
|
|
19375
|
+
};
|
|
19376
|
+
const onPopup = (p) => {
|
|
19377
|
+
if (p !== page) p.close().catch(() => {
|
|
19378
|
+
});
|
|
19379
|
+
};
|
|
19380
|
+
page.on("dialog", onDialog);
|
|
19381
|
+
page.context().on("page", onPopup);
|
|
19382
|
+
try {
|
|
19383
|
+
return await crawlFrom(page, opts, origin, () => {
|
|
19384
|
+
const out = blocked;
|
|
19385
|
+
blocked = [];
|
|
19386
|
+
return out;
|
|
19387
|
+
});
|
|
19388
|
+
} finally {
|
|
19389
|
+
page.off("dialog", onDialog);
|
|
19390
|
+
page.context().off("page", onPopup);
|
|
19391
|
+
await page.unroute("**/*").catch(() => {
|
|
19392
|
+
});
|
|
19393
|
+
}
|
|
19394
|
+
}
|
|
19395
|
+
async function crawlFrom(page, opts, origin, takeBlocked) {
|
|
19396
|
+
const deny = opts.deny ?? [];
|
|
19397
|
+
const settleMs = opts.settleMs ?? 1500;
|
|
19398
|
+
const { budget } = opts;
|
|
19399
|
+
const stats = {
|
|
19400
|
+
surfaces: 0,
|
|
19401
|
+
actions_tried: 0,
|
|
19402
|
+
skipped: 0,
|
|
19403
|
+
blocked_requests: 0,
|
|
19404
|
+
errors: 0,
|
|
19405
|
+
truncated: false
|
|
19406
|
+
};
|
|
19407
|
+
const surfaces = [];
|
|
19408
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
19409
|
+
const byPlaceView = /* @__PURE__ */ new Map();
|
|
19410
|
+
const signatureOf = (s) => exploreSignature(s.headings, s.title);
|
|
19411
|
+
function addSurface(state, place, view, path30, depth) {
|
|
19412
|
+
const key = exploreSurfaceKey(place, view);
|
|
19413
|
+
const surface = {
|
|
19414
|
+
key,
|
|
19415
|
+
url: state.url,
|
|
19416
|
+
place,
|
|
19417
|
+
view,
|
|
19418
|
+
signature: signatureOf(state),
|
|
19419
|
+
label: state.headings[0] ?? (state.title || place),
|
|
19420
|
+
headings: state.headings,
|
|
19421
|
+
fields: state.fields,
|
|
19422
|
+
depth,
|
|
19423
|
+
path: path30,
|
|
19424
|
+
actions: []
|
|
19425
|
+
};
|
|
19426
|
+
surfaces.push(surface);
|
|
19427
|
+
byKey.set(key, surface);
|
|
19428
|
+
byPlaceView.set(`${place}
|
|
19429
|
+
${surface.signature}`, key);
|
|
19430
|
+
stats.surfaces++;
|
|
19431
|
+
opts.log?.(` ${" ".repeat(depth)}${key} ${surface.label}`);
|
|
19432
|
+
return surface;
|
|
19433
|
+
}
|
|
19434
|
+
async function goto(url) {
|
|
19435
|
+
await page.goto(url, {
|
|
19436
|
+
waitUntil: "domcontentloaded",
|
|
19437
|
+
timeout: NAV_TIMEOUT_MS
|
|
19438
|
+
});
|
|
19439
|
+
await settle(page, settleMs);
|
|
19440
|
+
}
|
|
19441
|
+
async function replay(path30) {
|
|
19442
|
+
if (!("goto" in (path30[0] ?? {}))) await goto(opts.startUrl);
|
|
19443
|
+
for (const step of path30) {
|
|
19444
|
+
if ("goto" in step) {
|
|
19445
|
+
await goto(step.goto);
|
|
19446
|
+
} else {
|
|
19447
|
+
if (!await clickCandidate(page, step.click)) return false;
|
|
19448
|
+
await settle(page, settleMs);
|
|
19449
|
+
}
|
|
19450
|
+
}
|
|
19451
|
+
return true;
|
|
19452
|
+
}
|
|
19453
|
+
await goto(opts.startUrl);
|
|
19454
|
+
if (await page.evaluate("Boolean(navigator.serviceWorker?.controller)").catch(() => false)) {
|
|
19455
|
+
throw new Error(
|
|
19456
|
+
"A service worker controls this page, and the write guard cannot see its requests. Explore without --attach (service workers are blocked there)."
|
|
19457
|
+
);
|
|
19458
|
+
}
|
|
19459
|
+
const first = await readPage(page);
|
|
19460
|
+
const firstPlace = explorePlaceKey(first.url);
|
|
19461
|
+
if (!firstPlace || originOf(first.url) !== origin) {
|
|
19462
|
+
throw new Error(`The start URL led off the product, to ${first.url}`);
|
|
19463
|
+
}
|
|
19464
|
+
addSurface(first, firstPlace, null, [], 0);
|
|
19465
|
+
for (let i = 0; i < surfaces.length; i++) {
|
|
19466
|
+
const surface = surfaces[i];
|
|
19467
|
+
if (!await replay(surface.path)) {
|
|
19468
|
+
stats.errors++;
|
|
19469
|
+
surface.actions.push({
|
|
19470
|
+
role: "",
|
|
19471
|
+
name: "",
|
|
19472
|
+
occurrence: 0,
|
|
19473
|
+
outcome: { kind: "error", message: LOST }
|
|
19474
|
+
});
|
|
19475
|
+
continue;
|
|
19476
|
+
}
|
|
19477
|
+
const home = await readPage(page);
|
|
19478
|
+
const seen = new Set(home.candidates.map(candidateSig));
|
|
19479
|
+
const work = home.candidates.map((c) => ({ c, via: [] }));
|
|
19480
|
+
const homeList = home.candidates.map(candidateSig).join("\n");
|
|
19481
|
+
let pristine = true;
|
|
19482
|
+
for (let w = 0; w < work.length; w++) {
|
|
19483
|
+
const { c, via } = work[w];
|
|
19484
|
+
const action = {
|
|
19485
|
+
role: c.role,
|
|
19486
|
+
name: c.name,
|
|
19487
|
+
occurrence: c.occurrence,
|
|
19488
|
+
...c.href ? { href: c.href } : {},
|
|
19489
|
+
...via.length > 0 ? { via } : {},
|
|
19490
|
+
outcome: { kind: "same" }
|
|
19491
|
+
};
|
|
19492
|
+
surface.actions.push(action);
|
|
19493
|
+
const reason = skipReason(c, origin, deny);
|
|
19494
|
+
if (reason) {
|
|
19495
|
+
action.outcome = { kind: "skipped", reason };
|
|
19496
|
+
stats.skipped++;
|
|
19497
|
+
continue;
|
|
19498
|
+
}
|
|
19499
|
+
if (surface.depth >= budget.max_depth || stats.actions_tried >= budget.max_actions) {
|
|
19500
|
+
action.outcome = { kind: "skipped", reason: "budget" };
|
|
19501
|
+
stats.skipped++;
|
|
19502
|
+
stats.truncated = true;
|
|
19503
|
+
continue;
|
|
19504
|
+
}
|
|
19505
|
+
stats.actions_tried++;
|
|
19506
|
+
takeBlocked();
|
|
19507
|
+
const click = {
|
|
19508
|
+
click: { role: c.role, name: c.name, occurrence: c.occurrence }
|
|
19509
|
+
};
|
|
19510
|
+
let outcome;
|
|
19511
|
+
let revealed = 0;
|
|
19512
|
+
let left = false;
|
|
19513
|
+
let lost = false;
|
|
19514
|
+
try {
|
|
19515
|
+
if (!pristine || via.length > 0) {
|
|
19516
|
+
if (!await replay([...surface.path, ...via])) {
|
|
19517
|
+
throw new Error(LOST);
|
|
19518
|
+
}
|
|
19519
|
+
}
|
|
19520
|
+
pristine = false;
|
|
19521
|
+
const before = await readPage(page);
|
|
19522
|
+
await markDocument(page);
|
|
19523
|
+
if (c.newTab && c.href) {
|
|
19524
|
+
await goto(c.href);
|
|
19525
|
+
} else {
|
|
19526
|
+
if (!await clickCandidate(page, c)) {
|
|
19527
|
+
throw new Error("control is gone");
|
|
19528
|
+
}
|
|
19529
|
+
await settle(page, settleMs);
|
|
19530
|
+
}
|
|
19531
|
+
const after = await readPage(page);
|
|
19532
|
+
if (originOf(after.url) !== origin) {
|
|
19533
|
+
outcome = { kind: "external", url: after.url };
|
|
19534
|
+
left = true;
|
|
19535
|
+
} else {
|
|
19536
|
+
const place = explorePlaceKey(after.url) ?? before.url;
|
|
19537
|
+
const sig = signatureOf(after);
|
|
19538
|
+
const sameDoc = !(c.newTab && c.href) && await isSameDocument(page);
|
|
19539
|
+
const beforePlace = explorePlaceKey(before.url);
|
|
19540
|
+
let key = byPlaceView.get(`${place}
|
|
19541
|
+
${sig}`);
|
|
19542
|
+
let view = null;
|
|
19543
|
+
if (!key) {
|
|
19544
|
+
const inPage = sameDoc && place === beforePlace;
|
|
19545
|
+
view = inPage ? sig : null;
|
|
19546
|
+
key = exploreSurfaceKey(place, view);
|
|
19547
|
+
if (byKey.has(key)) byPlaceView.set(`${place}
|
|
19548
|
+
${sig}`, key);
|
|
19549
|
+
}
|
|
19550
|
+
if (key === surface.key) {
|
|
19551
|
+
outcome = { kind: "same" };
|
|
19552
|
+
const list2 = after.candidates.map(candidateSig).join("\n");
|
|
19553
|
+
for (const n of after.candidates) {
|
|
19554
|
+
const s = candidateSig(n);
|
|
19555
|
+
if (seen.has(s)) continue;
|
|
19556
|
+
seen.add(s);
|
|
19557
|
+
work.push({ c: n, via: [...via, click] });
|
|
19558
|
+
revealed++;
|
|
19559
|
+
}
|
|
19560
|
+
if (list2 === homeList) pristine = true;
|
|
19561
|
+
} else {
|
|
19562
|
+
outcome = { kind: "surface", surface: key };
|
|
19563
|
+
if (!byKey.has(key)) {
|
|
19564
|
+
if (stats.surfaces >= budget.max_surfaces) {
|
|
19565
|
+
stats.truncated = true;
|
|
19566
|
+
} else {
|
|
19567
|
+
const path30 = !sameDoc && view === null ? [{ goto: after.url }] : [...surface.path, ...via, click];
|
|
19568
|
+
addSurface(after, place, view, path30, surface.depth + 1);
|
|
19569
|
+
}
|
|
19570
|
+
}
|
|
19571
|
+
}
|
|
19572
|
+
}
|
|
19573
|
+
} catch (err) {
|
|
19574
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
19575
|
+
outcome = { kind: "error", message: msg.split("\n")[0].slice(0, 200) };
|
|
19576
|
+
stats.errors++;
|
|
19577
|
+
lost = msg === LOST;
|
|
19578
|
+
}
|
|
19579
|
+
action.outcome = outcome;
|
|
19580
|
+
if (revealed > 0) action.revealed = revealed;
|
|
19581
|
+
const writes = takeBlocked();
|
|
19582
|
+
if (writes.length > 0) {
|
|
19583
|
+
action.blocked_requests = [...new Set(writes)];
|
|
19584
|
+
stats.blocked_requests += writes.length;
|
|
19585
|
+
}
|
|
19586
|
+
if (left) pristine = false;
|
|
19587
|
+
if (lost) break;
|
|
19588
|
+
}
|
|
19589
|
+
}
|
|
19590
|
+
return { surfaces, stats };
|
|
19591
|
+
}
|
|
19592
|
+
var UNSAFE_NAME, UNSAFE_PATH, SAFE_METHODS, CLICK_TIMEOUT_MS, LOST, NAV_TIMEOUT_MS, READ_PAGE;
|
|
19593
|
+
var init_explore_crawl = __esm({
|
|
19594
|
+
"src/commands/explore-crawl.ts"() {
|
|
19595
|
+
"use strict";
|
|
19596
|
+
init_dist2();
|
|
19597
|
+
UNSAFE_NAME = new RegExp(
|
|
19598
|
+
`(?<![\\p{L}\\p{N}])(${[
|
|
19599
|
+
// English
|
|
19600
|
+
"delete",
|
|
19601
|
+
"remove",
|
|
19602
|
+
"destroy",
|
|
19603
|
+
"erase",
|
|
19604
|
+
"pay",
|
|
19605
|
+
"pay now",
|
|
19606
|
+
"purchase",
|
|
19607
|
+
"buy",
|
|
19608
|
+
"checkout",
|
|
19609
|
+
"place order",
|
|
19610
|
+
"order now",
|
|
19611
|
+
"send",
|
|
19612
|
+
"submit",
|
|
19613
|
+
"save",
|
|
19614
|
+
"confirm",
|
|
19615
|
+
"approve",
|
|
19616
|
+
"reject",
|
|
19617
|
+
"decline",
|
|
19618
|
+
"publish",
|
|
19619
|
+
"archive",
|
|
19620
|
+
"unsubscribe",
|
|
19621
|
+
"log ?out",
|
|
19622
|
+
"sign ?out",
|
|
19623
|
+
"reset",
|
|
19624
|
+
"transfer",
|
|
19625
|
+
"invite",
|
|
19626
|
+
"upload",
|
|
19627
|
+
"import",
|
|
19628
|
+
"deactivate",
|
|
19629
|
+
"close account",
|
|
19630
|
+
// Swedish
|
|
19631
|
+
"ta bort",
|
|
19632
|
+
"radera",
|
|
19633
|
+
"betala",
|
|
19634
|
+
"k\xF6p",
|
|
19635
|
+
"best\xE4ll",
|
|
19636
|
+
"skicka",
|
|
19637
|
+
"skicka in",
|
|
19638
|
+
"spara",
|
|
19639
|
+
"bekr\xE4fta",
|
|
19640
|
+
"godk\xE4nn",
|
|
19641
|
+
"avsl\xE5",
|
|
19642
|
+
"publicera",
|
|
19643
|
+
"arkivera",
|
|
19644
|
+
"logga ut",
|
|
19645
|
+
"\xE5terst\xE4ll",
|
|
19646
|
+
"\xF6verf\xF6r",
|
|
19647
|
+
"bjud in",
|
|
19648
|
+
"ladda upp"
|
|
19649
|
+
].join("|")})(?![\\p{L}\\p{N}])`,
|
|
19650
|
+
"iu"
|
|
19651
|
+
);
|
|
19652
|
+
UNSAFE_PATH = /(^|\/)(log-?out|sign-?out|logga-?ut|delete|remove|destroy|unsubscribe)(\/|$)/i;
|
|
19653
|
+
SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
19654
|
+
CLICK_TIMEOUT_MS = 3e3;
|
|
19655
|
+
LOST = "could not return to surface";
|
|
19656
|
+
NAV_TIMEOUT_MS = 15e3;
|
|
19657
|
+
READ_PAGE = String.raw`(() => {
|
|
19658
|
+
const SELECTOR = [
|
|
19659
|
+
"a[href]", "button", "summary",
|
|
19660
|
+
'input[type="button"]', 'input[type="submit"]',
|
|
19661
|
+
'input[type="reset"]', 'input[type="image"]',
|
|
19662
|
+
'[role="button"]', '[role="link"]', '[role="tab"]',
|
|
19663
|
+
'[role="menuitem"]', '[role="menuitemradio"]', '[role="menuitemcheckbox"]',
|
|
19664
|
+
].join(",");
|
|
19665
|
+
const clean = (s) => (s ?? "").replace(/\s+/g, " ").trim();
|
|
19666
|
+
const visible = (el) => {
|
|
19667
|
+
if (typeof el.checkVisibility === "function" &&
|
|
19668
|
+
!el.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true })) {
|
|
19669
|
+
return false;
|
|
19670
|
+
}
|
|
19671
|
+
const r = el.getBoundingClientRect();
|
|
19672
|
+
return r.width > 0 && r.height > 0;
|
|
19673
|
+
};
|
|
19674
|
+
const textOf = (id) => clean(document.getElementById(id)?.innerText);
|
|
19675
|
+
const nameOf = (el) => {
|
|
19676
|
+
const label = clean(el.getAttribute("aria-label"));
|
|
19677
|
+
if (label) return label;
|
|
19678
|
+
const by = el.getAttribute("aria-labelledby");
|
|
19679
|
+
if (by) {
|
|
19680
|
+
const t = clean(by.split(/\s+/).map(textOf).join(" "));
|
|
19681
|
+
if (t) return t;
|
|
19682
|
+
}
|
|
19683
|
+
if (el instanceof HTMLInputElement) {
|
|
19684
|
+
return clean(el.value) || clean(el.alt) || clean(el.title);
|
|
19685
|
+
}
|
|
19686
|
+
const text = clean(el.innerText);
|
|
19687
|
+
if (text) return text.slice(0, 80);
|
|
19688
|
+
const img = el.querySelector("img[alt]");
|
|
19689
|
+
return clean(img?.getAttribute("alt")) || clean(el.title);
|
|
19690
|
+
};
|
|
19691
|
+
const roleOf = (el) =>
|
|
19692
|
+
el.getAttribute("role") || (el.tagName === "A" ? "link" : "button");
|
|
19693
|
+
|
|
19694
|
+
for (const el of document.querySelectorAll("[data-iamu-x]")) {
|
|
19695
|
+
el.removeAttribute("data-iamu-x");
|
|
19696
|
+
}
|
|
19697
|
+
const seen = new Map();
|
|
19698
|
+
const candidates = [];
|
|
19699
|
+
let index = 0;
|
|
19700
|
+
for (const el of document.querySelectorAll(SELECTOR)) {
|
|
19701
|
+
if (!visible(el)) continue;
|
|
19702
|
+
if (el.disabled || el.getAttribute("aria-disabled") === "true") continue;
|
|
19703
|
+
const role = roleOf(el);
|
|
19704
|
+
const name = nameOf(el);
|
|
19705
|
+
const sig = role + "\n" + name;
|
|
19706
|
+
const occurrence = seen.get(sig) ?? 0;
|
|
19707
|
+
seen.set(sig, occurrence + 1);
|
|
19708
|
+
let submit = false;
|
|
19709
|
+
if (el instanceof HTMLButtonElement) {
|
|
19710
|
+
// A button in a form submits unless it says type="button".
|
|
19711
|
+
submit = el.form
|
|
19712
|
+
? el.type === "submit" || el.type === "reset"
|
|
19713
|
+
: el.getAttribute("type") === "submit";
|
|
19714
|
+
} else if (el instanceof HTMLInputElement) {
|
|
19715
|
+
submit = ["submit", "reset", "image"].includes(el.type);
|
|
19716
|
+
}
|
|
19717
|
+
const anchor = el instanceof HTMLAnchorElement ? el : null;
|
|
19718
|
+
el.setAttribute("data-iamu-x", String(index));
|
|
19719
|
+
candidates.push({
|
|
19720
|
+
index, role, name, occurrence,
|
|
19721
|
+
href: anchor ? anchor.href : null,
|
|
19722
|
+
download: anchor ? anchor.hasAttribute("download") : false,
|
|
19723
|
+
newTab: anchor ? anchor.target === "_blank" : false,
|
|
19724
|
+
submit,
|
|
19725
|
+
});
|
|
19726
|
+
index++;
|
|
19727
|
+
}
|
|
19728
|
+
|
|
19729
|
+
const headings = [];
|
|
19730
|
+
for (const el of document.querySelectorAll(
|
|
19731
|
+
'h1, h2, [role="heading"][aria-level="1"], [role="heading"][aria-level="2"]',
|
|
19732
|
+
)) {
|
|
19733
|
+
if (!visible(el)) continue;
|
|
19734
|
+
const t = clean(el.innerText);
|
|
19735
|
+
if (t) headings.push(t.slice(0, 120));
|
|
19736
|
+
}
|
|
19737
|
+
|
|
19738
|
+
const fields = [];
|
|
19739
|
+
for (const f of document.querySelectorAll("input, select, textarea")) {
|
|
19740
|
+
if (["hidden", "submit", "button", "reset", "image"].includes(f.type)) continue;
|
|
19741
|
+
if (!visible(f)) continue;
|
|
19742
|
+
const forLabel = f.id
|
|
19743
|
+
? document.querySelector('label[for="' + CSS.escape(f.id) + '"]')
|
|
19744
|
+
: null;
|
|
19745
|
+
const name =
|
|
19746
|
+
clean(f.getAttribute("aria-label")) ||
|
|
19747
|
+
clean(forLabel?.innerText) ||
|
|
19748
|
+
clean(f.closest("label")?.innerText) ||
|
|
19749
|
+
clean(f.placeholder) ||
|
|
19750
|
+
clean(f.name);
|
|
19751
|
+
if (name && !fields.includes(name)) fields.push(name.slice(0, 80));
|
|
19752
|
+
if (fields.length >= 40) break;
|
|
19753
|
+
}
|
|
19754
|
+
|
|
19755
|
+
return { url: location.href, title: clean(document.title), headings, fields, candidates };
|
|
19756
|
+
})()`;
|
|
19757
|
+
}
|
|
19758
|
+
});
|
|
19759
|
+
|
|
19760
|
+
// src/commands/explore.ts
|
|
19761
|
+
var explore_exports = {};
|
|
19762
|
+
__export(explore_exports, {
|
|
19763
|
+
exploreCoverageCommand: () => exploreCoverageCommand,
|
|
19764
|
+
exploreDiffCommand: () => exploreDiffCommand,
|
|
19765
|
+
exploreList: () => exploreList,
|
|
19766
|
+
exploreRun: () => exploreRun
|
|
19767
|
+
});
|
|
19768
|
+
import { promises as fs28 } from "fs";
|
|
19769
|
+
import path25 from "path";
|
|
19770
|
+
import chalk26 from "chalk";
|
|
19771
|
+
function fail2(message, json2) {
|
|
19772
|
+
if (json2) console.error(JSON.stringify({ error: message }));
|
|
19773
|
+
else console.error(chalk26.red(` ${message}`));
|
|
19774
|
+
process.exitCode = 1;
|
|
19775
|
+
}
|
|
19776
|
+
function positiveInt(raw, fallback, flag) {
|
|
19777
|
+
if (raw === void 0) return fallback;
|
|
19778
|
+
const n = Number(raw);
|
|
19779
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
19780
|
+
throw new Error(`${flag} must be a positive whole number, got "${raw}"`);
|
|
19781
|
+
}
|
|
19782
|
+
return n;
|
|
19783
|
+
}
|
|
19784
|
+
async function testabilityTarget2(projectRoot) {
|
|
19785
|
+
try {
|
|
19786
|
+
const raw = await fs28.readFile(
|
|
19787
|
+
path25.join(projectRoot, "iamu", "testability.md"),
|
|
19788
|
+
"utf8"
|
|
19789
|
+
);
|
|
19790
|
+
const { frontmatter } = parseFrontmatter(raw);
|
|
19791
|
+
return typeof frontmatter.target_url === "string" ? frontmatter.target_url : null;
|
|
19792
|
+
} catch {
|
|
19793
|
+
return null;
|
|
19794
|
+
}
|
|
19795
|
+
}
|
|
19796
|
+
async function openPage(opts) {
|
|
19797
|
+
const { chromium } = await import("playwright-core");
|
|
19798
|
+
const viewport = parseViewport(opts.viewport) ?? {
|
|
19799
|
+
width: 1280,
|
|
19800
|
+
height: 800
|
|
19801
|
+
};
|
|
19802
|
+
if (opts.attach) {
|
|
19803
|
+
const state = readBrowseState();
|
|
19804
|
+
if (!state) {
|
|
19805
|
+
throw new Error(
|
|
19806
|
+
"No browser to attach to \u2014 start one with `iamu browse start` and sign in first."
|
|
19807
|
+
);
|
|
19808
|
+
}
|
|
19809
|
+
const browser2 = await chromium.connectOverCDP(
|
|
19810
|
+
`http://127.0.0.1:${state.port}`
|
|
19811
|
+
);
|
|
19812
|
+
const context = browser2.contexts()[0] ?? await browser2.newContext();
|
|
19813
|
+
const page2 = await context.newPage();
|
|
19814
|
+
await page2.setViewportSize(viewport);
|
|
19815
|
+
return {
|
|
19816
|
+
page: page2,
|
|
19817
|
+
close: async () => {
|
|
19818
|
+
await page2.close().catch(() => {
|
|
19819
|
+
});
|
|
19820
|
+
await browser2.close().catch(() => {
|
|
19821
|
+
});
|
|
19822
|
+
}
|
|
19823
|
+
};
|
|
19824
|
+
}
|
|
19825
|
+
const chrome = findChrome();
|
|
19826
|
+
if (!chrome) {
|
|
19827
|
+
throw new Error(
|
|
19828
|
+
"No Chrome or Chromium found \u2014 install one or set IAMU_BROWSER."
|
|
19829
|
+
);
|
|
19830
|
+
}
|
|
19831
|
+
const browser = await chromium.launch({
|
|
19832
|
+
executablePath: chrome,
|
|
19833
|
+
headless: true,
|
|
19834
|
+
chromiumSandbox: !shouldDisableSandbox()
|
|
19835
|
+
});
|
|
19836
|
+
const page = await browser.newPage({ viewport, serviceWorkers: "block" });
|
|
19837
|
+
return { page, close: () => browser.close().catch(() => {
|
|
19838
|
+
}) };
|
|
19839
|
+
}
|
|
19840
|
+
async function exploreRun(url, opts) {
|
|
19841
|
+
const ctx = await resolveProject(opts);
|
|
19842
|
+
if (!ctx) return;
|
|
19843
|
+
const target = url ?? await testabilityTarget2(ctx.projectRoot);
|
|
19844
|
+
if (!target) {
|
|
19845
|
+
fail2(
|
|
19846
|
+
"No URL to explore \u2014 pass one (iamu explore http://localhost:3000) or set target_url in iamu/testability.md.",
|
|
19847
|
+
opts.json
|
|
19848
|
+
);
|
|
19849
|
+
return;
|
|
19850
|
+
}
|
|
19851
|
+
let startUrl;
|
|
19852
|
+
let budget;
|
|
19853
|
+
let deny;
|
|
19854
|
+
try {
|
|
19855
|
+
startUrl = new URL(target).href;
|
|
19856
|
+
budget = {
|
|
19857
|
+
max_surfaces: positiveInt(
|
|
19858
|
+
opts.maxSurfaces,
|
|
19859
|
+
DEFAULTS.max_surfaces,
|
|
19860
|
+
"--max-surfaces"
|
|
19861
|
+
),
|
|
19862
|
+
max_actions: positiveInt(
|
|
19863
|
+
opts.maxActions,
|
|
19864
|
+
DEFAULTS.max_actions,
|
|
19865
|
+
"--max-actions"
|
|
19866
|
+
),
|
|
19867
|
+
max_depth: positiveInt(opts.maxDepth, DEFAULTS.max_depth, "--max-depth")
|
|
19868
|
+
};
|
|
19869
|
+
deny = (opts.deny ?? []).map((d) => new RegExp(d, "i"));
|
|
19870
|
+
} catch (err) {
|
|
19871
|
+
fail2(err.message, opts.json);
|
|
19872
|
+
return;
|
|
19873
|
+
}
|
|
19874
|
+
const quiet = opts.json || process.env.IAMU_QUIET === "1";
|
|
19875
|
+
if (!quiet) {
|
|
19876
|
+
console.error(chalk26.dim(` Exploring ${startUrl} (read-only)\u2026`));
|
|
19877
|
+
}
|
|
19878
|
+
const now = /* @__PURE__ */ new Date();
|
|
19879
|
+
let opened = null;
|
|
19880
|
+
let result;
|
|
19881
|
+
try {
|
|
19882
|
+
opened = await openPage(opts);
|
|
19883
|
+
result = await crawl(opened.page, {
|
|
19884
|
+
startUrl,
|
|
19885
|
+
budget,
|
|
19886
|
+
deny,
|
|
19887
|
+
log: quiet ? void 0 : (line) => console.error(chalk26.dim(line))
|
|
19888
|
+
});
|
|
19889
|
+
} catch (err) {
|
|
19890
|
+
fail2(err.message.split("\n")[0], opts.json);
|
|
19891
|
+
return;
|
|
19892
|
+
} finally {
|
|
19893
|
+
await opened?.close();
|
|
19894
|
+
}
|
|
19895
|
+
const build = detectBuild({ cwd: ctx.projectRoot });
|
|
19896
|
+
const map2 = {
|
|
19897
|
+
schema: 1,
|
|
19898
|
+
id: composeExploreId(now),
|
|
19899
|
+
created_at: now.toISOString(),
|
|
19900
|
+
target_url: startUrl,
|
|
19901
|
+
...build ? { build: { ...build } } : {},
|
|
19902
|
+
budget,
|
|
19903
|
+
stats: result.stats,
|
|
19904
|
+
surfaces: result.surfaces
|
|
19905
|
+
};
|
|
19906
|
+
const file = await writeExploreMap(ctx.projectRoot, map2);
|
|
19907
|
+
const rel = path25.relative(process.cwd(), file);
|
|
19908
|
+
if (opts.json) {
|
|
19909
|
+
console.log(JSON.stringify({ id: map2.id, path: rel, stats: map2.stats }));
|
|
19910
|
+
return;
|
|
19911
|
+
}
|
|
19912
|
+
const s = map2.stats;
|
|
19913
|
+
console.log(
|
|
19914
|
+
`
|
|
19915
|
+
${chalk26.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` : ""}`
|
|
19916
|
+
);
|
|
19917
|
+
if (s.truncated) {
|
|
19918
|
+
console.log(
|
|
19919
|
+
chalk26.yellow(
|
|
19920
|
+
" Stopped by the budget before the product ran out \u2014 raise --max-surfaces / --max-actions / --max-depth to go further."
|
|
19921
|
+
)
|
|
19922
|
+
);
|
|
19923
|
+
}
|
|
19924
|
+
console.log(` Map: ${rel}`);
|
|
19925
|
+
console.log(chalk26.dim(" Next: iamu explore coverage"));
|
|
19926
|
+
}
|
|
19927
|
+
async function resolveMapId(projectRoot, id, offsetFromEnd = 1) {
|
|
19928
|
+
if (id && id !== "latest") return id;
|
|
19929
|
+
const ids = await listExploreMaps(projectRoot);
|
|
19930
|
+
return ids.at(-offsetFromEnd) ?? null;
|
|
19931
|
+
}
|
|
19932
|
+
async function exploreList(opts) {
|
|
19933
|
+
const ctx = await resolveProject(opts);
|
|
19934
|
+
if (!ctx) return;
|
|
19935
|
+
const rows = [];
|
|
19936
|
+
for (const id of await listExploreMaps(ctx.projectRoot)) {
|
|
19937
|
+
try {
|
|
19938
|
+
const m = await readExploreMap(ctx.projectRoot, id);
|
|
19939
|
+
rows.push({ id, target_url: m.target_url, surfaces: m.surfaces.length });
|
|
19940
|
+
} catch {
|
|
19941
|
+
}
|
|
19942
|
+
}
|
|
19943
|
+
if (opts.json) {
|
|
19944
|
+
console.log(JSON.stringify({ maps: rows }));
|
|
19945
|
+
return;
|
|
19946
|
+
}
|
|
19947
|
+
if (rows.length === 0) {
|
|
19948
|
+
console.log(" No maps yet. Run: iamu explore <url>");
|
|
19949
|
+
return;
|
|
19950
|
+
}
|
|
19951
|
+
for (const r of rows) {
|
|
19952
|
+
console.log(
|
|
19953
|
+
` ${r.id} ${r.surfaces} surfaces ${chalk26.dim(r.target_url)}`
|
|
19954
|
+
);
|
|
19955
|
+
}
|
|
19956
|
+
}
|
|
19957
|
+
async function exploreCoverageCommand(mapId, opts) {
|
|
19958
|
+
const ctx = await resolveProject(opts);
|
|
19959
|
+
if (!ctx) return;
|
|
19960
|
+
const id = await resolveMapId(ctx.projectRoot, mapId);
|
|
19961
|
+
if (!id) {
|
|
19962
|
+
fail2("No map yet. Run: iamu explore <url>", opts.json);
|
|
19963
|
+
return;
|
|
19964
|
+
}
|
|
19965
|
+
let map2;
|
|
19966
|
+
try {
|
|
19967
|
+
map2 = await readExploreMap(ctx.projectRoot, id);
|
|
19968
|
+
} catch (err) {
|
|
19969
|
+
fail2(err.message, opts.json);
|
|
19970
|
+
return;
|
|
19971
|
+
}
|
|
19972
|
+
const cov = exploreCoverage(map2, await readAllRunSteps(ctx.projectRoot));
|
|
19973
|
+
if (opts.json) {
|
|
19974
|
+
console.log(JSON.stringify(cov));
|
|
19975
|
+
return;
|
|
19976
|
+
}
|
|
19977
|
+
console.log(
|
|
19978
|
+
`
|
|
19979
|
+
Map ${id} (${map2.surfaces.length} surfaces) against ${cov.runs} runs, ${cov.steps} steps`
|
|
19980
|
+
);
|
|
19981
|
+
console.log(
|
|
19982
|
+
` ${chalk26.green(`${cov.reached} reached`)} \xB7 ${chalk26.yellow(`${cov.place_only} URL reached, view unclear`)} \xB7 ${chalk26.red(`${cov.unreached} never reached`)}`
|
|
19983
|
+
);
|
|
19984
|
+
const section = (title, rows, color) => {
|
|
19985
|
+
if (rows.length === 0) return;
|
|
19986
|
+
console.log(`
|
|
19987
|
+
${color(title)}`);
|
|
19988
|
+
for (const r of rows) {
|
|
19989
|
+
console.log(
|
|
19990
|
+
` ${r.key.padEnd(32)} ${r.label}${chalk26.dim(` (depth ${r.depth})`)}`
|
|
19991
|
+
);
|
|
19992
|
+
}
|
|
19993
|
+
};
|
|
19994
|
+
section(
|
|
19995
|
+
"Never reached by a run",
|
|
19996
|
+
cov.surfaces.filter((s) => s.status === "unreached"),
|
|
19997
|
+
chalk26.red
|
|
19998
|
+
);
|
|
19999
|
+
section(
|
|
20000
|
+
"URL reached, but no step names this view",
|
|
20001
|
+
cov.surfaces.filter((s) => s.status === "place_only"),
|
|
20002
|
+
chalk26.yellow
|
|
20003
|
+
);
|
|
20004
|
+
if (cov.run_only_places.length > 0) {
|
|
20005
|
+
console.log(`
|
|
20006
|
+
${chalk26.cyan("Walked by runs, not in the map")}`);
|
|
20007
|
+
for (const p of cov.run_only_places) {
|
|
20008
|
+
console.log(
|
|
20009
|
+
` ${p.place.padEnd(32)} ${p.steps} steps, ${p.runs.length} runs`
|
|
20010
|
+
);
|
|
20011
|
+
}
|
|
20012
|
+
}
|
|
20013
|
+
if (cov.runs === 0) {
|
|
20014
|
+
console.log(
|
|
20015
|
+
chalk26.dim(
|
|
20016
|
+
"\n No runs yet \u2014 every surface counts as never reached. Walk a scenario first (iamu-run)."
|
|
20017
|
+
)
|
|
20018
|
+
);
|
|
20019
|
+
}
|
|
20020
|
+
}
|
|
20021
|
+
async function exploreDiffCommand(a, b, opts) {
|
|
20022
|
+
const ctx = await resolveProject(opts);
|
|
20023
|
+
if (!ctx) return;
|
|
20024
|
+
const idA = a ?? await resolveMapId(ctx.projectRoot, void 0, 2);
|
|
20025
|
+
const idB = b ?? await resolveMapId(ctx.projectRoot, void 0, 1);
|
|
20026
|
+
if (!idA || !idB || idA === idB) {
|
|
20027
|
+
fail2(
|
|
20028
|
+
"Need two maps to compare \u2014 explore again (iamu explore <url>), or name both maps.",
|
|
20029
|
+
opts.json
|
|
20030
|
+
);
|
|
20031
|
+
return;
|
|
20032
|
+
}
|
|
20033
|
+
let mapA;
|
|
20034
|
+
let mapB;
|
|
20035
|
+
try {
|
|
20036
|
+
mapA = await readExploreMap(ctx.projectRoot, idA);
|
|
20037
|
+
mapB = await readExploreMap(ctx.projectRoot, idB);
|
|
20038
|
+
} catch (err) {
|
|
20039
|
+
fail2(err.message, opts.json);
|
|
20040
|
+
return;
|
|
20041
|
+
}
|
|
20042
|
+
const d = diffExploreMaps(mapA, mapB);
|
|
20043
|
+
if (opts.json) {
|
|
20044
|
+
console.log(JSON.stringify(d));
|
|
20045
|
+
return;
|
|
20046
|
+
}
|
|
20047
|
+
const pct = Math.round(d.surface_overlap * 100);
|
|
20048
|
+
console.log(`
|
|
20049
|
+
${idA} \u2192 ${idB}`);
|
|
20050
|
+
console.log(
|
|
20051
|
+
` ${chalk26.bold(`${pct}% of surfaces match`)} (${d.common} shared, ${d.only_a.length} only in the first, ${d.only_b.length} only in the second)`
|
|
20052
|
+
);
|
|
20053
|
+
if (d.only_a.length > 0) {
|
|
20054
|
+
console.log(`
|
|
20055
|
+
${chalk26.red("Only in the first")}`);
|
|
20056
|
+
for (const s of d.only_a) console.log(` ${s.key.padEnd(32)} ${s.label}`);
|
|
20057
|
+
}
|
|
20058
|
+
if (d.only_b.length > 0) {
|
|
20059
|
+
console.log(`
|
|
20060
|
+
${chalk26.green("Only in the second")}`);
|
|
20061
|
+
for (const s of d.only_b) console.log(` ${s.key.padEnd(32)} ${s.label}`);
|
|
20062
|
+
}
|
|
20063
|
+
if (d.changed_actions.length > 0) {
|
|
20064
|
+
console.log(
|
|
20065
|
+
`
|
|
20066
|
+
${chalk26.yellow(`Controls differ on ${d.changed_actions.length} shared surfaces`)}`
|
|
20067
|
+
);
|
|
20068
|
+
for (const c of d.changed_actions) {
|
|
20069
|
+
console.log(` ${c.key} ${c.label}`);
|
|
20070
|
+
for (const x of c.only_a) console.log(chalk26.red(` - ${x}`));
|
|
20071
|
+
for (const x of c.only_b) console.log(chalk26.green(` + ${x}`));
|
|
20072
|
+
}
|
|
20073
|
+
}
|
|
20074
|
+
}
|
|
20075
|
+
var DEFAULTS;
|
|
20076
|
+
var init_explore = __esm({
|
|
20077
|
+
"src/commands/explore.ts"() {
|
|
20078
|
+
"use strict";
|
|
20079
|
+
init_dist2();
|
|
20080
|
+
init_build_info();
|
|
20081
|
+
init_browse();
|
|
20082
|
+
init_context4();
|
|
20083
|
+
init_explore_crawl();
|
|
20084
|
+
DEFAULTS = { max_surfaces: 40, max_actions: 300, max_depth: 6 };
|
|
20085
|
+
}
|
|
20086
|
+
});
|
|
20087
|
+
|
|
18468
20088
|
// src/commands/completion.ts
|
|
18469
20089
|
var completion_exports = {};
|
|
18470
20090
|
__export(completion_exports, {
|
|
@@ -18655,13 +20275,14 @@ var init_completion = __esm({
|
|
|
18655
20275
|
var instructions_walk_exports = {};
|
|
18656
20276
|
__export(instructions_walk_exports, {
|
|
18657
20277
|
composeWalkerPrompt: () => composeWalkerPrompt,
|
|
20278
|
+
extractUntestableBoundaries: () => extractUntestableBoundaries,
|
|
18658
20279
|
instructionsWalk: () => instructionsWalk,
|
|
18659
20280
|
revisitSection: () => revisitSection,
|
|
18660
20281
|
viewportLine: () => viewportLine
|
|
18661
20282
|
});
|
|
18662
20283
|
import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
|
|
18663
20284
|
import { join as join9 } from "path";
|
|
18664
|
-
import
|
|
20285
|
+
import chalk27 from "chalk";
|
|
18665
20286
|
function selfCommand() {
|
|
18666
20287
|
const q = (p) => /[\s"'$]/.test(p) ? JSON.stringify(p) : p;
|
|
18667
20288
|
return `${q(process.execPath)} ${q(process.argv[1] ?? "iamu")}`;
|
|
@@ -18692,6 +20313,24 @@ function browseSurface(iamu, viewport) {
|
|
|
18692
20313
|
...viewport ? [viewportLine(viewport)] : []
|
|
18693
20314
|
].join("\n");
|
|
18694
20315
|
}
|
|
20316
|
+
function extractUntestableBoundaries(body) {
|
|
20317
|
+
const match2 = body.match(
|
|
20318
|
+
/##\s+Untestable\s+Boundaries[^\n]*\n([\s\S]*?)(?=\n##|\s*$)/i
|
|
20319
|
+
);
|
|
20320
|
+
if (!match2) return [];
|
|
20321
|
+
const section = match2[1];
|
|
20322
|
+
const items = [];
|
|
20323
|
+
for (const line of section.split("\n")) {
|
|
20324
|
+
const trimmed = line.trim();
|
|
20325
|
+
if (trimmed.startsWith("- ") || trimmed.startsWith("\u2022 ") || trimmed.startsWith("* ")) {
|
|
20326
|
+
const item = trimmed.slice(2).trim();
|
|
20327
|
+
if (item && !item.toLowerCase().includes("none documented")) {
|
|
20328
|
+
items.push(item);
|
|
20329
|
+
}
|
|
20330
|
+
}
|
|
20331
|
+
}
|
|
20332
|
+
return items;
|
|
20333
|
+
}
|
|
18695
20334
|
function guidanceLine(r) {
|
|
18696
20335
|
const rule = r.rule.replace(/\s+/g, " ");
|
|
18697
20336
|
return r.reason ? `- ${rule} (${r.reason.replace(/\s+/g, " ")})` : `- ${rule}`;
|
|
@@ -18764,6 +20403,23 @@ function composeWalkerPrompt(p) {
|
|
|
18764
20403
|
"",
|
|
18765
20404
|
"## First-person brief",
|
|
18766
20405
|
p.brief ?? "",
|
|
20406
|
+
...p.context ? ["", "## Product domain & regulatory context", p.context.trim()] : [],
|
|
20407
|
+
...p.state?.description || p.state?.approximation ? [
|
|
20408
|
+
"",
|
|
20409
|
+
"## Expected state & semantic squinting",
|
|
20410
|
+
p.state.description ? `Expected state: ${p.state.description}` : "",
|
|
20411
|
+
p.state.approximation ? `Approximation strategy: ${p.state.approximation}` : "",
|
|
20412
|
+
"Squinting Guardrails:",
|
|
20413
|
+
"\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.",
|
|
20414
|
+
"\u2022 Squinting does NOT permit drifting to unrelated settings, account profiles, or off-target pages. Stay strictly within the scenario journey.",
|
|
20415
|
+
"\u2022 If the system is completely empty or unseeded, and your persona role cannot create data, do NOT report false product bugs about the empty state. Note that preconditions are unseeded/blocked."
|
|
20416
|
+
].filter(Boolean) : [],
|
|
20417
|
+
...p.untestableBoundaries && p.untestableBoundaries.length > 0 ? [
|
|
20418
|
+
"",
|
|
20419
|
+
"## Untestable Boundaries (DO NOT WALK)",
|
|
20420
|
+
"These areas are un-mocked or live external services. Do NOT attempt to navigate, submit, or interact with them:",
|
|
20421
|
+
...p.untestableBoundaries.map((b) => `\u2022 ${b}`)
|
|
20422
|
+
] : [],
|
|
18767
20423
|
...revisitSection(p.revisit ?? []),
|
|
18768
20424
|
"",
|
|
18769
20425
|
"## Access",
|
|
@@ -18792,7 +20448,7 @@ function composeWalkerPrompt(p) {
|
|
|
18792
20448
|
`Screenshots to: ${rd}/screenshots/step-N.png \u2014 absolute paths.`,
|
|
18793
20449
|
`Record findings with: ${iamu} new-finding --scenario ${p.scenarioSlug} --run ${p.runId} --step <N> --title "..." --type <helps|hinders|missing|irrelevant|incorrect> --severity <critical|major|minor|observation> --ui-fact "..." --consequence "..." --task-impact <blocks|impairs|slows|neutral|accelerates|enables> --confidence <high|medium|low> --json \u2014 then add the body (description + "## Recommendation") below the frontmatter of the file it returns.`,
|
|
18794
20450
|
"`--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.",
|
|
18795
|
-
`Before you finish, write ${rd}/summary.json: {"what_worked": [...], "open_questions": [...]} \u2014 one sentence each, in the persona's language.`,
|
|
20451
|
+
`Before you finish, write ${rd}/summary.json: {"what_worked": [...], "open_questions": [...], "model": "<your model identifier if known, e.g. claude-3-5-haiku, gemini-3.8-flash, or unspecified>"} \u2014 one sentence each, in the persona's language.`,
|
|
18796
20452
|
"",
|
|
18797
20453
|
"Do not read the product's source code, other iamu/ entity files, or earlier runs: you know only what the persona knows.",
|
|
18798
20454
|
"",
|
|
@@ -18902,6 +20558,25 @@ async function instructionsWalk(opts) {
|
|
|
18902
20558
|
if (!targetUrl) {
|
|
18903
20559
|
targetUrl = sfm.target_url ?? null;
|
|
18904
20560
|
}
|
|
20561
|
+
const untestableBoundaries = [];
|
|
20562
|
+
try {
|
|
20563
|
+
const testabilityPath = join9(projectRoot, "iamu", "testability.md");
|
|
20564
|
+
if (existsSync12(testabilityPath)) {
|
|
20565
|
+
const raw = readFileSync8(testabilityPath, "utf-8");
|
|
20566
|
+
const { frontmatter, body } = parseFrontmatter(raw);
|
|
20567
|
+
if (!targetUrl && typeof frontmatter.target_url === "string" && frontmatter.target_url.trim().length > 0) {
|
|
20568
|
+
targetUrl = frontmatter.target_url.trim();
|
|
20569
|
+
}
|
|
20570
|
+
const fromFm = Array.isArray(
|
|
20571
|
+
frontmatter.untestable_boundaries
|
|
20572
|
+
) ? frontmatter.untestable_boundaries.filter(Boolean) : [];
|
|
20573
|
+
const fromBody = extractUntestableBoundaries(body);
|
|
20574
|
+
untestableBoundaries.push(
|
|
20575
|
+
...Array.from(/* @__PURE__ */ new Set([...fromFm, ...fromBody]))
|
|
20576
|
+
);
|
|
20577
|
+
}
|
|
20578
|
+
} catch {
|
|
20579
|
+
}
|
|
18905
20580
|
if (!revisit) {
|
|
18906
20581
|
try {
|
|
18907
20582
|
revisit = await computeRevisitPlaces(projectRoot, sfm.slug, targetUrl);
|
|
@@ -18912,7 +20587,8 @@ async function instructionsWalk(opts) {
|
|
|
18912
20587
|
const result = {
|
|
18913
20588
|
scenario: {
|
|
18914
20589
|
slug: sfm.slug,
|
|
18915
|
-
brief: scenario.body.trim() || null
|
|
20590
|
+
brief: scenario.body.trim() || null,
|
|
20591
|
+
state: sfm.state ?? null
|
|
18916
20592
|
},
|
|
18917
20593
|
persona: {
|
|
18918
20594
|
slug: sfm.persona,
|
|
@@ -18933,6 +20609,7 @@ async function instructionsWalk(opts) {
|
|
|
18933
20609
|
methodology,
|
|
18934
20610
|
dismissed_findings: dismissedFacts,
|
|
18935
20611
|
tester_guidance: guidance,
|
|
20612
|
+
untestable_boundaries: untestableBoundaries,
|
|
18936
20613
|
// Where fixes awaiting a re-run were seen (for you; the walker gets
|
|
18937
20614
|
// the places only, never which findings they belong to).
|
|
18938
20615
|
revisit,
|
|
@@ -18966,7 +20643,10 @@ async function instructionsWalk(opts) {
|
|
|
18966
20643
|
runDirPath,
|
|
18967
20644
|
projectRoot,
|
|
18968
20645
|
surface: opts.surface ?? "browse",
|
|
18969
|
-
viewport
|
|
20646
|
+
viewport,
|
|
20647
|
+
state: sfm.state ?? null,
|
|
20648
|
+
context: contextYml,
|
|
20649
|
+
untestableBoundaries
|
|
18970
20650
|
}) : null
|
|
18971
20651
|
};
|
|
18972
20652
|
if (opts.json) {
|
|
@@ -18974,30 +20654,40 @@ async function instructionsWalk(opts) {
|
|
|
18974
20654
|
return;
|
|
18975
20655
|
}
|
|
18976
20656
|
console.log();
|
|
18977
|
-
console.log(
|
|
18978
|
-
console.log(` ${
|
|
18979
|
-
console.log(` ${
|
|
18980
|
-
console.log(` ${
|
|
18981
|
-
if (
|
|
20657
|
+
console.log(chalk27.bold(" Walk instructions"));
|
|
20658
|
+
console.log(` ${chalk27.dim("scenario:")} ${sfm.slug}`);
|
|
20659
|
+
console.log(` ${chalk27.dim("persona:")} ${pfm.name} (${pfm.role})`);
|
|
20660
|
+
console.log(` ${chalk27.dim("outline:")} ${ofm.title}`);
|
|
20661
|
+
if (sfm.state?.description || sfm.state?.approximation) {
|
|
20662
|
+
console.log(
|
|
20663
|
+
` ${chalk27.dim("state:")} ${sfm.state.description ? sfm.state.description : sfm.state.approximation ?? ""}`
|
|
20664
|
+
);
|
|
20665
|
+
}
|
|
20666
|
+
if (targetUrl) console.log(` ${chalk27.dim("target:")} ${targetUrl}`);
|
|
18982
20667
|
console.log(
|
|
18983
|
-
` ${
|
|
20668
|
+
` ${chalk27.dim("access:")} ${projectAccess || personaAccess ? `${projectAccess ? "project" : ""}${projectAccess && personaAccess ? " + " : ""}${personaAccess ? "persona" : ""}` : "not recorded (walker would run as whoever is signed in)"}`
|
|
18984
20669
|
);
|
|
18985
20670
|
console.log(
|
|
18986
|
-
` ${
|
|
20671
|
+
` ${chalk27.dim("dismissed:")} ${dismissedFacts.length} findings to skip`
|
|
18987
20672
|
);
|
|
18988
20673
|
console.log(
|
|
18989
|
-
` ${
|
|
20674
|
+
` ${chalk27.dim("guidance:")} ${guidance.length} tester guidance rule${guidance.length === 1 ? "" : "s"}`
|
|
18990
20675
|
);
|
|
20676
|
+
if (untestableBoundaries.length > 0) {
|
|
20677
|
+
console.log(
|
|
20678
|
+
` ${chalk27.dim("boundaries:")} ${untestableBoundaries.length} untestable boundaries to avoid`
|
|
20679
|
+
);
|
|
20680
|
+
}
|
|
18991
20681
|
if (revisit.length > 0) {
|
|
18992
20682
|
console.log(
|
|
18993
|
-
` ${
|
|
20683
|
+
` ${chalk27.dim("re-check:")} ${revisit.length} place${revisit.length === 1 ? "" : "s"} where fixes await a re-run`
|
|
18994
20684
|
);
|
|
18995
20685
|
}
|
|
18996
20686
|
console.log(
|
|
18997
|
-
` ${
|
|
20687
|
+
` ${chalk27.dim("methodology:")} ${methodology ? "loaded" : "missing (run iamu update)"}`
|
|
18998
20688
|
);
|
|
18999
20689
|
console.log();
|
|
19000
|
-
console.log(
|
|
20690
|
+
console.log(chalk27.dim(" Use --json to get the full instruction bundle."));
|
|
19001
20691
|
console.log();
|
|
19002
20692
|
}
|
|
19003
20693
|
var SCREEN;
|
|
@@ -19023,8 +20713,8 @@ __export(new_finding_exports, {
|
|
|
19023
20713
|
});
|
|
19024
20714
|
import { randomUUID as randomUUID10 } from "crypto";
|
|
19025
20715
|
import { access, readFile as readFile6 } from "fs/promises";
|
|
19026
|
-
import
|
|
19027
|
-
import
|
|
20716
|
+
import path26 from "path";
|
|
20717
|
+
import chalk28 from "chalk";
|
|
19028
20718
|
async function newFinding(opts) {
|
|
19029
20719
|
const ctx = await resolveProject(opts);
|
|
19030
20720
|
if (!ctx) return;
|
|
@@ -19090,7 +20780,7 @@ async function newFinding(opts) {
|
|
|
19090
20780
|
}
|
|
19091
20781
|
try {
|
|
19092
20782
|
await access(
|
|
19093
|
-
|
|
20783
|
+
path26.join(runDirPath, "steps", `${String(n).padStart(3, "0")}.json`)
|
|
19094
20784
|
);
|
|
19095
20785
|
} catch {
|
|
19096
20786
|
commandError(
|
|
@@ -19105,7 +20795,7 @@ async function newFinding(opts) {
|
|
|
19105
20795
|
if (!scenarioSlug) {
|
|
19106
20796
|
try {
|
|
19107
20797
|
const manifestRaw = await readFile6(
|
|
19108
|
-
|
|
20798
|
+
path26.join(runDirPath, "walkthrough.json"),
|
|
19109
20799
|
"utf8"
|
|
19110
20800
|
);
|
|
19111
20801
|
const manifest = JSON.parse(manifestRaw);
|
|
@@ -19194,10 +20884,10 @@ async function newFinding(opts) {
|
|
|
19194
20884
|
return;
|
|
19195
20885
|
}
|
|
19196
20886
|
console.log();
|
|
19197
|
-
console.log(` ${symbols.success} Created finding: ${
|
|
19198
|
-
console.log(` ${
|
|
19199
|
-
console.log(` ${
|
|
19200
|
-
for (const w of warnings) console.log(` ${
|
|
20887
|
+
console.log(` ${symbols.success} Created finding: ${chalk28.cyan(slug)}`);
|
|
20888
|
+
console.log(` ${chalk28.dim("path:")} ${record.path}`);
|
|
20889
|
+
console.log(` ${chalk28.dim("id:")} ${id}`);
|
|
20890
|
+
for (const w of warnings) console.log(` ${chalk28.yellow("!")} ${w}`);
|
|
19201
20891
|
console.log();
|
|
19202
20892
|
} catch (err) {
|
|
19203
20893
|
commandError(
|
|
@@ -19222,8 +20912,8 @@ __export(view_results_exports, {
|
|
|
19222
20912
|
openBrowserResults: () => openBrowserResults,
|
|
19223
20913
|
promptViewResults: () => promptViewResults
|
|
19224
20914
|
});
|
|
19225
|
-
import { isCancel as isCancel4, select as
|
|
19226
|
-
import
|
|
20915
|
+
import { isCancel as isCancel4, select as select4 } from "@clack/prompts";
|
|
20916
|
+
import chalk29 from "chalk";
|
|
19227
20917
|
function launchCloudDashboard(config, runId) {
|
|
19228
20918
|
const cloudRunId = runId ? toCloudRunUuid(runId) : void 0;
|
|
19229
20919
|
const url = getCloudDashboardUrl(
|
|
@@ -19232,7 +20922,7 @@ function launchCloudDashboard(config, runId) {
|
|
|
19232
20922
|
cloudRunId
|
|
19233
20923
|
);
|
|
19234
20924
|
console.log();
|
|
19235
|
-
console.log(` ${symbols.success} Opening Iamu Cloud: ${
|
|
20925
|
+
console.log(` ${symbols.success} Opening Iamu Cloud: ${chalk29.cyan(url)}
|
|
19236
20926
|
`);
|
|
19237
20927
|
openBrowser(url);
|
|
19238
20928
|
}
|
|
@@ -19268,7 +20958,7 @@ async function promptViewResults(projectRoot, opts) {
|
|
|
19268
20958
|
},
|
|
19269
20959
|
{ label: "Skip", value: "skip" }
|
|
19270
20960
|
];
|
|
19271
|
-
const choice = await
|
|
20961
|
+
const choice = await select4({
|
|
19272
20962
|
message: "View results?",
|
|
19273
20963
|
options
|
|
19274
20964
|
});
|
|
@@ -19300,9 +20990,9 @@ __export(evaluate_exports, {
|
|
|
19300
20990
|
evaluate: () => evaluate,
|
|
19301
20991
|
evaluateRecord: () => evaluateRecord
|
|
19302
20992
|
});
|
|
19303
|
-
import { promises as
|
|
19304
|
-
import
|
|
19305
|
-
import
|
|
20993
|
+
import { promises as fs29 } from "fs";
|
|
20994
|
+
import path27 from "path";
|
|
20995
|
+
import chalk30 from "chalk";
|
|
19306
20996
|
import { z as z25 } from "zod";
|
|
19307
20997
|
function formatIssues(error) {
|
|
19308
20998
|
return error.issues.map(
|
|
@@ -19332,10 +21022,10 @@ async function evaluate(runId, opts) {
|
|
|
19332
21022
|
const resolved = await resolveProjectAndRunDir(runId, opts);
|
|
19333
21023
|
if (!resolved) return;
|
|
19334
21024
|
const { projectRoot, runDirPath } = resolved;
|
|
19335
|
-
const canonicalPath =
|
|
21025
|
+
const canonicalPath = path27.join(runDirPath, "canonical.json");
|
|
19336
21026
|
let canonical;
|
|
19337
21027
|
try {
|
|
19338
|
-
const raw = await
|
|
21028
|
+
const raw = await fs29.readFile(canonicalPath, "utf8");
|
|
19339
21029
|
canonical = JSON.parse(raw);
|
|
19340
21030
|
} catch {
|
|
19341
21031
|
commandError(
|
|
@@ -19391,8 +21081,21 @@ async function evaluate(runId, opts) {
|
|
|
19391
21081
|
)
|
|
19392
21082
|
};
|
|
19393
21083
|
const prompt = composeJudgePrompt(input);
|
|
21084
|
+
const promptFilePath = opts?.promptFile ?? path27.join(runDirPath, "judge_prompt.md");
|
|
21085
|
+
await fs29.writeFile(promptFilePath, prompt, "utf8");
|
|
19394
21086
|
if (opts?.json) {
|
|
19395
|
-
console.log(
|
|
21087
|
+
console.log(
|
|
21088
|
+
JSON.stringify(
|
|
21089
|
+
{
|
|
21090
|
+
...input,
|
|
21091
|
+
prompt_file: path27.relative(projectRoot, promptFilePath),
|
|
21092
|
+
prompt_file_abs: path27.resolve(promptFilePath),
|
|
21093
|
+
judge_prompt: prompt
|
|
21094
|
+
},
|
|
21095
|
+
null,
|
|
21096
|
+
2
|
|
21097
|
+
)
|
|
21098
|
+
);
|
|
19396
21099
|
return;
|
|
19397
21100
|
}
|
|
19398
21101
|
console.log(prompt);
|
|
@@ -19401,10 +21104,10 @@ async function evaluateRecord(runId, opts) {
|
|
|
19401
21104
|
const resolved = await resolveProjectAndRunDir(runId, opts);
|
|
19402
21105
|
if (!resolved) return;
|
|
19403
21106
|
const { projectRoot, runDirPath } = resolved;
|
|
19404
|
-
const canonicalPath =
|
|
21107
|
+
const canonicalPath = path27.join(runDirPath, "canonical.json");
|
|
19405
21108
|
let canonical;
|
|
19406
21109
|
try {
|
|
19407
|
-
const raw = await
|
|
21110
|
+
const raw = await fs29.readFile(canonicalPath, "utf8");
|
|
19408
21111
|
canonical = JSON.parse(raw);
|
|
19409
21112
|
} catch {
|
|
19410
21113
|
commandError(`No canonical.json for run: ${runId}`, opts);
|
|
@@ -19412,7 +21115,7 @@ async function evaluateRecord(runId, opts) {
|
|
|
19412
21115
|
}
|
|
19413
21116
|
let parsedFile;
|
|
19414
21117
|
try {
|
|
19415
|
-
const raw = await
|
|
21118
|
+
const raw = await fs29.readFile(opts.file, "utf8");
|
|
19416
21119
|
parsedFile = JSON.parse(raw);
|
|
19417
21120
|
} catch {
|
|
19418
21121
|
commandError(`Cannot read verdicts file: ${opts.file}`, opts);
|
|
@@ -19469,8 +21172,8 @@ ${formatIssues(fileResult.error)}`,
|
|
|
19469
21172
|
overall: inputVerdicts.overall
|
|
19470
21173
|
});
|
|
19471
21174
|
const reportContent = renderCanonicalMarkdown(canonicalForReport);
|
|
19472
|
-
const verdictsPath =
|
|
19473
|
-
const reportPath =
|
|
21175
|
+
const verdictsPath = path27.join(runDirPath, "verdicts.json");
|
|
21176
|
+
const reportPath = path27.join(runDirPath, "report.md");
|
|
19474
21177
|
await Promise.all([
|
|
19475
21178
|
atomicWriteFile(
|
|
19476
21179
|
verdictsPath,
|
|
@@ -19484,14 +21187,14 @@ ${formatIssues(fileResult.error)}`,
|
|
|
19484
21187
|
runId,
|
|
19485
21188
|
validatedVerdicts
|
|
19486
21189
|
);
|
|
19487
|
-
const cloudInfo = await pushRunToCloud(projectRoot, runId);
|
|
21190
|
+
const cloudInfo = opts?.push === false ? void 0 : await pushRunToCloud(projectRoot, runId);
|
|
19488
21191
|
if (opts?.json) {
|
|
19489
21192
|
console.log(
|
|
19490
21193
|
JSON.stringify(
|
|
19491
21194
|
{
|
|
19492
21195
|
runId,
|
|
19493
|
-
verdictsPath:
|
|
19494
|
-
reportPath:
|
|
21196
|
+
verdictsPath: path27.relative(projectRoot, verdictsPath),
|
|
21197
|
+
reportPath: path27.relative(projectRoot, reportPath),
|
|
19495
21198
|
verdictsCount: validatedVerdicts.length,
|
|
19496
21199
|
linkedCount: linked.length,
|
|
19497
21200
|
...cloudInfo ? { cloud: cloudInfo } : {}
|
|
@@ -19504,18 +21207,18 @@ ${formatIssues(fileResult.error)}`,
|
|
|
19504
21207
|
}
|
|
19505
21208
|
console.log();
|
|
19506
21209
|
console.log(
|
|
19507
|
-
` ${symbols.success} Recorded ${validatedVerdicts.length} verdicts for run ${
|
|
21210
|
+
` ${symbols.success} Recorded ${validatedVerdicts.length} verdicts for run ${chalk30.cyan(runId)}`
|
|
19508
21211
|
);
|
|
19509
21212
|
if (linked.length > 0) {
|
|
19510
21213
|
console.log(
|
|
19511
|
-
` ${
|
|
21214
|
+
` ${chalk30.dim("linked:")} ${linked.length} finding${linked.length === 1 ? "" : "s"} to the human test${linked.length === 1 ? "" : "s"} citing ${linked.length === 1 ? "it" : "them"}`
|
|
19512
21215
|
);
|
|
19513
21216
|
}
|
|
19514
21217
|
console.log(
|
|
19515
|
-
` ${
|
|
21218
|
+
` ${chalk30.dim("verdicts:")} ${path27.relative(projectRoot, verdictsPath)}`
|
|
19516
21219
|
);
|
|
19517
21220
|
console.log(
|
|
19518
|
-
` ${
|
|
21221
|
+
` ${chalk30.dim("report:")} ${path27.relative(projectRoot, reportPath)}`
|
|
19519
21222
|
);
|
|
19520
21223
|
printCloudRunInfo(cloudInfo);
|
|
19521
21224
|
await promptViewResults(projectRoot, { ...opts, runId });
|
|
@@ -19579,9 +21282,9 @@ __export(complete_exports, {
|
|
|
19579
21282
|
screenshotSizeWarning: () => screenshotSizeWarning,
|
|
19580
21283
|
validateCanonicalWalkthrough: () => validateCanonicalWalkthrough
|
|
19581
21284
|
});
|
|
19582
|
-
import { promises as
|
|
19583
|
-
import
|
|
19584
|
-
import
|
|
21285
|
+
import { promises as fs30 } from "fs";
|
|
21286
|
+
import path28 from "path";
|
|
21287
|
+
import chalk31 from "chalk";
|
|
19585
21288
|
async function complete(runId, opts) {
|
|
19586
21289
|
const run = await loadRun(runId, opts);
|
|
19587
21290
|
if (!run) return;
|
|
@@ -19591,7 +21294,7 @@ async function complete(runId, opts) {
|
|
|
19591
21294
|
}
|
|
19592
21295
|
if (!opts?.json) {
|
|
19593
21296
|
console.log();
|
|
19594
|
-
console.log(
|
|
21297
|
+
console.log(chalk31.bold(" Assembling canonical JSON\u2026"));
|
|
19595
21298
|
}
|
|
19596
21299
|
const provenanceAudit = auditStepProvenance(
|
|
19597
21300
|
await readStepsForRun(run.projectRoot, runId)
|
|
@@ -19601,6 +21304,36 @@ async function complete(runId, opts) {
|
|
|
19601
21304
|
return;
|
|
19602
21305
|
}
|
|
19603
21306
|
const completedAt = pickCompletedAt(run.manifest);
|
|
21307
|
+
const walkerSummary = await readWalkerSummary(run.runDirPath);
|
|
21308
|
+
let judgeReported;
|
|
21309
|
+
try {
|
|
21310
|
+
const verdictsRaw = await fs30.readFile(
|
|
21311
|
+
path28.join(run.runDirPath, "verdicts.json"),
|
|
21312
|
+
"utf8"
|
|
21313
|
+
);
|
|
21314
|
+
const parsed = JSON.parse(verdictsRaw);
|
|
21315
|
+
if (parsed.provenance?.model) {
|
|
21316
|
+
judgeReported = parsed.provenance.model;
|
|
21317
|
+
}
|
|
21318
|
+
} catch {
|
|
21319
|
+
}
|
|
21320
|
+
const stepCount = provenanceAudit.step_count;
|
|
21321
|
+
const durationSec = provenanceAudit.duration_seconds ?? (run.manifest.created_at && Date.parse(completedAt) > Date.parse(run.manifest.created_at) ? Math.round(
|
|
21322
|
+
(Date.parse(completedAt) - Date.parse(run.manifest.created_at)) / 1e3 * 10
|
|
21323
|
+
) / 10 : null);
|
|
21324
|
+
const velocitySec = provenanceAudit.avg_step_interval_seconds ?? (durationSec != null && stepCount > 1 ? Math.round(durationSec / (stepCount - 1) * 10) / 10 : null);
|
|
21325
|
+
const existingModels = run.manifest.models ?? {};
|
|
21326
|
+
const updatedModels = {
|
|
21327
|
+
...existingModels,
|
|
21328
|
+
...walkerSummary.model ? { walker_reported: walkerSummary.model } : {},
|
|
21329
|
+
...judgeReported ? { judge_reported: judgeReported } : {},
|
|
21330
|
+
...durationSec != null ? { total_duration_sec: durationSec } : {},
|
|
21331
|
+
...velocitySec != null ? { velocity_sec_per_step: velocitySec } : {}
|
|
21332
|
+
};
|
|
21333
|
+
run.manifest = {
|
|
21334
|
+
...run.manifest,
|
|
21335
|
+
models: updatedModels
|
|
21336
|
+
};
|
|
19604
21337
|
const assembled = await assembleRun(
|
|
19605
21338
|
run.projectRoot,
|
|
19606
21339
|
runId,
|
|
@@ -19627,16 +21360,16 @@ async function loadRun(runId, opts) {
|
|
|
19627
21360
|
if (!resolved) return null;
|
|
19628
21361
|
const { projectRoot, runDirPath } = resolved;
|
|
19629
21362
|
try {
|
|
19630
|
-
await
|
|
21363
|
+
await fs30.access(runDirPath);
|
|
19631
21364
|
} catch {
|
|
19632
21365
|
commandError(`Run not found: ${runId}`, opts);
|
|
19633
21366
|
return null;
|
|
19634
21367
|
}
|
|
19635
|
-
const manifestPath =
|
|
21368
|
+
const manifestPath = path28.join(runDirPath, "walkthrough.json");
|
|
19636
21369
|
let manifest;
|
|
19637
21370
|
try {
|
|
19638
21371
|
manifest = JSON.parse(
|
|
19639
|
-
await
|
|
21372
|
+
await fs30.readFile(manifestPath, "utf8")
|
|
19640
21373
|
);
|
|
19641
21374
|
} catch {
|
|
19642
21375
|
commandError(`No walkthrough.json found for run: ${runId}`, opts);
|
|
@@ -19648,8 +21381,8 @@ async function reportAlreadyCompleted(run, opts) {
|
|
|
19648
21381
|
const { runId, manifest } = run;
|
|
19649
21382
|
if (opts?.json) {
|
|
19650
21383
|
try {
|
|
19651
|
-
const raw = await
|
|
19652
|
-
|
|
21384
|
+
const raw = await fs30.readFile(
|
|
21385
|
+
path28.join(run.runDirPath, "canonical.json"),
|
|
19653
21386
|
"utf8"
|
|
19654
21387
|
);
|
|
19655
21388
|
console.log(raw.trim());
|
|
@@ -19669,18 +21402,18 @@ async function reportAlreadyCompleted(run, opts) {
|
|
|
19669
21402
|
return;
|
|
19670
21403
|
}
|
|
19671
21404
|
console.log();
|
|
19672
|
-
console.log(
|
|
19673
|
-
console.log(` status ${
|
|
21405
|
+
console.log(chalk31.bold(" Run: ") + chalk31.cyan(runId));
|
|
21406
|
+
console.log(` status ${chalk31.green("completed")}`);
|
|
19674
21407
|
if (manifest.completed_at) {
|
|
19675
|
-
console.log(` completed ${
|
|
21408
|
+
console.log(` completed ${chalk31.dim(manifest.completed_at)}`);
|
|
19676
21409
|
}
|
|
19677
21410
|
const viewUrl = await pushedRunUrl(run.projectRoot, runId);
|
|
19678
21411
|
if (viewUrl) {
|
|
19679
|
-
console.log(` view ${
|
|
21412
|
+
console.log(` view ${chalk31.underline(viewUrl)}`);
|
|
19680
21413
|
}
|
|
19681
21414
|
console.log();
|
|
19682
21415
|
console.log(
|
|
19683
|
-
|
|
21416
|
+
chalk31.dim(
|
|
19684
21417
|
" Already completed \u2014 nothing to do. Use --force to reassemble the report."
|
|
19685
21418
|
)
|
|
19686
21419
|
);
|
|
@@ -19696,13 +21429,13 @@ function reportStrictProvenanceFailure(audit, opts) {
|
|
|
19696
21429
|
}
|
|
19697
21430
|
console.error();
|
|
19698
21431
|
console.error(
|
|
19699
|
-
|
|
21432
|
+
chalk31.red(` ${symbols.error} Strict provenance audit failed:`)
|
|
19700
21433
|
);
|
|
19701
21434
|
for (const w of audit.warnings) {
|
|
19702
|
-
console.error(
|
|
21435
|
+
console.error(chalk31.red(` \u2022 ${w}`));
|
|
19703
21436
|
}
|
|
19704
21437
|
console.error(
|
|
19705
|
-
|
|
21438
|
+
chalk31.dim(
|
|
19706
21439
|
" Run was flagged as synthetic / scripted. Remove --strict-provenance to complete anyway."
|
|
19707
21440
|
)
|
|
19708
21441
|
);
|
|
@@ -19728,7 +21461,7 @@ async function collectCompletionExtras(run, opts) {
|
|
|
19728
21461
|
parseRunViewport(manifest.viewport)
|
|
19729
21462
|
);
|
|
19730
21463
|
const codeWarning = opts?.force ? null : codeChangedWarning(manifest.build, detectWorktreeState(projectRoot));
|
|
19731
|
-
const cloudInfo = await pushRunToCloud(projectRoot, runId);
|
|
21464
|
+
const cloudInfo = opts?.push === false ? void 0 : await pushRunToCloud(projectRoot, runId);
|
|
19732
21465
|
return { revisit, viewportWarning, codeWarning, cloudInfo };
|
|
19733
21466
|
}
|
|
19734
21467
|
function completeJsonPayload(canonical, extras) {
|
|
@@ -19743,30 +21476,41 @@ function completeJsonPayload(canonical, extras) {
|
|
|
19743
21476
|
function printCompletionSummary(run, completedAt, assembled, extras, provenanceAudit) {
|
|
19744
21477
|
const { runId, projectRoot, manifest } = run;
|
|
19745
21478
|
const { canonical } = assembled;
|
|
19746
|
-
const count = (n) => n === 0 ?
|
|
19747
|
-
console.log(
|
|
19748
|
-
console.log(` scenario ${
|
|
19749
|
-
console.log(` url ${
|
|
21479
|
+
const count = (n) => n === 0 ? chalk31.dim("0") : chalk31.cyan(String(n));
|
|
21480
|
+
console.log(chalk31.bold(" Run: ") + chalk31.cyan(runId));
|
|
21481
|
+
console.log(` scenario ${chalk31.dim(manifest.scenario_slug)}`);
|
|
21482
|
+
console.log(` url ${chalk31.dim(manifest.target_url)}`);
|
|
19750
21483
|
if (manifest.environment) {
|
|
19751
|
-
console.log(` env ${
|
|
21484
|
+
console.log(` env ${chalk31.dim(manifest.environment)}`);
|
|
19752
21485
|
}
|
|
19753
21486
|
if (manifest.build) {
|
|
19754
|
-
console.log(` build ${
|
|
21487
|
+
console.log(` build ${chalk31.dim(formatBuild(manifest.build))}`);
|
|
19755
21488
|
}
|
|
19756
21489
|
if (manifest.viewport) {
|
|
19757
|
-
console.log(` device ${
|
|
21490
|
+
console.log(` device ${chalk31.dim(formatViewport(manifest.viewport))}`);
|
|
19758
21491
|
}
|
|
19759
|
-
console.log(` status ${
|
|
21492
|
+
console.log(` status ${chalk31.green("completed")}`);
|
|
19760
21493
|
console.log(` findings ${count(canonical.findings.length)}`);
|
|
19761
21494
|
console.log(` steps ${count(canonical.steps.length)}`);
|
|
19762
|
-
console.log(` started ${
|
|
19763
|
-
console.log(` completed ${
|
|
21495
|
+
console.log(` started ${chalk31.dim(manifest.created_at)}`);
|
|
21496
|
+
console.log(` completed ${chalk31.dim(completedAt)}`);
|
|
21497
|
+
const models = manifest.models;
|
|
21498
|
+
if (models?.velocity_sec_per_step != null) {
|
|
21499
|
+
console.log(
|
|
21500
|
+
` velocity ${chalk31.cyan(`~${models.velocity_sec_per_step}s/step`)} ${chalk31.dim(`(${models.total_duration_sec}s total)`)}`
|
|
21501
|
+
);
|
|
21502
|
+
}
|
|
21503
|
+
if (models && (models.walker || models.judge || models.matcher)) {
|
|
21504
|
+
const w = models.walker_reported ? `${models.walker} (${models.walker_reported})` : models.walker ?? "fast";
|
|
21505
|
+
const j = models.judge_reported ? `${models.judge} (${models.judge_reported})` : models.judge ?? "thorough";
|
|
21506
|
+
console.log(` models ${chalk31.dim(`walker: ${w} \xB7 judge: ${j}`)}`);
|
|
21507
|
+
}
|
|
19764
21508
|
console.log();
|
|
19765
21509
|
console.log(
|
|
19766
|
-
` ${
|
|
21510
|
+
` ${chalk31.dim("canonical:")} ${chalk31.dim(path28.relative(projectRoot, assembled.canonicalPath))}`
|
|
19767
21511
|
);
|
|
19768
21512
|
console.log(
|
|
19769
|
-
` ${
|
|
21513
|
+
` ${chalk31.dim("report:")} ${chalk31.dim(path28.relative(projectRoot, assembled.reportPath))}`
|
|
19770
21514
|
);
|
|
19771
21515
|
if (extras.viewportWarning) {
|
|
19772
21516
|
console.log();
|
|
@@ -19774,7 +21518,7 @@ function printCompletionSummary(run, completedAt, assembled, extras, provenanceA
|
|
|
19774
21518
|
}
|
|
19775
21519
|
if (extras.codeWarning) {
|
|
19776
21520
|
console.log();
|
|
19777
|
-
console.log(` ${
|
|
21521
|
+
console.log(` ${chalk31.yellow("!")} ${extras.codeWarning}`);
|
|
19778
21522
|
}
|
|
19779
21523
|
if (extras.revisit) printRevisit(extras.revisit);
|
|
19780
21524
|
if (assembled.verification) printVerification(assembled.verification);
|
|
@@ -19786,13 +21530,13 @@ function printProvenanceWarnings(audit) {
|
|
|
19786
21530
|
if (audit.warnings.length === 0) return;
|
|
19787
21531
|
console.log();
|
|
19788
21532
|
console.log(
|
|
19789
|
-
` ${symbols.warning} ${
|
|
21533
|
+
` ${symbols.warning} ${chalk31.yellow("Step provenance audit flagged suspicious timing:")}`
|
|
19790
21534
|
);
|
|
19791
21535
|
for (const w of audit.warnings) {
|
|
19792
|
-
console.log(` ${
|
|
21536
|
+
console.log(` ${chalk31.dim("\u2022")} ${w}`);
|
|
19793
21537
|
}
|
|
19794
21538
|
console.log(
|
|
19795
|
-
|
|
21539
|
+
chalk31.dim(
|
|
19796
21540
|
" Run appears to be programmatically batch-generated rather than an interactive walkthrough."
|
|
19797
21541
|
)
|
|
19798
21542
|
);
|
|
@@ -19824,17 +21568,17 @@ function screenshotFitsViewport(width, expected) {
|
|
|
19824
21568
|
}
|
|
19825
21569
|
async function screenshotSizeWarning(runDirPath, viewport) {
|
|
19826
21570
|
if (!viewport) return null;
|
|
19827
|
-
const dir =
|
|
21571
|
+
const dir = path28.join(runDirPath, "screenshots");
|
|
19828
21572
|
let first;
|
|
19829
21573
|
try {
|
|
19830
|
-
first = (await
|
|
21574
|
+
first = (await fs30.readdir(dir)).filter((f) => f.toLowerCase().endsWith(".png")).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }))[0];
|
|
19831
21575
|
} catch {
|
|
19832
21576
|
return null;
|
|
19833
21577
|
}
|
|
19834
21578
|
if (!first) return null;
|
|
19835
21579
|
let size = null;
|
|
19836
21580
|
try {
|
|
19837
|
-
const handle = await
|
|
21581
|
+
const handle = await fs30.open(path28.join(dir, first), "r");
|
|
19838
21582
|
try {
|
|
19839
21583
|
const head = Buffer.alloc(24);
|
|
19840
21584
|
const { bytesRead } = await handle.read(head, 0, 24, 0);
|
|
@@ -19851,12 +21595,14 @@ async function screenshotSizeWarning(runDirPath, viewport) {
|
|
|
19851
21595
|
async function readWalkerSummary(runDirPath) {
|
|
19852
21596
|
try {
|
|
19853
21597
|
const data = JSON.parse(
|
|
19854
|
-
await
|
|
21598
|
+
await fs30.readFile(path28.join(runDirPath, "summary.json"), "utf8")
|
|
19855
21599
|
);
|
|
19856
21600
|
const strings = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && x.trim() !== "") : void 0;
|
|
21601
|
+
const model = typeof data.model === "string" && data.model.trim() !== "" ? data.model.trim() : void 0;
|
|
19857
21602
|
return {
|
|
19858
21603
|
what_worked: strings(data.what_worked),
|
|
19859
|
-
open_questions: strings(data.open_questions)
|
|
21604
|
+
open_questions: strings(data.open_questions),
|
|
21605
|
+
model
|
|
19860
21606
|
};
|
|
19861
21607
|
} catch {
|
|
19862
21608
|
return {};
|
|
@@ -19958,7 +21704,7 @@ function validateCanonicalWalkthrough(canonical) {
|
|
|
19958
21704
|
async function applyRecordedVerdicts(canonical, runDirPath) {
|
|
19959
21705
|
try {
|
|
19960
21706
|
const recorded = JSON.parse(
|
|
19961
|
-
await
|
|
21707
|
+
await fs30.readFile(path28.join(runDirPath, "verdicts.json"), "utf8")
|
|
19962
21708
|
);
|
|
19963
21709
|
if (Array.isArray(recorded.verdicts)) {
|
|
19964
21710
|
return applyVerdicts(canonical, recorded);
|
|
@@ -19968,7 +21714,7 @@ async function applyRecordedVerdicts(canonical, runDirPath) {
|
|
|
19968
21714
|
return canonical;
|
|
19969
21715
|
}
|
|
19970
21716
|
async function persistCanonical(runDirPath, canonical) {
|
|
19971
|
-
const canonicalPath =
|
|
21717
|
+
const canonicalPath = path28.join(runDirPath, "canonical.json");
|
|
19972
21718
|
await atomicWriteFile(
|
|
19973
21719
|
canonicalPath,
|
|
19974
21720
|
`${JSON.stringify(canonical, null, 2)}
|
|
@@ -19977,7 +21723,7 @@ async function persistCanonical(runDirPath, canonical) {
|
|
|
19977
21723
|
return canonicalPath;
|
|
19978
21724
|
}
|
|
19979
21725
|
async function persistReport(runDirPath, canonical) {
|
|
19980
|
-
const reportPath =
|
|
21726
|
+
const reportPath = path28.join(runDirPath, "report.md");
|
|
19981
21727
|
await atomicWriteFile(reportPath, renderCanonicalMarkdown(canonical));
|
|
19982
21728
|
return reportPath;
|
|
19983
21729
|
}
|
|
@@ -20077,17 +21823,17 @@ function placeLabel(p) {
|
|
|
20077
21823
|
function printRevisit(r) {
|
|
20078
21824
|
const missed = r.places.filter((p) => !p.reached);
|
|
20079
21825
|
console.log();
|
|
20080
|
-
const head = ` ${
|
|
21826
|
+
const head = ` ${chalk31.bold("Re-check places:")} reached ${r.reached} of ${r.requested}`;
|
|
20081
21827
|
if (missed.length === 0) {
|
|
20082
|
-
console.log(`${head} ${
|
|
21828
|
+
console.log(`${head} ${chalk31.green("\u2713")}`);
|
|
20083
21829
|
return;
|
|
20084
21830
|
}
|
|
20085
21831
|
console.log(`${head} \u2014 not reached:`);
|
|
20086
21832
|
for (const p of missed) {
|
|
20087
|
-
console.log(` ${
|
|
21833
|
+
console.log(` ${chalk31.yellow("\u25CB")} ${placeLabel(p)}`);
|
|
20088
21834
|
}
|
|
20089
21835
|
console.log(
|
|
20090
|
-
|
|
21836
|
+
chalk31.dim(
|
|
20091
21837
|
" Fixes there stay awaiting a re-run until a walk gets there."
|
|
20092
21838
|
)
|
|
20093
21839
|
);
|
|
@@ -20095,7 +21841,7 @@ function printRevisit(r) {
|
|
|
20095
21841
|
function printVerification(verification) {
|
|
20096
21842
|
console.log();
|
|
20097
21843
|
console.log(
|
|
20098
|
-
|
|
21844
|
+
chalk31.bold(" Since this scenario's last run") + chalk31.dim(` (${verification.baseline_run_id.slice(0, 30)}\u2026)`) + chalk31.bold(":")
|
|
20099
21845
|
);
|
|
20100
21846
|
if (verification.resolved.length > 0) {
|
|
20101
21847
|
console.log(
|
|
@@ -20104,7 +21850,7 @@ function printVerification(verification) {
|
|
|
20104
21850
|
}
|
|
20105
21851
|
if (verification.not_observed.length > 0) {
|
|
20106
21852
|
console.log(
|
|
20107
|
-
` ${symbols.pending} ${verification.not_observed.length} not seen this run ${
|
|
21853
|
+
` ${symbols.pending} ${verification.not_observed.length} not seen this run ${chalk31.dim("(not evidence of a fix)")}`
|
|
20108
21854
|
);
|
|
20109
21855
|
}
|
|
20110
21856
|
if (verification.persists.length > 0) {
|
|
@@ -20145,14 +21891,14 @@ __export(match_exports, {
|
|
|
20145
21891
|
match: () => match,
|
|
20146
21892
|
matchRecord: () => matchRecord
|
|
20147
21893
|
});
|
|
20148
|
-
import { promises as
|
|
20149
|
-
import
|
|
20150
|
-
import
|
|
21894
|
+
import { promises as fs31 } from "fs";
|
|
21895
|
+
import path29 from "path";
|
|
21896
|
+
import chalk32 from "chalk";
|
|
20151
21897
|
async function completedManifest2(runDirPath, runId, opts) {
|
|
20152
21898
|
let manifest;
|
|
20153
21899
|
try {
|
|
20154
21900
|
manifest = JSON.parse(
|
|
20155
|
-
await
|
|
21901
|
+
await fs31.readFile(path29.join(runDirPath, "walkthrough.json"), "utf8")
|
|
20156
21902
|
);
|
|
20157
21903
|
} catch {
|
|
20158
21904
|
commandError(`Run not found: ${runId}`, opts);
|
|
@@ -20181,15 +21927,34 @@ async function match(runId, opts) {
|
|
|
20181
21927
|
);
|
|
20182
21928
|
const needed = input.current.length > 0 && (input.prior.length > 0 || (input.guidance ?? []).length > 0);
|
|
20183
21929
|
const prompt = needed ? composeMatchPrompt(input) : null;
|
|
21930
|
+
const promptFilePath = opts?.promptFile ?? path29.join(runDirPath, "match_prompt.md");
|
|
21931
|
+
if (prompt) {
|
|
21932
|
+
await fs31.writeFile(promptFilePath, prompt, "utf8");
|
|
21933
|
+
} else {
|
|
21934
|
+
try {
|
|
21935
|
+
await fs31.unlink(promptFilePath);
|
|
21936
|
+
} catch {
|
|
21937
|
+
}
|
|
21938
|
+
}
|
|
20184
21939
|
if (opts?.json) {
|
|
20185
21940
|
console.log(
|
|
20186
|
-
JSON.stringify(
|
|
21941
|
+
JSON.stringify(
|
|
21942
|
+
{
|
|
21943
|
+
...input,
|
|
21944
|
+
needed,
|
|
21945
|
+
prompt_file: prompt ? path29.relative(ctx.projectRoot, promptFilePath) : null,
|
|
21946
|
+
prompt_file_abs: prompt ? path29.resolve(promptFilePath) : null,
|
|
21947
|
+
match_prompt: prompt
|
|
21948
|
+
},
|
|
21949
|
+
null,
|
|
21950
|
+
2
|
|
21951
|
+
)
|
|
20187
21952
|
);
|
|
20188
21953
|
return;
|
|
20189
21954
|
}
|
|
20190
21955
|
if (!prompt) {
|
|
20191
21956
|
console.log(
|
|
20192
|
-
|
|
21957
|
+
chalk32.dim(
|
|
20193
21958
|
" No earlier findings or tester guidance for this scenario to match against \u2014 nothing to do."
|
|
20194
21959
|
)
|
|
20195
21960
|
);
|
|
@@ -20205,7 +21970,7 @@ async function matchRecord(runId, opts) {
|
|
|
20205
21970
|
if (!manifest) return;
|
|
20206
21971
|
let answer;
|
|
20207
21972
|
try {
|
|
20208
|
-
answer = JSON.parse(await
|
|
21973
|
+
answer = JSON.parse(await fs31.readFile(opts.file, "utf8"));
|
|
20209
21974
|
} catch {
|
|
20210
21975
|
commandError(`Cannot read matches file: ${opts.file}`, opts);
|
|
20211
21976
|
return;
|
|
@@ -20246,17 +22011,17 @@ async function matchRecord(runId, opts) {
|
|
|
20246
22011
|
}
|
|
20247
22012
|
console.log();
|
|
20248
22013
|
console.log(
|
|
20249
|
-
` ${
|
|
22014
|
+
` ${chalk32.green("\u2713")} Matched ${result.linked} finding${result.linked === 1 ? "" : "s"} to earlier ones, ${result.new} first seen, ${result.rechecked_fixed} earlier re-checked fixed`
|
|
20250
22015
|
);
|
|
20251
22016
|
if (result.suppressed > 0) {
|
|
20252
22017
|
console.log(
|
|
20253
|
-
` ${
|
|
22018
|
+
` ${chalk32.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)`
|
|
20254
22019
|
);
|
|
20255
22020
|
}
|
|
20256
22021
|
if (verification) printVerification(verification);
|
|
20257
22022
|
console.log();
|
|
20258
22023
|
console.log(
|
|
20259
|
-
|
|
22024
|
+
chalk32.dim(" Run `iamu sync` to push the linked findings to the cloud.")
|
|
20260
22025
|
);
|
|
20261
22026
|
console.log();
|
|
20262
22027
|
}
|
|
@@ -20277,7 +22042,7 @@ __export(prune_exports, {
|
|
|
20277
22042
|
prune: () => prune
|
|
20278
22043
|
});
|
|
20279
22044
|
import { confirm, isCancel as isCancel5 } from "@clack/prompts";
|
|
20280
|
-
import
|
|
22045
|
+
import chalk33 from "chalk";
|
|
20281
22046
|
function formatBytes(bytes) {
|
|
20282
22047
|
if (bytes <= 0) return "0 B";
|
|
20283
22048
|
if (bytes < 1024) return `${bytes} B`;
|
|
@@ -20323,7 +22088,7 @@ async function prune(opts = {}) {
|
|
|
20323
22088
|
});
|
|
20324
22089
|
if (preview.prunedRuns.length === 0) {
|
|
20325
22090
|
console.log(
|
|
20326
|
-
|
|
22091
|
+
chalk33.dim(" No runs match the pruning criteria. Nothing to prune.")
|
|
20327
22092
|
);
|
|
20328
22093
|
return preview;
|
|
20329
22094
|
}
|
|
@@ -20332,7 +22097,7 @@ async function prune(opts = {}) {
|
|
|
20332
22097
|
message: `Prune ${mode} ${preview.prunedRuns.length} run(s) and reclaim ${formatBytes(preview.totalBytesReclaimed)}?`
|
|
20333
22098
|
});
|
|
20334
22099
|
if (isCancel5(confirmed) || !confirmed) {
|
|
20335
|
-
console.log(
|
|
22100
|
+
console.log(chalk33.yellow(" Pruning cancelled."));
|
|
20336
22101
|
return;
|
|
20337
22102
|
}
|
|
20338
22103
|
}
|
|
@@ -20343,14 +22108,14 @@ async function prune(opts = {}) {
|
|
|
20343
22108
|
}
|
|
20344
22109
|
if (result.dryRun) {
|
|
20345
22110
|
console.log(
|
|
20346
|
-
|
|
22111
|
+
chalk33.cyan(
|
|
20347
22112
|
`
|
|
20348
22113
|
[DRY RUN] Would prune ${result.prunedRuns.length} of ${result.totalRunsScanned} run(s):`
|
|
20349
22114
|
)
|
|
20350
22115
|
);
|
|
20351
22116
|
} else {
|
|
20352
22117
|
console.log(
|
|
20353
|
-
|
|
22118
|
+
chalk33.bold(
|
|
20354
22119
|
`
|
|
20355
22120
|
${symbols.success} Pruned ${result.prunedRuns.length} of ${result.totalRunsScanned} run(s):`
|
|
20356
22121
|
)
|
|
@@ -20358,18 +22123,18 @@ async function prune(opts = {}) {
|
|
|
20358
22123
|
}
|
|
20359
22124
|
if (result.prunedRuns.length === 0) {
|
|
20360
22125
|
console.log(
|
|
20361
|
-
|
|
22126
|
+
chalk33.dim(" No runs matched the pruning criteria. Nothing pruned.")
|
|
20362
22127
|
);
|
|
20363
22128
|
} else {
|
|
20364
22129
|
for (const run of result.prunedRuns) {
|
|
20365
22130
|
const mode = run.fullyDeleted ? "removed" : "screenshots purged";
|
|
20366
22131
|
const count = run.screenshotsDeleted > 0 ? `${run.screenshotsDeleted} screenshots, ` : "";
|
|
20367
22132
|
console.log(
|
|
20368
|
-
` ${
|
|
22133
|
+
` ${chalk33.dim("\u2022")} ${chalk33.bold(run.runId)} ${chalk33.dim(`(${run.scenarioSlug ?? "unknown scenario"})`)}: ${chalk33.yellow(formatBytes(run.bytesReclaimed))} (${count}${mode})`
|
|
20369
22134
|
);
|
|
20370
22135
|
}
|
|
20371
22136
|
console.log(
|
|
20372
|
-
|
|
22137
|
+
chalk33.dim(
|
|
20373
22138
|
`
|
|
20374
22139
|
Total reclaimed: ${formatBytes(result.totalBytesReclaimed)} (${result.totalScreenshotsDeleted} screenshots deleted)
|
|
20375
22140
|
`
|
|
@@ -20390,7 +22155,7 @@ var init_prune2 = __esm({
|
|
|
20390
22155
|
// src/commands/register-cloud.ts
|
|
20391
22156
|
import { Option } from "commander";
|
|
20392
22157
|
function registerCloudCommands(program) {
|
|
20393
|
-
program.command("sync").helpGroup("Cloud:").description("Push pending local changes to cloud").option("--status", "Show pending sync count without pushing").option(
|
|
22158
|
+
program.command("sync").alias("push").helpGroup("Cloud:").description("Push pending local changes to cloud").option("--status", "Show pending sync count without pushing").option(
|
|
20394
22159
|
"--keep-mine",
|
|
20395
22160
|
"On conflict, overwrite the cloud version with the local one. CLI only \u2014 replacing a teammate's change is an explicit human action, so it is deliberately not exposed through the iamu_sync MCP tool."
|
|
20396
22161
|
).option(
|
|
@@ -20414,7 +22179,7 @@ function registerCloudCommands(program) {
|
|
|
20414
22179
|
"Connect to local cloud stack (default port: 3000)"
|
|
20415
22180
|
).addOption(
|
|
20416
22181
|
new Option("-s, --staging", "Connect to staging cloud stack").hideHelp()
|
|
20417
|
-
).option("-t, --token <token>", "Authenticate directly with API token / key").option("--web-url <url>", "Custom web dashboard URL").option("--url <api-url>", "API URL (default: https://
|
|
22182
|
+
).option("-t, --token <token>", "Authenticate directly with API token / key").option("--token-stdin", "Read API token from standard input").option("--web-url <url>", "Custom web dashboard URL").option("--url <api-url>", "API URL (default: https://app.iamu.dev)").option("-y, --yes", "Accept default project without interactive selection").option(
|
|
20418
22183
|
"--project <project-id>",
|
|
20419
22184
|
"Cloud project ID to link to, or `new` to create one. Required in a non-interactive shell when cloud projects exist"
|
|
20420
22185
|
).option("--no-browser", "Do not launch browser automatically").addHelpText(
|
|
@@ -20503,13 +22268,13 @@ function registerFindingsCommands(program) {
|
|
|
20503
22268
|
|
|
20504
22269
|
// src/commands/register-project.ts
|
|
20505
22270
|
function registerProjectCommands(program) {
|
|
20506
|
-
program.command("init").helpGroup("Project:").description("Scaffold iamu/ + install skills").argument("[name]", "Project name (defaults to directory name)").option("-y, --yes", "Skip prompts and accept all detected defaults").option("--json", "Output initialization metadata as JSON").addHelpText(
|
|
22271
|
+
program.command("init").helpGroup("Project:").description("Scaffold iamu/ + install skills").argument("[name]", "Project name (defaults to directory name)").option("-y, --yes", "Skip prompts and accept all detected defaults").option("--cloud", "Connect to Iamu Cloud directly during init").option("--no-cloud", "Skip Iamu Cloud connection (local-only)").option("--json", "Output initialization metadata as JSON").addHelpText(
|
|
20507
22272
|
"after",
|
|
20508
|
-
"\nExamples:\n $ iamu init\n $ iamu init my-project -y\n $ iamu init --json"
|
|
22273
|
+
"\nExamples:\n $ iamu init\n $ iamu init my-project -y\n $ iamu init --cloud\n $ iamu init --json"
|
|
20509
22274
|
).action(
|
|
20510
22275
|
async (name, opts) => {
|
|
20511
22276
|
const { init: init2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
20512
|
-
await init2({ name, yes: opts.yes, json: opts.json });
|
|
22277
|
+
await init2({ name, yes: opts.yes, json: opts.json, cloud: opts.cloud });
|
|
20513
22278
|
}
|
|
20514
22279
|
);
|
|
20515
22280
|
program.command("status").helpGroup("Project:").description("Show project state: entities, runs, context, cloud link").option("--json", "Output as JSON").addHelpText(
|
|
@@ -20520,10 +22285,13 @@ function registerProjectCommands(program) {
|
|
|
20520
22285
|
await status2(opts);
|
|
20521
22286
|
});
|
|
20522
22287
|
program.command("doctor").helpGroup("Project:").description(
|
|
20523
|
-
"Diagnose environment: runtime, browser automation, skills, cloud"
|
|
22288
|
+
"Diagnose environment: runtime, browser automation, skills, cloud, and target testability"
|
|
22289
|
+
).option(
|
|
22290
|
+
"-t, --target <url>",
|
|
22291
|
+
"Probe target URL health, reachability, and auth redirects"
|
|
20524
22292
|
).option("--json", "Output diagnostics as JSON").addHelpText(
|
|
20525
22293
|
"after",
|
|
20526
|
-
"\nExamples:\n $ iamu doctor\n $ iamu doctor --json"
|
|
22294
|
+
"\nExamples:\n $ iamu doctor\n $ iamu doctor --target http://localhost:3000\n $ iamu doctor --json"
|
|
20527
22295
|
).action(async (opts) => {
|
|
20528
22296
|
const { doctor: doctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
|
|
20529
22297
|
await doctor2(opts);
|
|
@@ -20555,9 +22323,9 @@ function registerProjectCommands(program) {
|
|
|
20555
22323
|
"after",
|
|
20556
22324
|
"\nExamples:\n $ iamu validate --all\n $ iamu validate --all --fix --dry-run\n $ iamu validate iamu/personas/maria.md"
|
|
20557
22325
|
).action(
|
|
20558
|
-
async (
|
|
22326
|
+
async (path30, opts) => {
|
|
20559
22327
|
const { validate: validate2 } = await Promise.resolve().then(() => (init_validate(), validate_exports));
|
|
20560
|
-
await validate2(
|
|
22328
|
+
await validate2(path30, opts);
|
|
20561
22329
|
}
|
|
20562
22330
|
);
|
|
20563
22331
|
program.command("update").helpGroup("Project:").description("Update and repair agent skills and prompts to latest version").option(
|
|
@@ -20570,11 +22338,27 @@ function registerProjectCommands(program) {
|
|
|
20570
22338
|
program.command("new <type>").helpGroup("Project:").description(
|
|
20571
22339
|
"Create a new entity (persona, outline, capability, source, scenario)"
|
|
20572
22340
|
).requiredOption("--name <name>", "Entity name").option("--slug <slug>", "Custom entity slug (defaults to slugified name)").option("--role <role>", "Role description (persona only)").option("--disposition <text>", "Disposition / attitude (persona only)").option(
|
|
22341
|
+
"--access <role>",
|
|
22342
|
+
"Access role or testing account key (persona only)"
|
|
22343
|
+
).option("--body <text>", "Markdown body / narrative content").option("--narrative <text>", "Alias for --body").option(
|
|
20573
22344
|
"--description <text>",
|
|
20574
22345
|
"One-line description (capability, outline)"
|
|
22346
|
+
).option("--capability <slug>", "Primary capability slug (outline only)").option(
|
|
22347
|
+
"--tension <text>",
|
|
22348
|
+
"Structural tension driving this situation (outline only)"
|
|
22349
|
+
).option(
|
|
22350
|
+
"--human-test <question>",
|
|
22351
|
+
"Human-test question evaluated at run completion (outline only, repeatable)",
|
|
22352
|
+
(val, prev = []) => [...prev, val]
|
|
20575
22353
|
).option("--outline <slug>", "Outline slug (scenario only)").option("--persona <slug>", "Persona slug (scenario only)").option(
|
|
20576
22354
|
"--entry <path>",
|
|
20577
22355
|
"Where the situation starts, as a path on the product, e.g. /en/episodes/1 (scenario only)"
|
|
22356
|
+
).option(
|
|
22357
|
+
"--state-description <text>",
|
|
22358
|
+
"Assumed environment data/records state (scenario only)"
|
|
22359
|
+
).option(
|
|
22360
|
+
"--state-approximation <text>",
|
|
22361
|
+
"Guidance on how persona should approximate/squint if records are missing (scenario only)"
|
|
20578
22362
|
).option("--url <url>", "Document the source cites (source only, required)").option(
|
|
20579
22363
|
"--authority <level>",
|
|
20580
22364
|
"legislation | regulation | standard | company_policy | guideline | informational (source only, default informational)"
|
|
@@ -20587,6 +22371,54 @@ function registerProjectCommands(program) {
|
|
|
20587
22371
|
await newEntity2(type2, opts);
|
|
20588
22372
|
}
|
|
20589
22373
|
);
|
|
22374
|
+
const explore = program.command("explore").helpGroup("Project:").description(
|
|
22375
|
+
"Map the running product with a read-only crawl; compare the map with runs (coverage) or with another map (diff)"
|
|
22376
|
+
);
|
|
22377
|
+
explore.command("run [url]", { isDefault: true }).description(
|
|
22378
|
+
"Crawl from <url> (default: target_url in iamu/testability.md) and write iamu/.cache/explore/<id>/map.json"
|
|
22379
|
+
).option("--max-surfaces <n>", "Stop after this many surfaces (default 40)").option("--max-actions <n>", "Stop after this many clicks (default 300)").option(
|
|
22380
|
+
"--max-depth <n>",
|
|
22381
|
+
"Don't go more than this many clicks from the start (default 6)"
|
|
22382
|
+
).option(
|
|
22383
|
+
"--deny <pattern...>",
|
|
22384
|
+
"Never click a control whose name or link matches (regex, case-insensitive)"
|
|
22385
|
+
).option("--viewport <WxH>", "Page size, e.g. 390x844 (default 1280x800)").option(
|
|
22386
|
+
"--attach",
|
|
22387
|
+
"Crawl in a new tab of the `iamu browse` browser, keeping its sign-ins"
|
|
22388
|
+
).option("--json", "Output as JSON").addHelpText(
|
|
22389
|
+
"after",
|
|
22390
|
+
`
|
|
22391
|
+
Read-only: controls that read like writes (delete, pay, send, save, log out \u2026),
|
|
22392
|
+
form submits, downloads and other origins are never clicked, nothing is typed,
|
|
22393
|
+
and every request but GET/HEAD/OPTIONS is blocked before it leaves the browser.
|
|
22394
|
+
|
|
22395
|
+
Examples:
|
|
22396
|
+
$ iamu explore http://localhost:3000
|
|
22397
|
+
$ iamu explore --attach --deny "^Admin"
|
|
22398
|
+
$ iamu explore coverage
|
|
22399
|
+
$ iamu explore diff`
|
|
22400
|
+
).action(
|
|
22401
|
+
async (url, opts) => {
|
|
22402
|
+
const { exploreRun: exploreRun2 } = await Promise.resolve().then(() => (init_explore(), explore_exports));
|
|
22403
|
+
await exploreRun2(url, opts);
|
|
22404
|
+
}
|
|
22405
|
+
);
|
|
22406
|
+
explore.command("coverage [map]").description(
|
|
22407
|
+
"Surfaces of a map (default: latest) that no run reached, and places runs reached that the map lacks"
|
|
22408
|
+
).option("--json", "Output as JSON").action(async (map2, opts) => {
|
|
22409
|
+
const { exploreCoverageCommand: exploreCoverageCommand2 } = await Promise.resolve().then(() => (init_explore(), explore_exports));
|
|
22410
|
+
await exploreCoverageCommand2(map2, opts);
|
|
22411
|
+
});
|
|
22412
|
+
explore.command("diff [a] [b]").description("Compare two maps (default: the two latest)").option("--json", "Output as JSON").action(
|
|
22413
|
+
async (a, b, opts) => {
|
|
22414
|
+
const { exploreDiffCommand: exploreDiffCommand2 } = await Promise.resolve().then(() => (init_explore(), explore_exports));
|
|
22415
|
+
await exploreDiffCommand2(a, b, opts);
|
|
22416
|
+
}
|
|
22417
|
+
);
|
|
22418
|
+
explore.command("list").description("List maps").option("--json", "Output as JSON").action(async (opts) => {
|
|
22419
|
+
const { exploreList: exploreList2 } = await Promise.resolve().then(() => (init_explore(), explore_exports));
|
|
22420
|
+
await exploreList2(opts);
|
|
22421
|
+
});
|
|
20590
22422
|
program.command("completion [shell]").helpGroup("Project:").description("Output shell completion script (bash, zsh, fish)").action(async (shell) => {
|
|
20591
22423
|
const { completion: completion2 } = await Promise.resolve().then(() => (init_completion(), completion_exports));
|
|
20592
22424
|
completion2(program, shell);
|
|
@@ -20616,6 +22448,15 @@ function registerWalkthroughCommands(program) {
|
|
|
20616
22448
|
).option(
|
|
20617
22449
|
"--env <name>",
|
|
20618
22450
|
"Environment the product runs in (default: the target URL's origin)"
|
|
22451
|
+
).option(
|
|
22452
|
+
"--walker <tierOrModel>",
|
|
22453
|
+
"Model tier ('fast', 'thorough', 'inherit') or model ID for driving browser navigation (default: fast)"
|
|
22454
|
+
).option(
|
|
22455
|
+
"--judge <tierOrModel>",
|
|
22456
|
+
"Model tier ('fast', 'thorough', 'inherit') or model ID for human test evaluation (default: thorough)"
|
|
22457
|
+
).option(
|
|
22458
|
+
"--matcher <tierOrModel>",
|
|
22459
|
+
"Model tier ('fast', 'thorough', 'inherit') or model ID for finding matching (default: thorough)"
|
|
20619
22460
|
).option(
|
|
20620
22461
|
"--check",
|
|
20621
22462
|
"Enforce target URL reachability (fail fast if server is offline)"
|
|
@@ -20646,7 +22487,10 @@ function registerWalkthroughCommands(program) {
|
|
|
20646
22487
|
).option(
|
|
20647
22488
|
"--run <run-id>",
|
|
20648
22489
|
"Run ID to size browser (start) or record step into (step)"
|
|
20649
|
-
).option("--action <text>", "Action description (step only)").option("--note <text>", "Persona inner monologue / reasoning (step only)").option("--surface <name>", "Screen or surface name (step only)").option(
|
|
22490
|
+
).option("--action <text>", "Action description (step only)").option("--note <text>", "Persona inner monologue / reasoning (step only)").option("--surface <name>", "Screen or surface name (step only)").option(
|
|
22491
|
+
"--step-file <path>",
|
|
22492
|
+
"Read step payload (action, note, cmd) from a JSON file or '-' for stdin (step only)"
|
|
22493
|
+
).option("--width <px>", "Window width (start)").option("--height <px>", "Window height (start)").option("--headless", "Run without a window (start)").option(
|
|
20650
22494
|
"--port <port>",
|
|
20651
22495
|
"CDP port (start), default 9333",
|
|
20652
22496
|
(value) => parsePortOption(value)
|
|
@@ -20722,9 +22566,9 @@ function registerWalkthroughCommands(program) {
|
|
|
20722
22566
|
).option("-y, --yes", "Skip interactive prompts").option("--no-prompt", "Skip interactive prompt to view results").option("--view", "Open results in browser immediately without prompt").option(
|
|
20723
22567
|
"--strict-provenance",
|
|
20724
22568
|
"Fail if steps show synthetic or programmatic timing anomalies"
|
|
20725
|
-
).addHelpText(
|
|
22569
|
+
).option("--no-push", "Skip pushing the run to Iamu Cloud").addHelpText(
|
|
20726
22570
|
"after",
|
|
20727
|
-
"\nExamples:\n $ iamu complete <run-id>\n $ iamu complete <run-id> --view\n $ iamu complete <run-id> --no-prompt\n $ iamu complete <run-id> --json\n $ iamu complete <run-id> --strict-provenance"
|
|
22571
|
+
"\nExamples:\n $ iamu complete <run-id>\n $ iamu complete <run-id> --view\n $ iamu complete <run-id> --no-prompt\n $ iamu complete <run-id> --no-push\n $ iamu complete <run-id> --json\n $ iamu complete <run-id> --strict-provenance"
|
|
20728
22572
|
).action(
|
|
20729
22573
|
async (runId, opts) => {
|
|
20730
22574
|
const { complete: complete2 } = await Promise.resolve().then(() => (init_complete(), complete_exports));
|
|
@@ -20733,23 +22577,27 @@ function registerWalkthroughCommands(program) {
|
|
|
20733
22577
|
);
|
|
20734
22578
|
program.command("match <run-id>").helpGroup("Walkthrough:").description(
|
|
20735
22579
|
"Compose the prompt that links this run's findings to earlier ones"
|
|
20736
|
-
).option("--json", "Output the match input bundle as JSON").
|
|
20737
|
-
|
|
20738
|
-
|
|
20739
|
-
|
|
22580
|
+
).option("--json", "Output the match input bundle as JSON").option("--prompt-file <path>", "Write the generated prompt to a file").action(
|
|
22581
|
+
async (runId, opts) => {
|
|
22582
|
+
const { match: match2 } = await Promise.resolve().then(() => (init_match(), match_exports));
|
|
22583
|
+
await match2(runId, opts);
|
|
22584
|
+
}
|
|
22585
|
+
);
|
|
20740
22586
|
program.command("match-record <run-id>").helpGroup("Walkthrough:").description(
|
|
20741
22587
|
"Record finding matches and re-derive the run's since-last-run delta"
|
|
20742
22588
|
).requiredOption("--file <path>", "Path to matches JSON file").option("--json", "Output match results as JSON").action(async (runId, opts) => {
|
|
20743
22589
|
const { matchRecord: matchRecord2 } = await Promise.resolve().then(() => (init_match(), match_exports));
|
|
20744
22590
|
await matchRecord2(runId, opts);
|
|
20745
22591
|
});
|
|
20746
|
-
program.command("evaluate <run-id>").helpGroup("Walkthrough:").description("Compose evaluation prompt for human-test questions").option("--json", "Output evaluation input bundle as JSON").
|
|
20747
|
-
|
|
20748
|
-
|
|
20749
|
-
|
|
20750
|
-
|
|
22592
|
+
program.command("evaluate <run-id>").helpGroup("Walkthrough:").description("Compose evaluation prompt for human-test questions").option("--json", "Output evaluation input bundle as JSON").option("--prompt-file <path>", "Write the generated prompt to a file").action(
|
|
22593
|
+
async (runId, opts) => {
|
|
22594
|
+
const { evaluate: evaluate2 } = await Promise.resolve().then(() => (init_evaluate(), evaluate_exports));
|
|
22595
|
+
await evaluate2(runId, opts);
|
|
22596
|
+
}
|
|
22597
|
+
);
|
|
22598
|
+
program.command("evaluate-record <run-id>").helpGroup("Walkthrough:").description("Record evaluation verdicts into the run's verdicts.json").requiredOption("--file <path>", "Path to verdicts JSON file").option("--json", "Output summary as JSON").option("-y, --yes", "Skip interactive prompts").option("--no-prompt", "Skip interactive prompt to view results").option("--no-push", "Skip pushing the updated run to Iamu Cloud").option("--view", "Open results in browser immediately without prompt").addHelpText(
|
|
20751
22599
|
"after",
|
|
20752
|
-
"\nExamples:\n $ iamu evaluate-record <run-id> --file verdicts.json\n $ iamu evaluate-record <run-id> --file verdicts.json --no-prompt\n $ iamu evaluate-record <run-id> --file verdicts.json --json"
|
|
22600
|
+
"\nExamples:\n $ iamu evaluate-record <run-id> --file verdicts.json\n $ iamu evaluate-record <run-id> --file verdicts.json --no-prompt\n $ iamu evaluate-record <run-id> --file verdicts.json --no-push\n $ iamu evaluate-record <run-id> --file verdicts.json --json"
|
|
20753
22601
|
).action(
|
|
20754
22602
|
async (runId, opts) => {
|
|
20755
22603
|
const { evaluateRecord: evaluateRecord2 } = await Promise.resolve().then(() => (init_evaluate(), evaluate_exports));
|
|
@@ -20778,7 +22626,7 @@ function registerAllCommands(program) {
|
|
|
20778
22626
|
|
|
20779
22627
|
// src/ui/panic.ts
|
|
20780
22628
|
init_logger();
|
|
20781
|
-
import
|
|
22629
|
+
import chalk34 from "chalk";
|
|
20782
22630
|
var handlersRegistered = false;
|
|
20783
22631
|
var customSigintHandler = null;
|
|
20784
22632
|
function registerSigintCleanup(handler) {
|
|
@@ -20790,17 +22638,17 @@ function registerSigintCleanup(handler) {
|
|
|
20790
22638
|
function logFatalAndExit(title, errOrReason) {
|
|
20791
22639
|
process.stderr.write("\x1B[?25h");
|
|
20792
22640
|
if (isVerbose()) {
|
|
20793
|
-
console.error(
|
|
22641
|
+
console.error(chalk34.red(`
|
|
20794
22642
|
${title}:`));
|
|
20795
22643
|
console.error(
|
|
20796
22644
|
errOrReason instanceof Error ? errOrReason.stack || errOrReason : errOrReason
|
|
20797
22645
|
);
|
|
20798
22646
|
} else {
|
|
20799
22647
|
const msg = errOrReason instanceof Error ? errOrReason.message : String(errOrReason);
|
|
20800
|
-
console.error(
|
|
22648
|
+
console.error(chalk34.red(`
|
|
20801
22649
|
Fatal Error: ${msg}`));
|
|
20802
22650
|
console.error(
|
|
20803
|
-
|
|
22651
|
+
chalk34.dim(
|
|
20804
22652
|
" Run with --verbose or DEBUG=1 to view the full stack trace.\n"
|
|
20805
22653
|
)
|
|
20806
22654
|
);
|
|
@@ -20819,7 +22667,7 @@ function setupProcessHandlers() {
|
|
|
20819
22667
|
}
|
|
20820
22668
|
process.exit(130);
|
|
20821
22669
|
} else {
|
|
20822
|
-
console.error(
|
|
22670
|
+
console.error(chalk34.dim("\n Operation cancelled."));
|
|
20823
22671
|
process.exit(130);
|
|
20824
22672
|
}
|
|
20825
22673
|
});
|