@nextcommerce/campaigns-os 1.60.0 → 1.62.0
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/AGENTS.md +2 -1
- package/CHANGELOG.md +244 -0
- package/README.md +86 -438
- package/agents/claude/CLAUDE.md +20 -0
- package/agents/codex/AGENTS.md +19 -0
- package/agents/copilot/copilot-instructions.md +19 -0
- package/agents/cursor/campaigns-os.mdc +21 -0
- package/campaign-spec/README.md +7 -0
- package/campaign-spec/dist/analytics-vendors.d.ts +40 -0
- package/campaign-spec/dist/analytics-vendors.js +44 -0
- package/campaign-spec/dist/checkout-flow.d.ts +18 -0
- package/campaign-spec/dist/checkout-flow.js +28 -0
- package/campaign-spec/dist/index.d.ts +4 -2
- package/campaign-spec/dist/index.js +5 -1
- package/campaign-spec/dist/rules/analytics-contract-shape.d.ts +8 -0
- package/campaign-spec/dist/rules/analytics-contract-shape.js +39 -0
- package/campaign-spec/dist/rules/campaign-currencies.d.ts +29 -0
- package/campaign-spec/dist/rules/campaign-currencies.js +130 -0
- package/campaign-spec/dist/rules/checkout-path-has-packages.d.ts +31 -0
- package/campaign-spec/dist/rules/checkout-path-has-packages.js +79 -0
- package/campaign-spec/dist/rules/index.d.ts +2 -0
- package/campaign-spec/dist/rules/index.js +6 -0
- package/campaign-spec/dist/types.d.ts +43 -9
- package/compatibility.json +1 -1
- package/contracts/brand-theme-target-tokens.next-core.v0.json +95 -25
- package/contracts/campaign-cart-sdk-support-policy.v0.json +2 -2
- package/contracts/commerce-surface-catalog.json +68 -61
- package/contracts/effects.v1.json +549 -165
- package/contracts/fixtures/runtime-recipe/accept/current.json +3 -0
- package/contracts/fixtures/runtime-recipe/accept/minimal.json +3 -0
- package/contracts/fixtures/runtime-recipe/dist-states.json +1 -1
- package/contracts/fixtures/runtime-recipe/reject/advisory-enforcement.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/allowlist-without-hosts.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/committed-output-claim.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/engines-disagreement-warns.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/lifecycle-scripts-enabled.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-kind.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-network-policy.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-output-check.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-revision.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-step-id.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/unperformable-check-skipped.json +3 -0
- package/contracts/fixtures/runtime-recipe/reject/unpinned-lockfile.json +3 -0
- package/contracts/release-ledger.json +1767 -0
- package/contracts/runtime-recipe.campaigns-os-node-v1.json +3 -0
- package/contracts/supported-surface.json +10 -10
- package/contracts/template-brand-contract.shared-commerce.v0.json +23 -1
- package/docs/access-model.md +20 -0
- package/docs/brand-theme-bridge.md +62 -3
- package/docs/build-packet.md +456 -36
- package/docs/campaign-build-brief.md +16 -0
- package/docs/campaign-standardization-report.md +4 -4
- package/docs/campaigns-os-build-flow.md +112 -2
- package/docs/campaignspec-authoring-examples.md +141 -0
- package/docs/design-source-package.md +64 -8
- package/docs/effects.md +17 -9
- package/docs/entry-points.md +188 -0
- package/docs/local-setup.md +1 -1
- package/docs/orientation-contract-reference.md +1 -1
- package/docs/polish-evidence.md +42 -2
- package/docs/progress-snapshots.md +7 -4
- package/docs/qa-and-test-orders.md +197 -3
- package/docs/quickstart.md +438 -0
- package/docs/readback.md +154 -6
- package/docs/runtime-readiness.md +6 -3
- package/docs/sdk-storage-compatibility.md +4 -2
- package/docs/skills-revision.md +10 -10
- package/docs/source-adapters.md +213 -0
- package/package.json +10 -3
- package/schemas/campaign-design-source-package.v0.schema.json +8 -0
- package/schemas/campaign-runtime-build-context.v0.schema.json +9 -0
- package/schemas/campaign-runtime-build-packet.v0.schema.json +9 -0
- package/schemas/campaign-spec.v4.schema.json +19 -3
- package/schemas/campaign-spec.v5.schema.json +19 -3
- package/schemas/campaigns-os-readback.v2.schema.json +238 -1
- package/schemas/source-html-manifest.v0.schema.json +3 -0
- package/skills/campaign-lifecycle-orientation/SKILL.md +3 -3
- package/skills/campaign-readback-classification/SKILL.md +3 -3
- package/skills/campaign-run-evidence/SKILL.md +3 -3
- package/skills/contribution-intake/SKILL.md +3 -3
- package/skills/next-campaigns-build/SKILL.md +5 -4
- package/skills/next-campaigns-os/SKILL.md +7 -4
- package/skills/next-campaigns-os-setup/SKILL.md +5 -3
- package/skills/next-campaigns-polish/SKILL.md +8 -10
- package/skills/next-campaigns-qa/SKILL.md +7 -4
- package/skills/next-campaigns-sdk-bump/SKILL.md +151 -25
- package/skills.json +11 -11
- package/src/brand-theme.mjs +415 -9
- package/src/brief-summary.mjs +6 -2
- package/src/browser-launch.mjs +2 -1
- package/src/build-brief.mjs +13 -3
- package/src/built-site-scope.mjs +2 -2
- package/src/campaign-workspace.mjs +288 -15
- package/src/change-scope.mjs +284 -0
- package/src/cli.mjs +358 -97
- package/src/commercial-journey.mjs +16 -3
- package/src/commercial-parity.mjs +15 -7
- package/src/content-residue.mjs +4 -2
- package/src/design-file-reference.mjs +93 -0
- package/src/design-source-package.mjs +91 -1
- package/src/design-source-publication.mjs +9 -6
- package/src/directory-lock.mjs +73 -19
- package/src/doctor/checks.mjs +87 -81
- package/src/doctor/ci.mjs +177 -0
- package/src/doctor/inspect.mjs +41 -6
- package/src/doctor/next-step.mjs +7 -0
- package/src/doctor/source-provenance.mjs +74 -0
- package/src/doctor-sidecar.mjs +3 -2
- package/src/evidence-debt.mjs +159 -0
- package/src/finding-cause.mjs +12 -11
- package/src/input-refresh.mjs +339 -32
- package/src/invocation.mjs +7 -1
- package/src/live-campaign-refs.mjs +7 -2
- package/src/local-preview-policy.mjs +35 -2
- package/src/page-kit-analytics-vendors.mjs +132 -0
- package/src/parity/compare.mjs +316 -0
- package/src/parity/design-access.mjs +54 -0
- package/src/parity/element-parity-record.mjs +569 -0
- package/src/parity/probe.mjs +205 -0
- package/src/parity/snapshot.mjs +110 -0
- package/src/png-header.mjs +87 -0
- package/src/polish-browser.mjs +189 -10
- package/src/polish-captures-record.mjs +52 -0
- package/src/polish-deadline.mjs +14 -0
- package/src/polish-gate.mjs +25 -6
- package/src/polish-node.mjs +81 -7
- package/src/polish-readability.mjs +221 -9
- package/src/polish-visual-fixtures.mjs +74 -0
- package/src/polish-visual.mjs +613 -0
- package/src/progress-node.mjs +3 -2
- package/src/qa-analytics-correctness.mjs +14 -2
- package/src/qa-analytics-parity.mjs +111 -6
- package/src/qa-browser.mjs +40 -4
- package/src/qa-commercial-parity.mjs +50 -3
- package/src/qa-node.mjs +235 -22
- package/src/qa-publish.mjs +9 -8
- package/src/qa-sidecar.mjs +16 -4
- package/src/qa-vendor-beacons.mjs +232 -0
- package/src/qa-vendor-endpoints.mjs +306 -0
- package/src/qc-accept.mjs +176 -6
- package/src/qc-check-registry.mjs +42 -2
- package/src/qc-results.mjs +477 -42
- package/src/readback.mjs +113 -21
- package/src/route-identity.mjs +10 -0
- package/src/run-record.mjs +18 -10
- package/src/runtime-state-ignore.mjs +6 -2
- package/src/sdk-attribute-index.mjs +11 -11
- package/src/sdk-markup.mjs +77 -2
- package/src/sdk-storage-compatibility.mjs +115 -6
- package/src/source-html-intake.mjs +15 -1
- package/src/source-html-manifest.mjs +7 -0
- package/src/source-html-server.mjs +129 -0
- package/src/spec-derive-campaign.mjs +216 -0
- package/src/spec-derive.mjs +473 -53
- package/src/spec-identity.mjs +14 -0
- package/src/stage-ledger.mjs +40 -1
- package/src/stage-record.mjs +100 -17
- package/src/target-lock.mjs +11 -1
- package/src/theme-gate.mjs +2 -2
- package/src/visual-diff.mjs +296 -0
- package/src/visual-reference.mjs +126 -0
package/src/doctor-sidecar.mjs
CHANGED
|
@@ -73,8 +73,9 @@ export function writeDoctorSidecar(path, doctor, { command, targetRepo = null }
|
|
|
73
73
|
// qa policy set) stamp the retained snapshot stale instead of leaving a green
|
|
74
74
|
// lie on disk, while commands that DO recompute (doctor, prepare-build/start,
|
|
75
75
|
// next) rewrite the sidecar wholesale — which clears any stale stamp.
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
// `path` is the funnel's own sidecar (campaign-workspace.mjs doctorOutPath);
|
|
77
|
+
// without it, the project-level one under `targetBaseDir`.
|
|
78
|
+
export function markDoctorSidecarStale(targetBaseDir, { command = null, reason = null, path = doctorSidecarPath(targetBaseDir) } = {}) {
|
|
78
79
|
if (!existsSync(path)) return null;
|
|
79
80
|
let sidecar;
|
|
80
81
|
try {
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// Read-only build binding projection, shared by readback and other readers.
|
|
2
|
+
// No stage verdicts, clocks, Git metadata, processes, writes or network calls.
|
|
3
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
computeBuildFingerprint,
|
|
7
|
+
describeBuildOutputDrift,
|
|
8
|
+
pageIdForRoute,
|
|
9
|
+
resolveBuiltSiteScope,
|
|
10
|
+
routeForFile,
|
|
11
|
+
} from "./built-site-scope.mjs";
|
|
12
|
+
import { changeSince, classifyDrift } from "./change-scope.mjs";
|
|
13
|
+
import { normalizePublicRouteSlug } from "./route-identity.mjs";
|
|
14
|
+
import { isFileReadFailure } from "./cart-placeholders.mjs";
|
|
15
|
+
import { polishReviewedBuild, qaExtendedBuildFingerprint, qaRecordedBuildFingerprint, qaRecordedForCurrentBuild } from "./stage-ledger.mjs";
|
|
16
|
+
|
|
17
|
+
export const EVIDENCE_DEBT_STATUSES = Object.freeze(["current", "owed", "unknown"]);
|
|
18
|
+
|
|
19
|
+
const fingerprint = (value) => typeof value === "string" && value.trim() ? value.trim() : null;
|
|
20
|
+
const portable = (value) => value.split(sep).join("/");
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Compare recorded Polish/QA build bindings with bytes under _site/<slug>.
|
|
24
|
+
* `changes` is explicitly against the assembly manifest, which need not be
|
|
25
|
+
* the build an older stage reviewed. We cannot reconstruct an older manifest.
|
|
26
|
+
* Missing build inputs remain unknown; lifecycle-required stages are owed
|
|
27
|
+
* even without a stage binding. A matching binding alone is not a verdict.
|
|
28
|
+
*
|
|
29
|
+
* Each owed stage also carries `change`: what changed since the build that
|
|
30
|
+
* stage reviewed (change-scope.mjs), read from the rebuilds `record build`
|
|
31
|
+
* kept plus the drift on disk. A page-only change makes owed Polish
|
|
32
|
+
* `advisory` and owed QA `scope: "pages"` on `owed_pages`; anything else
|
|
33
|
+
* owes the full stage.
|
|
34
|
+
*/
|
|
35
|
+
export function assessEvidenceDebt({ packet = null, packetPath = null, report = null } = {}) {
|
|
36
|
+
const assembly = report?.stages?.assembly;
|
|
37
|
+
const recordedBuild = fingerprint(assembly?.build_fingerprint);
|
|
38
|
+
// Match polish-gate's sourceBuildFingerprint precedence without reading the
|
|
39
|
+
// capture package (often absent in a fresh checkout).
|
|
40
|
+
const polishBuild = fingerprint(report?.stages?.polish?.source_build_fingerprint)
|
|
41
|
+
|| fingerprint(report?.stages?.polish?.evidence?.source_build_fingerprint);
|
|
42
|
+
const qaBuild = qaRecordedBuildFingerprint(report);
|
|
43
|
+
let current = null;
|
|
44
|
+
let scope = null;
|
|
45
|
+
let outputRoot = null;
|
|
46
|
+
let reason = null;
|
|
47
|
+
let scopeReason = null;
|
|
48
|
+
let targetRepo = null;
|
|
49
|
+
const target = fingerprint(packet?.assembly?.target_repo);
|
|
50
|
+
const slug = normalizePublicRouteSlug(packet?.campaign?.public_route_slug);
|
|
51
|
+
if (!packet || !packetPath) reason = "packet_unavailable";
|
|
52
|
+
else if (!target || !slug) reason = "build_output_location_unknown";
|
|
53
|
+
else {
|
|
54
|
+
targetRepo = resolve(dirname(resolve(packetPath)), target);
|
|
55
|
+
outputRoot = join(targetRepo, "_site", slug);
|
|
56
|
+
try {
|
|
57
|
+
current = computeBuildFingerprint(outputRoot);
|
|
58
|
+
if (!current.ok) reason = "build_output_missing";
|
|
59
|
+
// Resolve only the chosen built directory: never discover another
|
|
60
|
+
// campaign, or fall back to source HTML when _site is absent.
|
|
61
|
+
else {
|
|
62
|
+
scope = resolveBuiltSiteScope(outputRoot);
|
|
63
|
+
if (!scope.ok) scopeReason = "build_output_pages_unresolved";
|
|
64
|
+
}
|
|
65
|
+
} catch (error) {
|
|
66
|
+
if (!isFileReadFailure(error)) throw error;
|
|
67
|
+
reason = "build_output_unreadable";
|
|
68
|
+
current = null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const currentBuild = current?.ok ? current.fingerprint : null;
|
|
72
|
+
const drift = currentBuild ? describeBuildOutputDrift(assembly, current) : null;
|
|
73
|
+
const manifestKnown = drift?.manifest === "recorded";
|
|
74
|
+
// A missing manifest hides page changes, never a known stage binding.
|
|
75
|
+
const bindingReason = reason || (!recordedBuild ? "assembly_build_fingerprint_missing" : null);
|
|
76
|
+
const changesReason = bindingReason || scopeReason || (!manifestKnown ? "build_manifest_unavailable" : null);
|
|
77
|
+
const stage = (stageRecord, recorded, matches) => {
|
|
78
|
+
// src/stage-record.mjs writes required_by: build; the dependency map in
|
|
79
|
+
// src/input-currency.mjs supplies brief/spec for input-change demotions.
|
|
80
|
+
// This is lifecycle debt even when retained evidence matches the output.
|
|
81
|
+
const required = stageRecord?.status === "required";
|
|
82
|
+
const requiredReason = ["build", "brief", "spec"].includes(stageRecord?.required_by)
|
|
83
|
+
? `stage_required_by_${stageRecord.required_by}` : "stage_required";
|
|
84
|
+
return {
|
|
85
|
+
status: bindingReason ? "unknown" : required ? "owed" : !recorded ? "unknown" : matches ? "current" : "owed",
|
|
86
|
+
matches_current_build: recorded && currentBuild ? matches : null,
|
|
87
|
+
recorded_build_fingerprint: recorded,
|
|
88
|
+
current_build_fingerprint: currentBuild,
|
|
89
|
+
reason: bindingReason || (required ? requiredReason : !recorded ? "stage_build_fingerprint_missing" : matches ? null : "build_fingerprint_changed"),
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const changes = {
|
|
94
|
+
status: changesReason ? "unknown" : "known",
|
|
95
|
+
reason: changesReason,
|
|
96
|
+
recorded_build_fingerprint: recordedBuild,
|
|
97
|
+
current_build_fingerprint: currentBuild,
|
|
98
|
+
changed_pages: [],
|
|
99
|
+
unmapped_files: [],
|
|
100
|
+
extra: manifestKnown ? drift.extra : [],
|
|
101
|
+
missing: manifestKnown ? drift.missing : [],
|
|
102
|
+
changed: manifestKnown ? drift.changed : [],
|
|
103
|
+
};
|
|
104
|
+
if (manifestKnown && scope?.ok) {
|
|
105
|
+
const pages = new Map((scope?.pages || []).map((page) => [
|
|
106
|
+
portable(relative(outputRoot, page.built_path)),
|
|
107
|
+
{ page_id: page.page_id, built_path: portable(relative(outputRoot, page.built_path)) },
|
|
108
|
+
]));
|
|
109
|
+
// Deleted HTML no longer appears in the disk map. Reuse its route/id
|
|
110
|
+
// projection for those manifest entries, without reading nonexistent files.
|
|
111
|
+
for (const path of drift.missing) {
|
|
112
|
+
if (!path.toLowerCase().endsWith(".html") || path.split("/").some((part) => part.startsWith(".") || part.startsWith("_") || part === "node_modules")) continue;
|
|
113
|
+
pages.set(path, { page_id: pageIdForRoute(routeForFile(outputRoot, join(outputRoot, path))), built_path: path });
|
|
114
|
+
}
|
|
115
|
+
const paths = [...new Set([...drift.extra, ...drift.missing, ...drift.changed])].sort();
|
|
116
|
+
for (const path of paths) {
|
|
117
|
+
const page = pages.get(path);
|
|
118
|
+
if (page) changes.changed_pages.push(page);
|
|
119
|
+
else changes.unmapped_files.push(path);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const driftClassification = classifyDrift({ changes, baseline: assembly, outputRoot, targetRepo, publicRouteSlug: slug });
|
|
123
|
+
changes.kind = driftClassification.kind;
|
|
124
|
+
changes.reasons = driftClassification.reasons;
|
|
125
|
+
const changeFor = (anchor) => changeSince(anchor, { assembly, drift: changes, driftClassification });
|
|
126
|
+
|
|
127
|
+
// Polish reviewed the build a terminal record is bound to; once `record
|
|
128
|
+
// build` has made it required, the build it reviewed is kept as
|
|
129
|
+
// owed_since_build_fingerprint (polishReviewedBuild). Only a Polish that
|
|
130
|
+
// reviewed a build can be advisory: a blocked or pending one, or one a
|
|
131
|
+
// brief or spec change made required, owes the full stage, before and
|
|
132
|
+
// after record build alike.
|
|
133
|
+
const polishRecord = report?.stages?.polish;
|
|
134
|
+
const polish = stage(polishRecord, polishBuild, Boolean(polishBuild && currentBuild && polishBuild === currentBuild));
|
|
135
|
+
if (polish.status === "owed") {
|
|
136
|
+
const reviewed = polishReviewedBuild(polishRecord);
|
|
137
|
+
polish.change = changeFor(reviewed || polishBuild || fingerprint(polishRecord?.owed_since_build_fingerprint));
|
|
138
|
+
polish.advisory = Boolean(reviewed) && polish.change.kind === "page_only";
|
|
139
|
+
} else {
|
|
140
|
+
polish.change = null;
|
|
141
|
+
polish.advisory = false;
|
|
142
|
+
}
|
|
143
|
+
// QA on the changed pages only extends a completed QA: the change is read
|
|
144
|
+
// from the build that QA checked (qaExtendedBuildFingerprint). A blocked,
|
|
145
|
+
// pending or skipped full run extends nothing and owes the full stage.
|
|
146
|
+
const qaRecord = report?.stages?.qa;
|
|
147
|
+
const qa = stage(qaRecord, qaBuild, qaRecordedForCurrentBuild(report, currentBuild));
|
|
148
|
+
if (qa.status === "owed") {
|
|
149
|
+
const extended = qaExtendedBuildFingerprint(report);
|
|
150
|
+
qa.change = changeFor(extended || qaBuild);
|
|
151
|
+
qa.scope = extended && qaRecord?.status !== "required" && qa.change.kind === "page_only" ? "pages" : "full";
|
|
152
|
+
qa.owed_pages = qa.scope === "pages" ? qa.change.changed_pages : [];
|
|
153
|
+
} else {
|
|
154
|
+
qa.change = null;
|
|
155
|
+
qa.scope = null;
|
|
156
|
+
qa.owed_pages = [];
|
|
157
|
+
}
|
|
158
|
+
return { polish, qa, changes };
|
|
159
|
+
}
|
package/src/finding-cause.mjs
CHANGED
|
@@ -330,7 +330,8 @@ import { iterateQaVerdicts } from "./qa-verdict-discovery.mjs";
|
|
|
330
330
|
import { readRunRecordsForTarget } from "./run-record.mjs";
|
|
331
331
|
|
|
332
332
|
/**
|
|
333
|
-
* The most recent Run Record for the same campaign identity under `baseDir
|
|
333
|
+
* The most recent Run Record for the same campaign identity under `baseDir`
|
|
334
|
+
* (in the packet's runtime directory, `runtimeRelPath`, when one is given),
|
|
334
335
|
* or null. Records are discovered newest-first, and the current run's record
|
|
335
336
|
* does not exist yet at classification time (both `qa run` and `doctor`
|
|
336
337
|
* complete before `run-record` assembles anything), so the first identity
|
|
@@ -341,9 +342,9 @@ import { readRunRecordsForTarget } from "./run-record.mjs";
|
|
|
341
342
|
* against a non-adjacent one and report findings introduced in between as
|
|
342
343
|
* pre-existing.
|
|
343
344
|
*/
|
|
344
|
-
export function findPriorRunRecord({ baseDir, mapId = null, localSpecId = null, currentRunId = null } = {}) {
|
|
345
|
+
export function findPriorRunRecord({ baseDir, runtimeRelPath = undefined, mapId = null, localSpecId = null, currentRunId = null } = {}) {
|
|
345
346
|
if (!text(baseDir)) return null;
|
|
346
|
-
for (const entry of readRunRecordsForTarget(baseDir)) {
|
|
347
|
+
for (const entry of readRunRecordsForTarget(baseDir, runtimeRelPath)) {
|
|
347
348
|
const record = entry?.record;
|
|
348
349
|
if (!record || typeof record !== "object" || Array.isArray(record)) continue;
|
|
349
350
|
if (currentRunId && record.run_id === currentRunId) continue;
|
|
@@ -448,8 +449,8 @@ function locateExternalPriorVerdict({ targetRepo, record, ref }) {
|
|
|
448
449
|
* The single-record boundary is unchanged: this still reads the final attempt
|
|
449
450
|
* of exactly one earlier run, never a merged view across runs.
|
|
450
451
|
*/
|
|
451
|
-
export function loadPriorQaVerdict({ baseDir, targetRepo = null, mapId = null, localSpecId = null, currentRunId = null } = {}) {
|
|
452
|
-
const record = findPriorRunRecord({ baseDir, mapId, localSpecId, currentRunId });
|
|
452
|
+
export function loadPriorQaVerdict({ baseDir, runtimeRelPath = undefined, targetRepo = null, mapId = null, localSpecId = null, currentRunId = null } = {}) {
|
|
453
|
+
const record = findPriorRunRecord({ baseDir, runtimeRelPath, mapId, localSpecId, currentRunId });
|
|
453
454
|
if (!record) return { verdict: null, record: null, path: null, reason: "no_prior_run" };
|
|
454
455
|
const ref = (Array.isArray(record.artifacts) ? record.artifacts : []).findLast((artifact) => artifact?.kind === "qa_verdict");
|
|
455
456
|
const refPath = text(ref?.path);
|
|
@@ -472,8 +473,8 @@ export function loadPriorQaVerdict({ baseDir, targetRepo = null, mapId = null, l
|
|
|
472
473
|
* carries these, so doctor cause labels work against existing history with no
|
|
473
474
|
* upgrade window. Returns `{ prior, record, reason }`.
|
|
474
475
|
*/
|
|
475
|
-
function loadPriorDoctorFindings({ baseDir, mapId = null, localSpecId = null, currentRunId = null } = {}) {
|
|
476
|
-
const record = findPriorRunRecord({ baseDir, mapId, localSpecId, currentRunId });
|
|
476
|
+
function loadPriorDoctorFindings({ baseDir, runtimeRelPath = undefined, mapId = null, localSpecId = null, currentRunId = null } = {}) {
|
|
477
|
+
const record = findPriorRunRecord({ baseDir, runtimeRelPath, mapId, localSpecId, currentRunId });
|
|
477
478
|
if (!record) return { prior: null, record: null, reason: "no_prior_run" };
|
|
478
479
|
const prior = priorSetFromRunRecordDoctor(record);
|
|
479
480
|
if (!prior) return { prior: null, record, reason: "prior_run_without_doctor_observations" };
|
|
@@ -496,9 +497,9 @@ function loadPriorDoctorFindings({ baseDir, mapId = null, localSpecId = null, cu
|
|
|
496
497
|
* raw map id) have no Run Record home, so they get `unknown` / `no_prior_run`
|
|
497
498
|
* throughout — which is the truth, not a silence.
|
|
498
499
|
*/
|
|
499
|
-
export function annotateQaAssertionCauses(assertions, { baseDir = null, targetRepo = null, mapId = null, localSpecId = null, currentRunId = null, isFinding } = {}) {
|
|
500
|
+
export function annotateQaAssertionCauses(assertions, { baseDir = null, runtimeRelPath = undefined, targetRepo = null, mapId = null, localSpecId = null, currentRunId = null, isFinding } = {}) {
|
|
500
501
|
const lookup = baseDir
|
|
501
|
-
? loadPriorQaVerdict({ baseDir, targetRepo, mapId, localSpecId, currentRunId })
|
|
502
|
+
? loadPriorQaVerdict({ baseDir, runtimeRelPath, targetRepo, mapId, localSpecId, currentRunId })
|
|
502
503
|
: { verdict: null, record: null, path: null, reason: "no_prior_run" };
|
|
503
504
|
const prior = lookup.verdict ? priorSetFromVerdict(lookup.verdict, { isFinding }) : null;
|
|
504
505
|
const noPriorReason = lookup.reason || "no_prior_run";
|
|
@@ -532,9 +533,9 @@ const CAUSE_SUMMARY_SCHEMA = "campaigns-os-finding-cause/v0";
|
|
|
532
533
|
* The doctor twin. `errors` and `warnings` are the doctor output's own arrays;
|
|
533
534
|
* both are stamped in place. Returns the same summary shape.
|
|
534
535
|
*/
|
|
535
|
-
export function annotateDoctorIssueCauses({ errors = [], warnings = [], baseDir = null, mapId = null, localSpecId = null, currentRunId = null } = {}) {
|
|
536
|
+
export function annotateDoctorIssueCauses({ errors = [], warnings = [], baseDir = null, runtimeRelPath = undefined, mapId = null, localSpecId = null, currentRunId = null } = {}) {
|
|
536
537
|
const lookup = baseDir
|
|
537
|
-
? loadPriorDoctorFindings({ baseDir, mapId, localSpecId, currentRunId })
|
|
538
|
+
? loadPriorDoctorFindings({ baseDir, runtimeRelPath, mapId, localSpecId, currentRunId })
|
|
538
539
|
: { prior: null, record: null, reason: "no_prior_run" };
|
|
539
540
|
const noPriorReason = lookup.reason || "no_prior_run";
|
|
540
541
|
const findings = [];
|