project-tiny-context-harness 0.2.70 → 0.2.71
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 +27 -17
- package/assets/README.md +21 -13
- package/assets/README.zh-CN.md +8 -2
- package/assets/agents/AGENTS_CORE.md +37 -30
- package/assets/skills/context_development_engineer/SKILL.md +14 -9
- package/assets/skills/context_product_plan/SKILL.md +13 -8
- package/assets/skills/context_surface_contract/SKILL.md +27 -19
- package/assets/skills/context_uiux_design/SKILL.md +13 -8
- package/assets/skills/superpowers-long-task/SKILL.md +14 -4
- package/dist/commands/index.js +9 -3
- package/dist/commands/validate.js +1 -1
- package/dist/lib/plan-acceptance-json.d.ts +15 -0
- package/dist/lib/plan-acceptance-json.js +129 -0
- package/dist/lib/plan-acceptance-validator.d.ts +2 -0
- package/dist/lib/plan-acceptance-validator.js +187 -0
- package/dist/lib/plan-contract-validator.d.ts +2 -0
- package/dist/lib/plan-contract-validator.js +127 -0
- package/dist/lib/plan-validator-common.d.ts +24 -0
- package/dist/lib/plan-validator-common.js +196 -0
- package/dist/lib/validators.d.ts +1 -1
- package/dist/lib/validators.js +8 -4
- package/package.json +1 -1
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { cell, hasRealPageEvidence, isBlankish, isRuntimeFacing, isUiFacing, parseRequiredTable, readRequiredFile, repoRelative, resolveInputFile, weakProofHit, assertReferencedPathsExist } from "./plan-validator-common.js";
|
|
2
|
+
const SOURCE_HEADERS = [
|
|
3
|
+
"Source item",
|
|
4
|
+
"Durable constraint",
|
|
5
|
+
"Type",
|
|
6
|
+
"Existing Context Hit",
|
|
7
|
+
"Context action",
|
|
8
|
+
"Owning Context",
|
|
9
|
+
"Coverage status"
|
|
10
|
+
];
|
|
11
|
+
const BINDING_HEADERS = [
|
|
12
|
+
"Context fact",
|
|
13
|
+
"Implementation obligation",
|
|
14
|
+
"Expected surfaces",
|
|
15
|
+
"Implemented paths",
|
|
16
|
+
"Forbidden shortcuts",
|
|
17
|
+
"Verification path",
|
|
18
|
+
"Binding status"
|
|
19
|
+
];
|
|
20
|
+
const SOURCE_STATUSES = new Set([
|
|
21
|
+
"covered",
|
|
22
|
+
"new_context_required",
|
|
23
|
+
"context_updated",
|
|
24
|
+
"task_local_only",
|
|
25
|
+
"out_of_scope_explicit",
|
|
26
|
+
"needs_user_decision",
|
|
27
|
+
"under_scoped"
|
|
28
|
+
]);
|
|
29
|
+
const BINDING_STATUSES = new Set([
|
|
30
|
+
"bound",
|
|
31
|
+
"partial",
|
|
32
|
+
"missing",
|
|
33
|
+
"blocked",
|
|
34
|
+
"out_of_scope_explicit",
|
|
35
|
+
"needs_user_decision",
|
|
36
|
+
"contradicted_by_current_state"
|
|
37
|
+
]);
|
|
38
|
+
const UNRESOLVED_SOURCE = new Set(["new_context_required", "needs_user_decision", "under_scoped"]);
|
|
39
|
+
const NON_BOUND = new Set(["partial", "missing", "blocked", "needs_user_decision", "contradicted_by_current_state"]);
|
|
40
|
+
export async function validatePlanContract(projectRoot, args = []) {
|
|
41
|
+
const info = [];
|
|
42
|
+
const errors = [];
|
|
43
|
+
const planPath = await resolveInputFile(projectRoot, args[0], "plan.md");
|
|
44
|
+
const content = await readRequiredFile(projectRoot, planPath, "plan contract", errors);
|
|
45
|
+
if (content === undefined) {
|
|
46
|
+
return { info, errors };
|
|
47
|
+
}
|
|
48
|
+
const sourceTable = parseRequiredTable(content, "Source-to-Context Coverage", SOURCE_HEADERS, "Source-to-Context Coverage", errors);
|
|
49
|
+
if (sourceTable?.headers.includes("implementation constraint")) {
|
|
50
|
+
errors.push("Source-to-Context Coverage must not include Implementation constraint; use Context-to-Implementation Binding instead");
|
|
51
|
+
}
|
|
52
|
+
const bindingTable = parseRequiredTable(content, "Context-to-Implementation Binding", BINDING_HEADERS, "Context-to-Implementation Binding", errors);
|
|
53
|
+
if (sourceTable) {
|
|
54
|
+
for (const row of sourceTable.rows) {
|
|
55
|
+
const status = cell(row, "Coverage status");
|
|
56
|
+
const label = `Source-to-Context Coverage row ${row.index}`;
|
|
57
|
+
if (!SOURCE_STATUSES.has(status)) {
|
|
58
|
+
errors.push(`${label} has unsupported Coverage status: ${status || "<empty>"}`);
|
|
59
|
+
}
|
|
60
|
+
if (UNRESOLVED_SOURCE.has(status)) {
|
|
61
|
+
errors.push(`${label} is unresolved (${status}) and cannot pass final plan-contract validation`);
|
|
62
|
+
}
|
|
63
|
+
if (status === "covered" && isBlankish(cell(row, "Existing Context Hit"))) {
|
|
64
|
+
errors.push(`${label} is covered but has no Existing Context Hit`);
|
|
65
|
+
}
|
|
66
|
+
if (status === "context_updated" && isBlankish(cell(row, "Owning Context"))) {
|
|
67
|
+
errors.push(`${label} is context_updated but has no Owning Context`);
|
|
68
|
+
}
|
|
69
|
+
await assertReferencedPathsExist(projectRoot, label, `${cell(row, "Existing Context Hit")} ${cell(row, "Owning Context")}`, errors);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (bindingTable) {
|
|
73
|
+
for (const row of bindingTable.rows) {
|
|
74
|
+
const status = cell(row, "Binding status");
|
|
75
|
+
const label = `Context-to-Implementation Binding row ${row.index}`;
|
|
76
|
+
const expectedSurfaces = cell(row, "Expected surfaces");
|
|
77
|
+
const implementedPaths = cell(row, "Implemented paths");
|
|
78
|
+
const verificationPath = cell(row, "Verification path");
|
|
79
|
+
const evidenceText = `${implementedPaths} ${verificationPath}`;
|
|
80
|
+
if (!BINDING_STATUSES.has(status)) {
|
|
81
|
+
errors.push(`${label} has unsupported Binding status: ${status || "<empty>"}`);
|
|
82
|
+
}
|
|
83
|
+
if (NON_BOUND.has(status)) {
|
|
84
|
+
errors.push(`${label} is ${status} and cannot pass final plan-contract validation`);
|
|
85
|
+
}
|
|
86
|
+
if (status === "bound") {
|
|
87
|
+
if (isBlankish(expectedSurfaces)) {
|
|
88
|
+
errors.push(`${label} is bound but has no Expected surfaces`);
|
|
89
|
+
}
|
|
90
|
+
if (isBlankish(implementedPaths)) {
|
|
91
|
+
errors.push(`${label} is bound but has no Implemented paths`);
|
|
92
|
+
}
|
|
93
|
+
if (isBlankish(verificationPath)) {
|
|
94
|
+
errors.push(`${label} is bound but has no Verification path`);
|
|
95
|
+
}
|
|
96
|
+
const weak = weakProofHit(`${cell(row, "Context fact")} ${cell(row, "Implementation obligation")} ${expectedSurfaces} ${evidenceText}`);
|
|
97
|
+
if (weak) {
|
|
98
|
+
errors.push(`${label} is bound but contains weak-proof language matching /${weak}/`);
|
|
99
|
+
}
|
|
100
|
+
if (isUiFacing(`${expectedSurfaces} ${cell(row, "Implementation obligation")}`) && !hasRealPageEvidence(evidenceText)) {
|
|
101
|
+
errors.push(`${label} is UI/surface-facing but lacks real page, route, browser or screenshot evidence`);
|
|
102
|
+
}
|
|
103
|
+
if (isRuntimeFacing(expectedSurfaces) && !isRuntimeFacing(evidenceText)) {
|
|
104
|
+
errors.push(`${label} expects runtime/API/worker coverage but implemented evidence does not name that surface`);
|
|
105
|
+
}
|
|
106
|
+
assertForbiddenShortcutsNotUsed(row, evidenceText, label, errors);
|
|
107
|
+
}
|
|
108
|
+
await assertReferencedPathsExist(projectRoot, label, `${cell(row, "Context fact")} ${implementedPaths} ${verificationPath}`, errors);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
info.push(`checked plan contract ${repoRelative(projectRoot, planPath)} source_rows=${sourceTable?.rows.length ?? 0} binding_rows=${bindingTable?.rows.length ?? 0}`);
|
|
112
|
+
if (errors.length === 0) {
|
|
113
|
+
info.push("Plan contract validation passed");
|
|
114
|
+
}
|
|
115
|
+
return { info, errors };
|
|
116
|
+
}
|
|
117
|
+
function assertForbiddenShortcutsNotUsed(row, evidenceText, label, errors) {
|
|
118
|
+
const shortcuts = cell(row, "Forbidden shortcuts")
|
|
119
|
+
.split(/[,;\n]/)
|
|
120
|
+
.map((item) => item.trim())
|
|
121
|
+
.filter((item) => item && !isBlankish(item));
|
|
122
|
+
for (const shortcut of shortcuts) {
|
|
123
|
+
if (evidenceText.toLowerCase().includes(shortcut.toLowerCase())) {
|
|
124
|
+
errors.push(`${label} is bound but its evidence uses forbidden shortcut: ${shortcut}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface PlanTableRow {
|
|
2
|
+
index: number;
|
|
3
|
+
line: number;
|
|
4
|
+
cells: Record<string, string>;
|
|
5
|
+
text: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ParsedPlanTable {
|
|
8
|
+
rows: PlanTableRow[];
|
|
9
|
+
headers: string[];
|
|
10
|
+
}
|
|
11
|
+
export declare function resolveInputFile(projectRoot: string, value: string | undefined, defaultPath: string): Promise<string>;
|
|
12
|
+
export declare function resolveInputDir(projectRoot: string, value: string | undefined, defaultPath: string): Promise<string>;
|
|
13
|
+
export declare function repoRelative(projectRoot: string, file: string): string;
|
|
14
|
+
export declare function readRequiredFile(projectRoot: string, file: string, label: string, errors: string[]): Promise<string | undefined>;
|
|
15
|
+
export declare function parseRequiredTable(content: string, heading: string, expectedHeaders: string[], label: string, errors: string[]): ParsedPlanTable | undefined;
|
|
16
|
+
export declare function cell(row: PlanTableRow, header: string): string;
|
|
17
|
+
export declare function isBlankish(value: unknown): boolean;
|
|
18
|
+
export declare function weakProofHit(text: string): string | undefined;
|
|
19
|
+
export declare function assertReferencedPathsExist(projectRoot: string, label: string, text: string, errors: string[]): Promise<void>;
|
|
20
|
+
export declare function primitiveText(value: unknown): string;
|
|
21
|
+
export declare function valuesAsArray(value: unknown): string[];
|
|
22
|
+
export declare function hasRealPageEvidence(text: string): boolean;
|
|
23
|
+
export declare function isUiFacing(text: string): boolean;
|
|
24
|
+
export declare function isRuntimeFacing(text: string): boolean;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { pathExists, readText } from "./fs.js";
|
|
4
|
+
const PATH_PATTERN = /(?:`([^`]+)`)|((?:\.\/)?(?:project_context|packages|tests|tmp|src|app|pages|components|\.codex|tools|examples)\/[A-Za-z0-9._@+/-]+(?:\.(?:md|ts|tsx|js|mjs|json|yaml|yml|toml|png|jpe?g|webp|html|css|scss|py|go|rs|java|cs|sh|ps1))?)/g;
|
|
5
|
+
const WEAK_PROOF_PATTERNS = [
|
|
6
|
+
/\bchecked path(?:s)?\b/i,
|
|
7
|
+
/\bsampled(?:[_ -]?only)?\b/i,
|
|
8
|
+
/\bnot live-proven\b/i,
|
|
9
|
+
/\blacks?\b/i,
|
|
10
|
+
/\bmissing\b/i,
|
|
11
|
+
/\bgap\b/i,
|
|
12
|
+
/\bpartial\b/i,
|
|
13
|
+
/\bblocked\b/i,
|
|
14
|
+
/\bunconfigured\b/i,
|
|
15
|
+
/\bnot visible by default\b/i
|
|
16
|
+
];
|
|
17
|
+
export async function resolveInputFile(projectRoot, value, defaultPath) {
|
|
18
|
+
const raw = value ?? defaultPath;
|
|
19
|
+
const absolute = path.isAbsolute(raw) ? path.resolve(raw) : path.resolve(projectRoot, raw);
|
|
20
|
+
let candidate = absolute;
|
|
21
|
+
try {
|
|
22
|
+
const stat = await fs.stat(absolute);
|
|
23
|
+
if (stat.isDirectory()) {
|
|
24
|
+
candidate = path.join(absolute, defaultPath);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
if (!path.extname(absolute)) {
|
|
29
|
+
candidate = path.join(absolute, defaultPath);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return candidate;
|
|
33
|
+
}
|
|
34
|
+
export async function resolveInputDir(projectRoot, value, defaultPath) {
|
|
35
|
+
return path.isAbsolute(value ?? defaultPath)
|
|
36
|
+
? path.resolve(value ?? defaultPath)
|
|
37
|
+
: path.resolve(projectRoot, value ?? defaultPath);
|
|
38
|
+
}
|
|
39
|
+
export function repoRelative(projectRoot, file) {
|
|
40
|
+
return path.relative(projectRoot, file).split(path.sep).join("/");
|
|
41
|
+
}
|
|
42
|
+
export async function readRequiredFile(projectRoot, file, label, errors) {
|
|
43
|
+
if (!isInside(projectRoot, file)) {
|
|
44
|
+
errors.push(`${label} must stay inside the project root: ${file}`);
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
if (!(await pathExists(file))) {
|
|
48
|
+
errors.push(`${label} is missing: ${repoRelative(projectRoot, file)}`);
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
return readText(file);
|
|
52
|
+
}
|
|
53
|
+
export function parseRequiredTable(content, heading, expectedHeaders, label, errors) {
|
|
54
|
+
const section = sectionBody(content, heading);
|
|
55
|
+
if (section === undefined) {
|
|
56
|
+
errors.push(`plan contract is missing section: ${heading}`);
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
const lines = section.split(/\r?\n/);
|
|
60
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
61
|
+
if (!lines[index].includes("|")) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const headers = splitTableLine(lines[index]);
|
|
65
|
+
const normalized = headers.map(normalizeHeader);
|
|
66
|
+
if (!expectedHeaders.every((header) => normalized.includes(normalizeHeader(header)))) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const rows = [];
|
|
70
|
+
for (let rowIndex = index + 1; rowIndex < lines.length; rowIndex += 1) {
|
|
71
|
+
const line = lines[rowIndex];
|
|
72
|
+
if (!line.includes("|")) {
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
if (isSeparatorLine(line)) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const values = splitTableLine(line);
|
|
79
|
+
const cells = {};
|
|
80
|
+
for (const [cellIndex, header] of normalized.entries()) {
|
|
81
|
+
cells[header] = values[cellIndex]?.trim() ?? "";
|
|
82
|
+
}
|
|
83
|
+
rows.push({
|
|
84
|
+
index: rows.length + 1,
|
|
85
|
+
line: rowIndex + 1,
|
|
86
|
+
cells,
|
|
87
|
+
text: Object.values(cells).join(" ")
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
const missing = expectedHeaders.filter((header) => !normalized.includes(normalizeHeader(header)));
|
|
91
|
+
if (missing.length > 0) {
|
|
92
|
+
errors.push(`${label} table is missing column(s): ${missing.join(", ")}`);
|
|
93
|
+
}
|
|
94
|
+
return { rows, headers: normalized };
|
|
95
|
+
}
|
|
96
|
+
errors.push(`${label} section must include a markdown table with columns: ${expectedHeaders.join(" | ")}`);
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
export function cell(row, header) {
|
|
100
|
+
return row.cells[normalizeHeader(header)] ?? "";
|
|
101
|
+
}
|
|
102
|
+
export function isBlankish(value) {
|
|
103
|
+
if (Array.isArray(value)) {
|
|
104
|
+
return value.length === 0 || value.every(isBlankish);
|
|
105
|
+
}
|
|
106
|
+
if (value === undefined || value === null) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
const text = String(value).trim().toLowerCase();
|
|
110
|
+
return text === "" || text === "-" || text === "n/a" || text === "na" || text === "none" || text === "[]" || text === "not applicable";
|
|
111
|
+
}
|
|
112
|
+
export function weakProofHit(text) {
|
|
113
|
+
return WEAK_PROOF_PATTERNS.find((pattern) => pattern.test(text))?.source;
|
|
114
|
+
}
|
|
115
|
+
export async function assertReferencedPathsExist(projectRoot, label, text, errors) {
|
|
116
|
+
for (const reference of extractPathReferences(text)) {
|
|
117
|
+
const absolute = path.resolve(projectRoot, reference);
|
|
118
|
+
if (!isInside(projectRoot, absolute)) {
|
|
119
|
+
errors.push(`${label} references a path outside the project root: ${reference}`);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (!(await pathExists(absolute))) {
|
|
123
|
+
errors.push(`${label} references missing path: ${reference}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
export function primitiveText(value) {
|
|
128
|
+
if (value === undefined || value === null) {
|
|
129
|
+
return "";
|
|
130
|
+
}
|
|
131
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
132
|
+
return String(value);
|
|
133
|
+
}
|
|
134
|
+
if (Array.isArray(value)) {
|
|
135
|
+
return value.map(primitiveText).join(" ");
|
|
136
|
+
}
|
|
137
|
+
if (typeof value === "object") {
|
|
138
|
+
return Object.values(value).map(primitiveText).join(" ");
|
|
139
|
+
}
|
|
140
|
+
return "";
|
|
141
|
+
}
|
|
142
|
+
export function valuesAsArray(value) {
|
|
143
|
+
if (Array.isArray(value)) {
|
|
144
|
+
return value.flatMap(valuesAsArray);
|
|
145
|
+
}
|
|
146
|
+
if (isBlankish(value)) {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
return String(value)
|
|
150
|
+
.split(/[,;\n]/)
|
|
151
|
+
.map((item) => item.trim())
|
|
152
|
+
.filter(Boolean);
|
|
153
|
+
}
|
|
154
|
+
export function hasRealPageEvidence(text) {
|
|
155
|
+
return /\b(real[- ]page|browser|route|screen|screenshot|url|localhost|https?:\/\/|page)\b/i.test(text);
|
|
156
|
+
}
|
|
157
|
+
export function isUiFacing(text) {
|
|
158
|
+
return /\b(ui|browser|page|screen|console|frontend|route|surface)\b/i.test(text);
|
|
159
|
+
}
|
|
160
|
+
export function isRuntimeFacing(text) {
|
|
161
|
+
return /\b(runtime|worker|api|schema|endpoint|service|queue|daemon|runner)\b/i.test(text);
|
|
162
|
+
}
|
|
163
|
+
function sectionBody(content, heading) {
|
|
164
|
+
const escaped = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
165
|
+
const match = new RegExp(`^##\\s+${escaped}\\s*$`, "im").exec(content);
|
|
166
|
+
if (!match) {
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
const rest = content.slice(match.index + match[0].length);
|
|
170
|
+
const next = /^##\s+/m.exec(rest);
|
|
171
|
+
return next ? rest.slice(0, next.index) : rest;
|
|
172
|
+
}
|
|
173
|
+
function splitTableLine(line) {
|
|
174
|
+
return line.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map((cellValue) => cellValue.trim());
|
|
175
|
+
}
|
|
176
|
+
function isSeparatorLine(line) {
|
|
177
|
+
return splitTableLine(line).every((value) => /^:?-{3,}:?$/.test(value.trim()));
|
|
178
|
+
}
|
|
179
|
+
function normalizeHeader(value) {
|
|
180
|
+
return value.replace(/`/g, "").trim().toLowerCase().replace(/[_-]+/g, " ").replace(/\s+/g, " ");
|
|
181
|
+
}
|
|
182
|
+
function extractPathReferences(text) {
|
|
183
|
+
const references = new Set();
|
|
184
|
+
for (const match of text.matchAll(PATH_PATTERN)) {
|
|
185
|
+
const raw = (match[1] ?? match[2] ?? "").trim();
|
|
186
|
+
if (!raw || raw.includes("*") || /^https?:\/\//i.test(raw)) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
references.add(raw.replace(/\\/g, "/").replace(/^\.\//, "").replace(/[),.;:]+$/, ""));
|
|
190
|
+
}
|
|
191
|
+
return [...references];
|
|
192
|
+
}
|
|
193
|
+
function isInside(root, target) {
|
|
194
|
+
const relative = path.relative(root, target);
|
|
195
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
196
|
+
}
|
package/dist/lib/validators.d.ts
CHANGED
|
@@ -2,4 +2,4 @@ export interface ValidatorReport {
|
|
|
2
2
|
info: string[];
|
|
3
3
|
errors: string[];
|
|
4
4
|
}
|
|
5
|
-
export declare function runValidator(projectRoot: string, gate: string): Promise<ValidatorReport>;
|
|
5
|
+
export declare function runValidator(projectRoot: string, gate: string, args?: string[]): Promise<ValidatorReport>;
|
package/dist/lib/validators.js
CHANGED
|
@@ -3,11 +3,15 @@ import { readConfig } from "./config.js";
|
|
|
3
3
|
import { harnessPath, harnessRoot } from "./harness-root.js";
|
|
4
4
|
import { listFiles, pathExists, readText } from "./fs.js";
|
|
5
5
|
import { runModularityCheck } from "./modularity.js";
|
|
6
|
+
import { validatePlanAcceptance } from "./plan-acceptance-validator.js";
|
|
7
|
+
import { validatePlanContract } from "./plan-contract-validator.js";
|
|
6
8
|
import { unsupportedSchemaMessage } from "./schema-guard.js";
|
|
7
9
|
const VALIDATORS = {
|
|
8
10
|
"validate-context": validateContext,
|
|
9
11
|
"validate-code-modularity": validateCodeModularity,
|
|
10
|
-
"validate-harness": validateHarness
|
|
12
|
+
"validate-harness": validateHarness,
|
|
13
|
+
"validate-plan-contract": validatePlanContract,
|
|
14
|
+
"validate-plan-acceptance": validatePlanAcceptance
|
|
11
15
|
};
|
|
12
16
|
const GLOBAL_REQUIRED_SECTIONS = [
|
|
13
17
|
...sectionSpecs([
|
|
@@ -67,17 +71,17 @@ const FAKE_VERIFICATION_PATTERNS = [
|
|
|
67
71
|
/\b验证(?:已)?通过\b/,
|
|
68
72
|
/\b部署(?:已)?成功\b/
|
|
69
73
|
];
|
|
70
|
-
export async function runValidator(projectRoot, gate) {
|
|
74
|
+
export async function runValidator(projectRoot, gate, args = []) {
|
|
71
75
|
const validator = VALIDATORS[gate];
|
|
72
76
|
if (!validator) {
|
|
73
77
|
return {
|
|
74
78
|
info: [],
|
|
75
79
|
errors: [
|
|
76
|
-
`unknown validator: ${gate}. Minimal Context Harness supports validate-context, validate-code-modularity and validate-
|
|
80
|
+
`unknown validator: ${gate}. Minimal Context Harness supports validate-context, validate-code-modularity, validate-harness, validate-plan-contract and validate-plan-acceptance only.`
|
|
77
81
|
]
|
|
78
82
|
};
|
|
79
83
|
}
|
|
80
|
-
return validator(projectRoot);
|
|
84
|
+
return validator(projectRoot, args);
|
|
81
85
|
}
|
|
82
86
|
async function validateHarness(projectRoot) {
|
|
83
87
|
const contextReport = await validateContext(projectRoot);
|