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