@iamu-dev/iamu 0.8.0-beta.2 → 0.8.0-beta.3
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/README.md +33 -5
- package/dist/bin.js +611 -169
- package/dist/commands/post-walk.d.ts +18 -0
- package/dist/commands/triage.d.ts +4 -2
- package/dist/index.js +605 -163
- package/dist/templates/prompts/design-review.md +1 -1
- package/dist/templates/prompts/designer.md +4 -3
- package/dist/templates/prompts/walk.md +6 -0
- package/dist/templates/skills/iamu-findings.md +13 -24
- package/dist/templates/skills/iamu-run.md +37 -14
- package/package.json +3 -3
package/dist/bin.js
CHANGED
|
@@ -522,6 +522,7 @@ var init_finding = __esm({
|
|
|
522
522
|
DISMISS_REASONS = [
|
|
523
523
|
"not_relevant",
|
|
524
524
|
"walker_error",
|
|
525
|
+
"out_of_scope",
|
|
525
526
|
"duplicate"
|
|
526
527
|
];
|
|
527
528
|
dismissReasonSchema = z9.enum(DISMISS_REASONS);
|
|
@@ -571,11 +572,12 @@ var init_finding = __esm({
|
|
|
571
572
|
addressed_at: isoTimestamp.nullish(),
|
|
572
573
|
/**
|
|
573
574
|
* Why a `dismissed` finding was dismissed: `not_relevant` (by design,
|
|
574
|
-
* out of scope), `walker_error` (the walker misread the product)
|
|
575
|
-
* `duplicate`. Absent means `not_relevant`.
|
|
576
|
-
* a tester guidance rule.
|
|
575
|
+
* out of scope), `walker_error` (the walker misread the product),
|
|
576
|
+
* `out_of_scope` or `duplicate`. Absent means `not_relevant`.
|
|
577
577
|
*/
|
|
578
578
|
dismiss_reason: dismissReasonSchema.nullish(),
|
|
579
|
+
/** Finding slug or id this repeats when `dismiss_reason` is `duplicate`. */
|
|
580
|
+
duplicate_of: z9.string().nullish(),
|
|
579
581
|
/**
|
|
580
582
|
* The active tester guidance rule this finding matches, set by
|
|
581
583
|
* `iamu match-record`. The finding is kept — the rule flagged it,
|
|
@@ -698,7 +700,11 @@ var init_guidance = __esm({
|
|
|
698
700
|
"../../packages/protocol/dist/schemas/frontmatter/guidance.js"() {
|
|
699
701
|
"use strict";
|
|
700
702
|
init_common();
|
|
701
|
-
GUIDANCE_KINDS = [
|
|
703
|
+
GUIDANCE_KINDS = [
|
|
704
|
+
"not_relevant",
|
|
705
|
+
"walker_error",
|
|
706
|
+
"out_of_scope"
|
|
707
|
+
];
|
|
702
708
|
guidanceKindSchema = z10.enum(GUIDANCE_KINDS);
|
|
703
709
|
guidanceStatusSchema = z10.enum(["active", "retired"]);
|
|
704
710
|
guidanceScopeSchema = z10.object({
|
|
@@ -1598,6 +1604,7 @@ var init_walkthrough_canonical = __esm({
|
|
|
1598
1604
|
canonicalStepSchema = z22.object({
|
|
1599
1605
|
id: z22.string(),
|
|
1600
1606
|
step_number: z22.number().int(),
|
|
1607
|
+
timestamp: z22.string().optional(),
|
|
1601
1608
|
url: z22.string().optional(),
|
|
1602
1609
|
/** The walker's short name for the screen. */
|
|
1603
1610
|
surface: z22.string().optional(),
|
|
@@ -5835,9 +5842,9 @@ function pidIsDead(rec) {
|
|
|
5835
5842
|
async function isStale(lockPath, rec, staleMs, now) {
|
|
5836
5843
|
if (rec === null) {
|
|
5837
5844
|
try {
|
|
5838
|
-
const
|
|
5839
|
-
if (
|
|
5840
|
-
const mtime =
|
|
5845
|
+
const stat4 = await fs6.stat(lockPath);
|
|
5846
|
+
if (stat4.size === 0) {
|
|
5847
|
+
const mtime = stat4.birthtimeMs && stat4.birthtimeMs > 0 ? stat4.birthtimeMs : stat4.mtimeMs;
|
|
5841
5848
|
if (now - mtime <= staleMs) {
|
|
5842
5849
|
return false;
|
|
5843
5850
|
}
|
|
@@ -6838,13 +6845,13 @@ function fenceTracker() {
|
|
|
6838
6845
|
};
|
|
6839
6846
|
}
|
|
6840
6847
|
function cleanName(raw, legacy) {
|
|
6841
|
-
let s = raw.replace(/\s+#+\s*$/, "").replace(/[*_`]/g, "").replace(/[‘’]/g, "'").replace(/[“”]/g, '"').replace(/\s+/g, " ").trim();
|
|
6848
|
+
let s = raw.replace(/\\(["'])/g, "$1").replace(/\s+#+\s*$/, "").replace(/[*_`]/g, "").replace(/[‘’]/g, "'").replace(/[“”]/g, '"').replace(/\s+/g, " ").trim();
|
|
6842
6849
|
if (legacy)
|
|
6843
6850
|
s = s.replace(LEGACY_ID, "");
|
|
6844
|
-
return s.replace(/[
|
|
6851
|
+
return s.replace(/[.:—–-]+$/, "").trim();
|
|
6845
6852
|
}
|
|
6846
6853
|
function normaliseRuleName(raw, legacy = false) {
|
|
6847
|
-
return cleanName(raw, legacy).toLowerCase();
|
|
6854
|
+
return cleanName(raw, legacy).replace(/["'\\]/g, "").replace(/\s+/g, " ").trim().toLowerCase();
|
|
6848
6855
|
}
|
|
6849
6856
|
function scanLines(text) {
|
|
6850
6857
|
const inFence = fenceTracker();
|
|
@@ -6913,7 +6920,7 @@ function rulesInText(text, opts = {}) {
|
|
|
6913
6920
|
continue;
|
|
6914
6921
|
if (l.heading) {
|
|
6915
6922
|
const { level, text: h } = l.heading;
|
|
6916
|
-
if (level >= 2 && level <=
|
|
6923
|
+
if (level >= 2 && level <= 5) {
|
|
6917
6924
|
let end2 = i + 1;
|
|
6918
6925
|
while (end2 < to) {
|
|
6919
6926
|
const next = lines[end2].heading;
|
|
@@ -6932,25 +6939,30 @@ function rulesInText(text, opts = {}) {
|
|
|
6932
6939
|
headings.push(l.heading);
|
|
6933
6940
|
continue;
|
|
6934
6941
|
}
|
|
6935
|
-
const
|
|
6936
|
-
const
|
|
6942
|
+
const unquoted = l.text.replace(/^\s*>\s?/, "");
|
|
6943
|
+
const item = LIST_ITEM.exec(unquoted);
|
|
6944
|
+
const rest = item ? unquoted.slice(item[0].length) : unquoted.trimStart();
|
|
6937
6945
|
const bold = BOLD_LEAD.exec(rest);
|
|
6938
6946
|
if (!bold)
|
|
6939
6947
|
continue;
|
|
6940
6948
|
const inner = bold[2].trim();
|
|
6941
|
-
|
|
6949
|
+
const afterBold = rest.slice(bold[0].length);
|
|
6950
|
+
const hasStop = /[.:!?—–]$/.test(inner) || Boolean(bold[3]) || afterBold.trim() === "";
|
|
6951
|
+
if (!hasStop)
|
|
6942
6952
|
continue;
|
|
6943
6953
|
const prev = i === 0 ? null : lines[i - 1];
|
|
6944
|
-
const
|
|
6954
|
+
const prevUnquoted = prev ? prev.text.replace(/^\s*>\s?/, "") : "";
|
|
6955
|
+
const startsParagraph = !prev || prevUnquoted.trim() === "" || Boolean(prev.heading);
|
|
6945
6956
|
if (!item && !startsParagraph)
|
|
6946
6957
|
continue;
|
|
6947
6958
|
const indent = item ? item[1].length : 0;
|
|
6948
6959
|
let end = i + 1;
|
|
6949
6960
|
while (end < to) {
|
|
6950
6961
|
const n = lines[end];
|
|
6951
|
-
|
|
6962
|
+
const nUnquoted = n.text.replace(/^\s*>\s?/, "");
|
|
6963
|
+
if (n.heading || !n.fenced && nUnquoted.trim() === "")
|
|
6952
6964
|
break;
|
|
6953
|
-
const nItem = LIST_ITEM.exec(
|
|
6965
|
+
const nItem = LIST_ITEM.exec(nUnquoted);
|
|
6954
6966
|
if (item && nItem && nItem[1].length <= indent)
|
|
6955
6967
|
break;
|
|
6956
6968
|
end++;
|
|
@@ -6975,7 +6987,19 @@ function matchRuleName(rules, citation, legacy = false) {
|
|
|
6975
6987
|
section: normaliseRuleName(citation.slice(0, at), legacy),
|
|
6976
6988
|
name: normaliseRuleName(citation.slice(at + 1), legacy)
|
|
6977
6989
|
} : null;
|
|
6978
|
-
|
|
6990
|
+
const exact = rules.filter((r) => {
|
|
6991
|
+
const rName = normaliseRuleName(r.name, legacy);
|
|
6992
|
+
const rSec = normaliseRuleName(r.section ?? "", legacy);
|
|
6993
|
+
return rName === whole || scoped !== null && rName === scoped.name && rSec === scoped.section;
|
|
6994
|
+
});
|
|
6995
|
+
if (exact.length > 0)
|
|
6996
|
+
return exact;
|
|
6997
|
+
const leadOf = (s) => normaliseRuleName(s.split(/\s*(?::|—|–|\s-\s)\s*/)[0] ?? "", legacy);
|
|
6998
|
+
return rules.filter((r) => {
|
|
6999
|
+
const rLead = leadOf(r.name);
|
|
7000
|
+
const rSec = normaliseRuleName(r.section ?? "", legacy);
|
|
7001
|
+
return rLead.length > 0 && rLead === whole || scoped !== null && rLead.length > 0 && rLead === scoped.name && rSec === scoped.section;
|
|
7002
|
+
});
|
|
6979
7003
|
}
|
|
6980
7004
|
async function findRule(entries, citation) {
|
|
6981
7005
|
const seen = /* @__PURE__ */ new Map();
|
|
@@ -7009,6 +7033,27 @@ async function citationFor(entries, match2) {
|
|
|
7009
7033
|
const alone = await findRule(entries, { rule: match2.name, file: match2.file });
|
|
7010
7034
|
return alone.length === 1 || !match2.section ? match2.name : `${match2.section} > ${match2.name}`;
|
|
7011
7035
|
}
|
|
7036
|
+
async function canonicalRulesInFile(file) {
|
|
7037
|
+
if (file.kind !== "design")
|
|
7038
|
+
return [];
|
|
7039
|
+
let text;
|
|
7040
|
+
try {
|
|
7041
|
+
text = await fs10.readFile(file.abs, "utf8");
|
|
7042
|
+
} catch {
|
|
7043
|
+
return [];
|
|
7044
|
+
}
|
|
7045
|
+
const legacy = file.path === LEGACY_DESIGN_DOC;
|
|
7046
|
+
const rules = rulesInText(text, { legacy, heading: file.heading }) ?? [];
|
|
7047
|
+
const counts = /* @__PURE__ */ new Map();
|
|
7048
|
+
for (const r of rules) {
|
|
7049
|
+
const key = normaliseRuleName(r.name, legacy);
|
|
7050
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
7051
|
+
}
|
|
7052
|
+
return rules.map((r) => {
|
|
7053
|
+
const key = normaliseRuleName(r.name, legacy);
|
|
7054
|
+
return (counts.get(key) ?? 0) === 1 || !r.section ? r.name : `${r.section} > ${r.name}`;
|
|
7055
|
+
});
|
|
7056
|
+
}
|
|
7012
7057
|
async function resolveRule(entries, citation) {
|
|
7013
7058
|
const matches = await findRule(entries, citation);
|
|
7014
7059
|
if (matches.length === 1)
|
|
@@ -7035,7 +7080,7 @@ var init_design_rules = __esm({
|
|
|
7035
7080
|
LEGACY_ID = new RegExp(`^${LEGACY_ID_PATTERN}\\s+`);
|
|
7036
7081
|
HEADING = /^(#{1,6})\s+(.+?)\s*$/;
|
|
7037
7082
|
LIST_ITEM = /^(\s*)(?:[-*+]|\d+[.)])\s+/;
|
|
7038
|
-
BOLD_LEAD = /^(\*\*|__)(.+?)\1
|
|
7083
|
+
BOLD_LEAD = /^(\*\*|__)(.+?)\1(?:\s*([.:—–]|\s-\s))?/;
|
|
7039
7084
|
sameFile = (f, file) => {
|
|
7040
7085
|
const want = file.trim().replace(/\\/g, "/");
|
|
7041
7086
|
return f.entry === want || f.path === want;
|
|
@@ -7723,15 +7768,15 @@ async function entryProblem(projectRoot, entry) {
|
|
|
7723
7768
|
if (!isInsideProjectRoot(projectRoot, abs)) {
|
|
7724
7769
|
return `Path escapes project root: ${rel}`;
|
|
7725
7770
|
}
|
|
7726
|
-
let
|
|
7771
|
+
let stat4;
|
|
7727
7772
|
try {
|
|
7728
|
-
|
|
7773
|
+
stat4 = await fs13.stat(abs);
|
|
7729
7774
|
} catch {
|
|
7730
7775
|
return `Not found: ${rel}`;
|
|
7731
7776
|
}
|
|
7732
7777
|
if (!heading)
|
|
7733
7778
|
return null;
|
|
7734
|
-
if (!
|
|
7779
|
+
if (!stat4.isFile())
|
|
7735
7780
|
return `${rel} is a folder; a section needs a file`;
|
|
7736
7781
|
const text = await fs13.readFile(abs, "utf8");
|
|
7737
7782
|
if (sectionLines(text, heading))
|
|
@@ -8707,7 +8752,14 @@ async function isProposalStale(projectRoot, p) {
|
|
|
8707
8752
|
}
|
|
8708
8753
|
async function readDesignContext(projectRoot, opts = {}) {
|
|
8709
8754
|
const state = await readDesignSources(projectRoot);
|
|
8710
|
-
const files = selectDesignFiles(state.file.files, opts.urls).map((e) =>
|
|
8755
|
+
const files = await Promise.all(selectDesignFiles(state.file.files, opts.urls).map(async (e) => {
|
|
8756
|
+
const resolved = resolveDesignFile(projectRoot, e);
|
|
8757
|
+
if (resolved.kind === "design") {
|
|
8758
|
+
const rules = await canonicalRulesInFile(resolved);
|
|
8759
|
+
return rules.length > 0 ? { ...resolved, rules } : resolved;
|
|
8760
|
+
}
|
|
8761
|
+
return resolved;
|
|
8762
|
+
}));
|
|
8711
8763
|
const proposals = [];
|
|
8712
8764
|
for (const p of await listDesignProposals(projectRoot)) {
|
|
8713
8765
|
proposals.push({
|
|
@@ -9636,7 +9688,7 @@ async function suppressionRules(projectRoot, outlineSlug) {
|
|
|
9636
9688
|
if (!outlineSlug)
|
|
9637
9689
|
return [];
|
|
9638
9690
|
try {
|
|
9639
|
-
return (await activeGuidanceFor(projectRoot, { outline: outlineSlug })).filter((r) => r.kind === "not_relevant").map((r) => ({ id: r.id, rule: r.rule }));
|
|
9691
|
+
return (await activeGuidanceFor(projectRoot, { outline: outlineSlug })).filter((r) => r.kind === "not_relevant" || r.kind === "out_of_scope").map((r) => ({ id: r.id, rule: r.rule }));
|
|
9640
9692
|
} catch {
|
|
9641
9693
|
return [];
|
|
9642
9694
|
}
|
|
@@ -10537,6 +10589,63 @@ async function readDesignerIgnored(projectRoot) {
|
|
|
10537
10589
|
return readForWrite(projectRoot).catch(() => []);
|
|
10538
10590
|
}
|
|
10539
10591
|
async function ignoreDesignerIssue(projectRoot, runId, issueId, opts = {}) {
|
|
10592
|
+
const reasonGiven = opts.reason?.trim() || null;
|
|
10593
|
+
const when = opts.now ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
10594
|
+
const cascadeDismissSiblings = async (targetFm, note) => {
|
|
10595
|
+
const cid = targetFm.canonical_id;
|
|
10596
|
+
if (!cid)
|
|
10597
|
+
return;
|
|
10598
|
+
const { entities } = await listEntities(projectRoot, "finding").catch(() => ({ entities: [] }));
|
|
10599
|
+
for (const sibling of entities) {
|
|
10600
|
+
if (sibling.frontmatter.id === targetFm.id)
|
|
10601
|
+
continue;
|
|
10602
|
+
if (sibling.frontmatter.canonical_id !== cid)
|
|
10603
|
+
continue;
|
|
10604
|
+
if (sibling.frontmatter.triage_status === "dismissed")
|
|
10605
|
+
continue;
|
|
10606
|
+
await writeEntity(projectRoot, "finding", {
|
|
10607
|
+
...sibling.frontmatter,
|
|
10608
|
+
triage_status: "dismissed",
|
|
10609
|
+
dismiss_reason: "not_relevant",
|
|
10610
|
+
triage_note: note,
|
|
10611
|
+
updated_at: when
|
|
10612
|
+
}, sibling.body);
|
|
10613
|
+
}
|
|
10614
|
+
};
|
|
10615
|
+
const trimmedRef = issueId.trim().toUpperCase();
|
|
10616
|
+
if (/^P\d+$/.test(trimmedRef)) {
|
|
10617
|
+
const refs2 = await fs21.readFile(designerRefsFile(runDir(projectRoot, runId)), "utf8").then((raw) => JSON.parse(raw)).catch(() => null);
|
|
10618
|
+
const findingId = refs2?.[trimmedRef];
|
|
10619
|
+
const pFile = findingId ? await readEntity(projectRoot, "finding", findingId).catch(() => null) : null;
|
|
10620
|
+
if (!pFile) {
|
|
10621
|
+
throw new DesignerRecordError("unknown_designer_finding", `${runId} has no designer finding ${issueId}`);
|
|
10622
|
+
}
|
|
10623
|
+
const already = pFile.frontmatter.triage_status === "dismissed";
|
|
10624
|
+
const note = reasonGiven ?? (pFile.frontmatter.triage_note?.trim() || "Dismissed via iamu designer-dismiss");
|
|
10625
|
+
if (!already || reasonGiven && reasonGiven !== pFile.frontmatter.triage_note) {
|
|
10626
|
+
await writeEntity(projectRoot, "finding", {
|
|
10627
|
+
...pFile.frontmatter,
|
|
10628
|
+
triage_status: "dismissed",
|
|
10629
|
+
dismiss_reason: "not_relevant",
|
|
10630
|
+
triage_note: note,
|
|
10631
|
+
updated_at: when
|
|
10632
|
+
}, pFile.body);
|
|
10633
|
+
await cascadeDismissSiblings(pFile.frontmatter, note);
|
|
10634
|
+
}
|
|
10635
|
+
return {
|
|
10636
|
+
entry: {
|
|
10637
|
+
title: pFile.frontmatter.title ?? pFile.frontmatter.slug,
|
|
10638
|
+
screen: pFile.frontmatter.screen ?? null,
|
|
10639
|
+
what: pFile.frontmatter.what ?? "",
|
|
10640
|
+
severity: pFile.frontmatter.severity ?? void 0,
|
|
10641
|
+
reason: reasonGiven ?? pFile.frontmatter.triage_note ?? null,
|
|
10642
|
+
run: runId,
|
|
10643
|
+
issue: trimmedRef,
|
|
10644
|
+
at: when
|
|
10645
|
+
},
|
|
10646
|
+
already
|
|
10647
|
+
};
|
|
10648
|
+
}
|
|
10540
10649
|
const record = await readDesignerRecord(projectRoot, runId);
|
|
10541
10650
|
if (!record) {
|
|
10542
10651
|
throw new DesignerRecordError("not_judged", `Run ${runId} has no designer's review to dismiss a finding of`);
|
|
@@ -10546,8 +10655,6 @@ async function ignoreDesignerIssue(projectRoot, runId, issueId, opts = {}) {
|
|
|
10546
10655
|
if (!issue) {
|
|
10547
10656
|
throw new DesignerRecordError("unknown_designer_finding", `${runId} has no designer finding ${issueId}`);
|
|
10548
10657
|
}
|
|
10549
|
-
const reasonGiven = opts.reason?.trim() || null;
|
|
10550
|
-
const when = opts.now ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
10551
10658
|
const file = issue.finding_id ? await readEntity(projectRoot, "finding", issue.finding_id).catch(() => null) : null;
|
|
10552
10659
|
if (file) {
|
|
10553
10660
|
const already = file.frontmatter.triage_status === "dismissed";
|
|
@@ -10560,6 +10667,7 @@ async function ignoreDesignerIssue(projectRoot, runId, issueId, opts = {}) {
|
|
|
10560
10667
|
triage_note: note,
|
|
10561
10668
|
updated_at: when
|
|
10562
10669
|
}, file.body);
|
|
10670
|
+
await cascadeDismissSiblings(file.frontmatter, note);
|
|
10563
10671
|
}
|
|
10564
10672
|
return {
|
|
10565
10673
|
entry: {
|
|
@@ -10926,6 +11034,7 @@ async function buildCanonical(projectRoot, manifest, completedAt, inputs) {
|
|
|
10926
11034
|
return {
|
|
10927
11035
|
id: `step-${stepNum}`,
|
|
10928
11036
|
step_number: stepNum,
|
|
11037
|
+
...typeof s.timestamp === "string" && s.timestamp ? { timestamp: s.timestamp } : {},
|
|
10929
11038
|
screen_title: s.action,
|
|
10930
11039
|
// Step files record the path relative to the run directory
|
|
10931
11040
|
// (`screenshots/step-1.png`); older ones recorded the bare file
|
|
@@ -11424,7 +11533,7 @@ async function assembleWalkthroughBundle(projectRoot, runId, opts = {}) {
|
|
|
11424
11533
|
scenarioIdForPush = manifest.scenario_slug;
|
|
11425
11534
|
}
|
|
11426
11535
|
}
|
|
11427
|
-
const bundleSteps = steps2.map((s) => {
|
|
11536
|
+
const bundleSteps = steps2.map((s, idx) => {
|
|
11428
11537
|
const shot = screenshots.get(s.step_number);
|
|
11429
11538
|
const ref = shot ? {
|
|
11430
11539
|
part_name: `screenshot_${s.step_number}`,
|
|
@@ -11432,7 +11541,7 @@ async function assembleWalkthroughBundle(projectRoot, runId, opts = {}) {
|
|
|
11432
11541
|
content_type: shot.contentType
|
|
11433
11542
|
} : null;
|
|
11434
11543
|
return {
|
|
11435
|
-
seq:
|
|
11544
|
+
seq: idx + 1,
|
|
11436
11545
|
title: s.action,
|
|
11437
11546
|
persona_reasoning: s.note ?? null,
|
|
11438
11547
|
grounding: s.grounding ?? null,
|
|
@@ -17054,9 +17163,9 @@ function withSessionStateLock(filePath, fn) {
|
|
|
17054
17163
|
const now = Date.now();
|
|
17055
17164
|
if (rec === null) {
|
|
17056
17165
|
try {
|
|
17057
|
-
const
|
|
17058
|
-
if (
|
|
17059
|
-
const mtime =
|
|
17166
|
+
const stat4 = statSync(targetPath);
|
|
17167
|
+
if (stat4.size === 0) {
|
|
17168
|
+
const mtime = stat4.birthtimeMs && stat4.birthtimeMs > 0 ? stat4.birthtimeMs : stat4.mtimeMs;
|
|
17060
17169
|
if (now - mtime <= staleMs)
|
|
17061
17170
|
return false;
|
|
17062
17171
|
}
|
|
@@ -18673,7 +18782,12 @@ async function buildSurfaceDossier(projectRoot, target, opts = {}) {
|
|
|
18673
18782
|
seenDismissed.add(d.title);
|
|
18674
18783
|
return true;
|
|
18675
18784
|
});
|
|
18676
|
-
const guidance = (await listEntities(projectRoot, "guidance")).entities.filter((g) => (g.frontmatter.status ?? "active") === "active").map((g) =>
|
|
18785
|
+
const guidance = (await listEntities(projectRoot, "guidance")).entities.filter((g) => (g.frontmatter.status ?? "active") === "active").map((g) => {
|
|
18786
|
+
const { rule, reason } = parseGuidanceBody(g.body);
|
|
18787
|
+
if (!rule)
|
|
18788
|
+
return "";
|
|
18789
|
+
return reason ? `${rule} (${reason})` : rule;
|
|
18790
|
+
}).filter(Boolean);
|
|
18677
18791
|
const ids = new Set(allOnSurface.map((r) => r.fm.id));
|
|
18678
18792
|
const directions = (await listDirections(projectRoot)).filter((d) => d.frontmatter.status !== "rejected" && d.frontmatter.absorbs.some((id) => ids.has(id))).map((d) => ({
|
|
18679
18793
|
slug: d.frontmatter.slug,
|
|
@@ -20517,6 +20631,23 @@ async function buildRunDesignerInput(projectRoot, runId) {
|
|
|
20517
20631
|
const ended = summary?.ended;
|
|
20518
20632
|
const testability = testabilityPath(projectRoot);
|
|
20519
20633
|
const pfm = persona?.frontmatter;
|
|
20634
|
+
const outlineSlug = outline?.frontmatter.slug ?? scenario?.frontmatter.outline ?? null;
|
|
20635
|
+
const guidanceRules = outlineSlug ? await activeGuidanceFor(projectRoot, { outline: outlineSlug }).catch(() => []) : (await listEntities(projectRoot, "guidance").catch(() => ({ entities: [] }))).entities.filter((g) => (g.frontmatter.status ?? "active") === "active").map((g) => {
|
|
20636
|
+
const { rule, reason: reason2 } = parseGuidanceBody(g.body);
|
|
20637
|
+
return { rule, reason: reason2 };
|
|
20638
|
+
}).filter((g) => Boolean(g.rule));
|
|
20639
|
+
const guidance = guidanceRules.map((r) => r.reason ? `${r.rule} (${r.reason})` : r.rule);
|
|
20640
|
+
const designFiles = await Promise.all(docs.map(async (f) => {
|
|
20641
|
+
const rules = f.kind === "design" ? f.rules ?? await canonicalRulesInFile(f) : void 0;
|
|
20642
|
+
return {
|
|
20643
|
+
entry: f.entry,
|
|
20644
|
+
kind: f.kind,
|
|
20645
|
+
abs: f.abs,
|
|
20646
|
+
heading: f.heading,
|
|
20647
|
+
applies_to: f.applies_to,
|
|
20648
|
+
...rules && rules.length > 0 ? { rules } : {}
|
|
20649
|
+
};
|
|
20650
|
+
}));
|
|
20520
20651
|
const bundle = {
|
|
20521
20652
|
run_id: runId,
|
|
20522
20653
|
project_root: projectRoot,
|
|
@@ -20541,13 +20672,7 @@ async function buildRunDesignerInput(projectRoot, runId) {
|
|
|
20541
20672
|
ended: ended && typeof ended.note === "string" ? { reached: ended.reached === true, note: ended.note } : null
|
|
20542
20673
|
} : null,
|
|
20543
20674
|
design: docs.length > 0 || proposals.length > 0 ? {
|
|
20544
|
-
files:
|
|
20545
|
-
entry: f.entry,
|
|
20546
|
-
kind: f.kind,
|
|
20547
|
-
abs: f.abs,
|
|
20548
|
-
heading: f.heading,
|
|
20549
|
-
applies_to: f.applies_to
|
|
20550
|
-
})),
|
|
20675
|
+
files: designFiles,
|
|
20551
20676
|
proposals: proposals.map((p) => ({
|
|
20552
20677
|
slug: p.slug,
|
|
20553
20678
|
kind: p.kind,
|
|
@@ -20575,6 +20700,7 @@ async function buildRunDesignerInput(projectRoot, runId) {
|
|
|
20575
20700
|
reason: e.reason
|
|
20576
20701
|
}))
|
|
20577
20702
|
],
|
|
20703
|
+
guidance,
|
|
20578
20704
|
facts: context?.facts ? designerFacts(context.facts) : null,
|
|
20579
20705
|
setup_notes: {
|
|
20580
20706
|
file: existsSync5(testability) ? testability : null,
|
|
@@ -20623,6 +20749,9 @@ var init_designer_input = __esm({
|
|
|
20623
20749
|
init_advice_input();
|
|
20624
20750
|
init_design_facts();
|
|
20625
20751
|
init_design_proposals();
|
|
20752
|
+
init_design_rules();
|
|
20753
|
+
init_entity();
|
|
20754
|
+
init_guidance2();
|
|
20626
20755
|
init_paths();
|
|
20627
20756
|
init_review_issues();
|
|
20628
20757
|
init_url_path();
|
|
@@ -20630,6 +20759,7 @@ var init_designer_input = __esm({
|
|
|
20630
20759
|
init_designer_ignored();
|
|
20631
20760
|
init_designer_ledger();
|
|
20632
20761
|
init_walkthrough_canonical2();
|
|
20762
|
+
init_dist();
|
|
20633
20763
|
LONG_VISIT = 6;
|
|
20634
20764
|
FRAMES_PER_VISIT = 8;
|
|
20635
20765
|
FRAMES_PER_RUN = 24;
|
|
@@ -21941,15 +22071,15 @@ function resolveApiUrl(configured) {
|
|
|
21941
22071
|
assertSecureApiUrl(url, configured ? "API URL" : "IAMU_API_URL");
|
|
21942
22072
|
return url;
|
|
21943
22073
|
}
|
|
21944
|
-
function parseStore(raw,
|
|
22074
|
+
function parseStore(raw, path74) {
|
|
21945
22075
|
let parsed;
|
|
21946
22076
|
try {
|
|
21947
22077
|
parsed = JSON.parse(raw);
|
|
21948
22078
|
} catch (err) {
|
|
21949
|
-
throw new CredentialsFileError(
|
|
22079
|
+
throw new CredentialsFileError(path74, "not valid JSON", err);
|
|
21950
22080
|
}
|
|
21951
22081
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
21952
|
-
throw new CredentialsFileError(
|
|
22082
|
+
throw new CredentialsFileError(path74, "expected a JSON object");
|
|
21953
22083
|
}
|
|
21954
22084
|
return parsed;
|
|
21955
22085
|
}
|
|
@@ -21957,26 +22087,26 @@ function isNotFound(err) {
|
|
|
21957
22087
|
return typeof err === "object" && err !== null && err.code === "ENOENT";
|
|
21958
22088
|
}
|
|
21959
22089
|
async function readCredentialsStore() {
|
|
21960
|
-
const
|
|
22090
|
+
const path74 = getCredentialsPath();
|
|
21961
22091
|
let raw;
|
|
21962
22092
|
try {
|
|
21963
|
-
raw = await readFile(
|
|
22093
|
+
raw = await readFile(path74, "utf-8");
|
|
21964
22094
|
} catch (err) {
|
|
21965
22095
|
if (isNotFound(err)) return {};
|
|
21966
22096
|
warnUnreadableStoreOnce(err);
|
|
21967
22097
|
throw err;
|
|
21968
22098
|
}
|
|
21969
22099
|
try {
|
|
21970
|
-
return parseStore(raw,
|
|
22100
|
+
return parseStore(raw, path74);
|
|
21971
22101
|
} catch (err) {
|
|
21972
22102
|
warnUnreadableStoreOnce(err);
|
|
21973
22103
|
throw err;
|
|
21974
22104
|
}
|
|
21975
22105
|
}
|
|
21976
22106
|
async function writeCredentialsStore(store) {
|
|
21977
|
-
const
|
|
21978
|
-
await mkdir(dirname(
|
|
21979
|
-
await atomicWriteFile(
|
|
22107
|
+
const path74 = getCredentialsPath();
|
|
22108
|
+
await mkdir(dirname(path74), { recursive: true, mode: 448 });
|
|
22109
|
+
await atomicWriteFile(path74, `${JSON.stringify(store, null, 2)}
|
|
21980
22110
|
`, {
|
|
21981
22111
|
mode: 384
|
|
21982
22112
|
});
|
|
@@ -22004,16 +22134,16 @@ function resolveApiKey(apiUrl) {
|
|
|
22004
22134
|
}
|
|
22005
22135
|
}
|
|
22006
22136
|
const normalized = url.replace(/\/+$/, "");
|
|
22007
|
-
for (const
|
|
22137
|
+
for (const path74 of candidatePaths) {
|
|
22008
22138
|
let raw;
|
|
22009
22139
|
try {
|
|
22010
|
-
raw = readFileSync2(
|
|
22140
|
+
raw = readFileSync2(path74, "utf-8");
|
|
22011
22141
|
} catch (err) {
|
|
22012
22142
|
if (!isNotFound(err)) warnUnreadableStoreOnce(err);
|
|
22013
22143
|
continue;
|
|
22014
22144
|
}
|
|
22015
22145
|
try {
|
|
22016
|
-
const store = parseStore(raw,
|
|
22146
|
+
const store = parseStore(raw, path74);
|
|
22017
22147
|
const entry = store[url] ?? store[normalized] ?? store[`${normalized}/`];
|
|
22018
22148
|
if (typeof entry !== "object" || entry === null) continue;
|
|
22019
22149
|
const token = entry.token;
|
|
@@ -22054,12 +22184,12 @@ var init_credentials = __esm({
|
|
|
22054
22184
|
* @param reason - What is wrong with it.
|
|
22055
22185
|
* @param cause - Underlying parse error, if any.
|
|
22056
22186
|
*/
|
|
22057
|
-
constructor(
|
|
22187
|
+
constructor(path74, reason, cause) {
|
|
22058
22188
|
super(
|
|
22059
|
-
`Credentials file ${
|
|
22189
|
+
`Credentials file ${path74} is corrupt (${reason}). Fix it by hand, or delete it and run \`iamu auth login\` again.`,
|
|
22060
22190
|
{
|
|
22061
22191
|
cause,
|
|
22062
|
-
suggestedAction: `Delete ${
|
|
22192
|
+
suggestedAction: `Delete ${path74} and run \`iamu auth login\`, or set IAMU_TOKEN.`
|
|
22063
22193
|
}
|
|
22064
22194
|
);
|
|
22065
22195
|
}
|
|
@@ -23028,8 +23158,8 @@ import { dirname as dirname3, join as join5, resolve as resolve2 } from "path";
|
|
|
23028
23158
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
23029
23159
|
import chalk6 from "chalk";
|
|
23030
23160
|
function getVersion() {
|
|
23031
|
-
if ("0.8.0-beta.
|
|
23032
|
-
return "0.8.0-beta.
|
|
23161
|
+
if ("0.8.0-beta.3") {
|
|
23162
|
+
return "0.8.0-beta.3";
|
|
23033
23163
|
}
|
|
23034
23164
|
try {
|
|
23035
23165
|
const thisDir = dirname3(fileURLToPath2(import.meta.url));
|
|
@@ -23141,16 +23271,16 @@ async function needsWrite(filePath, content) {
|
|
|
23141
23271
|
return true;
|
|
23142
23272
|
}
|
|
23143
23273
|
}
|
|
23144
|
-
function isCliWrittenSkill(
|
|
23274
|
+
function isCliWrittenSkill(path74) {
|
|
23145
23275
|
try {
|
|
23146
|
-
return /^generatedBy:/m.test(readFileSync6(
|
|
23276
|
+
return /^generatedBy:/m.test(readFileSync6(path74, "utf-8"));
|
|
23147
23277
|
} catch {
|
|
23148
23278
|
return false;
|
|
23149
23279
|
}
|
|
23150
23280
|
}
|
|
23151
|
-
function isCliWrittenPrompt(
|
|
23281
|
+
function isCliWrittenPrompt(path74) {
|
|
23152
23282
|
try {
|
|
23153
|
-
const content = readFileSync6(
|
|
23283
|
+
const content = readFileSync6(path74, "utf-8");
|
|
23154
23284
|
return /^generatedBy:/m.test(content) || /^#\s+iamu:/m.test(content) || /^description:\s*.*iamu/im.test(content);
|
|
23155
23285
|
} catch {
|
|
23156
23286
|
return false;
|
|
@@ -23162,8 +23292,8 @@ function planSkillFiles(hosts, projectRoot, version2) {
|
|
|
23162
23292
|
for (const host of hosts) {
|
|
23163
23293
|
if (pluginProvidesSkills(host, projectRoot)) {
|
|
23164
23294
|
for (const name of skillTemplates.keys()) {
|
|
23165
|
-
const
|
|
23166
|
-
if (isCliWrittenSkill(
|
|
23295
|
+
const path74 = join5(host.getSkillsDir(projectRoot), name, "SKILL.md");
|
|
23296
|
+
if (isCliWrittenSkill(path74)) files.push({ host, path: path74, content: null });
|
|
23167
23297
|
}
|
|
23168
23298
|
} else if (host.supportsSkills) {
|
|
23169
23299
|
const skillsDir = host.getSkillsDir(projectRoot);
|
|
@@ -25193,13 +25323,13 @@ function git(args2, cwd) {
|
|
|
25193
25323
|
}
|
|
25194
25324
|
function hashFile(absPath) {
|
|
25195
25325
|
const hash = createHash5("sha256");
|
|
25196
|
-
const
|
|
25197
|
-
if (
|
|
25326
|
+
const stat4 = lstatSync(absPath);
|
|
25327
|
+
if (stat4.isSymbolicLink()) {
|
|
25198
25328
|
hash.update(`symlink:${readlinkSync(absPath)}`);
|
|
25199
25329
|
return hash.digest("hex");
|
|
25200
25330
|
}
|
|
25201
|
-
if (!
|
|
25202
|
-
hash.update(`${
|
|
25331
|
+
if (!stat4.isFile()) {
|
|
25332
|
+
hash.update(`${stat4.isDirectory() ? "dir" : "other"}`);
|
|
25203
25333
|
return hash.digest("hex");
|
|
25204
25334
|
}
|
|
25205
25335
|
const fd = openSync3(absPath, "r");
|
|
@@ -31460,12 +31590,20 @@ function awaitingSteps(projectRoot, findings2) {
|
|
|
31460
31590
|
}
|
|
31461
31591
|
function defaultRule(kind, findings2) {
|
|
31462
31592
|
const titles = findings2.map((f) => f.frontmatter.title ?? f.frontmatter.ui_fact ?? f.slug).join("; ");
|
|
31463
|
-
|
|
31593
|
+
if (kind === "walker_error") {
|
|
31594
|
+
return `Earlier walkers got this wrong: ${titles}`;
|
|
31595
|
+
}
|
|
31596
|
+
if (kind === "out_of_scope") {
|
|
31597
|
+
return `Handled outside system; don't report: ${titles}`;
|
|
31598
|
+
}
|
|
31599
|
+
return `Don't report: ${titles}`;
|
|
31464
31600
|
}
|
|
31465
31601
|
async function resolveScope(projectRoot, flag, findings2, opts) {
|
|
31466
31602
|
if (flag === "project") return {};
|
|
31603
|
+
const withScenario = findings2.filter((f) => Boolean(f.frontmatter.scenario));
|
|
31604
|
+
if (withScenario.length === 0) return {};
|
|
31467
31605
|
const outlines = /* @__PURE__ */ new Set();
|
|
31468
|
-
for (const f of
|
|
31606
|
+
for (const f of withScenario) {
|
|
31469
31607
|
const scenario = await safeReadEntity(
|
|
31470
31608
|
projectRoot,
|
|
31471
31609
|
"scenario",
|
|
@@ -31506,6 +31644,47 @@ async function resolveScope(projectRoot, flag, findings2, opts) {
|
|
|
31506
31644
|
}
|
|
31507
31645
|
return { capability };
|
|
31508
31646
|
}
|
|
31647
|
+
async function resolveDesignerRefInTriage(projectRoot, ref) {
|
|
31648
|
+
const m = /^(?:([^#]+)#)?([DP]\d+)$/i.exec(ref.trim());
|
|
31649
|
+
if (!m) return null;
|
|
31650
|
+
const runPart = m[1]?.trim();
|
|
31651
|
+
const code = (m[2] ?? "").toUpperCase();
|
|
31652
|
+
const targetRunId = resolveRunId(projectRoot, runPart);
|
|
31653
|
+
if (!targetRunId) return null;
|
|
31654
|
+
if (code.startsWith("P")) {
|
|
31655
|
+
const refsRaw = await fs62.readFile(
|
|
31656
|
+
path59.join(runDir(projectRoot, targetRunId), DESIGNER_REFS_FILE),
|
|
31657
|
+
"utf8"
|
|
31658
|
+
).catch(() => null);
|
|
31659
|
+
if (!refsRaw) return null;
|
|
31660
|
+
try {
|
|
31661
|
+
const parsed = JSON.parse(refsRaw);
|
|
31662
|
+
const findingId = parsed[code];
|
|
31663
|
+
if (findingId) {
|
|
31664
|
+
return await readEntity(
|
|
31665
|
+
projectRoot,
|
|
31666
|
+
"finding",
|
|
31667
|
+
findingId
|
|
31668
|
+
);
|
|
31669
|
+
}
|
|
31670
|
+
} catch {
|
|
31671
|
+
return null;
|
|
31672
|
+
}
|
|
31673
|
+
return null;
|
|
31674
|
+
}
|
|
31675
|
+
const rec = await readDesignerRecord(projectRoot, targetRunId).catch(
|
|
31676
|
+
() => null
|
|
31677
|
+
);
|
|
31678
|
+
const issue = rec?.issues.find((i) => i.id.toUpperCase() === code);
|
|
31679
|
+
if (issue?.finding_id) {
|
|
31680
|
+
return await readEntity(
|
|
31681
|
+
projectRoot,
|
|
31682
|
+
"finding",
|
|
31683
|
+
issue.finding_id
|
|
31684
|
+
);
|
|
31685
|
+
}
|
|
31686
|
+
return null;
|
|
31687
|
+
}
|
|
31509
31688
|
async function triage(refs2, opts) {
|
|
31510
31689
|
const ctx = await resolveProject(opts);
|
|
31511
31690
|
if (!ctx) return;
|
|
@@ -31545,42 +31724,39 @@ async function triage(refs2, opts) {
|
|
|
31545
31724
|
}
|
|
31546
31725
|
reason = opts.reason;
|
|
31547
31726
|
}
|
|
31548
|
-
if (
|
|
31549
|
-
|
|
31550
|
-
|
|
31727
|
+
if (opts.duplicateOf !== void 0) {
|
|
31728
|
+
if (status2.data !== "dismissed") {
|
|
31729
|
+
commandError("--duplicate-of applies only to --status dismissed", {
|
|
31730
|
+
...opts,
|
|
31731
|
+
code: "invalid_duplicate_of_usage",
|
|
31732
|
+
fix: `iamu triage ${refs2.join(" ")} --status dismissed --duplicate-of ${opts.duplicateOf}`
|
|
31733
|
+
});
|
|
31734
|
+
return;
|
|
31735
|
+
}
|
|
31736
|
+
if (reason && reason !== "duplicate") {
|
|
31551
31737
|
commandError(
|
|
31552
|
-
|
|
31738
|
+
`--duplicate-of requires --reason duplicate \u2014 got "${reason}"`,
|
|
31553
31739
|
{
|
|
31554
31740
|
...opts,
|
|
31555
|
-
code: "
|
|
31556
|
-
fix: `iamu triage ${refs2.join(" ")} --status dismissed --
|
|
31741
|
+
code: "invalid_duplicate_of_usage",
|
|
31742
|
+
fix: `iamu triage ${refs2.join(" ")} --status dismissed --reason duplicate --duplicate-of ${opts.duplicateOf}`
|
|
31557
31743
|
}
|
|
31558
31744
|
);
|
|
31559
31745
|
return;
|
|
31560
31746
|
}
|
|
31747
|
+
reason = "duplicate";
|
|
31748
|
+
}
|
|
31749
|
+
if (status2.data === "dismissed") {
|
|
31750
|
+
reason ??= "not_relevant";
|
|
31561
31751
|
}
|
|
31562
31752
|
const isSweep = opts.sweep !== void 0 && opts.sweep !== false;
|
|
31563
31753
|
const sweepQuery = typeof opts.sweep === "string" ? opts.sweep.trim() : "";
|
|
31564
31754
|
if (isSweep && status2.data !== "dismissed" && status2.data !== "wont_fix") {
|
|
31565
|
-
commandError(
|
|
31566
|
-
|
|
31567
|
-
|
|
31568
|
-
|
|
31569
|
-
|
|
31570
|
-
fix: `iamu triage ${refs2.join(" ")} --status dismissed --note "by design" --sweep`
|
|
31571
|
-
}
|
|
31572
|
-
);
|
|
31573
|
-
return;
|
|
31574
|
-
}
|
|
31575
|
-
if (isSweep && status2.data === "wont_fix" && !opts.note?.trim()) {
|
|
31576
|
-
commandError(
|
|
31577
|
-
"Dismissing with --sweep needs --note naming why \u2014 it teaches the whole project",
|
|
31578
|
-
{
|
|
31579
|
-
...opts,
|
|
31580
|
-
code: "missing_dismiss_note",
|
|
31581
|
-
fix: `iamu triage ${refs2.join(" ")} --status wont_fix --note "by design" --sweep`
|
|
31582
|
-
}
|
|
31583
|
-
);
|
|
31755
|
+
commandError("--sweep applies only when dismissing (--status dismissed)", {
|
|
31756
|
+
...opts,
|
|
31757
|
+
code: "invalid_sweep_usage",
|
|
31758
|
+
fix: `iamu triage ${refs2.join(" ")} --status dismissed --sweep`
|
|
31759
|
+
});
|
|
31584
31760
|
return;
|
|
31585
31761
|
}
|
|
31586
31762
|
if (isSweep && opts.scope !== void 0) {
|
|
@@ -31623,6 +31799,9 @@ async function triage(refs2, opts) {
|
|
|
31623
31799
|
"finding",
|
|
31624
31800
|
ref
|
|
31625
31801
|
);
|
|
31802
|
+
if (!entity) {
|
|
31803
|
+
entity = await resolveDesignerRefInTriage(projectRoot, ref);
|
|
31804
|
+
}
|
|
31626
31805
|
} catch (err) {
|
|
31627
31806
|
commandError(
|
|
31628
31807
|
`Finding ${ref} does not validate: ${err instanceof Error ? err.message.split("\n")[0] : String(err)} \u2014 run \`iamu validate --all\``,
|
|
@@ -31699,29 +31878,25 @@ async function triage(refs2, opts) {
|
|
|
31699
31878
|
}
|
|
31700
31879
|
const allTargets = [...loaded, ...swept];
|
|
31701
31880
|
const persona = allTargets.filter((f) => isPersonaFinding(f.frontmatter));
|
|
31702
|
-
if (!isSweep && opts.rule !== void 0 && persona.length < loaded.length) {
|
|
31703
|
-
commandError(
|
|
31704
|
-
"--rule teaches the walker, and a designer finding is not the walker's: dismiss it without --rule (later designer's reviews are told not to raise it again)",
|
|
31705
|
-
{
|
|
31706
|
-
...opts,
|
|
31707
|
-
code: "rule_for_designer_finding",
|
|
31708
|
-
fix: `iamu triage ${refs2.join(" ")} --status dismissed --note "<why>"`
|
|
31709
|
-
}
|
|
31710
|
-
);
|
|
31711
|
-
return;
|
|
31712
|
-
}
|
|
31713
31881
|
const effectiveReason = reason ?? (isSweep ? "not_relevant" : null);
|
|
31714
|
-
const
|
|
31882
|
+
const duplicateTarget = status2.data === "dismissed" && effectiveReason === "duplicate" ? opts.duplicateOf?.trim() || null : null;
|
|
31883
|
+
const teaches = (status2.data === "dismissed" || isSweep && status2.data === "wont_fix") && (effectiveReason === "not_relevant" || effectiveReason === "walker_error" || effectiveReason === "out_of_scope") && opts.guidance !== false && (persona.length > 0 || isSweep || opts.rule !== void 0);
|
|
31715
31884
|
let scope = {};
|
|
31716
31885
|
if (teaches && !isSweep) {
|
|
31717
|
-
const
|
|
31886
|
+
const scopeTargets = persona.length > 0 ? persona : allTargets;
|
|
31887
|
+
const resolved = await resolveScope(
|
|
31888
|
+
projectRoot,
|
|
31889
|
+
scopeFlag,
|
|
31890
|
+
scopeTargets,
|
|
31891
|
+
opts
|
|
31892
|
+
);
|
|
31718
31893
|
if (!resolved) return;
|
|
31719
31894
|
scope = resolved;
|
|
31720
31895
|
}
|
|
31721
31896
|
let guidance = null;
|
|
31722
31897
|
if (teaches && effectiveReason) {
|
|
31723
31898
|
const ruleSources = allTargets.length > 0 ? allTargets : persona;
|
|
31724
|
-
const ruleText = opts.rule?.trim() || (sweepQuery && ruleSources.length === 0 ? effectiveReason === "walker_error" ? `Earlier walkers got this wrong: ${sweepQuery}` : `Don't report: ${sweepQuery}` : defaultRule(effectiveReason, ruleSources));
|
|
31899
|
+
const ruleText = opts.rule?.trim() || (sweepQuery && ruleSources.length === 0 ? effectiveReason === "walker_error" ? `Earlier walkers got this wrong: ${sweepQuery}` : effectiveReason === "out_of_scope" ? `Handled outside system; don't report: ${sweepQuery}` : `Don't report: ${sweepQuery}` : defaultRule(effectiveReason, ruleSources));
|
|
31725
31900
|
const rule = await createGuidanceRule(projectRoot, {
|
|
31726
31901
|
kind: effectiveReason,
|
|
31727
31902
|
rule: ruleText,
|
|
@@ -31762,7 +31937,8 @@ async function triage(refs2, opts) {
|
|
|
31762
31937
|
updated_at: now,
|
|
31763
31938
|
addressed_at: addressedAt(f.frontmatter),
|
|
31764
31939
|
dismiss_reason: status2.data === "dismissed" ? effectiveReason : null,
|
|
31765
|
-
|
|
31940
|
+
duplicate_of: duplicateTarget,
|
|
31941
|
+
...(isSweep || !isPersonaFinding(f.frontmatter)) && guidance ? { suppressed_by_guidance: guidance.id } : {}
|
|
31766
31942
|
};
|
|
31767
31943
|
if (opts.note !== void 0) fm.triage_note = opts.note.trim();
|
|
31768
31944
|
await writeEntity(projectRoot, "finding", fm, f.body);
|
|
@@ -31790,6 +31966,7 @@ async function triage(refs2, opts) {
|
|
|
31790
31966
|
updated_at: now,
|
|
31791
31967
|
addressed_at: addressedAt(c.frontmatter),
|
|
31792
31968
|
dismiss_reason: status2.data === "dismissed" ? effectiveReason : null,
|
|
31969
|
+
duplicate_of: duplicateTarget,
|
|
31793
31970
|
...isSweep && guidance ? { suppressed_by_guidance: guidance.id } : {},
|
|
31794
31971
|
...opts.note !== void 0 ? { triage_note: opts.note.trim() } : {}
|
|
31795
31972
|
},
|
|
@@ -32877,7 +33054,7 @@ var init_browse_driver = __esm({
|
|
|
32877
33054
|
});
|
|
32878
33055
|
|
|
32879
33056
|
// src/commands/step-numbering.ts
|
|
32880
|
-
import { open, readdir, unlink } from "fs/promises";
|
|
33057
|
+
import { open, readdir, stat as stat2, unlink } from "fs/promises";
|
|
32881
33058
|
import path61 from "path";
|
|
32882
33059
|
function stepFileName(stepNumber2) {
|
|
32883
33060
|
return `${String(stepNumber2).padStart(3, "0")}.json`;
|
|
@@ -32890,7 +33067,16 @@ async function peekNextStepNumber(stepsDir) {
|
|
|
32890
33067
|
let max = 0;
|
|
32891
33068
|
for (const f of files) {
|
|
32892
33069
|
const m = /^(\d+)\.json$/.exec(f);
|
|
32893
|
-
if (m)
|
|
33070
|
+
if (!m) continue;
|
|
33071
|
+
const fullPath = path61.join(stepsDir, f);
|
|
33072
|
+
const st = await stat2(fullPath).catch(() => null);
|
|
33073
|
+
if (!st) continue;
|
|
33074
|
+
if (st.size === 0 && Date.now() - st.mtimeMs > STALE_RESERVATION_MS) {
|
|
33075
|
+
await unlink(fullPath).catch(() => {
|
|
33076
|
+
});
|
|
33077
|
+
continue;
|
|
33078
|
+
}
|
|
33079
|
+
max = Math.max(max, Number(m[1]));
|
|
32894
33080
|
}
|
|
32895
33081
|
return max + 1;
|
|
32896
33082
|
}
|
|
@@ -32913,13 +33099,21 @@ async function reserveStepFile(stepsDir, limit) {
|
|
|
32913
33099
|
};
|
|
32914
33100
|
} catch (err) {
|
|
32915
33101
|
if (err.code !== "EEXIST") throw err;
|
|
33102
|
+
const st = await stat2(stepPath).catch(() => null);
|
|
33103
|
+
if (st && st.size === 0 && Date.now() - st.mtimeMs > STALE_RESERVATION_MS) {
|
|
33104
|
+
await unlink(stepPath).catch(() => {
|
|
33105
|
+
});
|
|
33106
|
+
continue;
|
|
33107
|
+
}
|
|
32916
33108
|
candidate = Math.max(candidate + 1, await peekNextStepNumber(stepsDir));
|
|
32917
33109
|
}
|
|
32918
33110
|
}
|
|
32919
33111
|
}
|
|
33112
|
+
var STALE_RESERVATION_MS;
|
|
32920
33113
|
var init_step_numbering = __esm({
|
|
32921
33114
|
"src/commands/step-numbering.ts"() {
|
|
32922
33115
|
"use strict";
|
|
33116
|
+
STALE_RESERVATION_MS = 2e3;
|
|
32923
33117
|
}
|
|
32924
33118
|
});
|
|
32925
33119
|
|
|
@@ -32931,7 +33125,7 @@ __export(step_exports, {
|
|
|
32931
33125
|
recordStep: () => recordStep,
|
|
32932
33126
|
stepCommand: () => stepCommand
|
|
32933
33127
|
});
|
|
32934
|
-
import { copyFile, mkdir as mkdir4, rename, stat as
|
|
33128
|
+
import { copyFile, mkdir as mkdir4, rename, stat as stat3 } from "fs/promises";
|
|
32935
33129
|
import path62 from "path";
|
|
32936
33130
|
import chalk30 from "chalk";
|
|
32937
33131
|
function parseMaxSteps(val) {
|
|
@@ -32961,9 +33155,10 @@ async function recordStep(input) {
|
|
|
32961
33155
|
runId: input.runId
|
|
32962
33156
|
});
|
|
32963
33157
|
const { stepNumber: stepNumber2, stepPath } = reservation;
|
|
33158
|
+
let parsedStep;
|
|
33159
|
+
let finalScreenshotPath = null;
|
|
32964
33160
|
try {
|
|
32965
33161
|
let screenshotRef = null;
|
|
32966
|
-
let finalScreenshotPath = null;
|
|
32967
33162
|
if (shotSource) {
|
|
32968
33163
|
const destName = `step-${stepNumber2}.png`;
|
|
32969
33164
|
const destPath = path62.join(screenshotsDir, destName);
|
|
@@ -32979,7 +33174,7 @@ async function recordStep(input) {
|
|
|
32979
33174
|
}
|
|
32980
33175
|
screenshotRef = path62.posix.join("screenshots", destName);
|
|
32981
33176
|
}
|
|
32982
|
-
|
|
33177
|
+
parsedStep = runStepSchema.parse({
|
|
32983
33178
|
step_number: stepNumber2,
|
|
32984
33179
|
timestamp: input.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
32985
33180
|
action: input.action.trim(),
|
|
@@ -32991,33 +33186,33 @@ async function recordStep(input) {
|
|
|
32991
33186
|
...input.extra ? { extra: input.extra } : {}
|
|
32992
33187
|
});
|
|
32993
33188
|
await writeStep(runDirPath, parsedStep);
|
|
32994
|
-
let cancelled = false;
|
|
32995
|
-
try {
|
|
32996
|
-
const liveRes = await sendLiveStep(
|
|
32997
|
-
input.projectRoot,
|
|
32998
|
-
input.runId,
|
|
32999
|
-
parsedStep,
|
|
33000
|
-
finalScreenshotPath
|
|
33001
|
-
);
|
|
33002
|
-
if (liveRes.cancelled) {
|
|
33003
|
-
cancelled = true;
|
|
33004
|
-
}
|
|
33005
|
-
} catch {
|
|
33006
|
-
}
|
|
33007
|
-
return {
|
|
33008
|
-
step: parsedStep,
|
|
33009
|
-
stepPath,
|
|
33010
|
-
screenshotPath: finalScreenshotPath,
|
|
33011
|
-
cancelled
|
|
33012
|
-
};
|
|
33013
33189
|
} catch (err) {
|
|
33014
33190
|
await reservation.release();
|
|
33015
33191
|
throw err;
|
|
33016
33192
|
}
|
|
33193
|
+
let cancelled = false;
|
|
33194
|
+
try {
|
|
33195
|
+
const liveRes = await sendLiveStep(
|
|
33196
|
+
input.projectRoot,
|
|
33197
|
+
input.runId,
|
|
33198
|
+
parsedStep,
|
|
33199
|
+
finalScreenshotPath
|
|
33200
|
+
);
|
|
33201
|
+
if (liveRes.cancelled) {
|
|
33202
|
+
cancelled = true;
|
|
33203
|
+
}
|
|
33204
|
+
} catch {
|
|
33205
|
+
}
|
|
33206
|
+
return {
|
|
33207
|
+
step: parsedStep,
|
|
33208
|
+
stepPath,
|
|
33209
|
+
screenshotPath: finalScreenshotPath,
|
|
33210
|
+
cancelled
|
|
33211
|
+
};
|
|
33017
33212
|
}
|
|
33018
33213
|
async function existingShot(shot) {
|
|
33019
33214
|
const resolved = path62.resolve(shot);
|
|
33020
|
-
const isFile = await
|
|
33215
|
+
const isFile = await stat3(resolved).then((s) => s.isFile()).catch(() => false);
|
|
33021
33216
|
if (!isFile) {
|
|
33022
33217
|
throw new Error(
|
|
33023
33218
|
`Screenshot not found: ${resolved} \u2014 save the screenshot first, or omit --shot.`
|
|
@@ -33353,6 +33548,7 @@ function chromeArgs(opts, port, headless, viewport) {
|
|
|
33353
33548
|
`--window-size=${win.width},${win.height}`,
|
|
33354
33549
|
"--no-first-run",
|
|
33355
33550
|
"--no-default-browser-check",
|
|
33551
|
+
"--disable-features=Translate",
|
|
33356
33552
|
"--password-store=basic",
|
|
33357
33553
|
"--use-mock-keychain",
|
|
33358
33554
|
...headless ? ["--headless=new", "--disable-dev-shm-usage", "--disable-gpu"] : [],
|
|
@@ -38352,7 +38548,7 @@ async function crawlFrom(page, opts, origin, takeBlocked) {
|
|
|
38352
38548
|
const byKey = /* @__PURE__ */ new Map();
|
|
38353
38549
|
const byPlaceView = /* @__PURE__ */ new Map();
|
|
38354
38550
|
const signatureOf = (s) => exploreSignature(s.headings, s.title);
|
|
38355
|
-
function addSurface(state, place, view,
|
|
38551
|
+
function addSurface(state, place, view, path74, depth) {
|
|
38356
38552
|
const key = exploreSurfaceKey(place, view);
|
|
38357
38553
|
const surface = {
|
|
38358
38554
|
key,
|
|
@@ -38364,7 +38560,7 @@ async function crawlFrom(page, opts, origin, takeBlocked) {
|
|
|
38364
38560
|
headings: state.headings,
|
|
38365
38561
|
fields: state.fields,
|
|
38366
38562
|
depth,
|
|
38367
|
-
path:
|
|
38563
|
+
path: path74,
|
|
38368
38564
|
actions: []
|
|
38369
38565
|
};
|
|
38370
38566
|
surfaces.push(surface);
|
|
@@ -38382,9 +38578,9 @@ ${surface.signature}`, key);
|
|
|
38382
38578
|
});
|
|
38383
38579
|
await settle(page, settleMs);
|
|
38384
38580
|
}
|
|
38385
|
-
async function replay(
|
|
38386
|
-
if (!("goto" in (
|
|
38387
|
-
for (const step of
|
|
38581
|
+
async function replay(path74) {
|
|
38582
|
+
if (!("goto" in (path74[0] ?? {}))) await goto(opts.startUrl);
|
|
38583
|
+
for (const step of path74) {
|
|
38388
38584
|
if ("goto" in step) {
|
|
38389
38585
|
await goto(step.goto);
|
|
38390
38586
|
} else {
|
|
@@ -38508,8 +38704,8 @@ ${sig}`, key);
|
|
|
38508
38704
|
if (stats.surfaces >= budget.max_surfaces) {
|
|
38509
38705
|
stats.truncated = true;
|
|
38510
38706
|
} else {
|
|
38511
|
-
const
|
|
38512
|
-
addSurface(after, place, view,
|
|
38707
|
+
const path74 = !sameDoc && view === null ? [{ goto: after.url }] : [...surface.path, ...via, click];
|
|
38708
|
+
addSurface(after, place, view, path74, surface.depth + 1);
|
|
38513
38709
|
}
|
|
38514
38710
|
}
|
|
38515
38711
|
}
|
|
@@ -39434,7 +39630,9 @@ function guidanceLine(r) {
|
|
|
39434
39630
|
return r.reason ? `- ${rule} (${r.reason.replace(/\s+/g, " ")})` : `- ${rule}`;
|
|
39435
39631
|
}
|
|
39436
39632
|
function guidanceSection(rules) {
|
|
39437
|
-
const notRelevant = rules.filter(
|
|
39633
|
+
const notRelevant = rules.filter(
|
|
39634
|
+
(r) => r.kind === "not_relevant" || r.kind === "out_of_scope"
|
|
39635
|
+
);
|
|
39438
39636
|
const misreads = rules.filter((r) => r.kind === "walker_error");
|
|
39439
39637
|
if (notRelevant.length === 0 && misreads.length === 0) return [];
|
|
39440
39638
|
const out2 = ["", "Tester guidance \u2014 the team's decisions from earlier runs:"];
|
|
@@ -39521,7 +39719,8 @@ function composeWalkerPrompt(p) {
|
|
|
39521
39719
|
"Squinting Guardrails:",
|
|
39522
39720
|
"\u2022 If the exact target record/option is not available, apply the approximation strategy above to pick the closest plausible entity within the domain flow.",
|
|
39523
39721
|
"\u2022 Squinting does NOT permit drifting to unrelated settings, account profiles, or off-target pages. Stay strictly within the scenario journey.",
|
|
39524
|
-
"\u2022 If the system is completely empty or unseeded, and your persona role cannot create data, do NOT report false product bugs about the empty state. End the walk as blocked (below)."
|
|
39722
|
+
"\u2022 If the system is completely empty or unseeded, and your persona role cannot create data, do NOT report false product bugs about the empty state. End the walk as blocked (below).",
|
|
39723
|
+
"\u2022 If the expected state includes seeded defects inside an inspected subject (e.g. an embedded app preview, screenshot, submission, or failing check that your persona is supposed to review or flag), treat those defects as the subject data to act on \u2014 never report them as bugs in the host product itself."
|
|
39525
39724
|
].filter(Boolean) : [],
|
|
39526
39725
|
...p.untestableBoundaries && p.untestableBoundaries.length > 0 ? [
|
|
39527
39726
|
"",
|
|
@@ -41098,6 +41297,235 @@ var init_designer = __esm({
|
|
|
41098
41297
|
}
|
|
41099
41298
|
});
|
|
41100
41299
|
|
|
41300
|
+
// src/commands/post-walk.ts
|
|
41301
|
+
var post_walk_exports = {};
|
|
41302
|
+
__export(post_walk_exports, {
|
|
41303
|
+
postWalk: () => postWalk
|
|
41304
|
+
});
|
|
41305
|
+
import { existsSync as existsSync23, promises as fs71 } from "fs";
|
|
41306
|
+
import path73 from "path";
|
|
41307
|
+
import chalk49 from "chalk";
|
|
41308
|
+
async function captureStageJson(fn) {
|
|
41309
|
+
const captured = [];
|
|
41310
|
+
const origLog = console.log;
|
|
41311
|
+
const origErr = console.error;
|
|
41312
|
+
console.log = (...args2) => {
|
|
41313
|
+
captured.push(args2.map(String).join(" "));
|
|
41314
|
+
};
|
|
41315
|
+
console.error = () => {
|
|
41316
|
+
};
|
|
41317
|
+
try {
|
|
41318
|
+
await fn();
|
|
41319
|
+
} finally {
|
|
41320
|
+
console.log = origLog;
|
|
41321
|
+
console.error = origErr;
|
|
41322
|
+
}
|
|
41323
|
+
const last = captured[captured.length - 1];
|
|
41324
|
+
if (!last) return null;
|
|
41325
|
+
try {
|
|
41326
|
+
return JSON.parse(last);
|
|
41327
|
+
} catch {
|
|
41328
|
+
return null;
|
|
41329
|
+
}
|
|
41330
|
+
}
|
|
41331
|
+
async function postWalk(runIdArg, opts = {}) {
|
|
41332
|
+
const ctx = await resolveProject(opts);
|
|
41333
|
+
if (!ctx) return;
|
|
41334
|
+
const { projectRoot } = ctx;
|
|
41335
|
+
const runId = resolveRunId(projectRoot, runIdArg ?? opts.run);
|
|
41336
|
+
if (!runId) {
|
|
41337
|
+
commandError(
|
|
41338
|
+
"Run ID is required. Pass <run-id>, specify --run <run-id>, or start a run first.",
|
|
41339
|
+
{
|
|
41340
|
+
...opts,
|
|
41341
|
+
code: "missing_run_id",
|
|
41342
|
+
fix: "iamu status --json"
|
|
41343
|
+
}
|
|
41344
|
+
);
|
|
41345
|
+
return;
|
|
41346
|
+
}
|
|
41347
|
+
const runDirPath = resolveRunDir(projectRoot, runId, opts);
|
|
41348
|
+
if (!runDirPath) return;
|
|
41349
|
+
const manifestPath = path73.join(runDirPath, "walkthrough.json");
|
|
41350
|
+
let manifest;
|
|
41351
|
+
try {
|
|
41352
|
+
manifest = JSON.parse(
|
|
41353
|
+
await fs71.readFile(manifestPath, "utf8")
|
|
41354
|
+
);
|
|
41355
|
+
} catch {
|
|
41356
|
+
commandError(`Run not found: ${runId}`, {
|
|
41357
|
+
...opts,
|
|
41358
|
+
code: "run_not_found",
|
|
41359
|
+
fix: "iamu status --json"
|
|
41360
|
+
});
|
|
41361
|
+
return;
|
|
41362
|
+
}
|
|
41363
|
+
if (manifest.status !== "completed") {
|
|
41364
|
+
await captureStageJson(
|
|
41365
|
+
() => complete(runId, { prompt: false, push: false, json: true })
|
|
41366
|
+
);
|
|
41367
|
+
if (process.exitCode && process.exitCode !== 0) return;
|
|
41368
|
+
manifest = JSON.parse(
|
|
41369
|
+
await fs71.readFile(manifestPath, "utf8")
|
|
41370
|
+
);
|
|
41371
|
+
}
|
|
41372
|
+
if (manifest.blocked) {
|
|
41373
|
+
const blockedResult = {
|
|
41374
|
+
run_id: runId,
|
|
41375
|
+
status: "blocked",
|
|
41376
|
+
blocked: manifest.blocked,
|
|
41377
|
+
recorded: [],
|
|
41378
|
+
stages: null
|
|
41379
|
+
};
|
|
41380
|
+
if (opts.json) {
|
|
41381
|
+
console.log(JSON.stringify(blockedResult, null, 2));
|
|
41382
|
+
} else {
|
|
41383
|
+
console.log(
|
|
41384
|
+
` ${chalk49.yellow("!")} Run ${runId} is blocked: ${manifest.blocked.reason}`
|
|
41385
|
+
);
|
|
41386
|
+
}
|
|
41387
|
+
return;
|
|
41388
|
+
}
|
|
41389
|
+
const recordedNow = [];
|
|
41390
|
+
const matchesAnswerPath = path73.join(runDirPath, "matches.json");
|
|
41391
|
+
if (existsSync23(matchesAnswerPath)) {
|
|
41392
|
+
const res = await captureStageJson(
|
|
41393
|
+
() => matchRecord(runId, { file: matchesAnswerPath, json: true })
|
|
41394
|
+
);
|
|
41395
|
+
if (res && !process.exitCode) {
|
|
41396
|
+
recordedNow.push("match");
|
|
41397
|
+
await fs71.unlink(matchesAnswerPath).catch(() => {
|
|
41398
|
+
});
|
|
41399
|
+
}
|
|
41400
|
+
}
|
|
41401
|
+
const judgeAnswerPath = path73.join(runDirPath, "judge_answer.json");
|
|
41402
|
+
if (existsSync23(judgeAnswerPath)) {
|
|
41403
|
+
const res = await captureStageJson(
|
|
41404
|
+
() => evaluateRecord(runId, { file: judgeAnswerPath, json: true })
|
|
41405
|
+
);
|
|
41406
|
+
if (res && !process.exitCode) {
|
|
41407
|
+
recordedNow.push("evaluate");
|
|
41408
|
+
await fs71.unlink(judgeAnswerPath).catch(() => {
|
|
41409
|
+
});
|
|
41410
|
+
}
|
|
41411
|
+
}
|
|
41412
|
+
const adviseAnswerPath = path73.join(runDirPath, "advise_answer.json");
|
|
41413
|
+
if (!opts.critic && !existsSync23(path73.join(runDirPath, ADVICE_FILE)) && existsSync23(adviseAnswerPath)) {
|
|
41414
|
+
const res = await captureStageJson(
|
|
41415
|
+
() => adviseRecord(runId, { file: adviseAnswerPath, json: true })
|
|
41416
|
+
);
|
|
41417
|
+
if (res && !process.exitCode) {
|
|
41418
|
+
recordedNow.push("advise");
|
|
41419
|
+
await fs71.unlink(adviseAnswerPath).catch(() => {
|
|
41420
|
+
});
|
|
41421
|
+
}
|
|
41422
|
+
}
|
|
41423
|
+
const designerAnswerPath = path73.join(runDirPath, DESIGNER_ANSWER_FILE);
|
|
41424
|
+
if (!existingDesignerFile(runDirPath)) {
|
|
41425
|
+
if (opts.critic) {
|
|
41426
|
+
await captureStageJson(
|
|
41427
|
+
() => designerRecord(runId, { skipped: "--critic", json: true })
|
|
41428
|
+
);
|
|
41429
|
+
recordedNow.push("designer(skipped)");
|
|
41430
|
+
} else if (opts.ux === false) {
|
|
41431
|
+
await captureStageJson(
|
|
41432
|
+
() => designerRecord(runId, { skipped: "--no-ux", json: true })
|
|
41433
|
+
);
|
|
41434
|
+
recordedNow.push("designer(skipped)");
|
|
41435
|
+
} else if (existsSync23(designerAnswerPath)) {
|
|
41436
|
+
const res = await captureStageJson(
|
|
41437
|
+
() => designerRecord(runId, {
|
|
41438
|
+
file: designerAnswerPath,
|
|
41439
|
+
byOrchestrator: opts.byOrchestrator,
|
|
41440
|
+
json: true
|
|
41441
|
+
})
|
|
41442
|
+
);
|
|
41443
|
+
if (res && !process.exitCode) {
|
|
41444
|
+
recordedNow.push("designer");
|
|
41445
|
+
await fs71.unlink(designerAnswerPath).catch(() => {
|
|
41446
|
+
});
|
|
41447
|
+
}
|
|
41448
|
+
}
|
|
41449
|
+
}
|
|
41450
|
+
const matchInfo = await captureStageJson(() => match(runId, { json: true }));
|
|
41451
|
+
const evalRecorded = existsSync23(path73.join(runDirPath, "verdicts.json"));
|
|
41452
|
+
const evalInfo = evalRecorded ? null : await captureStageJson(() => evaluate(runId, { json: true }));
|
|
41453
|
+
const adviseRecorded = existsSync23(path73.join(runDirPath, ADVICE_FILE));
|
|
41454
|
+
const adviseInfo = opts.critic || adviseRecorded ? null : await captureStageJson(() => advise(runId, { json: true }));
|
|
41455
|
+
const designerRecorded = Boolean(existingDesignerFile(runDirPath));
|
|
41456
|
+
const designerInfo = opts.critic || opts.ux === false || designerRecorded ? null : await captureStageJson(() => designer(runId, { json: true }));
|
|
41457
|
+
const payload = {
|
|
41458
|
+
run_id: runId,
|
|
41459
|
+
status: "completed",
|
|
41460
|
+
recorded: recordedNow,
|
|
41461
|
+
stages: {
|
|
41462
|
+
match: {
|
|
41463
|
+
needed: Boolean(matchInfo?.needed),
|
|
41464
|
+
prompt_file: matchInfo?.prompt_file ?? null,
|
|
41465
|
+
prompt_file_abs: matchInfo?.prompt_file_abs ?? null,
|
|
41466
|
+
answer_file_abs: matchesAnswerPath,
|
|
41467
|
+
record_command: `iamu match-record ${runId} --file - <<'EOF'`
|
|
41468
|
+
},
|
|
41469
|
+
evaluate: {
|
|
41470
|
+
needed: !evalRecorded,
|
|
41471
|
+
recorded: evalRecorded,
|
|
41472
|
+
prompt_file: evalInfo?.prompt_file ?? null,
|
|
41473
|
+
prompt_file_abs: evalInfo?.prompt_file_abs ?? null,
|
|
41474
|
+
answer_file_abs: judgeAnswerPath,
|
|
41475
|
+
record_command: `iamu evaluate-record ${runId} --file - <<'EOF'`
|
|
41476
|
+
},
|
|
41477
|
+
advise: {
|
|
41478
|
+
needed: Boolean(adviseInfo?.needed),
|
|
41479
|
+
recorded: adviseRecorded,
|
|
41480
|
+
reason: opts.critic ? "--critic" : adviseInfo?.reason ?? null,
|
|
41481
|
+
prompt_file: adviseInfo?.prompt_file ?? null,
|
|
41482
|
+
prompt_file_abs: adviseInfo?.prompt_file_abs ?? null,
|
|
41483
|
+
answer_file_abs: adviseAnswerPath,
|
|
41484
|
+
record_command: `iamu advise-record ${runId} --file - <<'EOF'`
|
|
41485
|
+
},
|
|
41486
|
+
designer: {
|
|
41487
|
+
needed: Boolean(designerInfo?.needed),
|
|
41488
|
+
recorded: designerRecorded,
|
|
41489
|
+
reason: opts.critic ? "--critic" : opts.ux === false ? "--no-ux" : designerInfo?.reason ?? null,
|
|
41490
|
+
prompt_file: designerInfo?.prompt_file ?? null,
|
|
41491
|
+
prompt_file_abs: designerInfo?.prompt_file_abs ?? null,
|
|
41492
|
+
answer_file_abs: designerAnswerPath,
|
|
41493
|
+
key_screenshots: designerInfo?.key_screenshots ?? [],
|
|
41494
|
+
offer_template: Boolean(designerInfo?.offer_template),
|
|
41495
|
+
record_command: `iamu designer-record ${runId} --file - <<'EOF'`
|
|
41496
|
+
}
|
|
41497
|
+
}
|
|
41498
|
+
};
|
|
41499
|
+
if (opts.json) {
|
|
41500
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
41501
|
+
return;
|
|
41502
|
+
}
|
|
41503
|
+
console.log();
|
|
41504
|
+
console.log(
|
|
41505
|
+
` ${chalk49.green("\u2713")} Post-walk pipeline for ${chalk49.cyan(runId)}`
|
|
41506
|
+
);
|
|
41507
|
+
if (recordedNow.length > 0) {
|
|
41508
|
+
console.log(` Recorded: ${recordedNow.join(", ")}`);
|
|
41509
|
+
}
|
|
41510
|
+
for (const [name, s] of Object.entries(payload.stages)) {
|
|
41511
|
+
const state = "recorded" in s && s.recorded ? chalk49.green("recorded") : s.needed ? chalk49.yellow(`needed (${s.prompt_file})`) : chalk49.dim("not needed");
|
|
41512
|
+
console.log(` ${name}: ${state}`);
|
|
41513
|
+
}
|
|
41514
|
+
console.log();
|
|
41515
|
+
}
|
|
41516
|
+
var init_post_walk = __esm({
|
|
41517
|
+
"src/commands/post-walk.ts"() {
|
|
41518
|
+
"use strict";
|
|
41519
|
+
init_dist2();
|
|
41520
|
+
init_advise();
|
|
41521
|
+
init_complete();
|
|
41522
|
+
init_context4();
|
|
41523
|
+
init_designer();
|
|
41524
|
+
init_evaluate();
|
|
41525
|
+
init_match();
|
|
41526
|
+
}
|
|
41527
|
+
});
|
|
41528
|
+
|
|
41101
41529
|
// src/commands/prune.ts
|
|
41102
41530
|
var prune_exports = {};
|
|
41103
41531
|
__export(prune_exports, {
|
|
@@ -41105,7 +41533,7 @@ __export(prune_exports, {
|
|
|
41105
41533
|
prune: () => prune
|
|
41106
41534
|
});
|
|
41107
41535
|
import { confirm, isCancel as isCancel5 } from "@clack/prompts";
|
|
41108
|
-
import
|
|
41536
|
+
import chalk50 from "chalk";
|
|
41109
41537
|
function formatBytes(bytes) {
|
|
41110
41538
|
if (bytes <= 0) return "0 B";
|
|
41111
41539
|
if (bytes < 1024) return `${bytes} B`;
|
|
@@ -41151,7 +41579,7 @@ async function prune(opts = {}) {
|
|
|
41151
41579
|
});
|
|
41152
41580
|
if (preview.prunedRuns.length === 0) {
|
|
41153
41581
|
console.log(
|
|
41154
|
-
|
|
41582
|
+
chalk50.dim(" No runs match the pruning criteria. Nothing to prune.")
|
|
41155
41583
|
);
|
|
41156
41584
|
return preview;
|
|
41157
41585
|
}
|
|
@@ -41160,7 +41588,7 @@ async function prune(opts = {}) {
|
|
|
41160
41588
|
message: `Prune ${mode} ${preview.prunedRuns.length} run(s) and reclaim ${formatBytes(preview.totalBytesReclaimed)}?`
|
|
41161
41589
|
});
|
|
41162
41590
|
if (isCancel5(confirmed) || !confirmed) {
|
|
41163
|
-
console.log(
|
|
41591
|
+
console.log(chalk50.yellow(" Pruning cancelled."));
|
|
41164
41592
|
return;
|
|
41165
41593
|
}
|
|
41166
41594
|
}
|
|
@@ -41171,14 +41599,14 @@ async function prune(opts = {}) {
|
|
|
41171
41599
|
}
|
|
41172
41600
|
if (result.dryRun) {
|
|
41173
41601
|
console.log(
|
|
41174
|
-
|
|
41602
|
+
chalk50.cyan(
|
|
41175
41603
|
`
|
|
41176
41604
|
[DRY RUN] Would prune ${result.prunedRuns.length} of ${result.totalRunsScanned} run(s):`
|
|
41177
41605
|
)
|
|
41178
41606
|
);
|
|
41179
41607
|
} else {
|
|
41180
41608
|
console.log(
|
|
41181
|
-
|
|
41609
|
+
chalk50.bold(
|
|
41182
41610
|
`
|
|
41183
41611
|
${symbols.success} Pruned ${result.prunedRuns.length} of ${result.totalRunsScanned} run(s):`
|
|
41184
41612
|
)
|
|
@@ -41186,18 +41614,18 @@ async function prune(opts = {}) {
|
|
|
41186
41614
|
}
|
|
41187
41615
|
if (result.prunedRuns.length === 0) {
|
|
41188
41616
|
console.log(
|
|
41189
|
-
|
|
41617
|
+
chalk50.dim(" No runs matched the pruning criteria. Nothing pruned.")
|
|
41190
41618
|
);
|
|
41191
41619
|
} else {
|
|
41192
41620
|
for (const run of result.prunedRuns) {
|
|
41193
41621
|
const mode = run.fullyDeleted ? "removed" : "screenshots purged";
|
|
41194
41622
|
const count = run.screenshotsDeleted > 0 ? `${run.screenshotsDeleted} screenshots, ` : "";
|
|
41195
41623
|
console.log(
|
|
41196
|
-
` ${
|
|
41624
|
+
` ${chalk50.dim("\u2022")} ${chalk50.bold(run.runId)} ${chalk50.dim(`(${run.scenarioSlug ?? "unknown scenario"})`)}: ${chalk50.yellow(formatBytes(run.bytesReclaimed))} (${count}${mode})`
|
|
41197
41625
|
);
|
|
41198
41626
|
}
|
|
41199
41627
|
console.log(
|
|
41200
|
-
|
|
41628
|
+
chalk50.dim(
|
|
41201
41629
|
`
|
|
41202
41630
|
Total reclaimed: ${formatBytes(result.totalBytesReclaimed)} (${result.totalScreenshotsDeleted} screenshots deleted)
|
|
41203
41631
|
`
|
|
@@ -41218,9 +41646,9 @@ var init_prune2 = __esm({
|
|
|
41218
41646
|
// src/bin.ts
|
|
41219
41647
|
init_dist();
|
|
41220
41648
|
init_context4();
|
|
41221
|
-
import { existsSync as
|
|
41649
|
+
import { existsSync as existsSync24 } from "fs";
|
|
41222
41650
|
import { resolve as resolve3 } from "path";
|
|
41223
|
-
import
|
|
41651
|
+
import chalk53 from "chalk";
|
|
41224
41652
|
import { Command } from "commander";
|
|
41225
41653
|
|
|
41226
41654
|
// src/commands/register-cloud.ts
|
|
@@ -41510,12 +41938,12 @@ function registerFindingsCommands(program2) {
|
|
|
41510
41938
|
await findings2(opts);
|
|
41511
41939
|
}
|
|
41512
41940
|
);
|
|
41513
|
-
program2.command("triage [findings...]").helpGroup("Findings:").description("Set findings' triage status").requiredOption(
|
|
41514
|
-
"--status <status>",
|
|
41515
|
-
"open, addressed, deferred, wont_fix or dismissed"
|
|
41516
|
-
).option("--note <text>", "Why \u2014 required when dismissing").option(
|
|
41941
|
+
program2.command("triage [findings...]").helpGroup("Findings:").description("Set findings' triage status").requiredOption("--status <status>", "open, addressed or dismissed").option("--note <text>", "Optional triage note or reason").option(
|
|
41517
41942
|
"--reason <reason>",
|
|
41518
|
-
"Dismiss reason: not_relevant (default), walker_error or duplicate"
|
|
41943
|
+
"Dismiss reason: not_relevant (default), walker_error, out_of_scope or duplicate"
|
|
41944
|
+
).option(
|
|
41945
|
+
"--duplicate-of <finding>",
|
|
41946
|
+
"Finding slug or id this repeats (implies --reason duplicate)"
|
|
41519
41947
|
).option(
|
|
41520
41948
|
"--rule <text>",
|
|
41521
41949
|
`Guidance rule text (default: "Don't report: <title>")`
|
|
@@ -41527,7 +41955,7 @@ function registerFindingsCommands(program2) {
|
|
|
41527
41955
|
"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"
|
|
41528
41956
|
).option("--no-guidance", "Dismiss without writing a tester guidance rule").option("--json", "Output as JSON").addHelpText(
|
|
41529
41957
|
"after",
|
|
41530
|
-
"\nDismissing as not_relevant or
|
|
41958
|
+
"\nDismissing as not_relevant, walker_error, or out_of_scope teaches the walker: it writes a\ntester guidance rule (skip it with --no-guidance)."
|
|
41531
41959
|
).action(
|
|
41532
41960
|
async (refs2, opts) => {
|
|
41533
41961
|
const { triage: triage2 } = await Promise.resolve().then(() => (init_triage(), triage_exports));
|
|
@@ -41626,9 +42054,9 @@ function registerProjectCommands(program2) {
|
|
|
41626
42054
|
"after",
|
|
41627
42055
|
"\nExamples:\n $ iamu validate --all\n $ iamu validate --all --fix --dry-run\n $ iamu validate iamu/personas/maria.md"
|
|
41628
42056
|
).action(
|
|
41629
|
-
async (
|
|
42057
|
+
async (path74, opts) => {
|
|
41630
42058
|
const { validate: validate2 } = await Promise.resolve().then(() => (init_validate(), validate_exports));
|
|
41631
|
-
await validate2(
|
|
42059
|
+
await validate2(path74, opts);
|
|
41632
42060
|
}
|
|
41633
42061
|
);
|
|
41634
42062
|
program2.command("update").helpGroup("Project:").description("Update and repair agent skills and prompts to latest version").option(
|
|
@@ -42070,6 +42498,20 @@ function registerWalkthroughCommands(program2) {
|
|
|
42070
42498
|
await complete2(runId, opts);
|
|
42071
42499
|
}
|
|
42072
42500
|
);
|
|
42501
|
+
program2.command("post-walk [run-id]").helpGroup("Walkthrough:").description(
|
|
42502
|
+
"Complete a run, record staged answers, and prepare post-walk prompts"
|
|
42503
|
+
).option("--run <run-id>", "Run ID (or pass as positional argument)").option("--critic", "Skip advisor and mark designer skipped (--critic)").option("--no-ux", "Mark designer's review skipped (--no-ux)").option(
|
|
42504
|
+
"--by-orchestrator",
|
|
42505
|
+
"Mark recorded designer answer as written by the orchestrator"
|
|
42506
|
+
).option("--json", "Output post-walk status and prompt paths as JSON").addHelpText(
|
|
42507
|
+
"after",
|
|
42508
|
+
"\nExamples:\n $ iamu post-walk <run-id> --json\n $ iamu post-walk <run-id> --critic --json"
|
|
42509
|
+
).action(
|
|
42510
|
+
async (runId, opts) => {
|
|
42511
|
+
const { postWalk: postWalk2 } = await Promise.resolve().then(() => (init_post_walk(), post_walk_exports));
|
|
42512
|
+
await postWalk2(runId, opts);
|
|
42513
|
+
}
|
|
42514
|
+
);
|
|
42073
42515
|
program2.command("match [run-id]").helpGroup("Walkthrough:").description(
|
|
42074
42516
|
"Compose the prompt that links this run's findings to earlier ones"
|
|
42075
42517
|
).option("--run <run-id>", "Run ID (or pass as positional argument)").option("--json", "Output the match input bundle as JSON").option("--prompt-file <path>", "Write the generated prompt to a file").action(
|
|
@@ -42228,7 +42670,7 @@ init_logger();
|
|
|
42228
42670
|
// src/ui/panic.ts
|
|
42229
42671
|
init_dist();
|
|
42230
42672
|
init_logger();
|
|
42231
|
-
import
|
|
42673
|
+
import chalk51 from "chalk";
|
|
42232
42674
|
var handlersRegistered = false;
|
|
42233
42675
|
var customSigintHandler = null;
|
|
42234
42676
|
function logFatalAndExit(title, errOrReason) {
|
|
@@ -42248,16 +42690,16 @@ function logFatalAndExit(title, errOrReason) {
|
|
|
42248
42690
|
process.exit(1);
|
|
42249
42691
|
}
|
|
42250
42692
|
if (isVerbose()) {
|
|
42251
|
-
console.error(
|
|
42693
|
+
console.error(chalk51.red(`
|
|
42252
42694
|
${title}:`));
|
|
42253
42695
|
console.error(
|
|
42254
42696
|
errOrReason instanceof Error ? errOrReason.stack || errOrReason : errOrReason
|
|
42255
42697
|
);
|
|
42256
42698
|
} else {
|
|
42257
|
-
console.error(
|
|
42699
|
+
console.error(chalk51.red(`
|
|
42258
42700
|
Fatal Error: ${msg}`));
|
|
42259
42701
|
console.error(
|
|
42260
|
-
|
|
42702
|
+
chalk51.dim(
|
|
42261
42703
|
" Run with --verbose or DEBUG=1 to view the full stack trace.\n"
|
|
42262
42704
|
)
|
|
42263
42705
|
);
|
|
@@ -42276,7 +42718,7 @@ function setupProcessHandlers() {
|
|
|
42276
42718
|
}
|
|
42277
42719
|
process.exit(130);
|
|
42278
42720
|
} else {
|
|
42279
|
-
console.error(
|
|
42721
|
+
console.error(chalk51.dim("\n Operation cancelled."));
|
|
42280
42722
|
process.exit(130);
|
|
42281
42723
|
}
|
|
42282
42724
|
});
|
|
@@ -42302,7 +42744,7 @@ function setupProcessHandlers() {
|
|
|
42302
42744
|
import { mkdir as mkdir7, readFile as readFile8, writeFile as writeFile5 } from "fs/promises";
|
|
42303
42745
|
import { homedir as homedir4 } from "os";
|
|
42304
42746
|
import { join as join12 } from "path";
|
|
42305
|
-
import
|
|
42747
|
+
import chalk52 from "chalk";
|
|
42306
42748
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
42307
42749
|
function getIamuHome() {
|
|
42308
42750
|
return process.env.IAMU_HOME || join12(homedir4(), ".iamu");
|
|
@@ -42387,7 +42829,7 @@ async function checkForUpdate(currentVersion) {
|
|
|
42387
42829
|
function printUpdateNotice(currentVersion, latestVersion) {
|
|
42388
42830
|
console.error();
|
|
42389
42831
|
console.error(
|
|
42390
|
-
` ${
|
|
42832
|
+
` ${chalk52.yellow("Update available:")} ${chalk52.dim(currentVersion)} \u2192 ${chalk52.green(latestVersion)} ${chalk52.dim("(run: npm i -g @iamu-dev/iamu)")}`
|
|
42391
42833
|
);
|
|
42392
42834
|
}
|
|
42393
42835
|
|
|
@@ -42418,7 +42860,7 @@ var program = new Command().name("iamu").description(
|
|
|
42418
42860
|
const effectiveCwd = root.cwd || process.env.IAMU_DIR || process.env.IAMU_CWD;
|
|
42419
42861
|
if (effectiveCwd) {
|
|
42420
42862
|
const targetDir = resolve3(process.cwd(), effectiveCwd);
|
|
42421
|
-
if (!
|
|
42863
|
+
if (!existsSync24(targetDir)) {
|
|
42422
42864
|
commandError(`Directory does not exist: ${effectiveCwd}`, {
|
|
42423
42865
|
...root,
|
|
42424
42866
|
code: "directory_not_found",
|
|
@@ -42441,7 +42883,7 @@ var program = new Command().name("iamu").description(
|
|
|
42441
42883
|
}
|
|
42442
42884
|
}
|
|
42443
42885
|
if (root.color === false || process.env.NO_COLOR) {
|
|
42444
|
-
|
|
42886
|
+
chalk53.level = 0;
|
|
42445
42887
|
}
|
|
42446
42888
|
if (root.verbose) {
|
|
42447
42889
|
process.env.IAMU_VERBOSE = "1";
|