@iamu-dev/iamu 0.8.0-beta.5 → 0.8.0-beta.6
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 +485 -193
- package/dist/build-hash.txt +1 -1
- package/dist/commands/triage.d.ts +6 -1
- package/dist/index.js +485 -193
- 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
|
@@ -6687,7 +6687,7 @@ var init_wire_compat = __esm({
|
|
|
6687
6687
|
function cloudSupportsGuidance(capabilities) {
|
|
6688
6688
|
return Boolean(capabilities?.includes(GUIDANCE_CAPABILITY) || capabilities?.includes(DESIGNER_FINDINGS_CAPABILITY));
|
|
6689
6689
|
}
|
|
6690
|
-
async function fetchServerCapabilities(apiUrl, apiKey, fetchFn, timeoutMs, onFailure) {
|
|
6690
|
+
async function fetchServerCapabilities(apiUrl, apiKey, fetchFn, timeoutMs, onFailure, onServerClock) {
|
|
6691
6691
|
const controller = new AbortController();
|
|
6692
6692
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
6693
6693
|
try {
|
|
@@ -6697,6 +6697,9 @@ async function fetchServerCapabilities(apiUrl, apiKey, fetchFn, timeoutMs, onFai
|
|
|
6697
6697
|
signal: controller.signal
|
|
6698
6698
|
};
|
|
6699
6699
|
const res = await fetchFn(`${apiUrl.replace(/\/+$/, "")}/api/meta`, init2);
|
|
6700
|
+
const offset = serverClockOffset(res);
|
|
6701
|
+
if (offset !== null)
|
|
6702
|
+
onServerClock?.(offset);
|
|
6700
6703
|
if (res.status === 404)
|
|
6701
6704
|
return [];
|
|
6702
6705
|
if (!res.ok) {
|
|
@@ -6718,6 +6721,16 @@ async function fetchServerCapabilities(apiUrl, apiKey, fetchFn, timeoutMs, onFai
|
|
|
6718
6721
|
clearTimeout(timer);
|
|
6719
6722
|
}
|
|
6720
6723
|
}
|
|
6724
|
+
function serverClockOffset(res) {
|
|
6725
|
+
const headers = res?.headers;
|
|
6726
|
+
if (!headers || typeof headers.get !== "function")
|
|
6727
|
+
return null;
|
|
6728
|
+
const raw = headers.get.call(headers, "date");
|
|
6729
|
+
if (typeof raw !== "string")
|
|
6730
|
+
return null;
|
|
6731
|
+
const at = Date.parse(raw);
|
|
6732
|
+
return Number.isNaN(at) ? null : at - Date.now();
|
|
6733
|
+
}
|
|
6721
6734
|
function shapeForServer(pending, capabilities) {
|
|
6722
6735
|
const knows = capabilities.includes(DESIGNER_FINDINGS_CAPABILITY);
|
|
6723
6736
|
const knowsProvenance = capabilities.includes(FINDING_PROVENANCE_CAPABILITY);
|
|
@@ -10687,7 +10700,7 @@ function compareIso(a, b) {
|
|
|
10687
10700
|
return ta - tb;
|
|
10688
10701
|
return String(a ?? "").localeCompare(String(b ?? ""));
|
|
10689
10702
|
}
|
|
10690
|
-
async function designerFindingsOn(projectRoot, screens, before, targetUrl = null) {
|
|
10703
|
+
async function designerFindingsOn(projectRoot, screens, before, targetUrl = null, settled = /* @__PURE__ */ new Set()) {
|
|
10691
10704
|
const latest = /* @__PURE__ */ new Map();
|
|
10692
10705
|
const cache = /* @__PURE__ */ new Map();
|
|
10693
10706
|
const wanted = new Set([...screens].map((k) => normalizeScreenKey(k, targetUrl)));
|
|
@@ -10711,7 +10724,7 @@ async function designerFindingsOn(projectRoot, screens, before, targetUrl = null
|
|
|
10711
10724
|
}
|
|
10712
10725
|
const all = [...latest.values()].sort((a, b) => compareIso(a.created_at, b.created_at));
|
|
10713
10726
|
return {
|
|
10714
|
-
open: all.filter(unsettled).map((fm, n) => ({
|
|
10727
|
+
open: all.filter((fm) => unsettled(fm) && !settled.has(fm.id)).map((fm, n) => ({
|
|
10715
10728
|
ref: `P${n + 1}`,
|
|
10716
10729
|
id: fm.id,
|
|
10717
10730
|
title: fm.title ?? fm.slug,
|
|
@@ -11204,6 +11217,26 @@ async function readReviewEvidence(projectRoot) {
|
|
|
11204
11217
|
}
|
|
11205
11218
|
return out2;
|
|
11206
11219
|
}
|
|
11220
|
+
async function verifiedDesignerFixes(projectRoot, before) {
|
|
11221
|
+
const addressed = (await listDesignerFindings(projectRoot)).map((f) => f.frontmatter).filter((fm) => fm.triage_status === "addressed" && !fm.design_cause_of);
|
|
11222
|
+
const out2 = /* @__PURE__ */ new Set();
|
|
11223
|
+
if (addressed.length === 0)
|
|
11224
|
+
return out2;
|
|
11225
|
+
const cutoff = toMillis(before);
|
|
11226
|
+
const runs = (await readRunEvidence(projectRoot)).filter((r) => {
|
|
11227
|
+
if (cutoff === null)
|
|
11228
|
+
return true;
|
|
11229
|
+
const start2 = toMillis(r.started_at);
|
|
11230
|
+
return start2 !== null && start2 < cutoff;
|
|
11231
|
+
});
|
|
11232
|
+
const states = deriveFixStates(addressed, runs);
|
|
11233
|
+
for (const fm of addressed) {
|
|
11234
|
+
if (fm.fix_status === "verified_resolved" || states.get(fm.slug)?.fix_state === "verified") {
|
|
11235
|
+
out2.add(fm.id);
|
|
11236
|
+
}
|
|
11237
|
+
}
|
|
11238
|
+
return out2;
|
|
11239
|
+
}
|
|
11207
11240
|
async function computeFixStates(projectRoot) {
|
|
11208
11241
|
const { entities } = await listEntities(projectRoot, "finding");
|
|
11209
11242
|
const findings2 = entities.map((e) => e.frontmatter);
|
|
@@ -12265,6 +12298,7 @@ async function canonicalFindingFiles(projectRoot, scenarioSlug, canonicalId) {
|
|
|
12265
12298
|
return out2;
|
|
12266
12299
|
}
|
|
12267
12300
|
async function markAddressedResolved(files, note) {
|
|
12301
|
+
const failed = [];
|
|
12268
12302
|
const trimmedNote = note?.trim();
|
|
12269
12303
|
for (const f of files) {
|
|
12270
12304
|
if (f.triage_status !== "addressed")
|
|
@@ -12280,9 +12314,14 @@ async function markAddressedResolved(files, note) {
|
|
|
12280
12314
|
if (updated !== raw)
|
|
12281
12315
|
await atomicWriteFile(f.file, updated);
|
|
12282
12316
|
});
|
|
12283
|
-
} catch {
|
|
12317
|
+
} catch (err) {
|
|
12318
|
+
failed.push({
|
|
12319
|
+
file: f.file,
|
|
12320
|
+
error: err instanceof Error ? err.message : String(err)
|
|
12321
|
+
});
|
|
12284
12322
|
}
|
|
12285
12323
|
}
|
|
12324
|
+
return failed;
|
|
12286
12325
|
}
|
|
12287
12326
|
function originOf(url) {
|
|
12288
12327
|
if (!url)
|
|
@@ -12465,7 +12504,9 @@ async function updateFixStatus(projectRoot, slug, status2) {
|
|
|
12465
12504
|
await atomicWriteFile(filePath, updated);
|
|
12466
12505
|
}
|
|
12467
12506
|
});
|
|
12468
|
-
|
|
12507
|
+
return null;
|
|
12508
|
+
} catch (err) {
|
|
12509
|
+
return err instanceof Error ? err.message : String(err);
|
|
12469
12510
|
}
|
|
12470
12511
|
}
|
|
12471
12512
|
async function listRunsInternal(projectRoot) {
|
|
@@ -12915,6 +12956,7 @@ async function mirrorLocalWalkthrough(projectRoot, runId, deps) {
|
|
|
12915
12956
|
body: form,
|
|
12916
12957
|
signal: controller.signal
|
|
12917
12958
|
});
|
|
12959
|
+
const clockOffset = serverClockOffset(response);
|
|
12918
12960
|
if (response.ok) {
|
|
12919
12961
|
let body = null;
|
|
12920
12962
|
try {
|
|
@@ -12960,7 +13002,8 @@ async function mirrorLocalWalkthrough(projectRoot, runId, deps) {
|
|
|
12960
13002
|
runCloudId: parsed.data.cloud_walkthrough_id,
|
|
12961
13003
|
contentHash,
|
|
12962
13004
|
cloudOutcome: parsed.data.status,
|
|
12963
|
-
...recordedFindings.length > 0 ? { recordedFindings } : {}
|
|
13005
|
+
...recordedFindings.length > 0 ? { recordedFindings } : {},
|
|
13006
|
+
...clockOffset !== null ? { serverClockOffsetMs: clockOffset } : {}
|
|
12964
13007
|
};
|
|
12965
13008
|
}
|
|
12966
13009
|
if (response.status === 409) {
|
|
@@ -13607,6 +13650,7 @@ async function drainRetryQueue(projectRoot, deps) {
|
|
|
13607
13650
|
const deleteUrl = projectEndpoint(config.cloud_api_url, config.cloud_project_id, "delete");
|
|
13608
13651
|
const remaining = [];
|
|
13609
13652
|
const failures = [];
|
|
13653
|
+
const pushedRuns = [];
|
|
13610
13654
|
let drained = 0;
|
|
13611
13655
|
let dropped = 0;
|
|
13612
13656
|
let notAttempted = 0;
|
|
@@ -13718,6 +13762,8 @@ async function drainRetryQueue(projectRoot, deps) {
|
|
|
13718
13762
|
});
|
|
13719
13763
|
if (result.mirrored || result.skippedReason === "bundle_too_large") {
|
|
13720
13764
|
drained++;
|
|
13765
|
+
if (result.status === "pushed")
|
|
13766
|
+
pushedRuns.push(entry.entity_id);
|
|
13721
13767
|
} else if (result.skippedReason === "missing_run" || result.skippedReason === "unsupported") {
|
|
13722
13768
|
drained++;
|
|
13723
13769
|
} else if (result.skippedReason === "cloud_conflict") {
|
|
@@ -13809,7 +13855,8 @@ async function drainRetryQueue(projectRoot, deps) {
|
|
|
13809
13855
|
dropped,
|
|
13810
13856
|
remaining: after.length,
|
|
13811
13857
|
failures,
|
|
13812
|
-
warnings
|
|
13858
|
+
warnings,
|
|
13859
|
+
...pushedRuns.length > 0 ? { pushedRuns } : {}
|
|
13813
13860
|
};
|
|
13814
13861
|
}
|
|
13815
13862
|
function unreadableBody(text, isDelete) {
|
|
@@ -14255,10 +14302,50 @@ async function ownEarlierWrites(projectRoot, batch, conflicts) {
|
|
|
14255
14302
|
}
|
|
14256
14303
|
return out2;
|
|
14257
14304
|
}
|
|
14305
|
+
function findingRun(frontmatter) {
|
|
14306
|
+
if (typeof frontmatter.run === "string" && frontmatter.run) {
|
|
14307
|
+
return frontmatter.run;
|
|
14308
|
+
}
|
|
14309
|
+
const seen = frontmatter.seen_in;
|
|
14310
|
+
if (typeof seen !== "string")
|
|
14311
|
+
return null;
|
|
14312
|
+
const i = seen.indexOf(":");
|
|
14313
|
+
return i >= 0 ? seen.slice(i + 1) : seen;
|
|
14314
|
+
}
|
|
14315
|
+
async function rowsFromOwnBundles(projectRoot, batch, conflicts, adopt) {
|
|
14316
|
+
if (!adopt || adopt.runs.size === 0 || conflicts.length === 0)
|
|
14317
|
+
return [];
|
|
14318
|
+
const bases = await readBaseHashes(projectRoot);
|
|
14319
|
+
const byId = new Map(batch.map((e) => [e.id, e]));
|
|
14320
|
+
const out2 = [];
|
|
14321
|
+
for (const c of conflicts) {
|
|
14322
|
+
const entity = byId.get(c.entity_id);
|
|
14323
|
+
if (!entity || entity.type !== "finding")
|
|
14324
|
+
continue;
|
|
14325
|
+
if (!c.cloud_hash?.startsWith("cloud:"))
|
|
14326
|
+
continue;
|
|
14327
|
+
const run = findingRun(entity.frontmatter);
|
|
14328
|
+
if (!run || !adopt.runs.has(run))
|
|
14329
|
+
continue;
|
|
14330
|
+
const at = c.cloud_updated_at ? Date.parse(c.cloud_updated_at) : Number.NaN;
|
|
14331
|
+
const slack = adopt.serverClock ? ADOPT_CLOCK_SLACK_MS : 0;
|
|
14332
|
+
if (Number.isNaN(at) || at < adopt.since - slack)
|
|
14333
|
+
continue;
|
|
14334
|
+
if (adopt.identity && c.cloud_changed_by !== adopt.identity)
|
|
14335
|
+
continue;
|
|
14336
|
+
await recordCloudBase(projectRoot, c.entity_id, c.cloud_hash);
|
|
14337
|
+
out2.push({
|
|
14338
|
+
entity,
|
|
14339
|
+
restamped: Boolean(c.base_hash || bases[c.entity_id])
|
|
14340
|
+
});
|
|
14341
|
+
}
|
|
14342
|
+
return out2;
|
|
14343
|
+
}
|
|
14258
14344
|
async function pushBatches(target, batches, byType, opts = {}) {
|
|
14259
14345
|
let pushed = 0;
|
|
14260
14346
|
let enqueued = 0;
|
|
14261
14347
|
const selfResolved = [];
|
|
14348
|
+
const adopted = [];
|
|
14262
14349
|
const conflicts = [];
|
|
14263
14350
|
const overwritten = [];
|
|
14264
14351
|
const errors = [];
|
|
@@ -14280,19 +14367,41 @@ async function pushBatches(target, batches, byType, opts = {}) {
|
|
|
14280
14367
|
conflicts,
|
|
14281
14368
|
overwritten,
|
|
14282
14369
|
errors,
|
|
14283
|
-
selfResolved
|
|
14370
|
+
selfResolved,
|
|
14371
|
+
adopted
|
|
14284
14372
|
};
|
|
14285
14373
|
}
|
|
14286
14374
|
absorb(first);
|
|
14287
14375
|
let unresolved = first.conflicts;
|
|
14288
14376
|
const own = await ownEarlierWrites(target.projectRoot, batch, unresolved);
|
|
14289
|
-
|
|
14290
|
-
|
|
14291
|
-
|
|
14292
|
-
|
|
14377
|
+
const ownIds = new Set(own.map((e) => e.id));
|
|
14378
|
+
const basesBefore = opts.adopt ? await readCloudBase(target.projectRoot) : {};
|
|
14379
|
+
const fromBundles = await rowsFromOwnBundles(target.projectRoot, batch, unresolved.filter((c) => !ownIds.has(c.entity_id)), opts.adopt);
|
|
14380
|
+
const resend = [...own, ...fromBundles.map((a) => a.entity)];
|
|
14381
|
+
if (resend.length > 0) {
|
|
14382
|
+
const resendIds = new Set(resend.map((e) => e.id));
|
|
14383
|
+
unresolved = unresolved.filter((c) => !resendIds.has(c.entity_id));
|
|
14384
|
+
const again = await pushOneBatch(target, resend, byType, `batch ${i} own-version retry`);
|
|
14293
14385
|
absorb(again);
|
|
14294
14386
|
conflicts.push(...again.conflicts);
|
|
14295
|
-
|
|
14387
|
+
const landed2 = new Set(again.landed);
|
|
14388
|
+
selfResolved.push(...own.filter((e) => landed2.has(e.id)).map((e) => e.id));
|
|
14389
|
+
for (const a of fromBundles) {
|
|
14390
|
+
if (landed2.has(a.entity.id)) {
|
|
14391
|
+
adopted.push({
|
|
14392
|
+
slug: a.entity.slug,
|
|
14393
|
+
restamped: a.restamped,
|
|
14394
|
+
serverClock: opts.adopt?.serverClock ?? false
|
|
14395
|
+
});
|
|
14396
|
+
continue;
|
|
14397
|
+
}
|
|
14398
|
+
const prior = basesBefore[a.entity.id];
|
|
14399
|
+
if (prior === void 0) {
|
|
14400
|
+
await forgetCloudBase(target.projectRoot, a.entity.id);
|
|
14401
|
+
} else {
|
|
14402
|
+
await recordCloudBase(target.projectRoot, a.entity.id, prior);
|
|
14403
|
+
}
|
|
14404
|
+
}
|
|
14296
14405
|
}
|
|
14297
14406
|
if (!opts.keepMine || unresolved.length === 0) {
|
|
14298
14407
|
conflicts.push(...unresolved);
|
|
@@ -14345,8 +14454,17 @@ async function pushBatches(target, batches, byType, opts = {}) {
|
|
|
14345
14454
|
}
|
|
14346
14455
|
await forgetQueuedUpserts(target.projectRoot, revoked);
|
|
14347
14456
|
}
|
|
14348
|
-
return {
|
|
14457
|
+
return {
|
|
14458
|
+
pushed,
|
|
14459
|
+
enqueued,
|
|
14460
|
+
conflicts,
|
|
14461
|
+
overwritten,
|
|
14462
|
+
errors,
|
|
14463
|
+
selfResolved,
|
|
14464
|
+
adopted
|
|
14465
|
+
};
|
|
14349
14466
|
}
|
|
14467
|
+
var ADOPT_CLOCK_SLACK_MS;
|
|
14350
14468
|
var init_push = __esm({
|
|
14351
14469
|
"../../packages/local-store/dist/sync/push.js"() {
|
|
14352
14470
|
"use strict";
|
|
@@ -14354,6 +14472,7 @@ var init_push = __esm({
|
|
|
14354
14472
|
init_server_reason();
|
|
14355
14473
|
init_sync_tracking();
|
|
14356
14474
|
init_types();
|
|
14475
|
+
ADOPT_CLOCK_SLACK_MS = 5e3;
|
|
14357
14476
|
}
|
|
14358
14477
|
});
|
|
14359
14478
|
|
|
@@ -14748,15 +14867,33 @@ async function syncWalkthroughs(projectRoot, deps, unpushableScenarios = /* @__P
|
|
|
14748
14867
|
const hold = deps.capabilities === null;
|
|
14749
14868
|
let held = 0;
|
|
14750
14869
|
const recordedFindings = /* @__PURE__ */ new Set();
|
|
14870
|
+
const pushedRuns = /* @__PURE__ */ new Set();
|
|
14871
|
+
let serverClockOffsetMs = null;
|
|
14751
14872
|
let runIds;
|
|
14752
14873
|
try {
|
|
14753
14874
|
runIds = await listRuns(projectRoot);
|
|
14754
14875
|
} catch (err) {
|
|
14755
14876
|
errors.push(`walkthrough scan failed: ${err instanceof Error ? err.message : String(err)} \u2014 check that iamu/.runs/ exists and is readable`);
|
|
14756
|
-
return {
|
|
14877
|
+
return {
|
|
14878
|
+
summary,
|
|
14879
|
+
errors,
|
|
14880
|
+
withheldRuns,
|
|
14881
|
+
held,
|
|
14882
|
+
recordedFindings,
|
|
14883
|
+
pushedRuns,
|
|
14884
|
+
serverClockOffsetMs
|
|
14885
|
+
};
|
|
14757
14886
|
}
|
|
14758
14887
|
if (runIds.length === 0)
|
|
14759
|
-
return {
|
|
14888
|
+
return {
|
|
14889
|
+
summary,
|
|
14890
|
+
errors,
|
|
14891
|
+
withheldRuns,
|
|
14892
|
+
held,
|
|
14893
|
+
recordedFindings,
|
|
14894
|
+
pushedRuns,
|
|
14895
|
+
serverClockOffsetMs
|
|
14896
|
+
};
|
|
14760
14897
|
const lastPush = await readLastPush(projectRoot);
|
|
14761
14898
|
const pendingWalkthroughQueueEntries = /* @__PURE__ */ new Set();
|
|
14762
14899
|
const retryQueue = await readRetryQueue(projectRoot);
|
|
@@ -14823,6 +14960,9 @@ async function syncWalkthroughs(projectRoot, deps, unpushableScenarios = /* @__P
|
|
|
14823
14960
|
});
|
|
14824
14961
|
if (result.status === "pushed" || result.status === "already_pushed") {
|
|
14825
14962
|
summary.pushed++;
|
|
14963
|
+
if (result.status === "pushed")
|
|
14964
|
+
pushedRuns.add(runId);
|
|
14965
|
+
serverClockOffsetMs ??= result.serverClockOffsetMs ?? null;
|
|
14826
14966
|
for (const id of result.recordedFindings ?? [])
|
|
14827
14967
|
recordedFindings.add(id);
|
|
14828
14968
|
} else if (result.skippedReason === "bundle_too_large" || result.skippedReason === "unsupported" || result.skippedReason === "missing_run" || result.skippedReason === "cloud_conflict") {
|
|
@@ -14845,7 +14985,15 @@ async function syncWalkthroughs(projectRoot, deps, unpushableScenarios = /* @__P
|
|
|
14845
14985
|
}
|
|
14846
14986
|
}
|
|
14847
14987
|
}
|
|
14848
|
-
return {
|
|
14988
|
+
return {
|
|
14989
|
+
summary,
|
|
14990
|
+
errors,
|
|
14991
|
+
withheldRuns,
|
|
14992
|
+
held,
|
|
14993
|
+
recordedFindings,
|
|
14994
|
+
pushedRuns,
|
|
14995
|
+
serverClockOffsetMs
|
|
14996
|
+
};
|
|
14849
14997
|
}
|
|
14850
14998
|
var HOLD_SHAPES, WALKTHROUGH_SKIP_HINTS;
|
|
14851
14999
|
var init_walkthroughs = __esm({
|
|
@@ -14895,6 +15043,7 @@ function mergePushProgress(target, outcome) {
|
|
|
14895
15043
|
if (!outcome)
|
|
14896
15044
|
return;
|
|
14897
15045
|
target.selfResolved.push(...outcome.selfResolved ?? []);
|
|
15046
|
+
target.adopted.push(...outcome.adopted ?? []);
|
|
14898
15047
|
target.pushed += outcome.pushed;
|
|
14899
15048
|
target.enqueued += outcome.enqueued;
|
|
14900
15049
|
target.conflicts.push(...outcome.conflicts);
|
|
@@ -14941,6 +15090,7 @@ async function pushDeferredFindings(projectRoot, capabilities, deferredFindings,
|
|
|
14941
15090
|
return pushBatches(target, toBatches(retryFindings, batchSize), byType, pushOpts);
|
|
14942
15091
|
}
|
|
14943
15092
|
async function runSync(projectRoot, deps) {
|
|
15093
|
+
const startedAt = Date.now();
|
|
14944
15094
|
const config = await readConfig(projectRoot);
|
|
14945
15095
|
if (!config.cloud_linked) {
|
|
14946
15096
|
return emptySyncResult([
|
|
@@ -14955,11 +15105,20 @@ async function runSync(projectRoot, deps) {
|
|
|
14955
15105
|
const apiKey = deps.apiKey;
|
|
14956
15106
|
const fetchFn = deps.fetch ?? globalThis.fetch;
|
|
14957
15107
|
let capabilitiesFailure = "";
|
|
15108
|
+
let serverClockOffsetMs = null;
|
|
14958
15109
|
const capabilities = deps.capabilities !== void 0 ? deps.capabilities : await fetchServerCapabilities(config.cloud_api_url, apiKey, fetchFn, deps.timeoutMs ?? DEFAULT_TIMEOUT_MS4, (detail) => {
|
|
14959
15110
|
capabilitiesFailure = detail;
|
|
15111
|
+
}, (offset) => {
|
|
15112
|
+
serverClockOffsetMs = offset;
|
|
14960
15113
|
});
|
|
14961
15114
|
deps = { ...deps, capabilities };
|
|
14962
|
-
const drain = await drainRetryQueue(projectRoot, deps);
|
|
15115
|
+
const { pushedRuns: drainedRuns = [], ...drain } = await drainRetryQueue(projectRoot, deps);
|
|
15116
|
+
const adoptFrom = (runs) => ({
|
|
15117
|
+
runs: /* @__PURE__ */ new Set([...drainedRuns, ...runs]),
|
|
15118
|
+
since: startedAt + (serverClockOffsetMs ?? 0),
|
|
15119
|
+
serverClock: serverClockOffsetMs !== null,
|
|
15120
|
+
identity: deps.identity ?? null
|
|
15121
|
+
});
|
|
14963
15122
|
const scanned = await scanPending(projectRoot);
|
|
14964
15123
|
if (scanned.errors.length === 0) {
|
|
14965
15124
|
await pruneSentHashes(projectRoot, scanned.seenIds);
|
|
@@ -14983,14 +15142,15 @@ async function runSync(projectRoot, deps) {
|
|
|
14983
15142
|
const pushOpts = { keepMine: deps.keepMine };
|
|
14984
15143
|
const guidance = scan.pending.filter((e) => e.type === "guidance");
|
|
14985
15144
|
const nonGuidance = scan.pending.filter((e) => e.type !== "guidance");
|
|
14986
|
-
const phase1 = await pushBatches(target, toBatches(nonGuidance, batchSize), byType, pushOpts);
|
|
15145
|
+
const phase1 = await pushBatches(target, toBatches(nonGuidance, batchSize), byType, { ...pushOpts, adopt: adoptFrom([]) });
|
|
14987
15146
|
const progress = {
|
|
14988
15147
|
pushed: phase1.pushed,
|
|
14989
15148
|
enqueued: phase1.enqueued,
|
|
14990
15149
|
conflicts: [...phase1.conflicts],
|
|
14991
15150
|
overwritten: [...phase1.overwritten],
|
|
14992
15151
|
errors,
|
|
14993
|
-
selfResolved: [...phase1.selfResolved]
|
|
15152
|
+
selfResolved: [...phase1.selfResolved],
|
|
15153
|
+
adopted: [...phase1.adopted]
|
|
14994
15154
|
};
|
|
14995
15155
|
progress.errors.push(...phase1.errors);
|
|
14996
15156
|
const notices = [];
|
|
@@ -15000,10 +15160,11 @@ async function runSync(projectRoot, deps) {
|
|
|
15000
15160
|
const guidanceResult = await pushGuidanceBatch(target, guidance, batchSize, byType, { ...pushOpts, detectUnsupported: !cloudSupportsGuidance(capabilities) }, notices);
|
|
15001
15161
|
mergePushProgress(progress, guidanceResult);
|
|
15002
15162
|
const walkthroughs = await syncWalkthroughs(projectRoot, deps, scan.unpushableScenarios);
|
|
15163
|
+
serverClockOffsetMs ??= walkthroughs.serverClockOffsetMs;
|
|
15003
15164
|
if (walkthroughs.errors.length > 0) {
|
|
15004
15165
|
progress.errors.push(...walkthroughs.errors);
|
|
15005
15166
|
}
|
|
15006
|
-
const phase3 = capabilities === null ? null : await pushDeferredFindings(projectRoot, capabilities, deferredFindings, walkthroughs, target, batchSize, byType, pushOpts, reportedRefErrors, progress.errors);
|
|
15167
|
+
const phase3 = capabilities === null ? null : await pushDeferredFindings(projectRoot, capabilities, deferredFindings, walkthroughs, target, batchSize, byType, { ...pushOpts, adopt: adoptFrom(walkthroughs.pushedRuns) }, reportedRefErrors, progress.errors);
|
|
15007
15168
|
mergePushProgress(progress, phase3);
|
|
15008
15169
|
if (capabilities === null) {
|
|
15009
15170
|
const heldFindings = shaped.heldFindings + deferredFindings.size;
|
|
@@ -15024,6 +15185,11 @@ async function runSync(projectRoot, deps) {
|
|
|
15024
15185
|
const deletion = await syncDeletions(projectRoot, config, deps);
|
|
15025
15186
|
progress.errors.push(...deletion.errors);
|
|
15026
15187
|
notices.push(...deletion.notices);
|
|
15188
|
+
for (const a of progress.adopted) {
|
|
15189
|
+
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)";
|
|
15190
|
+
const clock = a.serverClock ? "" : "; the server sent no clock, so only rows stamped after this sync began by this machine's clock counted";
|
|
15191
|
+
notices.push(`${a.slug}: ${what}${clock}`);
|
|
15192
|
+
}
|
|
15027
15193
|
if (progress.selfResolved.length > 0) {
|
|
15028
15194
|
const n = progress.selfResolved.length;
|
|
15029
15195
|
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 +19439,14 @@ async function buildRunDesignerInput(projectRoot, runId) {
|
|
|
19273
19439
|
await chooseKeyFrames(rd, grouped.candidates);
|
|
19274
19440
|
const screens = grouped.screens;
|
|
19275
19441
|
const screenKeys = new Set(screens.map((s) => s.key));
|
|
19276
|
-
const earlier = await designerFindingsOn(
|
|
19442
|
+
const earlier = await designerFindingsOn(
|
|
19443
|
+
projectRoot,
|
|
19444
|
+
screenKeys,
|
|
19445
|
+
manifest?.created_at ?? null,
|
|
19446
|
+
target,
|
|
19447
|
+
// A fix a designer's review already verified is not asked about again.
|
|
19448
|
+
await verifiedDesignerFixes(projectRoot, manifest?.created_at ?? null)
|
|
19449
|
+
);
|
|
19277
19450
|
for (const o of earlier.open)
|
|
19278
19451
|
refs2[o.ref] = o.id;
|
|
19279
19452
|
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 +19602,7 @@ var init_designer_input = __esm({
|
|
|
19429
19602
|
init_designer_files();
|
|
19430
19603
|
init_designer_ignored();
|
|
19431
19604
|
init_designer_ledger();
|
|
19605
|
+
init_fix_state();
|
|
19432
19606
|
init_walkthrough_canonical2();
|
|
19433
19607
|
init_dist();
|
|
19434
19608
|
LONG_VISIT = 6;
|
|
@@ -22381,6 +22555,69 @@ function stepNumberOf(s) {
|
|
|
22381
22555
|
const m = /^(?:step-)?(\d+)$/i.exec(s.trim());
|
|
22382
22556
|
return m ? Number(m[1]) : null;
|
|
22383
22557
|
}
|
|
22558
|
+
async function writeDesignerFixStatus(projectRoot, runId, verification, checks) {
|
|
22559
|
+
const verified = [];
|
|
22560
|
+
const untriaged = [];
|
|
22561
|
+
const warnings = [];
|
|
22562
|
+
const out2 = { verified, untriaged, warnings };
|
|
22563
|
+
const resolved = new Set(verification.resolved);
|
|
22564
|
+
const persists = new Set(verification.persists);
|
|
22565
|
+
if (resolved.size + persists.size === 0)
|
|
22566
|
+
return out2;
|
|
22567
|
+
const notWritten = (what, err) => warnings.push(`could not update ${what}: ${err instanceof Error ? err.message : String(err)}; designer.json holds the verification`);
|
|
22568
|
+
let files;
|
|
22569
|
+
try {
|
|
22570
|
+
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)));
|
|
22571
|
+
} catch (err) {
|
|
22572
|
+
notWritten("the checked findings' fix_status", err);
|
|
22573
|
+
return out2;
|
|
22574
|
+
}
|
|
22575
|
+
const checkOf = new Map(checks.map((c) => [c.canonical_id, c]));
|
|
22576
|
+
for (const cid of verification.resolved) {
|
|
22577
|
+
const mine = files.filter((fm) => fm.canonical_id === cid);
|
|
22578
|
+
if (mine.some((fm) => fm.triage_status === "addressed"))
|
|
22579
|
+
continue;
|
|
22580
|
+
const latest = mine.filter((fm) => fm.triage_status !== "dismissed").sort((a, b) => String(a.created_at ?? "").localeCompare(String(b.created_at ?? ""))).at(-1);
|
|
22581
|
+
if (latest) {
|
|
22582
|
+
untriaged.push({
|
|
22583
|
+
ref: checkOf.get(cid)?.ref ?? null,
|
|
22584
|
+
finding: latest.slug
|
|
22585
|
+
});
|
|
22586
|
+
}
|
|
22587
|
+
}
|
|
22588
|
+
let states;
|
|
22589
|
+
try {
|
|
22590
|
+
states = deriveFixStates(files, await readRunEvidence(projectRoot));
|
|
22591
|
+
} catch (err) {
|
|
22592
|
+
notWritten("the checked findings' fix_status", err);
|
|
22593
|
+
return out2;
|
|
22594
|
+
}
|
|
22595
|
+
const decidedHere = (slug, state) => {
|
|
22596
|
+
const s = states.get(slug);
|
|
22597
|
+
return s?.fix_state === state && s.decided_by === runId;
|
|
22598
|
+
};
|
|
22599
|
+
for (const fm of files) {
|
|
22600
|
+
if (fm.triage_status !== "addressed" || !fm.canonical_id)
|
|
22601
|
+
continue;
|
|
22602
|
+
if (resolved.has(fm.canonical_id) && decidedHere(fm.slug, "verified")) {
|
|
22603
|
+
const [failed] = await markAddressedResolved([
|
|
22604
|
+
{
|
|
22605
|
+
file: entityFilePath(projectRoot, "finding", fm.slug),
|
|
22606
|
+
triage_status: fm.triage_status
|
|
22607
|
+
}
|
|
22608
|
+
], checkOf.get(fm.canonical_id)?.note ?? void 0);
|
|
22609
|
+
if (failed)
|
|
22610
|
+
notWritten(fm.slug, failed.error);
|
|
22611
|
+
else
|
|
22612
|
+
verified.push(fm.slug);
|
|
22613
|
+
} else if (persists.has(fm.canonical_id) && decidedHere(fm.slug, "recurred")) {
|
|
22614
|
+
const error = await updateFixStatus(projectRoot, fm.slug, "verified_persists");
|
|
22615
|
+
if (error)
|
|
22616
|
+
notWritten(fm.slug, error);
|
|
22617
|
+
}
|
|
22618
|
+
}
|
|
22619
|
+
return out2;
|
|
22620
|
+
}
|
|
22384
22621
|
async function readRefs2(rd) {
|
|
22385
22622
|
try {
|
|
22386
22623
|
return JSON.parse(await fs49.readFile(designerRefsFile(rd), "utf8"));
|
|
@@ -22701,6 +22938,11 @@ async function recordDesigner(projectRoot, runId, answer, opts) {
|
|
|
22701
22938
|
};
|
|
22702
22939
|
await atomicWriteFile(file, `${JSON.stringify(record, null, 2)}
|
|
22703
22940
|
`);
|
|
22941
|
+
const fixes = await writeDesignerFixStatus(projectRoot, runId, verification, checks);
|
|
22942
|
+
warnings.push(...fixes.warnings);
|
|
22943
|
+
for (const u of fixes.untriaged) {
|
|
22944
|
+
warnings.push(`checked ${u.ref ?? "?"}: ${u.finding} is gone but was never marked addressed; its fix_status is left alone (checked_but_untriaged)`);
|
|
22945
|
+
}
|
|
22704
22946
|
const bySeverity = {};
|
|
22705
22947
|
const ruleCount = groupDesignerRules(rules);
|
|
22706
22948
|
for (const i of issues)
|
|
@@ -22714,7 +22956,9 @@ async function recordDesigner(projectRoot, runId, answer, opts) {
|
|
|
22714
22956
|
proposals: proposals.written,
|
|
22715
22957
|
rejected,
|
|
22716
22958
|
warnings,
|
|
22717
|
-
...invalidChecked.length > 0 ? { invalid_checked: invalidChecked } : {}
|
|
22959
|
+
...invalidChecked.length > 0 ? { invalid_checked: invalidChecked } : {},
|
|
22960
|
+
...fixes.verified.length > 0 ? { verified: fixes.verified } : {},
|
|
22961
|
+
...fixes.untriaged.length > 0 ? { checked_but_untriaged: fixes.untriaged } : {}
|
|
22718
22962
|
};
|
|
22719
22963
|
}
|
|
22720
22964
|
var DESIGNER_CATEGORIES, steps, refs, designerAnswerSchema;
|
|
@@ -22724,13 +22968,14 @@ var init_designer_record = __esm({
|
|
|
22724
22968
|
init_atomic();
|
|
22725
22969
|
init_design_proposals();
|
|
22726
22970
|
init_design_review();
|
|
22727
|
-
init_paths();
|
|
22728
22971
|
init_url_path();
|
|
22729
22972
|
init_review_issues();
|
|
22730
22973
|
init_designer_files();
|
|
22731
22974
|
init_designer_input();
|
|
22732
22975
|
init_designer_ledger();
|
|
22733
22976
|
init_entity();
|
|
22977
|
+
init_fix_state();
|
|
22978
|
+
init_paths();
|
|
22734
22979
|
init_walkthrough_canonical2();
|
|
22735
22980
|
DESIGNER_CATEGORIES = [
|
|
22736
22981
|
"hierarchy",
|
|
@@ -23847,15 +24092,15 @@ function resolveApiUrl(configured) {
|
|
|
23847
24092
|
assertSecureApiUrl(url, configured ? "API URL" : "IAMU_API_URL");
|
|
23848
24093
|
return url;
|
|
23849
24094
|
}
|
|
23850
|
-
function parseStore(raw,
|
|
24095
|
+
function parseStore(raw, path78) {
|
|
23851
24096
|
let parsed;
|
|
23852
24097
|
try {
|
|
23853
24098
|
parsed = JSON.parse(raw);
|
|
23854
24099
|
} catch (err) {
|
|
23855
|
-
throw new CredentialsFileError(
|
|
24100
|
+
throw new CredentialsFileError(path78, "not valid JSON", err);
|
|
23856
24101
|
}
|
|
23857
24102
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
23858
|
-
throw new CredentialsFileError(
|
|
24103
|
+
throw new CredentialsFileError(path78, "expected a JSON object");
|
|
23859
24104
|
}
|
|
23860
24105
|
return parsed;
|
|
23861
24106
|
}
|
|
@@ -23863,26 +24108,26 @@ function isNotFound(err) {
|
|
|
23863
24108
|
return typeof err === "object" && err !== null && err.code === "ENOENT";
|
|
23864
24109
|
}
|
|
23865
24110
|
async function readCredentialsStore() {
|
|
23866
|
-
const
|
|
24111
|
+
const path78 = getCredentialsPath();
|
|
23867
24112
|
let raw;
|
|
23868
24113
|
try {
|
|
23869
|
-
raw = await readFile(
|
|
24114
|
+
raw = await readFile(path78, "utf-8");
|
|
23870
24115
|
} catch (err) {
|
|
23871
24116
|
if (isNotFound(err)) return {};
|
|
23872
24117
|
warnUnreadableStoreOnce(err);
|
|
23873
24118
|
throw err;
|
|
23874
24119
|
}
|
|
23875
24120
|
try {
|
|
23876
|
-
return parseStore(raw,
|
|
24121
|
+
return parseStore(raw, path78);
|
|
23877
24122
|
} catch (err) {
|
|
23878
24123
|
warnUnreadableStoreOnce(err);
|
|
23879
24124
|
throw err;
|
|
23880
24125
|
}
|
|
23881
24126
|
}
|
|
23882
24127
|
async function writeCredentialsStore(store) {
|
|
23883
|
-
const
|
|
23884
|
-
await mkdir(dirname(
|
|
23885
|
-
await atomicWriteFile(
|
|
24128
|
+
const path78 = getCredentialsPath();
|
|
24129
|
+
await mkdir(dirname(path78), { recursive: true, mode: 448 });
|
|
24130
|
+
await atomicWriteFile(path78, `${JSON.stringify(store, null, 2)}
|
|
23886
24131
|
`, {
|
|
23887
24132
|
mode: 384
|
|
23888
24133
|
});
|
|
@@ -23910,16 +24155,16 @@ function resolveApiKey(apiUrl) {
|
|
|
23910
24155
|
}
|
|
23911
24156
|
}
|
|
23912
24157
|
const normalized = url.replace(/\/+$/, "");
|
|
23913
|
-
for (const
|
|
24158
|
+
for (const path78 of candidatePaths) {
|
|
23914
24159
|
let raw;
|
|
23915
24160
|
try {
|
|
23916
|
-
raw = readFileSync3(
|
|
24161
|
+
raw = readFileSync3(path78, "utf-8");
|
|
23917
24162
|
} catch (err) {
|
|
23918
24163
|
if (!isNotFound(err)) warnUnreadableStoreOnce(err);
|
|
23919
24164
|
continue;
|
|
23920
24165
|
}
|
|
23921
24166
|
try {
|
|
23922
|
-
const store = parseStore(raw,
|
|
24167
|
+
const store = parseStore(raw, path78);
|
|
23923
24168
|
const entry = store[url] ?? store[normalized] ?? store[`${normalized}/`];
|
|
23924
24169
|
if (typeof entry !== "object" || entry === null) continue;
|
|
23925
24170
|
const token = entry.token;
|
|
@@ -23960,12 +24205,12 @@ var init_credentials = __esm({
|
|
|
23960
24205
|
* @param reason - What is wrong with it.
|
|
23961
24206
|
* @param cause - Underlying parse error, if any.
|
|
23962
24207
|
*/
|
|
23963
|
-
constructor(
|
|
24208
|
+
constructor(path78, reason, cause) {
|
|
23964
24209
|
super(
|
|
23965
|
-
`Credentials file ${
|
|
24210
|
+
`Credentials file ${path78} is corrupt (${reason}). Fix it by hand, or delete it and run \`iamu auth login\` again.`,
|
|
23966
24211
|
{
|
|
23967
24212
|
cause,
|
|
23968
|
-
suggestedAction: `Delete ${
|
|
24213
|
+
suggestedAction: `Delete ${path78} and run \`iamu auth login\`, or set IAMU_TOKEN.`
|
|
23969
24214
|
}
|
|
23970
24215
|
);
|
|
23971
24216
|
}
|
|
@@ -24831,8 +25076,8 @@ import { dirname as dirname3, join as join5, relative, resolve as resolve2 } fro
|
|
|
24831
25076
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
24832
25077
|
import chalk6 from "chalk";
|
|
24833
25078
|
function getVersion() {
|
|
24834
|
-
if ("0.8.0-beta.
|
|
24835
|
-
return "0.8.0-beta.
|
|
25079
|
+
if ("0.8.0-beta.6") {
|
|
25080
|
+
return "0.8.0-beta.6";
|
|
24836
25081
|
}
|
|
24837
25082
|
try {
|
|
24838
25083
|
const thisDir = dirname3(fileURLToPath2(import.meta.url));
|
|
@@ -24955,10 +25200,10 @@ function hasGeneratedByMarker(content) {
|
|
|
24955
25200
|
function withoutMarker(content) {
|
|
24956
25201
|
return content.replace(/^generatedBy:[^\r\n]*\r?\n/m, "");
|
|
24957
25202
|
}
|
|
24958
|
-
function cliSkillState(
|
|
25203
|
+
function cliSkillState(path78, template) {
|
|
24959
25204
|
let content;
|
|
24960
25205
|
try {
|
|
24961
|
-
content = readFileSync7(
|
|
25206
|
+
content = readFileSync7(path78, "utf-8");
|
|
24962
25207
|
} catch {
|
|
24963
25208
|
return null;
|
|
24964
25209
|
}
|
|
@@ -24966,9 +25211,9 @@ function cliSkillState(path77, template) {
|
|
|
24966
25211
|
if (template === void 0) return "edited";
|
|
24967
25212
|
return withoutMarker(content) === withoutMarker(template) ? "clean" : "edited";
|
|
24968
25213
|
}
|
|
24969
|
-
function isCliWrittenPrompt(
|
|
25214
|
+
function isCliWrittenPrompt(path78) {
|
|
24970
25215
|
try {
|
|
24971
|
-
const content = readFileSync7(
|
|
25216
|
+
const content = readFileSync7(path78, "utf-8");
|
|
24972
25217
|
return hasGeneratedByMarker(content) || /^#\s+iamu:/m.test(content) || /^description:\s*.*iamu/im.test(content);
|
|
24973
25218
|
} catch {
|
|
24974
25219
|
return false;
|
|
@@ -24978,10 +25223,10 @@ function planSkillFiles(hosts, projectRoot, version) {
|
|
|
24978
25223
|
const skillTemplates = loadSkillTemplates(version);
|
|
24979
25224
|
const files = [];
|
|
24980
25225
|
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:
|
|
25226
|
+
const removeIfClean = (host, path78, name) => {
|
|
25227
|
+
const state = cliSkillState(path78, skillTemplates.get(name));
|
|
25228
|
+
if (state === "clean") files.push({ host, path: path78, content: null });
|
|
25229
|
+
else if (state === "edited") keptEdited.push({ host, path: path78 });
|
|
24985
25230
|
};
|
|
24986
25231
|
for (const host of hosts) {
|
|
24987
25232
|
if (pluginProvidesSkills(host, projectRoot)) {
|
|
@@ -25078,9 +25323,9 @@ async function findStaleSkills(projectRoot) {
|
|
|
25078
25323
|
}
|
|
25079
25324
|
return stale;
|
|
25080
25325
|
}
|
|
25081
|
-
async function removeSkillFile(
|
|
25082
|
-
await rm(
|
|
25083
|
-
await rmdir(dirname3(
|
|
25326
|
+
async function removeSkillFile(path78) {
|
|
25327
|
+
await rm(path78, { force: true });
|
|
25328
|
+
await rmdir(dirname3(path78)).catch(() => {
|
|
25084
25329
|
});
|
|
25085
25330
|
}
|
|
25086
25331
|
async function installSkillsForHosts(hosts, projectRoot, version, opts) {
|
|
@@ -25882,29 +26127,29 @@ async function writeSyncMcpEntry(mcpPath) {
|
|
|
25882
26127
|
return { removedLegacy };
|
|
25883
26128
|
}
|
|
25884
26129
|
async function registerSyncMcpForHost(host, projectRoot) {
|
|
25885
|
-
const
|
|
25886
|
-
if (
|
|
26130
|
+
const path78 = host.getMcpConfigPath(projectRoot);
|
|
26131
|
+
if (path78 === null) {
|
|
25887
26132
|
return {
|
|
25888
26133
|
status: "manual",
|
|
25889
26134
|
instruction: host.manualMcpSetup ?? `add an "iamu-sync" server that runs \`${SYNC_SERVER_COMMAND.join(" ")}\``
|
|
25890
26135
|
};
|
|
25891
26136
|
}
|
|
25892
26137
|
try {
|
|
25893
|
-
const { removedLegacy } = await writeSyncMcpEntry(
|
|
25894
|
-
return { status: "written", path:
|
|
26138
|
+
const { removedLegacy } = await writeSyncMcpEntry(path78);
|
|
26139
|
+
return { status: "written", path: path78, removedLegacy };
|
|
25895
26140
|
} catch (err) {
|
|
25896
26141
|
return {
|
|
25897
26142
|
status: "failed",
|
|
25898
|
-
path:
|
|
26143
|
+
path: path78,
|
|
25899
26144
|
message: err instanceof Error ? err.message : String(err)
|
|
25900
26145
|
};
|
|
25901
26146
|
}
|
|
25902
26147
|
}
|
|
25903
26148
|
async function removeLegacyMcpForHost(host, projectRoot) {
|
|
25904
|
-
const
|
|
25905
|
-
if (
|
|
26149
|
+
const path78 = host.getMcpConfigPath(projectRoot);
|
|
26150
|
+
if (path78 === null) return null;
|
|
25906
26151
|
try {
|
|
25907
|
-
return await removeLegacyIamuEntry(
|
|
26152
|
+
return await removeLegacyIamuEntry(path78) ? { status: "legacy_removed", path: path78, removedLegacy: true } : null;
|
|
25908
26153
|
} catch {
|
|
25909
26154
|
return null;
|
|
25910
26155
|
}
|
|
@@ -33482,6 +33727,7 @@ __export(triage_exports, {
|
|
|
33482
33727
|
triage: () => triage
|
|
33483
33728
|
});
|
|
33484
33729
|
import { promises as fs65 } from "fs";
|
|
33730
|
+
import path62 from "path";
|
|
33485
33731
|
import chalk29 from "chalk";
|
|
33486
33732
|
async function awaitingSteps(projectRoot, findings2) {
|
|
33487
33733
|
const scenarios = /* @__PURE__ */ new Set();
|
|
@@ -33620,14 +33866,53 @@ async function triage(refs2, opts) {
|
|
|
33620
33866
|
);
|
|
33621
33867
|
return;
|
|
33622
33868
|
}
|
|
33869
|
+
let note = opts.note;
|
|
33870
|
+
if (opts.noteFile !== void 0) {
|
|
33871
|
+
if (opts.note !== void 0) {
|
|
33872
|
+
commandError("Pass --note or --note-file, not both.", {
|
|
33873
|
+
...opts,
|
|
33874
|
+
code: "invalid_field_combination",
|
|
33875
|
+
fix: `iamu triage ${refs2.join(" ")} --status ${status2.data} --note-file <path>`
|
|
33876
|
+
});
|
|
33877
|
+
return;
|
|
33878
|
+
}
|
|
33879
|
+
try {
|
|
33880
|
+
const text = opts.noteFile === "-" ? await readStdinText() : await fs65.readFile(
|
|
33881
|
+
path62.resolve(process.cwd(), opts.noteFile),
|
|
33882
|
+
"utf8"
|
|
33883
|
+
);
|
|
33884
|
+
note = text.trim();
|
|
33885
|
+
} catch (err) {
|
|
33886
|
+
commandError(
|
|
33887
|
+
`Could not read --note-file ${opts.noteFile}: ${err instanceof Error ? err.message : String(err)}`,
|
|
33888
|
+
{
|
|
33889
|
+
...opts,
|
|
33890
|
+
code: "note_read_error",
|
|
33891
|
+
fix: `iamu triage ${refs2.join(" ")} --status ${status2.data} --note "<what changed>"`
|
|
33892
|
+
}
|
|
33893
|
+
);
|
|
33894
|
+
return;
|
|
33895
|
+
}
|
|
33896
|
+
if (!note) {
|
|
33897
|
+
commandError(`--note-file ${opts.noteFile} is empty`, {
|
|
33898
|
+
...opts,
|
|
33899
|
+
code: "note_empty",
|
|
33900
|
+
fix: `iamu triage ${refs2.join(" ")} --status ${status2.data} --note "<what changed>"`
|
|
33901
|
+
});
|
|
33902
|
+
return;
|
|
33903
|
+
}
|
|
33904
|
+
}
|
|
33623
33905
|
let reason = null;
|
|
33624
33906
|
if (opts.reason !== void 0) {
|
|
33625
33907
|
if (status2.data !== "dismissed") {
|
|
33626
|
-
commandError(
|
|
33627
|
-
|
|
33628
|
-
|
|
33629
|
-
|
|
33630
|
-
|
|
33908
|
+
commandError(
|
|
33909
|
+
"--reason applies only to --status dismissed \u2014 to record why or what changed, use --note",
|
|
33910
|
+
{
|
|
33911
|
+
...opts,
|
|
33912
|
+
code: "invalid_reason_usage",
|
|
33913
|
+
fix: `iamu triage ${refs2.join(" ")} --status ${status2.data} --note "<what changed>"`
|
|
33914
|
+
}
|
|
33915
|
+
);
|
|
33631
33916
|
return;
|
|
33632
33917
|
}
|
|
33633
33918
|
if (!DISMISS_REASONS.includes(opts.reason)) {
|
|
@@ -33819,7 +34104,7 @@ async function triage(refs2, opts) {
|
|
|
33819
34104
|
const rule = await createGuidanceRule(projectRoot, {
|
|
33820
34105
|
kind: effectiveReason,
|
|
33821
34106
|
rule: ruleText,
|
|
33822
|
-
reason:
|
|
34107
|
+
reason: note,
|
|
33823
34108
|
scope,
|
|
33824
34109
|
sourceCanonicalIds: allTargets.map((f) => f.frontmatter.canonical_id).filter((id) => Boolean(id && UUID_RE4.test(id)))
|
|
33825
34110
|
});
|
|
@@ -33830,7 +34115,7 @@ async function triage(refs2, opts) {
|
|
|
33830
34115
|
status: status2.data,
|
|
33831
34116
|
reason: effectiveReason,
|
|
33832
34117
|
duplicateOf: duplicateTarget,
|
|
33833
|
-
...
|
|
34118
|
+
...note !== void 0 ? { note } : {},
|
|
33834
34119
|
by: "triage",
|
|
33835
34120
|
cascade: true,
|
|
33836
34121
|
guidance: guidance ? { id: guidance.id, all: isSweep } : null,
|
|
@@ -33915,6 +34200,7 @@ var init_triage = __esm({
|
|
|
33915
34200
|
init_dist2();
|
|
33916
34201
|
init_dist();
|
|
33917
34202
|
init_agent_commands();
|
|
34203
|
+
init_answer_input();
|
|
33918
34204
|
init_complete();
|
|
33919
34205
|
init_context4();
|
|
33920
34206
|
SCOPES = ["project", "outline", "capability"];
|
|
@@ -34382,7 +34668,7 @@ var init_browse_driver = __esm({
|
|
|
34382
34668
|
|
|
34383
34669
|
// src/commands/browse-actions.ts
|
|
34384
34670
|
import { existsSync as existsSync15 } from "fs";
|
|
34385
|
-
import
|
|
34671
|
+
import path63 from "path";
|
|
34386
34672
|
function byRole(page, role, name, opts) {
|
|
34387
34673
|
const loc = page.getByRole(role, { name, exact: false });
|
|
34388
34674
|
if (opts.nth !== void 0) {
|
|
@@ -34683,7 +34969,7 @@ async function fillIndexedElement(page, index, text, submit) {
|
|
|
34683
34969
|
await fillOrSelect(loc.first(), text, submit);
|
|
34684
34970
|
}
|
|
34685
34971
|
async function setFilesOnElement(el, filePath) {
|
|
34686
|
-
const resolved =
|
|
34972
|
+
const resolved = path63.resolve(filePath);
|
|
34687
34973
|
if (SENSITIVE_UPLOAD_PATH.test(resolved)) {
|
|
34688
34974
|
throw new Error(
|
|
34689
34975
|
`Refusing to upload sensitive path: "${resolved}". Choose a test fixture file inside the project or temp directory.`
|
|
@@ -34916,7 +35202,7 @@ var init_browse_actions = __esm({
|
|
|
34916
35202
|
|
|
34917
35203
|
// src/commands/step-numbering.ts
|
|
34918
35204
|
import { open, readFile as readFile6, readdir, stat as stat2, unlink } from "fs/promises";
|
|
34919
|
-
import
|
|
35205
|
+
import path64 from "path";
|
|
34920
35206
|
function reservationMarker() {
|
|
34921
35207
|
const marker = {
|
|
34922
35208
|
reservation: { pid: process.pid, ts: Date.now() }
|
|
@@ -34958,7 +35244,7 @@ async function peekNextStepNumber(stepsDir) {
|
|
|
34958
35244
|
for (const f of files) {
|
|
34959
35245
|
const m = /^(\d+)\.json$/.exec(f);
|
|
34960
35246
|
if (!m) continue;
|
|
34961
|
-
const fullPath =
|
|
35247
|
+
const fullPath = path64.join(stepsDir, f);
|
|
34962
35248
|
const st = await stat2(fullPath).catch(() => null);
|
|
34963
35249
|
if (!st) continue;
|
|
34964
35250
|
if (await isAbandonedReservation(fullPath, st)) {
|
|
@@ -34976,7 +35262,7 @@ async function reserveStepFile(stepsDir, limit) {
|
|
|
34976
35262
|
if (candidate > limit.maxSteps) {
|
|
34977
35263
|
throw new Error(stepLimitMessage(limit.maxSteps, limit.runId));
|
|
34978
35264
|
}
|
|
34979
|
-
const stepPath =
|
|
35265
|
+
const stepPath = path64.join(stepsDir, stepFileName(candidate));
|
|
34980
35266
|
try {
|
|
34981
35267
|
const handle = await open(stepPath, "wx");
|
|
34982
35268
|
try {
|
|
@@ -35022,7 +35308,7 @@ __export(step_exports, {
|
|
|
35022
35308
|
});
|
|
35023
35309
|
import { existsSync as existsSync16 } from "fs";
|
|
35024
35310
|
import { copyFile, mkdir as mkdir4, rename, stat as stat3 } from "fs/promises";
|
|
35025
|
-
import
|
|
35311
|
+
import path65 from "path";
|
|
35026
35312
|
import chalk31 from "chalk";
|
|
35027
35313
|
function parseMaxSteps(val) {
|
|
35028
35314
|
if (typeof val === "number" && Number.isFinite(val) && val > 0) return val;
|
|
@@ -35038,11 +35324,11 @@ function parseMaxSteps(val) {
|
|
|
35038
35324
|
}
|
|
35039
35325
|
async function recordStep(input) {
|
|
35040
35326
|
const runDirPath = runDir(input.projectRoot, input.runId);
|
|
35041
|
-
if (!existsSync16(
|
|
35327
|
+
if (!existsSync16(path65.join(runDirPath, "walkthrough.json"))) {
|
|
35042
35328
|
throw new Error(`Run not found: ${input.runId}`);
|
|
35043
35329
|
}
|
|
35044
|
-
const stepsDir =
|
|
35045
|
-
const screenshotsDir =
|
|
35330
|
+
const stepsDir = path65.join(runDirPath, "steps");
|
|
35331
|
+
const screenshotsDir = path65.join(runDirPath, "screenshots");
|
|
35046
35332
|
await mkdir4(stepsDir, { recursive: true });
|
|
35047
35333
|
await mkdir4(screenshotsDir, { recursive: true });
|
|
35048
35334
|
const shotSource = input.shot ? await existingShot(input.shot) : null;
|
|
@@ -35057,9 +35343,9 @@ async function recordStep(input) {
|
|
|
35057
35343
|
let screenshotRef = null;
|
|
35058
35344
|
if (shotSource) {
|
|
35059
35345
|
const destName = `step-${stepNumber2}.png`;
|
|
35060
|
-
const destPath =
|
|
35346
|
+
const destPath = path65.join(screenshotsDir, destName);
|
|
35061
35347
|
finalScreenshotPath = destPath;
|
|
35062
|
-
if (
|
|
35348
|
+
if (path65.resolve(destPath) !== shotSource) {
|
|
35063
35349
|
if (input.moveShot) {
|
|
35064
35350
|
await rename(shotSource, destPath).catch(
|
|
35065
35351
|
() => copyFile(shotSource, destPath)
|
|
@@ -35068,16 +35354,16 @@ async function recordStep(input) {
|
|
|
35068
35354
|
await copyFile(shotSource, destPath);
|
|
35069
35355
|
}
|
|
35070
35356
|
}
|
|
35071
|
-
screenshotRef =
|
|
35357
|
+
screenshotRef = path65.posix.join("screenshots", destName);
|
|
35072
35358
|
}
|
|
35073
35359
|
let fullRef = null;
|
|
35074
35360
|
if (input.fullShot && existsSync16(input.fullShot)) {
|
|
35075
35361
|
const fullName = `step-${stepNumber2}.full.png`;
|
|
35076
35362
|
const moved = await rename(
|
|
35077
35363
|
input.fullShot,
|
|
35078
|
-
|
|
35364
|
+
path65.join(screenshotsDir, fullName)
|
|
35079
35365
|
).then(() => true).catch(() => false);
|
|
35080
|
-
if (moved) fullRef =
|
|
35366
|
+
if (moved) fullRef = path65.posix.join("screenshots", fullName);
|
|
35081
35367
|
}
|
|
35082
35368
|
parsedStep = runStepSchema.parse({
|
|
35083
35369
|
step_number: stepNumber2,
|
|
@@ -35124,7 +35410,7 @@ async function recordStep(input) {
|
|
|
35124
35410
|
};
|
|
35125
35411
|
}
|
|
35126
35412
|
async function existingShot(shot) {
|
|
35127
|
-
const resolved =
|
|
35413
|
+
const resolved = path65.resolve(shot);
|
|
35128
35414
|
const isFile = await stat3(resolved).then((s) => s.isFile()).catch(() => false);
|
|
35129
35415
|
if (!isFile) {
|
|
35130
35416
|
throw new Error(
|
|
@@ -35185,7 +35471,7 @@ async function stepCommand(runArg, opts) {
|
|
|
35185
35471
|
{
|
|
35186
35472
|
step_number: res.step.step_number,
|
|
35187
35473
|
cancelled: true,
|
|
35188
|
-
path:
|
|
35474
|
+
path: path65.relative(projectRoot, res.stepPath).split(path65.sep).join("/"),
|
|
35189
35475
|
action: res.step.action,
|
|
35190
35476
|
note: res.step.note,
|
|
35191
35477
|
url: res.step.url,
|
|
@@ -35213,7 +35499,7 @@ async function stepCommand(runArg, opts) {
|
|
|
35213
35499
|
JSON.stringify(
|
|
35214
35500
|
{
|
|
35215
35501
|
step_number: res.step.step_number,
|
|
35216
|
-
path:
|
|
35502
|
+
path: path65.relative(projectRoot, res.stepPath).split(path65.sep).join("/"),
|
|
35217
35503
|
action: res.step.action,
|
|
35218
35504
|
note: res.step.note,
|
|
35219
35505
|
url: res.step.url,
|
|
@@ -35242,7 +35528,7 @@ async function stepCommand(runArg, opts) {
|
|
|
35242
35528
|
console.log(` ${chalk31.dim("shot:")} ${res.step.screenshot}`);
|
|
35243
35529
|
}
|
|
35244
35530
|
console.log(
|
|
35245
|
-
` ${chalk31.dim("file:")} ${
|
|
35531
|
+
` ${chalk31.dim("file:")} ${path65.relative(projectRoot, res.stepPath)}`
|
|
35246
35532
|
);
|
|
35247
35533
|
console.log();
|
|
35248
35534
|
} else {
|
|
@@ -35314,16 +35600,16 @@ import {
|
|
|
35314
35600
|
import { rm as rm2 } from "fs/promises";
|
|
35315
35601
|
import { createServer } from "net";
|
|
35316
35602
|
import { homedir as homedir3 } from "os";
|
|
35317
|
-
import
|
|
35603
|
+
import path66 from "path";
|
|
35318
35604
|
import chalk32 from "chalk";
|
|
35319
35605
|
function stateDir() {
|
|
35320
|
-
return
|
|
35321
|
-
process.env.IAMU_HOME ??
|
|
35606
|
+
return path66.join(
|
|
35607
|
+
process.env.IAMU_HOME ?? path66.join(homedir3(), ".iamu"),
|
|
35322
35608
|
"browse"
|
|
35323
35609
|
);
|
|
35324
35610
|
}
|
|
35325
35611
|
function profileDir() {
|
|
35326
|
-
return
|
|
35612
|
+
return path66.join(stateDir(), "profile");
|
|
35327
35613
|
}
|
|
35328
35614
|
function privateDir(dir) {
|
|
35329
35615
|
mkdirSync3(dir, { recursive: true, mode: 448 });
|
|
@@ -35334,7 +35620,7 @@ function privateDir(dir) {
|
|
|
35334
35620
|
}
|
|
35335
35621
|
function stateFile(run) {
|
|
35336
35622
|
const key = run && STATE_KEY.test(run) && `${run}.json` !== SHARED_STATE ? `${run}.json` : SHARED_STATE;
|
|
35337
|
-
return
|
|
35623
|
+
return path66.join(stateDir(), key);
|
|
35338
35624
|
}
|
|
35339
35625
|
function loadState(file) {
|
|
35340
35626
|
try {
|
|
@@ -35375,7 +35661,7 @@ function storedStates() {
|
|
|
35375
35661
|
}
|
|
35376
35662
|
const out2 = [];
|
|
35377
35663
|
for (const name of names2.sort()) {
|
|
35378
|
-
const file =
|
|
35664
|
+
const file = path66.join(stateDir(), name);
|
|
35379
35665
|
const state = loadState(file);
|
|
35380
35666
|
if (state) out2.push({ state, file });
|
|
35381
35667
|
}
|
|
@@ -35393,16 +35679,16 @@ function removeState(file) {
|
|
|
35393
35679
|
}
|
|
35394
35680
|
function runStillOpen(state, now = Date.now()) {
|
|
35395
35681
|
if (!state.run || !state.project) return false;
|
|
35396
|
-
const dir =
|
|
35682
|
+
const dir = path66.join(state.project, "iamu", ".runs", state.run);
|
|
35397
35683
|
try {
|
|
35398
|
-
const manifestPath =
|
|
35684
|
+
const manifestPath = path66.join(dir, "walkthrough.json");
|
|
35399
35685
|
const manifest = JSON.parse(readFileSync10(manifestPath, "utf8"));
|
|
35400
35686
|
if (typeof manifest.status !== "string" || !OPEN_RUN_STATUSES.has(manifest.status)) {
|
|
35401
35687
|
return false;
|
|
35402
35688
|
}
|
|
35403
35689
|
let last = statSync3(manifestPath).mtimeMs;
|
|
35404
35690
|
try {
|
|
35405
|
-
last = Math.max(last, statSync3(
|
|
35691
|
+
last = Math.max(last, statSync3(path66.join(dir, "steps")).mtimeMs);
|
|
35406
35692
|
} catch {
|
|
35407
35693
|
}
|
|
35408
35694
|
return now - last < RUN_IDLE_MS;
|
|
@@ -35501,7 +35787,7 @@ async function requestedViewport(opts) {
|
|
|
35501
35787
|
let manifest;
|
|
35502
35788
|
try {
|
|
35503
35789
|
manifest = JSON.parse(
|
|
35504
|
-
readFileSync10(
|
|
35790
|
+
readFileSync10(path66.join(dir, "walkthrough.json"), "utf8")
|
|
35505
35791
|
);
|
|
35506
35792
|
} catch {
|
|
35507
35793
|
fail(
|
|
@@ -35680,7 +35966,7 @@ function computePageFingerprint(url, bodySnippet) {
|
|
|
35680
35966
|
return createHash6("sha256").update(`${url}|${bodySnippet.trim().replace(/\s+/g, " ")}`).digest("hex").slice(0, 16);
|
|
35681
35967
|
}
|
|
35682
35968
|
function checkAndRecordStagnation(runDirPath, fingerprint, action) {
|
|
35683
|
-
const cacheFile =
|
|
35969
|
+
const cacheFile = path66.join(runDirPath, ".stagnation.json");
|
|
35684
35970
|
let previous = null;
|
|
35685
35971
|
try {
|
|
35686
35972
|
if (existsSync17(cacheFile)) {
|
|
@@ -35961,8 +36247,8 @@ async function actOnPage(opts, attached, state, fn, onPostShot) {
|
|
|
35961
36247
|
let contentSnippet;
|
|
35962
36248
|
if (opts.shot) {
|
|
35963
36249
|
await waitForVisualStability(page);
|
|
35964
|
-
screenshot =
|
|
35965
|
-
mkdirSync3(
|
|
36250
|
+
screenshot = path66.resolve(opts.shot);
|
|
36251
|
+
mkdirSync3(path66.dirname(screenshot), { recursive: true });
|
|
35966
36252
|
await page.screenshot({
|
|
35967
36253
|
path: screenshot,
|
|
35968
36254
|
fullPage: Boolean(opts.full),
|
|
@@ -36172,7 +36458,7 @@ function httpOrigin(raw) {
|
|
|
36172
36458
|
function readTargetUrl(runDirPath) {
|
|
36173
36459
|
try {
|
|
36174
36460
|
const m = JSON.parse(
|
|
36175
|
-
readFileSync10(
|
|
36461
|
+
readFileSync10(path66.join(runDirPath, "walkthrough.json"), "utf8")
|
|
36176
36462
|
);
|
|
36177
36463
|
return typeof m.target_url === "string" ? m.target_url : null;
|
|
36178
36464
|
} catch {
|
|
@@ -36184,7 +36470,7 @@ function fullFramePlan(runDirPath, url, scrolled) {
|
|
|
36184
36470
|
let manifest = {};
|
|
36185
36471
|
try {
|
|
36186
36472
|
manifest = JSON.parse(
|
|
36187
|
-
readFileSync10(
|
|
36473
|
+
readFileSync10(path66.join(runDirPath, "walkthrough.json"), "utf8")
|
|
36188
36474
|
);
|
|
36189
36475
|
} catch {
|
|
36190
36476
|
}
|
|
@@ -36195,7 +36481,7 @@ function fullFramePlan(runDirPath, url, scrolled) {
|
|
|
36195
36481
|
if (designer2.includes(key) || scrolled) {
|
|
36196
36482
|
return { take: true, lastHeight: null };
|
|
36197
36483
|
}
|
|
36198
|
-
const stepsDir =
|
|
36484
|
+
const stepsDir = path66.join(runDirPath, "steps");
|
|
36199
36485
|
let names2 = [];
|
|
36200
36486
|
try {
|
|
36201
36487
|
names2 = readdirSync3(stepsDir).filter((n) => /^\d+\.json$/.test(n)).sort();
|
|
@@ -36206,7 +36492,7 @@ function fullFramePlan(runDirPath, url, scrolled) {
|
|
|
36206
36492
|
for (const name of names2) {
|
|
36207
36493
|
try {
|
|
36208
36494
|
const earlier = JSON.parse(
|
|
36209
|
-
readFileSync10(
|
|
36495
|
+
readFileSync10(path66.join(stepsDir, name), "utf8")
|
|
36210
36496
|
);
|
|
36211
36497
|
if (typeof earlier.url === "string" && stepScreenKey(earlier.url, target) === key) {
|
|
36212
36498
|
seen = true;
|
|
@@ -36273,7 +36559,7 @@ function printStep(opts, record, projectRoot, stagnationWarning, challengeWarnin
|
|
|
36273
36559
|
if (opts.json) {
|
|
36274
36560
|
const doc = {
|
|
36275
36561
|
step_number: step.step_number,
|
|
36276
|
-
path:
|
|
36562
|
+
path: path66.relative(projectRoot, record.stepPath).split(path66.sep).join("/"),
|
|
36277
36563
|
action: step.action,
|
|
36278
36564
|
note: step.note,
|
|
36279
36565
|
url: step.url,
|
|
@@ -36391,7 +36677,7 @@ async function clearStorage(opts, driver) {
|
|
|
36391
36677
|
try {
|
|
36392
36678
|
if (dir) {
|
|
36393
36679
|
target = JSON.parse(
|
|
36394
|
-
readFileSync10(
|
|
36680
|
+
readFileSync10(path66.join(dir, "walkthrough.json"), "utf8")
|
|
36395
36681
|
).target_url;
|
|
36396
36682
|
}
|
|
36397
36683
|
} catch {
|
|
@@ -36578,15 +36864,15 @@ async function browseStep(args2, opts, driver) {
|
|
|
36578
36864
|
))
|
|
36579
36865
|
return;
|
|
36580
36866
|
const maxSteps = parseMaxSteps(opts.maxSteps);
|
|
36581
|
-
if (await peekNextStepNumber(
|
|
36867
|
+
if (await peekNextStepNumber(path66.join(runDirPath, "steps")) > maxSteps) {
|
|
36582
36868
|
return fail(stepLimitMessage(maxSteps, opts.run), opts);
|
|
36583
36869
|
}
|
|
36584
|
-
const pendingShot =
|
|
36870
|
+
const pendingShot = path66.join(
|
|
36585
36871
|
runDirPath,
|
|
36586
36872
|
"screenshots",
|
|
36587
36873
|
`.pending-${randomUUID11()}.png`
|
|
36588
36874
|
);
|
|
36589
|
-
const pendingFull =
|
|
36875
|
+
const pendingFull = path66.join(
|
|
36590
36876
|
runDirPath,
|
|
36591
36877
|
"screenshots",
|
|
36592
36878
|
`.pending-${randomUUID11()}.full.png`
|
|
@@ -38469,18 +38755,18 @@ __export(preflight_exports, {
|
|
|
38469
38755
|
});
|
|
38470
38756
|
import { spawnSync } from "child_process";
|
|
38471
38757
|
import { existsSync as existsSync20, promises as fs67 } from "fs";
|
|
38472
|
-
import
|
|
38758
|
+
import path67 from "path";
|
|
38473
38759
|
import chalk36 from "chalk";
|
|
38474
38760
|
function detectPackageManager(targetDir) {
|
|
38475
|
-
if (existsSync20(
|
|
38476
|
-
if (existsSync20(
|
|
38477
|
-
if (existsSync20(
|
|
38761
|
+
if (existsSync20(path67.join(targetDir, "pnpm-lock.yaml"))) return "pnpm";
|
|
38762
|
+
if (existsSync20(path67.join(targetDir, "yarn.lock"))) return "yarn";
|
|
38763
|
+
if (existsSync20(path67.join(targetDir, "bun.lockb")) || existsSync20(path67.join(targetDir, "bun.lock"))) {
|
|
38478
38764
|
return "bun";
|
|
38479
38765
|
}
|
|
38480
38766
|
return "npm";
|
|
38481
38767
|
}
|
|
38482
38768
|
async function detectPreflightCommand(targetDir) {
|
|
38483
|
-
const pkgPath =
|
|
38769
|
+
const pkgPath = path67.join(targetDir, "package.json");
|
|
38484
38770
|
if (existsSync20(pkgPath)) {
|
|
38485
38771
|
try {
|
|
38486
38772
|
const raw = await fs67.readFile(pkgPath, "utf8");
|
|
@@ -38493,7 +38779,7 @@ async function detectPreflightCommand(targetDir) {
|
|
|
38493
38779
|
} catch {
|
|
38494
38780
|
}
|
|
38495
38781
|
}
|
|
38496
|
-
const tsconfigPath =
|
|
38782
|
+
const tsconfigPath = path67.join(targetDir, "tsconfig.json");
|
|
38497
38783
|
if (existsSync20(tsconfigPath)) {
|
|
38498
38784
|
return { command: "npx", args: ["--no-install", "tsc", "--noEmit"] };
|
|
38499
38785
|
}
|
|
@@ -38532,7 +38818,7 @@ async function runPreflightCheck(targetDir) {
|
|
|
38532
38818
|
};
|
|
38533
38819
|
}
|
|
38534
38820
|
async function preflight(opts = {}) {
|
|
38535
|
-
const targetDir = opts.cwd ?
|
|
38821
|
+
const targetDir = opts.cwd ? path67.resolve(opts.cwd) : process.cwd();
|
|
38536
38822
|
const result = await runPreflightCheck(targetDir);
|
|
38537
38823
|
if (result.ok) {
|
|
38538
38824
|
if (opts.json) {
|
|
@@ -39041,12 +39327,12 @@ __export(validate_exports, {
|
|
|
39041
39327
|
});
|
|
39042
39328
|
import { randomUUID as randomUUID12 } from "crypto";
|
|
39043
39329
|
import { promises as fs68 } from "fs";
|
|
39044
|
-
import
|
|
39330
|
+
import path68 from "path";
|
|
39045
39331
|
import chalk38 from "chalk";
|
|
39046
39332
|
function inferTypeFromPath(filePath, projectRoot) {
|
|
39047
|
-
const rel =
|
|
39048
|
-
if (rel.startsWith("..") ||
|
|
39049
|
-
const segments = rel.split(
|
|
39333
|
+
const rel = path68.relative(path68.join(projectRoot, "iamu"), filePath);
|
|
39334
|
+
if (rel.startsWith("..") || path68.isAbsolute(rel)) return null;
|
|
39335
|
+
const segments = rel.split(path68.sep);
|
|
39050
39336
|
if (segments.length < 2) return null;
|
|
39051
39337
|
return TYPE_BY_ALIAS.get(segments[0]) ?? null;
|
|
39052
39338
|
}
|
|
@@ -39063,7 +39349,7 @@ function applyZodValidation(schema2, data, result) {
|
|
|
39063
39349
|
}
|
|
39064
39350
|
function validateFile(filePath, raw, projectRoot) {
|
|
39065
39351
|
const result = { path: filePath, valid: true, errors: [] };
|
|
39066
|
-
if (
|
|
39352
|
+
if (path68.basename(filePath) === "config.json") {
|
|
39067
39353
|
let parsedJson;
|
|
39068
39354
|
try {
|
|
39069
39355
|
parsedJson = JSON.parse(raw);
|
|
@@ -39090,11 +39376,11 @@ function validateFile(filePath, raw, projectRoot) {
|
|
|
39090
39376
|
);
|
|
39091
39377
|
return result;
|
|
39092
39378
|
}
|
|
39093
|
-
if (
|
|
39379
|
+
if (path68.basename(filePath) === "testability.md") {
|
|
39094
39380
|
applyZodValidation(testabilityFrontmatterSchema, frontmatter, result);
|
|
39095
39381
|
return result;
|
|
39096
39382
|
}
|
|
39097
|
-
if (
|
|
39383
|
+
if (path68.basename(filePath) === "context.yml") {
|
|
39098
39384
|
applyZodValidation(contextFrontmatterSchema, frontmatter, result);
|
|
39099
39385
|
const warnings = [];
|
|
39100
39386
|
const hasNotes = Boolean(
|
|
@@ -39156,7 +39442,7 @@ async function buildRepairLookups(projectRoot) {
|
|
|
39156
39442
|
}
|
|
39157
39443
|
}
|
|
39158
39444
|
const runScenario = /* @__PURE__ */ new Map();
|
|
39159
|
-
const runsDir2 =
|
|
39445
|
+
const runsDir2 = path68.join(projectRoot, "iamu", ".runs");
|
|
39160
39446
|
let runIds = [];
|
|
39161
39447
|
try {
|
|
39162
39448
|
runIds = await fs68.readdir(runsDir2);
|
|
@@ -39166,7 +39452,7 @@ async function buildRepairLookups(projectRoot) {
|
|
|
39166
39452
|
try {
|
|
39167
39453
|
const manifest = JSON.parse(
|
|
39168
39454
|
await fs68.readFile(
|
|
39169
|
-
|
|
39455
|
+
path68.join(runsDir2, runId, "walkthrough.json"),
|
|
39170
39456
|
"utf8"
|
|
39171
39457
|
)
|
|
39172
39458
|
);
|
|
@@ -39197,7 +39483,7 @@ async function fixFindings(projectRoot, results, dryRun) {
|
|
|
39197
39483
|
parsed.frontmatter,
|
|
39198
39484
|
{
|
|
39199
39485
|
...lookups,
|
|
39200
|
-
fileSlug:
|
|
39486
|
+
fileSlug: path68.basename(result.path, ".md"),
|
|
39201
39487
|
newId: randomUUID12,
|
|
39202
39488
|
body: parsed.body
|
|
39203
39489
|
}
|
|
@@ -39239,7 +39525,7 @@ async function validate(filePath, opts) {
|
|
|
39239
39525
|
if (process.exitCode === 1) return;
|
|
39240
39526
|
const results = [];
|
|
39241
39527
|
if (filePath) {
|
|
39242
|
-
const abs =
|
|
39528
|
+
const abs = path68.isAbsolute(filePath) ? filePath : path68.resolve(process.cwd(), filePath);
|
|
39243
39529
|
try {
|
|
39244
39530
|
const raw = await fs68.readFile(abs, "utf8");
|
|
39245
39531
|
results.push(validateFile(abs, raw, projectRoot));
|
|
@@ -39255,7 +39541,7 @@ async function validate(filePath, opts) {
|
|
|
39255
39541
|
return;
|
|
39256
39542
|
}
|
|
39257
39543
|
} else if (opts.run) {
|
|
39258
|
-
const findingsDir =
|
|
39544
|
+
const findingsDir = path68.join(projectRoot, "iamu", ENTITY_DIRS.finding);
|
|
39259
39545
|
let names2 = [];
|
|
39260
39546
|
try {
|
|
39261
39547
|
names2 = await fs68.readdir(findingsDir);
|
|
@@ -39263,7 +39549,7 @@ async function validate(filePath, opts) {
|
|
|
39263
39549
|
}
|
|
39264
39550
|
for (const name of names2) {
|
|
39265
39551
|
if (!name.endsWith(".md")) continue;
|
|
39266
|
-
const abs =
|
|
39552
|
+
const abs = path68.join(findingsDir, name);
|
|
39267
39553
|
try {
|
|
39268
39554
|
const raw = await fs68.readFile(abs, "utf8");
|
|
39269
39555
|
const { frontmatter } = parseFrontmatter(raw);
|
|
@@ -39272,7 +39558,7 @@ async function validate(filePath, opts) {
|
|
|
39272
39558
|
}
|
|
39273
39559
|
} catch {
|
|
39274
39560
|
results.push({
|
|
39275
|
-
path:
|
|
39561
|
+
path: path68.relative(projectRoot, abs),
|
|
39276
39562
|
valid: false,
|
|
39277
39563
|
errors: ["Cannot read file or parse frontmatter"]
|
|
39278
39564
|
});
|
|
@@ -39305,7 +39591,7 @@ async function validate(filePath, opts) {
|
|
|
39305
39591
|
} else {
|
|
39306
39592
|
const typesToCheck = typeFilter ? ENTITY_TYPES.filter((t) => typeFilter.has(t)) : ENTITY_TYPES;
|
|
39307
39593
|
for (const type2 of typesToCheck) {
|
|
39308
|
-
const iamuTypeDir =
|
|
39594
|
+
const iamuTypeDir = path68.join(projectRoot, "iamu", ENTITY_DIRS[type2]);
|
|
39309
39595
|
let names2;
|
|
39310
39596
|
try {
|
|
39311
39597
|
names2 = await fs68.readdir(iamuTypeDir);
|
|
@@ -39314,13 +39600,13 @@ async function validate(filePath, opts) {
|
|
|
39314
39600
|
}
|
|
39315
39601
|
for (const name of names2) {
|
|
39316
39602
|
if (!name.endsWith(".md")) continue;
|
|
39317
|
-
const abs =
|
|
39603
|
+
const abs = path68.join(iamuTypeDir, name);
|
|
39318
39604
|
try {
|
|
39319
39605
|
const raw = await fs68.readFile(abs, "utf8");
|
|
39320
39606
|
results.push(validateFile(abs, raw, projectRoot));
|
|
39321
39607
|
} catch {
|
|
39322
39608
|
results.push({
|
|
39323
|
-
path:
|
|
39609
|
+
path: path68.relative(projectRoot, abs),
|
|
39324
39610
|
valid: false,
|
|
39325
39611
|
errors: ["Cannot read file"]
|
|
39326
39612
|
});
|
|
@@ -39328,7 +39614,7 @@ async function validate(filePath, opts) {
|
|
|
39328
39614
|
}
|
|
39329
39615
|
}
|
|
39330
39616
|
for (const extraFile of ["context.yml", "testability.md", "config.json"]) {
|
|
39331
|
-
const extraPath =
|
|
39617
|
+
const extraPath = path68.join(projectRoot, "iamu", extraFile);
|
|
39332
39618
|
try {
|
|
39333
39619
|
const raw = await fs68.readFile(extraPath, "utf8");
|
|
39334
39620
|
results.push(validateFile(extraPath, raw, projectRoot));
|
|
@@ -39354,19 +39640,19 @@ async function validate(filePath, opts) {
|
|
|
39354
39640
|
valid,
|
|
39355
39641
|
errors: results.filter((r) => !r.valid).flatMap(
|
|
39356
39642
|
(r) => r.errors.map((e) => ({
|
|
39357
|
-
path:
|
|
39643
|
+
path: path68.relative(projectRoot, r.path),
|
|
39358
39644
|
message: e
|
|
39359
39645
|
}))
|
|
39360
39646
|
),
|
|
39361
39647
|
warnings: results.filter((r) => r.warnings && r.warnings.length > 0).flatMap(
|
|
39362
39648
|
(r) => (r.warnings ?? []).map((w) => ({
|
|
39363
|
-
path:
|
|
39649
|
+
path: path68.relative(projectRoot, r.path),
|
|
39364
39650
|
message: w
|
|
39365
39651
|
}))
|
|
39366
39652
|
),
|
|
39367
39653
|
...opts.fix ? {
|
|
39368
39654
|
repaired: repaired.map((r) => ({
|
|
39369
|
-
path:
|
|
39655
|
+
path: path68.relative(projectRoot, r.path),
|
|
39370
39656
|
changes: r.repairs,
|
|
39371
39657
|
valid: r.valid
|
|
39372
39658
|
}))
|
|
@@ -39388,7 +39674,7 @@ async function validate(filePath, opts) {
|
|
|
39388
39674
|
}
|
|
39389
39675
|
let hasErrors = false;
|
|
39390
39676
|
for (const r of results) {
|
|
39391
|
-
const relPath =
|
|
39677
|
+
const relPath = path68.relative(projectRoot, r.path);
|
|
39392
39678
|
if (r.valid) {
|
|
39393
39679
|
if (r.warnings && r.warnings.length > 0) {
|
|
39394
39680
|
console.log(` ${symbols.warning} ` + chalk38.yellow(relPath));
|
|
@@ -39583,7 +39869,7 @@ __export(new_entity_exports, {
|
|
|
39583
39869
|
});
|
|
39584
39870
|
import { randomUUID as randomUUID13 } from "crypto";
|
|
39585
39871
|
import { readFile as readFile8 } from "fs/promises";
|
|
39586
|
-
import
|
|
39872
|
+
import path69 from "path";
|
|
39587
39873
|
import chalk40 from "chalk";
|
|
39588
39874
|
async function resolveBodyInput(opts, command, flag = "--body") {
|
|
39589
39875
|
if (opts.bodyFile !== void 0) {
|
|
@@ -39596,7 +39882,7 @@ async function resolveBodyInput(opts, command, flag = "--body") {
|
|
|
39596
39882
|
return null;
|
|
39597
39883
|
}
|
|
39598
39884
|
try {
|
|
39599
|
-
const body = opts.bodyFile === "-" ? await readStdinText() : await readFile8(
|
|
39885
|
+
const body = opts.bodyFile === "-" ? await readStdinText() : await readFile8(path69.resolve(process.cwd(), opts.bodyFile), "utf8");
|
|
39600
39886
|
return { body };
|
|
39601
39887
|
} catch (err) {
|
|
39602
39888
|
commandError(
|
|
@@ -39846,7 +40132,7 @@ __export(set_entity_exports, {
|
|
|
39846
40132
|
setEntity: () => setEntity
|
|
39847
40133
|
});
|
|
39848
40134
|
import { promises as fs69 } from "fs";
|
|
39849
|
-
import
|
|
40135
|
+
import path70 from "path";
|
|
39850
40136
|
import chalk41 from "chalk";
|
|
39851
40137
|
import { ZodError as ZodError2 } from "zod";
|
|
39852
40138
|
function normalizeAuthStrategy(raw) {
|
|
@@ -40037,7 +40323,7 @@ async function setEntity(type2, slug, opts) {
|
|
|
40037
40323
|
}
|
|
40038
40324
|
raw = Buffer.concat(chunks).toString("utf8");
|
|
40039
40325
|
} else {
|
|
40040
|
-
raw = await fs69.readFile(
|
|
40326
|
+
raw = await fs69.readFile(path70.resolve(projectRoot, opts.file), "utf8");
|
|
40041
40327
|
}
|
|
40042
40328
|
} catch (err) {
|
|
40043
40329
|
commandError(
|
|
@@ -40623,7 +40909,7 @@ __export(edit_entity_exports, {
|
|
|
40623
40909
|
});
|
|
40624
40910
|
import { spawn as spawn2 } from "child_process";
|
|
40625
40911
|
import { promises as fs70 } from "fs";
|
|
40626
|
-
import
|
|
40912
|
+
import path71 from "path";
|
|
40627
40913
|
import chalk42 from "chalk";
|
|
40628
40914
|
async function editEntity(type2, slug, opts = {}) {
|
|
40629
40915
|
const ctx = await resolveProject(opts);
|
|
@@ -40647,7 +40933,7 @@ async function editEntity(type2, slug, opts = {}) {
|
|
|
40647
40933
|
}
|
|
40648
40934
|
const effectiveSlug = isSpecial ? type2 : slug;
|
|
40649
40935
|
const resolvedPath = isSpecial ? type2 === "testability" ? testabilityPath(projectRoot) : contextPath(projectRoot) : await resolveEntityPath(projectRoot, type2, effectiveSlug);
|
|
40650
|
-
const filePath = resolvedPath ??
|
|
40936
|
+
const filePath = resolvedPath ?? path71.join(
|
|
40651
40937
|
projectRoot,
|
|
40652
40938
|
"iamu",
|
|
40653
40939
|
ENTITY_DIRS[type2],
|
|
@@ -40658,7 +40944,7 @@ async function editEntity(type2, slug, opts = {}) {
|
|
|
40658
40944
|
await fs70.access(filePath);
|
|
40659
40945
|
} catch {
|
|
40660
40946
|
commandError(
|
|
40661
|
-
`${type2}${isSpecial ? "" : ` "${effectiveSlug}"`} not found at ${
|
|
40947
|
+
`${type2}${isSpecial ? "" : ` "${effectiveSlug}"`} not found at ${path71.relative(projectRoot, filePath)}.`,
|
|
40662
40948
|
{
|
|
40663
40949
|
...opts,
|
|
40664
40950
|
code: "entity_not_found",
|
|
@@ -40669,7 +40955,7 @@ async function editEntity(type2, slug, opts = {}) {
|
|
|
40669
40955
|
}
|
|
40670
40956
|
if (!process.stdin.isTTY || process.env.CI) {
|
|
40671
40957
|
commandError(
|
|
40672
|
-
`Cannot open editor in a non-interactive environment. Edit the file directly: ${
|
|
40958
|
+
`Cannot open editor in a non-interactive environment. Edit the file directly: ${path71.relative(projectRoot, filePath)}`,
|
|
40673
40959
|
{
|
|
40674
40960
|
...opts,
|
|
40675
40961
|
code: "non_interactive_environment",
|
|
@@ -40745,7 +41031,7 @@ async function editEntity(type2, slug, opts = {}) {
|
|
|
40745
41031
|
console.log();
|
|
40746
41032
|
console.log(
|
|
40747
41033
|
chalk42.dim(
|
|
40748
|
-
` Fix the file and run: iamu validate ${
|
|
41034
|
+
` Fix the file and run: iamu validate ${path71.relative(projectRoot, filePath)}`
|
|
40749
41035
|
)
|
|
40750
41036
|
);
|
|
40751
41037
|
console.log();
|
|
@@ -40775,7 +41061,7 @@ async function editEntity(type2, slug, opts = {}) {
|
|
|
40775
41061
|
{
|
|
40776
41062
|
...opts,
|
|
40777
41063
|
code: "validation_error",
|
|
40778
|
-
fix: `iamu validate ${
|
|
41064
|
+
fix: `iamu validate ${path71.relative(projectRoot, filePath)}`
|
|
40779
41065
|
}
|
|
40780
41066
|
);
|
|
40781
41067
|
}
|
|
@@ -40944,7 +41230,7 @@ async function crawlFrom(page, opts, origin, takeBlocked) {
|
|
|
40944
41230
|
const byKey = /* @__PURE__ */ new Map();
|
|
40945
41231
|
const byPlaceView = /* @__PURE__ */ new Map();
|
|
40946
41232
|
const signatureOf = (s) => exploreSignature(s.headings, s.title);
|
|
40947
|
-
function addSurface(state, place, view,
|
|
41233
|
+
function addSurface(state, place, view, path78, depth) {
|
|
40948
41234
|
const key = exploreSurfaceKey(place, view);
|
|
40949
41235
|
const surface = {
|
|
40950
41236
|
key,
|
|
@@ -40956,7 +41242,7 @@ async function crawlFrom(page, opts, origin, takeBlocked) {
|
|
|
40956
41242
|
headings: state.headings,
|
|
40957
41243
|
fields: state.fields,
|
|
40958
41244
|
depth,
|
|
40959
|
-
path:
|
|
41245
|
+
path: path78,
|
|
40960
41246
|
actions: []
|
|
40961
41247
|
};
|
|
40962
41248
|
surfaces.push(surface);
|
|
@@ -40974,9 +41260,9 @@ ${surface.signature}`, key);
|
|
|
40974
41260
|
});
|
|
40975
41261
|
await settle(page, settleMs);
|
|
40976
41262
|
}
|
|
40977
|
-
async function replay(
|
|
40978
|
-
if (!("goto" in (
|
|
40979
|
-
for (const step of
|
|
41263
|
+
async function replay(path78) {
|
|
41264
|
+
if (!("goto" in (path78[0] ?? {}))) await goto(opts.startUrl);
|
|
41265
|
+
for (const step of path78) {
|
|
40980
41266
|
if ("goto" in step) {
|
|
40981
41267
|
await goto(step.goto);
|
|
40982
41268
|
} else {
|
|
@@ -41103,8 +41389,8 @@ ${sig}`, key);
|
|
|
41103
41389
|
if (stats.surfaces >= budget.max_surfaces) {
|
|
41104
41390
|
stats.truncated = true;
|
|
41105
41391
|
} else {
|
|
41106
|
-
const
|
|
41107
|
-
addSurface(after, place, view,
|
|
41392
|
+
const path78 = !sameDoc && view === null ? [{ goto: after.url }] : [...surface.path, ...via, click];
|
|
41393
|
+
addSurface(after, place, view, path78, surface.depth + 1);
|
|
41108
41394
|
}
|
|
41109
41395
|
}
|
|
41110
41396
|
}
|
|
@@ -41374,7 +41660,7 @@ __export(explore_exports, {
|
|
|
41374
41660
|
exploreList: () => exploreList,
|
|
41375
41661
|
exploreRun: () => exploreRun
|
|
41376
41662
|
});
|
|
41377
|
-
import
|
|
41663
|
+
import path72 from "path";
|
|
41378
41664
|
import chalk43 from "chalk";
|
|
41379
41665
|
function fail2(message, json2) {
|
|
41380
41666
|
if (json2) console.error(JSON.stringify({ error: message }));
|
|
@@ -41514,7 +41800,7 @@ async function exploreRun(url, opts) {
|
|
|
41514
41800
|
surfaces: result.surfaces
|
|
41515
41801
|
};
|
|
41516
41802
|
const file = await writeExploreMap(ctx.projectRoot, map2);
|
|
41517
|
-
const rel =
|
|
41803
|
+
const rel = path72.relative(process.cwd(), file);
|
|
41518
41804
|
if (opts.json) {
|
|
41519
41805
|
console.log(JSON.stringify({ id: map2.id, path: rel, stats: map2.stats }));
|
|
41520
41806
|
return;
|
|
@@ -42710,7 +42996,7 @@ __export(new_finding_exports, {
|
|
|
42710
42996
|
});
|
|
42711
42997
|
import { randomUUID as randomUUID14 } from "crypto";
|
|
42712
42998
|
import { access, readFile as readFile9 } from "fs/promises";
|
|
42713
|
-
import
|
|
42999
|
+
import path73 from "path";
|
|
42714
43000
|
import chalk46 from "chalk";
|
|
42715
43001
|
async function newFinding(opts) {
|
|
42716
43002
|
const ctx = await resolveProject(opts);
|
|
@@ -42798,7 +43084,7 @@ async function newFinding(opts) {
|
|
|
42798
43084
|
}
|
|
42799
43085
|
try {
|
|
42800
43086
|
await access(
|
|
42801
|
-
|
|
43087
|
+
path73.join(runDirPath, "steps", `${String(n).padStart(3, "0")}.json`)
|
|
42802
43088
|
);
|
|
42803
43089
|
} catch {
|
|
42804
43090
|
commandError(
|
|
@@ -42817,7 +43103,7 @@ async function newFinding(opts) {
|
|
|
42817
43103
|
if (!scenarioSlug) {
|
|
42818
43104
|
try {
|
|
42819
43105
|
const manifestRaw = await readFile9(
|
|
42820
|
-
|
|
43106
|
+
path73.join(runDirPath, "walkthrough.json"),
|
|
42821
43107
|
"utf8"
|
|
42822
43108
|
);
|
|
42823
43109
|
const manifest = JSON.parse(manifestRaw);
|
|
@@ -42964,7 +43250,7 @@ __export(summary_exports, {
|
|
|
42964
43250
|
summaryCommand: () => summaryCommand
|
|
42965
43251
|
});
|
|
42966
43252
|
import { promises as fs71 } from "fs";
|
|
42967
|
-
import
|
|
43253
|
+
import path74 from "path";
|
|
42968
43254
|
import chalk47 from "chalk";
|
|
42969
43255
|
function endedFrom(value) {
|
|
42970
43256
|
if (!value || typeof value !== "object") return void 0;
|
|
@@ -42995,7 +43281,7 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
42995
43281
|
let openQuestions = [];
|
|
42996
43282
|
let setupNotes = [];
|
|
42997
43283
|
let model = opts.model?.trim() || void 0;
|
|
42998
|
-
const summaryPath =
|
|
43284
|
+
const summaryPath = path74.join(runDirPath, "summary.json");
|
|
42999
43285
|
let blocked;
|
|
43000
43286
|
let ended;
|
|
43001
43287
|
try {
|
|
@@ -43015,7 +43301,7 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
43015
43301
|
if (opts.file === "-") {
|
|
43016
43302
|
raw = await readStdinText();
|
|
43017
43303
|
} else {
|
|
43018
|
-
raw = await fs71.readFile(
|
|
43304
|
+
raw = await fs71.readFile(path74.resolve(projectRoot, opts.file), "utf8");
|
|
43019
43305
|
}
|
|
43020
43306
|
const parsed = JSON.parse(raw);
|
|
43021
43307
|
if (Array.isArray(parsed.what_worked)) {
|
|
@@ -43092,7 +43378,7 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
43092
43378
|
JSON.stringify(
|
|
43093
43379
|
{
|
|
43094
43380
|
run_id: runId,
|
|
43095
|
-
path:
|
|
43381
|
+
path: path74.relative(projectRoot, summaryPath),
|
|
43096
43382
|
summary: data
|
|
43097
43383
|
},
|
|
43098
43384
|
null,
|
|
@@ -43154,7 +43440,7 @@ __export(advise_exports, {
|
|
|
43154
43440
|
readAdvisePrompt: () => readAdvisePrompt
|
|
43155
43441
|
});
|
|
43156
43442
|
import { existsSync as existsSync23, promises as fs72 } from "fs";
|
|
43157
|
-
import
|
|
43443
|
+
import path75 from "path";
|
|
43158
43444
|
import chalk48 from "chalk";
|
|
43159
43445
|
function readAdvisePrompt(projectRoot) {
|
|
43160
43446
|
return readInstalledPrompt(projectRoot, "advise.md");
|
|
@@ -43212,7 +43498,7 @@ async function advise(runIdArg, opts = {}) {
|
|
|
43212
43498
|
findings2 = input.findings.length;
|
|
43213
43499
|
needed = findings2 >= 2;
|
|
43214
43500
|
if (!needed) reason = "fewer than two open findings in the project";
|
|
43215
|
-
promptPath = opts.promptFile ??
|
|
43501
|
+
promptPath = opts.promptFile ?? path75.join(analysisDir(projectRoot), "advise_prompt.md");
|
|
43216
43502
|
} else {
|
|
43217
43503
|
const runId = resolveRunId(projectRoot, runIdArg ?? opts.run);
|
|
43218
43504
|
if (!requireRunId(runId, opts, "iamu advise <run-id>")) return;
|
|
@@ -43223,12 +43509,12 @@ async function advise(runIdArg, opts = {}) {
|
|
|
43223
43509
|
input = await buildAdviseInput(projectRoot, runId, manifest.scenario_slug);
|
|
43224
43510
|
refsDir = runDirPath;
|
|
43225
43511
|
findings2 = input.findings.length;
|
|
43226
|
-
const advised = existsSync23(
|
|
43512
|
+
const advised = existsSync23(path75.join(runDirPath, ADVICE_FILE));
|
|
43227
43513
|
needed = !manifest.blocked && findings2 > 0 && !advised;
|
|
43228
43514
|
if (manifest.blocked) reason = "the run was blocked";
|
|
43229
43515
|
else if (advised) reason = "the run is already advised";
|
|
43230
43516
|
else if (findings2 === 0) reason = "no open findings to advise";
|
|
43231
|
-
promptPath = opts.promptFile ??
|
|
43517
|
+
promptPath = opts.promptFile ?? path75.join(runDirPath, "advise_prompt.md");
|
|
43232
43518
|
}
|
|
43233
43519
|
if (needed) input = await withCodeIndex(projectRoot, input);
|
|
43234
43520
|
const { bundle, refs: refs2 } = promptBundle(input);
|
|
@@ -43238,11 +43524,11 @@ async function advise(runIdArg, opts = {}) {
|
|
|
43238
43524
|
promptSizeWarning(prompt)
|
|
43239
43525
|
].filter((w) => w !== null) : [];
|
|
43240
43526
|
if (prompt) {
|
|
43241
|
-
await fs72.mkdir(
|
|
43527
|
+
await fs72.mkdir(path75.dirname(promptPath), { recursive: true });
|
|
43242
43528
|
await fs72.writeFile(promptPath, prompt, "utf8");
|
|
43243
43529
|
await fs72.mkdir(refsDir, { recursive: true });
|
|
43244
43530
|
await fs72.writeFile(
|
|
43245
|
-
|
|
43531
|
+
path75.join(refsDir, ADVISE_REFS_FILE),
|
|
43246
43532
|
`${JSON.stringify(refs2, null, 2)}
|
|
43247
43533
|
`,
|
|
43248
43534
|
"utf8"
|
|
@@ -43257,8 +43543,8 @@ async function advise(runIdArg, opts = {}) {
|
|
|
43257
43543
|
...reason ? { reason } : {},
|
|
43258
43544
|
mode: opts.project ? "project" : "run",
|
|
43259
43545
|
findings: findings2,
|
|
43260
|
-
prompt_file: prompt ?
|
|
43261
|
-
prompt_file_abs: prompt ?
|
|
43546
|
+
prompt_file: prompt ? path75.relative(projectRoot, promptPath).split(path75.sep).join("/") : null,
|
|
43547
|
+
prompt_file_abs: prompt ? path75.resolve(promptPath) : null,
|
|
43262
43548
|
prompt_chars: prompt?.length ?? 0,
|
|
43263
43549
|
warnings,
|
|
43264
43550
|
advise_prompt: prompt
|
|
@@ -43334,7 +43620,7 @@ async function adviseRecord(runIdArg, opts) {
|
|
|
43334
43620
|
const { projectRoot } = ctx;
|
|
43335
43621
|
if (opts.project) {
|
|
43336
43622
|
const input2 = await readAnswerInput(opts, "advice", {
|
|
43337
|
-
defaultPath:
|
|
43623
|
+
defaultPath: path75.join(analysisDir(projectRoot), "answer.json"),
|
|
43338
43624
|
code: "missing_advice_input",
|
|
43339
43625
|
fix: "iamu advise-record --project --data '<json>'"
|
|
43340
43626
|
});
|
|
@@ -43366,7 +43652,7 @@ async function adviseRecord(runIdArg, opts) {
|
|
|
43366
43652
|
if (!manifest) return;
|
|
43367
43653
|
await logTiming(runDirPath, "advise-record", "start");
|
|
43368
43654
|
const input = await readAnswerInput(opts, "advice", {
|
|
43369
|
-
defaultPath:
|
|
43655
|
+
defaultPath: path75.join(runDir(projectRoot, runId), "advise_answer.json"),
|
|
43370
43656
|
code: "missing_advice_input",
|
|
43371
43657
|
fix: `iamu advise-record ${runId} --data '<json>'`
|
|
43372
43658
|
});
|
|
@@ -43416,7 +43702,7 @@ async function directionsList(opts) {
|
|
|
43416
43702
|
problem: d.texts.problem,
|
|
43417
43703
|
change: d.texts.change,
|
|
43418
43704
|
leading_practice: d.texts.leading_practice,
|
|
43419
|
-
path:
|
|
43705
|
+
path: path75.relative(ctx.projectRoot, d.path)
|
|
43420
43706
|
})),
|
|
43421
43707
|
null,
|
|
43422
43708
|
2
|
|
@@ -43565,7 +43851,7 @@ __export(designer_exports, {
|
|
|
43565
43851
|
designerRecord: () => designerRecord
|
|
43566
43852
|
});
|
|
43567
43853
|
import { promises as fs73 } from "fs";
|
|
43568
|
-
import
|
|
43854
|
+
import path76 from "path";
|
|
43569
43855
|
import chalk49 from "chalk";
|
|
43570
43856
|
import { ZodError as ZodError3 } from "zod";
|
|
43571
43857
|
function describeError(err) {
|
|
@@ -43585,7 +43871,7 @@ async function designer(runId, opts = {}) {
|
|
|
43585
43871
|
const { projectRoot, runDirPath } = ctx;
|
|
43586
43872
|
if (!await completedManifest2(runDirPath, runId, opts)) return;
|
|
43587
43873
|
const input = await buildRunDesignerInput(projectRoot, runId);
|
|
43588
|
-
const promptPath =
|
|
43874
|
+
const promptPath = path76.join(runDirPath, DESIGNER_PROMPT_FILE);
|
|
43589
43875
|
const prompt = input.needed ? composeDesignPrompt(
|
|
43590
43876
|
projectRoot,
|
|
43591
43877
|
"designer.md",
|
|
@@ -43594,7 +43880,7 @@ async function designer(runId, opts = {}) {
|
|
|
43594
43880
|
if (prompt) {
|
|
43595
43881
|
await fs73.writeFile(promptPath, prompt, "utf8");
|
|
43596
43882
|
await fs73.writeFile(
|
|
43597
|
-
|
|
43883
|
+
path76.join(runDirPath, DESIGNER_REFS_FILE),
|
|
43598
43884
|
`${JSON.stringify(input.refs, null, 2)}
|
|
43599
43885
|
`,
|
|
43600
43886
|
"utf8"
|
|
@@ -43605,7 +43891,7 @@ async function designer(runId, opts = {}) {
|
|
|
43605
43891
|
installedPromptWarning(projectRoot, "designer.md"),
|
|
43606
43892
|
...input.warnings
|
|
43607
43893
|
].filter((w) => w !== null) : [];
|
|
43608
|
-
const answer =
|
|
43894
|
+
const answer = path76.join(runDirPath, DESIGNER_ANSWER_FILE);
|
|
43609
43895
|
const result = {
|
|
43610
43896
|
needed: input.needed,
|
|
43611
43897
|
reason: input.reason,
|
|
@@ -43621,11 +43907,11 @@ async function designer(runId, opts = {}) {
|
|
|
43621
43907
|
title: f.title
|
|
43622
43908
|
})),
|
|
43623
43909
|
design_files: (input.bundle.design?.files ?? []).map((f) => f.entry),
|
|
43624
|
-
prompt_file: prompt ?
|
|
43910
|
+
prompt_file: prompt ? path76.relative(projectRoot, promptPath).split(path76.sep).join("/") : null,
|
|
43625
43911
|
prompt_file_abs: prompt ? promptPath : null,
|
|
43626
43912
|
prompt_chars: prompt?.length ?? 0,
|
|
43627
43913
|
answer_file_abs: prompt ? answer : null,
|
|
43628
|
-
next: prompt ? `Give a fresh agent ${
|
|
43914
|
+
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
43915
|
hint: input.judged_against === "general-craft" ? await designDocsHint(projectRoot) : null,
|
|
43630
43916
|
// No design doc recorded or found, and the template not declined:
|
|
43631
43917
|
// the run summary offers to start one (iamu-design).
|
|
@@ -43671,6 +43957,9 @@ function printDesignerRecord(res) {
|
|
|
43671
43957
|
` ${chalk49.cyan("\u25C6")} Proposed doc edit ${p.outcome}: ${p.slug} (iamu design proposals)`
|
|
43672
43958
|
);
|
|
43673
43959
|
}
|
|
43960
|
+
for (const slug of res.verified ?? []) {
|
|
43961
|
+
console.log(` ${chalk49.green("\u2713")} Fix verified: ${slug}`);
|
|
43962
|
+
}
|
|
43674
43963
|
for (const r of res.rejected) {
|
|
43675
43964
|
console.log(` ${chalk49.yellow("!")} Dropped ${r.id}: ${r.reason}`);
|
|
43676
43965
|
}
|
|
@@ -43756,7 +44045,7 @@ async function designerRecordLocked(projectRoot, runDirPath, runId, manifest, op
|
|
|
43756
44045
|
return;
|
|
43757
44046
|
}
|
|
43758
44047
|
const input = await readAnswerInput(opts, "designer's review", {
|
|
43759
|
-
defaultPath:
|
|
44048
|
+
defaultPath: path76.join(runDirPath, DESIGNER_ANSWER_FILE),
|
|
43760
44049
|
code: "missing_designer_answer",
|
|
43761
44050
|
fix: `iamu designer-record ${runId} --data '<json>'`
|
|
43762
44051
|
});
|
|
@@ -43888,7 +44177,7 @@ __export(post_walk_exports, {
|
|
|
43888
44177
|
postWalk: () => postWalk
|
|
43889
44178
|
});
|
|
43890
44179
|
import { existsSync as existsSync24, promises as fs74 } from "fs";
|
|
43891
|
-
import
|
|
44180
|
+
import path77 from "path";
|
|
43892
44181
|
import chalk50 from "chalk";
|
|
43893
44182
|
async function captureStageJson(fn) {
|
|
43894
44183
|
const captured = [];
|
|
@@ -43931,7 +44220,7 @@ async function postWalk(runIdArg, opts = {}) {
|
|
|
43931
44220
|
}
|
|
43932
44221
|
const runDirPath = requireRun(projectRoot, runId, opts);
|
|
43933
44222
|
if (!runDirPath) return;
|
|
43934
|
-
const manifestPath =
|
|
44223
|
+
const manifestPath = path77.join(runDirPath, "walkthrough.json");
|
|
43935
44224
|
let manifest;
|
|
43936
44225
|
try {
|
|
43937
44226
|
manifest = JSON.parse(
|
|
@@ -43972,7 +44261,7 @@ async function postWalk(runIdArg, opts = {}) {
|
|
|
43972
44261
|
return;
|
|
43973
44262
|
}
|
|
43974
44263
|
const recordedNow = [];
|
|
43975
|
-
const matchesAnswerPath =
|
|
44264
|
+
const matchesAnswerPath = path77.join(runDirPath, MATCH_ANSWER_FILE);
|
|
43976
44265
|
if (existsSync24(matchesAnswerPath)) {
|
|
43977
44266
|
const res = await captureStageJson(
|
|
43978
44267
|
() => matchRecord(runId, { file: matchesAnswerPath, json: true })
|
|
@@ -43983,7 +44272,7 @@ async function postWalk(runIdArg, opts = {}) {
|
|
|
43983
44272
|
});
|
|
43984
44273
|
}
|
|
43985
44274
|
}
|
|
43986
|
-
const judgeAnswerPath =
|
|
44275
|
+
const judgeAnswerPath = path77.join(runDirPath, "judge_answer.json");
|
|
43987
44276
|
if (existsSync24(judgeAnswerPath)) {
|
|
43988
44277
|
const res = await captureStageJson(
|
|
43989
44278
|
() => evaluateRecord(runId, { file: judgeAnswerPath, json: true })
|
|
@@ -43994,8 +44283,8 @@ async function postWalk(runIdArg, opts = {}) {
|
|
|
43994
44283
|
});
|
|
43995
44284
|
}
|
|
43996
44285
|
}
|
|
43997
|
-
const adviseAnswerPath =
|
|
43998
|
-
if (!opts.critic && !existsSync24(
|
|
44286
|
+
const adviseAnswerPath = path77.join(runDirPath, "advise_answer.json");
|
|
44287
|
+
if (!opts.critic && !existsSync24(path77.join(runDirPath, ADVICE_FILE)) && existsSync24(adviseAnswerPath)) {
|
|
43999
44288
|
const res = await captureStageJson(
|
|
44000
44289
|
() => adviseRecord(runId, { file: adviseAnswerPath, json: true })
|
|
44001
44290
|
);
|
|
@@ -44005,7 +44294,7 @@ async function postWalk(runIdArg, opts = {}) {
|
|
|
44005
44294
|
});
|
|
44006
44295
|
}
|
|
44007
44296
|
}
|
|
44008
|
-
const designerAnswerPath =
|
|
44297
|
+
const designerAnswerPath = path77.join(runDirPath, DESIGNER_ANSWER_FILE);
|
|
44009
44298
|
let designerWarnings = [];
|
|
44010
44299
|
let designerInvalidChecked = [];
|
|
44011
44300
|
if (!existingDesignerFile(runDirPath)) {
|
|
@@ -44037,9 +44326,9 @@ async function postWalk(runIdArg, opts = {}) {
|
|
|
44037
44326
|
}
|
|
44038
44327
|
}
|
|
44039
44328
|
const matchInfo = await captureStageJson(() => match(runId, { json: true }));
|
|
44040
|
-
const evalRecorded = existsSync24(
|
|
44329
|
+
const evalRecorded = existsSync24(path77.join(runDirPath, "verdicts.json"));
|
|
44041
44330
|
const evalInfo = evalRecorded ? null : await captureStageJson(() => evaluate(runId, { json: true }));
|
|
44042
|
-
const adviseRecorded = existsSync24(
|
|
44331
|
+
const adviseRecorded = existsSync24(path77.join(runDirPath, ADVICE_FILE));
|
|
44043
44332
|
const awaitingJudge = !opts.critic && !adviseRecorded && !evalRecorded;
|
|
44044
44333
|
const adviseInfo = opts.critic || adviseRecorded || awaitingJudge ? null : await captureStageJson(() => advise(runId, { json: true }));
|
|
44045
44334
|
const designerRecorded = Boolean(existingDesignerFile(runDirPath));
|
|
@@ -44538,7 +44827,10 @@ function registerFindingsCommands(program) {
|
|
|
44538
44827
|
program.command("triage [findings...]").helpGroup("Findings:").description("Set findings' triage status").requiredOption(
|
|
44539
44828
|
"--status <status>",
|
|
44540
44829
|
"open, addressed, dismissed, deferred or wont_fix"
|
|
44541
|
-
).option(
|
|
44830
|
+
).option(
|
|
44831
|
+
"--note <text>",
|
|
44832
|
+
"Triage note, any status. For addressed: one line on what changed on screen, so the next run's matcher can confirm the fix"
|
|
44833
|
+
).option("--note-file <path>", "Read the note from a file (- reads stdin)").option(
|
|
44542
44834
|
"--reason <reason>",
|
|
44543
44835
|
"Dismiss reason: not_relevant (default), walker_error, out_of_scope or duplicate"
|
|
44544
44836
|
).option(
|
|
@@ -44555,7 +44847,7 @@ function registerFindingsCommands(program) {
|
|
|
44555
44847
|
"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
44848
|
).option("--no-guidance", "Dismiss without writing a tester guidance rule").option("--json", "Output as JSON").addHelpText(
|
|
44557
44849
|
"after",
|
|
44558
|
-
|
|
44850
|
+
'\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
44851
|
).action(
|
|
44560
44852
|
async (refs2, opts) => {
|
|
44561
44853
|
const { triage: triage2 } = await Promise.resolve().then(() => (init_triage(), triage_exports));
|
|
@@ -44654,9 +44946,9 @@ function registerProjectCommands(program) {
|
|
|
44654
44946
|
"after",
|
|
44655
44947
|
"\nExamples:\n $ iamu validate --all\n $ iamu validate --all --fix --dry-run\n $ iamu validate iamu/personas/maria.md"
|
|
44656
44948
|
).action(
|
|
44657
|
-
async (
|
|
44949
|
+
async (path78, opts) => {
|
|
44658
44950
|
const { validate: validate2 } = await Promise.resolve().then(() => (init_validate(), validate_exports));
|
|
44659
|
-
await validate2(
|
|
44951
|
+
await validate2(path78, opts);
|
|
44660
44952
|
}
|
|
44661
44953
|
);
|
|
44662
44954
|
program.command("update").helpGroup("Project:").description("Update and repair agent skills and prompts to latest version").option(
|