@nextcommerce/campaigns-os 1.52.0 → 1.53.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/CHANGELOG.md +48 -0
- package/agents/claude/CLAUDE.md +2 -0
- package/agents/codex/AGENTS.md +2 -0
- package/agents/copilot/copilot-instructions.md +2 -0
- package/agents/cursor/campaigns-os.mdc +2 -0
- package/compatibility.json +1 -1
- package/contracts/effects.v1.json +201 -12
- package/contracts/release-ledger.json +327 -0
- package/contracts/supported-surface.json +2 -2
- package/docs/brand-theme-bridge.md +2 -0
- package/docs/build-packet.md +56 -15
- package/docs/campaign-build-brief.md +48 -17
- package/docs/effects.md +8 -0
- package/docs/local-setup.md +1 -1
- package/docs/orientation-contract-reference.md +1 -1
- package/docs/polish-evidence.md +2 -0
- package/docs/progress-snapshots.md +2 -4
- package/docs/qa-and-test-orders.md +2 -0
- package/docs/readback.md +19 -2
- package/docs/runtime-readiness.md +1 -1
- package/docs/skills-revision.md +10 -10
- package/package.json +1 -1
- 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 -3
- package/skills/next-campaigns-os/SKILL.md +10 -19
- package/skills/next-campaigns-os/references/session-intake.md +10 -6
- package/skills/next-campaigns-os-setup/SKILL.md +4 -3
- package/skills/next-campaigns-polish/SKILL.md +7 -5
- package/skills/next-campaigns-qa/SKILL.md +5 -3
- package/skills.json +10 -10
- package/src/brand-theme.mjs +21 -20
- package/src/brief-summary.mjs +275 -0
- package/src/build-brief.mjs +167 -15
- package/src/cli.mjs +160 -112
- package/src/contrast.mjs +649 -0
- package/src/design-source-publication.mjs +110 -3
- package/src/doctor/checks.mjs +129 -14
- package/src/doctor/next-step.mjs +15 -6
- package/src/input-currency.mjs +394 -0
- package/src/input-refresh.mjs +708 -0
- package/src/invocation.mjs +3 -1
- package/src/polish-browser.mjs +503 -201
- package/src/polish-deadline.mjs +7 -0
- package/src/polish-node.mjs +326 -46
- package/src/polish-readability.mjs +828 -0
- package/src/progress-node.mjs +15 -3
- package/src/qa-browser.mjs +131 -84
- package/src/qa-node.mjs +14 -3
- package/src/qc-accept.mjs +44 -8
- package/src/qc-check-registry.mjs +15 -7
- package/src/qc-results.mjs +326 -34
- package/src/readback.mjs +58 -10
- package/src/stage-ledger.mjs +235 -44
- package/src/stage-record.mjs +119 -26
- package/src/tooling-setup.mjs +1 -1
package/src/stage-record.mjs
CHANGED
|
@@ -46,7 +46,9 @@ import {
|
|
|
46
46
|
evaluatePolishGate,
|
|
47
47
|
} from "./polish-gate.mjs";
|
|
48
48
|
import { evaluateRecordedHiddenEagerMediaCheckpoint } from "./polish-node.mjs";
|
|
49
|
-
import {
|
|
49
|
+
import { effectiveStageStatus, effectiveStatusIsTerminal, inputStamps, stageWriteInputs } from "./input-currency.mjs";
|
|
50
|
+
import { demoteStages, recordBrief, recordSpec } from "./input-refresh.mjs";
|
|
51
|
+
import { applyDerivedAssemblyReportSummary, archiveStageRecord, assemblyReportMatchesPacket, commitAssemblyReport, inputChangeFor } from "./stage-ledger.mjs";
|
|
50
52
|
import { withTargetLockSync } from "./target-lock.mjs";
|
|
51
53
|
import { commerceScopeFromScope } from "./theme-gate.mjs";
|
|
52
54
|
|
|
@@ -59,6 +61,11 @@ const RECORD_FLAGS = Object.freeze(["packet", "context", "report", "dry-run", "j
|
|
|
59
61
|
const POLISH_RECORD_FLAGS = Object.freeze(["evidence"]);
|
|
60
62
|
const DEPLOY_RECORD_FLAGS = Object.freeze(["base-url"]);
|
|
61
63
|
const BUILD_RECORD_FLAGS = Object.freeze(["build-environment"]);
|
|
64
|
+
// `record brief` saves a brief file: --brief names it (otherwise intake's
|
|
65
|
+
// discovery finds it).
|
|
66
|
+
const BRIEF_RECORD_FLAGS = Object.freeze(["brief"]);
|
|
67
|
+
// `record spec` binds the CampaignSpec as it is now; it takes no extra flag.
|
|
68
|
+
const RECORD_SUBCOMMANDS = Object.freeze([...RECORD_STAGES, "brief", "spec"]);
|
|
62
69
|
// The page-kit environment the built output was rendered in, recorded on
|
|
63
70
|
// stages.assembly.evidence.build_environment (local proof mode builds in
|
|
64
71
|
// development; doctor and page-kit parity read it).
|
|
@@ -116,24 +123,24 @@ function typeName(value) {
|
|
|
116
123
|
|
|
117
124
|
// visual_review keys only `polish capture` writes. `record polish --evidence`
|
|
118
125
|
// refuses them by name and carries the captured values forward.
|
|
119
|
-
export const PACKAGE_OWNED_VISUAL_REVIEW_KEYS = Object.freeze(["page_load", "media_weight"]);
|
|
126
|
+
export const PACKAGE_OWNED_VISUAL_REVIEW_KEYS = Object.freeze(["page_load", "media_weight", "readability"]);
|
|
120
127
|
export const PACKAGE_OWNED_KEY_REFUSAL = "package_owned_key";
|
|
121
128
|
|
|
122
|
-
function refuseRecord(stage, problems) {
|
|
129
|
+
export function refuseRecord(stage, problems) {
|
|
123
130
|
return new Error(`record ${stage} refused; nothing was written:\n${problems.map((problem) => `- ${problem}`).join("\n")}`);
|
|
124
131
|
}
|
|
125
132
|
|
|
126
133
|
export function parseRecordArgs(args) {
|
|
127
134
|
const stage = args._[1];
|
|
128
|
-
if (!
|
|
129
|
-
throw refused(`Use: ${cmd("record")} <${
|
|
135
|
+
if (!RECORD_SUBCOMMANDS.includes(stage) || args._.length !== 2) {
|
|
136
|
+
throw refused(`Use: ${cmd("record")} <${RECORD_SUBCOMMANDS.join("|")}> --packet <campaign-runtime.build.json> [--context <json>] [--report <json>] [--dry-run] [--json]; record polish also takes --evidence <polish-evidence.json>, record deploy --base-url <served url>, record build [--build-environment <${BUILD_ENVIRONMENTS.join("|")}>], and record brief [--brief <yaml|json>].`);
|
|
130
137
|
}
|
|
131
|
-
const known = new Set([...RECORD_FLAGS, ...(stage === "polish" ? POLISH_RECORD_FLAGS : []), ...(stage === "deploy" ? DEPLOY_RECORD_FLAGS : []), ...(stage === "build" ? BUILD_RECORD_FLAGS : [])]);
|
|
138
|
+
const known = new Set([...RECORD_FLAGS, ...(stage === "polish" ? POLISH_RECORD_FLAGS : []), ...(stage === "deploy" ? DEPLOY_RECORD_FLAGS : []), ...(stage === "build" ? BUILD_RECORD_FLAGS : []), ...(stage === "brief" ? BRIEF_RECORD_FLAGS : [])]);
|
|
132
139
|
const unknown = Object.keys(args).filter((key) => key !== "_" && !known.has(key));
|
|
133
140
|
if (unknown.length) {
|
|
134
141
|
throw refused(`Unknown flag${unknown.length > 1 ? "s" : ""} for record ${stage}: ${unknown.map((key) => `--${key}`).join(", ")}. Known flags: ${[...known].map((key) => `--${key}`).join(", ")}.`);
|
|
135
142
|
}
|
|
136
|
-
for (const flag of ["context", "report", "run-id", "lifecycle-journal", "deviation-reason"]) {
|
|
143
|
+
for (const flag of ["context", "report", "run-id", "lifecycle-journal", "deviation-reason", "brief"]) {
|
|
137
144
|
if (Object.hasOwn(args, flag)) requireArg(args, flag);
|
|
138
145
|
}
|
|
139
146
|
if (Object.hasOwn(args, "dry-run") && args["dry-run"] !== true) {
|
|
@@ -151,6 +158,8 @@ export function parseRecordArgs(args) {
|
|
|
151
158
|
baseUrl: stage === "deploy" ? requireArg(args, "base-url") : null,
|
|
152
159
|
dryRun: args["dry-run"] === true,
|
|
153
160
|
buildEnvironment,
|
|
161
|
+
briefPath: stage === "brief" && Object.hasOwn(args, "brief") ? resolve(args.brief) : null,
|
|
162
|
+
deviationReason: optionalString(args["deviation-reason"]),
|
|
154
163
|
};
|
|
155
164
|
}
|
|
156
165
|
|
|
@@ -268,30 +277,72 @@ function composeSetup(report, context, { now, recordedBy }) {
|
|
|
268
277
|
return { report: nextReport, context: nextContext };
|
|
269
278
|
}
|
|
270
279
|
|
|
271
|
-
|
|
280
|
+
// The fields a record restates on every write: its input stamps, and the
|
|
281
|
+
// operator decision that applies only to the record that carried it.
|
|
282
|
+
const STAMP_FIELDS = Object.freeze(["source_brief_material", "source_spec_material_hash"]);
|
|
283
|
+
// A replaced record in any of these statuses is checked for an input change.
|
|
284
|
+
const REPLACED_COMPLETED_STATUSES = Object.freeze(["completed", "completed_with_warnings", "completed_partial"]);
|
|
285
|
+
|
|
286
|
+
// The stamps a completed record writes: the inputs current at this write
|
|
287
|
+
// (the absent brief value for a packet without a brief). A value that cannot
|
|
288
|
+
// be computed is left out, so the record reads unknown rather than current.
|
|
289
|
+
function currentStamps(inputs) {
|
|
290
|
+
const stamps = inputStamps(inputs);
|
|
291
|
+
return Object.fromEntries(Object.entries(stamps).filter(([, value]) => value !== null));
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function composeBuild(report, { now, recordedBy, fingerprint, buildEnvironment = null, inputs = {}, deviationReason = null }) {
|
|
272
295
|
const sourcePackageFingerprint = currentSourcePackageMaterialFingerprint(report);
|
|
273
296
|
const previousAssembly = stageObject(report, "assembly");
|
|
274
|
-
const
|
|
275
|
-
|
|
297
|
+
const stamps = currentStamps(inputs);
|
|
298
|
+
// A replaced completed record whose stamps differ from the current inputs
|
|
299
|
+
// records the change now, if nothing recorded it before.
|
|
300
|
+
const writeInputs = stageWriteInputs("assembly", inputs);
|
|
301
|
+
const detected = REPLACED_COMPLETED_STATUSES.includes(previousAssembly.status) ? writeInputs.detectChange(previousAssembly) : null;
|
|
302
|
+
let assembly = {
|
|
303
|
+
...withoutKeys(previousAssembly, ["source_package_material_fingerprint", "unchanged_output_reason", ...STAMP_FIELDS]),
|
|
276
304
|
...(buildEnvironment ? { evidence: { ...(isObject(previousAssembly.evidence) ? previousAssembly.evidence : {}), build_environment: buildEnvironment } } : {}),
|
|
277
305
|
stage: "assembly",
|
|
278
306
|
status: "completed",
|
|
279
307
|
build_fingerprint: fingerprint,
|
|
280
308
|
...(sourcePackageFingerprint ? { source_package_material_fingerprint: sourcePackageFingerprint } : {}),
|
|
309
|
+
...stamps,
|
|
281
310
|
completed_at: now,
|
|
282
311
|
recorded_by: recordedBy,
|
|
283
312
|
blockers: [],
|
|
284
313
|
};
|
|
285
|
-
|
|
286
|
-
//
|
|
287
|
-
//
|
|
314
|
+
if (detected) assembly.input_change = inputChangeFor("assembly", previousAssembly, { at: now, reason: detected });
|
|
315
|
+
// Output identical to the build the input change superseded does not make
|
|
316
|
+
// the build current, unless the operator's --deviation-reason records that
|
|
317
|
+
// the change needs no output change (completed_with_warnings).
|
|
318
|
+
const replayed = isObject(assembly.input_change) && assembly.input_change.superseded_build_fingerprint === fingerprint;
|
|
319
|
+
if (replayed && deviationReason) {
|
|
320
|
+
assembly.status = "completed_with_warnings";
|
|
321
|
+
assembly.unchanged_output_reason = {
|
|
322
|
+
text: deviationReason,
|
|
323
|
+
for_inputs: { brief_material: stamps.source_brief_material ?? null, spec_material_hash: stamps.source_spec_material_hash ?? null },
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
if (!replayed || deviationReason) {
|
|
327
|
+
if (writeInputs.stampsCurrent(assembly)) delete assembly.input_change;
|
|
328
|
+
}
|
|
329
|
+
assembly = archiveStageRecord(assembly, previousAssembly, assembly, { by: "record build", reason: detected || "rerecorded", at: now, stageKey: "assembly" });
|
|
330
|
+
// A detected change makes Polish and QA owed through the dependency map.
|
|
331
|
+
// Otherwise Polish evidence bound to this exact output stays and anything
|
|
332
|
+
// else is owed again; the evidence object is kept so `polish capture` has
|
|
333
|
+
// somewhere to attach page_load, and its stale identity fields are removed.
|
|
334
|
+
if (detected) {
|
|
335
|
+
const cause = detected === "spec_material_changed" ? "spec" : "presentation";
|
|
336
|
+
const demoted = demoteStages({ ...report, stages: { ...report.stages, assembly } }, { causes: [cause], now, by: "record build", only: ["polish", "qa"] });
|
|
337
|
+
return { report: demoted.report, context: null };
|
|
338
|
+
}
|
|
288
339
|
const previousPolish = stageObject(report, "polish");
|
|
289
340
|
const polishStillCurrent = stageIsTerminal(String(previousPolish.status || ""))
|
|
290
341
|
&& optionalString(previousPolish.source_build_fingerprint) === fingerprint;
|
|
291
342
|
const polish = polishStillCurrent
|
|
292
343
|
? previousPolish
|
|
293
344
|
: {
|
|
294
|
-
...withoutKeys(previousPolish, ["performed_by", "source_build_fingerprint", "source_package_material_fingerprint", "completed_at", "recorded_by"]),
|
|
345
|
+
...withoutKeys(previousPolish, ["performed_by", "source_build_fingerprint", "source_package_material_fingerprint", "completed_at", "recorded_by", ...STAMP_FIELDS]),
|
|
295
346
|
stage: "polish",
|
|
296
347
|
status: "required",
|
|
297
348
|
required_by: "build",
|
|
@@ -300,8 +351,11 @@ function composeBuild(report, { now, recordedBy, fingerprint, buildEnvironment =
|
|
|
300
351
|
return { report: { ...report, stages: { ...report.stages, assembly, polish } }, context: null };
|
|
301
352
|
}
|
|
302
353
|
|
|
303
|
-
function composePolish(report, { now, recordedBy, fingerprint, input }) {
|
|
354
|
+
function composePolish(report, { now, recordedBy, fingerprint, input, inputs = {} }) {
|
|
304
355
|
const previous = stageObject(report, "polish");
|
|
356
|
+
const completed = POLISH_COMPLETED_STATUSES.includes(input.status);
|
|
357
|
+
const writeInputs = stageWriteInputs("polish", inputs);
|
|
358
|
+
const detected = REPLACED_COMPLETED_STATUSES.includes(previous.status) ? writeInputs.detectChange(previous) : null;
|
|
305
359
|
const previousVisual = isObject(previous.evidence?.visual_review) ? previous.evidence.visual_review : {};
|
|
306
360
|
// A blocked or skipped record given no evidence keeps what is there (the
|
|
307
361
|
// capture's bounded evidence stays for diagnosis).
|
|
@@ -315,13 +369,16 @@ function composePolish(report, { now, recordedBy, fingerprint, input }) {
|
|
|
315
369
|
}
|
|
316
370
|
: previous.evidence;
|
|
317
371
|
const sourcePackageFingerprint = currentSourcePackageMaterialFingerprint(report);
|
|
318
|
-
|
|
319
|
-
...withoutKeys(previous, ["source_package_material_fingerprint", "completed_at", "skip_reason", "evidence"]),
|
|
372
|
+
let polish = {
|
|
373
|
+
...withoutKeys(previous, ["source_package_material_fingerprint", "completed_at", "skip_reason", "evidence", ...STAMP_FIELDS]),
|
|
320
374
|
stage: "polish",
|
|
321
375
|
status: input.status,
|
|
322
376
|
performed_by: POLISH_PRODUCER,
|
|
323
377
|
source_build_fingerprint: fingerprint,
|
|
324
378
|
...(sourcePackageFingerprint ? { source_package_material_fingerprint: sourcePackageFingerprint } : {}),
|
|
379
|
+
// Only a completed Polish is proof against inputs; a skip or a block
|
|
380
|
+
// carries no stamp.
|
|
381
|
+
...(completed ? currentStamps(inputs) : {}),
|
|
325
382
|
// A blocked Polish has not completed.
|
|
326
383
|
...(input.status === "blocked" ? {} : { completed_at: now }),
|
|
327
384
|
recorded_by: recordedBy,
|
|
@@ -329,6 +386,15 @@ function composePolish(report, { now, recordedBy, fingerprint, input }) {
|
|
|
329
386
|
...(input.skipReason ? { skip_reason: input.skipReason } : {}),
|
|
330
387
|
blockers: input.blockers,
|
|
331
388
|
};
|
|
389
|
+
if (detected) polish.input_change = inputChangeFor("polish", previous, { at: now, reason: detected });
|
|
390
|
+
// The change is cleared only by a completed record stamped with the current
|
|
391
|
+
// inputs whose attached package capture postdates it; any other write
|
|
392
|
+
// (a skip included) carries it.
|
|
393
|
+
if (completed && isObject(polish.input_change) && writeInputs.stampsCurrent(polish)) {
|
|
394
|
+
const capturedAt = polish.evidence?.visual_review?.page_load?.captured_at;
|
|
395
|
+
if (typeof capturedAt === "string" && Date.parse(capturedAt) > Date.parse(polish.input_change.at)) delete polish.input_change;
|
|
396
|
+
}
|
|
397
|
+
polish = archiveStageRecord(polish, previous, polish, { by: "record polish", reason: detected || "rerecorded", at: now, stageKey: "polish" });
|
|
332
398
|
const nextReport = { ...report, stages: { ...report.stages, polish } };
|
|
333
399
|
// A null defect on a report with no theme block says nothing to record.
|
|
334
400
|
if (input.hasRepairLoopDefect && (isObject(report.theme) || input.repairLoopDefect !== null)) {
|
|
@@ -618,10 +684,14 @@ function ladderProblems(stage, doctor, report) {
|
|
|
618
684
|
// The rule next's stage picker reads: on the local preview a missing polish
|
|
619
685
|
// is carried forward (local-preview-policy), and next moves on to deploy.
|
|
620
686
|
if (earlier === "polish" && doctor.derived?.polish_gate?.status === CARRIED_FORWARD) continue;
|
|
687
|
+
// The effective status: a stage owed again by an input change reads
|
|
688
|
+
// required, and one whose inputs cannot be confirmed reads unknown.
|
|
621
689
|
const key = reportKeyForCliStage(earlier);
|
|
622
690
|
const status = String(report.stages[key]?.status || "");
|
|
623
|
-
|
|
624
|
-
|
|
691
|
+
const effective = effectiveStageStatus(key, report.stages[key], doctor.derived?.input_currency);
|
|
692
|
+
if (!effectiveStatusIsTerminal(effective)) {
|
|
693
|
+
const why = effective === status ? "" : ` (recorded "${status || "(unset)"}"; ${doctor.derived?.input_currency?.reasons?.[key] || "inputs not confirmed"})`;
|
|
694
|
+
problems.push(`stages.${key}.status is "${effective || "(unset)"}"${why}, so next answers ${earlier}; run ${cmd("record")} ${earlier} first.`);
|
|
625
695
|
}
|
|
626
696
|
}
|
|
627
697
|
return problems;
|
|
@@ -709,7 +779,24 @@ function deployFacts(doctor, packet, probe) {
|
|
|
709
779
|
* records through recordStageCommand.
|
|
710
780
|
*/
|
|
711
781
|
export async function recordCommand(args, options = {}) {
|
|
712
|
-
const { stage, packetPath, baseUrl } = parseRecordArgs(args);
|
|
782
|
+
const { stage, packetPath, baseUrl, briefPath, dryRun } = parseRecordArgs(args);
|
|
783
|
+
if (stage === "brief") {
|
|
784
|
+
return recordBrief({
|
|
785
|
+
packetPath,
|
|
786
|
+
briefPath,
|
|
787
|
+
contextPath: args.context ? resolve(args.context) : undefined,
|
|
788
|
+
reportPath: args.report ? resolve(args.report) : undefined,
|
|
789
|
+
dryRun,
|
|
790
|
+
}, options.now ? { now: options.now } : {});
|
|
791
|
+
}
|
|
792
|
+
if (stage === "spec") {
|
|
793
|
+
return recordSpec({
|
|
794
|
+
packetPath,
|
|
795
|
+
contextPath: args.context ? resolve(args.context) : undefined,
|
|
796
|
+
reportPath: args.report ? resolve(args.report) : undefined,
|
|
797
|
+
dryRun,
|
|
798
|
+
}, options.now ? { now: options.now } : {});
|
|
799
|
+
}
|
|
713
800
|
const probe = stage === "deploy" && existsSync(packetPath)
|
|
714
801
|
? await probeLocalPreview({ packetPath, baseUrl, ...(options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}) })
|
|
715
802
|
: null;
|
|
@@ -735,7 +822,7 @@ function readPacketFile(stage, packetPath) {
|
|
|
735
822
|
* composed or written.
|
|
736
823
|
*/
|
|
737
824
|
export function recordStageCommand(args, { now = () => new Date(), beforeLock = null, afterDoctorRead = null, probe = null } = {}) {
|
|
738
|
-
const { stage, packetPath, evidencePath, dryRun, buildEnvironment } = parseRecordArgs(args);
|
|
825
|
+
const { stage, packetPath, evidencePath, dryRun, buildEnvironment, deviationReason } = parseRecordArgs(args);
|
|
739
826
|
if (!existsSync(packetPath)) throw new Error(`record ${stage}: Build Packet not found at ${packetPath}; run ${cmd("start")} or ${cmd("prepare-build")} first.`);
|
|
740
827
|
if (stage === "deploy" && !probe) throw new Error("record deploy needs the served-route probe; run it through recordCommand.");
|
|
741
828
|
// Operator input, not target state: no campaigns-os writer produces it.
|
|
@@ -756,7 +843,7 @@ export function recordStageCommand(args, { now = () => new Date(), beforeLock =
|
|
|
756
843
|
// A dry run writes nothing, so it takes no lock and creates no lock files
|
|
757
844
|
// (the commitAssemblyReport preview convention); it reads in the same order.
|
|
758
845
|
const run = () => recordUnderLock({
|
|
759
|
-
stage, packetPath, sidecars, lockedTarget, input, dryRun, timestamp, recordedBy, afterDoctorRead, buildEnvironment,
|
|
846
|
+
stage, packetPath, sidecars, lockedTarget, input, dryRun, timestamp, recordedBy, afterDoctorRead, buildEnvironment, deviationReason,
|
|
760
847
|
});
|
|
761
848
|
const recorded = dryRun ? run() : withTargetLockSync(lockedTarget, run, { command: `record ${stage}` });
|
|
762
849
|
const { composed, facts, layer, reportPath, contextPath, after } = recorded;
|
|
@@ -806,7 +893,13 @@ export function recordStageCommand(args, { now = () => new Date(), beforeLock =
|
|
|
806
893
|
// re-check, and the post-write doctor read for `next_stage`. No campaigns-os
|
|
807
894
|
// writer can rebind, rewrite or republish any of them between the read and
|
|
808
895
|
// the write.
|
|
809
|
-
|
|
896
|
+
// The inputs current at this record, as doctor read them under the lock.
|
|
897
|
+
function recordInputs(doctor) {
|
|
898
|
+
const currency = doctor.derived?.input_currency;
|
|
899
|
+
return { briefMaterial: currency?.brief?.current ?? null, specMaterial: currency?.spec?.current ?? null };
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
function recordUnderLock({ stage, packetPath, sidecars, lockedTarget, input, dryRun, timestamp, recordedBy, afterDoctorRead, buildEnvironment = null, deviationReason = null }) {
|
|
810
903
|
// The same workspace `next` resolves, so the record lands in the report
|
|
811
904
|
// `next` reads now, not the one it read before the lock was free.
|
|
812
905
|
const workspace = resolveCampaignWorkspace(packetPath, { ...sidecars, followContextPointer: true });
|
|
@@ -841,13 +934,13 @@ function recordUnderLock({ stage, packetPath, sidecars, lockedTarget, input, dry
|
|
|
841
934
|
const next = stage === "setup"
|
|
842
935
|
? composeSetup(report, context, { now: timestamp, recordedBy })
|
|
843
936
|
: stage === "build"
|
|
844
|
-
? composeBuild(report, { now: timestamp, recordedBy, fingerprint: facts.fingerprint, buildEnvironment })
|
|
937
|
+
? composeBuild(report, { now: timestamp, recordedBy, fingerprint: facts.fingerprint, buildEnvironment, inputs: recordInputs(doctor), deviationReason })
|
|
845
938
|
: stage === "theme"
|
|
846
939
|
? composeTheme(report, { now: timestamp, recordedBy, layer })
|
|
847
940
|
: stage === "deploy"
|
|
848
941
|
? composeDeploy(report, packet, { now: timestamp, recordedBy, probe: input })
|
|
849
|
-
: composePolish(report, { now: timestamp, recordedBy, fingerprint: facts.fingerprint, input });
|
|
850
|
-
applyDerivedAssemblyReportSummary(next.report);
|
|
942
|
+
: composePolish(report, { now: timestamp, recordedBy, fingerprint: facts.fingerprint, input, inputs: recordInputs(doctor) });
|
|
943
|
+
applyDerivedAssemblyReportSummary(next.report, recordInputs(doctor));
|
|
851
944
|
// The packet's one new value, deploy.preview_url, is checked by
|
|
852
945
|
// localPreviewUrl; the rest of the packet is as the operator left it.
|
|
853
946
|
validateRecord(stage, { report: next.report, context: next.context, packet, fingerprint: facts.fingerprint });
|
package/src/tooling-setup.mjs
CHANGED
|
@@ -119,7 +119,7 @@ export function setupTooling(args, { packageRoot, installSkills, installAgentCon
|
|
|
119
119
|
regularDestination(target, dest);
|
|
120
120
|
const source = { "CLAUDE.md": "agents/claude/CLAUDE.md", "AGENTS.md": "agents/codex/AGENTS.md", "campaigns-os.mdc": "agents/cursor/campaigns-os.mdc", "copilot-instructions.md": "agents/copilot/copilot-instructions.md" }[name];
|
|
121
121
|
if (existsSync(dest) && readFileSync(dest, "utf8") !== readFileSync(join(packageRoot, source), "utf8")) {
|
|
122
|
-
throw new Error(`tooling setup: ${dest} differs from this toolkit's context. Preserve and reconcile it before rerunning setup; no files were changed.`);
|
|
122
|
+
throw new Error(`tooling setup: ${dest} differs from this toolkit's context. Preserve and reconcile it before rerunning setup; no files were changed. Refresh it with \`install-agent-context --target .\` (overwrites all four files; review the diff), then rerun setup.`);
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
125
|
const prior = existsSync(instructions) ? readFileSync(instructions, "utf8") : "";
|