@iamu-dev/iamu 0.8.0-beta.5 → 0.8.0-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +518 -195
- package/dist/build-hash.txt +1 -1
- package/dist/commands/triage.d.ts +6 -1
- package/dist/index.js +518 -195
- package/dist/templates/skills/iamu-findings.md +2 -0
- package/dist/templates/skills/iamu-run.md +3 -3
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -6685,10 +6685,13 @@ var init_wire_compat = __esm({
|
|
|
6685
6685
|
});
|
|
6686
6686
|
|
|
6687
6687
|
// ../../packages/local-store/dist/sync/server-meta.js
|
|
6688
|
+
function cloudDistinguishesHumanEdits(capabilities) {
|
|
6689
|
+
return Boolean(capabilities?.includes(SYNC_TOKEN_CAPABILITY));
|
|
6690
|
+
}
|
|
6688
6691
|
function cloudSupportsGuidance(capabilities) {
|
|
6689
6692
|
return Boolean(capabilities?.includes(GUIDANCE_CAPABILITY) || capabilities?.includes(DESIGNER_FINDINGS_CAPABILITY));
|
|
6690
6693
|
}
|
|
6691
|
-
async function fetchServerCapabilities(apiUrl, apiKey, fetchFn, timeoutMs, onFailure) {
|
|
6694
|
+
async function fetchServerCapabilities(apiUrl, apiKey, fetchFn, timeoutMs, onFailure, onServerClock) {
|
|
6692
6695
|
const controller = new AbortController();
|
|
6693
6696
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
6694
6697
|
try {
|
|
@@ -6698,6 +6701,9 @@ async function fetchServerCapabilities(apiUrl, apiKey, fetchFn, timeoutMs, onFai
|
|
|
6698
6701
|
signal: controller.signal
|
|
6699
6702
|
};
|
|
6700
6703
|
const res = await fetchFn(`${apiUrl.replace(/\/+$/, "")}/api/meta`, init2);
|
|
6704
|
+
const offset = serverClockOffset(res);
|
|
6705
|
+
if (offset !== null)
|
|
6706
|
+
onServerClock?.(offset);
|
|
6701
6707
|
if (res.status === 404)
|
|
6702
6708
|
return [];
|
|
6703
6709
|
if (!res.ok) {
|
|
@@ -6719,6 +6725,16 @@ async function fetchServerCapabilities(apiUrl, apiKey, fetchFn, timeoutMs, onFai
|
|
|
6719
6725
|
clearTimeout(timer);
|
|
6720
6726
|
}
|
|
6721
6727
|
}
|
|
6728
|
+
function serverClockOffset(res) {
|
|
6729
|
+
const headers = res?.headers;
|
|
6730
|
+
if (!headers || typeof headers.get !== "function")
|
|
6731
|
+
return null;
|
|
6732
|
+
const raw = headers.get.call(headers, "date");
|
|
6733
|
+
if (typeof raw !== "string")
|
|
6734
|
+
return null;
|
|
6735
|
+
const at = Date.parse(raw);
|
|
6736
|
+
return Number.isNaN(at) ? null : at - Date.now();
|
|
6737
|
+
}
|
|
6722
6738
|
function shapeForServer(pending, capabilities) {
|
|
6723
6739
|
const knows = capabilities.includes(DESIGNER_FINDINGS_CAPABILITY);
|
|
6724
6740
|
const knowsProvenance = capabilities.includes(FINDING_PROVENANCE_CAPABILITY);
|
|
@@ -6740,7 +6756,7 @@ function shapeForServer(pending, capabilities) {
|
|
|
6740
6756
|
}
|
|
6741
6757
|
return { pending: out2, heldDesigner };
|
|
6742
6758
|
}
|
|
6743
|
-
var DESIGNER_FINDINGS_CAPABILITY, GUIDANCE_CAPABILITY, WALKTHROUGH_DELETE_CAPABILITY, FINDING_PROVENANCE_CAPABILITY, BUNDLE_FINDING_HASH_CAPABILITY, RUN_BLOCKED_CAPABILITY;
|
|
6759
|
+
var DESIGNER_FINDINGS_CAPABILITY, GUIDANCE_CAPABILITY, WALKTHROUGH_DELETE_CAPABILITY, FINDING_PROVENANCE_CAPABILITY, BUNDLE_FINDING_HASH_CAPABILITY, RUN_BLOCKED_CAPABILITY, SYNC_TOKEN_CAPABILITY;
|
|
6744
6760
|
var init_server_meta = __esm({
|
|
6745
6761
|
"../../packages/local-store/dist/sync/server-meta.js"() {
|
|
6746
6762
|
"use strict";
|
|
@@ -6752,6 +6768,7 @@ var init_server_meta = __esm({
|
|
|
6752
6768
|
FINDING_PROVENANCE_CAPABILITY = "finding-provenance";
|
|
6753
6769
|
BUNDLE_FINDING_HASH_CAPABILITY = "bundle-finding-hash";
|
|
6754
6770
|
RUN_BLOCKED_CAPABILITY = "run-blocked";
|
|
6771
|
+
SYNC_TOKEN_CAPABILITY = "sync-token";
|
|
6755
6772
|
}
|
|
6756
6773
|
});
|
|
6757
6774
|
|
|
@@ -10688,7 +10705,7 @@ function compareIso(a, b) {
|
|
|
10688
10705
|
return ta - tb;
|
|
10689
10706
|
return String(a ?? "").localeCompare(String(b ?? ""));
|
|
10690
10707
|
}
|
|
10691
|
-
async function designerFindingsOn(projectRoot, screens, before, targetUrl = null) {
|
|
10708
|
+
async function designerFindingsOn(projectRoot, screens, before, targetUrl = null, settled = /* @__PURE__ */ new Set()) {
|
|
10692
10709
|
const latest = /* @__PURE__ */ new Map();
|
|
10693
10710
|
const cache = /* @__PURE__ */ new Map();
|
|
10694
10711
|
const wanted = new Set([...screens].map((k) => normalizeScreenKey(k, targetUrl)));
|
|
@@ -10712,7 +10729,7 @@ async function designerFindingsOn(projectRoot, screens, before, targetUrl = null
|
|
|
10712
10729
|
}
|
|
10713
10730
|
const all = [...latest.values()].sort((a, b) => compareIso(a.created_at, b.created_at));
|
|
10714
10731
|
return {
|
|
10715
|
-
open: all.filter(unsettled).map((fm, n) => ({
|
|
10732
|
+
open: all.filter((fm) => unsettled(fm) && !settled.has(fm.id)).map((fm, n) => ({
|
|
10716
10733
|
ref: `P${n + 1}`,
|
|
10717
10734
|
id: fm.id,
|
|
10718
10735
|
title: fm.title ?? fm.slug,
|
|
@@ -11205,6 +11222,26 @@ async function readReviewEvidence(projectRoot) {
|
|
|
11205
11222
|
}
|
|
11206
11223
|
return out2;
|
|
11207
11224
|
}
|
|
11225
|
+
async function verifiedDesignerFixes(projectRoot, before) {
|
|
11226
|
+
const addressed = (await listDesignerFindings(projectRoot)).map((f) => f.frontmatter).filter((fm) => fm.triage_status === "addressed" && !fm.design_cause_of);
|
|
11227
|
+
const out2 = /* @__PURE__ */ new Set();
|
|
11228
|
+
if (addressed.length === 0)
|
|
11229
|
+
return out2;
|
|
11230
|
+
const cutoff = toMillis(before);
|
|
11231
|
+
const runs = (await readRunEvidence(projectRoot)).filter((r) => {
|
|
11232
|
+
if (cutoff === null)
|
|
11233
|
+
return true;
|
|
11234
|
+
const start2 = toMillis(r.started_at);
|
|
11235
|
+
return start2 !== null && start2 < cutoff;
|
|
11236
|
+
});
|
|
11237
|
+
const states = deriveFixStates(addressed, runs);
|
|
11238
|
+
for (const fm of addressed) {
|
|
11239
|
+
if (fm.fix_status === "verified_resolved" || states.get(fm.slug)?.fix_state === "verified") {
|
|
11240
|
+
out2.add(fm.id);
|
|
11241
|
+
}
|
|
11242
|
+
}
|
|
11243
|
+
return out2;
|
|
11244
|
+
}
|
|
11208
11245
|
async function computeFixStates(projectRoot) {
|
|
11209
11246
|
const { entities } = await listEntities(projectRoot, "finding");
|
|
11210
11247
|
const findings2 = entities.map((e) => e.frontmatter);
|
|
@@ -12266,6 +12303,7 @@ async function canonicalFindingFiles(projectRoot, scenarioSlug, canonicalId) {
|
|
|
12266
12303
|
return out2;
|
|
12267
12304
|
}
|
|
12268
12305
|
async function markAddressedResolved(files, note) {
|
|
12306
|
+
const failed = [];
|
|
12269
12307
|
const trimmedNote = note?.trim();
|
|
12270
12308
|
for (const f of files) {
|
|
12271
12309
|
if (f.triage_status !== "addressed")
|
|
@@ -12281,9 +12319,14 @@ async function markAddressedResolved(files, note) {
|
|
|
12281
12319
|
if (updated !== raw)
|
|
12282
12320
|
await atomicWriteFile(f.file, updated);
|
|
12283
12321
|
});
|
|
12284
|
-
} catch {
|
|
12322
|
+
} catch (err) {
|
|
12323
|
+
failed.push({
|
|
12324
|
+
file: f.file,
|
|
12325
|
+
error: err instanceof Error ? err.message : String(err)
|
|
12326
|
+
});
|
|
12285
12327
|
}
|
|
12286
12328
|
}
|
|
12329
|
+
return failed;
|
|
12287
12330
|
}
|
|
12288
12331
|
function originOf(url) {
|
|
12289
12332
|
if (!url)
|
|
@@ -12466,7 +12509,9 @@ async function updateFixStatus(projectRoot, slug, status2) {
|
|
|
12466
12509
|
await atomicWriteFile(filePath, updated);
|
|
12467
12510
|
}
|
|
12468
12511
|
});
|
|
12469
|
-
|
|
12512
|
+
return null;
|
|
12513
|
+
} catch (err) {
|
|
12514
|
+
return err instanceof Error ? err.message : String(err);
|
|
12470
12515
|
}
|
|
12471
12516
|
}
|
|
12472
12517
|
async function listRunsInternal(projectRoot) {
|
|
@@ -12916,6 +12961,7 @@ async function mirrorLocalWalkthrough(projectRoot, runId, deps) {
|
|
|
12916
12961
|
body: form,
|
|
12917
12962
|
signal: controller.signal
|
|
12918
12963
|
});
|
|
12964
|
+
const clockOffset = serverClockOffset(response);
|
|
12919
12965
|
if (response.ok) {
|
|
12920
12966
|
let body = null;
|
|
12921
12967
|
try {
|
|
@@ -12961,7 +13007,8 @@ async function mirrorLocalWalkthrough(projectRoot, runId, deps) {
|
|
|
12961
13007
|
runCloudId: parsed.data.cloud_walkthrough_id,
|
|
12962
13008
|
contentHash,
|
|
12963
13009
|
cloudOutcome: parsed.data.status,
|
|
12964
|
-
...recordedFindings.length > 0 ? { recordedFindings } : {}
|
|
13010
|
+
...recordedFindings.length > 0 ? { recordedFindings } : {},
|
|
13011
|
+
...clockOffset !== null ? { serverClockOffsetMs: clockOffset } : {}
|
|
12965
13012
|
};
|
|
12966
13013
|
}
|
|
12967
13014
|
if (response.status === 409) {
|
|
@@ -13608,6 +13655,7 @@ async function drainRetryQueue(projectRoot, deps) {
|
|
|
13608
13655
|
const deleteUrl = projectEndpoint(config.cloud_api_url, config.cloud_project_id, "delete");
|
|
13609
13656
|
const remaining = [];
|
|
13610
13657
|
const failures = [];
|
|
13658
|
+
const pushedRuns = [];
|
|
13611
13659
|
let drained = 0;
|
|
13612
13660
|
let dropped = 0;
|
|
13613
13661
|
let notAttempted = 0;
|
|
@@ -13719,6 +13767,8 @@ async function drainRetryQueue(projectRoot, deps) {
|
|
|
13719
13767
|
});
|
|
13720
13768
|
if (result.mirrored || result.skippedReason === "bundle_too_large") {
|
|
13721
13769
|
drained++;
|
|
13770
|
+
if (result.status === "pushed")
|
|
13771
|
+
pushedRuns.push(entry.entity_id);
|
|
13722
13772
|
} else if (result.skippedReason === "missing_run" || result.skippedReason === "unsupported") {
|
|
13723
13773
|
drained++;
|
|
13724
13774
|
} else if (result.skippedReason === "cloud_conflict") {
|
|
@@ -13810,7 +13860,8 @@ async function drainRetryQueue(projectRoot, deps) {
|
|
|
13810
13860
|
dropped,
|
|
13811
13861
|
remaining: after.length,
|
|
13812
13862
|
failures,
|
|
13813
|
-
warnings
|
|
13863
|
+
warnings,
|
|
13864
|
+
...pushedRuns.length > 0 ? { pushedRuns } : {}
|
|
13814
13865
|
};
|
|
13815
13866
|
}
|
|
13816
13867
|
function unreadableBody(text, isDelete) {
|
|
@@ -14234,6 +14285,18 @@ async function pushOneBatch(target, batch, byType, label, opts = {}) {
|
|
|
14234
14285
|
out2.errors.push(failure.message);
|
|
14235
14286
|
return out2;
|
|
14236
14287
|
}
|
|
14288
|
+
function cloudTokenOrigin(hash, humanTokens) {
|
|
14289
|
+
if (hash.startsWith("cloud-sync-"))
|
|
14290
|
+
return "projection";
|
|
14291
|
+
if (hash.startsWith("cloud-"))
|
|
14292
|
+
return humanTokens ? "person" : "unknown";
|
|
14293
|
+
if (hash.startsWith("cloud:"))
|
|
14294
|
+
return "unknown";
|
|
14295
|
+
return null;
|
|
14296
|
+
}
|
|
14297
|
+
function isCloudToken(hash) {
|
|
14298
|
+
return cloudTokenOrigin(hash, false) !== null;
|
|
14299
|
+
}
|
|
14237
14300
|
async function ownEarlierWrites(projectRoot, batch, conflicts) {
|
|
14238
14301
|
if (conflicts.length === 0)
|
|
14239
14302
|
return [];
|
|
@@ -14247,7 +14310,7 @@ async function ownEarlierWrites(projectRoot, batch, conflicts) {
|
|
|
14247
14310
|
const entity = byId.get(c.entity_id);
|
|
14248
14311
|
if (!entity || c.base_hash || bases[c.entity_id])
|
|
14249
14312
|
continue;
|
|
14250
|
-
if (!c.cloud_hash || c.cloud_hash
|
|
14313
|
+
if (!c.cloud_hash || isCloudToken(c.cloud_hash))
|
|
14251
14314
|
continue;
|
|
14252
14315
|
if (!sent[c.entity_id]?.includes(c.cloud_hash))
|
|
14253
14316
|
continue;
|
|
@@ -14256,10 +14319,59 @@ async function ownEarlierWrites(projectRoot, batch, conflicts) {
|
|
|
14256
14319
|
}
|
|
14257
14320
|
return out2;
|
|
14258
14321
|
}
|
|
14322
|
+
function findingRuns(frontmatter) {
|
|
14323
|
+
const out2 = /* @__PURE__ */ new Set();
|
|
14324
|
+
if (typeof frontmatter.run === "string" && frontmatter.run) {
|
|
14325
|
+
out2.add(frontmatter.run);
|
|
14326
|
+
}
|
|
14327
|
+
const seen = frontmatter.seen_in;
|
|
14328
|
+
if (typeof seen === "string") {
|
|
14329
|
+
const m = /^(?:ux|designer):(.+)$/.exec(seen);
|
|
14330
|
+
if (m?.[1])
|
|
14331
|
+
out2.add(m[1]);
|
|
14332
|
+
}
|
|
14333
|
+
return out2;
|
|
14334
|
+
}
|
|
14335
|
+
async function rowsToAdopt(projectRoot, batch, conflicts, adopt) {
|
|
14336
|
+
if (!adopt || conflicts.length === 0)
|
|
14337
|
+
return [];
|
|
14338
|
+
const bases = await readBaseHashes(projectRoot);
|
|
14339
|
+
const byId = new Map(batch.map((e) => [e.id, e]));
|
|
14340
|
+
const out2 = [];
|
|
14341
|
+
for (const c of conflicts) {
|
|
14342
|
+
const entity = byId.get(c.entity_id);
|
|
14343
|
+
if (!entity || entity.type !== "finding" || !c.cloud_hash)
|
|
14344
|
+
continue;
|
|
14345
|
+
const origin = cloudTokenOrigin(c.cloud_hash, adopt.humanTokens);
|
|
14346
|
+
if (origin === null || origin === "person")
|
|
14347
|
+
continue;
|
|
14348
|
+
if (origin === "unknown" && !stampedByThisPass(c, entity, adopt)) {
|
|
14349
|
+
continue;
|
|
14350
|
+
}
|
|
14351
|
+
await recordCloudBase(projectRoot, c.entity_id, c.cloud_hash);
|
|
14352
|
+
out2.push(origin === "projection" ? { entity, restamped: false, kind: "projection" } : {
|
|
14353
|
+
entity,
|
|
14354
|
+
restamped: Boolean(c.base_hash || bases[c.entity_id]),
|
|
14355
|
+
kind: "bundle"
|
|
14356
|
+
});
|
|
14357
|
+
}
|
|
14358
|
+
return out2;
|
|
14359
|
+
}
|
|
14360
|
+
function stampedByThisPass(c, entity, adopt) {
|
|
14361
|
+
const runs = findingRuns(entity.frontmatter);
|
|
14362
|
+
if (![...runs].some((r) => adopt.runs.has(r)))
|
|
14363
|
+
return false;
|
|
14364
|
+
const at = c.cloud_updated_at ? Date.parse(c.cloud_updated_at) : Number.NaN;
|
|
14365
|
+
const slack = adopt.serverClock ? ADOPT_CLOCK_SLACK_MS : 0;
|
|
14366
|
+
if (Number.isNaN(at) || at < adopt.since - slack)
|
|
14367
|
+
return false;
|
|
14368
|
+
return !adopt.identity || c.cloud_changed_by === adopt.identity;
|
|
14369
|
+
}
|
|
14259
14370
|
async function pushBatches(target, batches, byType, opts = {}) {
|
|
14260
14371
|
let pushed = 0;
|
|
14261
14372
|
let enqueued = 0;
|
|
14262
14373
|
const selfResolved = [];
|
|
14374
|
+
const adopted = [];
|
|
14263
14375
|
const conflicts = [];
|
|
14264
14376
|
const overwritten = [];
|
|
14265
14377
|
const errors = [];
|
|
@@ -14281,19 +14393,42 @@ async function pushBatches(target, batches, byType, opts = {}) {
|
|
|
14281
14393
|
conflicts,
|
|
14282
14394
|
overwritten,
|
|
14283
14395
|
errors,
|
|
14284
|
-
selfResolved
|
|
14396
|
+
selfResolved,
|
|
14397
|
+
adopted
|
|
14285
14398
|
};
|
|
14286
14399
|
}
|
|
14287
14400
|
absorb(first);
|
|
14288
14401
|
let unresolved = first.conflicts;
|
|
14289
14402
|
const own = await ownEarlierWrites(target.projectRoot, batch, unresolved);
|
|
14290
|
-
|
|
14291
|
-
|
|
14292
|
-
|
|
14293
|
-
|
|
14403
|
+
const ownIds = new Set(own.map((e) => e.id));
|
|
14404
|
+
const basesBefore = opts.adopt ? await readCloudBase(target.projectRoot) : {};
|
|
14405
|
+
const fromBundles = await rowsToAdopt(target.projectRoot, batch, unresolved.filter((c) => !ownIds.has(c.entity_id)), opts.adopt);
|
|
14406
|
+
const resend = [...own, ...fromBundles.map((a) => a.entity)];
|
|
14407
|
+
if (resend.length > 0) {
|
|
14408
|
+
const resendIds = new Set(resend.map((e) => e.id));
|
|
14409
|
+
unresolved = unresolved.filter((c) => !resendIds.has(c.entity_id));
|
|
14410
|
+
const again = await pushOneBatch(target, resend, byType, `batch ${i} own-version retry`);
|
|
14294
14411
|
absorb(again);
|
|
14295
14412
|
conflicts.push(...again.conflicts);
|
|
14296
|
-
|
|
14413
|
+
const landed2 = new Set(again.landed);
|
|
14414
|
+
selfResolved.push(...own.filter((e) => landed2.has(e.id)).map((e) => e.id));
|
|
14415
|
+
for (const a of fromBundles) {
|
|
14416
|
+
if (landed2.has(a.entity.id)) {
|
|
14417
|
+
adopted.push({
|
|
14418
|
+
slug: a.entity.slug,
|
|
14419
|
+
kind: a.kind,
|
|
14420
|
+
restamped: a.restamped,
|
|
14421
|
+
serverClock: opts.adopt?.serverClock ?? false
|
|
14422
|
+
});
|
|
14423
|
+
continue;
|
|
14424
|
+
}
|
|
14425
|
+
const prior = basesBefore[a.entity.id];
|
|
14426
|
+
if (prior === void 0) {
|
|
14427
|
+
await forgetCloudBase(target.projectRoot, a.entity.id);
|
|
14428
|
+
} else {
|
|
14429
|
+
await recordCloudBase(target.projectRoot, a.entity.id, prior);
|
|
14430
|
+
}
|
|
14431
|
+
}
|
|
14297
14432
|
}
|
|
14298
14433
|
if (!opts.keepMine || unresolved.length === 0) {
|
|
14299
14434
|
conflicts.push(...unresolved);
|
|
@@ -14346,8 +14481,17 @@ async function pushBatches(target, batches, byType, opts = {}) {
|
|
|
14346
14481
|
}
|
|
14347
14482
|
await forgetQueuedUpserts(target.projectRoot, revoked);
|
|
14348
14483
|
}
|
|
14349
|
-
return {
|
|
14484
|
+
return {
|
|
14485
|
+
pushed,
|
|
14486
|
+
enqueued,
|
|
14487
|
+
conflicts,
|
|
14488
|
+
overwritten,
|
|
14489
|
+
errors,
|
|
14490
|
+
selfResolved,
|
|
14491
|
+
adopted
|
|
14492
|
+
};
|
|
14350
14493
|
}
|
|
14494
|
+
var ADOPT_CLOCK_SLACK_MS;
|
|
14351
14495
|
var init_push = __esm({
|
|
14352
14496
|
"../../packages/local-store/dist/sync/push.js"() {
|
|
14353
14497
|
"use strict";
|
|
@@ -14355,6 +14499,7 @@ var init_push = __esm({
|
|
|
14355
14499
|
init_server_reason();
|
|
14356
14500
|
init_sync_tracking();
|
|
14357
14501
|
init_types();
|
|
14502
|
+
ADOPT_CLOCK_SLACK_MS = 5e3;
|
|
14358
14503
|
}
|
|
14359
14504
|
});
|
|
14360
14505
|
|
|
@@ -14749,15 +14894,33 @@ async function syncWalkthroughs(projectRoot, deps, unpushableScenarios = /* @__P
|
|
|
14749
14894
|
const hold = deps.capabilities === null;
|
|
14750
14895
|
let held = 0;
|
|
14751
14896
|
const recordedFindings = /* @__PURE__ */ new Set();
|
|
14897
|
+
const pushedRuns = /* @__PURE__ */ new Set();
|
|
14898
|
+
let serverClockOffsetMs = null;
|
|
14752
14899
|
let runIds;
|
|
14753
14900
|
try {
|
|
14754
14901
|
runIds = await listRuns(projectRoot);
|
|
14755
14902
|
} catch (err) {
|
|
14756
14903
|
errors.push(`walkthrough scan failed: ${err instanceof Error ? err.message : String(err)} \u2014 check that iamu/.runs/ exists and is readable`);
|
|
14757
|
-
return {
|
|
14904
|
+
return {
|
|
14905
|
+
summary,
|
|
14906
|
+
errors,
|
|
14907
|
+
withheldRuns,
|
|
14908
|
+
held,
|
|
14909
|
+
recordedFindings,
|
|
14910
|
+
pushedRuns,
|
|
14911
|
+
serverClockOffsetMs
|
|
14912
|
+
};
|
|
14758
14913
|
}
|
|
14759
14914
|
if (runIds.length === 0)
|
|
14760
|
-
return {
|
|
14915
|
+
return {
|
|
14916
|
+
summary,
|
|
14917
|
+
errors,
|
|
14918
|
+
withheldRuns,
|
|
14919
|
+
held,
|
|
14920
|
+
recordedFindings,
|
|
14921
|
+
pushedRuns,
|
|
14922
|
+
serverClockOffsetMs
|
|
14923
|
+
};
|
|
14761
14924
|
const lastPush = await readLastPush(projectRoot);
|
|
14762
14925
|
const pendingWalkthroughQueueEntries = /* @__PURE__ */ new Set();
|
|
14763
14926
|
const retryQueue = await readRetryQueue(projectRoot);
|
|
@@ -14824,6 +14987,9 @@ async function syncWalkthroughs(projectRoot, deps, unpushableScenarios = /* @__P
|
|
|
14824
14987
|
});
|
|
14825
14988
|
if (result.status === "pushed" || result.status === "already_pushed") {
|
|
14826
14989
|
summary.pushed++;
|
|
14990
|
+
if (result.status === "pushed")
|
|
14991
|
+
pushedRuns.add(runId);
|
|
14992
|
+
serverClockOffsetMs ??= result.serverClockOffsetMs ?? null;
|
|
14827
14993
|
for (const id of result.recordedFindings ?? [])
|
|
14828
14994
|
recordedFindings.add(id);
|
|
14829
14995
|
} else if (result.skippedReason === "bundle_too_large" || result.skippedReason === "unsupported" || result.skippedReason === "missing_run" || result.skippedReason === "cloud_conflict") {
|
|
@@ -14846,7 +15012,15 @@ async function syncWalkthroughs(projectRoot, deps, unpushableScenarios = /* @__P
|
|
|
14846
15012
|
}
|
|
14847
15013
|
}
|
|
14848
15014
|
}
|
|
14849
|
-
return {
|
|
15015
|
+
return {
|
|
15016
|
+
summary,
|
|
15017
|
+
errors,
|
|
15018
|
+
withheldRuns,
|
|
15019
|
+
held,
|
|
15020
|
+
recordedFindings,
|
|
15021
|
+
pushedRuns,
|
|
15022
|
+
serverClockOffsetMs
|
|
15023
|
+
};
|
|
14850
15024
|
}
|
|
14851
15025
|
var HOLD_SHAPES, WALKTHROUGH_SKIP_HINTS;
|
|
14852
15026
|
var init_walkthroughs = __esm({
|
|
@@ -14896,6 +15070,7 @@ function mergePushProgress(target, outcome) {
|
|
|
14896
15070
|
if (!outcome)
|
|
14897
15071
|
return;
|
|
14898
15072
|
target.selfResolved.push(...outcome.selfResolved ?? []);
|
|
15073
|
+
target.adopted.push(...outcome.adopted ?? []);
|
|
14899
15074
|
target.pushed += outcome.pushed;
|
|
14900
15075
|
target.enqueued += outcome.enqueued;
|
|
14901
15076
|
target.conflicts.push(...outcome.conflicts);
|
|
@@ -14942,6 +15117,7 @@ async function pushDeferredFindings(projectRoot, capabilities, deferredFindings,
|
|
|
14942
15117
|
return pushBatches(target, toBatches(retryFindings, batchSize), byType, pushOpts);
|
|
14943
15118
|
}
|
|
14944
15119
|
async function runSync(projectRoot, deps) {
|
|
15120
|
+
const startedAt = Date.now();
|
|
14945
15121
|
const config = await readConfig(projectRoot);
|
|
14946
15122
|
if (!config.cloud_linked) {
|
|
14947
15123
|
return emptySyncResult([
|
|
@@ -14956,11 +15132,21 @@ async function runSync(projectRoot, deps) {
|
|
|
14956
15132
|
const apiKey = deps.apiKey;
|
|
14957
15133
|
const fetchFn = deps.fetch ?? globalThis.fetch;
|
|
14958
15134
|
let capabilitiesFailure = "";
|
|
15135
|
+
let serverClockOffsetMs = null;
|
|
14959
15136
|
const capabilities = deps.capabilities !== void 0 ? deps.capabilities : await fetchServerCapabilities(config.cloud_api_url, apiKey, fetchFn, deps.timeoutMs ?? DEFAULT_TIMEOUT_MS4, (detail) => {
|
|
14960
15137
|
capabilitiesFailure = detail;
|
|
15138
|
+
}, (offset) => {
|
|
15139
|
+
serverClockOffsetMs = offset;
|
|
14961
15140
|
});
|
|
14962
15141
|
deps = { ...deps, capabilities };
|
|
14963
|
-
const drain = await drainRetryQueue(projectRoot, deps);
|
|
15142
|
+
const { pushedRuns: drainedRuns = [], ...drain } = await drainRetryQueue(projectRoot, deps);
|
|
15143
|
+
const adoptFrom = (runs) => ({
|
|
15144
|
+
runs: /* @__PURE__ */ new Set([...drainedRuns, ...runs]),
|
|
15145
|
+
since: startedAt + (serverClockOffsetMs ?? 0),
|
|
15146
|
+
serverClock: serverClockOffsetMs !== null,
|
|
15147
|
+
humanTokens: cloudDistinguishesHumanEdits(capabilities),
|
|
15148
|
+
identity: deps.identity ?? null
|
|
15149
|
+
});
|
|
14964
15150
|
const scanned = await scanPending(projectRoot);
|
|
14965
15151
|
if (scanned.errors.length === 0) {
|
|
14966
15152
|
await pruneSentHashes(projectRoot, scanned.seenIds);
|
|
@@ -14984,14 +15170,15 @@ async function runSync(projectRoot, deps) {
|
|
|
14984
15170
|
const pushOpts = { keepMine: deps.keepMine };
|
|
14985
15171
|
const guidance = scan.pending.filter((e) => e.type === "guidance");
|
|
14986
15172
|
const nonGuidance = scan.pending.filter((e) => e.type !== "guidance");
|
|
14987
|
-
const phase1 = await pushBatches(target, toBatches(nonGuidance, batchSize), byType, pushOpts);
|
|
15173
|
+
const phase1 = await pushBatches(target, toBatches(nonGuidance, batchSize), byType, { ...pushOpts, adopt: adoptFrom([]) });
|
|
14988
15174
|
const progress = {
|
|
14989
15175
|
pushed: phase1.pushed,
|
|
14990
15176
|
enqueued: phase1.enqueued,
|
|
14991
15177
|
conflicts: [...phase1.conflicts],
|
|
14992
15178
|
overwritten: [...phase1.overwritten],
|
|
14993
15179
|
errors,
|
|
14994
|
-
selfResolved: [...phase1.selfResolved]
|
|
15180
|
+
selfResolved: [...phase1.selfResolved],
|
|
15181
|
+
adopted: [...phase1.adopted]
|
|
14995
15182
|
};
|
|
14996
15183
|
progress.errors.push(...phase1.errors);
|
|
14997
15184
|
const notices = [];
|
|
@@ -15001,10 +15188,11 @@ async function runSync(projectRoot, deps) {
|
|
|
15001
15188
|
const guidanceResult = await pushGuidanceBatch(target, guidance, batchSize, byType, { ...pushOpts, detectUnsupported: !cloudSupportsGuidance(capabilities) }, notices);
|
|
15002
15189
|
mergePushProgress(progress, guidanceResult);
|
|
15003
15190
|
const walkthroughs = await syncWalkthroughs(projectRoot, deps, scan.unpushableScenarios);
|
|
15191
|
+
serverClockOffsetMs ??= walkthroughs.serverClockOffsetMs;
|
|
15004
15192
|
if (walkthroughs.errors.length > 0) {
|
|
15005
15193
|
progress.errors.push(...walkthroughs.errors);
|
|
15006
15194
|
}
|
|
15007
|
-
const phase3 = capabilities === null ? null : await pushDeferredFindings(projectRoot, capabilities, deferredFindings, walkthroughs, target, batchSize, byType, pushOpts, reportedRefErrors, progress.errors);
|
|
15195
|
+
const phase3 = capabilities === null ? null : await pushDeferredFindings(projectRoot, capabilities, deferredFindings, walkthroughs, target, batchSize, byType, { ...pushOpts, adopt: adoptFrom(walkthroughs.pushedRuns) }, reportedRefErrors, progress.errors);
|
|
15008
15196
|
mergePushProgress(progress, phase3);
|
|
15009
15197
|
if (capabilities === null) {
|
|
15010
15198
|
const heldFindings = shaped.heldFindings + deferredFindings.size;
|
|
@@ -15025,6 +15213,15 @@ async function runSync(projectRoot, deps) {
|
|
|
15025
15213
|
const deletion = await syncDeletions(projectRoot, config, deps);
|
|
15026
15214
|
progress.errors.push(...deletion.errors);
|
|
15027
15215
|
notices.push(...deletion.notices);
|
|
15216
|
+
for (const a of progress.adopted) {
|
|
15217
|
+
if (a.kind === "projection") {
|
|
15218
|
+
notices.push(`${a.slug}: kept the local version over the cloud's projected triage (automated, from a run's outcome)`);
|
|
15219
|
+
continue;
|
|
15220
|
+
}
|
|
15221
|
+
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)";
|
|
15222
|
+
const clock = a.serverClock ? "" : "; the server sent no clock, so only rows stamped after this sync began by this machine's clock counted";
|
|
15223
|
+
notices.push(`${a.slug}: ${what}${clock}`);
|
|
15224
|
+
}
|
|
15028
15225
|
if (progress.selfResolved.length > 0) {
|
|
15029
15226
|
const n = progress.selfResolved.length;
|
|
15030
15227
|
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 +19471,14 @@ async function buildRunDesignerInput(projectRoot, runId) {
|
|
|
19274
19471
|
await chooseKeyFrames(rd, grouped.candidates);
|
|
19275
19472
|
const screens = grouped.screens;
|
|
19276
19473
|
const screenKeys = new Set(screens.map((s) => s.key));
|
|
19277
|
-
const earlier = await designerFindingsOn(
|
|
19474
|
+
const earlier = await designerFindingsOn(
|
|
19475
|
+
projectRoot,
|
|
19476
|
+
screenKeys,
|
|
19477
|
+
manifest?.created_at ?? null,
|
|
19478
|
+
target,
|
|
19479
|
+
// A fix a designer's review already verified is not asked about again.
|
|
19480
|
+
await verifiedDesignerFixes(projectRoot, manifest?.created_at ?? null)
|
|
19481
|
+
);
|
|
19278
19482
|
for (const o of earlier.open)
|
|
19279
19483
|
refs2[o.ref] = o.id;
|
|
19280
19484
|
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 +19634,7 @@ var init_designer_input = __esm({
|
|
|
19430
19634
|
init_designer_files();
|
|
19431
19635
|
init_designer_ignored();
|
|
19432
19636
|
init_designer_ledger();
|
|
19637
|
+
init_fix_state();
|
|
19433
19638
|
init_walkthrough_canonical2();
|
|
19434
19639
|
init_dist();
|
|
19435
19640
|
LONG_VISIT = 6;
|
|
@@ -22382,6 +22587,69 @@ function stepNumberOf(s) {
|
|
|
22382
22587
|
const m = /^(?:step-)?(\d+)$/i.exec(s.trim());
|
|
22383
22588
|
return m ? Number(m[1]) : null;
|
|
22384
22589
|
}
|
|
22590
|
+
async function writeDesignerFixStatus(projectRoot, runId, verification, checks) {
|
|
22591
|
+
const verified = [];
|
|
22592
|
+
const untriaged = [];
|
|
22593
|
+
const warnings = [];
|
|
22594
|
+
const out2 = { verified, untriaged, warnings };
|
|
22595
|
+
const resolved = new Set(verification.resolved);
|
|
22596
|
+
const persists = new Set(verification.persists);
|
|
22597
|
+
if (resolved.size + persists.size === 0)
|
|
22598
|
+
return out2;
|
|
22599
|
+
const notWritten = (what, err) => warnings.push(`could not update ${what}: ${err instanceof Error ? err.message : String(err)}; designer.json holds the verification`);
|
|
22600
|
+
let files;
|
|
22601
|
+
try {
|
|
22602
|
+
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)));
|
|
22603
|
+
} catch (err) {
|
|
22604
|
+
notWritten("the checked findings' fix_status", err);
|
|
22605
|
+
return out2;
|
|
22606
|
+
}
|
|
22607
|
+
const checkOf = new Map(checks.map((c) => [c.canonical_id, c]));
|
|
22608
|
+
for (const cid of verification.resolved) {
|
|
22609
|
+
const mine = files.filter((fm) => fm.canonical_id === cid);
|
|
22610
|
+
if (mine.some((fm) => fm.triage_status === "addressed"))
|
|
22611
|
+
continue;
|
|
22612
|
+
const latest = mine.filter((fm) => fm.triage_status !== "dismissed").sort((a, b) => String(a.created_at ?? "").localeCompare(String(b.created_at ?? ""))).at(-1);
|
|
22613
|
+
if (latest) {
|
|
22614
|
+
untriaged.push({
|
|
22615
|
+
ref: checkOf.get(cid)?.ref ?? null,
|
|
22616
|
+
finding: latest.slug
|
|
22617
|
+
});
|
|
22618
|
+
}
|
|
22619
|
+
}
|
|
22620
|
+
let states;
|
|
22621
|
+
try {
|
|
22622
|
+
states = deriveFixStates(files, await readRunEvidence(projectRoot));
|
|
22623
|
+
} catch (err) {
|
|
22624
|
+
notWritten("the checked findings' fix_status", err);
|
|
22625
|
+
return out2;
|
|
22626
|
+
}
|
|
22627
|
+
const decidedHere = (slug, state) => {
|
|
22628
|
+
const s = states.get(slug);
|
|
22629
|
+
return s?.fix_state === state && s.decided_by === runId;
|
|
22630
|
+
};
|
|
22631
|
+
for (const fm of files) {
|
|
22632
|
+
if (fm.triage_status !== "addressed" || !fm.canonical_id)
|
|
22633
|
+
continue;
|
|
22634
|
+
if (resolved.has(fm.canonical_id) && decidedHere(fm.slug, "verified")) {
|
|
22635
|
+
const [failed] = await markAddressedResolved([
|
|
22636
|
+
{
|
|
22637
|
+
file: entityFilePath(projectRoot, "finding", fm.slug),
|
|
22638
|
+
triage_status: fm.triage_status
|
|
22639
|
+
}
|
|
22640
|
+
], checkOf.get(fm.canonical_id)?.note ?? void 0);
|
|
22641
|
+
if (failed)
|
|
22642
|
+
notWritten(fm.slug, failed.error);
|
|
22643
|
+
else
|
|
22644
|
+
verified.push(fm.slug);
|
|
22645
|
+
} else if (persists.has(fm.canonical_id) && decidedHere(fm.slug, "recurred")) {
|
|
22646
|
+
const error = await updateFixStatus(projectRoot, fm.slug, "verified_persists");
|
|
22647
|
+
if (error)
|
|
22648
|
+
notWritten(fm.slug, error);
|
|
22649
|
+
}
|
|
22650
|
+
}
|
|
22651
|
+
return out2;
|
|
22652
|
+
}
|
|
22385
22653
|
async function readRefs2(rd) {
|
|
22386
22654
|
try {
|
|
22387
22655
|
return JSON.parse(await fs49.readFile(designerRefsFile(rd), "utf8"));
|
|
@@ -22702,6 +22970,11 @@ async function recordDesigner(projectRoot, runId, answer, opts) {
|
|
|
22702
22970
|
};
|
|
22703
22971
|
await atomicWriteFile(file, `${JSON.stringify(record, null, 2)}
|
|
22704
22972
|
`);
|
|
22973
|
+
const fixes = await writeDesignerFixStatus(projectRoot, runId, verification, checks);
|
|
22974
|
+
warnings.push(...fixes.warnings);
|
|
22975
|
+
for (const u of fixes.untriaged) {
|
|
22976
|
+
warnings.push(`checked ${u.ref ?? "?"}: ${u.finding} is gone but was never marked addressed; its fix_status is left alone (checked_but_untriaged)`);
|
|
22977
|
+
}
|
|
22705
22978
|
const bySeverity = {};
|
|
22706
22979
|
const ruleCount = groupDesignerRules(rules);
|
|
22707
22980
|
for (const i of issues)
|
|
@@ -22715,7 +22988,9 @@ async function recordDesigner(projectRoot, runId, answer, opts) {
|
|
|
22715
22988
|
proposals: proposals.written,
|
|
22716
22989
|
rejected,
|
|
22717
22990
|
warnings,
|
|
22718
|
-
...invalidChecked.length > 0 ? { invalid_checked: invalidChecked } : {}
|
|
22991
|
+
...invalidChecked.length > 0 ? { invalid_checked: invalidChecked } : {},
|
|
22992
|
+
...fixes.verified.length > 0 ? { verified: fixes.verified } : {},
|
|
22993
|
+
...fixes.untriaged.length > 0 ? { checked_but_untriaged: fixes.untriaged } : {}
|
|
22719
22994
|
};
|
|
22720
22995
|
}
|
|
22721
22996
|
var DESIGNER_CATEGORIES, steps, refs, designerAnswerSchema;
|
|
@@ -22725,13 +23000,14 @@ var init_designer_record = __esm({
|
|
|
22725
23000
|
init_atomic();
|
|
22726
23001
|
init_design_proposals();
|
|
22727
23002
|
init_design_review();
|
|
22728
|
-
init_paths();
|
|
22729
23003
|
init_url_path();
|
|
22730
23004
|
init_review_issues();
|
|
22731
23005
|
init_designer_files();
|
|
22732
23006
|
init_designer_input();
|
|
22733
23007
|
init_designer_ledger();
|
|
22734
23008
|
init_entity();
|
|
23009
|
+
init_fix_state();
|
|
23010
|
+
init_paths();
|
|
22735
23011
|
init_walkthrough_canonical2();
|
|
22736
23012
|
DESIGNER_CATEGORIES = [
|
|
22737
23013
|
"hierarchy",
|
|
@@ -23774,15 +24050,15 @@ function resolveApiUrl(configured) {
|
|
|
23774
24050
|
assertSecureApiUrl(url, configured ? "API URL" : "IAMU_API_URL");
|
|
23775
24051
|
return url;
|
|
23776
24052
|
}
|
|
23777
|
-
function parseStore(raw,
|
|
24053
|
+
function parseStore(raw, path78) {
|
|
23778
24054
|
let parsed;
|
|
23779
24055
|
try {
|
|
23780
24056
|
parsed = JSON.parse(raw);
|
|
23781
24057
|
} catch (err) {
|
|
23782
|
-
throw new CredentialsFileError(
|
|
24058
|
+
throw new CredentialsFileError(path78, "not valid JSON", err);
|
|
23783
24059
|
}
|
|
23784
24060
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
23785
|
-
throw new CredentialsFileError(
|
|
24061
|
+
throw new CredentialsFileError(path78, "expected a JSON object");
|
|
23786
24062
|
}
|
|
23787
24063
|
return parsed;
|
|
23788
24064
|
}
|
|
@@ -23790,26 +24066,26 @@ function isNotFound(err) {
|
|
|
23790
24066
|
return typeof err === "object" && err !== null && err.code === "ENOENT";
|
|
23791
24067
|
}
|
|
23792
24068
|
async function readCredentialsStore() {
|
|
23793
|
-
const
|
|
24069
|
+
const path78 = getCredentialsPath();
|
|
23794
24070
|
let raw;
|
|
23795
24071
|
try {
|
|
23796
|
-
raw = await readFile(
|
|
24072
|
+
raw = await readFile(path78, "utf-8");
|
|
23797
24073
|
} catch (err) {
|
|
23798
24074
|
if (isNotFound(err)) return {};
|
|
23799
24075
|
warnUnreadableStoreOnce(err);
|
|
23800
24076
|
throw err;
|
|
23801
24077
|
}
|
|
23802
24078
|
try {
|
|
23803
|
-
return parseStore(raw,
|
|
24079
|
+
return parseStore(raw, path78);
|
|
23804
24080
|
} catch (err) {
|
|
23805
24081
|
warnUnreadableStoreOnce(err);
|
|
23806
24082
|
throw err;
|
|
23807
24083
|
}
|
|
23808
24084
|
}
|
|
23809
24085
|
async function writeCredentialsStore(store) {
|
|
23810
|
-
const
|
|
23811
|
-
await mkdir(dirname(
|
|
23812
|
-
await atomicWriteFile(
|
|
24086
|
+
const path78 = getCredentialsPath();
|
|
24087
|
+
await mkdir(dirname(path78), { recursive: true, mode: 448 });
|
|
24088
|
+
await atomicWriteFile(path78, `${JSON.stringify(store, null, 2)}
|
|
23813
24089
|
`, {
|
|
23814
24090
|
mode: 384
|
|
23815
24091
|
});
|
|
@@ -23837,16 +24113,16 @@ function resolveApiKey(apiUrl) {
|
|
|
23837
24113
|
}
|
|
23838
24114
|
}
|
|
23839
24115
|
const normalized = url.replace(/\/+$/, "");
|
|
23840
|
-
for (const
|
|
24116
|
+
for (const path78 of candidatePaths) {
|
|
23841
24117
|
let raw;
|
|
23842
24118
|
try {
|
|
23843
|
-
raw = readFileSync3(
|
|
24119
|
+
raw = readFileSync3(path78, "utf-8");
|
|
23844
24120
|
} catch (err) {
|
|
23845
24121
|
if (!isNotFound(err)) warnUnreadableStoreOnce(err);
|
|
23846
24122
|
continue;
|
|
23847
24123
|
}
|
|
23848
24124
|
try {
|
|
23849
|
-
const store = parseStore(raw,
|
|
24125
|
+
const store = parseStore(raw, path78);
|
|
23850
24126
|
const entry = store[url] ?? store[normalized] ?? store[`${normalized}/`];
|
|
23851
24127
|
if (typeof entry !== "object" || entry === null) continue;
|
|
23852
24128
|
const token = entry.token;
|
|
@@ -23887,12 +24163,12 @@ var init_credentials = __esm({
|
|
|
23887
24163
|
* @param reason - What is wrong with it.
|
|
23888
24164
|
* @param cause - Underlying parse error, if any.
|
|
23889
24165
|
*/
|
|
23890
|
-
constructor(
|
|
24166
|
+
constructor(path78, reason, cause) {
|
|
23891
24167
|
super(
|
|
23892
|
-
`Credentials file ${
|
|
24168
|
+
`Credentials file ${path78} is corrupt (${reason}). Fix it by hand, or delete it and run \`iamu auth login\` again.`,
|
|
23893
24169
|
{
|
|
23894
24170
|
cause,
|
|
23895
|
-
suggestedAction: `Delete ${
|
|
24171
|
+
suggestedAction: `Delete ${path78} and run \`iamu auth login\`, or set IAMU_TOKEN.`
|
|
23896
24172
|
}
|
|
23897
24173
|
);
|
|
23898
24174
|
}
|
|
@@ -24832,8 +25108,8 @@ import { dirname as dirname3, join as join5, relative, resolve as resolve2 } fro
|
|
|
24832
25108
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
24833
25109
|
import chalk6 from "chalk";
|
|
24834
25110
|
function getVersion() {
|
|
24835
|
-
if ("0.8.0-beta.
|
|
24836
|
-
return "0.8.0-beta.
|
|
25111
|
+
if ("0.8.0-beta.7") {
|
|
25112
|
+
return "0.8.0-beta.7";
|
|
24837
25113
|
}
|
|
24838
25114
|
try {
|
|
24839
25115
|
const thisDir = dirname3(fileURLToPath2(import.meta.url));
|
|
@@ -24956,10 +25232,10 @@ function hasGeneratedByMarker(content) {
|
|
|
24956
25232
|
function withoutMarker(content) {
|
|
24957
25233
|
return content.replace(/^generatedBy:[^\r\n]*\r?\n/m, "");
|
|
24958
25234
|
}
|
|
24959
|
-
function cliSkillState(
|
|
25235
|
+
function cliSkillState(path78, template) {
|
|
24960
25236
|
let content;
|
|
24961
25237
|
try {
|
|
24962
|
-
content = readFileSync7(
|
|
25238
|
+
content = readFileSync7(path78, "utf-8");
|
|
24963
25239
|
} catch {
|
|
24964
25240
|
return null;
|
|
24965
25241
|
}
|
|
@@ -24967,9 +25243,9 @@ function cliSkillState(path77, template) {
|
|
|
24967
25243
|
if (template === void 0) return "edited";
|
|
24968
25244
|
return withoutMarker(content) === withoutMarker(template) ? "clean" : "edited";
|
|
24969
25245
|
}
|
|
24970
|
-
function isCliWrittenPrompt(
|
|
25246
|
+
function isCliWrittenPrompt(path78) {
|
|
24971
25247
|
try {
|
|
24972
|
-
const content = readFileSync7(
|
|
25248
|
+
const content = readFileSync7(path78, "utf-8");
|
|
24973
25249
|
return hasGeneratedByMarker(content) || /^#\s+iamu:/m.test(content) || /^description:\s*.*iamu/im.test(content);
|
|
24974
25250
|
} catch {
|
|
24975
25251
|
return false;
|
|
@@ -24979,10 +25255,10 @@ function planSkillFiles(hosts, projectRoot, version2) {
|
|
|
24979
25255
|
const skillTemplates = loadSkillTemplates(version2);
|
|
24980
25256
|
const files = [];
|
|
24981
25257
|
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:
|
|
25258
|
+
const removeIfClean = (host, path78, name) => {
|
|
25259
|
+
const state = cliSkillState(path78, skillTemplates.get(name));
|
|
25260
|
+
if (state === "clean") files.push({ host, path: path78, content: null });
|
|
25261
|
+
else if (state === "edited") keptEdited.push({ host, path: path78 });
|
|
24986
25262
|
};
|
|
24987
25263
|
for (const host of hosts) {
|
|
24988
25264
|
if (pluginProvidesSkills(host, projectRoot)) {
|
|
@@ -25079,9 +25355,9 @@ async function findStaleSkills(projectRoot) {
|
|
|
25079
25355
|
}
|
|
25080
25356
|
return stale;
|
|
25081
25357
|
}
|
|
25082
|
-
async function removeSkillFile(
|
|
25083
|
-
await rm(
|
|
25084
|
-
await rmdir(dirname3(
|
|
25358
|
+
async function removeSkillFile(path78) {
|
|
25359
|
+
await rm(path78, { force: true });
|
|
25360
|
+
await rmdir(dirname3(path78)).catch(() => {
|
|
25085
25361
|
});
|
|
25086
25362
|
}
|
|
25087
25363
|
async function installSkillsForHosts(hosts, projectRoot, version2, opts) {
|
|
@@ -25883,29 +26159,29 @@ async function writeSyncMcpEntry(mcpPath) {
|
|
|
25883
26159
|
return { removedLegacy };
|
|
25884
26160
|
}
|
|
25885
26161
|
async function registerSyncMcpForHost(host, projectRoot) {
|
|
25886
|
-
const
|
|
25887
|
-
if (
|
|
26162
|
+
const path78 = host.getMcpConfigPath(projectRoot);
|
|
26163
|
+
if (path78 === null) {
|
|
25888
26164
|
return {
|
|
25889
26165
|
status: "manual",
|
|
25890
26166
|
instruction: host.manualMcpSetup ?? `add an "iamu-sync" server that runs \`${SYNC_SERVER_COMMAND.join(" ")}\``
|
|
25891
26167
|
};
|
|
25892
26168
|
}
|
|
25893
26169
|
try {
|
|
25894
|
-
const { removedLegacy } = await writeSyncMcpEntry(
|
|
25895
|
-
return { status: "written", path:
|
|
26170
|
+
const { removedLegacy } = await writeSyncMcpEntry(path78);
|
|
26171
|
+
return { status: "written", path: path78, removedLegacy };
|
|
25896
26172
|
} catch (err) {
|
|
25897
26173
|
return {
|
|
25898
26174
|
status: "failed",
|
|
25899
|
-
path:
|
|
26175
|
+
path: path78,
|
|
25900
26176
|
message: err instanceof Error ? err.message : String(err)
|
|
25901
26177
|
};
|
|
25902
26178
|
}
|
|
25903
26179
|
}
|
|
25904
26180
|
async function removeLegacyMcpForHost(host, projectRoot) {
|
|
25905
|
-
const
|
|
25906
|
-
if (
|
|
26181
|
+
const path78 = host.getMcpConfigPath(projectRoot);
|
|
26182
|
+
if (path78 === null) return null;
|
|
25907
26183
|
try {
|
|
25908
|
-
return await removeLegacyIamuEntry(
|
|
26184
|
+
return await removeLegacyIamuEntry(path78) ? { status: "legacy_removed", path: path78, removedLegacy: true } : null;
|
|
25909
26185
|
} catch {
|
|
25910
26186
|
return null;
|
|
25911
26187
|
}
|
|
@@ -33483,6 +33759,7 @@ __export(triage_exports, {
|
|
|
33483
33759
|
triage: () => triage
|
|
33484
33760
|
});
|
|
33485
33761
|
import { promises as fs65 } from "fs";
|
|
33762
|
+
import path62 from "path";
|
|
33486
33763
|
import chalk29 from "chalk";
|
|
33487
33764
|
async function awaitingSteps(projectRoot, findings2) {
|
|
33488
33765
|
const scenarios = /* @__PURE__ */ new Set();
|
|
@@ -33621,14 +33898,53 @@ async function triage(refs2, opts) {
|
|
|
33621
33898
|
);
|
|
33622
33899
|
return;
|
|
33623
33900
|
}
|
|
33901
|
+
let note = opts.note;
|
|
33902
|
+
if (opts.noteFile !== void 0) {
|
|
33903
|
+
if (opts.note !== void 0) {
|
|
33904
|
+
commandError("Pass --note or --note-file, not both.", {
|
|
33905
|
+
...opts,
|
|
33906
|
+
code: "invalid_field_combination",
|
|
33907
|
+
fix: `iamu triage ${refs2.join(" ")} --status ${status2.data} --note-file <path>`
|
|
33908
|
+
});
|
|
33909
|
+
return;
|
|
33910
|
+
}
|
|
33911
|
+
try {
|
|
33912
|
+
const text = opts.noteFile === "-" ? await readStdinText() : await fs65.readFile(
|
|
33913
|
+
path62.resolve(process.cwd(), opts.noteFile),
|
|
33914
|
+
"utf8"
|
|
33915
|
+
);
|
|
33916
|
+
note = text.trim();
|
|
33917
|
+
} catch (err) {
|
|
33918
|
+
commandError(
|
|
33919
|
+
`Could not read --note-file ${opts.noteFile}: ${err instanceof Error ? err.message : String(err)}`,
|
|
33920
|
+
{
|
|
33921
|
+
...opts,
|
|
33922
|
+
code: "note_read_error",
|
|
33923
|
+
fix: `iamu triage ${refs2.join(" ")} --status ${status2.data} --note "<what changed>"`
|
|
33924
|
+
}
|
|
33925
|
+
);
|
|
33926
|
+
return;
|
|
33927
|
+
}
|
|
33928
|
+
if (!note) {
|
|
33929
|
+
commandError(`--note-file ${opts.noteFile} is empty`, {
|
|
33930
|
+
...opts,
|
|
33931
|
+
code: "note_empty",
|
|
33932
|
+
fix: `iamu triage ${refs2.join(" ")} --status ${status2.data} --note "<what changed>"`
|
|
33933
|
+
});
|
|
33934
|
+
return;
|
|
33935
|
+
}
|
|
33936
|
+
}
|
|
33624
33937
|
let reason = null;
|
|
33625
33938
|
if (opts.reason !== void 0) {
|
|
33626
33939
|
if (status2.data !== "dismissed") {
|
|
33627
|
-
commandError(
|
|
33628
|
-
|
|
33629
|
-
|
|
33630
|
-
|
|
33631
|
-
|
|
33940
|
+
commandError(
|
|
33941
|
+
"--reason applies only to --status dismissed \u2014 to record why or what changed, use --note",
|
|
33942
|
+
{
|
|
33943
|
+
...opts,
|
|
33944
|
+
code: "invalid_reason_usage",
|
|
33945
|
+
fix: `iamu triage ${refs2.join(" ")} --status ${status2.data} --note "<what changed>"`
|
|
33946
|
+
}
|
|
33947
|
+
);
|
|
33632
33948
|
return;
|
|
33633
33949
|
}
|
|
33634
33950
|
if (!DISMISS_REASONS.includes(opts.reason)) {
|
|
@@ -33820,7 +34136,7 @@ async function triage(refs2, opts) {
|
|
|
33820
34136
|
const rule = await createGuidanceRule(projectRoot, {
|
|
33821
34137
|
kind: effectiveReason,
|
|
33822
34138
|
rule: ruleText,
|
|
33823
|
-
reason:
|
|
34139
|
+
reason: note,
|
|
33824
34140
|
scope,
|
|
33825
34141
|
sourceCanonicalIds: allTargets.map((f) => f.frontmatter.canonical_id).filter((id) => Boolean(id && UUID_RE4.test(id)))
|
|
33826
34142
|
});
|
|
@@ -33831,7 +34147,7 @@ async function triage(refs2, opts) {
|
|
|
33831
34147
|
status: status2.data,
|
|
33832
34148
|
reason: effectiveReason,
|
|
33833
34149
|
duplicateOf: duplicateTarget,
|
|
33834
|
-
...
|
|
34150
|
+
...note !== void 0 ? { note } : {},
|
|
33835
34151
|
by: "triage",
|
|
33836
34152
|
cascade: true,
|
|
33837
34153
|
guidance: guidance ? { id: guidance.id, all: isSweep } : null,
|
|
@@ -33916,6 +34232,7 @@ var init_triage = __esm({
|
|
|
33916
34232
|
init_dist2();
|
|
33917
34233
|
init_dist();
|
|
33918
34234
|
init_agent_commands();
|
|
34235
|
+
init_answer_input();
|
|
33919
34236
|
init_complete();
|
|
33920
34237
|
init_context4();
|
|
33921
34238
|
SCOPES = ["project", "outline", "capability"];
|
|
@@ -34383,7 +34700,7 @@ var init_browse_driver = __esm({
|
|
|
34383
34700
|
|
|
34384
34701
|
// src/commands/browse-actions.ts
|
|
34385
34702
|
import { existsSync as existsSync15 } from "fs";
|
|
34386
|
-
import
|
|
34703
|
+
import path63 from "path";
|
|
34387
34704
|
function byRole(page, role, name, opts) {
|
|
34388
34705
|
const loc = page.getByRole(role, { name, exact: false });
|
|
34389
34706
|
if (opts.nth !== void 0) {
|
|
@@ -34684,7 +35001,7 @@ async function fillIndexedElement(page, index, text, submit) {
|
|
|
34684
35001
|
await fillOrSelect(loc.first(), text, submit);
|
|
34685
35002
|
}
|
|
34686
35003
|
async function setFilesOnElement(el, filePath) {
|
|
34687
|
-
const resolved =
|
|
35004
|
+
const resolved = path63.resolve(filePath);
|
|
34688
35005
|
if (SENSITIVE_UPLOAD_PATH.test(resolved)) {
|
|
34689
35006
|
throw new Error(
|
|
34690
35007
|
`Refusing to upload sensitive path: "${resolved}". Choose a test fixture file inside the project or temp directory.`
|
|
@@ -34917,7 +35234,7 @@ var init_browse_actions = __esm({
|
|
|
34917
35234
|
|
|
34918
35235
|
// src/commands/step-numbering.ts
|
|
34919
35236
|
import { open, readFile as readFile6, readdir, stat as stat2, unlink } from "fs/promises";
|
|
34920
|
-
import
|
|
35237
|
+
import path64 from "path";
|
|
34921
35238
|
function reservationMarker() {
|
|
34922
35239
|
const marker = {
|
|
34923
35240
|
reservation: { pid: process.pid, ts: Date.now() }
|
|
@@ -34959,7 +35276,7 @@ async function peekNextStepNumber(stepsDir) {
|
|
|
34959
35276
|
for (const f of files) {
|
|
34960
35277
|
const m = /^(\d+)\.json$/.exec(f);
|
|
34961
35278
|
if (!m) continue;
|
|
34962
|
-
const fullPath =
|
|
35279
|
+
const fullPath = path64.join(stepsDir, f);
|
|
34963
35280
|
const st = await stat2(fullPath).catch(() => null);
|
|
34964
35281
|
if (!st) continue;
|
|
34965
35282
|
if (await isAbandonedReservation(fullPath, st)) {
|
|
@@ -34977,7 +35294,7 @@ async function reserveStepFile(stepsDir, limit) {
|
|
|
34977
35294
|
if (candidate > limit.maxSteps) {
|
|
34978
35295
|
throw new Error(stepLimitMessage(limit.maxSteps, limit.runId));
|
|
34979
35296
|
}
|
|
34980
|
-
const stepPath =
|
|
35297
|
+
const stepPath = path64.join(stepsDir, stepFileName(candidate));
|
|
34981
35298
|
try {
|
|
34982
35299
|
const handle = await open(stepPath, "wx");
|
|
34983
35300
|
try {
|
|
@@ -35023,7 +35340,7 @@ __export(step_exports, {
|
|
|
35023
35340
|
});
|
|
35024
35341
|
import { existsSync as existsSync16 } from "fs";
|
|
35025
35342
|
import { copyFile, mkdir as mkdir4, rename, stat as stat3 } from "fs/promises";
|
|
35026
|
-
import
|
|
35343
|
+
import path65 from "path";
|
|
35027
35344
|
import chalk31 from "chalk";
|
|
35028
35345
|
function parseMaxSteps(val) {
|
|
35029
35346
|
if (typeof val === "number" && Number.isFinite(val) && val > 0) return val;
|
|
@@ -35039,11 +35356,11 @@ function parseMaxSteps(val) {
|
|
|
35039
35356
|
}
|
|
35040
35357
|
async function recordStep(input) {
|
|
35041
35358
|
const runDirPath = runDir(input.projectRoot, input.runId);
|
|
35042
|
-
if (!existsSync16(
|
|
35359
|
+
if (!existsSync16(path65.join(runDirPath, "walkthrough.json"))) {
|
|
35043
35360
|
throw new Error(`Run not found: ${input.runId}`);
|
|
35044
35361
|
}
|
|
35045
|
-
const stepsDir =
|
|
35046
|
-
const screenshotsDir =
|
|
35362
|
+
const stepsDir = path65.join(runDirPath, "steps");
|
|
35363
|
+
const screenshotsDir = path65.join(runDirPath, "screenshots");
|
|
35047
35364
|
await mkdir4(stepsDir, { recursive: true });
|
|
35048
35365
|
await mkdir4(screenshotsDir, { recursive: true });
|
|
35049
35366
|
const shotSource = input.shot ? await existingShot(input.shot) : null;
|
|
@@ -35058,9 +35375,9 @@ async function recordStep(input) {
|
|
|
35058
35375
|
let screenshotRef = null;
|
|
35059
35376
|
if (shotSource) {
|
|
35060
35377
|
const destName = `step-${stepNumber2}.png`;
|
|
35061
|
-
const destPath =
|
|
35378
|
+
const destPath = path65.join(screenshotsDir, destName);
|
|
35062
35379
|
finalScreenshotPath = destPath;
|
|
35063
|
-
if (
|
|
35380
|
+
if (path65.resolve(destPath) !== shotSource) {
|
|
35064
35381
|
if (input.moveShot) {
|
|
35065
35382
|
await rename(shotSource, destPath).catch(
|
|
35066
35383
|
() => copyFile(shotSource, destPath)
|
|
@@ -35069,16 +35386,16 @@ async function recordStep(input) {
|
|
|
35069
35386
|
await copyFile(shotSource, destPath);
|
|
35070
35387
|
}
|
|
35071
35388
|
}
|
|
35072
|
-
screenshotRef =
|
|
35389
|
+
screenshotRef = path65.posix.join("screenshots", destName);
|
|
35073
35390
|
}
|
|
35074
35391
|
let fullRef = null;
|
|
35075
35392
|
if (input.fullShot && existsSync16(input.fullShot)) {
|
|
35076
35393
|
const fullName = `step-${stepNumber2}.full.png`;
|
|
35077
35394
|
const moved = await rename(
|
|
35078
35395
|
input.fullShot,
|
|
35079
|
-
|
|
35396
|
+
path65.join(screenshotsDir, fullName)
|
|
35080
35397
|
).then(() => true).catch(() => false);
|
|
35081
|
-
if (moved) fullRef =
|
|
35398
|
+
if (moved) fullRef = path65.posix.join("screenshots", fullName);
|
|
35082
35399
|
}
|
|
35083
35400
|
parsedStep = runStepSchema.parse({
|
|
35084
35401
|
step_number: stepNumber2,
|
|
@@ -35125,7 +35442,7 @@ async function recordStep(input) {
|
|
|
35125
35442
|
};
|
|
35126
35443
|
}
|
|
35127
35444
|
async function existingShot(shot) {
|
|
35128
|
-
const resolved =
|
|
35445
|
+
const resolved = path65.resolve(shot);
|
|
35129
35446
|
const isFile = await stat3(resolved).then((s) => s.isFile()).catch(() => false);
|
|
35130
35447
|
if (!isFile) {
|
|
35131
35448
|
throw new Error(
|
|
@@ -35186,7 +35503,7 @@ async function stepCommand(runArg, opts) {
|
|
|
35186
35503
|
{
|
|
35187
35504
|
step_number: res.step.step_number,
|
|
35188
35505
|
cancelled: true,
|
|
35189
|
-
path:
|
|
35506
|
+
path: path65.relative(projectRoot, res.stepPath).split(path65.sep).join("/"),
|
|
35190
35507
|
action: res.step.action,
|
|
35191
35508
|
note: res.step.note,
|
|
35192
35509
|
url: res.step.url,
|
|
@@ -35214,7 +35531,7 @@ async function stepCommand(runArg, opts) {
|
|
|
35214
35531
|
JSON.stringify(
|
|
35215
35532
|
{
|
|
35216
35533
|
step_number: res.step.step_number,
|
|
35217
|
-
path:
|
|
35534
|
+
path: path65.relative(projectRoot, res.stepPath).split(path65.sep).join("/"),
|
|
35218
35535
|
action: res.step.action,
|
|
35219
35536
|
note: res.step.note,
|
|
35220
35537
|
url: res.step.url,
|
|
@@ -35243,7 +35560,7 @@ async function stepCommand(runArg, opts) {
|
|
|
35243
35560
|
console.log(` ${chalk31.dim("shot:")} ${res.step.screenshot}`);
|
|
35244
35561
|
}
|
|
35245
35562
|
console.log(
|
|
35246
|
-
` ${chalk31.dim("file:")} ${
|
|
35563
|
+
` ${chalk31.dim("file:")} ${path65.relative(projectRoot, res.stepPath)}`
|
|
35247
35564
|
);
|
|
35248
35565
|
console.log();
|
|
35249
35566
|
} else {
|
|
@@ -35315,16 +35632,16 @@ import {
|
|
|
35315
35632
|
import { rm as rm2 } from "fs/promises";
|
|
35316
35633
|
import { createServer } from "net";
|
|
35317
35634
|
import { homedir as homedir3 } from "os";
|
|
35318
|
-
import
|
|
35635
|
+
import path66 from "path";
|
|
35319
35636
|
import chalk32 from "chalk";
|
|
35320
35637
|
function stateDir() {
|
|
35321
|
-
return
|
|
35322
|
-
process.env.IAMU_HOME ??
|
|
35638
|
+
return path66.join(
|
|
35639
|
+
process.env.IAMU_HOME ?? path66.join(homedir3(), ".iamu"),
|
|
35323
35640
|
"browse"
|
|
35324
35641
|
);
|
|
35325
35642
|
}
|
|
35326
35643
|
function profileDir() {
|
|
35327
|
-
return
|
|
35644
|
+
return path66.join(stateDir(), "profile");
|
|
35328
35645
|
}
|
|
35329
35646
|
function privateDir(dir) {
|
|
35330
35647
|
mkdirSync3(dir, { recursive: true, mode: 448 });
|
|
@@ -35335,7 +35652,7 @@ function privateDir(dir) {
|
|
|
35335
35652
|
}
|
|
35336
35653
|
function stateFile(run) {
|
|
35337
35654
|
const key = run && STATE_KEY.test(run) && `${run}.json` !== SHARED_STATE ? `${run}.json` : SHARED_STATE;
|
|
35338
|
-
return
|
|
35655
|
+
return path66.join(stateDir(), key);
|
|
35339
35656
|
}
|
|
35340
35657
|
function loadState(file) {
|
|
35341
35658
|
try {
|
|
@@ -35376,7 +35693,7 @@ function storedStates() {
|
|
|
35376
35693
|
}
|
|
35377
35694
|
const out2 = [];
|
|
35378
35695
|
for (const name of names2.sort()) {
|
|
35379
|
-
const file =
|
|
35696
|
+
const file = path66.join(stateDir(), name);
|
|
35380
35697
|
const state = loadState(file);
|
|
35381
35698
|
if (state) out2.push({ state, file });
|
|
35382
35699
|
}
|
|
@@ -35394,16 +35711,16 @@ function removeState(file) {
|
|
|
35394
35711
|
}
|
|
35395
35712
|
function runStillOpen(state, now = Date.now()) {
|
|
35396
35713
|
if (!state.run || !state.project) return false;
|
|
35397
|
-
const dir =
|
|
35714
|
+
const dir = path66.join(state.project, "iamu", ".runs", state.run);
|
|
35398
35715
|
try {
|
|
35399
|
-
const manifestPath =
|
|
35716
|
+
const manifestPath = path66.join(dir, "walkthrough.json");
|
|
35400
35717
|
const manifest = JSON.parse(readFileSync10(manifestPath, "utf8"));
|
|
35401
35718
|
if (typeof manifest.status !== "string" || !OPEN_RUN_STATUSES.has(manifest.status)) {
|
|
35402
35719
|
return false;
|
|
35403
35720
|
}
|
|
35404
35721
|
let last = statSync3(manifestPath).mtimeMs;
|
|
35405
35722
|
try {
|
|
35406
|
-
last = Math.max(last, statSync3(
|
|
35723
|
+
last = Math.max(last, statSync3(path66.join(dir, "steps")).mtimeMs);
|
|
35407
35724
|
} catch {
|
|
35408
35725
|
}
|
|
35409
35726
|
return now - last < RUN_IDLE_MS;
|
|
@@ -35502,7 +35819,7 @@ async function requestedViewport(opts) {
|
|
|
35502
35819
|
let manifest;
|
|
35503
35820
|
try {
|
|
35504
35821
|
manifest = JSON.parse(
|
|
35505
|
-
readFileSync10(
|
|
35822
|
+
readFileSync10(path66.join(dir, "walkthrough.json"), "utf8")
|
|
35506
35823
|
);
|
|
35507
35824
|
} catch {
|
|
35508
35825
|
fail(
|
|
@@ -35681,7 +35998,7 @@ function computePageFingerprint(url, bodySnippet) {
|
|
|
35681
35998
|
return createHash6("sha256").update(`${url}|${bodySnippet.trim().replace(/\s+/g, " ")}`).digest("hex").slice(0, 16);
|
|
35682
35999
|
}
|
|
35683
36000
|
function checkAndRecordStagnation(runDirPath, fingerprint, action) {
|
|
35684
|
-
const cacheFile =
|
|
36001
|
+
const cacheFile = path66.join(runDirPath, ".stagnation.json");
|
|
35685
36002
|
let previous = null;
|
|
35686
36003
|
try {
|
|
35687
36004
|
if (existsSync17(cacheFile)) {
|
|
@@ -35962,8 +36279,8 @@ async function actOnPage(opts, attached, state, fn, onPostShot) {
|
|
|
35962
36279
|
let contentSnippet;
|
|
35963
36280
|
if (opts.shot) {
|
|
35964
36281
|
await waitForVisualStability(page);
|
|
35965
|
-
screenshot =
|
|
35966
|
-
mkdirSync3(
|
|
36282
|
+
screenshot = path66.resolve(opts.shot);
|
|
36283
|
+
mkdirSync3(path66.dirname(screenshot), { recursive: true });
|
|
35967
36284
|
await page.screenshot({
|
|
35968
36285
|
path: screenshot,
|
|
35969
36286
|
fullPage: Boolean(opts.full),
|
|
@@ -36173,7 +36490,7 @@ function httpOrigin(raw) {
|
|
|
36173
36490
|
function readTargetUrl(runDirPath) {
|
|
36174
36491
|
try {
|
|
36175
36492
|
const m = JSON.parse(
|
|
36176
|
-
readFileSync10(
|
|
36493
|
+
readFileSync10(path66.join(runDirPath, "walkthrough.json"), "utf8")
|
|
36177
36494
|
);
|
|
36178
36495
|
return typeof m.target_url === "string" ? m.target_url : null;
|
|
36179
36496
|
} catch {
|
|
@@ -36185,7 +36502,7 @@ function fullFramePlan(runDirPath, url, scrolled) {
|
|
|
36185
36502
|
let manifest = {};
|
|
36186
36503
|
try {
|
|
36187
36504
|
manifest = JSON.parse(
|
|
36188
|
-
readFileSync10(
|
|
36505
|
+
readFileSync10(path66.join(runDirPath, "walkthrough.json"), "utf8")
|
|
36189
36506
|
);
|
|
36190
36507
|
} catch {
|
|
36191
36508
|
}
|
|
@@ -36196,7 +36513,7 @@ function fullFramePlan(runDirPath, url, scrolled) {
|
|
|
36196
36513
|
if (designer2.includes(key) || scrolled) {
|
|
36197
36514
|
return { take: true, lastHeight: null };
|
|
36198
36515
|
}
|
|
36199
|
-
const stepsDir =
|
|
36516
|
+
const stepsDir = path66.join(runDirPath, "steps");
|
|
36200
36517
|
let names2 = [];
|
|
36201
36518
|
try {
|
|
36202
36519
|
names2 = readdirSync3(stepsDir).filter((n) => /^\d+\.json$/.test(n)).sort();
|
|
@@ -36207,7 +36524,7 @@ function fullFramePlan(runDirPath, url, scrolled) {
|
|
|
36207
36524
|
for (const name of names2) {
|
|
36208
36525
|
try {
|
|
36209
36526
|
const earlier = JSON.parse(
|
|
36210
|
-
readFileSync10(
|
|
36527
|
+
readFileSync10(path66.join(stepsDir, name), "utf8")
|
|
36211
36528
|
);
|
|
36212
36529
|
if (typeof earlier.url === "string" && stepScreenKey(earlier.url, target) === key) {
|
|
36213
36530
|
seen = true;
|
|
@@ -36274,7 +36591,7 @@ function printStep(opts, record, projectRoot, stagnationWarning, challengeWarnin
|
|
|
36274
36591
|
if (opts.json) {
|
|
36275
36592
|
const doc = {
|
|
36276
36593
|
step_number: step.step_number,
|
|
36277
|
-
path:
|
|
36594
|
+
path: path66.relative(projectRoot, record.stepPath).split(path66.sep).join("/"),
|
|
36278
36595
|
action: step.action,
|
|
36279
36596
|
note: step.note,
|
|
36280
36597
|
url: step.url,
|
|
@@ -36392,7 +36709,7 @@ async function clearStorage(opts, driver) {
|
|
|
36392
36709
|
try {
|
|
36393
36710
|
if (dir) {
|
|
36394
36711
|
target = JSON.parse(
|
|
36395
|
-
readFileSync10(
|
|
36712
|
+
readFileSync10(path66.join(dir, "walkthrough.json"), "utf8")
|
|
36396
36713
|
).target_url;
|
|
36397
36714
|
}
|
|
36398
36715
|
} catch {
|
|
@@ -36579,15 +36896,15 @@ async function browseStep(args2, opts, driver) {
|
|
|
36579
36896
|
))
|
|
36580
36897
|
return;
|
|
36581
36898
|
const maxSteps = parseMaxSteps(opts.maxSteps);
|
|
36582
|
-
if (await peekNextStepNumber(
|
|
36899
|
+
if (await peekNextStepNumber(path66.join(runDirPath, "steps")) > maxSteps) {
|
|
36583
36900
|
return fail(stepLimitMessage(maxSteps, opts.run), opts);
|
|
36584
36901
|
}
|
|
36585
|
-
const pendingShot =
|
|
36902
|
+
const pendingShot = path66.join(
|
|
36586
36903
|
runDirPath,
|
|
36587
36904
|
"screenshots",
|
|
36588
36905
|
`.pending-${randomUUID11()}.png`
|
|
36589
36906
|
);
|
|
36590
|
-
const pendingFull =
|
|
36907
|
+
const pendingFull = path66.join(
|
|
36591
36908
|
runDirPath,
|
|
36592
36909
|
"screenshots",
|
|
36593
36910
|
`.pending-${randomUUID11()}.full.png`
|
|
@@ -38470,18 +38787,18 @@ __export(preflight_exports, {
|
|
|
38470
38787
|
});
|
|
38471
38788
|
import { spawnSync } from "child_process";
|
|
38472
38789
|
import { existsSync as existsSync20, promises as fs67 } from "fs";
|
|
38473
|
-
import
|
|
38790
|
+
import path67 from "path";
|
|
38474
38791
|
import chalk36 from "chalk";
|
|
38475
38792
|
function detectPackageManager(targetDir) {
|
|
38476
|
-
if (existsSync20(
|
|
38477
|
-
if (existsSync20(
|
|
38478
|
-
if (existsSync20(
|
|
38793
|
+
if (existsSync20(path67.join(targetDir, "pnpm-lock.yaml"))) return "pnpm";
|
|
38794
|
+
if (existsSync20(path67.join(targetDir, "yarn.lock"))) return "yarn";
|
|
38795
|
+
if (existsSync20(path67.join(targetDir, "bun.lockb")) || existsSync20(path67.join(targetDir, "bun.lock"))) {
|
|
38479
38796
|
return "bun";
|
|
38480
38797
|
}
|
|
38481
38798
|
return "npm";
|
|
38482
38799
|
}
|
|
38483
38800
|
async function detectPreflightCommand(targetDir) {
|
|
38484
|
-
const pkgPath =
|
|
38801
|
+
const pkgPath = path67.join(targetDir, "package.json");
|
|
38485
38802
|
if (existsSync20(pkgPath)) {
|
|
38486
38803
|
try {
|
|
38487
38804
|
const raw = await fs67.readFile(pkgPath, "utf8");
|
|
@@ -38494,7 +38811,7 @@ async function detectPreflightCommand(targetDir) {
|
|
|
38494
38811
|
} catch {
|
|
38495
38812
|
}
|
|
38496
38813
|
}
|
|
38497
|
-
const tsconfigPath =
|
|
38814
|
+
const tsconfigPath = path67.join(targetDir, "tsconfig.json");
|
|
38498
38815
|
if (existsSync20(tsconfigPath)) {
|
|
38499
38816
|
return { command: "npx", args: ["--no-install", "tsc", "--noEmit"] };
|
|
38500
38817
|
}
|
|
@@ -38533,7 +38850,7 @@ async function runPreflightCheck(targetDir) {
|
|
|
38533
38850
|
};
|
|
38534
38851
|
}
|
|
38535
38852
|
async function preflight(opts = {}) {
|
|
38536
|
-
const targetDir = opts.cwd ?
|
|
38853
|
+
const targetDir = opts.cwd ? path67.resolve(opts.cwd) : process.cwd();
|
|
38537
38854
|
const result = await runPreflightCheck(targetDir);
|
|
38538
38855
|
if (result.ok) {
|
|
38539
38856
|
if (opts.json) {
|
|
@@ -39042,12 +39359,12 @@ __export(validate_exports, {
|
|
|
39042
39359
|
});
|
|
39043
39360
|
import { randomUUID as randomUUID12 } from "crypto";
|
|
39044
39361
|
import { promises as fs68 } from "fs";
|
|
39045
|
-
import
|
|
39362
|
+
import path68 from "path";
|
|
39046
39363
|
import chalk38 from "chalk";
|
|
39047
39364
|
function inferTypeFromPath(filePath, projectRoot) {
|
|
39048
|
-
const rel =
|
|
39049
|
-
if (rel.startsWith("..") ||
|
|
39050
|
-
const segments = rel.split(
|
|
39365
|
+
const rel = path68.relative(path68.join(projectRoot, "iamu"), filePath);
|
|
39366
|
+
if (rel.startsWith("..") || path68.isAbsolute(rel)) return null;
|
|
39367
|
+
const segments = rel.split(path68.sep);
|
|
39051
39368
|
if (segments.length < 2) return null;
|
|
39052
39369
|
return TYPE_BY_ALIAS.get(segments[0]) ?? null;
|
|
39053
39370
|
}
|
|
@@ -39064,7 +39381,7 @@ function applyZodValidation(schema2, data, result) {
|
|
|
39064
39381
|
}
|
|
39065
39382
|
function validateFile(filePath, raw, projectRoot) {
|
|
39066
39383
|
const result = { path: filePath, valid: true, errors: [] };
|
|
39067
|
-
if (
|
|
39384
|
+
if (path68.basename(filePath) === "config.json") {
|
|
39068
39385
|
let parsedJson;
|
|
39069
39386
|
try {
|
|
39070
39387
|
parsedJson = JSON.parse(raw);
|
|
@@ -39091,11 +39408,11 @@ function validateFile(filePath, raw, projectRoot) {
|
|
|
39091
39408
|
);
|
|
39092
39409
|
return result;
|
|
39093
39410
|
}
|
|
39094
|
-
if (
|
|
39411
|
+
if (path68.basename(filePath) === "testability.md") {
|
|
39095
39412
|
applyZodValidation(testabilityFrontmatterSchema, frontmatter, result);
|
|
39096
39413
|
return result;
|
|
39097
39414
|
}
|
|
39098
|
-
if (
|
|
39415
|
+
if (path68.basename(filePath) === "context.yml") {
|
|
39099
39416
|
applyZodValidation(contextFrontmatterSchema, frontmatter, result);
|
|
39100
39417
|
const warnings = [];
|
|
39101
39418
|
const hasNotes = Boolean(
|
|
@@ -39157,7 +39474,7 @@ async function buildRepairLookups(projectRoot) {
|
|
|
39157
39474
|
}
|
|
39158
39475
|
}
|
|
39159
39476
|
const runScenario = /* @__PURE__ */ new Map();
|
|
39160
|
-
const runsDir2 =
|
|
39477
|
+
const runsDir2 = path68.join(projectRoot, "iamu", ".runs");
|
|
39161
39478
|
let runIds = [];
|
|
39162
39479
|
try {
|
|
39163
39480
|
runIds = await fs68.readdir(runsDir2);
|
|
@@ -39167,7 +39484,7 @@ async function buildRepairLookups(projectRoot) {
|
|
|
39167
39484
|
try {
|
|
39168
39485
|
const manifest = JSON.parse(
|
|
39169
39486
|
await fs68.readFile(
|
|
39170
|
-
|
|
39487
|
+
path68.join(runsDir2, runId, "walkthrough.json"),
|
|
39171
39488
|
"utf8"
|
|
39172
39489
|
)
|
|
39173
39490
|
);
|
|
@@ -39198,7 +39515,7 @@ async function fixFindings(projectRoot, results, dryRun) {
|
|
|
39198
39515
|
parsed.frontmatter,
|
|
39199
39516
|
{
|
|
39200
39517
|
...lookups,
|
|
39201
|
-
fileSlug:
|
|
39518
|
+
fileSlug: path68.basename(result.path, ".md"),
|
|
39202
39519
|
newId: randomUUID12,
|
|
39203
39520
|
body: parsed.body
|
|
39204
39521
|
}
|
|
@@ -39240,7 +39557,7 @@ async function validate(filePath, opts) {
|
|
|
39240
39557
|
if (process.exitCode === 1) return;
|
|
39241
39558
|
const results = [];
|
|
39242
39559
|
if (filePath) {
|
|
39243
|
-
const abs =
|
|
39560
|
+
const abs = path68.isAbsolute(filePath) ? filePath : path68.resolve(process.cwd(), filePath);
|
|
39244
39561
|
try {
|
|
39245
39562
|
const raw = await fs68.readFile(abs, "utf8");
|
|
39246
39563
|
results.push(validateFile(abs, raw, projectRoot));
|
|
@@ -39256,7 +39573,7 @@ async function validate(filePath, opts) {
|
|
|
39256
39573
|
return;
|
|
39257
39574
|
}
|
|
39258
39575
|
} else if (opts.run) {
|
|
39259
|
-
const findingsDir =
|
|
39576
|
+
const findingsDir = path68.join(projectRoot, "iamu", ENTITY_DIRS.finding);
|
|
39260
39577
|
let names2 = [];
|
|
39261
39578
|
try {
|
|
39262
39579
|
names2 = await fs68.readdir(findingsDir);
|
|
@@ -39264,7 +39581,7 @@ async function validate(filePath, opts) {
|
|
|
39264
39581
|
}
|
|
39265
39582
|
for (const name of names2) {
|
|
39266
39583
|
if (!name.endsWith(".md")) continue;
|
|
39267
|
-
const abs =
|
|
39584
|
+
const abs = path68.join(findingsDir, name);
|
|
39268
39585
|
try {
|
|
39269
39586
|
const raw = await fs68.readFile(abs, "utf8");
|
|
39270
39587
|
const { frontmatter } = parseFrontmatter(raw);
|
|
@@ -39273,7 +39590,7 @@ async function validate(filePath, opts) {
|
|
|
39273
39590
|
}
|
|
39274
39591
|
} catch {
|
|
39275
39592
|
results.push({
|
|
39276
|
-
path:
|
|
39593
|
+
path: path68.relative(projectRoot, abs),
|
|
39277
39594
|
valid: false,
|
|
39278
39595
|
errors: ["Cannot read file or parse frontmatter"]
|
|
39279
39596
|
});
|
|
@@ -39306,7 +39623,7 @@ async function validate(filePath, opts) {
|
|
|
39306
39623
|
} else {
|
|
39307
39624
|
const typesToCheck = typeFilter ? ENTITY_TYPES.filter((t) => typeFilter.has(t)) : ENTITY_TYPES;
|
|
39308
39625
|
for (const type2 of typesToCheck) {
|
|
39309
|
-
const iamuTypeDir =
|
|
39626
|
+
const iamuTypeDir = path68.join(projectRoot, "iamu", ENTITY_DIRS[type2]);
|
|
39310
39627
|
let names2;
|
|
39311
39628
|
try {
|
|
39312
39629
|
names2 = await fs68.readdir(iamuTypeDir);
|
|
@@ -39315,13 +39632,13 @@ async function validate(filePath, opts) {
|
|
|
39315
39632
|
}
|
|
39316
39633
|
for (const name of names2) {
|
|
39317
39634
|
if (!name.endsWith(".md")) continue;
|
|
39318
|
-
const abs =
|
|
39635
|
+
const abs = path68.join(iamuTypeDir, name);
|
|
39319
39636
|
try {
|
|
39320
39637
|
const raw = await fs68.readFile(abs, "utf8");
|
|
39321
39638
|
results.push(validateFile(abs, raw, projectRoot));
|
|
39322
39639
|
} catch {
|
|
39323
39640
|
results.push({
|
|
39324
|
-
path:
|
|
39641
|
+
path: path68.relative(projectRoot, abs),
|
|
39325
39642
|
valid: false,
|
|
39326
39643
|
errors: ["Cannot read file"]
|
|
39327
39644
|
});
|
|
@@ -39329,7 +39646,7 @@ async function validate(filePath, opts) {
|
|
|
39329
39646
|
}
|
|
39330
39647
|
}
|
|
39331
39648
|
for (const extraFile of ["context.yml", "testability.md", "config.json"]) {
|
|
39332
|
-
const extraPath =
|
|
39649
|
+
const extraPath = path68.join(projectRoot, "iamu", extraFile);
|
|
39333
39650
|
try {
|
|
39334
39651
|
const raw = await fs68.readFile(extraPath, "utf8");
|
|
39335
39652
|
results.push(validateFile(extraPath, raw, projectRoot));
|
|
@@ -39355,19 +39672,19 @@ async function validate(filePath, opts) {
|
|
|
39355
39672
|
valid,
|
|
39356
39673
|
errors: results.filter((r) => !r.valid).flatMap(
|
|
39357
39674
|
(r) => r.errors.map((e) => ({
|
|
39358
|
-
path:
|
|
39675
|
+
path: path68.relative(projectRoot, r.path),
|
|
39359
39676
|
message: e
|
|
39360
39677
|
}))
|
|
39361
39678
|
),
|
|
39362
39679
|
warnings: results.filter((r) => r.warnings && r.warnings.length > 0).flatMap(
|
|
39363
39680
|
(r) => (r.warnings ?? []).map((w) => ({
|
|
39364
|
-
path:
|
|
39681
|
+
path: path68.relative(projectRoot, r.path),
|
|
39365
39682
|
message: w
|
|
39366
39683
|
}))
|
|
39367
39684
|
),
|
|
39368
39685
|
...opts.fix ? {
|
|
39369
39686
|
repaired: repaired.map((r) => ({
|
|
39370
|
-
path:
|
|
39687
|
+
path: path68.relative(projectRoot, r.path),
|
|
39371
39688
|
changes: r.repairs,
|
|
39372
39689
|
valid: r.valid
|
|
39373
39690
|
}))
|
|
@@ -39389,7 +39706,7 @@ async function validate(filePath, opts) {
|
|
|
39389
39706
|
}
|
|
39390
39707
|
let hasErrors = false;
|
|
39391
39708
|
for (const r of results) {
|
|
39392
|
-
const relPath =
|
|
39709
|
+
const relPath = path68.relative(projectRoot, r.path);
|
|
39393
39710
|
if (r.valid) {
|
|
39394
39711
|
if (r.warnings && r.warnings.length > 0) {
|
|
39395
39712
|
console.log(` ${symbols.warning} ` + chalk38.yellow(relPath));
|
|
@@ -39584,7 +39901,7 @@ __export(new_entity_exports, {
|
|
|
39584
39901
|
});
|
|
39585
39902
|
import { randomUUID as randomUUID13 } from "crypto";
|
|
39586
39903
|
import { readFile as readFile8 } from "fs/promises";
|
|
39587
|
-
import
|
|
39904
|
+
import path69 from "path";
|
|
39588
39905
|
import chalk40 from "chalk";
|
|
39589
39906
|
async function resolveBodyInput(opts, command, flag = "--body") {
|
|
39590
39907
|
if (opts.bodyFile !== void 0) {
|
|
@@ -39597,7 +39914,7 @@ async function resolveBodyInput(opts, command, flag = "--body") {
|
|
|
39597
39914
|
return null;
|
|
39598
39915
|
}
|
|
39599
39916
|
try {
|
|
39600
|
-
const body = opts.bodyFile === "-" ? await readStdinText() : await readFile8(
|
|
39917
|
+
const body = opts.bodyFile === "-" ? await readStdinText() : await readFile8(path69.resolve(process.cwd(), opts.bodyFile), "utf8");
|
|
39601
39918
|
return { body };
|
|
39602
39919
|
} catch (err) {
|
|
39603
39920
|
commandError(
|
|
@@ -39847,7 +40164,7 @@ __export(set_entity_exports, {
|
|
|
39847
40164
|
setEntity: () => setEntity
|
|
39848
40165
|
});
|
|
39849
40166
|
import { promises as fs69 } from "fs";
|
|
39850
|
-
import
|
|
40167
|
+
import path70 from "path";
|
|
39851
40168
|
import chalk41 from "chalk";
|
|
39852
40169
|
import { ZodError as ZodError2 } from "zod";
|
|
39853
40170
|
function normalizeAuthStrategy(raw) {
|
|
@@ -40038,7 +40355,7 @@ async function setEntity(type2, slug, opts) {
|
|
|
40038
40355
|
}
|
|
40039
40356
|
raw = Buffer.concat(chunks).toString("utf8");
|
|
40040
40357
|
} else {
|
|
40041
|
-
raw = await fs69.readFile(
|
|
40358
|
+
raw = await fs69.readFile(path70.resolve(projectRoot, opts.file), "utf8");
|
|
40042
40359
|
}
|
|
40043
40360
|
} catch (err) {
|
|
40044
40361
|
commandError(
|
|
@@ -40624,7 +40941,7 @@ __export(edit_entity_exports, {
|
|
|
40624
40941
|
});
|
|
40625
40942
|
import { spawn as spawn2 } from "child_process";
|
|
40626
40943
|
import { promises as fs70 } from "fs";
|
|
40627
|
-
import
|
|
40944
|
+
import path71 from "path";
|
|
40628
40945
|
import chalk42 from "chalk";
|
|
40629
40946
|
async function editEntity(type2, slug, opts = {}) {
|
|
40630
40947
|
const ctx = await resolveProject(opts);
|
|
@@ -40648,7 +40965,7 @@ async function editEntity(type2, slug, opts = {}) {
|
|
|
40648
40965
|
}
|
|
40649
40966
|
const effectiveSlug = isSpecial ? type2 : slug;
|
|
40650
40967
|
const resolvedPath = isSpecial ? type2 === "testability" ? testabilityPath(projectRoot) : contextPath(projectRoot) : await resolveEntityPath(projectRoot, type2, effectiveSlug);
|
|
40651
|
-
const filePath = resolvedPath ??
|
|
40968
|
+
const filePath = resolvedPath ?? path71.join(
|
|
40652
40969
|
projectRoot,
|
|
40653
40970
|
"iamu",
|
|
40654
40971
|
ENTITY_DIRS[type2],
|
|
@@ -40659,7 +40976,7 @@ async function editEntity(type2, slug, opts = {}) {
|
|
|
40659
40976
|
await fs70.access(filePath);
|
|
40660
40977
|
} catch {
|
|
40661
40978
|
commandError(
|
|
40662
|
-
`${type2}${isSpecial ? "" : ` "${effectiveSlug}"`} not found at ${
|
|
40979
|
+
`${type2}${isSpecial ? "" : ` "${effectiveSlug}"`} not found at ${path71.relative(projectRoot, filePath)}.`,
|
|
40663
40980
|
{
|
|
40664
40981
|
...opts,
|
|
40665
40982
|
code: "entity_not_found",
|
|
@@ -40670,7 +40987,7 @@ async function editEntity(type2, slug, opts = {}) {
|
|
|
40670
40987
|
}
|
|
40671
40988
|
if (!process.stdin.isTTY || process.env.CI) {
|
|
40672
40989
|
commandError(
|
|
40673
|
-
`Cannot open editor in a non-interactive environment. Edit the file directly: ${
|
|
40990
|
+
`Cannot open editor in a non-interactive environment. Edit the file directly: ${path71.relative(projectRoot, filePath)}`,
|
|
40674
40991
|
{
|
|
40675
40992
|
...opts,
|
|
40676
40993
|
code: "non_interactive_environment",
|
|
@@ -40746,7 +41063,7 @@ async function editEntity(type2, slug, opts = {}) {
|
|
|
40746
41063
|
console.log();
|
|
40747
41064
|
console.log(
|
|
40748
41065
|
chalk42.dim(
|
|
40749
|
-
` Fix the file and run: iamu validate ${
|
|
41066
|
+
` Fix the file and run: iamu validate ${path71.relative(projectRoot, filePath)}`
|
|
40750
41067
|
)
|
|
40751
41068
|
);
|
|
40752
41069
|
console.log();
|
|
@@ -40776,7 +41093,7 @@ async function editEntity(type2, slug, opts = {}) {
|
|
|
40776
41093
|
{
|
|
40777
41094
|
...opts,
|
|
40778
41095
|
code: "validation_error",
|
|
40779
|
-
fix: `iamu validate ${
|
|
41096
|
+
fix: `iamu validate ${path71.relative(projectRoot, filePath)}`
|
|
40780
41097
|
}
|
|
40781
41098
|
);
|
|
40782
41099
|
}
|
|
@@ -40945,7 +41262,7 @@ async function crawlFrom(page, opts, origin, takeBlocked) {
|
|
|
40945
41262
|
const byKey = /* @__PURE__ */ new Map();
|
|
40946
41263
|
const byPlaceView = /* @__PURE__ */ new Map();
|
|
40947
41264
|
const signatureOf = (s) => exploreSignature(s.headings, s.title);
|
|
40948
|
-
function addSurface(state, place, view,
|
|
41265
|
+
function addSurface(state, place, view, path78, depth) {
|
|
40949
41266
|
const key = exploreSurfaceKey(place, view);
|
|
40950
41267
|
const surface = {
|
|
40951
41268
|
key,
|
|
@@ -40957,7 +41274,7 @@ async function crawlFrom(page, opts, origin, takeBlocked) {
|
|
|
40957
41274
|
headings: state.headings,
|
|
40958
41275
|
fields: state.fields,
|
|
40959
41276
|
depth,
|
|
40960
|
-
path:
|
|
41277
|
+
path: path78,
|
|
40961
41278
|
actions: []
|
|
40962
41279
|
};
|
|
40963
41280
|
surfaces.push(surface);
|
|
@@ -40975,9 +41292,9 @@ ${surface.signature}`, key);
|
|
|
40975
41292
|
});
|
|
40976
41293
|
await settle(page, settleMs);
|
|
40977
41294
|
}
|
|
40978
|
-
async function replay(
|
|
40979
|
-
if (!("goto" in (
|
|
40980
|
-
for (const step of
|
|
41295
|
+
async function replay(path78) {
|
|
41296
|
+
if (!("goto" in (path78[0] ?? {}))) await goto(opts.startUrl);
|
|
41297
|
+
for (const step of path78) {
|
|
40981
41298
|
if ("goto" in step) {
|
|
40982
41299
|
await goto(step.goto);
|
|
40983
41300
|
} else {
|
|
@@ -41104,8 +41421,8 @@ ${sig}`, key);
|
|
|
41104
41421
|
if (stats.surfaces >= budget.max_surfaces) {
|
|
41105
41422
|
stats.truncated = true;
|
|
41106
41423
|
} else {
|
|
41107
|
-
const
|
|
41108
|
-
addSurface(after, place, view,
|
|
41424
|
+
const path78 = !sameDoc && view === null ? [{ goto: after.url }] : [...surface.path, ...via, click];
|
|
41425
|
+
addSurface(after, place, view, path78, surface.depth + 1);
|
|
41109
41426
|
}
|
|
41110
41427
|
}
|
|
41111
41428
|
}
|
|
@@ -41375,7 +41692,7 @@ __export(explore_exports, {
|
|
|
41375
41692
|
exploreList: () => exploreList,
|
|
41376
41693
|
exploreRun: () => exploreRun
|
|
41377
41694
|
});
|
|
41378
|
-
import
|
|
41695
|
+
import path72 from "path";
|
|
41379
41696
|
import chalk43 from "chalk";
|
|
41380
41697
|
function fail2(message, json2) {
|
|
41381
41698
|
if (json2) console.error(JSON.stringify({ error: message }));
|
|
@@ -41515,7 +41832,7 @@ async function exploreRun(url, opts) {
|
|
|
41515
41832
|
surfaces: result.surfaces
|
|
41516
41833
|
};
|
|
41517
41834
|
const file = await writeExploreMap(ctx.projectRoot, map2);
|
|
41518
|
-
const rel =
|
|
41835
|
+
const rel = path72.relative(process.cwd(), file);
|
|
41519
41836
|
if (opts.json) {
|
|
41520
41837
|
console.log(JSON.stringify({ id: map2.id, path: rel, stats: map2.stats }));
|
|
41521
41838
|
return;
|
|
@@ -42711,7 +43028,7 @@ __export(new_finding_exports, {
|
|
|
42711
43028
|
});
|
|
42712
43029
|
import { randomUUID as randomUUID14 } from "crypto";
|
|
42713
43030
|
import { access, readFile as readFile9 } from "fs/promises";
|
|
42714
|
-
import
|
|
43031
|
+
import path73 from "path";
|
|
42715
43032
|
import chalk46 from "chalk";
|
|
42716
43033
|
async function newFinding(opts) {
|
|
42717
43034
|
const ctx = await resolveProject(opts);
|
|
@@ -42799,7 +43116,7 @@ async function newFinding(opts) {
|
|
|
42799
43116
|
}
|
|
42800
43117
|
try {
|
|
42801
43118
|
await access(
|
|
42802
|
-
|
|
43119
|
+
path73.join(runDirPath, "steps", `${String(n).padStart(3, "0")}.json`)
|
|
42803
43120
|
);
|
|
42804
43121
|
} catch {
|
|
42805
43122
|
commandError(
|
|
@@ -42818,7 +43135,7 @@ async function newFinding(opts) {
|
|
|
42818
43135
|
if (!scenarioSlug) {
|
|
42819
43136
|
try {
|
|
42820
43137
|
const manifestRaw = await readFile9(
|
|
42821
|
-
|
|
43138
|
+
path73.join(runDirPath, "walkthrough.json"),
|
|
42822
43139
|
"utf8"
|
|
42823
43140
|
);
|
|
42824
43141
|
const manifest = JSON.parse(manifestRaw);
|
|
@@ -42965,7 +43282,7 @@ __export(summary_exports, {
|
|
|
42965
43282
|
summaryCommand: () => summaryCommand
|
|
42966
43283
|
});
|
|
42967
43284
|
import { promises as fs71 } from "fs";
|
|
42968
|
-
import
|
|
43285
|
+
import path74 from "path";
|
|
42969
43286
|
import chalk47 from "chalk";
|
|
42970
43287
|
function endedFrom(value) {
|
|
42971
43288
|
if (!value || typeof value !== "object") return void 0;
|
|
@@ -42996,7 +43313,7 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
42996
43313
|
let openQuestions = [];
|
|
42997
43314
|
let setupNotes = [];
|
|
42998
43315
|
let model = opts.model?.trim() || void 0;
|
|
42999
|
-
const summaryPath =
|
|
43316
|
+
const summaryPath = path74.join(runDirPath, "summary.json");
|
|
43000
43317
|
let blocked;
|
|
43001
43318
|
let ended;
|
|
43002
43319
|
try {
|
|
@@ -43016,7 +43333,7 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
43016
43333
|
if (opts.file === "-") {
|
|
43017
43334
|
raw = await readStdinText();
|
|
43018
43335
|
} else {
|
|
43019
|
-
raw = await fs71.readFile(
|
|
43336
|
+
raw = await fs71.readFile(path74.resolve(projectRoot, opts.file), "utf8");
|
|
43020
43337
|
}
|
|
43021
43338
|
const parsed = JSON.parse(raw);
|
|
43022
43339
|
if (Array.isArray(parsed.what_worked)) {
|
|
@@ -43093,7 +43410,7 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
43093
43410
|
JSON.stringify(
|
|
43094
43411
|
{
|
|
43095
43412
|
run_id: runId,
|
|
43096
|
-
path:
|
|
43413
|
+
path: path74.relative(projectRoot, summaryPath),
|
|
43097
43414
|
summary: data
|
|
43098
43415
|
},
|
|
43099
43416
|
null,
|
|
@@ -43155,7 +43472,7 @@ __export(advise_exports, {
|
|
|
43155
43472
|
readAdvisePrompt: () => readAdvisePrompt
|
|
43156
43473
|
});
|
|
43157
43474
|
import { existsSync as existsSync23, promises as fs72 } from "fs";
|
|
43158
|
-
import
|
|
43475
|
+
import path75 from "path";
|
|
43159
43476
|
import chalk48 from "chalk";
|
|
43160
43477
|
function readAdvisePrompt(projectRoot) {
|
|
43161
43478
|
return readInstalledPrompt(projectRoot, "advise.md");
|
|
@@ -43213,7 +43530,7 @@ async function advise(runIdArg, opts = {}) {
|
|
|
43213
43530
|
findings2 = input.findings.length;
|
|
43214
43531
|
needed = findings2 >= 2;
|
|
43215
43532
|
if (!needed) reason = "fewer than two open findings in the project";
|
|
43216
|
-
promptPath = opts.promptFile ??
|
|
43533
|
+
promptPath = opts.promptFile ?? path75.join(analysisDir(projectRoot), "advise_prompt.md");
|
|
43217
43534
|
} else {
|
|
43218
43535
|
const runId = resolveRunId(projectRoot, runIdArg ?? opts.run);
|
|
43219
43536
|
if (!requireRunId(runId, opts, "iamu advise <run-id>")) return;
|
|
@@ -43224,12 +43541,12 @@ async function advise(runIdArg, opts = {}) {
|
|
|
43224
43541
|
input = await buildAdviseInput(projectRoot, runId, manifest.scenario_slug);
|
|
43225
43542
|
refsDir = runDirPath;
|
|
43226
43543
|
findings2 = input.findings.length;
|
|
43227
|
-
const advised = existsSync23(
|
|
43544
|
+
const advised = existsSync23(path75.join(runDirPath, ADVICE_FILE));
|
|
43228
43545
|
needed = !manifest.blocked && findings2 > 0 && !advised;
|
|
43229
43546
|
if (manifest.blocked) reason = "the run was blocked";
|
|
43230
43547
|
else if (advised) reason = "the run is already advised";
|
|
43231
43548
|
else if (findings2 === 0) reason = "no open findings to advise";
|
|
43232
|
-
promptPath = opts.promptFile ??
|
|
43549
|
+
promptPath = opts.promptFile ?? path75.join(runDirPath, "advise_prompt.md");
|
|
43233
43550
|
}
|
|
43234
43551
|
if (needed) input = await withCodeIndex(projectRoot, input);
|
|
43235
43552
|
const { bundle, refs: refs2 } = promptBundle(input);
|
|
@@ -43239,11 +43556,11 @@ async function advise(runIdArg, opts = {}) {
|
|
|
43239
43556
|
promptSizeWarning(prompt)
|
|
43240
43557
|
].filter((w) => w !== null) : [];
|
|
43241
43558
|
if (prompt) {
|
|
43242
|
-
await fs72.mkdir(
|
|
43559
|
+
await fs72.mkdir(path75.dirname(promptPath), { recursive: true });
|
|
43243
43560
|
await fs72.writeFile(promptPath, prompt, "utf8");
|
|
43244
43561
|
await fs72.mkdir(refsDir, { recursive: true });
|
|
43245
43562
|
await fs72.writeFile(
|
|
43246
|
-
|
|
43563
|
+
path75.join(refsDir, ADVISE_REFS_FILE),
|
|
43247
43564
|
`${JSON.stringify(refs2, null, 2)}
|
|
43248
43565
|
`,
|
|
43249
43566
|
"utf8"
|
|
@@ -43258,8 +43575,8 @@ async function advise(runIdArg, opts = {}) {
|
|
|
43258
43575
|
...reason ? { reason } : {},
|
|
43259
43576
|
mode: opts.project ? "project" : "run",
|
|
43260
43577
|
findings: findings2,
|
|
43261
|
-
prompt_file: prompt ?
|
|
43262
|
-
prompt_file_abs: prompt ?
|
|
43578
|
+
prompt_file: prompt ? path75.relative(projectRoot, promptPath).split(path75.sep).join("/") : null,
|
|
43579
|
+
prompt_file_abs: prompt ? path75.resolve(promptPath) : null,
|
|
43263
43580
|
prompt_chars: prompt?.length ?? 0,
|
|
43264
43581
|
warnings,
|
|
43265
43582
|
advise_prompt: prompt
|
|
@@ -43335,7 +43652,7 @@ async function adviseRecord(runIdArg, opts) {
|
|
|
43335
43652
|
const { projectRoot } = ctx;
|
|
43336
43653
|
if (opts.project) {
|
|
43337
43654
|
const input2 = await readAnswerInput(opts, "advice", {
|
|
43338
|
-
defaultPath:
|
|
43655
|
+
defaultPath: path75.join(analysisDir(projectRoot), "answer.json"),
|
|
43339
43656
|
code: "missing_advice_input",
|
|
43340
43657
|
fix: "iamu advise-record --project --data '<json>'"
|
|
43341
43658
|
});
|
|
@@ -43367,7 +43684,7 @@ async function adviseRecord(runIdArg, opts) {
|
|
|
43367
43684
|
if (!manifest) return;
|
|
43368
43685
|
await logTiming(runDirPath, "advise-record", "start");
|
|
43369
43686
|
const input = await readAnswerInput(opts, "advice", {
|
|
43370
|
-
defaultPath:
|
|
43687
|
+
defaultPath: path75.join(runDir(projectRoot, runId), "advise_answer.json"),
|
|
43371
43688
|
code: "missing_advice_input",
|
|
43372
43689
|
fix: `iamu advise-record ${runId} --data '<json>'`
|
|
43373
43690
|
});
|
|
@@ -43417,7 +43734,7 @@ async function directionsList(opts) {
|
|
|
43417
43734
|
problem: d.texts.problem,
|
|
43418
43735
|
change: d.texts.change,
|
|
43419
43736
|
leading_practice: d.texts.leading_practice,
|
|
43420
|
-
path:
|
|
43737
|
+
path: path75.relative(ctx.projectRoot, d.path)
|
|
43421
43738
|
})),
|
|
43422
43739
|
null,
|
|
43423
43740
|
2
|
|
@@ -43566,7 +43883,7 @@ __export(designer_exports, {
|
|
|
43566
43883
|
designerRecord: () => designerRecord
|
|
43567
43884
|
});
|
|
43568
43885
|
import { promises as fs73 } from "fs";
|
|
43569
|
-
import
|
|
43886
|
+
import path76 from "path";
|
|
43570
43887
|
import chalk49 from "chalk";
|
|
43571
43888
|
import { ZodError as ZodError3 } from "zod";
|
|
43572
43889
|
function describeError(err) {
|
|
@@ -43586,7 +43903,7 @@ async function designer(runId, opts = {}) {
|
|
|
43586
43903
|
const { projectRoot, runDirPath } = ctx;
|
|
43587
43904
|
if (!await completedManifest2(runDirPath, runId, opts)) return;
|
|
43588
43905
|
const input = await buildRunDesignerInput(projectRoot, runId);
|
|
43589
|
-
const promptPath =
|
|
43906
|
+
const promptPath = path76.join(runDirPath, DESIGNER_PROMPT_FILE);
|
|
43590
43907
|
const prompt = input.needed ? composeDesignPrompt(
|
|
43591
43908
|
projectRoot,
|
|
43592
43909
|
"designer.md",
|
|
@@ -43595,7 +43912,7 @@ async function designer(runId, opts = {}) {
|
|
|
43595
43912
|
if (prompt) {
|
|
43596
43913
|
await fs73.writeFile(promptPath, prompt, "utf8");
|
|
43597
43914
|
await fs73.writeFile(
|
|
43598
|
-
|
|
43915
|
+
path76.join(runDirPath, DESIGNER_REFS_FILE),
|
|
43599
43916
|
`${JSON.stringify(input.refs, null, 2)}
|
|
43600
43917
|
`,
|
|
43601
43918
|
"utf8"
|
|
@@ -43606,7 +43923,7 @@ async function designer(runId, opts = {}) {
|
|
|
43606
43923
|
installedPromptWarning(projectRoot, "designer.md"),
|
|
43607
43924
|
...input.warnings
|
|
43608
43925
|
].filter((w) => w !== null) : [];
|
|
43609
|
-
const answer =
|
|
43926
|
+
const answer = path76.join(runDirPath, DESIGNER_ANSWER_FILE);
|
|
43610
43927
|
const result = {
|
|
43611
43928
|
needed: input.needed,
|
|
43612
43929
|
reason: input.reason,
|
|
@@ -43622,11 +43939,11 @@ async function designer(runId, opts = {}) {
|
|
|
43622
43939
|
title: f.title
|
|
43623
43940
|
})),
|
|
43624
43941
|
design_files: (input.bundle.design?.files ?? []).map((f) => f.entry),
|
|
43625
|
-
prompt_file: prompt ?
|
|
43942
|
+
prompt_file: prompt ? path76.relative(projectRoot, promptPath).split(path76.sep).join("/") : null,
|
|
43626
43943
|
prompt_file_abs: prompt ? promptPath : null,
|
|
43627
43944
|
prompt_chars: prompt?.length ?? 0,
|
|
43628
43945
|
answer_file_abs: prompt ? answer : null,
|
|
43629
|
-
next: prompt ? `Give a fresh agent ${
|
|
43946
|
+
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
43947
|
hint: input.judged_against === "general-craft" ? await designDocsHint(projectRoot) : null,
|
|
43631
43948
|
// No design doc recorded or found, and the template not declined:
|
|
43632
43949
|
// the run summary offers to start one (iamu-design).
|
|
@@ -43672,6 +43989,9 @@ function printDesignerRecord(res) {
|
|
|
43672
43989
|
` ${chalk49.cyan("\u25C6")} Proposed doc edit ${p.outcome}: ${p.slug} (iamu design proposals)`
|
|
43673
43990
|
);
|
|
43674
43991
|
}
|
|
43992
|
+
for (const slug of res.verified ?? []) {
|
|
43993
|
+
console.log(` ${chalk49.green("\u2713")} Fix verified: ${slug}`);
|
|
43994
|
+
}
|
|
43675
43995
|
for (const r of res.rejected) {
|
|
43676
43996
|
console.log(` ${chalk49.yellow("!")} Dropped ${r.id}: ${r.reason}`);
|
|
43677
43997
|
}
|
|
@@ -43757,7 +44077,7 @@ async function designerRecordLocked(projectRoot, runDirPath, runId, manifest, op
|
|
|
43757
44077
|
return;
|
|
43758
44078
|
}
|
|
43759
44079
|
const input = await readAnswerInput(opts, "designer's review", {
|
|
43760
|
-
defaultPath:
|
|
44080
|
+
defaultPath: path76.join(runDirPath, DESIGNER_ANSWER_FILE),
|
|
43761
44081
|
code: "missing_designer_answer",
|
|
43762
44082
|
fix: `iamu designer-record ${runId} --data '<json>'`
|
|
43763
44083
|
});
|
|
@@ -43889,7 +44209,7 @@ __export(post_walk_exports, {
|
|
|
43889
44209
|
postWalk: () => postWalk
|
|
43890
44210
|
});
|
|
43891
44211
|
import { existsSync as existsSync24, promises as fs74 } from "fs";
|
|
43892
|
-
import
|
|
44212
|
+
import path77 from "path";
|
|
43893
44213
|
import chalk50 from "chalk";
|
|
43894
44214
|
async function captureStageJson(fn) {
|
|
43895
44215
|
const captured = [];
|
|
@@ -43932,7 +44252,7 @@ async function postWalk(runIdArg, opts = {}) {
|
|
|
43932
44252
|
}
|
|
43933
44253
|
const runDirPath = requireRun(projectRoot, runId, opts);
|
|
43934
44254
|
if (!runDirPath) return;
|
|
43935
|
-
const manifestPath =
|
|
44255
|
+
const manifestPath = path77.join(runDirPath, "walkthrough.json");
|
|
43936
44256
|
let manifest;
|
|
43937
44257
|
try {
|
|
43938
44258
|
manifest = JSON.parse(
|
|
@@ -43973,7 +44293,7 @@ async function postWalk(runIdArg, opts = {}) {
|
|
|
43973
44293
|
return;
|
|
43974
44294
|
}
|
|
43975
44295
|
const recordedNow = [];
|
|
43976
|
-
const matchesAnswerPath =
|
|
44296
|
+
const matchesAnswerPath = path77.join(runDirPath, MATCH_ANSWER_FILE);
|
|
43977
44297
|
if (existsSync24(matchesAnswerPath)) {
|
|
43978
44298
|
const res = await captureStageJson(
|
|
43979
44299
|
() => matchRecord(runId, { file: matchesAnswerPath, json: true })
|
|
@@ -43984,7 +44304,7 @@ async function postWalk(runIdArg, opts = {}) {
|
|
|
43984
44304
|
});
|
|
43985
44305
|
}
|
|
43986
44306
|
}
|
|
43987
|
-
const judgeAnswerPath =
|
|
44307
|
+
const judgeAnswerPath = path77.join(runDirPath, "judge_answer.json");
|
|
43988
44308
|
if (existsSync24(judgeAnswerPath)) {
|
|
43989
44309
|
const res = await captureStageJson(
|
|
43990
44310
|
() => evaluateRecord(runId, { file: judgeAnswerPath, json: true })
|
|
@@ -43995,8 +44315,8 @@ async function postWalk(runIdArg, opts = {}) {
|
|
|
43995
44315
|
});
|
|
43996
44316
|
}
|
|
43997
44317
|
}
|
|
43998
|
-
const adviseAnswerPath =
|
|
43999
|
-
if (!opts.critic && !existsSync24(
|
|
44318
|
+
const adviseAnswerPath = path77.join(runDirPath, "advise_answer.json");
|
|
44319
|
+
if (!opts.critic && !existsSync24(path77.join(runDirPath, ADVICE_FILE)) && existsSync24(adviseAnswerPath)) {
|
|
44000
44320
|
const res = await captureStageJson(
|
|
44001
44321
|
() => adviseRecord(runId, { file: adviseAnswerPath, json: true })
|
|
44002
44322
|
);
|
|
@@ -44006,7 +44326,7 @@ async function postWalk(runIdArg, opts = {}) {
|
|
|
44006
44326
|
});
|
|
44007
44327
|
}
|
|
44008
44328
|
}
|
|
44009
|
-
const designerAnswerPath =
|
|
44329
|
+
const designerAnswerPath = path77.join(runDirPath, DESIGNER_ANSWER_FILE);
|
|
44010
44330
|
let designerWarnings = [];
|
|
44011
44331
|
let designerInvalidChecked = [];
|
|
44012
44332
|
if (!existingDesignerFile(runDirPath)) {
|
|
@@ -44038,9 +44358,9 @@ async function postWalk(runIdArg, opts = {}) {
|
|
|
44038
44358
|
}
|
|
44039
44359
|
}
|
|
44040
44360
|
const matchInfo = await captureStageJson(() => match(runId, { json: true }));
|
|
44041
|
-
const evalRecorded = existsSync24(
|
|
44361
|
+
const evalRecorded = existsSync24(path77.join(runDirPath, "verdicts.json"));
|
|
44042
44362
|
const evalInfo = evalRecorded ? null : await captureStageJson(() => evaluate(runId, { json: true }));
|
|
44043
|
-
const adviseRecorded = existsSync24(
|
|
44363
|
+
const adviseRecorded = existsSync24(path77.join(runDirPath, ADVICE_FILE));
|
|
44044
44364
|
const awaitingJudge = !opts.critic && !adviseRecorded && !evalRecorded;
|
|
44045
44365
|
const adviseInfo = opts.critic || adviseRecorded || awaitingJudge ? null : await captureStageJson(() => advise(runId, { json: true }));
|
|
44046
44366
|
const designerRecorded = Boolean(existingDesignerFile(runDirPath));
|
|
@@ -44547,7 +44867,10 @@ function registerFindingsCommands(program2) {
|
|
|
44547
44867
|
program2.command("triage [findings...]").helpGroup("Findings:").description("Set findings' triage status").requiredOption(
|
|
44548
44868
|
"--status <status>",
|
|
44549
44869
|
"open, addressed, dismissed, deferred or wont_fix"
|
|
44550
|
-
).option(
|
|
44870
|
+
).option(
|
|
44871
|
+
"--note <text>",
|
|
44872
|
+
"Triage note, any status. For addressed: one line on what changed on screen, so the next run's matcher can confirm the fix"
|
|
44873
|
+
).option("--note-file <path>", "Read the note from a file (- reads stdin)").option(
|
|
44551
44874
|
"--reason <reason>",
|
|
44552
44875
|
"Dismiss reason: not_relevant (default), walker_error, out_of_scope or duplicate"
|
|
44553
44876
|
).option(
|
|
@@ -44564,7 +44887,7 @@ function registerFindingsCommands(program2) {
|
|
|
44564
44887
|
"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
44888
|
).option("--no-guidance", "Dismiss without writing a tester guidance rule").option("--json", "Output as JSON").addHelpText(
|
|
44566
44889
|
"after",
|
|
44567
|
-
|
|
44890
|
+
'\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
44891
|
).action(
|
|
44569
44892
|
async (refs2, opts) => {
|
|
44570
44893
|
const { triage: triage2 } = await Promise.resolve().then(() => (init_triage(), triage_exports));
|
|
@@ -44663,9 +44986,9 @@ function registerProjectCommands(program2) {
|
|
|
44663
44986
|
"after",
|
|
44664
44987
|
"\nExamples:\n $ iamu validate --all\n $ iamu validate --all --fix --dry-run\n $ iamu validate iamu/personas/maria.md"
|
|
44665
44988
|
).action(
|
|
44666
|
-
async (
|
|
44989
|
+
async (path78, opts) => {
|
|
44667
44990
|
const { validate: validate2 } = await Promise.resolve().then(() => (init_validate(), validate_exports));
|
|
44668
|
-
await validate2(
|
|
44991
|
+
await validate2(path78, opts);
|
|
44669
44992
|
}
|
|
44670
44993
|
);
|
|
44671
44994
|
program2.command("update").helpGroup("Project:").description("Update and repair agent skills and prompts to latest version").option(
|