@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/index.js
CHANGED
|
@@ -521,6 +521,7 @@ var init_finding = __esm({
|
|
|
521
521
|
DISMISS_REASONS = [
|
|
522
522
|
"not_relevant",
|
|
523
523
|
"walker_error",
|
|
524
|
+
"out_of_scope",
|
|
524
525
|
"duplicate"
|
|
525
526
|
];
|
|
526
527
|
dismissReasonSchema = z9.enum(DISMISS_REASONS);
|
|
@@ -570,11 +571,12 @@ var init_finding = __esm({
|
|
|
570
571
|
addressed_at: isoTimestamp.nullish(),
|
|
571
572
|
/**
|
|
572
573
|
* Why a `dismissed` finding was dismissed: `not_relevant` (by design,
|
|
573
|
-
* out of scope), `walker_error` (the walker misread the product)
|
|
574
|
-
* `duplicate`. Absent means `not_relevant`.
|
|
575
|
-
* a tester guidance rule.
|
|
574
|
+
* out of scope), `walker_error` (the walker misread the product),
|
|
575
|
+
* `out_of_scope` or `duplicate`. Absent means `not_relevant`.
|
|
576
576
|
*/
|
|
577
577
|
dismiss_reason: dismissReasonSchema.nullish(),
|
|
578
|
+
/** Finding slug or id this repeats when `dismiss_reason` is `duplicate`. */
|
|
579
|
+
duplicate_of: z9.string().nullish(),
|
|
578
580
|
/**
|
|
579
581
|
* The active tester guidance rule this finding matches, set by
|
|
580
582
|
* `iamu match-record`. The finding is kept — the rule flagged it,
|
|
@@ -697,7 +699,11 @@ var init_guidance = __esm({
|
|
|
697
699
|
"../../packages/protocol/dist/schemas/frontmatter/guidance.js"() {
|
|
698
700
|
"use strict";
|
|
699
701
|
init_common();
|
|
700
|
-
GUIDANCE_KINDS = [
|
|
702
|
+
GUIDANCE_KINDS = [
|
|
703
|
+
"not_relevant",
|
|
704
|
+
"walker_error",
|
|
705
|
+
"out_of_scope"
|
|
706
|
+
];
|
|
701
707
|
guidanceKindSchema = z10.enum(GUIDANCE_KINDS);
|
|
702
708
|
guidanceStatusSchema = z10.enum(["active", "retired"]);
|
|
703
709
|
guidanceScopeSchema = z10.object({
|
|
@@ -1597,6 +1603,7 @@ var init_walkthrough_canonical = __esm({
|
|
|
1597
1603
|
canonicalStepSchema = z22.object({
|
|
1598
1604
|
id: z22.string(),
|
|
1599
1605
|
step_number: z22.number().int(),
|
|
1606
|
+
timestamp: z22.string().optional(),
|
|
1600
1607
|
url: z22.string().optional(),
|
|
1601
1608
|
/** The walker's short name for the screen. */
|
|
1602
1609
|
surface: z22.string().optional(),
|
|
@@ -5834,9 +5841,9 @@ function pidIsDead(rec) {
|
|
|
5834
5841
|
async function isStale(lockPath, rec, staleMs, now) {
|
|
5835
5842
|
if (rec === null) {
|
|
5836
5843
|
try {
|
|
5837
|
-
const
|
|
5838
|
-
if (
|
|
5839
|
-
const mtime =
|
|
5844
|
+
const stat4 = await fs6.stat(lockPath);
|
|
5845
|
+
if (stat4.size === 0) {
|
|
5846
|
+
const mtime = stat4.birthtimeMs && stat4.birthtimeMs > 0 ? stat4.birthtimeMs : stat4.mtimeMs;
|
|
5840
5847
|
if (now - mtime <= staleMs) {
|
|
5841
5848
|
return false;
|
|
5842
5849
|
}
|
|
@@ -6837,13 +6844,13 @@ function fenceTracker() {
|
|
|
6837
6844
|
};
|
|
6838
6845
|
}
|
|
6839
6846
|
function cleanName(raw, legacy) {
|
|
6840
|
-
let s = raw.replace(/\s+#+\s*$/, "").replace(/[*_`]/g, "").replace(/[‘’]/g, "'").replace(/[“”]/g, '"').replace(/\s+/g, " ").trim();
|
|
6847
|
+
let s = raw.replace(/\\(["'])/g, "$1").replace(/\s+#+\s*$/, "").replace(/[*_`]/g, "").replace(/[‘’]/g, "'").replace(/[“”]/g, '"').replace(/\s+/g, " ").trim();
|
|
6841
6848
|
if (legacy)
|
|
6842
6849
|
s = s.replace(LEGACY_ID, "");
|
|
6843
|
-
return s.replace(/[
|
|
6850
|
+
return s.replace(/[.:—–-]+$/, "").trim();
|
|
6844
6851
|
}
|
|
6845
6852
|
function normaliseRuleName(raw, legacy = false) {
|
|
6846
|
-
return cleanName(raw, legacy).toLowerCase();
|
|
6853
|
+
return cleanName(raw, legacy).replace(/["'\\]/g, "").replace(/\s+/g, " ").trim().toLowerCase();
|
|
6847
6854
|
}
|
|
6848
6855
|
function scanLines(text) {
|
|
6849
6856
|
const inFence = fenceTracker();
|
|
@@ -6912,7 +6919,7 @@ function rulesInText(text, opts = {}) {
|
|
|
6912
6919
|
continue;
|
|
6913
6920
|
if (l.heading) {
|
|
6914
6921
|
const { level, text: h } = l.heading;
|
|
6915
|
-
if (level >= 2 && level <=
|
|
6922
|
+
if (level >= 2 && level <= 5) {
|
|
6916
6923
|
let end2 = i + 1;
|
|
6917
6924
|
while (end2 < to) {
|
|
6918
6925
|
const next = lines[end2].heading;
|
|
@@ -6931,25 +6938,30 @@ function rulesInText(text, opts = {}) {
|
|
|
6931
6938
|
headings.push(l.heading);
|
|
6932
6939
|
continue;
|
|
6933
6940
|
}
|
|
6934
|
-
const
|
|
6935
|
-
const
|
|
6941
|
+
const unquoted = l.text.replace(/^\s*>\s?/, "");
|
|
6942
|
+
const item = LIST_ITEM.exec(unquoted);
|
|
6943
|
+
const rest = item ? unquoted.slice(item[0].length) : unquoted.trimStart();
|
|
6936
6944
|
const bold = BOLD_LEAD.exec(rest);
|
|
6937
6945
|
if (!bold)
|
|
6938
6946
|
continue;
|
|
6939
6947
|
const inner = bold[2].trim();
|
|
6940
|
-
|
|
6948
|
+
const afterBold = rest.slice(bold[0].length);
|
|
6949
|
+
const hasStop = /[.:!?—–]$/.test(inner) || Boolean(bold[3]) || afterBold.trim() === "";
|
|
6950
|
+
if (!hasStop)
|
|
6941
6951
|
continue;
|
|
6942
6952
|
const prev = i === 0 ? null : lines[i - 1];
|
|
6943
|
-
const
|
|
6953
|
+
const prevUnquoted = prev ? prev.text.replace(/^\s*>\s?/, "") : "";
|
|
6954
|
+
const startsParagraph = !prev || prevUnquoted.trim() === "" || Boolean(prev.heading);
|
|
6944
6955
|
if (!item && !startsParagraph)
|
|
6945
6956
|
continue;
|
|
6946
6957
|
const indent = item ? item[1].length : 0;
|
|
6947
6958
|
let end = i + 1;
|
|
6948
6959
|
while (end < to) {
|
|
6949
6960
|
const n = lines[end];
|
|
6950
|
-
|
|
6961
|
+
const nUnquoted = n.text.replace(/^\s*>\s?/, "");
|
|
6962
|
+
if (n.heading || !n.fenced && nUnquoted.trim() === "")
|
|
6951
6963
|
break;
|
|
6952
|
-
const nItem = LIST_ITEM.exec(
|
|
6964
|
+
const nItem = LIST_ITEM.exec(nUnquoted);
|
|
6953
6965
|
if (item && nItem && nItem[1].length <= indent)
|
|
6954
6966
|
break;
|
|
6955
6967
|
end++;
|
|
@@ -6974,7 +6986,19 @@ function matchRuleName(rules, citation, legacy = false) {
|
|
|
6974
6986
|
section: normaliseRuleName(citation.slice(0, at), legacy),
|
|
6975
6987
|
name: normaliseRuleName(citation.slice(at + 1), legacy)
|
|
6976
6988
|
} : null;
|
|
6977
|
-
|
|
6989
|
+
const exact = rules.filter((r) => {
|
|
6990
|
+
const rName = normaliseRuleName(r.name, legacy);
|
|
6991
|
+
const rSec = normaliseRuleName(r.section ?? "", legacy);
|
|
6992
|
+
return rName === whole || scoped !== null && rName === scoped.name && rSec === scoped.section;
|
|
6993
|
+
});
|
|
6994
|
+
if (exact.length > 0)
|
|
6995
|
+
return exact;
|
|
6996
|
+
const leadOf = (s) => normaliseRuleName(s.split(/\s*(?::|—|–|\s-\s)\s*/)[0] ?? "", legacy);
|
|
6997
|
+
return rules.filter((r) => {
|
|
6998
|
+
const rLead = leadOf(r.name);
|
|
6999
|
+
const rSec = normaliseRuleName(r.section ?? "", legacy);
|
|
7000
|
+
return rLead.length > 0 && rLead === whole || scoped !== null && rLead.length > 0 && rLead === scoped.name && rSec === scoped.section;
|
|
7001
|
+
});
|
|
6978
7002
|
}
|
|
6979
7003
|
async function findRule(entries, citation) {
|
|
6980
7004
|
const seen = /* @__PURE__ */ new Map();
|
|
@@ -7008,6 +7032,27 @@ async function citationFor(entries, match2) {
|
|
|
7008
7032
|
const alone = await findRule(entries, { rule: match2.name, file: match2.file });
|
|
7009
7033
|
return alone.length === 1 || !match2.section ? match2.name : `${match2.section} > ${match2.name}`;
|
|
7010
7034
|
}
|
|
7035
|
+
async function canonicalRulesInFile(file) {
|
|
7036
|
+
if (file.kind !== "design")
|
|
7037
|
+
return [];
|
|
7038
|
+
let text;
|
|
7039
|
+
try {
|
|
7040
|
+
text = await fs10.readFile(file.abs, "utf8");
|
|
7041
|
+
} catch {
|
|
7042
|
+
return [];
|
|
7043
|
+
}
|
|
7044
|
+
const legacy = file.path === LEGACY_DESIGN_DOC;
|
|
7045
|
+
const rules = rulesInText(text, { legacy, heading: file.heading }) ?? [];
|
|
7046
|
+
const counts = /* @__PURE__ */ new Map();
|
|
7047
|
+
for (const r of rules) {
|
|
7048
|
+
const key = normaliseRuleName(r.name, legacy);
|
|
7049
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
7050
|
+
}
|
|
7051
|
+
return rules.map((r) => {
|
|
7052
|
+
const key = normaliseRuleName(r.name, legacy);
|
|
7053
|
+
return (counts.get(key) ?? 0) === 1 || !r.section ? r.name : `${r.section} > ${r.name}`;
|
|
7054
|
+
});
|
|
7055
|
+
}
|
|
7011
7056
|
async function resolveRule(entries, citation) {
|
|
7012
7057
|
const matches = await findRule(entries, citation);
|
|
7013
7058
|
if (matches.length === 1)
|
|
@@ -7034,7 +7079,7 @@ var init_design_rules = __esm({
|
|
|
7034
7079
|
LEGACY_ID = new RegExp(`^${LEGACY_ID_PATTERN}\\s+`);
|
|
7035
7080
|
HEADING = /^(#{1,6})\s+(.+?)\s*$/;
|
|
7036
7081
|
LIST_ITEM = /^(\s*)(?:[-*+]|\d+[.)])\s+/;
|
|
7037
|
-
BOLD_LEAD = /^(\*\*|__)(.+?)\1
|
|
7082
|
+
BOLD_LEAD = /^(\*\*|__)(.+?)\1(?:\s*([.:—–]|\s-\s))?/;
|
|
7038
7083
|
sameFile = (f, file) => {
|
|
7039
7084
|
const want = file.trim().replace(/\\/g, "/");
|
|
7040
7085
|
return f.entry === want || f.path === want;
|
|
@@ -7722,15 +7767,15 @@ async function entryProblem(projectRoot, entry) {
|
|
|
7722
7767
|
if (!isInsideProjectRoot(projectRoot, abs)) {
|
|
7723
7768
|
return `Path escapes project root: ${rel}`;
|
|
7724
7769
|
}
|
|
7725
|
-
let
|
|
7770
|
+
let stat4;
|
|
7726
7771
|
try {
|
|
7727
|
-
|
|
7772
|
+
stat4 = await fs13.stat(abs);
|
|
7728
7773
|
} catch {
|
|
7729
7774
|
return `Not found: ${rel}`;
|
|
7730
7775
|
}
|
|
7731
7776
|
if (!heading)
|
|
7732
7777
|
return null;
|
|
7733
|
-
if (!
|
|
7778
|
+
if (!stat4.isFile())
|
|
7734
7779
|
return `${rel} is a folder; a section needs a file`;
|
|
7735
7780
|
const text = await fs13.readFile(abs, "utf8");
|
|
7736
7781
|
if (sectionLines(text, heading))
|
|
@@ -8706,7 +8751,14 @@ async function isProposalStale(projectRoot, p) {
|
|
|
8706
8751
|
}
|
|
8707
8752
|
async function readDesignContext(projectRoot, opts = {}) {
|
|
8708
8753
|
const state = await readDesignSources(projectRoot);
|
|
8709
|
-
const files = selectDesignFiles(state.file.files, opts.urls).map((e) =>
|
|
8754
|
+
const files = await Promise.all(selectDesignFiles(state.file.files, opts.urls).map(async (e) => {
|
|
8755
|
+
const resolved = resolveDesignFile(projectRoot, e);
|
|
8756
|
+
if (resolved.kind === "design") {
|
|
8757
|
+
const rules = await canonicalRulesInFile(resolved);
|
|
8758
|
+
return rules.length > 0 ? { ...resolved, rules } : resolved;
|
|
8759
|
+
}
|
|
8760
|
+
return resolved;
|
|
8761
|
+
}));
|
|
8710
8762
|
const proposals = [];
|
|
8711
8763
|
for (const p of await listDesignProposals(projectRoot)) {
|
|
8712
8764
|
proposals.push({
|
|
@@ -9635,7 +9687,7 @@ async function suppressionRules(projectRoot, outlineSlug) {
|
|
|
9635
9687
|
if (!outlineSlug)
|
|
9636
9688
|
return [];
|
|
9637
9689
|
try {
|
|
9638
|
-
return (await activeGuidanceFor(projectRoot, { outline: outlineSlug })).filter((r) => r.kind === "not_relevant").map((r) => ({ id: r.id, rule: r.rule }));
|
|
9690
|
+
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 }));
|
|
9639
9691
|
} catch {
|
|
9640
9692
|
return [];
|
|
9641
9693
|
}
|
|
@@ -10536,6 +10588,63 @@ async function readDesignerIgnored(projectRoot) {
|
|
|
10536
10588
|
return readForWrite(projectRoot).catch(() => []);
|
|
10537
10589
|
}
|
|
10538
10590
|
async function ignoreDesignerIssue(projectRoot, runId, issueId, opts = {}) {
|
|
10591
|
+
const reasonGiven = opts.reason?.trim() || null;
|
|
10592
|
+
const when = opts.now ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
10593
|
+
const cascadeDismissSiblings = async (targetFm, note) => {
|
|
10594
|
+
const cid = targetFm.canonical_id;
|
|
10595
|
+
if (!cid)
|
|
10596
|
+
return;
|
|
10597
|
+
const { entities } = await listEntities(projectRoot, "finding").catch(() => ({ entities: [] }));
|
|
10598
|
+
for (const sibling of entities) {
|
|
10599
|
+
if (sibling.frontmatter.id === targetFm.id)
|
|
10600
|
+
continue;
|
|
10601
|
+
if (sibling.frontmatter.canonical_id !== cid)
|
|
10602
|
+
continue;
|
|
10603
|
+
if (sibling.frontmatter.triage_status === "dismissed")
|
|
10604
|
+
continue;
|
|
10605
|
+
await writeEntity(projectRoot, "finding", {
|
|
10606
|
+
...sibling.frontmatter,
|
|
10607
|
+
triage_status: "dismissed",
|
|
10608
|
+
dismiss_reason: "not_relevant",
|
|
10609
|
+
triage_note: note,
|
|
10610
|
+
updated_at: when
|
|
10611
|
+
}, sibling.body);
|
|
10612
|
+
}
|
|
10613
|
+
};
|
|
10614
|
+
const trimmedRef = issueId.trim().toUpperCase();
|
|
10615
|
+
if (/^P\d+$/.test(trimmedRef)) {
|
|
10616
|
+
const refs2 = await fs21.readFile(designerRefsFile(runDir(projectRoot, runId)), "utf8").then((raw) => JSON.parse(raw)).catch(() => null);
|
|
10617
|
+
const findingId = refs2?.[trimmedRef];
|
|
10618
|
+
const pFile = findingId ? await readEntity(projectRoot, "finding", findingId).catch(() => null) : null;
|
|
10619
|
+
if (!pFile) {
|
|
10620
|
+
throw new DesignerRecordError("unknown_designer_finding", `${runId} has no designer finding ${issueId}`);
|
|
10621
|
+
}
|
|
10622
|
+
const already = pFile.frontmatter.triage_status === "dismissed";
|
|
10623
|
+
const note = reasonGiven ?? (pFile.frontmatter.triage_note?.trim() || "Dismissed via iamu designer-dismiss");
|
|
10624
|
+
if (!already || reasonGiven && reasonGiven !== pFile.frontmatter.triage_note) {
|
|
10625
|
+
await writeEntity(projectRoot, "finding", {
|
|
10626
|
+
...pFile.frontmatter,
|
|
10627
|
+
triage_status: "dismissed",
|
|
10628
|
+
dismiss_reason: "not_relevant",
|
|
10629
|
+
triage_note: note,
|
|
10630
|
+
updated_at: when
|
|
10631
|
+
}, pFile.body);
|
|
10632
|
+
await cascadeDismissSiblings(pFile.frontmatter, note);
|
|
10633
|
+
}
|
|
10634
|
+
return {
|
|
10635
|
+
entry: {
|
|
10636
|
+
title: pFile.frontmatter.title ?? pFile.frontmatter.slug,
|
|
10637
|
+
screen: pFile.frontmatter.screen ?? null,
|
|
10638
|
+
what: pFile.frontmatter.what ?? "",
|
|
10639
|
+
severity: pFile.frontmatter.severity ?? void 0,
|
|
10640
|
+
reason: reasonGiven ?? pFile.frontmatter.triage_note ?? null,
|
|
10641
|
+
run: runId,
|
|
10642
|
+
issue: trimmedRef,
|
|
10643
|
+
at: when
|
|
10644
|
+
},
|
|
10645
|
+
already
|
|
10646
|
+
};
|
|
10647
|
+
}
|
|
10539
10648
|
const record = await readDesignerRecord(projectRoot, runId);
|
|
10540
10649
|
if (!record) {
|
|
10541
10650
|
throw new DesignerRecordError("not_judged", `Run ${runId} has no designer's review to dismiss a finding of`);
|
|
@@ -10545,8 +10654,6 @@ async function ignoreDesignerIssue(projectRoot, runId, issueId, opts = {}) {
|
|
|
10545
10654
|
if (!issue) {
|
|
10546
10655
|
throw new DesignerRecordError("unknown_designer_finding", `${runId} has no designer finding ${issueId}`);
|
|
10547
10656
|
}
|
|
10548
|
-
const reasonGiven = opts.reason?.trim() || null;
|
|
10549
|
-
const when = opts.now ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
10550
10657
|
const file = issue.finding_id ? await readEntity(projectRoot, "finding", issue.finding_id).catch(() => null) : null;
|
|
10551
10658
|
if (file) {
|
|
10552
10659
|
const already = file.frontmatter.triage_status === "dismissed";
|
|
@@ -10559,6 +10666,7 @@ async function ignoreDesignerIssue(projectRoot, runId, issueId, opts = {}) {
|
|
|
10559
10666
|
triage_note: note,
|
|
10560
10667
|
updated_at: when
|
|
10561
10668
|
}, file.body);
|
|
10669
|
+
await cascadeDismissSiblings(file.frontmatter, note);
|
|
10562
10670
|
}
|
|
10563
10671
|
return {
|
|
10564
10672
|
entry: {
|
|
@@ -10925,6 +11033,7 @@ async function buildCanonical(projectRoot, manifest, completedAt, inputs) {
|
|
|
10925
11033
|
return {
|
|
10926
11034
|
id: `step-${stepNum}`,
|
|
10927
11035
|
step_number: stepNum,
|
|
11036
|
+
...typeof s.timestamp === "string" && s.timestamp ? { timestamp: s.timestamp } : {},
|
|
10928
11037
|
screen_title: s.action,
|
|
10929
11038
|
// Step files record the path relative to the run directory
|
|
10930
11039
|
// (`screenshots/step-1.png`); older ones recorded the bare file
|
|
@@ -11423,7 +11532,7 @@ async function assembleWalkthroughBundle(projectRoot, runId, opts = {}) {
|
|
|
11423
11532
|
scenarioIdForPush = manifest.scenario_slug;
|
|
11424
11533
|
}
|
|
11425
11534
|
}
|
|
11426
|
-
const bundleSteps = steps2.map((s) => {
|
|
11535
|
+
const bundleSteps = steps2.map((s, idx) => {
|
|
11427
11536
|
const shot = screenshots.get(s.step_number);
|
|
11428
11537
|
const ref = shot ? {
|
|
11429
11538
|
part_name: `screenshot_${s.step_number}`,
|
|
@@ -11431,7 +11540,7 @@ async function assembleWalkthroughBundle(projectRoot, runId, opts = {}) {
|
|
|
11431
11540
|
content_type: shot.contentType
|
|
11432
11541
|
} : null;
|
|
11433
11542
|
return {
|
|
11434
|
-
seq:
|
|
11543
|
+
seq: idx + 1,
|
|
11435
11544
|
title: s.action,
|
|
11436
11545
|
persona_reasoning: s.note ?? null,
|
|
11437
11546
|
grounding: s.grounding ?? null,
|
|
@@ -17053,9 +17162,9 @@ function withSessionStateLock(filePath, fn) {
|
|
|
17053
17162
|
const now = Date.now();
|
|
17054
17163
|
if (rec === null) {
|
|
17055
17164
|
try {
|
|
17056
|
-
const
|
|
17057
|
-
if (
|
|
17058
|
-
const mtime =
|
|
17165
|
+
const stat4 = statSync(targetPath);
|
|
17166
|
+
if (stat4.size === 0) {
|
|
17167
|
+
const mtime = stat4.birthtimeMs && stat4.birthtimeMs > 0 ? stat4.birthtimeMs : stat4.mtimeMs;
|
|
17059
17168
|
if (now - mtime <= staleMs)
|
|
17060
17169
|
return false;
|
|
17061
17170
|
}
|
|
@@ -18672,7 +18781,12 @@ async function buildSurfaceDossier(projectRoot, target, opts = {}) {
|
|
|
18672
18781
|
seenDismissed.add(d.title);
|
|
18673
18782
|
return true;
|
|
18674
18783
|
});
|
|
18675
|
-
const guidance = (await listEntities(projectRoot, "guidance")).entities.filter((g) => (g.frontmatter.status ?? "active") === "active").map((g) =>
|
|
18784
|
+
const guidance = (await listEntities(projectRoot, "guidance")).entities.filter((g) => (g.frontmatter.status ?? "active") === "active").map((g) => {
|
|
18785
|
+
const { rule, reason } = parseGuidanceBody(g.body);
|
|
18786
|
+
if (!rule)
|
|
18787
|
+
return "";
|
|
18788
|
+
return reason ? `${rule} (${reason})` : rule;
|
|
18789
|
+
}).filter(Boolean);
|
|
18676
18790
|
const ids = new Set(allOnSurface.map((r) => r.fm.id));
|
|
18677
18791
|
const directions = (await listDirections(projectRoot)).filter((d) => d.frontmatter.status !== "rejected" && d.frontmatter.absorbs.some((id) => ids.has(id))).map((d) => ({
|
|
18678
18792
|
slug: d.frontmatter.slug,
|
|
@@ -20516,6 +20630,23 @@ async function buildRunDesignerInput(projectRoot, runId) {
|
|
|
20516
20630
|
const ended = summary?.ended;
|
|
20517
20631
|
const testability = testabilityPath(projectRoot);
|
|
20518
20632
|
const pfm = persona?.frontmatter;
|
|
20633
|
+
const outlineSlug = outline?.frontmatter.slug ?? scenario?.frontmatter.outline ?? null;
|
|
20634
|
+
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) => {
|
|
20635
|
+
const { rule, reason: reason2 } = parseGuidanceBody(g.body);
|
|
20636
|
+
return { rule, reason: reason2 };
|
|
20637
|
+
}).filter((g) => Boolean(g.rule));
|
|
20638
|
+
const guidance = guidanceRules.map((r) => r.reason ? `${r.rule} (${r.reason})` : r.rule);
|
|
20639
|
+
const designFiles = await Promise.all(docs.map(async (f) => {
|
|
20640
|
+
const rules = f.kind === "design" ? f.rules ?? await canonicalRulesInFile(f) : void 0;
|
|
20641
|
+
return {
|
|
20642
|
+
entry: f.entry,
|
|
20643
|
+
kind: f.kind,
|
|
20644
|
+
abs: f.abs,
|
|
20645
|
+
heading: f.heading,
|
|
20646
|
+
applies_to: f.applies_to,
|
|
20647
|
+
...rules && rules.length > 0 ? { rules } : {}
|
|
20648
|
+
};
|
|
20649
|
+
}));
|
|
20519
20650
|
const bundle = {
|
|
20520
20651
|
run_id: runId,
|
|
20521
20652
|
project_root: projectRoot,
|
|
@@ -20540,13 +20671,7 @@ async function buildRunDesignerInput(projectRoot, runId) {
|
|
|
20540
20671
|
ended: ended && typeof ended.note === "string" ? { reached: ended.reached === true, note: ended.note } : null
|
|
20541
20672
|
} : null,
|
|
20542
20673
|
design: docs.length > 0 || proposals.length > 0 ? {
|
|
20543
|
-
files:
|
|
20544
|
-
entry: f.entry,
|
|
20545
|
-
kind: f.kind,
|
|
20546
|
-
abs: f.abs,
|
|
20547
|
-
heading: f.heading,
|
|
20548
|
-
applies_to: f.applies_to
|
|
20549
|
-
})),
|
|
20674
|
+
files: designFiles,
|
|
20550
20675
|
proposals: proposals.map((p) => ({
|
|
20551
20676
|
slug: p.slug,
|
|
20552
20677
|
kind: p.kind,
|
|
@@ -20574,6 +20699,7 @@ async function buildRunDesignerInput(projectRoot, runId) {
|
|
|
20574
20699
|
reason: e.reason
|
|
20575
20700
|
}))
|
|
20576
20701
|
],
|
|
20702
|
+
guidance,
|
|
20577
20703
|
facts: context?.facts ? designerFacts(context.facts) : null,
|
|
20578
20704
|
setup_notes: {
|
|
20579
20705
|
file: existsSync5(testability) ? testability : null,
|
|
@@ -20622,6 +20748,9 @@ var init_designer_input = __esm({
|
|
|
20622
20748
|
init_advice_input();
|
|
20623
20749
|
init_design_facts();
|
|
20624
20750
|
init_design_proposals();
|
|
20751
|
+
init_design_rules();
|
|
20752
|
+
init_entity();
|
|
20753
|
+
init_guidance2();
|
|
20625
20754
|
init_paths();
|
|
20626
20755
|
init_review_issues();
|
|
20627
20756
|
init_url_path();
|
|
@@ -20629,6 +20758,7 @@ var init_designer_input = __esm({
|
|
|
20629
20758
|
init_designer_ignored();
|
|
20630
20759
|
init_designer_ledger();
|
|
20631
20760
|
init_walkthrough_canonical2();
|
|
20761
|
+
init_dist();
|
|
20632
20762
|
LONG_VISIT = 6;
|
|
20633
20763
|
FRAMES_PER_VISIT = 8;
|
|
20634
20764
|
FRAMES_PER_RUN = 24;
|
|
@@ -22014,15 +22144,15 @@ function resolveApiUrl(configured) {
|
|
|
22014
22144
|
assertSecureApiUrl(url, configured ? "API URL" : "IAMU_API_URL");
|
|
22015
22145
|
return url;
|
|
22016
22146
|
}
|
|
22017
|
-
function parseStore(raw,
|
|
22147
|
+
function parseStore(raw, path74) {
|
|
22018
22148
|
let parsed;
|
|
22019
22149
|
try {
|
|
22020
22150
|
parsed = JSON.parse(raw);
|
|
22021
22151
|
} catch (err) {
|
|
22022
|
-
throw new CredentialsFileError(
|
|
22152
|
+
throw new CredentialsFileError(path74, "not valid JSON", err);
|
|
22023
22153
|
}
|
|
22024
22154
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
22025
|
-
throw new CredentialsFileError(
|
|
22155
|
+
throw new CredentialsFileError(path74, "expected a JSON object");
|
|
22026
22156
|
}
|
|
22027
22157
|
return parsed;
|
|
22028
22158
|
}
|
|
@@ -22030,26 +22160,26 @@ function isNotFound(err) {
|
|
|
22030
22160
|
return typeof err === "object" && err !== null && err.code === "ENOENT";
|
|
22031
22161
|
}
|
|
22032
22162
|
async function readCredentialsStore() {
|
|
22033
|
-
const
|
|
22163
|
+
const path74 = getCredentialsPath();
|
|
22034
22164
|
let raw;
|
|
22035
22165
|
try {
|
|
22036
|
-
raw = await readFile(
|
|
22166
|
+
raw = await readFile(path74, "utf-8");
|
|
22037
22167
|
} catch (err) {
|
|
22038
22168
|
if (isNotFound(err)) return {};
|
|
22039
22169
|
warnUnreadableStoreOnce(err);
|
|
22040
22170
|
throw err;
|
|
22041
22171
|
}
|
|
22042
22172
|
try {
|
|
22043
|
-
return parseStore(raw,
|
|
22173
|
+
return parseStore(raw, path74);
|
|
22044
22174
|
} catch (err) {
|
|
22045
22175
|
warnUnreadableStoreOnce(err);
|
|
22046
22176
|
throw err;
|
|
22047
22177
|
}
|
|
22048
22178
|
}
|
|
22049
22179
|
async function writeCredentialsStore(store) {
|
|
22050
|
-
const
|
|
22051
|
-
await mkdir(dirname(
|
|
22052
|
-
await atomicWriteFile(
|
|
22180
|
+
const path74 = getCredentialsPath();
|
|
22181
|
+
await mkdir(dirname(path74), { recursive: true, mode: 448 });
|
|
22182
|
+
await atomicWriteFile(path74, `${JSON.stringify(store, null, 2)}
|
|
22053
22183
|
`, {
|
|
22054
22184
|
mode: 384
|
|
22055
22185
|
});
|
|
@@ -22077,16 +22207,16 @@ function resolveApiKey(apiUrl) {
|
|
|
22077
22207
|
}
|
|
22078
22208
|
}
|
|
22079
22209
|
const normalized = url.replace(/\/+$/, "");
|
|
22080
|
-
for (const
|
|
22210
|
+
for (const path74 of candidatePaths) {
|
|
22081
22211
|
let raw;
|
|
22082
22212
|
try {
|
|
22083
|
-
raw = readFileSync2(
|
|
22213
|
+
raw = readFileSync2(path74, "utf-8");
|
|
22084
22214
|
} catch (err) {
|
|
22085
22215
|
if (!isNotFound(err)) warnUnreadableStoreOnce(err);
|
|
22086
22216
|
continue;
|
|
22087
22217
|
}
|
|
22088
22218
|
try {
|
|
22089
|
-
const store = parseStore(raw,
|
|
22219
|
+
const store = parseStore(raw, path74);
|
|
22090
22220
|
const entry = store[url] ?? store[normalized] ?? store[`${normalized}/`];
|
|
22091
22221
|
if (typeof entry !== "object" || entry === null) continue;
|
|
22092
22222
|
const token = entry.token;
|
|
@@ -22127,12 +22257,12 @@ var init_credentials = __esm({
|
|
|
22127
22257
|
* @param reason - What is wrong with it.
|
|
22128
22258
|
* @param cause - Underlying parse error, if any.
|
|
22129
22259
|
*/
|
|
22130
|
-
constructor(
|
|
22260
|
+
constructor(path74, reason, cause) {
|
|
22131
22261
|
super(
|
|
22132
|
-
`Credentials file ${
|
|
22262
|
+
`Credentials file ${path74} is corrupt (${reason}). Fix it by hand, or delete it and run \`iamu auth login\` again.`,
|
|
22133
22263
|
{
|
|
22134
22264
|
cause,
|
|
22135
|
-
suggestedAction: `Delete ${
|
|
22265
|
+
suggestedAction: `Delete ${path74} and run \`iamu auth login\`, or set IAMU_TOKEN.`
|
|
22136
22266
|
}
|
|
22137
22267
|
);
|
|
22138
22268
|
}
|
|
@@ -23027,8 +23157,8 @@ import { dirname as dirname3, join as join5, resolve as resolve2 } from "path";
|
|
|
23027
23157
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
23028
23158
|
import chalk6 from "chalk";
|
|
23029
23159
|
function getVersion() {
|
|
23030
|
-
if ("0.8.0-beta.
|
|
23031
|
-
return "0.8.0-beta.
|
|
23160
|
+
if ("0.8.0-beta.3") {
|
|
23161
|
+
return "0.8.0-beta.3";
|
|
23032
23162
|
}
|
|
23033
23163
|
try {
|
|
23034
23164
|
const thisDir = dirname3(fileURLToPath2(import.meta.url));
|
|
@@ -23140,16 +23270,16 @@ async function needsWrite(filePath, content) {
|
|
|
23140
23270
|
return true;
|
|
23141
23271
|
}
|
|
23142
23272
|
}
|
|
23143
|
-
function isCliWrittenSkill(
|
|
23273
|
+
function isCliWrittenSkill(path74) {
|
|
23144
23274
|
try {
|
|
23145
|
-
return /^generatedBy:/m.test(readFileSync6(
|
|
23275
|
+
return /^generatedBy:/m.test(readFileSync6(path74, "utf-8"));
|
|
23146
23276
|
} catch {
|
|
23147
23277
|
return false;
|
|
23148
23278
|
}
|
|
23149
23279
|
}
|
|
23150
|
-
function isCliWrittenPrompt(
|
|
23280
|
+
function isCliWrittenPrompt(path74) {
|
|
23151
23281
|
try {
|
|
23152
|
-
const content = readFileSync6(
|
|
23282
|
+
const content = readFileSync6(path74, "utf-8");
|
|
23153
23283
|
return /^generatedBy:/m.test(content) || /^#\s+iamu:/m.test(content) || /^description:\s*.*iamu/im.test(content);
|
|
23154
23284
|
} catch {
|
|
23155
23285
|
return false;
|
|
@@ -23161,8 +23291,8 @@ function planSkillFiles(hosts, projectRoot, version) {
|
|
|
23161
23291
|
for (const host of hosts) {
|
|
23162
23292
|
if (pluginProvidesSkills(host, projectRoot)) {
|
|
23163
23293
|
for (const name of skillTemplates.keys()) {
|
|
23164
|
-
const
|
|
23165
|
-
if (isCliWrittenSkill(
|
|
23294
|
+
const path74 = join5(host.getSkillsDir(projectRoot), name, "SKILL.md");
|
|
23295
|
+
if (isCliWrittenSkill(path74)) files.push({ host, path: path74, content: null });
|
|
23166
23296
|
}
|
|
23167
23297
|
} else if (host.supportsSkills) {
|
|
23168
23298
|
const skillsDir = host.getSkillsDir(projectRoot);
|
|
@@ -25192,13 +25322,13 @@ function git(args2, cwd) {
|
|
|
25192
25322
|
}
|
|
25193
25323
|
function hashFile(absPath) {
|
|
25194
25324
|
const hash = createHash5("sha256");
|
|
25195
|
-
const
|
|
25196
|
-
if (
|
|
25325
|
+
const stat4 = lstatSync(absPath);
|
|
25326
|
+
if (stat4.isSymbolicLink()) {
|
|
25197
25327
|
hash.update(`symlink:${readlinkSync(absPath)}`);
|
|
25198
25328
|
return hash.digest("hex");
|
|
25199
25329
|
}
|
|
25200
|
-
if (!
|
|
25201
|
-
hash.update(`${
|
|
25330
|
+
if (!stat4.isFile()) {
|
|
25331
|
+
hash.update(`${stat4.isDirectory() ? "dir" : "other"}`);
|
|
25202
25332
|
return hash.digest("hex");
|
|
25203
25333
|
}
|
|
25204
25334
|
const fd = openSync3(absPath, "r");
|
|
@@ -31459,12 +31589,20 @@ function awaitingSteps(projectRoot, findings2) {
|
|
|
31459
31589
|
}
|
|
31460
31590
|
function defaultRule(kind, findings2) {
|
|
31461
31591
|
const titles = findings2.map((f) => f.frontmatter.title ?? f.frontmatter.ui_fact ?? f.slug).join("; ");
|
|
31462
|
-
|
|
31592
|
+
if (kind === "walker_error") {
|
|
31593
|
+
return `Earlier walkers got this wrong: ${titles}`;
|
|
31594
|
+
}
|
|
31595
|
+
if (kind === "out_of_scope") {
|
|
31596
|
+
return `Handled outside system; don't report: ${titles}`;
|
|
31597
|
+
}
|
|
31598
|
+
return `Don't report: ${titles}`;
|
|
31463
31599
|
}
|
|
31464
31600
|
async function resolveScope(projectRoot, flag, findings2, opts) {
|
|
31465
31601
|
if (flag === "project") return {};
|
|
31602
|
+
const withScenario = findings2.filter((f) => Boolean(f.frontmatter.scenario));
|
|
31603
|
+
if (withScenario.length === 0) return {};
|
|
31466
31604
|
const outlines = /* @__PURE__ */ new Set();
|
|
31467
|
-
for (const f of
|
|
31605
|
+
for (const f of withScenario) {
|
|
31468
31606
|
const scenario = await safeReadEntity(
|
|
31469
31607
|
projectRoot,
|
|
31470
31608
|
"scenario",
|
|
@@ -31505,6 +31643,47 @@ async function resolveScope(projectRoot, flag, findings2, opts) {
|
|
|
31505
31643
|
}
|
|
31506
31644
|
return { capability };
|
|
31507
31645
|
}
|
|
31646
|
+
async function resolveDesignerRefInTriage(projectRoot, ref) {
|
|
31647
|
+
const m = /^(?:([^#]+)#)?([DP]\d+)$/i.exec(ref.trim());
|
|
31648
|
+
if (!m) return null;
|
|
31649
|
+
const runPart = m[1]?.trim();
|
|
31650
|
+
const code = (m[2] ?? "").toUpperCase();
|
|
31651
|
+
const targetRunId = resolveRunId(projectRoot, runPart);
|
|
31652
|
+
if (!targetRunId) return null;
|
|
31653
|
+
if (code.startsWith("P")) {
|
|
31654
|
+
const refsRaw = await fs62.readFile(
|
|
31655
|
+
path59.join(runDir(projectRoot, targetRunId), DESIGNER_REFS_FILE),
|
|
31656
|
+
"utf8"
|
|
31657
|
+
).catch(() => null);
|
|
31658
|
+
if (!refsRaw) return null;
|
|
31659
|
+
try {
|
|
31660
|
+
const parsed = JSON.parse(refsRaw);
|
|
31661
|
+
const findingId = parsed[code];
|
|
31662
|
+
if (findingId) {
|
|
31663
|
+
return await readEntity(
|
|
31664
|
+
projectRoot,
|
|
31665
|
+
"finding",
|
|
31666
|
+
findingId
|
|
31667
|
+
);
|
|
31668
|
+
}
|
|
31669
|
+
} catch {
|
|
31670
|
+
return null;
|
|
31671
|
+
}
|
|
31672
|
+
return null;
|
|
31673
|
+
}
|
|
31674
|
+
const rec = await readDesignerRecord(projectRoot, targetRunId).catch(
|
|
31675
|
+
() => null
|
|
31676
|
+
);
|
|
31677
|
+
const issue = rec?.issues.find((i) => i.id.toUpperCase() === code);
|
|
31678
|
+
if (issue?.finding_id) {
|
|
31679
|
+
return await readEntity(
|
|
31680
|
+
projectRoot,
|
|
31681
|
+
"finding",
|
|
31682
|
+
issue.finding_id
|
|
31683
|
+
);
|
|
31684
|
+
}
|
|
31685
|
+
return null;
|
|
31686
|
+
}
|
|
31508
31687
|
async function triage(refs2, opts) {
|
|
31509
31688
|
const ctx = await resolveProject(opts);
|
|
31510
31689
|
if (!ctx) return;
|
|
@@ -31544,42 +31723,39 @@ async function triage(refs2, opts) {
|
|
|
31544
31723
|
}
|
|
31545
31724
|
reason = opts.reason;
|
|
31546
31725
|
}
|
|
31547
|
-
if (
|
|
31548
|
-
|
|
31549
|
-
|
|
31726
|
+
if (opts.duplicateOf !== void 0) {
|
|
31727
|
+
if (status2.data !== "dismissed") {
|
|
31728
|
+
commandError("--duplicate-of applies only to --status dismissed", {
|
|
31729
|
+
...opts,
|
|
31730
|
+
code: "invalid_duplicate_of_usage",
|
|
31731
|
+
fix: `iamu triage ${refs2.join(" ")} --status dismissed --duplicate-of ${opts.duplicateOf}`
|
|
31732
|
+
});
|
|
31733
|
+
return;
|
|
31734
|
+
}
|
|
31735
|
+
if (reason && reason !== "duplicate") {
|
|
31550
31736
|
commandError(
|
|
31551
|
-
|
|
31737
|
+
`--duplicate-of requires --reason duplicate \u2014 got "${reason}"`,
|
|
31552
31738
|
{
|
|
31553
31739
|
...opts,
|
|
31554
|
-
code: "
|
|
31555
|
-
fix: `iamu triage ${refs2.join(" ")} --status dismissed --
|
|
31740
|
+
code: "invalid_duplicate_of_usage",
|
|
31741
|
+
fix: `iamu triage ${refs2.join(" ")} --status dismissed --reason duplicate --duplicate-of ${opts.duplicateOf}`
|
|
31556
31742
|
}
|
|
31557
31743
|
);
|
|
31558
31744
|
return;
|
|
31559
31745
|
}
|
|
31746
|
+
reason = "duplicate";
|
|
31747
|
+
}
|
|
31748
|
+
if (status2.data === "dismissed") {
|
|
31749
|
+
reason ??= "not_relevant";
|
|
31560
31750
|
}
|
|
31561
31751
|
const isSweep = opts.sweep !== void 0 && opts.sweep !== false;
|
|
31562
31752
|
const sweepQuery = typeof opts.sweep === "string" ? opts.sweep.trim() : "";
|
|
31563
31753
|
if (isSweep && status2.data !== "dismissed" && status2.data !== "wont_fix") {
|
|
31564
|
-
commandError(
|
|
31565
|
-
|
|
31566
|
-
|
|
31567
|
-
|
|
31568
|
-
|
|
31569
|
-
fix: `iamu triage ${refs2.join(" ")} --status dismissed --note "by design" --sweep`
|
|
31570
|
-
}
|
|
31571
|
-
);
|
|
31572
|
-
return;
|
|
31573
|
-
}
|
|
31574
|
-
if (isSweep && status2.data === "wont_fix" && !opts.note?.trim()) {
|
|
31575
|
-
commandError(
|
|
31576
|
-
"Dismissing with --sweep needs --note naming why \u2014 it teaches the whole project",
|
|
31577
|
-
{
|
|
31578
|
-
...opts,
|
|
31579
|
-
code: "missing_dismiss_note",
|
|
31580
|
-
fix: `iamu triage ${refs2.join(" ")} --status wont_fix --note "by design" --sweep`
|
|
31581
|
-
}
|
|
31582
|
-
);
|
|
31754
|
+
commandError("--sweep applies only when dismissing (--status dismissed)", {
|
|
31755
|
+
...opts,
|
|
31756
|
+
code: "invalid_sweep_usage",
|
|
31757
|
+
fix: `iamu triage ${refs2.join(" ")} --status dismissed --sweep`
|
|
31758
|
+
});
|
|
31583
31759
|
return;
|
|
31584
31760
|
}
|
|
31585
31761
|
if (isSweep && opts.scope !== void 0) {
|
|
@@ -31622,6 +31798,9 @@ async function triage(refs2, opts) {
|
|
|
31622
31798
|
"finding",
|
|
31623
31799
|
ref
|
|
31624
31800
|
);
|
|
31801
|
+
if (!entity) {
|
|
31802
|
+
entity = await resolveDesignerRefInTriage(projectRoot, ref);
|
|
31803
|
+
}
|
|
31625
31804
|
} catch (err) {
|
|
31626
31805
|
commandError(
|
|
31627
31806
|
`Finding ${ref} does not validate: ${err instanceof Error ? err.message.split("\n")[0] : String(err)} \u2014 run \`iamu validate --all\``,
|
|
@@ -31698,29 +31877,25 @@ async function triage(refs2, opts) {
|
|
|
31698
31877
|
}
|
|
31699
31878
|
const allTargets = [...loaded, ...swept];
|
|
31700
31879
|
const persona = allTargets.filter((f) => isPersonaFinding(f.frontmatter));
|
|
31701
|
-
if (!isSweep && opts.rule !== void 0 && persona.length < loaded.length) {
|
|
31702
|
-
commandError(
|
|
31703
|
-
"--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)",
|
|
31704
|
-
{
|
|
31705
|
-
...opts,
|
|
31706
|
-
code: "rule_for_designer_finding",
|
|
31707
|
-
fix: `iamu triage ${refs2.join(" ")} --status dismissed --note "<why>"`
|
|
31708
|
-
}
|
|
31709
|
-
);
|
|
31710
|
-
return;
|
|
31711
|
-
}
|
|
31712
31880
|
const effectiveReason = reason ?? (isSweep ? "not_relevant" : null);
|
|
31713
|
-
const
|
|
31881
|
+
const duplicateTarget = status2.data === "dismissed" && effectiveReason === "duplicate" ? opts.duplicateOf?.trim() || null : null;
|
|
31882
|
+
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);
|
|
31714
31883
|
let scope = {};
|
|
31715
31884
|
if (teaches && !isSweep) {
|
|
31716
|
-
const
|
|
31885
|
+
const scopeTargets = persona.length > 0 ? persona : allTargets;
|
|
31886
|
+
const resolved = await resolveScope(
|
|
31887
|
+
projectRoot,
|
|
31888
|
+
scopeFlag,
|
|
31889
|
+
scopeTargets,
|
|
31890
|
+
opts
|
|
31891
|
+
);
|
|
31717
31892
|
if (!resolved) return;
|
|
31718
31893
|
scope = resolved;
|
|
31719
31894
|
}
|
|
31720
31895
|
let guidance = null;
|
|
31721
31896
|
if (teaches && effectiveReason) {
|
|
31722
31897
|
const ruleSources = allTargets.length > 0 ? allTargets : persona;
|
|
31723
|
-
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));
|
|
31898
|
+
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));
|
|
31724
31899
|
const rule = await createGuidanceRule(projectRoot, {
|
|
31725
31900
|
kind: effectiveReason,
|
|
31726
31901
|
rule: ruleText,
|
|
@@ -31761,7 +31936,8 @@ async function triage(refs2, opts) {
|
|
|
31761
31936
|
updated_at: now,
|
|
31762
31937
|
addressed_at: addressedAt(f.frontmatter),
|
|
31763
31938
|
dismiss_reason: status2.data === "dismissed" ? effectiveReason : null,
|
|
31764
|
-
|
|
31939
|
+
duplicate_of: duplicateTarget,
|
|
31940
|
+
...(isSweep || !isPersonaFinding(f.frontmatter)) && guidance ? { suppressed_by_guidance: guidance.id } : {}
|
|
31765
31941
|
};
|
|
31766
31942
|
if (opts.note !== void 0) fm.triage_note = opts.note.trim();
|
|
31767
31943
|
await writeEntity(projectRoot, "finding", fm, f.body);
|
|
@@ -31789,6 +31965,7 @@ async function triage(refs2, opts) {
|
|
|
31789
31965
|
updated_at: now,
|
|
31790
31966
|
addressed_at: addressedAt(c.frontmatter),
|
|
31791
31967
|
dismiss_reason: status2.data === "dismissed" ? effectiveReason : null,
|
|
31968
|
+
duplicate_of: duplicateTarget,
|
|
31792
31969
|
...isSweep && guidance ? { suppressed_by_guidance: guidance.id } : {},
|
|
31793
31970
|
...opts.note !== void 0 ? { triage_note: opts.note.trim() } : {}
|
|
31794
31971
|
},
|
|
@@ -32876,7 +33053,7 @@ var init_browse_driver = __esm({
|
|
|
32876
33053
|
});
|
|
32877
33054
|
|
|
32878
33055
|
// src/commands/step-numbering.ts
|
|
32879
|
-
import { open, readdir, unlink } from "fs/promises";
|
|
33056
|
+
import { open, readdir, stat as stat2, unlink } from "fs/promises";
|
|
32880
33057
|
import path61 from "path";
|
|
32881
33058
|
function stepFileName(stepNumber2) {
|
|
32882
33059
|
return `${String(stepNumber2).padStart(3, "0")}.json`;
|
|
@@ -32889,7 +33066,16 @@ async function peekNextStepNumber(stepsDir) {
|
|
|
32889
33066
|
let max = 0;
|
|
32890
33067
|
for (const f of files) {
|
|
32891
33068
|
const m = /^(\d+)\.json$/.exec(f);
|
|
32892
|
-
if (m)
|
|
33069
|
+
if (!m) continue;
|
|
33070
|
+
const fullPath = path61.join(stepsDir, f);
|
|
33071
|
+
const st = await stat2(fullPath).catch(() => null);
|
|
33072
|
+
if (!st) continue;
|
|
33073
|
+
if (st.size === 0 && Date.now() - st.mtimeMs > STALE_RESERVATION_MS) {
|
|
33074
|
+
await unlink(fullPath).catch(() => {
|
|
33075
|
+
});
|
|
33076
|
+
continue;
|
|
33077
|
+
}
|
|
33078
|
+
max = Math.max(max, Number(m[1]));
|
|
32893
33079
|
}
|
|
32894
33080
|
return max + 1;
|
|
32895
33081
|
}
|
|
@@ -32912,13 +33098,21 @@ async function reserveStepFile(stepsDir, limit) {
|
|
|
32912
33098
|
};
|
|
32913
33099
|
} catch (err) {
|
|
32914
33100
|
if (err.code !== "EEXIST") throw err;
|
|
33101
|
+
const st = await stat2(stepPath).catch(() => null);
|
|
33102
|
+
if (st && st.size === 0 && Date.now() - st.mtimeMs > STALE_RESERVATION_MS) {
|
|
33103
|
+
await unlink(stepPath).catch(() => {
|
|
33104
|
+
});
|
|
33105
|
+
continue;
|
|
33106
|
+
}
|
|
32915
33107
|
candidate = Math.max(candidate + 1, await peekNextStepNumber(stepsDir));
|
|
32916
33108
|
}
|
|
32917
33109
|
}
|
|
32918
33110
|
}
|
|
33111
|
+
var STALE_RESERVATION_MS;
|
|
32919
33112
|
var init_step_numbering = __esm({
|
|
32920
33113
|
"src/commands/step-numbering.ts"() {
|
|
32921
33114
|
"use strict";
|
|
33115
|
+
STALE_RESERVATION_MS = 2e3;
|
|
32922
33116
|
}
|
|
32923
33117
|
});
|
|
32924
33118
|
|
|
@@ -32930,7 +33124,7 @@ __export(step_exports, {
|
|
|
32930
33124
|
recordStep: () => recordStep,
|
|
32931
33125
|
stepCommand: () => stepCommand
|
|
32932
33126
|
});
|
|
32933
|
-
import { copyFile, mkdir as mkdir4, rename, stat as
|
|
33127
|
+
import { copyFile, mkdir as mkdir4, rename, stat as stat3 } from "fs/promises";
|
|
32934
33128
|
import path62 from "path";
|
|
32935
33129
|
import chalk30 from "chalk";
|
|
32936
33130
|
function parseMaxSteps(val) {
|
|
@@ -32960,9 +33154,10 @@ async function recordStep(input) {
|
|
|
32960
33154
|
runId: input.runId
|
|
32961
33155
|
});
|
|
32962
33156
|
const { stepNumber: stepNumber2, stepPath } = reservation;
|
|
33157
|
+
let parsedStep;
|
|
33158
|
+
let finalScreenshotPath = null;
|
|
32963
33159
|
try {
|
|
32964
33160
|
let screenshotRef = null;
|
|
32965
|
-
let finalScreenshotPath = null;
|
|
32966
33161
|
if (shotSource) {
|
|
32967
33162
|
const destName = `step-${stepNumber2}.png`;
|
|
32968
33163
|
const destPath = path62.join(screenshotsDir, destName);
|
|
@@ -32978,7 +33173,7 @@ async function recordStep(input) {
|
|
|
32978
33173
|
}
|
|
32979
33174
|
screenshotRef = path62.posix.join("screenshots", destName);
|
|
32980
33175
|
}
|
|
32981
|
-
|
|
33176
|
+
parsedStep = runStepSchema.parse({
|
|
32982
33177
|
step_number: stepNumber2,
|
|
32983
33178
|
timestamp: input.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
32984
33179
|
action: input.action.trim(),
|
|
@@ -32990,33 +33185,33 @@ async function recordStep(input) {
|
|
|
32990
33185
|
...input.extra ? { extra: input.extra } : {}
|
|
32991
33186
|
});
|
|
32992
33187
|
await writeStep(runDirPath, parsedStep);
|
|
32993
|
-
let cancelled = false;
|
|
32994
|
-
try {
|
|
32995
|
-
const liveRes = await sendLiveStep(
|
|
32996
|
-
input.projectRoot,
|
|
32997
|
-
input.runId,
|
|
32998
|
-
parsedStep,
|
|
32999
|
-
finalScreenshotPath
|
|
33000
|
-
);
|
|
33001
|
-
if (liveRes.cancelled) {
|
|
33002
|
-
cancelled = true;
|
|
33003
|
-
}
|
|
33004
|
-
} catch {
|
|
33005
|
-
}
|
|
33006
|
-
return {
|
|
33007
|
-
step: parsedStep,
|
|
33008
|
-
stepPath,
|
|
33009
|
-
screenshotPath: finalScreenshotPath,
|
|
33010
|
-
cancelled
|
|
33011
|
-
};
|
|
33012
33188
|
} catch (err) {
|
|
33013
33189
|
await reservation.release();
|
|
33014
33190
|
throw err;
|
|
33015
33191
|
}
|
|
33192
|
+
let cancelled = false;
|
|
33193
|
+
try {
|
|
33194
|
+
const liveRes = await sendLiveStep(
|
|
33195
|
+
input.projectRoot,
|
|
33196
|
+
input.runId,
|
|
33197
|
+
parsedStep,
|
|
33198
|
+
finalScreenshotPath
|
|
33199
|
+
);
|
|
33200
|
+
if (liveRes.cancelled) {
|
|
33201
|
+
cancelled = true;
|
|
33202
|
+
}
|
|
33203
|
+
} catch {
|
|
33204
|
+
}
|
|
33205
|
+
return {
|
|
33206
|
+
step: parsedStep,
|
|
33207
|
+
stepPath,
|
|
33208
|
+
screenshotPath: finalScreenshotPath,
|
|
33209
|
+
cancelled
|
|
33210
|
+
};
|
|
33016
33211
|
}
|
|
33017
33212
|
async function existingShot(shot) {
|
|
33018
33213
|
const resolved = path62.resolve(shot);
|
|
33019
|
-
const isFile = await
|
|
33214
|
+
const isFile = await stat3(resolved).then((s) => s.isFile()).catch(() => false);
|
|
33020
33215
|
if (!isFile) {
|
|
33021
33216
|
throw new Error(
|
|
33022
33217
|
`Screenshot not found: ${resolved} \u2014 save the screenshot first, or omit --shot.`
|
|
@@ -33352,6 +33547,7 @@ function chromeArgs(opts, port, headless, viewport) {
|
|
|
33352
33547
|
`--window-size=${win.width},${win.height}`,
|
|
33353
33548
|
"--no-first-run",
|
|
33354
33549
|
"--no-default-browser-check",
|
|
33550
|
+
"--disable-features=Translate",
|
|
33355
33551
|
"--password-store=basic",
|
|
33356
33552
|
"--use-mock-keychain",
|
|
33357
33553
|
...headless ? ["--headless=new", "--disable-dev-shm-usage", "--disable-gpu"] : [],
|
|
@@ -38351,7 +38547,7 @@ async function crawlFrom(page, opts, origin, takeBlocked) {
|
|
|
38351
38547
|
const byKey = /* @__PURE__ */ new Map();
|
|
38352
38548
|
const byPlaceView = /* @__PURE__ */ new Map();
|
|
38353
38549
|
const signatureOf = (s) => exploreSignature(s.headings, s.title);
|
|
38354
|
-
function addSurface(state, place, view,
|
|
38550
|
+
function addSurface(state, place, view, path74, depth) {
|
|
38355
38551
|
const key = exploreSurfaceKey(place, view);
|
|
38356
38552
|
const surface = {
|
|
38357
38553
|
key,
|
|
@@ -38363,7 +38559,7 @@ async function crawlFrom(page, opts, origin, takeBlocked) {
|
|
|
38363
38559
|
headings: state.headings,
|
|
38364
38560
|
fields: state.fields,
|
|
38365
38561
|
depth,
|
|
38366
|
-
path:
|
|
38562
|
+
path: path74,
|
|
38367
38563
|
actions: []
|
|
38368
38564
|
};
|
|
38369
38565
|
surfaces.push(surface);
|
|
@@ -38381,9 +38577,9 @@ ${surface.signature}`, key);
|
|
|
38381
38577
|
});
|
|
38382
38578
|
await settle(page, settleMs);
|
|
38383
38579
|
}
|
|
38384
|
-
async function replay(
|
|
38385
|
-
if (!("goto" in (
|
|
38386
|
-
for (const step of
|
|
38580
|
+
async function replay(path74) {
|
|
38581
|
+
if (!("goto" in (path74[0] ?? {}))) await goto(opts.startUrl);
|
|
38582
|
+
for (const step of path74) {
|
|
38387
38583
|
if ("goto" in step) {
|
|
38388
38584
|
await goto(step.goto);
|
|
38389
38585
|
} else {
|
|
@@ -38507,8 +38703,8 @@ ${sig}`, key);
|
|
|
38507
38703
|
if (stats.surfaces >= budget.max_surfaces) {
|
|
38508
38704
|
stats.truncated = true;
|
|
38509
38705
|
} else {
|
|
38510
|
-
const
|
|
38511
|
-
addSurface(after, place, view,
|
|
38706
|
+
const path74 = !sameDoc && view === null ? [{ goto: after.url }] : [...surface.path, ...via, click];
|
|
38707
|
+
addSurface(after, place, view, path74, surface.depth + 1);
|
|
38512
38708
|
}
|
|
38513
38709
|
}
|
|
38514
38710
|
}
|
|
@@ -39433,7 +39629,9 @@ function guidanceLine(r) {
|
|
|
39433
39629
|
return r.reason ? `- ${rule} (${r.reason.replace(/\s+/g, " ")})` : `- ${rule}`;
|
|
39434
39630
|
}
|
|
39435
39631
|
function guidanceSection(rules) {
|
|
39436
|
-
const notRelevant = rules.filter(
|
|
39632
|
+
const notRelevant = rules.filter(
|
|
39633
|
+
(r) => r.kind === "not_relevant" || r.kind === "out_of_scope"
|
|
39634
|
+
);
|
|
39437
39635
|
const misreads = rules.filter((r) => r.kind === "walker_error");
|
|
39438
39636
|
if (notRelevant.length === 0 && misreads.length === 0) return [];
|
|
39439
39637
|
const out2 = ["", "Tester guidance \u2014 the team's decisions from earlier runs:"];
|
|
@@ -39520,7 +39718,8 @@ function composeWalkerPrompt(p) {
|
|
|
39520
39718
|
"Squinting Guardrails:",
|
|
39521
39719
|
"\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.",
|
|
39522
39720
|
"\u2022 Squinting does NOT permit drifting to unrelated settings, account profiles, or off-target pages. Stay strictly within the scenario journey.",
|
|
39523
|
-
"\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)."
|
|
39721
|
+
"\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 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."
|
|
39524
39723
|
].filter(Boolean) : [],
|
|
39525
39724
|
...p.untestableBoundaries && p.untestableBoundaries.length > 0 ? [
|
|
39526
39725
|
"",
|
|
@@ -41097,6 +41296,235 @@ var init_designer = __esm({
|
|
|
41097
41296
|
}
|
|
41098
41297
|
});
|
|
41099
41298
|
|
|
41299
|
+
// src/commands/post-walk.ts
|
|
41300
|
+
var post_walk_exports = {};
|
|
41301
|
+
__export(post_walk_exports, {
|
|
41302
|
+
postWalk: () => postWalk
|
|
41303
|
+
});
|
|
41304
|
+
import { existsSync as existsSync23, promises as fs71 } from "fs";
|
|
41305
|
+
import path73 from "path";
|
|
41306
|
+
import chalk49 from "chalk";
|
|
41307
|
+
async function captureStageJson(fn) {
|
|
41308
|
+
const captured = [];
|
|
41309
|
+
const origLog = console.log;
|
|
41310
|
+
const origErr = console.error;
|
|
41311
|
+
console.log = (...args2) => {
|
|
41312
|
+
captured.push(args2.map(String).join(" "));
|
|
41313
|
+
};
|
|
41314
|
+
console.error = () => {
|
|
41315
|
+
};
|
|
41316
|
+
try {
|
|
41317
|
+
await fn();
|
|
41318
|
+
} finally {
|
|
41319
|
+
console.log = origLog;
|
|
41320
|
+
console.error = origErr;
|
|
41321
|
+
}
|
|
41322
|
+
const last = captured[captured.length - 1];
|
|
41323
|
+
if (!last) return null;
|
|
41324
|
+
try {
|
|
41325
|
+
return JSON.parse(last);
|
|
41326
|
+
} catch {
|
|
41327
|
+
return null;
|
|
41328
|
+
}
|
|
41329
|
+
}
|
|
41330
|
+
async function postWalk(runIdArg, opts = {}) {
|
|
41331
|
+
const ctx = await resolveProject(opts);
|
|
41332
|
+
if (!ctx) return;
|
|
41333
|
+
const { projectRoot } = ctx;
|
|
41334
|
+
const runId = resolveRunId(projectRoot, runIdArg ?? opts.run);
|
|
41335
|
+
if (!runId) {
|
|
41336
|
+
commandError(
|
|
41337
|
+
"Run ID is required. Pass <run-id>, specify --run <run-id>, or start a run first.",
|
|
41338
|
+
{
|
|
41339
|
+
...opts,
|
|
41340
|
+
code: "missing_run_id",
|
|
41341
|
+
fix: "iamu status --json"
|
|
41342
|
+
}
|
|
41343
|
+
);
|
|
41344
|
+
return;
|
|
41345
|
+
}
|
|
41346
|
+
const runDirPath = resolveRunDir(projectRoot, runId, opts);
|
|
41347
|
+
if (!runDirPath) return;
|
|
41348
|
+
const manifestPath = path73.join(runDirPath, "walkthrough.json");
|
|
41349
|
+
let manifest;
|
|
41350
|
+
try {
|
|
41351
|
+
manifest = JSON.parse(
|
|
41352
|
+
await fs71.readFile(manifestPath, "utf8")
|
|
41353
|
+
);
|
|
41354
|
+
} catch {
|
|
41355
|
+
commandError(`Run not found: ${runId}`, {
|
|
41356
|
+
...opts,
|
|
41357
|
+
code: "run_not_found",
|
|
41358
|
+
fix: "iamu status --json"
|
|
41359
|
+
});
|
|
41360
|
+
return;
|
|
41361
|
+
}
|
|
41362
|
+
if (manifest.status !== "completed") {
|
|
41363
|
+
await captureStageJson(
|
|
41364
|
+
() => complete(runId, { prompt: false, push: false, json: true })
|
|
41365
|
+
);
|
|
41366
|
+
if (process.exitCode && process.exitCode !== 0) return;
|
|
41367
|
+
manifest = JSON.parse(
|
|
41368
|
+
await fs71.readFile(manifestPath, "utf8")
|
|
41369
|
+
);
|
|
41370
|
+
}
|
|
41371
|
+
if (manifest.blocked) {
|
|
41372
|
+
const blockedResult = {
|
|
41373
|
+
run_id: runId,
|
|
41374
|
+
status: "blocked",
|
|
41375
|
+
blocked: manifest.blocked,
|
|
41376
|
+
recorded: [],
|
|
41377
|
+
stages: null
|
|
41378
|
+
};
|
|
41379
|
+
if (opts.json) {
|
|
41380
|
+
console.log(JSON.stringify(blockedResult, null, 2));
|
|
41381
|
+
} else {
|
|
41382
|
+
console.log(
|
|
41383
|
+
` ${chalk49.yellow("!")} Run ${runId} is blocked: ${manifest.blocked.reason}`
|
|
41384
|
+
);
|
|
41385
|
+
}
|
|
41386
|
+
return;
|
|
41387
|
+
}
|
|
41388
|
+
const recordedNow = [];
|
|
41389
|
+
const matchesAnswerPath = path73.join(runDirPath, "matches.json");
|
|
41390
|
+
if (existsSync23(matchesAnswerPath)) {
|
|
41391
|
+
const res = await captureStageJson(
|
|
41392
|
+
() => matchRecord(runId, { file: matchesAnswerPath, json: true })
|
|
41393
|
+
);
|
|
41394
|
+
if (res && !process.exitCode) {
|
|
41395
|
+
recordedNow.push("match");
|
|
41396
|
+
await fs71.unlink(matchesAnswerPath).catch(() => {
|
|
41397
|
+
});
|
|
41398
|
+
}
|
|
41399
|
+
}
|
|
41400
|
+
const judgeAnswerPath = path73.join(runDirPath, "judge_answer.json");
|
|
41401
|
+
if (existsSync23(judgeAnswerPath)) {
|
|
41402
|
+
const res = await captureStageJson(
|
|
41403
|
+
() => evaluateRecord(runId, { file: judgeAnswerPath, json: true })
|
|
41404
|
+
);
|
|
41405
|
+
if (res && !process.exitCode) {
|
|
41406
|
+
recordedNow.push("evaluate");
|
|
41407
|
+
await fs71.unlink(judgeAnswerPath).catch(() => {
|
|
41408
|
+
});
|
|
41409
|
+
}
|
|
41410
|
+
}
|
|
41411
|
+
const adviseAnswerPath = path73.join(runDirPath, "advise_answer.json");
|
|
41412
|
+
if (!opts.critic && !existsSync23(path73.join(runDirPath, ADVICE_FILE)) && existsSync23(adviseAnswerPath)) {
|
|
41413
|
+
const res = await captureStageJson(
|
|
41414
|
+
() => adviseRecord(runId, { file: adviseAnswerPath, json: true })
|
|
41415
|
+
);
|
|
41416
|
+
if (res && !process.exitCode) {
|
|
41417
|
+
recordedNow.push("advise");
|
|
41418
|
+
await fs71.unlink(adviseAnswerPath).catch(() => {
|
|
41419
|
+
});
|
|
41420
|
+
}
|
|
41421
|
+
}
|
|
41422
|
+
const designerAnswerPath = path73.join(runDirPath, DESIGNER_ANSWER_FILE);
|
|
41423
|
+
if (!existingDesignerFile(runDirPath)) {
|
|
41424
|
+
if (opts.critic) {
|
|
41425
|
+
await captureStageJson(
|
|
41426
|
+
() => designerRecord(runId, { skipped: "--critic", json: true })
|
|
41427
|
+
);
|
|
41428
|
+
recordedNow.push("designer(skipped)");
|
|
41429
|
+
} else if (opts.ux === false) {
|
|
41430
|
+
await captureStageJson(
|
|
41431
|
+
() => designerRecord(runId, { skipped: "--no-ux", json: true })
|
|
41432
|
+
);
|
|
41433
|
+
recordedNow.push("designer(skipped)");
|
|
41434
|
+
} else if (existsSync23(designerAnswerPath)) {
|
|
41435
|
+
const res = await captureStageJson(
|
|
41436
|
+
() => designerRecord(runId, {
|
|
41437
|
+
file: designerAnswerPath,
|
|
41438
|
+
byOrchestrator: opts.byOrchestrator,
|
|
41439
|
+
json: true
|
|
41440
|
+
})
|
|
41441
|
+
);
|
|
41442
|
+
if (res && !process.exitCode) {
|
|
41443
|
+
recordedNow.push("designer");
|
|
41444
|
+
await fs71.unlink(designerAnswerPath).catch(() => {
|
|
41445
|
+
});
|
|
41446
|
+
}
|
|
41447
|
+
}
|
|
41448
|
+
}
|
|
41449
|
+
const matchInfo = await captureStageJson(() => match(runId, { json: true }));
|
|
41450
|
+
const evalRecorded = existsSync23(path73.join(runDirPath, "verdicts.json"));
|
|
41451
|
+
const evalInfo = evalRecorded ? null : await captureStageJson(() => evaluate(runId, { json: true }));
|
|
41452
|
+
const adviseRecorded = existsSync23(path73.join(runDirPath, ADVICE_FILE));
|
|
41453
|
+
const adviseInfo = opts.critic || adviseRecorded ? null : await captureStageJson(() => advise(runId, { json: true }));
|
|
41454
|
+
const designerRecorded = Boolean(existingDesignerFile(runDirPath));
|
|
41455
|
+
const designerInfo = opts.critic || opts.ux === false || designerRecorded ? null : await captureStageJson(() => designer(runId, { json: true }));
|
|
41456
|
+
const payload = {
|
|
41457
|
+
run_id: runId,
|
|
41458
|
+
status: "completed",
|
|
41459
|
+
recorded: recordedNow,
|
|
41460
|
+
stages: {
|
|
41461
|
+
match: {
|
|
41462
|
+
needed: Boolean(matchInfo?.needed),
|
|
41463
|
+
prompt_file: matchInfo?.prompt_file ?? null,
|
|
41464
|
+
prompt_file_abs: matchInfo?.prompt_file_abs ?? null,
|
|
41465
|
+
answer_file_abs: matchesAnswerPath,
|
|
41466
|
+
record_command: `iamu match-record ${runId} --file - <<'EOF'`
|
|
41467
|
+
},
|
|
41468
|
+
evaluate: {
|
|
41469
|
+
needed: !evalRecorded,
|
|
41470
|
+
recorded: evalRecorded,
|
|
41471
|
+
prompt_file: evalInfo?.prompt_file ?? null,
|
|
41472
|
+
prompt_file_abs: evalInfo?.prompt_file_abs ?? null,
|
|
41473
|
+
answer_file_abs: judgeAnswerPath,
|
|
41474
|
+
record_command: `iamu evaluate-record ${runId} --file - <<'EOF'`
|
|
41475
|
+
},
|
|
41476
|
+
advise: {
|
|
41477
|
+
needed: Boolean(adviseInfo?.needed),
|
|
41478
|
+
recorded: adviseRecorded,
|
|
41479
|
+
reason: opts.critic ? "--critic" : adviseInfo?.reason ?? null,
|
|
41480
|
+
prompt_file: adviseInfo?.prompt_file ?? null,
|
|
41481
|
+
prompt_file_abs: adviseInfo?.prompt_file_abs ?? null,
|
|
41482
|
+
answer_file_abs: adviseAnswerPath,
|
|
41483
|
+
record_command: `iamu advise-record ${runId} --file - <<'EOF'`
|
|
41484
|
+
},
|
|
41485
|
+
designer: {
|
|
41486
|
+
needed: Boolean(designerInfo?.needed),
|
|
41487
|
+
recorded: designerRecorded,
|
|
41488
|
+
reason: opts.critic ? "--critic" : opts.ux === false ? "--no-ux" : designerInfo?.reason ?? null,
|
|
41489
|
+
prompt_file: designerInfo?.prompt_file ?? null,
|
|
41490
|
+
prompt_file_abs: designerInfo?.prompt_file_abs ?? null,
|
|
41491
|
+
answer_file_abs: designerAnswerPath,
|
|
41492
|
+
key_screenshots: designerInfo?.key_screenshots ?? [],
|
|
41493
|
+
offer_template: Boolean(designerInfo?.offer_template),
|
|
41494
|
+
record_command: `iamu designer-record ${runId} --file - <<'EOF'`
|
|
41495
|
+
}
|
|
41496
|
+
}
|
|
41497
|
+
};
|
|
41498
|
+
if (opts.json) {
|
|
41499
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
41500
|
+
return;
|
|
41501
|
+
}
|
|
41502
|
+
console.log();
|
|
41503
|
+
console.log(
|
|
41504
|
+
` ${chalk49.green("\u2713")} Post-walk pipeline for ${chalk49.cyan(runId)}`
|
|
41505
|
+
);
|
|
41506
|
+
if (recordedNow.length > 0) {
|
|
41507
|
+
console.log(` Recorded: ${recordedNow.join(", ")}`);
|
|
41508
|
+
}
|
|
41509
|
+
for (const [name, s] of Object.entries(payload.stages)) {
|
|
41510
|
+
const state = "recorded" in s && s.recorded ? chalk49.green("recorded") : s.needed ? chalk49.yellow(`needed (${s.prompt_file})`) : chalk49.dim("not needed");
|
|
41511
|
+
console.log(` ${name}: ${state}`);
|
|
41512
|
+
}
|
|
41513
|
+
console.log();
|
|
41514
|
+
}
|
|
41515
|
+
var init_post_walk = __esm({
|
|
41516
|
+
"src/commands/post-walk.ts"() {
|
|
41517
|
+
"use strict";
|
|
41518
|
+
init_dist2();
|
|
41519
|
+
init_advise();
|
|
41520
|
+
init_complete();
|
|
41521
|
+
init_context4();
|
|
41522
|
+
init_designer();
|
|
41523
|
+
init_evaluate();
|
|
41524
|
+
init_match();
|
|
41525
|
+
}
|
|
41526
|
+
});
|
|
41527
|
+
|
|
41100
41528
|
// src/commands/prune.ts
|
|
41101
41529
|
var prune_exports = {};
|
|
41102
41530
|
__export(prune_exports, {
|
|
@@ -41104,7 +41532,7 @@ __export(prune_exports, {
|
|
|
41104
41532
|
prune: () => prune
|
|
41105
41533
|
});
|
|
41106
41534
|
import { confirm, isCancel as isCancel5 } from "@clack/prompts";
|
|
41107
|
-
import
|
|
41535
|
+
import chalk50 from "chalk";
|
|
41108
41536
|
function formatBytes(bytes) {
|
|
41109
41537
|
if (bytes <= 0) return "0 B";
|
|
41110
41538
|
if (bytes < 1024) return `${bytes} B`;
|
|
@@ -41150,7 +41578,7 @@ async function prune(opts = {}) {
|
|
|
41150
41578
|
});
|
|
41151
41579
|
if (preview.prunedRuns.length === 0) {
|
|
41152
41580
|
console.log(
|
|
41153
|
-
|
|
41581
|
+
chalk50.dim(" No runs match the pruning criteria. Nothing to prune.")
|
|
41154
41582
|
);
|
|
41155
41583
|
return preview;
|
|
41156
41584
|
}
|
|
@@ -41159,7 +41587,7 @@ async function prune(opts = {}) {
|
|
|
41159
41587
|
message: `Prune ${mode} ${preview.prunedRuns.length} run(s) and reclaim ${formatBytes(preview.totalBytesReclaimed)}?`
|
|
41160
41588
|
});
|
|
41161
41589
|
if (isCancel5(confirmed) || !confirmed) {
|
|
41162
|
-
console.log(
|
|
41590
|
+
console.log(chalk50.yellow(" Pruning cancelled."));
|
|
41163
41591
|
return;
|
|
41164
41592
|
}
|
|
41165
41593
|
}
|
|
@@ -41170,14 +41598,14 @@ async function prune(opts = {}) {
|
|
|
41170
41598
|
}
|
|
41171
41599
|
if (result.dryRun) {
|
|
41172
41600
|
console.log(
|
|
41173
|
-
|
|
41601
|
+
chalk50.cyan(
|
|
41174
41602
|
`
|
|
41175
41603
|
[DRY RUN] Would prune ${result.prunedRuns.length} of ${result.totalRunsScanned} run(s):`
|
|
41176
41604
|
)
|
|
41177
41605
|
);
|
|
41178
41606
|
} else {
|
|
41179
41607
|
console.log(
|
|
41180
|
-
|
|
41608
|
+
chalk50.bold(
|
|
41181
41609
|
`
|
|
41182
41610
|
${symbols.success} Pruned ${result.prunedRuns.length} of ${result.totalRunsScanned} run(s):`
|
|
41183
41611
|
)
|
|
@@ -41185,18 +41613,18 @@ async function prune(opts = {}) {
|
|
|
41185
41613
|
}
|
|
41186
41614
|
if (result.prunedRuns.length === 0) {
|
|
41187
41615
|
console.log(
|
|
41188
|
-
|
|
41616
|
+
chalk50.dim(" No runs matched the pruning criteria. Nothing pruned.")
|
|
41189
41617
|
);
|
|
41190
41618
|
} else {
|
|
41191
41619
|
for (const run of result.prunedRuns) {
|
|
41192
41620
|
const mode = run.fullyDeleted ? "removed" : "screenshots purged";
|
|
41193
41621
|
const count = run.screenshotsDeleted > 0 ? `${run.screenshotsDeleted} screenshots, ` : "";
|
|
41194
41622
|
console.log(
|
|
41195
|
-
` ${
|
|
41623
|
+
` ${chalk50.dim("\u2022")} ${chalk50.bold(run.runId)} ${chalk50.dim(`(${run.scenarioSlug ?? "unknown scenario"})`)}: ${chalk50.yellow(formatBytes(run.bytesReclaimed))} (${count}${mode})`
|
|
41196
41624
|
);
|
|
41197
41625
|
}
|
|
41198
41626
|
console.log(
|
|
41199
|
-
|
|
41627
|
+
chalk50.dim(
|
|
41200
41628
|
`
|
|
41201
41629
|
Total reclaimed: ${formatBytes(result.totalBytesReclaimed)} (${result.totalScreenshotsDeleted} screenshots deleted)
|
|
41202
41630
|
`
|
|
@@ -41501,12 +41929,12 @@ function registerFindingsCommands(program) {
|
|
|
41501
41929
|
await findings2(opts);
|
|
41502
41930
|
}
|
|
41503
41931
|
);
|
|
41504
|
-
program.command("triage [findings...]").helpGroup("Findings:").description("Set findings' triage status").requiredOption(
|
|
41505
|
-
"--status <status>",
|
|
41506
|
-
"open, addressed, deferred, wont_fix or dismissed"
|
|
41507
|
-
).option("--note <text>", "Why \u2014 required when dismissing").option(
|
|
41932
|
+
program.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(
|
|
41508
41933
|
"--reason <reason>",
|
|
41509
|
-
"Dismiss reason: not_relevant (default), walker_error or duplicate"
|
|
41934
|
+
"Dismiss reason: not_relevant (default), walker_error, out_of_scope or duplicate"
|
|
41935
|
+
).option(
|
|
41936
|
+
"--duplicate-of <finding>",
|
|
41937
|
+
"Finding slug or id this repeats (implies --reason duplicate)"
|
|
41510
41938
|
).option(
|
|
41511
41939
|
"--rule <text>",
|
|
41512
41940
|
`Guidance rule text (default: "Don't report: <title>")`
|
|
@@ -41518,7 +41946,7 @@ function registerFindingsCommands(program) {
|
|
|
41518
41946
|
"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"
|
|
41519
41947
|
).option("--no-guidance", "Dismiss without writing a tester guidance rule").option("--json", "Output as JSON").addHelpText(
|
|
41520
41948
|
"after",
|
|
41521
|
-
"\nDismissing as not_relevant or
|
|
41949
|
+
"\nDismissing as not_relevant, walker_error, or out_of_scope teaches the walker: it writes a\ntester guidance rule (skip it with --no-guidance)."
|
|
41522
41950
|
).action(
|
|
41523
41951
|
async (refs2, opts) => {
|
|
41524
41952
|
const { triage: triage2 } = await Promise.resolve().then(() => (init_triage(), triage_exports));
|
|
@@ -41617,9 +42045,9 @@ function registerProjectCommands(program) {
|
|
|
41617
42045
|
"after",
|
|
41618
42046
|
"\nExamples:\n $ iamu validate --all\n $ iamu validate --all --fix --dry-run\n $ iamu validate iamu/personas/maria.md"
|
|
41619
42047
|
).action(
|
|
41620
|
-
async (
|
|
42048
|
+
async (path74, opts) => {
|
|
41621
42049
|
const { validate: validate2 } = await Promise.resolve().then(() => (init_validate(), validate_exports));
|
|
41622
|
-
await validate2(
|
|
42050
|
+
await validate2(path74, opts);
|
|
41623
42051
|
}
|
|
41624
42052
|
);
|
|
41625
42053
|
program.command("update").helpGroup("Project:").description("Update and repair agent skills and prompts to latest version").option(
|
|
@@ -42061,6 +42489,20 @@ function registerWalkthroughCommands(program) {
|
|
|
42061
42489
|
await complete2(runId, opts);
|
|
42062
42490
|
}
|
|
42063
42491
|
);
|
|
42492
|
+
program.command("post-walk [run-id]").helpGroup("Walkthrough:").description(
|
|
42493
|
+
"Complete a run, record staged answers, and prepare post-walk prompts"
|
|
42494
|
+
).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(
|
|
42495
|
+
"--by-orchestrator",
|
|
42496
|
+
"Mark recorded designer answer as written by the orchestrator"
|
|
42497
|
+
).option("--json", "Output post-walk status and prompt paths as JSON").addHelpText(
|
|
42498
|
+
"after",
|
|
42499
|
+
"\nExamples:\n $ iamu post-walk <run-id> --json\n $ iamu post-walk <run-id> --critic --json"
|
|
42500
|
+
).action(
|
|
42501
|
+
async (runId, opts) => {
|
|
42502
|
+
const { postWalk: postWalk2 } = await Promise.resolve().then(() => (init_post_walk(), post_walk_exports));
|
|
42503
|
+
await postWalk2(runId, opts);
|
|
42504
|
+
}
|
|
42505
|
+
);
|
|
42064
42506
|
program.command("match [run-id]").helpGroup("Walkthrough:").description(
|
|
42065
42507
|
"Compose the prompt that links this run's findings to earlier ones"
|
|
42066
42508
|
).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(
|
|
@@ -42215,7 +42657,7 @@ function registerAllCommands(program) {
|
|
|
42215
42657
|
// src/ui/panic.ts
|
|
42216
42658
|
init_dist();
|
|
42217
42659
|
init_logger();
|
|
42218
|
-
import
|
|
42660
|
+
import chalk51 from "chalk";
|
|
42219
42661
|
var handlersRegistered = false;
|
|
42220
42662
|
var customSigintHandler = null;
|
|
42221
42663
|
function registerSigintCleanup(handler) {
|
|
@@ -42241,16 +42683,16 @@ function logFatalAndExit(title, errOrReason) {
|
|
|
42241
42683
|
process.exit(1);
|
|
42242
42684
|
}
|
|
42243
42685
|
if (isVerbose()) {
|
|
42244
|
-
console.error(
|
|
42686
|
+
console.error(chalk51.red(`
|
|
42245
42687
|
${title}:`));
|
|
42246
42688
|
console.error(
|
|
42247
42689
|
errOrReason instanceof Error ? errOrReason.stack || errOrReason : errOrReason
|
|
42248
42690
|
);
|
|
42249
42691
|
} else {
|
|
42250
|
-
console.error(
|
|
42692
|
+
console.error(chalk51.red(`
|
|
42251
42693
|
Fatal Error: ${msg}`));
|
|
42252
42694
|
console.error(
|
|
42253
|
-
|
|
42695
|
+
chalk51.dim(
|
|
42254
42696
|
" Run with --verbose or DEBUG=1 to view the full stack trace.\n"
|
|
42255
42697
|
)
|
|
42256
42698
|
);
|
|
@@ -42269,7 +42711,7 @@ function setupProcessHandlers() {
|
|
|
42269
42711
|
}
|
|
42270
42712
|
process.exit(130);
|
|
42271
42713
|
} else {
|
|
42272
|
-
console.error(
|
|
42714
|
+
console.error(chalk51.dim("\n Operation cancelled."));
|
|
42273
42715
|
process.exit(130);
|
|
42274
42716
|
}
|
|
42275
42717
|
});
|