@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/bin.js
CHANGED
|
@@ -200,17 +200,24 @@ function defaultConfig(params) {
|
|
|
200
200
|
cloud_api_url: null
|
|
201
201
|
};
|
|
202
202
|
}
|
|
203
|
-
var baseProjectFields, localOnlyConfig, cloudLinkedConfig, projectConfigSchema;
|
|
203
|
+
var modelTierSchema, projectModelsConfigSchema, baseProjectFields, localOnlyConfig, cloudLinkedConfig, projectConfigSchema;
|
|
204
204
|
var init_config = __esm({
|
|
205
205
|
"../../packages/protocol/dist/schemas/frontmatter/config.js"() {
|
|
206
206
|
"use strict";
|
|
207
207
|
init_common();
|
|
208
208
|
init_version();
|
|
209
|
+
modelTierSchema = z4.string().min(1).max(128).describe("Model role tier ('fast', 'thorough', 'inherit') or a specific model identifier.");
|
|
210
|
+
projectModelsConfigSchema = z4.object({
|
|
211
|
+
walker: modelTierSchema.optional(),
|
|
212
|
+
matcher: modelTierSchema.optional(),
|
|
213
|
+
judge: modelTierSchema.optional()
|
|
214
|
+
}).strict();
|
|
209
215
|
baseProjectFields = {
|
|
210
216
|
schema_version: z4.literal(SCHEMA_VERSION),
|
|
211
217
|
id: uuidSchema,
|
|
212
218
|
project_slug: slugSchema,
|
|
213
|
-
project_name: z4.string().min(1)
|
|
219
|
+
project_name: z4.string().min(1),
|
|
220
|
+
models: projectModelsConfigSchema.optional()
|
|
214
221
|
};
|
|
215
222
|
localOnlyConfig = z4.object({
|
|
216
223
|
...baseProjectFields,
|
|
@@ -817,7 +824,14 @@ var init_scenario = __esm({
|
|
|
817
824
|
reviews: z15.array(z15.string().min(1).max(64)).max(16).default([]),
|
|
818
825
|
baseline_score: z15.number().min(0).max(100).nullish(),
|
|
819
826
|
current_score: z15.number().min(0).max(100).nullish(),
|
|
820
|
-
target_score: z15.number().min(0).max(100).default(95)
|
|
827
|
+
target_score: z15.number().min(0).max(100).default(95),
|
|
828
|
+
/** Environment state preconditions and approximation guidance. */
|
|
829
|
+
state: z15.object({
|
|
830
|
+
/** Description of data/records expected to exist in the environment. */
|
|
831
|
+
description: z15.string().max(2048).nullish(),
|
|
832
|
+
/** Guidance on how to squint or approximate if exact records are absent. */
|
|
833
|
+
approximation: z15.string().max(2048).nullish()
|
|
834
|
+
}).nullish()
|
|
821
835
|
});
|
|
822
836
|
}
|
|
823
837
|
});
|
|
@@ -1154,7 +1168,21 @@ var init_walkthrough_bundle = __esm({
|
|
|
1154
1168
|
judge_prompt: z19.string().optional(),
|
|
1155
1169
|
judge_model: z19.string().optional()
|
|
1156
1170
|
}).optional()
|
|
1157
|
-
}).optional()
|
|
1171
|
+
}).optional(),
|
|
1172
|
+
models: z19.object({
|
|
1173
|
+
walker: z19.string().optional(),
|
|
1174
|
+
matcher: z19.string().optional(),
|
|
1175
|
+
judge: z19.string().optional(),
|
|
1176
|
+
walker_reported: z19.string().optional(),
|
|
1177
|
+
matcher_reported: z19.string().optional(),
|
|
1178
|
+
judge_reported: z19.string().optional(),
|
|
1179
|
+
walker_tier: z19.string().optional(),
|
|
1180
|
+
matcher_tier: z19.string().optional(),
|
|
1181
|
+
judge_tier: z19.string().optional(),
|
|
1182
|
+
execution_mode: z19.enum(["multi_agent", "single_agent"]).optional(),
|
|
1183
|
+
velocity_sec_per_step: z19.number().optional(),
|
|
1184
|
+
total_duration_sec: z19.number().optional()
|
|
1185
|
+
}).optional().describe("AI model configuration and reported runtime provenance for this walkthrough.")
|
|
1158
1186
|
});
|
|
1159
1187
|
walkthroughBundleSchema = z19.object({
|
|
1160
1188
|
manifest: bundleManifestSchema,
|
|
@@ -1377,6 +1405,7 @@ var init_walkthrough_canonical = __esm({
|
|
|
1377
1405
|
build: buildMetadataSchema.optional(),
|
|
1378
1406
|
viewport: runViewportSchema.optional(),
|
|
1379
1407
|
environment: z20.string().optional(),
|
|
1408
|
+
models: z20.record(z20.unknown()).optional(),
|
|
1380
1409
|
findings: z20.array(canonicalFindingSchema),
|
|
1381
1410
|
steps: z20.array(canonicalStepSchema),
|
|
1382
1411
|
human_tests: z20.array(canonicalHumanTestSchema),
|
|
@@ -7248,6 +7277,7 @@ async function buildCanonical(projectRoot, manifest, completedAt, inputs) {
|
|
|
7248
7277
|
...manifest.build ? { build: manifest.build } : {},
|
|
7249
7278
|
...manifest.viewport ? { viewport: manifest.viewport } : {},
|
|
7250
7279
|
...manifest.environment ? { environment: manifest.environment } : {},
|
|
7280
|
+
...manifest.models ? { models: manifest.models } : {},
|
|
7251
7281
|
findings: canonicalFindings,
|
|
7252
7282
|
steps: canonicalSteps,
|
|
7253
7283
|
human_tests: humanTests,
|
|
@@ -7408,6 +7438,20 @@ function renderCanonicalMarkdown(canonical) {
|
|
|
7408
7438
|
lines.push(`- Viewport: ${canonical.viewport.device} (${canonical.viewport.width}\xD7${canonical.viewport.height})`);
|
|
7409
7439
|
}
|
|
7410
7440
|
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}`);
|
|
7441
|
+
const models = canonical.models;
|
|
7442
|
+
if (models) {
|
|
7443
|
+
const parts = [
|
|
7444
|
+
models.walker ? `Walker: ${models.walker}${models.walker_reported ? ` (${models.walker_reported})` : ""}` : "",
|
|
7445
|
+
models.judge ? `Judge: ${models.judge}${models.judge_reported ? ` (${models.judge_reported})` : ""}` : "",
|
|
7446
|
+
models.matcher ? `Matcher: ${models.matcher}` : ""
|
|
7447
|
+
].filter(Boolean);
|
|
7448
|
+
if (parts.length > 0) {
|
|
7449
|
+
lines.push(`- Models: ${parts.join(" \xB7 ")}`);
|
|
7450
|
+
}
|
|
7451
|
+
if (typeof models.velocity_sec_per_step === "number") {
|
|
7452
|
+
lines.push(`- Velocity: ~${models.velocity_sec_per_step}s/step (${models.total_duration_sec}s total)`);
|
|
7453
|
+
}
|
|
7454
|
+
}
|
|
7411
7455
|
const what = canonical.what_worked;
|
|
7412
7456
|
if (what && what.length > 0) {
|
|
7413
7457
|
lines.push("", "## What worked");
|
|
@@ -7777,6 +7821,7 @@ async function assembleWalkthroughBundle(projectRoot, runId) {
|
|
|
7777
7821
|
report_markdown: report3,
|
|
7778
7822
|
verdict: verdictArray,
|
|
7779
7823
|
judge_meta: judgeMeta,
|
|
7824
|
+
...manifest.models ? { models: manifest.models } : {},
|
|
7780
7825
|
...buildFields(manifest),
|
|
7781
7826
|
...await readCanonicalExtras(projectRoot, runId)
|
|
7782
7827
|
},
|
|
@@ -10443,9 +10488,275 @@ var init_prune = __esm({
|
|
|
10443
10488
|
}
|
|
10444
10489
|
});
|
|
10445
10490
|
|
|
10446
|
-
// ../../packages/local-store/dist/
|
|
10491
|
+
// ../../packages/local-store/dist/explore-map.js
|
|
10492
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
10447
10493
|
import { promises as fs19 } from "fs";
|
|
10448
10494
|
import path15 from "path";
|
|
10495
|
+
function templateToken(token) {
|
|
10496
|
+
if (token === "")
|
|
10497
|
+
return token;
|
|
10498
|
+
if (/^\d+$/.test(token))
|
|
10499
|
+
return ":id";
|
|
10500
|
+
if (UUID.test(token))
|
|
10501
|
+
return ":id";
|
|
10502
|
+
if (DATE.test(token))
|
|
10503
|
+
return ":date";
|
|
10504
|
+
if (/^[0-9a-f]{8,}$/i.test(token) && /\d/.test(token))
|
|
10505
|
+
return ":id";
|
|
10506
|
+
if (token.length >= 16 && /\d/.test(token) && /[a-z]/i.test(token)) {
|
|
10507
|
+
return ":id";
|
|
10508
|
+
}
|
|
10509
|
+
return token;
|
|
10510
|
+
}
|
|
10511
|
+
function templatePath(pathname) {
|
|
10512
|
+
const trimmed = pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname || "/";
|
|
10513
|
+
return trimmed.split("/").map((seg) => templateToken(decodeSegment(seg))).join("/");
|
|
10514
|
+
}
|
|
10515
|
+
function encodeQueryValue(v) {
|
|
10516
|
+
return v.startsWith(":") ? v : encodeURIComponent(v);
|
|
10517
|
+
}
|
|
10518
|
+
function decodeSegment(seg) {
|
|
10519
|
+
try {
|
|
10520
|
+
return decodeURIComponent(seg);
|
|
10521
|
+
} catch {
|
|
10522
|
+
return seg;
|
|
10523
|
+
}
|
|
10524
|
+
}
|
|
10525
|
+
function explorePlaceKey(raw) {
|
|
10526
|
+
if (!raw)
|
|
10527
|
+
return null;
|
|
10528
|
+
let u;
|
|
10529
|
+
try {
|
|
10530
|
+
u = new URL(raw);
|
|
10531
|
+
} catch {
|
|
10532
|
+
return null;
|
|
10533
|
+
}
|
|
10534
|
+
if (u.protocol !== "http:" && u.protocol !== "https:")
|
|
10535
|
+
return null;
|
|
10536
|
+
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));
|
|
10537
|
+
const query = params.map(([k, v]) => `${encodeURIComponent(k)}=${encodeQueryValue(v)}`).join("&");
|
|
10538
|
+
let key = templatePath(u.pathname);
|
|
10539
|
+
if (query)
|
|
10540
|
+
key += `?${query}`;
|
|
10541
|
+
const route = /^#!?(\/.*)$/.exec(u.hash);
|
|
10542
|
+
if (route) {
|
|
10543
|
+
const [hashPath] = route[1].split("?");
|
|
10544
|
+
key += `#${templatePath(hashPath)}`;
|
|
10545
|
+
}
|
|
10546
|
+
return key;
|
|
10547
|
+
}
|
|
10548
|
+
function exploreViewSignature(headings) {
|
|
10549
|
+
return headings.map((h) => h.trim().replace(/\s+/g, " ").replace(/\d+/g, "#").toLowerCase()).filter(Boolean).join(" | ");
|
|
10550
|
+
}
|
|
10551
|
+
function exploreSignature(headings, title) {
|
|
10552
|
+
return exploreViewSignature(headings) || title.trim().toLowerCase();
|
|
10553
|
+
}
|
|
10554
|
+
function matchExploreSurface(map2, url, signature) {
|
|
10555
|
+
const place = explorePlaceKey(url);
|
|
10556
|
+
if (!place)
|
|
10557
|
+
return null;
|
|
10558
|
+
const atPlace = map2.surfaces.filter((s) => s.place === place);
|
|
10559
|
+
return atPlace.find((s) => s.signature === signature) ?? (atPlace.length === 1 ? atPlace[0] : null);
|
|
10560
|
+
}
|
|
10561
|
+
function exploreSurfaceKey(place, view) {
|
|
10562
|
+
return view === null ? place : `${place}~${fnv1a(view)}`;
|
|
10563
|
+
}
|
|
10564
|
+
function fnv1a(s) {
|
|
10565
|
+
let h = 2166136261;
|
|
10566
|
+
for (let i = 0; i < s.length; i++) {
|
|
10567
|
+
h ^= s.charCodeAt(i);
|
|
10568
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
10569
|
+
}
|
|
10570
|
+
return h.toString(16).padStart(8, "0");
|
|
10571
|
+
}
|
|
10572
|
+
function exploreDir(projectRoot) {
|
|
10573
|
+
return path15.join(cacheDir(projectRoot), "explore");
|
|
10574
|
+
}
|
|
10575
|
+
function composeExploreId(now, randomHex) {
|
|
10576
|
+
const ts = now.toISOString().replace(/[:.]/g, "-").replace(/Z$/, "");
|
|
10577
|
+
return `${ts}-${randomHex ?? randomBytes2(3).toString("hex")}`;
|
|
10578
|
+
}
|
|
10579
|
+
function isValidExploreId(id) {
|
|
10580
|
+
return MAP_ID.test(id);
|
|
10581
|
+
}
|
|
10582
|
+
function mapPath(projectRoot, id) {
|
|
10583
|
+
if (!isValidExploreId(id))
|
|
10584
|
+
throw new Error(`Not an explore map id: ${id}`);
|
|
10585
|
+
return path15.join(exploreDir(projectRoot), id, "map.json");
|
|
10586
|
+
}
|
|
10587
|
+
async function writeExploreMap(projectRoot, map2) {
|
|
10588
|
+
const file = mapPath(projectRoot, map2.id);
|
|
10589
|
+
await fs19.mkdir(path15.dirname(file), { recursive: true });
|
|
10590
|
+
await atomicWriteFile(file, `${JSON.stringify(map2, null, 2)}
|
|
10591
|
+
`);
|
|
10592
|
+
return file;
|
|
10593
|
+
}
|
|
10594
|
+
async function readExploreMap(projectRoot, id) {
|
|
10595
|
+
const raw = await fs19.readFile(mapPath(projectRoot, id), "utf8");
|
|
10596
|
+
const map2 = JSON.parse(raw);
|
|
10597
|
+
if (map2?.schema !== EXPLORE_MAP_SCHEMA || !Array.isArray(map2.surfaces)) {
|
|
10598
|
+
throw new Error(`Unreadable explore map: ${id}`);
|
|
10599
|
+
}
|
|
10600
|
+
return map2;
|
|
10601
|
+
}
|
|
10602
|
+
async function listExploreMaps(projectRoot) {
|
|
10603
|
+
try {
|
|
10604
|
+
const entries = await fs19.readdir(exploreDir(projectRoot), {
|
|
10605
|
+
withFileTypes: true
|
|
10606
|
+
});
|
|
10607
|
+
return entries.filter((e) => e.isDirectory() && isValidExploreId(e.name)).map((e) => e.name).sort();
|
|
10608
|
+
} catch (err) {
|
|
10609
|
+
if (err.code === "ENOENT")
|
|
10610
|
+
return [];
|
|
10611
|
+
throw err;
|
|
10612
|
+
}
|
|
10613
|
+
}
|
|
10614
|
+
function exploreCoverage(map2, steps) {
|
|
10615
|
+
const byPlace = /* @__PURE__ */ new Map();
|
|
10616
|
+
for (const s of map2.surfaces) {
|
|
10617
|
+
byPlace.set(s.place, [...byPlace.get(s.place) ?? [], s]);
|
|
10618
|
+
}
|
|
10619
|
+
const reachedBy = /* @__PURE__ */ new Map();
|
|
10620
|
+
const unresolvedBy = /* @__PURE__ */ new Map();
|
|
10621
|
+
const runOnlyBy = /* @__PURE__ */ new Map();
|
|
10622
|
+
const add = (m, k, s) => m.set(k, [...m.get(k) ?? [], s]);
|
|
10623
|
+
for (const step of steps) {
|
|
10624
|
+
const place = explorePlaceKey(step.url);
|
|
10625
|
+
if (!place)
|
|
10626
|
+
continue;
|
|
10627
|
+
const atPlace = byPlace.get(place);
|
|
10628
|
+
if (!atPlace) {
|
|
10629
|
+
add(runOnlyBy, place, step);
|
|
10630
|
+
continue;
|
|
10631
|
+
}
|
|
10632
|
+
const hit = tieStep(map2, atPlace, step);
|
|
10633
|
+
if (hit.length === 0)
|
|
10634
|
+
add(unresolvedBy, place, step);
|
|
10635
|
+
for (const s of hit)
|
|
10636
|
+
add(reachedBy, s.key, step);
|
|
10637
|
+
}
|
|
10638
|
+
const surfaces = map2.surfaces.map((s) => {
|
|
10639
|
+
const base = {
|
|
10640
|
+
key: s.key,
|
|
10641
|
+
label: s.label,
|
|
10642
|
+
place: s.place,
|
|
10643
|
+
url: s.url,
|
|
10644
|
+
depth: s.depth
|
|
10645
|
+
};
|
|
10646
|
+
const reached = reachedBy.get(s.key);
|
|
10647
|
+
if (reached)
|
|
10648
|
+
return { ...base, status: "reached", runs: distinctRuns(reached) };
|
|
10649
|
+
const unresolved = unresolvedBy.get(s.place);
|
|
10650
|
+
if (unresolved) {
|
|
10651
|
+
return { ...base, status: "place_only", runs: distinctRuns(unresolved) };
|
|
10652
|
+
}
|
|
10653
|
+
return { ...base, status: "unreached", runs: [] };
|
|
10654
|
+
});
|
|
10655
|
+
const runOnly = [...runOnlyBy].map(([place, list2]) => ({
|
|
10656
|
+
place,
|
|
10657
|
+
steps: list2.length,
|
|
10658
|
+
runs: distinctRuns(list2)
|
|
10659
|
+
}));
|
|
10660
|
+
runOnly.sort((a, b) => b.steps - a.steps || a.place.localeCompare(b.place));
|
|
10661
|
+
return {
|
|
10662
|
+
map_id: map2.id,
|
|
10663
|
+
runs: new Set(steps.map((s) => s.run_id)).size,
|
|
10664
|
+
steps: steps.length,
|
|
10665
|
+
reached: surfaces.filter((s) => s.status === "reached").length,
|
|
10666
|
+
place_only: surfaces.filter((s) => s.status === "place_only").length,
|
|
10667
|
+
unreached: surfaces.filter((s) => s.status === "unreached").length,
|
|
10668
|
+
surfaces,
|
|
10669
|
+
run_only_places: runOnly
|
|
10670
|
+
};
|
|
10671
|
+
}
|
|
10672
|
+
function tieStep(map2, atPlace, step) {
|
|
10673
|
+
if (typeof step.signature === "string") {
|
|
10674
|
+
const m = matchExploreSurface(map2, step.url, step.signature);
|
|
10675
|
+
return m ? [m] : [];
|
|
10676
|
+
}
|
|
10677
|
+
if (atPlace.length === 1)
|
|
10678
|
+
return [atPlace[0]];
|
|
10679
|
+
const name = step.surface?.trim();
|
|
10680
|
+
if (!name)
|
|
10681
|
+
return [];
|
|
10682
|
+
return atPlace.filter((s) => [s.label, ...s.headings].filter(Boolean).some((n) => surfaceMatches(n, name) || surfaceMatches(name, n)));
|
|
10683
|
+
}
|
|
10684
|
+
function distinctRuns(steps) {
|
|
10685
|
+
return [...new Set(steps.map((s) => s.run_id))].sort();
|
|
10686
|
+
}
|
|
10687
|
+
async function readAllRunSteps(projectRoot) {
|
|
10688
|
+
const out = [];
|
|
10689
|
+
for (const runId of await listRuns(projectRoot)) {
|
|
10690
|
+
let steps;
|
|
10691
|
+
try {
|
|
10692
|
+
steps = await readStepsForRun(projectRoot, runId);
|
|
10693
|
+
} catch {
|
|
10694
|
+
continue;
|
|
10695
|
+
}
|
|
10696
|
+
for (const s of steps) {
|
|
10697
|
+
out.push({ run_id: runId, url: s.url, surface: s.surface });
|
|
10698
|
+
}
|
|
10699
|
+
}
|
|
10700
|
+
return out;
|
|
10701
|
+
}
|
|
10702
|
+
function actionSignature(a) {
|
|
10703
|
+
const where = a.outcome.kind === "surface" ? a.outcome.surface : a.outcome.kind === "skipped" ? `skipped:${a.outcome.reason}` : a.outcome.kind === "external" ? `external:${a.outcome.url}` : a.outcome.kind;
|
|
10704
|
+
return `${a.role} "${a.name}" \u2192 ${where}`;
|
|
10705
|
+
}
|
|
10706
|
+
function diffExploreMaps(a, b) {
|
|
10707
|
+
const aByKey = new Map(a.surfaces.map((s) => [s.key, s]));
|
|
10708
|
+
const bByKey = new Map(b.surfaces.map((s) => [s.key, s]));
|
|
10709
|
+
const ref = (s) => ({
|
|
10710
|
+
key: s.key,
|
|
10711
|
+
label: s.label
|
|
10712
|
+
});
|
|
10713
|
+
const onlyA = a.surfaces.filter((s) => !bByKey.has(s.key)).map(ref);
|
|
10714
|
+
const onlyB = b.surfaces.filter((s) => !aByKey.has(s.key)).map(ref);
|
|
10715
|
+
const changed = [];
|
|
10716
|
+
let common2 = 0;
|
|
10717
|
+
for (const sa of a.surfaces) {
|
|
10718
|
+
const sb = bByKey.get(sa.key);
|
|
10719
|
+
if (!sb)
|
|
10720
|
+
continue;
|
|
10721
|
+
common2++;
|
|
10722
|
+
const setA = new Set(sa.actions.map(actionSignature));
|
|
10723
|
+
const setB = new Set(sb.actions.map(actionSignature));
|
|
10724
|
+
const inA = [...setA].filter((x) => !setB.has(x)).sort();
|
|
10725
|
+
const inB = [...setB].filter((x) => !setA.has(x)).sort();
|
|
10726
|
+
if (inA.length > 0 || inB.length > 0) {
|
|
10727
|
+
changed.push({ ...ref(sa), only_a: inA, only_b: inB });
|
|
10728
|
+
}
|
|
10729
|
+
}
|
|
10730
|
+
const union = common2 + onlyA.length + onlyB.length;
|
|
10731
|
+
return {
|
|
10732
|
+
a: a.id,
|
|
10733
|
+
b: b.id,
|
|
10734
|
+
common: common2,
|
|
10735
|
+
only_a: onlyA,
|
|
10736
|
+
only_b: onlyB,
|
|
10737
|
+
surface_overlap: union === 0 ? 1 : common2 / union,
|
|
10738
|
+
changed_actions: changed
|
|
10739
|
+
};
|
|
10740
|
+
}
|
|
10741
|
+
var EXPLORE_MAP_SCHEMA, UUID, DATE, MAP_ID;
|
|
10742
|
+
var init_explore_map = __esm({
|
|
10743
|
+
"../../packages/local-store/dist/explore-map.js"() {
|
|
10744
|
+
"use strict";
|
|
10745
|
+
init_atomic();
|
|
10746
|
+
init_paths();
|
|
10747
|
+
init_revisit();
|
|
10748
|
+
init_runs();
|
|
10749
|
+
init_walkthrough_canonical2();
|
|
10750
|
+
EXPLORE_MAP_SCHEMA = 1;
|
|
10751
|
+
UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
10752
|
+
DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
10753
|
+
MAP_ID = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}-[0-9a-f]{6}$/;
|
|
10754
|
+
}
|
|
10755
|
+
});
|
|
10756
|
+
|
|
10757
|
+
// ../../packages/local-store/dist/human-test-link.js
|
|
10758
|
+
import { promises as fs20 } from "fs";
|
|
10759
|
+
import path16 from "path";
|
|
10449
10760
|
function humanTestLinks(verdicts) {
|
|
10450
10761
|
const best = /* @__PURE__ */ new Map();
|
|
10451
10762
|
for (const v of verdicts) {
|
|
@@ -10468,7 +10779,7 @@ async function linkFindingsToHumanTests(projectRoot, runId, verdicts) {
|
|
|
10468
10779
|
const dir = entityDirPath(projectRoot, "finding");
|
|
10469
10780
|
let names;
|
|
10470
10781
|
try {
|
|
10471
|
-
names = await
|
|
10782
|
+
names = await fs20.readdir(dir);
|
|
10472
10783
|
} catch {
|
|
10473
10784
|
return [];
|
|
10474
10785
|
}
|
|
@@ -10476,11 +10787,11 @@ async function linkFindingsToHumanTests(projectRoot, runId, verdicts) {
|
|
|
10476
10787
|
for (const name of names) {
|
|
10477
10788
|
if (!name.endsWith(".md") || name.startsWith("."))
|
|
10478
10789
|
continue;
|
|
10479
|
-
const file =
|
|
10790
|
+
const file = path16.join(dir, name);
|
|
10480
10791
|
let raw;
|
|
10481
10792
|
let fm;
|
|
10482
10793
|
try {
|
|
10483
|
-
raw = await
|
|
10794
|
+
raw = await fs20.readFile(file, "utf8");
|
|
10484
10795
|
fm = parseFrontmatter(raw).frontmatter;
|
|
10485
10796
|
} catch {
|
|
10486
10797
|
continue;
|
|
@@ -10711,14 +11022,14 @@ var init_cloud_client = __esm({
|
|
|
10711
11022
|
});
|
|
10712
11023
|
|
|
10713
11024
|
// ../../packages/local-store/dist/last-pull.js
|
|
10714
|
-
import { promises as
|
|
10715
|
-
import
|
|
11025
|
+
import { promises as fs21 } from "fs";
|
|
11026
|
+
import path17 from "path";
|
|
10716
11027
|
function lastPullPath(projectRoot) {
|
|
10717
|
-
return
|
|
11028
|
+
return path17.join(cacheDir(projectRoot), "last-pull.json");
|
|
10718
11029
|
}
|
|
10719
11030
|
async function readLastPull(projectRoot) {
|
|
10720
11031
|
try {
|
|
10721
|
-
const raw = await
|
|
11032
|
+
const raw = await fs21.readFile(lastPullPath(projectRoot), "utf8");
|
|
10722
11033
|
const parsed = JSON.parse(raw);
|
|
10723
11034
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
10724
11035
|
return parsed;
|
|
@@ -11004,7 +11315,7 @@ var init_frontmatter2 = __esm({
|
|
|
11004
11315
|
});
|
|
11005
11316
|
|
|
11006
11317
|
// ../../packages/local-store/dist/pull/write.js
|
|
11007
|
-
import { promises as
|
|
11318
|
+
import { promises as fs22 } from "fs";
|
|
11008
11319
|
async function loadLocalIndex(projectRoot, type2) {
|
|
11009
11320
|
const byId = /* @__PURE__ */ new Map();
|
|
11010
11321
|
const bySlug = /* @__PURE__ */ new Map();
|
|
@@ -11033,7 +11344,7 @@ async function classifyExisting(projectRoot, type2, cloudId, localSlug, targetSl
|
|
|
11033
11344
|
if (localSlug !== targetSlug)
|
|
11034
11345
|
return "untracked";
|
|
11035
11346
|
const lastSynced = localIndex.lastPushById[cloudId];
|
|
11036
|
-
const bytes = await
|
|
11347
|
+
const bytes = await fs22.readFile(entityFilePath(projectRoot, type2, localSlug), "utf8");
|
|
11037
11348
|
return lastSynced && hashNormalizedContent(bytes) === lastSynced ? "fast_forward" : "diverged";
|
|
11038
11349
|
}
|
|
11039
11350
|
async function writeOrSkip(projectRoot, type2, cloudId, slug, frontmatter, body, localIndex, mode, cloudVersion) {
|
|
@@ -11094,13 +11405,13 @@ async function writeOrSkip(projectRoot, type2, cloudId, slug, frontmatter, body,
|
|
|
11094
11405
|
}
|
|
11095
11406
|
if (existingSlugForId && existingSlugForId !== slug) {
|
|
11096
11407
|
try {
|
|
11097
|
-
await
|
|
11408
|
+
await fs22.unlink(entityFilePath(projectRoot, type2, existingSlugForId));
|
|
11098
11409
|
localIndex.bySlug.delete(existingSlugForId);
|
|
11099
11410
|
} catch {
|
|
11100
11411
|
}
|
|
11101
11412
|
}
|
|
11102
11413
|
await writeEntity(projectRoot, type2, frontmatter, text);
|
|
11103
|
-
const bytes = await
|
|
11414
|
+
const bytes = await fs22.readFile(entityFilePath(projectRoot, type2, slug), "utf8");
|
|
11104
11415
|
const hash = hashNormalizedContent(bytes);
|
|
11105
11416
|
localIndex.byId.set(cloudId, slug);
|
|
11106
11417
|
localIndex.bySlug.set(slug, cloudId);
|
|
@@ -11154,7 +11465,7 @@ var init_write = __esm({
|
|
|
11154
11465
|
});
|
|
11155
11466
|
|
|
11156
11467
|
// ../../packages/local-store/dist/pull/entities.js
|
|
11157
|
-
import { promises as
|
|
11468
|
+
import { promises as fs23 } from "fs";
|
|
11158
11469
|
function buildContextFrontmatter(payload) {
|
|
11159
11470
|
const cloudCtx = payload.context ?? {};
|
|
11160
11471
|
const glossary = (payload.glossary ?? []).map((g) => ({
|
|
@@ -11209,7 +11520,7 @@ async function pullContext(projectRoot, cloud, projectId, mode, result) {
|
|
|
11209
11520
|
const frontmatter = buildContextFrontmatter(payload);
|
|
11210
11521
|
let hasLocal = false;
|
|
11211
11522
|
try {
|
|
11212
|
-
await
|
|
11523
|
+
await fs23.access(contextPath(projectRoot));
|
|
11213
11524
|
hasLocal = true;
|
|
11214
11525
|
} catch {
|
|
11215
11526
|
hasLocal = false;
|
|
@@ -11239,7 +11550,7 @@ async function pullContext(projectRoot, cloud, projectId, mode, result) {
|
|
|
11239
11550
|
frontmatter,
|
|
11240
11551
|
notes_body: existingNotes
|
|
11241
11552
|
});
|
|
11242
|
-
const bytes = await
|
|
11553
|
+
const bytes = await fs23.readFile(contextPath(projectRoot), "utf8");
|
|
11243
11554
|
const hash = hashNormalizedContent(bytes);
|
|
11244
11555
|
await recordLastPull(projectRoot, "context:root", hash);
|
|
11245
11556
|
await recordLastPush(projectRoot, "context:root", hash);
|
|
@@ -11769,6 +12080,7 @@ var init_dist2 = __esm({
|
|
|
11769
12080
|
init_prune();
|
|
11770
12081
|
init_fix_state();
|
|
11771
12082
|
init_revisit();
|
|
12083
|
+
init_explore_map();
|
|
11772
12084
|
init_walkthrough_canonical2();
|
|
11773
12085
|
init_journey_diff();
|
|
11774
12086
|
init_finding_match();
|
|
@@ -11800,15 +12112,15 @@ function resolveApiUrl(configured) {
|
|
|
11800
12112
|
assertSecureApiUrl(url, configured ? "API URL" : "IAMU_API_URL");
|
|
11801
12113
|
return url;
|
|
11802
12114
|
}
|
|
11803
|
-
function parseStore(raw,
|
|
12115
|
+
function parseStore(raw, path30) {
|
|
11804
12116
|
let parsed;
|
|
11805
12117
|
try {
|
|
11806
12118
|
parsed = JSON.parse(raw);
|
|
11807
12119
|
} catch (err) {
|
|
11808
|
-
throw new CredentialsFileError(
|
|
12120
|
+
throw new CredentialsFileError(path30, "not valid JSON", err);
|
|
11809
12121
|
}
|
|
11810
12122
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
11811
|
-
throw new CredentialsFileError(
|
|
12123
|
+
throw new CredentialsFileError(path30, "expected a JSON object");
|
|
11812
12124
|
}
|
|
11813
12125
|
return parsed;
|
|
11814
12126
|
}
|
|
@@ -11816,26 +12128,26 @@ function isNotFound(err) {
|
|
|
11816
12128
|
return typeof err === "object" && err !== null && err.code === "ENOENT";
|
|
11817
12129
|
}
|
|
11818
12130
|
async function readCredentialsStore() {
|
|
11819
|
-
const
|
|
12131
|
+
const path30 = getCredentialsPath();
|
|
11820
12132
|
let raw;
|
|
11821
12133
|
try {
|
|
11822
|
-
raw = await readFile(
|
|
12134
|
+
raw = await readFile(path30, "utf-8");
|
|
11823
12135
|
} catch (err) {
|
|
11824
12136
|
if (isNotFound(err)) return {};
|
|
11825
12137
|
warnUnreadableStoreOnce(err);
|
|
11826
12138
|
throw err;
|
|
11827
12139
|
}
|
|
11828
12140
|
try {
|
|
11829
|
-
return parseStore(raw,
|
|
12141
|
+
return parseStore(raw, path30);
|
|
11830
12142
|
} catch (err) {
|
|
11831
12143
|
warnUnreadableStoreOnce(err);
|
|
11832
12144
|
throw err;
|
|
11833
12145
|
}
|
|
11834
12146
|
}
|
|
11835
12147
|
async function writeCredentialsStore(store) {
|
|
11836
|
-
const
|
|
11837
|
-
await mkdir(dirname(
|
|
11838
|
-
await atomicWriteFile(
|
|
12148
|
+
const path30 = getCredentialsPath();
|
|
12149
|
+
await mkdir(dirname(path30), { recursive: true, mode: 448 });
|
|
12150
|
+
await atomicWriteFile(path30, `${JSON.stringify(store, null, 2)}
|
|
11839
12151
|
`, {
|
|
11840
12152
|
mode: 384
|
|
11841
12153
|
});
|
|
@@ -11855,16 +12167,16 @@ function resolveApiKey(apiUrl) {
|
|
|
11855
12167
|
} catch {
|
|
11856
12168
|
return null;
|
|
11857
12169
|
}
|
|
11858
|
-
const
|
|
12170
|
+
const path30 = getCredentialsPath();
|
|
11859
12171
|
let raw;
|
|
11860
12172
|
try {
|
|
11861
|
-
raw = readFileSync(
|
|
12173
|
+
raw = readFileSync(path30, "utf-8");
|
|
11862
12174
|
} catch (err) {
|
|
11863
12175
|
if (!isNotFound(err)) warnUnreadableStoreOnce(err);
|
|
11864
12176
|
return null;
|
|
11865
12177
|
}
|
|
11866
12178
|
try {
|
|
11867
|
-
const entry = parseStore(raw,
|
|
12179
|
+
const entry = parseStore(raw, path30)[url];
|
|
11868
12180
|
if (typeof entry !== "object" || entry === null) return null;
|
|
11869
12181
|
const token = entry.token;
|
|
11870
12182
|
return typeof token === "string" && token ? token : null;
|
|
@@ -11896,19 +12208,19 @@ var init_credentials = __esm({
|
|
|
11896
12208
|
"use strict";
|
|
11897
12209
|
init_dist2();
|
|
11898
12210
|
init_dist();
|
|
11899
|
-
DEFAULT_API_URL = "https://
|
|
12211
|
+
DEFAULT_API_URL = "https://app.iamu.dev";
|
|
11900
12212
|
CredentialsFileError = class extends IamuConfigError {
|
|
11901
12213
|
/**
|
|
11902
12214
|
* @param path - The unreadable credentials file.
|
|
11903
12215
|
* @param reason - What is wrong with it.
|
|
11904
12216
|
* @param cause - Underlying parse error, if any.
|
|
11905
12217
|
*/
|
|
11906
|
-
constructor(
|
|
12218
|
+
constructor(path30, reason, cause) {
|
|
11907
12219
|
super(
|
|
11908
|
-
`Credentials file ${
|
|
12220
|
+
`Credentials file ${path30} is corrupt (${reason}). Fix it by hand, or delete it and run \`iamu auth login\` again.`,
|
|
11909
12221
|
{
|
|
11910
12222
|
cause,
|
|
11911
|
-
suggestedAction: `Delete ${
|
|
12223
|
+
suggestedAction: `Delete ${path30} and run \`iamu auth login\`, or set IAMU_TOKEN.`
|
|
11912
12224
|
}
|
|
11913
12225
|
);
|
|
11914
12226
|
}
|
|
@@ -11993,7 +12305,7 @@ async function resolveCloudProject(opts) {
|
|
|
11993
12305
|
commandError("Not cloud-linked. Run `iamu auth login` to connect.", opts);
|
|
11994
12306
|
return null;
|
|
11995
12307
|
}
|
|
11996
|
-
const apiUrl = ctx.config.cloud_api_url ??
|
|
12308
|
+
const apiUrl = ctx.config.cloud_api_url ?? DEFAULT_API_URL;
|
|
11997
12309
|
try {
|
|
11998
12310
|
assertSecureApiUrl(apiUrl, "cloud_api_url");
|
|
11999
12311
|
} catch (err) {
|
|
@@ -12287,15 +12599,6 @@ function mcpJson(cmd) {
|
|
|
12287
12599
|
2
|
|
12288
12600
|
);
|
|
12289
12601
|
}
|
|
12290
|
-
function vscodeMcpJson(cmd) {
|
|
12291
|
-
return JSON.stringify(
|
|
12292
|
-
{
|
|
12293
|
-
servers: { iamu: { type: "stdio", command: cmd[0], args: cmd.slice(1) } }
|
|
12294
|
-
},
|
|
12295
|
-
null,
|
|
12296
|
-
2
|
|
12297
|
-
);
|
|
12298
|
-
}
|
|
12299
12602
|
function detectHosts(projectRoot) {
|
|
12300
12603
|
return HOST_ADAPTERS.filter(
|
|
12301
12604
|
(host) => host.detectionPaths.some((p) => existsSync3(join3(projectRoot, p)))
|
|
@@ -12344,13 +12647,17 @@ var init_registry = __esm({
|
|
|
12344
12647
|
{
|
|
12345
12648
|
name: "GitHub Copilot",
|
|
12346
12649
|
value: "github-copilot",
|
|
12347
|
-
detectionPaths: [
|
|
12348
|
-
|
|
12349
|
-
|
|
12350
|
-
|
|
12351
|
-
|
|
12352
|
-
|
|
12353
|
-
|
|
12650
|
+
detectionPaths: [
|
|
12651
|
+
".github/copilot-instructions.md",
|
|
12652
|
+
".github/skills",
|
|
12653
|
+
".copilot"
|
|
12654
|
+
],
|
|
12655
|
+
supportsSkills: true,
|
|
12656
|
+
getSkillsDir: (root) => join3(root, ".github", "skills"),
|
|
12657
|
+
getCommandPath: (root, id) => join3(root, ".github", "skills", `iamu-${id}`, "SKILL.md"),
|
|
12658
|
+
formatCommand: richFormat,
|
|
12659
|
+
getMcpConfigPath: (root) => join3(root, ".mcp.json"),
|
|
12660
|
+
formatMcpConfig: mcpJson
|
|
12354
12661
|
},
|
|
12355
12662
|
{
|
|
12356
12663
|
name: "Cline",
|
|
@@ -12388,10 +12695,10 @@ var init_registry = __esm({
|
|
|
12388
12695
|
{
|
|
12389
12696
|
name: "Antigravity",
|
|
12390
12697
|
value: "antigravity",
|
|
12391
|
-
detectionPaths: [".
|
|
12698
|
+
detectionPaths: [".agent", ".agents", "AGENTS.md", ".gemini", "GEMINI.md"],
|
|
12392
12699
|
supportsSkills: true,
|
|
12393
|
-
getSkillsDir: (root) => existsSync3(join3(root, ".
|
|
12394
|
-
getCommandPath: (root, id) => existsSync3(join3(root, ".
|
|
12700
|
+
getSkillsDir: (root) => existsSync3(join3(root, ".agents")) ? join3(root, ".agents", "skills") : join3(root, ".agent", "skills"),
|
|
12701
|
+
getCommandPath: (root, id) => existsSync3(join3(root, ".agents")) ? join3(root, ".agents", "skills", `iamu-${id}`, "SKILL.md") : join3(root, ".agent", "skills", `iamu-${id}`, "SKILL.md"),
|
|
12395
12702
|
formatCommand: richFormat,
|
|
12396
12703
|
getMcpConfigPath: (root) => join3(root, ".mcp.json"),
|
|
12397
12704
|
formatMcpConfig: mcpJson
|
|
@@ -12525,9 +12832,17 @@ async function needsWrite(filePath, content) {
|
|
|
12525
12832
|
return true;
|
|
12526
12833
|
}
|
|
12527
12834
|
}
|
|
12528
|
-
function isCliWrittenSkill(
|
|
12835
|
+
function isCliWrittenSkill(path30) {
|
|
12836
|
+
try {
|
|
12837
|
+
return /^generatedBy:/m.test(readFileSync3(path30, "utf-8"));
|
|
12838
|
+
} catch {
|
|
12839
|
+
return false;
|
|
12840
|
+
}
|
|
12841
|
+
}
|
|
12842
|
+
function isCliWrittenPrompt(path30) {
|
|
12529
12843
|
try {
|
|
12530
|
-
|
|
12844
|
+
const content = readFileSync3(path30, "utf-8");
|
|
12845
|
+
return content.includes("iamu") || /^generatedBy:/m.test(content);
|
|
12531
12846
|
} catch {
|
|
12532
12847
|
return false;
|
|
12533
12848
|
}
|
|
@@ -12538,14 +12853,41 @@ function planSkillFiles(hosts, projectRoot, version2) {
|
|
|
12538
12853
|
for (const host of hosts) {
|
|
12539
12854
|
if (pluginProvidesSkills(host, projectRoot)) {
|
|
12540
12855
|
for (const name of skillTemplates.keys()) {
|
|
12541
|
-
const
|
|
12542
|
-
if (isCliWrittenSkill(
|
|
12856
|
+
const path30 = join4(host.getSkillsDir(projectRoot), name, "SKILL.md");
|
|
12857
|
+
if (isCliWrittenSkill(path30)) files.push({ host, path: path30, content: null });
|
|
12543
12858
|
}
|
|
12544
12859
|
} else if (host.supportsSkills) {
|
|
12545
12860
|
const skillsDir = host.getSkillsDir(projectRoot);
|
|
12546
12861
|
for (const [name, content] of skillTemplates) {
|
|
12547
12862
|
files.push({ host, path: join4(skillsDir, name, "SKILL.md"), content });
|
|
12548
12863
|
}
|
|
12864
|
+
if (host.value === "antigravity") {
|
|
12865
|
+
for (const name of skillTemplates.keys()) {
|
|
12866
|
+
const legacyPath = join4(
|
|
12867
|
+
projectRoot,
|
|
12868
|
+
".gemini",
|
|
12869
|
+
"skills",
|
|
12870
|
+
name,
|
|
12871
|
+
"SKILL.md"
|
|
12872
|
+
);
|
|
12873
|
+
if (isCliWrittenSkill(legacyPath)) {
|
|
12874
|
+
files.push({ host, path: legacyPath, content: null });
|
|
12875
|
+
}
|
|
12876
|
+
}
|
|
12877
|
+
}
|
|
12878
|
+
if (host.value === "github-copilot") {
|
|
12879
|
+
for (const cmd of commandsFromSkills(skillTemplates)) {
|
|
12880
|
+
const legacyPrompt = join4(
|
|
12881
|
+
projectRoot,
|
|
12882
|
+
".github",
|
|
12883
|
+
"prompts",
|
|
12884
|
+
`iamu-${cmd.id}.prompt.md`
|
|
12885
|
+
);
|
|
12886
|
+
if (isCliWrittenPrompt(legacyPrompt)) {
|
|
12887
|
+
files.push({ host, path: legacyPrompt, content: null });
|
|
12888
|
+
}
|
|
12889
|
+
}
|
|
12890
|
+
}
|
|
12549
12891
|
} else {
|
|
12550
12892
|
for (const cmd of commandsFromSkills(skillTemplates)) {
|
|
12551
12893
|
files.push({
|
|
@@ -12570,6 +12912,15 @@ function detectInstalledHosts(projectRoot) {
|
|
|
12570
12912
|
const skillDir = join4(host.getSkillsDir(projectRoot), "iamu-setup");
|
|
12571
12913
|
if (existsSync5(skillDir)) return true;
|
|
12572
12914
|
}
|
|
12915
|
+
if (host.value === "github-copilot") {
|
|
12916
|
+
const legacyPrompt = join4(
|
|
12917
|
+
projectRoot,
|
|
12918
|
+
".github",
|
|
12919
|
+
"prompts",
|
|
12920
|
+
"iamu-setup.prompt.md"
|
|
12921
|
+
);
|
|
12922
|
+
if (existsSync5(legacyPrompt)) return true;
|
|
12923
|
+
}
|
|
12573
12924
|
return false;
|
|
12574
12925
|
});
|
|
12575
12926
|
}
|
|
@@ -12636,7 +12987,13 @@ async function installSkillsForHosts(hosts, projectRoot, version2, opts) {
|
|
|
12636
12987
|
let hostUpdated = 0;
|
|
12637
12988
|
let hostTotal = 0;
|
|
12638
12989
|
for (const f of planned) {
|
|
12639
|
-
if (f.host !== host
|
|
12990
|
+
if (f.host !== host) continue;
|
|
12991
|
+
if (f.content === null) {
|
|
12992
|
+
await rm(f.path, { force: true });
|
|
12993
|
+
await rmdir(dirname3(f.path)).catch(() => {
|
|
12994
|
+
});
|
|
12995
|
+
continue;
|
|
12996
|
+
}
|
|
12640
12997
|
hostTotal++;
|
|
12641
12998
|
if (host.supportsSkills) totalSkills++;
|
|
12642
12999
|
else totalCommands++;
|
|
@@ -13482,12 +13839,24 @@ async function browserLogin(apiUrl, opts, t) {
|
|
|
13482
13839
|
chalk8.red(`
|
|
13483
13840
|
Auth session expired or failed (HTTP ${result.status}).`)
|
|
13484
13841
|
);
|
|
13842
|
+
console.error(
|
|
13843
|
+
chalk8.dim(" Re-run `iamu auth login` to start a new login session.\n")
|
|
13844
|
+
);
|
|
13485
13845
|
process.exitCode = 1;
|
|
13486
13846
|
return null;
|
|
13487
13847
|
}
|
|
13488
13848
|
if (result.kind === "timeout") {
|
|
13489
13849
|
s.stop("Authentication timed out.");
|
|
13490
|
-
console.error(
|
|
13850
|
+
console.error(
|
|
13851
|
+
chalk8.red(
|
|
13852
|
+
"\n Authentication timed out (no approval received within 2 minutes)."
|
|
13853
|
+
)
|
|
13854
|
+
);
|
|
13855
|
+
console.error(
|
|
13856
|
+
chalk8.dim(
|
|
13857
|
+
" \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"
|
|
13858
|
+
)
|
|
13859
|
+
);
|
|
13491
13860
|
process.exitCode = 1;
|
|
13492
13861
|
return null;
|
|
13493
13862
|
}
|
|
@@ -13513,7 +13882,12 @@ async function startAuthSession(apiUrl, timeoutMs) {
|
|
|
13513
13882
|
if (!res.ok) {
|
|
13514
13883
|
console.error(
|
|
13515
13884
|
chalk8.red(
|
|
13516
|
-
` Failed to start auth session: ${res.text || `HTTP ${res.status}`}`
|
|
13885
|
+
` Failed to start auth session at ${apiUrl}: ${res.text || `HTTP ${res.status}`}`
|
|
13886
|
+
)
|
|
13887
|
+
);
|
|
13888
|
+
console.error(
|
|
13889
|
+
chalk8.dim(
|
|
13890
|
+
" \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"
|
|
13517
13891
|
)
|
|
13518
13892
|
);
|
|
13519
13893
|
process.exitCode = 1;
|
|
@@ -13527,7 +13901,13 @@ async function startAuthSession(apiUrl, timeoutMs) {
|
|
|
13527
13901
|
return { sessionId: data.sessionId, loginUrl: data.loginUrl };
|
|
13528
13902
|
} catch (err) {
|
|
13529
13903
|
console.error(
|
|
13530
|
-
chalk8.red(
|
|
13904
|
+
chalk8.red(` Cannot reach Iamu Cloud (${apiUrl}).`) + chalk8.dim(`
|
|
13905
|
+
Reason: ${errorMessage(err)}`)
|
|
13906
|
+
);
|
|
13907
|
+
console.error(
|
|
13908
|
+
chalk8.dim(
|
|
13909
|
+
"\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"
|
|
13910
|
+
)
|
|
13531
13911
|
);
|
|
13532
13912
|
process.exitCode = 1;
|
|
13533
13913
|
return null;
|
|
@@ -13994,7 +14374,7 @@ import {
|
|
|
13994
14374
|
readlinkSync,
|
|
13995
14375
|
realpathSync as realpathSync2
|
|
13996
14376
|
} from "fs";
|
|
13997
|
-
import
|
|
14377
|
+
import path18 from "path";
|
|
13998
14378
|
function git(args, cwd) {
|
|
13999
14379
|
return execFileSync("git", args, {
|
|
14000
14380
|
cwd,
|
|
@@ -14047,8 +14427,8 @@ function detectWorktreeState(projectRoot, iamuDirName = IAMU_DIR) {
|
|
|
14047
14427
|
const top = git(["rev-parse", "--show-toplevel"], projectRoot).toString("utf8").trim();
|
|
14048
14428
|
if (!top) return void 0;
|
|
14049
14429
|
const realTop = realpathSync2(top);
|
|
14050
|
-
const realIamu =
|
|
14051
|
-
const iamuRel =
|
|
14430
|
+
const realIamu = path18.join(realpathSync2(projectRoot), iamuDirName);
|
|
14431
|
+
const iamuRel = path18.relative(realTop, realIamu).split(path18.sep).join("/");
|
|
14052
14432
|
const pathspec = iamuRel.startsWith("..") ? [":(top)"] : [":(top)", `:(top,exclude,literal)${iamuRel}`];
|
|
14053
14433
|
const status2 = git(
|
|
14054
14434
|
[
|
|
@@ -14078,7 +14458,7 @@ function detectWorktreeState(projectRoot, iamuDirName = IAMU_DIR) {
|
|
|
14078
14458
|
);
|
|
14079
14459
|
for (const file of sorted.slice(0, MAX_UNTRACKED_HASHED)) {
|
|
14080
14460
|
const rel = file.replace(/\/$/, "");
|
|
14081
|
-
hash.update(`${file}\0${hashFile(
|
|
14461
|
+
hash.update(`${file}\0${hashFile(path18.join(realTop, rel))}
|
|
14082
14462
|
`);
|
|
14083
14463
|
}
|
|
14084
14464
|
if (sorted.length > MAX_UNTRACKED_HASHED) {
|
|
@@ -14249,15 +14629,29 @@ __export(start_run_exports, {
|
|
|
14249
14629
|
formatBuild: () => formatBuild,
|
|
14250
14630
|
formatViewport: () => formatViewport,
|
|
14251
14631
|
resolveStartUrl: () => resolveStartUrl,
|
|
14252
|
-
startRun: () => startRun
|
|
14632
|
+
startRun: () => startRun,
|
|
14633
|
+
testabilityTarget: () => testabilityTarget
|
|
14253
14634
|
});
|
|
14254
|
-
import
|
|
14635
|
+
import fs24 from "fs/promises";
|
|
14636
|
+
import path19 from "path";
|
|
14255
14637
|
import chalk11 from "chalk";
|
|
14256
|
-
function
|
|
14257
|
-
|
|
14638
|
+
async function testabilityTarget(projectRoot) {
|
|
14639
|
+
try {
|
|
14640
|
+
const raw = await fs24.readFile(
|
|
14641
|
+
path19.join(projectRoot, "iamu", "testability.md"),
|
|
14642
|
+
"utf8"
|
|
14643
|
+
);
|
|
14644
|
+
const { frontmatter } = parseFrontmatter(raw);
|
|
14645
|
+
return typeof frontmatter.target_url === "string" && frontmatter.target_url.trim().length > 0 ? frontmatter.target_url.trim() : null;
|
|
14646
|
+
} catch {
|
|
14647
|
+
return null;
|
|
14648
|
+
}
|
|
14649
|
+
}
|
|
14650
|
+
function resolveStartUrl(target, scenario, fallbackTarget) {
|
|
14651
|
+
const base = target ?? scenario.target_url ?? fallbackTarget ?? null;
|
|
14258
14652
|
if (!base) {
|
|
14259
14653
|
return {
|
|
14260
|
-
error: "No target \u2014 pass --target <url>,
|
|
14654
|
+
error: "No target \u2014 pass --target <url>, set target_url on the scenario, or define target_url in iamu/testability.md."
|
|
14261
14655
|
};
|
|
14262
14656
|
}
|
|
14263
14657
|
let parsed;
|
|
@@ -14357,7 +14751,8 @@ async function startRun(scenarioSlug, opts) {
|
|
|
14357
14751
|
);
|
|
14358
14752
|
return;
|
|
14359
14753
|
}
|
|
14360
|
-
const
|
|
14754
|
+
const fallbackTarget = await testabilityTarget(projectRoot);
|
|
14755
|
+
const start2 = resolveStartUrl(opts.target, fm, fallbackTarget);
|
|
14361
14756
|
if ("error" in start2) {
|
|
14362
14757
|
commandError(start2.error, opts);
|
|
14363
14758
|
return;
|
|
@@ -14391,6 +14786,18 @@ async function startRun(scenarioSlug, opts) {
|
|
|
14391
14786
|
}
|
|
14392
14787
|
const build = detectBuild({ cwd: projectRoot, label: opts.build });
|
|
14393
14788
|
const environment = resolveEnvironment(opts.env, start2.url);
|
|
14789
|
+
const cfg = await readConfig(projectRoot).catch(() => null);
|
|
14790
|
+
const walker = opts.walker ?? process.env.IAMU_WALKER_MODEL ?? cfg?.models?.walker ?? "fast";
|
|
14791
|
+
const judge = opts.judge ?? process.env.IAMU_JUDGE_MODEL ?? cfg?.models?.judge ?? "thorough";
|
|
14792
|
+
const matcher = opts.matcher ?? process.env.IAMU_MATCHER_MODEL ?? cfg?.models?.matcher ?? "thorough";
|
|
14793
|
+
const models = {
|
|
14794
|
+
walker,
|
|
14795
|
+
judge,
|
|
14796
|
+
matcher,
|
|
14797
|
+
walker_tier: ["fast", "thorough", "inherit"].includes(walker) ? walker : "custom",
|
|
14798
|
+
judge_tier: ["fast", "thorough", "inherit"].includes(judge) ? judge : "custom",
|
|
14799
|
+
matcher_tier: ["fast", "thorough", "inherit"].includes(matcher) ? matcher : "custom"
|
|
14800
|
+
};
|
|
14394
14801
|
let revisit = [];
|
|
14395
14802
|
try {
|
|
14396
14803
|
revisit = await computeRevisitPlaces(projectRoot, scenarioSlug, start2.url);
|
|
@@ -14408,13 +14815,14 @@ async function startRun(scenarioSlug, opts) {
|
|
|
14408
14815
|
status: "running",
|
|
14409
14816
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14410
14817
|
completed_at: null,
|
|
14818
|
+
models,
|
|
14411
14819
|
...build ? { build } : {},
|
|
14412
14820
|
...environment ? { environment } : {},
|
|
14413
14821
|
...revisit.length > 0 ? { revisit } : {},
|
|
14414
14822
|
...viewport ? { viewport } : {}
|
|
14415
14823
|
};
|
|
14416
14824
|
await atomicWriteFile(
|
|
14417
|
-
|
|
14825
|
+
path19.join(run.absPath, "walkthrough.json"),
|
|
14418
14826
|
JSON.stringify(manifest, null, 2) + "\n"
|
|
14419
14827
|
);
|
|
14420
14828
|
if (opts.json) {
|
|
@@ -14422,13 +14830,14 @@ async function startRun(scenarioSlug, opts) {
|
|
|
14422
14830
|
JSON.stringify(
|
|
14423
14831
|
{
|
|
14424
14832
|
run_id: run.runId,
|
|
14425
|
-
path:
|
|
14833
|
+
path: path19.relative(projectRoot, run.absPath),
|
|
14426
14834
|
scenario_slug: scenarioSlug,
|
|
14427
14835
|
target_url: start2.url,
|
|
14428
14836
|
target_reachable: targetReachable,
|
|
14429
14837
|
...targetWarning ? { target_warning: targetWarning } : {},
|
|
14430
14838
|
persona_slug: fm.persona,
|
|
14431
14839
|
outline_slug: fm.outline,
|
|
14840
|
+
models,
|
|
14432
14841
|
build: build ?? null,
|
|
14433
14842
|
environment: environment ?? null,
|
|
14434
14843
|
revisit,
|
|
@@ -14447,6 +14856,9 @@ async function startRun(scenarioSlug, opts) {
|
|
|
14447
14856
|
console.log(` ${chalk11.dim("persona:")} ${fm.persona}`);
|
|
14448
14857
|
console.log(` ${chalk11.dim("outline:")} ${fm.outline}`);
|
|
14449
14858
|
console.log(` ${chalk11.dim("target:")} ${start2.url}`);
|
|
14859
|
+
console.log(
|
|
14860
|
+
` ${chalk11.dim("models:")} walker: ${chalk11.cyan(walker)} \xB7 judge: ${chalk11.cyan(judge)} \xB7 matcher: ${chalk11.cyan(matcher)}`
|
|
14861
|
+
);
|
|
14450
14862
|
if (targetWarning) {
|
|
14451
14863
|
console.log(` ${symbols.warning} ${chalk11.yellow(targetWarning)}`);
|
|
14452
14864
|
}
|
|
@@ -14466,7 +14878,7 @@ async function startRun(scenarioSlug, opts) {
|
|
|
14466
14878
|
);
|
|
14467
14879
|
}
|
|
14468
14880
|
console.log(
|
|
14469
|
-
` ${chalk11.dim("path:")} ${
|
|
14881
|
+
` ${chalk11.dim("path:")} ${path19.relative(projectRoot, run.absPath)}`
|
|
14470
14882
|
);
|
|
14471
14883
|
console.log();
|
|
14472
14884
|
console.log(` ${chalk11.bold("Next steps for blind execution:")}`);
|
|
@@ -14507,8 +14919,8 @@ var report_exports = {};
|
|
|
14507
14919
|
__export(report_exports, {
|
|
14508
14920
|
report: () => report
|
|
14509
14921
|
});
|
|
14510
|
-
import { promises as
|
|
14511
|
-
import
|
|
14922
|
+
import { promises as fs25 } from "fs";
|
|
14923
|
+
import path20 from "path";
|
|
14512
14924
|
import chalk12 from "chalk";
|
|
14513
14925
|
async function report(scenario, opts) {
|
|
14514
14926
|
const ctx = await resolveProject(opts);
|
|
@@ -14527,8 +14939,8 @@ async function report(scenario, opts) {
|
|
|
14527
14939
|
for (const rid of runs.reverse()) {
|
|
14528
14940
|
try {
|
|
14529
14941
|
const dir = runDir(projectRoot, rid);
|
|
14530
|
-
const mRaw = await
|
|
14531
|
-
|
|
14942
|
+
const mRaw = await fs25.readFile(
|
|
14943
|
+
path20.join(dir, "walkthrough.json"),
|
|
14532
14944
|
"utf8"
|
|
14533
14945
|
);
|
|
14534
14946
|
const manifest = JSON.parse(mRaw);
|
|
@@ -14536,8 +14948,8 @@ async function report(scenario, opts) {
|
|
|
14536
14948
|
if (scenario && manifest.scenario_slug !== scenario) continue;
|
|
14537
14949
|
let canonical = null;
|
|
14538
14950
|
try {
|
|
14539
|
-
const cRaw = await
|
|
14540
|
-
|
|
14951
|
+
const cRaw = await fs25.readFile(
|
|
14952
|
+
path20.join(dir, "canonical.json"),
|
|
14541
14953
|
"utf8"
|
|
14542
14954
|
);
|
|
14543
14955
|
canonical = JSON.parse(cRaw);
|
|
@@ -15744,12 +16156,13 @@ var init_serve = __esm({
|
|
|
15744
16156
|
// src/commands/init.ts
|
|
15745
16157
|
var init_exports = {};
|
|
15746
16158
|
__export(init_exports, {
|
|
15747
|
-
init: () => init
|
|
16159
|
+
init: () => init,
|
|
16160
|
+
setupCommand: () => setupCommand
|
|
15748
16161
|
});
|
|
15749
16162
|
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
|
|
15750
16163
|
import { mkdir as mkdir4, writeFile as writeFile3 } from "fs/promises";
|
|
15751
16164
|
import { basename as basename2, join as join5 } from "path";
|
|
15752
|
-
import { isCancel as isCancel3, multiselect } from "@clack/prompts";
|
|
16165
|
+
import { isCancel as isCancel3, multiselect, select as select3 } from "@clack/prompts";
|
|
15753
16166
|
import chalk17 from "chalk";
|
|
15754
16167
|
function loadPromptTemplates() {
|
|
15755
16168
|
const templates = /* @__PURE__ */ new Map();
|
|
@@ -15771,9 +16184,9 @@ function loadPromptTemplates() {
|
|
|
15771
16184
|
return templates;
|
|
15772
16185
|
}
|
|
15773
16186
|
function setupCommand(projectRoot, hosts) {
|
|
15774
|
-
if (!iamuPluginEnabled(projectRoot)) return "iamu-setup";
|
|
16187
|
+
if (!iamuPluginEnabled(projectRoot)) return "/iamu-setup";
|
|
15775
16188
|
if (hosts.every((h) => h.value === "claude")) return "/iamu:init";
|
|
15776
|
-
return "iamu-setup (Claude Code: /iamu:init)";
|
|
16189
|
+
return "/iamu-setup (Claude Code: /iamu:init)";
|
|
15777
16190
|
}
|
|
15778
16191
|
async function init(opts) {
|
|
15779
16192
|
const options = typeof opts === "string" ? { name: opts } : opts ?? {};
|
|
@@ -15793,10 +16206,10 @@ async function init(opts) {
|
|
|
15793
16206
|
}
|
|
15794
16207
|
let freshInit = false;
|
|
15795
16208
|
if (existsSync7(join5(iamuDir2, "config.json"))) {
|
|
15796
|
-
const
|
|
16209
|
+
const config2 = await readConfig(projectRoot);
|
|
15797
16210
|
if (!options.json) {
|
|
15798
16211
|
console.log(
|
|
15799
|
-
chalk17.yellow(" iamu/ already exists") + chalk17.dim(` (${
|
|
16212
|
+
chalk17.yellow(" iamu/ already exists") + chalk17.dim(` (${config2?.project_name ?? "unknown"})`)
|
|
15800
16213
|
);
|
|
15801
16214
|
}
|
|
15802
16215
|
} else {
|
|
@@ -15813,6 +16226,35 @@ async function init(opts) {
|
|
|
15813
16226
|
""
|
|
15814
16227
|
)
|
|
15815
16228
|
);
|
|
16229
|
+
const testabilityPath = join5(iamuDir2, "testability.md");
|
|
16230
|
+
if (!existsSync7(testabilityPath)) {
|
|
16231
|
+
await writeFile3(
|
|
16232
|
+
testabilityPath,
|
|
16233
|
+
serializeFrontmatter(
|
|
16234
|
+
{
|
|
16235
|
+
status: "draft",
|
|
16236
|
+
target_url: "http://localhost:3000"
|
|
16237
|
+
},
|
|
16238
|
+
`# Project Testability & Environment
|
|
16239
|
+
|
|
16240
|
+
Target URL and execution harness for walkthroughs.
|
|
16241
|
+
Audited and populated during /iamu:init.
|
|
16242
|
+
|
|
16243
|
+
## Target & Run Instructions
|
|
16244
|
+
- Default URL: http://localhost:3000
|
|
16245
|
+
|
|
16246
|
+
## Reachable Surfaces
|
|
16247
|
+
(Run /iamu:init to discover testable routes)
|
|
16248
|
+
|
|
16249
|
+
## Mocks & Data Fixtures
|
|
16250
|
+
(Run /iamu:init to inspect existing harnesses)
|
|
16251
|
+
|
|
16252
|
+
## Untestable Boundaries
|
|
16253
|
+
(None documented yet)
|
|
16254
|
+
`
|
|
16255
|
+
)
|
|
16256
|
+
);
|
|
16257
|
+
}
|
|
15816
16258
|
}
|
|
15817
16259
|
const promptTemplates = loadPromptTemplates();
|
|
15818
16260
|
const promptsDir = join5(iamuDir2, ".prompts");
|
|
@@ -15869,20 +16311,57 @@ async function init(opts) {
|
|
|
15869
16311
|
version2,
|
|
15870
16312
|
{ quiet: options.json }
|
|
15871
16313
|
);
|
|
15872
|
-
|
|
15873
|
-
|
|
15874
|
-
|
|
15875
|
-
|
|
15876
|
-
|
|
15877
|
-
|
|
16314
|
+
let config = await readConfig(projectRoot);
|
|
16315
|
+
const shouldPromptCloud = isInteractive && freshInit && !config.cloud_linked && options.cloud !== false;
|
|
16316
|
+
if (options.cloud === true && !config.cloud_linked) {
|
|
16317
|
+
try {
|
|
16318
|
+
await authLogin({ yes: options.yes });
|
|
16319
|
+
config = await readConfig(projectRoot);
|
|
16320
|
+
} catch {
|
|
16321
|
+
}
|
|
16322
|
+
} else if (shouldPromptCloud) {
|
|
16323
|
+
console.log();
|
|
16324
|
+
const cloudChoice = await select3({
|
|
16325
|
+
message: "Connect this project to Iamu Cloud? (Free)",
|
|
16326
|
+
options: [
|
|
16327
|
+
{
|
|
16328
|
+
value: "connect",
|
|
16329
|
+
label: "Connect to Iamu Cloud (opens browser)",
|
|
16330
|
+
hint: "recommended \u2014 shareable run reports, step replays, team sync"
|
|
16331
|
+
},
|
|
16332
|
+
{
|
|
16333
|
+
value: "local",
|
|
16334
|
+
label: "Continue local-only",
|
|
16335
|
+
hint: "skip for now (link anytime with `iamu auth login`)"
|
|
16336
|
+
}
|
|
16337
|
+
],
|
|
16338
|
+
initialValue: "connect"
|
|
16339
|
+
});
|
|
16340
|
+
if (!isCancel3(cloudChoice) && cloudChoice === "connect") {
|
|
16341
|
+
const prevExitCode = process.exitCode;
|
|
16342
|
+
try {
|
|
16343
|
+
await authLogin({ yes: options.yes });
|
|
16344
|
+
config = await readConfig(projectRoot);
|
|
16345
|
+
} catch {
|
|
16346
|
+
process.exitCode = prevExitCode;
|
|
15878
16347
|
if (!options.json) {
|
|
15879
16348
|
console.log(
|
|
15880
|
-
|
|
16349
|
+
chalk17.dim(
|
|
16350
|
+
" Cloud linking skipped. Your local setup is complete and fully functional."
|
|
16351
|
+
)
|
|
15881
16352
|
);
|
|
15882
16353
|
}
|
|
15883
16354
|
}
|
|
15884
16355
|
}
|
|
15885
|
-
}
|
|
16356
|
+
}
|
|
16357
|
+
if (config.cloud_linked) {
|
|
16358
|
+
for (const host of selectedHosts) {
|
|
16359
|
+
const mcpPath = host.getMcpConfigPath(projectRoot);
|
|
16360
|
+
await writeSyncMcpEntry(mcpPath);
|
|
16361
|
+
if (!options.json) {
|
|
16362
|
+
console.log(` ${symbols.success} ${host.name} \u2014 iamu-sync MCP server`);
|
|
16363
|
+
}
|
|
16364
|
+
}
|
|
15886
16365
|
}
|
|
15887
16366
|
if (options.json) {
|
|
15888
16367
|
console.log(
|
|
@@ -15894,7 +16373,10 @@ async function init(opts) {
|
|
|
15894
16373
|
fresh: freshInit,
|
|
15895
16374
|
tools: selectedHosts.map((h) => h.name),
|
|
15896
16375
|
skillsCount: totalSkills,
|
|
15897
|
-
commandsCount: totalCommands
|
|
16376
|
+
commandsCount: totalCommands,
|
|
16377
|
+
cloudLinked: Boolean(config.cloud_linked),
|
|
16378
|
+
cloudProjectId: config.cloud_project_id ?? null,
|
|
16379
|
+
dashboardUrl: config.cloud_linked && config.cloud_project_id ? getCloudDashboardUrl(void 0, config.cloud_project_id) : null
|
|
15898
16380
|
},
|
|
15899
16381
|
null,
|
|
15900
16382
|
2
|
|
@@ -15902,43 +16384,89 @@ async function init(opts) {
|
|
|
15902
16384
|
);
|
|
15903
16385
|
return;
|
|
15904
16386
|
}
|
|
15905
|
-
if (
|
|
16387
|
+
if (!options.json) {
|
|
15906
16388
|
console.log();
|
|
15907
|
-
const summaryParts = [];
|
|
15908
|
-
if (totalSkills > 0) summaryParts.push(`${totalSkills} skills`);
|
|
15909
|
-
if (totalCommands > 0) summaryParts.push(`${totalCommands} commands`);
|
|
15910
16389
|
console.log(
|
|
15911
|
-
chalk17.
|
|
15912
|
-
`
|
|
16390
|
+
chalk17.bold(
|
|
16391
|
+
` ${symbols.success} Project configured: ${chalk17.cyan(projectName)}`
|
|
15913
16392
|
)
|
|
15914
16393
|
);
|
|
15915
|
-
}
|
|
15916
|
-
console.log();
|
|
15917
|
-
if (freshInit) {
|
|
15918
|
-
console.log(chalk17.bold(" Next step:"));
|
|
15919
16394
|
console.log();
|
|
15920
|
-
|
|
16395
|
+
if (selectedHosts.length > 0) {
|
|
16396
|
+
const toolNames = selectedHosts.map((h) => h.name).join(", ");
|
|
16397
|
+
console.log(
|
|
16398
|
+
` ${chalk17.dim("Tools:")} ${toolNames} ${chalk17.dim(`(${totalSkills} skills + ${totalCommands} commands installed)`)}`
|
|
16399
|
+
);
|
|
16400
|
+
}
|
|
16401
|
+
if (config.cloud_linked && config.cloud_project_id) {
|
|
16402
|
+
const dashboardUrl = getCloudDashboardUrl(
|
|
16403
|
+
void 0,
|
|
16404
|
+
config.cloud_project_id
|
|
16405
|
+
);
|
|
16406
|
+
console.log(
|
|
16407
|
+
` ${chalk17.dim("Cloud:")} ${chalk17.green("Linked")} \xB7 ${chalk17.cyan(dashboardUrl)}`
|
|
16408
|
+
);
|
|
16409
|
+
} else {
|
|
16410
|
+
console.log(
|
|
16411
|
+
` ${chalk17.dim("Cloud:")} Local-only ${chalk17.dim("(link anytime with `iamu auth login`)")}`
|
|
16412
|
+
);
|
|
16413
|
+
}
|
|
15921
16414
|
console.log();
|
|
15922
|
-
|
|
16415
|
+
if (freshInit) {
|
|
16416
|
+
console.log(chalk17.bold(" Next steps:"));
|
|
16417
|
+
console.log();
|
|
16418
|
+
console.log(
|
|
16419
|
+
` ${chalk17.bold("1.")} Start your application's dev server:`
|
|
16420
|
+
);
|
|
16421
|
+
console.log(
|
|
16422
|
+
` ${chalk17.cyan("$ pnpm dev")} ${chalk17.dim("(or npm run dev)")}`
|
|
16423
|
+
);
|
|
16424
|
+
console.log();
|
|
16425
|
+
console.log(` ${chalk17.bold("2.")} In your AI agent chat, run:`);
|
|
16426
|
+
console.log(
|
|
16427
|
+
` ${chalk17.cyan(setupCommand(projectRoot, selectedHosts))}`
|
|
16428
|
+
);
|
|
16429
|
+
console.log();
|
|
16430
|
+
console.log(
|
|
16431
|
+
chalk17.dim(
|
|
16432
|
+
" Your agent will analyze your codebase, map user roles, and generate your first walkthrough scenario."
|
|
16433
|
+
)
|
|
16434
|
+
);
|
|
16435
|
+
console.log();
|
|
16436
|
+
console.log(chalk17.bold(" Core workflow:"));
|
|
16437
|
+
console.log(
|
|
16438
|
+
chalk17.dim(" \u2022 ") + chalk17.cyan("/iamu:init") + chalk17.dim(" Map domain & scaffold inaugural scenario")
|
|
16439
|
+
);
|
|
16440
|
+
console.log(
|
|
16441
|
+
chalk17.dim(" \u2022 ") + chalk17.cyan("iamu-run <slug>") + chalk17.dim(" Walk scenario in browser & record findings")
|
|
16442
|
+
);
|
|
16443
|
+
console.log(
|
|
16444
|
+
chalk17.dim(" \u2022 ") + chalk17.cyan("iamu-findings") + chalk17.dim(" Triage persona feedback & apply code fixes")
|
|
16445
|
+
);
|
|
16446
|
+
console.log(
|
|
16447
|
+
chalk17.dim(" \u2022 ") + chalk17.cyan("iamu-propose") + chalk17.dim(" Brainstorm high-risk workflows & new personas")
|
|
16448
|
+
);
|
|
16449
|
+
} else {
|
|
16450
|
+
console.log(chalk17.bold(" Updated:"));
|
|
16451
|
+
console.log();
|
|
16452
|
+
console.log(
|
|
16453
|
+
chalk17.dim(" Skills and commands regenerated to latest version.")
|
|
16454
|
+
);
|
|
16455
|
+
}
|
|
15923
16456
|
console.log();
|
|
15924
16457
|
console.log(
|
|
15925
|
-
chalk17.dim("
|
|
16458
|
+
` ${chalk17.dim("Docs:")} ${chalk17.underline("https://iamu.dev/docs")}`
|
|
15926
16459
|
);
|
|
15927
16460
|
console.log(
|
|
15928
|
-
chalk17.dim("
|
|
16461
|
+
` ${chalk17.dim("Quickstart:")} ${chalk17.underline("https://iamu.dev/quickstart")}`
|
|
15929
16462
|
);
|
|
15930
|
-
|
|
15931
|
-
|
|
16463
|
+
if (config.cloud_linked && config.cloud_project_id) {
|
|
16464
|
+
console.log(
|
|
16465
|
+
` ${chalk17.dim("Dashboard:")} ${chalk17.underline(getCloudDashboardUrl(void 0, config.cloud_project_id))}`
|
|
16466
|
+
);
|
|
16467
|
+
}
|
|
15932
16468
|
console.log();
|
|
15933
|
-
console.log(
|
|
15934
|
-
chalk17.dim(" Skills and commands regenerated to latest version.")
|
|
15935
|
-
);
|
|
15936
16469
|
}
|
|
15937
|
-
console.log();
|
|
15938
|
-
console.log(
|
|
15939
|
-
chalk17.dim(" Docs: ") + chalk17.underline("https://iamu.dev/quickstart")
|
|
15940
|
-
);
|
|
15941
|
-
console.log();
|
|
15942
16470
|
}
|
|
15943
16471
|
var init_init = __esm({
|
|
15944
16472
|
"src/commands/init.ts"() {
|
|
@@ -15946,7 +16474,9 @@ var init_init = __esm({
|
|
|
15946
16474
|
init_dist2();
|
|
15947
16475
|
init_claude_plugin();
|
|
15948
16476
|
init_registry();
|
|
16477
|
+
init_cloud_url();
|
|
15949
16478
|
init_symbols();
|
|
16479
|
+
init_auth();
|
|
15950
16480
|
init_mcp_config();
|
|
15951
16481
|
init_skills();
|
|
15952
16482
|
init_templates();
|
|
@@ -16242,7 +16772,10 @@ var init_browse_actions = __esm({
|
|
|
16242
16772
|
usage: "goto <url>",
|
|
16243
16773
|
minArgs: 1,
|
|
16244
16774
|
run: async (p, args) => {
|
|
16245
|
-
await p.goto(args[0], {
|
|
16775
|
+
await p.goto(args[0], {
|
|
16776
|
+
waitUntil: "domcontentloaded",
|
|
16777
|
+
timeout: 1e4
|
|
16778
|
+
});
|
|
16246
16779
|
}
|
|
16247
16780
|
},
|
|
16248
16781
|
click: {
|
|
@@ -16403,6 +16936,12 @@ var init_browse_driver = __esm({
|
|
|
16403
16936
|
commandLine: (pid) => readProcessCommandLine(pid),
|
|
16404
16937
|
kill(pid) {
|
|
16405
16938
|
try {
|
|
16939
|
+
if (process.platform !== "win32") {
|
|
16940
|
+
try {
|
|
16941
|
+
process.kill(-pid, "SIGTERM");
|
|
16942
|
+
} catch {
|
|
16943
|
+
}
|
|
16944
|
+
}
|
|
16406
16945
|
process.kill(pid, "SIGTERM");
|
|
16407
16946
|
} catch {
|
|
16408
16947
|
}
|
|
@@ -16413,7 +16952,7 @@ var init_browse_driver = __esm({
|
|
|
16413
16952
|
|
|
16414
16953
|
// src/commands/step-numbering.ts
|
|
16415
16954
|
import { open, readdir, unlink } from "fs/promises";
|
|
16416
|
-
import
|
|
16955
|
+
import path21 from "path";
|
|
16417
16956
|
function stepFileName(stepNumber2) {
|
|
16418
16957
|
return `${String(stepNumber2).padStart(3, "0")}.json`;
|
|
16419
16958
|
}
|
|
@@ -16435,7 +16974,7 @@ async function reserveStepFile(stepsDir, limit) {
|
|
|
16435
16974
|
if (candidate > limit.maxSteps) {
|
|
16436
16975
|
throw new Error(stepLimitMessage(limit.maxSteps, limit.runId));
|
|
16437
16976
|
}
|
|
16438
|
-
const stepPath =
|
|
16977
|
+
const stepPath = path21.join(stepsDir, stepFileName(candidate));
|
|
16439
16978
|
try {
|
|
16440
16979
|
const handle = await open(stepPath, "wx");
|
|
16441
16980
|
await handle.close();
|
|
@@ -16467,7 +17006,7 @@ __export(step_exports, {
|
|
|
16467
17006
|
stepCommand: () => stepCommand
|
|
16468
17007
|
});
|
|
16469
17008
|
import { copyFile, mkdir as mkdir5, stat } from "fs/promises";
|
|
16470
|
-
import
|
|
17009
|
+
import path22 from "path";
|
|
16471
17010
|
import chalk19 from "chalk";
|
|
16472
17011
|
function parseMaxSteps(val) {
|
|
16473
17012
|
if (typeof val === "number" && Number.isFinite(val) && val > 0) return val;
|
|
@@ -16486,8 +17025,8 @@ async function recordStep(input) {
|
|
|
16486
17025
|
if (!runDirPath) {
|
|
16487
17026
|
throw new Error(`Run directory not found for run "${input.runId}"`);
|
|
16488
17027
|
}
|
|
16489
|
-
const stepsDir =
|
|
16490
|
-
const screenshotsDir =
|
|
17028
|
+
const stepsDir = path22.join(runDirPath, "steps");
|
|
17029
|
+
const screenshotsDir = path22.join(runDirPath, "screenshots");
|
|
16491
17030
|
await mkdir5(stepsDir, { recursive: true });
|
|
16492
17031
|
await mkdir5(screenshotsDir, { recursive: true });
|
|
16493
17032
|
const shotSource = input.shot ? await existingShot(input.shot) : null;
|
|
@@ -16501,11 +17040,11 @@ async function recordStep(input) {
|
|
|
16501
17040
|
let finalScreenshotPath = null;
|
|
16502
17041
|
if (shotSource) {
|
|
16503
17042
|
const destName = `step-${stepNumber2}.png`;
|
|
16504
|
-
finalScreenshotPath =
|
|
16505
|
-
if (
|
|
17043
|
+
finalScreenshotPath = path22.join(screenshotsDir, destName);
|
|
17044
|
+
if (path22.resolve(finalScreenshotPath) !== shotSource) {
|
|
16506
17045
|
await copyFile(shotSource, finalScreenshotPath);
|
|
16507
17046
|
}
|
|
16508
|
-
screenshotRef =
|
|
17047
|
+
screenshotRef = path22.join("screenshots", destName);
|
|
16509
17048
|
}
|
|
16510
17049
|
const parsedStep = runStepSchema.parse({
|
|
16511
17050
|
step_number: stepNumber2,
|
|
@@ -16530,7 +17069,7 @@ async function recordStep(input) {
|
|
|
16530
17069
|
}
|
|
16531
17070
|
}
|
|
16532
17071
|
async function existingShot(shot) {
|
|
16533
|
-
const resolved =
|
|
17072
|
+
const resolved = path22.resolve(shot);
|
|
16534
17073
|
const isFile = await stat(resolved).then((s) => s.isFile()).catch(() => false);
|
|
16535
17074
|
if (!isFile) {
|
|
16536
17075
|
throw new Error(
|
|
@@ -16572,7 +17111,7 @@ async function stepCommand(runArg, opts) {
|
|
|
16572
17111
|
JSON.stringify(
|
|
16573
17112
|
{
|
|
16574
17113
|
step_number: res.step.step_number,
|
|
16575
|
-
path:
|
|
17114
|
+
path: path22.relative(projectRoot, res.stepPath),
|
|
16576
17115
|
action: res.step.action,
|
|
16577
17116
|
note: res.step.note,
|
|
16578
17117
|
url: res.step.url,
|
|
@@ -16599,7 +17138,7 @@ async function stepCommand(runArg, opts) {
|
|
|
16599
17138
|
console.log(` ${chalk19.dim("shot:")} ${res.step.screenshot}`);
|
|
16600
17139
|
}
|
|
16601
17140
|
console.log(
|
|
16602
|
-
` ${chalk19.dim("file:")} ${
|
|
17141
|
+
` ${chalk19.dim("file:")} ${path22.relative(projectRoot, res.stepPath)}`
|
|
16603
17142
|
);
|
|
16604
17143
|
console.log();
|
|
16605
17144
|
} catch (err) {
|
|
@@ -16629,6 +17168,7 @@ __export(browse_exports, {
|
|
|
16629
17168
|
readBrowseState: () => readBrowseState,
|
|
16630
17169
|
shouldDisableSandbox: () => shouldDisableSandbox,
|
|
16631
17170
|
start: () => start,
|
|
17171
|
+
waitForVisualStability: () => waitForVisualStability,
|
|
16632
17172
|
withPage: () => withPage
|
|
16633
17173
|
});
|
|
16634
17174
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
@@ -16641,21 +17181,21 @@ import {
|
|
|
16641
17181
|
} from "fs";
|
|
16642
17182
|
import { rm as rm2 } from "fs/promises";
|
|
16643
17183
|
import { homedir as homedir3 } from "os";
|
|
16644
|
-
import
|
|
17184
|
+
import path23 from "path";
|
|
16645
17185
|
import chalk20 from "chalk";
|
|
16646
17186
|
function stateDir() {
|
|
16647
|
-
return
|
|
16648
|
-
process.env.IAMU_HOME ??
|
|
17187
|
+
return path23.join(
|
|
17188
|
+
process.env.IAMU_HOME ?? path23.join(homedir3(), ".iamu"),
|
|
16649
17189
|
"browse"
|
|
16650
17190
|
);
|
|
16651
17191
|
}
|
|
16652
17192
|
function profileDir() {
|
|
16653
|
-
return
|
|
17193
|
+
return path23.join(stateDir(), "profile");
|
|
16654
17194
|
}
|
|
16655
17195
|
function readBrowseState() {
|
|
16656
17196
|
try {
|
|
16657
17197
|
return JSON.parse(
|
|
16658
|
-
readFileSync6(
|
|
17198
|
+
readFileSync6(path23.join(stateDir(), "state.json"), "utf8")
|
|
16659
17199
|
);
|
|
16660
17200
|
} catch {
|
|
16661
17201
|
return null;
|
|
@@ -16663,7 +17203,7 @@ function readBrowseState() {
|
|
|
16663
17203
|
}
|
|
16664
17204
|
function writeState(state) {
|
|
16665
17205
|
mkdirSync(stateDir(), { recursive: true });
|
|
16666
|
-
writeFileSync(
|
|
17206
|
+
writeFileSync(path23.join(stateDir(), "state.json"), JSON.stringify(state));
|
|
16667
17207
|
}
|
|
16668
17208
|
function fail(message, opts) {
|
|
16669
17209
|
if (opts?.json) {
|
|
@@ -16726,7 +17266,7 @@ async function requestedViewport(opts) {
|
|
|
16726
17266
|
let manifest;
|
|
16727
17267
|
try {
|
|
16728
17268
|
manifest = JSON.parse(
|
|
16729
|
-
readFileSync6(
|
|
17269
|
+
readFileSync6(path23.join(dir, "walkthrough.json"), "utf8")
|
|
16730
17270
|
);
|
|
16731
17271
|
} catch {
|
|
16732
17272
|
fail(
|
|
@@ -16778,6 +17318,8 @@ function chromeArgs(opts, port, headless) {
|
|
|
16778
17318
|
`--window-size=${opts.width ?? "1280"},${opts.height ?? "900"}`,
|
|
16779
17319
|
"--no-first-run",
|
|
16780
17320
|
"--no-default-browser-check",
|
|
17321
|
+
"--password-store=basic",
|
|
17322
|
+
"--use-mock-keychain",
|
|
16781
17323
|
...headless ? ["--headless=new", "--disable-dev-shm-usage", "--disable-gpu"] : [],
|
|
16782
17324
|
...shouldDisableSandbox() ? ["--no-sandbox"] : [],
|
|
16783
17325
|
"about:blank"
|
|
@@ -16830,11 +17372,50 @@ async function stop(opts, driver) {
|
|
|
16830
17372
|
await driver.closeBrowser(state.port);
|
|
16831
17373
|
if (state.pid) await reapBrowser(state.pid, profileDir(), driver);
|
|
16832
17374
|
try {
|
|
16833
|
-
unlinkSync(
|
|
17375
|
+
unlinkSync(path23.join(stateDir(), "state.json"));
|
|
16834
17376
|
} catch {
|
|
16835
17377
|
}
|
|
16836
17378
|
report2(opts, { stopped: true }, [` ${chalk20.green("\u2713")} Browser stopped`]);
|
|
16837
17379
|
}
|
|
17380
|
+
async function waitForVisualStability(page, maxWaitMs = 300) {
|
|
17381
|
+
let timer;
|
|
17382
|
+
try {
|
|
17383
|
+
const nodeTimeout = new Promise((resolve4) => {
|
|
17384
|
+
timer = setTimeout(resolve4, maxWaitMs + 100);
|
|
17385
|
+
});
|
|
17386
|
+
const browserWait = page.evaluate((maxWait) => {
|
|
17387
|
+
return new Promise((resolve4) => {
|
|
17388
|
+
const timeout = setTimeout(resolve4, maxWait);
|
|
17389
|
+
const done = () => {
|
|
17390
|
+
clearTimeout(timeout);
|
|
17391
|
+
resolve4();
|
|
17392
|
+
};
|
|
17393
|
+
const g = globalThis;
|
|
17394
|
+
if (typeof g.requestAnimationFrame !== "function") {
|
|
17395
|
+
done();
|
|
17396
|
+
return;
|
|
17397
|
+
}
|
|
17398
|
+
g.requestAnimationFrame(() => {
|
|
17399
|
+
g.requestAnimationFrame?.(() => {
|
|
17400
|
+
const animations = g.document?.getAnimations ? g.document.getAnimations().filter((a) => a.playState === "running" && a.finished) : [];
|
|
17401
|
+
if (animations.length === 0) {
|
|
17402
|
+
done();
|
|
17403
|
+
return;
|
|
17404
|
+
}
|
|
17405
|
+
Promise.allSettled(
|
|
17406
|
+
animations.map((a) => a.finished)
|
|
17407
|
+
).finally(done);
|
|
17408
|
+
});
|
|
17409
|
+
});
|
|
17410
|
+
});
|
|
17411
|
+
}, maxWaitMs).catch(() => {
|
|
17412
|
+
});
|
|
17413
|
+
await Promise.race([browserWait, nodeTimeout]);
|
|
17414
|
+
} catch {
|
|
17415
|
+
} finally {
|
|
17416
|
+
if (timer) clearTimeout(timer);
|
|
17417
|
+
}
|
|
17418
|
+
}
|
|
16838
17419
|
async function withPage(opts, driver, fn, target) {
|
|
16839
17420
|
const state = readBrowseState();
|
|
16840
17421
|
if (!state) {
|
|
@@ -16854,11 +17435,11 @@ async function withPage(opts, driver, fn, target) {
|
|
|
16854
17435
|
await fn(page);
|
|
16855
17436
|
await page.waitForLoadState("domcontentloaded").catch(() => {
|
|
16856
17437
|
});
|
|
16857
|
-
await page.waitForTimeout(400);
|
|
16858
17438
|
let screenshot = null;
|
|
16859
17439
|
if (opts.shot) {
|
|
16860
|
-
|
|
16861
|
-
|
|
17440
|
+
await waitForVisualStability(page);
|
|
17441
|
+
screenshot = path23.resolve(opts.shot);
|
|
17442
|
+
mkdirSync(path23.dirname(screenshot), { recursive: true });
|
|
16862
17443
|
await page.screenshot({ path: screenshot, fullPage: Boolean(opts.full) });
|
|
16863
17444
|
}
|
|
16864
17445
|
return { url: page.url(), title: await page.title(), screenshot };
|
|
@@ -16937,7 +17518,7 @@ function printStep(opts, record, projectRoot) {
|
|
|
16937
17518
|
if (opts.json) {
|
|
16938
17519
|
const doc = {
|
|
16939
17520
|
step_number: step.step_number,
|
|
16940
|
-
path:
|
|
17521
|
+
path: path23.relative(projectRoot, record.stepPath),
|
|
16941
17522
|
action: step.action,
|
|
16942
17523
|
note: step.note,
|
|
16943
17524
|
url: step.url,
|
|
@@ -16957,6 +17538,35 @@ function printStep(opts, record, projectRoot) {
|
|
|
16957
17538
|
}
|
|
16958
17539
|
}
|
|
16959
17540
|
async function browseStep(args, opts, driver) {
|
|
17541
|
+
if (opts.stepFile) {
|
|
17542
|
+
try {
|
|
17543
|
+
let raw;
|
|
17544
|
+
if (opts.stepFile === "-") {
|
|
17545
|
+
raw = readFileSync6(0, "utf8");
|
|
17546
|
+
} else {
|
|
17547
|
+
raw = readFileSync6(opts.stepFile, "utf8");
|
|
17548
|
+
}
|
|
17549
|
+
const data = JSON.parse(raw);
|
|
17550
|
+
if (data.run && typeof data.run === "string") opts.run = data.run;
|
|
17551
|
+
if (data.action && typeof data.action === "string")
|
|
17552
|
+
opts.action = data.action;
|
|
17553
|
+
if (data.note && typeof data.note === "string") opts.note = data.note;
|
|
17554
|
+
if (data.surface && typeof data.surface === "string")
|
|
17555
|
+
opts.surface = data.surface;
|
|
17556
|
+
if (args.length === 0 && data.cmd) {
|
|
17557
|
+
if (Array.isArray(data.cmd)) {
|
|
17558
|
+
args = data.cmd.map(String);
|
|
17559
|
+
} else if (typeof data.cmd === "string") {
|
|
17560
|
+
args = data.cmd.trim().split(/\s+/);
|
|
17561
|
+
}
|
|
17562
|
+
}
|
|
17563
|
+
} catch (err) {
|
|
17564
|
+
return fail(
|
|
17565
|
+
`Failed to read step payload from ${opts.stepFile}: ${err.message}`,
|
|
17566
|
+
opts
|
|
17567
|
+
);
|
|
17568
|
+
}
|
|
17569
|
+
}
|
|
16960
17570
|
if (!opts.run)
|
|
16961
17571
|
return fail("--run <run-id> is required for browse step.", opts);
|
|
16962
17572
|
if (!opts.action) {
|
|
@@ -16982,10 +17592,10 @@ async function browseStep(args, opts, driver) {
|
|
|
16982
17592
|
const runDirPath = resolveRunDir(ctx.projectRoot, opts.run);
|
|
16983
17593
|
if (!runDirPath) return;
|
|
16984
17594
|
const maxSteps = parseMaxSteps(opts.maxSteps);
|
|
16985
|
-
if (await peekNextStepNumber(
|
|
17595
|
+
if (await peekNextStepNumber(path23.join(runDirPath, "steps")) > maxSteps) {
|
|
16986
17596
|
return fail(stepLimitMessage(maxSteps, opts.run), opts);
|
|
16987
17597
|
}
|
|
16988
|
-
const pendingShot =
|
|
17598
|
+
const pendingShot = path23.join(
|
|
16989
17599
|
runDirPath,
|
|
16990
17600
|
"screenshots",
|
|
16991
17601
|
`.pending-${randomUUID7()}.png`
|
|
@@ -17056,11 +17666,14 @@ var doctor_exports = {};
|
|
|
17056
17666
|
__export(doctor_exports, {
|
|
17057
17667
|
checkCdpResponsive: () => checkCdpResponsive,
|
|
17058
17668
|
checkPortAvailable: () => checkPortAvailable,
|
|
17669
|
+
checkProjectHarnesses: () => checkProjectHarnesses,
|
|
17670
|
+
checkProjectTestabilityContract: () => checkProjectTestabilityContract,
|
|
17671
|
+
checkTargetHealth: () => checkTargetHealth,
|
|
17059
17672
|
doctor: () => doctor,
|
|
17060
17673
|
runDoctorChecks: () => runDoctorChecks
|
|
17061
17674
|
});
|
|
17062
17675
|
import { createServer } from "net";
|
|
17063
|
-
import { existsSync as existsSync10 } from "fs";
|
|
17676
|
+
import { existsSync as existsSync10, promises as fs26 } from "fs";
|
|
17064
17677
|
import { join as join7 } from "path";
|
|
17065
17678
|
import chalk21 from "chalk";
|
|
17066
17679
|
function checkPortAvailable(port) {
|
|
@@ -17338,11 +17951,186 @@ async function checkAgentIntegrations(projectRoot) {
|
|
|
17338
17951
|
}
|
|
17339
17952
|
return checks;
|
|
17340
17953
|
}
|
|
17341
|
-
async function
|
|
17954
|
+
async function checkTargetHealth(targetUrl) {
|
|
17955
|
+
const checks = [];
|
|
17956
|
+
let parsedUrl;
|
|
17957
|
+
try {
|
|
17958
|
+
parsedUrl = new URL(targetUrl);
|
|
17959
|
+
} catch {
|
|
17960
|
+
checks.push({
|
|
17961
|
+
category: "target",
|
|
17962
|
+
name: "Target URL Format",
|
|
17963
|
+
status: "error",
|
|
17964
|
+
message: `Invalid target URL: "${targetUrl}"`,
|
|
17965
|
+
remedy: "Provide a valid URL with protocol (e.g. http://localhost:3000)."
|
|
17966
|
+
});
|
|
17967
|
+
return checks;
|
|
17968
|
+
}
|
|
17969
|
+
try {
|
|
17970
|
+
const res = await fetch(targetUrl, {
|
|
17971
|
+
method: "GET",
|
|
17972
|
+
redirect: "manual",
|
|
17973
|
+
signal: AbortSignal.timeout(3e3)
|
|
17974
|
+
});
|
|
17975
|
+
if ([301, 302, 303, 307, 308].includes(res.status)) {
|
|
17976
|
+
const location = res.headers.get("location") || "";
|
|
17977
|
+
const isExternalAuth = /auth0\.com|okta\.com|clerk\.com|accounts\.google\.com|cognito|login\.microsoftonline\.com/i.test(
|
|
17978
|
+
location
|
|
17979
|
+
) || location.includes("/oauth");
|
|
17980
|
+
if (isExternalAuth) {
|
|
17981
|
+
let redirectHost = location;
|
|
17982
|
+
try {
|
|
17983
|
+
redirectHost = new URL(location, targetUrl).host;
|
|
17984
|
+
} catch {
|
|
17985
|
+
}
|
|
17986
|
+
checks.push({
|
|
17987
|
+
category: "target",
|
|
17988
|
+
name: "Authentication Boundary",
|
|
17989
|
+
status: "warn",
|
|
17990
|
+
message: `Target immediately redirects to external auth (${redirectHost})`,
|
|
17991
|
+
remedy: "Configure a dev auth bypass or prepare for interactive login handshake before walking."
|
|
17992
|
+
});
|
|
17993
|
+
return checks;
|
|
17994
|
+
}
|
|
17995
|
+
checks.push({
|
|
17996
|
+
category: "target",
|
|
17997
|
+
name: "Target Reachability",
|
|
17998
|
+
status: "ok",
|
|
17999
|
+
message: `HTTP ${res.status} redirect -> ${location}`
|
|
18000
|
+
});
|
|
18001
|
+
return checks;
|
|
18002
|
+
}
|
|
18003
|
+
if (res.status >= 500) {
|
|
18004
|
+
checks.push({
|
|
18005
|
+
category: "target",
|
|
18006
|
+
name: "Target Health",
|
|
18007
|
+
status: "error",
|
|
18008
|
+
message: `Target returned HTTP ${res.status} ${res.statusText || "Server Error"}`,
|
|
18009
|
+
remedy: "Check dev server logs for unhandled exceptions or missing environment variables."
|
|
18010
|
+
});
|
|
18011
|
+
return checks;
|
|
18012
|
+
}
|
|
18013
|
+
checks.push({
|
|
18014
|
+
category: "target",
|
|
18015
|
+
name: "Target Reachability",
|
|
18016
|
+
status: "ok",
|
|
18017
|
+
message: `HTTP ${res.status} OK (${parsedUrl.origin})`
|
|
18018
|
+
});
|
|
18019
|
+
} catch (err) {
|
|
18020
|
+
const message = err.message;
|
|
18021
|
+
checks.push({
|
|
18022
|
+
category: "target",
|
|
18023
|
+
name: "Target Reachability",
|
|
18024
|
+
status: "warn",
|
|
18025
|
+
message: `Cannot connect to ${targetUrl} (${message})`,
|
|
18026
|
+
remedy: "Ensure your dev server is running and listening on the specified port."
|
|
18027
|
+
});
|
|
18028
|
+
}
|
|
18029
|
+
return checks;
|
|
18030
|
+
}
|
|
18031
|
+
async function checkProjectHarnesses(projectRoot) {
|
|
18032
|
+
const checks = [];
|
|
18033
|
+
const pkgPath = join7(projectRoot, "package.json");
|
|
18034
|
+
if (!existsSync10(pkgPath)) return checks;
|
|
18035
|
+
try {
|
|
18036
|
+
const raw = await fs26.readFile(pkgPath, "utf8");
|
|
18037
|
+
const pkg = JSON.parse(raw);
|
|
18038
|
+
const scripts = pkg.scripts || {};
|
|
18039
|
+
const allDeps = {
|
|
18040
|
+
...pkg.dependencies || {},
|
|
18041
|
+
...pkg.devDependencies || {}
|
|
18042
|
+
};
|
|
18043
|
+
const devScripts = ["dev", "start", "dev:mock", "serve"].filter(
|
|
18044
|
+
(s) => s in scripts
|
|
18045
|
+
);
|
|
18046
|
+
if (devScripts.length > 0) {
|
|
18047
|
+
checks.push({
|
|
18048
|
+
category: "target",
|
|
18049
|
+
name: "Dev Server Script",
|
|
18050
|
+
status: "ok",
|
|
18051
|
+
message: `Available scripts: ${devScripts.map((s) => `pnpm ${s}`).join(", ")}`
|
|
18052
|
+
});
|
|
18053
|
+
} else {
|
|
18054
|
+
checks.push({
|
|
18055
|
+
category: "target",
|
|
18056
|
+
name: "Dev Server Script",
|
|
18057
|
+
status: "warn",
|
|
18058
|
+
message: "No standard dev script (dev, start, dev:mock) found in package.json",
|
|
18059
|
+
remedy: "Add a `dev` or `start` script to package.json to boot your app locally."
|
|
18060
|
+
});
|
|
18061
|
+
}
|
|
18062
|
+
if (allDeps.msw || existsSync10(join7(projectRoot, "src/mocks")) || existsSync10(join7(projectRoot, "mocks"))) {
|
|
18063
|
+
checks.push({
|
|
18064
|
+
category: "target",
|
|
18065
|
+
name: "Mock Harness (MSW)",
|
|
18066
|
+
status: "ok",
|
|
18067
|
+
message: "Mock Service Worker detected. API mocks can decouple walks from live backends."
|
|
18068
|
+
});
|
|
18069
|
+
}
|
|
18070
|
+
const seedScript = Object.keys(scripts).find(
|
|
18071
|
+
(s) => /seed|db:seed|fixtures/i.test(s)
|
|
18072
|
+
);
|
|
18073
|
+
if (seedScript) {
|
|
18074
|
+
checks.push({
|
|
18075
|
+
category: "target",
|
|
18076
|
+
name: "Database Seed",
|
|
18077
|
+
status: "ok",
|
|
18078
|
+
message: `Seed script detected (${seedScript}). Preconditions can be reset between runs.`
|
|
18079
|
+
});
|
|
18080
|
+
}
|
|
18081
|
+
} catch {
|
|
18082
|
+
}
|
|
18083
|
+
return checks;
|
|
18084
|
+
}
|
|
18085
|
+
async function checkProjectTestabilityContract(projectRoot) {
|
|
18086
|
+
const checks = [];
|
|
18087
|
+
const testabilityPath = join7(projectRoot, "iamu", "testability.md");
|
|
18088
|
+
if (!existsSync10(testabilityPath)) {
|
|
18089
|
+
checks.push({
|
|
18090
|
+
category: "target",
|
|
18091
|
+
name: "Testability Contract (testability.md)",
|
|
18092
|
+
status: "warn",
|
|
18093
|
+
message: "iamu/testability.md not found",
|
|
18094
|
+
remedy: "Run `/iamu:init` to audit your codebase and record testable routes, auth strategies, and mock harnesses."
|
|
18095
|
+
});
|
|
18096
|
+
return { checks };
|
|
18097
|
+
}
|
|
18098
|
+
try {
|
|
18099
|
+
const raw = await fs26.readFile(testabilityPath, "utf8");
|
|
18100
|
+
const { frontmatter } = parseFrontmatter(raw);
|
|
18101
|
+
const targetUrl = typeof frontmatter.target_url === "string" ? frontmatter.target_url : void 0;
|
|
18102
|
+
const details = [];
|
|
18103
|
+
if (targetUrl) details.push(`Target: ${targetUrl}`);
|
|
18104
|
+
if (frontmatter.dev_command) {
|
|
18105
|
+
details.push(`Command: ${frontmatter.dev_command}`);
|
|
18106
|
+
}
|
|
18107
|
+
if (frontmatter.auth_strategy) {
|
|
18108
|
+
details.push(`Auth: ${frontmatter.auth_strategy}`);
|
|
18109
|
+
}
|
|
18110
|
+
checks.push({
|
|
18111
|
+
category: "target",
|
|
18112
|
+
name: "Testability Contract (testability.md)",
|
|
18113
|
+
status: "ok",
|
|
18114
|
+
message: `Configured (${details.join(" \xB7 ") || "present"})`
|
|
18115
|
+
});
|
|
18116
|
+
return { checks, targetUrl };
|
|
18117
|
+
} catch (err) {
|
|
18118
|
+
checks.push({
|
|
18119
|
+
category: "target",
|
|
18120
|
+
name: "Testability Contract (testability.md)",
|
|
18121
|
+
status: "warn",
|
|
18122
|
+
message: `Unreadable testability contract: ${err.message}`,
|
|
18123
|
+
remedy: "Repair iamu/testability.md frontmatter."
|
|
18124
|
+
});
|
|
18125
|
+
return { checks };
|
|
18126
|
+
}
|
|
18127
|
+
}
|
|
18128
|
+
async function runDoctorChecks(projectRoot, opts) {
|
|
17342
18129
|
const checks = [
|
|
17343
18130
|
...checkRuntimeChecks(),
|
|
17344
18131
|
...await checkBrowserChecks()
|
|
17345
18132
|
];
|
|
18133
|
+
let effectiveTarget = opts?.target;
|
|
17346
18134
|
if (!projectRoot || !existsSync10(join7(projectRoot, "iamu"))) {
|
|
17347
18135
|
checks.push({
|
|
17348
18136
|
category: "project",
|
|
@@ -17362,6 +18150,15 @@ async function runDoctorChecks(projectRoot) {
|
|
|
17362
18150
|
checks.push(...await checkProjectConfiguration(projectRoot));
|
|
17363
18151
|
checks.push(...await checkProjectContextAndEntities(projectRoot));
|
|
17364
18152
|
checks.push(...await checkAgentIntegrations(projectRoot));
|
|
18153
|
+
checks.push(...await checkProjectHarnesses(projectRoot));
|
|
18154
|
+
const testability = await checkProjectTestabilityContract(projectRoot);
|
|
18155
|
+
checks.push(...testability.checks);
|
|
18156
|
+
if (!effectiveTarget && testability.targetUrl) {
|
|
18157
|
+
effectiveTarget = testability.targetUrl;
|
|
18158
|
+
}
|
|
18159
|
+
}
|
|
18160
|
+
if (effectiveTarget) {
|
|
18161
|
+
checks.push(...await checkTargetHealth(effectiveTarget));
|
|
17365
18162
|
}
|
|
17366
18163
|
const okCount = checks.filter((c) => c.status === "ok").length;
|
|
17367
18164
|
const warnCount = checks.filter((c) => c.status === "warn").length;
|
|
@@ -17378,7 +18175,7 @@ async function runDoctorChecks(projectRoot) {
|
|
|
17378
18175
|
}
|
|
17379
18176
|
async function doctor(opts) {
|
|
17380
18177
|
const projectRoot = findProjectRoot(process.cwd()) ?? void 0;
|
|
17381
|
-
const result = await runDoctorChecks(projectRoot);
|
|
18178
|
+
const result = await runDoctorChecks(projectRoot, { target: opts?.target });
|
|
17382
18179
|
if (opts?.json) {
|
|
17383
18180
|
console.log(JSON.stringify(result, null, 2));
|
|
17384
18181
|
if (!result.ok) process.exitCode = 1;
|
|
@@ -17392,7 +18189,8 @@ async function doctor(opts) {
|
|
|
17392
18189
|
{ key: "browser", title: "Browser Automation" },
|
|
17393
18190
|
{ key: "project", title: "Project Configuration" },
|
|
17394
18191
|
{ key: "agent", title: "Agent Integrations" },
|
|
17395
|
-
{ key: "cloud", title: "Cloud & Authentication" }
|
|
18192
|
+
{ key: "cloud", title: "Cloud & Authentication" },
|
|
18193
|
+
{ key: "target", title: "Target & Testability" }
|
|
17396
18194
|
];
|
|
17397
18195
|
for (const cat of categories) {
|
|
17398
18196
|
const catChecks = result.checks.filter((c) => c.category === cat.key);
|
|
@@ -17870,13 +18668,13 @@ __export(validate_exports, {
|
|
|
17870
18668
|
validate: () => validate
|
|
17871
18669
|
});
|
|
17872
18670
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
17873
|
-
import { promises as
|
|
17874
|
-
import
|
|
18671
|
+
import { promises as fs27 } from "fs";
|
|
18672
|
+
import path24 from "path";
|
|
17875
18673
|
import chalk23 from "chalk";
|
|
17876
18674
|
function inferTypeFromPath(filePath, projectRoot) {
|
|
17877
|
-
const rel =
|
|
17878
|
-
if (rel.startsWith("..") ||
|
|
17879
|
-
const segments = rel.split(
|
|
18675
|
+
const rel = path24.relative(path24.join(projectRoot, "iamu"), filePath);
|
|
18676
|
+
if (rel.startsWith("..") || path24.isAbsolute(rel)) return null;
|
|
18677
|
+
const segments = rel.split(path24.sep);
|
|
17880
18678
|
if (segments.length < 2) return null;
|
|
17881
18679
|
return TYPE_BY_ALIAS.get(segments[0]) ?? null;
|
|
17882
18680
|
}
|
|
@@ -17906,7 +18704,7 @@ function validateFile(filePath, raw, projectRoot) {
|
|
|
17906
18704
|
);
|
|
17907
18705
|
return result;
|
|
17908
18706
|
}
|
|
17909
|
-
if (
|
|
18707
|
+
if (path24.basename(filePath) === "context.yml") {
|
|
17910
18708
|
applyZodValidation(contextFrontmatterSchema, frontmatter, result);
|
|
17911
18709
|
const warnings = [];
|
|
17912
18710
|
const hasNotes = Boolean(
|
|
@@ -17966,17 +18764,17 @@ async function buildRepairLookups(projectRoot) {
|
|
|
17966
18764
|
}
|
|
17967
18765
|
}
|
|
17968
18766
|
const runScenario = /* @__PURE__ */ new Map();
|
|
17969
|
-
const runsDir2 =
|
|
18767
|
+
const runsDir2 = path24.join(projectRoot, "iamu", ".runs");
|
|
17970
18768
|
let runIds = [];
|
|
17971
18769
|
try {
|
|
17972
|
-
runIds = await
|
|
18770
|
+
runIds = await fs27.readdir(runsDir2);
|
|
17973
18771
|
} catch {
|
|
17974
18772
|
}
|
|
17975
18773
|
for (const runId of runIds) {
|
|
17976
18774
|
try {
|
|
17977
18775
|
const manifest = JSON.parse(
|
|
17978
|
-
await
|
|
17979
|
-
|
|
18776
|
+
await fs27.readFile(
|
|
18777
|
+
path24.join(runsDir2, runId, "walkthrough.json"),
|
|
17980
18778
|
"utf8"
|
|
17981
18779
|
)
|
|
17982
18780
|
);
|
|
@@ -17998,7 +18796,7 @@ async function fixFindings(projectRoot, results, dryRun) {
|
|
|
17998
18796
|
let raw;
|
|
17999
18797
|
let parsed;
|
|
18000
18798
|
try {
|
|
18001
|
-
raw = await
|
|
18799
|
+
raw = await fs27.readFile(result.path, "utf8");
|
|
18002
18800
|
parsed = parseFrontmatter(raw);
|
|
18003
18801
|
} catch {
|
|
18004
18802
|
continue;
|
|
@@ -18007,7 +18805,7 @@ async function fixFindings(projectRoot, results, dryRun) {
|
|
|
18007
18805
|
parsed.frontmatter,
|
|
18008
18806
|
{
|
|
18009
18807
|
...lookups,
|
|
18010
|
-
fileSlug:
|
|
18808
|
+
fileSlug: path24.basename(result.path, ".md"),
|
|
18011
18809
|
newId: randomUUID8,
|
|
18012
18810
|
body: parsed.body
|
|
18013
18811
|
}
|
|
@@ -18042,9 +18840,9 @@ async function validate(filePath, opts) {
|
|
|
18042
18840
|
if (process.exitCode === 1) return;
|
|
18043
18841
|
const results = [];
|
|
18044
18842
|
if (filePath) {
|
|
18045
|
-
const abs =
|
|
18843
|
+
const abs = path24.isAbsolute(filePath) ? filePath : path24.resolve(process.cwd(), filePath);
|
|
18046
18844
|
try {
|
|
18047
|
-
const raw = await
|
|
18845
|
+
const raw = await fs27.readFile(abs, "utf8");
|
|
18048
18846
|
results.push(validateFile(abs, raw, projectRoot));
|
|
18049
18847
|
} catch (err) {
|
|
18050
18848
|
commandError(
|
|
@@ -18054,24 +18852,24 @@ async function validate(filePath, opts) {
|
|
|
18054
18852
|
return;
|
|
18055
18853
|
}
|
|
18056
18854
|
} else if (opts.run) {
|
|
18057
|
-
const findingsDir =
|
|
18855
|
+
const findingsDir = path24.join(projectRoot, "iamu", ENTITY_DIRS.finding);
|
|
18058
18856
|
let names = [];
|
|
18059
18857
|
try {
|
|
18060
|
-
names = await
|
|
18858
|
+
names = await fs27.readdir(findingsDir);
|
|
18061
18859
|
} catch {
|
|
18062
18860
|
}
|
|
18063
18861
|
for (const name of names) {
|
|
18064
18862
|
if (!name.endsWith(".md")) continue;
|
|
18065
|
-
const abs =
|
|
18863
|
+
const abs = path24.join(findingsDir, name);
|
|
18066
18864
|
try {
|
|
18067
|
-
const raw = await
|
|
18865
|
+
const raw = await fs27.readFile(abs, "utf8");
|
|
18068
18866
|
const { frontmatter } = parseFrontmatter(raw);
|
|
18069
18867
|
if (frontmatter.run === opts.run) {
|
|
18070
18868
|
results.push(validateFile(abs, raw, projectRoot));
|
|
18071
18869
|
}
|
|
18072
18870
|
} catch {
|
|
18073
18871
|
results.push({
|
|
18074
|
-
path:
|
|
18872
|
+
path: path24.relative(projectRoot, abs),
|
|
18075
18873
|
valid: false,
|
|
18076
18874
|
errors: ["Cannot read file or parse frontmatter"]
|
|
18077
18875
|
});
|
|
@@ -18104,31 +18902,31 @@ async function validate(filePath, opts) {
|
|
|
18104
18902
|
} else {
|
|
18105
18903
|
const typesToCheck = typeFilter ? ENTITY_TYPES.filter((t) => typeFilter.has(t)) : ENTITY_TYPES;
|
|
18106
18904
|
for (const type2 of typesToCheck) {
|
|
18107
|
-
const iamuTypeDir =
|
|
18905
|
+
const iamuTypeDir = path24.join(projectRoot, "iamu", ENTITY_DIRS[type2]);
|
|
18108
18906
|
let names;
|
|
18109
18907
|
try {
|
|
18110
|
-
names = await
|
|
18908
|
+
names = await fs27.readdir(iamuTypeDir);
|
|
18111
18909
|
} catch {
|
|
18112
18910
|
continue;
|
|
18113
18911
|
}
|
|
18114
18912
|
for (const name of names) {
|
|
18115
18913
|
if (!name.endsWith(".md")) continue;
|
|
18116
|
-
const abs =
|
|
18914
|
+
const abs = path24.join(iamuTypeDir, name);
|
|
18117
18915
|
try {
|
|
18118
|
-
const raw = await
|
|
18916
|
+
const raw = await fs27.readFile(abs, "utf8");
|
|
18119
18917
|
results.push(validateFile(abs, raw, projectRoot));
|
|
18120
18918
|
} catch {
|
|
18121
18919
|
results.push({
|
|
18122
|
-
path:
|
|
18920
|
+
path: path24.relative(projectRoot, abs),
|
|
18123
18921
|
valid: false,
|
|
18124
18922
|
errors: ["Cannot read file"]
|
|
18125
18923
|
});
|
|
18126
18924
|
}
|
|
18127
18925
|
}
|
|
18128
18926
|
}
|
|
18129
|
-
const ctxPath =
|
|
18927
|
+
const ctxPath = path24.join(projectRoot, "iamu", "context.yml");
|
|
18130
18928
|
try {
|
|
18131
|
-
const raw = await
|
|
18929
|
+
const raw = await fs27.readFile(ctxPath, "utf8");
|
|
18132
18930
|
results.push(validateFile(ctxPath, raw, projectRoot));
|
|
18133
18931
|
} catch {
|
|
18134
18932
|
}
|
|
@@ -18147,19 +18945,19 @@ async function validate(filePath, opts) {
|
|
|
18147
18945
|
valid,
|
|
18148
18946
|
errors: results.filter((r) => !r.valid).flatMap(
|
|
18149
18947
|
(r) => r.errors.map((e) => ({
|
|
18150
|
-
path:
|
|
18948
|
+
path: path24.relative(projectRoot, r.path),
|
|
18151
18949
|
message: e
|
|
18152
18950
|
}))
|
|
18153
18951
|
),
|
|
18154
18952
|
warnings: results.filter((r) => r.warnings && r.warnings.length > 0).flatMap(
|
|
18155
18953
|
(r) => (r.warnings ?? []).map((w) => ({
|
|
18156
|
-
path:
|
|
18954
|
+
path: path24.relative(projectRoot, r.path),
|
|
18157
18955
|
message: w
|
|
18158
18956
|
}))
|
|
18159
18957
|
),
|
|
18160
18958
|
...opts.fix ? {
|
|
18161
18959
|
repaired: repaired.map((r) => ({
|
|
18162
|
-
path:
|
|
18960
|
+
path: path24.relative(projectRoot, r.path),
|
|
18163
18961
|
changes: r.repairs,
|
|
18164
18962
|
valid: r.valid
|
|
18165
18963
|
}))
|
|
@@ -18181,7 +18979,7 @@ async function validate(filePath, opts) {
|
|
|
18181
18979
|
}
|
|
18182
18980
|
let hasErrors = false;
|
|
18183
18981
|
for (const r of results) {
|
|
18184
|
-
const relPath =
|
|
18982
|
+
const relPath = path24.relative(projectRoot, r.path);
|
|
18185
18983
|
if (r.valid) {
|
|
18186
18984
|
if (r.warnings && r.warnings.length > 0) {
|
|
18187
18985
|
console.log(` ${symbols.warning} ` + chalk23.yellow(relPath));
|
|
@@ -18372,10 +19170,26 @@ function defaultFrontmatter(type2, id, slug, name, opts) {
|
|
|
18372
19170
|
...base,
|
|
18373
19171
|
name,
|
|
18374
19172
|
role: opts.role ?? "TODO",
|
|
18375
|
-
...opts.disposition ? { disposition: opts.disposition } : {}
|
|
19173
|
+
...opts.disposition ? { disposition: opts.disposition } : {},
|
|
19174
|
+
...opts.access ? { access: opts.access } : {}
|
|
18376
19175
|
};
|
|
18377
|
-
case "outline":
|
|
18378
|
-
|
|
19176
|
+
case "outline": {
|
|
19177
|
+
const tests = Array.isArray(opts.humanTest) ? opts.humanTest : opts.humanTest ? [opts.humanTest] : [];
|
|
19178
|
+
return {
|
|
19179
|
+
...base,
|
|
19180
|
+
title: name,
|
|
19181
|
+
...description,
|
|
19182
|
+
...opts.capability ? { primary_capability: opts.capability } : {},
|
|
19183
|
+
...opts.tension ? { tension: opts.tension } : {},
|
|
19184
|
+
...opts.narrative ? { narrative: opts.narrative } : {},
|
|
19185
|
+
...tests.length > 0 ? {
|
|
19186
|
+
human_tests: tests.map((t, idx) => ({
|
|
19187
|
+
id: `test-${idx + 1}`,
|
|
19188
|
+
question: t
|
|
19189
|
+
}))
|
|
19190
|
+
} : {}
|
|
19191
|
+
};
|
|
19192
|
+
}
|
|
18379
19193
|
case "capability":
|
|
18380
19194
|
return { ...base, title: name, ...description };
|
|
18381
19195
|
case "source":
|
|
@@ -18385,13 +19199,21 @@ function defaultFrontmatter(type2, id, slug, name, opts) {
|
|
|
18385
19199
|
url: opts.url,
|
|
18386
19200
|
authority_level: opts.authority ?? "informational"
|
|
18387
19201
|
};
|
|
18388
|
-
case "scenario":
|
|
19202
|
+
case "scenario": {
|
|
19203
|
+
const stateObj = opts.stateDescription || opts.stateApproximation ? {
|
|
19204
|
+
state: {
|
|
19205
|
+
...opts.stateDescription ? { description: opts.stateDescription } : {},
|
|
19206
|
+
...opts.stateApproximation ? { approximation: opts.stateApproximation } : {}
|
|
19207
|
+
}
|
|
19208
|
+
} : {};
|
|
18389
19209
|
return {
|
|
18390
19210
|
...base,
|
|
18391
19211
|
outline: opts.outline ?? null,
|
|
18392
19212
|
persona: opts.persona ?? null,
|
|
18393
|
-
...opts.entry ? { entry_path: opts.entry } : {}
|
|
19213
|
+
...opts.entry ? { entry_path: opts.entry } : {},
|
|
19214
|
+
...stateObj
|
|
18394
19215
|
};
|
|
19216
|
+
}
|
|
18395
19217
|
default:
|
|
18396
19218
|
return base;
|
|
18397
19219
|
}
|
|
@@ -18431,7 +19253,14 @@ async function newEntity(type2, opts) {
|
|
|
18431
19253
|
return;
|
|
18432
19254
|
}
|
|
18433
19255
|
}
|
|
18434
|
-
|
|
19256
|
+
let slug;
|
|
19257
|
+
if (opts.slug) {
|
|
19258
|
+
slug = slugSchema.safeParse(opts.slug).success ? opts.slug : slugify(opts.slug);
|
|
19259
|
+
} else if (slugSchema.safeParse(opts.name).success) {
|
|
19260
|
+
slug = opts.name;
|
|
19261
|
+
} else {
|
|
19262
|
+
slug = slugify(opts.name);
|
|
19263
|
+
}
|
|
18435
19264
|
if (await entityExists(projectRoot, entityType, slug)) {
|
|
18436
19265
|
commandError(
|
|
18437
19266
|
`Entity already exists: iamu/${ENTITY_DIRS[entityType]}/${slug}.md`,
|
|
@@ -18441,7 +19270,7 @@ async function newEntity(type2, opts) {
|
|
|
18441
19270
|
}
|
|
18442
19271
|
const id = randomUUID9();
|
|
18443
19272
|
const frontmatter = defaultFrontmatter(entityType, id, slug, opts.name, opts);
|
|
18444
|
-
const body = "";
|
|
19273
|
+
const body = opts.body ?? (entityType === "outline" ? "" : opts.narrative) ?? "";
|
|
18445
19274
|
try {
|
|
18446
19275
|
const record = await writeEntity(
|
|
18447
19276
|
projectRoot,
|
|
@@ -18486,6 +19315,797 @@ var init_new_entity = __esm({
|
|
|
18486
19315
|
}
|
|
18487
19316
|
});
|
|
18488
19317
|
|
|
19318
|
+
// src/commands/explore-crawl.ts
|
|
19319
|
+
function candidateSig(c) {
|
|
19320
|
+
return `${c.role}
|
|
19321
|
+
${c.name}
|
|
19322
|
+
${c.occurrence}`;
|
|
19323
|
+
}
|
|
19324
|
+
function originOf(url) {
|
|
19325
|
+
try {
|
|
19326
|
+
return new URL(url).origin;
|
|
19327
|
+
} catch {
|
|
19328
|
+
return null;
|
|
19329
|
+
}
|
|
19330
|
+
}
|
|
19331
|
+
function skipReason(c, origin, deny) {
|
|
19332
|
+
if (deny.some((re) => re.test(c.name) || c.href && re.test(c.href))) {
|
|
19333
|
+
return "denied";
|
|
19334
|
+
}
|
|
19335
|
+
if (c.submit) return "submit";
|
|
19336
|
+
if (UNSAFE_NAME.test(c.name)) return "unsafe_name";
|
|
19337
|
+
if (c.href) {
|
|
19338
|
+
let u = null;
|
|
19339
|
+
try {
|
|
19340
|
+
u = new URL(c.href);
|
|
19341
|
+
} catch {
|
|
19342
|
+
}
|
|
19343
|
+
if (u && u.protocol !== "javascript:") {
|
|
19344
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") return "external";
|
|
19345
|
+
if (u.origin !== origin) return "external";
|
|
19346
|
+
if (UNSAFE_PATH.test(u.pathname)) return "unsafe_name";
|
|
19347
|
+
}
|
|
19348
|
+
if (c.download) return "download";
|
|
19349
|
+
}
|
|
19350
|
+
return null;
|
|
19351
|
+
}
|
|
19352
|
+
async function settle(page, settleMs) {
|
|
19353
|
+
await page.waitForLoadState("domcontentloaded", { timeout: NAV_TIMEOUT_MS }).catch(() => {
|
|
19354
|
+
});
|
|
19355
|
+
await page.waitForLoadState("networkidle", { timeout: settleMs }).catch(() => {
|
|
19356
|
+
});
|
|
19357
|
+
}
|
|
19358
|
+
async function readPage(page) {
|
|
19359
|
+
return page.evaluate(READ_PAGE);
|
|
19360
|
+
}
|
|
19361
|
+
async function markDocument(page) {
|
|
19362
|
+
await page.evaluate("window.__iamuDoc = true").catch(() => {
|
|
19363
|
+
});
|
|
19364
|
+
}
|
|
19365
|
+
async function isSameDocument(page) {
|
|
19366
|
+
return page.evaluate("window.__iamuDoc === true").catch(() => false);
|
|
19367
|
+
}
|
|
19368
|
+
async function clickCandidate(page, want) {
|
|
19369
|
+
const state = await readPage(page);
|
|
19370
|
+
const c = state.candidates.find(
|
|
19371
|
+
(x) => candidateSig(x) === candidateSig(want)
|
|
19372
|
+
);
|
|
19373
|
+
if (!c) return false;
|
|
19374
|
+
await page.locator(`[data-iamu-x="${c.index}"]`).click({ timeout: CLICK_TIMEOUT_MS });
|
|
19375
|
+
return true;
|
|
19376
|
+
}
|
|
19377
|
+
async function crawl(page, opts) {
|
|
19378
|
+
const origin = originOf(opts.startUrl);
|
|
19379
|
+
if (!origin) throw new Error(`Not a URL: ${opts.startUrl}`);
|
|
19380
|
+
let blocked = [];
|
|
19381
|
+
await page.route("**/*", (route) => {
|
|
19382
|
+
const req = route.request();
|
|
19383
|
+
if (SAFE_METHODS.has(req.method())) return route.continue();
|
|
19384
|
+
let where = req.url();
|
|
19385
|
+
try {
|
|
19386
|
+
const u = new URL(where);
|
|
19387
|
+
where = u.origin === origin ? u.pathname : `${u.origin}${u.pathname}`;
|
|
19388
|
+
} catch {
|
|
19389
|
+
}
|
|
19390
|
+
blocked.push(`${req.method()} ${where}`);
|
|
19391
|
+
return route.abort("blockedbyclient");
|
|
19392
|
+
});
|
|
19393
|
+
const onDialog = (d) => {
|
|
19394
|
+
d.dismiss().catch(() => {
|
|
19395
|
+
});
|
|
19396
|
+
};
|
|
19397
|
+
const onPopup = (p) => {
|
|
19398
|
+
if (p !== page) p.close().catch(() => {
|
|
19399
|
+
});
|
|
19400
|
+
};
|
|
19401
|
+
page.on("dialog", onDialog);
|
|
19402
|
+
page.context().on("page", onPopup);
|
|
19403
|
+
try {
|
|
19404
|
+
return await crawlFrom(page, opts, origin, () => {
|
|
19405
|
+
const out = blocked;
|
|
19406
|
+
blocked = [];
|
|
19407
|
+
return out;
|
|
19408
|
+
});
|
|
19409
|
+
} finally {
|
|
19410
|
+
page.off("dialog", onDialog);
|
|
19411
|
+
page.context().off("page", onPopup);
|
|
19412
|
+
await page.unroute("**/*").catch(() => {
|
|
19413
|
+
});
|
|
19414
|
+
}
|
|
19415
|
+
}
|
|
19416
|
+
async function crawlFrom(page, opts, origin, takeBlocked) {
|
|
19417
|
+
const deny = opts.deny ?? [];
|
|
19418
|
+
const settleMs = opts.settleMs ?? 1500;
|
|
19419
|
+
const { budget } = opts;
|
|
19420
|
+
const stats = {
|
|
19421
|
+
surfaces: 0,
|
|
19422
|
+
actions_tried: 0,
|
|
19423
|
+
skipped: 0,
|
|
19424
|
+
blocked_requests: 0,
|
|
19425
|
+
errors: 0,
|
|
19426
|
+
truncated: false
|
|
19427
|
+
};
|
|
19428
|
+
const surfaces = [];
|
|
19429
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
19430
|
+
const byPlaceView = /* @__PURE__ */ new Map();
|
|
19431
|
+
const signatureOf = (s) => exploreSignature(s.headings, s.title);
|
|
19432
|
+
function addSurface(state, place, view, path30, depth) {
|
|
19433
|
+
const key = exploreSurfaceKey(place, view);
|
|
19434
|
+
const surface = {
|
|
19435
|
+
key,
|
|
19436
|
+
url: state.url,
|
|
19437
|
+
place,
|
|
19438
|
+
view,
|
|
19439
|
+
signature: signatureOf(state),
|
|
19440
|
+
label: state.headings[0] ?? (state.title || place),
|
|
19441
|
+
headings: state.headings,
|
|
19442
|
+
fields: state.fields,
|
|
19443
|
+
depth,
|
|
19444
|
+
path: path30,
|
|
19445
|
+
actions: []
|
|
19446
|
+
};
|
|
19447
|
+
surfaces.push(surface);
|
|
19448
|
+
byKey.set(key, surface);
|
|
19449
|
+
byPlaceView.set(`${place}
|
|
19450
|
+
${surface.signature}`, key);
|
|
19451
|
+
stats.surfaces++;
|
|
19452
|
+
opts.log?.(` ${" ".repeat(depth)}${key} ${surface.label}`);
|
|
19453
|
+
return surface;
|
|
19454
|
+
}
|
|
19455
|
+
async function goto(url) {
|
|
19456
|
+
await page.goto(url, {
|
|
19457
|
+
waitUntil: "domcontentloaded",
|
|
19458
|
+
timeout: NAV_TIMEOUT_MS
|
|
19459
|
+
});
|
|
19460
|
+
await settle(page, settleMs);
|
|
19461
|
+
}
|
|
19462
|
+
async function replay(path30) {
|
|
19463
|
+
if (!("goto" in (path30[0] ?? {}))) await goto(opts.startUrl);
|
|
19464
|
+
for (const step of path30) {
|
|
19465
|
+
if ("goto" in step) {
|
|
19466
|
+
await goto(step.goto);
|
|
19467
|
+
} else {
|
|
19468
|
+
if (!await clickCandidate(page, step.click)) return false;
|
|
19469
|
+
await settle(page, settleMs);
|
|
19470
|
+
}
|
|
19471
|
+
}
|
|
19472
|
+
return true;
|
|
19473
|
+
}
|
|
19474
|
+
await goto(opts.startUrl);
|
|
19475
|
+
if (await page.evaluate("Boolean(navigator.serviceWorker?.controller)").catch(() => false)) {
|
|
19476
|
+
throw new Error(
|
|
19477
|
+
"A service worker controls this page, and the write guard cannot see its requests. Explore without --attach (service workers are blocked there)."
|
|
19478
|
+
);
|
|
19479
|
+
}
|
|
19480
|
+
const first = await readPage(page);
|
|
19481
|
+
const firstPlace = explorePlaceKey(first.url);
|
|
19482
|
+
if (!firstPlace || originOf(first.url) !== origin) {
|
|
19483
|
+
throw new Error(`The start URL led off the product, to ${first.url}`);
|
|
19484
|
+
}
|
|
19485
|
+
addSurface(first, firstPlace, null, [], 0);
|
|
19486
|
+
for (let i = 0; i < surfaces.length; i++) {
|
|
19487
|
+
const surface = surfaces[i];
|
|
19488
|
+
if (!await replay(surface.path)) {
|
|
19489
|
+
stats.errors++;
|
|
19490
|
+
surface.actions.push({
|
|
19491
|
+
role: "",
|
|
19492
|
+
name: "",
|
|
19493
|
+
occurrence: 0,
|
|
19494
|
+
outcome: { kind: "error", message: LOST }
|
|
19495
|
+
});
|
|
19496
|
+
continue;
|
|
19497
|
+
}
|
|
19498
|
+
const home = await readPage(page);
|
|
19499
|
+
const seen = new Set(home.candidates.map(candidateSig));
|
|
19500
|
+
const work = home.candidates.map((c) => ({ c, via: [] }));
|
|
19501
|
+
const homeList = home.candidates.map(candidateSig).join("\n");
|
|
19502
|
+
let pristine = true;
|
|
19503
|
+
for (let w = 0; w < work.length; w++) {
|
|
19504
|
+
const { c, via } = work[w];
|
|
19505
|
+
const action = {
|
|
19506
|
+
role: c.role,
|
|
19507
|
+
name: c.name,
|
|
19508
|
+
occurrence: c.occurrence,
|
|
19509
|
+
...c.href ? { href: c.href } : {},
|
|
19510
|
+
...via.length > 0 ? { via } : {},
|
|
19511
|
+
outcome: { kind: "same" }
|
|
19512
|
+
};
|
|
19513
|
+
surface.actions.push(action);
|
|
19514
|
+
const reason = skipReason(c, origin, deny);
|
|
19515
|
+
if (reason) {
|
|
19516
|
+
action.outcome = { kind: "skipped", reason };
|
|
19517
|
+
stats.skipped++;
|
|
19518
|
+
continue;
|
|
19519
|
+
}
|
|
19520
|
+
if (surface.depth >= budget.max_depth || stats.actions_tried >= budget.max_actions) {
|
|
19521
|
+
action.outcome = { kind: "skipped", reason: "budget" };
|
|
19522
|
+
stats.skipped++;
|
|
19523
|
+
stats.truncated = true;
|
|
19524
|
+
continue;
|
|
19525
|
+
}
|
|
19526
|
+
stats.actions_tried++;
|
|
19527
|
+
takeBlocked();
|
|
19528
|
+
const click = {
|
|
19529
|
+
click: { role: c.role, name: c.name, occurrence: c.occurrence }
|
|
19530
|
+
};
|
|
19531
|
+
let outcome;
|
|
19532
|
+
let revealed = 0;
|
|
19533
|
+
let left = false;
|
|
19534
|
+
let lost = false;
|
|
19535
|
+
try {
|
|
19536
|
+
if (!pristine || via.length > 0) {
|
|
19537
|
+
if (!await replay([...surface.path, ...via])) {
|
|
19538
|
+
throw new Error(LOST);
|
|
19539
|
+
}
|
|
19540
|
+
}
|
|
19541
|
+
pristine = false;
|
|
19542
|
+
const before = await readPage(page);
|
|
19543
|
+
await markDocument(page);
|
|
19544
|
+
if (c.newTab && c.href) {
|
|
19545
|
+
await goto(c.href);
|
|
19546
|
+
} else {
|
|
19547
|
+
if (!await clickCandidate(page, c)) {
|
|
19548
|
+
throw new Error("control is gone");
|
|
19549
|
+
}
|
|
19550
|
+
await settle(page, settleMs);
|
|
19551
|
+
}
|
|
19552
|
+
const after = await readPage(page);
|
|
19553
|
+
if (originOf(after.url) !== origin) {
|
|
19554
|
+
outcome = { kind: "external", url: after.url };
|
|
19555
|
+
left = true;
|
|
19556
|
+
} else {
|
|
19557
|
+
const place = explorePlaceKey(after.url) ?? before.url;
|
|
19558
|
+
const sig = signatureOf(after);
|
|
19559
|
+
const sameDoc = !(c.newTab && c.href) && await isSameDocument(page);
|
|
19560
|
+
const beforePlace = explorePlaceKey(before.url);
|
|
19561
|
+
let key = byPlaceView.get(`${place}
|
|
19562
|
+
${sig}`);
|
|
19563
|
+
let view = null;
|
|
19564
|
+
if (!key) {
|
|
19565
|
+
const inPage = sameDoc && place === beforePlace;
|
|
19566
|
+
view = inPage ? sig : null;
|
|
19567
|
+
key = exploreSurfaceKey(place, view);
|
|
19568
|
+
if (byKey.has(key)) byPlaceView.set(`${place}
|
|
19569
|
+
${sig}`, key);
|
|
19570
|
+
}
|
|
19571
|
+
if (key === surface.key) {
|
|
19572
|
+
outcome = { kind: "same" };
|
|
19573
|
+
const list2 = after.candidates.map(candidateSig).join("\n");
|
|
19574
|
+
for (const n of after.candidates) {
|
|
19575
|
+
const s = candidateSig(n);
|
|
19576
|
+
if (seen.has(s)) continue;
|
|
19577
|
+
seen.add(s);
|
|
19578
|
+
work.push({ c: n, via: [...via, click] });
|
|
19579
|
+
revealed++;
|
|
19580
|
+
}
|
|
19581
|
+
if (list2 === homeList) pristine = true;
|
|
19582
|
+
} else {
|
|
19583
|
+
outcome = { kind: "surface", surface: key };
|
|
19584
|
+
if (!byKey.has(key)) {
|
|
19585
|
+
if (stats.surfaces >= budget.max_surfaces) {
|
|
19586
|
+
stats.truncated = true;
|
|
19587
|
+
} else {
|
|
19588
|
+
const path30 = !sameDoc && view === null ? [{ goto: after.url }] : [...surface.path, ...via, click];
|
|
19589
|
+
addSurface(after, place, view, path30, surface.depth + 1);
|
|
19590
|
+
}
|
|
19591
|
+
}
|
|
19592
|
+
}
|
|
19593
|
+
}
|
|
19594
|
+
} catch (err) {
|
|
19595
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
19596
|
+
outcome = { kind: "error", message: msg.split("\n")[0].slice(0, 200) };
|
|
19597
|
+
stats.errors++;
|
|
19598
|
+
lost = msg === LOST;
|
|
19599
|
+
}
|
|
19600
|
+
action.outcome = outcome;
|
|
19601
|
+
if (revealed > 0) action.revealed = revealed;
|
|
19602
|
+
const writes = takeBlocked();
|
|
19603
|
+
if (writes.length > 0) {
|
|
19604
|
+
action.blocked_requests = [...new Set(writes)];
|
|
19605
|
+
stats.blocked_requests += writes.length;
|
|
19606
|
+
}
|
|
19607
|
+
if (left) pristine = false;
|
|
19608
|
+
if (lost) break;
|
|
19609
|
+
}
|
|
19610
|
+
}
|
|
19611
|
+
return { surfaces, stats };
|
|
19612
|
+
}
|
|
19613
|
+
var UNSAFE_NAME, UNSAFE_PATH, SAFE_METHODS, CLICK_TIMEOUT_MS, LOST, NAV_TIMEOUT_MS, READ_PAGE;
|
|
19614
|
+
var init_explore_crawl = __esm({
|
|
19615
|
+
"src/commands/explore-crawl.ts"() {
|
|
19616
|
+
"use strict";
|
|
19617
|
+
init_dist2();
|
|
19618
|
+
UNSAFE_NAME = new RegExp(
|
|
19619
|
+
`(?<![\\p{L}\\p{N}])(${[
|
|
19620
|
+
// English
|
|
19621
|
+
"delete",
|
|
19622
|
+
"remove",
|
|
19623
|
+
"destroy",
|
|
19624
|
+
"erase",
|
|
19625
|
+
"pay",
|
|
19626
|
+
"pay now",
|
|
19627
|
+
"purchase",
|
|
19628
|
+
"buy",
|
|
19629
|
+
"checkout",
|
|
19630
|
+
"place order",
|
|
19631
|
+
"order now",
|
|
19632
|
+
"send",
|
|
19633
|
+
"submit",
|
|
19634
|
+
"save",
|
|
19635
|
+
"confirm",
|
|
19636
|
+
"approve",
|
|
19637
|
+
"reject",
|
|
19638
|
+
"decline",
|
|
19639
|
+
"publish",
|
|
19640
|
+
"archive",
|
|
19641
|
+
"unsubscribe",
|
|
19642
|
+
"log ?out",
|
|
19643
|
+
"sign ?out",
|
|
19644
|
+
"reset",
|
|
19645
|
+
"transfer",
|
|
19646
|
+
"invite",
|
|
19647
|
+
"upload",
|
|
19648
|
+
"import",
|
|
19649
|
+
"deactivate",
|
|
19650
|
+
"close account",
|
|
19651
|
+
// Swedish
|
|
19652
|
+
"ta bort",
|
|
19653
|
+
"radera",
|
|
19654
|
+
"betala",
|
|
19655
|
+
"k\xF6p",
|
|
19656
|
+
"best\xE4ll",
|
|
19657
|
+
"skicka",
|
|
19658
|
+
"skicka in",
|
|
19659
|
+
"spara",
|
|
19660
|
+
"bekr\xE4fta",
|
|
19661
|
+
"godk\xE4nn",
|
|
19662
|
+
"avsl\xE5",
|
|
19663
|
+
"publicera",
|
|
19664
|
+
"arkivera",
|
|
19665
|
+
"logga ut",
|
|
19666
|
+
"\xE5terst\xE4ll",
|
|
19667
|
+
"\xF6verf\xF6r",
|
|
19668
|
+
"bjud in",
|
|
19669
|
+
"ladda upp"
|
|
19670
|
+
].join("|")})(?![\\p{L}\\p{N}])`,
|
|
19671
|
+
"iu"
|
|
19672
|
+
);
|
|
19673
|
+
UNSAFE_PATH = /(^|\/)(log-?out|sign-?out|logga-?ut|delete|remove|destroy|unsubscribe)(\/|$)/i;
|
|
19674
|
+
SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
19675
|
+
CLICK_TIMEOUT_MS = 3e3;
|
|
19676
|
+
LOST = "could not return to surface";
|
|
19677
|
+
NAV_TIMEOUT_MS = 15e3;
|
|
19678
|
+
READ_PAGE = String.raw`(() => {
|
|
19679
|
+
const SELECTOR = [
|
|
19680
|
+
"a[href]", "button", "summary",
|
|
19681
|
+
'input[type="button"]', 'input[type="submit"]',
|
|
19682
|
+
'input[type="reset"]', 'input[type="image"]',
|
|
19683
|
+
'[role="button"]', '[role="link"]', '[role="tab"]',
|
|
19684
|
+
'[role="menuitem"]', '[role="menuitemradio"]', '[role="menuitemcheckbox"]',
|
|
19685
|
+
].join(",");
|
|
19686
|
+
const clean = (s) => (s ?? "").replace(/\s+/g, " ").trim();
|
|
19687
|
+
const visible = (el) => {
|
|
19688
|
+
if (typeof el.checkVisibility === "function" &&
|
|
19689
|
+
!el.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true })) {
|
|
19690
|
+
return false;
|
|
19691
|
+
}
|
|
19692
|
+
const r = el.getBoundingClientRect();
|
|
19693
|
+
return r.width > 0 && r.height > 0;
|
|
19694
|
+
};
|
|
19695
|
+
const textOf = (id) => clean(document.getElementById(id)?.innerText);
|
|
19696
|
+
const nameOf = (el) => {
|
|
19697
|
+
const label = clean(el.getAttribute("aria-label"));
|
|
19698
|
+
if (label) return label;
|
|
19699
|
+
const by = el.getAttribute("aria-labelledby");
|
|
19700
|
+
if (by) {
|
|
19701
|
+
const t = clean(by.split(/\s+/).map(textOf).join(" "));
|
|
19702
|
+
if (t) return t;
|
|
19703
|
+
}
|
|
19704
|
+
if (el instanceof HTMLInputElement) {
|
|
19705
|
+
return clean(el.value) || clean(el.alt) || clean(el.title);
|
|
19706
|
+
}
|
|
19707
|
+
const text = clean(el.innerText);
|
|
19708
|
+
if (text) return text.slice(0, 80);
|
|
19709
|
+
const img = el.querySelector("img[alt]");
|
|
19710
|
+
return clean(img?.getAttribute("alt")) || clean(el.title);
|
|
19711
|
+
};
|
|
19712
|
+
const roleOf = (el) =>
|
|
19713
|
+
el.getAttribute("role") || (el.tagName === "A" ? "link" : "button");
|
|
19714
|
+
|
|
19715
|
+
for (const el of document.querySelectorAll("[data-iamu-x]")) {
|
|
19716
|
+
el.removeAttribute("data-iamu-x");
|
|
19717
|
+
}
|
|
19718
|
+
const seen = new Map();
|
|
19719
|
+
const candidates = [];
|
|
19720
|
+
let index = 0;
|
|
19721
|
+
for (const el of document.querySelectorAll(SELECTOR)) {
|
|
19722
|
+
if (!visible(el)) continue;
|
|
19723
|
+
if (el.disabled || el.getAttribute("aria-disabled") === "true") continue;
|
|
19724
|
+
const role = roleOf(el);
|
|
19725
|
+
const name = nameOf(el);
|
|
19726
|
+
const sig = role + "\n" + name;
|
|
19727
|
+
const occurrence = seen.get(sig) ?? 0;
|
|
19728
|
+
seen.set(sig, occurrence + 1);
|
|
19729
|
+
let submit = false;
|
|
19730
|
+
if (el instanceof HTMLButtonElement) {
|
|
19731
|
+
// A button in a form submits unless it says type="button".
|
|
19732
|
+
submit = el.form
|
|
19733
|
+
? el.type === "submit" || el.type === "reset"
|
|
19734
|
+
: el.getAttribute("type") === "submit";
|
|
19735
|
+
} else if (el instanceof HTMLInputElement) {
|
|
19736
|
+
submit = ["submit", "reset", "image"].includes(el.type);
|
|
19737
|
+
}
|
|
19738
|
+
const anchor = el instanceof HTMLAnchorElement ? el : null;
|
|
19739
|
+
el.setAttribute("data-iamu-x", String(index));
|
|
19740
|
+
candidates.push({
|
|
19741
|
+
index, role, name, occurrence,
|
|
19742
|
+
href: anchor ? anchor.href : null,
|
|
19743
|
+
download: anchor ? anchor.hasAttribute("download") : false,
|
|
19744
|
+
newTab: anchor ? anchor.target === "_blank" : false,
|
|
19745
|
+
submit,
|
|
19746
|
+
});
|
|
19747
|
+
index++;
|
|
19748
|
+
}
|
|
19749
|
+
|
|
19750
|
+
const headings = [];
|
|
19751
|
+
for (const el of document.querySelectorAll(
|
|
19752
|
+
'h1, h2, [role="heading"][aria-level="1"], [role="heading"][aria-level="2"]',
|
|
19753
|
+
)) {
|
|
19754
|
+
if (!visible(el)) continue;
|
|
19755
|
+
const t = clean(el.innerText);
|
|
19756
|
+
if (t) headings.push(t.slice(0, 120));
|
|
19757
|
+
}
|
|
19758
|
+
|
|
19759
|
+
const fields = [];
|
|
19760
|
+
for (const f of document.querySelectorAll("input, select, textarea")) {
|
|
19761
|
+
if (["hidden", "submit", "button", "reset", "image"].includes(f.type)) continue;
|
|
19762
|
+
if (!visible(f)) continue;
|
|
19763
|
+
const forLabel = f.id
|
|
19764
|
+
? document.querySelector('label[for="' + CSS.escape(f.id) + '"]')
|
|
19765
|
+
: null;
|
|
19766
|
+
const name =
|
|
19767
|
+
clean(f.getAttribute("aria-label")) ||
|
|
19768
|
+
clean(forLabel?.innerText) ||
|
|
19769
|
+
clean(f.closest("label")?.innerText) ||
|
|
19770
|
+
clean(f.placeholder) ||
|
|
19771
|
+
clean(f.name);
|
|
19772
|
+
if (name && !fields.includes(name)) fields.push(name.slice(0, 80));
|
|
19773
|
+
if (fields.length >= 40) break;
|
|
19774
|
+
}
|
|
19775
|
+
|
|
19776
|
+
return { url: location.href, title: clean(document.title), headings, fields, candidates };
|
|
19777
|
+
})()`;
|
|
19778
|
+
}
|
|
19779
|
+
});
|
|
19780
|
+
|
|
19781
|
+
// src/commands/explore.ts
|
|
19782
|
+
var explore_exports = {};
|
|
19783
|
+
__export(explore_exports, {
|
|
19784
|
+
exploreCoverageCommand: () => exploreCoverageCommand,
|
|
19785
|
+
exploreDiffCommand: () => exploreDiffCommand,
|
|
19786
|
+
exploreList: () => exploreList,
|
|
19787
|
+
exploreRun: () => exploreRun
|
|
19788
|
+
});
|
|
19789
|
+
import { promises as fs28 } from "fs";
|
|
19790
|
+
import path25 from "path";
|
|
19791
|
+
import chalk26 from "chalk";
|
|
19792
|
+
function fail2(message, json2) {
|
|
19793
|
+
if (json2) console.error(JSON.stringify({ error: message }));
|
|
19794
|
+
else console.error(chalk26.red(` ${message}`));
|
|
19795
|
+
process.exitCode = 1;
|
|
19796
|
+
}
|
|
19797
|
+
function positiveInt(raw, fallback, flag) {
|
|
19798
|
+
if (raw === void 0) return fallback;
|
|
19799
|
+
const n = Number(raw);
|
|
19800
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
19801
|
+
throw new Error(`${flag} must be a positive whole number, got "${raw}"`);
|
|
19802
|
+
}
|
|
19803
|
+
return n;
|
|
19804
|
+
}
|
|
19805
|
+
async function testabilityTarget2(projectRoot) {
|
|
19806
|
+
try {
|
|
19807
|
+
const raw = await fs28.readFile(
|
|
19808
|
+
path25.join(projectRoot, "iamu", "testability.md"),
|
|
19809
|
+
"utf8"
|
|
19810
|
+
);
|
|
19811
|
+
const { frontmatter } = parseFrontmatter(raw);
|
|
19812
|
+
return typeof frontmatter.target_url === "string" ? frontmatter.target_url : null;
|
|
19813
|
+
} catch {
|
|
19814
|
+
return null;
|
|
19815
|
+
}
|
|
19816
|
+
}
|
|
19817
|
+
async function openPage(opts) {
|
|
19818
|
+
const { chromium } = await import("playwright-core");
|
|
19819
|
+
const viewport = parseViewport(opts.viewport) ?? {
|
|
19820
|
+
width: 1280,
|
|
19821
|
+
height: 800
|
|
19822
|
+
};
|
|
19823
|
+
if (opts.attach) {
|
|
19824
|
+
const state = readBrowseState();
|
|
19825
|
+
if (!state) {
|
|
19826
|
+
throw new Error(
|
|
19827
|
+
"No browser to attach to \u2014 start one with `iamu browse start` and sign in first."
|
|
19828
|
+
);
|
|
19829
|
+
}
|
|
19830
|
+
const browser2 = await chromium.connectOverCDP(
|
|
19831
|
+
`http://127.0.0.1:${state.port}`
|
|
19832
|
+
);
|
|
19833
|
+
const context = browser2.contexts()[0] ?? await browser2.newContext();
|
|
19834
|
+
const page2 = await context.newPage();
|
|
19835
|
+
await page2.setViewportSize(viewport);
|
|
19836
|
+
return {
|
|
19837
|
+
page: page2,
|
|
19838
|
+
close: async () => {
|
|
19839
|
+
await page2.close().catch(() => {
|
|
19840
|
+
});
|
|
19841
|
+
await browser2.close().catch(() => {
|
|
19842
|
+
});
|
|
19843
|
+
}
|
|
19844
|
+
};
|
|
19845
|
+
}
|
|
19846
|
+
const chrome = findChrome();
|
|
19847
|
+
if (!chrome) {
|
|
19848
|
+
throw new Error(
|
|
19849
|
+
"No Chrome or Chromium found \u2014 install one or set IAMU_BROWSER."
|
|
19850
|
+
);
|
|
19851
|
+
}
|
|
19852
|
+
const browser = await chromium.launch({
|
|
19853
|
+
executablePath: chrome,
|
|
19854
|
+
headless: true,
|
|
19855
|
+
chromiumSandbox: !shouldDisableSandbox()
|
|
19856
|
+
});
|
|
19857
|
+
const page = await browser.newPage({ viewport, serviceWorkers: "block" });
|
|
19858
|
+
return { page, close: () => browser.close().catch(() => {
|
|
19859
|
+
}) };
|
|
19860
|
+
}
|
|
19861
|
+
async function exploreRun(url, opts) {
|
|
19862
|
+
const ctx = await resolveProject(opts);
|
|
19863
|
+
if (!ctx) return;
|
|
19864
|
+
const target = url ?? await testabilityTarget2(ctx.projectRoot);
|
|
19865
|
+
if (!target) {
|
|
19866
|
+
fail2(
|
|
19867
|
+
"No URL to explore \u2014 pass one (iamu explore http://localhost:3000) or set target_url in iamu/testability.md.",
|
|
19868
|
+
opts.json
|
|
19869
|
+
);
|
|
19870
|
+
return;
|
|
19871
|
+
}
|
|
19872
|
+
let startUrl;
|
|
19873
|
+
let budget;
|
|
19874
|
+
let deny;
|
|
19875
|
+
try {
|
|
19876
|
+
startUrl = new URL(target).href;
|
|
19877
|
+
budget = {
|
|
19878
|
+
max_surfaces: positiveInt(
|
|
19879
|
+
opts.maxSurfaces,
|
|
19880
|
+
DEFAULTS.max_surfaces,
|
|
19881
|
+
"--max-surfaces"
|
|
19882
|
+
),
|
|
19883
|
+
max_actions: positiveInt(
|
|
19884
|
+
opts.maxActions,
|
|
19885
|
+
DEFAULTS.max_actions,
|
|
19886
|
+
"--max-actions"
|
|
19887
|
+
),
|
|
19888
|
+
max_depth: positiveInt(opts.maxDepth, DEFAULTS.max_depth, "--max-depth")
|
|
19889
|
+
};
|
|
19890
|
+
deny = (opts.deny ?? []).map((d) => new RegExp(d, "i"));
|
|
19891
|
+
} catch (err) {
|
|
19892
|
+
fail2(err.message, opts.json);
|
|
19893
|
+
return;
|
|
19894
|
+
}
|
|
19895
|
+
const quiet = opts.json || process.env.IAMU_QUIET === "1";
|
|
19896
|
+
if (!quiet) {
|
|
19897
|
+
console.error(chalk26.dim(` Exploring ${startUrl} (read-only)\u2026`));
|
|
19898
|
+
}
|
|
19899
|
+
const now = /* @__PURE__ */ new Date();
|
|
19900
|
+
let opened = null;
|
|
19901
|
+
let result;
|
|
19902
|
+
try {
|
|
19903
|
+
opened = await openPage(opts);
|
|
19904
|
+
result = await crawl(opened.page, {
|
|
19905
|
+
startUrl,
|
|
19906
|
+
budget,
|
|
19907
|
+
deny,
|
|
19908
|
+
log: quiet ? void 0 : (line) => console.error(chalk26.dim(line))
|
|
19909
|
+
});
|
|
19910
|
+
} catch (err) {
|
|
19911
|
+
fail2(err.message.split("\n")[0], opts.json);
|
|
19912
|
+
return;
|
|
19913
|
+
} finally {
|
|
19914
|
+
await opened?.close();
|
|
19915
|
+
}
|
|
19916
|
+
const build = detectBuild({ cwd: ctx.projectRoot });
|
|
19917
|
+
const map2 = {
|
|
19918
|
+
schema: 1,
|
|
19919
|
+
id: composeExploreId(now),
|
|
19920
|
+
created_at: now.toISOString(),
|
|
19921
|
+
target_url: startUrl,
|
|
19922
|
+
...build ? { build: { ...build } } : {},
|
|
19923
|
+
budget,
|
|
19924
|
+
stats: result.stats,
|
|
19925
|
+
surfaces: result.surfaces
|
|
19926
|
+
};
|
|
19927
|
+
const file = await writeExploreMap(ctx.projectRoot, map2);
|
|
19928
|
+
const rel = path25.relative(process.cwd(), file);
|
|
19929
|
+
if (opts.json) {
|
|
19930
|
+
console.log(JSON.stringify({ id: map2.id, path: rel, stats: map2.stats }));
|
|
19931
|
+
return;
|
|
19932
|
+
}
|
|
19933
|
+
const s = map2.stats;
|
|
19934
|
+
console.log(
|
|
19935
|
+
`
|
|
19936
|
+
${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` : ""}`
|
|
19937
|
+
);
|
|
19938
|
+
if (s.truncated) {
|
|
19939
|
+
console.log(
|
|
19940
|
+
chalk26.yellow(
|
|
19941
|
+
" Stopped by the budget before the product ran out \u2014 raise --max-surfaces / --max-actions / --max-depth to go further."
|
|
19942
|
+
)
|
|
19943
|
+
);
|
|
19944
|
+
}
|
|
19945
|
+
console.log(` Map: ${rel}`);
|
|
19946
|
+
console.log(chalk26.dim(" Next: iamu explore coverage"));
|
|
19947
|
+
}
|
|
19948
|
+
async function resolveMapId(projectRoot, id, offsetFromEnd = 1) {
|
|
19949
|
+
if (id && id !== "latest") return id;
|
|
19950
|
+
const ids = await listExploreMaps(projectRoot);
|
|
19951
|
+
return ids.at(-offsetFromEnd) ?? null;
|
|
19952
|
+
}
|
|
19953
|
+
async function exploreList(opts) {
|
|
19954
|
+
const ctx = await resolveProject(opts);
|
|
19955
|
+
if (!ctx) return;
|
|
19956
|
+
const rows = [];
|
|
19957
|
+
for (const id of await listExploreMaps(ctx.projectRoot)) {
|
|
19958
|
+
try {
|
|
19959
|
+
const m = await readExploreMap(ctx.projectRoot, id);
|
|
19960
|
+
rows.push({ id, target_url: m.target_url, surfaces: m.surfaces.length });
|
|
19961
|
+
} catch {
|
|
19962
|
+
}
|
|
19963
|
+
}
|
|
19964
|
+
if (opts.json) {
|
|
19965
|
+
console.log(JSON.stringify({ maps: rows }));
|
|
19966
|
+
return;
|
|
19967
|
+
}
|
|
19968
|
+
if (rows.length === 0) {
|
|
19969
|
+
console.log(" No maps yet. Run: iamu explore <url>");
|
|
19970
|
+
return;
|
|
19971
|
+
}
|
|
19972
|
+
for (const r of rows) {
|
|
19973
|
+
console.log(
|
|
19974
|
+
` ${r.id} ${r.surfaces} surfaces ${chalk26.dim(r.target_url)}`
|
|
19975
|
+
);
|
|
19976
|
+
}
|
|
19977
|
+
}
|
|
19978
|
+
async function exploreCoverageCommand(mapId, opts) {
|
|
19979
|
+
const ctx = await resolveProject(opts);
|
|
19980
|
+
if (!ctx) return;
|
|
19981
|
+
const id = await resolveMapId(ctx.projectRoot, mapId);
|
|
19982
|
+
if (!id) {
|
|
19983
|
+
fail2("No map yet. Run: iamu explore <url>", opts.json);
|
|
19984
|
+
return;
|
|
19985
|
+
}
|
|
19986
|
+
let map2;
|
|
19987
|
+
try {
|
|
19988
|
+
map2 = await readExploreMap(ctx.projectRoot, id);
|
|
19989
|
+
} catch (err) {
|
|
19990
|
+
fail2(err.message, opts.json);
|
|
19991
|
+
return;
|
|
19992
|
+
}
|
|
19993
|
+
const cov = exploreCoverage(map2, await readAllRunSteps(ctx.projectRoot));
|
|
19994
|
+
if (opts.json) {
|
|
19995
|
+
console.log(JSON.stringify(cov));
|
|
19996
|
+
return;
|
|
19997
|
+
}
|
|
19998
|
+
console.log(
|
|
19999
|
+
`
|
|
20000
|
+
Map ${id} (${map2.surfaces.length} surfaces) against ${cov.runs} runs, ${cov.steps} steps`
|
|
20001
|
+
);
|
|
20002
|
+
console.log(
|
|
20003
|
+
` ${chalk26.green(`${cov.reached} reached`)} \xB7 ${chalk26.yellow(`${cov.place_only} URL reached, view unclear`)} \xB7 ${chalk26.red(`${cov.unreached} never reached`)}`
|
|
20004
|
+
);
|
|
20005
|
+
const section = (title, rows, color) => {
|
|
20006
|
+
if (rows.length === 0) return;
|
|
20007
|
+
console.log(`
|
|
20008
|
+
${color(title)}`);
|
|
20009
|
+
for (const r of rows) {
|
|
20010
|
+
console.log(
|
|
20011
|
+
` ${r.key.padEnd(32)} ${r.label}${chalk26.dim(` (depth ${r.depth})`)}`
|
|
20012
|
+
);
|
|
20013
|
+
}
|
|
20014
|
+
};
|
|
20015
|
+
section(
|
|
20016
|
+
"Never reached by a run",
|
|
20017
|
+
cov.surfaces.filter((s) => s.status === "unreached"),
|
|
20018
|
+
chalk26.red
|
|
20019
|
+
);
|
|
20020
|
+
section(
|
|
20021
|
+
"URL reached, but no step names this view",
|
|
20022
|
+
cov.surfaces.filter((s) => s.status === "place_only"),
|
|
20023
|
+
chalk26.yellow
|
|
20024
|
+
);
|
|
20025
|
+
if (cov.run_only_places.length > 0) {
|
|
20026
|
+
console.log(`
|
|
20027
|
+
${chalk26.cyan("Walked by runs, not in the map")}`);
|
|
20028
|
+
for (const p of cov.run_only_places) {
|
|
20029
|
+
console.log(
|
|
20030
|
+
` ${p.place.padEnd(32)} ${p.steps} steps, ${p.runs.length} runs`
|
|
20031
|
+
);
|
|
20032
|
+
}
|
|
20033
|
+
}
|
|
20034
|
+
if (cov.runs === 0) {
|
|
20035
|
+
console.log(
|
|
20036
|
+
chalk26.dim(
|
|
20037
|
+
"\n No runs yet \u2014 every surface counts as never reached. Walk a scenario first (iamu-run)."
|
|
20038
|
+
)
|
|
20039
|
+
);
|
|
20040
|
+
}
|
|
20041
|
+
}
|
|
20042
|
+
async function exploreDiffCommand(a, b, opts) {
|
|
20043
|
+
const ctx = await resolveProject(opts);
|
|
20044
|
+
if (!ctx) return;
|
|
20045
|
+
const idA = a ?? await resolveMapId(ctx.projectRoot, void 0, 2);
|
|
20046
|
+
const idB = b ?? await resolveMapId(ctx.projectRoot, void 0, 1);
|
|
20047
|
+
if (!idA || !idB || idA === idB) {
|
|
20048
|
+
fail2(
|
|
20049
|
+
"Need two maps to compare \u2014 explore again (iamu explore <url>), or name both maps.",
|
|
20050
|
+
opts.json
|
|
20051
|
+
);
|
|
20052
|
+
return;
|
|
20053
|
+
}
|
|
20054
|
+
let mapA;
|
|
20055
|
+
let mapB;
|
|
20056
|
+
try {
|
|
20057
|
+
mapA = await readExploreMap(ctx.projectRoot, idA);
|
|
20058
|
+
mapB = await readExploreMap(ctx.projectRoot, idB);
|
|
20059
|
+
} catch (err) {
|
|
20060
|
+
fail2(err.message, opts.json);
|
|
20061
|
+
return;
|
|
20062
|
+
}
|
|
20063
|
+
const d = diffExploreMaps(mapA, mapB);
|
|
20064
|
+
if (opts.json) {
|
|
20065
|
+
console.log(JSON.stringify(d));
|
|
20066
|
+
return;
|
|
20067
|
+
}
|
|
20068
|
+
const pct = Math.round(d.surface_overlap * 100);
|
|
20069
|
+
console.log(`
|
|
20070
|
+
${idA} \u2192 ${idB}`);
|
|
20071
|
+
console.log(
|
|
20072
|
+
` ${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)`
|
|
20073
|
+
);
|
|
20074
|
+
if (d.only_a.length > 0) {
|
|
20075
|
+
console.log(`
|
|
20076
|
+
${chalk26.red("Only in the first")}`);
|
|
20077
|
+
for (const s of d.only_a) console.log(` ${s.key.padEnd(32)} ${s.label}`);
|
|
20078
|
+
}
|
|
20079
|
+
if (d.only_b.length > 0) {
|
|
20080
|
+
console.log(`
|
|
20081
|
+
${chalk26.green("Only in the second")}`);
|
|
20082
|
+
for (const s of d.only_b) console.log(` ${s.key.padEnd(32)} ${s.label}`);
|
|
20083
|
+
}
|
|
20084
|
+
if (d.changed_actions.length > 0) {
|
|
20085
|
+
console.log(
|
|
20086
|
+
`
|
|
20087
|
+
${chalk26.yellow(`Controls differ on ${d.changed_actions.length} shared surfaces`)}`
|
|
20088
|
+
);
|
|
20089
|
+
for (const c of d.changed_actions) {
|
|
20090
|
+
console.log(` ${c.key} ${c.label}`);
|
|
20091
|
+
for (const x of c.only_a) console.log(chalk26.red(` - ${x}`));
|
|
20092
|
+
for (const x of c.only_b) console.log(chalk26.green(` + ${x}`));
|
|
20093
|
+
}
|
|
20094
|
+
}
|
|
20095
|
+
}
|
|
20096
|
+
var DEFAULTS;
|
|
20097
|
+
var init_explore = __esm({
|
|
20098
|
+
"src/commands/explore.ts"() {
|
|
20099
|
+
"use strict";
|
|
20100
|
+
init_dist2();
|
|
20101
|
+
init_build_info();
|
|
20102
|
+
init_browse();
|
|
20103
|
+
init_context4();
|
|
20104
|
+
init_explore_crawl();
|
|
20105
|
+
DEFAULTS = { max_surfaces: 40, max_actions: 300, max_depth: 6 };
|
|
20106
|
+
}
|
|
20107
|
+
});
|
|
20108
|
+
|
|
18489
20109
|
// src/commands/completion.ts
|
|
18490
20110
|
var completion_exports = {};
|
|
18491
20111
|
__export(completion_exports, {
|
|
@@ -18676,13 +20296,14 @@ var init_completion = __esm({
|
|
|
18676
20296
|
var instructions_walk_exports = {};
|
|
18677
20297
|
__export(instructions_walk_exports, {
|
|
18678
20298
|
composeWalkerPrompt: () => composeWalkerPrompt,
|
|
20299
|
+
extractUntestableBoundaries: () => extractUntestableBoundaries,
|
|
18679
20300
|
instructionsWalk: () => instructionsWalk,
|
|
18680
20301
|
revisitSection: () => revisitSection,
|
|
18681
20302
|
viewportLine: () => viewportLine
|
|
18682
20303
|
});
|
|
18683
20304
|
import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
|
|
18684
20305
|
import { join as join9 } from "path";
|
|
18685
|
-
import
|
|
20306
|
+
import chalk27 from "chalk";
|
|
18686
20307
|
function selfCommand() {
|
|
18687
20308
|
const q = (p) => /[\s"'$]/.test(p) ? JSON.stringify(p) : p;
|
|
18688
20309
|
return `${q(process.execPath)} ${q(process.argv[1] ?? "iamu")}`;
|
|
@@ -18713,6 +20334,24 @@ function browseSurface(iamu, viewport) {
|
|
|
18713
20334
|
...viewport ? [viewportLine(viewport)] : []
|
|
18714
20335
|
].join("\n");
|
|
18715
20336
|
}
|
|
20337
|
+
function extractUntestableBoundaries(body) {
|
|
20338
|
+
const match2 = body.match(
|
|
20339
|
+
/##\s+Untestable\s+Boundaries[^\n]*\n([\s\S]*?)(?=\n##|\s*$)/i
|
|
20340
|
+
);
|
|
20341
|
+
if (!match2) return [];
|
|
20342
|
+
const section = match2[1];
|
|
20343
|
+
const items = [];
|
|
20344
|
+
for (const line of section.split("\n")) {
|
|
20345
|
+
const trimmed = line.trim();
|
|
20346
|
+
if (trimmed.startsWith("- ") || trimmed.startsWith("\u2022 ") || trimmed.startsWith("* ")) {
|
|
20347
|
+
const item = trimmed.slice(2).trim();
|
|
20348
|
+
if (item && !item.toLowerCase().includes("none documented")) {
|
|
20349
|
+
items.push(item);
|
|
20350
|
+
}
|
|
20351
|
+
}
|
|
20352
|
+
}
|
|
20353
|
+
return items;
|
|
20354
|
+
}
|
|
18716
20355
|
function guidanceLine(r) {
|
|
18717
20356
|
const rule = r.rule.replace(/\s+/g, " ");
|
|
18718
20357
|
return r.reason ? `- ${rule} (${r.reason.replace(/\s+/g, " ")})` : `- ${rule}`;
|
|
@@ -18785,6 +20424,23 @@ function composeWalkerPrompt(p) {
|
|
|
18785
20424
|
"",
|
|
18786
20425
|
"## First-person brief",
|
|
18787
20426
|
p.brief ?? "",
|
|
20427
|
+
...p.context ? ["", "## Product domain & regulatory context", p.context.trim()] : [],
|
|
20428
|
+
...p.state?.description || p.state?.approximation ? [
|
|
20429
|
+
"",
|
|
20430
|
+
"## Expected state & semantic squinting",
|
|
20431
|
+
p.state.description ? `Expected state: ${p.state.description}` : "",
|
|
20432
|
+
p.state.approximation ? `Approximation strategy: ${p.state.approximation}` : "",
|
|
20433
|
+
"Squinting Guardrails:",
|
|
20434
|
+
"\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.",
|
|
20435
|
+
"\u2022 Squinting does NOT permit drifting to unrelated settings, account profiles, or off-target pages. Stay strictly within the scenario journey.",
|
|
20436
|
+
"\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."
|
|
20437
|
+
].filter(Boolean) : [],
|
|
20438
|
+
...p.untestableBoundaries && p.untestableBoundaries.length > 0 ? [
|
|
20439
|
+
"",
|
|
20440
|
+
"## Untestable Boundaries (DO NOT WALK)",
|
|
20441
|
+
"These areas are un-mocked or live external services. Do NOT attempt to navigate, submit, or interact with them:",
|
|
20442
|
+
...p.untestableBoundaries.map((b) => `\u2022 ${b}`)
|
|
20443
|
+
] : [],
|
|
18788
20444
|
...revisitSection(p.revisit ?? []),
|
|
18789
20445
|
"",
|
|
18790
20446
|
"## Access",
|
|
@@ -18813,7 +20469,7 @@ function composeWalkerPrompt(p) {
|
|
|
18813
20469
|
`Screenshots to: ${rd}/screenshots/step-N.png \u2014 absolute paths.`,
|
|
18814
20470
|
`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.`,
|
|
18815
20471
|
"`--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.",
|
|
18816
|
-
`Before you finish, write ${rd}/summary.json: {"what_worked": [...], "open_questions": [...]} \u2014 one sentence each, in the persona's language.`,
|
|
20472
|
+
`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.`,
|
|
18817
20473
|
"",
|
|
18818
20474
|
"Do not read the product's source code, other iamu/ entity files, or earlier runs: you know only what the persona knows.",
|
|
18819
20475
|
"",
|
|
@@ -18923,6 +20579,25 @@ async function instructionsWalk(opts) {
|
|
|
18923
20579
|
if (!targetUrl) {
|
|
18924
20580
|
targetUrl = sfm.target_url ?? null;
|
|
18925
20581
|
}
|
|
20582
|
+
const untestableBoundaries = [];
|
|
20583
|
+
try {
|
|
20584
|
+
const testabilityPath = join9(projectRoot, "iamu", "testability.md");
|
|
20585
|
+
if (existsSync12(testabilityPath)) {
|
|
20586
|
+
const raw = readFileSync8(testabilityPath, "utf-8");
|
|
20587
|
+
const { frontmatter, body } = parseFrontmatter(raw);
|
|
20588
|
+
if (!targetUrl && typeof frontmatter.target_url === "string" && frontmatter.target_url.trim().length > 0) {
|
|
20589
|
+
targetUrl = frontmatter.target_url.trim();
|
|
20590
|
+
}
|
|
20591
|
+
const fromFm = Array.isArray(
|
|
20592
|
+
frontmatter.untestable_boundaries
|
|
20593
|
+
) ? frontmatter.untestable_boundaries.filter(Boolean) : [];
|
|
20594
|
+
const fromBody = extractUntestableBoundaries(body);
|
|
20595
|
+
untestableBoundaries.push(
|
|
20596
|
+
...Array.from(/* @__PURE__ */ new Set([...fromFm, ...fromBody]))
|
|
20597
|
+
);
|
|
20598
|
+
}
|
|
20599
|
+
} catch {
|
|
20600
|
+
}
|
|
18926
20601
|
if (!revisit) {
|
|
18927
20602
|
try {
|
|
18928
20603
|
revisit = await computeRevisitPlaces(projectRoot, sfm.slug, targetUrl);
|
|
@@ -18933,7 +20608,8 @@ async function instructionsWalk(opts) {
|
|
|
18933
20608
|
const result = {
|
|
18934
20609
|
scenario: {
|
|
18935
20610
|
slug: sfm.slug,
|
|
18936
|
-
brief: scenario.body.trim() || null
|
|
20611
|
+
brief: scenario.body.trim() || null,
|
|
20612
|
+
state: sfm.state ?? null
|
|
18937
20613
|
},
|
|
18938
20614
|
persona: {
|
|
18939
20615
|
slug: sfm.persona,
|
|
@@ -18954,6 +20630,7 @@ async function instructionsWalk(opts) {
|
|
|
18954
20630
|
methodology,
|
|
18955
20631
|
dismissed_findings: dismissedFacts,
|
|
18956
20632
|
tester_guidance: guidance,
|
|
20633
|
+
untestable_boundaries: untestableBoundaries,
|
|
18957
20634
|
// Where fixes awaiting a re-run were seen (for you; the walker gets
|
|
18958
20635
|
// the places only, never which findings they belong to).
|
|
18959
20636
|
revisit,
|
|
@@ -18987,7 +20664,10 @@ async function instructionsWalk(opts) {
|
|
|
18987
20664
|
runDirPath,
|
|
18988
20665
|
projectRoot,
|
|
18989
20666
|
surface: opts.surface ?? "browse",
|
|
18990
|
-
viewport
|
|
20667
|
+
viewport,
|
|
20668
|
+
state: sfm.state ?? null,
|
|
20669
|
+
context: contextYml,
|
|
20670
|
+
untestableBoundaries
|
|
18991
20671
|
}) : null
|
|
18992
20672
|
};
|
|
18993
20673
|
if (opts.json) {
|
|
@@ -18995,30 +20675,40 @@ async function instructionsWalk(opts) {
|
|
|
18995
20675
|
return;
|
|
18996
20676
|
}
|
|
18997
20677
|
console.log();
|
|
18998
|
-
console.log(
|
|
18999
|
-
console.log(` ${
|
|
19000
|
-
console.log(` ${
|
|
19001
|
-
console.log(` ${
|
|
19002
|
-
if (
|
|
20678
|
+
console.log(chalk27.bold(" Walk instructions"));
|
|
20679
|
+
console.log(` ${chalk27.dim("scenario:")} ${sfm.slug}`);
|
|
20680
|
+
console.log(` ${chalk27.dim("persona:")} ${pfm.name} (${pfm.role})`);
|
|
20681
|
+
console.log(` ${chalk27.dim("outline:")} ${ofm.title}`);
|
|
20682
|
+
if (sfm.state?.description || sfm.state?.approximation) {
|
|
20683
|
+
console.log(
|
|
20684
|
+
` ${chalk27.dim("state:")} ${sfm.state.description ? sfm.state.description : sfm.state.approximation ?? ""}`
|
|
20685
|
+
);
|
|
20686
|
+
}
|
|
20687
|
+
if (targetUrl) console.log(` ${chalk27.dim("target:")} ${targetUrl}`);
|
|
19003
20688
|
console.log(
|
|
19004
|
-
` ${
|
|
20689
|
+
` ${chalk27.dim("access:")} ${projectAccess || personaAccess ? `${projectAccess ? "project" : ""}${projectAccess && personaAccess ? " + " : ""}${personaAccess ? "persona" : ""}` : "not recorded (walker would run as whoever is signed in)"}`
|
|
19005
20690
|
);
|
|
19006
20691
|
console.log(
|
|
19007
|
-
` ${
|
|
20692
|
+
` ${chalk27.dim("dismissed:")} ${dismissedFacts.length} findings to skip`
|
|
19008
20693
|
);
|
|
19009
20694
|
console.log(
|
|
19010
|
-
` ${
|
|
20695
|
+
` ${chalk27.dim("guidance:")} ${guidance.length} tester guidance rule${guidance.length === 1 ? "" : "s"}`
|
|
19011
20696
|
);
|
|
20697
|
+
if (untestableBoundaries.length > 0) {
|
|
20698
|
+
console.log(
|
|
20699
|
+
` ${chalk27.dim("boundaries:")} ${untestableBoundaries.length} untestable boundaries to avoid`
|
|
20700
|
+
);
|
|
20701
|
+
}
|
|
19012
20702
|
if (revisit.length > 0) {
|
|
19013
20703
|
console.log(
|
|
19014
|
-
` ${
|
|
20704
|
+
` ${chalk27.dim("re-check:")} ${revisit.length} place${revisit.length === 1 ? "" : "s"} where fixes await a re-run`
|
|
19015
20705
|
);
|
|
19016
20706
|
}
|
|
19017
20707
|
console.log(
|
|
19018
|
-
` ${
|
|
20708
|
+
` ${chalk27.dim("methodology:")} ${methodology ? "loaded" : "missing (run iamu update)"}`
|
|
19019
20709
|
);
|
|
19020
20710
|
console.log();
|
|
19021
|
-
console.log(
|
|
20711
|
+
console.log(chalk27.dim(" Use --json to get the full instruction bundle."));
|
|
19022
20712
|
console.log();
|
|
19023
20713
|
}
|
|
19024
20714
|
var SCREEN;
|
|
@@ -19044,8 +20734,8 @@ __export(new_finding_exports, {
|
|
|
19044
20734
|
});
|
|
19045
20735
|
import { randomUUID as randomUUID10 } from "crypto";
|
|
19046
20736
|
import { access, readFile as readFile6 } from "fs/promises";
|
|
19047
|
-
import
|
|
19048
|
-
import
|
|
20737
|
+
import path26 from "path";
|
|
20738
|
+
import chalk28 from "chalk";
|
|
19049
20739
|
async function newFinding(opts) {
|
|
19050
20740
|
const ctx = await resolveProject(opts);
|
|
19051
20741
|
if (!ctx) return;
|
|
@@ -19111,7 +20801,7 @@ async function newFinding(opts) {
|
|
|
19111
20801
|
}
|
|
19112
20802
|
try {
|
|
19113
20803
|
await access(
|
|
19114
|
-
|
|
20804
|
+
path26.join(runDirPath, "steps", `${String(n).padStart(3, "0")}.json`)
|
|
19115
20805
|
);
|
|
19116
20806
|
} catch {
|
|
19117
20807
|
commandError(
|
|
@@ -19126,7 +20816,7 @@ async function newFinding(opts) {
|
|
|
19126
20816
|
if (!scenarioSlug) {
|
|
19127
20817
|
try {
|
|
19128
20818
|
const manifestRaw = await readFile6(
|
|
19129
|
-
|
|
20819
|
+
path26.join(runDirPath, "walkthrough.json"),
|
|
19130
20820
|
"utf8"
|
|
19131
20821
|
);
|
|
19132
20822
|
const manifest = JSON.parse(manifestRaw);
|
|
@@ -19215,10 +20905,10 @@ async function newFinding(opts) {
|
|
|
19215
20905
|
return;
|
|
19216
20906
|
}
|
|
19217
20907
|
console.log();
|
|
19218
|
-
console.log(` ${symbols.success} Created finding: ${
|
|
19219
|
-
console.log(` ${
|
|
19220
|
-
console.log(` ${
|
|
19221
|
-
for (const w of warnings) console.log(` ${
|
|
20908
|
+
console.log(` ${symbols.success} Created finding: ${chalk28.cyan(slug)}`);
|
|
20909
|
+
console.log(` ${chalk28.dim("path:")} ${record.path}`);
|
|
20910
|
+
console.log(` ${chalk28.dim("id:")} ${id}`);
|
|
20911
|
+
for (const w of warnings) console.log(` ${chalk28.yellow("!")} ${w}`);
|
|
19222
20912
|
console.log();
|
|
19223
20913
|
} catch (err) {
|
|
19224
20914
|
commandError(
|
|
@@ -19243,8 +20933,8 @@ __export(view_results_exports, {
|
|
|
19243
20933
|
openBrowserResults: () => openBrowserResults,
|
|
19244
20934
|
promptViewResults: () => promptViewResults
|
|
19245
20935
|
});
|
|
19246
|
-
import { isCancel as isCancel4, select as
|
|
19247
|
-
import
|
|
20936
|
+
import { isCancel as isCancel4, select as select4 } from "@clack/prompts";
|
|
20937
|
+
import chalk29 from "chalk";
|
|
19248
20938
|
function launchCloudDashboard(config, runId) {
|
|
19249
20939
|
const cloudRunId = runId ? toCloudRunUuid(runId) : void 0;
|
|
19250
20940
|
const url = getCloudDashboardUrl(
|
|
@@ -19253,7 +20943,7 @@ function launchCloudDashboard(config, runId) {
|
|
|
19253
20943
|
cloudRunId
|
|
19254
20944
|
);
|
|
19255
20945
|
console.log();
|
|
19256
|
-
console.log(` ${symbols.success} Opening Iamu Cloud: ${
|
|
20946
|
+
console.log(` ${symbols.success} Opening Iamu Cloud: ${chalk29.cyan(url)}
|
|
19257
20947
|
`);
|
|
19258
20948
|
openBrowser(url);
|
|
19259
20949
|
}
|
|
@@ -19289,7 +20979,7 @@ async function promptViewResults(projectRoot, opts) {
|
|
|
19289
20979
|
},
|
|
19290
20980
|
{ label: "Skip", value: "skip" }
|
|
19291
20981
|
];
|
|
19292
|
-
const choice = await
|
|
20982
|
+
const choice = await select4({
|
|
19293
20983
|
message: "View results?",
|
|
19294
20984
|
options
|
|
19295
20985
|
});
|
|
@@ -19321,9 +21011,9 @@ __export(evaluate_exports, {
|
|
|
19321
21011
|
evaluate: () => evaluate,
|
|
19322
21012
|
evaluateRecord: () => evaluateRecord
|
|
19323
21013
|
});
|
|
19324
|
-
import { promises as
|
|
19325
|
-
import
|
|
19326
|
-
import
|
|
21014
|
+
import { promises as fs29 } from "fs";
|
|
21015
|
+
import path27 from "path";
|
|
21016
|
+
import chalk30 from "chalk";
|
|
19327
21017
|
import { z as z25 } from "zod";
|
|
19328
21018
|
function formatIssues(error) {
|
|
19329
21019
|
return error.issues.map(
|
|
@@ -19353,10 +21043,10 @@ async function evaluate(runId, opts) {
|
|
|
19353
21043
|
const resolved = await resolveProjectAndRunDir(runId, opts);
|
|
19354
21044
|
if (!resolved) return;
|
|
19355
21045
|
const { projectRoot, runDirPath } = resolved;
|
|
19356
|
-
const canonicalPath =
|
|
21046
|
+
const canonicalPath = path27.join(runDirPath, "canonical.json");
|
|
19357
21047
|
let canonical;
|
|
19358
21048
|
try {
|
|
19359
|
-
const raw = await
|
|
21049
|
+
const raw = await fs29.readFile(canonicalPath, "utf8");
|
|
19360
21050
|
canonical = JSON.parse(raw);
|
|
19361
21051
|
} catch {
|
|
19362
21052
|
commandError(
|
|
@@ -19412,8 +21102,21 @@ async function evaluate(runId, opts) {
|
|
|
19412
21102
|
)
|
|
19413
21103
|
};
|
|
19414
21104
|
const prompt = composeJudgePrompt(input);
|
|
21105
|
+
const promptFilePath = opts?.promptFile ?? path27.join(runDirPath, "judge_prompt.md");
|
|
21106
|
+
await fs29.writeFile(promptFilePath, prompt, "utf8");
|
|
19415
21107
|
if (opts?.json) {
|
|
19416
|
-
console.log(
|
|
21108
|
+
console.log(
|
|
21109
|
+
JSON.stringify(
|
|
21110
|
+
{
|
|
21111
|
+
...input,
|
|
21112
|
+
prompt_file: path27.relative(projectRoot, promptFilePath),
|
|
21113
|
+
prompt_file_abs: path27.resolve(promptFilePath),
|
|
21114
|
+
judge_prompt: prompt
|
|
21115
|
+
},
|
|
21116
|
+
null,
|
|
21117
|
+
2
|
|
21118
|
+
)
|
|
21119
|
+
);
|
|
19417
21120
|
return;
|
|
19418
21121
|
}
|
|
19419
21122
|
console.log(prompt);
|
|
@@ -19422,10 +21125,10 @@ async function evaluateRecord(runId, opts) {
|
|
|
19422
21125
|
const resolved = await resolveProjectAndRunDir(runId, opts);
|
|
19423
21126
|
if (!resolved) return;
|
|
19424
21127
|
const { projectRoot, runDirPath } = resolved;
|
|
19425
|
-
const canonicalPath =
|
|
21128
|
+
const canonicalPath = path27.join(runDirPath, "canonical.json");
|
|
19426
21129
|
let canonical;
|
|
19427
21130
|
try {
|
|
19428
|
-
const raw = await
|
|
21131
|
+
const raw = await fs29.readFile(canonicalPath, "utf8");
|
|
19429
21132
|
canonical = JSON.parse(raw);
|
|
19430
21133
|
} catch {
|
|
19431
21134
|
commandError(`No canonical.json for run: ${runId}`, opts);
|
|
@@ -19433,7 +21136,7 @@ async function evaluateRecord(runId, opts) {
|
|
|
19433
21136
|
}
|
|
19434
21137
|
let parsedFile;
|
|
19435
21138
|
try {
|
|
19436
|
-
const raw = await
|
|
21139
|
+
const raw = await fs29.readFile(opts.file, "utf8");
|
|
19437
21140
|
parsedFile = JSON.parse(raw);
|
|
19438
21141
|
} catch {
|
|
19439
21142
|
commandError(`Cannot read verdicts file: ${opts.file}`, opts);
|
|
@@ -19490,8 +21193,8 @@ ${formatIssues(fileResult.error)}`,
|
|
|
19490
21193
|
overall: inputVerdicts.overall
|
|
19491
21194
|
});
|
|
19492
21195
|
const reportContent = renderCanonicalMarkdown(canonicalForReport);
|
|
19493
|
-
const verdictsPath =
|
|
19494
|
-
const reportPath =
|
|
21196
|
+
const verdictsPath = path27.join(runDirPath, "verdicts.json");
|
|
21197
|
+
const reportPath = path27.join(runDirPath, "report.md");
|
|
19495
21198
|
await Promise.all([
|
|
19496
21199
|
atomicWriteFile(
|
|
19497
21200
|
verdictsPath,
|
|
@@ -19505,14 +21208,14 @@ ${formatIssues(fileResult.error)}`,
|
|
|
19505
21208
|
runId,
|
|
19506
21209
|
validatedVerdicts
|
|
19507
21210
|
);
|
|
19508
|
-
const cloudInfo = await pushRunToCloud(projectRoot, runId);
|
|
21211
|
+
const cloudInfo = opts?.push === false ? void 0 : await pushRunToCloud(projectRoot, runId);
|
|
19509
21212
|
if (opts?.json) {
|
|
19510
21213
|
console.log(
|
|
19511
21214
|
JSON.stringify(
|
|
19512
21215
|
{
|
|
19513
21216
|
runId,
|
|
19514
|
-
verdictsPath:
|
|
19515
|
-
reportPath:
|
|
21217
|
+
verdictsPath: path27.relative(projectRoot, verdictsPath),
|
|
21218
|
+
reportPath: path27.relative(projectRoot, reportPath),
|
|
19516
21219
|
verdictsCount: validatedVerdicts.length,
|
|
19517
21220
|
linkedCount: linked.length,
|
|
19518
21221
|
...cloudInfo ? { cloud: cloudInfo } : {}
|
|
@@ -19525,18 +21228,18 @@ ${formatIssues(fileResult.error)}`,
|
|
|
19525
21228
|
}
|
|
19526
21229
|
console.log();
|
|
19527
21230
|
console.log(
|
|
19528
|
-
` ${symbols.success} Recorded ${validatedVerdicts.length} verdicts for run ${
|
|
21231
|
+
` ${symbols.success} Recorded ${validatedVerdicts.length} verdicts for run ${chalk30.cyan(runId)}`
|
|
19529
21232
|
);
|
|
19530
21233
|
if (linked.length > 0) {
|
|
19531
21234
|
console.log(
|
|
19532
|
-
` ${
|
|
21235
|
+
` ${chalk30.dim("linked:")} ${linked.length} finding${linked.length === 1 ? "" : "s"} to the human test${linked.length === 1 ? "" : "s"} citing ${linked.length === 1 ? "it" : "them"}`
|
|
19533
21236
|
);
|
|
19534
21237
|
}
|
|
19535
21238
|
console.log(
|
|
19536
|
-
` ${
|
|
21239
|
+
` ${chalk30.dim("verdicts:")} ${path27.relative(projectRoot, verdictsPath)}`
|
|
19537
21240
|
);
|
|
19538
21241
|
console.log(
|
|
19539
|
-
` ${
|
|
21242
|
+
` ${chalk30.dim("report:")} ${path27.relative(projectRoot, reportPath)}`
|
|
19540
21243
|
);
|
|
19541
21244
|
printCloudRunInfo(cloudInfo);
|
|
19542
21245
|
await promptViewResults(projectRoot, { ...opts, runId });
|
|
@@ -19600,9 +21303,9 @@ __export(complete_exports, {
|
|
|
19600
21303
|
screenshotSizeWarning: () => screenshotSizeWarning,
|
|
19601
21304
|
validateCanonicalWalkthrough: () => validateCanonicalWalkthrough
|
|
19602
21305
|
});
|
|
19603
|
-
import { promises as
|
|
19604
|
-
import
|
|
19605
|
-
import
|
|
21306
|
+
import { promises as fs30 } from "fs";
|
|
21307
|
+
import path28 from "path";
|
|
21308
|
+
import chalk31 from "chalk";
|
|
19606
21309
|
async function complete(runId, opts) {
|
|
19607
21310
|
const run = await loadRun(runId, opts);
|
|
19608
21311
|
if (!run) return;
|
|
@@ -19612,7 +21315,7 @@ async function complete(runId, opts) {
|
|
|
19612
21315
|
}
|
|
19613
21316
|
if (!opts?.json) {
|
|
19614
21317
|
console.log();
|
|
19615
|
-
console.log(
|
|
21318
|
+
console.log(chalk31.bold(" Assembling canonical JSON\u2026"));
|
|
19616
21319
|
}
|
|
19617
21320
|
const provenanceAudit = auditStepProvenance(
|
|
19618
21321
|
await readStepsForRun(run.projectRoot, runId)
|
|
@@ -19622,6 +21325,36 @@ async function complete(runId, opts) {
|
|
|
19622
21325
|
return;
|
|
19623
21326
|
}
|
|
19624
21327
|
const completedAt = pickCompletedAt(run.manifest);
|
|
21328
|
+
const walkerSummary = await readWalkerSummary(run.runDirPath);
|
|
21329
|
+
let judgeReported;
|
|
21330
|
+
try {
|
|
21331
|
+
const verdictsRaw = await fs30.readFile(
|
|
21332
|
+
path28.join(run.runDirPath, "verdicts.json"),
|
|
21333
|
+
"utf8"
|
|
21334
|
+
);
|
|
21335
|
+
const parsed = JSON.parse(verdictsRaw);
|
|
21336
|
+
if (parsed.provenance?.model) {
|
|
21337
|
+
judgeReported = parsed.provenance.model;
|
|
21338
|
+
}
|
|
21339
|
+
} catch {
|
|
21340
|
+
}
|
|
21341
|
+
const stepCount = provenanceAudit.step_count;
|
|
21342
|
+
const durationSec = provenanceAudit.duration_seconds ?? (run.manifest.created_at && Date.parse(completedAt) > Date.parse(run.manifest.created_at) ? Math.round(
|
|
21343
|
+
(Date.parse(completedAt) - Date.parse(run.manifest.created_at)) / 1e3 * 10
|
|
21344
|
+
) / 10 : null);
|
|
21345
|
+
const velocitySec = provenanceAudit.avg_step_interval_seconds ?? (durationSec != null && stepCount > 1 ? Math.round(durationSec / (stepCount - 1) * 10) / 10 : null);
|
|
21346
|
+
const existingModels = run.manifest.models ?? {};
|
|
21347
|
+
const updatedModels = {
|
|
21348
|
+
...existingModels,
|
|
21349
|
+
...walkerSummary.model ? { walker_reported: walkerSummary.model } : {},
|
|
21350
|
+
...judgeReported ? { judge_reported: judgeReported } : {},
|
|
21351
|
+
...durationSec != null ? { total_duration_sec: durationSec } : {},
|
|
21352
|
+
...velocitySec != null ? { velocity_sec_per_step: velocitySec } : {}
|
|
21353
|
+
};
|
|
21354
|
+
run.manifest = {
|
|
21355
|
+
...run.manifest,
|
|
21356
|
+
models: updatedModels
|
|
21357
|
+
};
|
|
19625
21358
|
const assembled = await assembleRun(
|
|
19626
21359
|
run.projectRoot,
|
|
19627
21360
|
runId,
|
|
@@ -19648,16 +21381,16 @@ async function loadRun(runId, opts) {
|
|
|
19648
21381
|
if (!resolved) return null;
|
|
19649
21382
|
const { projectRoot, runDirPath } = resolved;
|
|
19650
21383
|
try {
|
|
19651
|
-
await
|
|
21384
|
+
await fs30.access(runDirPath);
|
|
19652
21385
|
} catch {
|
|
19653
21386
|
commandError(`Run not found: ${runId}`, opts);
|
|
19654
21387
|
return null;
|
|
19655
21388
|
}
|
|
19656
|
-
const manifestPath =
|
|
21389
|
+
const manifestPath = path28.join(runDirPath, "walkthrough.json");
|
|
19657
21390
|
let manifest;
|
|
19658
21391
|
try {
|
|
19659
21392
|
manifest = JSON.parse(
|
|
19660
|
-
await
|
|
21393
|
+
await fs30.readFile(manifestPath, "utf8")
|
|
19661
21394
|
);
|
|
19662
21395
|
} catch {
|
|
19663
21396
|
commandError(`No walkthrough.json found for run: ${runId}`, opts);
|
|
@@ -19669,8 +21402,8 @@ async function reportAlreadyCompleted(run, opts) {
|
|
|
19669
21402
|
const { runId, manifest } = run;
|
|
19670
21403
|
if (opts?.json) {
|
|
19671
21404
|
try {
|
|
19672
|
-
const raw = await
|
|
19673
|
-
|
|
21405
|
+
const raw = await fs30.readFile(
|
|
21406
|
+
path28.join(run.runDirPath, "canonical.json"),
|
|
19674
21407
|
"utf8"
|
|
19675
21408
|
);
|
|
19676
21409
|
console.log(raw.trim());
|
|
@@ -19690,18 +21423,18 @@ async function reportAlreadyCompleted(run, opts) {
|
|
|
19690
21423
|
return;
|
|
19691
21424
|
}
|
|
19692
21425
|
console.log();
|
|
19693
|
-
console.log(
|
|
19694
|
-
console.log(` status ${
|
|
21426
|
+
console.log(chalk31.bold(" Run: ") + chalk31.cyan(runId));
|
|
21427
|
+
console.log(` status ${chalk31.green("completed")}`);
|
|
19695
21428
|
if (manifest.completed_at) {
|
|
19696
|
-
console.log(` completed ${
|
|
21429
|
+
console.log(` completed ${chalk31.dim(manifest.completed_at)}`);
|
|
19697
21430
|
}
|
|
19698
21431
|
const viewUrl = await pushedRunUrl(run.projectRoot, runId);
|
|
19699
21432
|
if (viewUrl) {
|
|
19700
|
-
console.log(` view ${
|
|
21433
|
+
console.log(` view ${chalk31.underline(viewUrl)}`);
|
|
19701
21434
|
}
|
|
19702
21435
|
console.log();
|
|
19703
21436
|
console.log(
|
|
19704
|
-
|
|
21437
|
+
chalk31.dim(
|
|
19705
21438
|
" Already completed \u2014 nothing to do. Use --force to reassemble the report."
|
|
19706
21439
|
)
|
|
19707
21440
|
);
|
|
@@ -19717,13 +21450,13 @@ function reportStrictProvenanceFailure(audit, opts) {
|
|
|
19717
21450
|
}
|
|
19718
21451
|
console.error();
|
|
19719
21452
|
console.error(
|
|
19720
|
-
|
|
21453
|
+
chalk31.red(` ${symbols.error} Strict provenance audit failed:`)
|
|
19721
21454
|
);
|
|
19722
21455
|
for (const w of audit.warnings) {
|
|
19723
|
-
console.error(
|
|
21456
|
+
console.error(chalk31.red(` \u2022 ${w}`));
|
|
19724
21457
|
}
|
|
19725
21458
|
console.error(
|
|
19726
|
-
|
|
21459
|
+
chalk31.dim(
|
|
19727
21460
|
" Run was flagged as synthetic / scripted. Remove --strict-provenance to complete anyway."
|
|
19728
21461
|
)
|
|
19729
21462
|
);
|
|
@@ -19749,7 +21482,7 @@ async function collectCompletionExtras(run, opts) {
|
|
|
19749
21482
|
parseRunViewport(manifest.viewport)
|
|
19750
21483
|
);
|
|
19751
21484
|
const codeWarning = opts?.force ? null : codeChangedWarning(manifest.build, detectWorktreeState(projectRoot));
|
|
19752
|
-
const cloudInfo = await pushRunToCloud(projectRoot, runId);
|
|
21485
|
+
const cloudInfo = opts?.push === false ? void 0 : await pushRunToCloud(projectRoot, runId);
|
|
19753
21486
|
return { revisit, viewportWarning, codeWarning, cloudInfo };
|
|
19754
21487
|
}
|
|
19755
21488
|
function completeJsonPayload(canonical, extras) {
|
|
@@ -19764,30 +21497,41 @@ function completeJsonPayload(canonical, extras) {
|
|
|
19764
21497
|
function printCompletionSummary(run, completedAt, assembled, extras, provenanceAudit) {
|
|
19765
21498
|
const { runId, projectRoot, manifest } = run;
|
|
19766
21499
|
const { canonical } = assembled;
|
|
19767
|
-
const count = (n) => n === 0 ?
|
|
19768
|
-
console.log(
|
|
19769
|
-
console.log(` scenario ${
|
|
19770
|
-
console.log(` url ${
|
|
21500
|
+
const count = (n) => n === 0 ? chalk31.dim("0") : chalk31.cyan(String(n));
|
|
21501
|
+
console.log(chalk31.bold(" Run: ") + chalk31.cyan(runId));
|
|
21502
|
+
console.log(` scenario ${chalk31.dim(manifest.scenario_slug)}`);
|
|
21503
|
+
console.log(` url ${chalk31.dim(manifest.target_url)}`);
|
|
19771
21504
|
if (manifest.environment) {
|
|
19772
|
-
console.log(` env ${
|
|
21505
|
+
console.log(` env ${chalk31.dim(manifest.environment)}`);
|
|
19773
21506
|
}
|
|
19774
21507
|
if (manifest.build) {
|
|
19775
|
-
console.log(` build ${
|
|
21508
|
+
console.log(` build ${chalk31.dim(formatBuild(manifest.build))}`);
|
|
19776
21509
|
}
|
|
19777
21510
|
if (manifest.viewport) {
|
|
19778
|
-
console.log(` device ${
|
|
21511
|
+
console.log(` device ${chalk31.dim(formatViewport(manifest.viewport))}`);
|
|
19779
21512
|
}
|
|
19780
|
-
console.log(` status ${
|
|
21513
|
+
console.log(` status ${chalk31.green("completed")}`);
|
|
19781
21514
|
console.log(` findings ${count(canonical.findings.length)}`);
|
|
19782
21515
|
console.log(` steps ${count(canonical.steps.length)}`);
|
|
19783
|
-
console.log(` started ${
|
|
19784
|
-
console.log(` completed ${
|
|
21516
|
+
console.log(` started ${chalk31.dim(manifest.created_at)}`);
|
|
21517
|
+
console.log(` completed ${chalk31.dim(completedAt)}`);
|
|
21518
|
+
const models = manifest.models;
|
|
21519
|
+
if (models?.velocity_sec_per_step != null) {
|
|
21520
|
+
console.log(
|
|
21521
|
+
` velocity ${chalk31.cyan(`~${models.velocity_sec_per_step}s/step`)} ${chalk31.dim(`(${models.total_duration_sec}s total)`)}`
|
|
21522
|
+
);
|
|
21523
|
+
}
|
|
21524
|
+
if (models && (models.walker || models.judge || models.matcher)) {
|
|
21525
|
+
const w = models.walker_reported ? `${models.walker} (${models.walker_reported})` : models.walker ?? "fast";
|
|
21526
|
+
const j = models.judge_reported ? `${models.judge} (${models.judge_reported})` : models.judge ?? "thorough";
|
|
21527
|
+
console.log(` models ${chalk31.dim(`walker: ${w} \xB7 judge: ${j}`)}`);
|
|
21528
|
+
}
|
|
19785
21529
|
console.log();
|
|
19786
21530
|
console.log(
|
|
19787
|
-
` ${
|
|
21531
|
+
` ${chalk31.dim("canonical:")} ${chalk31.dim(path28.relative(projectRoot, assembled.canonicalPath))}`
|
|
19788
21532
|
);
|
|
19789
21533
|
console.log(
|
|
19790
|
-
` ${
|
|
21534
|
+
` ${chalk31.dim("report:")} ${chalk31.dim(path28.relative(projectRoot, assembled.reportPath))}`
|
|
19791
21535
|
);
|
|
19792
21536
|
if (extras.viewportWarning) {
|
|
19793
21537
|
console.log();
|
|
@@ -19795,7 +21539,7 @@ function printCompletionSummary(run, completedAt, assembled, extras, provenanceA
|
|
|
19795
21539
|
}
|
|
19796
21540
|
if (extras.codeWarning) {
|
|
19797
21541
|
console.log();
|
|
19798
|
-
console.log(` ${
|
|
21542
|
+
console.log(` ${chalk31.yellow("!")} ${extras.codeWarning}`);
|
|
19799
21543
|
}
|
|
19800
21544
|
if (extras.revisit) printRevisit(extras.revisit);
|
|
19801
21545
|
if (assembled.verification) printVerification(assembled.verification);
|
|
@@ -19807,13 +21551,13 @@ function printProvenanceWarnings(audit) {
|
|
|
19807
21551
|
if (audit.warnings.length === 0) return;
|
|
19808
21552
|
console.log();
|
|
19809
21553
|
console.log(
|
|
19810
|
-
` ${symbols.warning} ${
|
|
21554
|
+
` ${symbols.warning} ${chalk31.yellow("Step provenance audit flagged suspicious timing:")}`
|
|
19811
21555
|
);
|
|
19812
21556
|
for (const w of audit.warnings) {
|
|
19813
|
-
console.log(` ${
|
|
21557
|
+
console.log(` ${chalk31.dim("\u2022")} ${w}`);
|
|
19814
21558
|
}
|
|
19815
21559
|
console.log(
|
|
19816
|
-
|
|
21560
|
+
chalk31.dim(
|
|
19817
21561
|
" Run appears to be programmatically batch-generated rather than an interactive walkthrough."
|
|
19818
21562
|
)
|
|
19819
21563
|
);
|
|
@@ -19845,17 +21589,17 @@ function screenshotFitsViewport(width, expected) {
|
|
|
19845
21589
|
}
|
|
19846
21590
|
async function screenshotSizeWarning(runDirPath, viewport) {
|
|
19847
21591
|
if (!viewport) return null;
|
|
19848
|
-
const dir =
|
|
21592
|
+
const dir = path28.join(runDirPath, "screenshots");
|
|
19849
21593
|
let first;
|
|
19850
21594
|
try {
|
|
19851
|
-
first = (await
|
|
21595
|
+
first = (await fs30.readdir(dir)).filter((f) => f.toLowerCase().endsWith(".png")).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }))[0];
|
|
19852
21596
|
} catch {
|
|
19853
21597
|
return null;
|
|
19854
21598
|
}
|
|
19855
21599
|
if (!first) return null;
|
|
19856
21600
|
let size = null;
|
|
19857
21601
|
try {
|
|
19858
|
-
const handle = await
|
|
21602
|
+
const handle = await fs30.open(path28.join(dir, first), "r");
|
|
19859
21603
|
try {
|
|
19860
21604
|
const head = Buffer.alloc(24);
|
|
19861
21605
|
const { bytesRead } = await handle.read(head, 0, 24, 0);
|
|
@@ -19872,12 +21616,14 @@ async function screenshotSizeWarning(runDirPath, viewport) {
|
|
|
19872
21616
|
async function readWalkerSummary(runDirPath) {
|
|
19873
21617
|
try {
|
|
19874
21618
|
const data = JSON.parse(
|
|
19875
|
-
await
|
|
21619
|
+
await fs30.readFile(path28.join(runDirPath, "summary.json"), "utf8")
|
|
19876
21620
|
);
|
|
19877
21621
|
const strings = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && x.trim() !== "") : void 0;
|
|
21622
|
+
const model = typeof data.model === "string" && data.model.trim() !== "" ? data.model.trim() : void 0;
|
|
19878
21623
|
return {
|
|
19879
21624
|
what_worked: strings(data.what_worked),
|
|
19880
|
-
open_questions: strings(data.open_questions)
|
|
21625
|
+
open_questions: strings(data.open_questions),
|
|
21626
|
+
model
|
|
19881
21627
|
};
|
|
19882
21628
|
} catch {
|
|
19883
21629
|
return {};
|
|
@@ -19979,7 +21725,7 @@ function validateCanonicalWalkthrough(canonical) {
|
|
|
19979
21725
|
async function applyRecordedVerdicts(canonical, runDirPath) {
|
|
19980
21726
|
try {
|
|
19981
21727
|
const recorded = JSON.parse(
|
|
19982
|
-
await
|
|
21728
|
+
await fs30.readFile(path28.join(runDirPath, "verdicts.json"), "utf8")
|
|
19983
21729
|
);
|
|
19984
21730
|
if (Array.isArray(recorded.verdicts)) {
|
|
19985
21731
|
return applyVerdicts(canonical, recorded);
|
|
@@ -19989,7 +21735,7 @@ async function applyRecordedVerdicts(canonical, runDirPath) {
|
|
|
19989
21735
|
return canonical;
|
|
19990
21736
|
}
|
|
19991
21737
|
async function persistCanonical(runDirPath, canonical) {
|
|
19992
|
-
const canonicalPath =
|
|
21738
|
+
const canonicalPath = path28.join(runDirPath, "canonical.json");
|
|
19993
21739
|
await atomicWriteFile(
|
|
19994
21740
|
canonicalPath,
|
|
19995
21741
|
`${JSON.stringify(canonical, null, 2)}
|
|
@@ -19998,7 +21744,7 @@ async function persistCanonical(runDirPath, canonical) {
|
|
|
19998
21744
|
return canonicalPath;
|
|
19999
21745
|
}
|
|
20000
21746
|
async function persistReport(runDirPath, canonical) {
|
|
20001
|
-
const reportPath =
|
|
21747
|
+
const reportPath = path28.join(runDirPath, "report.md");
|
|
20002
21748
|
await atomicWriteFile(reportPath, renderCanonicalMarkdown(canonical));
|
|
20003
21749
|
return reportPath;
|
|
20004
21750
|
}
|
|
@@ -20098,17 +21844,17 @@ function placeLabel(p) {
|
|
|
20098
21844
|
function printRevisit(r) {
|
|
20099
21845
|
const missed = r.places.filter((p) => !p.reached);
|
|
20100
21846
|
console.log();
|
|
20101
|
-
const head = ` ${
|
|
21847
|
+
const head = ` ${chalk31.bold("Re-check places:")} reached ${r.reached} of ${r.requested}`;
|
|
20102
21848
|
if (missed.length === 0) {
|
|
20103
|
-
console.log(`${head} ${
|
|
21849
|
+
console.log(`${head} ${chalk31.green("\u2713")}`);
|
|
20104
21850
|
return;
|
|
20105
21851
|
}
|
|
20106
21852
|
console.log(`${head} \u2014 not reached:`);
|
|
20107
21853
|
for (const p of missed) {
|
|
20108
|
-
console.log(` ${
|
|
21854
|
+
console.log(` ${chalk31.yellow("\u25CB")} ${placeLabel(p)}`);
|
|
20109
21855
|
}
|
|
20110
21856
|
console.log(
|
|
20111
|
-
|
|
21857
|
+
chalk31.dim(
|
|
20112
21858
|
" Fixes there stay awaiting a re-run until a walk gets there."
|
|
20113
21859
|
)
|
|
20114
21860
|
);
|
|
@@ -20116,7 +21862,7 @@ function printRevisit(r) {
|
|
|
20116
21862
|
function printVerification(verification) {
|
|
20117
21863
|
console.log();
|
|
20118
21864
|
console.log(
|
|
20119
|
-
|
|
21865
|
+
chalk31.bold(" Since this scenario's last run") + chalk31.dim(` (${verification.baseline_run_id.slice(0, 30)}\u2026)`) + chalk31.bold(":")
|
|
20120
21866
|
);
|
|
20121
21867
|
if (verification.resolved.length > 0) {
|
|
20122
21868
|
console.log(
|
|
@@ -20125,7 +21871,7 @@ function printVerification(verification) {
|
|
|
20125
21871
|
}
|
|
20126
21872
|
if (verification.not_observed.length > 0) {
|
|
20127
21873
|
console.log(
|
|
20128
|
-
` ${symbols.pending} ${verification.not_observed.length} not seen this run ${
|
|
21874
|
+
` ${symbols.pending} ${verification.not_observed.length} not seen this run ${chalk31.dim("(not evidence of a fix)")}`
|
|
20129
21875
|
);
|
|
20130
21876
|
}
|
|
20131
21877
|
if (verification.persists.length > 0) {
|
|
@@ -20166,14 +21912,14 @@ __export(match_exports, {
|
|
|
20166
21912
|
match: () => match,
|
|
20167
21913
|
matchRecord: () => matchRecord
|
|
20168
21914
|
});
|
|
20169
|
-
import { promises as
|
|
20170
|
-
import
|
|
20171
|
-
import
|
|
21915
|
+
import { promises as fs31 } from "fs";
|
|
21916
|
+
import path29 from "path";
|
|
21917
|
+
import chalk32 from "chalk";
|
|
20172
21918
|
async function completedManifest2(runDirPath, runId, opts) {
|
|
20173
21919
|
let manifest;
|
|
20174
21920
|
try {
|
|
20175
21921
|
manifest = JSON.parse(
|
|
20176
|
-
await
|
|
21922
|
+
await fs31.readFile(path29.join(runDirPath, "walkthrough.json"), "utf8")
|
|
20177
21923
|
);
|
|
20178
21924
|
} catch {
|
|
20179
21925
|
commandError(`Run not found: ${runId}`, opts);
|
|
@@ -20202,15 +21948,34 @@ async function match(runId, opts) {
|
|
|
20202
21948
|
);
|
|
20203
21949
|
const needed = input.current.length > 0 && (input.prior.length > 0 || (input.guidance ?? []).length > 0);
|
|
20204
21950
|
const prompt = needed ? composeMatchPrompt(input) : null;
|
|
21951
|
+
const promptFilePath = opts?.promptFile ?? path29.join(runDirPath, "match_prompt.md");
|
|
21952
|
+
if (prompt) {
|
|
21953
|
+
await fs31.writeFile(promptFilePath, prompt, "utf8");
|
|
21954
|
+
} else {
|
|
21955
|
+
try {
|
|
21956
|
+
await fs31.unlink(promptFilePath);
|
|
21957
|
+
} catch {
|
|
21958
|
+
}
|
|
21959
|
+
}
|
|
20205
21960
|
if (opts?.json) {
|
|
20206
21961
|
console.log(
|
|
20207
|
-
JSON.stringify(
|
|
21962
|
+
JSON.stringify(
|
|
21963
|
+
{
|
|
21964
|
+
...input,
|
|
21965
|
+
needed,
|
|
21966
|
+
prompt_file: prompt ? path29.relative(ctx.projectRoot, promptFilePath) : null,
|
|
21967
|
+
prompt_file_abs: prompt ? path29.resolve(promptFilePath) : null,
|
|
21968
|
+
match_prompt: prompt
|
|
21969
|
+
},
|
|
21970
|
+
null,
|
|
21971
|
+
2
|
|
21972
|
+
)
|
|
20208
21973
|
);
|
|
20209
21974
|
return;
|
|
20210
21975
|
}
|
|
20211
21976
|
if (!prompt) {
|
|
20212
21977
|
console.log(
|
|
20213
|
-
|
|
21978
|
+
chalk32.dim(
|
|
20214
21979
|
" No earlier findings or tester guidance for this scenario to match against \u2014 nothing to do."
|
|
20215
21980
|
)
|
|
20216
21981
|
);
|
|
@@ -20226,7 +21991,7 @@ async function matchRecord(runId, opts) {
|
|
|
20226
21991
|
if (!manifest) return;
|
|
20227
21992
|
let answer;
|
|
20228
21993
|
try {
|
|
20229
|
-
answer = JSON.parse(await
|
|
21994
|
+
answer = JSON.parse(await fs31.readFile(opts.file, "utf8"));
|
|
20230
21995
|
} catch {
|
|
20231
21996
|
commandError(`Cannot read matches file: ${opts.file}`, opts);
|
|
20232
21997
|
return;
|
|
@@ -20267,17 +22032,17 @@ async function matchRecord(runId, opts) {
|
|
|
20267
22032
|
}
|
|
20268
22033
|
console.log();
|
|
20269
22034
|
console.log(
|
|
20270
|
-
` ${
|
|
22035
|
+
` ${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`
|
|
20271
22036
|
);
|
|
20272
22037
|
if (result.suppressed > 0) {
|
|
20273
22038
|
console.log(
|
|
20274
|
-
` ${
|
|
22039
|
+
` ${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)`
|
|
20275
22040
|
);
|
|
20276
22041
|
}
|
|
20277
22042
|
if (verification) printVerification(verification);
|
|
20278
22043
|
console.log();
|
|
20279
22044
|
console.log(
|
|
20280
|
-
|
|
22045
|
+
chalk32.dim(" Run `iamu sync` to push the linked findings to the cloud.")
|
|
20281
22046
|
);
|
|
20282
22047
|
console.log();
|
|
20283
22048
|
}
|
|
@@ -20298,7 +22063,7 @@ __export(prune_exports, {
|
|
|
20298
22063
|
prune: () => prune
|
|
20299
22064
|
});
|
|
20300
22065
|
import { confirm, isCancel as isCancel5 } from "@clack/prompts";
|
|
20301
|
-
import
|
|
22066
|
+
import chalk33 from "chalk";
|
|
20302
22067
|
function formatBytes(bytes) {
|
|
20303
22068
|
if (bytes <= 0) return "0 B";
|
|
20304
22069
|
if (bytes < 1024) return `${bytes} B`;
|
|
@@ -20344,7 +22109,7 @@ async function prune(opts = {}) {
|
|
|
20344
22109
|
});
|
|
20345
22110
|
if (preview.prunedRuns.length === 0) {
|
|
20346
22111
|
console.log(
|
|
20347
|
-
|
|
22112
|
+
chalk33.dim(" No runs match the pruning criteria. Nothing to prune.")
|
|
20348
22113
|
);
|
|
20349
22114
|
return preview;
|
|
20350
22115
|
}
|
|
@@ -20353,7 +22118,7 @@ async function prune(opts = {}) {
|
|
|
20353
22118
|
message: `Prune ${mode} ${preview.prunedRuns.length} run(s) and reclaim ${formatBytes(preview.totalBytesReclaimed)}?`
|
|
20354
22119
|
});
|
|
20355
22120
|
if (isCancel5(confirmed) || !confirmed) {
|
|
20356
|
-
console.log(
|
|
22121
|
+
console.log(chalk33.yellow(" Pruning cancelled."));
|
|
20357
22122
|
return;
|
|
20358
22123
|
}
|
|
20359
22124
|
}
|
|
@@ -20364,14 +22129,14 @@ async function prune(opts = {}) {
|
|
|
20364
22129
|
}
|
|
20365
22130
|
if (result.dryRun) {
|
|
20366
22131
|
console.log(
|
|
20367
|
-
|
|
22132
|
+
chalk33.cyan(
|
|
20368
22133
|
`
|
|
20369
22134
|
[DRY RUN] Would prune ${result.prunedRuns.length} of ${result.totalRunsScanned} run(s):`
|
|
20370
22135
|
)
|
|
20371
22136
|
);
|
|
20372
22137
|
} else {
|
|
20373
22138
|
console.log(
|
|
20374
|
-
|
|
22139
|
+
chalk33.bold(
|
|
20375
22140
|
`
|
|
20376
22141
|
${symbols.success} Pruned ${result.prunedRuns.length} of ${result.totalRunsScanned} run(s):`
|
|
20377
22142
|
)
|
|
@@ -20379,18 +22144,18 @@ async function prune(opts = {}) {
|
|
|
20379
22144
|
}
|
|
20380
22145
|
if (result.prunedRuns.length === 0) {
|
|
20381
22146
|
console.log(
|
|
20382
|
-
|
|
22147
|
+
chalk33.dim(" No runs matched the pruning criteria. Nothing pruned.")
|
|
20383
22148
|
);
|
|
20384
22149
|
} else {
|
|
20385
22150
|
for (const run of result.prunedRuns) {
|
|
20386
22151
|
const mode = run.fullyDeleted ? "removed" : "screenshots purged";
|
|
20387
22152
|
const count = run.screenshotsDeleted > 0 ? `${run.screenshotsDeleted} screenshots, ` : "";
|
|
20388
22153
|
console.log(
|
|
20389
|
-
` ${
|
|
22154
|
+
` ${chalk33.dim("\u2022")} ${chalk33.bold(run.runId)} ${chalk33.dim(`(${run.scenarioSlug ?? "unknown scenario"})`)}: ${chalk33.yellow(formatBytes(run.bytesReclaimed))} (${count}${mode})`
|
|
20390
22155
|
);
|
|
20391
22156
|
}
|
|
20392
22157
|
console.log(
|
|
20393
|
-
|
|
22158
|
+
chalk33.dim(
|
|
20394
22159
|
`
|
|
20395
22160
|
Total reclaimed: ${formatBytes(result.totalBytesReclaimed)} (${result.totalScreenshotsDeleted} screenshots deleted)
|
|
20396
22161
|
`
|
|
@@ -20413,13 +22178,13 @@ init_context4();
|
|
|
20413
22178
|
import { existsSync as existsSync13, readFileSync as readFileSync9 } from "fs";
|
|
20414
22179
|
import { dirname as dirname5, resolve as resolve3 } from "path";
|
|
20415
22180
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
20416
|
-
import
|
|
22181
|
+
import chalk36 from "chalk";
|
|
20417
22182
|
import { Command } from "commander";
|
|
20418
22183
|
|
|
20419
22184
|
// src/commands/register-cloud.ts
|
|
20420
22185
|
import { Option } from "commander";
|
|
20421
22186
|
function registerCloudCommands(program2) {
|
|
20422
|
-
program2.command("sync").helpGroup("Cloud:").description("Push pending local changes to cloud").option("--status", "Show pending sync count without pushing").option(
|
|
22187
|
+
program2.command("sync").alias("push").helpGroup("Cloud:").description("Push pending local changes to cloud").option("--status", "Show pending sync count without pushing").option(
|
|
20423
22188
|
"--keep-mine",
|
|
20424
22189
|
"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."
|
|
20425
22190
|
).option(
|
|
@@ -20443,7 +22208,7 @@ function registerCloudCommands(program2) {
|
|
|
20443
22208
|
"Connect to local cloud stack (default port: 3000)"
|
|
20444
22209
|
).addOption(
|
|
20445
22210
|
new Option("-s, --staging", "Connect to staging cloud stack").hideHelp()
|
|
20446
|
-
).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://
|
|
22211
|
+
).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(
|
|
20447
22212
|
"--project <project-id>",
|
|
20448
22213
|
"Cloud project ID to link to, or `new` to create one. Required in a non-interactive shell when cloud projects exist"
|
|
20449
22214
|
).option("--no-browser", "Do not launch browser automatically").addHelpText(
|
|
@@ -20532,13 +22297,13 @@ function registerFindingsCommands(program2) {
|
|
|
20532
22297
|
|
|
20533
22298
|
// src/commands/register-project.ts
|
|
20534
22299
|
function registerProjectCommands(program2) {
|
|
20535
|
-
program2.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(
|
|
22300
|
+
program2.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(
|
|
20536
22301
|
"after",
|
|
20537
|
-
"\nExamples:\n $ iamu init\n $ iamu init my-project -y\n $ iamu init --json"
|
|
22302
|
+
"\nExamples:\n $ iamu init\n $ iamu init my-project -y\n $ iamu init --cloud\n $ iamu init --json"
|
|
20538
22303
|
).action(
|
|
20539
22304
|
async (name, opts) => {
|
|
20540
22305
|
const { init: init2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
20541
|
-
await init2({ name, yes: opts.yes, json: opts.json });
|
|
22306
|
+
await init2({ name, yes: opts.yes, json: opts.json, cloud: opts.cloud });
|
|
20542
22307
|
}
|
|
20543
22308
|
);
|
|
20544
22309
|
program2.command("status").helpGroup("Project:").description("Show project state: entities, runs, context, cloud link").option("--json", "Output as JSON").addHelpText(
|
|
@@ -20549,10 +22314,13 @@ function registerProjectCommands(program2) {
|
|
|
20549
22314
|
await status2(opts);
|
|
20550
22315
|
});
|
|
20551
22316
|
program2.command("doctor").helpGroup("Project:").description(
|
|
20552
|
-
"Diagnose environment: runtime, browser automation, skills, cloud"
|
|
22317
|
+
"Diagnose environment: runtime, browser automation, skills, cloud, and target testability"
|
|
22318
|
+
).option(
|
|
22319
|
+
"-t, --target <url>",
|
|
22320
|
+
"Probe target URL health, reachability, and auth redirects"
|
|
20553
22321
|
).option("--json", "Output diagnostics as JSON").addHelpText(
|
|
20554
22322
|
"after",
|
|
20555
|
-
"\nExamples:\n $ iamu doctor\n $ iamu doctor --json"
|
|
22323
|
+
"\nExamples:\n $ iamu doctor\n $ iamu doctor --target http://localhost:3000\n $ iamu doctor --json"
|
|
20556
22324
|
).action(async (opts) => {
|
|
20557
22325
|
const { doctor: doctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
|
|
20558
22326
|
await doctor2(opts);
|
|
@@ -20584,9 +22352,9 @@ function registerProjectCommands(program2) {
|
|
|
20584
22352
|
"after",
|
|
20585
22353
|
"\nExamples:\n $ iamu validate --all\n $ iamu validate --all --fix --dry-run\n $ iamu validate iamu/personas/maria.md"
|
|
20586
22354
|
).action(
|
|
20587
|
-
async (
|
|
22355
|
+
async (path30, opts) => {
|
|
20588
22356
|
const { validate: validate2 } = await Promise.resolve().then(() => (init_validate(), validate_exports));
|
|
20589
|
-
await validate2(
|
|
22357
|
+
await validate2(path30, opts);
|
|
20590
22358
|
}
|
|
20591
22359
|
);
|
|
20592
22360
|
program2.command("update").helpGroup("Project:").description("Update and repair agent skills and prompts to latest version").option(
|
|
@@ -20599,11 +22367,27 @@ function registerProjectCommands(program2) {
|
|
|
20599
22367
|
program2.command("new <type>").helpGroup("Project:").description(
|
|
20600
22368
|
"Create a new entity (persona, outline, capability, source, scenario)"
|
|
20601
22369
|
).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(
|
|
22370
|
+
"--access <role>",
|
|
22371
|
+
"Access role or testing account key (persona only)"
|
|
22372
|
+
).option("--body <text>", "Markdown body / narrative content").option("--narrative <text>", "Alias for --body").option(
|
|
20602
22373
|
"--description <text>",
|
|
20603
22374
|
"One-line description (capability, outline)"
|
|
22375
|
+
).option("--capability <slug>", "Primary capability slug (outline only)").option(
|
|
22376
|
+
"--tension <text>",
|
|
22377
|
+
"Structural tension driving this situation (outline only)"
|
|
22378
|
+
).option(
|
|
22379
|
+
"--human-test <question>",
|
|
22380
|
+
"Human-test question evaluated at run completion (outline only, repeatable)",
|
|
22381
|
+
(val, prev = []) => [...prev, val]
|
|
20604
22382
|
).option("--outline <slug>", "Outline slug (scenario only)").option("--persona <slug>", "Persona slug (scenario only)").option(
|
|
20605
22383
|
"--entry <path>",
|
|
20606
22384
|
"Where the situation starts, as a path on the product, e.g. /en/episodes/1 (scenario only)"
|
|
22385
|
+
).option(
|
|
22386
|
+
"--state-description <text>",
|
|
22387
|
+
"Assumed environment data/records state (scenario only)"
|
|
22388
|
+
).option(
|
|
22389
|
+
"--state-approximation <text>",
|
|
22390
|
+
"Guidance on how persona should approximate/squint if records are missing (scenario only)"
|
|
20607
22391
|
).option("--url <url>", "Document the source cites (source only, required)").option(
|
|
20608
22392
|
"--authority <level>",
|
|
20609
22393
|
"legislation | regulation | standard | company_policy | guideline | informational (source only, default informational)"
|
|
@@ -20616,6 +22400,54 @@ function registerProjectCommands(program2) {
|
|
|
20616
22400
|
await newEntity2(type2, opts);
|
|
20617
22401
|
}
|
|
20618
22402
|
);
|
|
22403
|
+
const explore = program2.command("explore").helpGroup("Project:").description(
|
|
22404
|
+
"Map the running product with a read-only crawl; compare the map with runs (coverage) or with another map (diff)"
|
|
22405
|
+
);
|
|
22406
|
+
explore.command("run [url]", { isDefault: true }).description(
|
|
22407
|
+
"Crawl from <url> (default: target_url in iamu/testability.md) and write iamu/.cache/explore/<id>/map.json"
|
|
22408
|
+
).option("--max-surfaces <n>", "Stop after this many surfaces (default 40)").option("--max-actions <n>", "Stop after this many clicks (default 300)").option(
|
|
22409
|
+
"--max-depth <n>",
|
|
22410
|
+
"Don't go more than this many clicks from the start (default 6)"
|
|
22411
|
+
).option(
|
|
22412
|
+
"--deny <pattern...>",
|
|
22413
|
+
"Never click a control whose name or link matches (regex, case-insensitive)"
|
|
22414
|
+
).option("--viewport <WxH>", "Page size, e.g. 390x844 (default 1280x800)").option(
|
|
22415
|
+
"--attach",
|
|
22416
|
+
"Crawl in a new tab of the `iamu browse` browser, keeping its sign-ins"
|
|
22417
|
+
).option("--json", "Output as JSON").addHelpText(
|
|
22418
|
+
"after",
|
|
22419
|
+
`
|
|
22420
|
+
Read-only: controls that read like writes (delete, pay, send, save, log out \u2026),
|
|
22421
|
+
form submits, downloads and other origins are never clicked, nothing is typed,
|
|
22422
|
+
and every request but GET/HEAD/OPTIONS is blocked before it leaves the browser.
|
|
22423
|
+
|
|
22424
|
+
Examples:
|
|
22425
|
+
$ iamu explore http://localhost:3000
|
|
22426
|
+
$ iamu explore --attach --deny "^Admin"
|
|
22427
|
+
$ iamu explore coverage
|
|
22428
|
+
$ iamu explore diff`
|
|
22429
|
+
).action(
|
|
22430
|
+
async (url, opts) => {
|
|
22431
|
+
const { exploreRun: exploreRun2 } = await Promise.resolve().then(() => (init_explore(), explore_exports));
|
|
22432
|
+
await exploreRun2(url, opts);
|
|
22433
|
+
}
|
|
22434
|
+
);
|
|
22435
|
+
explore.command("coverage [map]").description(
|
|
22436
|
+
"Surfaces of a map (default: latest) that no run reached, and places runs reached that the map lacks"
|
|
22437
|
+
).option("--json", "Output as JSON").action(async (map2, opts) => {
|
|
22438
|
+
const { exploreCoverageCommand: exploreCoverageCommand2 } = await Promise.resolve().then(() => (init_explore(), explore_exports));
|
|
22439
|
+
await exploreCoverageCommand2(map2, opts);
|
|
22440
|
+
});
|
|
22441
|
+
explore.command("diff [a] [b]").description("Compare two maps (default: the two latest)").option("--json", "Output as JSON").action(
|
|
22442
|
+
async (a, b, opts) => {
|
|
22443
|
+
const { exploreDiffCommand: exploreDiffCommand2 } = await Promise.resolve().then(() => (init_explore(), explore_exports));
|
|
22444
|
+
await exploreDiffCommand2(a, b, opts);
|
|
22445
|
+
}
|
|
22446
|
+
);
|
|
22447
|
+
explore.command("list").description("List maps").option("--json", "Output as JSON").action(async (opts) => {
|
|
22448
|
+
const { exploreList: exploreList2 } = await Promise.resolve().then(() => (init_explore(), explore_exports));
|
|
22449
|
+
await exploreList2(opts);
|
|
22450
|
+
});
|
|
20619
22451
|
program2.command("completion [shell]").helpGroup("Project:").description("Output shell completion script (bash, zsh, fish)").action(async (shell) => {
|
|
20620
22452
|
const { completion: completion2 } = await Promise.resolve().then(() => (init_completion(), completion_exports));
|
|
20621
22453
|
completion2(program2, shell);
|
|
@@ -20645,6 +22477,15 @@ function registerWalkthroughCommands(program2) {
|
|
|
20645
22477
|
).option(
|
|
20646
22478
|
"--env <name>",
|
|
20647
22479
|
"Environment the product runs in (default: the target URL's origin)"
|
|
22480
|
+
).option(
|
|
22481
|
+
"--walker <tierOrModel>",
|
|
22482
|
+
"Model tier ('fast', 'thorough', 'inherit') or model ID for driving browser navigation (default: fast)"
|
|
22483
|
+
).option(
|
|
22484
|
+
"--judge <tierOrModel>",
|
|
22485
|
+
"Model tier ('fast', 'thorough', 'inherit') or model ID for human test evaluation (default: thorough)"
|
|
22486
|
+
).option(
|
|
22487
|
+
"--matcher <tierOrModel>",
|
|
22488
|
+
"Model tier ('fast', 'thorough', 'inherit') or model ID for finding matching (default: thorough)"
|
|
20648
22489
|
).option(
|
|
20649
22490
|
"--check",
|
|
20650
22491
|
"Enforce target URL reachability (fail fast if server is offline)"
|
|
@@ -20675,7 +22516,10 @@ function registerWalkthroughCommands(program2) {
|
|
|
20675
22516
|
).option(
|
|
20676
22517
|
"--run <run-id>",
|
|
20677
22518
|
"Run ID to size browser (start) or record step into (step)"
|
|
20678
|
-
).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(
|
|
22519
|
+
).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(
|
|
22520
|
+
"--step-file <path>",
|
|
22521
|
+
"Read step payload (action, note, cmd) from a JSON file or '-' for stdin (step only)"
|
|
22522
|
+
).option("--width <px>", "Window width (start)").option("--height <px>", "Window height (start)").option("--headless", "Run without a window (start)").option(
|
|
20679
22523
|
"--port <port>",
|
|
20680
22524
|
"CDP port (start), default 9333",
|
|
20681
22525
|
(value) => parsePortOption(value)
|
|
@@ -20751,9 +22595,9 @@ function registerWalkthroughCommands(program2) {
|
|
|
20751
22595
|
).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(
|
|
20752
22596
|
"--strict-provenance",
|
|
20753
22597
|
"Fail if steps show synthetic or programmatic timing anomalies"
|
|
20754
|
-
).addHelpText(
|
|
22598
|
+
).option("--no-push", "Skip pushing the run to Iamu Cloud").addHelpText(
|
|
20755
22599
|
"after",
|
|
20756
|
-
"\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"
|
|
22600
|
+
"\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"
|
|
20757
22601
|
).action(
|
|
20758
22602
|
async (runId, opts) => {
|
|
20759
22603
|
const { complete: complete2 } = await Promise.resolve().then(() => (init_complete(), complete_exports));
|
|
@@ -20762,23 +22606,27 @@ function registerWalkthroughCommands(program2) {
|
|
|
20762
22606
|
);
|
|
20763
22607
|
program2.command("match <run-id>").helpGroup("Walkthrough:").description(
|
|
20764
22608
|
"Compose the prompt that links this run's findings to earlier ones"
|
|
20765
|
-
).option("--json", "Output the match input bundle as JSON").
|
|
20766
|
-
|
|
20767
|
-
|
|
20768
|
-
|
|
22609
|
+
).option("--json", "Output the match input bundle as JSON").option("--prompt-file <path>", "Write the generated prompt to a file").action(
|
|
22610
|
+
async (runId, opts) => {
|
|
22611
|
+
const { match: match2 } = await Promise.resolve().then(() => (init_match(), match_exports));
|
|
22612
|
+
await match2(runId, opts);
|
|
22613
|
+
}
|
|
22614
|
+
);
|
|
20769
22615
|
program2.command("match-record <run-id>").helpGroup("Walkthrough:").description(
|
|
20770
22616
|
"Record finding matches and re-derive the run's since-last-run delta"
|
|
20771
22617
|
).requiredOption("--file <path>", "Path to matches JSON file").option("--json", "Output match results as JSON").action(async (runId, opts) => {
|
|
20772
22618
|
const { matchRecord: matchRecord2 } = await Promise.resolve().then(() => (init_match(), match_exports));
|
|
20773
22619
|
await matchRecord2(runId, opts);
|
|
20774
22620
|
});
|
|
20775
|
-
program2.command("evaluate <run-id>").helpGroup("Walkthrough:").description("Compose evaluation prompt for human-test questions").option("--json", "Output evaluation input bundle as JSON").
|
|
20776
|
-
|
|
20777
|
-
|
|
20778
|
-
|
|
20779
|
-
|
|
22621
|
+
program2.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(
|
|
22622
|
+
async (runId, opts) => {
|
|
22623
|
+
const { evaluate: evaluate2 } = await Promise.resolve().then(() => (init_evaluate(), evaluate_exports));
|
|
22624
|
+
await evaluate2(runId, opts);
|
|
22625
|
+
}
|
|
22626
|
+
);
|
|
22627
|
+
program2.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(
|
|
20780
22628
|
"after",
|
|
20781
|
-
"\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"
|
|
22629
|
+
"\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"
|
|
20782
22630
|
).action(
|
|
20783
22631
|
async (runId, opts) => {
|
|
20784
22632
|
const { evaluateRecord: evaluateRecord2 } = await Promise.resolve().then(() => (init_evaluate(), evaluate_exports));
|
|
@@ -20807,23 +22655,23 @@ function registerAllCommands(program2) {
|
|
|
20807
22655
|
|
|
20808
22656
|
// src/ui/panic.ts
|
|
20809
22657
|
init_logger();
|
|
20810
|
-
import
|
|
22658
|
+
import chalk34 from "chalk";
|
|
20811
22659
|
var handlersRegistered = false;
|
|
20812
22660
|
var customSigintHandler = null;
|
|
20813
22661
|
function logFatalAndExit(title, errOrReason) {
|
|
20814
22662
|
process.stderr.write("\x1B[?25h");
|
|
20815
22663
|
if (isVerbose()) {
|
|
20816
|
-
console.error(
|
|
22664
|
+
console.error(chalk34.red(`
|
|
20817
22665
|
${title}:`));
|
|
20818
22666
|
console.error(
|
|
20819
22667
|
errOrReason instanceof Error ? errOrReason.stack || errOrReason : errOrReason
|
|
20820
22668
|
);
|
|
20821
22669
|
} else {
|
|
20822
22670
|
const msg = errOrReason instanceof Error ? errOrReason.message : String(errOrReason);
|
|
20823
|
-
console.error(
|
|
22671
|
+
console.error(chalk34.red(`
|
|
20824
22672
|
Fatal Error: ${msg}`));
|
|
20825
22673
|
console.error(
|
|
20826
|
-
|
|
22674
|
+
chalk34.dim(
|
|
20827
22675
|
" Run with --verbose or DEBUG=1 to view the full stack trace.\n"
|
|
20828
22676
|
)
|
|
20829
22677
|
);
|
|
@@ -20842,7 +22690,7 @@ function setupProcessHandlers() {
|
|
|
20842
22690
|
}
|
|
20843
22691
|
process.exit(130);
|
|
20844
22692
|
} else {
|
|
20845
|
-
console.error(
|
|
22693
|
+
console.error(chalk34.dim("\n Operation cancelled."));
|
|
20846
22694
|
process.exit(130);
|
|
20847
22695
|
}
|
|
20848
22696
|
});
|
|
@@ -20868,7 +22716,7 @@ function setupProcessHandlers() {
|
|
|
20868
22716
|
import { mkdir as mkdir7, readFile as readFile7, writeFile as writeFile5 } from "fs/promises";
|
|
20869
22717
|
import { homedir as homedir4 } from "os";
|
|
20870
22718
|
import { join as join10 } from "path";
|
|
20871
|
-
import
|
|
22719
|
+
import chalk35 from "chalk";
|
|
20872
22720
|
var CACHE_FILE = join10(homedir4(), ".iamu", "update-check.json");
|
|
20873
22721
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
20874
22722
|
function isSemverGreater(latest, current) {
|
|
@@ -20921,7 +22769,7 @@ async function checkForUpdate(currentVersion) {
|
|
|
20921
22769
|
function printUpdateNotice(currentVersion, latestVersion) {
|
|
20922
22770
|
console.error();
|
|
20923
22771
|
console.error(
|
|
20924
|
-
` ${
|
|
22772
|
+
` ${chalk35.yellow("Update available:")} ${chalk35.dim(currentVersion)} \u2192 ${chalk35.green(latestVersion)} ${chalk35.dim("(run: npm i -g @iamu-dev/iamu)")}`
|
|
20925
22773
|
);
|
|
20926
22774
|
}
|
|
20927
22775
|
|
|
@@ -20966,7 +22814,7 @@ var program = new Command().name("iamu").description(
|
|
|
20966
22814
|
}
|
|
20967
22815
|
}
|
|
20968
22816
|
if (root.color === false || process.env.NO_COLOR) {
|
|
20969
|
-
|
|
22817
|
+
chalk36.level = 0;
|
|
20970
22818
|
}
|
|
20971
22819
|
if (root.verbose) {
|
|
20972
22820
|
process.env.IAMU_VERBOSE = "1";
|