@iamu-dev/iamu 0.4.0-beta.0 → 0.4.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +1688 -1238
- package/dist/commands/summary.d.ts +4 -0
- package/dist/index.js +1688 -1238
- package/dist/templates/prompts/walk.md +5 -4
- package/dist/templates/skills/iamu-findings.md +10 -10
- package/dist/templates/skills/iamu-new.md +5 -4
- package/dist/templates/skills/iamu-propose.md +5 -4
- package/dist/templates/skills/iamu-run.md +14 -7
- package/dist/templates/skills/iamu-status.md +7 -7
- package/dist/ui/html-report.d.ts +35 -3
- package/dist/ui/report-view.d.ts +21 -0
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -1433,7 +1433,11 @@ var init_walkthrough_canonical = __esm({
|
|
|
1433
1433
|
slug: z22.string(),
|
|
1434
1434
|
persona_slug: z22.string(),
|
|
1435
1435
|
outline_slug: z22.string(),
|
|
1436
|
-
brief: z22.string()
|
|
1436
|
+
brief: z22.string(),
|
|
1437
|
+
/** Display names, so reports never show slugs. Absent on older runs. */
|
|
1438
|
+
persona_name: z22.string().optional(),
|
|
1439
|
+
persona_role: z22.string().optional(),
|
|
1440
|
+
outline_title: z22.string().optional()
|
|
1437
1441
|
}).passthrough();
|
|
1438
1442
|
canonicalTargetSchema = z22.object({
|
|
1439
1443
|
url: z22.string(),
|
|
@@ -1467,6 +1471,10 @@ var init_walkthrough_canonical = __esm({
|
|
|
1467
1471
|
}).passthrough();
|
|
1468
1472
|
canonicalFindingSchema = z22.object({
|
|
1469
1473
|
id: z22.string(),
|
|
1474
|
+
/** The finding's short title. Absent on older runs. */
|
|
1475
|
+
title: z22.string().nullable().optional(),
|
|
1476
|
+
/** The page it was seen on (the finding's own, else its step's). */
|
|
1477
|
+
page_url: z22.string().nullable().optional(),
|
|
1470
1478
|
/** Stable across runs for pattern tracking — null until cross-run wiring lands. */
|
|
1471
1479
|
canonical_id: z22.string().nullable(),
|
|
1472
1480
|
type: findingTypeEnum,
|
|
@@ -1728,14 +1736,14 @@ Bad background: "Construction worker. Experienced in the field. Uses a smartphon
|
|
|
1728
1736
|
- observation: An enhancement opportunity. Not friction, but a missed chance to help.
|
|
1729
1737
|
|
|
1730
1738
|
Finding type pairing:
|
|
1731
|
-
- helps \u2192 always observation severity. Record
|
|
1739
|
+
- helps \u2192 always observation severity. Record each one you meet: something the product does that this person would miss if it were gone.
|
|
1732
1740
|
- hinders \u2192 critical, major, or minor.
|
|
1733
1741
|
- missing \u2192 critical, major, or minor.
|
|
1734
1742
|
- incorrect \u2192 critical or major only.
|
|
1735
1743
|
- irrelevant \u2192 minor or observation.
|
|
1736
1744
|
|
|
1737
1745
|
Do NOT record findings that amount to "this screen is clear and easy to use." The absence of friction is not a finding. Record helps only when the product actively supports the persona in a way that would be missed if removed.`;
|
|
1738
|
-
HELPS_CALIBRATION = `Helps calibration: A helps finding records something the product does that actively supports the persona in a way that would be missed if removed. "The button is easy to find" is not a helps finding \u2014 that's baseline. "The system auto-saves my draft so I don't lose work when interrupted" IS a helps finding \u2014 removing it would hurt this persona. Record
|
|
1746
|
+
HELPS_CALIBRATION = `Helps calibration: A helps finding records something the product does that actively supports the persona in a way that would be missed if removed. "The button is easy to find" is not a helps finding \u2014 that's baseline. "The system auto-saves my draft so I don't lose work when interrupted" IS a helps finding \u2014 removing it would hurt this persona. Record every one you meet, with the same ui_fact and consequence as any finding; a full walk usually has several. They are what a later change must not break: whoever fixes the problems reads them to know what to protect.`;
|
|
1739
1747
|
COGNITIVE_BOUNDING = `Cognitive bounding: Your reasoning ability exceeds this persona's. Their cognitive profile describes how they process information, what they're good at, and where they hit limits. Use it to generate authentic friction signals \u2014 not to lobotomize the walkthrough.
|
|
1740
1748
|
|
|
1741
1749
|
The cognitive profile includes strengths AND limits. A construction worker who skims text may also have excellent spatial memory for page layouts. A teenager who's impatient may pattern-match UI conventions instantly. Apply both sides \u2014 strengths let the persona sail through some things; limits create friction at others.
|
|
@@ -1918,7 +1926,7 @@ var init_prompt_registry = __esm({
|
|
|
1918
1926
|
},
|
|
1919
1927
|
walkthrough: {
|
|
1920
1928
|
id: "walkthroughPhilosophy",
|
|
1921
|
-
version: "2.
|
|
1929
|
+
version: "2.1.0",
|
|
1922
1930
|
compose: (opts) => composeWalkthroughPhilosophy(opts)
|
|
1923
1931
|
},
|
|
1924
1932
|
scenarioCraft: {
|
|
@@ -6849,413 +6857,196 @@ var init_revisit = __esm({
|
|
|
6849
6857
|
}
|
|
6850
6858
|
});
|
|
6851
6859
|
|
|
6852
|
-
// ../../packages/local-store/dist/
|
|
6853
|
-
|
|
6854
|
-
|
|
6860
|
+
// ../../packages/local-store/dist/directions.js
|
|
6861
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
6862
|
+
import { promises as fs12 } from "fs";
|
|
6863
|
+
import path9 from "path";
|
|
6864
|
+
import { z as z25 } from "zod";
|
|
6865
|
+
function directionsDir(projectRoot) {
|
|
6866
|
+
return path9.join(iamuDir(projectRoot), DIRECTIONS_DIR);
|
|
6855
6867
|
}
|
|
6856
|
-
function
|
|
6857
|
-
|
|
6858
|
-
return 0;
|
|
6859
|
-
if (!a.length)
|
|
6860
|
-
return b.length;
|
|
6861
|
-
if (!b.length)
|
|
6862
|
-
return a.length;
|
|
6863
|
-
const row = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
6864
|
-
for (let i = 1; i <= a.length; i++) {
|
|
6865
|
-
let prev = i;
|
|
6866
|
-
for (let j = 1; j <= b.length; j++) {
|
|
6867
|
-
const val = a[i - 1] === b[j - 1] ? row[j - 1] : Math.min(row[j - 1], prev, row[j]) + 1;
|
|
6868
|
-
row[j - 1] = prev;
|
|
6869
|
-
prev = val;
|
|
6870
|
-
}
|
|
6871
|
-
row[b.length] = prev;
|
|
6872
|
-
}
|
|
6873
|
-
return row[b.length];
|
|
6868
|
+
function directionPath(projectRoot, slug) {
|
|
6869
|
+
return path9.join(directionsDir(projectRoot), `${slug}.md`);
|
|
6874
6870
|
}
|
|
6875
|
-
function
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
|
|
6871
|
+
function renderBody(texts) {
|
|
6872
|
+
return [
|
|
6873
|
+
"## Problem",
|
|
6874
|
+
"",
|
|
6875
|
+
texts.problem.trim(),
|
|
6876
|
+
"",
|
|
6877
|
+
"## Change",
|
|
6878
|
+
"",
|
|
6879
|
+
texts.change.trim(),
|
|
6880
|
+
"",
|
|
6881
|
+
"## Leading practice",
|
|
6882
|
+
"",
|
|
6883
|
+
texts.leading_practice.trim(),
|
|
6884
|
+
""
|
|
6885
|
+
].join("\n");
|
|
6879
6886
|
}
|
|
6880
|
-
function
|
|
6881
|
-
const
|
|
6882
|
-
|
|
6883
|
-
return /* @__PURE__ */ new Set();
|
|
6884
|
-
return new Set(norm.split(/\s+/).filter(Boolean));
|
|
6887
|
+
function section(body, heading) {
|
|
6888
|
+
const re = new RegExp(`^## ${heading}\\s*\\n([\\s\\S]*?)(?=^## |$(?![\\s\\S]))`, "m");
|
|
6889
|
+
return re.exec(body)?.[1]?.trim() ?? "";
|
|
6885
6890
|
}
|
|
6886
|
-
function
|
|
6887
|
-
const
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
}
|
|
6908
|
-
return 0;
|
|
6909
|
-
}
|
|
6910
|
-
const union2 = tokensA.size + tokensB.size - intersection;
|
|
6911
|
-
const jaccard = union2 > 0 ? intersection / union2 : 0;
|
|
6912
|
-
const minSize = Math.min(tokensA.size, tokensB.size);
|
|
6913
|
-
const subsetRatio = minSize > 0 ? intersection / minSize : 0;
|
|
6914
|
-
if (normA.includes(normB) || normB.includes(normA) || subsetRatio === 1) {
|
|
6915
|
-
return Math.max(0.85, 0.7 + 0.3 * jaccard);
|
|
6891
|
+
function parseDirection(raw, file) {
|
|
6892
|
+
const { frontmatter, body } = parseFrontmatter(raw);
|
|
6893
|
+
return {
|
|
6894
|
+
frontmatter: directionFrontmatterSchema.parse(frontmatter),
|
|
6895
|
+
texts: {
|
|
6896
|
+
problem: section(body, "Problem"),
|
|
6897
|
+
change: section(body, "Change"),
|
|
6898
|
+
leading_practice: section(body, "Leading practice")
|
|
6899
|
+
},
|
|
6900
|
+
path: file
|
|
6901
|
+
};
|
|
6902
|
+
}
|
|
6903
|
+
async function readDirection(projectRoot, slug) {
|
|
6904
|
+
const file = directionPath(projectRoot, slug);
|
|
6905
|
+
let raw;
|
|
6906
|
+
try {
|
|
6907
|
+
raw = await fs12.readFile(file, "utf8");
|
|
6908
|
+
} catch (err) {
|
|
6909
|
+
if (err.code === "ENOENT")
|
|
6910
|
+
return null;
|
|
6911
|
+
throw err;
|
|
6916
6912
|
}
|
|
6917
|
-
return
|
|
6913
|
+
return parseDirection(raw, file);
|
|
6918
6914
|
}
|
|
6919
|
-
function
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
urlScore = 1;
|
|
6928
|
-
} else {
|
|
6929
|
-
const pathA = normUrlA.split("?")[0];
|
|
6930
|
-
const pathB = normUrlB.split("?")[0];
|
|
6931
|
-
urlScore = pathA === pathB ? 0.8 : 0;
|
|
6932
|
-
}
|
|
6915
|
+
async function listDirections(projectRoot) {
|
|
6916
|
+
let names2;
|
|
6917
|
+
try {
|
|
6918
|
+
names2 = await fs12.readdir(directionsDir(projectRoot));
|
|
6919
|
+
} catch (err) {
|
|
6920
|
+
if (err.code === "ENOENT")
|
|
6921
|
+
return [];
|
|
6922
|
+
throw err;
|
|
6933
6923
|
}
|
|
6934
|
-
const
|
|
6935
|
-
const
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
} else {
|
|
6943
|
-
surfaceScore = 0;
|
|
6924
|
+
const out = [];
|
|
6925
|
+
for (const name of names2.sort()) {
|
|
6926
|
+
if (!name.endsWith(".md"))
|
|
6927
|
+
continue;
|
|
6928
|
+
const file = path9.join(directionsDir(projectRoot), name);
|
|
6929
|
+
try {
|
|
6930
|
+
out.push(parseDirection(await fs12.readFile(file, "utf8"), file));
|
|
6931
|
+
} catch {
|
|
6944
6932
|
}
|
|
6945
6933
|
}
|
|
6946
|
-
|
|
6947
|
-
if (urlScore >= 0.8 && surfaceScore >= 0.8)
|
|
6948
|
-
return 1;
|
|
6949
|
-
if (urlScore >= 0.8)
|
|
6950
|
-
return 0.85;
|
|
6951
|
-
if (surfaceScore >= 0.8)
|
|
6952
|
-
return 0.75;
|
|
6953
|
-
return 0;
|
|
6954
|
-
}
|
|
6955
|
-
if (urlScore !== null)
|
|
6956
|
-
return urlScore;
|
|
6957
|
-
if (surfaceScore !== null)
|
|
6958
|
-
return surfaceScore;
|
|
6959
|
-
return 0.5;
|
|
6934
|
+
return out.sort((a, b) => b.frontmatter.updated_at.localeCompare(a.frontmatter.updated_at));
|
|
6960
6935
|
}
|
|
6961
|
-
function
|
|
6962
|
-
const
|
|
6963
|
-
const
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
}
|
|
6967
|
-
if (rSim === 0) {
|
|
6968
|
-
return aSim >= 0.8 ? aSim * 0.4 : 0.1;
|
|
6969
|
-
}
|
|
6970
|
-
return 0.5 * rSim + 0.5 * aSim;
|
|
6936
|
+
async function writeDirection(projectRoot, fm, texts) {
|
|
6937
|
+
const file = directionPath(projectRoot, fm.slug);
|
|
6938
|
+
const validated = directionFrontmatterSchema.parse(fm);
|
|
6939
|
+
await atomicWriteFile(file, serializeFrontmatter(validated, renderBody(texts)));
|
|
6940
|
+
return file;
|
|
6971
6941
|
}
|
|
6972
|
-
function
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
6980
|
-
}
|
|
6942
|
+
async function upsertDirection(projectRoot, proposal, source, advisedBy, now = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
6943
|
+
const existing = await readDirection(projectRoot, proposal.slug);
|
|
6944
|
+
const texts = {
|
|
6945
|
+
problem: proposal.problem,
|
|
6946
|
+
change: proposal.change,
|
|
6947
|
+
leading_practice: proposal.leading_practice
|
|
6948
|
+
};
|
|
6949
|
+
if (existing?.frontmatter.status === "rejected") {
|
|
6950
|
+
return { outcome: "previously_rejected" };
|
|
6981
6951
|
}
|
|
6982
|
-
|
|
6952
|
+
const prev = existing?.frontmatter;
|
|
6953
|
+
const fm = {
|
|
6954
|
+
id: prev?.id ?? randomUUID5(),
|
|
6955
|
+
slug: proposal.slug,
|
|
6956
|
+
title: proposal.title,
|
|
6957
|
+
status: prev?.status ?? "proposed",
|
|
6958
|
+
size: proposal.size,
|
|
6959
|
+
absorbs: union(prev?.absorbs ?? [], proposal.absorbs, (x) => x),
|
|
6960
|
+
reach: union(prev?.reach ?? [], proposal.reach, (r) => `${r.scenario}:${r.effect}`),
|
|
6961
|
+
verify: union(prev?.verify ?? [], proposal.verify, (x) => x),
|
|
6962
|
+
builds_on: union(prev?.builds_on ?? [], proposal.builds_on, (r) => `${r.path}#${r.symbol}`),
|
|
6963
|
+
seen_in_runs: union(prev?.seen_in_runs ?? [], [source], (x) => x),
|
|
6964
|
+
advised_by: advisedBy,
|
|
6965
|
+
created_at: prev?.created_at ?? now,
|
|
6966
|
+
updated_at: now
|
|
6967
|
+
};
|
|
6968
|
+
const file = await writeDirection(projectRoot, fm, texts);
|
|
6969
|
+
return {
|
|
6970
|
+
outcome: existing ? "updated" : "created",
|
|
6971
|
+
direction: { frontmatter: fm, texts, path: file }
|
|
6972
|
+
};
|
|
6983
6973
|
}
|
|
6984
|
-
function
|
|
6985
|
-
|
|
6986
|
-
|
|
6987
|
-
|
|
6988
|
-
|
|
6974
|
+
async function setDirectionStatus(projectRoot, slug, status2, now = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
6975
|
+
const existing = await readDirection(projectRoot, slug);
|
|
6976
|
+
if (!existing)
|
|
6977
|
+
return null;
|
|
6978
|
+
const fm = { ...existing.frontmatter, status: status2, updated_at: now };
|
|
6979
|
+
await writeDirection(projectRoot, fm, existing.texts);
|
|
6980
|
+
return { ...existing, frontmatter: fm };
|
|
6989
6981
|
}
|
|
6990
|
-
|
|
6991
|
-
|
|
6992
|
-
|
|
6993
|
-
const GAP_PENALTY = -0.7;
|
|
6994
|
-
const MATCH_THRESHOLD = 0.45;
|
|
6995
|
-
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
6996
|
-
for (let i2 = 0; i2 <= m; i2++)
|
|
6997
|
-
dp[i2][0] = i2 * GAP_PENALTY;
|
|
6998
|
-
for (let j2 = 0; j2 <= n; j2++)
|
|
6999
|
-
dp[0][j2] = j2 * GAP_PENALTY;
|
|
7000
|
-
for (let i2 = 1; i2 <= m; i2++) {
|
|
7001
|
-
for (let j2 = 1; j2 <= n; j2++) {
|
|
7002
|
-
const sim = stepSimilarity(baselineSteps[i2 - 1], currentSteps[j2 - 1]);
|
|
7003
|
-
const matchScore = sim >= MATCH_THRESHOLD ? sim * 2 - 0.2 : -1.5;
|
|
7004
|
-
const match2 = dp[i2 - 1][j2 - 1] + matchScore;
|
|
7005
|
-
const delP = dp[i2 - 1][j2] + GAP_PENALTY;
|
|
7006
|
-
const insC = dp[i2][j2 - 1] + GAP_PENALTY;
|
|
7007
|
-
dp[i2][j2] = Math.max(match2, delP, insC);
|
|
7008
|
-
}
|
|
7009
|
-
}
|
|
7010
|
-
const alignedPairs = [];
|
|
7011
|
-
let i = m;
|
|
7012
|
-
let j = n;
|
|
7013
|
-
while (i > 0 || j > 0) {
|
|
7014
|
-
if (i > 0 && j > 0) {
|
|
7015
|
-
const sim = stepSimilarity(baselineSteps[i - 1], currentSteps[j - 1]);
|
|
7016
|
-
const matchScore = sim >= MATCH_THRESHOLD ? sim * 2 - 0.2 : -1.5;
|
|
7017
|
-
const scoreFromMatch = dp[i - 1][j - 1] + matchScore;
|
|
7018
|
-
if (Math.abs(dp[i][j] - scoreFromMatch) < 1e-6 && sim >= MATCH_THRESHOLD) {
|
|
7019
|
-
alignedPairs.push({
|
|
7020
|
-
baseline: baselineSteps[i - 1],
|
|
7021
|
-
current: currentSteps[j - 1]
|
|
7022
|
-
});
|
|
7023
|
-
i--;
|
|
7024
|
-
j--;
|
|
7025
|
-
continue;
|
|
7026
|
-
}
|
|
7027
|
-
}
|
|
7028
|
-
if (j > 0 && Math.abs(dp[i][j] - (dp[i][j - 1] + GAP_PENALTY)) < 1e-6) {
|
|
7029
|
-
alignedPairs.push({
|
|
7030
|
-
baseline: null,
|
|
7031
|
-
current: currentSteps[j - 1]
|
|
7032
|
-
});
|
|
7033
|
-
j--;
|
|
7034
|
-
} else if (i > 0 && Math.abs(dp[i][j] - (dp[i - 1][j] + GAP_PENALTY)) < 1e-6) {
|
|
7035
|
-
alignedPairs.push({
|
|
7036
|
-
baseline: baselineSteps[i - 1],
|
|
7037
|
-
current: null
|
|
7038
|
-
});
|
|
7039
|
-
i--;
|
|
7040
|
-
} else if (i > 0 && j > 0) {
|
|
7041
|
-
alignedPairs.push({
|
|
7042
|
-
baseline: baselineSteps[i - 1],
|
|
7043
|
-
current: currentSteps[j - 1]
|
|
7044
|
-
});
|
|
7045
|
-
i--;
|
|
7046
|
-
j--;
|
|
7047
|
-
} else if (i > 0) {
|
|
7048
|
-
alignedPairs.push({
|
|
7049
|
-
baseline: baselineSteps[i - 1],
|
|
7050
|
-
current: null
|
|
7051
|
-
});
|
|
7052
|
-
i--;
|
|
7053
|
-
} else {
|
|
7054
|
-
alignedPairs.push({
|
|
7055
|
-
baseline: null,
|
|
7056
|
-
current: currentSteps[j - 1]
|
|
7057
|
-
});
|
|
7058
|
-
j--;
|
|
7059
|
-
}
|
|
7060
|
-
}
|
|
7061
|
-
alignedPairs.reverse();
|
|
7062
|
-
const maxBaselineStepNum = baselineSteps.length > 0 ? Math.max(...baselineSteps.map((s) => s.step_number)) : -1;
|
|
7063
|
-
const alignedSteps = [];
|
|
7064
|
-
const stats = {
|
|
7065
|
-
totalAlignedSteps: alignedPairs.length,
|
|
7066
|
-
same: 0,
|
|
7067
|
-
resolved: 0,
|
|
7068
|
-
persists: 0,
|
|
7069
|
-
regressions: 0,
|
|
7070
|
-
stalledInBaseline: 0,
|
|
7071
|
-
bypassed: 0,
|
|
7072
|
-
newPath: 0
|
|
7073
|
-
};
|
|
7074
|
-
for (let idx = 0; idx < alignedPairs.length; idx++) {
|
|
7075
|
-
const pair = alignedPairs[idx];
|
|
7076
|
-
const bStep = pair.baseline;
|
|
7077
|
-
const cStep = pair.current;
|
|
7078
|
-
const bFindings = bStep ? baselineFindings.filter((f) => findingMatchesStep(f, bStep.step_number)) : [];
|
|
7079
|
-
const cFindings = cStep ? currentFindings.filter((f) => findingMatchesStep(f, cStep.step_number)) : [];
|
|
7080
|
-
const bProblems = bFindings.filter((f) => isProblemFinding(f.type));
|
|
7081
|
-
const cProblems = cFindings.filter((f) => isProblemFinding(f.type));
|
|
7082
|
-
let status2 = "same";
|
|
7083
|
-
let summary = "Clean";
|
|
7084
|
-
if (bStep && cStep) {
|
|
7085
|
-
const wasStall = bStep.step_number === maxBaselineStepNum && (bProblems.length > 0 || isStallNote(bStep.note));
|
|
7086
|
-
if (bProblems.length > 0 && cProblems.length === 0) {
|
|
7087
|
-
status2 = "resolved";
|
|
7088
|
-
stats.resolved++;
|
|
7089
|
-
const probDesc = bProblems[0].ui_fact || bProblems[0].title || bProblems[0].id;
|
|
7090
|
-
summary = wasStall ? `Previous stall resolved \u2014 journey unblocked (${probDesc})` : `Resolved previous issue: ${probDesc}`;
|
|
7091
|
-
} else if (bProblems.length > 0 && cProblems.length > 0) {
|
|
7092
|
-
status2 = "persists";
|
|
7093
|
-
stats.persists++;
|
|
7094
|
-
const probDesc = cProblems[0].ui_fact || cProblems[0].title || cProblems[0].id;
|
|
7095
|
-
summary = `Problem persists: ${probDesc}`;
|
|
7096
|
-
} else if (bProblems.length === 0 && cProblems.length > 0) {
|
|
7097
|
-
status2 = "regression";
|
|
7098
|
-
stats.regressions++;
|
|
7099
|
-
const probDesc = cProblems[0].ui_fact || cProblems[0].title || cProblems[0].id;
|
|
7100
|
-
summary = `New issue reported: ${probDesc}`;
|
|
7101
|
-
} else {
|
|
7102
|
-
status2 = "same";
|
|
7103
|
-
stats.same++;
|
|
7104
|
-
summary = "Clean";
|
|
7105
|
-
}
|
|
7106
|
-
} else if (bStep && !cStep) {
|
|
7107
|
-
const wasStall = bStep.step_number === maxBaselineStepNum && (bProblems.length > 0 || isStallNote(bStep.note));
|
|
7108
|
-
if (wasStall) {
|
|
7109
|
-
status2 = "stalled";
|
|
7110
|
-
stats.stalledInBaseline++;
|
|
7111
|
-
const probDesc = bProblems[0]?.ui_fact || bProblems[0]?.title || "journey halted";
|
|
7112
|
-
summary = `Stalled in baseline: ${probDesc}`;
|
|
7113
|
-
} else {
|
|
7114
|
-
status2 = "bypassed";
|
|
7115
|
-
stats.bypassed++;
|
|
7116
|
-
summary = "Bypassed in current run";
|
|
7117
|
-
}
|
|
7118
|
-
} else if (!bStep && cStep) {
|
|
7119
|
-
if (cProblems.length > 0) {
|
|
7120
|
-
status2 = "regression";
|
|
7121
|
-
stats.regressions++;
|
|
7122
|
-
const probDesc = cProblems[0].ui_fact || cProblems[0].title || cProblems[0].id;
|
|
7123
|
-
summary = `New step with issue: ${probDesc}`;
|
|
7124
|
-
} else {
|
|
7125
|
-
status2 = "new_path";
|
|
7126
|
-
stats.newPath++;
|
|
7127
|
-
summary = "New path / unblocked progress";
|
|
7128
|
-
}
|
|
7129
|
-
}
|
|
7130
|
-
alignedSteps.push({
|
|
7131
|
-
index: idx + 1,
|
|
7132
|
-
status: status2,
|
|
7133
|
-
baselineStep: bStep,
|
|
7134
|
-
currentStep: cStep,
|
|
7135
|
-
baselineFindings: bFindings,
|
|
7136
|
-
currentFindings: cFindings,
|
|
7137
|
-
summary
|
|
7138
|
-
});
|
|
7139
|
-
}
|
|
7140
|
-
return {
|
|
7141
|
-
scenario,
|
|
7142
|
-
baselineRunId,
|
|
7143
|
-
currentRunId,
|
|
7144
|
-
steps: alignedSteps,
|
|
7145
|
-
stats
|
|
7146
|
-
};
|
|
7147
|
-
}
|
|
7148
|
-
function toStepFindingInfo(f) {
|
|
7149
|
-
return {
|
|
7150
|
-
id: f.frontmatter.id,
|
|
7151
|
-
title: f.frontmatter.title ?? f.slug,
|
|
7152
|
-
type: f.frontmatter.type,
|
|
7153
|
-
severity: f.frontmatter.severity,
|
|
7154
|
-
ui_fact: f.frontmatter.ui_fact,
|
|
7155
|
-
consequence: f.frontmatter.consequence,
|
|
7156
|
-
canonical_id: f.frontmatter.canonical_id,
|
|
7157
|
-
walkthrough_step_id: f.frontmatter.walkthrough_step_id
|
|
7158
|
-
};
|
|
7159
|
-
}
|
|
7160
|
-
function toJourneyStepInfo(s) {
|
|
7161
|
-
return {
|
|
7162
|
-
step_number: s.step_number,
|
|
7163
|
-
action: s.action,
|
|
7164
|
-
url: s.url ?? void 0,
|
|
7165
|
-
surface: s.surface ?? void 0,
|
|
7166
|
-
note: s.note ?? void 0
|
|
7167
|
-
};
|
|
7168
|
-
}
|
|
7169
|
-
function journeyStepRoute(step) {
|
|
7170
|
-
return step.currentStep?.url && normalizePlaceKey(step.currentStep.url) || step.baselineStep?.url && normalizePlaceKey(step.baselineStep.url) || step.currentStep?.surface || step.baselineStep?.surface || "\u2014";
|
|
7171
|
-
}
|
|
7172
|
-
async function computeJourneyDiff(projectRoot, baselineRunId, currentRunId, scenarioSlug) {
|
|
7173
|
-
const [bStepsRaw, bFindingsRaw, cStepsRaw, cFindingsRaw] = await Promise.all([
|
|
7174
|
-
readStepsForRun(projectRoot, baselineRunId),
|
|
7175
|
-
readFindingsForRun(projectRoot, baselineRunId),
|
|
7176
|
-
readStepsForRun(projectRoot, currentRunId),
|
|
7177
|
-
readFindingsForRun(projectRoot, currentRunId)
|
|
7178
|
-
]);
|
|
7179
|
-
const baselineSteps = bStepsRaw.map(toJourneyStepInfo);
|
|
7180
|
-
const baselineFindings = bFindingsRaw.map(toStepFindingInfo);
|
|
7181
|
-
const currentSteps = cStepsRaw.map(toJourneyStepInfo);
|
|
7182
|
-
const currentFindings = cFindingsRaw.map(toStepFindingInfo);
|
|
7183
|
-
return alignJourneySteps(scenarioSlug ?? "", baselineRunId, currentRunId, baselineSteps, baselineFindings, currentSteps, currentFindings);
|
|
7184
|
-
}
|
|
7185
|
-
function renderJourneyDiffMarkdown(diff) {
|
|
7186
|
-
const lines = [
|
|
7187
|
-
"### Chronological journey diff",
|
|
7188
|
-
"",
|
|
7189
|
-
`Baseline: \`${diff.baselineRunId}\` vs Current: \`${diff.currentRunId}\``
|
|
7190
|
-
];
|
|
7191
|
-
const parts = [];
|
|
7192
|
-
if (diff.stats.same > 0)
|
|
7193
|
-
parts.push(`${diff.stats.same} unchanged`);
|
|
7194
|
-
if (diff.stats.resolved > 0)
|
|
7195
|
-
parts.push(`${diff.stats.resolved} resolved`);
|
|
7196
|
-
if (diff.stats.persists > 0)
|
|
7197
|
-
parts.push(`${diff.stats.persists} persists`);
|
|
7198
|
-
if (diff.stats.regressions > 0)
|
|
7199
|
-
parts.push(`${diff.stats.regressions} new issues`);
|
|
7200
|
-
if (diff.stats.stalledInBaseline > 0)
|
|
7201
|
-
parts.push(`${diff.stats.stalledInBaseline} stalled in baseline`);
|
|
7202
|
-
if (diff.stats.bypassed > 0)
|
|
7203
|
-
parts.push(`${diff.stats.bypassed} bypassed`);
|
|
7204
|
-
if (diff.stats.newPath > 0)
|
|
7205
|
-
parts.push(`${diff.stats.newPath} new path steps`);
|
|
7206
|
-
if (parts.length > 0) {
|
|
7207
|
-
lines.push(`- **Summary**: ${parts.join(", ")}`);
|
|
7208
|
-
}
|
|
7209
|
-
lines.push("", "| # | Route | Baseline (Run N-1) | Current (Run N) | Status | Notes |", "|---|-------|--------------------|-----------------|--------|-------|");
|
|
7210
|
-
for (const step of diff.steps) {
|
|
7211
|
-
const route = journeyStepRoute(step);
|
|
7212
|
-
const baseAction = step.baselineStep ? `Step ${step.baselineStep.step_number}: ${escapeMarkdownCell(step.baselineStep.action)}` : "\u2014";
|
|
7213
|
-
const currAction = step.currentStep ? `Step ${step.currentStep.step_number}: ${escapeMarkdownCell(step.currentStep.action)}` : "\u2014";
|
|
7214
|
-
const statusBadge = `\`${step.status}\``;
|
|
7215
|
-
const notes = escapeMarkdownCell(step.summary);
|
|
7216
|
-
lines.push(`| ${step.index} | \`${escapeMarkdownCell(route)}\` | ${baseAction} | ${currAction} | ${statusBadge} | ${notes} |`);
|
|
7217
|
-
}
|
|
7218
|
-
return lines.join("\n");
|
|
7219
|
-
}
|
|
7220
|
-
function escapeMarkdownCell(text) {
|
|
7221
|
-
return text.replace(/\|/g, "\\|").replace(/\n/g, " ");
|
|
7222
|
-
}
|
|
7223
|
-
var PROBLEM_TYPES2, STALL_KEYWORDS;
|
|
7224
|
-
var init_journey_diff = __esm({
|
|
7225
|
-
"../../packages/local-store/dist/journey-diff.js"() {
|
|
6982
|
+
var DIRECTIONS_DIR, DIRECTION_STATUSES, directionStatusSchema, directionSizeSchema, directionReachSchema, codeRefSchema, directionFrontmatterSchema, union;
|
|
6983
|
+
var init_directions = __esm({
|
|
6984
|
+
"../../packages/local-store/dist/directions.js"() {
|
|
7226
6985
|
"use strict";
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
"
|
|
7233
|
-
"
|
|
7234
|
-
"
|
|
7235
|
-
"
|
|
7236
|
-
"cannot proceed",
|
|
7237
|
-
"could not proceed",
|
|
7238
|
-
"failed to load",
|
|
7239
|
-
"timed out"
|
|
6986
|
+
init_atomic();
|
|
6987
|
+
init_markdown();
|
|
6988
|
+
init_paths();
|
|
6989
|
+
DIRECTIONS_DIR = "directions";
|
|
6990
|
+
DIRECTION_STATUSES = [
|
|
6991
|
+
"proposed",
|
|
6992
|
+
"accepted",
|
|
6993
|
+
"done",
|
|
6994
|
+
"rejected"
|
|
7240
6995
|
];
|
|
6996
|
+
directionStatusSchema = z25.enum(DIRECTION_STATUSES);
|
|
6997
|
+
directionSizeSchema = z25.enum(["small", "medium", "large"]);
|
|
6998
|
+
directionReachSchema = z25.object({
|
|
6999
|
+
scenario: z25.string().min(1),
|
|
7000
|
+
effect: z25.enum(["helps", "risk"]),
|
|
7001
|
+
reason: z25.string().min(1)
|
|
7002
|
+
});
|
|
7003
|
+
codeRefSchema = z25.object({
|
|
7004
|
+
path: z25.string().min(1),
|
|
7005
|
+
symbol: z25.string().min(1)
|
|
7006
|
+
});
|
|
7007
|
+
directionFrontmatterSchema = z25.object({
|
|
7008
|
+
id: z25.string().uuid(),
|
|
7009
|
+
slug: z25.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
|
|
7010
|
+
title: z25.string().min(1),
|
|
7011
|
+
status: directionStatusSchema.default("proposed"),
|
|
7012
|
+
size: directionSizeSchema,
|
|
7013
|
+
/** Finding ids this direction would resolve. */
|
|
7014
|
+
absorbs: z25.array(z25.string().min(1)).default([]),
|
|
7015
|
+
/** Predicted effect on other scenarios — never evidence. */
|
|
7016
|
+
reach: z25.array(directionReachSchema).default([]),
|
|
7017
|
+
/** Scenarios to re-run once the direction lands. */
|
|
7018
|
+
verify: z25.array(z25.string().min(1)).default([]),
|
|
7019
|
+
builds_on: z25.array(codeRefSchema).default([]),
|
|
7020
|
+
/** Runs (or `project:<timestamp>`) whose analysis proposed it. */
|
|
7021
|
+
seen_in_runs: z25.array(z25.string().min(1)).default([]),
|
|
7022
|
+
advised_by: z25.string().regex(/^advisor@\S+$/),
|
|
7023
|
+
created_at: z25.string(),
|
|
7024
|
+
updated_at: z25.string()
|
|
7025
|
+
});
|
|
7026
|
+
union = (a, b, key) => {
|
|
7027
|
+
const seen = /* @__PURE__ */ new Map();
|
|
7028
|
+
for (const x of [...a, ...b])
|
|
7029
|
+
seen.set(key(x), x);
|
|
7030
|
+
return [...seen.values()];
|
|
7031
|
+
};
|
|
7241
7032
|
}
|
|
7242
7033
|
});
|
|
7243
7034
|
|
|
7244
7035
|
// ../../packages/local-store/dist/walkthrough-canonical.js
|
|
7245
|
-
import { createHash as createHash2, randomUUID as
|
|
7246
|
-
import { promises as
|
|
7247
|
-
import
|
|
7036
|
+
import { createHash as createHash2, randomUUID as randomUUID6 } from "crypto";
|
|
7037
|
+
import { promises as fs13 } from "fs";
|
|
7038
|
+
import path10 from "path";
|
|
7248
7039
|
async function readStepsForRun(projectRoot, runId) {
|
|
7249
7040
|
const runDirPath = runDir(projectRoot, runId);
|
|
7250
|
-
const stepsDir =
|
|
7041
|
+
const stepsDir = path10.join(runDirPath, "steps");
|
|
7251
7042
|
try {
|
|
7252
|
-
const names2 = await
|
|
7043
|
+
const names2 = await fs13.readdir(stepsDir);
|
|
7253
7044
|
const jsonFiles = names2.filter((n) => n.endsWith(".json")).sort();
|
|
7254
7045
|
if (jsonFiles.length > 0) {
|
|
7255
7046
|
const out2 = [];
|
|
7256
7047
|
for (const name of jsonFiles) {
|
|
7257
7048
|
try {
|
|
7258
|
-
const raw2 = await
|
|
7049
|
+
const raw2 = await fs13.readFile(path10.join(stepsDir, name), "utf8");
|
|
7259
7050
|
const parsed = JSON.parse(raw2);
|
|
7260
7051
|
if (parsed.step_number == null) {
|
|
7261
7052
|
const numFromName = Number.parseInt(name.replace(/\.json$/, ""), 10);
|
|
@@ -7269,10 +7060,10 @@ async function readStepsForRun(projectRoot, runId) {
|
|
|
7269
7060
|
}
|
|
7270
7061
|
} catch {
|
|
7271
7062
|
}
|
|
7272
|
-
const stepsPath =
|
|
7063
|
+
const stepsPath = path10.join(runDirPath, "steps.jsonl");
|
|
7273
7064
|
let raw;
|
|
7274
7065
|
try {
|
|
7275
|
-
raw = await
|
|
7066
|
+
raw = await fs13.readFile(stepsPath, "utf8");
|
|
7276
7067
|
} catch (err) {
|
|
7277
7068
|
if (err.code === "ENOENT")
|
|
7278
7069
|
return [];
|
|
@@ -7297,7 +7088,7 @@ async function readFindingsForRun(projectRoot, runId) {
|
|
|
7297
7088
|
const findingsDir = entityDirPath(projectRoot, "finding");
|
|
7298
7089
|
let names2;
|
|
7299
7090
|
try {
|
|
7300
|
-
names2 = await
|
|
7091
|
+
names2 = await fs13.readdir(findingsDir);
|
|
7301
7092
|
} catch (err) {
|
|
7302
7093
|
if (err.code === "ENOENT")
|
|
7303
7094
|
return [];
|
|
@@ -7307,8 +7098,8 @@ async function readFindingsForRun(projectRoot, runId) {
|
|
|
7307
7098
|
for (const name of names2) {
|
|
7308
7099
|
if (!name.endsWith(".md") || name.startsWith("."))
|
|
7309
7100
|
continue;
|
|
7310
|
-
const filePath =
|
|
7311
|
-
const raw = await
|
|
7101
|
+
const filePath = path10.join(findingsDir, name);
|
|
7102
|
+
const raw = await fs13.readFile(filePath, "utf8");
|
|
7312
7103
|
try {
|
|
7313
7104
|
const { frontmatter, body } = parseFrontmatter(raw);
|
|
7314
7105
|
const fm = frontmatter;
|
|
@@ -7339,7 +7130,7 @@ async function resolveCanonicalIds(projectRoot, scenarioSlug, currentRunId, curr
|
|
|
7339
7130
|
const findingsDir = entityDirPath(projectRoot, "finding");
|
|
7340
7131
|
let allNames;
|
|
7341
7132
|
try {
|
|
7342
|
-
allNames = await
|
|
7133
|
+
allNames = await fs13.readdir(findingsDir);
|
|
7343
7134
|
} catch {
|
|
7344
7135
|
allNames = [];
|
|
7345
7136
|
}
|
|
@@ -7347,7 +7138,7 @@ async function resolveCanonicalIds(projectRoot, scenarioSlug, currentRunId, curr
|
|
|
7347
7138
|
if (!name.endsWith(".md") || name.startsWith("."))
|
|
7348
7139
|
continue;
|
|
7349
7140
|
try {
|
|
7350
|
-
const raw = await
|
|
7141
|
+
const raw = await fs13.readFile(path10.join(findingsDir, name), "utf8");
|
|
7351
7142
|
const { frontmatter } = parseFrontmatter(raw);
|
|
7352
7143
|
const fm = frontmatter;
|
|
7353
7144
|
if (!fm.scenario || fm.scenario !== scenarioSlug)
|
|
@@ -7358,14 +7149,14 @@ async function resolveCanonicalIds(projectRoot, scenarioSlug, currentRunId, curr
|
|
|
7358
7149
|
if (fm.canonical_id) {
|
|
7359
7150
|
priorIndex.set(fp, fm.canonical_id);
|
|
7360
7151
|
} else if (!priorIndex.has(fp)) {
|
|
7361
|
-
priorIndex.set(fp,
|
|
7152
|
+
priorIndex.set(fp, randomUUID6());
|
|
7362
7153
|
}
|
|
7363
7154
|
} catch {
|
|
7364
7155
|
}
|
|
7365
7156
|
}
|
|
7366
7157
|
for (const f of currentFindings) {
|
|
7367
7158
|
const fp = findingFingerprint(f.frontmatter.scenario, f.frontmatter.type, f.frontmatter.ui_fact);
|
|
7368
|
-
const canonicalId = f.frontmatter.canonical_id ?? priorIndex.get(fp) ??
|
|
7159
|
+
const canonicalId = f.frontmatter.canonical_id ?? priorIndex.get(fp) ?? randomUUID6();
|
|
7369
7160
|
result.set(f.frontmatter.id, canonicalId);
|
|
7370
7161
|
if (!f.frontmatter.canonical_id) {
|
|
7371
7162
|
try {
|
|
@@ -7373,9 +7164,9 @@ async function resolveCanonicalIds(projectRoot, scenarioSlug, currentRunId, curr
|
|
|
7373
7164
|
for (const name of allNames) {
|
|
7374
7165
|
if (!name.endsWith(".md") || name.startsWith("."))
|
|
7375
7166
|
continue;
|
|
7376
|
-
const candidatePath =
|
|
7167
|
+
const candidatePath = path10.join(findingsDir, name);
|
|
7377
7168
|
try {
|
|
7378
|
-
const candidateRaw = await
|
|
7169
|
+
const candidateRaw = await fs13.readFile(candidatePath, "utf8");
|
|
7379
7170
|
const { frontmatter: cFm } = parseFrontmatter(candidateRaw);
|
|
7380
7171
|
if (cFm.id === f.frontmatter.id) {
|
|
7381
7172
|
targetPath = candidatePath;
|
|
@@ -7386,7 +7177,7 @@ async function resolveCanonicalIds(projectRoot, scenarioSlug, currentRunId, curr
|
|
|
7386
7177
|
}
|
|
7387
7178
|
if (targetPath) {
|
|
7388
7179
|
await withFileLock(targetPath, async () => {
|
|
7389
|
-
const raw = await
|
|
7180
|
+
const raw = await fs13.readFile(targetPath, "utf8");
|
|
7390
7181
|
const updated = raw.replace(/^(---\n[\s\S]*?)(---\n)/m, `$1canonical_id: ${canonicalId}
|
|
7391
7182
|
$2`);
|
|
7392
7183
|
if (updated !== raw) {
|
|
@@ -7411,7 +7202,7 @@ function extractRecommendationFromBody(body) {
|
|
|
7411
7202
|
}
|
|
7412
7203
|
async function readAnalysis(projectRoot, runId) {
|
|
7413
7204
|
try {
|
|
7414
|
-
const raw = JSON.parse(await
|
|
7205
|
+
const raw = JSON.parse(await fs13.readFile(path10.join(runDir(projectRoot, runId), "advice.json"), "utf8"));
|
|
7415
7206
|
return {
|
|
7416
7207
|
advisor: `advisor@${raw.advisor?.version ?? "unknown"}`,
|
|
7417
7208
|
directions: (raw.directions ?? []).map((d) => ({
|
|
@@ -7441,9 +7232,13 @@ async function buildCanonical(projectRoot, manifest, completedAt, inputs) {
|
|
|
7441
7232
|
} : null;
|
|
7442
7233
|
const outlineEntity = scenario ? await readEntity(projectRoot, "outline", scenario.outline) : null;
|
|
7443
7234
|
const outline = outlineEntity ? {
|
|
7235
|
+
title: outlineEntity.frontmatter.title,
|
|
7444
7236
|
human_tests: outlineEntity.frontmatter.human_tests ?? []
|
|
7445
7237
|
} : null;
|
|
7238
|
+
const personaEntity = scenario?.persona ? await readEntity(projectRoot, "persona", scenario.persona).catch(() => null) : null;
|
|
7239
|
+
const personaName = personaEntity?.frontmatter.name ?? null;
|
|
7446
7240
|
const steps = await readStepsForRun(projectRoot, manifest.run_id);
|
|
7241
|
+
const history = await scenarioRunHistory(projectRoot, manifest.scenario_slug, manifest.run_id);
|
|
7447
7242
|
const findings2 = await readFindingsForRun(projectRoot, manifest.run_id);
|
|
7448
7243
|
const analysis = await readAnalysis(projectRoot, manifest.run_id);
|
|
7449
7244
|
const canonicalSteps = steps.map((s, idx) => {
|
|
@@ -7455,7 +7250,7 @@ async function buildCanonical(projectRoot, manifest, completedAt, inputs) {
|
|
|
7455
7250
|
// Step files record the path relative to the run directory
|
|
7456
7251
|
// (`screenshots/step-1.png`); older ones recorded the bare file
|
|
7457
7252
|
// name. Normalize on the file name so the prefix is never doubled.
|
|
7458
|
-
screenshot_ref: s.screenshot ?
|
|
7253
|
+
screenshot_ref: s.screenshot ? path10.join("screenshots", path10.basename(s.screenshot)) : null,
|
|
7459
7254
|
persona_reasoning: s.note,
|
|
7460
7255
|
grounding: s.grounding ?? null,
|
|
7461
7256
|
action: s.action,
|
|
@@ -7463,9 +7258,15 @@ async function buildCanonical(projectRoot, manifest, completedAt, inputs) {
|
|
|
7463
7258
|
...typeof s.surface === "string" && s.surface ? { surface: s.surface } : {}
|
|
7464
7259
|
};
|
|
7465
7260
|
});
|
|
7261
|
+
const checks = await runChecks(projectRoot, manifest, canonicalSteps, history.previousStartedAt);
|
|
7466
7262
|
const canonicalMap = await resolveCanonicalIds(projectRoot, manifest.scenario_slug, manifest.run_id, findings2);
|
|
7263
|
+
const stepUrl2 = new Map(canonicalSteps.map((st) => [st.id, st.url ?? null]));
|
|
7467
7264
|
const canonicalFindings = findings2.map((f) => ({
|
|
7468
7265
|
id: f.frontmatter.id,
|
|
7266
|
+
// Additive keys (D3): the short title and the page it was seen on,
|
|
7267
|
+
// so reports never fall back to ids or bare step numbers.
|
|
7268
|
+
title: f.frontmatter.title ?? null,
|
|
7269
|
+
page_url: f.frontmatter.page_url ?? (f.frontmatter.walkthrough_step_id ? stepUrl2.get(f.frontmatter.walkthrough_step_id) : null) ?? null,
|
|
7469
7270
|
canonical_id: canonicalMap.get(f.frontmatter.id) ?? null,
|
|
7470
7271
|
type: f.frontmatter.type,
|
|
7471
7272
|
severity: f.frontmatter.severity ?? null,
|
|
@@ -7497,7 +7298,10 @@ async function buildCanonical(projectRoot, manifest, completedAt, inputs) {
|
|
|
7497
7298
|
slug: manifest.scenario_slug,
|
|
7498
7299
|
persona_slug: scenario?.persona ?? "",
|
|
7499
7300
|
outline_slug: scenario?.outline ?? "",
|
|
7500
|
-
brief: scenario?.brief ?? ""
|
|
7301
|
+
brief: scenario?.brief ?? "",
|
|
7302
|
+
...personaName ? { persona_name: personaName } : {},
|
|
7303
|
+
...personaEntity?.frontmatter.role ? { persona_role: personaEntity.frontmatter.role } : {},
|
|
7304
|
+
...outline?.title ? { outline_title: outline.title } : {}
|
|
7501
7305
|
},
|
|
7502
7306
|
target: {
|
|
7503
7307
|
url: manifest.target_url,
|
|
@@ -7523,7 +7327,7 @@ async function buildCanonical(projectRoot, manifest, completedAt, inputs) {
|
|
|
7523
7327
|
steps: canonicalSteps,
|
|
7524
7328
|
human_tests: humanTests,
|
|
7525
7329
|
human_test_overall: {
|
|
7526
|
-
question: outline ? `Would ${scenario?.persona ?? "the persona"} come back and use this voluntarily?` : "Would the persona come back and use this voluntarily?",
|
|
7330
|
+
question: outline ? `Would ${personaName ?? scenario?.persona ?? "the persona"} come back and use this voluntarily?` : "Would the persona come back and use this voluntarily?",
|
|
7527
7331
|
answer: null,
|
|
7528
7332
|
reasoning: null
|
|
7529
7333
|
},
|
|
@@ -7534,21 +7338,65 @@ async function buildCanonical(projectRoot, manifest, completedAt, inputs) {
|
|
|
7534
7338
|
// Consumers MUST preserve unknown keys (per design D3).
|
|
7535
7339
|
...inputs.what_worked && inputs.what_worked.length > 0 ? { what_worked: inputs.what_worked } : {},
|
|
7536
7340
|
...inputs.open_questions && inputs.open_questions.length > 0 ? { open_questions: inputs.open_questions } : {},
|
|
7341
|
+
...inputs.setup_notes && inputs.setup_notes.length > 0 ? { setup_notes: inputs.setup_notes } : {},
|
|
7537
7342
|
...manifest.blocked ? { blocked: manifest.blocked } : {},
|
|
7538
|
-
...analysis ? { analysis } : {}
|
|
7343
|
+
...analysis ? { analysis } : {},
|
|
7344
|
+
run_number: history.runNumber,
|
|
7345
|
+
...checks ? { checks } : {}
|
|
7539
7346
|
};
|
|
7540
7347
|
}
|
|
7541
|
-
async function
|
|
7542
|
-
const
|
|
7543
|
-
|
|
7544
|
-
|
|
7545
|
-
|
|
7546
|
-
const
|
|
7547
|
-
|
|
7348
|
+
async function runChecks(projectRoot, manifest, steps, previousStartedAt) {
|
|
7349
|
+
const places = manifest.revisit ?? [];
|
|
7350
|
+
const fixes = [];
|
|
7351
|
+
if (places.length > 0) {
|
|
7352
|
+
const { entities } = await listEntities(projectRoot, "finding");
|
|
7353
|
+
const byId = new Map(entities.map((e) => [e.frontmatter.id, e.frontmatter]));
|
|
7354
|
+
for (const place of revisitCoverage(places, steps).places) {
|
|
7355
|
+
for (const id of place.finding_ids) {
|
|
7356
|
+
const f = byId.get(id);
|
|
7357
|
+
if (!f || f.type === "helps")
|
|
7358
|
+
continue;
|
|
7359
|
+
fixes.push({
|
|
7360
|
+
canonical_id: f.canonical_id ?? null,
|
|
7361
|
+
title: f.title ?? f.slug,
|
|
7362
|
+
fix: f.triage_note ?? null,
|
|
7363
|
+
page: place.url,
|
|
7364
|
+
reached: place.reached
|
|
7365
|
+
});
|
|
7366
|
+
}
|
|
7367
|
+
}
|
|
7368
|
+
}
|
|
7369
|
+
const directions = previousStartedAt ? (await listDirections(projectRoot).catch(() => [])).filter((d) => d.frontmatter.status === "done" && d.frontmatter.verify.includes(manifest.scenario_slug) && d.frontmatter.updated_at > previousStartedAt).map((d) => d.frontmatter.title) : [];
|
|
7370
|
+
return fixes.length > 0 || directions.length > 0 ? { fixes, directions } : null;
|
|
7371
|
+
}
|
|
7372
|
+
async function scenarioRunHistory(projectRoot, scenarioSlug, runId) {
|
|
7373
|
+
let runNumber = 1;
|
|
7374
|
+
let previousStartedAt = null;
|
|
7375
|
+
for (const rid of await listRunsInternal(projectRoot)) {
|
|
7376
|
+
if (rid >= runId)
|
|
7377
|
+
continue;
|
|
7378
|
+
try {
|
|
7379
|
+
const m = JSON.parse(await fs13.readFile(path10.join(runDir(projectRoot, rid), "walkthrough.json"), "utf8"));
|
|
7380
|
+
if (m.scenario_slug === scenarioSlug && m.status === "completed") {
|
|
7381
|
+
runNumber++;
|
|
7382
|
+
previousStartedAt = m.created_at;
|
|
7383
|
+
}
|
|
7384
|
+
} catch {
|
|
7385
|
+
}
|
|
7386
|
+
}
|
|
7387
|
+
return { runNumber, previousStartedAt };
|
|
7388
|
+
}
|
|
7389
|
+
async function compareWithBaseline(projectRoot, scenarioSlug, currentRunId, currentFindings) {
|
|
7390
|
+
const allRuns = await listRunsInternal(projectRoot);
|
|
7391
|
+
let baselineRunId = null;
|
|
7392
|
+
let baselineManifest = null;
|
|
7393
|
+
for (let i = allRuns.length - 1; i >= 0; i--) {
|
|
7394
|
+
const rid = allRuns[i];
|
|
7395
|
+
if (rid === currentRunId)
|
|
7548
7396
|
continue;
|
|
7549
7397
|
try {
|
|
7550
|
-
const mPath =
|
|
7551
|
-
const raw = await
|
|
7398
|
+
const mPath = path10.join(runDir(projectRoot, rid), "walkthrough.json");
|
|
7399
|
+
const raw = await fs13.readFile(mPath, "utf8");
|
|
7552
7400
|
const m = JSON.parse(raw);
|
|
7553
7401
|
if (m.scenario_slug === scenarioSlug && m.status === "completed" && !m.blocked) {
|
|
7554
7402
|
baselineRunId = rid;
|
|
@@ -7570,15 +7418,21 @@ async function compareWithBaseline(projectRoot, scenarioSlug, currentRunId, curr
|
|
|
7570
7418
|
const uncertain = [];
|
|
7571
7419
|
const matches = await readMatches(projectRoot, currentRunId);
|
|
7572
7420
|
const recheckedFixed = new Set((matches?.rechecked_fixed ?? []).map((r) => r.canonical_id));
|
|
7421
|
+
const recheckOf = new Map((matches?.rechecked_fixed ?? []).map((r) => [r.canonical_id, r]));
|
|
7422
|
+
const matchNote = new Map((matches?.matches ?? []).filter((m) => m.canonical_id && m.reasoning).map((m) => [m.canonical_id, m.reasoning]));
|
|
7423
|
+
const recurrence = new Map(currentFindings.filter((f) => f.canonical_id).map((f) => [f.canonical_id, f]));
|
|
7573
7424
|
if (recheckedFixed.size > 0) {
|
|
7574
7425
|
const prior = await priorCanonicals(projectRoot, scenarioSlug, currentRunId);
|
|
7575
7426
|
for (const p of prior) {
|
|
7576
7427
|
if (!recheckedFixed.has(p.canonical_id))
|
|
7577
7428
|
continue;
|
|
7429
|
+
const recheck = recheckOf.get(p.canonical_id);
|
|
7578
7430
|
resolved.push({
|
|
7579
7431
|
canonical_id: p.canonical_id,
|
|
7580
7432
|
title: p.title,
|
|
7581
|
-
type: p.type
|
|
7433
|
+
type: p.type,
|
|
7434
|
+
...recheck?.reasoning ? { note: recheck.reasoning } : {},
|
|
7435
|
+
...recheck ? { step_ids: recheck.step_ids } : {}
|
|
7582
7436
|
});
|
|
7583
7437
|
await setCanonicalFixStatus(projectRoot, scenarioSlug, p.canonical_id, "verified_resolved");
|
|
7584
7438
|
}
|
|
@@ -7595,11 +7449,20 @@ async function compareWithBaseline(projectRoot, scenarioSlug, currentRunId, curr
|
|
|
7595
7449
|
});
|
|
7596
7450
|
continue;
|
|
7597
7451
|
}
|
|
7452
|
+
const fixedBefore = bf.frontmatter.triage_status === "addressed";
|
|
7598
7453
|
if (currentCanonicalIds.has(cid)) {
|
|
7454
|
+
const note = matchNote.get(cid);
|
|
7599
7455
|
persists.push({
|
|
7600
7456
|
canonical_id: cid,
|
|
7601
7457
|
title: bf.frontmatter.title ?? bf.frontmatter.slug,
|
|
7602
|
-
type: bf.frontmatter.type
|
|
7458
|
+
type: bf.frontmatter.type,
|
|
7459
|
+
...note ? { note } : {},
|
|
7460
|
+
step_ids: recurrence.get(cid)?.linked_step_ids ?? [],
|
|
7461
|
+
...fixedBefore ? { fixed_before: true } : {},
|
|
7462
|
+
...bf.frontmatter.severity && recurrence.get(cid)?.severity ? {
|
|
7463
|
+
severity_before: bf.frontmatter.severity,
|
|
7464
|
+
severity_now: recurrence.get(cid)?.severity
|
|
7465
|
+
} : {}
|
|
7603
7466
|
});
|
|
7604
7467
|
await updateFixStatus(projectRoot, bf.frontmatter.slug, "verified_persists");
|
|
7605
7468
|
} else if (recheckedFixed.has(cid)) {
|
|
@@ -7607,7 +7470,8 @@ async function compareWithBaseline(projectRoot, scenarioSlug, currentRunId, curr
|
|
|
7607
7470
|
notObserved.push({
|
|
7608
7471
|
canonical_id: cid,
|
|
7609
7472
|
title: bf.frontmatter.title ?? bf.frontmatter.slug,
|
|
7610
|
-
type: bf.frontmatter.type
|
|
7473
|
+
type: bf.frontmatter.type,
|
|
7474
|
+
...fixedBefore ? { fixed_before: true } : {}
|
|
7611
7475
|
});
|
|
7612
7476
|
}
|
|
7613
7477
|
}
|
|
@@ -7629,9 +7493,9 @@ async function compareWithBaseline(projectRoot, scenarioSlug, currentRunId, curr
|
|
|
7629
7493
|
async function updateFixStatus(projectRoot, slug, status2) {
|
|
7630
7494
|
try {
|
|
7631
7495
|
const findingsDir = entityDirPath(projectRoot, "finding");
|
|
7632
|
-
const filePath =
|
|
7496
|
+
const filePath = path10.join(findingsDir, `${slug}.md`);
|
|
7633
7497
|
await withFileLock(filePath, async () => {
|
|
7634
|
-
const raw = await
|
|
7498
|
+
const raw = await fs13.readFile(filePath, "utf8");
|
|
7635
7499
|
const updated = raw.replace(/^(fix_status:\s*).*$/m, `$1${status2}`);
|
|
7636
7500
|
if (updated !== raw) {
|
|
7637
7501
|
await atomicWriteFile(filePath, updated);
|
|
@@ -7645,210 +7509,19 @@ fix_status: ${status2}`);
|
|
|
7645
7509
|
}
|
|
7646
7510
|
}
|
|
7647
7511
|
async function listRunsInternal(projectRoot) {
|
|
7648
|
-
const dir =
|
|
7512
|
+
const dir = path10.join(projectRoot, "iamu", ".runs");
|
|
7649
7513
|
try {
|
|
7650
|
-
const names2 = await
|
|
7514
|
+
const names2 = await fs13.readdir(dir);
|
|
7651
7515
|
return names2.filter((n) => !n.startsWith(".")).sort();
|
|
7652
7516
|
} catch {
|
|
7653
7517
|
return [];
|
|
7654
7518
|
}
|
|
7655
7519
|
}
|
|
7656
|
-
function renderCanonicalMarkdown(canonical) {
|
|
7657
|
-
const lines = [
|
|
7658
|
-
"<!-- GENERATED: do not edit by hand. Regen via complete_walkthrough or local-store renderer. -->",
|
|
7659
|
-
"",
|
|
7660
|
-
`# Walkthrough report: ${canonical.scenario.slug}`,
|
|
7661
|
-
"",
|
|
7662
|
-
...canonical.blocked ? [
|
|
7663
|
-
"> **Blocked \u2014 the walk could not start as it was set up.**",
|
|
7664
|
-
`> ${canonical.blocked.reason}`,
|
|
7665
|
-
">",
|
|
7666
|
-
"> This run is not compared with earlier runs and proves nothing about earlier findings. Fix the setup (the scenario's starting state or the persona's access) and run it again.",
|
|
7667
|
-
""
|
|
7668
|
-
] : [],
|
|
7669
|
-
`- Run ID: ${canonical.run_id}`,
|
|
7670
|
-
`- Target URL: ${canonical.target.url}`
|
|
7671
|
-
];
|
|
7672
|
-
if (canonical.environment) {
|
|
7673
|
-
lines.push(`- Environment: ${canonical.environment}`);
|
|
7674
|
-
}
|
|
7675
|
-
if (canonical.build) {
|
|
7676
|
-
const commit = canonical.build.git_sha?.slice(0, 7);
|
|
7677
|
-
const buildDetails = [
|
|
7678
|
-
canonical.build.label,
|
|
7679
|
-
canonical.build.git_branch,
|
|
7680
|
-
canonical.build.git_dirty && canonical.build.worktree_hash ? `${commit ?? "HEAD"} + uncommitted changes (${canonical.build.worktree_hash.slice(0, 6)}\u2026)` : commit,
|
|
7681
|
-
canonical.build.ci?.provider
|
|
7682
|
-
].filter(Boolean).join(" \xB7 ");
|
|
7683
|
-
if (buildDetails) {
|
|
7684
|
-
lines.push(`- Build: ${buildDetails}`);
|
|
7685
|
-
}
|
|
7686
|
-
}
|
|
7687
|
-
if (canonical.viewport) {
|
|
7688
|
-
lines.push(`- Viewport: ${canonical.viewport.device} (${canonical.viewport.width}\xD7${canonical.viewport.height})`);
|
|
7689
|
-
}
|
|
7690
|
-
lines.push(`- Started: ${canonical.provenance.started_at}`, `- Completed: ${canonical.provenance.completed_at}`, `- Walker prompt: ${canonical.provenance.walker_prompt.id}@${canonical.provenance.walker_prompt.version}`, `- Findings: ${canonical.findings.length}`, `- Steps: ${canonical.steps.length}`);
|
|
7691
|
-
const models = canonical.models;
|
|
7692
|
-
if (models) {
|
|
7693
|
-
const parts = [
|
|
7694
|
-
models.walker ? `Walker: ${models.walker}${models.walker_reported ? ` (${models.walker_reported})` : ""}` : "",
|
|
7695
|
-
models.judge ? `Judge: ${models.judge}${models.judge_reported ? ` (${models.judge_reported})` : ""}` : "",
|
|
7696
|
-
models.matcher ? `Matcher: ${models.matcher}` : ""
|
|
7697
|
-
].filter(Boolean);
|
|
7698
|
-
if (parts.length > 0) {
|
|
7699
|
-
lines.push(`- Models: ${parts.join(" \xB7 ")}`);
|
|
7700
|
-
}
|
|
7701
|
-
if (typeof models.velocity_sec_per_step === "number") {
|
|
7702
|
-
lines.push(`- Velocity: ~${models.velocity_sec_per_step}s/step (${models.total_duration_sec}s total)`);
|
|
7703
|
-
}
|
|
7704
|
-
}
|
|
7705
|
-
const what = canonical.what_worked;
|
|
7706
|
-
if (what && what.length > 0) {
|
|
7707
|
-
lines.push("", "## What worked");
|
|
7708
|
-
for (const w of what)
|
|
7709
|
-
lines.push(`- ${w}`);
|
|
7710
|
-
}
|
|
7711
|
-
const open2 = canonical.open_questions;
|
|
7712
|
-
if (open2 && open2.length > 0) {
|
|
7713
|
-
lines.push("", "## Open questions");
|
|
7714
|
-
for (const q of open2)
|
|
7715
|
-
lines.push(`- ${q}`);
|
|
7716
|
-
}
|
|
7717
|
-
const verification = canonical.verification;
|
|
7718
|
-
if (verification) {
|
|
7719
|
-
lines.push("", "## Since this scenario's last run", "", `Baseline: ${verification.baseline_run_id}`);
|
|
7720
|
-
const group = (label, items, note = "") => {
|
|
7721
|
-
if (items.length === 0)
|
|
7722
|
-
return;
|
|
7723
|
-
lines.push("", `**${label}** (${items.length})${note}`);
|
|
7724
|
-
for (const i of items)
|
|
7725
|
-
lines.push(`- ${i.title}`);
|
|
7726
|
-
};
|
|
7727
|
-
group("Resolved", verification.resolved, " \u2014 re-checked in this run");
|
|
7728
|
-
group("Persists", verification.persists);
|
|
7729
|
-
group("New since that run", verification.new_findings);
|
|
7730
|
-
group("Not seen this run", verification.not_observed, " \u2014 not evidence of a fix");
|
|
7731
|
-
group("Uncertain", verification.uncertain);
|
|
7732
|
-
}
|
|
7733
|
-
const diff = canonical.journey_diff;
|
|
7734
|
-
if (diff) {
|
|
7735
|
-
lines.push("", renderJourneyDiffMarkdown(diff));
|
|
7736
|
-
}
|
|
7737
|
-
lines.push("", "## Findings");
|
|
7738
|
-
if (canonical.findings.length === 0) {
|
|
7739
|
-
lines.push("", "_No findings recorded._");
|
|
7740
|
-
} else {
|
|
7741
|
-
const severityRank = ["critical", "major", "minor", "observation"];
|
|
7742
|
-
const rank = (value, order) => {
|
|
7743
|
-
const i = order.indexOf(value ?? "");
|
|
7744
|
-
return i === -1 ? order.length : i;
|
|
7745
|
-
};
|
|
7746
|
-
const typeRank = ["incorrect", "hinders", "missing", "irrelevant", "helps"];
|
|
7747
|
-
const ordered = [...canonical.findings].sort((a, b) => rank(a.severity, severityRank) - rank(b.severity, severityRank) || rank(a.type, typeRank) - rank(b.type, typeRank));
|
|
7748
|
-
for (const f of ordered) {
|
|
7749
|
-
lines.push("", `### ${f.type} / ${f.severity ?? "unranked"} \u2014 ${f.id}`);
|
|
7750
|
-
if (f.ui_fact === null || f.consequence === null) {
|
|
7751
|
-
lines.push("", "> _Legacy finding: predates the UI-fact / consequence depth gate._");
|
|
7752
|
-
}
|
|
7753
|
-
if (f.ui_fact)
|
|
7754
|
-
lines.push("", "**UI fact.** ", f.ui_fact);
|
|
7755
|
-
if (f.consequence)
|
|
7756
|
-
lines.push("", "**Consequence.** ", f.consequence);
|
|
7757
|
-
if (f.recommendation)
|
|
7758
|
-
lines.push("", "**Recommendation.** ", f.recommendation);
|
|
7759
|
-
for (const stepId of f.linked_step_ids) {
|
|
7760
|
-
const step = canonical.steps.find((st) => st.id === stepId);
|
|
7761
|
-
const label = step ? `Step ${step.step_number}` : stepId;
|
|
7762
|
-
lines.push("", step?.screenshot_ref ? `**Where.** ${label}
|
|
7763
|
-
|
|
7764
|
-
` : `**Where.** ${label}`);
|
|
7765
|
-
}
|
|
7766
|
-
if (f.linked_human_test_id) {
|
|
7767
|
-
lines.push("", `Human test: ${f.linked_human_test_id}`);
|
|
7768
|
-
}
|
|
7769
|
-
}
|
|
7770
|
-
}
|
|
7771
|
-
const analysis = canonical.analysis;
|
|
7772
|
-
if (analysis && analysis.directions.length > 0) {
|
|
7773
|
-
lines.push("", "## Directions", "", `_Structural changes proposed by ${analysis.advisor} after the walk. Reach is a prediction until the listed scenarios are re-run._`);
|
|
7774
|
-
for (const d of analysis.directions) {
|
|
7775
|
-
lines.push("", `### ${d.title} (${d.size})`, "", d.change);
|
|
7776
|
-
lines.push("", `Absorbs ${d.absorbs.length} finding${d.absorbs.length === 1 ? "" : "s"}.`);
|
|
7777
|
-
const helps = d.reach.filter((r) => r.effect === "helps");
|
|
7778
|
-
const risks = d.reach.filter((r) => r.effect === "risk");
|
|
7779
|
-
for (const r of helps)
|
|
7780
|
-
lines.push(`- Likely helps ${r.scenario}: ${r.reason}`);
|
|
7781
|
-
for (const r of risks)
|
|
7782
|
-
lines.push(`- Could hurt ${r.scenario}: ${r.reason}`);
|
|
7783
|
-
if (d.verify.length > 0)
|
|
7784
|
-
lines.push("", `Re-run to verify: ${d.verify.join(", ")}`);
|
|
7785
|
-
}
|
|
7786
|
-
}
|
|
7787
|
-
if (analysis && analysis.observations.length > 0) {
|
|
7788
|
-
lines.push("", "## Also worth knowing", "");
|
|
7789
|
-
for (const o of analysis.observations) {
|
|
7790
|
-
const label = o.kind === "coverage_gap" ? "Not walked" : "Feature risk";
|
|
7791
|
-
lines.push(`- **${label}: ${o.title}.** ${o.detail}`);
|
|
7792
|
-
if (o.suggested_scenario) {
|
|
7793
|
-
lines.push(` Suggested scenario: ${o.suggested_scenario}`);
|
|
7794
|
-
}
|
|
7795
|
-
}
|
|
7796
|
-
}
|
|
7797
|
-
lines.push("", "## Human tests");
|
|
7798
|
-
if (canonical.human_tests.length === 0) {
|
|
7799
|
-
lines.push("", "_No human tests on the outline._");
|
|
7800
|
-
} else {
|
|
7801
|
-
for (const ht of canonical.human_tests) {
|
|
7802
|
-
lines.push("", `- **${ht.question}**`);
|
|
7803
|
-
if (ht.verdict === null) {
|
|
7804
|
-
if (canonical.blocked) {
|
|
7805
|
-
lines.push(` - Verdict: _skipped (walk blocked)_`);
|
|
7806
|
-
} else {
|
|
7807
|
-
lines.push(` - Verdict: _pending judge_ (walker does not self-grade)`);
|
|
7808
|
-
}
|
|
7809
|
-
} else {
|
|
7810
|
-
lines.push(` - Verdict: **${ht.verdict}**`);
|
|
7811
|
-
if (ht.reasoning)
|
|
7812
|
-
lines.push(` - Reasoning: ${ht.reasoning}`);
|
|
7813
|
-
}
|
|
7814
|
-
}
|
|
7815
|
-
}
|
|
7816
|
-
lines.push("", "## Human test overall", "", `> ${canonical.human_test_overall.question}`);
|
|
7817
|
-
if (canonical.human_test_overall.answer === null) {
|
|
7818
|
-
if (canonical.blocked) {
|
|
7819
|
-
lines.push("", "_Skipped (walk blocked)._");
|
|
7820
|
-
} else {
|
|
7821
|
-
lines.push("", "_Pending judge stage._");
|
|
7822
|
-
}
|
|
7823
|
-
} else {
|
|
7824
|
-
lines.push("", canonical.human_test_overall.answer);
|
|
7825
|
-
if (canonical.human_test_overall.reasoning)
|
|
7826
|
-
lines.push("", canonical.human_test_overall.reasoning);
|
|
7827
|
-
}
|
|
7828
|
-
lines.push("", "## Steps");
|
|
7829
|
-
if (canonical.steps.length === 0) {
|
|
7830
|
-
lines.push("", "_No steps recorded._");
|
|
7831
|
-
} else {
|
|
7832
|
-
for (const s of canonical.steps) {
|
|
7833
|
-
lines.push("", `### ${s.step_number}. ${s.screen_title ?? "(untitled)"} \u2014 ${s.id}`);
|
|
7834
|
-
const where = [s.surface, s.url].filter(Boolean).join(" \u2014 ");
|
|
7835
|
-
if (where)
|
|
7836
|
-
lines.push("", `Page: ${where}`);
|
|
7837
|
-
if (s.persona_reasoning)
|
|
7838
|
-
lines.push("", `> ${s.persona_reasoning.replace(/\n/g, "\n> ")}`);
|
|
7839
|
-
if (s.grounding)
|
|
7840
|
-
lines.push("", `Grounding: ${s.grounding}`);
|
|
7841
|
-
if (s.screenshot_ref)
|
|
7842
|
-
lines.push("", ``);
|
|
7843
|
-
}
|
|
7844
|
-
}
|
|
7845
|
-
return `${lines.join("\n")}
|
|
7846
|
-
`;
|
|
7847
|
-
}
|
|
7848
7520
|
var init_walkthrough_canonical2 = __esm({
|
|
7849
7521
|
"../../packages/local-store/dist/walkthrough-canonical.js"() {
|
|
7850
7522
|
"use strict";
|
|
7851
|
-
|
|
7523
|
+
init_revisit();
|
|
7524
|
+
init_directions();
|
|
7852
7525
|
init_atomic();
|
|
7853
7526
|
init_file_lock();
|
|
7854
7527
|
init_dist();
|
|
@@ -7870,8 +7543,8 @@ __export(walkthrough_mirror_exports, {
|
|
|
7870
7543
|
walkthroughBundleSchema: () => walkthroughBundleSchema
|
|
7871
7544
|
});
|
|
7872
7545
|
import { createHash as createHash3 } from "crypto";
|
|
7873
|
-
import { promises as
|
|
7874
|
-
import
|
|
7546
|
+
import { promises as fs14 } from "fs";
|
|
7547
|
+
import path11 from "path";
|
|
7875
7548
|
function toCloudRunUuid(runId) {
|
|
7876
7549
|
if (UUID_RE.test(runId))
|
|
7877
7550
|
return runId.toLowerCase();
|
|
@@ -7921,18 +7594,18 @@ function buildPushUrl(cloudApiUrl, cloudProjectId) {
|
|
|
7921
7594
|
return `${base}/api/projects/${cloudProjectId}/walkthroughs/push`;
|
|
7922
7595
|
}
|
|
7923
7596
|
async function readLocalManifest(projectRoot, runId) {
|
|
7924
|
-
const p =
|
|
7597
|
+
const p = path11.join(runDir(projectRoot, runId), "walkthrough.json");
|
|
7925
7598
|
try {
|
|
7926
|
-
const raw = await
|
|
7599
|
+
const raw = await fs14.readFile(p, "utf8");
|
|
7927
7600
|
return JSON.parse(raw);
|
|
7928
7601
|
} catch {
|
|
7929
7602
|
return null;
|
|
7930
7603
|
}
|
|
7931
7604
|
}
|
|
7932
7605
|
async function readLocalReport(projectRoot, runId) {
|
|
7933
|
-
const p =
|
|
7606
|
+
const p = path11.join(runDir(projectRoot, runId), "report.md");
|
|
7934
7607
|
try {
|
|
7935
|
-
return await
|
|
7608
|
+
return await fs14.readFile(p, "utf8");
|
|
7936
7609
|
} catch (err) {
|
|
7937
7610
|
if (err.code === "ENOENT")
|
|
7938
7611
|
return "";
|
|
@@ -7940,10 +7613,10 @@ async function readLocalReport(projectRoot, runId) {
|
|
|
7940
7613
|
}
|
|
7941
7614
|
}
|
|
7942
7615
|
async function readScreenshotBytes(projectRoot, runId) {
|
|
7943
|
-
const dir =
|
|
7616
|
+
const dir = path11.join(runDir(projectRoot, runId), SCREENSHOTS_DIR);
|
|
7944
7617
|
let names2;
|
|
7945
7618
|
try {
|
|
7946
|
-
names2 = await
|
|
7619
|
+
names2 = await fs14.readdir(dir);
|
|
7947
7620
|
} catch (err) {
|
|
7948
7621
|
if (err.code === "ENOENT")
|
|
7949
7622
|
return /* @__PURE__ */ new Map();
|
|
@@ -7959,15 +7632,15 @@ async function readScreenshotBytes(projectRoot, runId) {
|
|
|
7959
7632
|
const contentType = EXT_TO_CONTENT_TYPE[ext === ".jpeg" ? ".jpeg" : ext];
|
|
7960
7633
|
if (!contentType)
|
|
7961
7634
|
continue;
|
|
7962
|
-
const bytes = await
|
|
7635
|
+
const bytes = await fs14.readFile(path11.join(dir, name));
|
|
7963
7636
|
out.set(seq2, { bytes, filename: name, contentType });
|
|
7964
7637
|
}
|
|
7965
7638
|
return out;
|
|
7966
7639
|
}
|
|
7967
7640
|
async function readVerdictsJson(projectRoot, runId) {
|
|
7968
|
-
const filePath =
|
|
7641
|
+
const filePath = path11.join(runDir(projectRoot, runId), "verdicts.json");
|
|
7969
7642
|
try {
|
|
7970
|
-
const raw = await
|
|
7643
|
+
const raw = await fs14.readFile(filePath, "utf-8");
|
|
7971
7644
|
return JSON.parse(raw);
|
|
7972
7645
|
} catch {
|
|
7973
7646
|
return null;
|
|
@@ -7976,9 +7649,9 @@ async function readVerdictsJson(projectRoot, runId) {
|
|
|
7976
7649
|
async function readProvenance(projectRoot, runId, verdicts) {
|
|
7977
7650
|
if (verdicts?.provenance)
|
|
7978
7651
|
return verdicts.provenance;
|
|
7979
|
-
const filePath =
|
|
7652
|
+
const filePath = path11.join(runDir(projectRoot, runId), "canonical.json");
|
|
7980
7653
|
try {
|
|
7981
|
-
const raw = await
|
|
7654
|
+
const raw = await fs14.readFile(filePath, "utf-8");
|
|
7982
7655
|
const data = JSON.parse(raw);
|
|
7983
7656
|
return data.provenance ?? null;
|
|
7984
7657
|
} catch {
|
|
@@ -7987,7 +7660,7 @@ async function readProvenance(projectRoot, runId, verdicts) {
|
|
|
7987
7660
|
}
|
|
7988
7661
|
async function readCanonicalExtras(projectRoot, runId) {
|
|
7989
7662
|
try {
|
|
7990
|
-
const data = JSON.parse(await
|
|
7663
|
+
const data = JSON.parse(await fs14.readFile(path11.join(runDir(projectRoot, runId), "canonical.json"), "utf-8"));
|
|
7991
7664
|
const strings = (v) => Array.isArray(v) && v.every((x) => typeof x === "string") ? v : void 0;
|
|
7992
7665
|
const verification = bundleVerificationSchema.safeParse(data.verification);
|
|
7993
7666
|
let blocked;
|
|
@@ -8736,7 +8409,7 @@ var init_refs = __esm({
|
|
|
8736
8409
|
});
|
|
8737
8410
|
|
|
8738
8411
|
// ../../packages/local-store/dist/sync/types.js
|
|
8739
|
-
import { z as
|
|
8412
|
+
import { z as z26 } from "zod";
|
|
8740
8413
|
function toRef(d) {
|
|
8741
8414
|
return { type: d.type, id: d.id, label: d.label };
|
|
8742
8415
|
}
|
|
@@ -8801,17 +8474,17 @@ var init_types = __esm({
|
|
|
8801
8474
|
DEFAULT_TIMEOUT_MS4 = 3e4;
|
|
8802
8475
|
MAX_BATCH_BYTES = 6 * 1024 * 1024;
|
|
8803
8476
|
BASE_HASH_BYTES = 96;
|
|
8804
|
-
importConflictSchema =
|
|
8805
|
-
type:
|
|
8806
|
-
id:
|
|
8807
|
-
base_hash:
|
|
8808
|
-
cloud_hash:
|
|
8809
|
-
cloud_updated_at:
|
|
8810
|
-
cloud_changed_by:
|
|
8477
|
+
importConflictSchema = z26.object({
|
|
8478
|
+
type: z26.string(),
|
|
8479
|
+
id: z26.string(),
|
|
8480
|
+
base_hash: z26.string().nullish(),
|
|
8481
|
+
cloud_hash: z26.string(),
|
|
8482
|
+
cloud_updated_at: z26.string().nullish(),
|
|
8483
|
+
cloud_changed_by: z26.string().nullish()
|
|
8811
8484
|
});
|
|
8812
|
-
importResponseSchema =
|
|
8813
|
-
imported:
|
|
8814
|
-
conflicts:
|
|
8485
|
+
importResponseSchema = z26.object({
|
|
8486
|
+
imported: z26.number().int().min(0),
|
|
8487
|
+
conflicts: z26.array(importConflictSchema).optional()
|
|
8815
8488
|
});
|
|
8816
8489
|
}
|
|
8817
8490
|
});
|
|
@@ -9062,7 +8735,7 @@ var init_push = __esm({
|
|
|
9062
8735
|
});
|
|
9063
8736
|
|
|
9064
8737
|
// ../../packages/local-store/dist/sync/context.js
|
|
9065
|
-
import { promises as
|
|
8738
|
+
import { promises as fs15 } from "fs";
|
|
9066
8739
|
async function syncContext(target) {
|
|
9067
8740
|
const { projectRoot } = target;
|
|
9068
8741
|
const summary = zeroContext();
|
|
@@ -9070,7 +8743,7 @@ async function syncContext(target) {
|
|
|
9070
8743
|
const conflicts = [];
|
|
9071
8744
|
let raw;
|
|
9072
8745
|
try {
|
|
9073
|
-
raw = await
|
|
8746
|
+
raw = await fs15.readFile(contextPath(projectRoot), "utf8");
|
|
9074
8747
|
} catch (err) {
|
|
9075
8748
|
if (err.code === "ENOENT")
|
|
9076
8749
|
return { summary, errors, conflicts };
|
|
@@ -9151,7 +8824,7 @@ var init_context3 = __esm({
|
|
|
9151
8824
|
// ../../packages/local-store/dist/git-branches.js
|
|
9152
8825
|
import { execFile } from "child_process";
|
|
9153
8826
|
import { realpathSync } from "fs";
|
|
9154
|
-
import
|
|
8827
|
+
import path12 from "path";
|
|
9155
8828
|
function defaultGitExec(cwd, args, input) {
|
|
9156
8829
|
return new Promise((resolve4) => {
|
|
9157
8830
|
let settled = false;
|
|
@@ -9210,7 +8883,7 @@ async function otherBranchesWithFiles(cwd, absPaths, opts = {}) {
|
|
|
9210
8883
|
return out;
|
|
9211
8884
|
return null;
|
|
9212
8885
|
}
|
|
9213
|
-
const top =
|
|
8886
|
+
const top = path12.resolve(topRes.stdout.trim());
|
|
9214
8887
|
if (!top)
|
|
9215
8888
|
return out;
|
|
9216
8889
|
const refsRes = await git2(cwd, [
|
|
@@ -9248,8 +8921,8 @@ async function otherBranchesWithFiles(cwd, absPaths, opts = {}) {
|
|
|
9248
8921
|
} catch {
|
|
9249
8922
|
}
|
|
9250
8923
|
const rel = absPaths.map((abs) => {
|
|
9251
|
-
const fromCwd =
|
|
9252
|
-
return
|
|
8924
|
+
const fromCwd = path12.relative(cwd, abs);
|
|
8925
|
+
return path12.relative(top, path12.resolve(base, fromCwd)).split(path12.sep).join("/");
|
|
9253
8926
|
});
|
|
9254
8927
|
const queries = [];
|
|
9255
8928
|
const lines = [];
|
|
@@ -9287,8 +8960,8 @@ var init_git_branches = __esm({
|
|
|
9287
8960
|
});
|
|
9288
8961
|
|
|
9289
8962
|
// ../../packages/local-store/dist/deletions.js
|
|
9290
|
-
import { promises as
|
|
9291
|
-
import
|
|
8963
|
+
import { promises as fs16 } from "fs";
|
|
8964
|
+
import path13 from "path";
|
|
9292
8965
|
function describeDeletionCount(type2, n) {
|
|
9293
8966
|
const nouns = {
|
|
9294
8967
|
persona: ["persona", "personas"],
|
|
@@ -9315,7 +8988,7 @@ function summarizeDeletions(items) {
|
|
|
9315
8988
|
}
|
|
9316
8989
|
async function readPushedEntities(projectRoot) {
|
|
9317
8990
|
try {
|
|
9318
|
-
const raw = await
|
|
8991
|
+
const raw = await fs16.readFile(pushedEntitiesPath(projectRoot), "utf8");
|
|
9319
8992
|
const parsed = JSON.parse(raw);
|
|
9320
8993
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
9321
8994
|
return {};
|
|
@@ -9354,11 +9027,11 @@ async function rememberPushedEntities(projectRoot, known) {
|
|
|
9354
9027
|
}
|
|
9355
9028
|
async function readLenient(projectRoot, relPath) {
|
|
9356
9029
|
try {
|
|
9357
|
-
const raw = await
|
|
9030
|
+
const raw = await fs16.readFile(path13.join(projectRoot, relPath), "utf8");
|
|
9358
9031
|
const fm = parseFrontmatter(raw).frontmatter;
|
|
9359
9032
|
if (typeof fm.id !== "string" || fm.id.length === 0)
|
|
9360
9033
|
return null;
|
|
9361
|
-
const slug = typeof fm.slug === "string" ? fm.slug :
|
|
9034
|
+
const slug = typeof fm.slug === "string" ? fm.slug : path13.basename(relPath, ".md");
|
|
9362
9035
|
const run = typeof fm.run === "string" ? fm.run : void 0;
|
|
9363
9036
|
return { id: fm.id, slug, run };
|
|
9364
9037
|
} catch {
|
|
@@ -9780,7 +9453,7 @@ var init_deletions2 = __esm({
|
|
|
9780
9453
|
});
|
|
9781
9454
|
|
|
9782
9455
|
// ../../packages/local-store/dist/sync/scan.js
|
|
9783
|
-
import { promises as
|
|
9456
|
+
import { promises as fs17 } from "fs";
|
|
9784
9457
|
function blockedRef(refs, blocked) {
|
|
9785
9458
|
if (!refs)
|
|
9786
9459
|
return null;
|
|
@@ -9876,7 +9549,7 @@ async function scanPending(projectRoot) {
|
|
|
9876
9549
|
continue;
|
|
9877
9550
|
let bytes;
|
|
9878
9551
|
try {
|
|
9879
|
-
bytes = await
|
|
9552
|
+
bytes = await fs17.readFile(entity.absPath, "utf8");
|
|
9880
9553
|
} catch {
|
|
9881
9554
|
continue;
|
|
9882
9555
|
}
|
|
@@ -9937,7 +9610,7 @@ var init_scan = __esm({
|
|
|
9937
9610
|
});
|
|
9938
9611
|
|
|
9939
9612
|
// ../../packages/local-store/dist/sync/walkthroughs.js
|
|
9940
|
-
import { promises as
|
|
9613
|
+
import { promises as fs18 } from "fs";
|
|
9941
9614
|
import nodePath from "path";
|
|
9942
9615
|
async function syncWalkthroughs(projectRoot, deps, unpushableScenarios = /* @__PURE__ */ new Set()) {
|
|
9943
9616
|
const summary = zeroWalkthroughs();
|
|
@@ -9965,7 +9638,7 @@ async function syncWalkthroughs(projectRoot, deps, unpushableScenarios = /* @__P
|
|
|
9965
9638
|
const manifestPath = nodePath.join(runDir(projectRoot, runId), "walkthrough.json");
|
|
9966
9639
|
let manifestRaw;
|
|
9967
9640
|
try {
|
|
9968
|
-
manifestRaw = await
|
|
9641
|
+
manifestRaw = await fs18.readFile(manifestPath, "utf8");
|
|
9969
9642
|
} catch {
|
|
9970
9643
|
continue;
|
|
9971
9644
|
}
|
|
@@ -10304,8 +9977,8 @@ var init_disconnect = __esm({
|
|
|
10304
9977
|
});
|
|
10305
9978
|
|
|
10306
9979
|
// ../../packages/local-store/dist/status.js
|
|
10307
|
-
import { promises as
|
|
10308
|
-
import
|
|
9980
|
+
import { promises as fs19 } from "fs";
|
|
9981
|
+
import path14 from "path";
|
|
10309
9982
|
async function countType(projectRoot, type2) {
|
|
10310
9983
|
const result = await listEntities(projectRoot, type2);
|
|
10311
9984
|
const contentHashes = /* @__PURE__ */ new Map();
|
|
@@ -10315,7 +9988,7 @@ async function countType(projectRoot, type2) {
|
|
|
10315
9988
|
if (typeof id !== "string" || typeof slug !== "string")
|
|
10316
9989
|
continue;
|
|
10317
9990
|
try {
|
|
10318
|
-
const raw = await
|
|
9991
|
+
const raw = await fs19.readFile(entity.absPath, "utf8");
|
|
10319
9992
|
contentHashes.set(id, hashNormalizedContent(raw));
|
|
10320
9993
|
} catch {
|
|
10321
9994
|
}
|
|
@@ -10409,7 +10082,7 @@ async function countContext(projectRoot, opts = {
|
|
|
10409
10082
|
return base;
|
|
10410
10083
|
let pending = true;
|
|
10411
10084
|
try {
|
|
10412
|
-
const bytes = await
|
|
10085
|
+
const bytes = await fs19.readFile(contextPath(projectRoot), "utf8");
|
|
10413
10086
|
const hash = hashNormalizedContent(bytes);
|
|
10414
10087
|
pending = opts.lastPush["context:root"] !== hash;
|
|
10415
10088
|
} catch {
|
|
@@ -10428,10 +10101,10 @@ async function countWalkthroughs(projectRoot, opts = { cloudLinked: false, lastP
|
|
|
10428
10101
|
let pushed = 0;
|
|
10429
10102
|
let pending = 0;
|
|
10430
10103
|
for (const runId of valid) {
|
|
10431
|
-
const manifestPath =
|
|
10104
|
+
const manifestPath = path14.join(projectRoot, "iamu", ".runs", runId, "walkthrough.json");
|
|
10432
10105
|
let completed = false;
|
|
10433
10106
|
try {
|
|
10434
|
-
const raw = await
|
|
10107
|
+
const raw = await fs19.readFile(manifestPath, "utf8");
|
|
10435
10108
|
const manifest = JSON.parse(raw);
|
|
10436
10109
|
completed = manifest.status === "completed";
|
|
10437
10110
|
} catch {
|
|
@@ -10451,7 +10124,7 @@ async function getProjectStatus(projectRoot, opts = {}) {
|
|
|
10451
10124
|
const config = await readConfig(projectRoot);
|
|
10452
10125
|
const iamuPath = iamuDir(projectRoot);
|
|
10453
10126
|
try {
|
|
10454
|
-
await
|
|
10127
|
+
await fs19.access(iamuPath);
|
|
10455
10128
|
} catch {
|
|
10456
10129
|
throw new Error(`status: iamu/ directory missing at ${iamuPath}`);
|
|
10457
10130
|
}
|
|
@@ -10633,22 +10306,22 @@ var init_status = __esm({
|
|
|
10633
10306
|
});
|
|
10634
10307
|
|
|
10635
10308
|
// ../../packages/local-store/dist/prune.js
|
|
10636
|
-
import { promises as
|
|
10637
|
-
import
|
|
10309
|
+
import { promises as fs20 } from "fs";
|
|
10310
|
+
import path15 from "path";
|
|
10638
10311
|
async function getDirectoryStats(dirPath) {
|
|
10639
10312
|
let bytes = 0;
|
|
10640
10313
|
let count = 0;
|
|
10641
10314
|
try {
|
|
10642
|
-
const entries = await
|
|
10315
|
+
const entries = await fs20.readdir(dirPath, { withFileTypes: true });
|
|
10643
10316
|
for (const entry of entries) {
|
|
10644
|
-
const fullPath =
|
|
10317
|
+
const fullPath = path15.join(dirPath, entry.name);
|
|
10645
10318
|
if (entry.isDirectory()) {
|
|
10646
10319
|
const sub = await getDirectoryStats(fullPath);
|
|
10647
10320
|
bytes += sub.bytes;
|
|
10648
10321
|
count += sub.count;
|
|
10649
10322
|
} else if (entry.isFile()) {
|
|
10650
10323
|
try {
|
|
10651
|
-
const s = await
|
|
10324
|
+
const s = await fs20.stat(fullPath);
|
|
10652
10325
|
bytes += s.size;
|
|
10653
10326
|
count += 1;
|
|
10654
10327
|
} catch {
|
|
@@ -10664,12 +10337,12 @@ async function getScreenshotsStats(screenshotsDir) {
|
|
|
10664
10337
|
let count = 0;
|
|
10665
10338
|
const paths = [];
|
|
10666
10339
|
try {
|
|
10667
|
-
const entries = await
|
|
10340
|
+
const entries = await fs20.readdir(screenshotsDir, { withFileTypes: true });
|
|
10668
10341
|
for (const entry of entries) {
|
|
10669
10342
|
if (entry.isFile() && (entry.name.endsWith(".png") || entry.name.endsWith(".jpg") || entry.name.endsWith(".jpeg") || entry.name.endsWith(".webp"))) {
|
|
10670
|
-
const fullPath =
|
|
10343
|
+
const fullPath = path15.join(screenshotsDir, entry.name);
|
|
10671
10344
|
try {
|
|
10672
|
-
const s = await
|
|
10345
|
+
const s = await fs20.stat(fullPath);
|
|
10673
10346
|
bytes += s.size;
|
|
10674
10347
|
count += 1;
|
|
10675
10348
|
paths.push(fullPath);
|
|
@@ -10689,8 +10362,8 @@ async function pruneRuns(projectRoot, opts = {}) {
|
|
|
10689
10362
|
let timestamp2 = null;
|
|
10690
10363
|
let scenarioSlug;
|
|
10691
10364
|
try {
|
|
10692
|
-
const manifestPath =
|
|
10693
|
-
const raw = await
|
|
10365
|
+
const manifestPath = path15.join(dirPath, "walkthrough.json");
|
|
10366
|
+
const raw = await fs20.readFile(manifestPath, "utf-8");
|
|
10694
10367
|
const parsed = JSON.parse(raw);
|
|
10695
10368
|
if (parsed.scenario_slug)
|
|
10696
10369
|
scenarioSlug = parsed.scenario_slug;
|
|
@@ -10702,7 +10375,7 @@ async function pruneRuns(projectRoot, opts = {}) {
|
|
|
10702
10375
|
}
|
|
10703
10376
|
if (!timestamp2 || Number.isNaN(timestamp2.getTime())) {
|
|
10704
10377
|
try {
|
|
10705
|
-
const st = await
|
|
10378
|
+
const st = await fs20.stat(dirPath);
|
|
10706
10379
|
timestamp2 = st.mtime;
|
|
10707
10380
|
} catch {
|
|
10708
10381
|
timestamp2 = /* @__PURE__ */ new Date();
|
|
@@ -10737,12 +10410,12 @@ async function pruneRuns(projectRoot, opts = {}) {
|
|
|
10737
10410
|
let totalScreenshotsDeleted = 0;
|
|
10738
10411
|
let totalBytesReclaimed = 0;
|
|
10739
10412
|
for (const c of candidates) {
|
|
10740
|
-
const screenshotsDir =
|
|
10413
|
+
const screenshotsDir = path15.join(c.dirPath, "screenshots");
|
|
10741
10414
|
const shotStats = await getScreenshotsStats(screenshotsDir);
|
|
10742
10415
|
if (opts.screenshotsOnly) {
|
|
10743
10416
|
if (!opts.dryRun) {
|
|
10744
10417
|
for (const shotPath of shotStats.paths) {
|
|
10745
|
-
await
|
|
10418
|
+
await fs20.unlink(shotPath).catch(() => {
|
|
10746
10419
|
});
|
|
10747
10420
|
}
|
|
10748
10421
|
}
|
|
@@ -10759,7 +10432,7 @@ async function pruneRuns(projectRoot, opts = {}) {
|
|
|
10759
10432
|
} else {
|
|
10760
10433
|
const dirStats = await getDirectoryStats(c.dirPath);
|
|
10761
10434
|
if (!opts.dryRun) {
|
|
10762
|
-
await
|
|
10435
|
+
await fs20.rm(c.dirPath, { recursive: true, force: true });
|
|
10763
10436
|
}
|
|
10764
10437
|
prunedRuns.push({
|
|
10765
10438
|
runId: c.runId,
|
|
@@ -10791,8 +10464,8 @@ var init_prune = __esm({
|
|
|
10791
10464
|
|
|
10792
10465
|
// ../../packages/local-store/dist/explore-map.js
|
|
10793
10466
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
10794
|
-
import { promises as
|
|
10795
|
-
import
|
|
10467
|
+
import { promises as fs21 } from "fs";
|
|
10468
|
+
import path16 from "path";
|
|
10796
10469
|
function templateToken(token) {
|
|
10797
10470
|
if (token === "")
|
|
10798
10471
|
return token;
|
|
@@ -10871,7 +10544,7 @@ function fnv1a(s) {
|
|
|
10871
10544
|
return h.toString(16).padStart(8, "0");
|
|
10872
10545
|
}
|
|
10873
10546
|
function exploreDir(projectRoot) {
|
|
10874
|
-
return
|
|
10547
|
+
return path16.join(cacheDir(projectRoot), "explore");
|
|
10875
10548
|
}
|
|
10876
10549
|
function composeExploreId(now, randomHex) {
|
|
10877
10550
|
const ts = now.toISOString().replace(/[:.]/g, "-").replace(/Z$/, "");
|
|
@@ -10883,17 +10556,17 @@ function isValidExploreId(id) {
|
|
|
10883
10556
|
function mapPath(projectRoot, id) {
|
|
10884
10557
|
if (!isValidExploreId(id))
|
|
10885
10558
|
throw new Error(`Not an explore map id: ${id}`);
|
|
10886
|
-
return
|
|
10559
|
+
return path16.join(exploreDir(projectRoot), id, "map.json");
|
|
10887
10560
|
}
|
|
10888
10561
|
async function writeExploreMap(projectRoot, map2) {
|
|
10889
10562
|
const file = mapPath(projectRoot, map2.id);
|
|
10890
|
-
await
|
|
10563
|
+
await fs21.mkdir(path16.dirname(file), { recursive: true });
|
|
10891
10564
|
await atomicWriteFile(file, `${JSON.stringify(map2, null, 2)}
|
|
10892
10565
|
`);
|
|
10893
10566
|
return file;
|
|
10894
10567
|
}
|
|
10895
10568
|
async function readExploreMap(projectRoot, id) {
|
|
10896
|
-
const raw = await
|
|
10569
|
+
const raw = await fs21.readFile(mapPath(projectRoot, id), "utf8");
|
|
10897
10570
|
const map2 = JSON.parse(raw);
|
|
10898
10571
|
if (map2?.schema !== EXPLORE_MAP_SCHEMA || !Array.isArray(map2.surfaces)) {
|
|
10899
10572
|
throw new Error(`Unreadable explore map: ${id}`);
|
|
@@ -10902,7 +10575,7 @@ async function readExploreMap(projectRoot, id) {
|
|
|
10902
10575
|
}
|
|
10903
10576
|
async function listExploreMaps(projectRoot) {
|
|
10904
10577
|
try {
|
|
10905
|
-
const entries = await
|
|
10578
|
+
const entries = await fs21.readdir(exploreDir(projectRoot), {
|
|
10906
10579
|
withFileTypes: true
|
|
10907
10580
|
});
|
|
10908
10581
|
return entries.filter((e) => e.isDirectory() && isValidExploreId(e.name)).map((e) => e.name).sort();
|
|
@@ -11003,10 +10676,10 @@ async function readAllRunSteps(projectRoot) {
|
|
|
11003
10676
|
function matchSurfaceCapabilities(place, capabilities) {
|
|
11004
10677
|
const normPlace = place.length > 1 ? place.replace(/\/+$/, "") : place;
|
|
11005
10678
|
const matchedCaps = /* @__PURE__ */ new Map();
|
|
11006
|
-
for (const
|
|
11007
|
-
if (!
|
|
10679
|
+
for (const cap2 of capabilities) {
|
|
10680
|
+
if (!cap2.paths || cap2.paths.length === 0)
|
|
11008
10681
|
continue;
|
|
11009
|
-
for (const rawPath of
|
|
10682
|
+
for (const rawPath of cap2.paths) {
|
|
11010
10683
|
if (!rawPath)
|
|
11011
10684
|
continue;
|
|
11012
10685
|
const cleanPath = rawPath.length > 1 ? rawPath.replace(/\/+$/, "") : rawPath;
|
|
@@ -11017,9 +10690,9 @@ function matchSurfaceCapabilities(place, capabilities) {
|
|
|
11017
10690
|
isMatch = normPlace === cleanPath || normPlace.startsWith(`${cleanPath}/`) || normPlace.startsWith(`${cleanPath}?`) || normPlace.startsWith(`${cleanPath}#`);
|
|
11018
10691
|
}
|
|
11019
10692
|
if (isMatch) {
|
|
11020
|
-
const prevLen = matchedCaps.get(
|
|
10693
|
+
const prevLen = matchedCaps.get(cap2.slug) ?? -1;
|
|
11021
10694
|
if (cleanPath.length > prevLen) {
|
|
11022
|
-
matchedCaps.set(
|
|
10695
|
+
matchedCaps.set(cap2.slug, cleanPath.length);
|
|
11023
10696
|
}
|
|
11024
10697
|
}
|
|
11025
10698
|
}
|
|
@@ -11122,94 +10795,831 @@ function groupUnassignedByPrefix(surfaces) {
|
|
|
11122
10795
|
g.examples.push(pathOnly);
|
|
11123
10796
|
}
|
|
11124
10797
|
}
|
|
11125
|
-
return [...groups.values()].sort((a, b) => b.surfaces - a.surfaces || a.prefix.localeCompare(b.prefix));
|
|
10798
|
+
return [...groups.values()].sort((a, b) => b.surfaces - a.surfaces || a.prefix.localeCompare(b.prefix));
|
|
10799
|
+
}
|
|
10800
|
+
async function readCapabilityCoverage(projectRoot, coverage) {
|
|
10801
|
+
const [capsResult, outlinesResult] = await Promise.all([
|
|
10802
|
+
listEntities(projectRoot, "capability"),
|
|
10803
|
+
listEntities(projectRoot, "outline")
|
|
10804
|
+
]);
|
|
10805
|
+
const caps = capsResult.entities.map((e) => ({
|
|
10806
|
+
slug: e.frontmatter.slug,
|
|
10807
|
+
title: e.frontmatter.title,
|
|
10808
|
+
area: e.frontmatter.area ?? null,
|
|
10809
|
+
paths: e.frontmatter.paths ?? []
|
|
10810
|
+
}));
|
|
10811
|
+
const outlines = outlinesResult.entities.map((e) => ({
|
|
10812
|
+
primary_capability: e.frontmatter.primary_capability,
|
|
10813
|
+
additional_capabilities: e.frontmatter.additional_capabilities ?? []
|
|
10814
|
+
}));
|
|
10815
|
+
return summarizeCapabilityCoverage(coverage.surfaces, caps, outlines);
|
|
10816
|
+
}
|
|
10817
|
+
function actionSignature(a) {
|
|
10818
|
+
const where = a.outcome.kind === "surface" ? a.outcome.surface : a.outcome.kind === "skipped" ? `skipped:${a.outcome.reason}` : a.outcome.kind === "external" ? `external:${a.outcome.url}` : a.outcome.kind;
|
|
10819
|
+
return `${a.role} "${a.name}" \u2192 ${where}`;
|
|
10820
|
+
}
|
|
10821
|
+
function diffExploreMaps(a, b) {
|
|
10822
|
+
const aByKey = new Map(a.surfaces.map((s) => [s.key, s]));
|
|
10823
|
+
const bByKey = new Map(b.surfaces.map((s) => [s.key, s]));
|
|
10824
|
+
const ref = (s) => ({
|
|
10825
|
+
key: s.key,
|
|
10826
|
+
label: s.label
|
|
10827
|
+
});
|
|
10828
|
+
const onlyA = a.surfaces.filter((s) => !bByKey.has(s.key)).map(ref);
|
|
10829
|
+
const onlyB = b.surfaces.filter((s) => !aByKey.has(s.key)).map(ref);
|
|
10830
|
+
const changed = [];
|
|
10831
|
+
let common2 = 0;
|
|
10832
|
+
for (const sa of a.surfaces) {
|
|
10833
|
+
const sb = bByKey.get(sa.key);
|
|
10834
|
+
if (!sb)
|
|
10835
|
+
continue;
|
|
10836
|
+
common2++;
|
|
10837
|
+
const setA = new Set(sa.actions.map(actionSignature));
|
|
10838
|
+
const setB = new Set(sb.actions.map(actionSignature));
|
|
10839
|
+
const inA = [...setA].filter((x) => !setB.has(x)).sort();
|
|
10840
|
+
const inB = [...setB].filter((x) => !setA.has(x)).sort();
|
|
10841
|
+
if (inA.length > 0 || inB.length > 0) {
|
|
10842
|
+
changed.push({ ...ref(sa), only_a: inA, only_b: inB });
|
|
10843
|
+
}
|
|
10844
|
+
}
|
|
10845
|
+
const union2 = common2 + onlyA.length + onlyB.length;
|
|
10846
|
+
return {
|
|
10847
|
+
a: a.id,
|
|
10848
|
+
b: b.id,
|
|
10849
|
+
common: common2,
|
|
10850
|
+
only_a: onlyA,
|
|
10851
|
+
only_b: onlyB,
|
|
10852
|
+
surface_overlap: union2 === 0 ? 1 : common2 / union2,
|
|
10853
|
+
changed_actions: changed
|
|
10854
|
+
};
|
|
10855
|
+
}
|
|
10856
|
+
var EXPLORE_MAP_SCHEMA, UUID, DATE, MAP_ID;
|
|
10857
|
+
var init_explore_map = __esm({
|
|
10858
|
+
"../../packages/local-store/dist/explore-map.js"() {
|
|
10859
|
+
"use strict";
|
|
10860
|
+
init_atomic();
|
|
10861
|
+
init_entity();
|
|
10862
|
+
init_paths();
|
|
10863
|
+
init_revisit();
|
|
10864
|
+
init_runs();
|
|
10865
|
+
init_walkthrough_canonical2();
|
|
10866
|
+
EXPLORE_MAP_SCHEMA = 1;
|
|
10867
|
+
UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
10868
|
+
DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
10869
|
+
MAP_ID = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}-[0-9a-f]{6}$/;
|
|
10870
|
+
}
|
|
10871
|
+
});
|
|
10872
|
+
|
|
10873
|
+
// ../../packages/local-store/dist/journey-diff.js
|
|
10874
|
+
function isProblemFinding(type2) {
|
|
10875
|
+
return PROBLEM_TYPES2.has(type2.toLowerCase());
|
|
10876
|
+
}
|
|
10877
|
+
function levenshteinDistance(a, b) {
|
|
10878
|
+
if (a === b)
|
|
10879
|
+
return 0;
|
|
10880
|
+
if (!a.length)
|
|
10881
|
+
return b.length;
|
|
10882
|
+
if (!b.length)
|
|
10883
|
+
return a.length;
|
|
10884
|
+
const row = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
10885
|
+
for (let i = 1; i <= a.length; i++) {
|
|
10886
|
+
let prev = i;
|
|
10887
|
+
for (let j = 1; j <= b.length; j++) {
|
|
10888
|
+
const val = a[i - 1] === b[j - 1] ? row[j - 1] : Math.min(row[j - 1], prev, row[j]) + 1;
|
|
10889
|
+
row[j - 1] = prev;
|
|
10890
|
+
prev = val;
|
|
10891
|
+
}
|
|
10892
|
+
row[b.length] = prev;
|
|
10893
|
+
}
|
|
10894
|
+
return row[b.length];
|
|
10895
|
+
}
|
|
10896
|
+
function normalizeAction(action) {
|
|
10897
|
+
if (!action)
|
|
10898
|
+
return "";
|
|
10899
|
+
return action.toLowerCase().replace(/['"`]/g, "").replace(/[^\p{L}\p{N}\s/_-]+/gu, " ").replace(/\s+/g, " ").trim();
|
|
10900
|
+
}
|
|
10901
|
+
function actionTokens(action) {
|
|
10902
|
+
const norm = normalizeAction(action);
|
|
10903
|
+
if (!norm)
|
|
10904
|
+
return /* @__PURE__ */ new Set();
|
|
10905
|
+
return new Set(norm.split(/\s+/).filter(Boolean));
|
|
10906
|
+
}
|
|
10907
|
+
function actionSimilarity(a, b) {
|
|
10908
|
+
const normA = normalizeAction(a);
|
|
10909
|
+
const normB = normalizeAction(b);
|
|
10910
|
+
if (!normA && !normB)
|
|
10911
|
+
return 1;
|
|
10912
|
+
if (!normA || !normB)
|
|
10913
|
+
return 0;
|
|
10914
|
+
if (normA === normB)
|
|
10915
|
+
return 1;
|
|
10916
|
+
const tokensA = actionTokens(normA);
|
|
10917
|
+
const tokensB = actionTokens(normB);
|
|
10918
|
+
let intersection = 0;
|
|
10919
|
+
for (const t of tokensA) {
|
|
10920
|
+
if (tokensB.has(t))
|
|
10921
|
+
intersection++;
|
|
10922
|
+
}
|
|
10923
|
+
if (intersection === 0) {
|
|
10924
|
+
if (tokensA.size <= 1 && tokensB.size <= 1) {
|
|
10925
|
+
const maxLen = Math.max(normA.length, normB.length);
|
|
10926
|
+
const levDist = levenshteinDistance(normA, normB);
|
|
10927
|
+
return levDist <= 2 && maxLen > 0 ? 1 - levDist / maxLen : 0;
|
|
10928
|
+
}
|
|
10929
|
+
return 0;
|
|
10930
|
+
}
|
|
10931
|
+
const union2 = tokensA.size + tokensB.size - intersection;
|
|
10932
|
+
const jaccard = union2 > 0 ? intersection / union2 : 0;
|
|
10933
|
+
const minSize = Math.min(tokensA.size, tokensB.size);
|
|
10934
|
+
const subsetRatio = minSize > 0 ? intersection / minSize : 0;
|
|
10935
|
+
if (normA.includes(normB) || normB.includes(normA) || subsetRatio === 1) {
|
|
10936
|
+
return Math.max(0.85, 0.7 + 0.3 * jaccard);
|
|
10937
|
+
}
|
|
10938
|
+
return 0.5 * jaccard + 0.5 * subsetRatio;
|
|
10939
|
+
}
|
|
10940
|
+
function routeSimilarity(urlA, surfaceA, urlB, surfaceB) {
|
|
10941
|
+
const normUrlA = normalizePlaceKey(urlA);
|
|
10942
|
+
const normUrlB = normalizePlaceKey(urlB);
|
|
10943
|
+
const hasUrlA = normUrlA !== null;
|
|
10944
|
+
const hasUrlB = normUrlB !== null;
|
|
10945
|
+
let urlScore = null;
|
|
10946
|
+
if (hasUrlA && hasUrlB) {
|
|
10947
|
+
if (normUrlA === normUrlB) {
|
|
10948
|
+
urlScore = 1;
|
|
10949
|
+
} else {
|
|
10950
|
+
const pathA = normUrlA.split("?")[0];
|
|
10951
|
+
const pathB = normUrlB.split("?")[0];
|
|
10952
|
+
urlScore = pathA === pathB ? 0.8 : 0;
|
|
10953
|
+
}
|
|
10954
|
+
}
|
|
10955
|
+
const sA = surfaceA?.trim();
|
|
10956
|
+
const sB = surfaceB?.trim();
|
|
10957
|
+
let surfaceScore = null;
|
|
10958
|
+
if (sA && sB) {
|
|
10959
|
+
if (surfaceKey(sA) === surfaceKey(sB)) {
|
|
10960
|
+
surfaceScore = 1;
|
|
10961
|
+
} else if (surfaceMatches(sA, sB) || surfaceMatches(sB, sA)) {
|
|
10962
|
+
surfaceScore = 0.8;
|
|
10963
|
+
} else {
|
|
10964
|
+
surfaceScore = 0;
|
|
10965
|
+
}
|
|
10966
|
+
}
|
|
10967
|
+
if (urlScore !== null && surfaceScore !== null) {
|
|
10968
|
+
if (urlScore >= 0.8 && surfaceScore >= 0.8)
|
|
10969
|
+
return 1;
|
|
10970
|
+
if (urlScore >= 0.8)
|
|
10971
|
+
return 0.85;
|
|
10972
|
+
if (surfaceScore >= 0.8)
|
|
10973
|
+
return 0.75;
|
|
10974
|
+
return 0;
|
|
10975
|
+
}
|
|
10976
|
+
if (urlScore !== null)
|
|
10977
|
+
return urlScore;
|
|
10978
|
+
if (surfaceScore !== null)
|
|
10979
|
+
return surfaceScore;
|
|
10980
|
+
return 0.5;
|
|
10981
|
+
}
|
|
10982
|
+
function stepSimilarity(a, b) {
|
|
10983
|
+
const rSim = routeSimilarity(a.url, a.surface, b.url, b.surface);
|
|
10984
|
+
const aSim = actionSimilarity(a.action, b.action);
|
|
10985
|
+
if (aSim < 0.25 && a.action && b.action) {
|
|
10986
|
+
return Math.min(0.2, rSim * 0.2);
|
|
10987
|
+
}
|
|
10988
|
+
if (rSim === 0) {
|
|
10989
|
+
return aSim >= 0.8 ? aSim * 0.4 : 0.1;
|
|
10990
|
+
}
|
|
10991
|
+
return 0.5 * rSim + 0.5 * aSim;
|
|
10992
|
+
}
|
|
10993
|
+
function findingMatchesStep(finding, stepNumber2) {
|
|
10994
|
+
if (finding.linked_step_ids && finding.linked_step_ids.length > 0) {
|
|
10995
|
+
return finding.linked_step_ids.some((id) => id === `step-${stepNumber2}` || id === String(stepNumber2));
|
|
10996
|
+
}
|
|
10997
|
+
if (finding.walkthrough_step_id) {
|
|
10998
|
+
const raw = finding.walkthrough_step_id.trim();
|
|
10999
|
+
if (raw === `step-${stepNumber2}` || raw === String(stepNumber2)) {
|
|
11000
|
+
return true;
|
|
11001
|
+
}
|
|
11002
|
+
}
|
|
11003
|
+
return false;
|
|
11004
|
+
}
|
|
11005
|
+
function isStallNote(note) {
|
|
11006
|
+
if (!note)
|
|
11007
|
+
return false;
|
|
11008
|
+
const lower = note.toLowerCase();
|
|
11009
|
+
return STALL_KEYWORDS.some((kw) => lower.includes(kw));
|
|
11010
|
+
}
|
|
11011
|
+
function alignJourneySteps(scenario, baselineRunId, currentRunId, baselineSteps, baselineFindings, currentSteps, currentFindings) {
|
|
11012
|
+
const m = baselineSteps.length;
|
|
11013
|
+
const n = currentSteps.length;
|
|
11014
|
+
const GAP_PENALTY = -0.7;
|
|
11015
|
+
const MATCH_THRESHOLD = 0.45;
|
|
11016
|
+
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
11017
|
+
for (let i2 = 0; i2 <= m; i2++)
|
|
11018
|
+
dp[i2][0] = i2 * GAP_PENALTY;
|
|
11019
|
+
for (let j2 = 0; j2 <= n; j2++)
|
|
11020
|
+
dp[0][j2] = j2 * GAP_PENALTY;
|
|
11021
|
+
for (let i2 = 1; i2 <= m; i2++) {
|
|
11022
|
+
for (let j2 = 1; j2 <= n; j2++) {
|
|
11023
|
+
const sim = stepSimilarity(baselineSteps[i2 - 1], currentSteps[j2 - 1]);
|
|
11024
|
+
const matchScore = sim >= MATCH_THRESHOLD ? sim * 2 - 0.2 : -1.5;
|
|
11025
|
+
const match2 = dp[i2 - 1][j2 - 1] + matchScore;
|
|
11026
|
+
const delP = dp[i2 - 1][j2] + GAP_PENALTY;
|
|
11027
|
+
const insC = dp[i2][j2 - 1] + GAP_PENALTY;
|
|
11028
|
+
dp[i2][j2] = Math.max(match2, delP, insC);
|
|
11029
|
+
}
|
|
11030
|
+
}
|
|
11031
|
+
const alignedPairs = [];
|
|
11032
|
+
let i = m;
|
|
11033
|
+
let j = n;
|
|
11034
|
+
while (i > 0 || j > 0) {
|
|
11035
|
+
if (i > 0 && j > 0) {
|
|
11036
|
+
const sim = stepSimilarity(baselineSteps[i - 1], currentSteps[j - 1]);
|
|
11037
|
+
const matchScore = sim >= MATCH_THRESHOLD ? sim * 2 - 0.2 : -1.5;
|
|
11038
|
+
const scoreFromMatch = dp[i - 1][j - 1] + matchScore;
|
|
11039
|
+
if (Math.abs(dp[i][j] - scoreFromMatch) < 1e-6 && sim >= MATCH_THRESHOLD) {
|
|
11040
|
+
alignedPairs.push({
|
|
11041
|
+
baseline: baselineSteps[i - 1],
|
|
11042
|
+
current: currentSteps[j - 1]
|
|
11043
|
+
});
|
|
11044
|
+
i--;
|
|
11045
|
+
j--;
|
|
11046
|
+
continue;
|
|
11047
|
+
}
|
|
11048
|
+
}
|
|
11049
|
+
if (j > 0 && Math.abs(dp[i][j] - (dp[i][j - 1] + GAP_PENALTY)) < 1e-6) {
|
|
11050
|
+
alignedPairs.push({
|
|
11051
|
+
baseline: null,
|
|
11052
|
+
current: currentSteps[j - 1]
|
|
11053
|
+
});
|
|
11054
|
+
j--;
|
|
11055
|
+
} else if (i > 0 && Math.abs(dp[i][j] - (dp[i - 1][j] + GAP_PENALTY)) < 1e-6) {
|
|
11056
|
+
alignedPairs.push({
|
|
11057
|
+
baseline: baselineSteps[i - 1],
|
|
11058
|
+
current: null
|
|
11059
|
+
});
|
|
11060
|
+
i--;
|
|
11061
|
+
} else if (i > 0 && j > 0) {
|
|
11062
|
+
alignedPairs.push({
|
|
11063
|
+
baseline: baselineSteps[i - 1],
|
|
11064
|
+
current: currentSteps[j - 1]
|
|
11065
|
+
});
|
|
11066
|
+
i--;
|
|
11067
|
+
j--;
|
|
11068
|
+
} else if (i > 0) {
|
|
11069
|
+
alignedPairs.push({
|
|
11070
|
+
baseline: baselineSteps[i - 1],
|
|
11071
|
+
current: null
|
|
11072
|
+
});
|
|
11073
|
+
i--;
|
|
11074
|
+
} else {
|
|
11075
|
+
alignedPairs.push({
|
|
11076
|
+
baseline: null,
|
|
11077
|
+
current: currentSteps[j - 1]
|
|
11078
|
+
});
|
|
11079
|
+
j--;
|
|
11080
|
+
}
|
|
11081
|
+
}
|
|
11082
|
+
alignedPairs.reverse();
|
|
11083
|
+
const maxBaselineStepNum = baselineSteps.length > 0 ? Math.max(...baselineSteps.map((s) => s.step_number)) : -1;
|
|
11084
|
+
const alignedSteps = [];
|
|
11085
|
+
const stats = {
|
|
11086
|
+
totalAlignedSteps: alignedPairs.length,
|
|
11087
|
+
same: 0,
|
|
11088
|
+
resolved: 0,
|
|
11089
|
+
persists: 0,
|
|
11090
|
+
regressions: 0,
|
|
11091
|
+
stalledInBaseline: 0,
|
|
11092
|
+
bypassed: 0,
|
|
11093
|
+
newPath: 0
|
|
11094
|
+
};
|
|
11095
|
+
for (let idx = 0; idx < alignedPairs.length; idx++) {
|
|
11096
|
+
const pair = alignedPairs[idx];
|
|
11097
|
+
const bStep = pair.baseline;
|
|
11098
|
+
const cStep = pair.current;
|
|
11099
|
+
const bFindings = bStep ? baselineFindings.filter((f) => findingMatchesStep(f, bStep.step_number)) : [];
|
|
11100
|
+
const cFindings = cStep ? currentFindings.filter((f) => findingMatchesStep(f, cStep.step_number)) : [];
|
|
11101
|
+
const bProblems = bFindings.filter((f) => isProblemFinding(f.type));
|
|
11102
|
+
const cProblems = cFindings.filter((f) => isProblemFinding(f.type));
|
|
11103
|
+
let status2 = "same";
|
|
11104
|
+
let summary = "Clean";
|
|
11105
|
+
if (bStep && cStep) {
|
|
11106
|
+
const wasStall = bStep.step_number === maxBaselineStepNum && (bProblems.length > 0 || isStallNote(bStep.note));
|
|
11107
|
+
if (bProblems.length > 0 && cProblems.length === 0) {
|
|
11108
|
+
status2 = "resolved";
|
|
11109
|
+
stats.resolved++;
|
|
11110
|
+
const probDesc = bProblems[0].ui_fact || bProblems[0].title || bProblems[0].id;
|
|
11111
|
+
summary = wasStall ? `Previous stall resolved \u2014 journey unblocked (${probDesc})` : `Resolved previous issue: ${probDesc}`;
|
|
11112
|
+
} else if (bProblems.length > 0 && cProblems.length > 0) {
|
|
11113
|
+
status2 = "persists";
|
|
11114
|
+
stats.persists++;
|
|
11115
|
+
const probDesc = cProblems[0].ui_fact || cProblems[0].title || cProblems[0].id;
|
|
11116
|
+
summary = `Problem persists: ${probDesc}`;
|
|
11117
|
+
} else if (bProblems.length === 0 && cProblems.length > 0) {
|
|
11118
|
+
status2 = "regression";
|
|
11119
|
+
stats.regressions++;
|
|
11120
|
+
const probDesc = cProblems[0].ui_fact || cProblems[0].title || cProblems[0].id;
|
|
11121
|
+
summary = `New issue reported: ${probDesc}`;
|
|
11122
|
+
} else {
|
|
11123
|
+
status2 = "same";
|
|
11124
|
+
stats.same++;
|
|
11125
|
+
summary = "Clean";
|
|
11126
|
+
}
|
|
11127
|
+
} else if (bStep && !cStep) {
|
|
11128
|
+
const wasStall = bStep.step_number === maxBaselineStepNum && (bProblems.length > 0 || isStallNote(bStep.note));
|
|
11129
|
+
if (wasStall) {
|
|
11130
|
+
status2 = "stalled";
|
|
11131
|
+
stats.stalledInBaseline++;
|
|
11132
|
+
const probDesc = bProblems[0]?.ui_fact || bProblems[0]?.title || "journey halted";
|
|
11133
|
+
summary = `Stalled in baseline: ${probDesc}`;
|
|
11134
|
+
} else {
|
|
11135
|
+
status2 = "bypassed";
|
|
11136
|
+
stats.bypassed++;
|
|
11137
|
+
summary = "Bypassed in current run";
|
|
11138
|
+
}
|
|
11139
|
+
} else if (!bStep && cStep) {
|
|
11140
|
+
if (cProblems.length > 0) {
|
|
11141
|
+
status2 = "regression";
|
|
11142
|
+
stats.regressions++;
|
|
11143
|
+
const probDesc = cProblems[0].ui_fact || cProblems[0].title || cProblems[0].id;
|
|
11144
|
+
summary = `New step with issue: ${probDesc}`;
|
|
11145
|
+
} else {
|
|
11146
|
+
status2 = "new_path";
|
|
11147
|
+
stats.newPath++;
|
|
11148
|
+
summary = "New path / unblocked progress";
|
|
11149
|
+
}
|
|
11150
|
+
}
|
|
11151
|
+
alignedSteps.push({
|
|
11152
|
+
index: idx + 1,
|
|
11153
|
+
status: status2,
|
|
11154
|
+
baselineStep: bStep,
|
|
11155
|
+
currentStep: cStep,
|
|
11156
|
+
baselineFindings: bFindings,
|
|
11157
|
+
currentFindings: cFindings,
|
|
11158
|
+
summary
|
|
11159
|
+
});
|
|
11160
|
+
}
|
|
11161
|
+
return {
|
|
11162
|
+
scenario,
|
|
11163
|
+
baselineRunId,
|
|
11164
|
+
currentRunId,
|
|
11165
|
+
steps: alignedSteps,
|
|
11166
|
+
stats
|
|
11167
|
+
};
|
|
11168
|
+
}
|
|
11169
|
+
function toStepFindingInfo(f) {
|
|
11170
|
+
return {
|
|
11171
|
+
id: f.frontmatter.id,
|
|
11172
|
+
title: f.frontmatter.title ?? f.slug,
|
|
11173
|
+
type: f.frontmatter.type,
|
|
11174
|
+
severity: f.frontmatter.severity,
|
|
11175
|
+
ui_fact: f.frontmatter.ui_fact,
|
|
11176
|
+
consequence: f.frontmatter.consequence,
|
|
11177
|
+
canonical_id: f.frontmatter.canonical_id,
|
|
11178
|
+
walkthrough_step_id: f.frontmatter.walkthrough_step_id
|
|
11179
|
+
};
|
|
11180
|
+
}
|
|
11181
|
+
function toJourneyStepInfo(s) {
|
|
11182
|
+
return {
|
|
11183
|
+
step_number: s.step_number,
|
|
11184
|
+
action: s.action,
|
|
11185
|
+
url: s.url ?? void 0,
|
|
11186
|
+
surface: s.surface ?? void 0,
|
|
11187
|
+
note: s.note ?? void 0
|
|
11188
|
+
};
|
|
11189
|
+
}
|
|
11190
|
+
function journeyStepRoute(step) {
|
|
11191
|
+
return step.currentStep?.url && normalizePlaceKey(step.currentStep.url) || step.baselineStep?.url && normalizePlaceKey(step.baselineStep.url) || step.currentStep?.surface || step.baselineStep?.surface || "\u2014";
|
|
11192
|
+
}
|
|
11193
|
+
async function computeJourneyDiff(projectRoot, baselineRunId, currentRunId, scenarioSlug) {
|
|
11194
|
+
const [bStepsRaw, bFindingsRaw, cStepsRaw, cFindingsRaw] = await Promise.all([
|
|
11195
|
+
readStepsForRun(projectRoot, baselineRunId),
|
|
11196
|
+
readFindingsForRun(projectRoot, baselineRunId),
|
|
11197
|
+
readStepsForRun(projectRoot, currentRunId),
|
|
11198
|
+
readFindingsForRun(projectRoot, currentRunId)
|
|
11199
|
+
]);
|
|
11200
|
+
const baselineSteps = bStepsRaw.map(toJourneyStepInfo);
|
|
11201
|
+
const baselineFindings = bFindingsRaw.map(toStepFindingInfo);
|
|
11202
|
+
const currentSteps = cStepsRaw.map(toJourneyStepInfo);
|
|
11203
|
+
const currentFindings = cFindingsRaw.map(toStepFindingInfo);
|
|
11204
|
+
return alignJourneySteps(scenarioSlug ?? "", baselineRunId, currentRunId, baselineSteps, baselineFindings, currentSteps, currentFindings);
|
|
11205
|
+
}
|
|
11206
|
+
function renderJourneyDiffMarkdown(diff) {
|
|
11207
|
+
const lines = [
|
|
11208
|
+
"### Chronological journey diff",
|
|
11209
|
+
"",
|
|
11210
|
+
`Baseline: \`${diff.baselineRunId}\` vs Current: \`${diff.currentRunId}\``
|
|
11211
|
+
];
|
|
11212
|
+
const parts = [];
|
|
11213
|
+
if (diff.stats.same > 0)
|
|
11214
|
+
parts.push(`${diff.stats.same} unchanged`);
|
|
11215
|
+
if (diff.stats.resolved > 0)
|
|
11216
|
+
parts.push(`${diff.stats.resolved} resolved`);
|
|
11217
|
+
if (diff.stats.persists > 0)
|
|
11218
|
+
parts.push(`${diff.stats.persists} persists`);
|
|
11219
|
+
if (diff.stats.regressions > 0)
|
|
11220
|
+
parts.push(`${diff.stats.regressions} new issues`);
|
|
11221
|
+
if (diff.stats.stalledInBaseline > 0)
|
|
11222
|
+
parts.push(`${diff.stats.stalledInBaseline} stalled in baseline`);
|
|
11223
|
+
if (diff.stats.bypassed > 0)
|
|
11224
|
+
parts.push(`${diff.stats.bypassed} bypassed`);
|
|
11225
|
+
if (diff.stats.newPath > 0)
|
|
11226
|
+
parts.push(`${diff.stats.newPath} new path steps`);
|
|
11227
|
+
if (parts.length > 0) {
|
|
11228
|
+
lines.push(`- **Summary**: ${parts.join(", ")}`);
|
|
11229
|
+
}
|
|
11230
|
+
lines.push("", "| # | Route | Baseline (Run N-1) | Current (Run N) | Status | Notes |", "|---|-------|--------------------|-----------------|--------|-------|");
|
|
11231
|
+
for (const step of diff.steps) {
|
|
11232
|
+
const route = journeyStepRoute(step);
|
|
11233
|
+
const baseAction = step.baselineStep ? `Step ${step.baselineStep.step_number}: ${escapeMarkdownCell(step.baselineStep.action)}` : "\u2014";
|
|
11234
|
+
const currAction = step.currentStep ? `Step ${step.currentStep.step_number}: ${escapeMarkdownCell(step.currentStep.action)}` : "\u2014";
|
|
11235
|
+
const statusBadge = `\`${step.status}\``;
|
|
11236
|
+
const notes = escapeMarkdownCell(step.summary);
|
|
11237
|
+
lines.push(`| ${step.index} | \`${escapeMarkdownCell(route)}\` | ${baseAction} | ${currAction} | ${statusBadge} | ${notes} |`);
|
|
11238
|
+
}
|
|
11239
|
+
return lines.join("\n");
|
|
11240
|
+
}
|
|
11241
|
+
function escapeMarkdownCell(text) {
|
|
11242
|
+
return text.replace(/\|/g, "\\|").replace(/\n/g, " ");
|
|
11243
|
+
}
|
|
11244
|
+
var PROBLEM_TYPES2, STALL_KEYWORDS;
|
|
11245
|
+
var init_journey_diff = __esm({
|
|
11246
|
+
"../../packages/local-store/dist/journey-diff.js"() {
|
|
11247
|
+
"use strict";
|
|
11248
|
+
init_revisit();
|
|
11249
|
+
init_walkthrough_canonical2();
|
|
11250
|
+
PROBLEM_TYPES2 = /* @__PURE__ */ new Set(["hinders", "missing", "incorrect"]);
|
|
11251
|
+
STALL_KEYWORDS = [
|
|
11252
|
+
"blocked",
|
|
11253
|
+
"unable to proceed",
|
|
11254
|
+
"stalled",
|
|
11255
|
+
"halted",
|
|
11256
|
+
"stuck",
|
|
11257
|
+
"cannot proceed",
|
|
11258
|
+
"could not proceed",
|
|
11259
|
+
"failed to load",
|
|
11260
|
+
"timed out"
|
|
11261
|
+
];
|
|
11262
|
+
}
|
|
11263
|
+
});
|
|
11264
|
+
|
|
11265
|
+
// ../../packages/local-store/dist/report-markdown.js
|
|
11266
|
+
function runVerdict(canonical) {
|
|
11267
|
+
const judged = (canonical?.human_tests ?? []).map((t) => t.verdict).filter((v) => v === "yes" || v === "no" || v === "partial");
|
|
11268
|
+
if (judged.length === 0)
|
|
11269
|
+
return null;
|
|
11270
|
+
if (judged.every((v) => v === "yes"))
|
|
11271
|
+
return "yes";
|
|
11272
|
+
if (judged.every((v) => v === "no"))
|
|
11273
|
+
return "no";
|
|
11274
|
+
return "partial";
|
|
11275
|
+
}
|
|
11276
|
+
function rank(value, order) {
|
|
11277
|
+
const i = order.indexOf(value ?? "");
|
|
11278
|
+
return i === -1 ? order.length : i;
|
|
11279
|
+
}
|
|
11280
|
+
function pagePath(url) {
|
|
11281
|
+
if (!url)
|
|
11282
|
+
return null;
|
|
11283
|
+
try {
|
|
11284
|
+
return new URL(url).pathname;
|
|
11285
|
+
} catch {
|
|
11286
|
+
return url;
|
|
11287
|
+
}
|
|
11288
|
+
}
|
|
11289
|
+
function dayOf(value) {
|
|
11290
|
+
const m = /^(\d{4}-\d{2}-\d{2})/.exec(value ?? "");
|
|
11291
|
+
return m?.[1] ?? null;
|
|
11292
|
+
}
|
|
11293
|
+
function stepNumbers(ids) {
|
|
11294
|
+
const nums = (ids ?? []).map((id) => /\d+/.exec(id)?.[0]).filter((n) => Boolean(n));
|
|
11295
|
+
if (nums.length === 0)
|
|
11296
|
+
return "";
|
|
11297
|
+
return ` (step${nums.length === 1 ? "" : "s"} ${nums.join(", ")})`;
|
|
11298
|
+
}
|
|
11299
|
+
function titleOf(f) {
|
|
11300
|
+
if (f.title)
|
|
11301
|
+
return f.title;
|
|
11302
|
+
const fact = f.ui_fact ?? f.id;
|
|
11303
|
+
return /^(.+?[.!?])(\s|$)/.exec(fact)?.[1] ?? fact;
|
|
11304
|
+
}
|
|
11305
|
+
function renderHumanTest(c, lines) {
|
|
11306
|
+
lines.push("", "## The human test");
|
|
11307
|
+
const verdict = runVerdict(c);
|
|
11308
|
+
const label = verdict ? `**${cap(verdict)}**` : c.blocked ? "_skipped (walk blocked)_" : "_pending judge_";
|
|
11309
|
+
lines.push("", `**${c.human_test_overall.question}** ${label}`);
|
|
11310
|
+
if (c.human_test_overall.answer) {
|
|
11311
|
+
lines.push("", c.human_test_overall.answer);
|
|
11312
|
+
if (c.human_test_overall.reasoning) {
|
|
11313
|
+
lines.push("", c.human_test_overall.reasoning);
|
|
11314
|
+
}
|
|
11315
|
+
}
|
|
11316
|
+
if (c.human_tests.length === 0) {
|
|
11317
|
+
lines.push("", "_No human tests on the outline._");
|
|
11318
|
+
return;
|
|
11319
|
+
}
|
|
11320
|
+
lines.push("");
|
|
11321
|
+
for (const ht of c.human_tests) {
|
|
11322
|
+
const v = ht.verdict ? `**${cap(ht.verdict.replace("_", " "))}.**` : "_Pending._";
|
|
11323
|
+
lines.push(`- ${stripAnswerHint(ht.question)} ${v}${ht.reasoning ? ` ${ht.reasoning}` : ""}`);
|
|
11324
|
+
}
|
|
11325
|
+
}
|
|
11326
|
+
function severityShift(before, now) {
|
|
11327
|
+
if (!before || !now || before === now)
|
|
11328
|
+
return "";
|
|
11329
|
+
const b = rank(before, SEVERITY_ORDER);
|
|
11330
|
+
const n = rank(now, SEVERITY_ORDER);
|
|
11331
|
+
if (b === n)
|
|
11332
|
+
return "";
|
|
11333
|
+
return `${n > b ? "less severe" : "worse"}, ${before} \u2192 ${now}`;
|
|
11334
|
+
}
|
|
11335
|
+
function checkOutcomes(c) {
|
|
11336
|
+
const checks = c.checks;
|
|
11337
|
+
if (!checks)
|
|
11338
|
+
return [];
|
|
11339
|
+
const v = c.verification;
|
|
11340
|
+
const fixed = new Set(v?.resolved.map((r) => r.canonical_id) ?? []);
|
|
11341
|
+
const seen = new Set(c.findings.map((f) => f.canonical_id).filter(Boolean));
|
|
11342
|
+
return checks.fixes.map((f) => ({
|
|
11343
|
+
...f,
|
|
11344
|
+
outcome: f.canonical_id && fixed.has(f.canonical_id) ? "fixed" : f.canonical_id && seen.has(f.canonical_id) ? "came_back" : "unconfirmed"
|
|
11345
|
+
}));
|
|
11346
|
+
}
|
|
11347
|
+
function renderChecks(c, checks, lines) {
|
|
11348
|
+
lines.push("", "## What this run checks");
|
|
11349
|
+
const fixes = checkOutcomes(c);
|
|
11350
|
+
if (fixes.length > 0) {
|
|
11351
|
+
const count = (o) => fixes.filter((f) => f.outcome === o).length;
|
|
11352
|
+
lines.push("", `${fixes.length} fix${fixes.length === 1 ? " was" : "es were"} waiting for a re-check: ${count("fixed")} confirmed fixed, ${count("came_back")} came back, ${count("unconfirmed")} not confirmed either way.`, "");
|
|
11353
|
+
const order = ["came_back", "fixed", "unconfirmed"];
|
|
11354
|
+
const sorted = [...fixes].sort((a, b) => order.indexOf(a.outcome) - order.indexOf(b.outcome));
|
|
11355
|
+
for (const f of sorted) {
|
|
11356
|
+
const where = [
|
|
11357
|
+
pagePath(f.page) ? `\`${pagePath(f.page)}\`` : "",
|
|
11358
|
+
f.reached ? "" : "not reached"
|
|
11359
|
+
].filter(Boolean).join(", ");
|
|
11360
|
+
lines.push(`- ${OUTCOME_TEXT[f.outcome]}: **${f.title}**${f.fix ? `. Fix: ${f.fix}` : ""}${where ? ` (${where})` : ""}`);
|
|
11361
|
+
}
|
|
11362
|
+
}
|
|
11363
|
+
if (checks.directions.length > 0) {
|
|
11364
|
+
lines.push("", `Directions completed since the last run: ${checks.directions.map((d) => `**${d}**`).join(", ")}.`);
|
|
11365
|
+
}
|
|
11366
|
+
}
|
|
11367
|
+
function sinceRow(item, outcome) {
|
|
11368
|
+
const qualifiers = [
|
|
11369
|
+
outcome === "Persists" ? severityShift(item.severity_before, item.severity_now) : "",
|
|
11370
|
+
outcome === "Persists" && item.fixed_before ? "the fix did not hold" : ""
|
|
11371
|
+
].filter(Boolean);
|
|
11372
|
+
const now = outcome === "Resolved" || outcome === "Persists" ? `**${outcome}**${qualifiers.length > 0 ? ` (${qualifiers.join("; ")})` : ""}` : outcome;
|
|
11373
|
+
const note = outcome === "Not seen" ? `The walk did not pass this way${item.fixed_before ? " (fixed, awaiting a re-check)" : ""}. Not evidence of a fix.` : `${item.note ?? ""}${stepNumbers(item.step_ids)}`.trim();
|
|
11374
|
+
return `| ${cell(item.title)} | ${now} | ${cell(note) || "\u2014"} |`;
|
|
11375
|
+
}
|
|
11376
|
+
function renderSince(v, newProblems, newStrengths, lines) {
|
|
11377
|
+
const day = dayOf(v.baseline_run_id);
|
|
11378
|
+
lines.push("", "## Since the last run", "", `Compared with the run of ${day ?? v.baseline_run_id}.`);
|
|
11379
|
+
const rows = [
|
|
11380
|
+
...v.resolved.map((i) => sinceRow(i, "Resolved")),
|
|
11381
|
+
...v.persists.map((i) => sinceRow(i, "Persists")),
|
|
11382
|
+
...v.not_observed.map((i) => sinceRow(i, "Not seen")),
|
|
11383
|
+
...v.uncertain.map((i) => sinceRow(i, "Unclear"))
|
|
11384
|
+
];
|
|
11385
|
+
if (rows.length > 0) {
|
|
11386
|
+
lines.push("", "| Earlier finding | Now | Notes |", "|---|---|---|", ...rows);
|
|
11387
|
+
}
|
|
11388
|
+
const added = [
|
|
11389
|
+
newProblems > 0 ? `${newProblems} problem${newProblems === 1 ? "" : "s"}` : "",
|
|
11390
|
+
newStrengths > 0 ? `${newStrengths} strength${newStrengths === 1 ? "" : "s"}` : ""
|
|
11391
|
+
].filter(Boolean);
|
|
11392
|
+
lines.push("", added.length > 0 ? `New this run: ${added.join(" and ")}, marked _new_ below.` : "Nothing new this run.");
|
|
11393
|
+
}
|
|
11394
|
+
function renderFinding(c, f, n, since, lines) {
|
|
11395
|
+
lines.push("", `### ${n}. ${titleOf(f)}`);
|
|
11396
|
+
const steps = f.linked_step_ids.map((id) => c.steps.find((s) => s.id === id)).filter((s) => Boolean(s));
|
|
11397
|
+
const page = pagePath(f.page_url ?? steps[0]?.url);
|
|
11398
|
+
const where = [
|
|
11399
|
+
page ? `\`${page}\`` : "",
|
|
11400
|
+
steps.length > 0 ? `step ${steps.map((s) => s.step_number).join(", ")}` : ""
|
|
11401
|
+
].filter(Boolean).join(" ");
|
|
11402
|
+
const meta = [
|
|
11403
|
+
f.severity ? `**${cap(f.severity)}**` : "",
|
|
11404
|
+
f.type,
|
|
11405
|
+
f.task_impact ? TASK_IMPACT[f.task_impact] : "",
|
|
11406
|
+
where,
|
|
11407
|
+
since ? `_${since}_` : ""
|
|
11408
|
+
].filter(Boolean);
|
|
11409
|
+
lines.push("", meta.join(" \xB7 "));
|
|
11410
|
+
if (f.ui_fact === null || f.consequence === null) {
|
|
11411
|
+
lines.push("", "> _Legacy finding: predates the UI-fact / consequence depth gate._");
|
|
11412
|
+
}
|
|
11413
|
+
if (f.ui_fact)
|
|
11414
|
+
lines.push("", f.ui_fact);
|
|
11415
|
+
if (f.consequence) {
|
|
11416
|
+
const who = c.scenario.persona_name;
|
|
11417
|
+
lines.push("", `> ${f.consequence}${who ? ` \u2014 ${who}` : ""}`);
|
|
11418
|
+
}
|
|
11419
|
+
if (f.recommendation)
|
|
11420
|
+
lines.push("", `**Recommendation:** ${f.recommendation}`);
|
|
11421
|
+
for (const s of steps) {
|
|
11422
|
+
if (s.screenshot_ref) {
|
|
11423
|
+
lines.push("", ``);
|
|
11424
|
+
}
|
|
11425
|
+
}
|
|
11426
|
+
}
|
|
11427
|
+
function renderAnalysis(analysis, lines) {
|
|
11428
|
+
if (analysis.directions.length > 0) {
|
|
11429
|
+
lines.push("", "## Directions", "", `_Structural changes proposed by ${analysis.advisor} after the walk. Reach is a prediction until the listed scenarios are re-run._`);
|
|
11430
|
+
for (const d of analysis.directions) {
|
|
11431
|
+
lines.push("", `### ${d.title} (${d.size})`, "", d.change);
|
|
11432
|
+
lines.push("", `Absorbs ${d.absorbs.length} finding${d.absorbs.length === 1 ? "" : "s"}.`);
|
|
11433
|
+
for (const r of d.reach.filter((x) => x.effect === "helps"))
|
|
11434
|
+
lines.push(`- Likely helps ${r.scenario}: ${r.reason}`);
|
|
11435
|
+
for (const r of d.reach.filter((x) => x.effect === "risk"))
|
|
11436
|
+
lines.push(`- Could hurt ${r.scenario}: ${r.reason}`);
|
|
11437
|
+
if (d.verify.length > 0)
|
|
11438
|
+
lines.push("", `Re-run to verify: ${d.verify.join(", ")}`);
|
|
11439
|
+
}
|
|
11440
|
+
}
|
|
11441
|
+
if (analysis.observations.length > 0) {
|
|
11442
|
+
lines.push("", "## Also worth knowing", "");
|
|
11443
|
+
for (const o of analysis.observations) {
|
|
11444
|
+
const label = o.kind === "coverage_gap" ? "Not walked" : "Feature risk";
|
|
11445
|
+
lines.push(`- **${label}: ${o.title}.** ${o.detail}`);
|
|
11446
|
+
if (o.suggested_scenario) {
|
|
11447
|
+
lines.push(` Suggested scenario: ${o.suggested_scenario}`);
|
|
11448
|
+
}
|
|
11449
|
+
}
|
|
11450
|
+
}
|
|
11126
11451
|
}
|
|
11127
|
-
|
|
11128
|
-
|
|
11129
|
-
|
|
11130
|
-
|
|
11131
|
-
|
|
11132
|
-
|
|
11133
|
-
|
|
11134
|
-
|
|
11135
|
-
|
|
11136
|
-
|
|
11137
|
-
|
|
11138
|
-
|
|
11139
|
-
|
|
11140
|
-
|
|
11141
|
-
}
|
|
11142
|
-
|
|
11452
|
+
function renderRunDetails(c, lines) {
|
|
11453
|
+
lines.push("", "## Run details", "", `- Run ID: ${c.run_id}`, `- Target URL: ${c.target.url}`);
|
|
11454
|
+
if (c.environment)
|
|
11455
|
+
lines.push(`- Environment: ${c.environment}`);
|
|
11456
|
+
if (c.build) {
|
|
11457
|
+
const commit = c.build.git_sha?.slice(0, 7);
|
|
11458
|
+
const build = [
|
|
11459
|
+
c.build.label,
|
|
11460
|
+
c.build.git_branch,
|
|
11461
|
+
c.build.git_dirty && c.build.worktree_hash ? `${commit ?? "HEAD"} + uncommitted changes (${c.build.worktree_hash.slice(0, 6)}\u2026)` : commit,
|
|
11462
|
+
c.build.ci?.provider
|
|
11463
|
+
].filter(Boolean).join(" \xB7 ");
|
|
11464
|
+
if (build)
|
|
11465
|
+
lines.push(`- Build: ${build}`);
|
|
11466
|
+
}
|
|
11467
|
+
if (c.viewport) {
|
|
11468
|
+
lines.push(`- Viewport: ${c.viewport.device} (${c.viewport.width}\xD7${c.viewport.height})`);
|
|
11469
|
+
}
|
|
11470
|
+
lines.push(`- Started: ${c.provenance.started_at}`, `- Completed: ${c.provenance.completed_at}`, `- Walker prompt: ${c.provenance.walker_prompt.id}@${c.provenance.walker_prompt.version}`);
|
|
11471
|
+
const models = c.models;
|
|
11472
|
+
if (models) {
|
|
11473
|
+
const parts = [
|
|
11474
|
+
models.walker ? `Walker: ${models.walker}${models.walker_reported ? ` (${models.walker_reported})` : ""}` : "",
|
|
11475
|
+
models.judge ? `Judge: ${models.judge}${models.judge_reported ? ` (${models.judge_reported})` : ""}` : "",
|
|
11476
|
+
models.matcher ? `Matcher: ${models.matcher}` : ""
|
|
11477
|
+
].filter(Boolean);
|
|
11478
|
+
if (parts.length > 0)
|
|
11479
|
+
lines.push(`- Models: ${parts.join(" \xB7 ")}`);
|
|
11480
|
+
if (typeof models.velocity_sec_per_step === "number") {
|
|
11481
|
+
lines.push(`- Velocity: ~${models.velocity_sec_per_step}s/step (${models.total_duration_sec}s total)`);
|
|
11482
|
+
}
|
|
11483
|
+
}
|
|
11484
|
+
const diff = c.journey_diff;
|
|
11485
|
+
if (diff)
|
|
11486
|
+
lines.push("", renderJourneyDiffMarkdown(diff));
|
|
11143
11487
|
}
|
|
11144
|
-
function
|
|
11145
|
-
|
|
11146
|
-
|
|
11488
|
+
function renderSteps(c, lines) {
|
|
11489
|
+
lines.push("", "## Steps");
|
|
11490
|
+
if (c.steps.length === 0) {
|
|
11491
|
+
lines.push("", "_No steps recorded._");
|
|
11492
|
+
return;
|
|
11493
|
+
}
|
|
11494
|
+
for (const s of c.steps) {
|
|
11495
|
+
lines.push("", `### ${s.step_number}. ${s.screen_title ?? "(untitled)"} \u2014 ${s.id}`);
|
|
11496
|
+
const where = [s.surface, s.url].filter(Boolean).join(" \u2014 ");
|
|
11497
|
+
if (where)
|
|
11498
|
+
lines.push("", `Page: ${where}`);
|
|
11499
|
+
if (s.persona_reasoning)
|
|
11500
|
+
lines.push("", `> ${s.persona_reasoning.replace(/\n/g, "\n> ")}`);
|
|
11501
|
+
if (s.grounding)
|
|
11502
|
+
lines.push("", `Grounding: ${s.grounding}`);
|
|
11503
|
+
if (s.screenshot_ref)
|
|
11504
|
+
lines.push("", ``);
|
|
11505
|
+
}
|
|
11147
11506
|
}
|
|
11148
|
-
function
|
|
11149
|
-
const
|
|
11150
|
-
const
|
|
11151
|
-
const
|
|
11152
|
-
|
|
11153
|
-
|
|
11154
|
-
|
|
11155
|
-
|
|
11156
|
-
|
|
11157
|
-
|
|
11158
|
-
|
|
11159
|
-
|
|
11160
|
-
|
|
11161
|
-
|
|
11162
|
-
|
|
11163
|
-
|
|
11164
|
-
|
|
11165
|
-
|
|
11166
|
-
|
|
11167
|
-
|
|
11168
|
-
|
|
11169
|
-
|
|
11507
|
+
function renderCanonicalMarkdown(canonical) {
|
|
11508
|
+
const c = canonical;
|
|
11509
|
+
const persona = c.scenario.persona_name ?? c.scenario.persona_slug;
|
|
11510
|
+
const situation = c.scenario.outline_title ?? c.scenario.outline_slug;
|
|
11511
|
+
const heading = persona && situation ? `${persona} \xB7 ${situation}` : c.scenario.slug;
|
|
11512
|
+
const runNumber = c.run_number;
|
|
11513
|
+
const subtitle = [
|
|
11514
|
+
dayOf(c.provenance.completed_at),
|
|
11515
|
+
runNumber ? `run ${runNumber} of this scenario` : "",
|
|
11516
|
+
`${c.steps.length} step${c.steps.length === 1 ? "" : "s"}`,
|
|
11517
|
+
c.target.url
|
|
11518
|
+
].filter(Boolean).join(" \xB7 ");
|
|
11519
|
+
const lines = [
|
|
11520
|
+
"<!-- GENERATED: do not edit by hand. Regen via complete_walkthrough or local-store renderer. -->",
|
|
11521
|
+
"",
|
|
11522
|
+
`# ${heading}`,
|
|
11523
|
+
"",
|
|
11524
|
+
subtitle
|
|
11525
|
+
];
|
|
11526
|
+
if (c.scenario.persona_role) {
|
|
11527
|
+
lines.push("", `${persona}, ${c.scenario.persona_role}.`);
|
|
11528
|
+
}
|
|
11529
|
+
if (c.blocked) {
|
|
11530
|
+
lines.push("", "> **Blocked \u2014 the walk could not start as it was set up.**", `> ${c.blocked.reason}`, ">", "> This run is not compared with earlier runs and proves nothing about earlier findings. Fix the setup (the scenario's starting state or the persona's access) and run it again.");
|
|
11531
|
+
}
|
|
11532
|
+
const checks = c.checks;
|
|
11533
|
+
if (checks)
|
|
11534
|
+
renderChecks(c, checks, lines);
|
|
11535
|
+
renderHumanTest(c, lines);
|
|
11536
|
+
const verification = c.verification;
|
|
11537
|
+
const newIds = new Set(verification?.new_findings.map((n) => n.id) ?? []);
|
|
11538
|
+
const problems = c.findings.filter((f) => f.type !== "helps").sort((a, b) => rank(a.severity, SEVERITY_ORDER) - rank(b.severity, SEVERITY_ORDER) || rank(a.type, TYPE_ORDER) - rank(b.type, TYPE_ORDER));
|
|
11539
|
+
const strengths = c.findings.filter((f) => f.type === "helps");
|
|
11540
|
+
if (verification) {
|
|
11541
|
+
renderSince(verification, problems.filter((f) => newIds.has(f.id)).length, strengths.filter((f) => newIds.has(f.id)).length, lines);
|
|
11542
|
+
}
|
|
11543
|
+
lines.push("", "## Findings");
|
|
11544
|
+
if (problems.length === 0) {
|
|
11545
|
+
lines.push("", "_No problems recorded._");
|
|
11546
|
+
} else {
|
|
11547
|
+
const bySeverity = SEVERITY_ORDER.map((s) => {
|
|
11548
|
+
const n = problems.filter((f) => f.severity === s).length;
|
|
11549
|
+
return n > 0 ? `${n} ${s}` : "";
|
|
11550
|
+
}).filter(Boolean);
|
|
11551
|
+
lines.push("", `${problems.length} problem${problems.length === 1 ? "" : "s"} (${bySeverity.join(", ")}), worst first.`);
|
|
11552
|
+
const persisting = new Set(verification?.persists.map((p) => p.canonical_id) ?? []);
|
|
11553
|
+
problems.forEach((f, i) => {
|
|
11554
|
+
const since = newIds.has(f.id) ? "new" : f.canonical_id && persisting.has(f.canonical_id) ? "persists" : null;
|
|
11555
|
+
renderFinding(c, f, i + 1, since, lines);
|
|
11556
|
+
});
|
|
11557
|
+
}
|
|
11558
|
+
const analysis = c.analysis;
|
|
11559
|
+
if (analysis)
|
|
11560
|
+
renderAnalysis(analysis, lines);
|
|
11561
|
+
const what = c.what_worked ?? [];
|
|
11562
|
+
if (strengths.length > 0 || what.length > 0) {
|
|
11563
|
+
lines.push("", "## What worked", "");
|
|
11564
|
+
for (const f of strengths) {
|
|
11565
|
+
lines.push(`- **${titleOf(f)}.** ${f.ui_fact ?? ""}`.trimEnd());
|
|
11170
11566
|
}
|
|
11567
|
+
for (const w of what)
|
|
11568
|
+
lines.push(`- ${w}`);
|
|
11171
11569
|
}
|
|
11172
|
-
const
|
|
11173
|
-
|
|
11174
|
-
|
|
11175
|
-
|
|
11176
|
-
|
|
11177
|
-
|
|
11178
|
-
|
|
11179
|
-
|
|
11180
|
-
|
|
11181
|
-
|
|
11570
|
+
const open2 = c.open_questions ?? [];
|
|
11571
|
+
if (open2.length > 0) {
|
|
11572
|
+
lines.push("", "## Open questions", "");
|
|
11573
|
+
for (const q of open2)
|
|
11574
|
+
lines.push(`- ${q}`);
|
|
11575
|
+
}
|
|
11576
|
+
const setup = c.setup_notes ?? [];
|
|
11577
|
+
if (setup.length > 0) {
|
|
11578
|
+
lines.push("", "## Test setup notes", "", "_Doubts about the test, not the product: for whoever stages the scenario._", "");
|
|
11579
|
+
for (const n of setup)
|
|
11580
|
+
lines.push(`- ${n}`);
|
|
11581
|
+
}
|
|
11582
|
+
renderSteps(c, lines);
|
|
11583
|
+
renderRunDetails(c, lines);
|
|
11584
|
+
return `${lines.join("\n")}
|
|
11585
|
+
`;
|
|
11182
11586
|
}
|
|
11183
|
-
var
|
|
11184
|
-
var
|
|
11185
|
-
"../../packages/local-store/dist/
|
|
11587
|
+
var SEVERITY_ORDER, TYPE_ORDER, TASK_IMPACT, cap, stripAnswerHint, cell, OUTCOME_TEXT;
|
|
11588
|
+
var init_report_markdown = __esm({
|
|
11589
|
+
"../../packages/local-store/dist/report-markdown.js"() {
|
|
11186
11590
|
"use strict";
|
|
11187
|
-
|
|
11188
|
-
|
|
11189
|
-
|
|
11190
|
-
|
|
11191
|
-
|
|
11192
|
-
|
|
11193
|
-
|
|
11194
|
-
|
|
11195
|
-
|
|
11196
|
-
|
|
11591
|
+
init_journey_diff();
|
|
11592
|
+
SEVERITY_ORDER = ["critical", "major", "minor", "observation"];
|
|
11593
|
+
TYPE_ORDER = ["incorrect", "hinders", "missing", "irrelevant", "helps"];
|
|
11594
|
+
TASK_IMPACT = {
|
|
11595
|
+
blocks: "blocks the task",
|
|
11596
|
+
impairs: "impairs the task",
|
|
11597
|
+
slows: "slows the task"
|
|
11598
|
+
};
|
|
11599
|
+
cap = (s) => s.charAt(0).toUpperCase() + s.slice(1);
|
|
11600
|
+
stripAnswerHint = (q) => q.replace(/\s*\((?:yes|no|partial)(?:\s*\/\s*(?:yes|no|partial))+\)\s*$/i, "");
|
|
11601
|
+
cell = (s) => s.replace(/\|/g, "\\|").replace(/\n+/g, " ");
|
|
11602
|
+
OUTCOME_TEXT = {
|
|
11603
|
+
fixed: "**confirmed fixed**",
|
|
11604
|
+
came_back: "**came back**",
|
|
11605
|
+
unconfirmed: "not confirmed"
|
|
11606
|
+
};
|
|
11197
11607
|
}
|
|
11198
11608
|
});
|
|
11199
11609
|
|
|
11200
11610
|
// ../../packages/local-store/dist/human-test-link.js
|
|
11201
|
-
import { promises as
|
|
11202
|
-
import
|
|
11611
|
+
import { promises as fs22 } from "fs";
|
|
11612
|
+
import path17 from "path";
|
|
11203
11613
|
function humanTestLinks(verdicts) {
|
|
11204
11614
|
const best = /* @__PURE__ */ new Map();
|
|
11205
11615
|
for (const v of verdicts) {
|
|
11206
|
-
const
|
|
11207
|
-
if (
|
|
11616
|
+
const rank2 = VERDICT_RANK[v.verdict];
|
|
11617
|
+
if (rank2 === void 0)
|
|
11208
11618
|
continue;
|
|
11209
11619
|
for (const findingId of v.grounding_finding_ids) {
|
|
11210
11620
|
const seen = best.get(findingId);
|
|
11211
|
-
if (!seen ||
|
|
11212
|
-
best.set(findingId, { test: v.human_test_id, rank });
|
|
11621
|
+
if (!seen || rank2 < seen.rank) {
|
|
11622
|
+
best.set(findingId, { test: v.human_test_id, rank: rank2 });
|
|
11213
11623
|
}
|
|
11214
11624
|
}
|
|
11215
11625
|
}
|
|
@@ -11222,7 +11632,7 @@ async function linkFindingsToHumanTests(projectRoot, runId, verdicts) {
|
|
|
11222
11632
|
const dir = entityDirPath(projectRoot, "finding");
|
|
11223
11633
|
let names2;
|
|
11224
11634
|
try {
|
|
11225
|
-
names2 = await
|
|
11635
|
+
names2 = await fs22.readdir(dir);
|
|
11226
11636
|
} catch {
|
|
11227
11637
|
return [];
|
|
11228
11638
|
}
|
|
@@ -11230,11 +11640,11 @@ async function linkFindingsToHumanTests(projectRoot, runId, verdicts) {
|
|
|
11230
11640
|
for (const name of names2) {
|
|
11231
11641
|
if (!name.endsWith(".md") || name.startsWith("."))
|
|
11232
11642
|
continue;
|
|
11233
|
-
const file =
|
|
11643
|
+
const file = path17.join(dir, name);
|
|
11234
11644
|
let raw;
|
|
11235
11645
|
let fm;
|
|
11236
11646
|
try {
|
|
11237
|
-
raw = await
|
|
11647
|
+
raw = await fs22.readFile(file, "utf8");
|
|
11238
11648
|
fm = parseFrontmatter(raw).frontmatter;
|
|
11239
11649
|
} catch {
|
|
11240
11650
|
continue;
|
|
@@ -11465,14 +11875,14 @@ var init_cloud_client = __esm({
|
|
|
11465
11875
|
});
|
|
11466
11876
|
|
|
11467
11877
|
// ../../packages/local-store/dist/last-pull.js
|
|
11468
|
-
import { promises as
|
|
11469
|
-
import
|
|
11878
|
+
import { promises as fs23 } from "fs";
|
|
11879
|
+
import path18 from "path";
|
|
11470
11880
|
function lastPullPath(projectRoot) {
|
|
11471
|
-
return
|
|
11881
|
+
return path18.join(cacheDir(projectRoot), "last-pull.json");
|
|
11472
11882
|
}
|
|
11473
11883
|
async function readLastPull(projectRoot) {
|
|
11474
11884
|
try {
|
|
11475
|
-
const raw = await
|
|
11885
|
+
const raw = await fs23.readFile(lastPullPath(projectRoot), "utf8");
|
|
11476
11886
|
const parsed = JSON.parse(raw);
|
|
11477
11887
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
11478
11888
|
return parsed;
|
|
@@ -11578,7 +11988,7 @@ var init_types2 = __esm({
|
|
|
11578
11988
|
});
|
|
11579
11989
|
|
|
11580
11990
|
// ../../packages/local-store/dist/pull/frontmatter.js
|
|
11581
|
-
import { randomUUID as
|
|
11991
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
11582
11992
|
function resolveBody(row, fallback) {
|
|
11583
11993
|
if (typeof row.bodyMarkdown === "string") {
|
|
11584
11994
|
return { text: row.bodyMarkdown, fromCloud: true };
|
|
@@ -11620,7 +12030,7 @@ function buildOutlineFrontmatter(row, slug, capabilityIdToSlug) {
|
|
|
11620
12030
|
missingCaps.push(capId);
|
|
11621
12031
|
}
|
|
11622
12032
|
const humanTests = (row.humanTests ?? []).map((h) => ({
|
|
11623
|
-
id: h.id ??
|
|
12033
|
+
id: h.id ?? randomUUID7().slice(0, 8),
|
|
11624
12034
|
question: h.question
|
|
11625
12035
|
}));
|
|
11626
12036
|
const frontmatter = {
|
|
@@ -11787,7 +12197,7 @@ var init_frontmatter2 = __esm({
|
|
|
11787
12197
|
});
|
|
11788
12198
|
|
|
11789
12199
|
// ../../packages/local-store/dist/pull/write.js
|
|
11790
|
-
import { promises as
|
|
12200
|
+
import { promises as fs24 } from "fs";
|
|
11791
12201
|
async function loadLocalIndex(projectRoot, type2) {
|
|
11792
12202
|
const byId = /* @__PURE__ */ new Map();
|
|
11793
12203
|
const bySlug = /* @__PURE__ */ new Map();
|
|
@@ -11818,7 +12228,7 @@ async function classifyExisting(projectRoot, type2, cloudId, localSlug, targetSl
|
|
|
11818
12228
|
if (localSlug !== targetSlug)
|
|
11819
12229
|
return "untracked";
|
|
11820
12230
|
const lastSynced = localIndex.lastPushById[cloudId];
|
|
11821
|
-
const bytes = await
|
|
12231
|
+
const bytes = await fs24.readFile(entityFilePath(projectRoot, type2, localSlug), "utf8");
|
|
11822
12232
|
return lastSynced && hashNormalizedContent(bytes) === lastSynced ? "fast_forward" : "diverged";
|
|
11823
12233
|
}
|
|
11824
12234
|
async function writeOrSkip(projectRoot, type2, cloudId, slug, frontmatter, body, localIndex, mode, cloudVersion) {
|
|
@@ -11879,13 +12289,13 @@ async function writeOrSkip(projectRoot, type2, cloudId, slug, frontmatter, body,
|
|
|
11879
12289
|
}
|
|
11880
12290
|
if (existingSlugForId && existingSlugForId !== slug) {
|
|
11881
12291
|
try {
|
|
11882
|
-
await
|
|
12292
|
+
await fs24.unlink(entityFilePath(projectRoot, type2, existingSlugForId));
|
|
11883
12293
|
localIndex.bySlug.delete(existingSlugForId);
|
|
11884
12294
|
} catch {
|
|
11885
12295
|
}
|
|
11886
12296
|
}
|
|
11887
12297
|
await writeEntity(projectRoot, type2, frontmatter, text);
|
|
11888
|
-
const bytes = await
|
|
12298
|
+
const bytes = await fs24.readFile(entityFilePath(projectRoot, type2, slug), "utf8");
|
|
11889
12299
|
const hash = hashNormalizedContent(bytes);
|
|
11890
12300
|
localIndex.byId.set(cloudId, slug);
|
|
11891
12301
|
localIndex.bySlug.set(slug, cloudId);
|
|
@@ -11939,7 +12349,7 @@ var init_write = __esm({
|
|
|
11939
12349
|
});
|
|
11940
12350
|
|
|
11941
12351
|
// ../../packages/local-store/dist/pull/entities.js
|
|
11942
|
-
import { promises as
|
|
12352
|
+
import { promises as fs25 } from "fs";
|
|
11943
12353
|
function buildContextFrontmatter(payload) {
|
|
11944
12354
|
const cloudCtx = payload.context ?? {};
|
|
11945
12355
|
const glossary = (payload.glossary ?? []).map((g) => ({
|
|
@@ -11994,7 +12404,7 @@ async function pullContext(projectRoot, cloud, projectId, mode, result) {
|
|
|
11994
12404
|
const frontmatter = buildContextFrontmatter(payload);
|
|
11995
12405
|
let hasLocal = false;
|
|
11996
12406
|
try {
|
|
11997
|
-
await
|
|
12407
|
+
await fs25.access(contextPath(projectRoot));
|
|
11998
12408
|
hasLocal = true;
|
|
11999
12409
|
} catch {
|
|
12000
12410
|
hasLocal = false;
|
|
@@ -12024,7 +12434,7 @@ async function pullContext(projectRoot, cloud, projectId, mode, result) {
|
|
|
12024
12434
|
frontmatter,
|
|
12025
12435
|
notes_body: existingNotes
|
|
12026
12436
|
});
|
|
12027
|
-
const bytes = await
|
|
12437
|
+
const bytes = await fs25.readFile(contextPath(projectRoot), "utf8");
|
|
12028
12438
|
const hash = hashNormalizedContent(bytes);
|
|
12029
12439
|
await recordLastPull(projectRoot, "context:root", hash);
|
|
12030
12440
|
await recordLastPush(projectRoot, "context:root", hash);
|
|
@@ -12539,14 +12949,14 @@ var init_repair = __esm({
|
|
|
12539
12949
|
});
|
|
12540
12950
|
|
|
12541
12951
|
// ../../packages/local-store/dist/session-state.js
|
|
12542
|
-
import { randomUUID as
|
|
12952
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
12543
12953
|
import { closeSync as closeSync2, existsSync as existsSync2, fsyncSync as fsyncSync2, mkdirSync as mkdirSync2, openSync as openSync2, readFileSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
12544
12954
|
import os2 from "os";
|
|
12545
|
-
import
|
|
12955
|
+
import path19 from "path";
|
|
12546
12956
|
function quarantineCorruptedSessionState(file, raw) {
|
|
12547
12957
|
try {
|
|
12548
|
-
const dir =
|
|
12549
|
-
const quarantine =
|
|
12958
|
+
const dir = path19.dirname(file);
|
|
12959
|
+
const quarantine = path19.join(dir, `session-state.corrupted.${Date.now()}.json`);
|
|
12550
12960
|
atomicWriteFileSync(quarantine, raw);
|
|
12551
12961
|
} catch {
|
|
12552
12962
|
}
|
|
@@ -12555,8 +12965,8 @@ function withSessionStateLock(filePath, fn) {
|
|
|
12555
12965
|
const lockPath = `${filePath}.lock`;
|
|
12556
12966
|
const deadline = Date.now() + 5e3;
|
|
12557
12967
|
const staleMs = 15e3;
|
|
12558
|
-
const token =
|
|
12559
|
-
mkdirSync2(
|
|
12968
|
+
const token = randomUUID8();
|
|
12969
|
+
mkdirSync2(path19.dirname(lockPath), { recursive: true });
|
|
12560
12970
|
const tryCreate = () => {
|
|
12561
12971
|
try {
|
|
12562
12972
|
const fd = openSync2(lockPath, "wx", 420);
|
|
@@ -12630,242 +13040,67 @@ function readSessionState(projectRoot) {
|
|
|
12630
13040
|
const file = sessionStatePath(projectRoot);
|
|
12631
13041
|
if (!existsSync2(file))
|
|
12632
13042
|
return null;
|
|
12633
|
-
try {
|
|
12634
|
-
const raw = readFileSync(file, "utf-8");
|
|
12635
|
-
if (!raw.trim())
|
|
12636
|
-
return null;
|
|
12637
|
-
const parsed = JSON.parse(raw);
|
|
12638
|
-
if (parsed && typeof parsed === "object" && parsed.version === 1) {
|
|
12639
|
-
return parsed;
|
|
12640
|
-
}
|
|
12641
|
-
quarantineCorruptedSessionState(file, raw);
|
|
12642
|
-
return null;
|
|
12643
|
-
} catch {
|
|
12644
|
-
try {
|
|
12645
|
-
const raw = readFileSync(file, "utf-8");
|
|
12646
|
-
if (raw.trim()) {
|
|
12647
|
-
quarantineCorruptedSessionState(file, raw);
|
|
12648
|
-
}
|
|
12649
|
-
} catch {
|
|
12650
|
-
}
|
|
12651
|
-
return null;
|
|
12652
|
-
}
|
|
12653
|
-
}
|
|
12654
|
-
function updateSessionState(projectRoot, patch) {
|
|
12655
|
-
const file = sessionStatePath(projectRoot);
|
|
12656
|
-
const cacheDir2 = path18.dirname(file);
|
|
12657
|
-
if (!existsSync2(cacheDir2)) {
|
|
12658
|
-
mkdirSync2(cacheDir2, { recursive: true });
|
|
12659
|
-
}
|
|
12660
|
-
return withSessionStateLock(file, () => {
|
|
12661
|
-
const existing = readSessionState(projectRoot);
|
|
12662
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
12663
|
-
const cleanRunPatch = patch.active_run ? Object.fromEntries(Object.entries(patch.active_run).filter(([_, v]) => v !== void 0)) : void 0;
|
|
12664
|
-
const cleanFindingPatch = patch.active_finding ? Object.fromEntries(Object.entries(patch.active_finding).filter(([_, v]) => v !== void 0)) : void 0;
|
|
12665
|
-
const next = {
|
|
12666
|
-
version: 1,
|
|
12667
|
-
updated_at: now,
|
|
12668
|
-
active_scenario: patch.active_scenario ? { slug: patch.active_scenario.slug, updated_at: now } : existing?.active_scenario,
|
|
12669
|
-
active_run: patch.active_run ? {
|
|
12670
|
-
id: patch.active_run.id ?? existing?.active_run?.id ?? "",
|
|
12671
|
-
scenario_slug: patch.active_run.scenario_slug ?? existing?.active_run?.scenario_slug ?? "",
|
|
12672
|
-
status: patch.active_run.status ?? existing?.active_run?.status ?? "running",
|
|
12673
|
-
...existing?.active_run ?? {},
|
|
12674
|
-
...cleanRunPatch,
|
|
12675
|
-
updated_at: now
|
|
12676
|
-
} : existing?.active_run,
|
|
12677
|
-
active_finding: patch.active_finding ? {
|
|
12678
|
-
slug: patch.active_finding.slug,
|
|
12679
|
-
...existing?.active_finding ?? {},
|
|
12680
|
-
...cleanFindingPatch,
|
|
12681
|
-
updated_at: now
|
|
12682
|
-
} : existing?.active_finding,
|
|
12683
|
-
active_persona: patch.active_persona ? { slug: patch.active_persona.slug, updated_at: now } : existing?.active_persona
|
|
12684
|
-
};
|
|
12685
|
-
atomicWriteFileSync(file, JSON.stringify(next, null, 2) + "\n");
|
|
12686
|
-
return next;
|
|
12687
|
-
});
|
|
12688
|
-
}
|
|
12689
|
-
var init_session_state = __esm({
|
|
12690
|
-
"../../packages/local-store/dist/session-state.js"() {
|
|
12691
|
-
"use strict";
|
|
12692
|
-
init_atomic();
|
|
12693
|
-
init_paths();
|
|
12694
|
-
}
|
|
12695
|
-
});
|
|
12696
|
-
|
|
12697
|
-
// ../../packages/local-store/dist/directions.js
|
|
12698
|
-
import { randomUUID as randomUUID8 } from "crypto";
|
|
12699
|
-
import { promises as fs25 } from "fs";
|
|
12700
|
-
import path19 from "path";
|
|
12701
|
-
import { z as z26 } from "zod";
|
|
12702
|
-
function directionsDir(projectRoot) {
|
|
12703
|
-
return path19.join(iamuDir(projectRoot), DIRECTIONS_DIR);
|
|
12704
|
-
}
|
|
12705
|
-
function directionPath(projectRoot, slug) {
|
|
12706
|
-
return path19.join(directionsDir(projectRoot), `${slug}.md`);
|
|
12707
|
-
}
|
|
12708
|
-
function renderBody(texts) {
|
|
12709
|
-
return [
|
|
12710
|
-
"## Problem",
|
|
12711
|
-
"",
|
|
12712
|
-
texts.problem.trim(),
|
|
12713
|
-
"",
|
|
12714
|
-
"## Change",
|
|
12715
|
-
"",
|
|
12716
|
-
texts.change.trim(),
|
|
12717
|
-
"",
|
|
12718
|
-
"## Leading practice",
|
|
12719
|
-
"",
|
|
12720
|
-
texts.leading_practice.trim(),
|
|
12721
|
-
""
|
|
12722
|
-
].join("\n");
|
|
12723
|
-
}
|
|
12724
|
-
function section(body, heading) {
|
|
12725
|
-
const re = new RegExp(`^## ${heading}\\s*\\n([\\s\\S]*?)(?=^## |$(?![\\s\\S]))`, "m");
|
|
12726
|
-
return re.exec(body)?.[1]?.trim() ?? "";
|
|
12727
|
-
}
|
|
12728
|
-
function parseDirection(raw, file) {
|
|
12729
|
-
const { frontmatter, body } = parseFrontmatter(raw);
|
|
12730
|
-
return {
|
|
12731
|
-
frontmatter: directionFrontmatterSchema.parse(frontmatter),
|
|
12732
|
-
texts: {
|
|
12733
|
-
problem: section(body, "Problem"),
|
|
12734
|
-
change: section(body, "Change"),
|
|
12735
|
-
leading_practice: section(body, "Leading practice")
|
|
12736
|
-
},
|
|
12737
|
-
path: file
|
|
12738
|
-
};
|
|
12739
|
-
}
|
|
12740
|
-
async function readDirection(projectRoot, slug) {
|
|
12741
|
-
const file = directionPath(projectRoot, slug);
|
|
12742
|
-
let raw;
|
|
12743
|
-
try {
|
|
12744
|
-
raw = await fs25.readFile(file, "utf8");
|
|
12745
|
-
} catch (err) {
|
|
12746
|
-
if (err.code === "ENOENT")
|
|
12747
|
-
return null;
|
|
12748
|
-
throw err;
|
|
12749
|
-
}
|
|
12750
|
-
return parseDirection(raw, file);
|
|
12751
|
-
}
|
|
12752
|
-
async function listDirections(projectRoot) {
|
|
12753
|
-
let names2;
|
|
12754
|
-
try {
|
|
12755
|
-
names2 = await fs25.readdir(directionsDir(projectRoot));
|
|
12756
|
-
} catch (err) {
|
|
12757
|
-
if (err.code === "ENOENT")
|
|
12758
|
-
return [];
|
|
12759
|
-
throw err;
|
|
12760
|
-
}
|
|
12761
|
-
const out = [];
|
|
12762
|
-
for (const name of names2.sort()) {
|
|
12763
|
-
if (!name.endsWith(".md"))
|
|
12764
|
-
continue;
|
|
12765
|
-
const file = path19.join(directionsDir(projectRoot), name);
|
|
13043
|
+
try {
|
|
13044
|
+
const raw = readFileSync(file, "utf-8");
|
|
13045
|
+
if (!raw.trim())
|
|
13046
|
+
return null;
|
|
13047
|
+
const parsed = JSON.parse(raw);
|
|
13048
|
+
if (parsed && typeof parsed === "object" && parsed.version === 1) {
|
|
13049
|
+
return parsed;
|
|
13050
|
+
}
|
|
13051
|
+
quarantineCorruptedSessionState(file, raw);
|
|
13052
|
+
return null;
|
|
13053
|
+
} catch {
|
|
12766
13054
|
try {
|
|
12767
|
-
|
|
13055
|
+
const raw = readFileSync(file, "utf-8");
|
|
13056
|
+
if (raw.trim()) {
|
|
13057
|
+
quarantineCorruptedSessionState(file, raw);
|
|
13058
|
+
}
|
|
12768
13059
|
} catch {
|
|
12769
13060
|
}
|
|
13061
|
+
return null;
|
|
12770
13062
|
}
|
|
12771
|
-
return out.sort((a, b) => b.frontmatter.updated_at.localeCompare(a.frontmatter.updated_at));
|
|
12772
|
-
}
|
|
12773
|
-
async function writeDirection(projectRoot, fm, texts) {
|
|
12774
|
-
const file = directionPath(projectRoot, fm.slug);
|
|
12775
|
-
const validated = directionFrontmatterSchema.parse(fm);
|
|
12776
|
-
await atomicWriteFile(file, serializeFrontmatter(validated, renderBody(texts)));
|
|
12777
|
-
return file;
|
|
12778
13063
|
}
|
|
12779
|
-
|
|
12780
|
-
const
|
|
12781
|
-
const
|
|
12782
|
-
|
|
12783
|
-
|
|
12784
|
-
leading_practice: proposal.leading_practice
|
|
12785
|
-
};
|
|
12786
|
-
if (existing?.frontmatter.status === "rejected") {
|
|
12787
|
-
return { outcome: "previously_rejected" };
|
|
13064
|
+
function updateSessionState(projectRoot, patch) {
|
|
13065
|
+
const file = sessionStatePath(projectRoot);
|
|
13066
|
+
const cacheDir2 = path19.dirname(file);
|
|
13067
|
+
if (!existsSync2(cacheDir2)) {
|
|
13068
|
+
mkdirSync2(cacheDir2, { recursive: true });
|
|
12788
13069
|
}
|
|
12789
|
-
|
|
12790
|
-
|
|
12791
|
-
|
|
12792
|
-
|
|
12793
|
-
|
|
12794
|
-
|
|
12795
|
-
|
|
12796
|
-
|
|
12797
|
-
|
|
12798
|
-
|
|
12799
|
-
|
|
12800
|
-
|
|
12801
|
-
|
|
12802
|
-
|
|
12803
|
-
|
|
12804
|
-
|
|
12805
|
-
|
|
12806
|
-
|
|
12807
|
-
|
|
12808
|
-
|
|
12809
|
-
|
|
12810
|
-
|
|
12811
|
-
|
|
12812
|
-
|
|
12813
|
-
|
|
12814
|
-
|
|
12815
|
-
|
|
12816
|
-
|
|
12817
|
-
return { ...existing, frontmatter: fm };
|
|
13070
|
+
return withSessionStateLock(file, () => {
|
|
13071
|
+
const existing = readSessionState(projectRoot);
|
|
13072
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
13073
|
+
const cleanRunPatch = patch.active_run ? Object.fromEntries(Object.entries(patch.active_run).filter(([_, v]) => v !== void 0)) : void 0;
|
|
13074
|
+
const cleanFindingPatch = patch.active_finding ? Object.fromEntries(Object.entries(patch.active_finding).filter(([_, v]) => v !== void 0)) : void 0;
|
|
13075
|
+
const next = {
|
|
13076
|
+
version: 1,
|
|
13077
|
+
updated_at: now,
|
|
13078
|
+
active_scenario: patch.active_scenario ? { slug: patch.active_scenario.slug, updated_at: now } : existing?.active_scenario,
|
|
13079
|
+
active_run: patch.active_run ? {
|
|
13080
|
+
id: patch.active_run.id ?? existing?.active_run?.id ?? "",
|
|
13081
|
+
scenario_slug: patch.active_run.scenario_slug ?? existing?.active_run?.scenario_slug ?? "",
|
|
13082
|
+
status: patch.active_run.status ?? existing?.active_run?.status ?? "running",
|
|
13083
|
+
...existing?.active_run ?? {},
|
|
13084
|
+
...cleanRunPatch,
|
|
13085
|
+
updated_at: now
|
|
13086
|
+
} : existing?.active_run,
|
|
13087
|
+
active_finding: patch.active_finding ? {
|
|
13088
|
+
slug: patch.active_finding.slug,
|
|
13089
|
+
...existing?.active_finding ?? {},
|
|
13090
|
+
...cleanFindingPatch,
|
|
13091
|
+
updated_at: now
|
|
13092
|
+
} : existing?.active_finding,
|
|
13093
|
+
active_persona: patch.active_persona ? { slug: patch.active_persona.slug, updated_at: now } : existing?.active_persona
|
|
13094
|
+
};
|
|
13095
|
+
atomicWriteFileSync(file, JSON.stringify(next, null, 2) + "\n");
|
|
13096
|
+
return next;
|
|
13097
|
+
});
|
|
12818
13098
|
}
|
|
12819
|
-
var
|
|
12820
|
-
|
|
12821
|
-
"../../packages/local-store/dist/directions.js"() {
|
|
13099
|
+
var init_session_state = __esm({
|
|
13100
|
+
"../../packages/local-store/dist/session-state.js"() {
|
|
12822
13101
|
"use strict";
|
|
12823
13102
|
init_atomic();
|
|
12824
|
-
init_markdown();
|
|
12825
13103
|
init_paths();
|
|
12826
|
-
DIRECTIONS_DIR = "directions";
|
|
12827
|
-
DIRECTION_STATUSES = [
|
|
12828
|
-
"proposed",
|
|
12829
|
-
"accepted",
|
|
12830
|
-
"done",
|
|
12831
|
-
"rejected"
|
|
12832
|
-
];
|
|
12833
|
-
directionStatusSchema = z26.enum(DIRECTION_STATUSES);
|
|
12834
|
-
directionSizeSchema = z26.enum(["small", "medium", "large"]);
|
|
12835
|
-
directionReachSchema = z26.object({
|
|
12836
|
-
scenario: z26.string().min(1),
|
|
12837
|
-
effect: z26.enum(["helps", "risk"]),
|
|
12838
|
-
reason: z26.string().min(1)
|
|
12839
|
-
});
|
|
12840
|
-
codeRefSchema = z26.object({
|
|
12841
|
-
path: z26.string().min(1),
|
|
12842
|
-
symbol: z26.string().min(1)
|
|
12843
|
-
});
|
|
12844
|
-
directionFrontmatterSchema = z26.object({
|
|
12845
|
-
id: z26.string().uuid(),
|
|
12846
|
-
slug: z26.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
|
|
12847
|
-
title: z26.string().min(1),
|
|
12848
|
-
status: directionStatusSchema.default("proposed"),
|
|
12849
|
-
size: directionSizeSchema,
|
|
12850
|
-
/** Finding ids this direction would resolve. */
|
|
12851
|
-
absorbs: z26.array(z26.string().min(1)).default([]),
|
|
12852
|
-
/** Predicted effect on other scenarios — never evidence. */
|
|
12853
|
-
reach: z26.array(directionReachSchema).default([]),
|
|
12854
|
-
/** Scenarios to re-run once the direction lands. */
|
|
12855
|
-
verify: z26.array(z26.string().min(1)).default([]),
|
|
12856
|
-
builds_on: z26.array(codeRefSchema).default([]),
|
|
12857
|
-
/** Runs (or `project:<timestamp>`) whose analysis proposed it. */
|
|
12858
|
-
seen_in_runs: z26.array(z26.string().min(1)).default([]),
|
|
12859
|
-
advised_by: z26.string().regex(/^advisor@\S+$/),
|
|
12860
|
-
created_at: z26.string(),
|
|
12861
|
-
updated_at: z26.string()
|
|
12862
|
-
});
|
|
12863
|
-
union = (a, b, key) => {
|
|
12864
|
-
const seen = /* @__PURE__ */ new Map();
|
|
12865
|
-
for (const x of [...a, ...b])
|
|
12866
|
-
seen.set(key(x), x);
|
|
12867
|
-
return [...seen.values()];
|
|
12868
|
-
};
|
|
12869
13104
|
}
|
|
12870
13105
|
});
|
|
12871
13106
|
|
|
@@ -12994,7 +13229,7 @@ async function readContextSummary(projectRoot) {
|
|
|
12994
13229
|
return null;
|
|
12995
13230
|
}
|
|
12996
13231
|
}
|
|
12997
|
-
function
|
|
13232
|
+
function pagePath2(url) {
|
|
12998
13233
|
try {
|
|
12999
13234
|
return new URL(url).pathname || "/";
|
|
13000
13235
|
} catch {
|
|
@@ -13018,7 +13253,7 @@ async function walkedCoverage(projectRoot) {
|
|
|
13018
13253
|
const entry = out.get(slug) ?? { paths: /* @__PURE__ */ new Set(), runs: 0 };
|
|
13019
13254
|
entry.runs++;
|
|
13020
13255
|
for (const step of await readStepsForRun(projectRoot, name)) {
|
|
13021
|
-
const p = typeof step.url === "string" ?
|
|
13256
|
+
const p = typeof step.url === "string" ? pagePath2(step.url) : null;
|
|
13022
13257
|
if (p)
|
|
13023
13258
|
entry.paths.add(p);
|
|
13024
13259
|
}
|
|
@@ -13673,6 +13908,7 @@ var init_dist2 = __esm({
|
|
|
13673
13908
|
init_revisit();
|
|
13674
13909
|
init_explore_map();
|
|
13675
13910
|
init_walkthrough_canonical2();
|
|
13911
|
+
init_report_markdown();
|
|
13676
13912
|
init_journey_diff();
|
|
13677
13913
|
init_finding_match();
|
|
13678
13914
|
init_guidance2();
|
|
@@ -13707,15 +13943,15 @@ function resolveApiUrl(configured) {
|
|
|
13707
13943
|
assertSecureApiUrl(url, configured ? "API URL" : "IAMU_API_URL");
|
|
13708
13944
|
return url;
|
|
13709
13945
|
}
|
|
13710
|
-
function parseStore(raw,
|
|
13946
|
+
function parseStore(raw, path44) {
|
|
13711
13947
|
let parsed;
|
|
13712
13948
|
try {
|
|
13713
13949
|
parsed = JSON.parse(raw);
|
|
13714
13950
|
} catch (err) {
|
|
13715
|
-
throw new CredentialsFileError(
|
|
13951
|
+
throw new CredentialsFileError(path44, "not valid JSON", err);
|
|
13716
13952
|
}
|
|
13717
13953
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
13718
|
-
throw new CredentialsFileError(
|
|
13954
|
+
throw new CredentialsFileError(path44, "expected a JSON object");
|
|
13719
13955
|
}
|
|
13720
13956
|
return parsed;
|
|
13721
13957
|
}
|
|
@@ -13723,26 +13959,26 @@ function isNotFound(err) {
|
|
|
13723
13959
|
return typeof err === "object" && err !== null && err.code === "ENOENT";
|
|
13724
13960
|
}
|
|
13725
13961
|
async function readCredentialsStore() {
|
|
13726
|
-
const
|
|
13962
|
+
const path44 = getCredentialsPath();
|
|
13727
13963
|
let raw;
|
|
13728
13964
|
try {
|
|
13729
|
-
raw = await readFile(
|
|
13965
|
+
raw = await readFile(path44, "utf-8");
|
|
13730
13966
|
} catch (err) {
|
|
13731
13967
|
if (isNotFound(err)) return {};
|
|
13732
13968
|
warnUnreadableStoreOnce(err);
|
|
13733
13969
|
throw err;
|
|
13734
13970
|
}
|
|
13735
13971
|
try {
|
|
13736
|
-
return parseStore(raw,
|
|
13972
|
+
return parseStore(raw, path44);
|
|
13737
13973
|
} catch (err) {
|
|
13738
13974
|
warnUnreadableStoreOnce(err);
|
|
13739
13975
|
throw err;
|
|
13740
13976
|
}
|
|
13741
13977
|
}
|
|
13742
13978
|
async function writeCredentialsStore(store) {
|
|
13743
|
-
const
|
|
13744
|
-
await mkdir(dirname(
|
|
13745
|
-
await atomicWriteFile(
|
|
13979
|
+
const path44 = getCredentialsPath();
|
|
13980
|
+
await mkdir(dirname(path44), { recursive: true, mode: 448 });
|
|
13981
|
+
await atomicWriteFile(path44, `${JSON.stringify(store, null, 2)}
|
|
13746
13982
|
`, {
|
|
13747
13983
|
mode: 384
|
|
13748
13984
|
});
|
|
@@ -13762,16 +13998,16 @@ function resolveApiKey(apiUrl) {
|
|
|
13762
13998
|
} catch {
|
|
13763
13999
|
return null;
|
|
13764
14000
|
}
|
|
13765
|
-
const
|
|
14001
|
+
const path44 = getCredentialsPath();
|
|
13766
14002
|
let raw;
|
|
13767
14003
|
try {
|
|
13768
|
-
raw = readFileSync2(
|
|
14004
|
+
raw = readFileSync2(path44, "utf-8");
|
|
13769
14005
|
} catch (err) {
|
|
13770
14006
|
if (!isNotFound(err)) warnUnreadableStoreOnce(err);
|
|
13771
14007
|
return null;
|
|
13772
14008
|
}
|
|
13773
14009
|
try {
|
|
13774
|
-
const entry = parseStore(raw,
|
|
14010
|
+
const entry = parseStore(raw, path44)[url];
|
|
13775
14011
|
if (typeof entry !== "object" || entry === null) return null;
|
|
13776
14012
|
const token = entry.token;
|
|
13777
14013
|
return typeof token === "string" && token ? token : null;
|
|
@@ -13810,12 +14046,12 @@ var init_credentials = __esm({
|
|
|
13810
14046
|
* @param reason - What is wrong with it.
|
|
13811
14047
|
* @param cause - Underlying parse error, if any.
|
|
13812
14048
|
*/
|
|
13813
|
-
constructor(
|
|
14049
|
+
constructor(path44, reason, cause) {
|
|
13814
14050
|
super(
|
|
13815
|
-
`Credentials file ${
|
|
14051
|
+
`Credentials file ${path44} is corrupt (${reason}). Fix it by hand, or delete it and run \`iamu auth login\` again.`,
|
|
13816
14052
|
{
|
|
13817
14053
|
cause,
|
|
13818
|
-
suggestedAction: `Delete ${
|
|
14054
|
+
suggestedAction: `Delete ${path44} and run \`iamu auth login\`, or set IAMU_TOKEN.`
|
|
13819
14055
|
}
|
|
13820
14056
|
);
|
|
13821
14057
|
}
|
|
@@ -14701,8 +14937,8 @@ import { dirname as dirname3, join as join4, resolve as resolve2 } from "path";
|
|
|
14701
14937
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
14702
14938
|
import chalk6 from "chalk";
|
|
14703
14939
|
function getVersion() {
|
|
14704
|
-
if ("0.4.0-beta.
|
|
14705
|
-
return "0.4.0-beta.
|
|
14940
|
+
if ("0.4.0-beta.1") {
|
|
14941
|
+
return "0.4.0-beta.1";
|
|
14706
14942
|
}
|
|
14707
14943
|
try {
|
|
14708
14944
|
const thisDir = dirname3(fileURLToPath2(import.meta.url));
|
|
@@ -14809,16 +15045,16 @@ async function needsWrite(filePath, content) {
|
|
|
14809
15045
|
return true;
|
|
14810
15046
|
}
|
|
14811
15047
|
}
|
|
14812
|
-
function isCliWrittenSkill(
|
|
15048
|
+
function isCliWrittenSkill(path44) {
|
|
14813
15049
|
try {
|
|
14814
|
-
return /^generatedBy:/m.test(readFileSync4(
|
|
15050
|
+
return /^generatedBy:/m.test(readFileSync4(path44, "utf-8"));
|
|
14815
15051
|
} catch {
|
|
14816
15052
|
return false;
|
|
14817
15053
|
}
|
|
14818
15054
|
}
|
|
14819
|
-
function isCliWrittenPrompt(
|
|
15055
|
+
function isCliWrittenPrompt(path44) {
|
|
14820
15056
|
try {
|
|
14821
|
-
const content = readFileSync4(
|
|
15057
|
+
const content = readFileSync4(path44, "utf-8");
|
|
14822
15058
|
return content.includes("iamu") || /^generatedBy:/m.test(content);
|
|
14823
15059
|
} catch {
|
|
14824
15060
|
return false;
|
|
@@ -14830,8 +15066,8 @@ function planSkillFiles(hosts, projectRoot, version2) {
|
|
|
14830
15066
|
for (const host of hosts) {
|
|
14831
15067
|
if (pluginProvidesSkills(host, projectRoot)) {
|
|
14832
15068
|
for (const name of skillTemplates.keys()) {
|
|
14833
|
-
const
|
|
14834
|
-
if (isCliWrittenSkill(
|
|
15069
|
+
const path44 = join4(host.getSkillsDir(projectRoot), name, "SKILL.md");
|
|
15070
|
+
if (isCliWrittenSkill(path44)) files.push({ host, path: path44, content: null });
|
|
14835
15071
|
}
|
|
14836
15072
|
} else if (host.supportsSkills) {
|
|
14837
15073
|
const skillsDir = host.getSkillsDir(projectRoot);
|
|
@@ -16388,8 +16624,17 @@ function lead(text) {
|
|
|
16388
16624
|
}
|
|
16389
16625
|
function severityCell(severity) {
|
|
16390
16626
|
const s = (severity ?? "\u2014").toUpperCase();
|
|
16391
|
-
const
|
|
16392
|
-
return severity === "critical" ? chalk12.red(
|
|
16627
|
+
const cell2 = padRight(s, 8);
|
|
16628
|
+
return severity === "critical" ? chalk12.red(cell2) : severity === "major" ? chalk12.yellow(cell2) : chalk12.dim(cell2);
|
|
16629
|
+
}
|
|
16630
|
+
function severityMix(r) {
|
|
16631
|
+
if (r.problems === 0) return chalk12.green("no problems");
|
|
16632
|
+
const { critical, major, minor } = r.bySeverity;
|
|
16633
|
+
return [
|
|
16634
|
+
critical > 0 ? chalk12.red(`${critical} critical`) : "",
|
|
16635
|
+
major > 0 ? chalk12.yellow(`${major} major`) : "",
|
|
16636
|
+
minor > 0 ? chalk12.dim(`${minor} minor`) : ""
|
|
16637
|
+
].filter(Boolean).join(" ");
|
|
16393
16638
|
}
|
|
16394
16639
|
function deltaCell(r) {
|
|
16395
16640
|
const parts = [
|
|
@@ -16407,6 +16652,13 @@ function renderRunView(v, width = 100) {
|
|
|
16407
16652
|
);
|
|
16408
16653
|
const meta = [shortDate(v.completedAt), `${v.steps} steps`, v.targetUrl].filter(Boolean).join(" \xB7 ");
|
|
16409
16654
|
lines.push(` ${chalk12.dim(meta)}`);
|
|
16655
|
+
if (v.checks) {
|
|
16656
|
+
const parts = [
|
|
16657
|
+
v.checks.fixes > 0 ? `re-checked ${v.checks.fixes} fix${v.checks.fixes === 1 ? "" : "es"}: ${v.checks.fixed} confirmed, ${v.checks.cameBack} came back, ${v.checks.fixes - v.checks.fixed - v.checks.cameBack} not confirmed` : "",
|
|
16658
|
+
v.checks.directions.length > 0 ? `after ${v.checks.directions.length} direction${v.checks.directions.length === 1 ? "" : "s"} landed` : ""
|
|
16659
|
+
].filter(Boolean);
|
|
16660
|
+
lines.push(` ${chalk12.dim(`This run ${parts.join(", ")}`)}`);
|
|
16661
|
+
}
|
|
16410
16662
|
lines.push("");
|
|
16411
16663
|
const color = verdictColor(v.verdict.label);
|
|
16412
16664
|
const question = v.verdict.question ?? "Would they come back?";
|
|
@@ -16434,12 +16686,21 @@ function renderRunView(v, width = 100) {
|
|
|
16434
16686
|
);
|
|
16435
16687
|
for (const item of s.items) {
|
|
16436
16688
|
const mark = SINCE_MARKS[item.outcome];
|
|
16437
|
-
|
|
16689
|
+
const qualifiers = [
|
|
16690
|
+
item.outcome === "persists" ? item.shift : "",
|
|
16691
|
+
item.outcome === "persists" && item.fixedBefore ? "fix did not hold" : ""
|
|
16692
|
+
].filter(Boolean);
|
|
16693
|
+
const held = qualifiers.length > 0 ? (item.fixedBefore ? chalk12.red : chalk12.dim)(
|
|
16694
|
+
` (${qualifiers.join("; ")})`
|
|
16695
|
+
) : "";
|
|
16696
|
+
lines.push(
|
|
16697
|
+
` ${mark} ${truncate(item.title, w - 18 - stripAnsi(held).length)}${held}`
|
|
16698
|
+
);
|
|
16438
16699
|
}
|
|
16439
16700
|
}
|
|
16440
16701
|
lines.push("");
|
|
16441
16702
|
const problems = [...v.findings].sort(
|
|
16442
|
-
(a, b) =>
|
|
16703
|
+
(a, b) => SEVERITY_ORDER2.indexOf(a.severity ?? "") - SEVERITY_ORDER2.indexOf(b.severity ?? "") || TYPE_ORDER2.indexOf(a.type) - TYPE_ORDER2.indexOf(b.type)
|
|
16443
16704
|
);
|
|
16444
16705
|
const strengthNote = v.strengths.length > 0 ? `, ${v.strengths.length} strength${v.strengths.length === 1 ? "" : "s"}` : "";
|
|
16445
16706
|
lines.push(
|
|
@@ -16450,15 +16711,23 @@ function renderRunView(v, width = 100) {
|
|
|
16450
16711
|
24,
|
|
16451
16712
|
Math.max(0, ...problems.map((f) => (f.where ?? "").length))
|
|
16452
16713
|
);
|
|
16453
|
-
const
|
|
16714
|
+
const tagWidth = problems.some((f) => f.persists) ? 9 : 0;
|
|
16715
|
+
const titleWidth = Math.max(
|
|
16716
|
+
24,
|
|
16717
|
+
w - 4 - 3 - 9 - 10 - whereWidth - 2 - tagWidth
|
|
16718
|
+
);
|
|
16454
16719
|
problems.forEach((f, i) => {
|
|
16455
16720
|
const where = chalk12.dim(
|
|
16456
16721
|
padRight(truncate(f.where ?? "", whereWidth), whereWidth)
|
|
16457
16722
|
);
|
|
16458
16723
|
const tag = f.persists ? chalk12.red(" persists") : "";
|
|
16724
|
+
const titleLines = wrap(f.title, titleWidth, "");
|
|
16459
16725
|
lines.push(
|
|
16460
|
-
` ${padRight(String(i + 1), 3)}${severityCell(f.severity)} ${padRight(chalk12.dim(f.type), 9)} ${padRight(
|
|
16726
|
+
` ${padRight(String(i + 1), 3)}${severityCell(f.severity)} ${padRight(chalk12.dim(f.type), 9)} ${padRight(titleLines[0] ?? "", titleWidth)} ${where}${tag}`
|
|
16461
16727
|
);
|
|
16728
|
+
for (const more of titleLines.slice(1)) {
|
|
16729
|
+
lines.push(`${" ".repeat(26)}${more}`);
|
|
16730
|
+
}
|
|
16462
16731
|
if (f.recommendation && (f.severity === "critical" || f.severity === "major")) {
|
|
16463
16732
|
const fix = wrap(`\u2192 ${lead(f.recommendation)}`, w - 30, " ".repeat(26));
|
|
16464
16733
|
if (fix.length > 2) {
|
|
@@ -16484,8 +16753,13 @@ function renderRunView(v, width = 100) {
|
|
|
16484
16753
|
);
|
|
16485
16754
|
}
|
|
16486
16755
|
}
|
|
16487
|
-
if (v.observations.length > 0) {
|
|
16756
|
+
if (v.observations.length > 0 || v.setupNotes.length > 0) {
|
|
16488
16757
|
lines.push("", ` ${chalk12.bold("Also worth knowing")}`);
|
|
16758
|
+
for (const n of v.setupNotes) {
|
|
16759
|
+
lines.push(
|
|
16760
|
+
` ${chalk12.dim("\xB7")} ${chalk12.dim("Test setup:")} ${truncate(n, w - 22)}`
|
|
16761
|
+
);
|
|
16762
|
+
}
|
|
16489
16763
|
for (const o of v.observations) {
|
|
16490
16764
|
const label = o.kind === "coverage_gap" ? "Not walked" : "Feature risk";
|
|
16491
16765
|
lines.push(
|
|
@@ -16520,7 +16794,7 @@ function renderOverview(v, width = 100) {
|
|
|
16520
16794
|
}
|
|
16521
16795
|
for (const r of shown) {
|
|
16522
16796
|
lines.push(
|
|
16523
|
-
` ${padRight(chalk12.dim(shortDate(r.completedAt)), 13)} ${verdictColor(r.label)(padRight(r.label, 8))} ${padRight(
|
|
16797
|
+
` ${padRight(chalk12.dim(shortDate(r.completedAt)), 13)} ${verdictColor(r.label)(padRight(r.label, 8))} ${padRight(severityMix(r), 24)} ${deltaCell(r)}`
|
|
16524
16798
|
);
|
|
16525
16799
|
}
|
|
16526
16800
|
if (s.latestAnswer) {
|
|
@@ -16549,7 +16823,7 @@ function renderOverview(v, width = 100) {
|
|
|
16549
16823
|
);
|
|
16550
16824
|
return lines.map((l) => l.trimEnd());
|
|
16551
16825
|
}
|
|
16552
|
-
var SINCE_MARKS,
|
|
16826
|
+
var SINCE_MARKS, SEVERITY_ORDER2, TYPE_ORDER2;
|
|
16553
16827
|
var init_report_view = __esm({
|
|
16554
16828
|
"src/ui/report-view.ts"() {
|
|
16555
16829
|
"use strict";
|
|
@@ -16560,8 +16834,8 @@ var init_report_view = __esm({
|
|
|
16560
16834
|
not_seen: chalk12.dim(padRight("\u25CB not seen", 12)),
|
|
16561
16835
|
uncertain: chalk12.dim(padRight("? unclear", 12))
|
|
16562
16836
|
};
|
|
16563
|
-
|
|
16564
|
-
|
|
16837
|
+
SEVERITY_ORDER2 = ["critical", "major", "minor", "observation"];
|
|
16838
|
+
TYPE_ORDER2 = ["incorrect", "hinders", "missing", "irrelevant"];
|
|
16565
16839
|
}
|
|
16566
16840
|
});
|
|
16567
16841
|
|
|
@@ -16950,9 +17224,7 @@ ${formatIssues(fileResult.error)}`,
|
|
|
16950
17224
|
console.log(
|
|
16951
17225
|
` ${chalk14.dim("verdicts:")} ${path23.relative(projectRoot, verdictsPath)}`
|
|
16952
17226
|
);
|
|
16953
|
-
console.log(
|
|
16954
|
-
` ${chalk14.dim("report:")} ${path23.relative(projectRoot, reportPath)}`
|
|
16955
|
-
);
|
|
17227
|
+
console.log(` ${chalk14.dim("report:")} ${reportPath}`);
|
|
16956
17228
|
printCloudRunInfo(cloudInfo);
|
|
16957
17229
|
await promptViewResults(projectRoot, { ...opts, runId });
|
|
16958
17230
|
}
|
|
@@ -17008,7 +17280,10 @@ function sinceItems(v) {
|
|
|
17008
17280
|
return groups.flatMap(
|
|
17009
17281
|
([outcome, items]) => (items ?? []).map((i) => ({
|
|
17010
17282
|
title: i.title ?? i.canonical_id ?? "untitled",
|
|
17011
|
-
outcome
|
|
17283
|
+
outcome,
|
|
17284
|
+
note: i.note ?? null,
|
|
17285
|
+
fixedBefore: Boolean(i.fixed_before),
|
|
17286
|
+
shift: outcome === "persists" ? severityShift(i.severity_before, i.severity_now) : ""
|
|
17012
17287
|
}))
|
|
17013
17288
|
);
|
|
17014
17289
|
}
|
|
@@ -17037,11 +17312,8 @@ async function readCompletedRuns(projectRoot, runIds, scenario) {
|
|
|
17037
17312
|
return out;
|
|
17038
17313
|
}
|
|
17039
17314
|
function verdictLabel(c) {
|
|
17040
|
-
const
|
|
17041
|
-
|
|
17042
|
-
if (judged.every((v) => v === "yes")) return "YES";
|
|
17043
|
-
if (judged.every((v) => v === "no")) return "NO";
|
|
17044
|
-
return "PARTIAL";
|
|
17315
|
+
const v = runVerdict(c);
|
|
17316
|
+
return v ? v.toUpperCase() : "PENDING";
|
|
17045
17317
|
}
|
|
17046
17318
|
function testLabel(verdict) {
|
|
17047
17319
|
return verdict === "yes" ? "YES" : verdict === "no" ? "NO" : verdict === "partial" ? "PARTIAL" : "PENDING";
|
|
@@ -17095,6 +17367,17 @@ async function openDirections(projectRoot) {
|
|
|
17095
17367
|
(d) => d.frontmatter.status === "proposed" || d.frontmatter.status === "accepted"
|
|
17096
17368
|
);
|
|
17097
17369
|
}
|
|
17370
|
+
function checksOf(c) {
|
|
17371
|
+
const checks = c?.checks;
|
|
17372
|
+
if (!c || !checks) return null;
|
|
17373
|
+
const fixes = checkOutcomes(c);
|
|
17374
|
+
return {
|
|
17375
|
+
fixes: fixes.length,
|
|
17376
|
+
fixed: fixes.filter((f) => f.outcome === "fixed").length,
|
|
17377
|
+
cameBack: fixes.filter((f) => f.outcome === "came_back").length,
|
|
17378
|
+
directions: checks.directions
|
|
17379
|
+
};
|
|
17380
|
+
}
|
|
17098
17381
|
async function buildRunView(projectRoot, run) {
|
|
17099
17382
|
const c = run.canonical;
|
|
17100
17383
|
const slugs = slugsOf(run);
|
|
@@ -17110,12 +17393,13 @@ async function buildRunView(projectRoot, run) {
|
|
|
17110
17393
|
const v = verificationOf(c);
|
|
17111
17394
|
const persisting = new Set(v?.persists.map((p) => p.canonical_id) ?? []);
|
|
17112
17395
|
const added = new Set(v?.new_findings.map((n) => n.id) ?? []);
|
|
17396
|
+
const stepUrl2 = new Map((c?.steps ?? []).map((st) => [st.id, st.url]));
|
|
17113
17397
|
const findings2 = [];
|
|
17114
17398
|
const strengths = [];
|
|
17115
17399
|
for (const f of c?.findings ?? []) {
|
|
17116
17400
|
const entity = byId.get(f.id);
|
|
17117
17401
|
const fm = entity?.frontmatter;
|
|
17118
|
-
const title = fm?.title ?? firstSentence(f.ui_fact ?? fm?.ui_fact ?? f.id);
|
|
17402
|
+
const title = f.title ?? fm?.title ?? firstSentence(f.ui_fact ?? fm?.ui_fact ?? f.id);
|
|
17119
17403
|
if (f.type === "helps") {
|
|
17120
17404
|
strengths.push(title);
|
|
17121
17405
|
continue;
|
|
@@ -17129,7 +17413,7 @@ async function buildRunView(projectRoot, run) {
|
|
|
17129
17413
|
recommendation: f.recommendation || null,
|
|
17130
17414
|
isNew: added.has(f.id),
|
|
17131
17415
|
where: whereOf(
|
|
17132
|
-
fm?.page_url,
|
|
17416
|
+
f.page_url ?? fm?.page_url ?? stepUrl2.get(f.linked_step_ids?.[0] ?? ""),
|
|
17133
17417
|
fm?.walkthrough_step_id ?? f.linked_step_ids?.[0]
|
|
17134
17418
|
),
|
|
17135
17419
|
persists: Boolean(f.canonical_id && persisting.has(f.canonical_id))
|
|
@@ -17172,7 +17456,7 @@ async function buildRunView(projectRoot, run) {
|
|
|
17172
17456
|
answer: overall?.answer ?? null
|
|
17173
17457
|
},
|
|
17174
17458
|
tests: tests.map((t) => ({
|
|
17175
|
-
question: t.question,
|
|
17459
|
+
question: stripAnswerHint(t.question),
|
|
17176
17460
|
label: testLabel(t.verdict)
|
|
17177
17461
|
})),
|
|
17178
17462
|
since: v ? {
|
|
@@ -17191,9 +17475,17 @@ async function buildRunView(projectRoot, run) {
|
|
|
17191
17475
|
absorbs: d.absorbs?.length ?? 0,
|
|
17192
17476
|
verify: d.verify?.length ?? 0
|
|
17193
17477
|
})),
|
|
17194
|
-
observations: analysis?.observations ?? []
|
|
17478
|
+
observations: analysis?.observations ?? [],
|
|
17479
|
+
checks: checksOf(c),
|
|
17480
|
+
setupNotes: c?.setup_notes ?? []
|
|
17195
17481
|
};
|
|
17196
17482
|
}
|
|
17483
|
+
function severityCounts(c) {
|
|
17484
|
+
const problems = (c?.findings ?? []).filter(isProblem);
|
|
17485
|
+
const critical = problems.filter((f) => f.severity === "critical").length;
|
|
17486
|
+
const major = problems.filter((f) => f.severity === "major").length;
|
|
17487
|
+
return { critical, major, minor: problems.length - critical - major };
|
|
17488
|
+
}
|
|
17197
17489
|
async function buildOverview(projectRoot, runs) {
|
|
17198
17490
|
const groups = /* @__PURE__ */ new Map();
|
|
17199
17491
|
for (const r of runs) {
|
|
@@ -17220,6 +17512,7 @@ async function buildOverview(projectRoot, runs) {
|
|
|
17220
17512
|
completedAt: r.manifest.completed_at ?? null,
|
|
17221
17513
|
label: verdictLabel(r.canonical),
|
|
17222
17514
|
problems: (r.canonical?.findings ?? []).filter(isProblem).length,
|
|
17515
|
+
bySeverity: severityCounts(r.canonical),
|
|
17223
17516
|
resolved: v?.resolved.length ?? 0,
|
|
17224
17517
|
persists: v?.persists.length ?? 0,
|
|
17225
17518
|
added: v?.new_findings.filter(isProblem).length ?? 0
|
|
@@ -17271,6 +17564,10 @@ __export(html_report_exports, {
|
|
|
17271
17564
|
});
|
|
17272
17565
|
import { promises as fs31 } from "fs";
|
|
17273
17566
|
import path25 from "path";
|
|
17567
|
+
function firstSentenceOf(text) {
|
|
17568
|
+
if (!text) return null;
|
|
17569
|
+
return /^(.+?[.!?])(\s|$)/.exec(text.trim())?.[1] ?? text.trim();
|
|
17570
|
+
}
|
|
17274
17571
|
function esc(str2) {
|
|
17275
17572
|
if (!str2) return "";
|
|
17276
17573
|
return str2.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -17377,6 +17674,70 @@ function renderStepScreenshotHtml(s) {
|
|
|
17377
17674
|
</div>
|
|
17378
17675
|
`;
|
|
17379
17676
|
}
|
|
17677
|
+
function sortProblems(list2) {
|
|
17678
|
+
const r = (v, order) => {
|
|
17679
|
+
const i = order.indexOf(v ?? "");
|
|
17680
|
+
return i === -1 ? order.length : i;
|
|
17681
|
+
};
|
|
17682
|
+
return [...list2].sort(
|
|
17683
|
+
(a, b) => r(a.severity, SEVERITY_RANK) - r(b.severity, SEVERITY_RANK) || r(a.type, TYPE_RANK) - r(b.type, TYPE_RANK)
|
|
17684
|
+
);
|
|
17685
|
+
}
|
|
17686
|
+
function inlineMarkdown(text) {
|
|
17687
|
+
return esc(text).replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>").replace(/\n+/g, "<br>");
|
|
17688
|
+
}
|
|
17689
|
+
function persistsLabel(i) {
|
|
17690
|
+
const qualifiers = [
|
|
17691
|
+
severityShift(i.severity_before, i.severity_now),
|
|
17692
|
+
i.fixed_before ? "fix did not hold" : ""
|
|
17693
|
+
].filter(Boolean);
|
|
17694
|
+
return qualifiers.length > 0 ? `Persists (${qualifiers.join("; ")})` : "Persists";
|
|
17695
|
+
}
|
|
17696
|
+
function renderChecks2(fixes, directions) {
|
|
17697
|
+
const items = fixes.map(
|
|
17698
|
+
(f) => `<li style="margin-bottom:0.25rem"><strong>${CHECK_LABEL[f.outcome]}:</strong> ${esc(f.title)}${f.fix ? `. Fix: ${esc(f.fix)}` : ""}${f.reached ? "" : " <em>(not reached)</em>"}</li>`
|
|
17699
|
+
).join("");
|
|
17700
|
+
const landed = directions.length > 0 ? `<p style="font-size:13px">Directions completed since the last run: ${directions.map((d) => `<strong>${esc(d)}</strong>`).join(", ")}.</p>` : "";
|
|
17701
|
+
return `
|
|
17702
|
+
<section>
|
|
17703
|
+
<h2>What this run checks</h2>
|
|
17704
|
+
<div class="card">
|
|
17705
|
+
${items ? `<p style="font-size:13px">${fixes.length} fix${fixes.length === 1 ? " was" : "es were"} waiting for a re-check.</p><ul style="padding-left:1.25rem;font-size:13px;line-height:1.6">${items}</ul>` : ""}
|
|
17706
|
+
${landed}
|
|
17707
|
+
</div>
|
|
17708
|
+
</section>`;
|
|
17709
|
+
}
|
|
17710
|
+
function renderSinceTable(v) {
|
|
17711
|
+
const steps = (ids) => ids && ids.length > 0 ? ` (step${ids.length === 1 ? "" : "s"} ${ids.map((id) => /\d+/.exec(id)?.[0] ?? id).join(", ")})` : "";
|
|
17712
|
+
const td = 'style="padding:6px 8px;border-top:1px solid #E5E7EB;vertical-align:top"';
|
|
17713
|
+
const row = (i, now, note) => `<tr><td ${td}>${esc(i.title)}</td><td ${td}><strong>${esc(now)}</strong></td><td ${td}>${esc(note) || "\u2014"}</td></tr>`;
|
|
17714
|
+
const rows = [
|
|
17715
|
+
...v.resolved.map(
|
|
17716
|
+
(i) => row(i, "Resolved", `${i.note ?? ""}${steps(i.step_ids)}`)
|
|
17717
|
+
),
|
|
17718
|
+
...v.persists.map(
|
|
17719
|
+
(i) => row(i, persistsLabel(i), `${i.note ?? ""}${steps(i.step_ids)}`)
|
|
17720
|
+
),
|
|
17721
|
+
...(v.notObserved ?? []).map(
|
|
17722
|
+
(i) => row(
|
|
17723
|
+
i,
|
|
17724
|
+
"Not seen",
|
|
17725
|
+
`The walk did not pass this way${i.fixed_before ? " (fixed, awaiting a re-check)" : ""}. Not evidence of a fix.`
|
|
17726
|
+
)
|
|
17727
|
+
)
|
|
17728
|
+
];
|
|
17729
|
+
if (rows.length === 0) return "";
|
|
17730
|
+
return `
|
|
17731
|
+
<section>
|
|
17732
|
+
<h2>Since the last run</h2>
|
|
17733
|
+
<div class="card">
|
|
17734
|
+
<table style="width:100%;border-collapse:collapse;font-size:13px;line-height:1.5">
|
|
17735
|
+
<thead><tr><th style="text-align:left;padding:6px 8px">Earlier finding</th><th style="text-align:left;padding:6px 8px">Now</th><th style="text-align:left;padding:6px 8px">Notes</th></tr></thead>
|
|
17736
|
+
<tbody>${rows.join("")}</tbody>
|
|
17737
|
+
</table>
|
|
17738
|
+
</div>
|
|
17739
|
+
</section>`;
|
|
17740
|
+
}
|
|
17380
17741
|
function generateHtmlReport(data) {
|
|
17381
17742
|
const {
|
|
17382
17743
|
scenarioName,
|
|
@@ -17385,6 +17746,7 @@ function generateHtmlReport(data) {
|
|
|
17385
17746
|
steps,
|
|
17386
17747
|
findings: findings2,
|
|
17387
17748
|
verdicts,
|
|
17749
|
+
overall,
|
|
17388
17750
|
verification,
|
|
17389
17751
|
whatWorked,
|
|
17390
17752
|
openQuestions,
|
|
@@ -17395,16 +17757,17 @@ function generateHtmlReport(data) {
|
|
|
17395
17757
|
runNumber
|
|
17396
17758
|
} = data;
|
|
17397
17759
|
const dateStr = formatDate(completedAt ?? startedAt);
|
|
17398
|
-
const
|
|
17399
|
-
|
|
17760
|
+
const problems = sortProblems(findings2.filter((f) => f.type !== "helps"));
|
|
17761
|
+
const strengths = findings2.filter((f) => f.type === "helps");
|
|
17762
|
+
const criticalCount = problems.filter(
|
|
17763
|
+
(f) => f.severity === "critical"
|
|
17400
17764
|
).length;
|
|
17401
|
-
const majorCount =
|
|
17402
|
-
|
|
17765
|
+
const majorCount = problems.filter((f) => f.severity === "major").length;
|
|
17766
|
+
const minorCount = problems.filter(
|
|
17767
|
+
(f) => f.severity !== "critical" && f.severity !== "major"
|
|
17403
17768
|
).length;
|
|
17404
|
-
const
|
|
17405
|
-
|
|
17406
|
-
).length;
|
|
17407
|
-
const helpsCount = findings2.filter((f) => f.type === "helps").length;
|
|
17769
|
+
const helpsCount = strengths.length;
|
|
17770
|
+
const newProblems = verification ? verification.newFindings.filter((n) => n.type !== "helps").length : 0;
|
|
17408
17771
|
return `<!DOCTYPE html>
|
|
17409
17772
|
<html lang="en">
|
|
17410
17773
|
<head>
|
|
@@ -17879,16 +18242,27 @@ function generateHtmlReport(data) {
|
|
|
17879
18242
|
<div class="verification-notice">
|
|
17880
18243
|
Since last run: <strong>${verification.resolved.length} resolved</strong> \xB7
|
|
17881
18244
|
<strong>${verification.persists.length} persists</strong> \xB7
|
|
17882
|
-
<strong>${
|
|
18245
|
+
<strong>${newProblems} new</strong>
|
|
17883
18246
|
</div>` : ""}
|
|
17884
18247
|
</div>
|
|
17885
18248
|
|
|
17886
|
-
|
|
17887
|
-
|
|
18249
|
+
${data.checks ? renderChecks2(data.checks.fixes, data.checks.directions) : ""}
|
|
18250
|
+
|
|
18251
|
+
<!-- The Human Test: the run's outcome, first -->
|
|
18252
|
+
${overall || verdicts && verdicts.length > 0 ? `
|
|
17888
18253
|
<section>
|
|
17889
|
-
<h2>The Human Test
|
|
18254
|
+
<h2>The Human Test</h2>
|
|
17890
18255
|
<div class="card">
|
|
17891
|
-
${
|
|
18256
|
+
${overall ? `
|
|
18257
|
+
<div class="verdict-item">
|
|
18258
|
+
<div class="verdict-header">
|
|
18259
|
+
<span style="font-weight:600;font-size:15px">${esc(overall.question)}</span>
|
|
18260
|
+
<span class="badge badge-${esc(overall.label)}">${esc(overall.label)}</span>
|
|
18261
|
+
</div>
|
|
18262
|
+
${overall.answer ? `<div class="prose-voice" style="margin-top:0.35rem;font-weight:500">${esc(overall.answer)}</div>` : ""}
|
|
18263
|
+
${overall.reasoning ? `<div class="prose-voice" style="margin-top:0.35rem">${esc(overall.reasoning)}</div>` : ""}
|
|
18264
|
+
</div>` : ""}
|
|
18265
|
+
${(verdicts ?? []).map(
|
|
17892
18266
|
(v) => `
|
|
17893
18267
|
<div class="verdict-item">
|
|
17894
18268
|
<div class="verdict-header">
|
|
@@ -17903,14 +18277,46 @@ function generateHtmlReport(data) {
|
|
|
17903
18277
|
</section>
|
|
17904
18278
|
` : ""}
|
|
17905
18279
|
|
|
18280
|
+
${verification ? renderSinceTable(verification) : ""}
|
|
18281
|
+
|
|
18282
|
+
<!-- Findings, worst first -->
|
|
18283
|
+
${problems.length > 0 ? `
|
|
18284
|
+
<section>
|
|
18285
|
+
<h2>Findings (${problems.length})</h2>
|
|
18286
|
+
${problems.map(
|
|
18287
|
+
(f, i) => `
|
|
18288
|
+
<div class="finding-item">
|
|
18289
|
+
<div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:0.35rem 0.75rem">
|
|
18290
|
+
<span class="finding-title">${i + 1}. ${esc(f.title)}${f.isNew ? ' <span class="badge">new</span>' : f.persists ? ' <span class="badge">persists</span>' : ""}</span>
|
|
18291
|
+
<span class="badge badge-${esc(f.severity ?? f.type)}">${esc(f.severity ?? f.type)}</span>
|
|
18292
|
+
</div>
|
|
18293
|
+
<div class="finding-meta">${esc([f.type, f.where].filter(Boolean).join(" \xB7 "))}</div>
|
|
18294
|
+
${f.uiFact ? `<div class="finding-meta">${esc(f.uiFact)}</div>` : ""}
|
|
18295
|
+
${f.consequence ? `<blockquote class="finding-meta prose-voice">${esc(f.consequence)}${persona?.name ? ` \u2014 ${esc(persona.name)}` : ""}</blockquote>` : ""}
|
|
18296
|
+
${f.recommendation ? `<div class="finding-meta finding-rec"><strong>Recommendation:</strong> ${inlineMarkdown(f.recommendation)}</div>` : ""}
|
|
18297
|
+
</div>
|
|
18298
|
+
`
|
|
18299
|
+
).join("")}
|
|
18300
|
+
</section>
|
|
18301
|
+
` : ""}
|
|
18302
|
+
|
|
17906
18303
|
<!-- What Worked & Open Questions -->
|
|
17907
|
-
${whatWorked && whatWorked.length > 0 || openQuestions && openQuestions.length > 0 ? `
|
|
18304
|
+
${strengths.length > 0 || whatWorked && whatWorked.length > 0 || openQuestions && openQuestions.length > 0 || data.setupNotes && data.setupNotes.length > 0 ? `
|
|
17908
18305
|
<section style="display:flex;gap:1.25rem;flex-wrap:wrap">
|
|
17909
|
-
${whatWorked && whatWorked.length > 0 ? `
|
|
18306
|
+
${strengths.length > 0 || whatWorked && whatWorked.length > 0 ? `
|
|
17910
18307
|
<div class="card" style="flex:1;min-width:260px">
|
|
17911
18308
|
<div class="card-label">What Worked</div>
|
|
17912
18309
|
<ul style="padding-left:1.25rem;font-size:13px;line-height:1.6">
|
|
17913
|
-
${
|
|
18310
|
+
${strengths.map((f) => `<li style="margin-bottom:0.25rem"><strong>${esc(f.title)}.</strong> ${esc(f.uiFact)}</li>`).join("")}
|
|
18311
|
+
${(whatWorked ?? []).map((w) => `<li style="margin-bottom:0.25rem">${esc(w)}</li>`).join("")}
|
|
18312
|
+
</ul>
|
|
18313
|
+
</div>
|
|
18314
|
+
` : ""}
|
|
18315
|
+
${data.setupNotes && data.setupNotes.length > 0 ? `
|
|
18316
|
+
<div class="card" style="flex:1;min-width:260px">
|
|
18317
|
+
<div class="card-label">Test Setup Notes</div>
|
|
18318
|
+
<ul style="padding-left:1.25rem;font-size:13px;line-height:1.6">
|
|
18319
|
+
${data.setupNotes.map((n) => `<li style="margin-bottom:0.25rem">${esc(n)}</li>`).join("")}
|
|
17914
18320
|
</ul>
|
|
17915
18321
|
</div>
|
|
17916
18322
|
` : ""}
|
|
@@ -17925,26 +18331,6 @@ function generateHtmlReport(data) {
|
|
|
17925
18331
|
</section>
|
|
17926
18332
|
` : ""}
|
|
17927
18333
|
|
|
17928
|
-
<!-- Findings Overview -->
|
|
17929
|
-
${findings2.length > 0 ? `
|
|
17930
|
-
<section>
|
|
17931
|
-
<h2>Findings (${findings2.length})</h2>
|
|
17932
|
-
${findings2.map(
|
|
17933
|
-
(f) => `
|
|
17934
|
-
<div class="finding-item">
|
|
17935
|
-
<div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:0.35rem 0.75rem">
|
|
17936
|
-
<span class="finding-title">${esc(f.title)}</span>
|
|
17937
|
-
<span class="badge badge-${esc(f.severity ?? f.type)}">${esc(f.severity ?? f.type)}</span>
|
|
17938
|
-
</div>
|
|
17939
|
-
${f.uiFact ? `<div class="finding-meta"><strong>Observed:</strong> ${esc(f.uiFact)}</div>` : ""}
|
|
17940
|
-
${f.consequence ? `<div class="finding-meta"><strong>Consequence:</strong> ${esc(f.consequence)}</div>` : ""}
|
|
17941
|
-
${f.recommendation ? `<div class="finding-meta finding-rec"><strong>Recommendation:</strong> ${esc(f.recommendation)}</div>` : ""}
|
|
17942
|
-
</div>
|
|
17943
|
-
`
|
|
17944
|
-
).join("")}
|
|
17945
|
-
</section>
|
|
17946
|
-
` : ""}
|
|
17947
|
-
|
|
17948
18334
|
<!-- Step Timeline with Connected Gutter -->
|
|
17949
18335
|
${steps.length > 0 ? `
|
|
17950
18336
|
<section>
|
|
@@ -18012,7 +18398,6 @@ async function buildReportDataFromRun(projectRoot, runId) {
|
|
|
18012
18398
|
scenarioSlug
|
|
18013
18399
|
);
|
|
18014
18400
|
if (record) {
|
|
18015
|
-
scenarioName = record.frontmatter.slug || scenarioSlug;
|
|
18016
18401
|
scenarioNarrative = record.body?.trim() || null;
|
|
18017
18402
|
}
|
|
18018
18403
|
} catch {
|
|
@@ -18068,47 +18453,82 @@ async function buildReportDataFromRun(projectRoot, runId) {
|
|
|
18068
18453
|
screenshotUrl
|
|
18069
18454
|
});
|
|
18070
18455
|
}
|
|
18456
|
+
const names2 = canonical?.scenario;
|
|
18457
|
+
if (names2?.outline_title) {
|
|
18458
|
+
scenarioName = `${names2.persona_name ?? personaData?.name ?? names2.persona_slug} \xB7 ${names2.outline_title}`;
|
|
18459
|
+
}
|
|
18460
|
+
const verification = canonical?.verification;
|
|
18461
|
+
const newIds = new Set((verification?.new_findings ?? []).map((n) => n.id));
|
|
18462
|
+
const persistingIds = new Set(
|
|
18463
|
+
(verification?.persists ?? []).map(
|
|
18464
|
+
(p) => p.canonical_id
|
|
18465
|
+
)
|
|
18466
|
+
);
|
|
18071
18467
|
const findings2 = (canonical?.findings ?? []).map((f) => {
|
|
18072
|
-
const
|
|
18073
|
-
|
|
18468
|
+
const step = rawSteps.find((st) => st.id === f.linked_step_ids[0]);
|
|
18469
|
+
let page = null;
|
|
18470
|
+
const url = f.page_url ?? step?.url;
|
|
18471
|
+
if (url) {
|
|
18472
|
+
try {
|
|
18473
|
+
page = new URL(url).pathname;
|
|
18474
|
+
} catch {
|
|
18475
|
+
page = url;
|
|
18476
|
+
}
|
|
18477
|
+
}
|
|
18074
18478
|
return {
|
|
18075
18479
|
id: f.id,
|
|
18076
|
-
title,
|
|
18480
|
+
title: f.title?.trim() || firstSentenceOf(f.ui_fact) || f.id,
|
|
18077
18481
|
type: f.type,
|
|
18078
18482
|
severity: f.severity,
|
|
18079
18483
|
uiFact: f.ui_fact,
|
|
18080
18484
|
consequence: f.consequence,
|
|
18081
|
-
recommendation: f.recommendation
|
|
18485
|
+
recommendation: f.recommendation,
|
|
18486
|
+
where: [page, step ? `step ${step.step_number}` : null].filter(Boolean).join(" \xB7 ") || null,
|
|
18487
|
+
isNew: newIds.has(f.id),
|
|
18488
|
+
persists: Boolean(f.canonical_id && persistingIds.has(f.canonical_id))
|
|
18082
18489
|
};
|
|
18083
18490
|
});
|
|
18084
18491
|
const verdicts = (canonical?.human_tests ?? []).map(
|
|
18085
18492
|
(ht) => ({
|
|
18086
|
-
question: ht.question,
|
|
18493
|
+
question: stripAnswerHint(ht.question),
|
|
18087
18494
|
rating: ht.verdict || "pending",
|
|
18088
18495
|
reasoning: typeof ht.reasoning === "string" ? ht.reasoning : void 0
|
|
18089
18496
|
})
|
|
18090
18497
|
);
|
|
18091
|
-
const verification = canonical?.verification;
|
|
18092
18498
|
const verificationData = verification ? {
|
|
18093
18499
|
resolved: verification.resolved ?? [],
|
|
18094
18500
|
persists: verification.persists ?? [],
|
|
18501
|
+
notObserved: verification.not_observed ?? [],
|
|
18095
18502
|
newFindings: verification.new_findings ?? []
|
|
18096
18503
|
} : null;
|
|
18504
|
+
const verdict = runVerdict(canonical);
|
|
18505
|
+
const overall = canonical?.human_test_overall ? {
|
|
18506
|
+
question: canonical.human_test_overall.question,
|
|
18507
|
+
label: verdict ?? "pending",
|
|
18508
|
+
answer: canonical.human_test_overall.answer,
|
|
18509
|
+
reasoning: canonical.human_test_overall.reasoning
|
|
18510
|
+
} : null;
|
|
18097
18511
|
return {
|
|
18098
18512
|
scenarioName,
|
|
18099
18513
|
scenarioNarrative,
|
|
18100
18514
|
persona: personaData,
|
|
18101
18515
|
walkthroughId: runId,
|
|
18102
|
-
runNumber: manifest.run_number ?? null,
|
|
18516
|
+
runNumber: manifest.run_number ?? canonical?.run_number ?? null,
|
|
18103
18517
|
startedAt: manifest.started_at ?? canonical?.provenance?.started_at ?? null,
|
|
18104
18518
|
completedAt: manifest.completed_at ?? canonical?.provenance?.completed_at ?? null,
|
|
18105
18519
|
targetUrl: manifest.target_url ?? canonical?.target?.url ?? null,
|
|
18106
18520
|
steps,
|
|
18107
18521
|
findings: findings2,
|
|
18108
18522
|
verdicts,
|
|
18523
|
+
overall,
|
|
18109
18524
|
verification: verificationData,
|
|
18110
18525
|
whatWorked: canonical?.what_worked,
|
|
18111
|
-
openQuestions: canonical?.open_questions
|
|
18526
|
+
openQuestions: canonical?.open_questions,
|
|
18527
|
+
setupNotes: canonical?.setup_notes,
|
|
18528
|
+
checks: (() => {
|
|
18529
|
+
const raw = canonical?.checks;
|
|
18530
|
+
return canonical && raw ? { fixes: checkOutcomes(canonical), directions: raw.directions } : null;
|
|
18531
|
+
})()
|
|
18112
18532
|
};
|
|
18113
18533
|
}
|
|
18114
18534
|
async function createLocalHtmlReport(projectRoot, runId, destination) {
|
|
@@ -18118,11 +18538,19 @@ async function createLocalHtmlReport(projectRoot, runId, destination) {
|
|
|
18118
18538
|
await atomicWriteFile(targetPath, html);
|
|
18119
18539
|
return { filePath: targetPath, html };
|
|
18120
18540
|
}
|
|
18541
|
+
var SEVERITY_RANK, TYPE_RANK, CHECK_LABEL;
|
|
18121
18542
|
var init_html_report = __esm({
|
|
18122
18543
|
"src/ui/html-report.ts"() {
|
|
18123
18544
|
"use strict";
|
|
18124
18545
|
init_dist2();
|
|
18125
18546
|
init_evaluate();
|
|
18547
|
+
SEVERITY_RANK = ["critical", "major", "minor", "observation"];
|
|
18548
|
+
TYPE_RANK = ["incorrect", "hinders", "missing", "irrelevant"];
|
|
18549
|
+
CHECK_LABEL = {
|
|
18550
|
+
fixed: "Confirmed fixed",
|
|
18551
|
+
came_back: "Came back",
|
|
18552
|
+
unconfirmed: "Not confirmed"
|
|
18553
|
+
};
|
|
18126
18554
|
}
|
|
18127
18555
|
});
|
|
18128
18556
|
|
|
@@ -18131,6 +18559,7 @@ var report_exports = {};
|
|
|
18131
18559
|
__export(report_exports, {
|
|
18132
18560
|
report: () => report
|
|
18133
18561
|
});
|
|
18562
|
+
import path26 from "path";
|
|
18134
18563
|
import chalk15 from "chalk";
|
|
18135
18564
|
async function report(scenario, opts) {
|
|
18136
18565
|
const ctx = await resolveProject(opts);
|
|
@@ -18251,6 +18680,9 @@ function jsonRows(projectRoot, runs) {
|
|
|
18251
18680
|
human_tests: r.canonical?.human_tests ?? [],
|
|
18252
18681
|
human_test_overall: r.canonical?.human_test_overall,
|
|
18253
18682
|
summary: await buildRunView(projectRoot, r),
|
|
18683
|
+
// Absolute, so a terminal can open them (relative paths are not links).
|
|
18684
|
+
run_dir: runDir(projectRoot, r.runId),
|
|
18685
|
+
report_path: path26.join(runDir(projectRoot, r.runId), "report.md"),
|
|
18254
18686
|
...cloudUrl ? { cloud_url: cloudUrl } : {}
|
|
18255
18687
|
};
|
|
18256
18688
|
})
|
|
@@ -18275,7 +18707,7 @@ __export(open_exports, {
|
|
|
18275
18707
|
openDashboard: () => openDashboard
|
|
18276
18708
|
});
|
|
18277
18709
|
import { existsSync as existsSync8, promises as fs32 } from "fs";
|
|
18278
|
-
import
|
|
18710
|
+
import path27 from "path";
|
|
18279
18711
|
import { isCancel as isCancel3, select as select3 } from "@clack/prompts";
|
|
18280
18712
|
import chalk16 from "chalk";
|
|
18281
18713
|
async function openDashboard(runId, opts) {
|
|
@@ -18371,7 +18803,7 @@ async function openLocalReport(projectRoot, runId, opts) {
|
|
|
18371
18803
|
for (const rid of [...runs].reverse()) {
|
|
18372
18804
|
try {
|
|
18373
18805
|
const manifestRaw = await fs32.readFile(
|
|
18374
|
-
|
|
18806
|
+
path27.join(runDir(projectRoot, rid), "walkthrough.json"),
|
|
18375
18807
|
"utf8"
|
|
18376
18808
|
);
|
|
18377
18809
|
const manifest = JSON.parse(manifestRaw);
|
|
@@ -20146,7 +20578,7 @@ var init_status2 = __esm({
|
|
|
20146
20578
|
|
|
20147
20579
|
// src/commands/browse-actions.ts
|
|
20148
20580
|
import { existsSync as existsSync11 } from "fs";
|
|
20149
|
-
import
|
|
20581
|
+
import path28 from "path";
|
|
20150
20582
|
function byRole(page, role, name, opts) {
|
|
20151
20583
|
const loc = page.getByRole(role, { name, exact: false });
|
|
20152
20584
|
return opts.nth !== void 0 ? loc.nth(Number(opts.nth)) : loc.first();
|
|
@@ -20337,7 +20769,7 @@ async function fillIndexedElement(page, index, text, submit) {
|
|
|
20337
20769
|
await fillOrSelect(loc.first(), text, submit);
|
|
20338
20770
|
}
|
|
20339
20771
|
async function setFilesOnElement(el, filePath) {
|
|
20340
|
-
const resolved =
|
|
20772
|
+
const resolved = path28.resolve(filePath);
|
|
20341
20773
|
if (!existsSync11(resolved)) {
|
|
20342
20774
|
throw new Error(`Upload file does not exist: "${resolved}"`);
|
|
20343
20775
|
}
|
|
@@ -20677,7 +21109,7 @@ var init_browse_driver = __esm({
|
|
|
20677
21109
|
|
|
20678
21110
|
// src/commands/step-numbering.ts
|
|
20679
21111
|
import { open, readdir as readdir2, unlink } from "fs/promises";
|
|
20680
|
-
import
|
|
21112
|
+
import path29 from "path";
|
|
20681
21113
|
function stepFileName(stepNumber2) {
|
|
20682
21114
|
return `${String(stepNumber2).padStart(3, "0")}.json`;
|
|
20683
21115
|
}
|
|
@@ -20699,7 +21131,7 @@ async function reserveStepFile(stepsDir, limit) {
|
|
|
20699
21131
|
if (candidate > limit.maxSteps) {
|
|
20700
21132
|
throw new Error(stepLimitMessage(limit.maxSteps, limit.runId));
|
|
20701
21133
|
}
|
|
20702
|
-
const stepPath =
|
|
21134
|
+
const stepPath = path29.join(stepsDir, stepFileName(candidate));
|
|
20703
21135
|
try {
|
|
20704
21136
|
const handle = await open(stepPath, "wx");
|
|
20705
21137
|
await handle.close();
|
|
@@ -20731,7 +21163,7 @@ __export(step_exports, {
|
|
|
20731
21163
|
stepCommand: () => stepCommand
|
|
20732
21164
|
});
|
|
20733
21165
|
import { copyFile, mkdir as mkdir5, stat as stat2 } from "fs/promises";
|
|
20734
|
-
import
|
|
21166
|
+
import path30 from "path";
|
|
20735
21167
|
import chalk22 from "chalk";
|
|
20736
21168
|
function parseMaxSteps(val) {
|
|
20737
21169
|
if (typeof val === "number" && Number.isFinite(val) && val > 0) return val;
|
|
@@ -20750,8 +21182,8 @@ async function recordStep(input) {
|
|
|
20750
21182
|
if (!runDirPath) {
|
|
20751
21183
|
throw new Error(`Run directory not found for run "${input.runId}"`);
|
|
20752
21184
|
}
|
|
20753
|
-
const stepsDir =
|
|
20754
|
-
const screenshotsDir =
|
|
21185
|
+
const stepsDir = path30.join(runDirPath, "steps");
|
|
21186
|
+
const screenshotsDir = path30.join(runDirPath, "screenshots");
|
|
20755
21187
|
await mkdir5(stepsDir, { recursive: true });
|
|
20756
21188
|
await mkdir5(screenshotsDir, { recursive: true });
|
|
20757
21189
|
const shotSource = input.shot ? await existingShot(input.shot) : null;
|
|
@@ -20765,11 +21197,11 @@ async function recordStep(input) {
|
|
|
20765
21197
|
let finalScreenshotPath = null;
|
|
20766
21198
|
if (shotSource) {
|
|
20767
21199
|
const destName = `step-${stepNumber2}.png`;
|
|
20768
|
-
finalScreenshotPath =
|
|
20769
|
-
if (
|
|
21200
|
+
finalScreenshotPath = path30.join(screenshotsDir, destName);
|
|
21201
|
+
if (path30.resolve(finalScreenshotPath) !== shotSource) {
|
|
20770
21202
|
await copyFile(shotSource, finalScreenshotPath);
|
|
20771
21203
|
}
|
|
20772
|
-
screenshotRef =
|
|
21204
|
+
screenshotRef = path30.join("screenshots", destName);
|
|
20773
21205
|
}
|
|
20774
21206
|
const parsedStep = runStepSchema.parse({
|
|
20775
21207
|
step_number: stepNumber2,
|
|
@@ -20808,7 +21240,7 @@ async function recordStep(input) {
|
|
|
20808
21240
|
}
|
|
20809
21241
|
}
|
|
20810
21242
|
async function existingShot(shot) {
|
|
20811
|
-
const resolved =
|
|
21243
|
+
const resolved = path30.resolve(shot);
|
|
20812
21244
|
const isFile = await stat2(resolved).then((s) => s.isFile()).catch(() => false);
|
|
20813
21245
|
if (!isFile) {
|
|
20814
21246
|
throw new Error(
|
|
@@ -20862,7 +21294,7 @@ async function stepCommand(runArg, opts) {
|
|
|
20862
21294
|
{
|
|
20863
21295
|
step_number: res.step.step_number,
|
|
20864
21296
|
cancelled: true,
|
|
20865
|
-
path:
|
|
21297
|
+
path: path30.relative(projectRoot, res.stepPath),
|
|
20866
21298
|
action: res.step.action,
|
|
20867
21299
|
note: res.step.note,
|
|
20868
21300
|
url: res.step.url,
|
|
@@ -20890,7 +21322,7 @@ async function stepCommand(runArg, opts) {
|
|
|
20890
21322
|
JSON.stringify(
|
|
20891
21323
|
{
|
|
20892
21324
|
step_number: res.step.step_number,
|
|
20893
|
-
path:
|
|
21325
|
+
path: path30.relative(projectRoot, res.stepPath),
|
|
20894
21326
|
action: res.step.action,
|
|
20895
21327
|
note: res.step.note,
|
|
20896
21328
|
url: res.step.url,
|
|
@@ -20919,7 +21351,7 @@ async function stepCommand(runArg, opts) {
|
|
|
20919
21351
|
console.log(` ${chalk22.dim("shot:")} ${res.step.screenshot}`);
|
|
20920
21352
|
}
|
|
20921
21353
|
console.log(
|
|
20922
|
-
` ${chalk22.dim("file:")} ${
|
|
21354
|
+
` ${chalk22.dim("file:")} ${path30.relative(projectRoot, res.stepPath)}`
|
|
20923
21355
|
);
|
|
20924
21356
|
console.log();
|
|
20925
21357
|
} else {
|
|
@@ -20980,21 +21412,21 @@ import {
|
|
|
20980
21412
|
} from "fs";
|
|
20981
21413
|
import { rm as rm2 } from "fs/promises";
|
|
20982
21414
|
import { homedir as homedir3 } from "os";
|
|
20983
|
-
import
|
|
21415
|
+
import path31 from "path";
|
|
20984
21416
|
import chalk23 from "chalk";
|
|
20985
21417
|
function stateDir() {
|
|
20986
|
-
return
|
|
20987
|
-
process.env.IAMU_HOME ??
|
|
21418
|
+
return path31.join(
|
|
21419
|
+
process.env.IAMU_HOME ?? path31.join(homedir3(), ".iamu"),
|
|
20988
21420
|
"browse"
|
|
20989
21421
|
);
|
|
20990
21422
|
}
|
|
20991
21423
|
function profileDir() {
|
|
20992
|
-
return
|
|
21424
|
+
return path31.join(stateDir(), "profile");
|
|
20993
21425
|
}
|
|
20994
21426
|
function readBrowseState() {
|
|
20995
21427
|
try {
|
|
20996
21428
|
return JSON.parse(
|
|
20997
|
-
readFileSync6(
|
|
21429
|
+
readFileSync6(path31.join(stateDir(), "state.json"), "utf8")
|
|
20998
21430
|
);
|
|
20999
21431
|
} catch {
|
|
21000
21432
|
return null;
|
|
@@ -21002,7 +21434,7 @@ function readBrowseState() {
|
|
|
21002
21434
|
}
|
|
21003
21435
|
function writeState(state) {
|
|
21004
21436
|
mkdirSync3(stateDir(), { recursive: true });
|
|
21005
|
-
writeFileSync3(
|
|
21437
|
+
writeFileSync3(path31.join(stateDir(), "state.json"), JSON.stringify(state));
|
|
21006
21438
|
}
|
|
21007
21439
|
function fail(message, opts) {
|
|
21008
21440
|
if (opts?.json) {
|
|
@@ -21066,7 +21498,7 @@ async function requestedViewport(opts) {
|
|
|
21066
21498
|
let manifest;
|
|
21067
21499
|
try {
|
|
21068
21500
|
manifest = JSON.parse(
|
|
21069
|
-
readFileSync6(
|
|
21501
|
+
readFileSync6(path31.join(dir, "walkthrough.json"), "utf8")
|
|
21070
21502
|
);
|
|
21071
21503
|
} catch {
|
|
21072
21504
|
fail(
|
|
@@ -21172,7 +21604,7 @@ async function stop(opts, driver) {
|
|
|
21172
21604
|
await driver.closeBrowser(state.port);
|
|
21173
21605
|
if (state.pid) await reapBrowser(state.pid, profileDir(), driver);
|
|
21174
21606
|
try {
|
|
21175
|
-
unlinkSync3(
|
|
21607
|
+
unlinkSync3(path31.join(stateDir(), "state.json"));
|
|
21176
21608
|
} catch {
|
|
21177
21609
|
}
|
|
21178
21610
|
report2(opts, { stopped: true }, [` ${chalk23.green("\u2713")} Browser stopped`]);
|
|
@@ -21181,8 +21613,8 @@ function computePageFingerprint(url, bodySnippet) {
|
|
|
21181
21613
|
return createHash4("sha256").update(`${url}|${bodySnippet.trim().replace(/\s+/g, " ")}`).digest("hex").slice(0, 16);
|
|
21182
21614
|
}
|
|
21183
21615
|
function checkAndRecordStagnation(projectRoot, runId, fingerprint, action) {
|
|
21184
|
-
const cacheDir2 =
|
|
21185
|
-
const cacheFile =
|
|
21616
|
+
const cacheDir2 = path31.join(projectRoot, "iamu", ".cache");
|
|
21617
|
+
const cacheFile = path31.join(cacheDir2, `stagnation-${runId}.json`);
|
|
21186
21618
|
let previous = null;
|
|
21187
21619
|
try {
|
|
21188
21620
|
if (existsSync12(cacheFile)) {
|
|
@@ -21352,8 +21784,8 @@ async function withPage(opts, driver, fn, target) {
|
|
|
21352
21784
|
let contentSnippet;
|
|
21353
21785
|
if (opts.shot) {
|
|
21354
21786
|
await waitForVisualStability(page);
|
|
21355
|
-
screenshot =
|
|
21356
|
-
mkdirSync3(
|
|
21787
|
+
screenshot = path31.resolve(opts.shot);
|
|
21788
|
+
mkdirSync3(path31.dirname(screenshot), { recursive: true });
|
|
21357
21789
|
await page.screenshot({ path: screenshot, fullPage: Boolean(opts.full) });
|
|
21358
21790
|
if (typeof page.evaluate === "function") {
|
|
21359
21791
|
contentSnippet = await page.evaluate(() => {
|
|
@@ -21518,7 +21950,7 @@ function printStep(opts, record, projectRoot, stagnationWarning, challengeWarnin
|
|
|
21518
21950
|
if (opts.json) {
|
|
21519
21951
|
const doc = {
|
|
21520
21952
|
step_number: step.step_number,
|
|
21521
|
-
path:
|
|
21953
|
+
path: path31.relative(projectRoot, record.stepPath),
|
|
21522
21954
|
action: step.action,
|
|
21523
21955
|
note: step.note,
|
|
21524
21956
|
url: step.url,
|
|
@@ -21621,10 +22053,10 @@ async function browseStep(args, opts, driver) {
|
|
|
21621
22053
|
const runDirPath = resolveRunDir(ctx.projectRoot, opts.run);
|
|
21622
22054
|
if (!runDirPath) return;
|
|
21623
22055
|
const maxSteps = parseMaxSteps(opts.maxSteps);
|
|
21624
|
-
if (await peekNextStepNumber(
|
|
22056
|
+
if (await peekNextStepNumber(path31.join(runDirPath, "steps")) > maxSteps) {
|
|
21625
22057
|
return fail(stepLimitMessage(maxSteps, opts.run), opts);
|
|
21626
22058
|
}
|
|
21627
|
-
const pendingShot =
|
|
22059
|
+
const pendingShot = path31.join(
|
|
21628
22060
|
runDirPath,
|
|
21629
22061
|
"screenshots",
|
|
21630
22062
|
`.pending-${randomUUID9()}.png`
|
|
@@ -22476,10 +22908,10 @@ __export(preflight_exports, {
|
|
|
22476
22908
|
});
|
|
22477
22909
|
import { spawnSync } from "child_process";
|
|
22478
22910
|
import { existsSync as existsSync14, promises as fs34 } from "fs";
|
|
22479
|
-
import
|
|
22911
|
+
import path32 from "path";
|
|
22480
22912
|
import chalk25 from "chalk";
|
|
22481
22913
|
async function detectPreflightCommand(targetDir) {
|
|
22482
|
-
const pkgPath =
|
|
22914
|
+
const pkgPath = path32.join(targetDir, "package.json");
|
|
22483
22915
|
if (existsSync14(pkgPath)) {
|
|
22484
22916
|
try {
|
|
22485
22917
|
const raw = await fs34.readFile(pkgPath, "utf8");
|
|
@@ -22494,12 +22926,12 @@ async function detectPreflightCommand(targetDir) {
|
|
|
22494
22926
|
} catch {
|
|
22495
22927
|
}
|
|
22496
22928
|
}
|
|
22497
|
-
const tsconfigPath =
|
|
22929
|
+
const tsconfigPath = path32.join(targetDir, "tsconfig.json");
|
|
22498
22930
|
if (existsSync14(tsconfigPath)) {
|
|
22499
22931
|
return { command: "npx", args: ["--no-install", "tsc", "--noEmit"] };
|
|
22500
22932
|
}
|
|
22501
|
-
const biomePath =
|
|
22502
|
-
const biomecPath =
|
|
22933
|
+
const biomePath = path32.join(targetDir, "biome.json");
|
|
22934
|
+
const biomecPath = path32.join(targetDir, "biome.jsonc");
|
|
22503
22935
|
if (existsSync14(biomePath) || existsSync14(biomecPath)) {
|
|
22504
22936
|
return { command: "npx", args: ["--no-install", "biome", "check"] };
|
|
22505
22937
|
}
|
|
@@ -22536,7 +22968,7 @@ async function runPreflightCheck(targetDir) {
|
|
|
22536
22968
|
};
|
|
22537
22969
|
}
|
|
22538
22970
|
async function preflight(opts = {}) {
|
|
22539
|
-
const targetDir = opts.cwd ?
|
|
22971
|
+
const targetDir = opts.cwd ? path32.resolve(opts.cwd) : process.cwd();
|
|
22540
22972
|
const result = await runPreflightCheck(targetDir);
|
|
22541
22973
|
if (result.ok) {
|
|
22542
22974
|
if (opts.json) {
|
|
@@ -22608,7 +23040,7 @@ import {
|
|
|
22608
23040
|
readlinkSync,
|
|
22609
23041
|
realpathSync as realpathSync2
|
|
22610
23042
|
} from "fs";
|
|
22611
|
-
import
|
|
23043
|
+
import path33 from "path";
|
|
22612
23044
|
function git(args, cwd) {
|
|
22613
23045
|
return execFileSync("git", args, {
|
|
22614
23046
|
cwd,
|
|
@@ -22661,8 +23093,8 @@ function detectWorktreeState(projectRoot, iamuDirName = IAMU_DIR) {
|
|
|
22661
23093
|
const top = git(["rev-parse", "--show-toplevel"], projectRoot).toString("utf8").trim();
|
|
22662
23094
|
if (!top) return void 0;
|
|
22663
23095
|
const realTop = realpathSync2(top);
|
|
22664
|
-
const realIamu =
|
|
22665
|
-
const iamuRel =
|
|
23096
|
+
const realIamu = path33.join(realpathSync2(projectRoot), iamuDirName);
|
|
23097
|
+
const iamuRel = path33.relative(realTop, realIamu).split(path33.sep).join("/");
|
|
22666
23098
|
const pathspec = iamuRel.startsWith("..") ? [":(top)"] : [":(top)", `:(top,exclude,literal)${iamuRel}`];
|
|
22667
23099
|
const status2 = git(
|
|
22668
23100
|
[
|
|
@@ -22692,7 +23124,7 @@ function detectWorktreeState(projectRoot, iamuDirName = IAMU_DIR) {
|
|
|
22692
23124
|
);
|
|
22693
23125
|
for (const file of sorted.slice(0, MAX_UNTRACKED_HASHED)) {
|
|
22694
23126
|
const rel = file.replace(/\/$/, "");
|
|
22695
|
-
hash.update(`${file}\0${hashFile(
|
|
23127
|
+
hash.update(`${file}\0${hashFile(path33.join(realTop, rel))}
|
|
22696
23128
|
`);
|
|
22697
23129
|
}
|
|
22698
23130
|
if (sorted.length > MAX_UNTRACKED_HASHED) {
|
|
@@ -23298,12 +23730,12 @@ __export(validate_exports, {
|
|
|
23298
23730
|
});
|
|
23299
23731
|
import { randomUUID as randomUUID10 } from "crypto";
|
|
23300
23732
|
import { promises as fs35 } from "fs";
|
|
23301
|
-
import
|
|
23733
|
+
import path34 from "path";
|
|
23302
23734
|
import chalk27 from "chalk";
|
|
23303
23735
|
function inferTypeFromPath(filePath, projectRoot) {
|
|
23304
|
-
const rel =
|
|
23305
|
-
if (rel.startsWith("..") ||
|
|
23306
|
-
const segments = rel.split(
|
|
23736
|
+
const rel = path34.relative(path34.join(projectRoot, "iamu"), filePath);
|
|
23737
|
+
if (rel.startsWith("..") || path34.isAbsolute(rel)) return null;
|
|
23738
|
+
const segments = rel.split(path34.sep);
|
|
23307
23739
|
if (segments.length < 2) return null;
|
|
23308
23740
|
return TYPE_BY_ALIAS.get(segments[0]) ?? null;
|
|
23309
23741
|
}
|
|
@@ -23333,7 +23765,7 @@ function validateFile(filePath, raw, projectRoot) {
|
|
|
23333
23765
|
);
|
|
23334
23766
|
return result;
|
|
23335
23767
|
}
|
|
23336
|
-
if (
|
|
23768
|
+
if (path34.basename(filePath) === "context.yml") {
|
|
23337
23769
|
applyZodValidation(contextFrontmatterSchema, frontmatter, result);
|
|
23338
23770
|
const warnings = [];
|
|
23339
23771
|
const hasNotes = Boolean(
|
|
@@ -23402,7 +23834,7 @@ async function buildRepairLookups(projectRoot) {
|
|
|
23402
23834
|
}
|
|
23403
23835
|
}
|
|
23404
23836
|
const runScenario = /* @__PURE__ */ new Map();
|
|
23405
|
-
const runsDir2 =
|
|
23837
|
+
const runsDir2 = path34.join(projectRoot, "iamu", ".runs");
|
|
23406
23838
|
let runIds = [];
|
|
23407
23839
|
try {
|
|
23408
23840
|
runIds = await fs35.readdir(runsDir2);
|
|
@@ -23412,7 +23844,7 @@ async function buildRepairLookups(projectRoot) {
|
|
|
23412
23844
|
try {
|
|
23413
23845
|
const manifest = JSON.parse(
|
|
23414
23846
|
await fs35.readFile(
|
|
23415
|
-
|
|
23847
|
+
path34.join(runsDir2, runId, "walkthrough.json"),
|
|
23416
23848
|
"utf8"
|
|
23417
23849
|
)
|
|
23418
23850
|
);
|
|
@@ -23443,7 +23875,7 @@ async function fixFindings(projectRoot, results, dryRun) {
|
|
|
23443
23875
|
parsed.frontmatter,
|
|
23444
23876
|
{
|
|
23445
23877
|
...lookups,
|
|
23446
|
-
fileSlug:
|
|
23878
|
+
fileSlug: path34.basename(result.path, ".md"),
|
|
23447
23879
|
newId: randomUUID10,
|
|
23448
23880
|
body: parsed.body
|
|
23449
23881
|
}
|
|
@@ -23482,7 +23914,7 @@ async function validate(filePath, opts) {
|
|
|
23482
23914
|
if (process.exitCode === 1) return;
|
|
23483
23915
|
const results = [];
|
|
23484
23916
|
if (filePath) {
|
|
23485
|
-
const abs =
|
|
23917
|
+
const abs = path34.isAbsolute(filePath) ? filePath : path34.resolve(process.cwd(), filePath);
|
|
23486
23918
|
try {
|
|
23487
23919
|
const raw = await fs35.readFile(abs, "utf8");
|
|
23488
23920
|
results.push(validateFile(abs, raw, projectRoot));
|
|
@@ -23498,7 +23930,7 @@ async function validate(filePath, opts) {
|
|
|
23498
23930
|
return;
|
|
23499
23931
|
}
|
|
23500
23932
|
} else if (opts.run) {
|
|
23501
|
-
const findingsDir =
|
|
23933
|
+
const findingsDir = path34.join(projectRoot, "iamu", ENTITY_DIRS.finding);
|
|
23502
23934
|
let names2 = [];
|
|
23503
23935
|
try {
|
|
23504
23936
|
names2 = await fs35.readdir(findingsDir);
|
|
@@ -23506,7 +23938,7 @@ async function validate(filePath, opts) {
|
|
|
23506
23938
|
}
|
|
23507
23939
|
for (const name of names2) {
|
|
23508
23940
|
if (!name.endsWith(".md")) continue;
|
|
23509
|
-
const abs =
|
|
23941
|
+
const abs = path34.join(findingsDir, name);
|
|
23510
23942
|
try {
|
|
23511
23943
|
const raw = await fs35.readFile(abs, "utf8");
|
|
23512
23944
|
const { frontmatter } = parseFrontmatter(raw);
|
|
@@ -23515,7 +23947,7 @@ async function validate(filePath, opts) {
|
|
|
23515
23947
|
}
|
|
23516
23948
|
} catch {
|
|
23517
23949
|
results.push({
|
|
23518
|
-
path:
|
|
23950
|
+
path: path34.relative(projectRoot, abs),
|
|
23519
23951
|
valid: false,
|
|
23520
23952
|
errors: ["Cannot read file or parse frontmatter"]
|
|
23521
23953
|
});
|
|
@@ -23548,7 +23980,7 @@ async function validate(filePath, opts) {
|
|
|
23548
23980
|
} else {
|
|
23549
23981
|
const typesToCheck = typeFilter ? ENTITY_TYPES.filter((t) => typeFilter.has(t)) : ENTITY_TYPES;
|
|
23550
23982
|
for (const type2 of typesToCheck) {
|
|
23551
|
-
const iamuTypeDir =
|
|
23983
|
+
const iamuTypeDir = path34.join(projectRoot, "iamu", ENTITY_DIRS[type2]);
|
|
23552
23984
|
let names2;
|
|
23553
23985
|
try {
|
|
23554
23986
|
names2 = await fs35.readdir(iamuTypeDir);
|
|
@@ -23557,20 +23989,20 @@ async function validate(filePath, opts) {
|
|
|
23557
23989
|
}
|
|
23558
23990
|
for (const name of names2) {
|
|
23559
23991
|
if (!name.endsWith(".md")) continue;
|
|
23560
|
-
const abs =
|
|
23992
|
+
const abs = path34.join(iamuTypeDir, name);
|
|
23561
23993
|
try {
|
|
23562
23994
|
const raw = await fs35.readFile(abs, "utf8");
|
|
23563
23995
|
results.push(validateFile(abs, raw, projectRoot));
|
|
23564
23996
|
} catch {
|
|
23565
23997
|
results.push({
|
|
23566
|
-
path:
|
|
23998
|
+
path: path34.relative(projectRoot, abs),
|
|
23567
23999
|
valid: false,
|
|
23568
24000
|
errors: ["Cannot read file"]
|
|
23569
24001
|
});
|
|
23570
24002
|
}
|
|
23571
24003
|
}
|
|
23572
24004
|
}
|
|
23573
|
-
const ctxPath =
|
|
24005
|
+
const ctxPath = path34.join(projectRoot, "iamu", "context.yml");
|
|
23574
24006
|
try {
|
|
23575
24007
|
const raw = await fs35.readFile(ctxPath, "utf8");
|
|
23576
24008
|
results.push(validateFile(ctxPath, raw, projectRoot));
|
|
@@ -23595,19 +24027,19 @@ async function validate(filePath, opts) {
|
|
|
23595
24027
|
valid,
|
|
23596
24028
|
errors: results.filter((r) => !r.valid).flatMap(
|
|
23597
24029
|
(r) => r.errors.map((e) => ({
|
|
23598
|
-
path:
|
|
24030
|
+
path: path34.relative(projectRoot, r.path),
|
|
23599
24031
|
message: e
|
|
23600
24032
|
}))
|
|
23601
24033
|
),
|
|
23602
24034
|
warnings: results.filter((r) => r.warnings && r.warnings.length > 0).flatMap(
|
|
23603
24035
|
(r) => (r.warnings ?? []).map((w) => ({
|
|
23604
|
-
path:
|
|
24036
|
+
path: path34.relative(projectRoot, r.path),
|
|
23605
24037
|
message: w
|
|
23606
24038
|
}))
|
|
23607
24039
|
),
|
|
23608
24040
|
...opts.fix ? {
|
|
23609
24041
|
repaired: repaired.map((r) => ({
|
|
23610
|
-
path:
|
|
24042
|
+
path: path34.relative(projectRoot, r.path),
|
|
23611
24043
|
changes: r.repairs,
|
|
23612
24044
|
valid: r.valid
|
|
23613
24045
|
}))
|
|
@@ -23629,7 +24061,7 @@ async function validate(filePath, opts) {
|
|
|
23629
24061
|
}
|
|
23630
24062
|
let hasErrors = false;
|
|
23631
24063
|
for (const r of results) {
|
|
23632
|
-
const relPath =
|
|
24064
|
+
const relPath = path34.relative(projectRoot, r.path);
|
|
23633
24065
|
if (r.valid) {
|
|
23634
24066
|
if (r.warnings && r.warnings.length > 0) {
|
|
23635
24067
|
console.log(` ${symbols.warning} ` + chalk27.yellow(relPath));
|
|
@@ -24030,7 +24462,7 @@ __export(set_entity_exports, {
|
|
|
24030
24462
|
setEntity: () => setEntity
|
|
24031
24463
|
});
|
|
24032
24464
|
import { promises as fs36 } from "fs";
|
|
24033
|
-
import
|
|
24465
|
+
import path35 from "path";
|
|
24034
24466
|
import chalk30 from "chalk";
|
|
24035
24467
|
import { ZodError as ZodError2 } from "zod";
|
|
24036
24468
|
function given(opts, key) {
|
|
@@ -24171,7 +24603,7 @@ async function setEntity(type2, slug, opts) {
|
|
|
24171
24603
|
}
|
|
24172
24604
|
raw = Buffer.concat(chunks).toString("utf8");
|
|
24173
24605
|
} else {
|
|
24174
|
-
raw = await fs36.readFile(
|
|
24606
|
+
raw = await fs36.readFile(path35.resolve(projectRoot, opts.file), "utf8");
|
|
24175
24607
|
}
|
|
24176
24608
|
} catch (err) {
|
|
24177
24609
|
commandError(
|
|
@@ -24599,7 +25031,7 @@ __export(edit_entity_exports, {
|
|
|
24599
25031
|
});
|
|
24600
25032
|
import { spawn as spawn2 } from "child_process";
|
|
24601
25033
|
import { promises as fs37 } from "fs";
|
|
24602
|
-
import
|
|
25034
|
+
import path36 from "path";
|
|
24603
25035
|
import chalk31 from "chalk";
|
|
24604
25036
|
async function editEntity(type2, slug, opts = {}) {
|
|
24605
25037
|
const ctx = await resolveProject(opts);
|
|
@@ -24613,19 +25045,19 @@ async function editEntity(type2, slug, opts = {}) {
|
|
|
24613
25045
|
return;
|
|
24614
25046
|
}
|
|
24615
25047
|
const dir = ENTITY_DIRS[type2];
|
|
24616
|
-
const filePath =
|
|
25048
|
+
const filePath = path36.join(projectRoot, "iamu", dir, `${slug}.md`);
|
|
24617
25049
|
try {
|
|
24618
25050
|
await fs37.access(filePath);
|
|
24619
25051
|
} catch {
|
|
24620
25052
|
commandError(
|
|
24621
|
-
`${type2} "${slug}" not found at ${
|
|
25053
|
+
`${type2} "${slug}" not found at ${path36.relative(projectRoot, filePath)}.`,
|
|
24622
25054
|
{ ...opts, code: "entity_not_found", fix: `iamu list ${type2}s` }
|
|
24623
25055
|
);
|
|
24624
25056
|
return;
|
|
24625
25057
|
}
|
|
24626
25058
|
if (!process.stdin.isTTY || process.env.CI) {
|
|
24627
25059
|
commandError(
|
|
24628
|
-
`Cannot open editor in a non-interactive environment. Edit the file directly: ${
|
|
25060
|
+
`Cannot open editor in a non-interactive environment. Edit the file directly: ${path36.relative(projectRoot, filePath)}`,
|
|
24629
25061
|
{
|
|
24630
25062
|
...opts,
|
|
24631
25063
|
code: "non_interactive_environment",
|
|
@@ -24700,7 +25132,7 @@ async function editEntity(type2, slug, opts = {}) {
|
|
|
24700
25132
|
console.log();
|
|
24701
25133
|
console.log(
|
|
24702
25134
|
chalk31.dim(
|
|
24703
|
-
` Fix the file and run: iamu validate ${
|
|
25135
|
+
` Fix the file and run: iamu validate ${path36.relative(projectRoot, filePath)}`
|
|
24704
25136
|
)
|
|
24705
25137
|
);
|
|
24706
25138
|
console.log();
|
|
@@ -24721,7 +25153,7 @@ async function editEntity(type2, slug, opts = {}) {
|
|
|
24721
25153
|
{
|
|
24722
25154
|
...opts,
|
|
24723
25155
|
code: "validation_error",
|
|
24724
|
-
fix: `iamu validate ${
|
|
25156
|
+
fix: `iamu validate ${path36.relative(projectRoot, filePath)}`
|
|
24725
25157
|
}
|
|
24726
25158
|
);
|
|
24727
25159
|
}
|
|
@@ -24858,7 +25290,7 @@ async function crawlFrom(page, opts, origin, takeBlocked) {
|
|
|
24858
25290
|
const byKey = /* @__PURE__ */ new Map();
|
|
24859
25291
|
const byPlaceView = /* @__PURE__ */ new Map();
|
|
24860
25292
|
const signatureOf = (s) => exploreSignature(s.headings, s.title);
|
|
24861
|
-
function addSurface(state, place, view,
|
|
25293
|
+
function addSurface(state, place, view, path44, depth) {
|
|
24862
25294
|
const key = exploreSurfaceKey(place, view);
|
|
24863
25295
|
const surface = {
|
|
24864
25296
|
key,
|
|
@@ -24870,7 +25302,7 @@ async function crawlFrom(page, opts, origin, takeBlocked) {
|
|
|
24870
25302
|
headings: state.headings,
|
|
24871
25303
|
fields: state.fields,
|
|
24872
25304
|
depth,
|
|
24873
|
-
path:
|
|
25305
|
+
path: path44,
|
|
24874
25306
|
actions: []
|
|
24875
25307
|
};
|
|
24876
25308
|
surfaces.push(surface);
|
|
@@ -24888,9 +25320,9 @@ ${surface.signature}`, key);
|
|
|
24888
25320
|
});
|
|
24889
25321
|
await settle(page, settleMs);
|
|
24890
25322
|
}
|
|
24891
|
-
async function replay(
|
|
24892
|
-
if (!("goto" in (
|
|
24893
|
-
for (const step of
|
|
25323
|
+
async function replay(path44) {
|
|
25324
|
+
if (!("goto" in (path44[0] ?? {}))) await goto(opts.startUrl);
|
|
25325
|
+
for (const step of path44) {
|
|
24894
25326
|
if ("goto" in step) {
|
|
24895
25327
|
await goto(step.goto);
|
|
24896
25328
|
} else {
|
|
@@ -25014,8 +25446,8 @@ ${sig}`, key);
|
|
|
25014
25446
|
if (stats.surfaces >= budget.max_surfaces) {
|
|
25015
25447
|
stats.truncated = true;
|
|
25016
25448
|
} else {
|
|
25017
|
-
const
|
|
25018
|
-
addSurface(after, place, view,
|
|
25449
|
+
const path44 = !sameDoc && view === null ? [{ goto: after.url }] : [...surface.path, ...via, click];
|
|
25450
|
+
addSurface(after, place, view, path44, surface.depth + 1);
|
|
25019
25451
|
}
|
|
25020
25452
|
}
|
|
25021
25453
|
}
|
|
@@ -25215,7 +25647,7 @@ __export(explore_exports, {
|
|
|
25215
25647
|
exploreList: () => exploreList,
|
|
25216
25648
|
exploreRun: () => exploreRun
|
|
25217
25649
|
});
|
|
25218
|
-
import
|
|
25650
|
+
import path37 from "path";
|
|
25219
25651
|
import chalk32 from "chalk";
|
|
25220
25652
|
function fail2(message, json2) {
|
|
25221
25653
|
if (json2) console.error(JSON.stringify({ error: message }));
|
|
@@ -25349,7 +25781,7 @@ async function exploreRun(url, opts) {
|
|
|
25349
25781
|
surfaces: result.surfaces
|
|
25350
25782
|
};
|
|
25351
25783
|
const file = await writeExploreMap(ctx.projectRoot, map2);
|
|
25352
|
-
const rel =
|
|
25784
|
+
const rel = path37.relative(process.cwd(), file);
|
|
25353
25785
|
if (opts.json) {
|
|
25354
25786
|
console.log(JSON.stringify({ id: map2.id, path: rel, stats: map2.stats }));
|
|
25355
25787
|
return;
|
|
@@ -25455,15 +25887,15 @@ async function exploreCoverageCommand(mapId, opts) {
|
|
|
25455
25887
|
" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
|
|
25456
25888
|
)
|
|
25457
25889
|
);
|
|
25458
|
-
for (const
|
|
25459
|
-
const title =
|
|
25460
|
-
const surfaces = `${
|
|
25890
|
+
for (const cap2 of capReport.capabilities) {
|
|
25891
|
+
const title = cap2.slug.padEnd(24);
|
|
25892
|
+
const surfaces = `${cap2.reached_surfaces}/${cap2.total_surfaces}`.padStart(
|
|
25461
25893
|
8
|
|
25462
25894
|
);
|
|
25463
|
-
const covStr = `${
|
|
25464
|
-
const scenarios = `${
|
|
25465
|
-
const untestedFlag =
|
|
25466
|
-
const covColored =
|
|
25895
|
+
const covStr = `${cap2.coverage_pct}%`.padStart(8);
|
|
25896
|
+
const scenarios = `${cap2.primary_scenarios} / ${cap2.secondary_scenarios}`;
|
|
25897
|
+
const untestedFlag = cap2.primary_scenarios === 0 && cap2.unreached_surfaces > 0 ? chalk32.yellow(" (untested)") : "";
|
|
25898
|
+
const covColored = cap2.coverage_pct === 100 ? chalk32.green(covStr) : cap2.coverage_pct === 0 ? chalk32.red(covStr) : chalk32.yellow(covStr);
|
|
25467
25899
|
console.log(
|
|
25468
25900
|
` ${title} ${surfaces} ${covColored} ${scenarios}${untestedFlag}`
|
|
25469
25901
|
);
|
|
@@ -25501,10 +25933,10 @@ async function exploreCoverageCommand(mapId, opts) {
|
|
|
25501
25933
|
const hasGaps = capReport.capabilities.some((c) => c.unreached_surfaces > 0) || capReport.unassigned_surfaces.some((s) => s.status !== "reached");
|
|
25502
25934
|
if (hasGaps) {
|
|
25503
25935
|
console.log(chalk32.red("\n UNREACHED SURFACES (Gaps)"));
|
|
25504
|
-
for (const
|
|
25505
|
-
const gaps =
|
|
25936
|
+
for (const cap2 of capReport.capabilities) {
|
|
25937
|
+
const gaps = cap2.surfaces.filter((s) => s.status !== "reached");
|
|
25506
25938
|
if (gaps.length === 0) continue;
|
|
25507
|
-
console.log(` ${chalk32.bold(
|
|
25939
|
+
console.log(` ${chalk32.bold(cap2.slug)}:`);
|
|
25508
25940
|
for (const s of gaps) {
|
|
25509
25941
|
console.log(
|
|
25510
25942
|
` ${chalk32.red("\u25CB")} ${s.key.padEnd(32)} ${s.label}${chalk32.dim(` (depth ${s.depth})`)}`
|
|
@@ -25525,10 +25957,10 @@ async function exploreCoverageCommand(mapId, opts) {
|
|
|
25525
25957
|
}
|
|
25526
25958
|
if (opts.all) {
|
|
25527
25959
|
console.log(chalk32.green("\n REACHED SURFACES"));
|
|
25528
|
-
for (const
|
|
25529
|
-
const reached =
|
|
25960
|
+
for (const cap2 of capReport.capabilities) {
|
|
25961
|
+
const reached = cap2.surfaces.filter((s) => s.status === "reached");
|
|
25530
25962
|
if (reached.length === 0) continue;
|
|
25531
|
-
console.log(` ${chalk32.bold(
|
|
25963
|
+
console.log(` ${chalk32.bold(cap2.slug)}:`);
|
|
25532
25964
|
for (const s of reached) {
|
|
25533
25965
|
console.log(
|
|
25534
25966
|
` ${chalk32.green("\u2713")} ${s.key.padEnd(32)} ${s.label}${chalk32.dim(` (depth ${s.depth})`)}`
|
|
@@ -25850,7 +26282,7 @@ __export(start_run_exports, {
|
|
|
25850
26282
|
startRun: () => startRun,
|
|
25851
26283
|
testabilityTarget: () => testabilityTarget2
|
|
25852
26284
|
});
|
|
25853
|
-
import
|
|
26285
|
+
import path38 from "path";
|
|
25854
26286
|
import chalk33 from "chalk";
|
|
25855
26287
|
async function testabilityTarget2(projectRoot) {
|
|
25856
26288
|
try {
|
|
@@ -26094,7 +26526,7 @@ async function startRun(scenarioSlugArg, opts) {
|
|
|
26094
26526
|
...viewport ? { viewport } : {}
|
|
26095
26527
|
};
|
|
26096
26528
|
await atomicWriteFile(
|
|
26097
|
-
|
|
26529
|
+
path38.join(run.absPath, "walkthrough.json"),
|
|
26098
26530
|
JSON.stringify(manifest, null, 2) + "\n"
|
|
26099
26531
|
);
|
|
26100
26532
|
const liveRes = await registerLiveRun({
|
|
@@ -26110,7 +26542,7 @@ async function startRun(scenarioSlugArg, opts) {
|
|
|
26110
26542
|
JSON.stringify(
|
|
26111
26543
|
{
|
|
26112
26544
|
run_id: run.runId,
|
|
26113
|
-
path:
|
|
26545
|
+
path: path38.relative(projectRoot, run.absPath),
|
|
26114
26546
|
scenario_slug: scenarioSlug,
|
|
26115
26547
|
target_url: start2.url,
|
|
26116
26548
|
target_reachable: targetReachable,
|
|
@@ -26164,7 +26596,7 @@ async function startRun(scenarioSlugArg, opts) {
|
|
|
26164
26596
|
);
|
|
26165
26597
|
}
|
|
26166
26598
|
console.log(
|
|
26167
|
-
` ${chalk33.dim("path:")} ${
|
|
26599
|
+
` ${chalk33.dim("path:")} ${path38.relative(projectRoot, run.absPath)}`
|
|
26168
26600
|
);
|
|
26169
26601
|
console.log();
|
|
26170
26602
|
console.log(` ${chalk33.bold("Next steps for blind execution:")}`);
|
|
@@ -26394,7 +26826,7 @@ function composeWalkerPrompt(p) {
|
|
|
26394
26826
|
`Screenshots to: ${rd}/screenshots/step-N.png \u2014 absolute paths.`,
|
|
26395
26827
|
`Record findings with: ${iamu} new-finding --scenario ${p.scenarioSlug} --run ${p.runId} --step <N> --title "..." --type <helps|hinders|missing|irrelevant|incorrect> --severity <critical|major|minor|observation> --ui-fact "..." --consequence "..." --task-impact <blocks|impairs|slows|neutral|accelerates|enables> --confidence <high|medium|low> --need "<what you needed, no solution>" --description "..." --json`,
|
|
26396
26828
|
"`--step <N>` is the step whose screenshot shows the problem (or strength): it is the evidence people see next to the finding, so record that step and its screenshot first, and pick the screenshot where the thing is actually visible.",
|
|
26397
|
-
`Before you finish, record synthesis with: ${iamu} summary ${p.runId} --worked "..." --worked "..." --question "..."
|
|
26829
|
+
`Before you finish, record synthesis with: ${iamu} summary ${p.runId} --worked "..." --worked "..." --question "..." [--setup-note "..."]. --worked: what worked for this persona, one point each. --question: decisions for the product team the walk raised. --setup-note: doubts about the test setup (missing data, accounts, dates), never the product.`,
|
|
26398
26830
|
`If the walk cannot start as it was set up \u2014 you cannot become this persona (the switch or login fails, or you are someone else), the world the situation needs is not there and you cannot create it, or the starting page does not load \u2014 stop immediately and end the walk with: ${iamu} summary ${p.runId} --blocked "<what was missing, as you saw it>". Do NOT record preliminary findings if preconditions are not met. That is about the setup, not the product: when the product has no way to do what you need, that is a finding (--type missing), not a block. Do not record findings about the missing setup itself.`,
|
|
26399
26831
|
"Execute everything via CLI commands in the shell. Never invoke host Edit or Write tools on files in the workspace.",
|
|
26400
26832
|
"",
|
|
@@ -27044,7 +27476,7 @@ __export(new_finding_exports, {
|
|
|
27044
27476
|
});
|
|
27045
27477
|
import { randomUUID as randomUUID12 } from "crypto";
|
|
27046
27478
|
import { access, readFile as readFile7 } from "fs/promises";
|
|
27047
|
-
import
|
|
27479
|
+
import path39 from "path";
|
|
27048
27480
|
import chalk36 from "chalk";
|
|
27049
27481
|
async function newFinding(opts) {
|
|
27050
27482
|
const ctx = await resolveProject(opts);
|
|
@@ -27121,7 +27553,7 @@ async function newFinding(opts) {
|
|
|
27121
27553
|
}
|
|
27122
27554
|
try {
|
|
27123
27555
|
await access(
|
|
27124
|
-
|
|
27556
|
+
path39.join(runDirPath, "steps", `${String(n).padStart(3, "0")}.json`)
|
|
27125
27557
|
);
|
|
27126
27558
|
} catch {
|
|
27127
27559
|
commandError(
|
|
@@ -27140,7 +27572,7 @@ async function newFinding(opts) {
|
|
|
27140
27572
|
if (!scenarioSlug) {
|
|
27141
27573
|
try {
|
|
27142
27574
|
const manifestRaw = await readFile7(
|
|
27143
|
-
|
|
27575
|
+
path39.join(runDirPath, "walkthrough.json"),
|
|
27144
27576
|
"utf8"
|
|
27145
27577
|
);
|
|
27146
27578
|
const manifest = JSON.parse(manifestRaw);
|
|
@@ -27287,7 +27719,7 @@ __export(summary_exports, {
|
|
|
27287
27719
|
summaryCommand: () => summaryCommand
|
|
27288
27720
|
});
|
|
27289
27721
|
import { promises as fs38 } from "fs";
|
|
27290
|
-
import
|
|
27722
|
+
import path40 from "path";
|
|
27291
27723
|
import chalk37 from "chalk";
|
|
27292
27724
|
function blockedFrom(value) {
|
|
27293
27725
|
const reason = value && typeof value === "object" ? value.reason : value;
|
|
@@ -27310,12 +27742,18 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
27310
27742
|
if (!runDirPath) return;
|
|
27311
27743
|
let whatWorked = [];
|
|
27312
27744
|
let openQuestions = [];
|
|
27745
|
+
let setupNotes = [];
|
|
27313
27746
|
let model = opts.model?.trim() || void 0;
|
|
27314
|
-
const summaryPath =
|
|
27747
|
+
const summaryPath = path40.join(runDirPath, "summary.json");
|
|
27315
27748
|
let blocked;
|
|
27316
27749
|
try {
|
|
27317
27750
|
const prev = JSON.parse(await fs38.readFile(summaryPath, "utf8"));
|
|
27318
27751
|
blocked = blockedFrom(prev.blocked);
|
|
27752
|
+
if (Array.isArray(prev.setup_notes)) {
|
|
27753
|
+
setupNotes = prev.setup_notes.filter(
|
|
27754
|
+
(x) => typeof x === "string" && x.trim() !== ""
|
|
27755
|
+
);
|
|
27756
|
+
}
|
|
27319
27757
|
} catch {
|
|
27320
27758
|
}
|
|
27321
27759
|
if (opts.file) {
|
|
@@ -27324,7 +27762,7 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
27324
27762
|
if (opts.file === "-") {
|
|
27325
27763
|
raw = await readStdinText();
|
|
27326
27764
|
} else {
|
|
27327
|
-
raw = await fs38.readFile(
|
|
27765
|
+
raw = await fs38.readFile(path40.resolve(projectRoot, opts.file), "utf8");
|
|
27328
27766
|
}
|
|
27329
27767
|
const parsed = JSON.parse(raw);
|
|
27330
27768
|
if (Array.isArray(parsed.what_worked)) {
|
|
@@ -27337,6 +27775,11 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
27337
27775
|
(x) => typeof x === "string" && x.trim() !== ""
|
|
27338
27776
|
);
|
|
27339
27777
|
}
|
|
27778
|
+
if (Array.isArray(parsed.setup_notes)) {
|
|
27779
|
+
setupNotes = parsed.setup_notes.filter(
|
|
27780
|
+
(x) => typeof x === "string" && x.trim() !== ""
|
|
27781
|
+
);
|
|
27782
|
+
}
|
|
27340
27783
|
if (typeof parsed.model === "string" && parsed.model.trim()) {
|
|
27341
27784
|
model = parsed.model.trim();
|
|
27342
27785
|
}
|
|
@@ -27362,6 +27805,7 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
27362
27805
|
if (cliQuestions.length > 0) {
|
|
27363
27806
|
openQuestions = [...openQuestions, ...cliQuestions];
|
|
27364
27807
|
}
|
|
27808
|
+
setupNotes = [.../* @__PURE__ */ new Set([...setupNotes, ...toList(opts.setupNote)])];
|
|
27365
27809
|
if (opts.blocked === false) {
|
|
27366
27810
|
blocked = void 0;
|
|
27367
27811
|
} else {
|
|
@@ -27370,6 +27814,7 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
27370
27814
|
const data = {
|
|
27371
27815
|
what_worked: whatWorked,
|
|
27372
27816
|
open_questions: openQuestions,
|
|
27817
|
+
setup_notes: setupNotes,
|
|
27373
27818
|
...model ? { model } : {},
|
|
27374
27819
|
...blocked ? { blocked } : {}
|
|
27375
27820
|
};
|
|
@@ -27380,7 +27825,7 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
27380
27825
|
JSON.stringify(
|
|
27381
27826
|
{
|
|
27382
27827
|
run_id: runId,
|
|
27383
|
-
path:
|
|
27828
|
+
path: path40.relative(projectRoot, summaryPath),
|
|
27384
27829
|
summary: data
|
|
27385
27830
|
},
|
|
27386
27831
|
null,
|
|
@@ -27406,6 +27851,11 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
27406
27851
|
` ${chalk37.dim("open questions:")} ${data.open_questions.length} item(s)`
|
|
27407
27852
|
);
|
|
27408
27853
|
}
|
|
27854
|
+
if (data.setup_notes.length > 0) {
|
|
27855
|
+
console.log(
|
|
27856
|
+
` ${chalk37.dim("setup notes:")} ${data.setup_notes.length} item(s)`
|
|
27857
|
+
);
|
|
27858
|
+
}
|
|
27409
27859
|
console.log();
|
|
27410
27860
|
}
|
|
27411
27861
|
var init_summary = __esm({
|
|
@@ -27440,7 +27890,7 @@ __export(complete_exports, {
|
|
|
27440
27890
|
validateCanonicalWalkthrough: () => validateCanonicalWalkthrough
|
|
27441
27891
|
});
|
|
27442
27892
|
import { promises as fs39 } from "fs";
|
|
27443
|
-
import
|
|
27893
|
+
import path41 from "path";
|
|
27444
27894
|
import chalk38 from "chalk";
|
|
27445
27895
|
async function complete(runIdArg, opts) {
|
|
27446
27896
|
const ctx = await resolveProject(opts);
|
|
@@ -27477,7 +27927,7 @@ async function complete(runIdArg, opts) {
|
|
|
27477
27927
|
let judgeReported;
|
|
27478
27928
|
try {
|
|
27479
27929
|
const verdictsRaw = await fs39.readFile(
|
|
27480
|
-
|
|
27930
|
+
path41.join(run.runDirPath, "verdicts.json"),
|
|
27481
27931
|
"utf8"
|
|
27482
27932
|
);
|
|
27483
27933
|
const parsed = JSON.parse(verdictsRaw);
|
|
@@ -27553,7 +28003,7 @@ async function loadRun(runId, opts) {
|
|
|
27553
28003
|
});
|
|
27554
28004
|
return null;
|
|
27555
28005
|
}
|
|
27556
|
-
const manifestPath =
|
|
28006
|
+
const manifestPath = path41.join(runDirPath, "walkthrough.json");
|
|
27557
28007
|
let manifest;
|
|
27558
28008
|
try {
|
|
27559
28009
|
manifest = JSON.parse(
|
|
@@ -27584,7 +28034,7 @@ async function reportAlreadyCompleted(run, opts) {
|
|
|
27584
28034
|
if (opts?.json) {
|
|
27585
28035
|
try {
|
|
27586
28036
|
const raw = await fs39.readFile(
|
|
27587
|
-
|
|
28037
|
+
path41.join(run.runDirPath, "canonical.json"),
|
|
27588
28038
|
"utf8"
|
|
27589
28039
|
);
|
|
27590
28040
|
console.log(raw.trim());
|
|
@@ -27720,15 +28170,11 @@ function printCompletionSummary(run, completedAt, assembled, extras, provenanceA
|
|
|
27720
28170
|
}
|
|
27721
28171
|
console.log();
|
|
27722
28172
|
console.log(
|
|
27723
|
-
` ${chalk38.dim("canonical:")} ${chalk38.dim(
|
|
27724
|
-
);
|
|
27725
|
-
console.log(
|
|
27726
|
-
` ${chalk38.dim("report:")} ${chalk38.dim(path40.relative(projectRoot, assembled.reportPath))}`
|
|
28173
|
+
` ${chalk38.dim("canonical:")} ${chalk38.dim(path41.relative(projectRoot, assembled.canonicalPath))}`
|
|
27727
28174
|
);
|
|
28175
|
+
console.log(` ${chalk38.dim("report:")} ${assembled.reportPath}`);
|
|
27728
28176
|
if (assembled.htmlReportPath) {
|
|
27729
|
-
console.log(
|
|
27730
|
-
` ${chalk38.dim("html:")} ${chalk38.dim(path40.relative(projectRoot, assembled.htmlReportPath))}`
|
|
27731
|
-
);
|
|
28177
|
+
console.log(` ${chalk38.dim("html:")} ${assembled.htmlReportPath}`);
|
|
27732
28178
|
}
|
|
27733
28179
|
if (extras.viewportWarning) {
|
|
27734
28180
|
console.log();
|
|
@@ -27797,7 +28243,7 @@ function screenshotFitsViewport(width, expected) {
|
|
|
27797
28243
|
}
|
|
27798
28244
|
async function screenshotSizeWarning(runDirPath, viewport) {
|
|
27799
28245
|
if (!viewport) return null;
|
|
27800
|
-
const dir =
|
|
28246
|
+
const dir = path41.join(runDirPath, "screenshots");
|
|
27801
28247
|
let first;
|
|
27802
28248
|
try {
|
|
27803
28249
|
first = (await fs39.readdir(dir)).filter((f) => f.toLowerCase().endsWith(".png")).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }))[0];
|
|
@@ -27807,7 +28253,7 @@ async function screenshotSizeWarning(runDirPath, viewport) {
|
|
|
27807
28253
|
if (!first) return null;
|
|
27808
28254
|
let size = null;
|
|
27809
28255
|
try {
|
|
27810
|
-
const handle = await fs39.open(
|
|
28256
|
+
const handle = await fs39.open(path41.join(dir, first), "r");
|
|
27811
28257
|
try {
|
|
27812
28258
|
const head = Buffer.alloc(24);
|
|
27813
28259
|
const { bytesRead } = await handle.read(head, 0, 24, 0);
|
|
@@ -27824,7 +28270,7 @@ async function screenshotSizeWarning(runDirPath, viewport) {
|
|
|
27824
28270
|
async function readWalkerSummary(runDirPath) {
|
|
27825
28271
|
try {
|
|
27826
28272
|
const data = JSON.parse(
|
|
27827
|
-
await fs39.readFile(
|
|
28273
|
+
await fs39.readFile(path41.join(runDirPath, "summary.json"), "utf8")
|
|
27828
28274
|
);
|
|
27829
28275
|
const strings = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && x.trim() !== "") : void 0;
|
|
27830
28276
|
const model = typeof data.model === "string" && data.model.trim() !== "" ? data.model.trim() : void 0;
|
|
@@ -27832,6 +28278,7 @@ async function readWalkerSummary(runDirPath) {
|
|
|
27832
28278
|
return {
|
|
27833
28279
|
what_worked: strings(data.what_worked),
|
|
27834
28280
|
open_questions: strings(data.open_questions),
|
|
28281
|
+
setup_notes: strings(data.setup_notes),
|
|
27835
28282
|
model,
|
|
27836
28283
|
...typeof reason === "string" && reason.trim() !== "" ? { blocked: { reason: reason.trim() } } : {}
|
|
27837
28284
|
};
|
|
@@ -27933,7 +28380,7 @@ function validateCanonicalWalkthrough(canonical) {
|
|
|
27933
28380
|
return canonicalWalkthroughSchema.parse(canonical);
|
|
27934
28381
|
}
|
|
27935
28382
|
async function persistCanonical(runDirPath, canonical) {
|
|
27936
|
-
const canonicalPath =
|
|
28383
|
+
const canonicalPath = path41.join(runDirPath, "canonical.json");
|
|
27937
28384
|
await atomicWriteFile(
|
|
27938
28385
|
canonicalPath,
|
|
27939
28386
|
`${JSON.stringify(canonical, null, 2)}
|
|
@@ -27942,7 +28389,7 @@ async function persistCanonical(runDirPath, canonical) {
|
|
|
27942
28389
|
return canonicalPath;
|
|
27943
28390
|
}
|
|
27944
28391
|
async function persistReport(runDirPath, canonical) {
|
|
27945
|
-
const reportPath =
|
|
28392
|
+
const reportPath = path41.join(runDirPath, "report.md");
|
|
27946
28393
|
await atomicWriteFile(reportPath, renderCanonicalMarkdown(canonical));
|
|
27947
28394
|
return reportPath;
|
|
27948
28395
|
}
|
|
@@ -28121,13 +28568,13 @@ __export(match_exports, {
|
|
|
28121
28568
|
matchRecord: () => matchRecord
|
|
28122
28569
|
});
|
|
28123
28570
|
import { promises as fs40 } from "fs";
|
|
28124
|
-
import
|
|
28571
|
+
import path42 from "path";
|
|
28125
28572
|
import chalk39 from "chalk";
|
|
28126
28573
|
async function completedManifest2(runDirPath, runId, opts) {
|
|
28127
28574
|
let manifest;
|
|
28128
28575
|
try {
|
|
28129
28576
|
manifest = JSON.parse(
|
|
28130
|
-
await fs40.readFile(
|
|
28577
|
+
await fs40.readFile(path42.join(runDirPath, "walkthrough.json"), "utf8")
|
|
28131
28578
|
);
|
|
28132
28579
|
} catch {
|
|
28133
28580
|
commandError(`Run not found: ${runId}`, {
|
|
@@ -28164,7 +28611,7 @@ async function match(runId, opts) {
|
|
|
28164
28611
|
);
|
|
28165
28612
|
const needed = !manifest.blocked && input.current.length > 0 && (input.prior.length > 0 || (input.guidance ?? []).length > 0);
|
|
28166
28613
|
const prompt = needed ? composeMatchPrompt(input) : null;
|
|
28167
|
-
const promptFilePath = opts?.promptFile ??
|
|
28614
|
+
const promptFilePath = opts?.promptFile ?? path42.join(runDirPath, "match_prompt.md");
|
|
28168
28615
|
if (prompt) {
|
|
28169
28616
|
await fs40.writeFile(promptFilePath, prompt, "utf8");
|
|
28170
28617
|
} else {
|
|
@@ -28180,8 +28627,8 @@ async function match(runId, opts) {
|
|
|
28180
28627
|
...input,
|
|
28181
28628
|
needed,
|
|
28182
28629
|
...manifest.blocked ? { blocked: manifest.blocked } : {},
|
|
28183
|
-
prompt_file: prompt ?
|
|
28184
|
-
prompt_file_abs: prompt ?
|
|
28630
|
+
prompt_file: prompt ? path42.relative(ctx.projectRoot, promptFilePath) : null,
|
|
28631
|
+
prompt_file_abs: prompt ? path42.resolve(promptFilePath) : null,
|
|
28185
28632
|
match_prompt: prompt
|
|
28186
28633
|
},
|
|
28187
28634
|
null,
|
|
@@ -28209,7 +28656,7 @@ async function matchRecord(runId, opts) {
|
|
|
28209
28656
|
const manifest = await completedManifest2(runDirPath, runId, opts);
|
|
28210
28657
|
if (!manifest) return;
|
|
28211
28658
|
const input = await readAnswerInput(opts, "matches", {
|
|
28212
|
-
defaultPath:
|
|
28659
|
+
defaultPath: path42.join(runDirPath, "matches.json"),
|
|
28213
28660
|
code: "missing_matches_input",
|
|
28214
28661
|
fix: `iamu match-record ${runId} --data '<json>'`
|
|
28215
28662
|
});
|
|
@@ -28288,10 +28735,10 @@ __export(advise_exports, {
|
|
|
28288
28735
|
readAdvisePrompt: () => readAdvisePrompt
|
|
28289
28736
|
});
|
|
28290
28737
|
import { existsSync as existsSync17, promises as fs41, readFileSync as readFileSync10 } from "fs";
|
|
28291
|
-
import
|
|
28738
|
+
import path43 from "path";
|
|
28292
28739
|
import chalk40 from "chalk";
|
|
28293
28740
|
function readAdvisePrompt(projectRoot) {
|
|
28294
|
-
const installed =
|
|
28741
|
+
const installed = path43.join(projectRoot, "iamu", ".prompts", "advise.md");
|
|
28295
28742
|
if (existsSync17(installed)) {
|
|
28296
28743
|
try {
|
|
28297
28744
|
return readFileSync10(installed, "utf8");
|
|
@@ -28336,7 +28783,7 @@ async function advise(runId, opts = {}) {
|
|
|
28336
28783
|
findings2 = input.findings.length;
|
|
28337
28784
|
needed = findings2 >= 2;
|
|
28338
28785
|
if (!needed) reason = "fewer than two open findings in the project";
|
|
28339
|
-
promptPath = opts.promptFile ??
|
|
28786
|
+
promptPath = opts.promptFile ?? path43.join(analysisDir(projectRoot), "advise_prompt.md");
|
|
28340
28787
|
} else {
|
|
28341
28788
|
if (!requireRunId(runId, opts, "iamu advise <run-id>")) return;
|
|
28342
28789
|
const runDirPath = resolveRunDir(projectRoot, runId, opts);
|
|
@@ -28346,21 +28793,21 @@ async function advise(runId, opts = {}) {
|
|
|
28346
28793
|
input = await buildAdviseInput(projectRoot, runId, manifest.scenario_slug);
|
|
28347
28794
|
refsDir = runDirPath;
|
|
28348
28795
|
findings2 = input.findings.length;
|
|
28349
|
-
const advised = existsSync17(
|
|
28796
|
+
const advised = existsSync17(path43.join(runDirPath, ADVICE_FILE));
|
|
28350
28797
|
needed = !manifest.blocked && findings2 > 0 && !advised;
|
|
28351
28798
|
if (manifest.blocked) reason = "the run was blocked";
|
|
28352
28799
|
else if (advised) reason = "the run is already advised";
|
|
28353
28800
|
else if (findings2 === 0) reason = "no open findings to advise";
|
|
28354
|
-
promptPath = opts.promptFile ??
|
|
28801
|
+
promptPath = opts.promptFile ?? path43.join(runDirPath, "advise_prompt.md");
|
|
28355
28802
|
}
|
|
28356
28803
|
const { bundle, refs } = promptBundle(input);
|
|
28357
28804
|
const prompt = needed ? composePrompt(projectRoot, bundle) : null;
|
|
28358
28805
|
if (prompt) {
|
|
28359
|
-
await fs41.mkdir(
|
|
28806
|
+
await fs41.mkdir(path43.dirname(promptPath), { recursive: true });
|
|
28360
28807
|
await fs41.writeFile(promptPath, prompt, "utf8");
|
|
28361
28808
|
await fs41.mkdir(refsDir, { recursive: true });
|
|
28362
28809
|
await fs41.writeFile(
|
|
28363
|
-
|
|
28810
|
+
path43.join(refsDir, ADVISE_REFS_FILE),
|
|
28364
28811
|
`${JSON.stringify(refs, null, 2)}
|
|
28365
28812
|
`,
|
|
28366
28813
|
"utf8"
|
|
@@ -28374,8 +28821,8 @@ async function advise(runId, opts = {}) {
|
|
|
28374
28821
|
...reason ? { reason } : {},
|
|
28375
28822
|
mode: opts.project ? "project" : "run",
|
|
28376
28823
|
findings: findings2,
|
|
28377
|
-
prompt_file: prompt ?
|
|
28378
|
-
prompt_file_abs: prompt ?
|
|
28824
|
+
prompt_file: prompt ? path43.relative(projectRoot, promptPath) : null,
|
|
28825
|
+
prompt_file_abs: prompt ? path43.resolve(promptPath) : null,
|
|
28379
28826
|
prompt_chars: prompt?.length ?? 0,
|
|
28380
28827
|
advise_prompt: prompt
|
|
28381
28828
|
},
|
|
@@ -28439,7 +28886,7 @@ async function adviseRecord(runId, opts) {
|
|
|
28439
28886
|
const { projectRoot } = ctx;
|
|
28440
28887
|
if (opts.project) {
|
|
28441
28888
|
const input2 = await readAnswerInput(opts, "advice", {
|
|
28442
|
-
defaultPath:
|
|
28889
|
+
defaultPath: path43.join(analysisDir(projectRoot), "answer.json"),
|
|
28443
28890
|
code: "missing_advice_input",
|
|
28444
28891
|
fix: "iamu advise-record --project --data '<json>'"
|
|
28445
28892
|
});
|
|
@@ -28469,7 +28916,7 @@ async function adviseRecord(runId, opts) {
|
|
|
28469
28916
|
const manifest = await completedManifest2(runDirPath, runId, opts);
|
|
28470
28917
|
if (!manifest) return;
|
|
28471
28918
|
const input = await readAnswerInput(opts, "advice", {
|
|
28472
|
-
defaultPath:
|
|
28919
|
+
defaultPath: path43.join(runDir(projectRoot, runId), "advise_answer.json"),
|
|
28473
28920
|
code: "missing_advice_input",
|
|
28474
28921
|
fix: `iamu advise-record ${runId} --data '<json>'`
|
|
28475
28922
|
});
|
|
@@ -28503,9 +28950,9 @@ async function directionsList(opts) {
|
|
|
28503
28950
|
const ctx = await resolveProject(opts);
|
|
28504
28951
|
if (!ctx) return;
|
|
28505
28952
|
const all = await listDirections(ctx.projectRoot);
|
|
28506
|
-
const
|
|
28953
|
+
const rank2 = (s) => ["accepted", "proposed", "done", "rejected"].indexOf(s);
|
|
28507
28954
|
const sorted = [...all].sort(
|
|
28508
|
-
(a, b) =>
|
|
28955
|
+
(a, b) => rank2(a.frontmatter.status) - rank2(b.frontmatter.status)
|
|
28509
28956
|
);
|
|
28510
28957
|
if (opts.json) {
|
|
28511
28958
|
console.log(
|
|
@@ -28515,7 +28962,7 @@ async function directionsList(opts) {
|
|
|
28515
28962
|
problem: d.texts.problem,
|
|
28516
28963
|
change: d.texts.change,
|
|
28517
28964
|
leading_practice: d.texts.leading_practice,
|
|
28518
|
-
path:
|
|
28965
|
+
path: path43.relative(ctx.projectRoot, d.path)
|
|
28519
28966
|
})),
|
|
28520
28967
|
null,
|
|
28521
28968
|
2
|
|
@@ -28896,9 +29343,9 @@ function registerProjectCommands(program2) {
|
|
|
28896
29343
|
"after",
|
|
28897
29344
|
"\nExamples:\n $ iamu validate --all\n $ iamu validate --all --fix --dry-run\n $ iamu validate iamu/personas/maria.md"
|
|
28898
29345
|
).action(
|
|
28899
|
-
async (
|
|
29346
|
+
async (path44, opts) => {
|
|
28900
29347
|
const { validate: validate2 } = await Promise.resolve().then(() => (init_validate(), validate_exports));
|
|
28901
|
-
await validate2(
|
|
29348
|
+
await validate2(path44, opts);
|
|
28902
29349
|
}
|
|
28903
29350
|
);
|
|
28904
29351
|
program2.command("update").helpGroup("Project:").description("Update and repair agent skills and prompts to latest version").option(
|
|
@@ -29272,13 +29719,16 @@ function registerWalkthroughCommands(program2) {
|
|
|
29272
29719
|
}
|
|
29273
29720
|
);
|
|
29274
29721
|
program2.command("summary [run-id]").helpGroup("Walkthrough:").description(
|
|
29275
|
-
"Record walker summary (what worked, open questions, or that the walk was blocked) atomically"
|
|
29722
|
+
"Record walker summary (what worked, open questions, setup notes, or that the walk was blocked) atomically"
|
|
29276
29723
|
).option("--run <run-id>", "Run ID (or pass as positional argument)").option(
|
|
29277
29724
|
"--worked <note...>",
|
|
29278
29725
|
"Things that worked well (can be specified multiple times)"
|
|
29279
29726
|
).option(
|
|
29280
29727
|
"--question <note...>",
|
|
29281
|
-
"
|
|
29728
|
+
"Questions for the product team the walk raised (can be specified multiple times)"
|
|
29729
|
+
).option(
|
|
29730
|
+
"--setup-note <note...>",
|
|
29731
|
+
"Doubts about the test setup, not the product: missing data, accounts, dates (can be specified multiple times)"
|
|
29282
29732
|
).option("--model <name>", "Model identifier (e.g. flash, haiku)").option(
|
|
29283
29733
|
"--blocked <reason>",
|
|
29284
29734
|
"The walk could not start as set up (persona unreachable, world missing): what was missing"
|