@iamu-dev/iamu 0.8.0-beta.5 → 0.8.0-beta.7
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 +518 -195
- package/dist/build-hash.txt +1 -1
- package/dist/commands/triage.d.ts +6 -1
- package/dist/index.js +518 -195
- package/dist/templates/skills/iamu-findings.md +2 -0
- package/dist/templates/skills/iamu-run.md +3 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6684,10 +6684,13 @@ var init_wire_compat = __esm({
|
|
|
6684
6684
|
});
|
|
6685
6685
|
|
|
6686
6686
|
// ../../packages/local-store/dist/sync/server-meta.js
|
|
6687
|
+
function cloudDistinguishesHumanEdits(capabilities) {
|
|
6688
|
+
return Boolean(capabilities?.includes(SYNC_TOKEN_CAPABILITY));
|
|
6689
|
+
}
|
|
6687
6690
|
function cloudSupportsGuidance(capabilities) {
|
|
6688
6691
|
return Boolean(capabilities?.includes(GUIDANCE_CAPABILITY) || capabilities?.includes(DESIGNER_FINDINGS_CAPABILITY));
|
|
6689
6692
|
}
|
|
6690
|
-
async function fetchServerCapabilities(apiUrl, apiKey, fetchFn, timeoutMs, onFailure) {
|
|
6693
|
+
async function fetchServerCapabilities(apiUrl, apiKey, fetchFn, timeoutMs, onFailure, onServerClock) {
|
|
6691
6694
|
const controller = new AbortController();
|
|
6692
6695
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
6693
6696
|
try {
|
|
@@ -6697,6 +6700,9 @@ async function fetchServerCapabilities(apiUrl, apiKey, fetchFn, timeoutMs, onFai
|
|
|
6697
6700
|
signal: controller.signal
|
|
6698
6701
|
};
|
|
6699
6702
|
const res = await fetchFn(`${apiUrl.replace(/\/+$/, "")}/api/meta`, init2);
|
|
6703
|
+
const offset = serverClockOffset(res);
|
|
6704
|
+
if (offset !== null)
|
|
6705
|
+
onServerClock?.(offset);
|
|
6700
6706
|
if (res.status === 404)
|
|
6701
6707
|
return [];
|
|
6702
6708
|
if (!res.ok) {
|
|
@@ -6718,6 +6724,16 @@ async function fetchServerCapabilities(apiUrl, apiKey, fetchFn, timeoutMs, onFai
|
|
|
6718
6724
|
clearTimeout(timer);
|
|
6719
6725
|
}
|
|
6720
6726
|
}
|
|
6727
|
+
function serverClockOffset(res) {
|
|
6728
|
+
const headers = res?.headers;
|
|
6729
|
+
if (!headers || typeof headers.get !== "function")
|
|
6730
|
+
return null;
|
|
6731
|
+
const raw = headers.get.call(headers, "date");
|
|
6732
|
+
if (typeof raw !== "string")
|
|
6733
|
+
return null;
|
|
6734
|
+
const at = Date.parse(raw);
|
|
6735
|
+
return Number.isNaN(at) ? null : at - Date.now();
|
|
6736
|
+
}
|
|
6721
6737
|
function shapeForServer(pending, capabilities) {
|
|
6722
6738
|
const knows = capabilities.includes(DESIGNER_FINDINGS_CAPABILITY);
|
|
6723
6739
|
const knowsProvenance = capabilities.includes(FINDING_PROVENANCE_CAPABILITY);
|
|
@@ -6739,7 +6755,7 @@ function shapeForServer(pending, capabilities) {
|
|
|
6739
6755
|
}
|
|
6740
6756
|
return { pending: out2, heldDesigner };
|
|
6741
6757
|
}
|
|
6742
|
-
var DESIGNER_FINDINGS_CAPABILITY, GUIDANCE_CAPABILITY, WALKTHROUGH_DELETE_CAPABILITY, FINDING_PROVENANCE_CAPABILITY, BUNDLE_FINDING_HASH_CAPABILITY, RUN_BLOCKED_CAPABILITY;
|
|
6758
|
+
var DESIGNER_FINDINGS_CAPABILITY, GUIDANCE_CAPABILITY, WALKTHROUGH_DELETE_CAPABILITY, FINDING_PROVENANCE_CAPABILITY, BUNDLE_FINDING_HASH_CAPABILITY, RUN_BLOCKED_CAPABILITY, SYNC_TOKEN_CAPABILITY;
|
|
6743
6759
|
var init_server_meta = __esm({
|
|
6744
6760
|
"../../packages/local-store/dist/sync/server-meta.js"() {
|
|
6745
6761
|
"use strict";
|
|
@@ -6751,6 +6767,7 @@ var init_server_meta = __esm({
|
|
|
6751
6767
|
FINDING_PROVENANCE_CAPABILITY = "finding-provenance";
|
|
6752
6768
|
BUNDLE_FINDING_HASH_CAPABILITY = "bundle-finding-hash";
|
|
6753
6769
|
RUN_BLOCKED_CAPABILITY = "run-blocked";
|
|
6770
|
+
SYNC_TOKEN_CAPABILITY = "sync-token";
|
|
6754
6771
|
}
|
|
6755
6772
|
});
|
|
6756
6773
|
|
|
@@ -10687,7 +10704,7 @@ function compareIso(a, b) {
|
|
|
10687
10704
|
return ta - tb;
|
|
10688
10705
|
return String(a ?? "").localeCompare(String(b ?? ""));
|
|
10689
10706
|
}
|
|
10690
|
-
async function designerFindingsOn(projectRoot, screens, before, targetUrl = null) {
|
|
10707
|
+
async function designerFindingsOn(projectRoot, screens, before, targetUrl = null, settled = /* @__PURE__ */ new Set()) {
|
|
10691
10708
|
const latest = /* @__PURE__ */ new Map();
|
|
10692
10709
|
const cache = /* @__PURE__ */ new Map();
|
|
10693
10710
|
const wanted = new Set([...screens].map((k) => normalizeScreenKey(k, targetUrl)));
|
|
@@ -10711,7 +10728,7 @@ async function designerFindingsOn(projectRoot, screens, before, targetUrl = null
|
|
|
10711
10728
|
}
|
|
10712
10729
|
const all = [...latest.values()].sort((a, b) => compareIso(a.created_at, b.created_at));
|
|
10713
10730
|
return {
|
|
10714
|
-
open: all.filter(unsettled).map((fm, n) => ({
|
|
10731
|
+
open: all.filter((fm) => unsettled(fm) && !settled.has(fm.id)).map((fm, n) => ({
|
|
10715
10732
|
ref: `P${n + 1}`,
|
|
10716
10733
|
id: fm.id,
|
|
10717
10734
|
title: fm.title ?? fm.slug,
|
|
@@ -11204,6 +11221,26 @@ async function readReviewEvidence(projectRoot) {
|
|
|
11204
11221
|
}
|
|
11205
11222
|
return out2;
|
|
11206
11223
|
}
|
|
11224
|
+
async function verifiedDesignerFixes(projectRoot, before) {
|
|
11225
|
+
const addressed = (await listDesignerFindings(projectRoot)).map((f) => f.frontmatter).filter((fm) => fm.triage_status === "addressed" && !fm.design_cause_of);
|
|
11226
|
+
const out2 = /* @__PURE__ */ new Set();
|
|
11227
|
+
if (addressed.length === 0)
|
|
11228
|
+
return out2;
|
|
11229
|
+
const cutoff = toMillis(before);
|
|
11230
|
+
const runs = (await readRunEvidence(projectRoot)).filter((r) => {
|
|
11231
|
+
if (cutoff === null)
|
|
11232
|
+
return true;
|
|
11233
|
+
const start2 = toMillis(r.started_at);
|
|
11234
|
+
return start2 !== null && start2 < cutoff;
|
|
11235
|
+
});
|
|
11236
|
+
const states = deriveFixStates(addressed, runs);
|
|
11237
|
+
for (const fm of addressed) {
|
|
11238
|
+
if (fm.fix_status === "verified_resolved" || states.get(fm.slug)?.fix_state === "verified") {
|
|
11239
|
+
out2.add(fm.id);
|
|
11240
|
+
}
|
|
11241
|
+
}
|
|
11242
|
+
return out2;
|
|
11243
|
+
}
|
|
11207
11244
|
async function computeFixStates(projectRoot) {
|
|
11208
11245
|
const { entities } = await listEntities(projectRoot, "finding");
|
|
11209
11246
|
const findings2 = entities.map((e) => e.frontmatter);
|
|
@@ -12265,6 +12302,7 @@ async function canonicalFindingFiles(projectRoot, scenarioSlug, canonicalId) {
|
|
|
12265
12302
|
return out2;
|
|
12266
12303
|
}
|
|
12267
12304
|
async function markAddressedResolved(files, note) {
|
|
12305
|
+
const failed = [];
|
|
12268
12306
|
const trimmedNote = note?.trim();
|
|
12269
12307
|
for (const f of files) {
|
|
12270
12308
|
if (f.triage_status !== "addressed")
|
|
@@ -12280,9 +12318,14 @@ async function markAddressedResolved(files, note) {
|
|
|
12280
12318
|
if (updated !== raw)
|
|
12281
12319
|
await atomicWriteFile(f.file, updated);
|
|
12282
12320
|
});
|
|
12283
|
-
} catch {
|
|
12321
|
+
} catch (err) {
|
|
12322
|
+
failed.push({
|
|
12323
|
+
file: f.file,
|
|
12324
|
+
error: err instanceof Error ? err.message : String(err)
|
|
12325
|
+
});
|
|
12284
12326
|
}
|
|
12285
12327
|
}
|
|
12328
|
+
return failed;
|
|
12286
12329
|
}
|
|
12287
12330
|
function originOf(url) {
|
|
12288
12331
|
if (!url)
|
|
@@ -12465,7 +12508,9 @@ async function updateFixStatus(projectRoot, slug, status2) {
|
|
|
12465
12508
|
await atomicWriteFile(filePath, updated);
|
|
12466
12509
|
}
|
|
12467
12510
|
});
|
|
12468
|
-
|
|
12511
|
+
return null;
|
|
12512
|
+
} catch (err) {
|
|
12513
|
+
return err instanceof Error ? err.message : String(err);
|
|
12469
12514
|
}
|
|
12470
12515
|
}
|
|
12471
12516
|
async function listRunsInternal(projectRoot) {
|
|
@@ -12915,6 +12960,7 @@ async function mirrorLocalWalkthrough(projectRoot, runId, deps) {
|
|
|
12915
12960
|
body: form,
|
|
12916
12961
|
signal: controller.signal
|
|
12917
12962
|
});
|
|
12963
|
+
const clockOffset = serverClockOffset(response);
|
|
12918
12964
|
if (response.ok) {
|
|
12919
12965
|
let body = null;
|
|
12920
12966
|
try {
|
|
@@ -12960,7 +13006,8 @@ async function mirrorLocalWalkthrough(projectRoot, runId, deps) {
|
|
|
12960
13006
|
runCloudId: parsed.data.cloud_walkthrough_id,
|
|
12961
13007
|
contentHash,
|
|
12962
13008
|
cloudOutcome: parsed.data.status,
|
|
12963
|
-
...recordedFindings.length > 0 ? { recordedFindings } : {}
|
|
13009
|
+
...recordedFindings.length > 0 ? { recordedFindings } : {},
|
|
13010
|
+
...clockOffset !== null ? { serverClockOffsetMs: clockOffset } : {}
|
|
12964
13011
|
};
|
|
12965
13012
|
}
|
|
12966
13013
|
if (response.status === 409) {
|
|
@@ -13607,6 +13654,7 @@ async function drainRetryQueue(projectRoot, deps) {
|
|
|
13607
13654
|
const deleteUrl = projectEndpoint(config.cloud_api_url, config.cloud_project_id, "delete");
|
|
13608
13655
|
const remaining = [];
|
|
13609
13656
|
const failures = [];
|
|
13657
|
+
const pushedRuns = [];
|
|
13610
13658
|
let drained = 0;
|
|
13611
13659
|
let dropped = 0;
|
|
13612
13660
|
let notAttempted = 0;
|
|
@@ -13718,6 +13766,8 @@ async function drainRetryQueue(projectRoot, deps) {
|
|
|
13718
13766
|
});
|
|
13719
13767
|
if (result.mirrored || result.skippedReason === "bundle_too_large") {
|
|
13720
13768
|
drained++;
|
|
13769
|
+
if (result.status === "pushed")
|
|
13770
|
+
pushedRuns.push(entry.entity_id);
|
|
13721
13771
|
} else if (result.skippedReason === "missing_run" || result.skippedReason === "unsupported") {
|
|
13722
13772
|
drained++;
|
|
13723
13773
|
} else if (result.skippedReason === "cloud_conflict") {
|
|
@@ -13809,7 +13859,8 @@ async function drainRetryQueue(projectRoot, deps) {
|
|
|
13809
13859
|
dropped,
|
|
13810
13860
|
remaining: after.length,
|
|
13811
13861
|
failures,
|
|
13812
|
-
warnings
|
|
13862
|
+
warnings,
|
|
13863
|
+
...pushedRuns.length > 0 ? { pushedRuns } : {}
|
|
13813
13864
|
};
|
|
13814
13865
|
}
|
|
13815
13866
|
function unreadableBody(text, isDelete) {
|
|
@@ -14233,6 +14284,18 @@ async function pushOneBatch(target, batch, byType, label, opts = {}) {
|
|
|
14233
14284
|
out2.errors.push(failure.message);
|
|
14234
14285
|
return out2;
|
|
14235
14286
|
}
|
|
14287
|
+
function cloudTokenOrigin(hash, humanTokens) {
|
|
14288
|
+
if (hash.startsWith("cloud-sync-"))
|
|
14289
|
+
return "projection";
|
|
14290
|
+
if (hash.startsWith("cloud-"))
|
|
14291
|
+
return humanTokens ? "person" : "unknown";
|
|
14292
|
+
if (hash.startsWith("cloud:"))
|
|
14293
|
+
return "unknown";
|
|
14294
|
+
return null;
|
|
14295
|
+
}
|
|
14296
|
+
function isCloudToken(hash) {
|
|
14297
|
+
return cloudTokenOrigin(hash, false) !== null;
|
|
14298
|
+
}
|
|
14236
14299
|
async function ownEarlierWrites(projectRoot, batch, conflicts) {
|
|
14237
14300
|
if (conflicts.length === 0)
|
|
14238
14301
|
return [];
|
|
@@ -14246,7 +14309,7 @@ async function ownEarlierWrites(projectRoot, batch, conflicts) {
|
|
|
14246
14309
|
const entity = byId.get(c.entity_id);
|
|
14247
14310
|
if (!entity || c.base_hash || bases[c.entity_id])
|
|
14248
14311
|
continue;
|
|
14249
|
-
if (!c.cloud_hash || c.cloud_hash
|
|
14312
|
+
if (!c.cloud_hash || isCloudToken(c.cloud_hash))
|
|
14250
14313
|
continue;
|
|
14251
14314
|
if (!sent[c.entity_id]?.includes(c.cloud_hash))
|
|
14252
14315
|
continue;
|
|
@@ -14255,10 +14318,59 @@ async function ownEarlierWrites(projectRoot, batch, conflicts) {
|
|
|
14255
14318
|
}
|
|
14256
14319
|
return out2;
|
|
14257
14320
|
}
|
|
14321
|
+
function findingRuns(frontmatter) {
|
|
14322
|
+
const out2 = /* @__PURE__ */ new Set();
|
|
14323
|
+
if (typeof frontmatter.run === "string" && frontmatter.run) {
|
|
14324
|
+
out2.add(frontmatter.run);
|
|
14325
|
+
}
|
|
14326
|
+
const seen = frontmatter.seen_in;
|
|
14327
|
+
if (typeof seen === "string") {
|
|
14328
|
+
const m = /^(?:ux|designer):(.+)$/.exec(seen);
|
|
14329
|
+
if (m?.[1])
|
|
14330
|
+
out2.add(m[1]);
|
|
14331
|
+
}
|
|
14332
|
+
return out2;
|
|
14333
|
+
}
|
|
14334
|
+
async function rowsToAdopt(projectRoot, batch, conflicts, adopt) {
|
|
14335
|
+
if (!adopt || conflicts.length === 0)
|
|
14336
|
+
return [];
|
|
14337
|
+
const bases = await readBaseHashes(projectRoot);
|
|
14338
|
+
const byId = new Map(batch.map((e) => [e.id, e]));
|
|
14339
|
+
const out2 = [];
|
|
14340
|
+
for (const c of conflicts) {
|
|
14341
|
+
const entity = byId.get(c.entity_id);
|
|
14342
|
+
if (!entity || entity.type !== "finding" || !c.cloud_hash)
|
|
14343
|
+
continue;
|
|
14344
|
+
const origin = cloudTokenOrigin(c.cloud_hash, adopt.humanTokens);
|
|
14345
|
+
if (origin === null || origin === "person")
|
|
14346
|
+
continue;
|
|
14347
|
+
if (origin === "unknown" && !stampedByThisPass(c, entity, adopt)) {
|
|
14348
|
+
continue;
|
|
14349
|
+
}
|
|
14350
|
+
await recordCloudBase(projectRoot, c.entity_id, c.cloud_hash);
|
|
14351
|
+
out2.push(origin === "projection" ? { entity, restamped: false, kind: "projection" } : {
|
|
14352
|
+
entity,
|
|
14353
|
+
restamped: Boolean(c.base_hash || bases[c.entity_id]),
|
|
14354
|
+
kind: "bundle"
|
|
14355
|
+
});
|
|
14356
|
+
}
|
|
14357
|
+
return out2;
|
|
14358
|
+
}
|
|
14359
|
+
function stampedByThisPass(c, entity, adopt) {
|
|
14360
|
+
const runs = findingRuns(entity.frontmatter);
|
|
14361
|
+
if (![...runs].some((r) => adopt.runs.has(r)))
|
|
14362
|
+
return false;
|
|
14363
|
+
const at = c.cloud_updated_at ? Date.parse(c.cloud_updated_at) : Number.NaN;
|
|
14364
|
+
const slack = adopt.serverClock ? ADOPT_CLOCK_SLACK_MS : 0;
|
|
14365
|
+
if (Number.isNaN(at) || at < adopt.since - slack)
|
|
14366
|
+
return false;
|
|
14367
|
+
return !adopt.identity || c.cloud_changed_by === adopt.identity;
|
|
14368
|
+
}
|
|
14258
14369
|
async function pushBatches(target, batches, byType, opts = {}) {
|
|
14259
14370
|
let pushed = 0;
|
|
14260
14371
|
let enqueued = 0;
|
|
14261
14372
|
const selfResolved = [];
|
|
14373
|
+
const adopted = [];
|
|
14262
14374
|
const conflicts = [];
|
|
14263
14375
|
const overwritten = [];
|
|
14264
14376
|
const errors = [];
|
|
@@ -14280,19 +14392,42 @@ async function pushBatches(target, batches, byType, opts = {}) {
|
|
|
14280
14392
|
conflicts,
|
|
14281
14393
|
overwritten,
|
|
14282
14394
|
errors,
|
|
14283
|
-
selfResolved
|
|
14395
|
+
selfResolved,
|
|
14396
|
+
adopted
|
|
14284
14397
|
};
|
|
14285
14398
|
}
|
|
14286
14399
|
absorb(first);
|
|
14287
14400
|
let unresolved = first.conflicts;
|
|
14288
14401
|
const own = await ownEarlierWrites(target.projectRoot, batch, unresolved);
|
|
14289
|
-
|
|
14290
|
-
|
|
14291
|
-
|
|
14292
|
-
|
|
14402
|
+
const ownIds = new Set(own.map((e) => e.id));
|
|
14403
|
+
const basesBefore = opts.adopt ? await readCloudBase(target.projectRoot) : {};
|
|
14404
|
+
const fromBundles = await rowsToAdopt(target.projectRoot, batch, unresolved.filter((c) => !ownIds.has(c.entity_id)), opts.adopt);
|
|
14405
|
+
const resend = [...own, ...fromBundles.map((a) => a.entity)];
|
|
14406
|
+
if (resend.length > 0) {
|
|
14407
|
+
const resendIds = new Set(resend.map((e) => e.id));
|
|
14408
|
+
unresolved = unresolved.filter((c) => !resendIds.has(c.entity_id));
|
|
14409
|
+
const again = await pushOneBatch(target, resend, byType, `batch ${i} own-version retry`);
|
|
14293
14410
|
absorb(again);
|
|
14294
14411
|
conflicts.push(...again.conflicts);
|
|
14295
|
-
|
|
14412
|
+
const landed2 = new Set(again.landed);
|
|
14413
|
+
selfResolved.push(...own.filter((e) => landed2.has(e.id)).map((e) => e.id));
|
|
14414
|
+
for (const a of fromBundles) {
|
|
14415
|
+
if (landed2.has(a.entity.id)) {
|
|
14416
|
+
adopted.push({
|
|
14417
|
+
slug: a.entity.slug,
|
|
14418
|
+
kind: a.kind,
|
|
14419
|
+
restamped: a.restamped,
|
|
14420
|
+
serverClock: opts.adopt?.serverClock ?? false
|
|
14421
|
+
});
|
|
14422
|
+
continue;
|
|
14423
|
+
}
|
|
14424
|
+
const prior = basesBefore[a.entity.id];
|
|
14425
|
+
if (prior === void 0) {
|
|
14426
|
+
await forgetCloudBase(target.projectRoot, a.entity.id);
|
|
14427
|
+
} else {
|
|
14428
|
+
await recordCloudBase(target.projectRoot, a.entity.id, prior);
|
|
14429
|
+
}
|
|
14430
|
+
}
|
|
14296
14431
|
}
|
|
14297
14432
|
if (!opts.keepMine || unresolved.length === 0) {
|
|
14298
14433
|
conflicts.push(...unresolved);
|
|
@@ -14345,8 +14480,17 @@ async function pushBatches(target, batches, byType, opts = {}) {
|
|
|
14345
14480
|
}
|
|
14346
14481
|
await forgetQueuedUpserts(target.projectRoot, revoked);
|
|
14347
14482
|
}
|
|
14348
|
-
return {
|
|
14483
|
+
return {
|
|
14484
|
+
pushed,
|
|
14485
|
+
enqueued,
|
|
14486
|
+
conflicts,
|
|
14487
|
+
overwritten,
|
|
14488
|
+
errors,
|
|
14489
|
+
selfResolved,
|
|
14490
|
+
adopted
|
|
14491
|
+
};
|
|
14349
14492
|
}
|
|
14493
|
+
var ADOPT_CLOCK_SLACK_MS;
|
|
14350
14494
|
var init_push = __esm({
|
|
14351
14495
|
"../../packages/local-store/dist/sync/push.js"() {
|
|
14352
14496
|
"use strict";
|
|
@@ -14354,6 +14498,7 @@ var init_push = __esm({
|
|
|
14354
14498
|
init_server_reason();
|
|
14355
14499
|
init_sync_tracking();
|
|
14356
14500
|
init_types();
|
|
14501
|
+
ADOPT_CLOCK_SLACK_MS = 5e3;
|
|
14357
14502
|
}
|
|
14358
14503
|
});
|
|
14359
14504
|
|
|
@@ -14748,15 +14893,33 @@ async function syncWalkthroughs(projectRoot, deps, unpushableScenarios = /* @__P
|
|
|
14748
14893
|
const hold = deps.capabilities === null;
|
|
14749
14894
|
let held = 0;
|
|
14750
14895
|
const recordedFindings = /* @__PURE__ */ new Set();
|
|
14896
|
+
const pushedRuns = /* @__PURE__ */ new Set();
|
|
14897
|
+
let serverClockOffsetMs = null;
|
|
14751
14898
|
let runIds;
|
|
14752
14899
|
try {
|
|
14753
14900
|
runIds = await listRuns(projectRoot);
|
|
14754
14901
|
} catch (err) {
|
|
14755
14902
|
errors.push(`walkthrough scan failed: ${err instanceof Error ? err.message : String(err)} \u2014 check that iamu/.runs/ exists and is readable`);
|
|
14756
|
-
return {
|
|
14903
|
+
return {
|
|
14904
|
+
summary,
|
|
14905
|
+
errors,
|
|
14906
|
+
withheldRuns,
|
|
14907
|
+
held,
|
|
14908
|
+
recordedFindings,
|
|
14909
|
+
pushedRuns,
|
|
14910
|
+
serverClockOffsetMs
|
|
14911
|
+
};
|
|
14757
14912
|
}
|
|
14758
14913
|
if (runIds.length === 0)
|
|
14759
|
-
return {
|
|
14914
|
+
return {
|
|
14915
|
+
summary,
|
|
14916
|
+
errors,
|
|
14917
|
+
withheldRuns,
|
|
14918
|
+
held,
|
|
14919
|
+
recordedFindings,
|
|
14920
|
+
pushedRuns,
|
|
14921
|
+
serverClockOffsetMs
|
|
14922
|
+
};
|
|
14760
14923
|
const lastPush = await readLastPush(projectRoot);
|
|
14761
14924
|
const pendingWalkthroughQueueEntries = /* @__PURE__ */ new Set();
|
|
14762
14925
|
const retryQueue = await readRetryQueue(projectRoot);
|
|
@@ -14823,6 +14986,9 @@ async function syncWalkthroughs(projectRoot, deps, unpushableScenarios = /* @__P
|
|
|
14823
14986
|
});
|
|
14824
14987
|
if (result.status === "pushed" || result.status === "already_pushed") {
|
|
14825
14988
|
summary.pushed++;
|
|
14989
|
+
if (result.status === "pushed")
|
|
14990
|
+
pushedRuns.add(runId);
|
|
14991
|
+
serverClockOffsetMs ??= result.serverClockOffsetMs ?? null;
|
|
14826
14992
|
for (const id of result.recordedFindings ?? [])
|
|
14827
14993
|
recordedFindings.add(id);
|
|
14828
14994
|
} else if (result.skippedReason === "bundle_too_large" || result.skippedReason === "unsupported" || result.skippedReason === "missing_run" || result.skippedReason === "cloud_conflict") {
|
|
@@ -14845,7 +15011,15 @@ async function syncWalkthroughs(projectRoot, deps, unpushableScenarios = /* @__P
|
|
|
14845
15011
|
}
|
|
14846
15012
|
}
|
|
14847
15013
|
}
|
|
14848
|
-
return {
|
|
15014
|
+
return {
|
|
15015
|
+
summary,
|
|
15016
|
+
errors,
|
|
15017
|
+
withheldRuns,
|
|
15018
|
+
held,
|
|
15019
|
+
recordedFindings,
|
|
15020
|
+
pushedRuns,
|
|
15021
|
+
serverClockOffsetMs
|
|
15022
|
+
};
|
|
14849
15023
|
}
|
|
14850
15024
|
var HOLD_SHAPES, WALKTHROUGH_SKIP_HINTS;
|
|
14851
15025
|
var init_walkthroughs = __esm({
|
|
@@ -14895,6 +15069,7 @@ function mergePushProgress(target, outcome) {
|
|
|
14895
15069
|
if (!outcome)
|
|
14896
15070
|
return;
|
|
14897
15071
|
target.selfResolved.push(...outcome.selfResolved ?? []);
|
|
15072
|
+
target.adopted.push(...outcome.adopted ?? []);
|
|
14898
15073
|
target.pushed += outcome.pushed;
|
|
14899
15074
|
target.enqueued += outcome.enqueued;
|
|
14900
15075
|
target.conflicts.push(...outcome.conflicts);
|
|
@@ -14941,6 +15116,7 @@ async function pushDeferredFindings(projectRoot, capabilities, deferredFindings,
|
|
|
14941
15116
|
return pushBatches(target, toBatches(retryFindings, batchSize), byType, pushOpts);
|
|
14942
15117
|
}
|
|
14943
15118
|
async function runSync(projectRoot, deps) {
|
|
15119
|
+
const startedAt = Date.now();
|
|
14944
15120
|
const config = await readConfig(projectRoot);
|
|
14945
15121
|
if (!config.cloud_linked) {
|
|
14946
15122
|
return emptySyncResult([
|
|
@@ -14955,11 +15131,21 @@ async function runSync(projectRoot, deps) {
|
|
|
14955
15131
|
const apiKey = deps.apiKey;
|
|
14956
15132
|
const fetchFn = deps.fetch ?? globalThis.fetch;
|
|
14957
15133
|
let capabilitiesFailure = "";
|
|
15134
|
+
let serverClockOffsetMs = null;
|
|
14958
15135
|
const capabilities = deps.capabilities !== void 0 ? deps.capabilities : await fetchServerCapabilities(config.cloud_api_url, apiKey, fetchFn, deps.timeoutMs ?? DEFAULT_TIMEOUT_MS4, (detail) => {
|
|
14959
15136
|
capabilitiesFailure = detail;
|
|
15137
|
+
}, (offset) => {
|
|
15138
|
+
serverClockOffsetMs = offset;
|
|
14960
15139
|
});
|
|
14961
15140
|
deps = { ...deps, capabilities };
|
|
14962
|
-
const drain = await drainRetryQueue(projectRoot, deps);
|
|
15141
|
+
const { pushedRuns: drainedRuns = [], ...drain } = await drainRetryQueue(projectRoot, deps);
|
|
15142
|
+
const adoptFrom = (runs) => ({
|
|
15143
|
+
runs: /* @__PURE__ */ new Set([...drainedRuns, ...runs]),
|
|
15144
|
+
since: startedAt + (serverClockOffsetMs ?? 0),
|
|
15145
|
+
serverClock: serverClockOffsetMs !== null,
|
|
15146
|
+
humanTokens: cloudDistinguishesHumanEdits(capabilities),
|
|
15147
|
+
identity: deps.identity ?? null
|
|
15148
|
+
});
|
|
14963
15149
|
const scanned = await scanPending(projectRoot);
|
|
14964
15150
|
if (scanned.errors.length === 0) {
|
|
14965
15151
|
await pruneSentHashes(projectRoot, scanned.seenIds);
|
|
@@ -14983,14 +15169,15 @@ async function runSync(projectRoot, deps) {
|
|
|
14983
15169
|
const pushOpts = { keepMine: deps.keepMine };
|
|
14984
15170
|
const guidance = scan.pending.filter((e) => e.type === "guidance");
|
|
14985
15171
|
const nonGuidance = scan.pending.filter((e) => e.type !== "guidance");
|
|
14986
|
-
const phase1 = await pushBatches(target, toBatches(nonGuidance, batchSize), byType, pushOpts);
|
|
15172
|
+
const phase1 = await pushBatches(target, toBatches(nonGuidance, batchSize), byType, { ...pushOpts, adopt: adoptFrom([]) });
|
|
14987
15173
|
const progress = {
|
|
14988
15174
|
pushed: phase1.pushed,
|
|
14989
15175
|
enqueued: phase1.enqueued,
|
|
14990
15176
|
conflicts: [...phase1.conflicts],
|
|
14991
15177
|
overwritten: [...phase1.overwritten],
|
|
14992
15178
|
errors,
|
|
14993
|
-
selfResolved: [...phase1.selfResolved]
|
|
15179
|
+
selfResolved: [...phase1.selfResolved],
|
|
15180
|
+
adopted: [...phase1.adopted]
|
|
14994
15181
|
};
|
|
14995
15182
|
progress.errors.push(...phase1.errors);
|
|
14996
15183
|
const notices = [];
|
|
@@ -15000,10 +15187,11 @@ async function runSync(projectRoot, deps) {
|
|
|
15000
15187
|
const guidanceResult = await pushGuidanceBatch(target, guidance, batchSize, byType, { ...pushOpts, detectUnsupported: !cloudSupportsGuidance(capabilities) }, notices);
|
|
15001
15188
|
mergePushProgress(progress, guidanceResult);
|
|
15002
15189
|
const walkthroughs = await syncWalkthroughs(projectRoot, deps, scan.unpushableScenarios);
|
|
15190
|
+
serverClockOffsetMs ??= walkthroughs.serverClockOffsetMs;
|
|
15003
15191
|
if (walkthroughs.errors.length > 0) {
|
|
15004
15192
|
progress.errors.push(...walkthroughs.errors);
|
|
15005
15193
|
}
|
|
15006
|
-
const phase3 = capabilities === null ? null : await pushDeferredFindings(projectRoot, capabilities, deferredFindings, walkthroughs, target, batchSize, byType, pushOpts, reportedRefErrors, progress.errors);
|
|
15194
|
+
const phase3 = capabilities === null ? null : await pushDeferredFindings(projectRoot, capabilities, deferredFindings, walkthroughs, target, batchSize, byType, { ...pushOpts, adopt: adoptFrom(walkthroughs.pushedRuns) }, reportedRefErrors, progress.errors);
|
|
15007
15195
|
mergePushProgress(progress, phase3);
|
|
15008
15196
|
if (capabilities === null) {
|
|
15009
15197
|
const heldFindings = shaped.heldFindings + deferredFindings.size;
|
|
@@ -15024,6 +15212,15 @@ async function runSync(projectRoot, deps) {
|
|
|
15024
15212
|
const deletion = await syncDeletions(projectRoot, config, deps);
|
|
15025
15213
|
progress.errors.push(...deletion.errors);
|
|
15026
15214
|
notices.push(...deletion.notices);
|
|
15215
|
+
for (const a of progress.adopted) {
|
|
15216
|
+
if (a.kind === "projection") {
|
|
15217
|
+
notices.push(`${a.slug}: kept the local version over the cloud's projected triage (automated, from a run's outcome)`);
|
|
15218
|
+
continue;
|
|
15219
|
+
}
|
|
15220
|
+
const what = a.restamped ? "overwrote the cloud row re-stamped during this sync (its version token came from this sync's own bundle of the finding's run)" : "overwrote the cloud row stamped during this sync (its version token came from this sync's own bundle)";
|
|
15221
|
+
const clock = a.serverClock ? "" : "; the server sent no clock, so only rows stamped after this sync began by this machine's clock counted";
|
|
15222
|
+
notices.push(`${a.slug}: ${what}${clock}`);
|
|
15223
|
+
}
|
|
15027
15224
|
if (progress.selfResolved.length > 0) {
|
|
15028
15225
|
const n = progress.selfResolved.length;
|
|
15029
15226
|
notices.push(`${n} conflict${n === 1 ? " was" : "s were"} this machine's own earlier push (it reached the cloud without being recorded); re-sent against it`);
|
|
@@ -19273,7 +19470,14 @@ async function buildRunDesignerInput(projectRoot, runId) {
|
|
|
19273
19470
|
await chooseKeyFrames(rd, grouped.candidates);
|
|
19274
19471
|
const screens = grouped.screens;
|
|
19275
19472
|
const screenKeys = new Set(screens.map((s) => s.key));
|
|
19276
|
-
const earlier = await designerFindingsOn(
|
|
19473
|
+
const earlier = await designerFindingsOn(
|
|
19474
|
+
projectRoot,
|
|
19475
|
+
screenKeys,
|
|
19476
|
+
manifest?.created_at ?? null,
|
|
19477
|
+
target,
|
|
19478
|
+
// A fix a designer's review already verified is not asked about again.
|
|
19479
|
+
await verifiedDesignerFixes(projectRoot, manifest?.created_at ?? null)
|
|
19480
|
+
);
|
|
19277
19481
|
for (const o of earlier.open)
|
|
19278
19482
|
refs2[o.ref] = o.id;
|
|
19279
19483
|
const fullFrameScreens = new Set(screens.filter((s) => s.visits.some((v) => v.steps.some((st) => existsSync4(path34.join(rd, "screenshots", `step-${st.step}.full.png`))))).map((s) => s.key));
|
|
@@ -19429,6 +19633,7 @@ var init_designer_input = __esm({
|
|
|
19429
19633
|
init_designer_files();
|
|
19430
19634
|
init_designer_ignored();
|
|
19431
19635
|
init_designer_ledger();
|
|
19636
|
+
init_fix_state();
|
|
19432
19637
|
init_walkthrough_canonical2();
|
|
19433
19638
|
init_dist();
|
|
19434
19639
|
LONG_VISIT = 6;
|
|
@@ -22381,6 +22586,69 @@ function stepNumberOf(s) {
|
|
|
22381
22586
|
const m = /^(?:step-)?(\d+)$/i.exec(s.trim());
|
|
22382
22587
|
return m ? Number(m[1]) : null;
|
|
22383
22588
|
}
|
|
22589
|
+
async function writeDesignerFixStatus(projectRoot, runId, verification, checks) {
|
|
22590
|
+
const verified = [];
|
|
22591
|
+
const untriaged = [];
|
|
22592
|
+
const warnings = [];
|
|
22593
|
+
const out2 = { verified, untriaged, warnings };
|
|
22594
|
+
const resolved = new Set(verification.resolved);
|
|
22595
|
+
const persists = new Set(verification.persists);
|
|
22596
|
+
if (resolved.size + persists.size === 0)
|
|
22597
|
+
return out2;
|
|
22598
|
+
const notWritten = (what, err) => warnings.push(`could not update ${what}: ${err instanceof Error ? err.message : String(err)}; designer.json holds the verification`);
|
|
22599
|
+
let files;
|
|
22600
|
+
try {
|
|
22601
|
+
files = (await listDesignerFindings(projectRoot)).map((f) => f.frontmatter).filter((fm) => !fm.design_cause_of && fm.canonical_id && (resolved.has(fm.canonical_id) || persists.has(fm.canonical_id)));
|
|
22602
|
+
} catch (err) {
|
|
22603
|
+
notWritten("the checked findings' fix_status", err);
|
|
22604
|
+
return out2;
|
|
22605
|
+
}
|
|
22606
|
+
const checkOf = new Map(checks.map((c) => [c.canonical_id, c]));
|
|
22607
|
+
for (const cid of verification.resolved) {
|
|
22608
|
+
const mine = files.filter((fm) => fm.canonical_id === cid);
|
|
22609
|
+
if (mine.some((fm) => fm.triage_status === "addressed"))
|
|
22610
|
+
continue;
|
|
22611
|
+
const latest = mine.filter((fm) => fm.triage_status !== "dismissed").sort((a, b) => String(a.created_at ?? "").localeCompare(String(b.created_at ?? ""))).at(-1);
|
|
22612
|
+
if (latest) {
|
|
22613
|
+
untriaged.push({
|
|
22614
|
+
ref: checkOf.get(cid)?.ref ?? null,
|
|
22615
|
+
finding: latest.slug
|
|
22616
|
+
});
|
|
22617
|
+
}
|
|
22618
|
+
}
|
|
22619
|
+
let states;
|
|
22620
|
+
try {
|
|
22621
|
+
states = deriveFixStates(files, await readRunEvidence(projectRoot));
|
|
22622
|
+
} catch (err) {
|
|
22623
|
+
notWritten("the checked findings' fix_status", err);
|
|
22624
|
+
return out2;
|
|
22625
|
+
}
|
|
22626
|
+
const decidedHere = (slug, state) => {
|
|
22627
|
+
const s = states.get(slug);
|
|
22628
|
+
return s?.fix_state === state && s.decided_by === runId;
|
|
22629
|
+
};
|
|
22630
|
+
for (const fm of files) {
|
|
22631
|
+
if (fm.triage_status !== "addressed" || !fm.canonical_id)
|
|
22632
|
+
continue;
|
|
22633
|
+
if (resolved.has(fm.canonical_id) && decidedHere(fm.slug, "verified")) {
|
|
22634
|
+
const [failed] = await markAddressedResolved([
|
|
22635
|
+
{
|
|
22636
|
+
file: entityFilePath(projectRoot, "finding", fm.slug),
|
|
22637
|
+
triage_status: fm.triage_status
|
|
22638
|
+
}
|
|
22639
|
+
], checkOf.get(fm.canonical_id)?.note ?? void 0);
|
|
22640
|
+
if (failed)
|
|
22641
|
+
notWritten(fm.slug, failed.error);
|
|
22642
|
+
else
|
|
22643
|
+
verified.push(fm.slug);
|
|
22644
|
+
} else if (persists.has(fm.canonical_id) && decidedHere(fm.slug, "recurred")) {
|
|
22645
|
+
const error = await updateFixStatus(projectRoot, fm.slug, "verified_persists");
|
|
22646
|
+
if (error)
|
|
22647
|
+
notWritten(fm.slug, error);
|
|
22648
|
+
}
|
|
22649
|
+
}
|
|
22650
|
+
return out2;
|
|
22651
|
+
}
|
|
22384
22652
|
async function readRefs2(rd) {
|
|
22385
22653
|
try {
|
|
22386
22654
|
return JSON.parse(await fs49.readFile(designerRefsFile(rd), "utf8"));
|
|
@@ -22701,6 +22969,11 @@ async function recordDesigner(projectRoot, runId, answer, opts) {
|
|
|
22701
22969
|
};
|
|
22702
22970
|
await atomicWriteFile(file, `${JSON.stringify(record, null, 2)}
|
|
22703
22971
|
`);
|
|
22972
|
+
const fixes = await writeDesignerFixStatus(projectRoot, runId, verification, checks);
|
|
22973
|
+
warnings.push(...fixes.warnings);
|
|
22974
|
+
for (const u of fixes.untriaged) {
|
|
22975
|
+
warnings.push(`checked ${u.ref ?? "?"}: ${u.finding} is gone but was never marked addressed; its fix_status is left alone (checked_but_untriaged)`);
|
|
22976
|
+
}
|
|
22704
22977
|
const bySeverity = {};
|
|
22705
22978
|
const ruleCount = groupDesignerRules(rules);
|
|
22706
22979
|
for (const i of issues)
|
|
@@ -22714,7 +22987,9 @@ async function recordDesigner(projectRoot, runId, answer, opts) {
|
|
|
22714
22987
|
proposals: proposals.written,
|
|
22715
22988
|
rejected,
|
|
22716
22989
|
warnings,
|
|
22717
|
-
...invalidChecked.length > 0 ? { invalid_checked: invalidChecked } : {}
|
|
22990
|
+
...invalidChecked.length > 0 ? { invalid_checked: invalidChecked } : {},
|
|
22991
|
+
...fixes.verified.length > 0 ? { verified: fixes.verified } : {},
|
|
22992
|
+
...fixes.untriaged.length > 0 ? { checked_but_untriaged: fixes.untriaged } : {}
|
|
22718
22993
|
};
|
|
22719
22994
|
}
|
|
22720
22995
|
var DESIGNER_CATEGORIES, steps, refs, designerAnswerSchema;
|
|
@@ -22724,13 +22999,14 @@ var init_designer_record = __esm({
|
|
|
22724
22999
|
init_atomic();
|
|
22725
23000
|
init_design_proposals();
|
|
22726
23001
|
init_design_review();
|
|
22727
|
-
init_paths();
|
|
22728
23002
|
init_url_path();
|
|
22729
23003
|
init_review_issues();
|
|
22730
23004
|
init_designer_files();
|
|
22731
23005
|
init_designer_input();
|
|
22732
23006
|
init_designer_ledger();
|
|
22733
23007
|
init_entity();
|
|
23008
|
+
init_fix_state();
|
|
23009
|
+
init_paths();
|
|
22734
23010
|
init_walkthrough_canonical2();
|
|
22735
23011
|
DESIGNER_CATEGORIES = [
|
|
22736
23012
|
"hierarchy",
|
|
@@ -23847,15 +24123,15 @@ function resolveApiUrl(configured) {
|
|
|
23847
24123
|
assertSecureApiUrl(url, configured ? "API URL" : "IAMU_API_URL");
|
|
23848
24124
|
return url;
|
|
23849
24125
|
}
|
|
23850
|
-
function parseStore(raw,
|
|
24126
|
+
function parseStore(raw, path78) {
|
|
23851
24127
|
let parsed;
|
|
23852
24128
|
try {
|
|
23853
24129
|
parsed = JSON.parse(raw);
|
|
23854
24130
|
} catch (err) {
|
|
23855
|
-
throw new CredentialsFileError(
|
|
24131
|
+
throw new CredentialsFileError(path78, "not valid JSON", err);
|
|
23856
24132
|
}
|
|
23857
24133
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
23858
|
-
throw new CredentialsFileError(
|
|
24134
|
+
throw new CredentialsFileError(path78, "expected a JSON object");
|
|
23859
24135
|
}
|
|
23860
24136
|
return parsed;
|
|
23861
24137
|
}
|
|
@@ -23863,26 +24139,26 @@ function isNotFound(err) {
|
|
|
23863
24139
|
return typeof err === "object" && err !== null && err.code === "ENOENT";
|
|
23864
24140
|
}
|
|
23865
24141
|
async function readCredentialsStore() {
|
|
23866
|
-
const
|
|
24142
|
+
const path78 = getCredentialsPath();
|
|
23867
24143
|
let raw;
|
|
23868
24144
|
try {
|
|
23869
|
-
raw = await readFile(
|
|
24145
|
+
raw = await readFile(path78, "utf-8");
|
|
23870
24146
|
} catch (err) {
|
|
23871
24147
|
if (isNotFound(err)) return {};
|
|
23872
24148
|
warnUnreadableStoreOnce(err);
|
|
23873
24149
|
throw err;
|
|
23874
24150
|
}
|
|
23875
24151
|
try {
|
|
23876
|
-
return parseStore(raw,
|
|
24152
|
+
return parseStore(raw, path78);
|
|
23877
24153
|
} catch (err) {
|
|
23878
24154
|
warnUnreadableStoreOnce(err);
|
|
23879
24155
|
throw err;
|
|
23880
24156
|
}
|
|
23881
24157
|
}
|
|
23882
24158
|
async function writeCredentialsStore(store) {
|
|
23883
|
-
const
|
|
23884
|
-
await mkdir(dirname(
|
|
23885
|
-
await atomicWriteFile(
|
|
24159
|
+
const path78 = getCredentialsPath();
|
|
24160
|
+
await mkdir(dirname(path78), { recursive: true, mode: 448 });
|
|
24161
|
+
await atomicWriteFile(path78, `${JSON.stringify(store, null, 2)}
|
|
23886
24162
|
`, {
|
|
23887
24163
|
mode: 384
|
|
23888
24164
|
});
|
|
@@ -23910,16 +24186,16 @@ function resolveApiKey(apiUrl) {
|
|
|
23910
24186
|
}
|
|
23911
24187
|
}
|
|
23912
24188
|
const normalized = url.replace(/\/+$/, "");
|
|
23913
|
-
for (const
|
|
24189
|
+
for (const path78 of candidatePaths) {
|
|
23914
24190
|
let raw;
|
|
23915
24191
|
try {
|
|
23916
|
-
raw = readFileSync3(
|
|
24192
|
+
raw = readFileSync3(path78, "utf-8");
|
|
23917
24193
|
} catch (err) {
|
|
23918
24194
|
if (!isNotFound(err)) warnUnreadableStoreOnce(err);
|
|
23919
24195
|
continue;
|
|
23920
24196
|
}
|
|
23921
24197
|
try {
|
|
23922
|
-
const store = parseStore(raw,
|
|
24198
|
+
const store = parseStore(raw, path78);
|
|
23923
24199
|
const entry = store[url] ?? store[normalized] ?? store[`${normalized}/`];
|
|
23924
24200
|
if (typeof entry !== "object" || entry === null) continue;
|
|
23925
24201
|
const token = entry.token;
|
|
@@ -23960,12 +24236,12 @@ var init_credentials = __esm({
|
|
|
23960
24236
|
* @param reason - What is wrong with it.
|
|
23961
24237
|
* @param cause - Underlying parse error, if any.
|
|
23962
24238
|
*/
|
|
23963
|
-
constructor(
|
|
24239
|
+
constructor(path78, reason, cause) {
|
|
23964
24240
|
super(
|
|
23965
|
-
`Credentials file ${
|
|
24241
|
+
`Credentials file ${path78} is corrupt (${reason}). Fix it by hand, or delete it and run \`iamu auth login\` again.`,
|
|
23966
24242
|
{
|
|
23967
24243
|
cause,
|
|
23968
|
-
suggestedAction: `Delete ${
|
|
24244
|
+
suggestedAction: `Delete ${path78} and run \`iamu auth login\`, or set IAMU_TOKEN.`
|
|
23969
24245
|
}
|
|
23970
24246
|
);
|
|
23971
24247
|
}
|
|
@@ -24831,8 +25107,8 @@ import { dirname as dirname3, join as join5, relative, resolve as resolve2 } fro
|
|
|
24831
25107
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
24832
25108
|
import chalk6 from "chalk";
|
|
24833
25109
|
function getVersion() {
|
|
24834
|
-
if ("0.8.0-beta.
|
|
24835
|
-
return "0.8.0-beta.
|
|
25110
|
+
if ("0.8.0-beta.7") {
|
|
25111
|
+
return "0.8.0-beta.7";
|
|
24836
25112
|
}
|
|
24837
25113
|
try {
|
|
24838
25114
|
const thisDir = dirname3(fileURLToPath2(import.meta.url));
|
|
@@ -24955,10 +25231,10 @@ function hasGeneratedByMarker(content) {
|
|
|
24955
25231
|
function withoutMarker(content) {
|
|
24956
25232
|
return content.replace(/^generatedBy:[^\r\n]*\r?\n/m, "");
|
|
24957
25233
|
}
|
|
24958
|
-
function cliSkillState(
|
|
25234
|
+
function cliSkillState(path78, template) {
|
|
24959
25235
|
let content;
|
|
24960
25236
|
try {
|
|
24961
|
-
content = readFileSync7(
|
|
25237
|
+
content = readFileSync7(path78, "utf-8");
|
|
24962
25238
|
} catch {
|
|
24963
25239
|
return null;
|
|
24964
25240
|
}
|
|
@@ -24966,9 +25242,9 @@ function cliSkillState(path77, template) {
|
|
|
24966
25242
|
if (template === void 0) return "edited";
|
|
24967
25243
|
return withoutMarker(content) === withoutMarker(template) ? "clean" : "edited";
|
|
24968
25244
|
}
|
|
24969
|
-
function isCliWrittenPrompt(
|
|
25245
|
+
function isCliWrittenPrompt(path78) {
|
|
24970
25246
|
try {
|
|
24971
|
-
const content = readFileSync7(
|
|
25247
|
+
const content = readFileSync7(path78, "utf-8");
|
|
24972
25248
|
return hasGeneratedByMarker(content) || /^#\s+iamu:/m.test(content) || /^description:\s*.*iamu/im.test(content);
|
|
24973
25249
|
} catch {
|
|
24974
25250
|
return false;
|
|
@@ -24978,10 +25254,10 @@ function planSkillFiles(hosts, projectRoot, version) {
|
|
|
24978
25254
|
const skillTemplates = loadSkillTemplates(version);
|
|
24979
25255
|
const files = [];
|
|
24980
25256
|
const keptEdited = [];
|
|
24981
|
-
const removeIfClean = (host,
|
|
24982
|
-
const state = cliSkillState(
|
|
24983
|
-
if (state === "clean") files.push({ host, path:
|
|
24984
|
-
else if (state === "edited") keptEdited.push({ host, path:
|
|
25257
|
+
const removeIfClean = (host, path78, name) => {
|
|
25258
|
+
const state = cliSkillState(path78, skillTemplates.get(name));
|
|
25259
|
+
if (state === "clean") files.push({ host, path: path78, content: null });
|
|
25260
|
+
else if (state === "edited") keptEdited.push({ host, path: path78 });
|
|
24985
25261
|
};
|
|
24986
25262
|
for (const host of hosts) {
|
|
24987
25263
|
if (pluginProvidesSkills(host, projectRoot)) {
|
|
@@ -25078,9 +25354,9 @@ async function findStaleSkills(projectRoot) {
|
|
|
25078
25354
|
}
|
|
25079
25355
|
return stale;
|
|
25080
25356
|
}
|
|
25081
|
-
async function removeSkillFile(
|
|
25082
|
-
await rm(
|
|
25083
|
-
await rmdir(dirname3(
|
|
25357
|
+
async function removeSkillFile(path78) {
|
|
25358
|
+
await rm(path78, { force: true });
|
|
25359
|
+
await rmdir(dirname3(path78)).catch(() => {
|
|
25084
25360
|
});
|
|
25085
25361
|
}
|
|
25086
25362
|
async function installSkillsForHosts(hosts, projectRoot, version, opts) {
|
|
@@ -25882,29 +26158,29 @@ async function writeSyncMcpEntry(mcpPath) {
|
|
|
25882
26158
|
return { removedLegacy };
|
|
25883
26159
|
}
|
|
25884
26160
|
async function registerSyncMcpForHost(host, projectRoot) {
|
|
25885
|
-
const
|
|
25886
|
-
if (
|
|
26161
|
+
const path78 = host.getMcpConfigPath(projectRoot);
|
|
26162
|
+
if (path78 === null) {
|
|
25887
26163
|
return {
|
|
25888
26164
|
status: "manual",
|
|
25889
26165
|
instruction: host.manualMcpSetup ?? `add an "iamu-sync" server that runs \`${SYNC_SERVER_COMMAND.join(" ")}\``
|
|
25890
26166
|
};
|
|
25891
26167
|
}
|
|
25892
26168
|
try {
|
|
25893
|
-
const { removedLegacy } = await writeSyncMcpEntry(
|
|
25894
|
-
return { status: "written", path:
|
|
26169
|
+
const { removedLegacy } = await writeSyncMcpEntry(path78);
|
|
26170
|
+
return { status: "written", path: path78, removedLegacy };
|
|
25895
26171
|
} catch (err) {
|
|
25896
26172
|
return {
|
|
25897
26173
|
status: "failed",
|
|
25898
|
-
path:
|
|
26174
|
+
path: path78,
|
|
25899
26175
|
message: err instanceof Error ? err.message : String(err)
|
|
25900
26176
|
};
|
|
25901
26177
|
}
|
|
25902
26178
|
}
|
|
25903
26179
|
async function removeLegacyMcpForHost(host, projectRoot) {
|
|
25904
|
-
const
|
|
25905
|
-
if (
|
|
26180
|
+
const path78 = host.getMcpConfigPath(projectRoot);
|
|
26181
|
+
if (path78 === null) return null;
|
|
25906
26182
|
try {
|
|
25907
|
-
return await removeLegacyIamuEntry(
|
|
26183
|
+
return await removeLegacyIamuEntry(path78) ? { status: "legacy_removed", path: path78, removedLegacy: true } : null;
|
|
25908
26184
|
} catch {
|
|
25909
26185
|
return null;
|
|
25910
26186
|
}
|
|
@@ -33482,6 +33758,7 @@ __export(triage_exports, {
|
|
|
33482
33758
|
triage: () => triage
|
|
33483
33759
|
});
|
|
33484
33760
|
import { promises as fs65 } from "fs";
|
|
33761
|
+
import path62 from "path";
|
|
33485
33762
|
import chalk29 from "chalk";
|
|
33486
33763
|
async function awaitingSteps(projectRoot, findings2) {
|
|
33487
33764
|
const scenarios = /* @__PURE__ */ new Set();
|
|
@@ -33620,14 +33897,53 @@ async function triage(refs2, opts) {
|
|
|
33620
33897
|
);
|
|
33621
33898
|
return;
|
|
33622
33899
|
}
|
|
33900
|
+
let note = opts.note;
|
|
33901
|
+
if (opts.noteFile !== void 0) {
|
|
33902
|
+
if (opts.note !== void 0) {
|
|
33903
|
+
commandError("Pass --note or --note-file, not both.", {
|
|
33904
|
+
...opts,
|
|
33905
|
+
code: "invalid_field_combination",
|
|
33906
|
+
fix: `iamu triage ${refs2.join(" ")} --status ${status2.data} --note-file <path>`
|
|
33907
|
+
});
|
|
33908
|
+
return;
|
|
33909
|
+
}
|
|
33910
|
+
try {
|
|
33911
|
+
const text = opts.noteFile === "-" ? await readStdinText() : await fs65.readFile(
|
|
33912
|
+
path62.resolve(process.cwd(), opts.noteFile),
|
|
33913
|
+
"utf8"
|
|
33914
|
+
);
|
|
33915
|
+
note = text.trim();
|
|
33916
|
+
} catch (err) {
|
|
33917
|
+
commandError(
|
|
33918
|
+
`Could not read --note-file ${opts.noteFile}: ${err instanceof Error ? err.message : String(err)}`,
|
|
33919
|
+
{
|
|
33920
|
+
...opts,
|
|
33921
|
+
code: "note_read_error",
|
|
33922
|
+
fix: `iamu triage ${refs2.join(" ")} --status ${status2.data} --note "<what changed>"`
|
|
33923
|
+
}
|
|
33924
|
+
);
|
|
33925
|
+
return;
|
|
33926
|
+
}
|
|
33927
|
+
if (!note) {
|
|
33928
|
+
commandError(`--note-file ${opts.noteFile} is empty`, {
|
|
33929
|
+
...opts,
|
|
33930
|
+
code: "note_empty",
|
|
33931
|
+
fix: `iamu triage ${refs2.join(" ")} --status ${status2.data} --note "<what changed>"`
|
|
33932
|
+
});
|
|
33933
|
+
return;
|
|
33934
|
+
}
|
|
33935
|
+
}
|
|
33623
33936
|
let reason = null;
|
|
33624
33937
|
if (opts.reason !== void 0) {
|
|
33625
33938
|
if (status2.data !== "dismissed") {
|
|
33626
|
-
commandError(
|
|
33627
|
-
|
|
33628
|
-
|
|
33629
|
-
|
|
33630
|
-
|
|
33939
|
+
commandError(
|
|
33940
|
+
"--reason applies only to --status dismissed \u2014 to record why or what changed, use --note",
|
|
33941
|
+
{
|
|
33942
|
+
...opts,
|
|
33943
|
+
code: "invalid_reason_usage",
|
|
33944
|
+
fix: `iamu triage ${refs2.join(" ")} --status ${status2.data} --note "<what changed>"`
|
|
33945
|
+
}
|
|
33946
|
+
);
|
|
33631
33947
|
return;
|
|
33632
33948
|
}
|
|
33633
33949
|
if (!DISMISS_REASONS.includes(opts.reason)) {
|
|
@@ -33819,7 +34135,7 @@ async function triage(refs2, opts) {
|
|
|
33819
34135
|
const rule = await createGuidanceRule(projectRoot, {
|
|
33820
34136
|
kind: effectiveReason,
|
|
33821
34137
|
rule: ruleText,
|
|
33822
|
-
reason:
|
|
34138
|
+
reason: note,
|
|
33823
34139
|
scope,
|
|
33824
34140
|
sourceCanonicalIds: allTargets.map((f) => f.frontmatter.canonical_id).filter((id) => Boolean(id && UUID_RE4.test(id)))
|
|
33825
34141
|
});
|
|
@@ -33830,7 +34146,7 @@ async function triage(refs2, opts) {
|
|
|
33830
34146
|
status: status2.data,
|
|
33831
34147
|
reason: effectiveReason,
|
|
33832
34148
|
duplicateOf: duplicateTarget,
|
|
33833
|
-
...
|
|
34149
|
+
...note !== void 0 ? { note } : {},
|
|
33834
34150
|
by: "triage",
|
|
33835
34151
|
cascade: true,
|
|
33836
34152
|
guidance: guidance ? { id: guidance.id, all: isSweep } : null,
|
|
@@ -33915,6 +34231,7 @@ var init_triage = __esm({
|
|
|
33915
34231
|
init_dist2();
|
|
33916
34232
|
init_dist();
|
|
33917
34233
|
init_agent_commands();
|
|
34234
|
+
init_answer_input();
|
|
33918
34235
|
init_complete();
|
|
33919
34236
|
init_context4();
|
|
33920
34237
|
SCOPES = ["project", "outline", "capability"];
|
|
@@ -34382,7 +34699,7 @@ var init_browse_driver = __esm({
|
|
|
34382
34699
|
|
|
34383
34700
|
// src/commands/browse-actions.ts
|
|
34384
34701
|
import { existsSync as existsSync15 } from "fs";
|
|
34385
|
-
import
|
|
34702
|
+
import path63 from "path";
|
|
34386
34703
|
function byRole(page, role, name, opts) {
|
|
34387
34704
|
const loc = page.getByRole(role, { name, exact: false });
|
|
34388
34705
|
if (opts.nth !== void 0) {
|
|
@@ -34683,7 +35000,7 @@ async function fillIndexedElement(page, index, text, submit) {
|
|
|
34683
35000
|
await fillOrSelect(loc.first(), text, submit);
|
|
34684
35001
|
}
|
|
34685
35002
|
async function setFilesOnElement(el, filePath) {
|
|
34686
|
-
const resolved =
|
|
35003
|
+
const resolved = path63.resolve(filePath);
|
|
34687
35004
|
if (SENSITIVE_UPLOAD_PATH.test(resolved)) {
|
|
34688
35005
|
throw new Error(
|
|
34689
35006
|
`Refusing to upload sensitive path: "${resolved}". Choose a test fixture file inside the project or temp directory.`
|
|
@@ -34916,7 +35233,7 @@ var init_browse_actions = __esm({
|
|
|
34916
35233
|
|
|
34917
35234
|
// src/commands/step-numbering.ts
|
|
34918
35235
|
import { open, readFile as readFile6, readdir, stat as stat2, unlink } from "fs/promises";
|
|
34919
|
-
import
|
|
35236
|
+
import path64 from "path";
|
|
34920
35237
|
function reservationMarker() {
|
|
34921
35238
|
const marker = {
|
|
34922
35239
|
reservation: { pid: process.pid, ts: Date.now() }
|
|
@@ -34958,7 +35275,7 @@ async function peekNextStepNumber(stepsDir) {
|
|
|
34958
35275
|
for (const f of files) {
|
|
34959
35276
|
const m = /^(\d+)\.json$/.exec(f);
|
|
34960
35277
|
if (!m) continue;
|
|
34961
|
-
const fullPath =
|
|
35278
|
+
const fullPath = path64.join(stepsDir, f);
|
|
34962
35279
|
const st = await stat2(fullPath).catch(() => null);
|
|
34963
35280
|
if (!st) continue;
|
|
34964
35281
|
if (await isAbandonedReservation(fullPath, st)) {
|
|
@@ -34976,7 +35293,7 @@ async function reserveStepFile(stepsDir, limit) {
|
|
|
34976
35293
|
if (candidate > limit.maxSteps) {
|
|
34977
35294
|
throw new Error(stepLimitMessage(limit.maxSteps, limit.runId));
|
|
34978
35295
|
}
|
|
34979
|
-
const stepPath =
|
|
35296
|
+
const stepPath = path64.join(stepsDir, stepFileName(candidate));
|
|
34980
35297
|
try {
|
|
34981
35298
|
const handle = await open(stepPath, "wx");
|
|
34982
35299
|
try {
|
|
@@ -35022,7 +35339,7 @@ __export(step_exports, {
|
|
|
35022
35339
|
});
|
|
35023
35340
|
import { existsSync as existsSync16 } from "fs";
|
|
35024
35341
|
import { copyFile, mkdir as mkdir4, rename, stat as stat3 } from "fs/promises";
|
|
35025
|
-
import
|
|
35342
|
+
import path65 from "path";
|
|
35026
35343
|
import chalk31 from "chalk";
|
|
35027
35344
|
function parseMaxSteps(val) {
|
|
35028
35345
|
if (typeof val === "number" && Number.isFinite(val) && val > 0) return val;
|
|
@@ -35038,11 +35355,11 @@ function parseMaxSteps(val) {
|
|
|
35038
35355
|
}
|
|
35039
35356
|
async function recordStep(input) {
|
|
35040
35357
|
const runDirPath = runDir(input.projectRoot, input.runId);
|
|
35041
|
-
if (!existsSync16(
|
|
35358
|
+
if (!existsSync16(path65.join(runDirPath, "walkthrough.json"))) {
|
|
35042
35359
|
throw new Error(`Run not found: ${input.runId}`);
|
|
35043
35360
|
}
|
|
35044
|
-
const stepsDir =
|
|
35045
|
-
const screenshotsDir =
|
|
35361
|
+
const stepsDir = path65.join(runDirPath, "steps");
|
|
35362
|
+
const screenshotsDir = path65.join(runDirPath, "screenshots");
|
|
35046
35363
|
await mkdir4(stepsDir, { recursive: true });
|
|
35047
35364
|
await mkdir4(screenshotsDir, { recursive: true });
|
|
35048
35365
|
const shotSource = input.shot ? await existingShot(input.shot) : null;
|
|
@@ -35057,9 +35374,9 @@ async function recordStep(input) {
|
|
|
35057
35374
|
let screenshotRef = null;
|
|
35058
35375
|
if (shotSource) {
|
|
35059
35376
|
const destName = `step-${stepNumber2}.png`;
|
|
35060
|
-
const destPath =
|
|
35377
|
+
const destPath = path65.join(screenshotsDir, destName);
|
|
35061
35378
|
finalScreenshotPath = destPath;
|
|
35062
|
-
if (
|
|
35379
|
+
if (path65.resolve(destPath) !== shotSource) {
|
|
35063
35380
|
if (input.moveShot) {
|
|
35064
35381
|
await rename(shotSource, destPath).catch(
|
|
35065
35382
|
() => copyFile(shotSource, destPath)
|
|
@@ -35068,16 +35385,16 @@ async function recordStep(input) {
|
|
|
35068
35385
|
await copyFile(shotSource, destPath);
|
|
35069
35386
|
}
|
|
35070
35387
|
}
|
|
35071
|
-
screenshotRef =
|
|
35388
|
+
screenshotRef = path65.posix.join("screenshots", destName);
|
|
35072
35389
|
}
|
|
35073
35390
|
let fullRef = null;
|
|
35074
35391
|
if (input.fullShot && existsSync16(input.fullShot)) {
|
|
35075
35392
|
const fullName = `step-${stepNumber2}.full.png`;
|
|
35076
35393
|
const moved = await rename(
|
|
35077
35394
|
input.fullShot,
|
|
35078
|
-
|
|
35395
|
+
path65.join(screenshotsDir, fullName)
|
|
35079
35396
|
).then(() => true).catch(() => false);
|
|
35080
|
-
if (moved) fullRef =
|
|
35397
|
+
if (moved) fullRef = path65.posix.join("screenshots", fullName);
|
|
35081
35398
|
}
|
|
35082
35399
|
parsedStep = runStepSchema.parse({
|
|
35083
35400
|
step_number: stepNumber2,
|
|
@@ -35124,7 +35441,7 @@ async function recordStep(input) {
|
|
|
35124
35441
|
};
|
|
35125
35442
|
}
|
|
35126
35443
|
async function existingShot(shot) {
|
|
35127
|
-
const resolved =
|
|
35444
|
+
const resolved = path65.resolve(shot);
|
|
35128
35445
|
const isFile = await stat3(resolved).then((s) => s.isFile()).catch(() => false);
|
|
35129
35446
|
if (!isFile) {
|
|
35130
35447
|
throw new Error(
|
|
@@ -35185,7 +35502,7 @@ async function stepCommand(runArg, opts) {
|
|
|
35185
35502
|
{
|
|
35186
35503
|
step_number: res.step.step_number,
|
|
35187
35504
|
cancelled: true,
|
|
35188
|
-
path:
|
|
35505
|
+
path: path65.relative(projectRoot, res.stepPath).split(path65.sep).join("/"),
|
|
35189
35506
|
action: res.step.action,
|
|
35190
35507
|
note: res.step.note,
|
|
35191
35508
|
url: res.step.url,
|
|
@@ -35213,7 +35530,7 @@ async function stepCommand(runArg, opts) {
|
|
|
35213
35530
|
JSON.stringify(
|
|
35214
35531
|
{
|
|
35215
35532
|
step_number: res.step.step_number,
|
|
35216
|
-
path:
|
|
35533
|
+
path: path65.relative(projectRoot, res.stepPath).split(path65.sep).join("/"),
|
|
35217
35534
|
action: res.step.action,
|
|
35218
35535
|
note: res.step.note,
|
|
35219
35536
|
url: res.step.url,
|
|
@@ -35242,7 +35559,7 @@ async function stepCommand(runArg, opts) {
|
|
|
35242
35559
|
console.log(` ${chalk31.dim("shot:")} ${res.step.screenshot}`);
|
|
35243
35560
|
}
|
|
35244
35561
|
console.log(
|
|
35245
|
-
` ${chalk31.dim("file:")} ${
|
|
35562
|
+
` ${chalk31.dim("file:")} ${path65.relative(projectRoot, res.stepPath)}`
|
|
35246
35563
|
);
|
|
35247
35564
|
console.log();
|
|
35248
35565
|
} else {
|
|
@@ -35314,16 +35631,16 @@ import {
|
|
|
35314
35631
|
import { rm as rm2 } from "fs/promises";
|
|
35315
35632
|
import { createServer } from "net";
|
|
35316
35633
|
import { homedir as homedir3 } from "os";
|
|
35317
|
-
import
|
|
35634
|
+
import path66 from "path";
|
|
35318
35635
|
import chalk32 from "chalk";
|
|
35319
35636
|
function stateDir() {
|
|
35320
|
-
return
|
|
35321
|
-
process.env.IAMU_HOME ??
|
|
35637
|
+
return path66.join(
|
|
35638
|
+
process.env.IAMU_HOME ?? path66.join(homedir3(), ".iamu"),
|
|
35322
35639
|
"browse"
|
|
35323
35640
|
);
|
|
35324
35641
|
}
|
|
35325
35642
|
function profileDir() {
|
|
35326
|
-
return
|
|
35643
|
+
return path66.join(stateDir(), "profile");
|
|
35327
35644
|
}
|
|
35328
35645
|
function privateDir(dir) {
|
|
35329
35646
|
mkdirSync3(dir, { recursive: true, mode: 448 });
|
|
@@ -35334,7 +35651,7 @@ function privateDir(dir) {
|
|
|
35334
35651
|
}
|
|
35335
35652
|
function stateFile(run) {
|
|
35336
35653
|
const key = run && STATE_KEY.test(run) && `${run}.json` !== SHARED_STATE ? `${run}.json` : SHARED_STATE;
|
|
35337
|
-
return
|
|
35654
|
+
return path66.join(stateDir(), key);
|
|
35338
35655
|
}
|
|
35339
35656
|
function loadState(file) {
|
|
35340
35657
|
try {
|
|
@@ -35375,7 +35692,7 @@ function storedStates() {
|
|
|
35375
35692
|
}
|
|
35376
35693
|
const out2 = [];
|
|
35377
35694
|
for (const name of names2.sort()) {
|
|
35378
|
-
const file =
|
|
35695
|
+
const file = path66.join(stateDir(), name);
|
|
35379
35696
|
const state = loadState(file);
|
|
35380
35697
|
if (state) out2.push({ state, file });
|
|
35381
35698
|
}
|
|
@@ -35393,16 +35710,16 @@ function removeState(file) {
|
|
|
35393
35710
|
}
|
|
35394
35711
|
function runStillOpen(state, now = Date.now()) {
|
|
35395
35712
|
if (!state.run || !state.project) return false;
|
|
35396
|
-
const dir =
|
|
35713
|
+
const dir = path66.join(state.project, "iamu", ".runs", state.run);
|
|
35397
35714
|
try {
|
|
35398
|
-
const manifestPath =
|
|
35715
|
+
const manifestPath = path66.join(dir, "walkthrough.json");
|
|
35399
35716
|
const manifest = JSON.parse(readFileSync10(manifestPath, "utf8"));
|
|
35400
35717
|
if (typeof manifest.status !== "string" || !OPEN_RUN_STATUSES.has(manifest.status)) {
|
|
35401
35718
|
return false;
|
|
35402
35719
|
}
|
|
35403
35720
|
let last = statSync3(manifestPath).mtimeMs;
|
|
35404
35721
|
try {
|
|
35405
|
-
last = Math.max(last, statSync3(
|
|
35722
|
+
last = Math.max(last, statSync3(path66.join(dir, "steps")).mtimeMs);
|
|
35406
35723
|
} catch {
|
|
35407
35724
|
}
|
|
35408
35725
|
return now - last < RUN_IDLE_MS;
|
|
@@ -35501,7 +35818,7 @@ async function requestedViewport(opts) {
|
|
|
35501
35818
|
let manifest;
|
|
35502
35819
|
try {
|
|
35503
35820
|
manifest = JSON.parse(
|
|
35504
|
-
readFileSync10(
|
|
35821
|
+
readFileSync10(path66.join(dir, "walkthrough.json"), "utf8")
|
|
35505
35822
|
);
|
|
35506
35823
|
} catch {
|
|
35507
35824
|
fail(
|
|
@@ -35680,7 +35997,7 @@ function computePageFingerprint(url, bodySnippet) {
|
|
|
35680
35997
|
return createHash6("sha256").update(`${url}|${bodySnippet.trim().replace(/\s+/g, " ")}`).digest("hex").slice(0, 16);
|
|
35681
35998
|
}
|
|
35682
35999
|
function checkAndRecordStagnation(runDirPath, fingerprint, action) {
|
|
35683
|
-
const cacheFile =
|
|
36000
|
+
const cacheFile = path66.join(runDirPath, ".stagnation.json");
|
|
35684
36001
|
let previous = null;
|
|
35685
36002
|
try {
|
|
35686
36003
|
if (existsSync17(cacheFile)) {
|
|
@@ -35961,8 +36278,8 @@ async function actOnPage(opts, attached, state, fn, onPostShot) {
|
|
|
35961
36278
|
let contentSnippet;
|
|
35962
36279
|
if (opts.shot) {
|
|
35963
36280
|
await waitForVisualStability(page);
|
|
35964
|
-
screenshot =
|
|
35965
|
-
mkdirSync3(
|
|
36281
|
+
screenshot = path66.resolve(opts.shot);
|
|
36282
|
+
mkdirSync3(path66.dirname(screenshot), { recursive: true });
|
|
35966
36283
|
await page.screenshot({
|
|
35967
36284
|
path: screenshot,
|
|
35968
36285
|
fullPage: Boolean(opts.full),
|
|
@@ -36172,7 +36489,7 @@ function httpOrigin(raw) {
|
|
|
36172
36489
|
function readTargetUrl(runDirPath) {
|
|
36173
36490
|
try {
|
|
36174
36491
|
const m = JSON.parse(
|
|
36175
|
-
readFileSync10(
|
|
36492
|
+
readFileSync10(path66.join(runDirPath, "walkthrough.json"), "utf8")
|
|
36176
36493
|
);
|
|
36177
36494
|
return typeof m.target_url === "string" ? m.target_url : null;
|
|
36178
36495
|
} catch {
|
|
@@ -36184,7 +36501,7 @@ function fullFramePlan(runDirPath, url, scrolled) {
|
|
|
36184
36501
|
let manifest = {};
|
|
36185
36502
|
try {
|
|
36186
36503
|
manifest = JSON.parse(
|
|
36187
|
-
readFileSync10(
|
|
36504
|
+
readFileSync10(path66.join(runDirPath, "walkthrough.json"), "utf8")
|
|
36188
36505
|
);
|
|
36189
36506
|
} catch {
|
|
36190
36507
|
}
|
|
@@ -36195,7 +36512,7 @@ function fullFramePlan(runDirPath, url, scrolled) {
|
|
|
36195
36512
|
if (designer2.includes(key) || scrolled) {
|
|
36196
36513
|
return { take: true, lastHeight: null };
|
|
36197
36514
|
}
|
|
36198
|
-
const stepsDir =
|
|
36515
|
+
const stepsDir = path66.join(runDirPath, "steps");
|
|
36199
36516
|
let names2 = [];
|
|
36200
36517
|
try {
|
|
36201
36518
|
names2 = readdirSync3(stepsDir).filter((n) => /^\d+\.json$/.test(n)).sort();
|
|
@@ -36206,7 +36523,7 @@ function fullFramePlan(runDirPath, url, scrolled) {
|
|
|
36206
36523
|
for (const name of names2) {
|
|
36207
36524
|
try {
|
|
36208
36525
|
const earlier = JSON.parse(
|
|
36209
|
-
readFileSync10(
|
|
36526
|
+
readFileSync10(path66.join(stepsDir, name), "utf8")
|
|
36210
36527
|
);
|
|
36211
36528
|
if (typeof earlier.url === "string" && stepScreenKey(earlier.url, target) === key) {
|
|
36212
36529
|
seen = true;
|
|
@@ -36273,7 +36590,7 @@ function printStep(opts, record, projectRoot, stagnationWarning, challengeWarnin
|
|
|
36273
36590
|
if (opts.json) {
|
|
36274
36591
|
const doc = {
|
|
36275
36592
|
step_number: step.step_number,
|
|
36276
|
-
path:
|
|
36593
|
+
path: path66.relative(projectRoot, record.stepPath).split(path66.sep).join("/"),
|
|
36277
36594
|
action: step.action,
|
|
36278
36595
|
note: step.note,
|
|
36279
36596
|
url: step.url,
|
|
@@ -36391,7 +36708,7 @@ async function clearStorage(opts, driver) {
|
|
|
36391
36708
|
try {
|
|
36392
36709
|
if (dir) {
|
|
36393
36710
|
target = JSON.parse(
|
|
36394
|
-
readFileSync10(
|
|
36711
|
+
readFileSync10(path66.join(dir, "walkthrough.json"), "utf8")
|
|
36395
36712
|
).target_url;
|
|
36396
36713
|
}
|
|
36397
36714
|
} catch {
|
|
@@ -36578,15 +36895,15 @@ async function browseStep(args2, opts, driver) {
|
|
|
36578
36895
|
))
|
|
36579
36896
|
return;
|
|
36580
36897
|
const maxSteps = parseMaxSteps(opts.maxSteps);
|
|
36581
|
-
if (await peekNextStepNumber(
|
|
36898
|
+
if (await peekNextStepNumber(path66.join(runDirPath, "steps")) > maxSteps) {
|
|
36582
36899
|
return fail(stepLimitMessage(maxSteps, opts.run), opts);
|
|
36583
36900
|
}
|
|
36584
|
-
const pendingShot =
|
|
36901
|
+
const pendingShot = path66.join(
|
|
36585
36902
|
runDirPath,
|
|
36586
36903
|
"screenshots",
|
|
36587
36904
|
`.pending-${randomUUID11()}.png`
|
|
36588
36905
|
);
|
|
36589
|
-
const pendingFull =
|
|
36906
|
+
const pendingFull = path66.join(
|
|
36590
36907
|
runDirPath,
|
|
36591
36908
|
"screenshots",
|
|
36592
36909
|
`.pending-${randomUUID11()}.full.png`
|
|
@@ -38469,18 +38786,18 @@ __export(preflight_exports, {
|
|
|
38469
38786
|
});
|
|
38470
38787
|
import { spawnSync } from "child_process";
|
|
38471
38788
|
import { existsSync as existsSync20, promises as fs67 } from "fs";
|
|
38472
|
-
import
|
|
38789
|
+
import path67 from "path";
|
|
38473
38790
|
import chalk36 from "chalk";
|
|
38474
38791
|
function detectPackageManager(targetDir) {
|
|
38475
|
-
if (existsSync20(
|
|
38476
|
-
if (existsSync20(
|
|
38477
|
-
if (existsSync20(
|
|
38792
|
+
if (existsSync20(path67.join(targetDir, "pnpm-lock.yaml"))) return "pnpm";
|
|
38793
|
+
if (existsSync20(path67.join(targetDir, "yarn.lock"))) return "yarn";
|
|
38794
|
+
if (existsSync20(path67.join(targetDir, "bun.lockb")) || existsSync20(path67.join(targetDir, "bun.lock"))) {
|
|
38478
38795
|
return "bun";
|
|
38479
38796
|
}
|
|
38480
38797
|
return "npm";
|
|
38481
38798
|
}
|
|
38482
38799
|
async function detectPreflightCommand(targetDir) {
|
|
38483
|
-
const pkgPath =
|
|
38800
|
+
const pkgPath = path67.join(targetDir, "package.json");
|
|
38484
38801
|
if (existsSync20(pkgPath)) {
|
|
38485
38802
|
try {
|
|
38486
38803
|
const raw = await fs67.readFile(pkgPath, "utf8");
|
|
@@ -38493,7 +38810,7 @@ async function detectPreflightCommand(targetDir) {
|
|
|
38493
38810
|
} catch {
|
|
38494
38811
|
}
|
|
38495
38812
|
}
|
|
38496
|
-
const tsconfigPath =
|
|
38813
|
+
const tsconfigPath = path67.join(targetDir, "tsconfig.json");
|
|
38497
38814
|
if (existsSync20(tsconfigPath)) {
|
|
38498
38815
|
return { command: "npx", args: ["--no-install", "tsc", "--noEmit"] };
|
|
38499
38816
|
}
|
|
@@ -38532,7 +38849,7 @@ async function runPreflightCheck(targetDir) {
|
|
|
38532
38849
|
};
|
|
38533
38850
|
}
|
|
38534
38851
|
async function preflight(opts = {}) {
|
|
38535
|
-
const targetDir = opts.cwd ?
|
|
38852
|
+
const targetDir = opts.cwd ? path67.resolve(opts.cwd) : process.cwd();
|
|
38536
38853
|
const result = await runPreflightCheck(targetDir);
|
|
38537
38854
|
if (result.ok) {
|
|
38538
38855
|
if (opts.json) {
|
|
@@ -39041,12 +39358,12 @@ __export(validate_exports, {
|
|
|
39041
39358
|
});
|
|
39042
39359
|
import { randomUUID as randomUUID12 } from "crypto";
|
|
39043
39360
|
import { promises as fs68 } from "fs";
|
|
39044
|
-
import
|
|
39361
|
+
import path68 from "path";
|
|
39045
39362
|
import chalk38 from "chalk";
|
|
39046
39363
|
function inferTypeFromPath(filePath, projectRoot) {
|
|
39047
|
-
const rel =
|
|
39048
|
-
if (rel.startsWith("..") ||
|
|
39049
|
-
const segments = rel.split(
|
|
39364
|
+
const rel = path68.relative(path68.join(projectRoot, "iamu"), filePath);
|
|
39365
|
+
if (rel.startsWith("..") || path68.isAbsolute(rel)) return null;
|
|
39366
|
+
const segments = rel.split(path68.sep);
|
|
39050
39367
|
if (segments.length < 2) return null;
|
|
39051
39368
|
return TYPE_BY_ALIAS.get(segments[0]) ?? null;
|
|
39052
39369
|
}
|
|
@@ -39063,7 +39380,7 @@ function applyZodValidation(schema2, data, result) {
|
|
|
39063
39380
|
}
|
|
39064
39381
|
function validateFile(filePath, raw, projectRoot) {
|
|
39065
39382
|
const result = { path: filePath, valid: true, errors: [] };
|
|
39066
|
-
if (
|
|
39383
|
+
if (path68.basename(filePath) === "config.json") {
|
|
39067
39384
|
let parsedJson;
|
|
39068
39385
|
try {
|
|
39069
39386
|
parsedJson = JSON.parse(raw);
|
|
@@ -39090,11 +39407,11 @@ function validateFile(filePath, raw, projectRoot) {
|
|
|
39090
39407
|
);
|
|
39091
39408
|
return result;
|
|
39092
39409
|
}
|
|
39093
|
-
if (
|
|
39410
|
+
if (path68.basename(filePath) === "testability.md") {
|
|
39094
39411
|
applyZodValidation(testabilityFrontmatterSchema, frontmatter, result);
|
|
39095
39412
|
return result;
|
|
39096
39413
|
}
|
|
39097
|
-
if (
|
|
39414
|
+
if (path68.basename(filePath) === "context.yml") {
|
|
39098
39415
|
applyZodValidation(contextFrontmatterSchema, frontmatter, result);
|
|
39099
39416
|
const warnings = [];
|
|
39100
39417
|
const hasNotes = Boolean(
|
|
@@ -39156,7 +39473,7 @@ async function buildRepairLookups(projectRoot) {
|
|
|
39156
39473
|
}
|
|
39157
39474
|
}
|
|
39158
39475
|
const runScenario = /* @__PURE__ */ new Map();
|
|
39159
|
-
const runsDir2 =
|
|
39476
|
+
const runsDir2 = path68.join(projectRoot, "iamu", ".runs");
|
|
39160
39477
|
let runIds = [];
|
|
39161
39478
|
try {
|
|
39162
39479
|
runIds = await fs68.readdir(runsDir2);
|
|
@@ -39166,7 +39483,7 @@ async function buildRepairLookups(projectRoot) {
|
|
|
39166
39483
|
try {
|
|
39167
39484
|
const manifest = JSON.parse(
|
|
39168
39485
|
await fs68.readFile(
|
|
39169
|
-
|
|
39486
|
+
path68.join(runsDir2, runId, "walkthrough.json"),
|
|
39170
39487
|
"utf8"
|
|
39171
39488
|
)
|
|
39172
39489
|
);
|
|
@@ -39197,7 +39514,7 @@ async function fixFindings(projectRoot, results, dryRun) {
|
|
|
39197
39514
|
parsed.frontmatter,
|
|
39198
39515
|
{
|
|
39199
39516
|
...lookups,
|
|
39200
|
-
fileSlug:
|
|
39517
|
+
fileSlug: path68.basename(result.path, ".md"),
|
|
39201
39518
|
newId: randomUUID12,
|
|
39202
39519
|
body: parsed.body
|
|
39203
39520
|
}
|
|
@@ -39239,7 +39556,7 @@ async function validate(filePath, opts) {
|
|
|
39239
39556
|
if (process.exitCode === 1) return;
|
|
39240
39557
|
const results = [];
|
|
39241
39558
|
if (filePath) {
|
|
39242
|
-
const abs =
|
|
39559
|
+
const abs = path68.isAbsolute(filePath) ? filePath : path68.resolve(process.cwd(), filePath);
|
|
39243
39560
|
try {
|
|
39244
39561
|
const raw = await fs68.readFile(abs, "utf8");
|
|
39245
39562
|
results.push(validateFile(abs, raw, projectRoot));
|
|
@@ -39255,7 +39572,7 @@ async function validate(filePath, opts) {
|
|
|
39255
39572
|
return;
|
|
39256
39573
|
}
|
|
39257
39574
|
} else if (opts.run) {
|
|
39258
|
-
const findingsDir =
|
|
39575
|
+
const findingsDir = path68.join(projectRoot, "iamu", ENTITY_DIRS.finding);
|
|
39259
39576
|
let names2 = [];
|
|
39260
39577
|
try {
|
|
39261
39578
|
names2 = await fs68.readdir(findingsDir);
|
|
@@ -39263,7 +39580,7 @@ async function validate(filePath, opts) {
|
|
|
39263
39580
|
}
|
|
39264
39581
|
for (const name of names2) {
|
|
39265
39582
|
if (!name.endsWith(".md")) continue;
|
|
39266
|
-
const abs =
|
|
39583
|
+
const abs = path68.join(findingsDir, name);
|
|
39267
39584
|
try {
|
|
39268
39585
|
const raw = await fs68.readFile(abs, "utf8");
|
|
39269
39586
|
const { frontmatter } = parseFrontmatter(raw);
|
|
@@ -39272,7 +39589,7 @@ async function validate(filePath, opts) {
|
|
|
39272
39589
|
}
|
|
39273
39590
|
} catch {
|
|
39274
39591
|
results.push({
|
|
39275
|
-
path:
|
|
39592
|
+
path: path68.relative(projectRoot, abs),
|
|
39276
39593
|
valid: false,
|
|
39277
39594
|
errors: ["Cannot read file or parse frontmatter"]
|
|
39278
39595
|
});
|
|
@@ -39305,7 +39622,7 @@ async function validate(filePath, opts) {
|
|
|
39305
39622
|
} else {
|
|
39306
39623
|
const typesToCheck = typeFilter ? ENTITY_TYPES.filter((t) => typeFilter.has(t)) : ENTITY_TYPES;
|
|
39307
39624
|
for (const type2 of typesToCheck) {
|
|
39308
|
-
const iamuTypeDir =
|
|
39625
|
+
const iamuTypeDir = path68.join(projectRoot, "iamu", ENTITY_DIRS[type2]);
|
|
39309
39626
|
let names2;
|
|
39310
39627
|
try {
|
|
39311
39628
|
names2 = await fs68.readdir(iamuTypeDir);
|
|
@@ -39314,13 +39631,13 @@ async function validate(filePath, opts) {
|
|
|
39314
39631
|
}
|
|
39315
39632
|
for (const name of names2) {
|
|
39316
39633
|
if (!name.endsWith(".md")) continue;
|
|
39317
|
-
const abs =
|
|
39634
|
+
const abs = path68.join(iamuTypeDir, name);
|
|
39318
39635
|
try {
|
|
39319
39636
|
const raw = await fs68.readFile(abs, "utf8");
|
|
39320
39637
|
results.push(validateFile(abs, raw, projectRoot));
|
|
39321
39638
|
} catch {
|
|
39322
39639
|
results.push({
|
|
39323
|
-
path:
|
|
39640
|
+
path: path68.relative(projectRoot, abs),
|
|
39324
39641
|
valid: false,
|
|
39325
39642
|
errors: ["Cannot read file"]
|
|
39326
39643
|
});
|
|
@@ -39328,7 +39645,7 @@ async function validate(filePath, opts) {
|
|
|
39328
39645
|
}
|
|
39329
39646
|
}
|
|
39330
39647
|
for (const extraFile of ["context.yml", "testability.md", "config.json"]) {
|
|
39331
|
-
const extraPath =
|
|
39648
|
+
const extraPath = path68.join(projectRoot, "iamu", extraFile);
|
|
39332
39649
|
try {
|
|
39333
39650
|
const raw = await fs68.readFile(extraPath, "utf8");
|
|
39334
39651
|
results.push(validateFile(extraPath, raw, projectRoot));
|
|
@@ -39354,19 +39671,19 @@ async function validate(filePath, opts) {
|
|
|
39354
39671
|
valid,
|
|
39355
39672
|
errors: results.filter((r) => !r.valid).flatMap(
|
|
39356
39673
|
(r) => r.errors.map((e) => ({
|
|
39357
|
-
path:
|
|
39674
|
+
path: path68.relative(projectRoot, r.path),
|
|
39358
39675
|
message: e
|
|
39359
39676
|
}))
|
|
39360
39677
|
),
|
|
39361
39678
|
warnings: results.filter((r) => r.warnings && r.warnings.length > 0).flatMap(
|
|
39362
39679
|
(r) => (r.warnings ?? []).map((w) => ({
|
|
39363
|
-
path:
|
|
39680
|
+
path: path68.relative(projectRoot, r.path),
|
|
39364
39681
|
message: w
|
|
39365
39682
|
}))
|
|
39366
39683
|
),
|
|
39367
39684
|
...opts.fix ? {
|
|
39368
39685
|
repaired: repaired.map((r) => ({
|
|
39369
|
-
path:
|
|
39686
|
+
path: path68.relative(projectRoot, r.path),
|
|
39370
39687
|
changes: r.repairs,
|
|
39371
39688
|
valid: r.valid
|
|
39372
39689
|
}))
|
|
@@ -39388,7 +39705,7 @@ async function validate(filePath, opts) {
|
|
|
39388
39705
|
}
|
|
39389
39706
|
let hasErrors = false;
|
|
39390
39707
|
for (const r of results) {
|
|
39391
|
-
const relPath =
|
|
39708
|
+
const relPath = path68.relative(projectRoot, r.path);
|
|
39392
39709
|
if (r.valid) {
|
|
39393
39710
|
if (r.warnings && r.warnings.length > 0) {
|
|
39394
39711
|
console.log(` ${symbols.warning} ` + chalk38.yellow(relPath));
|
|
@@ -39583,7 +39900,7 @@ __export(new_entity_exports, {
|
|
|
39583
39900
|
});
|
|
39584
39901
|
import { randomUUID as randomUUID13 } from "crypto";
|
|
39585
39902
|
import { readFile as readFile8 } from "fs/promises";
|
|
39586
|
-
import
|
|
39903
|
+
import path69 from "path";
|
|
39587
39904
|
import chalk40 from "chalk";
|
|
39588
39905
|
async function resolveBodyInput(opts, command, flag = "--body") {
|
|
39589
39906
|
if (opts.bodyFile !== void 0) {
|
|
@@ -39596,7 +39913,7 @@ async function resolveBodyInput(opts, command, flag = "--body") {
|
|
|
39596
39913
|
return null;
|
|
39597
39914
|
}
|
|
39598
39915
|
try {
|
|
39599
|
-
const body = opts.bodyFile === "-" ? await readStdinText() : await readFile8(
|
|
39916
|
+
const body = opts.bodyFile === "-" ? await readStdinText() : await readFile8(path69.resolve(process.cwd(), opts.bodyFile), "utf8");
|
|
39600
39917
|
return { body };
|
|
39601
39918
|
} catch (err) {
|
|
39602
39919
|
commandError(
|
|
@@ -39846,7 +40163,7 @@ __export(set_entity_exports, {
|
|
|
39846
40163
|
setEntity: () => setEntity
|
|
39847
40164
|
});
|
|
39848
40165
|
import { promises as fs69 } from "fs";
|
|
39849
|
-
import
|
|
40166
|
+
import path70 from "path";
|
|
39850
40167
|
import chalk41 from "chalk";
|
|
39851
40168
|
import { ZodError as ZodError2 } from "zod";
|
|
39852
40169
|
function normalizeAuthStrategy(raw) {
|
|
@@ -40037,7 +40354,7 @@ async function setEntity(type2, slug, opts) {
|
|
|
40037
40354
|
}
|
|
40038
40355
|
raw = Buffer.concat(chunks).toString("utf8");
|
|
40039
40356
|
} else {
|
|
40040
|
-
raw = await fs69.readFile(
|
|
40357
|
+
raw = await fs69.readFile(path70.resolve(projectRoot, opts.file), "utf8");
|
|
40041
40358
|
}
|
|
40042
40359
|
} catch (err) {
|
|
40043
40360
|
commandError(
|
|
@@ -40623,7 +40940,7 @@ __export(edit_entity_exports, {
|
|
|
40623
40940
|
});
|
|
40624
40941
|
import { spawn as spawn2 } from "child_process";
|
|
40625
40942
|
import { promises as fs70 } from "fs";
|
|
40626
|
-
import
|
|
40943
|
+
import path71 from "path";
|
|
40627
40944
|
import chalk42 from "chalk";
|
|
40628
40945
|
async function editEntity(type2, slug, opts = {}) {
|
|
40629
40946
|
const ctx = await resolveProject(opts);
|
|
@@ -40647,7 +40964,7 @@ async function editEntity(type2, slug, opts = {}) {
|
|
|
40647
40964
|
}
|
|
40648
40965
|
const effectiveSlug = isSpecial ? type2 : slug;
|
|
40649
40966
|
const resolvedPath = isSpecial ? type2 === "testability" ? testabilityPath(projectRoot) : contextPath(projectRoot) : await resolveEntityPath(projectRoot, type2, effectiveSlug);
|
|
40650
|
-
const filePath = resolvedPath ??
|
|
40967
|
+
const filePath = resolvedPath ?? path71.join(
|
|
40651
40968
|
projectRoot,
|
|
40652
40969
|
"iamu",
|
|
40653
40970
|
ENTITY_DIRS[type2],
|
|
@@ -40658,7 +40975,7 @@ async function editEntity(type2, slug, opts = {}) {
|
|
|
40658
40975
|
await fs70.access(filePath);
|
|
40659
40976
|
} catch {
|
|
40660
40977
|
commandError(
|
|
40661
|
-
`${type2}${isSpecial ? "" : ` "${effectiveSlug}"`} not found at ${
|
|
40978
|
+
`${type2}${isSpecial ? "" : ` "${effectiveSlug}"`} not found at ${path71.relative(projectRoot, filePath)}.`,
|
|
40662
40979
|
{
|
|
40663
40980
|
...opts,
|
|
40664
40981
|
code: "entity_not_found",
|
|
@@ -40669,7 +40986,7 @@ async function editEntity(type2, slug, opts = {}) {
|
|
|
40669
40986
|
}
|
|
40670
40987
|
if (!process.stdin.isTTY || process.env.CI) {
|
|
40671
40988
|
commandError(
|
|
40672
|
-
`Cannot open editor in a non-interactive environment. Edit the file directly: ${
|
|
40989
|
+
`Cannot open editor in a non-interactive environment. Edit the file directly: ${path71.relative(projectRoot, filePath)}`,
|
|
40673
40990
|
{
|
|
40674
40991
|
...opts,
|
|
40675
40992
|
code: "non_interactive_environment",
|
|
@@ -40745,7 +41062,7 @@ async function editEntity(type2, slug, opts = {}) {
|
|
|
40745
41062
|
console.log();
|
|
40746
41063
|
console.log(
|
|
40747
41064
|
chalk42.dim(
|
|
40748
|
-
` Fix the file and run: iamu validate ${
|
|
41065
|
+
` Fix the file and run: iamu validate ${path71.relative(projectRoot, filePath)}`
|
|
40749
41066
|
)
|
|
40750
41067
|
);
|
|
40751
41068
|
console.log();
|
|
@@ -40775,7 +41092,7 @@ async function editEntity(type2, slug, opts = {}) {
|
|
|
40775
41092
|
{
|
|
40776
41093
|
...opts,
|
|
40777
41094
|
code: "validation_error",
|
|
40778
|
-
fix: `iamu validate ${
|
|
41095
|
+
fix: `iamu validate ${path71.relative(projectRoot, filePath)}`
|
|
40779
41096
|
}
|
|
40780
41097
|
);
|
|
40781
41098
|
}
|
|
@@ -40944,7 +41261,7 @@ async function crawlFrom(page, opts, origin, takeBlocked) {
|
|
|
40944
41261
|
const byKey = /* @__PURE__ */ new Map();
|
|
40945
41262
|
const byPlaceView = /* @__PURE__ */ new Map();
|
|
40946
41263
|
const signatureOf = (s) => exploreSignature(s.headings, s.title);
|
|
40947
|
-
function addSurface(state, place, view,
|
|
41264
|
+
function addSurface(state, place, view, path78, depth) {
|
|
40948
41265
|
const key = exploreSurfaceKey(place, view);
|
|
40949
41266
|
const surface = {
|
|
40950
41267
|
key,
|
|
@@ -40956,7 +41273,7 @@ async function crawlFrom(page, opts, origin, takeBlocked) {
|
|
|
40956
41273
|
headings: state.headings,
|
|
40957
41274
|
fields: state.fields,
|
|
40958
41275
|
depth,
|
|
40959
|
-
path:
|
|
41276
|
+
path: path78,
|
|
40960
41277
|
actions: []
|
|
40961
41278
|
};
|
|
40962
41279
|
surfaces.push(surface);
|
|
@@ -40974,9 +41291,9 @@ ${surface.signature}`, key);
|
|
|
40974
41291
|
});
|
|
40975
41292
|
await settle(page, settleMs);
|
|
40976
41293
|
}
|
|
40977
|
-
async function replay(
|
|
40978
|
-
if (!("goto" in (
|
|
40979
|
-
for (const step of
|
|
41294
|
+
async function replay(path78) {
|
|
41295
|
+
if (!("goto" in (path78[0] ?? {}))) await goto(opts.startUrl);
|
|
41296
|
+
for (const step of path78) {
|
|
40980
41297
|
if ("goto" in step) {
|
|
40981
41298
|
await goto(step.goto);
|
|
40982
41299
|
} else {
|
|
@@ -41103,8 +41420,8 @@ ${sig}`, key);
|
|
|
41103
41420
|
if (stats.surfaces >= budget.max_surfaces) {
|
|
41104
41421
|
stats.truncated = true;
|
|
41105
41422
|
} else {
|
|
41106
|
-
const
|
|
41107
|
-
addSurface(after, place, view,
|
|
41423
|
+
const path78 = !sameDoc && view === null ? [{ goto: after.url }] : [...surface.path, ...via, click];
|
|
41424
|
+
addSurface(after, place, view, path78, surface.depth + 1);
|
|
41108
41425
|
}
|
|
41109
41426
|
}
|
|
41110
41427
|
}
|
|
@@ -41374,7 +41691,7 @@ __export(explore_exports, {
|
|
|
41374
41691
|
exploreList: () => exploreList,
|
|
41375
41692
|
exploreRun: () => exploreRun
|
|
41376
41693
|
});
|
|
41377
|
-
import
|
|
41694
|
+
import path72 from "path";
|
|
41378
41695
|
import chalk43 from "chalk";
|
|
41379
41696
|
function fail2(message, json2) {
|
|
41380
41697
|
if (json2) console.error(JSON.stringify({ error: message }));
|
|
@@ -41514,7 +41831,7 @@ async function exploreRun(url, opts) {
|
|
|
41514
41831
|
surfaces: result.surfaces
|
|
41515
41832
|
};
|
|
41516
41833
|
const file = await writeExploreMap(ctx.projectRoot, map2);
|
|
41517
|
-
const rel =
|
|
41834
|
+
const rel = path72.relative(process.cwd(), file);
|
|
41518
41835
|
if (opts.json) {
|
|
41519
41836
|
console.log(JSON.stringify({ id: map2.id, path: rel, stats: map2.stats }));
|
|
41520
41837
|
return;
|
|
@@ -42710,7 +43027,7 @@ __export(new_finding_exports, {
|
|
|
42710
43027
|
});
|
|
42711
43028
|
import { randomUUID as randomUUID14 } from "crypto";
|
|
42712
43029
|
import { access, readFile as readFile9 } from "fs/promises";
|
|
42713
|
-
import
|
|
43030
|
+
import path73 from "path";
|
|
42714
43031
|
import chalk46 from "chalk";
|
|
42715
43032
|
async function newFinding(opts) {
|
|
42716
43033
|
const ctx = await resolveProject(opts);
|
|
@@ -42798,7 +43115,7 @@ async function newFinding(opts) {
|
|
|
42798
43115
|
}
|
|
42799
43116
|
try {
|
|
42800
43117
|
await access(
|
|
42801
|
-
|
|
43118
|
+
path73.join(runDirPath, "steps", `${String(n).padStart(3, "0")}.json`)
|
|
42802
43119
|
);
|
|
42803
43120
|
} catch {
|
|
42804
43121
|
commandError(
|
|
@@ -42817,7 +43134,7 @@ async function newFinding(opts) {
|
|
|
42817
43134
|
if (!scenarioSlug) {
|
|
42818
43135
|
try {
|
|
42819
43136
|
const manifestRaw = await readFile9(
|
|
42820
|
-
|
|
43137
|
+
path73.join(runDirPath, "walkthrough.json"),
|
|
42821
43138
|
"utf8"
|
|
42822
43139
|
);
|
|
42823
43140
|
const manifest = JSON.parse(manifestRaw);
|
|
@@ -42964,7 +43281,7 @@ __export(summary_exports, {
|
|
|
42964
43281
|
summaryCommand: () => summaryCommand
|
|
42965
43282
|
});
|
|
42966
43283
|
import { promises as fs71 } from "fs";
|
|
42967
|
-
import
|
|
43284
|
+
import path74 from "path";
|
|
42968
43285
|
import chalk47 from "chalk";
|
|
42969
43286
|
function endedFrom(value) {
|
|
42970
43287
|
if (!value || typeof value !== "object") return void 0;
|
|
@@ -42995,7 +43312,7 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
42995
43312
|
let openQuestions = [];
|
|
42996
43313
|
let setupNotes = [];
|
|
42997
43314
|
let model = opts.model?.trim() || void 0;
|
|
42998
|
-
const summaryPath =
|
|
43315
|
+
const summaryPath = path74.join(runDirPath, "summary.json");
|
|
42999
43316
|
let blocked;
|
|
43000
43317
|
let ended;
|
|
43001
43318
|
try {
|
|
@@ -43015,7 +43332,7 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
43015
43332
|
if (opts.file === "-") {
|
|
43016
43333
|
raw = await readStdinText();
|
|
43017
43334
|
} else {
|
|
43018
|
-
raw = await fs71.readFile(
|
|
43335
|
+
raw = await fs71.readFile(path74.resolve(projectRoot, opts.file), "utf8");
|
|
43019
43336
|
}
|
|
43020
43337
|
const parsed = JSON.parse(raw);
|
|
43021
43338
|
if (Array.isArray(parsed.what_worked)) {
|
|
@@ -43092,7 +43409,7 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
43092
43409
|
JSON.stringify(
|
|
43093
43410
|
{
|
|
43094
43411
|
run_id: runId,
|
|
43095
|
-
path:
|
|
43412
|
+
path: path74.relative(projectRoot, summaryPath),
|
|
43096
43413
|
summary: data
|
|
43097
43414
|
},
|
|
43098
43415
|
null,
|
|
@@ -43154,7 +43471,7 @@ __export(advise_exports, {
|
|
|
43154
43471
|
readAdvisePrompt: () => readAdvisePrompt
|
|
43155
43472
|
});
|
|
43156
43473
|
import { existsSync as existsSync23, promises as fs72 } from "fs";
|
|
43157
|
-
import
|
|
43474
|
+
import path75 from "path";
|
|
43158
43475
|
import chalk48 from "chalk";
|
|
43159
43476
|
function readAdvisePrompt(projectRoot) {
|
|
43160
43477
|
return readInstalledPrompt(projectRoot, "advise.md");
|
|
@@ -43212,7 +43529,7 @@ async function advise(runIdArg, opts = {}) {
|
|
|
43212
43529
|
findings2 = input.findings.length;
|
|
43213
43530
|
needed = findings2 >= 2;
|
|
43214
43531
|
if (!needed) reason = "fewer than two open findings in the project";
|
|
43215
|
-
promptPath = opts.promptFile ??
|
|
43532
|
+
promptPath = opts.promptFile ?? path75.join(analysisDir(projectRoot), "advise_prompt.md");
|
|
43216
43533
|
} else {
|
|
43217
43534
|
const runId = resolveRunId(projectRoot, runIdArg ?? opts.run);
|
|
43218
43535
|
if (!requireRunId(runId, opts, "iamu advise <run-id>")) return;
|
|
@@ -43223,12 +43540,12 @@ async function advise(runIdArg, opts = {}) {
|
|
|
43223
43540
|
input = await buildAdviseInput(projectRoot, runId, manifest.scenario_slug);
|
|
43224
43541
|
refsDir = runDirPath;
|
|
43225
43542
|
findings2 = input.findings.length;
|
|
43226
|
-
const advised = existsSync23(
|
|
43543
|
+
const advised = existsSync23(path75.join(runDirPath, ADVICE_FILE));
|
|
43227
43544
|
needed = !manifest.blocked && findings2 > 0 && !advised;
|
|
43228
43545
|
if (manifest.blocked) reason = "the run was blocked";
|
|
43229
43546
|
else if (advised) reason = "the run is already advised";
|
|
43230
43547
|
else if (findings2 === 0) reason = "no open findings to advise";
|
|
43231
|
-
promptPath = opts.promptFile ??
|
|
43548
|
+
promptPath = opts.promptFile ?? path75.join(runDirPath, "advise_prompt.md");
|
|
43232
43549
|
}
|
|
43233
43550
|
if (needed) input = await withCodeIndex(projectRoot, input);
|
|
43234
43551
|
const { bundle, refs: refs2 } = promptBundle(input);
|
|
@@ -43238,11 +43555,11 @@ async function advise(runIdArg, opts = {}) {
|
|
|
43238
43555
|
promptSizeWarning(prompt)
|
|
43239
43556
|
].filter((w) => w !== null) : [];
|
|
43240
43557
|
if (prompt) {
|
|
43241
|
-
await fs72.mkdir(
|
|
43558
|
+
await fs72.mkdir(path75.dirname(promptPath), { recursive: true });
|
|
43242
43559
|
await fs72.writeFile(promptPath, prompt, "utf8");
|
|
43243
43560
|
await fs72.mkdir(refsDir, { recursive: true });
|
|
43244
43561
|
await fs72.writeFile(
|
|
43245
|
-
|
|
43562
|
+
path75.join(refsDir, ADVISE_REFS_FILE),
|
|
43246
43563
|
`${JSON.stringify(refs2, null, 2)}
|
|
43247
43564
|
`,
|
|
43248
43565
|
"utf8"
|
|
@@ -43257,8 +43574,8 @@ async function advise(runIdArg, opts = {}) {
|
|
|
43257
43574
|
...reason ? { reason } : {},
|
|
43258
43575
|
mode: opts.project ? "project" : "run",
|
|
43259
43576
|
findings: findings2,
|
|
43260
|
-
prompt_file: prompt ?
|
|
43261
|
-
prompt_file_abs: prompt ?
|
|
43577
|
+
prompt_file: prompt ? path75.relative(projectRoot, promptPath).split(path75.sep).join("/") : null,
|
|
43578
|
+
prompt_file_abs: prompt ? path75.resolve(promptPath) : null,
|
|
43262
43579
|
prompt_chars: prompt?.length ?? 0,
|
|
43263
43580
|
warnings,
|
|
43264
43581
|
advise_prompt: prompt
|
|
@@ -43334,7 +43651,7 @@ async function adviseRecord(runIdArg, opts) {
|
|
|
43334
43651
|
const { projectRoot } = ctx;
|
|
43335
43652
|
if (opts.project) {
|
|
43336
43653
|
const input2 = await readAnswerInput(opts, "advice", {
|
|
43337
|
-
defaultPath:
|
|
43654
|
+
defaultPath: path75.join(analysisDir(projectRoot), "answer.json"),
|
|
43338
43655
|
code: "missing_advice_input",
|
|
43339
43656
|
fix: "iamu advise-record --project --data '<json>'"
|
|
43340
43657
|
});
|
|
@@ -43366,7 +43683,7 @@ async function adviseRecord(runIdArg, opts) {
|
|
|
43366
43683
|
if (!manifest) return;
|
|
43367
43684
|
await logTiming(runDirPath, "advise-record", "start");
|
|
43368
43685
|
const input = await readAnswerInput(opts, "advice", {
|
|
43369
|
-
defaultPath:
|
|
43686
|
+
defaultPath: path75.join(runDir(projectRoot, runId), "advise_answer.json"),
|
|
43370
43687
|
code: "missing_advice_input",
|
|
43371
43688
|
fix: `iamu advise-record ${runId} --data '<json>'`
|
|
43372
43689
|
});
|
|
@@ -43416,7 +43733,7 @@ async function directionsList(opts) {
|
|
|
43416
43733
|
problem: d.texts.problem,
|
|
43417
43734
|
change: d.texts.change,
|
|
43418
43735
|
leading_practice: d.texts.leading_practice,
|
|
43419
|
-
path:
|
|
43736
|
+
path: path75.relative(ctx.projectRoot, d.path)
|
|
43420
43737
|
})),
|
|
43421
43738
|
null,
|
|
43422
43739
|
2
|
|
@@ -43565,7 +43882,7 @@ __export(designer_exports, {
|
|
|
43565
43882
|
designerRecord: () => designerRecord
|
|
43566
43883
|
});
|
|
43567
43884
|
import { promises as fs73 } from "fs";
|
|
43568
|
-
import
|
|
43885
|
+
import path76 from "path";
|
|
43569
43886
|
import chalk49 from "chalk";
|
|
43570
43887
|
import { ZodError as ZodError3 } from "zod";
|
|
43571
43888
|
function describeError(err) {
|
|
@@ -43585,7 +43902,7 @@ async function designer(runId, opts = {}) {
|
|
|
43585
43902
|
const { projectRoot, runDirPath } = ctx;
|
|
43586
43903
|
if (!await completedManifest2(runDirPath, runId, opts)) return;
|
|
43587
43904
|
const input = await buildRunDesignerInput(projectRoot, runId);
|
|
43588
|
-
const promptPath =
|
|
43905
|
+
const promptPath = path76.join(runDirPath, DESIGNER_PROMPT_FILE);
|
|
43589
43906
|
const prompt = input.needed ? composeDesignPrompt(
|
|
43590
43907
|
projectRoot,
|
|
43591
43908
|
"designer.md",
|
|
@@ -43594,7 +43911,7 @@ async function designer(runId, opts = {}) {
|
|
|
43594
43911
|
if (prompt) {
|
|
43595
43912
|
await fs73.writeFile(promptPath, prompt, "utf8");
|
|
43596
43913
|
await fs73.writeFile(
|
|
43597
|
-
|
|
43914
|
+
path76.join(runDirPath, DESIGNER_REFS_FILE),
|
|
43598
43915
|
`${JSON.stringify(input.refs, null, 2)}
|
|
43599
43916
|
`,
|
|
43600
43917
|
"utf8"
|
|
@@ -43605,7 +43922,7 @@ async function designer(runId, opts = {}) {
|
|
|
43605
43922
|
installedPromptWarning(projectRoot, "designer.md"),
|
|
43606
43923
|
...input.warnings
|
|
43607
43924
|
].filter((w) => w !== null) : [];
|
|
43608
|
-
const answer =
|
|
43925
|
+
const answer = path76.join(runDirPath, DESIGNER_ANSWER_FILE);
|
|
43609
43926
|
const result = {
|
|
43610
43927
|
needed: input.needed,
|
|
43611
43928
|
reason: input.reason,
|
|
@@ -43621,11 +43938,11 @@ async function designer(runId, opts = {}) {
|
|
|
43621
43938
|
title: f.title
|
|
43622
43939
|
})),
|
|
43623
43940
|
design_files: (input.bundle.design?.files ?? []).map((f) => f.entry),
|
|
43624
|
-
prompt_file: prompt ?
|
|
43941
|
+
prompt_file: prompt ? path76.relative(projectRoot, promptPath).split(path76.sep).join("/") : null,
|
|
43625
43942
|
prompt_file_abs: prompt ? promptPath : null,
|
|
43626
43943
|
prompt_chars: prompt?.length ?? 0,
|
|
43627
43944
|
answer_file_abs: prompt ? answer : null,
|
|
43628
|
-
next: prompt ? `Give a fresh agent ${
|
|
43945
|
+
next: prompt ? `Give a fresh agent ${path76.relative(projectRoot, promptPath)} as its only context, then record its answer: iamu designer-record ${runId} --data '<answer JSON>' (or --file <path>)` : null,
|
|
43629
43946
|
hint: input.judged_against === "general-craft" ? await designDocsHint(projectRoot) : null,
|
|
43630
43947
|
// No design doc recorded or found, and the template not declined:
|
|
43631
43948
|
// the run summary offers to start one (iamu-design).
|
|
@@ -43671,6 +43988,9 @@ function printDesignerRecord(res) {
|
|
|
43671
43988
|
` ${chalk49.cyan("\u25C6")} Proposed doc edit ${p.outcome}: ${p.slug} (iamu design proposals)`
|
|
43672
43989
|
);
|
|
43673
43990
|
}
|
|
43991
|
+
for (const slug of res.verified ?? []) {
|
|
43992
|
+
console.log(` ${chalk49.green("\u2713")} Fix verified: ${slug}`);
|
|
43993
|
+
}
|
|
43674
43994
|
for (const r of res.rejected) {
|
|
43675
43995
|
console.log(` ${chalk49.yellow("!")} Dropped ${r.id}: ${r.reason}`);
|
|
43676
43996
|
}
|
|
@@ -43756,7 +44076,7 @@ async function designerRecordLocked(projectRoot, runDirPath, runId, manifest, op
|
|
|
43756
44076
|
return;
|
|
43757
44077
|
}
|
|
43758
44078
|
const input = await readAnswerInput(opts, "designer's review", {
|
|
43759
|
-
defaultPath:
|
|
44079
|
+
defaultPath: path76.join(runDirPath, DESIGNER_ANSWER_FILE),
|
|
43760
44080
|
code: "missing_designer_answer",
|
|
43761
44081
|
fix: `iamu designer-record ${runId} --data '<json>'`
|
|
43762
44082
|
});
|
|
@@ -43888,7 +44208,7 @@ __export(post_walk_exports, {
|
|
|
43888
44208
|
postWalk: () => postWalk
|
|
43889
44209
|
});
|
|
43890
44210
|
import { existsSync as existsSync24, promises as fs74 } from "fs";
|
|
43891
|
-
import
|
|
44211
|
+
import path77 from "path";
|
|
43892
44212
|
import chalk50 from "chalk";
|
|
43893
44213
|
async function captureStageJson(fn) {
|
|
43894
44214
|
const captured = [];
|
|
@@ -43931,7 +44251,7 @@ async function postWalk(runIdArg, opts = {}) {
|
|
|
43931
44251
|
}
|
|
43932
44252
|
const runDirPath = requireRun(projectRoot, runId, opts);
|
|
43933
44253
|
if (!runDirPath) return;
|
|
43934
|
-
const manifestPath =
|
|
44254
|
+
const manifestPath = path77.join(runDirPath, "walkthrough.json");
|
|
43935
44255
|
let manifest;
|
|
43936
44256
|
try {
|
|
43937
44257
|
manifest = JSON.parse(
|
|
@@ -43972,7 +44292,7 @@ async function postWalk(runIdArg, opts = {}) {
|
|
|
43972
44292
|
return;
|
|
43973
44293
|
}
|
|
43974
44294
|
const recordedNow = [];
|
|
43975
|
-
const matchesAnswerPath =
|
|
44295
|
+
const matchesAnswerPath = path77.join(runDirPath, MATCH_ANSWER_FILE);
|
|
43976
44296
|
if (existsSync24(matchesAnswerPath)) {
|
|
43977
44297
|
const res = await captureStageJson(
|
|
43978
44298
|
() => matchRecord(runId, { file: matchesAnswerPath, json: true })
|
|
@@ -43983,7 +44303,7 @@ async function postWalk(runIdArg, opts = {}) {
|
|
|
43983
44303
|
});
|
|
43984
44304
|
}
|
|
43985
44305
|
}
|
|
43986
|
-
const judgeAnswerPath =
|
|
44306
|
+
const judgeAnswerPath = path77.join(runDirPath, "judge_answer.json");
|
|
43987
44307
|
if (existsSync24(judgeAnswerPath)) {
|
|
43988
44308
|
const res = await captureStageJson(
|
|
43989
44309
|
() => evaluateRecord(runId, { file: judgeAnswerPath, json: true })
|
|
@@ -43994,8 +44314,8 @@ async function postWalk(runIdArg, opts = {}) {
|
|
|
43994
44314
|
});
|
|
43995
44315
|
}
|
|
43996
44316
|
}
|
|
43997
|
-
const adviseAnswerPath =
|
|
43998
|
-
if (!opts.critic && !existsSync24(
|
|
44317
|
+
const adviseAnswerPath = path77.join(runDirPath, "advise_answer.json");
|
|
44318
|
+
if (!opts.critic && !existsSync24(path77.join(runDirPath, ADVICE_FILE)) && existsSync24(adviseAnswerPath)) {
|
|
43999
44319
|
const res = await captureStageJson(
|
|
44000
44320
|
() => adviseRecord(runId, { file: adviseAnswerPath, json: true })
|
|
44001
44321
|
);
|
|
@@ -44005,7 +44325,7 @@ async function postWalk(runIdArg, opts = {}) {
|
|
|
44005
44325
|
});
|
|
44006
44326
|
}
|
|
44007
44327
|
}
|
|
44008
|
-
const designerAnswerPath =
|
|
44328
|
+
const designerAnswerPath = path77.join(runDirPath, DESIGNER_ANSWER_FILE);
|
|
44009
44329
|
let designerWarnings = [];
|
|
44010
44330
|
let designerInvalidChecked = [];
|
|
44011
44331
|
if (!existingDesignerFile(runDirPath)) {
|
|
@@ -44037,9 +44357,9 @@ async function postWalk(runIdArg, opts = {}) {
|
|
|
44037
44357
|
}
|
|
44038
44358
|
}
|
|
44039
44359
|
const matchInfo = await captureStageJson(() => match(runId, { json: true }));
|
|
44040
|
-
const evalRecorded = existsSync24(
|
|
44360
|
+
const evalRecorded = existsSync24(path77.join(runDirPath, "verdicts.json"));
|
|
44041
44361
|
const evalInfo = evalRecorded ? null : await captureStageJson(() => evaluate(runId, { json: true }));
|
|
44042
|
-
const adviseRecorded = existsSync24(
|
|
44362
|
+
const adviseRecorded = existsSync24(path77.join(runDirPath, ADVICE_FILE));
|
|
44043
44363
|
const awaitingJudge = !opts.critic && !adviseRecorded && !evalRecorded;
|
|
44044
44364
|
const adviseInfo = opts.critic || adviseRecorded || awaitingJudge ? null : await captureStageJson(() => advise(runId, { json: true }));
|
|
44045
44365
|
const designerRecorded = Boolean(existingDesignerFile(runDirPath));
|
|
@@ -44538,7 +44858,10 @@ function registerFindingsCommands(program) {
|
|
|
44538
44858
|
program.command("triage [findings...]").helpGroup("Findings:").description("Set findings' triage status").requiredOption(
|
|
44539
44859
|
"--status <status>",
|
|
44540
44860
|
"open, addressed, dismissed, deferred or wont_fix"
|
|
44541
|
-
).option(
|
|
44861
|
+
).option(
|
|
44862
|
+
"--note <text>",
|
|
44863
|
+
"Triage note, any status. For addressed: one line on what changed on screen, so the next run's matcher can confirm the fix"
|
|
44864
|
+
).option("--note-file <path>", "Read the note from a file (- reads stdin)").option(
|
|
44542
44865
|
"--reason <reason>",
|
|
44543
44866
|
"Dismiss reason: not_relevant (default), walker_error, out_of_scope or duplicate"
|
|
44544
44867
|
).option(
|
|
@@ -44555,7 +44878,7 @@ function registerFindingsCommands(program) {
|
|
|
44555
44878
|
"Project-wide sweep ('we never want X'): dismiss all matching open persona & designer findings across every scenario/surface and record a project-wide guidance rule"
|
|
44556
44879
|
).option("--no-guidance", "Dismiss without writing a tester guidance rule").option("--json", "Output as JSON").addHelpText(
|
|
44557
44880
|
"after",
|
|
44558
|
-
|
|
44881
|
+
'\nDismissing as not_relevant, walker_error, or out_of_scope teaches the walker: it writes a\ntester guidance rule (skip it with --no-guidance).\n\nExamples:\n $ iamu triage <finding> --status addressed --note "Icons on /orders now carry text labels"\n $ iamu triage <finding> --status dismissed --reason walker_error --note "<why>"'
|
|
44559
44882
|
).action(
|
|
44560
44883
|
async (refs2, opts) => {
|
|
44561
44884
|
const { triage: triage2 } = await Promise.resolve().then(() => (init_triage(), triage_exports));
|
|
@@ -44654,9 +44977,9 @@ function registerProjectCommands(program) {
|
|
|
44654
44977
|
"after",
|
|
44655
44978
|
"\nExamples:\n $ iamu validate --all\n $ iamu validate --all --fix --dry-run\n $ iamu validate iamu/personas/maria.md"
|
|
44656
44979
|
).action(
|
|
44657
|
-
async (
|
|
44980
|
+
async (path78, opts) => {
|
|
44658
44981
|
const { validate: validate2 } = await Promise.resolve().then(() => (init_validate(), validate_exports));
|
|
44659
|
-
await validate2(
|
|
44982
|
+
await validate2(path78, opts);
|
|
44660
44983
|
}
|
|
44661
44984
|
);
|
|
44662
44985
|
program.command("update").helpGroup("Project:").description("Update and repair agent skills and prompts to latest version").option(
|