@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/index.js
CHANGED
|
@@ -1432,7 +1432,11 @@ var init_walkthrough_canonical = __esm({
|
|
|
1432
1432
|
slug: z22.string(),
|
|
1433
1433
|
persona_slug: z22.string(),
|
|
1434
1434
|
outline_slug: z22.string(),
|
|
1435
|
-
brief: z22.string()
|
|
1435
|
+
brief: z22.string(),
|
|
1436
|
+
/** Display names, so reports never show slugs. Absent on older runs. */
|
|
1437
|
+
persona_name: z22.string().optional(),
|
|
1438
|
+
persona_role: z22.string().optional(),
|
|
1439
|
+
outline_title: z22.string().optional()
|
|
1436
1440
|
}).passthrough();
|
|
1437
1441
|
canonicalTargetSchema = z22.object({
|
|
1438
1442
|
url: z22.string(),
|
|
@@ -1466,6 +1470,10 @@ var init_walkthrough_canonical = __esm({
|
|
|
1466
1470
|
}).passthrough();
|
|
1467
1471
|
canonicalFindingSchema = z22.object({
|
|
1468
1472
|
id: z22.string(),
|
|
1473
|
+
/** The finding's short title. Absent on older runs. */
|
|
1474
|
+
title: z22.string().nullable().optional(),
|
|
1475
|
+
/** The page it was seen on (the finding's own, else its step's). */
|
|
1476
|
+
page_url: z22.string().nullable().optional(),
|
|
1469
1477
|
/** Stable across runs for pattern tracking — null until cross-run wiring lands. */
|
|
1470
1478
|
canonical_id: z22.string().nullable(),
|
|
1471
1479
|
type: findingTypeEnum,
|
|
@@ -1727,14 +1735,14 @@ Bad background: "Construction worker. Experienced in the field. Uses a smartphon
|
|
|
1727
1735
|
- observation: An enhancement opportunity. Not friction, but a missed chance to help.
|
|
1728
1736
|
|
|
1729
1737
|
Finding type pairing:
|
|
1730
|
-
- helps \u2192 always observation severity. Record
|
|
1738
|
+
- helps \u2192 always observation severity. Record each one you meet: something the product does that this person would miss if it were gone.
|
|
1731
1739
|
- hinders \u2192 critical, major, or minor.
|
|
1732
1740
|
- missing \u2192 critical, major, or minor.
|
|
1733
1741
|
- incorrect \u2192 critical or major only.
|
|
1734
1742
|
- irrelevant \u2192 minor or observation.
|
|
1735
1743
|
|
|
1736
1744
|
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.`;
|
|
1737
|
-
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
|
|
1745
|
+
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.`;
|
|
1738
1746
|
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.
|
|
1739
1747
|
|
|
1740
1748
|
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.
|
|
@@ -1917,7 +1925,7 @@ var init_prompt_registry = __esm({
|
|
|
1917
1925
|
},
|
|
1918
1926
|
walkthrough: {
|
|
1919
1927
|
id: "walkthroughPhilosophy",
|
|
1920
|
-
version: "2.
|
|
1928
|
+
version: "2.1.0",
|
|
1921
1929
|
compose: (opts) => composeWalkthroughPhilosophy(opts)
|
|
1922
1930
|
},
|
|
1923
1931
|
scenarioCraft: {
|
|
@@ -6848,413 +6856,196 @@ var init_revisit = __esm({
|
|
|
6848
6856
|
}
|
|
6849
6857
|
});
|
|
6850
6858
|
|
|
6851
|
-
// ../../packages/local-store/dist/
|
|
6852
|
-
|
|
6853
|
-
|
|
6859
|
+
// ../../packages/local-store/dist/directions.js
|
|
6860
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
6861
|
+
import { promises as fs12 } from "fs";
|
|
6862
|
+
import path9 from "path";
|
|
6863
|
+
import { z as z25 } from "zod";
|
|
6864
|
+
function directionsDir(projectRoot) {
|
|
6865
|
+
return path9.join(iamuDir(projectRoot), DIRECTIONS_DIR);
|
|
6854
6866
|
}
|
|
6855
|
-
function
|
|
6856
|
-
|
|
6857
|
-
return 0;
|
|
6858
|
-
if (!a.length)
|
|
6859
|
-
return b.length;
|
|
6860
|
-
if (!b.length)
|
|
6861
|
-
return a.length;
|
|
6862
|
-
const row = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
6863
|
-
for (let i = 1; i <= a.length; i++) {
|
|
6864
|
-
let prev = i;
|
|
6865
|
-
for (let j = 1; j <= b.length; j++) {
|
|
6866
|
-
const val = a[i - 1] === b[j - 1] ? row[j - 1] : Math.min(row[j - 1], prev, row[j]) + 1;
|
|
6867
|
-
row[j - 1] = prev;
|
|
6868
|
-
prev = val;
|
|
6869
|
-
}
|
|
6870
|
-
row[b.length] = prev;
|
|
6871
|
-
}
|
|
6872
|
-
return row[b.length];
|
|
6867
|
+
function directionPath(projectRoot, slug) {
|
|
6868
|
+
return path9.join(directionsDir(projectRoot), `${slug}.md`);
|
|
6873
6869
|
}
|
|
6874
|
-
function
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
6870
|
+
function renderBody(texts) {
|
|
6871
|
+
return [
|
|
6872
|
+
"## Problem",
|
|
6873
|
+
"",
|
|
6874
|
+
texts.problem.trim(),
|
|
6875
|
+
"",
|
|
6876
|
+
"## Change",
|
|
6877
|
+
"",
|
|
6878
|
+
texts.change.trim(),
|
|
6879
|
+
"",
|
|
6880
|
+
"## Leading practice",
|
|
6881
|
+
"",
|
|
6882
|
+
texts.leading_practice.trim(),
|
|
6883
|
+
""
|
|
6884
|
+
].join("\n");
|
|
6878
6885
|
}
|
|
6879
|
-
function
|
|
6880
|
-
const
|
|
6881
|
-
|
|
6882
|
-
return /* @__PURE__ */ new Set();
|
|
6883
|
-
return new Set(norm.split(/\s+/).filter(Boolean));
|
|
6886
|
+
function section(body, heading) {
|
|
6887
|
+
const re = new RegExp(`^## ${heading}\\s*\\n([\\s\\S]*?)(?=^## |$(?![\\s\\S]))`, "m");
|
|
6888
|
+
return re.exec(body)?.[1]?.trim() ?? "";
|
|
6884
6889
|
}
|
|
6885
|
-
function
|
|
6886
|
-
const
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
}
|
|
6907
|
-
return 0;
|
|
6908
|
-
}
|
|
6909
|
-
const union2 = tokensA.size + tokensB.size - intersection;
|
|
6910
|
-
const jaccard = union2 > 0 ? intersection / union2 : 0;
|
|
6911
|
-
const minSize = Math.min(tokensA.size, tokensB.size);
|
|
6912
|
-
const subsetRatio = minSize > 0 ? intersection / minSize : 0;
|
|
6913
|
-
if (normA.includes(normB) || normB.includes(normA) || subsetRatio === 1) {
|
|
6914
|
-
return Math.max(0.85, 0.7 + 0.3 * jaccard);
|
|
6890
|
+
function parseDirection(raw, file) {
|
|
6891
|
+
const { frontmatter, body } = parseFrontmatter(raw);
|
|
6892
|
+
return {
|
|
6893
|
+
frontmatter: directionFrontmatterSchema.parse(frontmatter),
|
|
6894
|
+
texts: {
|
|
6895
|
+
problem: section(body, "Problem"),
|
|
6896
|
+
change: section(body, "Change"),
|
|
6897
|
+
leading_practice: section(body, "Leading practice")
|
|
6898
|
+
},
|
|
6899
|
+
path: file
|
|
6900
|
+
};
|
|
6901
|
+
}
|
|
6902
|
+
async function readDirection(projectRoot, slug) {
|
|
6903
|
+
const file = directionPath(projectRoot, slug);
|
|
6904
|
+
let raw;
|
|
6905
|
+
try {
|
|
6906
|
+
raw = await fs12.readFile(file, "utf8");
|
|
6907
|
+
} catch (err) {
|
|
6908
|
+
if (err.code === "ENOENT")
|
|
6909
|
+
return null;
|
|
6910
|
+
throw err;
|
|
6915
6911
|
}
|
|
6916
|
-
return
|
|
6912
|
+
return parseDirection(raw, file);
|
|
6917
6913
|
}
|
|
6918
|
-
function
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
urlScore = 1;
|
|
6927
|
-
} else {
|
|
6928
|
-
const pathA = normUrlA.split("?")[0];
|
|
6929
|
-
const pathB = normUrlB.split("?")[0];
|
|
6930
|
-
urlScore = pathA === pathB ? 0.8 : 0;
|
|
6931
|
-
}
|
|
6914
|
+
async function listDirections(projectRoot) {
|
|
6915
|
+
let names2;
|
|
6916
|
+
try {
|
|
6917
|
+
names2 = await fs12.readdir(directionsDir(projectRoot));
|
|
6918
|
+
} catch (err) {
|
|
6919
|
+
if (err.code === "ENOENT")
|
|
6920
|
+
return [];
|
|
6921
|
+
throw err;
|
|
6932
6922
|
}
|
|
6933
|
-
const
|
|
6934
|
-
const
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
} else {
|
|
6942
|
-
surfaceScore = 0;
|
|
6923
|
+
const out = [];
|
|
6924
|
+
for (const name of names2.sort()) {
|
|
6925
|
+
if (!name.endsWith(".md"))
|
|
6926
|
+
continue;
|
|
6927
|
+
const file = path9.join(directionsDir(projectRoot), name);
|
|
6928
|
+
try {
|
|
6929
|
+
out.push(parseDirection(await fs12.readFile(file, "utf8"), file));
|
|
6930
|
+
} catch {
|
|
6943
6931
|
}
|
|
6944
6932
|
}
|
|
6945
|
-
|
|
6946
|
-
if (urlScore >= 0.8 && surfaceScore >= 0.8)
|
|
6947
|
-
return 1;
|
|
6948
|
-
if (urlScore >= 0.8)
|
|
6949
|
-
return 0.85;
|
|
6950
|
-
if (surfaceScore >= 0.8)
|
|
6951
|
-
return 0.75;
|
|
6952
|
-
return 0;
|
|
6953
|
-
}
|
|
6954
|
-
if (urlScore !== null)
|
|
6955
|
-
return urlScore;
|
|
6956
|
-
if (surfaceScore !== null)
|
|
6957
|
-
return surfaceScore;
|
|
6958
|
-
return 0.5;
|
|
6933
|
+
return out.sort((a, b) => b.frontmatter.updated_at.localeCompare(a.frontmatter.updated_at));
|
|
6959
6934
|
}
|
|
6960
|
-
function
|
|
6961
|
-
const
|
|
6962
|
-
const
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
}
|
|
6966
|
-
if (rSim === 0) {
|
|
6967
|
-
return aSim >= 0.8 ? aSim * 0.4 : 0.1;
|
|
6968
|
-
}
|
|
6969
|
-
return 0.5 * rSim + 0.5 * aSim;
|
|
6935
|
+
async function writeDirection(projectRoot, fm, texts) {
|
|
6936
|
+
const file = directionPath(projectRoot, fm.slug);
|
|
6937
|
+
const validated = directionFrontmatterSchema.parse(fm);
|
|
6938
|
+
await atomicWriteFile(file, serializeFrontmatter(validated, renderBody(texts)));
|
|
6939
|
+
return file;
|
|
6970
6940
|
}
|
|
6971
|
-
function
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
}
|
|
6941
|
+
async function upsertDirection(projectRoot, proposal, source, advisedBy, now = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
6942
|
+
const existing = await readDirection(projectRoot, proposal.slug);
|
|
6943
|
+
const texts = {
|
|
6944
|
+
problem: proposal.problem,
|
|
6945
|
+
change: proposal.change,
|
|
6946
|
+
leading_practice: proposal.leading_practice
|
|
6947
|
+
};
|
|
6948
|
+
if (existing?.frontmatter.status === "rejected") {
|
|
6949
|
+
return { outcome: "previously_rejected" };
|
|
6980
6950
|
}
|
|
6981
|
-
|
|
6951
|
+
const prev = existing?.frontmatter;
|
|
6952
|
+
const fm = {
|
|
6953
|
+
id: prev?.id ?? randomUUID5(),
|
|
6954
|
+
slug: proposal.slug,
|
|
6955
|
+
title: proposal.title,
|
|
6956
|
+
status: prev?.status ?? "proposed",
|
|
6957
|
+
size: proposal.size,
|
|
6958
|
+
absorbs: union(prev?.absorbs ?? [], proposal.absorbs, (x) => x),
|
|
6959
|
+
reach: union(prev?.reach ?? [], proposal.reach, (r) => `${r.scenario}:${r.effect}`),
|
|
6960
|
+
verify: union(prev?.verify ?? [], proposal.verify, (x) => x),
|
|
6961
|
+
builds_on: union(prev?.builds_on ?? [], proposal.builds_on, (r) => `${r.path}#${r.symbol}`),
|
|
6962
|
+
seen_in_runs: union(prev?.seen_in_runs ?? [], [source], (x) => x),
|
|
6963
|
+
advised_by: advisedBy,
|
|
6964
|
+
created_at: prev?.created_at ?? now,
|
|
6965
|
+
updated_at: now
|
|
6966
|
+
};
|
|
6967
|
+
const file = await writeDirection(projectRoot, fm, texts);
|
|
6968
|
+
return {
|
|
6969
|
+
outcome: existing ? "updated" : "created",
|
|
6970
|
+
direction: { frontmatter: fm, texts, path: file }
|
|
6971
|
+
};
|
|
6982
6972
|
}
|
|
6983
|
-
function
|
|
6984
|
-
|
|
6985
|
-
|
|
6986
|
-
|
|
6987
|
-
|
|
6973
|
+
async function setDirectionStatus(projectRoot, slug, status2, now = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
6974
|
+
const existing = await readDirection(projectRoot, slug);
|
|
6975
|
+
if (!existing)
|
|
6976
|
+
return null;
|
|
6977
|
+
const fm = { ...existing.frontmatter, status: status2, updated_at: now };
|
|
6978
|
+
await writeDirection(projectRoot, fm, existing.texts);
|
|
6979
|
+
return { ...existing, frontmatter: fm };
|
|
6988
6980
|
}
|
|
6989
|
-
|
|
6990
|
-
|
|
6991
|
-
|
|
6992
|
-
const GAP_PENALTY = -0.7;
|
|
6993
|
-
const MATCH_THRESHOLD = 0.45;
|
|
6994
|
-
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
6995
|
-
for (let i2 = 0; i2 <= m; i2++)
|
|
6996
|
-
dp[i2][0] = i2 * GAP_PENALTY;
|
|
6997
|
-
for (let j2 = 0; j2 <= n; j2++)
|
|
6998
|
-
dp[0][j2] = j2 * GAP_PENALTY;
|
|
6999
|
-
for (let i2 = 1; i2 <= m; i2++) {
|
|
7000
|
-
for (let j2 = 1; j2 <= n; j2++) {
|
|
7001
|
-
const sim = stepSimilarity(baselineSteps[i2 - 1], currentSteps[j2 - 1]);
|
|
7002
|
-
const matchScore = sim >= MATCH_THRESHOLD ? sim * 2 - 0.2 : -1.5;
|
|
7003
|
-
const match2 = dp[i2 - 1][j2 - 1] + matchScore;
|
|
7004
|
-
const delP = dp[i2 - 1][j2] + GAP_PENALTY;
|
|
7005
|
-
const insC = dp[i2][j2 - 1] + GAP_PENALTY;
|
|
7006
|
-
dp[i2][j2] = Math.max(match2, delP, insC);
|
|
7007
|
-
}
|
|
7008
|
-
}
|
|
7009
|
-
const alignedPairs = [];
|
|
7010
|
-
let i = m;
|
|
7011
|
-
let j = n;
|
|
7012
|
-
while (i > 0 || j > 0) {
|
|
7013
|
-
if (i > 0 && j > 0) {
|
|
7014
|
-
const sim = stepSimilarity(baselineSteps[i - 1], currentSteps[j - 1]);
|
|
7015
|
-
const matchScore = sim >= MATCH_THRESHOLD ? sim * 2 - 0.2 : -1.5;
|
|
7016
|
-
const scoreFromMatch = dp[i - 1][j - 1] + matchScore;
|
|
7017
|
-
if (Math.abs(dp[i][j] - scoreFromMatch) < 1e-6 && sim >= MATCH_THRESHOLD) {
|
|
7018
|
-
alignedPairs.push({
|
|
7019
|
-
baseline: baselineSteps[i - 1],
|
|
7020
|
-
current: currentSteps[j - 1]
|
|
7021
|
-
});
|
|
7022
|
-
i--;
|
|
7023
|
-
j--;
|
|
7024
|
-
continue;
|
|
7025
|
-
}
|
|
7026
|
-
}
|
|
7027
|
-
if (j > 0 && Math.abs(dp[i][j] - (dp[i][j - 1] + GAP_PENALTY)) < 1e-6) {
|
|
7028
|
-
alignedPairs.push({
|
|
7029
|
-
baseline: null,
|
|
7030
|
-
current: currentSteps[j - 1]
|
|
7031
|
-
});
|
|
7032
|
-
j--;
|
|
7033
|
-
} else if (i > 0 && Math.abs(dp[i][j] - (dp[i - 1][j] + GAP_PENALTY)) < 1e-6) {
|
|
7034
|
-
alignedPairs.push({
|
|
7035
|
-
baseline: baselineSteps[i - 1],
|
|
7036
|
-
current: null
|
|
7037
|
-
});
|
|
7038
|
-
i--;
|
|
7039
|
-
} else if (i > 0 && j > 0) {
|
|
7040
|
-
alignedPairs.push({
|
|
7041
|
-
baseline: baselineSteps[i - 1],
|
|
7042
|
-
current: currentSteps[j - 1]
|
|
7043
|
-
});
|
|
7044
|
-
i--;
|
|
7045
|
-
j--;
|
|
7046
|
-
} else if (i > 0) {
|
|
7047
|
-
alignedPairs.push({
|
|
7048
|
-
baseline: baselineSteps[i - 1],
|
|
7049
|
-
current: null
|
|
7050
|
-
});
|
|
7051
|
-
i--;
|
|
7052
|
-
} else {
|
|
7053
|
-
alignedPairs.push({
|
|
7054
|
-
baseline: null,
|
|
7055
|
-
current: currentSteps[j - 1]
|
|
7056
|
-
});
|
|
7057
|
-
j--;
|
|
7058
|
-
}
|
|
7059
|
-
}
|
|
7060
|
-
alignedPairs.reverse();
|
|
7061
|
-
const maxBaselineStepNum = baselineSteps.length > 0 ? Math.max(...baselineSteps.map((s) => s.step_number)) : -1;
|
|
7062
|
-
const alignedSteps = [];
|
|
7063
|
-
const stats = {
|
|
7064
|
-
totalAlignedSteps: alignedPairs.length,
|
|
7065
|
-
same: 0,
|
|
7066
|
-
resolved: 0,
|
|
7067
|
-
persists: 0,
|
|
7068
|
-
regressions: 0,
|
|
7069
|
-
stalledInBaseline: 0,
|
|
7070
|
-
bypassed: 0,
|
|
7071
|
-
newPath: 0
|
|
7072
|
-
};
|
|
7073
|
-
for (let idx = 0; idx < alignedPairs.length; idx++) {
|
|
7074
|
-
const pair = alignedPairs[idx];
|
|
7075
|
-
const bStep = pair.baseline;
|
|
7076
|
-
const cStep = pair.current;
|
|
7077
|
-
const bFindings = bStep ? baselineFindings.filter((f) => findingMatchesStep(f, bStep.step_number)) : [];
|
|
7078
|
-
const cFindings = cStep ? currentFindings.filter((f) => findingMatchesStep(f, cStep.step_number)) : [];
|
|
7079
|
-
const bProblems = bFindings.filter((f) => isProblemFinding(f.type));
|
|
7080
|
-
const cProblems = cFindings.filter((f) => isProblemFinding(f.type));
|
|
7081
|
-
let status2 = "same";
|
|
7082
|
-
let summary = "Clean";
|
|
7083
|
-
if (bStep && cStep) {
|
|
7084
|
-
const wasStall = bStep.step_number === maxBaselineStepNum && (bProblems.length > 0 || isStallNote(bStep.note));
|
|
7085
|
-
if (bProblems.length > 0 && cProblems.length === 0) {
|
|
7086
|
-
status2 = "resolved";
|
|
7087
|
-
stats.resolved++;
|
|
7088
|
-
const probDesc = bProblems[0].ui_fact || bProblems[0].title || bProblems[0].id;
|
|
7089
|
-
summary = wasStall ? `Previous stall resolved \u2014 journey unblocked (${probDesc})` : `Resolved previous issue: ${probDesc}`;
|
|
7090
|
-
} else if (bProblems.length > 0 && cProblems.length > 0) {
|
|
7091
|
-
status2 = "persists";
|
|
7092
|
-
stats.persists++;
|
|
7093
|
-
const probDesc = cProblems[0].ui_fact || cProblems[0].title || cProblems[0].id;
|
|
7094
|
-
summary = `Problem persists: ${probDesc}`;
|
|
7095
|
-
} else if (bProblems.length === 0 && cProblems.length > 0) {
|
|
7096
|
-
status2 = "regression";
|
|
7097
|
-
stats.regressions++;
|
|
7098
|
-
const probDesc = cProblems[0].ui_fact || cProblems[0].title || cProblems[0].id;
|
|
7099
|
-
summary = `New issue reported: ${probDesc}`;
|
|
7100
|
-
} else {
|
|
7101
|
-
status2 = "same";
|
|
7102
|
-
stats.same++;
|
|
7103
|
-
summary = "Clean";
|
|
7104
|
-
}
|
|
7105
|
-
} else if (bStep && !cStep) {
|
|
7106
|
-
const wasStall = bStep.step_number === maxBaselineStepNum && (bProblems.length > 0 || isStallNote(bStep.note));
|
|
7107
|
-
if (wasStall) {
|
|
7108
|
-
status2 = "stalled";
|
|
7109
|
-
stats.stalledInBaseline++;
|
|
7110
|
-
const probDesc = bProblems[0]?.ui_fact || bProblems[0]?.title || "journey halted";
|
|
7111
|
-
summary = `Stalled in baseline: ${probDesc}`;
|
|
7112
|
-
} else {
|
|
7113
|
-
status2 = "bypassed";
|
|
7114
|
-
stats.bypassed++;
|
|
7115
|
-
summary = "Bypassed in current run";
|
|
7116
|
-
}
|
|
7117
|
-
} else if (!bStep && cStep) {
|
|
7118
|
-
if (cProblems.length > 0) {
|
|
7119
|
-
status2 = "regression";
|
|
7120
|
-
stats.regressions++;
|
|
7121
|
-
const probDesc = cProblems[0].ui_fact || cProblems[0].title || cProblems[0].id;
|
|
7122
|
-
summary = `New step with issue: ${probDesc}`;
|
|
7123
|
-
} else {
|
|
7124
|
-
status2 = "new_path";
|
|
7125
|
-
stats.newPath++;
|
|
7126
|
-
summary = "New path / unblocked progress";
|
|
7127
|
-
}
|
|
7128
|
-
}
|
|
7129
|
-
alignedSteps.push({
|
|
7130
|
-
index: idx + 1,
|
|
7131
|
-
status: status2,
|
|
7132
|
-
baselineStep: bStep,
|
|
7133
|
-
currentStep: cStep,
|
|
7134
|
-
baselineFindings: bFindings,
|
|
7135
|
-
currentFindings: cFindings,
|
|
7136
|
-
summary
|
|
7137
|
-
});
|
|
7138
|
-
}
|
|
7139
|
-
return {
|
|
7140
|
-
scenario,
|
|
7141
|
-
baselineRunId,
|
|
7142
|
-
currentRunId,
|
|
7143
|
-
steps: alignedSteps,
|
|
7144
|
-
stats
|
|
7145
|
-
};
|
|
7146
|
-
}
|
|
7147
|
-
function toStepFindingInfo(f) {
|
|
7148
|
-
return {
|
|
7149
|
-
id: f.frontmatter.id,
|
|
7150
|
-
title: f.frontmatter.title ?? f.slug,
|
|
7151
|
-
type: f.frontmatter.type,
|
|
7152
|
-
severity: f.frontmatter.severity,
|
|
7153
|
-
ui_fact: f.frontmatter.ui_fact,
|
|
7154
|
-
consequence: f.frontmatter.consequence,
|
|
7155
|
-
canonical_id: f.frontmatter.canonical_id,
|
|
7156
|
-
walkthrough_step_id: f.frontmatter.walkthrough_step_id
|
|
7157
|
-
};
|
|
7158
|
-
}
|
|
7159
|
-
function toJourneyStepInfo(s) {
|
|
7160
|
-
return {
|
|
7161
|
-
step_number: s.step_number,
|
|
7162
|
-
action: s.action,
|
|
7163
|
-
url: s.url ?? void 0,
|
|
7164
|
-
surface: s.surface ?? void 0,
|
|
7165
|
-
note: s.note ?? void 0
|
|
7166
|
-
};
|
|
7167
|
-
}
|
|
7168
|
-
function journeyStepRoute(step) {
|
|
7169
|
-
return step.currentStep?.url && normalizePlaceKey(step.currentStep.url) || step.baselineStep?.url && normalizePlaceKey(step.baselineStep.url) || step.currentStep?.surface || step.baselineStep?.surface || "\u2014";
|
|
7170
|
-
}
|
|
7171
|
-
async function computeJourneyDiff(projectRoot, baselineRunId, currentRunId, scenarioSlug) {
|
|
7172
|
-
const [bStepsRaw, bFindingsRaw, cStepsRaw, cFindingsRaw] = await Promise.all([
|
|
7173
|
-
readStepsForRun(projectRoot, baselineRunId),
|
|
7174
|
-
readFindingsForRun(projectRoot, baselineRunId),
|
|
7175
|
-
readStepsForRun(projectRoot, currentRunId),
|
|
7176
|
-
readFindingsForRun(projectRoot, currentRunId)
|
|
7177
|
-
]);
|
|
7178
|
-
const baselineSteps = bStepsRaw.map(toJourneyStepInfo);
|
|
7179
|
-
const baselineFindings = bFindingsRaw.map(toStepFindingInfo);
|
|
7180
|
-
const currentSteps = cStepsRaw.map(toJourneyStepInfo);
|
|
7181
|
-
const currentFindings = cFindingsRaw.map(toStepFindingInfo);
|
|
7182
|
-
return alignJourneySteps(scenarioSlug ?? "", baselineRunId, currentRunId, baselineSteps, baselineFindings, currentSteps, currentFindings);
|
|
7183
|
-
}
|
|
7184
|
-
function renderJourneyDiffMarkdown(diff) {
|
|
7185
|
-
const lines = [
|
|
7186
|
-
"### Chronological journey diff",
|
|
7187
|
-
"",
|
|
7188
|
-
`Baseline: \`${diff.baselineRunId}\` vs Current: \`${diff.currentRunId}\``
|
|
7189
|
-
];
|
|
7190
|
-
const parts = [];
|
|
7191
|
-
if (diff.stats.same > 0)
|
|
7192
|
-
parts.push(`${diff.stats.same} unchanged`);
|
|
7193
|
-
if (diff.stats.resolved > 0)
|
|
7194
|
-
parts.push(`${diff.stats.resolved} resolved`);
|
|
7195
|
-
if (diff.stats.persists > 0)
|
|
7196
|
-
parts.push(`${diff.stats.persists} persists`);
|
|
7197
|
-
if (diff.stats.regressions > 0)
|
|
7198
|
-
parts.push(`${diff.stats.regressions} new issues`);
|
|
7199
|
-
if (diff.stats.stalledInBaseline > 0)
|
|
7200
|
-
parts.push(`${diff.stats.stalledInBaseline} stalled in baseline`);
|
|
7201
|
-
if (diff.stats.bypassed > 0)
|
|
7202
|
-
parts.push(`${diff.stats.bypassed} bypassed`);
|
|
7203
|
-
if (diff.stats.newPath > 0)
|
|
7204
|
-
parts.push(`${diff.stats.newPath} new path steps`);
|
|
7205
|
-
if (parts.length > 0) {
|
|
7206
|
-
lines.push(`- **Summary**: ${parts.join(", ")}`);
|
|
7207
|
-
}
|
|
7208
|
-
lines.push("", "| # | Route | Baseline (Run N-1) | Current (Run N) | Status | Notes |", "|---|-------|--------------------|-----------------|--------|-------|");
|
|
7209
|
-
for (const step of diff.steps) {
|
|
7210
|
-
const route = journeyStepRoute(step);
|
|
7211
|
-
const baseAction = step.baselineStep ? `Step ${step.baselineStep.step_number}: ${escapeMarkdownCell(step.baselineStep.action)}` : "\u2014";
|
|
7212
|
-
const currAction = step.currentStep ? `Step ${step.currentStep.step_number}: ${escapeMarkdownCell(step.currentStep.action)}` : "\u2014";
|
|
7213
|
-
const statusBadge = `\`${step.status}\``;
|
|
7214
|
-
const notes = escapeMarkdownCell(step.summary);
|
|
7215
|
-
lines.push(`| ${step.index} | \`${escapeMarkdownCell(route)}\` | ${baseAction} | ${currAction} | ${statusBadge} | ${notes} |`);
|
|
7216
|
-
}
|
|
7217
|
-
return lines.join("\n");
|
|
7218
|
-
}
|
|
7219
|
-
function escapeMarkdownCell(text) {
|
|
7220
|
-
return text.replace(/\|/g, "\\|").replace(/\n/g, " ");
|
|
7221
|
-
}
|
|
7222
|
-
var PROBLEM_TYPES2, STALL_KEYWORDS;
|
|
7223
|
-
var init_journey_diff = __esm({
|
|
7224
|
-
"../../packages/local-store/dist/journey-diff.js"() {
|
|
6981
|
+
var DIRECTIONS_DIR, DIRECTION_STATUSES, directionStatusSchema, directionSizeSchema, directionReachSchema, codeRefSchema, directionFrontmatterSchema, union;
|
|
6982
|
+
var init_directions = __esm({
|
|
6983
|
+
"../../packages/local-store/dist/directions.js"() {
|
|
7225
6984
|
"use strict";
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
"
|
|
7232
|
-
"
|
|
7233
|
-
"
|
|
7234
|
-
"
|
|
7235
|
-
"cannot proceed",
|
|
7236
|
-
"could not proceed",
|
|
7237
|
-
"failed to load",
|
|
7238
|
-
"timed out"
|
|
6985
|
+
init_atomic();
|
|
6986
|
+
init_markdown();
|
|
6987
|
+
init_paths();
|
|
6988
|
+
DIRECTIONS_DIR = "directions";
|
|
6989
|
+
DIRECTION_STATUSES = [
|
|
6990
|
+
"proposed",
|
|
6991
|
+
"accepted",
|
|
6992
|
+
"done",
|
|
6993
|
+
"rejected"
|
|
7239
6994
|
];
|
|
6995
|
+
directionStatusSchema = z25.enum(DIRECTION_STATUSES);
|
|
6996
|
+
directionSizeSchema = z25.enum(["small", "medium", "large"]);
|
|
6997
|
+
directionReachSchema = z25.object({
|
|
6998
|
+
scenario: z25.string().min(1),
|
|
6999
|
+
effect: z25.enum(["helps", "risk"]),
|
|
7000
|
+
reason: z25.string().min(1)
|
|
7001
|
+
});
|
|
7002
|
+
codeRefSchema = z25.object({
|
|
7003
|
+
path: z25.string().min(1),
|
|
7004
|
+
symbol: z25.string().min(1)
|
|
7005
|
+
});
|
|
7006
|
+
directionFrontmatterSchema = z25.object({
|
|
7007
|
+
id: z25.string().uuid(),
|
|
7008
|
+
slug: z25.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
|
|
7009
|
+
title: z25.string().min(1),
|
|
7010
|
+
status: directionStatusSchema.default("proposed"),
|
|
7011
|
+
size: directionSizeSchema,
|
|
7012
|
+
/** Finding ids this direction would resolve. */
|
|
7013
|
+
absorbs: z25.array(z25.string().min(1)).default([]),
|
|
7014
|
+
/** Predicted effect on other scenarios — never evidence. */
|
|
7015
|
+
reach: z25.array(directionReachSchema).default([]),
|
|
7016
|
+
/** Scenarios to re-run once the direction lands. */
|
|
7017
|
+
verify: z25.array(z25.string().min(1)).default([]),
|
|
7018
|
+
builds_on: z25.array(codeRefSchema).default([]),
|
|
7019
|
+
/** Runs (or `project:<timestamp>`) whose analysis proposed it. */
|
|
7020
|
+
seen_in_runs: z25.array(z25.string().min(1)).default([]),
|
|
7021
|
+
advised_by: z25.string().regex(/^advisor@\S+$/),
|
|
7022
|
+
created_at: z25.string(),
|
|
7023
|
+
updated_at: z25.string()
|
|
7024
|
+
});
|
|
7025
|
+
union = (a, b, key) => {
|
|
7026
|
+
const seen = /* @__PURE__ */ new Map();
|
|
7027
|
+
for (const x of [...a, ...b])
|
|
7028
|
+
seen.set(key(x), x);
|
|
7029
|
+
return [...seen.values()];
|
|
7030
|
+
};
|
|
7240
7031
|
}
|
|
7241
7032
|
});
|
|
7242
7033
|
|
|
7243
7034
|
// ../../packages/local-store/dist/walkthrough-canonical.js
|
|
7244
|
-
import { createHash as createHash2, randomUUID as
|
|
7245
|
-
import { promises as
|
|
7246
|
-
import
|
|
7035
|
+
import { createHash as createHash2, randomUUID as randomUUID6 } from "crypto";
|
|
7036
|
+
import { promises as fs13 } from "fs";
|
|
7037
|
+
import path10 from "path";
|
|
7247
7038
|
async function readStepsForRun(projectRoot, runId) {
|
|
7248
7039
|
const runDirPath = runDir(projectRoot, runId);
|
|
7249
|
-
const stepsDir =
|
|
7040
|
+
const stepsDir = path10.join(runDirPath, "steps");
|
|
7250
7041
|
try {
|
|
7251
|
-
const names2 = await
|
|
7042
|
+
const names2 = await fs13.readdir(stepsDir);
|
|
7252
7043
|
const jsonFiles = names2.filter((n) => n.endsWith(".json")).sort();
|
|
7253
7044
|
if (jsonFiles.length > 0) {
|
|
7254
7045
|
const out2 = [];
|
|
7255
7046
|
for (const name of jsonFiles) {
|
|
7256
7047
|
try {
|
|
7257
|
-
const raw2 = await
|
|
7048
|
+
const raw2 = await fs13.readFile(path10.join(stepsDir, name), "utf8");
|
|
7258
7049
|
const parsed = JSON.parse(raw2);
|
|
7259
7050
|
if (parsed.step_number == null) {
|
|
7260
7051
|
const numFromName = Number.parseInt(name.replace(/\.json$/, ""), 10);
|
|
@@ -7268,10 +7059,10 @@ async function readStepsForRun(projectRoot, runId) {
|
|
|
7268
7059
|
}
|
|
7269
7060
|
} catch {
|
|
7270
7061
|
}
|
|
7271
|
-
const stepsPath =
|
|
7062
|
+
const stepsPath = path10.join(runDirPath, "steps.jsonl");
|
|
7272
7063
|
let raw;
|
|
7273
7064
|
try {
|
|
7274
|
-
raw = await
|
|
7065
|
+
raw = await fs13.readFile(stepsPath, "utf8");
|
|
7275
7066
|
} catch (err) {
|
|
7276
7067
|
if (err.code === "ENOENT")
|
|
7277
7068
|
return [];
|
|
@@ -7296,7 +7087,7 @@ async function readFindingsForRun(projectRoot, runId) {
|
|
|
7296
7087
|
const findingsDir = entityDirPath(projectRoot, "finding");
|
|
7297
7088
|
let names2;
|
|
7298
7089
|
try {
|
|
7299
|
-
names2 = await
|
|
7090
|
+
names2 = await fs13.readdir(findingsDir);
|
|
7300
7091
|
} catch (err) {
|
|
7301
7092
|
if (err.code === "ENOENT")
|
|
7302
7093
|
return [];
|
|
@@ -7306,8 +7097,8 @@ async function readFindingsForRun(projectRoot, runId) {
|
|
|
7306
7097
|
for (const name of names2) {
|
|
7307
7098
|
if (!name.endsWith(".md") || name.startsWith("."))
|
|
7308
7099
|
continue;
|
|
7309
|
-
const filePath =
|
|
7310
|
-
const raw = await
|
|
7100
|
+
const filePath = path10.join(findingsDir, name);
|
|
7101
|
+
const raw = await fs13.readFile(filePath, "utf8");
|
|
7311
7102
|
try {
|
|
7312
7103
|
const { frontmatter, body } = parseFrontmatter(raw);
|
|
7313
7104
|
const fm = frontmatter;
|
|
@@ -7338,7 +7129,7 @@ async function resolveCanonicalIds(projectRoot, scenarioSlug, currentRunId, curr
|
|
|
7338
7129
|
const findingsDir = entityDirPath(projectRoot, "finding");
|
|
7339
7130
|
let allNames;
|
|
7340
7131
|
try {
|
|
7341
|
-
allNames = await
|
|
7132
|
+
allNames = await fs13.readdir(findingsDir);
|
|
7342
7133
|
} catch {
|
|
7343
7134
|
allNames = [];
|
|
7344
7135
|
}
|
|
@@ -7346,7 +7137,7 @@ async function resolveCanonicalIds(projectRoot, scenarioSlug, currentRunId, curr
|
|
|
7346
7137
|
if (!name.endsWith(".md") || name.startsWith("."))
|
|
7347
7138
|
continue;
|
|
7348
7139
|
try {
|
|
7349
|
-
const raw = await
|
|
7140
|
+
const raw = await fs13.readFile(path10.join(findingsDir, name), "utf8");
|
|
7350
7141
|
const { frontmatter } = parseFrontmatter(raw);
|
|
7351
7142
|
const fm = frontmatter;
|
|
7352
7143
|
if (!fm.scenario || fm.scenario !== scenarioSlug)
|
|
@@ -7357,14 +7148,14 @@ async function resolveCanonicalIds(projectRoot, scenarioSlug, currentRunId, curr
|
|
|
7357
7148
|
if (fm.canonical_id) {
|
|
7358
7149
|
priorIndex.set(fp, fm.canonical_id);
|
|
7359
7150
|
} else if (!priorIndex.has(fp)) {
|
|
7360
|
-
priorIndex.set(fp,
|
|
7151
|
+
priorIndex.set(fp, randomUUID6());
|
|
7361
7152
|
}
|
|
7362
7153
|
} catch {
|
|
7363
7154
|
}
|
|
7364
7155
|
}
|
|
7365
7156
|
for (const f of currentFindings) {
|
|
7366
7157
|
const fp = findingFingerprint(f.frontmatter.scenario, f.frontmatter.type, f.frontmatter.ui_fact);
|
|
7367
|
-
const canonicalId = f.frontmatter.canonical_id ?? priorIndex.get(fp) ??
|
|
7158
|
+
const canonicalId = f.frontmatter.canonical_id ?? priorIndex.get(fp) ?? randomUUID6();
|
|
7368
7159
|
result.set(f.frontmatter.id, canonicalId);
|
|
7369
7160
|
if (!f.frontmatter.canonical_id) {
|
|
7370
7161
|
try {
|
|
@@ -7372,9 +7163,9 @@ async function resolveCanonicalIds(projectRoot, scenarioSlug, currentRunId, curr
|
|
|
7372
7163
|
for (const name of allNames) {
|
|
7373
7164
|
if (!name.endsWith(".md") || name.startsWith("."))
|
|
7374
7165
|
continue;
|
|
7375
|
-
const candidatePath =
|
|
7166
|
+
const candidatePath = path10.join(findingsDir, name);
|
|
7376
7167
|
try {
|
|
7377
|
-
const candidateRaw = await
|
|
7168
|
+
const candidateRaw = await fs13.readFile(candidatePath, "utf8");
|
|
7378
7169
|
const { frontmatter: cFm } = parseFrontmatter(candidateRaw);
|
|
7379
7170
|
if (cFm.id === f.frontmatter.id) {
|
|
7380
7171
|
targetPath = candidatePath;
|
|
@@ -7385,7 +7176,7 @@ async function resolveCanonicalIds(projectRoot, scenarioSlug, currentRunId, curr
|
|
|
7385
7176
|
}
|
|
7386
7177
|
if (targetPath) {
|
|
7387
7178
|
await withFileLock(targetPath, async () => {
|
|
7388
|
-
const raw = await
|
|
7179
|
+
const raw = await fs13.readFile(targetPath, "utf8");
|
|
7389
7180
|
const updated = raw.replace(/^(---\n[\s\S]*?)(---\n)/m, `$1canonical_id: ${canonicalId}
|
|
7390
7181
|
$2`);
|
|
7391
7182
|
if (updated !== raw) {
|
|
@@ -7410,7 +7201,7 @@ function extractRecommendationFromBody(body) {
|
|
|
7410
7201
|
}
|
|
7411
7202
|
async function readAnalysis(projectRoot, runId) {
|
|
7412
7203
|
try {
|
|
7413
|
-
const raw = JSON.parse(await
|
|
7204
|
+
const raw = JSON.parse(await fs13.readFile(path10.join(runDir(projectRoot, runId), "advice.json"), "utf8"));
|
|
7414
7205
|
return {
|
|
7415
7206
|
advisor: `advisor@${raw.advisor?.version ?? "unknown"}`,
|
|
7416
7207
|
directions: (raw.directions ?? []).map((d) => ({
|
|
@@ -7440,9 +7231,13 @@ async function buildCanonical(projectRoot, manifest, completedAt, inputs) {
|
|
|
7440
7231
|
} : null;
|
|
7441
7232
|
const outlineEntity = scenario ? await readEntity(projectRoot, "outline", scenario.outline) : null;
|
|
7442
7233
|
const outline = outlineEntity ? {
|
|
7234
|
+
title: outlineEntity.frontmatter.title,
|
|
7443
7235
|
human_tests: outlineEntity.frontmatter.human_tests ?? []
|
|
7444
7236
|
} : null;
|
|
7237
|
+
const personaEntity = scenario?.persona ? await readEntity(projectRoot, "persona", scenario.persona).catch(() => null) : null;
|
|
7238
|
+
const personaName = personaEntity?.frontmatter.name ?? null;
|
|
7445
7239
|
const steps = await readStepsForRun(projectRoot, manifest.run_id);
|
|
7240
|
+
const history = await scenarioRunHistory(projectRoot, manifest.scenario_slug, manifest.run_id);
|
|
7446
7241
|
const findings2 = await readFindingsForRun(projectRoot, manifest.run_id);
|
|
7447
7242
|
const analysis = await readAnalysis(projectRoot, manifest.run_id);
|
|
7448
7243
|
const canonicalSteps = steps.map((s, idx) => {
|
|
@@ -7454,7 +7249,7 @@ async function buildCanonical(projectRoot, manifest, completedAt, inputs) {
|
|
|
7454
7249
|
// Step files record the path relative to the run directory
|
|
7455
7250
|
// (`screenshots/step-1.png`); older ones recorded the bare file
|
|
7456
7251
|
// name. Normalize on the file name so the prefix is never doubled.
|
|
7457
|
-
screenshot_ref: s.screenshot ?
|
|
7252
|
+
screenshot_ref: s.screenshot ? path10.join("screenshots", path10.basename(s.screenshot)) : null,
|
|
7458
7253
|
persona_reasoning: s.note,
|
|
7459
7254
|
grounding: s.grounding ?? null,
|
|
7460
7255
|
action: s.action,
|
|
@@ -7462,9 +7257,15 @@ async function buildCanonical(projectRoot, manifest, completedAt, inputs) {
|
|
|
7462
7257
|
...typeof s.surface === "string" && s.surface ? { surface: s.surface } : {}
|
|
7463
7258
|
};
|
|
7464
7259
|
});
|
|
7260
|
+
const checks = await runChecks(projectRoot, manifest, canonicalSteps, history.previousStartedAt);
|
|
7465
7261
|
const canonicalMap = await resolveCanonicalIds(projectRoot, manifest.scenario_slug, manifest.run_id, findings2);
|
|
7262
|
+
const stepUrl2 = new Map(canonicalSteps.map((st) => [st.id, st.url ?? null]));
|
|
7466
7263
|
const canonicalFindings = findings2.map((f) => ({
|
|
7467
7264
|
id: f.frontmatter.id,
|
|
7265
|
+
// Additive keys (D3): the short title and the page it was seen on,
|
|
7266
|
+
// so reports never fall back to ids or bare step numbers.
|
|
7267
|
+
title: f.frontmatter.title ?? null,
|
|
7268
|
+
page_url: f.frontmatter.page_url ?? (f.frontmatter.walkthrough_step_id ? stepUrl2.get(f.frontmatter.walkthrough_step_id) : null) ?? null,
|
|
7468
7269
|
canonical_id: canonicalMap.get(f.frontmatter.id) ?? null,
|
|
7469
7270
|
type: f.frontmatter.type,
|
|
7470
7271
|
severity: f.frontmatter.severity ?? null,
|
|
@@ -7496,7 +7297,10 @@ async function buildCanonical(projectRoot, manifest, completedAt, inputs) {
|
|
|
7496
7297
|
slug: manifest.scenario_slug,
|
|
7497
7298
|
persona_slug: scenario?.persona ?? "",
|
|
7498
7299
|
outline_slug: scenario?.outline ?? "",
|
|
7499
|
-
brief: scenario?.brief ?? ""
|
|
7300
|
+
brief: scenario?.brief ?? "",
|
|
7301
|
+
...personaName ? { persona_name: personaName } : {},
|
|
7302
|
+
...personaEntity?.frontmatter.role ? { persona_role: personaEntity.frontmatter.role } : {},
|
|
7303
|
+
...outline?.title ? { outline_title: outline.title } : {}
|
|
7500
7304
|
},
|
|
7501
7305
|
target: {
|
|
7502
7306
|
url: manifest.target_url,
|
|
@@ -7522,7 +7326,7 @@ async function buildCanonical(projectRoot, manifest, completedAt, inputs) {
|
|
|
7522
7326
|
steps: canonicalSteps,
|
|
7523
7327
|
human_tests: humanTests,
|
|
7524
7328
|
human_test_overall: {
|
|
7525
|
-
question: outline ? `Would ${scenario?.persona ?? "the persona"} come back and use this voluntarily?` : "Would the persona come back and use this voluntarily?",
|
|
7329
|
+
question: outline ? `Would ${personaName ?? scenario?.persona ?? "the persona"} come back and use this voluntarily?` : "Would the persona come back and use this voluntarily?",
|
|
7526
7330
|
answer: null,
|
|
7527
7331
|
reasoning: null
|
|
7528
7332
|
},
|
|
@@ -7533,21 +7337,65 @@ async function buildCanonical(projectRoot, manifest, completedAt, inputs) {
|
|
|
7533
7337
|
// Consumers MUST preserve unknown keys (per design D3).
|
|
7534
7338
|
...inputs.what_worked && inputs.what_worked.length > 0 ? { what_worked: inputs.what_worked } : {},
|
|
7535
7339
|
...inputs.open_questions && inputs.open_questions.length > 0 ? { open_questions: inputs.open_questions } : {},
|
|
7340
|
+
...inputs.setup_notes && inputs.setup_notes.length > 0 ? { setup_notes: inputs.setup_notes } : {},
|
|
7536
7341
|
...manifest.blocked ? { blocked: manifest.blocked } : {},
|
|
7537
|
-
...analysis ? { analysis } : {}
|
|
7342
|
+
...analysis ? { analysis } : {},
|
|
7343
|
+
run_number: history.runNumber,
|
|
7344
|
+
...checks ? { checks } : {}
|
|
7538
7345
|
};
|
|
7539
7346
|
}
|
|
7540
|
-
async function
|
|
7541
|
-
const
|
|
7542
|
-
|
|
7543
|
-
|
|
7544
|
-
|
|
7545
|
-
const
|
|
7546
|
-
|
|
7347
|
+
async function runChecks(projectRoot, manifest, steps, previousStartedAt) {
|
|
7348
|
+
const places = manifest.revisit ?? [];
|
|
7349
|
+
const fixes = [];
|
|
7350
|
+
if (places.length > 0) {
|
|
7351
|
+
const { entities } = await listEntities(projectRoot, "finding");
|
|
7352
|
+
const byId = new Map(entities.map((e) => [e.frontmatter.id, e.frontmatter]));
|
|
7353
|
+
for (const place of revisitCoverage(places, steps).places) {
|
|
7354
|
+
for (const id of place.finding_ids) {
|
|
7355
|
+
const f = byId.get(id);
|
|
7356
|
+
if (!f || f.type === "helps")
|
|
7357
|
+
continue;
|
|
7358
|
+
fixes.push({
|
|
7359
|
+
canonical_id: f.canonical_id ?? null,
|
|
7360
|
+
title: f.title ?? f.slug,
|
|
7361
|
+
fix: f.triage_note ?? null,
|
|
7362
|
+
page: place.url,
|
|
7363
|
+
reached: place.reached
|
|
7364
|
+
});
|
|
7365
|
+
}
|
|
7366
|
+
}
|
|
7367
|
+
}
|
|
7368
|
+
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) : [];
|
|
7369
|
+
return fixes.length > 0 || directions.length > 0 ? { fixes, directions } : null;
|
|
7370
|
+
}
|
|
7371
|
+
async function scenarioRunHistory(projectRoot, scenarioSlug, runId) {
|
|
7372
|
+
let runNumber = 1;
|
|
7373
|
+
let previousStartedAt = null;
|
|
7374
|
+
for (const rid of await listRunsInternal(projectRoot)) {
|
|
7375
|
+
if (rid >= runId)
|
|
7376
|
+
continue;
|
|
7377
|
+
try {
|
|
7378
|
+
const m = JSON.parse(await fs13.readFile(path10.join(runDir(projectRoot, rid), "walkthrough.json"), "utf8"));
|
|
7379
|
+
if (m.scenario_slug === scenarioSlug && m.status === "completed") {
|
|
7380
|
+
runNumber++;
|
|
7381
|
+
previousStartedAt = m.created_at;
|
|
7382
|
+
}
|
|
7383
|
+
} catch {
|
|
7384
|
+
}
|
|
7385
|
+
}
|
|
7386
|
+
return { runNumber, previousStartedAt };
|
|
7387
|
+
}
|
|
7388
|
+
async function compareWithBaseline(projectRoot, scenarioSlug, currentRunId, currentFindings) {
|
|
7389
|
+
const allRuns = await listRunsInternal(projectRoot);
|
|
7390
|
+
let baselineRunId = null;
|
|
7391
|
+
let baselineManifest = null;
|
|
7392
|
+
for (let i = allRuns.length - 1; i >= 0; i--) {
|
|
7393
|
+
const rid = allRuns[i];
|
|
7394
|
+
if (rid === currentRunId)
|
|
7547
7395
|
continue;
|
|
7548
7396
|
try {
|
|
7549
|
-
const mPath =
|
|
7550
|
-
const raw = await
|
|
7397
|
+
const mPath = path10.join(runDir(projectRoot, rid), "walkthrough.json");
|
|
7398
|
+
const raw = await fs13.readFile(mPath, "utf8");
|
|
7551
7399
|
const m = JSON.parse(raw);
|
|
7552
7400
|
if (m.scenario_slug === scenarioSlug && m.status === "completed" && !m.blocked) {
|
|
7553
7401
|
baselineRunId = rid;
|
|
@@ -7569,15 +7417,21 @@ async function compareWithBaseline(projectRoot, scenarioSlug, currentRunId, curr
|
|
|
7569
7417
|
const uncertain = [];
|
|
7570
7418
|
const matches = await readMatches(projectRoot, currentRunId);
|
|
7571
7419
|
const recheckedFixed = new Set((matches?.rechecked_fixed ?? []).map((r) => r.canonical_id));
|
|
7420
|
+
const recheckOf = new Map((matches?.rechecked_fixed ?? []).map((r) => [r.canonical_id, r]));
|
|
7421
|
+
const matchNote = new Map((matches?.matches ?? []).filter((m) => m.canonical_id && m.reasoning).map((m) => [m.canonical_id, m.reasoning]));
|
|
7422
|
+
const recurrence = new Map(currentFindings.filter((f) => f.canonical_id).map((f) => [f.canonical_id, f]));
|
|
7572
7423
|
if (recheckedFixed.size > 0) {
|
|
7573
7424
|
const prior = await priorCanonicals(projectRoot, scenarioSlug, currentRunId);
|
|
7574
7425
|
for (const p of prior) {
|
|
7575
7426
|
if (!recheckedFixed.has(p.canonical_id))
|
|
7576
7427
|
continue;
|
|
7428
|
+
const recheck = recheckOf.get(p.canonical_id);
|
|
7577
7429
|
resolved.push({
|
|
7578
7430
|
canonical_id: p.canonical_id,
|
|
7579
7431
|
title: p.title,
|
|
7580
|
-
type: p.type
|
|
7432
|
+
type: p.type,
|
|
7433
|
+
...recheck?.reasoning ? { note: recheck.reasoning } : {},
|
|
7434
|
+
...recheck ? { step_ids: recheck.step_ids } : {}
|
|
7581
7435
|
});
|
|
7582
7436
|
await setCanonicalFixStatus(projectRoot, scenarioSlug, p.canonical_id, "verified_resolved");
|
|
7583
7437
|
}
|
|
@@ -7594,11 +7448,20 @@ async function compareWithBaseline(projectRoot, scenarioSlug, currentRunId, curr
|
|
|
7594
7448
|
});
|
|
7595
7449
|
continue;
|
|
7596
7450
|
}
|
|
7451
|
+
const fixedBefore = bf.frontmatter.triage_status === "addressed";
|
|
7597
7452
|
if (currentCanonicalIds.has(cid)) {
|
|
7453
|
+
const note = matchNote.get(cid);
|
|
7598
7454
|
persists.push({
|
|
7599
7455
|
canonical_id: cid,
|
|
7600
7456
|
title: bf.frontmatter.title ?? bf.frontmatter.slug,
|
|
7601
|
-
type: bf.frontmatter.type
|
|
7457
|
+
type: bf.frontmatter.type,
|
|
7458
|
+
...note ? { note } : {},
|
|
7459
|
+
step_ids: recurrence.get(cid)?.linked_step_ids ?? [],
|
|
7460
|
+
...fixedBefore ? { fixed_before: true } : {},
|
|
7461
|
+
...bf.frontmatter.severity && recurrence.get(cid)?.severity ? {
|
|
7462
|
+
severity_before: bf.frontmatter.severity,
|
|
7463
|
+
severity_now: recurrence.get(cid)?.severity
|
|
7464
|
+
} : {}
|
|
7602
7465
|
});
|
|
7603
7466
|
await updateFixStatus(projectRoot, bf.frontmatter.slug, "verified_persists");
|
|
7604
7467
|
} else if (recheckedFixed.has(cid)) {
|
|
@@ -7606,7 +7469,8 @@ async function compareWithBaseline(projectRoot, scenarioSlug, currentRunId, curr
|
|
|
7606
7469
|
notObserved.push({
|
|
7607
7470
|
canonical_id: cid,
|
|
7608
7471
|
title: bf.frontmatter.title ?? bf.frontmatter.slug,
|
|
7609
|
-
type: bf.frontmatter.type
|
|
7472
|
+
type: bf.frontmatter.type,
|
|
7473
|
+
...fixedBefore ? { fixed_before: true } : {}
|
|
7610
7474
|
});
|
|
7611
7475
|
}
|
|
7612
7476
|
}
|
|
@@ -7628,9 +7492,9 @@ async function compareWithBaseline(projectRoot, scenarioSlug, currentRunId, curr
|
|
|
7628
7492
|
async function updateFixStatus(projectRoot, slug, status2) {
|
|
7629
7493
|
try {
|
|
7630
7494
|
const findingsDir = entityDirPath(projectRoot, "finding");
|
|
7631
|
-
const filePath =
|
|
7495
|
+
const filePath = path10.join(findingsDir, `${slug}.md`);
|
|
7632
7496
|
await withFileLock(filePath, async () => {
|
|
7633
|
-
const raw = await
|
|
7497
|
+
const raw = await fs13.readFile(filePath, "utf8");
|
|
7634
7498
|
const updated = raw.replace(/^(fix_status:\s*).*$/m, `$1${status2}`);
|
|
7635
7499
|
if (updated !== raw) {
|
|
7636
7500
|
await atomicWriteFile(filePath, updated);
|
|
@@ -7644,210 +7508,19 @@ fix_status: ${status2}`);
|
|
|
7644
7508
|
}
|
|
7645
7509
|
}
|
|
7646
7510
|
async function listRunsInternal(projectRoot) {
|
|
7647
|
-
const dir =
|
|
7511
|
+
const dir = path10.join(projectRoot, "iamu", ".runs");
|
|
7648
7512
|
try {
|
|
7649
|
-
const names2 = await
|
|
7513
|
+
const names2 = await fs13.readdir(dir);
|
|
7650
7514
|
return names2.filter((n) => !n.startsWith(".")).sort();
|
|
7651
7515
|
} catch {
|
|
7652
7516
|
return [];
|
|
7653
7517
|
}
|
|
7654
7518
|
}
|
|
7655
|
-
function renderCanonicalMarkdown(canonical) {
|
|
7656
|
-
const lines = [
|
|
7657
|
-
"<!-- GENERATED: do not edit by hand. Regen via complete_walkthrough or local-store renderer. -->",
|
|
7658
|
-
"",
|
|
7659
|
-
`# Walkthrough report: ${canonical.scenario.slug}`,
|
|
7660
|
-
"",
|
|
7661
|
-
...canonical.blocked ? [
|
|
7662
|
-
"> **Blocked \u2014 the walk could not start as it was set up.**",
|
|
7663
|
-
`> ${canonical.blocked.reason}`,
|
|
7664
|
-
">",
|
|
7665
|
-
"> 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.",
|
|
7666
|
-
""
|
|
7667
|
-
] : [],
|
|
7668
|
-
`- Run ID: ${canonical.run_id}`,
|
|
7669
|
-
`- Target URL: ${canonical.target.url}`
|
|
7670
|
-
];
|
|
7671
|
-
if (canonical.environment) {
|
|
7672
|
-
lines.push(`- Environment: ${canonical.environment}`);
|
|
7673
|
-
}
|
|
7674
|
-
if (canonical.build) {
|
|
7675
|
-
const commit = canonical.build.git_sha?.slice(0, 7);
|
|
7676
|
-
const buildDetails = [
|
|
7677
|
-
canonical.build.label,
|
|
7678
|
-
canonical.build.git_branch,
|
|
7679
|
-
canonical.build.git_dirty && canonical.build.worktree_hash ? `${commit ?? "HEAD"} + uncommitted changes (${canonical.build.worktree_hash.slice(0, 6)}\u2026)` : commit,
|
|
7680
|
-
canonical.build.ci?.provider
|
|
7681
|
-
].filter(Boolean).join(" \xB7 ");
|
|
7682
|
-
if (buildDetails) {
|
|
7683
|
-
lines.push(`- Build: ${buildDetails}`);
|
|
7684
|
-
}
|
|
7685
|
-
}
|
|
7686
|
-
if (canonical.viewport) {
|
|
7687
|
-
lines.push(`- Viewport: ${canonical.viewport.device} (${canonical.viewport.width}\xD7${canonical.viewport.height})`);
|
|
7688
|
-
}
|
|
7689
|
-
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}`);
|
|
7690
|
-
const models = canonical.models;
|
|
7691
|
-
if (models) {
|
|
7692
|
-
const parts = [
|
|
7693
|
-
models.walker ? `Walker: ${models.walker}${models.walker_reported ? ` (${models.walker_reported})` : ""}` : "",
|
|
7694
|
-
models.judge ? `Judge: ${models.judge}${models.judge_reported ? ` (${models.judge_reported})` : ""}` : "",
|
|
7695
|
-
models.matcher ? `Matcher: ${models.matcher}` : ""
|
|
7696
|
-
].filter(Boolean);
|
|
7697
|
-
if (parts.length > 0) {
|
|
7698
|
-
lines.push(`- Models: ${parts.join(" \xB7 ")}`);
|
|
7699
|
-
}
|
|
7700
|
-
if (typeof models.velocity_sec_per_step === "number") {
|
|
7701
|
-
lines.push(`- Velocity: ~${models.velocity_sec_per_step}s/step (${models.total_duration_sec}s total)`);
|
|
7702
|
-
}
|
|
7703
|
-
}
|
|
7704
|
-
const what = canonical.what_worked;
|
|
7705
|
-
if (what && what.length > 0) {
|
|
7706
|
-
lines.push("", "## What worked");
|
|
7707
|
-
for (const w of what)
|
|
7708
|
-
lines.push(`- ${w}`);
|
|
7709
|
-
}
|
|
7710
|
-
const open2 = canonical.open_questions;
|
|
7711
|
-
if (open2 && open2.length > 0) {
|
|
7712
|
-
lines.push("", "## Open questions");
|
|
7713
|
-
for (const q of open2)
|
|
7714
|
-
lines.push(`- ${q}`);
|
|
7715
|
-
}
|
|
7716
|
-
const verification = canonical.verification;
|
|
7717
|
-
if (verification) {
|
|
7718
|
-
lines.push("", "## Since this scenario's last run", "", `Baseline: ${verification.baseline_run_id}`);
|
|
7719
|
-
const group = (label, items, note = "") => {
|
|
7720
|
-
if (items.length === 0)
|
|
7721
|
-
return;
|
|
7722
|
-
lines.push("", `**${label}** (${items.length})${note}`);
|
|
7723
|
-
for (const i of items)
|
|
7724
|
-
lines.push(`- ${i.title}`);
|
|
7725
|
-
};
|
|
7726
|
-
group("Resolved", verification.resolved, " \u2014 re-checked in this run");
|
|
7727
|
-
group("Persists", verification.persists);
|
|
7728
|
-
group("New since that run", verification.new_findings);
|
|
7729
|
-
group("Not seen this run", verification.not_observed, " \u2014 not evidence of a fix");
|
|
7730
|
-
group("Uncertain", verification.uncertain);
|
|
7731
|
-
}
|
|
7732
|
-
const diff = canonical.journey_diff;
|
|
7733
|
-
if (diff) {
|
|
7734
|
-
lines.push("", renderJourneyDiffMarkdown(diff));
|
|
7735
|
-
}
|
|
7736
|
-
lines.push("", "## Findings");
|
|
7737
|
-
if (canonical.findings.length === 0) {
|
|
7738
|
-
lines.push("", "_No findings recorded._");
|
|
7739
|
-
} else {
|
|
7740
|
-
const severityRank = ["critical", "major", "minor", "observation"];
|
|
7741
|
-
const rank = (value, order) => {
|
|
7742
|
-
const i = order.indexOf(value ?? "");
|
|
7743
|
-
return i === -1 ? order.length : i;
|
|
7744
|
-
};
|
|
7745
|
-
const typeRank = ["incorrect", "hinders", "missing", "irrelevant", "helps"];
|
|
7746
|
-
const ordered = [...canonical.findings].sort((a, b) => rank(a.severity, severityRank) - rank(b.severity, severityRank) || rank(a.type, typeRank) - rank(b.type, typeRank));
|
|
7747
|
-
for (const f of ordered) {
|
|
7748
|
-
lines.push("", `### ${f.type} / ${f.severity ?? "unranked"} \u2014 ${f.id}`);
|
|
7749
|
-
if (f.ui_fact === null || f.consequence === null) {
|
|
7750
|
-
lines.push("", "> _Legacy finding: predates the UI-fact / consequence depth gate._");
|
|
7751
|
-
}
|
|
7752
|
-
if (f.ui_fact)
|
|
7753
|
-
lines.push("", "**UI fact.** ", f.ui_fact);
|
|
7754
|
-
if (f.consequence)
|
|
7755
|
-
lines.push("", "**Consequence.** ", f.consequence);
|
|
7756
|
-
if (f.recommendation)
|
|
7757
|
-
lines.push("", "**Recommendation.** ", f.recommendation);
|
|
7758
|
-
for (const stepId of f.linked_step_ids) {
|
|
7759
|
-
const step = canonical.steps.find((st) => st.id === stepId);
|
|
7760
|
-
const label = step ? `Step ${step.step_number}` : stepId;
|
|
7761
|
-
lines.push("", step?.screenshot_ref ? `**Where.** ${label}
|
|
7762
|
-
|
|
7763
|
-
` : `**Where.** ${label}`);
|
|
7764
|
-
}
|
|
7765
|
-
if (f.linked_human_test_id) {
|
|
7766
|
-
lines.push("", `Human test: ${f.linked_human_test_id}`);
|
|
7767
|
-
}
|
|
7768
|
-
}
|
|
7769
|
-
}
|
|
7770
|
-
const analysis = canonical.analysis;
|
|
7771
|
-
if (analysis && analysis.directions.length > 0) {
|
|
7772
|
-
lines.push("", "## Directions", "", `_Structural changes proposed by ${analysis.advisor} after the walk. Reach is a prediction until the listed scenarios are re-run._`);
|
|
7773
|
-
for (const d of analysis.directions) {
|
|
7774
|
-
lines.push("", `### ${d.title} (${d.size})`, "", d.change);
|
|
7775
|
-
lines.push("", `Absorbs ${d.absorbs.length} finding${d.absorbs.length === 1 ? "" : "s"}.`);
|
|
7776
|
-
const helps = d.reach.filter((r) => r.effect === "helps");
|
|
7777
|
-
const risks = d.reach.filter((r) => r.effect === "risk");
|
|
7778
|
-
for (const r of helps)
|
|
7779
|
-
lines.push(`- Likely helps ${r.scenario}: ${r.reason}`);
|
|
7780
|
-
for (const r of risks)
|
|
7781
|
-
lines.push(`- Could hurt ${r.scenario}: ${r.reason}`);
|
|
7782
|
-
if (d.verify.length > 0)
|
|
7783
|
-
lines.push("", `Re-run to verify: ${d.verify.join(", ")}`);
|
|
7784
|
-
}
|
|
7785
|
-
}
|
|
7786
|
-
if (analysis && analysis.observations.length > 0) {
|
|
7787
|
-
lines.push("", "## Also worth knowing", "");
|
|
7788
|
-
for (const o of analysis.observations) {
|
|
7789
|
-
const label = o.kind === "coverage_gap" ? "Not walked" : "Feature risk";
|
|
7790
|
-
lines.push(`- **${label}: ${o.title}.** ${o.detail}`);
|
|
7791
|
-
if (o.suggested_scenario) {
|
|
7792
|
-
lines.push(` Suggested scenario: ${o.suggested_scenario}`);
|
|
7793
|
-
}
|
|
7794
|
-
}
|
|
7795
|
-
}
|
|
7796
|
-
lines.push("", "## Human tests");
|
|
7797
|
-
if (canonical.human_tests.length === 0) {
|
|
7798
|
-
lines.push("", "_No human tests on the outline._");
|
|
7799
|
-
} else {
|
|
7800
|
-
for (const ht of canonical.human_tests) {
|
|
7801
|
-
lines.push("", `- **${ht.question}**`);
|
|
7802
|
-
if (ht.verdict === null) {
|
|
7803
|
-
if (canonical.blocked) {
|
|
7804
|
-
lines.push(` - Verdict: _skipped (walk blocked)_`);
|
|
7805
|
-
} else {
|
|
7806
|
-
lines.push(` - Verdict: _pending judge_ (walker does not self-grade)`);
|
|
7807
|
-
}
|
|
7808
|
-
} else {
|
|
7809
|
-
lines.push(` - Verdict: **${ht.verdict}**`);
|
|
7810
|
-
if (ht.reasoning)
|
|
7811
|
-
lines.push(` - Reasoning: ${ht.reasoning}`);
|
|
7812
|
-
}
|
|
7813
|
-
}
|
|
7814
|
-
}
|
|
7815
|
-
lines.push("", "## Human test overall", "", `> ${canonical.human_test_overall.question}`);
|
|
7816
|
-
if (canonical.human_test_overall.answer === null) {
|
|
7817
|
-
if (canonical.blocked) {
|
|
7818
|
-
lines.push("", "_Skipped (walk blocked)._");
|
|
7819
|
-
} else {
|
|
7820
|
-
lines.push("", "_Pending judge stage._");
|
|
7821
|
-
}
|
|
7822
|
-
} else {
|
|
7823
|
-
lines.push("", canonical.human_test_overall.answer);
|
|
7824
|
-
if (canonical.human_test_overall.reasoning)
|
|
7825
|
-
lines.push("", canonical.human_test_overall.reasoning);
|
|
7826
|
-
}
|
|
7827
|
-
lines.push("", "## Steps");
|
|
7828
|
-
if (canonical.steps.length === 0) {
|
|
7829
|
-
lines.push("", "_No steps recorded._");
|
|
7830
|
-
} else {
|
|
7831
|
-
for (const s of canonical.steps) {
|
|
7832
|
-
lines.push("", `### ${s.step_number}. ${s.screen_title ?? "(untitled)"} \u2014 ${s.id}`);
|
|
7833
|
-
const where = [s.surface, s.url].filter(Boolean).join(" \u2014 ");
|
|
7834
|
-
if (where)
|
|
7835
|
-
lines.push("", `Page: ${where}`);
|
|
7836
|
-
if (s.persona_reasoning)
|
|
7837
|
-
lines.push("", `> ${s.persona_reasoning.replace(/\n/g, "\n> ")}`);
|
|
7838
|
-
if (s.grounding)
|
|
7839
|
-
lines.push("", `Grounding: ${s.grounding}`);
|
|
7840
|
-
if (s.screenshot_ref)
|
|
7841
|
-
lines.push("", ``);
|
|
7842
|
-
}
|
|
7843
|
-
}
|
|
7844
|
-
return `${lines.join("\n")}
|
|
7845
|
-
`;
|
|
7846
|
-
}
|
|
7847
7519
|
var init_walkthrough_canonical2 = __esm({
|
|
7848
7520
|
"../../packages/local-store/dist/walkthrough-canonical.js"() {
|
|
7849
7521
|
"use strict";
|
|
7850
|
-
|
|
7522
|
+
init_revisit();
|
|
7523
|
+
init_directions();
|
|
7851
7524
|
init_atomic();
|
|
7852
7525
|
init_file_lock();
|
|
7853
7526
|
init_dist();
|
|
@@ -7869,8 +7542,8 @@ __export(walkthrough_mirror_exports, {
|
|
|
7869
7542
|
walkthroughBundleSchema: () => walkthroughBundleSchema
|
|
7870
7543
|
});
|
|
7871
7544
|
import { createHash as createHash3 } from "crypto";
|
|
7872
|
-
import { promises as
|
|
7873
|
-
import
|
|
7545
|
+
import { promises as fs14 } from "fs";
|
|
7546
|
+
import path11 from "path";
|
|
7874
7547
|
function toCloudRunUuid(runId) {
|
|
7875
7548
|
if (UUID_RE.test(runId))
|
|
7876
7549
|
return runId.toLowerCase();
|
|
@@ -7920,18 +7593,18 @@ function buildPushUrl(cloudApiUrl, cloudProjectId) {
|
|
|
7920
7593
|
return `${base}/api/projects/${cloudProjectId}/walkthroughs/push`;
|
|
7921
7594
|
}
|
|
7922
7595
|
async function readLocalManifest(projectRoot, runId) {
|
|
7923
|
-
const p =
|
|
7596
|
+
const p = path11.join(runDir(projectRoot, runId), "walkthrough.json");
|
|
7924
7597
|
try {
|
|
7925
|
-
const raw = await
|
|
7598
|
+
const raw = await fs14.readFile(p, "utf8");
|
|
7926
7599
|
return JSON.parse(raw);
|
|
7927
7600
|
} catch {
|
|
7928
7601
|
return null;
|
|
7929
7602
|
}
|
|
7930
7603
|
}
|
|
7931
7604
|
async function readLocalReport(projectRoot, runId) {
|
|
7932
|
-
const p =
|
|
7605
|
+
const p = path11.join(runDir(projectRoot, runId), "report.md");
|
|
7933
7606
|
try {
|
|
7934
|
-
return await
|
|
7607
|
+
return await fs14.readFile(p, "utf8");
|
|
7935
7608
|
} catch (err) {
|
|
7936
7609
|
if (err.code === "ENOENT")
|
|
7937
7610
|
return "";
|
|
@@ -7939,10 +7612,10 @@ async function readLocalReport(projectRoot, runId) {
|
|
|
7939
7612
|
}
|
|
7940
7613
|
}
|
|
7941
7614
|
async function readScreenshotBytes(projectRoot, runId) {
|
|
7942
|
-
const dir =
|
|
7615
|
+
const dir = path11.join(runDir(projectRoot, runId), SCREENSHOTS_DIR);
|
|
7943
7616
|
let names2;
|
|
7944
7617
|
try {
|
|
7945
|
-
names2 = await
|
|
7618
|
+
names2 = await fs14.readdir(dir);
|
|
7946
7619
|
} catch (err) {
|
|
7947
7620
|
if (err.code === "ENOENT")
|
|
7948
7621
|
return /* @__PURE__ */ new Map();
|
|
@@ -7958,15 +7631,15 @@ async function readScreenshotBytes(projectRoot, runId) {
|
|
|
7958
7631
|
const contentType = EXT_TO_CONTENT_TYPE[ext === ".jpeg" ? ".jpeg" : ext];
|
|
7959
7632
|
if (!contentType)
|
|
7960
7633
|
continue;
|
|
7961
|
-
const bytes = await
|
|
7634
|
+
const bytes = await fs14.readFile(path11.join(dir, name));
|
|
7962
7635
|
out.set(seq2, { bytes, filename: name, contentType });
|
|
7963
7636
|
}
|
|
7964
7637
|
return out;
|
|
7965
7638
|
}
|
|
7966
7639
|
async function readVerdictsJson(projectRoot, runId) {
|
|
7967
|
-
const filePath =
|
|
7640
|
+
const filePath = path11.join(runDir(projectRoot, runId), "verdicts.json");
|
|
7968
7641
|
try {
|
|
7969
|
-
const raw = await
|
|
7642
|
+
const raw = await fs14.readFile(filePath, "utf-8");
|
|
7970
7643
|
return JSON.parse(raw);
|
|
7971
7644
|
} catch {
|
|
7972
7645
|
return null;
|
|
@@ -7975,9 +7648,9 @@ async function readVerdictsJson(projectRoot, runId) {
|
|
|
7975
7648
|
async function readProvenance(projectRoot, runId, verdicts) {
|
|
7976
7649
|
if (verdicts?.provenance)
|
|
7977
7650
|
return verdicts.provenance;
|
|
7978
|
-
const filePath =
|
|
7651
|
+
const filePath = path11.join(runDir(projectRoot, runId), "canonical.json");
|
|
7979
7652
|
try {
|
|
7980
|
-
const raw = await
|
|
7653
|
+
const raw = await fs14.readFile(filePath, "utf-8");
|
|
7981
7654
|
const data = JSON.parse(raw);
|
|
7982
7655
|
return data.provenance ?? null;
|
|
7983
7656
|
} catch {
|
|
@@ -7986,7 +7659,7 @@ async function readProvenance(projectRoot, runId, verdicts) {
|
|
|
7986
7659
|
}
|
|
7987
7660
|
async function readCanonicalExtras(projectRoot, runId) {
|
|
7988
7661
|
try {
|
|
7989
|
-
const data = JSON.parse(await
|
|
7662
|
+
const data = JSON.parse(await fs14.readFile(path11.join(runDir(projectRoot, runId), "canonical.json"), "utf-8"));
|
|
7990
7663
|
const strings = (v) => Array.isArray(v) && v.every((x) => typeof x === "string") ? v : void 0;
|
|
7991
7664
|
const verification = bundleVerificationSchema.safeParse(data.verification);
|
|
7992
7665
|
let blocked;
|
|
@@ -8735,7 +8408,7 @@ var init_refs = __esm({
|
|
|
8735
8408
|
});
|
|
8736
8409
|
|
|
8737
8410
|
// ../../packages/local-store/dist/sync/types.js
|
|
8738
|
-
import { z as
|
|
8411
|
+
import { z as z26 } from "zod";
|
|
8739
8412
|
function toRef(d) {
|
|
8740
8413
|
return { type: d.type, id: d.id, label: d.label };
|
|
8741
8414
|
}
|
|
@@ -8800,17 +8473,17 @@ var init_types = __esm({
|
|
|
8800
8473
|
DEFAULT_TIMEOUT_MS4 = 3e4;
|
|
8801
8474
|
MAX_BATCH_BYTES = 6 * 1024 * 1024;
|
|
8802
8475
|
BASE_HASH_BYTES = 96;
|
|
8803
|
-
importConflictSchema =
|
|
8804
|
-
type:
|
|
8805
|
-
id:
|
|
8806
|
-
base_hash:
|
|
8807
|
-
cloud_hash:
|
|
8808
|
-
cloud_updated_at:
|
|
8809
|
-
cloud_changed_by:
|
|
8476
|
+
importConflictSchema = z26.object({
|
|
8477
|
+
type: z26.string(),
|
|
8478
|
+
id: z26.string(),
|
|
8479
|
+
base_hash: z26.string().nullish(),
|
|
8480
|
+
cloud_hash: z26.string(),
|
|
8481
|
+
cloud_updated_at: z26.string().nullish(),
|
|
8482
|
+
cloud_changed_by: z26.string().nullish()
|
|
8810
8483
|
});
|
|
8811
|
-
importResponseSchema =
|
|
8812
|
-
imported:
|
|
8813
|
-
conflicts:
|
|
8484
|
+
importResponseSchema = z26.object({
|
|
8485
|
+
imported: z26.number().int().min(0),
|
|
8486
|
+
conflicts: z26.array(importConflictSchema).optional()
|
|
8814
8487
|
});
|
|
8815
8488
|
}
|
|
8816
8489
|
});
|
|
@@ -9061,7 +8734,7 @@ var init_push = __esm({
|
|
|
9061
8734
|
});
|
|
9062
8735
|
|
|
9063
8736
|
// ../../packages/local-store/dist/sync/context.js
|
|
9064
|
-
import { promises as
|
|
8737
|
+
import { promises as fs15 } from "fs";
|
|
9065
8738
|
async function syncContext(target) {
|
|
9066
8739
|
const { projectRoot } = target;
|
|
9067
8740
|
const summary = zeroContext();
|
|
@@ -9069,7 +8742,7 @@ async function syncContext(target) {
|
|
|
9069
8742
|
const conflicts = [];
|
|
9070
8743
|
let raw;
|
|
9071
8744
|
try {
|
|
9072
|
-
raw = await
|
|
8745
|
+
raw = await fs15.readFile(contextPath(projectRoot), "utf8");
|
|
9073
8746
|
} catch (err) {
|
|
9074
8747
|
if (err.code === "ENOENT")
|
|
9075
8748
|
return { summary, errors, conflicts };
|
|
@@ -9150,7 +8823,7 @@ var init_context3 = __esm({
|
|
|
9150
8823
|
// ../../packages/local-store/dist/git-branches.js
|
|
9151
8824
|
import { execFile } from "child_process";
|
|
9152
8825
|
import { realpathSync } from "fs";
|
|
9153
|
-
import
|
|
8826
|
+
import path12 from "path";
|
|
9154
8827
|
function defaultGitExec(cwd, args, input) {
|
|
9155
8828
|
return new Promise((resolve3) => {
|
|
9156
8829
|
let settled = false;
|
|
@@ -9209,7 +8882,7 @@ async function otherBranchesWithFiles(cwd, absPaths, opts = {}) {
|
|
|
9209
8882
|
return out;
|
|
9210
8883
|
return null;
|
|
9211
8884
|
}
|
|
9212
|
-
const top =
|
|
8885
|
+
const top = path12.resolve(topRes.stdout.trim());
|
|
9213
8886
|
if (!top)
|
|
9214
8887
|
return out;
|
|
9215
8888
|
const refsRes = await git2(cwd, [
|
|
@@ -9247,8 +8920,8 @@ async function otherBranchesWithFiles(cwd, absPaths, opts = {}) {
|
|
|
9247
8920
|
} catch {
|
|
9248
8921
|
}
|
|
9249
8922
|
const rel = absPaths.map((abs) => {
|
|
9250
|
-
const fromCwd =
|
|
9251
|
-
return
|
|
8923
|
+
const fromCwd = path12.relative(cwd, abs);
|
|
8924
|
+
return path12.relative(top, path12.resolve(base, fromCwd)).split(path12.sep).join("/");
|
|
9252
8925
|
});
|
|
9253
8926
|
const queries = [];
|
|
9254
8927
|
const lines = [];
|
|
@@ -9286,8 +8959,8 @@ var init_git_branches = __esm({
|
|
|
9286
8959
|
});
|
|
9287
8960
|
|
|
9288
8961
|
// ../../packages/local-store/dist/deletions.js
|
|
9289
|
-
import { promises as
|
|
9290
|
-
import
|
|
8962
|
+
import { promises as fs16 } from "fs";
|
|
8963
|
+
import path13 from "path";
|
|
9291
8964
|
function describeDeletionCount(type2, n) {
|
|
9292
8965
|
const nouns = {
|
|
9293
8966
|
persona: ["persona", "personas"],
|
|
@@ -9314,7 +8987,7 @@ function summarizeDeletions(items) {
|
|
|
9314
8987
|
}
|
|
9315
8988
|
async function readPushedEntities(projectRoot) {
|
|
9316
8989
|
try {
|
|
9317
|
-
const raw = await
|
|
8990
|
+
const raw = await fs16.readFile(pushedEntitiesPath(projectRoot), "utf8");
|
|
9318
8991
|
const parsed = JSON.parse(raw);
|
|
9319
8992
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
9320
8993
|
return {};
|
|
@@ -9353,11 +9026,11 @@ async function rememberPushedEntities(projectRoot, known) {
|
|
|
9353
9026
|
}
|
|
9354
9027
|
async function readLenient(projectRoot, relPath) {
|
|
9355
9028
|
try {
|
|
9356
|
-
const raw = await
|
|
9029
|
+
const raw = await fs16.readFile(path13.join(projectRoot, relPath), "utf8");
|
|
9357
9030
|
const fm = parseFrontmatter(raw).frontmatter;
|
|
9358
9031
|
if (typeof fm.id !== "string" || fm.id.length === 0)
|
|
9359
9032
|
return null;
|
|
9360
|
-
const slug = typeof fm.slug === "string" ? fm.slug :
|
|
9033
|
+
const slug = typeof fm.slug === "string" ? fm.slug : path13.basename(relPath, ".md");
|
|
9361
9034
|
const run = typeof fm.run === "string" ? fm.run : void 0;
|
|
9362
9035
|
return { id: fm.id, slug, run };
|
|
9363
9036
|
} catch {
|
|
@@ -9779,7 +9452,7 @@ var init_deletions2 = __esm({
|
|
|
9779
9452
|
});
|
|
9780
9453
|
|
|
9781
9454
|
// ../../packages/local-store/dist/sync/scan.js
|
|
9782
|
-
import { promises as
|
|
9455
|
+
import { promises as fs17 } from "fs";
|
|
9783
9456
|
function blockedRef(refs, blocked) {
|
|
9784
9457
|
if (!refs)
|
|
9785
9458
|
return null;
|
|
@@ -9875,7 +9548,7 @@ async function scanPending(projectRoot) {
|
|
|
9875
9548
|
continue;
|
|
9876
9549
|
let bytes;
|
|
9877
9550
|
try {
|
|
9878
|
-
bytes = await
|
|
9551
|
+
bytes = await fs17.readFile(entity.absPath, "utf8");
|
|
9879
9552
|
} catch {
|
|
9880
9553
|
continue;
|
|
9881
9554
|
}
|
|
@@ -9936,7 +9609,7 @@ var init_scan = __esm({
|
|
|
9936
9609
|
});
|
|
9937
9610
|
|
|
9938
9611
|
// ../../packages/local-store/dist/sync/walkthroughs.js
|
|
9939
|
-
import { promises as
|
|
9612
|
+
import { promises as fs18 } from "fs";
|
|
9940
9613
|
import nodePath from "path";
|
|
9941
9614
|
async function syncWalkthroughs(projectRoot, deps, unpushableScenarios = /* @__PURE__ */ new Set()) {
|
|
9942
9615
|
const summary = zeroWalkthroughs();
|
|
@@ -9964,7 +9637,7 @@ async function syncWalkthroughs(projectRoot, deps, unpushableScenarios = /* @__P
|
|
|
9964
9637
|
const manifestPath = nodePath.join(runDir(projectRoot, runId), "walkthrough.json");
|
|
9965
9638
|
let manifestRaw;
|
|
9966
9639
|
try {
|
|
9967
|
-
manifestRaw = await
|
|
9640
|
+
manifestRaw = await fs18.readFile(manifestPath, "utf8");
|
|
9968
9641
|
} catch {
|
|
9969
9642
|
continue;
|
|
9970
9643
|
}
|
|
@@ -10303,8 +9976,8 @@ var init_disconnect = __esm({
|
|
|
10303
9976
|
});
|
|
10304
9977
|
|
|
10305
9978
|
// ../../packages/local-store/dist/status.js
|
|
10306
|
-
import { promises as
|
|
10307
|
-
import
|
|
9979
|
+
import { promises as fs19 } from "fs";
|
|
9980
|
+
import path14 from "path";
|
|
10308
9981
|
async function countType(projectRoot, type2) {
|
|
10309
9982
|
const result = await listEntities(projectRoot, type2);
|
|
10310
9983
|
const contentHashes = /* @__PURE__ */ new Map();
|
|
@@ -10314,7 +9987,7 @@ async function countType(projectRoot, type2) {
|
|
|
10314
9987
|
if (typeof id !== "string" || typeof slug !== "string")
|
|
10315
9988
|
continue;
|
|
10316
9989
|
try {
|
|
10317
|
-
const raw = await
|
|
9990
|
+
const raw = await fs19.readFile(entity.absPath, "utf8");
|
|
10318
9991
|
contentHashes.set(id, hashNormalizedContent(raw));
|
|
10319
9992
|
} catch {
|
|
10320
9993
|
}
|
|
@@ -10408,7 +10081,7 @@ async function countContext(projectRoot, opts = {
|
|
|
10408
10081
|
return base;
|
|
10409
10082
|
let pending = true;
|
|
10410
10083
|
try {
|
|
10411
|
-
const bytes = await
|
|
10084
|
+
const bytes = await fs19.readFile(contextPath(projectRoot), "utf8");
|
|
10412
10085
|
const hash = hashNormalizedContent(bytes);
|
|
10413
10086
|
pending = opts.lastPush["context:root"] !== hash;
|
|
10414
10087
|
} catch {
|
|
@@ -10427,10 +10100,10 @@ async function countWalkthroughs(projectRoot, opts = { cloudLinked: false, lastP
|
|
|
10427
10100
|
let pushed = 0;
|
|
10428
10101
|
let pending = 0;
|
|
10429
10102
|
for (const runId of valid) {
|
|
10430
|
-
const manifestPath =
|
|
10103
|
+
const manifestPath = path14.join(projectRoot, "iamu", ".runs", runId, "walkthrough.json");
|
|
10431
10104
|
let completed = false;
|
|
10432
10105
|
try {
|
|
10433
|
-
const raw = await
|
|
10106
|
+
const raw = await fs19.readFile(manifestPath, "utf8");
|
|
10434
10107
|
const manifest = JSON.parse(raw);
|
|
10435
10108
|
completed = manifest.status === "completed";
|
|
10436
10109
|
} catch {
|
|
@@ -10450,7 +10123,7 @@ async function getProjectStatus(projectRoot, opts = {}) {
|
|
|
10450
10123
|
const config = await readConfig(projectRoot);
|
|
10451
10124
|
const iamuPath = iamuDir(projectRoot);
|
|
10452
10125
|
try {
|
|
10453
|
-
await
|
|
10126
|
+
await fs19.access(iamuPath);
|
|
10454
10127
|
} catch {
|
|
10455
10128
|
throw new Error(`status: iamu/ directory missing at ${iamuPath}`);
|
|
10456
10129
|
}
|
|
@@ -10632,22 +10305,22 @@ var init_status = __esm({
|
|
|
10632
10305
|
});
|
|
10633
10306
|
|
|
10634
10307
|
// ../../packages/local-store/dist/prune.js
|
|
10635
|
-
import { promises as
|
|
10636
|
-
import
|
|
10308
|
+
import { promises as fs20 } from "fs";
|
|
10309
|
+
import path15 from "path";
|
|
10637
10310
|
async function getDirectoryStats(dirPath) {
|
|
10638
10311
|
let bytes = 0;
|
|
10639
10312
|
let count = 0;
|
|
10640
10313
|
try {
|
|
10641
|
-
const entries = await
|
|
10314
|
+
const entries = await fs20.readdir(dirPath, { withFileTypes: true });
|
|
10642
10315
|
for (const entry of entries) {
|
|
10643
|
-
const fullPath =
|
|
10316
|
+
const fullPath = path15.join(dirPath, entry.name);
|
|
10644
10317
|
if (entry.isDirectory()) {
|
|
10645
10318
|
const sub = await getDirectoryStats(fullPath);
|
|
10646
10319
|
bytes += sub.bytes;
|
|
10647
10320
|
count += sub.count;
|
|
10648
10321
|
} else if (entry.isFile()) {
|
|
10649
10322
|
try {
|
|
10650
|
-
const s = await
|
|
10323
|
+
const s = await fs20.stat(fullPath);
|
|
10651
10324
|
bytes += s.size;
|
|
10652
10325
|
count += 1;
|
|
10653
10326
|
} catch {
|
|
@@ -10663,12 +10336,12 @@ async function getScreenshotsStats(screenshotsDir) {
|
|
|
10663
10336
|
let count = 0;
|
|
10664
10337
|
const paths = [];
|
|
10665
10338
|
try {
|
|
10666
|
-
const entries = await
|
|
10339
|
+
const entries = await fs20.readdir(screenshotsDir, { withFileTypes: true });
|
|
10667
10340
|
for (const entry of entries) {
|
|
10668
10341
|
if (entry.isFile() && (entry.name.endsWith(".png") || entry.name.endsWith(".jpg") || entry.name.endsWith(".jpeg") || entry.name.endsWith(".webp"))) {
|
|
10669
|
-
const fullPath =
|
|
10342
|
+
const fullPath = path15.join(screenshotsDir, entry.name);
|
|
10670
10343
|
try {
|
|
10671
|
-
const s = await
|
|
10344
|
+
const s = await fs20.stat(fullPath);
|
|
10672
10345
|
bytes += s.size;
|
|
10673
10346
|
count += 1;
|
|
10674
10347
|
paths.push(fullPath);
|
|
@@ -10688,8 +10361,8 @@ async function pruneRuns(projectRoot, opts = {}) {
|
|
|
10688
10361
|
let timestamp2 = null;
|
|
10689
10362
|
let scenarioSlug;
|
|
10690
10363
|
try {
|
|
10691
|
-
const manifestPath =
|
|
10692
|
-
const raw = await
|
|
10364
|
+
const manifestPath = path15.join(dirPath, "walkthrough.json");
|
|
10365
|
+
const raw = await fs20.readFile(manifestPath, "utf-8");
|
|
10693
10366
|
const parsed = JSON.parse(raw);
|
|
10694
10367
|
if (parsed.scenario_slug)
|
|
10695
10368
|
scenarioSlug = parsed.scenario_slug;
|
|
@@ -10701,7 +10374,7 @@ async function pruneRuns(projectRoot, opts = {}) {
|
|
|
10701
10374
|
}
|
|
10702
10375
|
if (!timestamp2 || Number.isNaN(timestamp2.getTime())) {
|
|
10703
10376
|
try {
|
|
10704
|
-
const st = await
|
|
10377
|
+
const st = await fs20.stat(dirPath);
|
|
10705
10378
|
timestamp2 = st.mtime;
|
|
10706
10379
|
} catch {
|
|
10707
10380
|
timestamp2 = /* @__PURE__ */ new Date();
|
|
@@ -10736,12 +10409,12 @@ async function pruneRuns(projectRoot, opts = {}) {
|
|
|
10736
10409
|
let totalScreenshotsDeleted = 0;
|
|
10737
10410
|
let totalBytesReclaimed = 0;
|
|
10738
10411
|
for (const c of candidates) {
|
|
10739
|
-
const screenshotsDir =
|
|
10412
|
+
const screenshotsDir = path15.join(c.dirPath, "screenshots");
|
|
10740
10413
|
const shotStats = await getScreenshotsStats(screenshotsDir);
|
|
10741
10414
|
if (opts.screenshotsOnly) {
|
|
10742
10415
|
if (!opts.dryRun) {
|
|
10743
10416
|
for (const shotPath of shotStats.paths) {
|
|
10744
|
-
await
|
|
10417
|
+
await fs20.unlink(shotPath).catch(() => {
|
|
10745
10418
|
});
|
|
10746
10419
|
}
|
|
10747
10420
|
}
|
|
@@ -10758,7 +10431,7 @@ async function pruneRuns(projectRoot, opts = {}) {
|
|
|
10758
10431
|
} else {
|
|
10759
10432
|
const dirStats = await getDirectoryStats(c.dirPath);
|
|
10760
10433
|
if (!opts.dryRun) {
|
|
10761
|
-
await
|
|
10434
|
+
await fs20.rm(c.dirPath, { recursive: true, force: true });
|
|
10762
10435
|
}
|
|
10763
10436
|
prunedRuns.push({
|
|
10764
10437
|
runId: c.runId,
|
|
@@ -10790,8 +10463,8 @@ var init_prune = __esm({
|
|
|
10790
10463
|
|
|
10791
10464
|
// ../../packages/local-store/dist/explore-map.js
|
|
10792
10465
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
10793
|
-
import { promises as
|
|
10794
|
-
import
|
|
10466
|
+
import { promises as fs21 } from "fs";
|
|
10467
|
+
import path16 from "path";
|
|
10795
10468
|
function templateToken(token) {
|
|
10796
10469
|
if (token === "")
|
|
10797
10470
|
return token;
|
|
@@ -10870,7 +10543,7 @@ function fnv1a(s) {
|
|
|
10870
10543
|
return h.toString(16).padStart(8, "0");
|
|
10871
10544
|
}
|
|
10872
10545
|
function exploreDir(projectRoot) {
|
|
10873
|
-
return
|
|
10546
|
+
return path16.join(cacheDir(projectRoot), "explore");
|
|
10874
10547
|
}
|
|
10875
10548
|
function composeExploreId(now, randomHex) {
|
|
10876
10549
|
const ts = now.toISOString().replace(/[:.]/g, "-").replace(/Z$/, "");
|
|
@@ -10882,17 +10555,17 @@ function isValidExploreId(id) {
|
|
|
10882
10555
|
function mapPath(projectRoot, id) {
|
|
10883
10556
|
if (!isValidExploreId(id))
|
|
10884
10557
|
throw new Error(`Not an explore map id: ${id}`);
|
|
10885
|
-
return
|
|
10558
|
+
return path16.join(exploreDir(projectRoot), id, "map.json");
|
|
10886
10559
|
}
|
|
10887
10560
|
async function writeExploreMap(projectRoot, map2) {
|
|
10888
10561
|
const file = mapPath(projectRoot, map2.id);
|
|
10889
|
-
await
|
|
10562
|
+
await fs21.mkdir(path16.dirname(file), { recursive: true });
|
|
10890
10563
|
await atomicWriteFile(file, `${JSON.stringify(map2, null, 2)}
|
|
10891
10564
|
`);
|
|
10892
10565
|
return file;
|
|
10893
10566
|
}
|
|
10894
10567
|
async function readExploreMap(projectRoot, id) {
|
|
10895
|
-
const raw = await
|
|
10568
|
+
const raw = await fs21.readFile(mapPath(projectRoot, id), "utf8");
|
|
10896
10569
|
const map2 = JSON.parse(raw);
|
|
10897
10570
|
if (map2?.schema !== EXPLORE_MAP_SCHEMA || !Array.isArray(map2.surfaces)) {
|
|
10898
10571
|
throw new Error(`Unreadable explore map: ${id}`);
|
|
@@ -10901,7 +10574,7 @@ async function readExploreMap(projectRoot, id) {
|
|
|
10901
10574
|
}
|
|
10902
10575
|
async function listExploreMaps(projectRoot) {
|
|
10903
10576
|
try {
|
|
10904
|
-
const entries = await
|
|
10577
|
+
const entries = await fs21.readdir(exploreDir(projectRoot), {
|
|
10905
10578
|
withFileTypes: true
|
|
10906
10579
|
});
|
|
10907
10580
|
return entries.filter((e) => e.isDirectory() && isValidExploreId(e.name)).map((e) => e.name).sort();
|
|
@@ -11002,10 +10675,10 @@ async function readAllRunSteps(projectRoot) {
|
|
|
11002
10675
|
function matchSurfaceCapabilities(place, capabilities) {
|
|
11003
10676
|
const normPlace = place.length > 1 ? place.replace(/\/+$/, "") : place;
|
|
11004
10677
|
const matchedCaps = /* @__PURE__ */ new Map();
|
|
11005
|
-
for (const
|
|
11006
|
-
if (!
|
|
10678
|
+
for (const cap2 of capabilities) {
|
|
10679
|
+
if (!cap2.paths || cap2.paths.length === 0)
|
|
11007
10680
|
continue;
|
|
11008
|
-
for (const rawPath of
|
|
10681
|
+
for (const rawPath of cap2.paths) {
|
|
11009
10682
|
if (!rawPath)
|
|
11010
10683
|
continue;
|
|
11011
10684
|
const cleanPath = rawPath.length > 1 ? rawPath.replace(/\/+$/, "") : rawPath;
|
|
@@ -11016,9 +10689,9 @@ function matchSurfaceCapabilities(place, capabilities) {
|
|
|
11016
10689
|
isMatch = normPlace === cleanPath || normPlace.startsWith(`${cleanPath}/`) || normPlace.startsWith(`${cleanPath}?`) || normPlace.startsWith(`${cleanPath}#`);
|
|
11017
10690
|
}
|
|
11018
10691
|
if (isMatch) {
|
|
11019
|
-
const prevLen = matchedCaps.get(
|
|
10692
|
+
const prevLen = matchedCaps.get(cap2.slug) ?? -1;
|
|
11020
10693
|
if (cleanPath.length > prevLen) {
|
|
11021
|
-
matchedCaps.set(
|
|
10694
|
+
matchedCaps.set(cap2.slug, cleanPath.length);
|
|
11022
10695
|
}
|
|
11023
10696
|
}
|
|
11024
10697
|
}
|
|
@@ -11121,94 +10794,831 @@ function groupUnassignedByPrefix(surfaces) {
|
|
|
11121
10794
|
g.examples.push(pathOnly);
|
|
11122
10795
|
}
|
|
11123
10796
|
}
|
|
11124
|
-
return [...groups.values()].sort((a, b) => b.surfaces - a.surfaces || a.prefix.localeCompare(b.prefix));
|
|
10797
|
+
return [...groups.values()].sort((a, b) => b.surfaces - a.surfaces || a.prefix.localeCompare(b.prefix));
|
|
10798
|
+
}
|
|
10799
|
+
async function readCapabilityCoverage(projectRoot, coverage) {
|
|
10800
|
+
const [capsResult, outlinesResult] = await Promise.all([
|
|
10801
|
+
listEntities(projectRoot, "capability"),
|
|
10802
|
+
listEntities(projectRoot, "outline")
|
|
10803
|
+
]);
|
|
10804
|
+
const caps = capsResult.entities.map((e) => ({
|
|
10805
|
+
slug: e.frontmatter.slug,
|
|
10806
|
+
title: e.frontmatter.title,
|
|
10807
|
+
area: e.frontmatter.area ?? null,
|
|
10808
|
+
paths: e.frontmatter.paths ?? []
|
|
10809
|
+
}));
|
|
10810
|
+
const outlines = outlinesResult.entities.map((e) => ({
|
|
10811
|
+
primary_capability: e.frontmatter.primary_capability,
|
|
10812
|
+
additional_capabilities: e.frontmatter.additional_capabilities ?? []
|
|
10813
|
+
}));
|
|
10814
|
+
return summarizeCapabilityCoverage(coverage.surfaces, caps, outlines);
|
|
10815
|
+
}
|
|
10816
|
+
function actionSignature(a) {
|
|
10817
|
+
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;
|
|
10818
|
+
return `${a.role} "${a.name}" \u2192 ${where}`;
|
|
10819
|
+
}
|
|
10820
|
+
function diffExploreMaps(a, b) {
|
|
10821
|
+
const aByKey = new Map(a.surfaces.map((s) => [s.key, s]));
|
|
10822
|
+
const bByKey = new Map(b.surfaces.map((s) => [s.key, s]));
|
|
10823
|
+
const ref = (s) => ({
|
|
10824
|
+
key: s.key,
|
|
10825
|
+
label: s.label
|
|
10826
|
+
});
|
|
10827
|
+
const onlyA = a.surfaces.filter((s) => !bByKey.has(s.key)).map(ref);
|
|
10828
|
+
const onlyB = b.surfaces.filter((s) => !aByKey.has(s.key)).map(ref);
|
|
10829
|
+
const changed = [];
|
|
10830
|
+
let common2 = 0;
|
|
10831
|
+
for (const sa of a.surfaces) {
|
|
10832
|
+
const sb = bByKey.get(sa.key);
|
|
10833
|
+
if (!sb)
|
|
10834
|
+
continue;
|
|
10835
|
+
common2++;
|
|
10836
|
+
const setA = new Set(sa.actions.map(actionSignature));
|
|
10837
|
+
const setB = new Set(sb.actions.map(actionSignature));
|
|
10838
|
+
const inA = [...setA].filter((x) => !setB.has(x)).sort();
|
|
10839
|
+
const inB = [...setB].filter((x) => !setA.has(x)).sort();
|
|
10840
|
+
if (inA.length > 0 || inB.length > 0) {
|
|
10841
|
+
changed.push({ ...ref(sa), only_a: inA, only_b: inB });
|
|
10842
|
+
}
|
|
10843
|
+
}
|
|
10844
|
+
const union2 = common2 + onlyA.length + onlyB.length;
|
|
10845
|
+
return {
|
|
10846
|
+
a: a.id,
|
|
10847
|
+
b: b.id,
|
|
10848
|
+
common: common2,
|
|
10849
|
+
only_a: onlyA,
|
|
10850
|
+
only_b: onlyB,
|
|
10851
|
+
surface_overlap: union2 === 0 ? 1 : common2 / union2,
|
|
10852
|
+
changed_actions: changed
|
|
10853
|
+
};
|
|
10854
|
+
}
|
|
10855
|
+
var EXPLORE_MAP_SCHEMA, UUID, DATE, MAP_ID;
|
|
10856
|
+
var init_explore_map = __esm({
|
|
10857
|
+
"../../packages/local-store/dist/explore-map.js"() {
|
|
10858
|
+
"use strict";
|
|
10859
|
+
init_atomic();
|
|
10860
|
+
init_entity();
|
|
10861
|
+
init_paths();
|
|
10862
|
+
init_revisit();
|
|
10863
|
+
init_runs();
|
|
10864
|
+
init_walkthrough_canonical2();
|
|
10865
|
+
EXPLORE_MAP_SCHEMA = 1;
|
|
10866
|
+
UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
10867
|
+
DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
10868
|
+
MAP_ID = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}-[0-9a-f]{6}$/;
|
|
10869
|
+
}
|
|
10870
|
+
});
|
|
10871
|
+
|
|
10872
|
+
// ../../packages/local-store/dist/journey-diff.js
|
|
10873
|
+
function isProblemFinding(type2) {
|
|
10874
|
+
return PROBLEM_TYPES2.has(type2.toLowerCase());
|
|
10875
|
+
}
|
|
10876
|
+
function levenshteinDistance(a, b) {
|
|
10877
|
+
if (a === b)
|
|
10878
|
+
return 0;
|
|
10879
|
+
if (!a.length)
|
|
10880
|
+
return b.length;
|
|
10881
|
+
if (!b.length)
|
|
10882
|
+
return a.length;
|
|
10883
|
+
const row = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
10884
|
+
for (let i = 1; i <= a.length; i++) {
|
|
10885
|
+
let prev = i;
|
|
10886
|
+
for (let j = 1; j <= b.length; j++) {
|
|
10887
|
+
const val = a[i - 1] === b[j - 1] ? row[j - 1] : Math.min(row[j - 1], prev, row[j]) + 1;
|
|
10888
|
+
row[j - 1] = prev;
|
|
10889
|
+
prev = val;
|
|
10890
|
+
}
|
|
10891
|
+
row[b.length] = prev;
|
|
10892
|
+
}
|
|
10893
|
+
return row[b.length];
|
|
10894
|
+
}
|
|
10895
|
+
function normalizeAction(action) {
|
|
10896
|
+
if (!action)
|
|
10897
|
+
return "";
|
|
10898
|
+
return action.toLowerCase().replace(/['"`]/g, "").replace(/[^\p{L}\p{N}\s/_-]+/gu, " ").replace(/\s+/g, " ").trim();
|
|
10899
|
+
}
|
|
10900
|
+
function actionTokens(action) {
|
|
10901
|
+
const norm = normalizeAction(action);
|
|
10902
|
+
if (!norm)
|
|
10903
|
+
return /* @__PURE__ */ new Set();
|
|
10904
|
+
return new Set(norm.split(/\s+/).filter(Boolean));
|
|
10905
|
+
}
|
|
10906
|
+
function actionSimilarity(a, b) {
|
|
10907
|
+
const normA = normalizeAction(a);
|
|
10908
|
+
const normB = normalizeAction(b);
|
|
10909
|
+
if (!normA && !normB)
|
|
10910
|
+
return 1;
|
|
10911
|
+
if (!normA || !normB)
|
|
10912
|
+
return 0;
|
|
10913
|
+
if (normA === normB)
|
|
10914
|
+
return 1;
|
|
10915
|
+
const tokensA = actionTokens(normA);
|
|
10916
|
+
const tokensB = actionTokens(normB);
|
|
10917
|
+
let intersection = 0;
|
|
10918
|
+
for (const t of tokensA) {
|
|
10919
|
+
if (tokensB.has(t))
|
|
10920
|
+
intersection++;
|
|
10921
|
+
}
|
|
10922
|
+
if (intersection === 0) {
|
|
10923
|
+
if (tokensA.size <= 1 && tokensB.size <= 1) {
|
|
10924
|
+
const maxLen = Math.max(normA.length, normB.length);
|
|
10925
|
+
const levDist = levenshteinDistance(normA, normB);
|
|
10926
|
+
return levDist <= 2 && maxLen > 0 ? 1 - levDist / maxLen : 0;
|
|
10927
|
+
}
|
|
10928
|
+
return 0;
|
|
10929
|
+
}
|
|
10930
|
+
const union2 = tokensA.size + tokensB.size - intersection;
|
|
10931
|
+
const jaccard = union2 > 0 ? intersection / union2 : 0;
|
|
10932
|
+
const minSize = Math.min(tokensA.size, tokensB.size);
|
|
10933
|
+
const subsetRatio = minSize > 0 ? intersection / minSize : 0;
|
|
10934
|
+
if (normA.includes(normB) || normB.includes(normA) || subsetRatio === 1) {
|
|
10935
|
+
return Math.max(0.85, 0.7 + 0.3 * jaccard);
|
|
10936
|
+
}
|
|
10937
|
+
return 0.5 * jaccard + 0.5 * subsetRatio;
|
|
10938
|
+
}
|
|
10939
|
+
function routeSimilarity(urlA, surfaceA, urlB, surfaceB) {
|
|
10940
|
+
const normUrlA = normalizePlaceKey(urlA);
|
|
10941
|
+
const normUrlB = normalizePlaceKey(urlB);
|
|
10942
|
+
const hasUrlA = normUrlA !== null;
|
|
10943
|
+
const hasUrlB = normUrlB !== null;
|
|
10944
|
+
let urlScore = null;
|
|
10945
|
+
if (hasUrlA && hasUrlB) {
|
|
10946
|
+
if (normUrlA === normUrlB) {
|
|
10947
|
+
urlScore = 1;
|
|
10948
|
+
} else {
|
|
10949
|
+
const pathA = normUrlA.split("?")[0];
|
|
10950
|
+
const pathB = normUrlB.split("?")[0];
|
|
10951
|
+
urlScore = pathA === pathB ? 0.8 : 0;
|
|
10952
|
+
}
|
|
10953
|
+
}
|
|
10954
|
+
const sA = surfaceA?.trim();
|
|
10955
|
+
const sB = surfaceB?.trim();
|
|
10956
|
+
let surfaceScore = null;
|
|
10957
|
+
if (sA && sB) {
|
|
10958
|
+
if (surfaceKey(sA) === surfaceKey(sB)) {
|
|
10959
|
+
surfaceScore = 1;
|
|
10960
|
+
} else if (surfaceMatches(sA, sB) || surfaceMatches(sB, sA)) {
|
|
10961
|
+
surfaceScore = 0.8;
|
|
10962
|
+
} else {
|
|
10963
|
+
surfaceScore = 0;
|
|
10964
|
+
}
|
|
10965
|
+
}
|
|
10966
|
+
if (urlScore !== null && surfaceScore !== null) {
|
|
10967
|
+
if (urlScore >= 0.8 && surfaceScore >= 0.8)
|
|
10968
|
+
return 1;
|
|
10969
|
+
if (urlScore >= 0.8)
|
|
10970
|
+
return 0.85;
|
|
10971
|
+
if (surfaceScore >= 0.8)
|
|
10972
|
+
return 0.75;
|
|
10973
|
+
return 0;
|
|
10974
|
+
}
|
|
10975
|
+
if (urlScore !== null)
|
|
10976
|
+
return urlScore;
|
|
10977
|
+
if (surfaceScore !== null)
|
|
10978
|
+
return surfaceScore;
|
|
10979
|
+
return 0.5;
|
|
10980
|
+
}
|
|
10981
|
+
function stepSimilarity(a, b) {
|
|
10982
|
+
const rSim = routeSimilarity(a.url, a.surface, b.url, b.surface);
|
|
10983
|
+
const aSim = actionSimilarity(a.action, b.action);
|
|
10984
|
+
if (aSim < 0.25 && a.action && b.action) {
|
|
10985
|
+
return Math.min(0.2, rSim * 0.2);
|
|
10986
|
+
}
|
|
10987
|
+
if (rSim === 0) {
|
|
10988
|
+
return aSim >= 0.8 ? aSim * 0.4 : 0.1;
|
|
10989
|
+
}
|
|
10990
|
+
return 0.5 * rSim + 0.5 * aSim;
|
|
10991
|
+
}
|
|
10992
|
+
function findingMatchesStep(finding, stepNumber2) {
|
|
10993
|
+
if (finding.linked_step_ids && finding.linked_step_ids.length > 0) {
|
|
10994
|
+
return finding.linked_step_ids.some((id) => id === `step-${stepNumber2}` || id === String(stepNumber2));
|
|
10995
|
+
}
|
|
10996
|
+
if (finding.walkthrough_step_id) {
|
|
10997
|
+
const raw = finding.walkthrough_step_id.trim();
|
|
10998
|
+
if (raw === `step-${stepNumber2}` || raw === String(stepNumber2)) {
|
|
10999
|
+
return true;
|
|
11000
|
+
}
|
|
11001
|
+
}
|
|
11002
|
+
return false;
|
|
11003
|
+
}
|
|
11004
|
+
function isStallNote(note) {
|
|
11005
|
+
if (!note)
|
|
11006
|
+
return false;
|
|
11007
|
+
const lower = note.toLowerCase();
|
|
11008
|
+
return STALL_KEYWORDS.some((kw) => lower.includes(kw));
|
|
11009
|
+
}
|
|
11010
|
+
function alignJourneySteps(scenario, baselineRunId, currentRunId, baselineSteps, baselineFindings, currentSteps, currentFindings) {
|
|
11011
|
+
const m = baselineSteps.length;
|
|
11012
|
+
const n = currentSteps.length;
|
|
11013
|
+
const GAP_PENALTY = -0.7;
|
|
11014
|
+
const MATCH_THRESHOLD = 0.45;
|
|
11015
|
+
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
11016
|
+
for (let i2 = 0; i2 <= m; i2++)
|
|
11017
|
+
dp[i2][0] = i2 * GAP_PENALTY;
|
|
11018
|
+
for (let j2 = 0; j2 <= n; j2++)
|
|
11019
|
+
dp[0][j2] = j2 * GAP_PENALTY;
|
|
11020
|
+
for (let i2 = 1; i2 <= m; i2++) {
|
|
11021
|
+
for (let j2 = 1; j2 <= n; j2++) {
|
|
11022
|
+
const sim = stepSimilarity(baselineSteps[i2 - 1], currentSteps[j2 - 1]);
|
|
11023
|
+
const matchScore = sim >= MATCH_THRESHOLD ? sim * 2 - 0.2 : -1.5;
|
|
11024
|
+
const match2 = dp[i2 - 1][j2 - 1] + matchScore;
|
|
11025
|
+
const delP = dp[i2 - 1][j2] + GAP_PENALTY;
|
|
11026
|
+
const insC = dp[i2][j2 - 1] + GAP_PENALTY;
|
|
11027
|
+
dp[i2][j2] = Math.max(match2, delP, insC);
|
|
11028
|
+
}
|
|
11029
|
+
}
|
|
11030
|
+
const alignedPairs = [];
|
|
11031
|
+
let i = m;
|
|
11032
|
+
let j = n;
|
|
11033
|
+
while (i > 0 || j > 0) {
|
|
11034
|
+
if (i > 0 && j > 0) {
|
|
11035
|
+
const sim = stepSimilarity(baselineSteps[i - 1], currentSteps[j - 1]);
|
|
11036
|
+
const matchScore = sim >= MATCH_THRESHOLD ? sim * 2 - 0.2 : -1.5;
|
|
11037
|
+
const scoreFromMatch = dp[i - 1][j - 1] + matchScore;
|
|
11038
|
+
if (Math.abs(dp[i][j] - scoreFromMatch) < 1e-6 && sim >= MATCH_THRESHOLD) {
|
|
11039
|
+
alignedPairs.push({
|
|
11040
|
+
baseline: baselineSteps[i - 1],
|
|
11041
|
+
current: currentSteps[j - 1]
|
|
11042
|
+
});
|
|
11043
|
+
i--;
|
|
11044
|
+
j--;
|
|
11045
|
+
continue;
|
|
11046
|
+
}
|
|
11047
|
+
}
|
|
11048
|
+
if (j > 0 && Math.abs(dp[i][j] - (dp[i][j - 1] + GAP_PENALTY)) < 1e-6) {
|
|
11049
|
+
alignedPairs.push({
|
|
11050
|
+
baseline: null,
|
|
11051
|
+
current: currentSteps[j - 1]
|
|
11052
|
+
});
|
|
11053
|
+
j--;
|
|
11054
|
+
} else if (i > 0 && Math.abs(dp[i][j] - (dp[i - 1][j] + GAP_PENALTY)) < 1e-6) {
|
|
11055
|
+
alignedPairs.push({
|
|
11056
|
+
baseline: baselineSteps[i - 1],
|
|
11057
|
+
current: null
|
|
11058
|
+
});
|
|
11059
|
+
i--;
|
|
11060
|
+
} else if (i > 0 && j > 0) {
|
|
11061
|
+
alignedPairs.push({
|
|
11062
|
+
baseline: baselineSteps[i - 1],
|
|
11063
|
+
current: currentSteps[j - 1]
|
|
11064
|
+
});
|
|
11065
|
+
i--;
|
|
11066
|
+
j--;
|
|
11067
|
+
} else if (i > 0) {
|
|
11068
|
+
alignedPairs.push({
|
|
11069
|
+
baseline: baselineSteps[i - 1],
|
|
11070
|
+
current: null
|
|
11071
|
+
});
|
|
11072
|
+
i--;
|
|
11073
|
+
} else {
|
|
11074
|
+
alignedPairs.push({
|
|
11075
|
+
baseline: null,
|
|
11076
|
+
current: currentSteps[j - 1]
|
|
11077
|
+
});
|
|
11078
|
+
j--;
|
|
11079
|
+
}
|
|
11080
|
+
}
|
|
11081
|
+
alignedPairs.reverse();
|
|
11082
|
+
const maxBaselineStepNum = baselineSteps.length > 0 ? Math.max(...baselineSteps.map((s) => s.step_number)) : -1;
|
|
11083
|
+
const alignedSteps = [];
|
|
11084
|
+
const stats = {
|
|
11085
|
+
totalAlignedSteps: alignedPairs.length,
|
|
11086
|
+
same: 0,
|
|
11087
|
+
resolved: 0,
|
|
11088
|
+
persists: 0,
|
|
11089
|
+
regressions: 0,
|
|
11090
|
+
stalledInBaseline: 0,
|
|
11091
|
+
bypassed: 0,
|
|
11092
|
+
newPath: 0
|
|
11093
|
+
};
|
|
11094
|
+
for (let idx = 0; idx < alignedPairs.length; idx++) {
|
|
11095
|
+
const pair = alignedPairs[idx];
|
|
11096
|
+
const bStep = pair.baseline;
|
|
11097
|
+
const cStep = pair.current;
|
|
11098
|
+
const bFindings = bStep ? baselineFindings.filter((f) => findingMatchesStep(f, bStep.step_number)) : [];
|
|
11099
|
+
const cFindings = cStep ? currentFindings.filter((f) => findingMatchesStep(f, cStep.step_number)) : [];
|
|
11100
|
+
const bProblems = bFindings.filter((f) => isProblemFinding(f.type));
|
|
11101
|
+
const cProblems = cFindings.filter((f) => isProblemFinding(f.type));
|
|
11102
|
+
let status2 = "same";
|
|
11103
|
+
let summary = "Clean";
|
|
11104
|
+
if (bStep && cStep) {
|
|
11105
|
+
const wasStall = bStep.step_number === maxBaselineStepNum && (bProblems.length > 0 || isStallNote(bStep.note));
|
|
11106
|
+
if (bProblems.length > 0 && cProblems.length === 0) {
|
|
11107
|
+
status2 = "resolved";
|
|
11108
|
+
stats.resolved++;
|
|
11109
|
+
const probDesc = bProblems[0].ui_fact || bProblems[0].title || bProblems[0].id;
|
|
11110
|
+
summary = wasStall ? `Previous stall resolved \u2014 journey unblocked (${probDesc})` : `Resolved previous issue: ${probDesc}`;
|
|
11111
|
+
} else if (bProblems.length > 0 && cProblems.length > 0) {
|
|
11112
|
+
status2 = "persists";
|
|
11113
|
+
stats.persists++;
|
|
11114
|
+
const probDesc = cProblems[0].ui_fact || cProblems[0].title || cProblems[0].id;
|
|
11115
|
+
summary = `Problem persists: ${probDesc}`;
|
|
11116
|
+
} else if (bProblems.length === 0 && cProblems.length > 0) {
|
|
11117
|
+
status2 = "regression";
|
|
11118
|
+
stats.regressions++;
|
|
11119
|
+
const probDesc = cProblems[0].ui_fact || cProblems[0].title || cProblems[0].id;
|
|
11120
|
+
summary = `New issue reported: ${probDesc}`;
|
|
11121
|
+
} else {
|
|
11122
|
+
status2 = "same";
|
|
11123
|
+
stats.same++;
|
|
11124
|
+
summary = "Clean";
|
|
11125
|
+
}
|
|
11126
|
+
} else if (bStep && !cStep) {
|
|
11127
|
+
const wasStall = bStep.step_number === maxBaselineStepNum && (bProblems.length > 0 || isStallNote(bStep.note));
|
|
11128
|
+
if (wasStall) {
|
|
11129
|
+
status2 = "stalled";
|
|
11130
|
+
stats.stalledInBaseline++;
|
|
11131
|
+
const probDesc = bProblems[0]?.ui_fact || bProblems[0]?.title || "journey halted";
|
|
11132
|
+
summary = `Stalled in baseline: ${probDesc}`;
|
|
11133
|
+
} else {
|
|
11134
|
+
status2 = "bypassed";
|
|
11135
|
+
stats.bypassed++;
|
|
11136
|
+
summary = "Bypassed in current run";
|
|
11137
|
+
}
|
|
11138
|
+
} else if (!bStep && cStep) {
|
|
11139
|
+
if (cProblems.length > 0) {
|
|
11140
|
+
status2 = "regression";
|
|
11141
|
+
stats.regressions++;
|
|
11142
|
+
const probDesc = cProblems[0].ui_fact || cProblems[0].title || cProblems[0].id;
|
|
11143
|
+
summary = `New step with issue: ${probDesc}`;
|
|
11144
|
+
} else {
|
|
11145
|
+
status2 = "new_path";
|
|
11146
|
+
stats.newPath++;
|
|
11147
|
+
summary = "New path / unblocked progress";
|
|
11148
|
+
}
|
|
11149
|
+
}
|
|
11150
|
+
alignedSteps.push({
|
|
11151
|
+
index: idx + 1,
|
|
11152
|
+
status: status2,
|
|
11153
|
+
baselineStep: bStep,
|
|
11154
|
+
currentStep: cStep,
|
|
11155
|
+
baselineFindings: bFindings,
|
|
11156
|
+
currentFindings: cFindings,
|
|
11157
|
+
summary
|
|
11158
|
+
});
|
|
11159
|
+
}
|
|
11160
|
+
return {
|
|
11161
|
+
scenario,
|
|
11162
|
+
baselineRunId,
|
|
11163
|
+
currentRunId,
|
|
11164
|
+
steps: alignedSteps,
|
|
11165
|
+
stats
|
|
11166
|
+
};
|
|
11167
|
+
}
|
|
11168
|
+
function toStepFindingInfo(f) {
|
|
11169
|
+
return {
|
|
11170
|
+
id: f.frontmatter.id,
|
|
11171
|
+
title: f.frontmatter.title ?? f.slug,
|
|
11172
|
+
type: f.frontmatter.type,
|
|
11173
|
+
severity: f.frontmatter.severity,
|
|
11174
|
+
ui_fact: f.frontmatter.ui_fact,
|
|
11175
|
+
consequence: f.frontmatter.consequence,
|
|
11176
|
+
canonical_id: f.frontmatter.canonical_id,
|
|
11177
|
+
walkthrough_step_id: f.frontmatter.walkthrough_step_id
|
|
11178
|
+
};
|
|
11179
|
+
}
|
|
11180
|
+
function toJourneyStepInfo(s) {
|
|
11181
|
+
return {
|
|
11182
|
+
step_number: s.step_number,
|
|
11183
|
+
action: s.action,
|
|
11184
|
+
url: s.url ?? void 0,
|
|
11185
|
+
surface: s.surface ?? void 0,
|
|
11186
|
+
note: s.note ?? void 0
|
|
11187
|
+
};
|
|
11188
|
+
}
|
|
11189
|
+
function journeyStepRoute(step) {
|
|
11190
|
+
return step.currentStep?.url && normalizePlaceKey(step.currentStep.url) || step.baselineStep?.url && normalizePlaceKey(step.baselineStep.url) || step.currentStep?.surface || step.baselineStep?.surface || "\u2014";
|
|
11191
|
+
}
|
|
11192
|
+
async function computeJourneyDiff(projectRoot, baselineRunId, currentRunId, scenarioSlug) {
|
|
11193
|
+
const [bStepsRaw, bFindingsRaw, cStepsRaw, cFindingsRaw] = await Promise.all([
|
|
11194
|
+
readStepsForRun(projectRoot, baselineRunId),
|
|
11195
|
+
readFindingsForRun(projectRoot, baselineRunId),
|
|
11196
|
+
readStepsForRun(projectRoot, currentRunId),
|
|
11197
|
+
readFindingsForRun(projectRoot, currentRunId)
|
|
11198
|
+
]);
|
|
11199
|
+
const baselineSteps = bStepsRaw.map(toJourneyStepInfo);
|
|
11200
|
+
const baselineFindings = bFindingsRaw.map(toStepFindingInfo);
|
|
11201
|
+
const currentSteps = cStepsRaw.map(toJourneyStepInfo);
|
|
11202
|
+
const currentFindings = cFindingsRaw.map(toStepFindingInfo);
|
|
11203
|
+
return alignJourneySteps(scenarioSlug ?? "", baselineRunId, currentRunId, baselineSteps, baselineFindings, currentSteps, currentFindings);
|
|
11204
|
+
}
|
|
11205
|
+
function renderJourneyDiffMarkdown(diff) {
|
|
11206
|
+
const lines = [
|
|
11207
|
+
"### Chronological journey diff",
|
|
11208
|
+
"",
|
|
11209
|
+
`Baseline: \`${diff.baselineRunId}\` vs Current: \`${diff.currentRunId}\``
|
|
11210
|
+
];
|
|
11211
|
+
const parts = [];
|
|
11212
|
+
if (diff.stats.same > 0)
|
|
11213
|
+
parts.push(`${diff.stats.same} unchanged`);
|
|
11214
|
+
if (diff.stats.resolved > 0)
|
|
11215
|
+
parts.push(`${diff.stats.resolved} resolved`);
|
|
11216
|
+
if (diff.stats.persists > 0)
|
|
11217
|
+
parts.push(`${diff.stats.persists} persists`);
|
|
11218
|
+
if (diff.stats.regressions > 0)
|
|
11219
|
+
parts.push(`${diff.stats.regressions} new issues`);
|
|
11220
|
+
if (diff.stats.stalledInBaseline > 0)
|
|
11221
|
+
parts.push(`${diff.stats.stalledInBaseline} stalled in baseline`);
|
|
11222
|
+
if (diff.stats.bypassed > 0)
|
|
11223
|
+
parts.push(`${diff.stats.bypassed} bypassed`);
|
|
11224
|
+
if (diff.stats.newPath > 0)
|
|
11225
|
+
parts.push(`${diff.stats.newPath} new path steps`);
|
|
11226
|
+
if (parts.length > 0) {
|
|
11227
|
+
lines.push(`- **Summary**: ${parts.join(", ")}`);
|
|
11228
|
+
}
|
|
11229
|
+
lines.push("", "| # | Route | Baseline (Run N-1) | Current (Run N) | Status | Notes |", "|---|-------|--------------------|-----------------|--------|-------|");
|
|
11230
|
+
for (const step of diff.steps) {
|
|
11231
|
+
const route = journeyStepRoute(step);
|
|
11232
|
+
const baseAction = step.baselineStep ? `Step ${step.baselineStep.step_number}: ${escapeMarkdownCell(step.baselineStep.action)}` : "\u2014";
|
|
11233
|
+
const currAction = step.currentStep ? `Step ${step.currentStep.step_number}: ${escapeMarkdownCell(step.currentStep.action)}` : "\u2014";
|
|
11234
|
+
const statusBadge = `\`${step.status}\``;
|
|
11235
|
+
const notes = escapeMarkdownCell(step.summary);
|
|
11236
|
+
lines.push(`| ${step.index} | \`${escapeMarkdownCell(route)}\` | ${baseAction} | ${currAction} | ${statusBadge} | ${notes} |`);
|
|
11237
|
+
}
|
|
11238
|
+
return lines.join("\n");
|
|
11239
|
+
}
|
|
11240
|
+
function escapeMarkdownCell(text) {
|
|
11241
|
+
return text.replace(/\|/g, "\\|").replace(/\n/g, " ");
|
|
11242
|
+
}
|
|
11243
|
+
var PROBLEM_TYPES2, STALL_KEYWORDS;
|
|
11244
|
+
var init_journey_diff = __esm({
|
|
11245
|
+
"../../packages/local-store/dist/journey-diff.js"() {
|
|
11246
|
+
"use strict";
|
|
11247
|
+
init_revisit();
|
|
11248
|
+
init_walkthrough_canonical2();
|
|
11249
|
+
PROBLEM_TYPES2 = /* @__PURE__ */ new Set(["hinders", "missing", "incorrect"]);
|
|
11250
|
+
STALL_KEYWORDS = [
|
|
11251
|
+
"blocked",
|
|
11252
|
+
"unable to proceed",
|
|
11253
|
+
"stalled",
|
|
11254
|
+
"halted",
|
|
11255
|
+
"stuck",
|
|
11256
|
+
"cannot proceed",
|
|
11257
|
+
"could not proceed",
|
|
11258
|
+
"failed to load",
|
|
11259
|
+
"timed out"
|
|
11260
|
+
];
|
|
11261
|
+
}
|
|
11262
|
+
});
|
|
11263
|
+
|
|
11264
|
+
// ../../packages/local-store/dist/report-markdown.js
|
|
11265
|
+
function runVerdict(canonical) {
|
|
11266
|
+
const judged = (canonical?.human_tests ?? []).map((t) => t.verdict).filter((v) => v === "yes" || v === "no" || v === "partial");
|
|
11267
|
+
if (judged.length === 0)
|
|
11268
|
+
return null;
|
|
11269
|
+
if (judged.every((v) => v === "yes"))
|
|
11270
|
+
return "yes";
|
|
11271
|
+
if (judged.every((v) => v === "no"))
|
|
11272
|
+
return "no";
|
|
11273
|
+
return "partial";
|
|
11274
|
+
}
|
|
11275
|
+
function rank(value, order) {
|
|
11276
|
+
const i = order.indexOf(value ?? "");
|
|
11277
|
+
return i === -1 ? order.length : i;
|
|
11278
|
+
}
|
|
11279
|
+
function pagePath(url) {
|
|
11280
|
+
if (!url)
|
|
11281
|
+
return null;
|
|
11282
|
+
try {
|
|
11283
|
+
return new URL(url).pathname;
|
|
11284
|
+
} catch {
|
|
11285
|
+
return url;
|
|
11286
|
+
}
|
|
11287
|
+
}
|
|
11288
|
+
function dayOf(value) {
|
|
11289
|
+
const m = /^(\d{4}-\d{2}-\d{2})/.exec(value ?? "");
|
|
11290
|
+
return m?.[1] ?? null;
|
|
11291
|
+
}
|
|
11292
|
+
function stepNumbers(ids) {
|
|
11293
|
+
const nums = (ids ?? []).map((id) => /\d+/.exec(id)?.[0]).filter((n) => Boolean(n));
|
|
11294
|
+
if (nums.length === 0)
|
|
11295
|
+
return "";
|
|
11296
|
+
return ` (step${nums.length === 1 ? "" : "s"} ${nums.join(", ")})`;
|
|
11297
|
+
}
|
|
11298
|
+
function titleOf(f) {
|
|
11299
|
+
if (f.title)
|
|
11300
|
+
return f.title;
|
|
11301
|
+
const fact = f.ui_fact ?? f.id;
|
|
11302
|
+
return /^(.+?[.!?])(\s|$)/.exec(fact)?.[1] ?? fact;
|
|
11303
|
+
}
|
|
11304
|
+
function renderHumanTest(c, lines) {
|
|
11305
|
+
lines.push("", "## The human test");
|
|
11306
|
+
const verdict = runVerdict(c);
|
|
11307
|
+
const label = verdict ? `**${cap(verdict)}**` : c.blocked ? "_skipped (walk blocked)_" : "_pending judge_";
|
|
11308
|
+
lines.push("", `**${c.human_test_overall.question}** ${label}`);
|
|
11309
|
+
if (c.human_test_overall.answer) {
|
|
11310
|
+
lines.push("", c.human_test_overall.answer);
|
|
11311
|
+
if (c.human_test_overall.reasoning) {
|
|
11312
|
+
lines.push("", c.human_test_overall.reasoning);
|
|
11313
|
+
}
|
|
11314
|
+
}
|
|
11315
|
+
if (c.human_tests.length === 0) {
|
|
11316
|
+
lines.push("", "_No human tests on the outline._");
|
|
11317
|
+
return;
|
|
11318
|
+
}
|
|
11319
|
+
lines.push("");
|
|
11320
|
+
for (const ht of c.human_tests) {
|
|
11321
|
+
const v = ht.verdict ? `**${cap(ht.verdict.replace("_", " "))}.**` : "_Pending._";
|
|
11322
|
+
lines.push(`- ${stripAnswerHint(ht.question)} ${v}${ht.reasoning ? ` ${ht.reasoning}` : ""}`);
|
|
11323
|
+
}
|
|
11324
|
+
}
|
|
11325
|
+
function severityShift(before, now) {
|
|
11326
|
+
if (!before || !now || before === now)
|
|
11327
|
+
return "";
|
|
11328
|
+
const b = rank(before, SEVERITY_ORDER);
|
|
11329
|
+
const n = rank(now, SEVERITY_ORDER);
|
|
11330
|
+
if (b === n)
|
|
11331
|
+
return "";
|
|
11332
|
+
return `${n > b ? "less severe" : "worse"}, ${before} \u2192 ${now}`;
|
|
11333
|
+
}
|
|
11334
|
+
function checkOutcomes(c) {
|
|
11335
|
+
const checks = c.checks;
|
|
11336
|
+
if (!checks)
|
|
11337
|
+
return [];
|
|
11338
|
+
const v = c.verification;
|
|
11339
|
+
const fixed = new Set(v?.resolved.map((r) => r.canonical_id) ?? []);
|
|
11340
|
+
const seen = new Set(c.findings.map((f) => f.canonical_id).filter(Boolean));
|
|
11341
|
+
return checks.fixes.map((f) => ({
|
|
11342
|
+
...f,
|
|
11343
|
+
outcome: f.canonical_id && fixed.has(f.canonical_id) ? "fixed" : f.canonical_id && seen.has(f.canonical_id) ? "came_back" : "unconfirmed"
|
|
11344
|
+
}));
|
|
11345
|
+
}
|
|
11346
|
+
function renderChecks(c, checks, lines) {
|
|
11347
|
+
lines.push("", "## What this run checks");
|
|
11348
|
+
const fixes = checkOutcomes(c);
|
|
11349
|
+
if (fixes.length > 0) {
|
|
11350
|
+
const count = (o) => fixes.filter((f) => f.outcome === o).length;
|
|
11351
|
+
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.`, "");
|
|
11352
|
+
const order = ["came_back", "fixed", "unconfirmed"];
|
|
11353
|
+
const sorted = [...fixes].sort((a, b) => order.indexOf(a.outcome) - order.indexOf(b.outcome));
|
|
11354
|
+
for (const f of sorted) {
|
|
11355
|
+
const where = [
|
|
11356
|
+
pagePath(f.page) ? `\`${pagePath(f.page)}\`` : "",
|
|
11357
|
+
f.reached ? "" : "not reached"
|
|
11358
|
+
].filter(Boolean).join(", ");
|
|
11359
|
+
lines.push(`- ${OUTCOME_TEXT[f.outcome]}: **${f.title}**${f.fix ? `. Fix: ${f.fix}` : ""}${where ? ` (${where})` : ""}`);
|
|
11360
|
+
}
|
|
11361
|
+
}
|
|
11362
|
+
if (checks.directions.length > 0) {
|
|
11363
|
+
lines.push("", `Directions completed since the last run: ${checks.directions.map((d) => `**${d}**`).join(", ")}.`);
|
|
11364
|
+
}
|
|
11365
|
+
}
|
|
11366
|
+
function sinceRow(item, outcome) {
|
|
11367
|
+
const qualifiers = [
|
|
11368
|
+
outcome === "Persists" ? severityShift(item.severity_before, item.severity_now) : "",
|
|
11369
|
+
outcome === "Persists" && item.fixed_before ? "the fix did not hold" : ""
|
|
11370
|
+
].filter(Boolean);
|
|
11371
|
+
const now = outcome === "Resolved" || outcome === "Persists" ? `**${outcome}**${qualifiers.length > 0 ? ` (${qualifiers.join("; ")})` : ""}` : outcome;
|
|
11372
|
+
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();
|
|
11373
|
+
return `| ${cell(item.title)} | ${now} | ${cell(note) || "\u2014"} |`;
|
|
11374
|
+
}
|
|
11375
|
+
function renderSince(v, newProblems, newStrengths, lines) {
|
|
11376
|
+
const day = dayOf(v.baseline_run_id);
|
|
11377
|
+
lines.push("", "## Since the last run", "", `Compared with the run of ${day ?? v.baseline_run_id}.`);
|
|
11378
|
+
const rows = [
|
|
11379
|
+
...v.resolved.map((i) => sinceRow(i, "Resolved")),
|
|
11380
|
+
...v.persists.map((i) => sinceRow(i, "Persists")),
|
|
11381
|
+
...v.not_observed.map((i) => sinceRow(i, "Not seen")),
|
|
11382
|
+
...v.uncertain.map((i) => sinceRow(i, "Unclear"))
|
|
11383
|
+
];
|
|
11384
|
+
if (rows.length > 0) {
|
|
11385
|
+
lines.push("", "| Earlier finding | Now | Notes |", "|---|---|---|", ...rows);
|
|
11386
|
+
}
|
|
11387
|
+
const added = [
|
|
11388
|
+
newProblems > 0 ? `${newProblems} problem${newProblems === 1 ? "" : "s"}` : "",
|
|
11389
|
+
newStrengths > 0 ? `${newStrengths} strength${newStrengths === 1 ? "" : "s"}` : ""
|
|
11390
|
+
].filter(Boolean);
|
|
11391
|
+
lines.push("", added.length > 0 ? `New this run: ${added.join(" and ")}, marked _new_ below.` : "Nothing new this run.");
|
|
11392
|
+
}
|
|
11393
|
+
function renderFinding(c, f, n, since, lines) {
|
|
11394
|
+
lines.push("", `### ${n}. ${titleOf(f)}`);
|
|
11395
|
+
const steps = f.linked_step_ids.map((id) => c.steps.find((s) => s.id === id)).filter((s) => Boolean(s));
|
|
11396
|
+
const page = pagePath(f.page_url ?? steps[0]?.url);
|
|
11397
|
+
const where = [
|
|
11398
|
+
page ? `\`${page}\`` : "",
|
|
11399
|
+
steps.length > 0 ? `step ${steps.map((s) => s.step_number).join(", ")}` : ""
|
|
11400
|
+
].filter(Boolean).join(" ");
|
|
11401
|
+
const meta = [
|
|
11402
|
+
f.severity ? `**${cap(f.severity)}**` : "",
|
|
11403
|
+
f.type,
|
|
11404
|
+
f.task_impact ? TASK_IMPACT[f.task_impact] : "",
|
|
11405
|
+
where,
|
|
11406
|
+
since ? `_${since}_` : ""
|
|
11407
|
+
].filter(Boolean);
|
|
11408
|
+
lines.push("", meta.join(" \xB7 "));
|
|
11409
|
+
if (f.ui_fact === null || f.consequence === null) {
|
|
11410
|
+
lines.push("", "> _Legacy finding: predates the UI-fact / consequence depth gate._");
|
|
11411
|
+
}
|
|
11412
|
+
if (f.ui_fact)
|
|
11413
|
+
lines.push("", f.ui_fact);
|
|
11414
|
+
if (f.consequence) {
|
|
11415
|
+
const who = c.scenario.persona_name;
|
|
11416
|
+
lines.push("", `> ${f.consequence}${who ? ` \u2014 ${who}` : ""}`);
|
|
11417
|
+
}
|
|
11418
|
+
if (f.recommendation)
|
|
11419
|
+
lines.push("", `**Recommendation:** ${f.recommendation}`);
|
|
11420
|
+
for (const s of steps) {
|
|
11421
|
+
if (s.screenshot_ref) {
|
|
11422
|
+
lines.push("", ``);
|
|
11423
|
+
}
|
|
11424
|
+
}
|
|
11425
|
+
}
|
|
11426
|
+
function renderAnalysis(analysis, lines) {
|
|
11427
|
+
if (analysis.directions.length > 0) {
|
|
11428
|
+
lines.push("", "## Directions", "", `_Structural changes proposed by ${analysis.advisor} after the walk. Reach is a prediction until the listed scenarios are re-run._`);
|
|
11429
|
+
for (const d of analysis.directions) {
|
|
11430
|
+
lines.push("", `### ${d.title} (${d.size})`, "", d.change);
|
|
11431
|
+
lines.push("", `Absorbs ${d.absorbs.length} finding${d.absorbs.length === 1 ? "" : "s"}.`);
|
|
11432
|
+
for (const r of d.reach.filter((x) => x.effect === "helps"))
|
|
11433
|
+
lines.push(`- Likely helps ${r.scenario}: ${r.reason}`);
|
|
11434
|
+
for (const r of d.reach.filter((x) => x.effect === "risk"))
|
|
11435
|
+
lines.push(`- Could hurt ${r.scenario}: ${r.reason}`);
|
|
11436
|
+
if (d.verify.length > 0)
|
|
11437
|
+
lines.push("", `Re-run to verify: ${d.verify.join(", ")}`);
|
|
11438
|
+
}
|
|
11439
|
+
}
|
|
11440
|
+
if (analysis.observations.length > 0) {
|
|
11441
|
+
lines.push("", "## Also worth knowing", "");
|
|
11442
|
+
for (const o of analysis.observations) {
|
|
11443
|
+
const label = o.kind === "coverage_gap" ? "Not walked" : "Feature risk";
|
|
11444
|
+
lines.push(`- **${label}: ${o.title}.** ${o.detail}`);
|
|
11445
|
+
if (o.suggested_scenario) {
|
|
11446
|
+
lines.push(` Suggested scenario: ${o.suggested_scenario}`);
|
|
11447
|
+
}
|
|
11448
|
+
}
|
|
11449
|
+
}
|
|
11125
11450
|
}
|
|
11126
|
-
|
|
11127
|
-
|
|
11128
|
-
|
|
11129
|
-
|
|
11130
|
-
|
|
11131
|
-
|
|
11132
|
-
|
|
11133
|
-
|
|
11134
|
-
|
|
11135
|
-
|
|
11136
|
-
|
|
11137
|
-
|
|
11138
|
-
|
|
11139
|
-
|
|
11140
|
-
}
|
|
11141
|
-
|
|
11451
|
+
function renderRunDetails(c, lines) {
|
|
11452
|
+
lines.push("", "## Run details", "", `- Run ID: ${c.run_id}`, `- Target URL: ${c.target.url}`);
|
|
11453
|
+
if (c.environment)
|
|
11454
|
+
lines.push(`- Environment: ${c.environment}`);
|
|
11455
|
+
if (c.build) {
|
|
11456
|
+
const commit = c.build.git_sha?.slice(0, 7);
|
|
11457
|
+
const build = [
|
|
11458
|
+
c.build.label,
|
|
11459
|
+
c.build.git_branch,
|
|
11460
|
+
c.build.git_dirty && c.build.worktree_hash ? `${commit ?? "HEAD"} + uncommitted changes (${c.build.worktree_hash.slice(0, 6)}\u2026)` : commit,
|
|
11461
|
+
c.build.ci?.provider
|
|
11462
|
+
].filter(Boolean).join(" \xB7 ");
|
|
11463
|
+
if (build)
|
|
11464
|
+
lines.push(`- Build: ${build}`);
|
|
11465
|
+
}
|
|
11466
|
+
if (c.viewport) {
|
|
11467
|
+
lines.push(`- Viewport: ${c.viewport.device} (${c.viewport.width}\xD7${c.viewport.height})`);
|
|
11468
|
+
}
|
|
11469
|
+
lines.push(`- Started: ${c.provenance.started_at}`, `- Completed: ${c.provenance.completed_at}`, `- Walker prompt: ${c.provenance.walker_prompt.id}@${c.provenance.walker_prompt.version}`);
|
|
11470
|
+
const models = c.models;
|
|
11471
|
+
if (models) {
|
|
11472
|
+
const parts = [
|
|
11473
|
+
models.walker ? `Walker: ${models.walker}${models.walker_reported ? ` (${models.walker_reported})` : ""}` : "",
|
|
11474
|
+
models.judge ? `Judge: ${models.judge}${models.judge_reported ? ` (${models.judge_reported})` : ""}` : "",
|
|
11475
|
+
models.matcher ? `Matcher: ${models.matcher}` : ""
|
|
11476
|
+
].filter(Boolean);
|
|
11477
|
+
if (parts.length > 0)
|
|
11478
|
+
lines.push(`- Models: ${parts.join(" \xB7 ")}`);
|
|
11479
|
+
if (typeof models.velocity_sec_per_step === "number") {
|
|
11480
|
+
lines.push(`- Velocity: ~${models.velocity_sec_per_step}s/step (${models.total_duration_sec}s total)`);
|
|
11481
|
+
}
|
|
11482
|
+
}
|
|
11483
|
+
const diff = c.journey_diff;
|
|
11484
|
+
if (diff)
|
|
11485
|
+
lines.push("", renderJourneyDiffMarkdown(diff));
|
|
11142
11486
|
}
|
|
11143
|
-
function
|
|
11144
|
-
|
|
11145
|
-
|
|
11487
|
+
function renderSteps(c, lines) {
|
|
11488
|
+
lines.push("", "## Steps");
|
|
11489
|
+
if (c.steps.length === 0) {
|
|
11490
|
+
lines.push("", "_No steps recorded._");
|
|
11491
|
+
return;
|
|
11492
|
+
}
|
|
11493
|
+
for (const s of c.steps) {
|
|
11494
|
+
lines.push("", `### ${s.step_number}. ${s.screen_title ?? "(untitled)"} \u2014 ${s.id}`);
|
|
11495
|
+
const where = [s.surface, s.url].filter(Boolean).join(" \u2014 ");
|
|
11496
|
+
if (where)
|
|
11497
|
+
lines.push("", `Page: ${where}`);
|
|
11498
|
+
if (s.persona_reasoning)
|
|
11499
|
+
lines.push("", `> ${s.persona_reasoning.replace(/\n/g, "\n> ")}`);
|
|
11500
|
+
if (s.grounding)
|
|
11501
|
+
lines.push("", `Grounding: ${s.grounding}`);
|
|
11502
|
+
if (s.screenshot_ref)
|
|
11503
|
+
lines.push("", ``);
|
|
11504
|
+
}
|
|
11146
11505
|
}
|
|
11147
|
-
function
|
|
11148
|
-
const
|
|
11149
|
-
const
|
|
11150
|
-
const
|
|
11151
|
-
|
|
11152
|
-
|
|
11153
|
-
|
|
11154
|
-
|
|
11155
|
-
|
|
11156
|
-
|
|
11157
|
-
|
|
11158
|
-
|
|
11159
|
-
|
|
11160
|
-
|
|
11161
|
-
|
|
11162
|
-
|
|
11163
|
-
|
|
11164
|
-
|
|
11165
|
-
|
|
11166
|
-
|
|
11167
|
-
|
|
11168
|
-
|
|
11506
|
+
function renderCanonicalMarkdown(canonical) {
|
|
11507
|
+
const c = canonical;
|
|
11508
|
+
const persona = c.scenario.persona_name ?? c.scenario.persona_slug;
|
|
11509
|
+
const situation = c.scenario.outline_title ?? c.scenario.outline_slug;
|
|
11510
|
+
const heading = persona && situation ? `${persona} \xB7 ${situation}` : c.scenario.slug;
|
|
11511
|
+
const runNumber = c.run_number;
|
|
11512
|
+
const subtitle = [
|
|
11513
|
+
dayOf(c.provenance.completed_at),
|
|
11514
|
+
runNumber ? `run ${runNumber} of this scenario` : "",
|
|
11515
|
+
`${c.steps.length} step${c.steps.length === 1 ? "" : "s"}`,
|
|
11516
|
+
c.target.url
|
|
11517
|
+
].filter(Boolean).join(" \xB7 ");
|
|
11518
|
+
const lines = [
|
|
11519
|
+
"<!-- GENERATED: do not edit by hand. Regen via complete_walkthrough or local-store renderer. -->",
|
|
11520
|
+
"",
|
|
11521
|
+
`# ${heading}`,
|
|
11522
|
+
"",
|
|
11523
|
+
subtitle
|
|
11524
|
+
];
|
|
11525
|
+
if (c.scenario.persona_role) {
|
|
11526
|
+
lines.push("", `${persona}, ${c.scenario.persona_role}.`);
|
|
11527
|
+
}
|
|
11528
|
+
if (c.blocked) {
|
|
11529
|
+
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.");
|
|
11530
|
+
}
|
|
11531
|
+
const checks = c.checks;
|
|
11532
|
+
if (checks)
|
|
11533
|
+
renderChecks(c, checks, lines);
|
|
11534
|
+
renderHumanTest(c, lines);
|
|
11535
|
+
const verification = c.verification;
|
|
11536
|
+
const newIds = new Set(verification?.new_findings.map((n) => n.id) ?? []);
|
|
11537
|
+
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));
|
|
11538
|
+
const strengths = c.findings.filter((f) => f.type === "helps");
|
|
11539
|
+
if (verification) {
|
|
11540
|
+
renderSince(verification, problems.filter((f) => newIds.has(f.id)).length, strengths.filter((f) => newIds.has(f.id)).length, lines);
|
|
11541
|
+
}
|
|
11542
|
+
lines.push("", "## Findings");
|
|
11543
|
+
if (problems.length === 0) {
|
|
11544
|
+
lines.push("", "_No problems recorded._");
|
|
11545
|
+
} else {
|
|
11546
|
+
const bySeverity = SEVERITY_ORDER.map((s) => {
|
|
11547
|
+
const n = problems.filter((f) => f.severity === s).length;
|
|
11548
|
+
return n > 0 ? `${n} ${s}` : "";
|
|
11549
|
+
}).filter(Boolean);
|
|
11550
|
+
lines.push("", `${problems.length} problem${problems.length === 1 ? "" : "s"} (${bySeverity.join(", ")}), worst first.`);
|
|
11551
|
+
const persisting = new Set(verification?.persists.map((p) => p.canonical_id) ?? []);
|
|
11552
|
+
problems.forEach((f, i) => {
|
|
11553
|
+
const since = newIds.has(f.id) ? "new" : f.canonical_id && persisting.has(f.canonical_id) ? "persists" : null;
|
|
11554
|
+
renderFinding(c, f, i + 1, since, lines);
|
|
11555
|
+
});
|
|
11556
|
+
}
|
|
11557
|
+
const analysis = c.analysis;
|
|
11558
|
+
if (analysis)
|
|
11559
|
+
renderAnalysis(analysis, lines);
|
|
11560
|
+
const what = c.what_worked ?? [];
|
|
11561
|
+
if (strengths.length > 0 || what.length > 0) {
|
|
11562
|
+
lines.push("", "## What worked", "");
|
|
11563
|
+
for (const f of strengths) {
|
|
11564
|
+
lines.push(`- **${titleOf(f)}.** ${f.ui_fact ?? ""}`.trimEnd());
|
|
11169
11565
|
}
|
|
11566
|
+
for (const w of what)
|
|
11567
|
+
lines.push(`- ${w}`);
|
|
11170
11568
|
}
|
|
11171
|
-
const
|
|
11172
|
-
|
|
11173
|
-
|
|
11174
|
-
|
|
11175
|
-
|
|
11176
|
-
|
|
11177
|
-
|
|
11178
|
-
|
|
11179
|
-
|
|
11180
|
-
|
|
11569
|
+
const open2 = c.open_questions ?? [];
|
|
11570
|
+
if (open2.length > 0) {
|
|
11571
|
+
lines.push("", "## Open questions", "");
|
|
11572
|
+
for (const q of open2)
|
|
11573
|
+
lines.push(`- ${q}`);
|
|
11574
|
+
}
|
|
11575
|
+
const setup = c.setup_notes ?? [];
|
|
11576
|
+
if (setup.length > 0) {
|
|
11577
|
+
lines.push("", "## Test setup notes", "", "_Doubts about the test, not the product: for whoever stages the scenario._", "");
|
|
11578
|
+
for (const n of setup)
|
|
11579
|
+
lines.push(`- ${n}`);
|
|
11580
|
+
}
|
|
11581
|
+
renderSteps(c, lines);
|
|
11582
|
+
renderRunDetails(c, lines);
|
|
11583
|
+
return `${lines.join("\n")}
|
|
11584
|
+
`;
|
|
11181
11585
|
}
|
|
11182
|
-
var
|
|
11183
|
-
var
|
|
11184
|
-
"../../packages/local-store/dist/
|
|
11586
|
+
var SEVERITY_ORDER, TYPE_ORDER, TASK_IMPACT, cap, stripAnswerHint, cell, OUTCOME_TEXT;
|
|
11587
|
+
var init_report_markdown = __esm({
|
|
11588
|
+
"../../packages/local-store/dist/report-markdown.js"() {
|
|
11185
11589
|
"use strict";
|
|
11186
|
-
|
|
11187
|
-
|
|
11188
|
-
|
|
11189
|
-
|
|
11190
|
-
|
|
11191
|
-
|
|
11192
|
-
|
|
11193
|
-
|
|
11194
|
-
|
|
11195
|
-
|
|
11590
|
+
init_journey_diff();
|
|
11591
|
+
SEVERITY_ORDER = ["critical", "major", "minor", "observation"];
|
|
11592
|
+
TYPE_ORDER = ["incorrect", "hinders", "missing", "irrelevant", "helps"];
|
|
11593
|
+
TASK_IMPACT = {
|
|
11594
|
+
blocks: "blocks the task",
|
|
11595
|
+
impairs: "impairs the task",
|
|
11596
|
+
slows: "slows the task"
|
|
11597
|
+
};
|
|
11598
|
+
cap = (s) => s.charAt(0).toUpperCase() + s.slice(1);
|
|
11599
|
+
stripAnswerHint = (q) => q.replace(/\s*\((?:yes|no|partial)(?:\s*\/\s*(?:yes|no|partial))+\)\s*$/i, "");
|
|
11600
|
+
cell = (s) => s.replace(/\|/g, "\\|").replace(/\n+/g, " ");
|
|
11601
|
+
OUTCOME_TEXT = {
|
|
11602
|
+
fixed: "**confirmed fixed**",
|
|
11603
|
+
came_back: "**came back**",
|
|
11604
|
+
unconfirmed: "not confirmed"
|
|
11605
|
+
};
|
|
11196
11606
|
}
|
|
11197
11607
|
});
|
|
11198
11608
|
|
|
11199
11609
|
// ../../packages/local-store/dist/human-test-link.js
|
|
11200
|
-
import { promises as
|
|
11201
|
-
import
|
|
11610
|
+
import { promises as fs22 } from "fs";
|
|
11611
|
+
import path17 from "path";
|
|
11202
11612
|
function humanTestLinks(verdicts) {
|
|
11203
11613
|
const best = /* @__PURE__ */ new Map();
|
|
11204
11614
|
for (const v of verdicts) {
|
|
11205
|
-
const
|
|
11206
|
-
if (
|
|
11615
|
+
const rank2 = VERDICT_RANK[v.verdict];
|
|
11616
|
+
if (rank2 === void 0)
|
|
11207
11617
|
continue;
|
|
11208
11618
|
for (const findingId of v.grounding_finding_ids) {
|
|
11209
11619
|
const seen = best.get(findingId);
|
|
11210
|
-
if (!seen ||
|
|
11211
|
-
best.set(findingId, { test: v.human_test_id, rank });
|
|
11620
|
+
if (!seen || rank2 < seen.rank) {
|
|
11621
|
+
best.set(findingId, { test: v.human_test_id, rank: rank2 });
|
|
11212
11622
|
}
|
|
11213
11623
|
}
|
|
11214
11624
|
}
|
|
@@ -11221,7 +11631,7 @@ async function linkFindingsToHumanTests(projectRoot, runId, verdicts) {
|
|
|
11221
11631
|
const dir = entityDirPath(projectRoot, "finding");
|
|
11222
11632
|
let names2;
|
|
11223
11633
|
try {
|
|
11224
|
-
names2 = await
|
|
11634
|
+
names2 = await fs22.readdir(dir);
|
|
11225
11635
|
} catch {
|
|
11226
11636
|
return [];
|
|
11227
11637
|
}
|
|
@@ -11229,11 +11639,11 @@ async function linkFindingsToHumanTests(projectRoot, runId, verdicts) {
|
|
|
11229
11639
|
for (const name of names2) {
|
|
11230
11640
|
if (!name.endsWith(".md") || name.startsWith("."))
|
|
11231
11641
|
continue;
|
|
11232
|
-
const file =
|
|
11642
|
+
const file = path17.join(dir, name);
|
|
11233
11643
|
let raw;
|
|
11234
11644
|
let fm;
|
|
11235
11645
|
try {
|
|
11236
|
-
raw = await
|
|
11646
|
+
raw = await fs22.readFile(file, "utf8");
|
|
11237
11647
|
fm = parseFrontmatter(raw).frontmatter;
|
|
11238
11648
|
} catch {
|
|
11239
11649
|
continue;
|
|
@@ -11464,14 +11874,14 @@ var init_cloud_client = __esm({
|
|
|
11464
11874
|
});
|
|
11465
11875
|
|
|
11466
11876
|
// ../../packages/local-store/dist/last-pull.js
|
|
11467
|
-
import { promises as
|
|
11468
|
-
import
|
|
11877
|
+
import { promises as fs23 } from "fs";
|
|
11878
|
+
import path18 from "path";
|
|
11469
11879
|
function lastPullPath(projectRoot) {
|
|
11470
|
-
return
|
|
11880
|
+
return path18.join(cacheDir(projectRoot), "last-pull.json");
|
|
11471
11881
|
}
|
|
11472
11882
|
async function readLastPull(projectRoot) {
|
|
11473
11883
|
try {
|
|
11474
|
-
const raw = await
|
|
11884
|
+
const raw = await fs23.readFile(lastPullPath(projectRoot), "utf8");
|
|
11475
11885
|
const parsed = JSON.parse(raw);
|
|
11476
11886
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
11477
11887
|
return parsed;
|
|
@@ -11577,7 +11987,7 @@ var init_types2 = __esm({
|
|
|
11577
11987
|
});
|
|
11578
11988
|
|
|
11579
11989
|
// ../../packages/local-store/dist/pull/frontmatter.js
|
|
11580
|
-
import { randomUUID as
|
|
11990
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
11581
11991
|
function resolveBody(row, fallback) {
|
|
11582
11992
|
if (typeof row.bodyMarkdown === "string") {
|
|
11583
11993
|
return { text: row.bodyMarkdown, fromCloud: true };
|
|
@@ -11619,7 +12029,7 @@ function buildOutlineFrontmatter(row, slug, capabilityIdToSlug) {
|
|
|
11619
12029
|
missingCaps.push(capId);
|
|
11620
12030
|
}
|
|
11621
12031
|
const humanTests = (row.humanTests ?? []).map((h) => ({
|
|
11622
|
-
id: h.id ??
|
|
12032
|
+
id: h.id ?? randomUUID7().slice(0, 8),
|
|
11623
12033
|
question: h.question
|
|
11624
12034
|
}));
|
|
11625
12035
|
const frontmatter = {
|
|
@@ -11786,7 +12196,7 @@ var init_frontmatter2 = __esm({
|
|
|
11786
12196
|
});
|
|
11787
12197
|
|
|
11788
12198
|
// ../../packages/local-store/dist/pull/write.js
|
|
11789
|
-
import { promises as
|
|
12199
|
+
import { promises as fs24 } from "fs";
|
|
11790
12200
|
async function loadLocalIndex(projectRoot, type2) {
|
|
11791
12201
|
const byId = /* @__PURE__ */ new Map();
|
|
11792
12202
|
const bySlug = /* @__PURE__ */ new Map();
|
|
@@ -11817,7 +12227,7 @@ async function classifyExisting(projectRoot, type2, cloudId, localSlug, targetSl
|
|
|
11817
12227
|
if (localSlug !== targetSlug)
|
|
11818
12228
|
return "untracked";
|
|
11819
12229
|
const lastSynced = localIndex.lastPushById[cloudId];
|
|
11820
|
-
const bytes = await
|
|
12230
|
+
const bytes = await fs24.readFile(entityFilePath(projectRoot, type2, localSlug), "utf8");
|
|
11821
12231
|
return lastSynced && hashNormalizedContent(bytes) === lastSynced ? "fast_forward" : "diverged";
|
|
11822
12232
|
}
|
|
11823
12233
|
async function writeOrSkip(projectRoot, type2, cloudId, slug, frontmatter, body, localIndex, mode, cloudVersion) {
|
|
@@ -11878,13 +12288,13 @@ async function writeOrSkip(projectRoot, type2, cloudId, slug, frontmatter, body,
|
|
|
11878
12288
|
}
|
|
11879
12289
|
if (existingSlugForId && existingSlugForId !== slug) {
|
|
11880
12290
|
try {
|
|
11881
|
-
await
|
|
12291
|
+
await fs24.unlink(entityFilePath(projectRoot, type2, existingSlugForId));
|
|
11882
12292
|
localIndex.bySlug.delete(existingSlugForId);
|
|
11883
12293
|
} catch {
|
|
11884
12294
|
}
|
|
11885
12295
|
}
|
|
11886
12296
|
await writeEntity(projectRoot, type2, frontmatter, text);
|
|
11887
|
-
const bytes = await
|
|
12297
|
+
const bytes = await fs24.readFile(entityFilePath(projectRoot, type2, slug), "utf8");
|
|
11888
12298
|
const hash = hashNormalizedContent(bytes);
|
|
11889
12299
|
localIndex.byId.set(cloudId, slug);
|
|
11890
12300
|
localIndex.bySlug.set(slug, cloudId);
|
|
@@ -11938,7 +12348,7 @@ var init_write = __esm({
|
|
|
11938
12348
|
});
|
|
11939
12349
|
|
|
11940
12350
|
// ../../packages/local-store/dist/pull/entities.js
|
|
11941
|
-
import { promises as
|
|
12351
|
+
import { promises as fs25 } from "fs";
|
|
11942
12352
|
function buildContextFrontmatter(payload) {
|
|
11943
12353
|
const cloudCtx = payload.context ?? {};
|
|
11944
12354
|
const glossary = (payload.glossary ?? []).map((g) => ({
|
|
@@ -11993,7 +12403,7 @@ async function pullContext(projectRoot, cloud, projectId, mode, result) {
|
|
|
11993
12403
|
const frontmatter = buildContextFrontmatter(payload);
|
|
11994
12404
|
let hasLocal = false;
|
|
11995
12405
|
try {
|
|
11996
|
-
await
|
|
12406
|
+
await fs25.access(contextPath(projectRoot));
|
|
11997
12407
|
hasLocal = true;
|
|
11998
12408
|
} catch {
|
|
11999
12409
|
hasLocal = false;
|
|
@@ -12023,7 +12433,7 @@ async function pullContext(projectRoot, cloud, projectId, mode, result) {
|
|
|
12023
12433
|
frontmatter,
|
|
12024
12434
|
notes_body: existingNotes
|
|
12025
12435
|
});
|
|
12026
|
-
const bytes = await
|
|
12436
|
+
const bytes = await fs25.readFile(contextPath(projectRoot), "utf8");
|
|
12027
12437
|
const hash = hashNormalizedContent(bytes);
|
|
12028
12438
|
await recordLastPull(projectRoot, "context:root", hash);
|
|
12029
12439
|
await recordLastPush(projectRoot, "context:root", hash);
|
|
@@ -12538,14 +12948,14 @@ var init_repair = __esm({
|
|
|
12538
12948
|
});
|
|
12539
12949
|
|
|
12540
12950
|
// ../../packages/local-store/dist/session-state.js
|
|
12541
|
-
import { randomUUID as
|
|
12951
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
12542
12952
|
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";
|
|
12543
12953
|
import os2 from "os";
|
|
12544
|
-
import
|
|
12954
|
+
import path19 from "path";
|
|
12545
12955
|
function quarantineCorruptedSessionState(file, raw) {
|
|
12546
12956
|
try {
|
|
12547
|
-
const dir =
|
|
12548
|
-
const quarantine =
|
|
12957
|
+
const dir = path19.dirname(file);
|
|
12958
|
+
const quarantine = path19.join(dir, `session-state.corrupted.${Date.now()}.json`);
|
|
12549
12959
|
atomicWriteFileSync(quarantine, raw);
|
|
12550
12960
|
} catch {
|
|
12551
12961
|
}
|
|
@@ -12554,8 +12964,8 @@ function withSessionStateLock(filePath, fn) {
|
|
|
12554
12964
|
const lockPath = `${filePath}.lock`;
|
|
12555
12965
|
const deadline = Date.now() + 5e3;
|
|
12556
12966
|
const staleMs = 15e3;
|
|
12557
|
-
const token =
|
|
12558
|
-
mkdirSync2(
|
|
12967
|
+
const token = randomUUID8();
|
|
12968
|
+
mkdirSync2(path19.dirname(lockPath), { recursive: true });
|
|
12559
12969
|
const tryCreate = () => {
|
|
12560
12970
|
try {
|
|
12561
12971
|
const fd = openSync2(lockPath, "wx", 420);
|
|
@@ -12629,242 +13039,67 @@ function readSessionState(projectRoot) {
|
|
|
12629
13039
|
const file = sessionStatePath(projectRoot);
|
|
12630
13040
|
if (!existsSync2(file))
|
|
12631
13041
|
return null;
|
|
12632
|
-
try {
|
|
12633
|
-
const raw = readFileSync(file, "utf-8");
|
|
12634
|
-
if (!raw.trim())
|
|
12635
|
-
return null;
|
|
12636
|
-
const parsed = JSON.parse(raw);
|
|
12637
|
-
if (parsed && typeof parsed === "object" && parsed.version === 1) {
|
|
12638
|
-
return parsed;
|
|
12639
|
-
}
|
|
12640
|
-
quarantineCorruptedSessionState(file, raw);
|
|
12641
|
-
return null;
|
|
12642
|
-
} catch {
|
|
12643
|
-
try {
|
|
12644
|
-
const raw = readFileSync(file, "utf-8");
|
|
12645
|
-
if (raw.trim()) {
|
|
12646
|
-
quarantineCorruptedSessionState(file, raw);
|
|
12647
|
-
}
|
|
12648
|
-
} catch {
|
|
12649
|
-
}
|
|
12650
|
-
return null;
|
|
12651
|
-
}
|
|
12652
|
-
}
|
|
12653
|
-
function updateSessionState(projectRoot, patch) {
|
|
12654
|
-
const file = sessionStatePath(projectRoot);
|
|
12655
|
-
const cacheDir2 = path18.dirname(file);
|
|
12656
|
-
if (!existsSync2(cacheDir2)) {
|
|
12657
|
-
mkdirSync2(cacheDir2, { recursive: true });
|
|
12658
|
-
}
|
|
12659
|
-
return withSessionStateLock(file, () => {
|
|
12660
|
-
const existing = readSessionState(projectRoot);
|
|
12661
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
12662
|
-
const cleanRunPatch = patch.active_run ? Object.fromEntries(Object.entries(patch.active_run).filter(([_, v]) => v !== void 0)) : void 0;
|
|
12663
|
-
const cleanFindingPatch = patch.active_finding ? Object.fromEntries(Object.entries(patch.active_finding).filter(([_, v]) => v !== void 0)) : void 0;
|
|
12664
|
-
const next = {
|
|
12665
|
-
version: 1,
|
|
12666
|
-
updated_at: now,
|
|
12667
|
-
active_scenario: patch.active_scenario ? { slug: patch.active_scenario.slug, updated_at: now } : existing?.active_scenario,
|
|
12668
|
-
active_run: patch.active_run ? {
|
|
12669
|
-
id: patch.active_run.id ?? existing?.active_run?.id ?? "",
|
|
12670
|
-
scenario_slug: patch.active_run.scenario_slug ?? existing?.active_run?.scenario_slug ?? "",
|
|
12671
|
-
status: patch.active_run.status ?? existing?.active_run?.status ?? "running",
|
|
12672
|
-
...existing?.active_run ?? {},
|
|
12673
|
-
...cleanRunPatch,
|
|
12674
|
-
updated_at: now
|
|
12675
|
-
} : existing?.active_run,
|
|
12676
|
-
active_finding: patch.active_finding ? {
|
|
12677
|
-
slug: patch.active_finding.slug,
|
|
12678
|
-
...existing?.active_finding ?? {},
|
|
12679
|
-
...cleanFindingPatch,
|
|
12680
|
-
updated_at: now
|
|
12681
|
-
} : existing?.active_finding,
|
|
12682
|
-
active_persona: patch.active_persona ? { slug: patch.active_persona.slug, updated_at: now } : existing?.active_persona
|
|
12683
|
-
};
|
|
12684
|
-
atomicWriteFileSync(file, JSON.stringify(next, null, 2) + "\n");
|
|
12685
|
-
return next;
|
|
12686
|
-
});
|
|
12687
|
-
}
|
|
12688
|
-
var init_session_state = __esm({
|
|
12689
|
-
"../../packages/local-store/dist/session-state.js"() {
|
|
12690
|
-
"use strict";
|
|
12691
|
-
init_atomic();
|
|
12692
|
-
init_paths();
|
|
12693
|
-
}
|
|
12694
|
-
});
|
|
12695
|
-
|
|
12696
|
-
// ../../packages/local-store/dist/directions.js
|
|
12697
|
-
import { randomUUID as randomUUID8 } from "crypto";
|
|
12698
|
-
import { promises as fs25 } from "fs";
|
|
12699
|
-
import path19 from "path";
|
|
12700
|
-
import { z as z26 } from "zod";
|
|
12701
|
-
function directionsDir(projectRoot) {
|
|
12702
|
-
return path19.join(iamuDir(projectRoot), DIRECTIONS_DIR);
|
|
12703
|
-
}
|
|
12704
|
-
function directionPath(projectRoot, slug) {
|
|
12705
|
-
return path19.join(directionsDir(projectRoot), `${slug}.md`);
|
|
12706
|
-
}
|
|
12707
|
-
function renderBody(texts) {
|
|
12708
|
-
return [
|
|
12709
|
-
"## Problem",
|
|
12710
|
-
"",
|
|
12711
|
-
texts.problem.trim(),
|
|
12712
|
-
"",
|
|
12713
|
-
"## Change",
|
|
12714
|
-
"",
|
|
12715
|
-
texts.change.trim(),
|
|
12716
|
-
"",
|
|
12717
|
-
"## Leading practice",
|
|
12718
|
-
"",
|
|
12719
|
-
texts.leading_practice.trim(),
|
|
12720
|
-
""
|
|
12721
|
-
].join("\n");
|
|
12722
|
-
}
|
|
12723
|
-
function section(body, heading) {
|
|
12724
|
-
const re = new RegExp(`^## ${heading}\\s*\\n([\\s\\S]*?)(?=^## |$(?![\\s\\S]))`, "m");
|
|
12725
|
-
return re.exec(body)?.[1]?.trim() ?? "";
|
|
12726
|
-
}
|
|
12727
|
-
function parseDirection(raw, file) {
|
|
12728
|
-
const { frontmatter, body } = parseFrontmatter(raw);
|
|
12729
|
-
return {
|
|
12730
|
-
frontmatter: directionFrontmatterSchema.parse(frontmatter),
|
|
12731
|
-
texts: {
|
|
12732
|
-
problem: section(body, "Problem"),
|
|
12733
|
-
change: section(body, "Change"),
|
|
12734
|
-
leading_practice: section(body, "Leading practice")
|
|
12735
|
-
},
|
|
12736
|
-
path: file
|
|
12737
|
-
};
|
|
12738
|
-
}
|
|
12739
|
-
async function readDirection(projectRoot, slug) {
|
|
12740
|
-
const file = directionPath(projectRoot, slug);
|
|
12741
|
-
let raw;
|
|
12742
|
-
try {
|
|
12743
|
-
raw = await fs25.readFile(file, "utf8");
|
|
12744
|
-
} catch (err) {
|
|
12745
|
-
if (err.code === "ENOENT")
|
|
12746
|
-
return null;
|
|
12747
|
-
throw err;
|
|
12748
|
-
}
|
|
12749
|
-
return parseDirection(raw, file);
|
|
12750
|
-
}
|
|
12751
|
-
async function listDirections(projectRoot) {
|
|
12752
|
-
let names2;
|
|
12753
|
-
try {
|
|
12754
|
-
names2 = await fs25.readdir(directionsDir(projectRoot));
|
|
12755
|
-
} catch (err) {
|
|
12756
|
-
if (err.code === "ENOENT")
|
|
12757
|
-
return [];
|
|
12758
|
-
throw err;
|
|
12759
|
-
}
|
|
12760
|
-
const out = [];
|
|
12761
|
-
for (const name of names2.sort()) {
|
|
12762
|
-
if (!name.endsWith(".md"))
|
|
12763
|
-
continue;
|
|
12764
|
-
const file = path19.join(directionsDir(projectRoot), name);
|
|
13042
|
+
try {
|
|
13043
|
+
const raw = readFileSync(file, "utf-8");
|
|
13044
|
+
if (!raw.trim())
|
|
13045
|
+
return null;
|
|
13046
|
+
const parsed = JSON.parse(raw);
|
|
13047
|
+
if (parsed && typeof parsed === "object" && parsed.version === 1) {
|
|
13048
|
+
return parsed;
|
|
13049
|
+
}
|
|
13050
|
+
quarantineCorruptedSessionState(file, raw);
|
|
13051
|
+
return null;
|
|
13052
|
+
} catch {
|
|
12765
13053
|
try {
|
|
12766
|
-
|
|
13054
|
+
const raw = readFileSync(file, "utf-8");
|
|
13055
|
+
if (raw.trim()) {
|
|
13056
|
+
quarantineCorruptedSessionState(file, raw);
|
|
13057
|
+
}
|
|
12767
13058
|
} catch {
|
|
12768
13059
|
}
|
|
13060
|
+
return null;
|
|
12769
13061
|
}
|
|
12770
|
-
return out.sort((a, b) => b.frontmatter.updated_at.localeCompare(a.frontmatter.updated_at));
|
|
12771
|
-
}
|
|
12772
|
-
async function writeDirection(projectRoot, fm, texts) {
|
|
12773
|
-
const file = directionPath(projectRoot, fm.slug);
|
|
12774
|
-
const validated = directionFrontmatterSchema.parse(fm);
|
|
12775
|
-
await atomicWriteFile(file, serializeFrontmatter(validated, renderBody(texts)));
|
|
12776
|
-
return file;
|
|
12777
13062
|
}
|
|
12778
|
-
|
|
12779
|
-
const
|
|
12780
|
-
const
|
|
12781
|
-
|
|
12782
|
-
|
|
12783
|
-
leading_practice: proposal.leading_practice
|
|
12784
|
-
};
|
|
12785
|
-
if (existing?.frontmatter.status === "rejected") {
|
|
12786
|
-
return { outcome: "previously_rejected" };
|
|
13063
|
+
function updateSessionState(projectRoot, patch) {
|
|
13064
|
+
const file = sessionStatePath(projectRoot);
|
|
13065
|
+
const cacheDir2 = path19.dirname(file);
|
|
13066
|
+
if (!existsSync2(cacheDir2)) {
|
|
13067
|
+
mkdirSync2(cacheDir2, { recursive: true });
|
|
12787
13068
|
}
|
|
12788
|
-
|
|
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
|
-
return { ...existing, frontmatter: fm };
|
|
13069
|
+
return withSessionStateLock(file, () => {
|
|
13070
|
+
const existing = readSessionState(projectRoot);
|
|
13071
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
13072
|
+
const cleanRunPatch = patch.active_run ? Object.fromEntries(Object.entries(patch.active_run).filter(([_, v]) => v !== void 0)) : void 0;
|
|
13073
|
+
const cleanFindingPatch = patch.active_finding ? Object.fromEntries(Object.entries(patch.active_finding).filter(([_, v]) => v !== void 0)) : void 0;
|
|
13074
|
+
const next = {
|
|
13075
|
+
version: 1,
|
|
13076
|
+
updated_at: now,
|
|
13077
|
+
active_scenario: patch.active_scenario ? { slug: patch.active_scenario.slug, updated_at: now } : existing?.active_scenario,
|
|
13078
|
+
active_run: patch.active_run ? {
|
|
13079
|
+
id: patch.active_run.id ?? existing?.active_run?.id ?? "",
|
|
13080
|
+
scenario_slug: patch.active_run.scenario_slug ?? existing?.active_run?.scenario_slug ?? "",
|
|
13081
|
+
status: patch.active_run.status ?? existing?.active_run?.status ?? "running",
|
|
13082
|
+
...existing?.active_run ?? {},
|
|
13083
|
+
...cleanRunPatch,
|
|
13084
|
+
updated_at: now
|
|
13085
|
+
} : existing?.active_run,
|
|
13086
|
+
active_finding: patch.active_finding ? {
|
|
13087
|
+
slug: patch.active_finding.slug,
|
|
13088
|
+
...existing?.active_finding ?? {},
|
|
13089
|
+
...cleanFindingPatch,
|
|
13090
|
+
updated_at: now
|
|
13091
|
+
} : existing?.active_finding,
|
|
13092
|
+
active_persona: patch.active_persona ? { slug: patch.active_persona.slug, updated_at: now } : existing?.active_persona
|
|
13093
|
+
};
|
|
13094
|
+
atomicWriteFileSync(file, JSON.stringify(next, null, 2) + "\n");
|
|
13095
|
+
return next;
|
|
13096
|
+
});
|
|
12817
13097
|
}
|
|
12818
|
-
var
|
|
12819
|
-
|
|
12820
|
-
"../../packages/local-store/dist/directions.js"() {
|
|
13098
|
+
var init_session_state = __esm({
|
|
13099
|
+
"../../packages/local-store/dist/session-state.js"() {
|
|
12821
13100
|
"use strict";
|
|
12822
13101
|
init_atomic();
|
|
12823
|
-
init_markdown();
|
|
12824
13102
|
init_paths();
|
|
12825
|
-
DIRECTIONS_DIR = "directions";
|
|
12826
|
-
DIRECTION_STATUSES = [
|
|
12827
|
-
"proposed",
|
|
12828
|
-
"accepted",
|
|
12829
|
-
"done",
|
|
12830
|
-
"rejected"
|
|
12831
|
-
];
|
|
12832
|
-
directionStatusSchema = z26.enum(DIRECTION_STATUSES);
|
|
12833
|
-
directionSizeSchema = z26.enum(["small", "medium", "large"]);
|
|
12834
|
-
directionReachSchema = z26.object({
|
|
12835
|
-
scenario: z26.string().min(1),
|
|
12836
|
-
effect: z26.enum(["helps", "risk"]),
|
|
12837
|
-
reason: z26.string().min(1)
|
|
12838
|
-
});
|
|
12839
|
-
codeRefSchema = z26.object({
|
|
12840
|
-
path: z26.string().min(1),
|
|
12841
|
-
symbol: z26.string().min(1)
|
|
12842
|
-
});
|
|
12843
|
-
directionFrontmatterSchema = z26.object({
|
|
12844
|
-
id: z26.string().uuid(),
|
|
12845
|
-
slug: z26.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
|
|
12846
|
-
title: z26.string().min(1),
|
|
12847
|
-
status: directionStatusSchema.default("proposed"),
|
|
12848
|
-
size: directionSizeSchema,
|
|
12849
|
-
/** Finding ids this direction would resolve. */
|
|
12850
|
-
absorbs: z26.array(z26.string().min(1)).default([]),
|
|
12851
|
-
/** Predicted effect on other scenarios — never evidence. */
|
|
12852
|
-
reach: z26.array(directionReachSchema).default([]),
|
|
12853
|
-
/** Scenarios to re-run once the direction lands. */
|
|
12854
|
-
verify: z26.array(z26.string().min(1)).default([]),
|
|
12855
|
-
builds_on: z26.array(codeRefSchema).default([]),
|
|
12856
|
-
/** Runs (or `project:<timestamp>`) whose analysis proposed it. */
|
|
12857
|
-
seen_in_runs: z26.array(z26.string().min(1)).default([]),
|
|
12858
|
-
advised_by: z26.string().regex(/^advisor@\S+$/),
|
|
12859
|
-
created_at: z26.string(),
|
|
12860
|
-
updated_at: z26.string()
|
|
12861
|
-
});
|
|
12862
|
-
union = (a, b, key) => {
|
|
12863
|
-
const seen = /* @__PURE__ */ new Map();
|
|
12864
|
-
for (const x of [...a, ...b])
|
|
12865
|
-
seen.set(key(x), x);
|
|
12866
|
-
return [...seen.values()];
|
|
12867
|
-
};
|
|
12868
13103
|
}
|
|
12869
13104
|
});
|
|
12870
13105
|
|
|
@@ -12993,7 +13228,7 @@ async function readContextSummary(projectRoot) {
|
|
|
12993
13228
|
return null;
|
|
12994
13229
|
}
|
|
12995
13230
|
}
|
|
12996
|
-
function
|
|
13231
|
+
function pagePath2(url) {
|
|
12997
13232
|
try {
|
|
12998
13233
|
return new URL(url).pathname || "/";
|
|
12999
13234
|
} catch {
|
|
@@ -13017,7 +13252,7 @@ async function walkedCoverage(projectRoot) {
|
|
|
13017
13252
|
const entry = out.get(slug) ?? { paths: /* @__PURE__ */ new Set(), runs: 0 };
|
|
13018
13253
|
entry.runs++;
|
|
13019
13254
|
for (const step of await readStepsForRun(projectRoot, name)) {
|
|
13020
|
-
const p = typeof step.url === "string" ?
|
|
13255
|
+
const p = typeof step.url === "string" ? pagePath2(step.url) : null;
|
|
13021
13256
|
if (p)
|
|
13022
13257
|
entry.paths.add(p);
|
|
13023
13258
|
}
|
|
@@ -13672,6 +13907,7 @@ var init_dist2 = __esm({
|
|
|
13672
13907
|
init_revisit();
|
|
13673
13908
|
init_explore_map();
|
|
13674
13909
|
init_walkthrough_canonical2();
|
|
13910
|
+
init_report_markdown();
|
|
13675
13911
|
init_journey_diff();
|
|
13676
13912
|
init_finding_match();
|
|
13677
13913
|
init_guidance2();
|
|
@@ -13780,15 +14016,15 @@ function resolveApiUrl(configured) {
|
|
|
13780
14016
|
assertSecureApiUrl(url, configured ? "API URL" : "IAMU_API_URL");
|
|
13781
14017
|
return url;
|
|
13782
14018
|
}
|
|
13783
|
-
function parseStore(raw,
|
|
14019
|
+
function parseStore(raw, path44) {
|
|
13784
14020
|
let parsed;
|
|
13785
14021
|
try {
|
|
13786
14022
|
parsed = JSON.parse(raw);
|
|
13787
14023
|
} catch (err) {
|
|
13788
|
-
throw new CredentialsFileError(
|
|
14024
|
+
throw new CredentialsFileError(path44, "not valid JSON", err);
|
|
13789
14025
|
}
|
|
13790
14026
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
13791
|
-
throw new CredentialsFileError(
|
|
14027
|
+
throw new CredentialsFileError(path44, "expected a JSON object");
|
|
13792
14028
|
}
|
|
13793
14029
|
return parsed;
|
|
13794
14030
|
}
|
|
@@ -13796,26 +14032,26 @@ function isNotFound(err) {
|
|
|
13796
14032
|
return typeof err === "object" && err !== null && err.code === "ENOENT";
|
|
13797
14033
|
}
|
|
13798
14034
|
async function readCredentialsStore() {
|
|
13799
|
-
const
|
|
14035
|
+
const path44 = getCredentialsPath();
|
|
13800
14036
|
let raw;
|
|
13801
14037
|
try {
|
|
13802
|
-
raw = await readFile(
|
|
14038
|
+
raw = await readFile(path44, "utf-8");
|
|
13803
14039
|
} catch (err) {
|
|
13804
14040
|
if (isNotFound(err)) return {};
|
|
13805
14041
|
warnUnreadableStoreOnce(err);
|
|
13806
14042
|
throw err;
|
|
13807
14043
|
}
|
|
13808
14044
|
try {
|
|
13809
|
-
return parseStore(raw,
|
|
14045
|
+
return parseStore(raw, path44);
|
|
13810
14046
|
} catch (err) {
|
|
13811
14047
|
warnUnreadableStoreOnce(err);
|
|
13812
14048
|
throw err;
|
|
13813
14049
|
}
|
|
13814
14050
|
}
|
|
13815
14051
|
async function writeCredentialsStore(store) {
|
|
13816
|
-
const
|
|
13817
|
-
await mkdir(dirname(
|
|
13818
|
-
await atomicWriteFile(
|
|
14052
|
+
const path44 = getCredentialsPath();
|
|
14053
|
+
await mkdir(dirname(path44), { recursive: true, mode: 448 });
|
|
14054
|
+
await atomicWriteFile(path44, `${JSON.stringify(store, null, 2)}
|
|
13819
14055
|
`, {
|
|
13820
14056
|
mode: 384
|
|
13821
14057
|
});
|
|
@@ -13835,16 +14071,16 @@ function resolveApiKey(apiUrl) {
|
|
|
13835
14071
|
} catch {
|
|
13836
14072
|
return null;
|
|
13837
14073
|
}
|
|
13838
|
-
const
|
|
14074
|
+
const path44 = getCredentialsPath();
|
|
13839
14075
|
let raw;
|
|
13840
14076
|
try {
|
|
13841
|
-
raw = readFileSync2(
|
|
14077
|
+
raw = readFileSync2(path44, "utf-8");
|
|
13842
14078
|
} catch (err) {
|
|
13843
14079
|
if (!isNotFound(err)) warnUnreadableStoreOnce(err);
|
|
13844
14080
|
return null;
|
|
13845
14081
|
}
|
|
13846
14082
|
try {
|
|
13847
|
-
const entry = parseStore(raw,
|
|
14083
|
+
const entry = parseStore(raw, path44)[url];
|
|
13848
14084
|
if (typeof entry !== "object" || entry === null) return null;
|
|
13849
14085
|
const token = entry.token;
|
|
13850
14086
|
return typeof token === "string" && token ? token : null;
|
|
@@ -13883,12 +14119,12 @@ var init_credentials = __esm({
|
|
|
13883
14119
|
* @param reason - What is wrong with it.
|
|
13884
14120
|
* @param cause - Underlying parse error, if any.
|
|
13885
14121
|
*/
|
|
13886
|
-
constructor(
|
|
14122
|
+
constructor(path44, reason, cause) {
|
|
13887
14123
|
super(
|
|
13888
|
-
`Credentials file ${
|
|
14124
|
+
`Credentials file ${path44} is corrupt (${reason}). Fix it by hand, or delete it and run \`iamu auth login\` again.`,
|
|
13889
14125
|
{
|
|
13890
14126
|
cause,
|
|
13891
|
-
suggestedAction: `Delete ${
|
|
14127
|
+
suggestedAction: `Delete ${path44} and run \`iamu auth login\`, or set IAMU_TOKEN.`
|
|
13892
14128
|
}
|
|
13893
14129
|
);
|
|
13894
14130
|
}
|
|
@@ -14669,8 +14905,8 @@ import { dirname as dirname3, join as join4, resolve as resolve2 } from "path";
|
|
|
14669
14905
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
14670
14906
|
import chalk6 from "chalk";
|
|
14671
14907
|
function getVersion() {
|
|
14672
|
-
if ("0.4.0-beta.
|
|
14673
|
-
return "0.4.0-beta.
|
|
14908
|
+
if ("0.4.0-beta.1") {
|
|
14909
|
+
return "0.4.0-beta.1";
|
|
14674
14910
|
}
|
|
14675
14911
|
try {
|
|
14676
14912
|
const thisDir = dirname3(fileURLToPath2(import.meta.url));
|
|
@@ -14777,16 +15013,16 @@ async function needsWrite(filePath, content) {
|
|
|
14777
15013
|
return true;
|
|
14778
15014
|
}
|
|
14779
15015
|
}
|
|
14780
|
-
function isCliWrittenSkill(
|
|
15016
|
+
function isCliWrittenSkill(path44) {
|
|
14781
15017
|
try {
|
|
14782
|
-
return /^generatedBy:/m.test(readFileSync4(
|
|
15018
|
+
return /^generatedBy:/m.test(readFileSync4(path44, "utf-8"));
|
|
14783
15019
|
} catch {
|
|
14784
15020
|
return false;
|
|
14785
15021
|
}
|
|
14786
15022
|
}
|
|
14787
|
-
function isCliWrittenPrompt(
|
|
15023
|
+
function isCliWrittenPrompt(path44) {
|
|
14788
15024
|
try {
|
|
14789
|
-
const content = readFileSync4(
|
|
15025
|
+
const content = readFileSync4(path44, "utf-8");
|
|
14790
15026
|
return content.includes("iamu") || /^generatedBy:/m.test(content);
|
|
14791
15027
|
} catch {
|
|
14792
15028
|
return false;
|
|
@@ -14798,8 +15034,8 @@ function planSkillFiles(hosts, projectRoot, version) {
|
|
|
14798
15034
|
for (const host of hosts) {
|
|
14799
15035
|
if (pluginProvidesSkills(host, projectRoot)) {
|
|
14800
15036
|
for (const name of skillTemplates.keys()) {
|
|
14801
|
-
const
|
|
14802
|
-
if (isCliWrittenSkill(
|
|
15037
|
+
const path44 = join4(host.getSkillsDir(projectRoot), name, "SKILL.md");
|
|
15038
|
+
if (isCliWrittenSkill(path44)) files.push({ host, path: path44, content: null });
|
|
14803
15039
|
}
|
|
14804
15040
|
} else if (host.supportsSkills) {
|
|
14805
15041
|
const skillsDir = host.getSkillsDir(projectRoot);
|
|
@@ -16356,8 +16592,17 @@ function lead(text) {
|
|
|
16356
16592
|
}
|
|
16357
16593
|
function severityCell(severity) {
|
|
16358
16594
|
const s = (severity ?? "\u2014").toUpperCase();
|
|
16359
|
-
const
|
|
16360
|
-
return severity === "critical" ? chalk12.red(
|
|
16595
|
+
const cell2 = padRight(s, 8);
|
|
16596
|
+
return severity === "critical" ? chalk12.red(cell2) : severity === "major" ? chalk12.yellow(cell2) : chalk12.dim(cell2);
|
|
16597
|
+
}
|
|
16598
|
+
function severityMix(r) {
|
|
16599
|
+
if (r.problems === 0) return chalk12.green("no problems");
|
|
16600
|
+
const { critical, major, minor } = r.bySeverity;
|
|
16601
|
+
return [
|
|
16602
|
+
critical > 0 ? chalk12.red(`${critical} critical`) : "",
|
|
16603
|
+
major > 0 ? chalk12.yellow(`${major} major`) : "",
|
|
16604
|
+
minor > 0 ? chalk12.dim(`${minor} minor`) : ""
|
|
16605
|
+
].filter(Boolean).join(" ");
|
|
16361
16606
|
}
|
|
16362
16607
|
function deltaCell(r) {
|
|
16363
16608
|
const parts = [
|
|
@@ -16375,6 +16620,13 @@ function renderRunView(v, width = 100) {
|
|
|
16375
16620
|
);
|
|
16376
16621
|
const meta = [shortDate(v.completedAt), `${v.steps} steps`, v.targetUrl].filter(Boolean).join(" \xB7 ");
|
|
16377
16622
|
lines.push(` ${chalk12.dim(meta)}`);
|
|
16623
|
+
if (v.checks) {
|
|
16624
|
+
const parts = [
|
|
16625
|
+
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` : "",
|
|
16626
|
+
v.checks.directions.length > 0 ? `after ${v.checks.directions.length} direction${v.checks.directions.length === 1 ? "" : "s"} landed` : ""
|
|
16627
|
+
].filter(Boolean);
|
|
16628
|
+
lines.push(` ${chalk12.dim(`This run ${parts.join(", ")}`)}`);
|
|
16629
|
+
}
|
|
16378
16630
|
lines.push("");
|
|
16379
16631
|
const color = verdictColor(v.verdict.label);
|
|
16380
16632
|
const question = v.verdict.question ?? "Would they come back?";
|
|
@@ -16402,12 +16654,21 @@ function renderRunView(v, width = 100) {
|
|
|
16402
16654
|
);
|
|
16403
16655
|
for (const item of s.items) {
|
|
16404
16656
|
const mark = SINCE_MARKS[item.outcome];
|
|
16405
|
-
|
|
16657
|
+
const qualifiers = [
|
|
16658
|
+
item.outcome === "persists" ? item.shift : "",
|
|
16659
|
+
item.outcome === "persists" && item.fixedBefore ? "fix did not hold" : ""
|
|
16660
|
+
].filter(Boolean);
|
|
16661
|
+
const held = qualifiers.length > 0 ? (item.fixedBefore ? chalk12.red : chalk12.dim)(
|
|
16662
|
+
` (${qualifiers.join("; ")})`
|
|
16663
|
+
) : "";
|
|
16664
|
+
lines.push(
|
|
16665
|
+
` ${mark} ${truncate(item.title, w - 18 - stripAnsi(held).length)}${held}`
|
|
16666
|
+
);
|
|
16406
16667
|
}
|
|
16407
16668
|
}
|
|
16408
16669
|
lines.push("");
|
|
16409
16670
|
const problems = [...v.findings].sort(
|
|
16410
|
-
(a, b) =>
|
|
16671
|
+
(a, b) => SEVERITY_ORDER2.indexOf(a.severity ?? "") - SEVERITY_ORDER2.indexOf(b.severity ?? "") || TYPE_ORDER2.indexOf(a.type) - TYPE_ORDER2.indexOf(b.type)
|
|
16411
16672
|
);
|
|
16412
16673
|
const strengthNote = v.strengths.length > 0 ? `, ${v.strengths.length} strength${v.strengths.length === 1 ? "" : "s"}` : "";
|
|
16413
16674
|
lines.push(
|
|
@@ -16418,15 +16679,23 @@ function renderRunView(v, width = 100) {
|
|
|
16418
16679
|
24,
|
|
16419
16680
|
Math.max(0, ...problems.map((f) => (f.where ?? "").length))
|
|
16420
16681
|
);
|
|
16421
|
-
const
|
|
16682
|
+
const tagWidth = problems.some((f) => f.persists) ? 9 : 0;
|
|
16683
|
+
const titleWidth = Math.max(
|
|
16684
|
+
24,
|
|
16685
|
+
w - 4 - 3 - 9 - 10 - whereWidth - 2 - tagWidth
|
|
16686
|
+
);
|
|
16422
16687
|
problems.forEach((f, i) => {
|
|
16423
16688
|
const where = chalk12.dim(
|
|
16424
16689
|
padRight(truncate(f.where ?? "", whereWidth), whereWidth)
|
|
16425
16690
|
);
|
|
16426
16691
|
const tag = f.persists ? chalk12.red(" persists") : "";
|
|
16692
|
+
const titleLines = wrap(f.title, titleWidth, "");
|
|
16427
16693
|
lines.push(
|
|
16428
|
-
` ${padRight(String(i + 1), 3)}${severityCell(f.severity)} ${padRight(chalk12.dim(f.type), 9)} ${padRight(
|
|
16694
|
+
` ${padRight(String(i + 1), 3)}${severityCell(f.severity)} ${padRight(chalk12.dim(f.type), 9)} ${padRight(titleLines[0] ?? "", titleWidth)} ${where}${tag}`
|
|
16429
16695
|
);
|
|
16696
|
+
for (const more of titleLines.slice(1)) {
|
|
16697
|
+
lines.push(`${" ".repeat(26)}${more}`);
|
|
16698
|
+
}
|
|
16430
16699
|
if (f.recommendation && (f.severity === "critical" || f.severity === "major")) {
|
|
16431
16700
|
const fix = wrap(`\u2192 ${lead(f.recommendation)}`, w - 30, " ".repeat(26));
|
|
16432
16701
|
if (fix.length > 2) {
|
|
@@ -16452,8 +16721,13 @@ function renderRunView(v, width = 100) {
|
|
|
16452
16721
|
);
|
|
16453
16722
|
}
|
|
16454
16723
|
}
|
|
16455
|
-
if (v.observations.length > 0) {
|
|
16724
|
+
if (v.observations.length > 0 || v.setupNotes.length > 0) {
|
|
16456
16725
|
lines.push("", ` ${chalk12.bold("Also worth knowing")}`);
|
|
16726
|
+
for (const n of v.setupNotes) {
|
|
16727
|
+
lines.push(
|
|
16728
|
+
` ${chalk12.dim("\xB7")} ${chalk12.dim("Test setup:")} ${truncate(n, w - 22)}`
|
|
16729
|
+
);
|
|
16730
|
+
}
|
|
16457
16731
|
for (const o of v.observations) {
|
|
16458
16732
|
const label = o.kind === "coverage_gap" ? "Not walked" : "Feature risk";
|
|
16459
16733
|
lines.push(
|
|
@@ -16488,7 +16762,7 @@ function renderOverview(v, width = 100) {
|
|
|
16488
16762
|
}
|
|
16489
16763
|
for (const r of shown) {
|
|
16490
16764
|
lines.push(
|
|
16491
|
-
` ${padRight(chalk12.dim(shortDate(r.completedAt)), 13)} ${verdictColor(r.label)(padRight(r.label, 8))} ${padRight(
|
|
16765
|
+
` ${padRight(chalk12.dim(shortDate(r.completedAt)), 13)} ${verdictColor(r.label)(padRight(r.label, 8))} ${padRight(severityMix(r), 24)} ${deltaCell(r)}`
|
|
16492
16766
|
);
|
|
16493
16767
|
}
|
|
16494
16768
|
if (s.latestAnswer) {
|
|
@@ -16517,7 +16791,7 @@ function renderOverview(v, width = 100) {
|
|
|
16517
16791
|
);
|
|
16518
16792
|
return lines.map((l) => l.trimEnd());
|
|
16519
16793
|
}
|
|
16520
|
-
var SINCE_MARKS,
|
|
16794
|
+
var SINCE_MARKS, SEVERITY_ORDER2, TYPE_ORDER2;
|
|
16521
16795
|
var init_report_view = __esm({
|
|
16522
16796
|
"src/ui/report-view.ts"() {
|
|
16523
16797
|
"use strict";
|
|
@@ -16528,8 +16802,8 @@ var init_report_view = __esm({
|
|
|
16528
16802
|
not_seen: chalk12.dim(padRight("\u25CB not seen", 12)),
|
|
16529
16803
|
uncertain: chalk12.dim(padRight("? unclear", 12))
|
|
16530
16804
|
};
|
|
16531
|
-
|
|
16532
|
-
|
|
16805
|
+
SEVERITY_ORDER2 = ["critical", "major", "minor", "observation"];
|
|
16806
|
+
TYPE_ORDER2 = ["incorrect", "hinders", "missing", "irrelevant"];
|
|
16533
16807
|
}
|
|
16534
16808
|
});
|
|
16535
16809
|
|
|
@@ -16918,9 +17192,7 @@ ${formatIssues(fileResult.error)}`,
|
|
|
16918
17192
|
console.log(
|
|
16919
17193
|
` ${chalk14.dim("verdicts:")} ${path23.relative(projectRoot, verdictsPath)}`
|
|
16920
17194
|
);
|
|
16921
|
-
console.log(
|
|
16922
|
-
` ${chalk14.dim("report:")} ${path23.relative(projectRoot, reportPath)}`
|
|
16923
|
-
);
|
|
17195
|
+
console.log(` ${chalk14.dim("report:")} ${reportPath}`);
|
|
16924
17196
|
printCloudRunInfo(cloudInfo);
|
|
16925
17197
|
await promptViewResults(projectRoot, { ...opts, runId });
|
|
16926
17198
|
}
|
|
@@ -16976,7 +17248,10 @@ function sinceItems(v) {
|
|
|
16976
17248
|
return groups.flatMap(
|
|
16977
17249
|
([outcome, items]) => (items ?? []).map((i) => ({
|
|
16978
17250
|
title: i.title ?? i.canonical_id ?? "untitled",
|
|
16979
|
-
outcome
|
|
17251
|
+
outcome,
|
|
17252
|
+
note: i.note ?? null,
|
|
17253
|
+
fixedBefore: Boolean(i.fixed_before),
|
|
17254
|
+
shift: outcome === "persists" ? severityShift(i.severity_before, i.severity_now) : ""
|
|
16980
17255
|
}))
|
|
16981
17256
|
);
|
|
16982
17257
|
}
|
|
@@ -17005,11 +17280,8 @@ async function readCompletedRuns(projectRoot, runIds, scenario) {
|
|
|
17005
17280
|
return out;
|
|
17006
17281
|
}
|
|
17007
17282
|
function verdictLabel(c) {
|
|
17008
|
-
const
|
|
17009
|
-
|
|
17010
|
-
if (judged.every((v) => v === "yes")) return "YES";
|
|
17011
|
-
if (judged.every((v) => v === "no")) return "NO";
|
|
17012
|
-
return "PARTIAL";
|
|
17283
|
+
const v = runVerdict(c);
|
|
17284
|
+
return v ? v.toUpperCase() : "PENDING";
|
|
17013
17285
|
}
|
|
17014
17286
|
function testLabel(verdict) {
|
|
17015
17287
|
return verdict === "yes" ? "YES" : verdict === "no" ? "NO" : verdict === "partial" ? "PARTIAL" : "PENDING";
|
|
@@ -17063,6 +17335,17 @@ async function openDirections(projectRoot) {
|
|
|
17063
17335
|
(d) => d.frontmatter.status === "proposed" || d.frontmatter.status === "accepted"
|
|
17064
17336
|
);
|
|
17065
17337
|
}
|
|
17338
|
+
function checksOf(c) {
|
|
17339
|
+
const checks = c?.checks;
|
|
17340
|
+
if (!c || !checks) return null;
|
|
17341
|
+
const fixes = checkOutcomes(c);
|
|
17342
|
+
return {
|
|
17343
|
+
fixes: fixes.length,
|
|
17344
|
+
fixed: fixes.filter((f) => f.outcome === "fixed").length,
|
|
17345
|
+
cameBack: fixes.filter((f) => f.outcome === "came_back").length,
|
|
17346
|
+
directions: checks.directions
|
|
17347
|
+
};
|
|
17348
|
+
}
|
|
17066
17349
|
async function buildRunView(projectRoot, run) {
|
|
17067
17350
|
const c = run.canonical;
|
|
17068
17351
|
const slugs = slugsOf(run);
|
|
@@ -17078,12 +17361,13 @@ async function buildRunView(projectRoot, run) {
|
|
|
17078
17361
|
const v = verificationOf(c);
|
|
17079
17362
|
const persisting = new Set(v?.persists.map((p) => p.canonical_id) ?? []);
|
|
17080
17363
|
const added = new Set(v?.new_findings.map((n) => n.id) ?? []);
|
|
17364
|
+
const stepUrl2 = new Map((c?.steps ?? []).map((st) => [st.id, st.url]));
|
|
17081
17365
|
const findings2 = [];
|
|
17082
17366
|
const strengths = [];
|
|
17083
17367
|
for (const f of c?.findings ?? []) {
|
|
17084
17368
|
const entity = byId.get(f.id);
|
|
17085
17369
|
const fm = entity?.frontmatter;
|
|
17086
|
-
const title = fm?.title ?? firstSentence(f.ui_fact ?? fm?.ui_fact ?? f.id);
|
|
17370
|
+
const title = f.title ?? fm?.title ?? firstSentence(f.ui_fact ?? fm?.ui_fact ?? f.id);
|
|
17087
17371
|
if (f.type === "helps") {
|
|
17088
17372
|
strengths.push(title);
|
|
17089
17373
|
continue;
|
|
@@ -17097,7 +17381,7 @@ async function buildRunView(projectRoot, run) {
|
|
|
17097
17381
|
recommendation: f.recommendation || null,
|
|
17098
17382
|
isNew: added.has(f.id),
|
|
17099
17383
|
where: whereOf(
|
|
17100
|
-
fm?.page_url,
|
|
17384
|
+
f.page_url ?? fm?.page_url ?? stepUrl2.get(f.linked_step_ids?.[0] ?? ""),
|
|
17101
17385
|
fm?.walkthrough_step_id ?? f.linked_step_ids?.[0]
|
|
17102
17386
|
),
|
|
17103
17387
|
persists: Boolean(f.canonical_id && persisting.has(f.canonical_id))
|
|
@@ -17140,7 +17424,7 @@ async function buildRunView(projectRoot, run) {
|
|
|
17140
17424
|
answer: overall?.answer ?? null
|
|
17141
17425
|
},
|
|
17142
17426
|
tests: tests.map((t) => ({
|
|
17143
|
-
question: t.question,
|
|
17427
|
+
question: stripAnswerHint(t.question),
|
|
17144
17428
|
label: testLabel(t.verdict)
|
|
17145
17429
|
})),
|
|
17146
17430
|
since: v ? {
|
|
@@ -17159,9 +17443,17 @@ async function buildRunView(projectRoot, run) {
|
|
|
17159
17443
|
absorbs: d.absorbs?.length ?? 0,
|
|
17160
17444
|
verify: d.verify?.length ?? 0
|
|
17161
17445
|
})),
|
|
17162
|
-
observations: analysis?.observations ?? []
|
|
17446
|
+
observations: analysis?.observations ?? [],
|
|
17447
|
+
checks: checksOf(c),
|
|
17448
|
+
setupNotes: c?.setup_notes ?? []
|
|
17163
17449
|
};
|
|
17164
17450
|
}
|
|
17451
|
+
function severityCounts(c) {
|
|
17452
|
+
const problems = (c?.findings ?? []).filter(isProblem);
|
|
17453
|
+
const critical = problems.filter((f) => f.severity === "critical").length;
|
|
17454
|
+
const major = problems.filter((f) => f.severity === "major").length;
|
|
17455
|
+
return { critical, major, minor: problems.length - critical - major };
|
|
17456
|
+
}
|
|
17165
17457
|
async function buildOverview(projectRoot, runs) {
|
|
17166
17458
|
const groups = /* @__PURE__ */ new Map();
|
|
17167
17459
|
for (const r of runs) {
|
|
@@ -17188,6 +17480,7 @@ async function buildOverview(projectRoot, runs) {
|
|
|
17188
17480
|
completedAt: r.manifest.completed_at ?? null,
|
|
17189
17481
|
label: verdictLabel(r.canonical),
|
|
17190
17482
|
problems: (r.canonical?.findings ?? []).filter(isProblem).length,
|
|
17483
|
+
bySeverity: severityCounts(r.canonical),
|
|
17191
17484
|
resolved: v?.resolved.length ?? 0,
|
|
17192
17485
|
persists: v?.persists.length ?? 0,
|
|
17193
17486
|
added: v?.new_findings.filter(isProblem).length ?? 0
|
|
@@ -17239,6 +17532,10 @@ __export(html_report_exports, {
|
|
|
17239
17532
|
});
|
|
17240
17533
|
import { promises as fs31 } from "fs";
|
|
17241
17534
|
import path25 from "path";
|
|
17535
|
+
function firstSentenceOf(text) {
|
|
17536
|
+
if (!text) return null;
|
|
17537
|
+
return /^(.+?[.!?])(\s|$)/.exec(text.trim())?.[1] ?? text.trim();
|
|
17538
|
+
}
|
|
17242
17539
|
function esc(str2) {
|
|
17243
17540
|
if (!str2) return "";
|
|
17244
17541
|
return str2.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -17345,6 +17642,70 @@ function renderStepScreenshotHtml(s) {
|
|
|
17345
17642
|
</div>
|
|
17346
17643
|
`;
|
|
17347
17644
|
}
|
|
17645
|
+
function sortProblems(list2) {
|
|
17646
|
+
const r = (v, order) => {
|
|
17647
|
+
const i = order.indexOf(v ?? "");
|
|
17648
|
+
return i === -1 ? order.length : i;
|
|
17649
|
+
};
|
|
17650
|
+
return [...list2].sort(
|
|
17651
|
+
(a, b) => r(a.severity, SEVERITY_RANK) - r(b.severity, SEVERITY_RANK) || r(a.type, TYPE_RANK) - r(b.type, TYPE_RANK)
|
|
17652
|
+
);
|
|
17653
|
+
}
|
|
17654
|
+
function inlineMarkdown(text) {
|
|
17655
|
+
return esc(text).replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>").replace(/\n+/g, "<br>");
|
|
17656
|
+
}
|
|
17657
|
+
function persistsLabel(i) {
|
|
17658
|
+
const qualifiers = [
|
|
17659
|
+
severityShift(i.severity_before, i.severity_now),
|
|
17660
|
+
i.fixed_before ? "fix did not hold" : ""
|
|
17661
|
+
].filter(Boolean);
|
|
17662
|
+
return qualifiers.length > 0 ? `Persists (${qualifiers.join("; ")})` : "Persists";
|
|
17663
|
+
}
|
|
17664
|
+
function renderChecks2(fixes, directions) {
|
|
17665
|
+
const items = fixes.map(
|
|
17666
|
+
(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>`
|
|
17667
|
+
).join("");
|
|
17668
|
+
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>` : "";
|
|
17669
|
+
return `
|
|
17670
|
+
<section>
|
|
17671
|
+
<h2>What this run checks</h2>
|
|
17672
|
+
<div class="card">
|
|
17673
|
+
${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>` : ""}
|
|
17674
|
+
${landed}
|
|
17675
|
+
</div>
|
|
17676
|
+
</section>`;
|
|
17677
|
+
}
|
|
17678
|
+
function renderSinceTable(v) {
|
|
17679
|
+
const steps = (ids) => ids && ids.length > 0 ? ` (step${ids.length === 1 ? "" : "s"} ${ids.map((id) => /\d+/.exec(id)?.[0] ?? id).join(", ")})` : "";
|
|
17680
|
+
const td = 'style="padding:6px 8px;border-top:1px solid #E5E7EB;vertical-align:top"';
|
|
17681
|
+
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>`;
|
|
17682
|
+
const rows = [
|
|
17683
|
+
...v.resolved.map(
|
|
17684
|
+
(i) => row(i, "Resolved", `${i.note ?? ""}${steps(i.step_ids)}`)
|
|
17685
|
+
),
|
|
17686
|
+
...v.persists.map(
|
|
17687
|
+
(i) => row(i, persistsLabel(i), `${i.note ?? ""}${steps(i.step_ids)}`)
|
|
17688
|
+
),
|
|
17689
|
+
...(v.notObserved ?? []).map(
|
|
17690
|
+
(i) => row(
|
|
17691
|
+
i,
|
|
17692
|
+
"Not seen",
|
|
17693
|
+
`The walk did not pass this way${i.fixed_before ? " (fixed, awaiting a re-check)" : ""}. Not evidence of a fix.`
|
|
17694
|
+
)
|
|
17695
|
+
)
|
|
17696
|
+
];
|
|
17697
|
+
if (rows.length === 0) return "";
|
|
17698
|
+
return `
|
|
17699
|
+
<section>
|
|
17700
|
+
<h2>Since the last run</h2>
|
|
17701
|
+
<div class="card">
|
|
17702
|
+
<table style="width:100%;border-collapse:collapse;font-size:13px;line-height:1.5">
|
|
17703
|
+
<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>
|
|
17704
|
+
<tbody>${rows.join("")}</tbody>
|
|
17705
|
+
</table>
|
|
17706
|
+
</div>
|
|
17707
|
+
</section>`;
|
|
17708
|
+
}
|
|
17348
17709
|
function generateHtmlReport(data) {
|
|
17349
17710
|
const {
|
|
17350
17711
|
scenarioName,
|
|
@@ -17353,6 +17714,7 @@ function generateHtmlReport(data) {
|
|
|
17353
17714
|
steps,
|
|
17354
17715
|
findings: findings2,
|
|
17355
17716
|
verdicts,
|
|
17717
|
+
overall,
|
|
17356
17718
|
verification,
|
|
17357
17719
|
whatWorked,
|
|
17358
17720
|
openQuestions,
|
|
@@ -17363,16 +17725,17 @@ function generateHtmlReport(data) {
|
|
|
17363
17725
|
runNumber
|
|
17364
17726
|
} = data;
|
|
17365
17727
|
const dateStr = formatDate(completedAt ?? startedAt);
|
|
17366
|
-
const
|
|
17367
|
-
|
|
17728
|
+
const problems = sortProblems(findings2.filter((f) => f.type !== "helps"));
|
|
17729
|
+
const strengths = findings2.filter((f) => f.type === "helps");
|
|
17730
|
+
const criticalCount = problems.filter(
|
|
17731
|
+
(f) => f.severity === "critical"
|
|
17368
17732
|
).length;
|
|
17369
|
-
const majorCount =
|
|
17370
|
-
|
|
17733
|
+
const majorCount = problems.filter((f) => f.severity === "major").length;
|
|
17734
|
+
const minorCount = problems.filter(
|
|
17735
|
+
(f) => f.severity !== "critical" && f.severity !== "major"
|
|
17371
17736
|
).length;
|
|
17372
|
-
const
|
|
17373
|
-
|
|
17374
|
-
).length;
|
|
17375
|
-
const helpsCount = findings2.filter((f) => f.type === "helps").length;
|
|
17737
|
+
const helpsCount = strengths.length;
|
|
17738
|
+
const newProblems = verification ? verification.newFindings.filter((n) => n.type !== "helps").length : 0;
|
|
17376
17739
|
return `<!DOCTYPE html>
|
|
17377
17740
|
<html lang="en">
|
|
17378
17741
|
<head>
|
|
@@ -17847,16 +18210,27 @@ function generateHtmlReport(data) {
|
|
|
17847
18210
|
<div class="verification-notice">
|
|
17848
18211
|
Since last run: <strong>${verification.resolved.length} resolved</strong> \xB7
|
|
17849
18212
|
<strong>${verification.persists.length} persists</strong> \xB7
|
|
17850
|
-
<strong>${
|
|
18213
|
+
<strong>${newProblems} new</strong>
|
|
17851
18214
|
</div>` : ""}
|
|
17852
18215
|
</div>
|
|
17853
18216
|
|
|
17854
|
-
|
|
17855
|
-
|
|
18217
|
+
${data.checks ? renderChecks2(data.checks.fixes, data.checks.directions) : ""}
|
|
18218
|
+
|
|
18219
|
+
<!-- The Human Test: the run's outcome, first -->
|
|
18220
|
+
${overall || verdicts && verdicts.length > 0 ? `
|
|
17856
18221
|
<section>
|
|
17857
|
-
<h2>The Human Test
|
|
18222
|
+
<h2>The Human Test</h2>
|
|
17858
18223
|
<div class="card">
|
|
17859
|
-
${
|
|
18224
|
+
${overall ? `
|
|
18225
|
+
<div class="verdict-item">
|
|
18226
|
+
<div class="verdict-header">
|
|
18227
|
+
<span style="font-weight:600;font-size:15px">${esc(overall.question)}</span>
|
|
18228
|
+
<span class="badge badge-${esc(overall.label)}">${esc(overall.label)}</span>
|
|
18229
|
+
</div>
|
|
18230
|
+
${overall.answer ? `<div class="prose-voice" style="margin-top:0.35rem;font-weight:500">${esc(overall.answer)}</div>` : ""}
|
|
18231
|
+
${overall.reasoning ? `<div class="prose-voice" style="margin-top:0.35rem">${esc(overall.reasoning)}</div>` : ""}
|
|
18232
|
+
</div>` : ""}
|
|
18233
|
+
${(verdicts ?? []).map(
|
|
17860
18234
|
(v) => `
|
|
17861
18235
|
<div class="verdict-item">
|
|
17862
18236
|
<div class="verdict-header">
|
|
@@ -17871,14 +18245,46 @@ function generateHtmlReport(data) {
|
|
|
17871
18245
|
</section>
|
|
17872
18246
|
` : ""}
|
|
17873
18247
|
|
|
18248
|
+
${verification ? renderSinceTable(verification) : ""}
|
|
18249
|
+
|
|
18250
|
+
<!-- Findings, worst first -->
|
|
18251
|
+
${problems.length > 0 ? `
|
|
18252
|
+
<section>
|
|
18253
|
+
<h2>Findings (${problems.length})</h2>
|
|
18254
|
+
${problems.map(
|
|
18255
|
+
(f, i) => `
|
|
18256
|
+
<div class="finding-item">
|
|
18257
|
+
<div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:0.35rem 0.75rem">
|
|
18258
|
+
<span class="finding-title">${i + 1}. ${esc(f.title)}${f.isNew ? ' <span class="badge">new</span>' : f.persists ? ' <span class="badge">persists</span>' : ""}</span>
|
|
18259
|
+
<span class="badge badge-${esc(f.severity ?? f.type)}">${esc(f.severity ?? f.type)}</span>
|
|
18260
|
+
</div>
|
|
18261
|
+
<div class="finding-meta">${esc([f.type, f.where].filter(Boolean).join(" \xB7 "))}</div>
|
|
18262
|
+
${f.uiFact ? `<div class="finding-meta">${esc(f.uiFact)}</div>` : ""}
|
|
18263
|
+
${f.consequence ? `<blockquote class="finding-meta prose-voice">${esc(f.consequence)}${persona?.name ? ` \u2014 ${esc(persona.name)}` : ""}</blockquote>` : ""}
|
|
18264
|
+
${f.recommendation ? `<div class="finding-meta finding-rec"><strong>Recommendation:</strong> ${inlineMarkdown(f.recommendation)}</div>` : ""}
|
|
18265
|
+
</div>
|
|
18266
|
+
`
|
|
18267
|
+
).join("")}
|
|
18268
|
+
</section>
|
|
18269
|
+
` : ""}
|
|
18270
|
+
|
|
17874
18271
|
<!-- What Worked & Open Questions -->
|
|
17875
|
-
${whatWorked && whatWorked.length > 0 || openQuestions && openQuestions.length > 0 ? `
|
|
18272
|
+
${strengths.length > 0 || whatWorked && whatWorked.length > 0 || openQuestions && openQuestions.length > 0 || data.setupNotes && data.setupNotes.length > 0 ? `
|
|
17876
18273
|
<section style="display:flex;gap:1.25rem;flex-wrap:wrap">
|
|
17877
|
-
${whatWorked && whatWorked.length > 0 ? `
|
|
18274
|
+
${strengths.length > 0 || whatWorked && whatWorked.length > 0 ? `
|
|
17878
18275
|
<div class="card" style="flex:1;min-width:260px">
|
|
17879
18276
|
<div class="card-label">What Worked</div>
|
|
17880
18277
|
<ul style="padding-left:1.25rem;font-size:13px;line-height:1.6">
|
|
17881
|
-
${
|
|
18278
|
+
${strengths.map((f) => `<li style="margin-bottom:0.25rem"><strong>${esc(f.title)}.</strong> ${esc(f.uiFact)}</li>`).join("")}
|
|
18279
|
+
${(whatWorked ?? []).map((w) => `<li style="margin-bottom:0.25rem">${esc(w)}</li>`).join("")}
|
|
18280
|
+
</ul>
|
|
18281
|
+
</div>
|
|
18282
|
+
` : ""}
|
|
18283
|
+
${data.setupNotes && data.setupNotes.length > 0 ? `
|
|
18284
|
+
<div class="card" style="flex:1;min-width:260px">
|
|
18285
|
+
<div class="card-label">Test Setup Notes</div>
|
|
18286
|
+
<ul style="padding-left:1.25rem;font-size:13px;line-height:1.6">
|
|
18287
|
+
${data.setupNotes.map((n) => `<li style="margin-bottom:0.25rem">${esc(n)}</li>`).join("")}
|
|
17882
18288
|
</ul>
|
|
17883
18289
|
</div>
|
|
17884
18290
|
` : ""}
|
|
@@ -17893,26 +18299,6 @@ function generateHtmlReport(data) {
|
|
|
17893
18299
|
</section>
|
|
17894
18300
|
` : ""}
|
|
17895
18301
|
|
|
17896
|
-
<!-- Findings Overview -->
|
|
17897
|
-
${findings2.length > 0 ? `
|
|
17898
|
-
<section>
|
|
17899
|
-
<h2>Findings (${findings2.length})</h2>
|
|
17900
|
-
${findings2.map(
|
|
17901
|
-
(f) => `
|
|
17902
|
-
<div class="finding-item">
|
|
17903
|
-
<div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:0.35rem 0.75rem">
|
|
17904
|
-
<span class="finding-title">${esc(f.title)}</span>
|
|
17905
|
-
<span class="badge badge-${esc(f.severity ?? f.type)}">${esc(f.severity ?? f.type)}</span>
|
|
17906
|
-
</div>
|
|
17907
|
-
${f.uiFact ? `<div class="finding-meta"><strong>Observed:</strong> ${esc(f.uiFact)}</div>` : ""}
|
|
17908
|
-
${f.consequence ? `<div class="finding-meta"><strong>Consequence:</strong> ${esc(f.consequence)}</div>` : ""}
|
|
17909
|
-
${f.recommendation ? `<div class="finding-meta finding-rec"><strong>Recommendation:</strong> ${esc(f.recommendation)}</div>` : ""}
|
|
17910
|
-
</div>
|
|
17911
|
-
`
|
|
17912
|
-
).join("")}
|
|
17913
|
-
</section>
|
|
17914
|
-
` : ""}
|
|
17915
|
-
|
|
17916
18302
|
<!-- Step Timeline with Connected Gutter -->
|
|
17917
18303
|
${steps.length > 0 ? `
|
|
17918
18304
|
<section>
|
|
@@ -17980,7 +18366,6 @@ async function buildReportDataFromRun(projectRoot, runId) {
|
|
|
17980
18366
|
scenarioSlug
|
|
17981
18367
|
);
|
|
17982
18368
|
if (record) {
|
|
17983
|
-
scenarioName = record.frontmatter.slug || scenarioSlug;
|
|
17984
18369
|
scenarioNarrative = record.body?.trim() || null;
|
|
17985
18370
|
}
|
|
17986
18371
|
} catch {
|
|
@@ -18036,47 +18421,82 @@ async function buildReportDataFromRun(projectRoot, runId) {
|
|
|
18036
18421
|
screenshotUrl
|
|
18037
18422
|
});
|
|
18038
18423
|
}
|
|
18424
|
+
const names2 = canonical?.scenario;
|
|
18425
|
+
if (names2?.outline_title) {
|
|
18426
|
+
scenarioName = `${names2.persona_name ?? personaData?.name ?? names2.persona_slug} \xB7 ${names2.outline_title}`;
|
|
18427
|
+
}
|
|
18428
|
+
const verification = canonical?.verification;
|
|
18429
|
+
const newIds = new Set((verification?.new_findings ?? []).map((n) => n.id));
|
|
18430
|
+
const persistingIds = new Set(
|
|
18431
|
+
(verification?.persists ?? []).map(
|
|
18432
|
+
(p) => p.canonical_id
|
|
18433
|
+
)
|
|
18434
|
+
);
|
|
18039
18435
|
const findings2 = (canonical?.findings ?? []).map((f) => {
|
|
18040
|
-
const
|
|
18041
|
-
|
|
18436
|
+
const step = rawSteps.find((st) => st.id === f.linked_step_ids[0]);
|
|
18437
|
+
let page = null;
|
|
18438
|
+
const url = f.page_url ?? step?.url;
|
|
18439
|
+
if (url) {
|
|
18440
|
+
try {
|
|
18441
|
+
page = new URL(url).pathname;
|
|
18442
|
+
} catch {
|
|
18443
|
+
page = url;
|
|
18444
|
+
}
|
|
18445
|
+
}
|
|
18042
18446
|
return {
|
|
18043
18447
|
id: f.id,
|
|
18044
|
-
title,
|
|
18448
|
+
title: f.title?.trim() || firstSentenceOf(f.ui_fact) || f.id,
|
|
18045
18449
|
type: f.type,
|
|
18046
18450
|
severity: f.severity,
|
|
18047
18451
|
uiFact: f.ui_fact,
|
|
18048
18452
|
consequence: f.consequence,
|
|
18049
|
-
recommendation: f.recommendation
|
|
18453
|
+
recommendation: f.recommendation,
|
|
18454
|
+
where: [page, step ? `step ${step.step_number}` : null].filter(Boolean).join(" \xB7 ") || null,
|
|
18455
|
+
isNew: newIds.has(f.id),
|
|
18456
|
+
persists: Boolean(f.canonical_id && persistingIds.has(f.canonical_id))
|
|
18050
18457
|
};
|
|
18051
18458
|
});
|
|
18052
18459
|
const verdicts = (canonical?.human_tests ?? []).map(
|
|
18053
18460
|
(ht) => ({
|
|
18054
|
-
question: ht.question,
|
|
18461
|
+
question: stripAnswerHint(ht.question),
|
|
18055
18462
|
rating: ht.verdict || "pending",
|
|
18056
18463
|
reasoning: typeof ht.reasoning === "string" ? ht.reasoning : void 0
|
|
18057
18464
|
})
|
|
18058
18465
|
);
|
|
18059
|
-
const verification = canonical?.verification;
|
|
18060
18466
|
const verificationData = verification ? {
|
|
18061
18467
|
resolved: verification.resolved ?? [],
|
|
18062
18468
|
persists: verification.persists ?? [],
|
|
18469
|
+
notObserved: verification.not_observed ?? [],
|
|
18063
18470
|
newFindings: verification.new_findings ?? []
|
|
18064
18471
|
} : null;
|
|
18472
|
+
const verdict = runVerdict(canonical);
|
|
18473
|
+
const overall = canonical?.human_test_overall ? {
|
|
18474
|
+
question: canonical.human_test_overall.question,
|
|
18475
|
+
label: verdict ?? "pending",
|
|
18476
|
+
answer: canonical.human_test_overall.answer,
|
|
18477
|
+
reasoning: canonical.human_test_overall.reasoning
|
|
18478
|
+
} : null;
|
|
18065
18479
|
return {
|
|
18066
18480
|
scenarioName,
|
|
18067
18481
|
scenarioNarrative,
|
|
18068
18482
|
persona: personaData,
|
|
18069
18483
|
walkthroughId: runId,
|
|
18070
|
-
runNumber: manifest.run_number ?? null,
|
|
18484
|
+
runNumber: manifest.run_number ?? canonical?.run_number ?? null,
|
|
18071
18485
|
startedAt: manifest.started_at ?? canonical?.provenance?.started_at ?? null,
|
|
18072
18486
|
completedAt: manifest.completed_at ?? canonical?.provenance?.completed_at ?? null,
|
|
18073
18487
|
targetUrl: manifest.target_url ?? canonical?.target?.url ?? null,
|
|
18074
18488
|
steps,
|
|
18075
18489
|
findings: findings2,
|
|
18076
18490
|
verdicts,
|
|
18491
|
+
overall,
|
|
18077
18492
|
verification: verificationData,
|
|
18078
18493
|
whatWorked: canonical?.what_worked,
|
|
18079
|
-
openQuestions: canonical?.open_questions
|
|
18494
|
+
openQuestions: canonical?.open_questions,
|
|
18495
|
+
setupNotes: canonical?.setup_notes,
|
|
18496
|
+
checks: (() => {
|
|
18497
|
+
const raw = canonical?.checks;
|
|
18498
|
+
return canonical && raw ? { fixes: checkOutcomes(canonical), directions: raw.directions } : null;
|
|
18499
|
+
})()
|
|
18080
18500
|
};
|
|
18081
18501
|
}
|
|
18082
18502
|
async function createLocalHtmlReport(projectRoot, runId, destination) {
|
|
@@ -18086,11 +18506,19 @@ async function createLocalHtmlReport(projectRoot, runId, destination) {
|
|
|
18086
18506
|
await atomicWriteFile(targetPath, html);
|
|
18087
18507
|
return { filePath: targetPath, html };
|
|
18088
18508
|
}
|
|
18509
|
+
var SEVERITY_RANK, TYPE_RANK, CHECK_LABEL;
|
|
18089
18510
|
var init_html_report = __esm({
|
|
18090
18511
|
"src/ui/html-report.ts"() {
|
|
18091
18512
|
"use strict";
|
|
18092
18513
|
init_dist2();
|
|
18093
18514
|
init_evaluate();
|
|
18515
|
+
SEVERITY_RANK = ["critical", "major", "minor", "observation"];
|
|
18516
|
+
TYPE_RANK = ["incorrect", "hinders", "missing", "irrelevant"];
|
|
18517
|
+
CHECK_LABEL = {
|
|
18518
|
+
fixed: "Confirmed fixed",
|
|
18519
|
+
came_back: "Came back",
|
|
18520
|
+
unconfirmed: "Not confirmed"
|
|
18521
|
+
};
|
|
18094
18522
|
}
|
|
18095
18523
|
});
|
|
18096
18524
|
|
|
@@ -18099,6 +18527,7 @@ var report_exports = {};
|
|
|
18099
18527
|
__export(report_exports, {
|
|
18100
18528
|
report: () => report
|
|
18101
18529
|
});
|
|
18530
|
+
import path26 from "path";
|
|
18102
18531
|
import chalk15 from "chalk";
|
|
18103
18532
|
async function report(scenario, opts) {
|
|
18104
18533
|
const ctx = await resolveProject(opts);
|
|
@@ -18219,6 +18648,9 @@ function jsonRows(projectRoot, runs) {
|
|
|
18219
18648
|
human_tests: r.canonical?.human_tests ?? [],
|
|
18220
18649
|
human_test_overall: r.canonical?.human_test_overall,
|
|
18221
18650
|
summary: await buildRunView(projectRoot, r),
|
|
18651
|
+
// Absolute, so a terminal can open them (relative paths are not links).
|
|
18652
|
+
run_dir: runDir(projectRoot, r.runId),
|
|
18653
|
+
report_path: path26.join(runDir(projectRoot, r.runId), "report.md"),
|
|
18222
18654
|
...cloudUrl ? { cloud_url: cloudUrl } : {}
|
|
18223
18655
|
};
|
|
18224
18656
|
})
|
|
@@ -18243,7 +18675,7 @@ __export(open_exports, {
|
|
|
18243
18675
|
openDashboard: () => openDashboard
|
|
18244
18676
|
});
|
|
18245
18677
|
import { existsSync as existsSync8, promises as fs32 } from "fs";
|
|
18246
|
-
import
|
|
18678
|
+
import path27 from "path";
|
|
18247
18679
|
import { isCancel as isCancel3, select as select3 } from "@clack/prompts";
|
|
18248
18680
|
import chalk16 from "chalk";
|
|
18249
18681
|
async function openDashboard(runId, opts) {
|
|
@@ -18339,7 +18771,7 @@ async function openLocalReport(projectRoot, runId, opts) {
|
|
|
18339
18771
|
for (const rid of [...runs].reverse()) {
|
|
18340
18772
|
try {
|
|
18341
18773
|
const manifestRaw = await fs32.readFile(
|
|
18342
|
-
|
|
18774
|
+
path27.join(runDir(projectRoot, rid), "walkthrough.json"),
|
|
18343
18775
|
"utf8"
|
|
18344
18776
|
);
|
|
18345
18777
|
const manifest = JSON.parse(manifestRaw);
|
|
@@ -20114,7 +20546,7 @@ var init_status2 = __esm({
|
|
|
20114
20546
|
|
|
20115
20547
|
// src/commands/browse-actions.ts
|
|
20116
20548
|
import { existsSync as existsSync11 } from "fs";
|
|
20117
|
-
import
|
|
20549
|
+
import path28 from "path";
|
|
20118
20550
|
function byRole(page, role, name, opts) {
|
|
20119
20551
|
const loc = page.getByRole(role, { name, exact: false });
|
|
20120
20552
|
return opts.nth !== void 0 ? loc.nth(Number(opts.nth)) : loc.first();
|
|
@@ -20305,7 +20737,7 @@ async function fillIndexedElement(page, index, text, submit) {
|
|
|
20305
20737
|
await fillOrSelect(loc.first(), text, submit);
|
|
20306
20738
|
}
|
|
20307
20739
|
async function setFilesOnElement(el, filePath) {
|
|
20308
|
-
const resolved =
|
|
20740
|
+
const resolved = path28.resolve(filePath);
|
|
20309
20741
|
if (!existsSync11(resolved)) {
|
|
20310
20742
|
throw new Error(`Upload file does not exist: "${resolved}"`);
|
|
20311
20743
|
}
|
|
@@ -20645,7 +21077,7 @@ var init_browse_driver = __esm({
|
|
|
20645
21077
|
|
|
20646
21078
|
// src/commands/step-numbering.ts
|
|
20647
21079
|
import { open, readdir as readdir2, unlink } from "fs/promises";
|
|
20648
|
-
import
|
|
21080
|
+
import path29 from "path";
|
|
20649
21081
|
function stepFileName(stepNumber2) {
|
|
20650
21082
|
return `${String(stepNumber2).padStart(3, "0")}.json`;
|
|
20651
21083
|
}
|
|
@@ -20667,7 +21099,7 @@ async function reserveStepFile(stepsDir, limit) {
|
|
|
20667
21099
|
if (candidate > limit.maxSteps) {
|
|
20668
21100
|
throw new Error(stepLimitMessage(limit.maxSteps, limit.runId));
|
|
20669
21101
|
}
|
|
20670
|
-
const stepPath =
|
|
21102
|
+
const stepPath = path29.join(stepsDir, stepFileName(candidate));
|
|
20671
21103
|
try {
|
|
20672
21104
|
const handle = await open(stepPath, "wx");
|
|
20673
21105
|
await handle.close();
|
|
@@ -20699,7 +21131,7 @@ __export(step_exports, {
|
|
|
20699
21131
|
stepCommand: () => stepCommand
|
|
20700
21132
|
});
|
|
20701
21133
|
import { copyFile, mkdir as mkdir5, stat as stat2 } from "fs/promises";
|
|
20702
|
-
import
|
|
21134
|
+
import path30 from "path";
|
|
20703
21135
|
import chalk22 from "chalk";
|
|
20704
21136
|
function parseMaxSteps(val) {
|
|
20705
21137
|
if (typeof val === "number" && Number.isFinite(val) && val > 0) return val;
|
|
@@ -20718,8 +21150,8 @@ async function recordStep(input) {
|
|
|
20718
21150
|
if (!runDirPath) {
|
|
20719
21151
|
throw new Error(`Run directory not found for run "${input.runId}"`);
|
|
20720
21152
|
}
|
|
20721
|
-
const stepsDir =
|
|
20722
|
-
const screenshotsDir =
|
|
21153
|
+
const stepsDir = path30.join(runDirPath, "steps");
|
|
21154
|
+
const screenshotsDir = path30.join(runDirPath, "screenshots");
|
|
20723
21155
|
await mkdir5(stepsDir, { recursive: true });
|
|
20724
21156
|
await mkdir5(screenshotsDir, { recursive: true });
|
|
20725
21157
|
const shotSource = input.shot ? await existingShot(input.shot) : null;
|
|
@@ -20733,11 +21165,11 @@ async function recordStep(input) {
|
|
|
20733
21165
|
let finalScreenshotPath = null;
|
|
20734
21166
|
if (shotSource) {
|
|
20735
21167
|
const destName = `step-${stepNumber2}.png`;
|
|
20736
|
-
finalScreenshotPath =
|
|
20737
|
-
if (
|
|
21168
|
+
finalScreenshotPath = path30.join(screenshotsDir, destName);
|
|
21169
|
+
if (path30.resolve(finalScreenshotPath) !== shotSource) {
|
|
20738
21170
|
await copyFile(shotSource, finalScreenshotPath);
|
|
20739
21171
|
}
|
|
20740
|
-
screenshotRef =
|
|
21172
|
+
screenshotRef = path30.join("screenshots", destName);
|
|
20741
21173
|
}
|
|
20742
21174
|
const parsedStep = runStepSchema.parse({
|
|
20743
21175
|
step_number: stepNumber2,
|
|
@@ -20776,7 +21208,7 @@ async function recordStep(input) {
|
|
|
20776
21208
|
}
|
|
20777
21209
|
}
|
|
20778
21210
|
async function existingShot(shot) {
|
|
20779
|
-
const resolved =
|
|
21211
|
+
const resolved = path30.resolve(shot);
|
|
20780
21212
|
const isFile = await stat2(resolved).then((s) => s.isFile()).catch(() => false);
|
|
20781
21213
|
if (!isFile) {
|
|
20782
21214
|
throw new Error(
|
|
@@ -20830,7 +21262,7 @@ async function stepCommand(runArg, opts) {
|
|
|
20830
21262
|
{
|
|
20831
21263
|
step_number: res.step.step_number,
|
|
20832
21264
|
cancelled: true,
|
|
20833
|
-
path:
|
|
21265
|
+
path: path30.relative(projectRoot, res.stepPath),
|
|
20834
21266
|
action: res.step.action,
|
|
20835
21267
|
note: res.step.note,
|
|
20836
21268
|
url: res.step.url,
|
|
@@ -20858,7 +21290,7 @@ async function stepCommand(runArg, opts) {
|
|
|
20858
21290
|
JSON.stringify(
|
|
20859
21291
|
{
|
|
20860
21292
|
step_number: res.step.step_number,
|
|
20861
|
-
path:
|
|
21293
|
+
path: path30.relative(projectRoot, res.stepPath),
|
|
20862
21294
|
action: res.step.action,
|
|
20863
21295
|
note: res.step.note,
|
|
20864
21296
|
url: res.step.url,
|
|
@@ -20887,7 +21319,7 @@ async function stepCommand(runArg, opts) {
|
|
|
20887
21319
|
console.log(` ${chalk22.dim("shot:")} ${res.step.screenshot}`);
|
|
20888
21320
|
}
|
|
20889
21321
|
console.log(
|
|
20890
|
-
` ${chalk22.dim("file:")} ${
|
|
21322
|
+
` ${chalk22.dim("file:")} ${path30.relative(projectRoot, res.stepPath)}`
|
|
20891
21323
|
);
|
|
20892
21324
|
console.log();
|
|
20893
21325
|
} else {
|
|
@@ -20948,21 +21380,21 @@ import {
|
|
|
20948
21380
|
} from "fs";
|
|
20949
21381
|
import { rm as rm2 } from "fs/promises";
|
|
20950
21382
|
import { homedir as homedir3 } from "os";
|
|
20951
|
-
import
|
|
21383
|
+
import path31 from "path";
|
|
20952
21384
|
import chalk23 from "chalk";
|
|
20953
21385
|
function stateDir() {
|
|
20954
|
-
return
|
|
20955
|
-
process.env.IAMU_HOME ??
|
|
21386
|
+
return path31.join(
|
|
21387
|
+
process.env.IAMU_HOME ?? path31.join(homedir3(), ".iamu"),
|
|
20956
21388
|
"browse"
|
|
20957
21389
|
);
|
|
20958
21390
|
}
|
|
20959
21391
|
function profileDir() {
|
|
20960
|
-
return
|
|
21392
|
+
return path31.join(stateDir(), "profile");
|
|
20961
21393
|
}
|
|
20962
21394
|
function readBrowseState() {
|
|
20963
21395
|
try {
|
|
20964
21396
|
return JSON.parse(
|
|
20965
|
-
readFileSync6(
|
|
21397
|
+
readFileSync6(path31.join(stateDir(), "state.json"), "utf8")
|
|
20966
21398
|
);
|
|
20967
21399
|
} catch {
|
|
20968
21400
|
return null;
|
|
@@ -20970,7 +21402,7 @@ function readBrowseState() {
|
|
|
20970
21402
|
}
|
|
20971
21403
|
function writeState(state) {
|
|
20972
21404
|
mkdirSync3(stateDir(), { recursive: true });
|
|
20973
|
-
writeFileSync3(
|
|
21405
|
+
writeFileSync3(path31.join(stateDir(), "state.json"), JSON.stringify(state));
|
|
20974
21406
|
}
|
|
20975
21407
|
function fail(message, opts) {
|
|
20976
21408
|
if (opts?.json) {
|
|
@@ -21034,7 +21466,7 @@ async function requestedViewport(opts) {
|
|
|
21034
21466
|
let manifest;
|
|
21035
21467
|
try {
|
|
21036
21468
|
manifest = JSON.parse(
|
|
21037
|
-
readFileSync6(
|
|
21469
|
+
readFileSync6(path31.join(dir, "walkthrough.json"), "utf8")
|
|
21038
21470
|
);
|
|
21039
21471
|
} catch {
|
|
21040
21472
|
fail(
|
|
@@ -21140,7 +21572,7 @@ async function stop(opts, driver) {
|
|
|
21140
21572
|
await driver.closeBrowser(state.port);
|
|
21141
21573
|
if (state.pid) await reapBrowser(state.pid, profileDir(), driver);
|
|
21142
21574
|
try {
|
|
21143
|
-
unlinkSync3(
|
|
21575
|
+
unlinkSync3(path31.join(stateDir(), "state.json"));
|
|
21144
21576
|
} catch {
|
|
21145
21577
|
}
|
|
21146
21578
|
report2(opts, { stopped: true }, [` ${chalk23.green("\u2713")} Browser stopped`]);
|
|
@@ -21149,8 +21581,8 @@ function computePageFingerprint(url, bodySnippet) {
|
|
|
21149
21581
|
return createHash4("sha256").update(`${url}|${bodySnippet.trim().replace(/\s+/g, " ")}`).digest("hex").slice(0, 16);
|
|
21150
21582
|
}
|
|
21151
21583
|
function checkAndRecordStagnation(projectRoot, runId, fingerprint, action) {
|
|
21152
|
-
const cacheDir2 =
|
|
21153
|
-
const cacheFile =
|
|
21584
|
+
const cacheDir2 = path31.join(projectRoot, "iamu", ".cache");
|
|
21585
|
+
const cacheFile = path31.join(cacheDir2, `stagnation-${runId}.json`);
|
|
21154
21586
|
let previous = null;
|
|
21155
21587
|
try {
|
|
21156
21588
|
if (existsSync12(cacheFile)) {
|
|
@@ -21320,8 +21752,8 @@ async function withPage(opts, driver, fn, target) {
|
|
|
21320
21752
|
let contentSnippet;
|
|
21321
21753
|
if (opts.shot) {
|
|
21322
21754
|
await waitForVisualStability(page);
|
|
21323
|
-
screenshot =
|
|
21324
|
-
mkdirSync3(
|
|
21755
|
+
screenshot = path31.resolve(opts.shot);
|
|
21756
|
+
mkdirSync3(path31.dirname(screenshot), { recursive: true });
|
|
21325
21757
|
await page.screenshot({ path: screenshot, fullPage: Boolean(opts.full) });
|
|
21326
21758
|
if (typeof page.evaluate === "function") {
|
|
21327
21759
|
contentSnippet = await page.evaluate(() => {
|
|
@@ -21486,7 +21918,7 @@ function printStep(opts, record, projectRoot, stagnationWarning, challengeWarnin
|
|
|
21486
21918
|
if (opts.json) {
|
|
21487
21919
|
const doc = {
|
|
21488
21920
|
step_number: step.step_number,
|
|
21489
|
-
path:
|
|
21921
|
+
path: path31.relative(projectRoot, record.stepPath),
|
|
21490
21922
|
action: step.action,
|
|
21491
21923
|
note: step.note,
|
|
21492
21924
|
url: step.url,
|
|
@@ -21589,10 +22021,10 @@ async function browseStep(args, opts, driver) {
|
|
|
21589
22021
|
const runDirPath = resolveRunDir(ctx.projectRoot, opts.run);
|
|
21590
22022
|
if (!runDirPath) return;
|
|
21591
22023
|
const maxSteps = parseMaxSteps(opts.maxSteps);
|
|
21592
|
-
if (await peekNextStepNumber(
|
|
22024
|
+
if (await peekNextStepNumber(path31.join(runDirPath, "steps")) > maxSteps) {
|
|
21593
22025
|
return fail(stepLimitMessage(maxSteps, opts.run), opts);
|
|
21594
22026
|
}
|
|
21595
|
-
const pendingShot =
|
|
22027
|
+
const pendingShot = path31.join(
|
|
21596
22028
|
runDirPath,
|
|
21597
22029
|
"screenshots",
|
|
21598
22030
|
`.pending-${randomUUID9()}.png`
|
|
@@ -22444,10 +22876,10 @@ __export(preflight_exports, {
|
|
|
22444
22876
|
});
|
|
22445
22877
|
import { spawnSync } from "child_process";
|
|
22446
22878
|
import { existsSync as existsSync14, promises as fs34 } from "fs";
|
|
22447
|
-
import
|
|
22879
|
+
import path32 from "path";
|
|
22448
22880
|
import chalk25 from "chalk";
|
|
22449
22881
|
async function detectPreflightCommand(targetDir) {
|
|
22450
|
-
const pkgPath =
|
|
22882
|
+
const pkgPath = path32.join(targetDir, "package.json");
|
|
22451
22883
|
if (existsSync14(pkgPath)) {
|
|
22452
22884
|
try {
|
|
22453
22885
|
const raw = await fs34.readFile(pkgPath, "utf8");
|
|
@@ -22462,12 +22894,12 @@ async function detectPreflightCommand(targetDir) {
|
|
|
22462
22894
|
} catch {
|
|
22463
22895
|
}
|
|
22464
22896
|
}
|
|
22465
|
-
const tsconfigPath =
|
|
22897
|
+
const tsconfigPath = path32.join(targetDir, "tsconfig.json");
|
|
22466
22898
|
if (existsSync14(tsconfigPath)) {
|
|
22467
22899
|
return { command: "npx", args: ["--no-install", "tsc", "--noEmit"] };
|
|
22468
22900
|
}
|
|
22469
|
-
const biomePath =
|
|
22470
|
-
const biomecPath =
|
|
22901
|
+
const biomePath = path32.join(targetDir, "biome.json");
|
|
22902
|
+
const biomecPath = path32.join(targetDir, "biome.jsonc");
|
|
22471
22903
|
if (existsSync14(biomePath) || existsSync14(biomecPath)) {
|
|
22472
22904
|
return { command: "npx", args: ["--no-install", "biome", "check"] };
|
|
22473
22905
|
}
|
|
@@ -22504,7 +22936,7 @@ async function runPreflightCheck(targetDir) {
|
|
|
22504
22936
|
};
|
|
22505
22937
|
}
|
|
22506
22938
|
async function preflight(opts = {}) {
|
|
22507
|
-
const targetDir = opts.cwd ?
|
|
22939
|
+
const targetDir = opts.cwd ? path32.resolve(opts.cwd) : process.cwd();
|
|
22508
22940
|
const result = await runPreflightCheck(targetDir);
|
|
22509
22941
|
if (result.ok) {
|
|
22510
22942
|
if (opts.json) {
|
|
@@ -22576,7 +23008,7 @@ import {
|
|
|
22576
23008
|
readlinkSync,
|
|
22577
23009
|
realpathSync as realpathSync2
|
|
22578
23010
|
} from "fs";
|
|
22579
|
-
import
|
|
23011
|
+
import path33 from "path";
|
|
22580
23012
|
function git(args, cwd) {
|
|
22581
23013
|
return execFileSync("git", args, {
|
|
22582
23014
|
cwd,
|
|
@@ -22629,8 +23061,8 @@ function detectWorktreeState(projectRoot, iamuDirName = IAMU_DIR) {
|
|
|
22629
23061
|
const top = git(["rev-parse", "--show-toplevel"], projectRoot).toString("utf8").trim();
|
|
22630
23062
|
if (!top) return void 0;
|
|
22631
23063
|
const realTop = realpathSync2(top);
|
|
22632
|
-
const realIamu =
|
|
22633
|
-
const iamuRel =
|
|
23064
|
+
const realIamu = path33.join(realpathSync2(projectRoot), iamuDirName);
|
|
23065
|
+
const iamuRel = path33.relative(realTop, realIamu).split(path33.sep).join("/");
|
|
22634
23066
|
const pathspec = iamuRel.startsWith("..") ? [":(top)"] : [":(top)", `:(top,exclude,literal)${iamuRel}`];
|
|
22635
23067
|
const status2 = git(
|
|
22636
23068
|
[
|
|
@@ -22660,7 +23092,7 @@ function detectWorktreeState(projectRoot, iamuDirName = IAMU_DIR) {
|
|
|
22660
23092
|
);
|
|
22661
23093
|
for (const file of sorted.slice(0, MAX_UNTRACKED_HASHED)) {
|
|
22662
23094
|
const rel = file.replace(/\/$/, "");
|
|
22663
|
-
hash.update(`${file}\0${hashFile(
|
|
23095
|
+
hash.update(`${file}\0${hashFile(path33.join(realTop, rel))}
|
|
22664
23096
|
`);
|
|
22665
23097
|
}
|
|
22666
23098
|
if (sorted.length > MAX_UNTRACKED_HASHED) {
|
|
@@ -23266,12 +23698,12 @@ __export(validate_exports, {
|
|
|
23266
23698
|
});
|
|
23267
23699
|
import { randomUUID as randomUUID10 } from "crypto";
|
|
23268
23700
|
import { promises as fs35 } from "fs";
|
|
23269
|
-
import
|
|
23701
|
+
import path34 from "path";
|
|
23270
23702
|
import chalk27 from "chalk";
|
|
23271
23703
|
function inferTypeFromPath(filePath, projectRoot) {
|
|
23272
|
-
const rel =
|
|
23273
|
-
if (rel.startsWith("..") ||
|
|
23274
|
-
const segments = rel.split(
|
|
23704
|
+
const rel = path34.relative(path34.join(projectRoot, "iamu"), filePath);
|
|
23705
|
+
if (rel.startsWith("..") || path34.isAbsolute(rel)) return null;
|
|
23706
|
+
const segments = rel.split(path34.sep);
|
|
23275
23707
|
if (segments.length < 2) return null;
|
|
23276
23708
|
return TYPE_BY_ALIAS.get(segments[0]) ?? null;
|
|
23277
23709
|
}
|
|
@@ -23301,7 +23733,7 @@ function validateFile(filePath, raw, projectRoot) {
|
|
|
23301
23733
|
);
|
|
23302
23734
|
return result;
|
|
23303
23735
|
}
|
|
23304
|
-
if (
|
|
23736
|
+
if (path34.basename(filePath) === "context.yml") {
|
|
23305
23737
|
applyZodValidation(contextFrontmatterSchema, frontmatter, result);
|
|
23306
23738
|
const warnings = [];
|
|
23307
23739
|
const hasNotes = Boolean(
|
|
@@ -23370,7 +23802,7 @@ async function buildRepairLookups(projectRoot) {
|
|
|
23370
23802
|
}
|
|
23371
23803
|
}
|
|
23372
23804
|
const runScenario = /* @__PURE__ */ new Map();
|
|
23373
|
-
const runsDir2 =
|
|
23805
|
+
const runsDir2 = path34.join(projectRoot, "iamu", ".runs");
|
|
23374
23806
|
let runIds = [];
|
|
23375
23807
|
try {
|
|
23376
23808
|
runIds = await fs35.readdir(runsDir2);
|
|
@@ -23380,7 +23812,7 @@ async function buildRepairLookups(projectRoot) {
|
|
|
23380
23812
|
try {
|
|
23381
23813
|
const manifest = JSON.parse(
|
|
23382
23814
|
await fs35.readFile(
|
|
23383
|
-
|
|
23815
|
+
path34.join(runsDir2, runId, "walkthrough.json"),
|
|
23384
23816
|
"utf8"
|
|
23385
23817
|
)
|
|
23386
23818
|
);
|
|
@@ -23411,7 +23843,7 @@ async function fixFindings(projectRoot, results, dryRun) {
|
|
|
23411
23843
|
parsed.frontmatter,
|
|
23412
23844
|
{
|
|
23413
23845
|
...lookups,
|
|
23414
|
-
fileSlug:
|
|
23846
|
+
fileSlug: path34.basename(result.path, ".md"),
|
|
23415
23847
|
newId: randomUUID10,
|
|
23416
23848
|
body: parsed.body
|
|
23417
23849
|
}
|
|
@@ -23450,7 +23882,7 @@ async function validate(filePath, opts) {
|
|
|
23450
23882
|
if (process.exitCode === 1) return;
|
|
23451
23883
|
const results = [];
|
|
23452
23884
|
if (filePath) {
|
|
23453
|
-
const abs =
|
|
23885
|
+
const abs = path34.isAbsolute(filePath) ? filePath : path34.resolve(process.cwd(), filePath);
|
|
23454
23886
|
try {
|
|
23455
23887
|
const raw = await fs35.readFile(abs, "utf8");
|
|
23456
23888
|
results.push(validateFile(abs, raw, projectRoot));
|
|
@@ -23466,7 +23898,7 @@ async function validate(filePath, opts) {
|
|
|
23466
23898
|
return;
|
|
23467
23899
|
}
|
|
23468
23900
|
} else if (opts.run) {
|
|
23469
|
-
const findingsDir =
|
|
23901
|
+
const findingsDir = path34.join(projectRoot, "iamu", ENTITY_DIRS.finding);
|
|
23470
23902
|
let names2 = [];
|
|
23471
23903
|
try {
|
|
23472
23904
|
names2 = await fs35.readdir(findingsDir);
|
|
@@ -23474,7 +23906,7 @@ async function validate(filePath, opts) {
|
|
|
23474
23906
|
}
|
|
23475
23907
|
for (const name of names2) {
|
|
23476
23908
|
if (!name.endsWith(".md")) continue;
|
|
23477
|
-
const abs =
|
|
23909
|
+
const abs = path34.join(findingsDir, name);
|
|
23478
23910
|
try {
|
|
23479
23911
|
const raw = await fs35.readFile(abs, "utf8");
|
|
23480
23912
|
const { frontmatter } = parseFrontmatter(raw);
|
|
@@ -23483,7 +23915,7 @@ async function validate(filePath, opts) {
|
|
|
23483
23915
|
}
|
|
23484
23916
|
} catch {
|
|
23485
23917
|
results.push({
|
|
23486
|
-
path:
|
|
23918
|
+
path: path34.relative(projectRoot, abs),
|
|
23487
23919
|
valid: false,
|
|
23488
23920
|
errors: ["Cannot read file or parse frontmatter"]
|
|
23489
23921
|
});
|
|
@@ -23516,7 +23948,7 @@ async function validate(filePath, opts) {
|
|
|
23516
23948
|
} else {
|
|
23517
23949
|
const typesToCheck = typeFilter ? ENTITY_TYPES.filter((t) => typeFilter.has(t)) : ENTITY_TYPES;
|
|
23518
23950
|
for (const type2 of typesToCheck) {
|
|
23519
|
-
const iamuTypeDir =
|
|
23951
|
+
const iamuTypeDir = path34.join(projectRoot, "iamu", ENTITY_DIRS[type2]);
|
|
23520
23952
|
let names2;
|
|
23521
23953
|
try {
|
|
23522
23954
|
names2 = await fs35.readdir(iamuTypeDir);
|
|
@@ -23525,20 +23957,20 @@ async function validate(filePath, opts) {
|
|
|
23525
23957
|
}
|
|
23526
23958
|
for (const name of names2) {
|
|
23527
23959
|
if (!name.endsWith(".md")) continue;
|
|
23528
|
-
const abs =
|
|
23960
|
+
const abs = path34.join(iamuTypeDir, name);
|
|
23529
23961
|
try {
|
|
23530
23962
|
const raw = await fs35.readFile(abs, "utf8");
|
|
23531
23963
|
results.push(validateFile(abs, raw, projectRoot));
|
|
23532
23964
|
} catch {
|
|
23533
23965
|
results.push({
|
|
23534
|
-
path:
|
|
23966
|
+
path: path34.relative(projectRoot, abs),
|
|
23535
23967
|
valid: false,
|
|
23536
23968
|
errors: ["Cannot read file"]
|
|
23537
23969
|
});
|
|
23538
23970
|
}
|
|
23539
23971
|
}
|
|
23540
23972
|
}
|
|
23541
|
-
const ctxPath =
|
|
23973
|
+
const ctxPath = path34.join(projectRoot, "iamu", "context.yml");
|
|
23542
23974
|
try {
|
|
23543
23975
|
const raw = await fs35.readFile(ctxPath, "utf8");
|
|
23544
23976
|
results.push(validateFile(ctxPath, raw, projectRoot));
|
|
@@ -23563,19 +23995,19 @@ async function validate(filePath, opts) {
|
|
|
23563
23995
|
valid,
|
|
23564
23996
|
errors: results.filter((r) => !r.valid).flatMap(
|
|
23565
23997
|
(r) => r.errors.map((e) => ({
|
|
23566
|
-
path:
|
|
23998
|
+
path: path34.relative(projectRoot, r.path),
|
|
23567
23999
|
message: e
|
|
23568
24000
|
}))
|
|
23569
24001
|
),
|
|
23570
24002
|
warnings: results.filter((r) => r.warnings && r.warnings.length > 0).flatMap(
|
|
23571
24003
|
(r) => (r.warnings ?? []).map((w) => ({
|
|
23572
|
-
path:
|
|
24004
|
+
path: path34.relative(projectRoot, r.path),
|
|
23573
24005
|
message: w
|
|
23574
24006
|
}))
|
|
23575
24007
|
),
|
|
23576
24008
|
...opts.fix ? {
|
|
23577
24009
|
repaired: repaired.map((r) => ({
|
|
23578
|
-
path:
|
|
24010
|
+
path: path34.relative(projectRoot, r.path),
|
|
23579
24011
|
changes: r.repairs,
|
|
23580
24012
|
valid: r.valid
|
|
23581
24013
|
}))
|
|
@@ -23597,7 +24029,7 @@ async function validate(filePath, opts) {
|
|
|
23597
24029
|
}
|
|
23598
24030
|
let hasErrors = false;
|
|
23599
24031
|
for (const r of results) {
|
|
23600
|
-
const relPath =
|
|
24032
|
+
const relPath = path34.relative(projectRoot, r.path);
|
|
23601
24033
|
if (r.valid) {
|
|
23602
24034
|
if (r.warnings && r.warnings.length > 0) {
|
|
23603
24035
|
console.log(` ${symbols.warning} ` + chalk27.yellow(relPath));
|
|
@@ -23998,7 +24430,7 @@ __export(set_entity_exports, {
|
|
|
23998
24430
|
setEntity: () => setEntity
|
|
23999
24431
|
});
|
|
24000
24432
|
import { promises as fs36 } from "fs";
|
|
24001
|
-
import
|
|
24433
|
+
import path35 from "path";
|
|
24002
24434
|
import chalk30 from "chalk";
|
|
24003
24435
|
import { ZodError as ZodError2 } from "zod";
|
|
24004
24436
|
function given(opts, key) {
|
|
@@ -24139,7 +24571,7 @@ async function setEntity(type2, slug, opts) {
|
|
|
24139
24571
|
}
|
|
24140
24572
|
raw = Buffer.concat(chunks).toString("utf8");
|
|
24141
24573
|
} else {
|
|
24142
|
-
raw = await fs36.readFile(
|
|
24574
|
+
raw = await fs36.readFile(path35.resolve(projectRoot, opts.file), "utf8");
|
|
24143
24575
|
}
|
|
24144
24576
|
} catch (err) {
|
|
24145
24577
|
commandError(
|
|
@@ -24567,7 +24999,7 @@ __export(edit_entity_exports, {
|
|
|
24567
24999
|
});
|
|
24568
25000
|
import { spawn as spawn2 } from "child_process";
|
|
24569
25001
|
import { promises as fs37 } from "fs";
|
|
24570
|
-
import
|
|
25002
|
+
import path36 from "path";
|
|
24571
25003
|
import chalk31 from "chalk";
|
|
24572
25004
|
async function editEntity(type2, slug, opts = {}) {
|
|
24573
25005
|
const ctx = await resolveProject(opts);
|
|
@@ -24581,19 +25013,19 @@ async function editEntity(type2, slug, opts = {}) {
|
|
|
24581
25013
|
return;
|
|
24582
25014
|
}
|
|
24583
25015
|
const dir = ENTITY_DIRS[type2];
|
|
24584
|
-
const filePath =
|
|
25016
|
+
const filePath = path36.join(projectRoot, "iamu", dir, `${slug}.md`);
|
|
24585
25017
|
try {
|
|
24586
25018
|
await fs37.access(filePath);
|
|
24587
25019
|
} catch {
|
|
24588
25020
|
commandError(
|
|
24589
|
-
`${type2} "${slug}" not found at ${
|
|
25021
|
+
`${type2} "${slug}" not found at ${path36.relative(projectRoot, filePath)}.`,
|
|
24590
25022
|
{ ...opts, code: "entity_not_found", fix: `iamu list ${type2}s` }
|
|
24591
25023
|
);
|
|
24592
25024
|
return;
|
|
24593
25025
|
}
|
|
24594
25026
|
if (!process.stdin.isTTY || process.env.CI) {
|
|
24595
25027
|
commandError(
|
|
24596
|
-
`Cannot open editor in a non-interactive environment. Edit the file directly: ${
|
|
25028
|
+
`Cannot open editor in a non-interactive environment. Edit the file directly: ${path36.relative(projectRoot, filePath)}`,
|
|
24597
25029
|
{
|
|
24598
25030
|
...opts,
|
|
24599
25031
|
code: "non_interactive_environment",
|
|
@@ -24668,7 +25100,7 @@ async function editEntity(type2, slug, opts = {}) {
|
|
|
24668
25100
|
console.log();
|
|
24669
25101
|
console.log(
|
|
24670
25102
|
chalk31.dim(
|
|
24671
|
-
` Fix the file and run: iamu validate ${
|
|
25103
|
+
` Fix the file and run: iamu validate ${path36.relative(projectRoot, filePath)}`
|
|
24672
25104
|
)
|
|
24673
25105
|
);
|
|
24674
25106
|
console.log();
|
|
@@ -24689,7 +25121,7 @@ async function editEntity(type2, slug, opts = {}) {
|
|
|
24689
25121
|
{
|
|
24690
25122
|
...opts,
|
|
24691
25123
|
code: "validation_error",
|
|
24692
|
-
fix: `iamu validate ${
|
|
25124
|
+
fix: `iamu validate ${path36.relative(projectRoot, filePath)}`
|
|
24693
25125
|
}
|
|
24694
25126
|
);
|
|
24695
25127
|
}
|
|
@@ -24826,7 +25258,7 @@ async function crawlFrom(page, opts, origin, takeBlocked) {
|
|
|
24826
25258
|
const byKey = /* @__PURE__ */ new Map();
|
|
24827
25259
|
const byPlaceView = /* @__PURE__ */ new Map();
|
|
24828
25260
|
const signatureOf = (s) => exploreSignature(s.headings, s.title);
|
|
24829
|
-
function addSurface(state, place, view,
|
|
25261
|
+
function addSurface(state, place, view, path44, depth) {
|
|
24830
25262
|
const key = exploreSurfaceKey(place, view);
|
|
24831
25263
|
const surface = {
|
|
24832
25264
|
key,
|
|
@@ -24838,7 +25270,7 @@ async function crawlFrom(page, opts, origin, takeBlocked) {
|
|
|
24838
25270
|
headings: state.headings,
|
|
24839
25271
|
fields: state.fields,
|
|
24840
25272
|
depth,
|
|
24841
|
-
path:
|
|
25273
|
+
path: path44,
|
|
24842
25274
|
actions: []
|
|
24843
25275
|
};
|
|
24844
25276
|
surfaces.push(surface);
|
|
@@ -24856,9 +25288,9 @@ ${surface.signature}`, key);
|
|
|
24856
25288
|
});
|
|
24857
25289
|
await settle(page, settleMs);
|
|
24858
25290
|
}
|
|
24859
|
-
async function replay(
|
|
24860
|
-
if (!("goto" in (
|
|
24861
|
-
for (const step of
|
|
25291
|
+
async function replay(path44) {
|
|
25292
|
+
if (!("goto" in (path44[0] ?? {}))) await goto(opts.startUrl);
|
|
25293
|
+
for (const step of path44) {
|
|
24862
25294
|
if ("goto" in step) {
|
|
24863
25295
|
await goto(step.goto);
|
|
24864
25296
|
} else {
|
|
@@ -24982,8 +25414,8 @@ ${sig}`, key);
|
|
|
24982
25414
|
if (stats.surfaces >= budget.max_surfaces) {
|
|
24983
25415
|
stats.truncated = true;
|
|
24984
25416
|
} else {
|
|
24985
|
-
const
|
|
24986
|
-
addSurface(after, place, view,
|
|
25417
|
+
const path44 = !sameDoc && view === null ? [{ goto: after.url }] : [...surface.path, ...via, click];
|
|
25418
|
+
addSurface(after, place, view, path44, surface.depth + 1);
|
|
24987
25419
|
}
|
|
24988
25420
|
}
|
|
24989
25421
|
}
|
|
@@ -25183,7 +25615,7 @@ __export(explore_exports, {
|
|
|
25183
25615
|
exploreList: () => exploreList,
|
|
25184
25616
|
exploreRun: () => exploreRun
|
|
25185
25617
|
});
|
|
25186
|
-
import
|
|
25618
|
+
import path37 from "path";
|
|
25187
25619
|
import chalk32 from "chalk";
|
|
25188
25620
|
function fail2(message, json2) {
|
|
25189
25621
|
if (json2) console.error(JSON.stringify({ error: message }));
|
|
@@ -25317,7 +25749,7 @@ async function exploreRun(url, opts) {
|
|
|
25317
25749
|
surfaces: result.surfaces
|
|
25318
25750
|
};
|
|
25319
25751
|
const file = await writeExploreMap(ctx.projectRoot, map2);
|
|
25320
|
-
const rel =
|
|
25752
|
+
const rel = path37.relative(process.cwd(), file);
|
|
25321
25753
|
if (opts.json) {
|
|
25322
25754
|
console.log(JSON.stringify({ id: map2.id, path: rel, stats: map2.stats }));
|
|
25323
25755
|
return;
|
|
@@ -25423,15 +25855,15 @@ async function exploreCoverageCommand(mapId, opts) {
|
|
|
25423
25855
|
" \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"
|
|
25424
25856
|
)
|
|
25425
25857
|
);
|
|
25426
|
-
for (const
|
|
25427
|
-
const title =
|
|
25428
|
-
const surfaces = `${
|
|
25858
|
+
for (const cap2 of capReport.capabilities) {
|
|
25859
|
+
const title = cap2.slug.padEnd(24);
|
|
25860
|
+
const surfaces = `${cap2.reached_surfaces}/${cap2.total_surfaces}`.padStart(
|
|
25429
25861
|
8
|
|
25430
25862
|
);
|
|
25431
|
-
const covStr = `${
|
|
25432
|
-
const scenarios = `${
|
|
25433
|
-
const untestedFlag =
|
|
25434
|
-
const covColored =
|
|
25863
|
+
const covStr = `${cap2.coverage_pct}%`.padStart(8);
|
|
25864
|
+
const scenarios = `${cap2.primary_scenarios} / ${cap2.secondary_scenarios}`;
|
|
25865
|
+
const untestedFlag = cap2.primary_scenarios === 0 && cap2.unreached_surfaces > 0 ? chalk32.yellow(" (untested)") : "";
|
|
25866
|
+
const covColored = cap2.coverage_pct === 100 ? chalk32.green(covStr) : cap2.coverage_pct === 0 ? chalk32.red(covStr) : chalk32.yellow(covStr);
|
|
25435
25867
|
console.log(
|
|
25436
25868
|
` ${title} ${surfaces} ${covColored} ${scenarios}${untestedFlag}`
|
|
25437
25869
|
);
|
|
@@ -25469,10 +25901,10 @@ async function exploreCoverageCommand(mapId, opts) {
|
|
|
25469
25901
|
const hasGaps = capReport.capabilities.some((c) => c.unreached_surfaces > 0) || capReport.unassigned_surfaces.some((s) => s.status !== "reached");
|
|
25470
25902
|
if (hasGaps) {
|
|
25471
25903
|
console.log(chalk32.red("\n UNREACHED SURFACES (Gaps)"));
|
|
25472
|
-
for (const
|
|
25473
|
-
const gaps =
|
|
25904
|
+
for (const cap2 of capReport.capabilities) {
|
|
25905
|
+
const gaps = cap2.surfaces.filter((s) => s.status !== "reached");
|
|
25474
25906
|
if (gaps.length === 0) continue;
|
|
25475
|
-
console.log(` ${chalk32.bold(
|
|
25907
|
+
console.log(` ${chalk32.bold(cap2.slug)}:`);
|
|
25476
25908
|
for (const s of gaps) {
|
|
25477
25909
|
console.log(
|
|
25478
25910
|
` ${chalk32.red("\u25CB")} ${s.key.padEnd(32)} ${s.label}${chalk32.dim(` (depth ${s.depth})`)}`
|
|
@@ -25493,10 +25925,10 @@ async function exploreCoverageCommand(mapId, opts) {
|
|
|
25493
25925
|
}
|
|
25494
25926
|
if (opts.all) {
|
|
25495
25927
|
console.log(chalk32.green("\n REACHED SURFACES"));
|
|
25496
|
-
for (const
|
|
25497
|
-
const reached =
|
|
25928
|
+
for (const cap2 of capReport.capabilities) {
|
|
25929
|
+
const reached = cap2.surfaces.filter((s) => s.status === "reached");
|
|
25498
25930
|
if (reached.length === 0) continue;
|
|
25499
|
-
console.log(` ${chalk32.bold(
|
|
25931
|
+
console.log(` ${chalk32.bold(cap2.slug)}:`);
|
|
25500
25932
|
for (const s of reached) {
|
|
25501
25933
|
console.log(
|
|
25502
25934
|
` ${chalk32.green("\u2713")} ${s.key.padEnd(32)} ${s.label}${chalk32.dim(` (depth ${s.depth})`)}`
|
|
@@ -25818,7 +26250,7 @@ __export(start_run_exports, {
|
|
|
25818
26250
|
startRun: () => startRun,
|
|
25819
26251
|
testabilityTarget: () => testabilityTarget2
|
|
25820
26252
|
});
|
|
25821
|
-
import
|
|
26253
|
+
import path38 from "path";
|
|
25822
26254
|
import chalk33 from "chalk";
|
|
25823
26255
|
async function testabilityTarget2(projectRoot) {
|
|
25824
26256
|
try {
|
|
@@ -26062,7 +26494,7 @@ async function startRun(scenarioSlugArg, opts) {
|
|
|
26062
26494
|
...viewport ? { viewport } : {}
|
|
26063
26495
|
};
|
|
26064
26496
|
await atomicWriteFile(
|
|
26065
|
-
|
|
26497
|
+
path38.join(run.absPath, "walkthrough.json"),
|
|
26066
26498
|
JSON.stringify(manifest, null, 2) + "\n"
|
|
26067
26499
|
);
|
|
26068
26500
|
const liveRes = await registerLiveRun({
|
|
@@ -26078,7 +26510,7 @@ async function startRun(scenarioSlugArg, opts) {
|
|
|
26078
26510
|
JSON.stringify(
|
|
26079
26511
|
{
|
|
26080
26512
|
run_id: run.runId,
|
|
26081
|
-
path:
|
|
26513
|
+
path: path38.relative(projectRoot, run.absPath),
|
|
26082
26514
|
scenario_slug: scenarioSlug,
|
|
26083
26515
|
target_url: start2.url,
|
|
26084
26516
|
target_reachable: targetReachable,
|
|
@@ -26132,7 +26564,7 @@ async function startRun(scenarioSlugArg, opts) {
|
|
|
26132
26564
|
);
|
|
26133
26565
|
}
|
|
26134
26566
|
console.log(
|
|
26135
|
-
` ${chalk33.dim("path:")} ${
|
|
26567
|
+
` ${chalk33.dim("path:")} ${path38.relative(projectRoot, run.absPath)}`
|
|
26136
26568
|
);
|
|
26137
26569
|
console.log();
|
|
26138
26570
|
console.log(` ${chalk33.bold("Next steps for blind execution:")}`);
|
|
@@ -26362,7 +26794,7 @@ function composeWalkerPrompt(p) {
|
|
|
26362
26794
|
`Screenshots to: ${rd}/screenshots/step-N.png \u2014 absolute paths.`,
|
|
26363
26795
|
`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`,
|
|
26364
26796
|
"`--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.",
|
|
26365
|
-
`Before you finish, record synthesis with: ${iamu} summary ${p.runId} --worked "..." --worked "..." --question "..."
|
|
26797
|
+
`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.`,
|
|
26366
26798
|
`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.`,
|
|
26367
26799
|
"Execute everything via CLI commands in the shell. Never invoke host Edit or Write tools on files in the workspace.",
|
|
26368
26800
|
"",
|
|
@@ -27012,7 +27444,7 @@ __export(new_finding_exports, {
|
|
|
27012
27444
|
});
|
|
27013
27445
|
import { randomUUID as randomUUID12 } from "crypto";
|
|
27014
27446
|
import { access, readFile as readFile7 } from "fs/promises";
|
|
27015
|
-
import
|
|
27447
|
+
import path39 from "path";
|
|
27016
27448
|
import chalk36 from "chalk";
|
|
27017
27449
|
async function newFinding(opts) {
|
|
27018
27450
|
const ctx = await resolveProject(opts);
|
|
@@ -27089,7 +27521,7 @@ async function newFinding(opts) {
|
|
|
27089
27521
|
}
|
|
27090
27522
|
try {
|
|
27091
27523
|
await access(
|
|
27092
|
-
|
|
27524
|
+
path39.join(runDirPath, "steps", `${String(n).padStart(3, "0")}.json`)
|
|
27093
27525
|
);
|
|
27094
27526
|
} catch {
|
|
27095
27527
|
commandError(
|
|
@@ -27108,7 +27540,7 @@ async function newFinding(opts) {
|
|
|
27108
27540
|
if (!scenarioSlug) {
|
|
27109
27541
|
try {
|
|
27110
27542
|
const manifestRaw = await readFile7(
|
|
27111
|
-
|
|
27543
|
+
path39.join(runDirPath, "walkthrough.json"),
|
|
27112
27544
|
"utf8"
|
|
27113
27545
|
);
|
|
27114
27546
|
const manifest = JSON.parse(manifestRaw);
|
|
@@ -27255,7 +27687,7 @@ __export(summary_exports, {
|
|
|
27255
27687
|
summaryCommand: () => summaryCommand
|
|
27256
27688
|
});
|
|
27257
27689
|
import { promises as fs38 } from "fs";
|
|
27258
|
-
import
|
|
27690
|
+
import path40 from "path";
|
|
27259
27691
|
import chalk37 from "chalk";
|
|
27260
27692
|
function blockedFrom(value) {
|
|
27261
27693
|
const reason = value && typeof value === "object" ? value.reason : value;
|
|
@@ -27278,12 +27710,18 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
27278
27710
|
if (!runDirPath) return;
|
|
27279
27711
|
let whatWorked = [];
|
|
27280
27712
|
let openQuestions = [];
|
|
27713
|
+
let setupNotes = [];
|
|
27281
27714
|
let model = opts.model?.trim() || void 0;
|
|
27282
|
-
const summaryPath =
|
|
27715
|
+
const summaryPath = path40.join(runDirPath, "summary.json");
|
|
27283
27716
|
let blocked;
|
|
27284
27717
|
try {
|
|
27285
27718
|
const prev = JSON.parse(await fs38.readFile(summaryPath, "utf8"));
|
|
27286
27719
|
blocked = blockedFrom(prev.blocked);
|
|
27720
|
+
if (Array.isArray(prev.setup_notes)) {
|
|
27721
|
+
setupNotes = prev.setup_notes.filter(
|
|
27722
|
+
(x) => typeof x === "string" && x.trim() !== ""
|
|
27723
|
+
);
|
|
27724
|
+
}
|
|
27287
27725
|
} catch {
|
|
27288
27726
|
}
|
|
27289
27727
|
if (opts.file) {
|
|
@@ -27292,7 +27730,7 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
27292
27730
|
if (opts.file === "-") {
|
|
27293
27731
|
raw = await readStdinText();
|
|
27294
27732
|
} else {
|
|
27295
|
-
raw = await fs38.readFile(
|
|
27733
|
+
raw = await fs38.readFile(path40.resolve(projectRoot, opts.file), "utf8");
|
|
27296
27734
|
}
|
|
27297
27735
|
const parsed = JSON.parse(raw);
|
|
27298
27736
|
if (Array.isArray(parsed.what_worked)) {
|
|
@@ -27305,6 +27743,11 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
27305
27743
|
(x) => typeof x === "string" && x.trim() !== ""
|
|
27306
27744
|
);
|
|
27307
27745
|
}
|
|
27746
|
+
if (Array.isArray(parsed.setup_notes)) {
|
|
27747
|
+
setupNotes = parsed.setup_notes.filter(
|
|
27748
|
+
(x) => typeof x === "string" && x.trim() !== ""
|
|
27749
|
+
);
|
|
27750
|
+
}
|
|
27308
27751
|
if (typeof parsed.model === "string" && parsed.model.trim()) {
|
|
27309
27752
|
model = parsed.model.trim();
|
|
27310
27753
|
}
|
|
@@ -27330,6 +27773,7 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
27330
27773
|
if (cliQuestions.length > 0) {
|
|
27331
27774
|
openQuestions = [...openQuestions, ...cliQuestions];
|
|
27332
27775
|
}
|
|
27776
|
+
setupNotes = [.../* @__PURE__ */ new Set([...setupNotes, ...toList(opts.setupNote)])];
|
|
27333
27777
|
if (opts.blocked === false) {
|
|
27334
27778
|
blocked = void 0;
|
|
27335
27779
|
} else {
|
|
@@ -27338,6 +27782,7 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
27338
27782
|
const data = {
|
|
27339
27783
|
what_worked: whatWorked,
|
|
27340
27784
|
open_questions: openQuestions,
|
|
27785
|
+
setup_notes: setupNotes,
|
|
27341
27786
|
...model ? { model } : {},
|
|
27342
27787
|
...blocked ? { blocked } : {}
|
|
27343
27788
|
};
|
|
@@ -27348,7 +27793,7 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
27348
27793
|
JSON.stringify(
|
|
27349
27794
|
{
|
|
27350
27795
|
run_id: runId,
|
|
27351
|
-
path:
|
|
27796
|
+
path: path40.relative(projectRoot, summaryPath),
|
|
27352
27797
|
summary: data
|
|
27353
27798
|
},
|
|
27354
27799
|
null,
|
|
@@ -27374,6 +27819,11 @@ async function summaryCommand(runIdArg, opts) {
|
|
|
27374
27819
|
` ${chalk37.dim("open questions:")} ${data.open_questions.length} item(s)`
|
|
27375
27820
|
);
|
|
27376
27821
|
}
|
|
27822
|
+
if (data.setup_notes.length > 0) {
|
|
27823
|
+
console.log(
|
|
27824
|
+
` ${chalk37.dim("setup notes:")} ${data.setup_notes.length} item(s)`
|
|
27825
|
+
);
|
|
27826
|
+
}
|
|
27377
27827
|
console.log();
|
|
27378
27828
|
}
|
|
27379
27829
|
var init_summary = __esm({
|
|
@@ -27408,7 +27858,7 @@ __export(complete_exports, {
|
|
|
27408
27858
|
validateCanonicalWalkthrough: () => validateCanonicalWalkthrough
|
|
27409
27859
|
});
|
|
27410
27860
|
import { promises as fs39 } from "fs";
|
|
27411
|
-
import
|
|
27861
|
+
import path41 from "path";
|
|
27412
27862
|
import chalk38 from "chalk";
|
|
27413
27863
|
async function complete(runIdArg, opts) {
|
|
27414
27864
|
const ctx = await resolveProject(opts);
|
|
@@ -27445,7 +27895,7 @@ async function complete(runIdArg, opts) {
|
|
|
27445
27895
|
let judgeReported;
|
|
27446
27896
|
try {
|
|
27447
27897
|
const verdictsRaw = await fs39.readFile(
|
|
27448
|
-
|
|
27898
|
+
path41.join(run.runDirPath, "verdicts.json"),
|
|
27449
27899
|
"utf8"
|
|
27450
27900
|
);
|
|
27451
27901
|
const parsed = JSON.parse(verdictsRaw);
|
|
@@ -27521,7 +27971,7 @@ async function loadRun(runId, opts) {
|
|
|
27521
27971
|
});
|
|
27522
27972
|
return null;
|
|
27523
27973
|
}
|
|
27524
|
-
const manifestPath =
|
|
27974
|
+
const manifestPath = path41.join(runDirPath, "walkthrough.json");
|
|
27525
27975
|
let manifest;
|
|
27526
27976
|
try {
|
|
27527
27977
|
manifest = JSON.parse(
|
|
@@ -27552,7 +28002,7 @@ async function reportAlreadyCompleted(run, opts) {
|
|
|
27552
28002
|
if (opts?.json) {
|
|
27553
28003
|
try {
|
|
27554
28004
|
const raw = await fs39.readFile(
|
|
27555
|
-
|
|
28005
|
+
path41.join(run.runDirPath, "canonical.json"),
|
|
27556
28006
|
"utf8"
|
|
27557
28007
|
);
|
|
27558
28008
|
console.log(raw.trim());
|
|
@@ -27688,15 +28138,11 @@ function printCompletionSummary(run, completedAt, assembled, extras, provenanceA
|
|
|
27688
28138
|
}
|
|
27689
28139
|
console.log();
|
|
27690
28140
|
console.log(
|
|
27691
|
-
` ${chalk38.dim("canonical:")} ${chalk38.dim(
|
|
27692
|
-
);
|
|
27693
|
-
console.log(
|
|
27694
|
-
` ${chalk38.dim("report:")} ${chalk38.dim(path40.relative(projectRoot, assembled.reportPath))}`
|
|
28141
|
+
` ${chalk38.dim("canonical:")} ${chalk38.dim(path41.relative(projectRoot, assembled.canonicalPath))}`
|
|
27695
28142
|
);
|
|
28143
|
+
console.log(` ${chalk38.dim("report:")} ${assembled.reportPath}`);
|
|
27696
28144
|
if (assembled.htmlReportPath) {
|
|
27697
|
-
console.log(
|
|
27698
|
-
` ${chalk38.dim("html:")} ${chalk38.dim(path40.relative(projectRoot, assembled.htmlReportPath))}`
|
|
27699
|
-
);
|
|
28145
|
+
console.log(` ${chalk38.dim("html:")} ${assembled.htmlReportPath}`);
|
|
27700
28146
|
}
|
|
27701
28147
|
if (extras.viewportWarning) {
|
|
27702
28148
|
console.log();
|
|
@@ -27765,7 +28211,7 @@ function screenshotFitsViewport(width, expected) {
|
|
|
27765
28211
|
}
|
|
27766
28212
|
async function screenshotSizeWarning(runDirPath, viewport) {
|
|
27767
28213
|
if (!viewport) return null;
|
|
27768
|
-
const dir =
|
|
28214
|
+
const dir = path41.join(runDirPath, "screenshots");
|
|
27769
28215
|
let first;
|
|
27770
28216
|
try {
|
|
27771
28217
|
first = (await fs39.readdir(dir)).filter((f) => f.toLowerCase().endsWith(".png")).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }))[0];
|
|
@@ -27775,7 +28221,7 @@ async function screenshotSizeWarning(runDirPath, viewport) {
|
|
|
27775
28221
|
if (!first) return null;
|
|
27776
28222
|
let size = null;
|
|
27777
28223
|
try {
|
|
27778
|
-
const handle = await fs39.open(
|
|
28224
|
+
const handle = await fs39.open(path41.join(dir, first), "r");
|
|
27779
28225
|
try {
|
|
27780
28226
|
const head = Buffer.alloc(24);
|
|
27781
28227
|
const { bytesRead } = await handle.read(head, 0, 24, 0);
|
|
@@ -27792,7 +28238,7 @@ async function screenshotSizeWarning(runDirPath, viewport) {
|
|
|
27792
28238
|
async function readWalkerSummary(runDirPath) {
|
|
27793
28239
|
try {
|
|
27794
28240
|
const data = JSON.parse(
|
|
27795
|
-
await fs39.readFile(
|
|
28241
|
+
await fs39.readFile(path41.join(runDirPath, "summary.json"), "utf8")
|
|
27796
28242
|
);
|
|
27797
28243
|
const strings = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && x.trim() !== "") : void 0;
|
|
27798
28244
|
const model = typeof data.model === "string" && data.model.trim() !== "" ? data.model.trim() : void 0;
|
|
@@ -27800,6 +28246,7 @@ async function readWalkerSummary(runDirPath) {
|
|
|
27800
28246
|
return {
|
|
27801
28247
|
what_worked: strings(data.what_worked),
|
|
27802
28248
|
open_questions: strings(data.open_questions),
|
|
28249
|
+
setup_notes: strings(data.setup_notes),
|
|
27803
28250
|
model,
|
|
27804
28251
|
...typeof reason === "string" && reason.trim() !== "" ? { blocked: { reason: reason.trim() } } : {}
|
|
27805
28252
|
};
|
|
@@ -27901,7 +28348,7 @@ function validateCanonicalWalkthrough(canonical) {
|
|
|
27901
28348
|
return canonicalWalkthroughSchema.parse(canonical);
|
|
27902
28349
|
}
|
|
27903
28350
|
async function persistCanonical(runDirPath, canonical) {
|
|
27904
|
-
const canonicalPath =
|
|
28351
|
+
const canonicalPath = path41.join(runDirPath, "canonical.json");
|
|
27905
28352
|
await atomicWriteFile(
|
|
27906
28353
|
canonicalPath,
|
|
27907
28354
|
`${JSON.stringify(canonical, null, 2)}
|
|
@@ -27910,7 +28357,7 @@ async function persistCanonical(runDirPath, canonical) {
|
|
|
27910
28357
|
return canonicalPath;
|
|
27911
28358
|
}
|
|
27912
28359
|
async function persistReport(runDirPath, canonical) {
|
|
27913
|
-
const reportPath =
|
|
28360
|
+
const reportPath = path41.join(runDirPath, "report.md");
|
|
27914
28361
|
await atomicWriteFile(reportPath, renderCanonicalMarkdown(canonical));
|
|
27915
28362
|
return reportPath;
|
|
27916
28363
|
}
|
|
@@ -28089,13 +28536,13 @@ __export(match_exports, {
|
|
|
28089
28536
|
matchRecord: () => matchRecord
|
|
28090
28537
|
});
|
|
28091
28538
|
import { promises as fs40 } from "fs";
|
|
28092
|
-
import
|
|
28539
|
+
import path42 from "path";
|
|
28093
28540
|
import chalk39 from "chalk";
|
|
28094
28541
|
async function completedManifest2(runDirPath, runId, opts) {
|
|
28095
28542
|
let manifest;
|
|
28096
28543
|
try {
|
|
28097
28544
|
manifest = JSON.parse(
|
|
28098
|
-
await fs40.readFile(
|
|
28545
|
+
await fs40.readFile(path42.join(runDirPath, "walkthrough.json"), "utf8")
|
|
28099
28546
|
);
|
|
28100
28547
|
} catch {
|
|
28101
28548
|
commandError(`Run not found: ${runId}`, {
|
|
@@ -28132,7 +28579,7 @@ async function match(runId, opts) {
|
|
|
28132
28579
|
);
|
|
28133
28580
|
const needed = !manifest.blocked && input.current.length > 0 && (input.prior.length > 0 || (input.guidance ?? []).length > 0);
|
|
28134
28581
|
const prompt = needed ? composeMatchPrompt(input) : null;
|
|
28135
|
-
const promptFilePath = opts?.promptFile ??
|
|
28582
|
+
const promptFilePath = opts?.promptFile ?? path42.join(runDirPath, "match_prompt.md");
|
|
28136
28583
|
if (prompt) {
|
|
28137
28584
|
await fs40.writeFile(promptFilePath, prompt, "utf8");
|
|
28138
28585
|
} else {
|
|
@@ -28148,8 +28595,8 @@ async function match(runId, opts) {
|
|
|
28148
28595
|
...input,
|
|
28149
28596
|
needed,
|
|
28150
28597
|
...manifest.blocked ? { blocked: manifest.blocked } : {},
|
|
28151
|
-
prompt_file: prompt ?
|
|
28152
|
-
prompt_file_abs: prompt ?
|
|
28598
|
+
prompt_file: prompt ? path42.relative(ctx.projectRoot, promptFilePath) : null,
|
|
28599
|
+
prompt_file_abs: prompt ? path42.resolve(promptFilePath) : null,
|
|
28153
28600
|
match_prompt: prompt
|
|
28154
28601
|
},
|
|
28155
28602
|
null,
|
|
@@ -28177,7 +28624,7 @@ async function matchRecord(runId, opts) {
|
|
|
28177
28624
|
const manifest = await completedManifest2(runDirPath, runId, opts);
|
|
28178
28625
|
if (!manifest) return;
|
|
28179
28626
|
const input = await readAnswerInput(opts, "matches", {
|
|
28180
|
-
defaultPath:
|
|
28627
|
+
defaultPath: path42.join(runDirPath, "matches.json"),
|
|
28181
28628
|
code: "missing_matches_input",
|
|
28182
28629
|
fix: `iamu match-record ${runId} --data '<json>'`
|
|
28183
28630
|
});
|
|
@@ -28256,10 +28703,10 @@ __export(advise_exports, {
|
|
|
28256
28703
|
readAdvisePrompt: () => readAdvisePrompt
|
|
28257
28704
|
});
|
|
28258
28705
|
import { existsSync as existsSync17, promises as fs41, readFileSync as readFileSync10 } from "fs";
|
|
28259
|
-
import
|
|
28706
|
+
import path43 from "path";
|
|
28260
28707
|
import chalk40 from "chalk";
|
|
28261
28708
|
function readAdvisePrompt(projectRoot) {
|
|
28262
|
-
const installed =
|
|
28709
|
+
const installed = path43.join(projectRoot, "iamu", ".prompts", "advise.md");
|
|
28263
28710
|
if (existsSync17(installed)) {
|
|
28264
28711
|
try {
|
|
28265
28712
|
return readFileSync10(installed, "utf8");
|
|
@@ -28304,7 +28751,7 @@ async function advise(runId, opts = {}) {
|
|
|
28304
28751
|
findings2 = input.findings.length;
|
|
28305
28752
|
needed = findings2 >= 2;
|
|
28306
28753
|
if (!needed) reason = "fewer than two open findings in the project";
|
|
28307
|
-
promptPath = opts.promptFile ??
|
|
28754
|
+
promptPath = opts.promptFile ?? path43.join(analysisDir(projectRoot), "advise_prompt.md");
|
|
28308
28755
|
} else {
|
|
28309
28756
|
if (!requireRunId(runId, opts, "iamu advise <run-id>")) return;
|
|
28310
28757
|
const runDirPath = resolveRunDir(projectRoot, runId, opts);
|
|
@@ -28314,21 +28761,21 @@ async function advise(runId, opts = {}) {
|
|
|
28314
28761
|
input = await buildAdviseInput(projectRoot, runId, manifest.scenario_slug);
|
|
28315
28762
|
refsDir = runDirPath;
|
|
28316
28763
|
findings2 = input.findings.length;
|
|
28317
|
-
const advised = existsSync17(
|
|
28764
|
+
const advised = existsSync17(path43.join(runDirPath, ADVICE_FILE));
|
|
28318
28765
|
needed = !manifest.blocked && findings2 > 0 && !advised;
|
|
28319
28766
|
if (manifest.blocked) reason = "the run was blocked";
|
|
28320
28767
|
else if (advised) reason = "the run is already advised";
|
|
28321
28768
|
else if (findings2 === 0) reason = "no open findings to advise";
|
|
28322
|
-
promptPath = opts.promptFile ??
|
|
28769
|
+
promptPath = opts.promptFile ?? path43.join(runDirPath, "advise_prompt.md");
|
|
28323
28770
|
}
|
|
28324
28771
|
const { bundle, refs } = promptBundle(input);
|
|
28325
28772
|
const prompt = needed ? composePrompt(projectRoot, bundle) : null;
|
|
28326
28773
|
if (prompt) {
|
|
28327
|
-
await fs41.mkdir(
|
|
28774
|
+
await fs41.mkdir(path43.dirname(promptPath), { recursive: true });
|
|
28328
28775
|
await fs41.writeFile(promptPath, prompt, "utf8");
|
|
28329
28776
|
await fs41.mkdir(refsDir, { recursive: true });
|
|
28330
28777
|
await fs41.writeFile(
|
|
28331
|
-
|
|
28778
|
+
path43.join(refsDir, ADVISE_REFS_FILE),
|
|
28332
28779
|
`${JSON.stringify(refs, null, 2)}
|
|
28333
28780
|
`,
|
|
28334
28781
|
"utf8"
|
|
@@ -28342,8 +28789,8 @@ async function advise(runId, opts = {}) {
|
|
|
28342
28789
|
...reason ? { reason } : {},
|
|
28343
28790
|
mode: opts.project ? "project" : "run",
|
|
28344
28791
|
findings: findings2,
|
|
28345
|
-
prompt_file: prompt ?
|
|
28346
|
-
prompt_file_abs: prompt ?
|
|
28792
|
+
prompt_file: prompt ? path43.relative(projectRoot, promptPath) : null,
|
|
28793
|
+
prompt_file_abs: prompt ? path43.resolve(promptPath) : null,
|
|
28347
28794
|
prompt_chars: prompt?.length ?? 0,
|
|
28348
28795
|
advise_prompt: prompt
|
|
28349
28796
|
},
|
|
@@ -28407,7 +28854,7 @@ async function adviseRecord(runId, opts) {
|
|
|
28407
28854
|
const { projectRoot } = ctx;
|
|
28408
28855
|
if (opts.project) {
|
|
28409
28856
|
const input2 = await readAnswerInput(opts, "advice", {
|
|
28410
|
-
defaultPath:
|
|
28857
|
+
defaultPath: path43.join(analysisDir(projectRoot), "answer.json"),
|
|
28411
28858
|
code: "missing_advice_input",
|
|
28412
28859
|
fix: "iamu advise-record --project --data '<json>'"
|
|
28413
28860
|
});
|
|
@@ -28437,7 +28884,7 @@ async function adviseRecord(runId, opts) {
|
|
|
28437
28884
|
const manifest = await completedManifest2(runDirPath, runId, opts);
|
|
28438
28885
|
if (!manifest) return;
|
|
28439
28886
|
const input = await readAnswerInput(opts, "advice", {
|
|
28440
|
-
defaultPath:
|
|
28887
|
+
defaultPath: path43.join(runDir(projectRoot, runId), "advise_answer.json"),
|
|
28441
28888
|
code: "missing_advice_input",
|
|
28442
28889
|
fix: `iamu advise-record ${runId} --data '<json>'`
|
|
28443
28890
|
});
|
|
@@ -28471,9 +28918,9 @@ async function directionsList(opts) {
|
|
|
28471
28918
|
const ctx = await resolveProject(opts);
|
|
28472
28919
|
if (!ctx) return;
|
|
28473
28920
|
const all = await listDirections(ctx.projectRoot);
|
|
28474
|
-
const
|
|
28921
|
+
const rank2 = (s) => ["accepted", "proposed", "done", "rejected"].indexOf(s);
|
|
28475
28922
|
const sorted = [...all].sort(
|
|
28476
|
-
(a, b) =>
|
|
28923
|
+
(a, b) => rank2(a.frontmatter.status) - rank2(b.frontmatter.status)
|
|
28477
28924
|
);
|
|
28478
28925
|
if (opts.json) {
|
|
28479
28926
|
console.log(
|
|
@@ -28483,7 +28930,7 @@ async function directionsList(opts) {
|
|
|
28483
28930
|
problem: d.texts.problem,
|
|
28484
28931
|
change: d.texts.change,
|
|
28485
28932
|
leading_practice: d.texts.leading_practice,
|
|
28486
|
-
path:
|
|
28933
|
+
path: path43.relative(ctx.projectRoot, d.path)
|
|
28487
28934
|
})),
|
|
28488
28935
|
null,
|
|
28489
28936
|
2
|
|
@@ -28856,9 +29303,9 @@ function registerProjectCommands(program) {
|
|
|
28856
29303
|
"after",
|
|
28857
29304
|
"\nExamples:\n $ iamu validate --all\n $ iamu validate --all --fix --dry-run\n $ iamu validate iamu/personas/maria.md"
|
|
28858
29305
|
).action(
|
|
28859
|
-
async (
|
|
29306
|
+
async (path44, opts) => {
|
|
28860
29307
|
const { validate: validate2 } = await Promise.resolve().then(() => (init_validate(), validate_exports));
|
|
28861
|
-
await validate2(
|
|
29308
|
+
await validate2(path44, opts);
|
|
28862
29309
|
}
|
|
28863
29310
|
);
|
|
28864
29311
|
program.command("update").helpGroup("Project:").description("Update and repair agent skills and prompts to latest version").option(
|
|
@@ -29232,13 +29679,16 @@ function registerWalkthroughCommands(program) {
|
|
|
29232
29679
|
}
|
|
29233
29680
|
);
|
|
29234
29681
|
program.command("summary [run-id]").helpGroup("Walkthrough:").description(
|
|
29235
|
-
"Record walker summary (what worked, open questions, or that the walk was blocked) atomically"
|
|
29682
|
+
"Record walker summary (what worked, open questions, setup notes, or that the walk was blocked) atomically"
|
|
29236
29683
|
).option("--run <run-id>", "Run ID (or pass as positional argument)").option(
|
|
29237
29684
|
"--worked <note...>",
|
|
29238
29685
|
"Things that worked well (can be specified multiple times)"
|
|
29239
29686
|
).option(
|
|
29240
29687
|
"--question <note...>",
|
|
29241
|
-
"
|
|
29688
|
+
"Questions for the product team the walk raised (can be specified multiple times)"
|
|
29689
|
+
).option(
|
|
29690
|
+
"--setup-note <note...>",
|
|
29691
|
+
"Doubts about the test setup, not the product: missing data, accounts, dates (can be specified multiple times)"
|
|
29242
29692
|
).option("--model <name>", "Model identifier (e.g. flash, haiku)").option(
|
|
29243
29693
|
"--blocked <reason>",
|
|
29244
29694
|
"The walk could not start as set up (persona unreachable, world missing): what was missing"
|