vibe-coding-master 0.7.26 → 0.7.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/dist/backend/cli/install-vcm-harness.js +37 -7
- package/dist/backend/server.js +2 -1
- package/dist/backend/services/architect-restart-service.js +17 -2
- package/dist/backend/services/gate-review-service.js +83 -26
- package/dist/backend/templates/handoff.js +29 -15
- package/dist/backend/templates/harness/architect-agent.js +2 -2
- package/dist/backend/templates/harness/coder-agent.js +12 -1
- package/dist/backend/templates/harness/coder-worker-agent.js +12 -1
- package/dist/backend/templates/harness/gate-review.js +11 -5
- package/dist/backend/templates/harness/project-manager-agent.js +7 -3
- package/dist/backend/templates/harness/restart-architect-skill.js +1 -1
- package/dist/backend/templates/harness/tester-agent.js +10 -6
- package/dist/backend/templates/harness/vcm-architecture-interview-skill.js +2 -2
- package/dist/backend/templates/harness/vcm-final-acceptance-skill.js +3 -3
- package/dist/backend/templates/harness/vcm-route-message-skill.js +2 -5
- package/dist/shared/validation/artifact-check.js +103 -49
- package/dist/shared/validation/artifact-contract.js +22 -0
- package/package.json +1 -1
- package/scripts/uninstall-vcm-harness.mjs +5 -0
package/README.md
CHANGED
|
@@ -148,12 +148,15 @@ the active task worktree:
|
|
|
148
148
|
- `.claude/agents/**`
|
|
149
149
|
- `.claude/skills/**`
|
|
150
150
|
- `.claude/settings.json` hooks
|
|
151
|
-
- `.ai/tools/**`
|
|
151
|
+
- VCM protocol and runtime tools under `.ai/tools/**`
|
|
152
152
|
- `.gitignore` entries for VCM runtime state and task worktrees
|
|
153
|
-
- generated-context tooling
|
|
153
|
+
- initial project-owned generated-context tooling
|
|
154
154
|
- pull request template
|
|
155
155
|
|
|
156
156
|
VCM preserves user-authored content outside VCM managed blocks.
|
|
157
|
+
VCM seeds `.ai/tools/generate-module-index` and
|
|
158
|
+
`.ai/tools/generate-public-surface` only when missing. After installation these
|
|
159
|
+
generators belong to the project and later harness updates do not replace them.
|
|
157
160
|
|
|
158
161
|
The fixed harness install is deterministic and creates a commit in the active
|
|
159
162
|
task worktree. Bootstrap is AI-assisted and is run through the Harness Engineer
|
|
@@ -229,13 +229,7 @@ const DURABLE_DOC_TEMPLATES = [
|
|
|
229
229
|
content: "# Testing\n"
|
|
230
230
|
},
|
|
231
231
|
];
|
|
232
|
-
const
|
|
233
|
-
{
|
|
234
|
-
path: ".ai/tools/check-durable-docs",
|
|
235
|
-
category: "durable-docs-tool",
|
|
236
|
-
mode: 0o755,
|
|
237
|
-
templatePath: "scripts/harness-tools/check-durable-docs"
|
|
238
|
-
},
|
|
232
|
+
const PROJECT_OWNED_FILES = [
|
|
239
233
|
{
|
|
240
234
|
path: ".ai/tools/generate-module-index",
|
|
241
235
|
category: "generated-context-tool",
|
|
@@ -247,6 +241,14 @@ const WHOLE_FILES = [
|
|
|
247
241
|
category: "generated-context-tool",
|
|
248
242
|
mode: 0o755,
|
|
249
243
|
templatePath: "scripts/harness-tools/generate-public-surface"
|
|
244
|
+
}
|
|
245
|
+
];
|
|
246
|
+
const WHOLE_FILES = [
|
|
247
|
+
{
|
|
248
|
+
path: ".ai/tools/check-durable-docs",
|
|
249
|
+
category: "durable-docs-tool",
|
|
250
|
+
mode: 0o755,
|
|
251
|
+
templatePath: "scripts/harness-tools/check-durable-docs"
|
|
250
252
|
},
|
|
251
253
|
{
|
|
252
254
|
path: ".claude/skills/vcm-architecture-interview/SKILL.md",
|
|
@@ -397,6 +399,9 @@ async function main() {
|
|
|
397
399
|
for (const file of WHOLE_FILES) {
|
|
398
400
|
await installWholeFile({ projectRoot, file, dryRun, operations });
|
|
399
401
|
}
|
|
402
|
+
for (const file of PROJECT_OWNED_FILES) {
|
|
403
|
+
await installProjectOwnedFile({ projectRoot, file, dryRun, operations });
|
|
404
|
+
}
|
|
400
405
|
await removeLegacyFlatSkillFiles({ projectRoot, dryRun, operations });
|
|
401
406
|
await removeLegacyCodexHarnessPaths({ projectRoot, dryRun, operations });
|
|
402
407
|
await installManifest({
|
|
@@ -511,6 +516,14 @@ async function buildManifest(projectRoot) {
|
|
|
511
516
|
...fixedDirectories().map((directory) => manifestEntry(directory, "directory", directoryCategory(directory), "vcm-created")),
|
|
512
517
|
manifestEntry("docs/GLOSSARY.md", "file", "project-glossary", "project-owned"),
|
|
513
518
|
manifestEntry("docs/CODING_STANDARDS.md", "file", "project-coding-standards", "project-owned"),
|
|
519
|
+
...PROJECT_OWNED_FILES.map((file) => ({
|
|
520
|
+
path: file.path,
|
|
521
|
+
entryType: "file",
|
|
522
|
+
category: file.category,
|
|
523
|
+
ownership: "project-owned",
|
|
524
|
+
source: "vcm-template",
|
|
525
|
+
lifecycle: "long-term"
|
|
526
|
+
})),
|
|
514
527
|
...WHOLE_FILES.map((file) => ({
|
|
515
528
|
path: file.path,
|
|
516
529
|
entryType: "file",
|
|
@@ -820,6 +833,23 @@ async function installWholeFile({ projectRoot, file, dryRun, operations }) {
|
|
|
820
833
|
action: "write fixed VCM file"
|
|
821
834
|
});
|
|
822
835
|
}
|
|
836
|
+
async function installProjectOwnedFile({ projectRoot, file, dryRun, operations }) {
|
|
837
|
+
const targetPath = resolveInside(projectRoot, file.path);
|
|
838
|
+
if (await pathExists(targetPath)) {
|
|
839
|
+
operations.push(skip(file.path, "exists; project-owned"));
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
const content = await wholeFileContent(file);
|
|
843
|
+
await writeIfChanged({
|
|
844
|
+
targetPath,
|
|
845
|
+
relativePath: file.path,
|
|
846
|
+
content: ensureTrailingNewline(content),
|
|
847
|
+
mode: file.mode,
|
|
848
|
+
dryRun,
|
|
849
|
+
operations,
|
|
850
|
+
action: "seed project-owned VCM file"
|
|
851
|
+
});
|
|
852
|
+
}
|
|
823
853
|
async function removeLegacyFlatSkillFiles({ projectRoot, dryRun, operations }) {
|
|
824
854
|
const wholeFilesByPath = new Map(WHOLE_FILES.map((file) => [file.path, file]));
|
|
825
855
|
for (const legacy of LEGACY_FLAT_SKILL_FILES) {
|
package/dist/backend/server.js
CHANGED
|
@@ -312,7 +312,8 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
312
312
|
taskService,
|
|
313
313
|
appSettings,
|
|
314
314
|
sessionService,
|
|
315
|
-
roundService
|
|
315
|
+
roundService,
|
|
316
|
+
onArchitecturePlanDisposition: ({ repoRoot, taskSlug, accepted }) => architectRestartService.recordArchitectureGateDisposition(repoRoot, taskSlug, accepted)
|
|
316
317
|
});
|
|
317
318
|
const translationWorkerService = createTranslationWorkerService({
|
|
318
319
|
fs,
|
|
@@ -14,7 +14,7 @@ Before performing any assigned work, read:
|
|
|
14
14
|
- the current scaffold commit and worktree state
|
|
15
15
|
- the latest Gate Review report when present
|
|
16
16
|
|
|
17
|
-
Treat the current artifacts and worktree as the source of truth. Do not repeat the completed interview or planning work unless
|
|
17
|
+
Treat the current artifacts and worktree as the source of truth. The architecture-plan Gate has accepted the current planning artifacts or VCM recorded an explicit Gate exception. Do not repeat the completed interview or planning work unless a later route explicitly reopens it.`;
|
|
18
18
|
export function createArchitectRestartService(deps) {
|
|
19
19
|
const pendingByTask = new Map();
|
|
20
20
|
return {
|
|
@@ -24,6 +24,11 @@ export function createArchitectRestartService(deps) {
|
|
|
24
24
|
const key = taskKey(repoRoot, taskSlug);
|
|
25
25
|
const existing = pendingByTask.get(key);
|
|
26
26
|
if (existing?.sessionId === session.id) {
|
|
27
|
+
existing.stopped = false;
|
|
28
|
+
existing.deliveredMessageId = undefined;
|
|
29
|
+
existing.acceptedMessageId = undefined;
|
|
30
|
+
existing.gateAccepted = false;
|
|
31
|
+
existing.executing = false;
|
|
27
32
|
return { taskSlug, sessionId: session.id, status: "scheduled" };
|
|
28
33
|
}
|
|
29
34
|
pendingByTask.set(key, {
|
|
@@ -31,6 +36,7 @@ export function createArchitectRestartService(deps) {
|
|
|
31
36
|
taskSlug,
|
|
32
37
|
sessionId: session.id,
|
|
33
38
|
stopped: false,
|
|
39
|
+
gateAccepted: false,
|
|
34
40
|
executing: false
|
|
35
41
|
});
|
|
36
42
|
return { taskSlug, sessionId: session.id, status: "scheduled" };
|
|
@@ -65,6 +71,14 @@ export function createArchitectRestartService(deps) {
|
|
|
65
71
|
pending.acceptedMessageId = message.id;
|
|
66
72
|
await tryRestart(pending);
|
|
67
73
|
},
|
|
74
|
+
async recordArchitectureGateDisposition(repoRoot, taskSlug, accepted) {
|
|
75
|
+
const pending = pendingByTask.get(taskKey(repoRoot, taskSlug));
|
|
76
|
+
if (!pending) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
pending.gateAccepted = accepted;
|
|
80
|
+
await tryRestart(pending);
|
|
81
|
+
},
|
|
68
82
|
clear(repoRoot, taskSlug) {
|
|
69
83
|
pendingByTask.delete(taskKey(repoRoot, taskSlug));
|
|
70
84
|
}
|
|
@@ -95,7 +109,8 @@ export function createArchitectRestartService(deps) {
|
|
|
95
109
|
if (pending.executing
|
|
96
110
|
|| !pending.stopped
|
|
97
111
|
|| !pending.deliveredMessageId
|
|
98
|
-
|| pending.deliveredMessageId !== pending.acceptedMessageId
|
|
112
|
+
|| pending.deliveredMessageId !== pending.acceptedMessageId
|
|
113
|
+
|| !pending.gateAccepted) {
|
|
99
114
|
return;
|
|
100
115
|
}
|
|
101
116
|
const session = await deps.sessionService.getRoleSession(pending.repoRoot, pending.taskSlug, ARCHITECT_ROLE);
|
|
@@ -121,6 +121,7 @@ export function createGateReviewService(deps) {
|
|
|
121
121
|
error: undefined
|
|
122
122
|
}, now());
|
|
123
123
|
await saveIndex(deps.fs, context.taskRepoRoot, index);
|
|
124
|
+
await notifyArchitecturePlanDisposition(context, gate, true);
|
|
124
125
|
return { status: "disabled", gate, record: index.gates[gate], message: "Gate review is disabled." };
|
|
125
126
|
}
|
|
126
127
|
if (!record.required) {
|
|
@@ -130,6 +131,7 @@ export function createGateReviewService(deps) {
|
|
|
130
131
|
error: undefined
|
|
131
132
|
}, now());
|
|
132
133
|
await saveIndex(deps.fs, context.taskRepoRoot, index);
|
|
134
|
+
await notifyArchitecturePlanDisposition(context, gate, true);
|
|
133
135
|
return { status: "not_required", gate, record: index.gates[gate], message: "This gate is not required." };
|
|
134
136
|
}
|
|
135
137
|
if (index.activeGate && index.activeGate !== gate) {
|
|
@@ -348,6 +350,7 @@ export function createGateReviewService(deps) {
|
|
|
348
350
|
&& record.status === "completed"
|
|
349
351
|
&& record.decision === "approve"
|
|
350
352
|
&& record.inputHash === inputHash) {
|
|
353
|
+
await notifyArchitecturePlanDisposition(context, gate, true);
|
|
351
354
|
return {
|
|
352
355
|
status: "already_approved",
|
|
353
356
|
gate,
|
|
@@ -359,6 +362,7 @@ export function createGateReviewService(deps) {
|
|
|
359
362
|
const requestId = createRequestId(gate);
|
|
360
363
|
const requestPath = path.posix.join(REQUESTS_DIR, `${requestId}.json`);
|
|
361
364
|
const promptPath = path.posix.join(REQUESTS_DIR, `${requestId}.prompt.md`);
|
|
365
|
+
const requestReportPath = reportPathForRequest(requestId);
|
|
362
366
|
const nextRecord = {
|
|
363
367
|
...record,
|
|
364
368
|
status: "running",
|
|
@@ -402,10 +406,12 @@ export function createGateReviewService(deps) {
|
|
|
402
406
|
codeDiffSource,
|
|
403
407
|
codeDiffSources,
|
|
404
408
|
codeDiff: codeDiffInput,
|
|
405
|
-
reportPath:
|
|
409
|
+
reportPath: requestReportPath,
|
|
410
|
+
latestReportPath: nextRecord.reportPath,
|
|
406
411
|
promptPath: nextRecord.promptPath
|
|
407
412
|
});
|
|
408
413
|
await saveIndex(deps.fs, context.taskRepoRoot, index);
|
|
414
|
+
await notifyArchitecturePlanDisposition(context, gate, false);
|
|
409
415
|
void runGateReview(context, gate, requestId, codeDiffInput, codeDiffSources).catch(() => {
|
|
410
416
|
// runGateReview records failures in the persisted gate state.
|
|
411
417
|
});
|
|
@@ -456,11 +462,15 @@ export function createGateReviewService(deps) {
|
|
|
456
462
|
eventName: "UserPromptSubmit"
|
|
457
463
|
});
|
|
458
464
|
const parsed = await waitForGateReport(deps.fs, context.taskRepoRoot, gate, requestId, now(), reportPollIntervalMs);
|
|
465
|
+
await publishLatestGateReport(deps.fs, context.taskRepoRoot, gate, parsed.content);
|
|
459
466
|
const completedAt = now();
|
|
460
467
|
await updateRequestStatus(deps.fs, context, requestId, "completed", {
|
|
461
468
|
completedAt,
|
|
462
469
|
decision: parsed.decision,
|
|
463
|
-
|
|
470
|
+
summary: parsed.summary,
|
|
471
|
+
findings: parsed.findings,
|
|
472
|
+
reportPath: parsed.reportPath,
|
|
473
|
+
latestReportPath: reportPathForGate(gate)
|
|
464
474
|
});
|
|
465
475
|
activeRuns.delete(runKey);
|
|
466
476
|
await updateGateRecord(context, gate, {
|
|
@@ -474,6 +484,7 @@ export function createGateReviewService(deps) {
|
|
|
474
484
|
callbackError: undefined,
|
|
475
485
|
updatedAt: completedAt
|
|
476
486
|
}, { clearActiveGate: true });
|
|
487
|
+
await notifyArchitecturePlanDisposition(context, gate, parsed.decision === "approve");
|
|
477
488
|
await callbackProjectManager(context, gate, "completed", parsed.decision, parsed.reportPath);
|
|
478
489
|
}
|
|
479
490
|
catch (error) {
|
|
@@ -492,7 +503,8 @@ export function createGateReviewService(deps) {
|
|
|
492
503
|
callbackError: undefined,
|
|
493
504
|
updatedAt: timestamp
|
|
494
505
|
}, { clearActiveGate: true });
|
|
495
|
-
await
|
|
506
|
+
await notifyArchitecturePlanDisposition(context, gate, false);
|
|
507
|
+
await callbackProjectManager(context, gate, "failed", undefined, reportPathForRequest(requestId), message);
|
|
496
508
|
}
|
|
497
509
|
finally {
|
|
498
510
|
activeRuns.delete(runKey);
|
|
@@ -570,6 +582,21 @@ export function createGateReviewService(deps) {
|
|
|
570
582
|
});
|
|
571
583
|
}
|
|
572
584
|
}
|
|
585
|
+
async function notifyArchitecturePlanDisposition(context, gate, accepted) {
|
|
586
|
+
if (gate !== "architecture-plan" || !deps.onArchitecturePlanDisposition) {
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
try {
|
|
590
|
+
await deps.onArchitecturePlanDisposition({
|
|
591
|
+
repoRoot: context.repoRoot,
|
|
592
|
+
taskSlug: context.taskSlug,
|
|
593
|
+
accepted
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
catch {
|
|
597
|
+
// Gate state remains authoritative even if the deferred session restart cannot run yet.
|
|
598
|
+
}
|
|
599
|
+
}
|
|
573
600
|
return {
|
|
574
601
|
async getState(repoRoot, taskSlug) {
|
|
575
602
|
const context = await getContext(repoRoot, taskSlug);
|
|
@@ -628,6 +655,7 @@ export function createGateReviewService(deps) {
|
|
|
628
655
|
callbackError: undefined,
|
|
629
656
|
updatedAt: now()
|
|
630
657
|
}, { clearActiveGate: true });
|
|
658
|
+
await notifyArchitecturePlanDisposition(context, gate, true);
|
|
631
659
|
await callbackProjectManager(context, gate, "skipped", undefined, index.gates[gate].reportPath);
|
|
632
660
|
return loadIndex(deps.fs, context, now());
|
|
633
661
|
},
|
|
@@ -653,12 +681,13 @@ export function createGateReviewService(deps) {
|
|
|
653
681
|
callbackError: undefined,
|
|
654
682
|
updatedAt: now()
|
|
655
683
|
}, { clearActiveGate: true });
|
|
684
|
+
await notifyArchitecturePlanDisposition(context, gate, true);
|
|
656
685
|
await callbackProjectManager(context, gate, "overridden", "approve", index.gates[gate].reportPath);
|
|
657
686
|
return loadIndex(deps.fs, context, now());
|
|
658
687
|
},
|
|
659
688
|
async readReport(repoRoot, taskSlug, gate) {
|
|
660
689
|
const context = await getContext(repoRoot, taskSlug);
|
|
661
|
-
return parseGateReport(deps.fs, context.taskRepoRoot, gate, undefined, now());
|
|
690
|
+
return parseGateReport(deps.fs, context.taskRepoRoot, gate, undefined, now(), reportPathForGate(gate));
|
|
662
691
|
}
|
|
663
692
|
};
|
|
664
693
|
}
|
|
@@ -985,10 +1014,12 @@ async function readArchitectureBriefError(fs, taskRepoRoot) {
|
|
|
985
1014
|
const content = await fs.readText(absolutePath);
|
|
986
1015
|
const check = checkMarkdownArtifact("architecture-brief", relativePath, content);
|
|
987
1016
|
if (check.status !== "ok") {
|
|
988
|
-
return `${relativePath} is incomplete
|
|
1017
|
+
return `${relativePath} is incomplete and cannot start architecture-plan review. ${formatArtifactCheckFailure(check)}`;
|
|
989
1018
|
}
|
|
990
|
-
|
|
991
|
-
|
|
1019
|
+
const status = /^\s*Architecture Brief Status\s*:\s*(.+?)\s*$/im.exec(content)?.[1]?.trim();
|
|
1020
|
+
if (status?.toLowerCase() !== "confirmed") {
|
|
1021
|
+
return `${relativePath} is not confirmed and cannot start architecture-plan review. `
|
|
1022
|
+
+ `Architecture Brief Status must be exactly "confirmed"; found ${renderFoundValue(status)}.`;
|
|
992
1023
|
}
|
|
993
1024
|
return undefined;
|
|
994
1025
|
}
|
|
@@ -1003,12 +1034,8 @@ async function readValidationReportError(fs, taskRepoRoot) {
|
|
|
1003
1034
|
if (check.status === "ok") {
|
|
1004
1035
|
return undefined;
|
|
1005
1036
|
}
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
check.invalidFields.length > 0 ? check.invalidFields.join(" ") : "",
|
|
1009
|
-
check.hasPlaceholder ? "contains placeholders" : ""
|
|
1010
|
-
].filter(Boolean).join("; ");
|
|
1011
|
-
return `${relativePath} is incomplete and cannot start validation-adequacy review.${details ? ` ${details}` : ""}`;
|
|
1037
|
+
return `${relativePath} is incomplete and cannot start validation-adequacy review. `
|
|
1038
|
+
+ formatValidationArtifactFailure(check, content);
|
|
1012
1039
|
}
|
|
1013
1040
|
async function readArchitectureEvidenceError(fs, taskRepoRoot) {
|
|
1014
1041
|
const relativePath = ".ai/vcm/handoffs/architecture-evidence.md";
|
|
@@ -1020,8 +1047,10 @@ async function readArchitectureEvidenceError(fs, taskRepoRoot) {
|
|
|
1020
1047
|
if (content.trim().length === 0) {
|
|
1021
1048
|
return `${relativePath} is empty. Complete architecture evidence before requesting architecture-plan review.`;
|
|
1022
1049
|
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1050
|
+
const status = /^\s*Architecture Evidence Status\s*:\s*(.+?)\s*$/im.exec(content)?.[1]?.trim();
|
|
1051
|
+
if (status?.toLowerCase() !== "complete") {
|
|
1052
|
+
return `${relativePath} is incomplete and cannot start architecture-plan review. `
|
|
1053
|
+
+ `Architecture Evidence Status must be exactly "complete"; found ${renderFoundValue(status)}.`;
|
|
1025
1054
|
}
|
|
1026
1055
|
return undefined;
|
|
1027
1056
|
}
|
|
@@ -1036,7 +1065,7 @@ function splitLines(value) {
|
|
|
1036
1065
|
.filter(Boolean);
|
|
1037
1066
|
}
|
|
1038
1067
|
function buildGatePrompt(context, gate, requestId, codeDiffInput, codeDiffSources) {
|
|
1039
|
-
const reportPath =
|
|
1068
|
+
const reportPath = reportPathForRequest(requestId);
|
|
1040
1069
|
const absoluteReportPath = resolveRepoPath(context.taskRepoRoot, reportPath);
|
|
1041
1070
|
const evidence = getSourceArtifacts(gate, codeDiffSources)
|
|
1042
1071
|
.map((relativePath) => `- ${relativePath}`)
|
|
@@ -1089,9 +1118,10 @@ Summary: <one or two sentences>
|
|
|
1089
1118
|
[/VCM GATE REVIEW]`;
|
|
1090
1119
|
}
|
|
1091
1120
|
async function waitForGateReport(fs, taskRepoRoot, gate, requestId, timestamp, intervalMs) {
|
|
1121
|
+
const reportPath = reportPathForRequest(requestId);
|
|
1092
1122
|
while (true) {
|
|
1093
1123
|
try {
|
|
1094
|
-
return await parseGateReport(fs, taskRepoRoot, gate, requestId, timestamp);
|
|
1124
|
+
return await parseGateReport(fs, taskRepoRoot, gate, requestId, timestamp, reportPath);
|
|
1095
1125
|
}
|
|
1096
1126
|
catch (error) {
|
|
1097
1127
|
if (!isPendingReportError(error)) {
|
|
@@ -1101,8 +1131,7 @@ async function waitForGateReport(fs, taskRepoRoot, gate, requestId, timestamp, i
|
|
|
1101
1131
|
await delay(intervalMs);
|
|
1102
1132
|
}
|
|
1103
1133
|
}
|
|
1104
|
-
async function parseGateReport(fs, taskRepoRoot, gate, requestId, timestamp) {
|
|
1105
|
-
const reportPath = reportPathForGate(gate);
|
|
1134
|
+
async function parseGateReport(fs, taskRepoRoot, gate, requestId, timestamp, reportPath) {
|
|
1106
1135
|
const absolutePath = resolveRepoPath(taskRepoRoot, reportPath);
|
|
1107
1136
|
if (!(await fs.pathExists(absolutePath))) {
|
|
1108
1137
|
throw new VcmError({
|
|
@@ -1231,18 +1260,35 @@ async function validateValidationApprovalInput(fs, taskRepoRoot) {
|
|
|
1231
1260
|
if (check.status === "ok") {
|
|
1232
1261
|
return;
|
|
1233
1262
|
}
|
|
1234
|
-
const details = [
|
|
1235
|
-
`status=${check.status}`,
|
|
1236
|
-
check.missingHeadings.length > 0 ? `missing headings: ${check.missingHeadings.join(", ")}` : "",
|
|
1237
|
-
check.invalidFields.length > 0 ? check.invalidFields.join(" ") : "",
|
|
1238
|
-
check.hasPlaceholder ? "contains placeholders" : ""
|
|
1239
|
-
].filter(Boolean).join("; ");
|
|
1240
1263
|
throw new VcmError({
|
|
1241
1264
|
code: "GATE_REVIEW_VALIDATION_INPUT_INCOMPLETE",
|
|
1242
|
-
message: `Validation-adequacy cannot approve incomplete Tester evidence in ${relativePath}.
|
|
1265
|
+
message: `Validation-adequacy cannot approve incomplete Tester evidence in ${relativePath}. `
|
|
1266
|
+
+ formatValidationArtifactFailure(check, content),
|
|
1243
1267
|
statusCode: 500
|
|
1244
1268
|
});
|
|
1245
1269
|
}
|
|
1270
|
+
function formatValidationArtifactFailure(check, content) {
|
|
1271
|
+
return /^\s*Test Result\s*:\s*incomplete\s*$/im.test(content ?? "")
|
|
1272
|
+
? 'Test Result must be exactly one of "pass|fail"; found "incomplete".'
|
|
1273
|
+
: formatArtifactCheckFailure(check);
|
|
1274
|
+
}
|
|
1275
|
+
function formatArtifactCheckFailure(check) {
|
|
1276
|
+
const details = [
|
|
1277
|
+
check.status === "missing" ? "Artifact is missing." : "",
|
|
1278
|
+
check.status === "empty" ? "Artifact is empty." : "",
|
|
1279
|
+
check.missingHeadings.length > 0
|
|
1280
|
+
? `Missing headings: ${check.missingHeadings.join(", ")}.`
|
|
1281
|
+
: "",
|
|
1282
|
+
...check.invalidFields,
|
|
1283
|
+
check.hasPlaceholder ? "Replace every standalone TBD, Not run yet, or draft-status placeholder." : ""
|
|
1284
|
+
].filter(Boolean);
|
|
1285
|
+
return details.length > 0
|
|
1286
|
+
? details.join(" ")
|
|
1287
|
+
: "Artifact is not in a gate-ready terminal state.";
|
|
1288
|
+
}
|
|
1289
|
+
function renderFoundValue(value) {
|
|
1290
|
+
return value && value.trim().length > 0 ? JSON.stringify(value.trim()) : "<missing>";
|
|
1291
|
+
}
|
|
1246
1292
|
function validateRequestChangeFindings(findings) {
|
|
1247
1293
|
if (findings.length === 0) {
|
|
1248
1294
|
throw new VcmError({
|
|
@@ -1296,9 +1342,20 @@ async function updateRequestStatus(fs, context, requestId, status, patch) {
|
|
|
1296
1342
|
updatedAt: new Date().toISOString()
|
|
1297
1343
|
});
|
|
1298
1344
|
}
|
|
1345
|
+
async function publishLatestGateReport(fs, taskRepoRoot, gate, content) {
|
|
1346
|
+
const latestPath = resolveRepoPath(taskRepoRoot, reportPathForGate(gate));
|
|
1347
|
+
if (fs.writeTextAtomic) {
|
|
1348
|
+
await fs.writeTextAtomic(latestPath, content);
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
await fs.writeText(latestPath, content);
|
|
1352
|
+
}
|
|
1299
1353
|
function reportPathForGate(gate) {
|
|
1300
1354
|
return path.posix.join(GATE_REVIEW_DIR, `${gate}-review.md`);
|
|
1301
1355
|
}
|
|
1356
|
+
function reportPathForRequest(requestId) {
|
|
1357
|
+
return path.posix.join(REQUESTS_DIR, `${requestId}.report.md`);
|
|
1358
|
+
}
|
|
1302
1359
|
function promptPathForRequest(requestId) {
|
|
1303
1360
|
return path.posix.join(REQUESTS_DIR, `${requestId}.prompt.md`);
|
|
1304
1361
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { ARCHITECTURE_BRIEF_STATUSES, ARCHITECTURE_PLAN_RESULTS, DOCS_SYNC_DECISIONS, FINAL_ACCEPTANCE_DECISIONS, L3_ACTIONS, L3_REQUIRED_VALUES, STRICT_NONE_VALUE, TEST_RESULTS, renderArtifactOptions } from "../../shared/validation/artifact-contract.js";
|
|
1
2
|
export function renderArchitectureBriefTemplate(taskSlug) {
|
|
2
3
|
return `# Architecture Brief: ${taskSlug}
|
|
3
4
|
|
|
4
|
-
Architecture Brief Status:
|
|
5
|
+
Architecture Brief Status: ${renderArtifactOptions(ARCHITECTURE_BRIEF_STATUSES)}
|
|
5
6
|
|
|
6
7
|
## Accepted Outcome
|
|
7
8
|
|
|
@@ -17,7 +18,7 @@ TBD
|
|
|
17
18
|
|
|
18
19
|
## Unresolved User Decisions
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
${STRICT_NONE_VALUE}
|
|
21
22
|
|
|
22
23
|
## User Confirmation
|
|
23
24
|
|
|
@@ -27,7 +28,7 @@ TBD
|
|
|
27
28
|
export function renderArchitecturePlanTemplate(taskSlug) {
|
|
28
29
|
return `# Architecture Plan: ${taskSlug}
|
|
29
30
|
|
|
30
|
-
Planning Result:
|
|
31
|
+
Planning Result: ${renderArtifactOptions(ARCHITECTURE_PLAN_RESULTS)}
|
|
31
32
|
|
|
32
33
|
## Accepted Scope
|
|
33
34
|
|
|
@@ -100,10 +101,13 @@ TBD
|
|
|
100
101
|
Task-specific context and coder guidance go here, not in source-code comments.
|
|
101
102
|
Source-code comments should only describe durable behavior, contracts, invariants,
|
|
102
103
|
error boundaries, or non-obvious logic that should remain useful after this task.
|
|
104
|
+
Use an ID matching \`[A-Z]{2,6}-[0-9]{1,4}\`, choose exactly one Action
|
|
105
|
+
(\`create\`, \`change\`, or \`delete\`), put the repo-relative File path in
|
|
106
|
+
backticks, and enumerate every implementation item explicitly.
|
|
103
107
|
|
|
104
108
|
| ID | Action | File | Symbol Or Site | Coder Work | Allowed Implementation Freedom | Behavior / Contract Proof Point |
|
|
105
109
|
| --- | --- | --- | --- | --- | --- | --- |
|
|
106
|
-
|
|
|
110
|
+
| <ID> | <create|change|delete> | \`<repo-relative-file>\` | TBD | TBD | TBD | TBD |
|
|
107
111
|
|
|
108
112
|
## Scaffold Build Evidence
|
|
109
113
|
|
|
@@ -143,7 +147,7 @@ At task close, promote still-relevant confirmed issues to \`docs/known-issues.md
|
|
|
143
147
|
export function renderTestReportTemplate(taskSlug) {
|
|
144
148
|
return `# Test Report: ${taskSlug}
|
|
145
149
|
|
|
146
|
-
Test Result:
|
|
150
|
+
Test Result: ${renderArtifactOptions(TEST_RESULTS)}
|
|
147
151
|
|
|
148
152
|
## Evidence Reviewed
|
|
149
153
|
|
|
@@ -157,9 +161,19 @@ TBD
|
|
|
157
161
|
|
|
158
162
|
TBD
|
|
159
163
|
|
|
164
|
+
## Validation Progress
|
|
165
|
+
|
|
166
|
+
### Completed Validation
|
|
167
|
+
|
|
168
|
+
TBD
|
|
169
|
+
|
|
170
|
+
### Remaining Validation
|
|
171
|
+
|
|
172
|
+
${STRICT_NONE_VALUE}
|
|
173
|
+
|
|
160
174
|
## L3 Coverage
|
|
161
175
|
|
|
162
|
-
L3 Required:
|
|
176
|
+
L3 Required: ${renderArtifactOptions(L3_REQUIRED_VALUES)}
|
|
163
177
|
|
|
164
178
|
### Trigger Assessment
|
|
165
179
|
|
|
@@ -169,7 +183,7 @@ TBD
|
|
|
169
183
|
|
|
170
184
|
| Flow | Trigger | Case ID | Test File | Entry Point | Final Observable Result | Action | Result |
|
|
171
185
|
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
172
|
-
| TBD | TBD | TBD | TBD | TBD | TBD |
|
|
186
|
+
| TBD | TBD | TBD | TBD | TBD | TBD | <${renderArtifactOptions(L3_ACTIONS)}> | TBD |
|
|
173
187
|
|
|
174
188
|
### L3 Commands And Evidence
|
|
175
189
|
|
|
@@ -189,27 +203,27 @@ TBD
|
|
|
189
203
|
|
|
190
204
|
## Failed Expectations
|
|
191
205
|
|
|
192
|
-
|
|
206
|
+
${STRICT_NONE_VALUE}
|
|
193
207
|
|
|
194
208
|
## Reproduction Steps
|
|
195
209
|
|
|
196
|
-
|
|
210
|
+
${STRICT_NONE_VALUE}
|
|
197
211
|
|
|
198
212
|
## Skipped Checks With Reasons
|
|
199
213
|
|
|
200
|
-
|
|
214
|
+
${STRICT_NONE_VALUE}
|
|
201
215
|
|
|
202
216
|
## Coverage Gaps
|
|
203
217
|
|
|
204
|
-
|
|
218
|
+
${STRICT_NONE_VALUE}
|
|
205
219
|
|
|
206
220
|
## Blocking Validation Issues
|
|
207
221
|
|
|
208
|
-
|
|
222
|
+
${STRICT_NONE_VALUE}
|
|
209
223
|
|
|
210
224
|
## User Approval Evidence
|
|
211
225
|
|
|
212
|
-
|
|
226
|
+
${STRICT_NONE_VALUE}
|
|
213
227
|
`;
|
|
214
228
|
}
|
|
215
229
|
export function renderCoderCompletionTemplate(taskSlug) {
|
|
@@ -338,7 +352,7 @@ TBD
|
|
|
338
352
|
|
|
339
353
|
## Decision
|
|
340
354
|
|
|
341
|
-
|
|
355
|
+
${renderArtifactOptions(DOCS_SYNC_DECISIONS)}
|
|
342
356
|
`;
|
|
343
357
|
}
|
|
344
358
|
export function renderFinalAcceptanceTemplate(taskSlug) {
|
|
@@ -346,7 +360,7 @@ export function renderFinalAcceptanceTemplate(taskSlug) {
|
|
|
346
360
|
|
|
347
361
|
## Decision
|
|
348
362
|
|
|
349
|
-
|
|
363
|
+
${renderArtifactOptions(FINAL_ACCEPTANCE_DECISIONS)}
|
|
350
364
|
|
|
351
365
|
## Evidence Reviewed
|
|
352
366
|
|
|
@@ -78,7 +78,7 @@ ${renderRoleMemoryRules("architect")}
|
|
|
78
78
|
- \`Architecture Decision\`: use the required Changed Behavior Flow, Ownership, Data Flow, Lifecycle, Boundaries, Invariants, Failure Model, and Decision Rationale subsections. Describe why the design fits verified current code.
|
|
79
79
|
- \`Module/File Plan\`: list each affected module, changed or created file, file responsibility, why it is in scope, expected change, dependency direction, user-visible behavior change, durable comment needs, and every non-private callable surface intended for use outside its file. For every ledger item that consumes or sources cross-module data, name the module and symbol that owns or produces the data, trace the source-to-consumer path, and identify every field, parameter, accessor, trait method, command field, dependency, or other cross-file surface required by that path.
|
|
80
80
|
- \`Public Surface Impact\`: state changed APIs, routes, commands, events, exports, storage formats, configuration, UI behavior, visibility changes, side effects, error boundaries, expected callers, or explicitly state none.
|
|
81
|
-
- \`Scaffold Manifest\`: an item ledger — one entry per implementation item. An item is one
|
|
81
|
+
- \`Scaffold Manifest\`: an item ledger — one entry per implementation item. Use columns in the exact order \`ID | Action | File | ...\`; use an ID matching \`AA-1\` through \`AAAAAA-9999\`, an Action of exactly \`create\`, \`change\`, or \`delete\`, and a backticked repo-relative File path. An item is one created body or surface, one required change site — one contiguous edit region inside an existing body or surface — or one deletion of a body, site, or file. An item not in the ledger is not in the plan; coder must not implement it.
|
|
82
82
|
- Each ledger entry carries, in this column order: a unique stable ID such as \`SCF-001\`, action, exact file path, symbol or site, coder work, allowed implementation freedom, and a behavior/contract proof point. Per-file evidence, why-in-scope, and durable-comment needs live in the Module/File Plan, not in the ledger. Open-ended coverage language ("as work proceeds", "replicate", "etc.", "and others") is forbidden anywhere in the ledger.
|
|
83
83
|
- IDs and markers correspond one to one: every \`create\`, \`change\`, and \`delete\` entry has exactly one \`VCM:CODE <ID>\` marker pre-placed at its declared file and site; a \`delete\` marker sits on the code to be removed and leaves with it.
|
|
84
84
|
- The Scaffold Manifest is complete only when the ledger ID set and the tree's \`VCM:CODE\` ID set are equal, each ID appears exactly once on each side, and each marker sits in its declared file (\`.ai/tools/check-scaffold-ledger\` automates the check). Any mismatch means the plan is not complete.
|
|
@@ -116,7 +116,7 @@ ${renderRoleMemoryRules("architect")}
|
|
|
116
116
|
#### Planning Completion
|
|
117
117
|
|
|
118
118
|
- After the complete plan, scaffold, reconciliation, L0 evidence, and commits are ready, use the \`restart-architect\` skill before writing the completed Architect-to-PM route message.
|
|
119
|
-
- After VCM reports the restart is scheduled, write the route message with both architecture artifacts and the plan, then end the turn. Do not wait for or inspect the replacement session.
|
|
119
|
+
- After VCM reports the restart is scheduled, write the route message with both architecture artifacts and the plan, then end the turn. VCM keeps this session for any architecture-plan Gate revision and restarts it only after the Gate is accepted. Do not wait for or inspect the replacement session.
|
|
120
120
|
|
|
121
121
|
### Complete Task Planning
|
|
122
122
|
|
|
@@ -46,7 +46,18 @@ ${renderRoleMemoryRules("coder")}
|
|
|
46
46
|
- Use workers when the task has at least 20 \`VCM:CODE\` markers and the marker distribution can form at least two worker-sized groups.
|
|
47
47
|
- Under a complete scaffold, marker implementations are order-independent — signatures, types, and cross-item contracts are frozen by the scaffold — so never serialize worker-sized groups for presumed implementation-order dependencies. When a group's module-scoped checks need peers that are still unimplemented, narrow that worker's assigned validation scope instead of serializing.
|
|
48
48
|
- An item counts as blocked only when a genuine implementation attempt has produced objective compile/check evidence already reported under the failure rules; prediction never blocks an item. A blocked marker item never exempts the remaining markers from worker dispatch.
|
|
49
|
-
- Before invoking workers, count \`VCM:CODE\` markers by module and create one runtime state file per worker under \`.ai/vcm/coder-workers/tasks/<worker-id>.json\` with
|
|
49
|
+
- Before invoking workers, count \`VCM:CODE\` markers by module and create one runtime state file per worker under \`.ai/vcm/coder-workers/tasks/<worker-id>.json\` with exactly this initial shape:
|
|
50
|
+
|
|
51
|
+
\`\`\`json
|
|
52
|
+
{
|
|
53
|
+
"workerId": "<worker-id>",
|
|
54
|
+
"status": "running",
|
|
55
|
+
"reportPath": ".ai/vcm/coder-workers/reports/<worker-id>.md",
|
|
56
|
+
"handled": false
|
|
57
|
+
}
|
|
58
|
+
\`\`\`
|
|
59
|
+
|
|
60
|
+
- After a worker completes, its state must retain those fields, set \`status\` to \`completed\`, and add the exact \`commitHash\` from its report. Only Coder changes \`handled\` to \`true\` after inspecting that report and commit.
|
|
50
61
|
- Create one worker task for each module with more than 10 \`VCM:CODE\` markers.
|
|
51
62
|
- Group modules with 10 or fewer \`VCM:CODE\` markers into one small-modules worker when their combined marker count is more than 10.
|
|
52
63
|
- If the combined small-module marker count is 10 or fewer, Coder handles those modules directly after worker results return.
|
|
@@ -14,8 +14,19 @@ You are \`vcm-coder-worker\`, a bounded implementation worker invoked by Coder.
|
|
|
14
14
|
### Worker Runtime State
|
|
15
15
|
|
|
16
16
|
- Worker runtime status is only \`running\` or \`completed\`.
|
|
17
|
-
- Coder creates the assigned worker state with
|
|
17
|
+
- Coder creates the assigned worker state with this exact initial shape:
|
|
18
|
+
|
|
19
|
+
\`\`\`json
|
|
20
|
+
{
|
|
21
|
+
"workerId": "<worker-id>",
|
|
22
|
+
"status": "running",
|
|
23
|
+
"reportPath": ".ai/vcm/coder-workers/reports/<worker-id>.md",
|
|
24
|
+
"handled": false
|
|
25
|
+
}
|
|
26
|
+
\`\`\`
|
|
27
|
+
|
|
18
28
|
- After the sweep of assigned items and their assigned checks, commit the assigned files. After the commit succeeds, write the assigned report with the commit hash and \`Implementation Result: success|has_failed_items\`, then update only the assigned worker state to \`completed\` with the same \`commitHash\` as the final step.
|
|
29
|
+
- The completed state must retain \`workerId\`, \`reportPath\`, and \`handled: false\`, set \`status\` to \`completed\`, and add \`"commitHash": "<exact-report-commit-hash>"\`.
|
|
19
30
|
- Use \`completed\` only after every assigned item reached a terminal state. A successful item has green assigned proof and its marker removed. A failed item has a genuine attempt committed with objective failure evidence and its marker retained. Use \`success\` only when every item succeeded; otherwise use \`has_failed_items\`.
|
|
20
31
|
- If execution is interrupted before the sweep, commit, or report completes, leave the worker state as \`running\`. Coder must resume the worker or take over the remaining work.
|
|
21
32
|
- Do not set \`handled: true\`; only Coder may do that after reviewing and integrating the worker result.
|
|
@@ -135,6 +135,10 @@ when the active flow produced an architecture plan. Read
|
|
|
135
135
|
When the report contains an approved Coverage Gap, also read the relevant
|
|
136
136
|
Architect Debug and Architecture Diagnosis evidence.
|
|
137
137
|
|
|
138
|
+
Validation-adequacy reviews only a terminal \`Test Result: pass|fail\`.
|
|
139
|
+
\`Test Result: incomplete\` is Tester continuation state and must not enter this
|
|
140
|
+
gate.
|
|
141
|
+
|
|
138
142
|
Reconstruct the accepted validation target, observable behavior, and risks
|
|
139
143
|
from the active flow evidence and current implementation. Treat Tester
|
|
140
144
|
conclusions, green commands, and
|
|
@@ -460,7 +464,7 @@ Use this skill at every project-manager Gate Review trigger point and whenever V
|
|
|
460
464
|
## Trigger Points
|
|
461
465
|
|
|
462
466
|
- \`architecture-plan\`: after the user confirms \`.ai/vcm/handoffs/architecture-brief.md\` and architect writes \`.ai/vcm/handoffs/architecture-plan.md\`, before coder dispatch.
|
|
463
|
-
- \`validation-adequacy\`: after tester writes
|
|
467
|
+
- \`validation-adequacy\`: after tester writes a terminal \`Test Result: pass|fail\` that the active flow permits to reach the gate, before post-validation docs sync or final acceptance in a code-delivery flow, or before Validation-Only Flow completion. Never request this gate for \`Test Result: incomplete\`.
|
|
464
468
|
- \`code-diff\`: after Coder returns \`Decision: ready_for_review\`, Architect Debug Mode completes a code fix, or Architecture Diagnosis Mode completes a code fix, before PM routes to Tester. Identify the source with \`--source coder\`, \`--source architect-debug\`, or \`--source architect-diagnosis\`.
|
|
465
469
|
|
|
466
470
|
## Request
|
|
@@ -508,7 +512,7 @@ from pathlib import Path
|
|
|
508
512
|
|
|
509
513
|
GATES = ("architecture-plan", "validation-adequacy", "code-diff")
|
|
510
514
|
CODE_DIFF_SOURCES = ("coder", "architect-debug", "architect-diagnosis")
|
|
511
|
-
|
|
515
|
+
LATEST_REPORTS = {
|
|
512
516
|
"architecture-plan": ".ai/vcm/gate-reviews/architecture-plan-review.md",
|
|
513
517
|
"validation-adequacy": ".ai/vcm/gate-reviews/validation-adequacy-review.md",
|
|
514
518
|
"code-diff": ".ai/vcm/gate-reviews/code-diff-review.md",
|
|
@@ -931,13 +935,14 @@ def local_request(gate: str, source: str | None) -> int:
|
|
|
931
935
|
and gate_record.get("decision") == "approve"
|
|
932
936
|
and gate_record.get("inputHash") == current_hash
|
|
933
937
|
):
|
|
934
|
-
print_result("already_approved", gate=gate, report=gate_record.get("reportPath",
|
|
938
|
+
print_result("already_approved", gate=gate, report=gate_record.get("reportPath", LATEST_REPORTS[gate]))
|
|
935
939
|
return 0
|
|
936
940
|
|
|
937
941
|
rid = request_id(gate)
|
|
938
942
|
request_path = root / ".ai/vcm/gate-reviews/requests" / f"{rid}.json"
|
|
939
943
|
prompt_path = f".ai/vcm/gate-reviews/requests/{rid}.prompt.md"
|
|
940
|
-
report_path =
|
|
944
|
+
report_path = f".ai/vcm/gate-reviews/requests/{rid}.report.md"
|
|
945
|
+
latest_report_path = LATEST_REPORTS[gate]
|
|
941
946
|
requested_at = now_iso()
|
|
942
947
|
write_json(request_path, {
|
|
943
948
|
"version": 1,
|
|
@@ -950,6 +955,7 @@ def local_request(gate: str, source: str | None) -> int:
|
|
|
950
955
|
"codeDiffSources": sources,
|
|
951
956
|
"codeDiff": code_diff or None,
|
|
952
957
|
"reportPath": report_path,
|
|
958
|
+
"latestReportPath": latest_report_path,
|
|
953
959
|
"promptPath": prompt_path,
|
|
954
960
|
})
|
|
955
961
|
|
|
@@ -959,7 +965,7 @@ def local_request(gate: str, source: str | None) -> int:
|
|
|
959
965
|
"required": True,
|
|
960
966
|
"status": "running",
|
|
961
967
|
"decision": None,
|
|
962
|
-
"reportPath":
|
|
968
|
+
"reportPath": latest_report_path,
|
|
963
969
|
"promptPath": prompt_path,
|
|
964
970
|
"inputHash": current_hash,
|
|
965
971
|
"baseCommit": code_diff.get("baseCommit"),
|
|
@@ -93,6 +93,7 @@ PM may leave this path only through the allowed branches below.
|
|
|
93
93
|
- **Coder Continuation:** If Coder returns \`Decision: incomplete\`, lacks the required completion artifact, or has not completed implementation and L0/L1 validation, route Coder again — this is the only route for an in-progress sweep. Problems recorded inside an incomplete report are sweep state, not routable failures; PM routes problems onward only from a post-sweep \`failed\` report carrying the consolidated per-item disposition.
|
|
94
94
|
- **Coder Failure Debug:** If Coder returns \`Decision: failed\` with compile, typecheck, or L0/L1 failure evidence after implementation, suspend the main flow and enter Architect Debug Branch.
|
|
95
95
|
- **Code-Diff Correction:** If the code-diff Gate returns \`request_changes\`, suspend the main flow and enter Architect Debug Branch with the Gate report.
|
|
96
|
+
- **Tester Continuation:** If Tester returns \`Test Result: incomplete\`, route Tester again to continue the recorded remaining validation. Do not enter Debug, Diagnosis, or validation-adequacy Gate Review.
|
|
96
97
|
- **Tester Failure:** If Tester returns \`Test Result: fail\` for the original Coder implementation, enter Architect Debug Branch.
|
|
97
98
|
- **Validation Revision:** If the validation-adequacy Gate returns \`request_changes\`, route the report to Tester, then rerun the validation-adequacy Gate after Tester updates the tests or test report.
|
|
98
99
|
- **Docs Sync Correction:** \`Decision: synced\` or \`unchanged\` continues to Final Acceptance. \`Decision: blocked\` remains at docs sync unless the report identifies an allowed Debug, Diagnosis, or user-decision branch.
|
|
@@ -151,6 +152,7 @@ The shared path is:
|
|
|
151
152
|
|
|
152
153
|
- **Normal Plan Required:** If Architect returns \`normal architecture plan required\`, enter Code-Change Flow at Architect planning. When Debug is a branch of Code-Change Flow, resume that parent flow at Architect planning.
|
|
153
154
|
- **Code-Diff Revision:** If the code-diff Gate returns \`request_changes\`, route the report to Architect Debug Mode and rerun \`code-diff --source architect-debug\` after correction.
|
|
155
|
+
- **Tester Continuation:** If Tester returns \`Test Result: incomplete\`, route Tester again to continue the recorded remaining validation.
|
|
154
156
|
- **Architecture Diagnosis:** If Tester returns \`Test Result: fail\`, enter Architecture Diagnosis Branch.
|
|
155
157
|
|
|
156
158
|
#### Successful Exit
|
|
@@ -180,6 +182,7 @@ Architecture Diagnosis Mode must run before another Debug Mode fix or Coder disp
|
|
|
180
182
|
#### Allowed Branches
|
|
181
183
|
|
|
182
184
|
- **Code-Diff Revision:** If the code-diff Gate returns \`request_changes\`, route the report to Architecture Diagnosis Mode and rerun \`code-diff --source architect-diagnosis\` after correction.
|
|
185
|
+
- **Tester Continuation:** If Tester returns \`Test Result: incomplete\`, route Tester again to continue the recorded remaining validation.
|
|
183
186
|
- **Tester Failure:** If Tester returns \`Test Result: fail\` for the Diagnosis implementation, pause and report to the user. If required validation remains unavailable, ask whether the user explicitly approves retaining that exact Coverage Gap.
|
|
184
187
|
|
|
185
188
|
#### Successful Exit
|
|
@@ -242,7 +245,7 @@ The flow is:
|
|
|
242
245
|
|
|
243
246
|
\`Tester validation and test update -> validation-adequacy Gate -> PM completion\`
|
|
244
247
|
|
|
245
|
-
Tester must
|
|
248
|
+
Tester must write \`.ai/vcm/handoffs/test-report.md\` and return \`Test Result: pass|fail|incomplete\`.
|
|
246
249
|
|
|
247
250
|
If Tester changes tests, fixtures, test-only helpers, or \`docs/TESTING.md\`, Tester must commit those changes and record the changed files and commit in \`test-report.md\`.
|
|
248
251
|
|
|
@@ -252,7 +255,7 @@ PM may leave this path only through the allowed branches below.
|
|
|
252
255
|
|
|
253
256
|
#### Allowed Branches
|
|
254
257
|
|
|
255
|
-
- **Tester Continuation:** If
|
|
258
|
+
- **Tester Continuation:** If Tester returns \`Test Result: incomplete\`, route Tester again to continue the recorded remaining validation.
|
|
256
259
|
- **Validation Revision:** If the validation-adequacy Gate returns \`request_changes\`, route the report to Tester and rerun the Gate after correction.
|
|
257
260
|
- **Code Change Required:** If the accepted outcome requires production-code, runtime-behavior, public-contract, dependency, or system-architecture changes, enter Code-Change Flow at Architect planning.
|
|
258
261
|
- **User Decision:** If validation requires missing user intent, credentials, environment access, sensitive data, real cost, or external authorization, pause and ask the user.
|
|
@@ -321,6 +324,7 @@ PM may lightly rewrite the user's words to:
|
|
|
321
324
|
- In an Architect Debug Branch or Architecture Diagnosis Branch, track the parent flow, resume point, Architect result, test report, and required Gate Review results. Do not require a branch-level final acceptance report.
|
|
322
325
|
- In an Architect Debug Flow or Architecture Diagnosis Flow that produces code changes, track the Architect result, test report, required Gate Review results, docs-sync report, and final acceptance report.
|
|
323
326
|
- In Docs-Only Flow, complete only when Architect returns \`Decision: synced\` or \`Decision: unchanged\` with complete evidence. In Validation-Only Flow, complete only from a complete \`test-report.md\` after the validation-adequacy Gate finishes successfully.
|
|
327
|
+
- A Tester \`Test Result: incomplete\` is continuation state, not failure evidence. Route Tester again and do not run validation-adequacy Gate Review or Final Acceptance from it.
|
|
324
328
|
- The Architect does not begin planning until \`architecture-brief.md\` is confirmed (this happens inside the same Architect Interview-and-planning turn, not a separate PM route). Advance to the next gate only when the required role artifact/result is complete and PM routing rules allow that gate.
|
|
325
329
|
- If a required artifact is missing, stale, blocked, or asks for a decision, route the issue to the responsible role or user.
|
|
326
330
|
- In Code-Change Flow, Architect Debug Flow, and an Architecture Diagnosis Flow that produces code changes, request Architect post-validation docs sync after Tester completes. Architect Debug Branch and Architecture Diagnosis Branch return to their recorded resume points after Tester passes.
|
|
@@ -329,7 +333,7 @@ PM may lightly rewrite the user's words to:
|
|
|
329
333
|
|
|
330
334
|
- Gate Review requests are mandatory and unconditional. At every trigger point, use the \`vcm-gate-review\` skill to run \`.ai/tools/request-gate-review\` with the matching gate and code source arguments without first judging whether Gate Review is enabled. The tool (via VCM) is the single source of truth for enable state; never skip the run because you assume Gate Review is off or because the worktree has no gate-review index yet.
|
|
331
335
|
- The tool's first output line decides the next step: \`disabled\`, \`not_required\`, or \`already_approved\` continue the normal VCM flow; \`started\` or \`running\` stop the turn and wait for the VCM callback; \`failed_to_start\` is a hard stop — report it to the user and do not silently proceed past the gate.
|
|
332
|
-
- Trigger points (run each unconditionally): after the architecture brief is confirmed and Architect completes planning, before coder dispatch run \`architecture-plan\`; before post-validation docs sync or final acceptance in a code-delivery flow, or before Validation-Only Flow completion, run \`validation-adequacy\`; after any Coder \`Decision: ready_for_review\` result run \`code-diff --source coder\`; after any Architect Debug Mode completed code fix run \`code-diff --source architect-debug\`; after any Architecture Diagnosis Mode completed code fix run \`code-diff --source architect-diagnosis\`. Run code-diff before routing to Tester.
|
|
336
|
+
- Trigger points (run each unconditionally): after the architecture brief is confirmed and Architect completes planning, before coder dispatch run \`architecture-plan\`; after Tester returns a terminal \`Test Result: pass|fail\` that the active flow permits to reach the gate, before post-validation docs sync or final acceptance in a code-delivery flow, or before Validation-Only Flow completion, run \`validation-adequacy\`; after any Coder \`Decision: ready_for_review\` result run \`code-diff --source coder\`; after any Architect Debug Mode completed code fix run \`code-diff --source architect-debug\`; after any Architecture Diagnosis Mode completed code fix run \`code-diff --source architect-diagnosis\`. Never run validation-adequacy for \`Test Result: incomplete\`. Run code-diff before routing to Tester.
|
|
333
337
|
- PM does not inspect commits or decide whether code changes exist. At a \`code-diff\` trigger point, run the tool; the tool decides \`disabled\`, \`not_required\`, \`already_approved\`, or starts review.
|
|
334
338
|
- Do not run \`code-diff\` for incomplete, failed, planning-only, Docs-Only Flow, Validation-Only Flow, PR-Preparation Flow, or Communication-Only Flow.
|
|
335
339
|
- Gate Review trigger points apply only when the active delivery flow reaches that milestone. Do not run Gate Review for Communication-Only Flow.
|
|
@@ -9,7 +9,7 @@ Run:
|
|
|
9
9
|
.ai/tools/request-architect-restart
|
|
10
10
|
\`\`\`
|
|
11
11
|
|
|
12
|
-
If VCM reports \`scheduled\`, write the completed Architect-to-PM route message and end the turn. VCM
|
|
12
|
+
If VCM reports \`scheduled\`, write the completed Architect-to-PM route message and end the turn. VCM keeps the current Architect session through any architecture-plan Gate revision rounds and restarts it only after the route is accepted by PM and that Gate is approved or explicitly excepted.
|
|
13
13
|
|
|
14
14
|
Do not use this skill for incomplete planning, user clarification, Debug Mode, Architecture Diagnosis Mode, or docs sync.`;
|
|
15
15
|
}
|
|
@@ -26,7 +26,7 @@ ${renderRoleMemoryRules("tester")}
|
|
|
26
26
|
- Do not treat "looks normal", "no error", log absence, or implementation reasoning as validation evidence.
|
|
27
27
|
- Coder may write and run L0/L1 baseline tests during implementation, but Tester owns final test adequacy for all validation levels.
|
|
28
28
|
- Review Coder-provided L0/L1 evidence and changed unit tests against \`docs/CODING_STANDARDS.md\`; confirm changed callable units have required success, failure, boundary, validation, branching, error-handling, lifecycle, retry, or state-transition coverage.
|
|
29
|
-
- If required L0/L1 coverage is missing or weak, add or update the required tests. If the
|
|
29
|
+
- If required L0/L1 coverage is missing or weak, add or update the required tests. If the current turn ends while that work can continue in another Tester turn and no blocking issue has been found, return \`Test Result: incomplete\` with completed and remaining validation. If Tester continuation cannot resolve the missing coverage, return \`Test Result: fail\` with concrete blocking evidence.
|
|
30
30
|
- Own L2/L3/L4 final-validation design, execution, and acceptance evidence.
|
|
31
31
|
- Targeted diagnostic L2 checks run by Coder or Architect are implementation evidence only and do not replace Tester final validation.
|
|
32
32
|
- Use L2 integration coverage when changed behavior crosses internal module or component boundaries and can be completely proved from a stable integration entry point without triggering the mandatory L3 rules below.
|
|
@@ -53,7 +53,8 @@ ${renderRoleMemoryRules("tester")}
|
|
|
53
53
|
- Before exact user approval is routed by project-manager, record missing required coverage under \`Blocking Validation Issues\`, keep \`Coverage Gaps\` as \`None\`, and return \`Test Result: fail\`.
|
|
54
54
|
- Add a Coverage Gap only after project-manager routes the user's exact approval for that specific unresolved gap. Record the approval verbatim in \`User Approval Evidence\`.
|
|
55
55
|
- User approval permits the gap to remain and the workflow to continue; it does not change the factual \`Test Result: fail\`.
|
|
56
|
-
- If
|
|
56
|
+
- If the current turn ends before required validation finishes, use \`Test Result: incomplete\` only when no blocking issue has been found and Tester can continue the remaining checks in another turn.
|
|
57
|
+
- A required check that fails, is skipped, or cannot be completed by Tester continuation is a blocking validation issue and requires \`Test Result: fail\`.
|
|
57
58
|
- Update \`docs/TESTING.md\` when validation strategy, commands, level mapping, integration/E2E case definitions, selection rules, final-validation cleanup, test gaps, or test expectations change.
|
|
58
59
|
|
|
59
60
|
### Mandatory L3 End-To-End Coverage
|
|
@@ -125,7 +126,7 @@ Coverage Gap.
|
|
|
125
126
|
|
|
126
127
|
### Outputs
|
|
127
128
|
|
|
128
|
-
- Write \`.ai/vcm/handoffs/test-report.md\` with \`Test Result: pass|fail\`, evidence reviewed, tests added or updated, coverage mapping, commands run or checked, validation results, failed expectations, reproduction steps, skipped checks with reasons, coverage gaps, blocking validation issues, and user approval evidence.
|
|
129
|
+
- Write \`.ai/vcm/handoffs/test-report.md\` with \`Test Result: pass|fail|incomplete\`, evidence reviewed, tests added or updated, coverage mapping, validation progress, commands run or checked, validation results, failed expectations, reproduction steps, skipped checks with reasons, coverage gaps, blocking validation issues, and user approval evidence.
|
|
129
130
|
- \`test-report.md\` must include this L3 section:
|
|
130
131
|
|
|
131
132
|
\`\`\`md
|
|
@@ -150,12 +151,15 @@ L3 Required: yes|no
|
|
|
150
151
|
- In Validation-Only Flow, if tests, fixtures, test-only helpers, or \`docs/TESTING.md\` changed, commit those changes before reporting and record the changed files and commit in \`test-report.md\`. If no tracked files changed, record that no commit was required.
|
|
151
152
|
- \`test-report.md\` is the current validation evidence, not a log; when rewriting it, carry forward still-unresolved findings or explicitly mark them resolved instead of dropping them.
|
|
152
153
|
- In \`Coverage Mapping\`, map each accepted changed behavior or relevant risk to its validation level, actual test file and case or external evidence, exercised entry path and key assertions, result, and any remaining gap.
|
|
154
|
+
- In \`Validation Progress\`, record \`Completed Validation\` and \`Remaining Validation\`. A final \`pass\` report must set remaining validation to \`None\`.
|
|
153
155
|
- Use \`pass\` only when required validation completed and no blocking test failure, missing required coverage, unacceptable test weakness, or unresolved validation risk remains.
|
|
154
|
-
- Use \`fail\` when tests fail, coverage is insufficient,
|
|
155
|
-
-
|
|
156
|
+
- Use \`fail\` only when tests fail, coverage is insufficient and Tester continuation cannot resolve it, required validation is blocked from completion, test quality is unacceptable, or validation risk needs project-manager routing.
|
|
157
|
+
- Use \`incomplete\` only when required validation remains, no blocking issue has been found, and another Tester turn can continue the recorded remaining work.
|
|
158
|
+
- When \`Test Result: pass\`, the entire body of \`Remaining Validation\`, \`Failed Expectations\`, \`Coverage Gaps\`, \`Blocking Validation Issues\`, and \`User Approval Evidence\` must be exactly \`None.\` with no additional text.
|
|
159
|
+
- When \`Test Result: incomplete\`, \`Completed Validation\` and \`Remaining Validation\` must both contain concrete progress, while the entire body of \`Failed Expectations\`, \`Coverage Gaps\`, \`Blocking Validation Issues\`, and \`User Approval Evidence\` must be exactly \`None.\` with no additional text.
|
|
156
160
|
- When \`Test Result: fail\`, \`Blocking Validation Issues\` must list concrete blocking evidence.
|
|
157
161
|
- When \`Coverage Gaps\` is not \`None\`, \`Test Result\` must be \`fail\`, \`User Approval Evidence\` must contain the user's exact authorization, and every recorded gap must match that authorization.
|
|
158
|
-
- When no gap has been approved, \`User Approval Evidence\` must be \`None
|
|
162
|
+
- When no gap has been approved, the entire \`User Approval Evidence\` section must be exactly \`None.\` with no additional text.
|
|
159
163
|
- For feature or cross-boundary changes, map required L2 integration coverage and mandatory L3 coverage separately. If required coverage is unavailable, report it as a blocking issue.
|
|
160
164
|
- For changed or newly added tests, state why the assertions prove real behavior rather than fixture-specific, implementation-specific, or mock-only behavior.
|
|
161
165
|
- Report confirmed unresolved issues that should survive current-task cleanup in \`.ai/vcm/handoffs/test-report.md\`; do not write \`.ai/vcm/handoffs/known-issues.md\` (architect-owned).
|
|
@@ -65,14 +65,14 @@ Architecture Brief Status: interviewing|confirmed
|
|
|
65
65
|
|
|
66
66
|
## Unresolved User Decisions
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
None.
|
|
69
69
|
|
|
70
70
|
## User Confirmation
|
|
71
71
|
|
|
72
72
|
...
|
|
73
73
|
\`\`\`
|
|
74
74
|
|
|
75
|
-
Record concise confirmed requirements and constraints. Tag each entry under Confirmed User Decisions with its provenance and depth — [user-stated | architect-proposed, user-approved | architect-inferred] and [intent-level | mechanism-level] — and record the user's real input faithfully as a short summary; never present an architect inference as a user requirement. Record correctness-critical mechanism choices surfaced during the interview with their options, your recommendation, the user's decision, and the rejected alternative, so later stages can tell a chosen mechanism from an inferred one. Keep this to decisions and their provenance — not a full implementation design, and not a transcript.
|
|
75
|
+
Record concise confirmed requirements and constraints. Tag each entry under Confirmed User Decisions with its provenance and depth — [user-stated | architect-proposed, user-approved | architect-inferred] and [intent-level | mechanism-level] — and record the user's real input faithfully as a short summary; never present an architect inference as a user requirement. Record correctness-critical mechanism choices surfaced during the interview with their options, your recommendation, the user's decision, and the rejected alternative, so later stages can tell a chosen mechanism from an inferred one. Keep this to decisions and their provenance — not a full implementation design, and not a transcript. When no user-owned decision remains, the entire Unresolved User Decisions section must be exactly \`None.\` with no additional text.
|
|
76
76
|
|
|
77
77
|
Maintain the evidence artifact with this structure:
|
|
78
78
|
|
|
@@ -30,7 +30,7 @@ Check whether the required role evidence exists, is current, and gives a clear r
|
|
|
30
30
|
Acceptable evidence must show:
|
|
31
31
|
|
|
32
32
|
- architect plan, architecture diagnosis, or docs-sync decision when required by the completed flow
|
|
33
|
-
- tester \`Test Result: pass|fail\` and validation evidence when code, behavior, tests, or generated context changed
|
|
33
|
+
- tester terminal \`Test Result: pass|fail\` and validation evidence when code, behavior, tests, or generated context changed; \`incomplete\` is not acceptance evidence
|
|
34
34
|
- required Gate Review decisions, skip reasons, or override reasons when Gate Reviews were enabled
|
|
35
35
|
- known-issues disposition when unresolved findings were recorded
|
|
36
36
|
- explicit user approval for accepted high-risk decisions or intentionally skipped required gates
|
|
@@ -58,7 +58,7 @@ Check:
|
|
|
58
58
|
- required route was followed, or an explicit user-approved exception is recorded
|
|
59
59
|
- required handoff artifacts exist and are current
|
|
60
60
|
- architecture plan, Architecture Diagnosis, Replan, or architect follow-up completion is recorded when required by the flow
|
|
61
|
-
- tester report records \`Test Result: pass|fail\`, validation commands, results, and skipped checks with reasons
|
|
61
|
+
- tester report records terminal \`Test Result: pass|fail\`, validation commands, results, and skipped checks with reasons; do not accept \`Test Result: incomplete\`
|
|
62
62
|
- required Gate Reviews are approved, or skipped/overridden through a VCM-recorded user action
|
|
63
63
|
- Gate Review enable state is confirmed authoritatively: do not infer that no Gate Reviews were required from an absent or empty \`.ai/vcm/gate-reviews/index.json\`. When Gate Review is enabled, a missing index or a required gate without a recorded decision means the gate was skipped — run the matching command from the \`vcm-gate-review\` skill, including the code source for \`code-diff\`, and do not accept until each required gate returns \`approve\`/\`already_approved\`, \`disabled\`/\`not_required\`, or a VCM-recorded user skip/override
|
|
64
64
|
- docs-sync report records docs updated, docs intentionally left unchanged, or required follow-up when docs sync was required
|
|
@@ -94,7 +94,7 @@ Use this structure:
|
|
|
94
94
|
|
|
95
95
|
## Decision
|
|
96
96
|
|
|
97
|
-
accepted
|
|
97
|
+
accepted|accepted-with-known-risks|needs-coder-follow-up|needs-architect-follow-up|needs-docs-sync|blocked-by-user-decision
|
|
98
98
|
|
|
99
99
|
## Evidence Reviewed
|
|
100
100
|
|
|
@@ -56,9 +56,7 @@ type: task
|
|
|
56
56
|
workflow_flow: code-change
|
|
57
57
|
workflow_step: coder-implementation
|
|
58
58
|
workflow_status: active
|
|
59
|
-
artifact_refs:
|
|
60
|
-
- .ai/vcm/handoffs/architecture-plan.md
|
|
61
|
-
- docs/plans/example.md
|
|
59
|
+
artifact_refs: .ai/vcm/handoffs/architecture-plan.md, docs/plans/example.md
|
|
62
60
|
---
|
|
63
61
|
|
|
64
62
|
Summary:
|
|
@@ -84,8 +82,7 @@ For non-PM reports, use:
|
|
|
84
82
|
\`\`\`md
|
|
85
83
|
---
|
|
86
84
|
type: result
|
|
87
|
-
artifact_refs:
|
|
88
|
-
- .ai/vcm/handoffs/example.md
|
|
85
|
+
artifact_refs: .ai/vcm/handoffs/example.md
|
|
89
86
|
---
|
|
90
87
|
|
|
91
88
|
Summary:
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ARCHITECTURE_BRIEF_STATUSES, ARCHITECTURE_PLAN_RESULTS, DOCS_SYNC_DECISIONS, FINAL_ACCEPTANCE_DECISIONS, L3_ACTIONS, L3_REQUIRED_VALUES, STRICT_NONE_VALUE, TEST_RESULTS } from "./artifact-contract.js";
|
|
1
2
|
const REQUIRED_HEADINGS = {
|
|
2
3
|
"architecture-brief": [
|
|
3
4
|
"Accepted Outcome",
|
|
@@ -39,6 +40,9 @@ const REQUIRED_HEADINGS = {
|
|
|
39
40
|
"Evidence Reviewed",
|
|
40
41
|
"Tests Added Or Updated",
|
|
41
42
|
"Coverage Mapping",
|
|
43
|
+
"Validation Progress",
|
|
44
|
+
"Completed Validation",
|
|
45
|
+
"Remaining Validation",
|
|
42
46
|
"L3 Coverage",
|
|
43
47
|
"Trigger Assessment",
|
|
44
48
|
"Affected End-To-End Flows",
|
|
@@ -105,6 +109,8 @@ export function checkMarkdownArtifact(kind, artifactPath, content) {
|
|
|
105
109
|
const missingHeadings = REQUIRED_HEADINGS[kind].filter((heading) => !hasHeading(trimmed, heading));
|
|
106
110
|
const hasPlaceholder = PLACEHOLDER_PATTERN.test(trimmed);
|
|
107
111
|
const invalidFields = validateArtifactFields(kind, trimmed);
|
|
112
|
+
const isWorkInProgress = kind === "test-report"
|
|
113
|
+
&& /^\s*Test Result\s*:\s*incomplete\s*$/im.test(trimmed);
|
|
108
114
|
return {
|
|
109
115
|
kind,
|
|
110
116
|
path: artifactPath,
|
|
@@ -113,45 +119,47 @@ export function checkMarkdownArtifact(kind, artifactPath, content) {
|
|
|
113
119
|
hasPlaceholder,
|
|
114
120
|
missingHeadings,
|
|
115
121
|
invalidFields,
|
|
116
|
-
status: missingHeadings.length === 0
|
|
122
|
+
status: missingHeadings.length === 0
|
|
123
|
+
&& !hasPlaceholder
|
|
124
|
+
&& invalidFields.length === 0
|
|
125
|
+
&& !isWorkInProgress
|
|
126
|
+
? "ok"
|
|
127
|
+
: "incomplete"
|
|
117
128
|
};
|
|
118
129
|
}
|
|
119
130
|
function validateArtifactFields(kind, content) {
|
|
120
131
|
if (kind === "architecture-plan") {
|
|
121
|
-
const result =
|
|
122
|
-
|
|
123
|
-
return ["Planning Result is required and must be complete."];
|
|
124
|
-
}
|
|
125
|
-
return result === "complete"
|
|
132
|
+
const result = readInlineField(content, "Planning Result");
|
|
133
|
+
return result === ARCHITECTURE_PLAN_RESULTS[0]
|
|
126
134
|
? []
|
|
127
|
-
: [
|
|
135
|
+
: [renderExactFieldError("Planning Result", [ARCHITECTURE_PLAN_RESULTS[0]], result)];
|
|
128
136
|
}
|
|
129
137
|
if (kind === "architecture-brief") {
|
|
130
|
-
const status =
|
|
131
|
-
const invalidFields = status
|
|
138
|
+
const status = readInlineField(content, "Architecture Brief Status");
|
|
139
|
+
const invalidFields = isAllowedValue(status, ARCHITECTURE_BRIEF_STATUSES)
|
|
132
140
|
? []
|
|
133
|
-
: ["Architecture Brief Status
|
|
141
|
+
: [renderExactFieldError("Architecture Brief Status", ARCHITECTURE_BRIEF_STATUSES, status)];
|
|
134
142
|
if (status === "confirmed") {
|
|
135
|
-
const unresolved =
|
|
136
|
-
if (!
|
|
137
|
-
invalidFields.push("Unresolved User Decisions
|
|
143
|
+
const unresolved = readArtifactSectionContent(content, "Unresolved User Decisions");
|
|
144
|
+
if (!isExactNone(unresolved)) {
|
|
145
|
+
invalidFields.push(renderExactSectionError("Unresolved User Decisions", STRICT_NONE_VALUE, unresolved, "when Architecture Brief Status is confirmed"));
|
|
138
146
|
}
|
|
139
147
|
}
|
|
140
148
|
return invalidFields;
|
|
141
149
|
}
|
|
142
150
|
if (kind === "test-report") {
|
|
143
|
-
const result =
|
|
144
|
-
const invalidFields = result
|
|
151
|
+
const result = readInlineField(content, "Test Result");
|
|
152
|
+
const invalidFields = isAllowedValue(result, TEST_RESULTS)
|
|
145
153
|
? []
|
|
146
|
-
: ["Test Result
|
|
147
|
-
const l3Required =
|
|
148
|
-
if (l3Required
|
|
149
|
-
invalidFields.push("L3 Required
|
|
154
|
+
: [renderExactFieldError("Test Result", TEST_RESULTS, result)];
|
|
155
|
+
const l3Required = readInlineField(content, "L3 Required");
|
|
156
|
+
if (!isAllowedValue(l3Required, L3_REQUIRED_VALUES)) {
|
|
157
|
+
invalidFields.push(renderExactFieldError("L3 Required", L3_REQUIRED_VALUES, l3Required));
|
|
150
158
|
}
|
|
151
|
-
const l3TriggerAssessment =
|
|
159
|
+
const l3TriggerAssessment = readArtifactSectionContent(content, "Trigger Assessment");
|
|
152
160
|
const l3AffectedFlows = readArtifactSectionContent(content, "Affected End-To-End Flows");
|
|
153
|
-
const l3Commands =
|
|
154
|
-
const l3NotRequiredEvidence =
|
|
161
|
+
const l3Commands = readArtifactSectionContent(content, "L3 Commands And Evidence");
|
|
162
|
+
const l3NotRequiredEvidence = readArtifactSectionContent(content, "Not-Required Evidence");
|
|
155
163
|
if (l3Required === "yes") {
|
|
156
164
|
if (!hasSubstantiveSectionValue(l3TriggerAssessment)) {
|
|
157
165
|
invalidFields.push("Trigger Assessment is required when L3 Required is yes.");
|
|
@@ -166,21 +174,51 @@ function validateArtifactFields(kind, content) {
|
|
|
166
174
|
if (l3Required === "no" && !hasSubstantiveSectionValue(l3NotRequiredEvidence)) {
|
|
167
175
|
invalidFields.push("Not-Required Evidence is required when L3 Required is no.");
|
|
168
176
|
}
|
|
169
|
-
const coverageGaps =
|
|
170
|
-
const blockingIssues =
|
|
171
|
-
const userApproval =
|
|
172
|
-
const
|
|
173
|
-
const
|
|
174
|
-
const
|
|
177
|
+
const coverageGaps = readArtifactSectionContent(content, "Coverage Gaps");
|
|
178
|
+
const blockingIssues = readArtifactSectionContent(content, "Blocking Validation Issues");
|
|
179
|
+
const userApproval = readArtifactSectionContent(content, "User Approval Evidence");
|
|
180
|
+
const failedExpectations = readArtifactSectionContent(content, "Failed Expectations");
|
|
181
|
+
const completedValidation = readArtifactSectionContent(content, "Completed Validation");
|
|
182
|
+
const remainingValidation = readArtifactSectionContent(content, "Remaining Validation");
|
|
183
|
+
const hasCoverageGaps = hasSubstantiveSectionValue(coverageGaps);
|
|
184
|
+
const hasBlockingIssues = hasSubstantiveSectionValue(blockingIssues);
|
|
185
|
+
const hasUserApproval = hasSubstantiveSectionValue(userApproval);
|
|
186
|
+
const hasFailedExpectations = hasSubstantiveSectionValue(failedExpectations);
|
|
175
187
|
if (result === "pass") {
|
|
176
|
-
if (!coverageGaps
|
|
177
|
-
invalidFields.push("Coverage Gaps
|
|
188
|
+
if (!isExactNone(coverageGaps)) {
|
|
189
|
+
invalidFields.push(renderExactSectionError("Coverage Gaps", STRICT_NONE_VALUE, coverageGaps, "when Test Result is pass"));
|
|
190
|
+
}
|
|
191
|
+
if (!isExactNone(blockingIssues)) {
|
|
192
|
+
invalidFields.push(renderExactSectionError("Blocking Validation Issues", STRICT_NONE_VALUE, blockingIssues, "when Test Result is pass"));
|
|
193
|
+
}
|
|
194
|
+
if (!isExactNone(userApproval)) {
|
|
195
|
+
invalidFields.push(renderExactSectionError("User Approval Evidence", STRICT_NONE_VALUE, userApproval, "when Test Result is pass"));
|
|
178
196
|
}
|
|
179
|
-
if (!
|
|
180
|
-
invalidFields.push("
|
|
197
|
+
if (!isExactNone(failedExpectations)) {
|
|
198
|
+
invalidFields.push(renderExactSectionError("Failed Expectations", STRICT_NONE_VALUE, failedExpectations, "when Test Result is pass"));
|
|
181
199
|
}
|
|
182
|
-
if (!
|
|
183
|
-
invalidFields.push("
|
|
200
|
+
if (!isExactNone(remainingValidation)) {
|
|
201
|
+
invalidFields.push(renderExactSectionError("Remaining Validation", STRICT_NONE_VALUE, remainingValidation, "when Test Result is pass"));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (result === "incomplete") {
|
|
205
|
+
if (!hasSubstantiveSectionValue(completedValidation)) {
|
|
206
|
+
invalidFields.push("Completed Validation must record progress when Test Result is incomplete.");
|
|
207
|
+
}
|
|
208
|
+
if (!hasSubstantiveSectionValue(remainingValidation)) {
|
|
209
|
+
invalidFields.push("Remaining Validation must list continuation work when Test Result is incomplete.");
|
|
210
|
+
}
|
|
211
|
+
if (!isExactNone(coverageGaps)) {
|
|
212
|
+
invalidFields.push(renderExactSectionError("Coverage Gaps", STRICT_NONE_VALUE, coverageGaps, "when Test Result is incomplete"));
|
|
213
|
+
}
|
|
214
|
+
if (!isExactNone(blockingIssues)) {
|
|
215
|
+
invalidFields.push(renderExactSectionError("Blocking Validation Issues", STRICT_NONE_VALUE, blockingIssues, "when Test Result is incomplete"));
|
|
216
|
+
}
|
|
217
|
+
if (!isExactNone(userApproval)) {
|
|
218
|
+
invalidFields.push(renderExactSectionError("User Approval Evidence", STRICT_NONE_VALUE, userApproval, "when Test Result is incomplete"));
|
|
219
|
+
}
|
|
220
|
+
if (!isExactNone(failedExpectations)) {
|
|
221
|
+
invalidFields.push(renderExactSectionError("Failed Expectations", STRICT_NONE_VALUE, failedExpectations, "when Test Result is incomplete"));
|
|
184
222
|
}
|
|
185
223
|
}
|
|
186
224
|
if (result === "fail" && !hasBlockingIssues) {
|
|
@@ -195,22 +233,15 @@ function validateArtifactFields(kind, content) {
|
|
|
195
233
|
}
|
|
196
234
|
}
|
|
197
235
|
else if (hasUserApproval) {
|
|
198
|
-
invalidFields.push("User Approval Evidence
|
|
236
|
+
invalidFields.push(renderExactSectionError("User Approval Evidence", STRICT_NONE_VALUE, userApproval, "when no Coverage Gaps are recorded"));
|
|
199
237
|
}
|
|
200
238
|
return invalidFields;
|
|
201
239
|
}
|
|
202
240
|
if (kind === "docs-sync-report") {
|
|
203
|
-
return validateDecision(content,
|
|
241
|
+
return validateDecision(content, DOCS_SYNC_DECISIONS);
|
|
204
242
|
}
|
|
205
243
|
if (kind === "final-acceptance") {
|
|
206
|
-
return validateDecision(content,
|
|
207
|
-
"accepted",
|
|
208
|
-
"accepted-with-known-risks",
|
|
209
|
-
"needs-coder-follow-up",
|
|
210
|
-
"needs-architect-follow-up",
|
|
211
|
-
"needs-docs-sync",
|
|
212
|
-
"blocked-by-user-decision"
|
|
213
|
-
]);
|
|
244
|
+
return validateDecision(content, FINAL_ACCEPTANCE_DECISIONS);
|
|
214
245
|
}
|
|
215
246
|
return [];
|
|
216
247
|
}
|
|
@@ -221,7 +252,7 @@ function hasCompleteL3FlowMapping(value) {
|
|
|
221
252
|
if (!value) {
|
|
222
253
|
return false;
|
|
223
254
|
}
|
|
224
|
-
const allowedActions = new Set(
|
|
255
|
+
const allowedActions = new Set(L3_ACTIONS);
|
|
225
256
|
return value
|
|
226
257
|
.split(/\r?\n/)
|
|
227
258
|
.map((line) => line.trim())
|
|
@@ -234,10 +265,10 @@ function hasCompleteL3FlowMapping(value) {
|
|
|
234
265
|
});
|
|
235
266
|
}
|
|
236
267
|
function validateDecision(content, allowed) {
|
|
237
|
-
const decision =
|
|
238
|
-
return decision && allowed.includes(decision)
|
|
268
|
+
const decision = readArtifactSectionContent(content, "Decision")?.trim();
|
|
269
|
+
return decision && allowed.includes(decision.toLowerCase())
|
|
239
270
|
? []
|
|
240
|
-
: [
|
|
271
|
+
: [renderExactSectionError("Decision", allowed.join("|"), decision)];
|
|
241
272
|
}
|
|
242
273
|
export function readArtifactSectionValue(content, heading) {
|
|
243
274
|
return readArtifactSectionContent(content, heading)
|
|
@@ -245,7 +276,7 @@ export function readArtifactSectionValue(content, heading) {
|
|
|
245
276
|
.map((line) => line.trim())
|
|
246
277
|
.find(Boolean);
|
|
247
278
|
}
|
|
248
|
-
function readArtifactSectionContent(content, heading) {
|
|
279
|
+
export function readArtifactSectionContent(content, heading) {
|
|
249
280
|
const match = new RegExp(`^#{1,6}\\s+${escapeRegExp(heading)}\\s*$`, "im").exec(content);
|
|
250
281
|
if (!match || match.index === undefined) {
|
|
251
282
|
return undefined;
|
|
@@ -257,6 +288,29 @@ function readArtifactSectionContent(content, heading) {
|
|
|
257
288
|
: afterHeading.slice(0, nextHeading.index);
|
|
258
289
|
return section.trim();
|
|
259
290
|
}
|
|
291
|
+
function readInlineField(content, field) {
|
|
292
|
+
return new RegExp(`^\\s*${escapeRegExp(field)}\\s*:\\s*(.+?)\\s*$`, "im")
|
|
293
|
+
.exec(content)?.[1]?.trim().toLowerCase();
|
|
294
|
+
}
|
|
295
|
+
function isAllowedValue(value, allowed) {
|
|
296
|
+
return value !== undefined && allowed.includes(value);
|
|
297
|
+
}
|
|
298
|
+
function isExactNone(value) {
|
|
299
|
+
return value?.trim() === STRICT_NONE_VALUE;
|
|
300
|
+
}
|
|
301
|
+
function renderExactFieldError(field, allowed, found) {
|
|
302
|
+
return `${field} must be exactly one of "${allowed.join("|")}"; found ${renderFoundValue(found)}.`;
|
|
303
|
+
}
|
|
304
|
+
function renderExactSectionError(section, expected, found, condition) {
|
|
305
|
+
const suffix = condition ? ` ${condition}` : "";
|
|
306
|
+
return `${section} must contain exactly "${expected}"${suffix}; found ${renderFoundValue(found)}.`;
|
|
307
|
+
}
|
|
308
|
+
function renderFoundValue(value) {
|
|
309
|
+
if (value === undefined || value.trim().length === 0) {
|
|
310
|
+
return "<missing>";
|
|
311
|
+
}
|
|
312
|
+
return JSON.stringify(value.trim().replace(/\s+/g, " "));
|
|
313
|
+
}
|
|
260
314
|
function hasHeading(content, heading) {
|
|
261
315
|
const pattern = new RegExp(`^#{1,6}\\s+${escapeRegExp(heading)}\\s*$`, "im");
|
|
262
316
|
return pattern.test(content);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const STRICT_NONE_VALUE = "None.";
|
|
2
|
+
export const ARCHITECTURE_BRIEF_STATUSES = ["interviewing", "confirmed"];
|
|
3
|
+
export const ARCHITECTURE_PLAN_RESULTS = [
|
|
4
|
+
"complete",
|
|
5
|
+
"incomplete",
|
|
6
|
+
"user clarification required"
|
|
7
|
+
];
|
|
8
|
+
export const TEST_RESULTS = ["pass", "fail", "incomplete"];
|
|
9
|
+
export const L3_REQUIRED_VALUES = ["yes", "no"];
|
|
10
|
+
export const L3_ACTIONS = ["run-existing", "updated", "added"];
|
|
11
|
+
export const DOCS_SYNC_DECISIONS = ["synced", "unchanged", "blocked"];
|
|
12
|
+
export const FINAL_ACCEPTANCE_DECISIONS = [
|
|
13
|
+
"accepted",
|
|
14
|
+
"accepted-with-known-risks",
|
|
15
|
+
"needs-coder-follow-up",
|
|
16
|
+
"needs-architect-follow-up",
|
|
17
|
+
"needs-docs-sync",
|
|
18
|
+
"blocked-by-user-decision"
|
|
19
|
+
];
|
|
20
|
+
export function renderArtifactOptions(values) {
|
|
21
|
+
return values.join("|");
|
|
22
|
+
}
|
package/package.json
CHANGED
|
@@ -137,6 +137,11 @@ async function processEntry(context) {
|
|
|
137
137
|
return;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
if (entry.ownership === "project-owned") {
|
|
141
|
+
context.operations.push(skip(entry.path, "project-owned; preserved"));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
140
145
|
if (entry.ownership === "managed-block" || uninstallAction === "remove-managed-block") {
|
|
141
146
|
await removeManagedBlock(context);
|
|
142
147
|
return;
|