project-tiny-context-harness 0.2.78 → 0.2.80
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 +15 -11
- package/assets/README.md +16 -12
- package/assets/README.zh-CN.md +10 -6
- package/assets/agents/AGENTS_CORE.md +42 -42
- package/assets/skills/composite-long-task-workflow/SKILL.md +212 -0
- package/assets/skills/composite-long-task-workflow/assets/execution-binding.template.md +31 -0
- package/assets/skills/composite-long-task-workflow/assets/goal-objective.template.md +22 -0
- package/assets/skills/composite-long-task-workflow/references/composite-long-task-workflow-protocol.md +633 -0
- package/assets/skills/normal-long-task/SKILL.md +12 -12
- package/dist/commands/composite-long-task.d.ts +6 -0
- package/dist/commands/composite-long-task.js +103 -0
- package/dist/commands/index.js +5 -3
- package/dist/commands/superpowers.js +6 -87
- package/dist/lib/composite-long-task-renderer.d.ts +12 -0
- package/dist/lib/composite-long-task-renderer.js +153 -0
- package/dist/lib/superpowers-task-compile.js +5 -121
- package/dist/lib/superpowers-task-derive.js +1 -1
- package/dist/lib/superpowers-task-gates.js +6 -4
- package/dist/lib/superpowers-task-source-compile.d.ts +5 -0
- package/dist/lib/superpowers-task-source-compile.js +190 -0
- package/dist/lib/superpowers-task-source-parser.d.ts +23 -0
- package/dist/lib/superpowers-task-source-parser.js +224 -0
- package/dist/lib/superpowers-task-state-schema.d.ts +6 -0
- package/dist/lib/superpowers-task-validator.d.ts +1 -0
- package/dist/lib/superpowers-task-validator.js +16 -3
- package/package.json +1 -1
- package/assets/skills/superpowers-long-task/SKILL.md +0 -602
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { fieldArray, fieldBoolean, fieldLine, fieldText, parseDocumentFields, parseHeadingDefinitions } from "./superpowers-task-source-parser.js";
|
|
2
|
+
export const DEFAULT_LAYERS = ["code", "test"];
|
|
3
|
+
const PRODUCT_DELIVERY_SCOPES = new Set(["system_capability_build", "representative_sample_validation", "full_population_operation", "mixed_scope_requires_boundary"]);
|
|
4
|
+
const PLAN_DELIVERY_SCOPES = new Set(["system_capability_build", "representative_sample_validation", "full_population_operation", "out_of_scope_backlog"]);
|
|
5
|
+
const ACCEPTANCE_SCOPES = new Set(["system_capability_build", "representative_sample_validation", "full_population_operation", "full_population_not_required"]);
|
|
6
|
+
const PRODUCT_FIELDS = new Set([
|
|
7
|
+
"delivery_scope",
|
|
8
|
+
"full_population_required",
|
|
9
|
+
"representative_samples_validate",
|
|
10
|
+
"representative_samples_do_not_validate",
|
|
11
|
+
"out_of_scope_backlog",
|
|
12
|
+
"source_authority",
|
|
13
|
+
"product_goal",
|
|
14
|
+
"surface_ia_lock",
|
|
15
|
+
"decision_lock",
|
|
16
|
+
"context_delta",
|
|
17
|
+
"source_to_context_coverage",
|
|
18
|
+
"acceptance_semantics",
|
|
19
|
+
"impact"
|
|
20
|
+
]);
|
|
21
|
+
const PLAN_FIELDS = new Set([
|
|
22
|
+
"delivery_scope",
|
|
23
|
+
"capability_target",
|
|
24
|
+
"representative_samples",
|
|
25
|
+
"full_population_boundary",
|
|
26
|
+
"non_required_population",
|
|
27
|
+
"owner_surfaces",
|
|
28
|
+
"forbidden_surfaces",
|
|
29
|
+
"implementation_paths",
|
|
30
|
+
"required_tests",
|
|
31
|
+
"related_acs",
|
|
32
|
+
"requirement_ref",
|
|
33
|
+
"decision_id",
|
|
34
|
+
"proof_layer_ids",
|
|
35
|
+
"api_schema_changes",
|
|
36
|
+
"state_machine",
|
|
37
|
+
"data_flow",
|
|
38
|
+
"worker_runtime_behavior",
|
|
39
|
+
"ui_ia_changes",
|
|
40
|
+
"migration_plan",
|
|
41
|
+
"evidence_artifacts",
|
|
42
|
+
"explicit_no_test_scope",
|
|
43
|
+
"non_completing_shortcuts",
|
|
44
|
+
"substitution_policy",
|
|
45
|
+
"drift_severity",
|
|
46
|
+
"partial_conditions",
|
|
47
|
+
"blockers",
|
|
48
|
+
"context_fact_refs"
|
|
49
|
+
]);
|
|
50
|
+
const ACCEPTANCE_FIELDS = new Set([
|
|
51
|
+
"checklist_source",
|
|
52
|
+
"acceptance_scope",
|
|
53
|
+
"ac_validates",
|
|
54
|
+
"ac_does_not_validate",
|
|
55
|
+
"sample_boundary",
|
|
56
|
+
"full_population_required",
|
|
57
|
+
"related_plan_items",
|
|
58
|
+
"required_proof_layers",
|
|
59
|
+
"ac_type",
|
|
60
|
+
"proof_chain",
|
|
61
|
+
"verification_method",
|
|
62
|
+
"fail_conditions",
|
|
63
|
+
"invalid_evidence",
|
|
64
|
+
"substitution_policy",
|
|
65
|
+
"missing_layer_downgrade",
|
|
66
|
+
"auditor_expectation",
|
|
67
|
+
"out_of_scope_na_approval_source",
|
|
68
|
+
"required_test_ids",
|
|
69
|
+
"explicit_no_test_scope",
|
|
70
|
+
"hard_blockers",
|
|
71
|
+
"validates_explanation",
|
|
72
|
+
"does_not_validate_explanation",
|
|
73
|
+
"final_evidence_expected",
|
|
74
|
+
"test_cases"
|
|
75
|
+
]);
|
|
76
|
+
export function parseProductArchitectureScope(content, sourceFile) {
|
|
77
|
+
const fields = parseDocumentFields(content, sourceFile, PRODUCT_FIELDS);
|
|
78
|
+
const errors = [];
|
|
79
|
+
const scope = {
|
|
80
|
+
delivery_scope: requireEnum(errors, "Product / Architecture Source", "delivery_scope", fields, PRODUCT_DELIVERY_SCOPES, sourceFile, 1),
|
|
81
|
+
full_population_required: requireBoolean(errors, "Product / Architecture Source", "full_population_required", fields, sourceFile, 1),
|
|
82
|
+
representative_samples_validate: requireArray(errors, "Product / Architecture Source", "representative_samples_validate", fields, sourceFile, 1),
|
|
83
|
+
representative_samples_do_not_validate: requireArray(errors, "Product / Architecture Source", "representative_samples_do_not_validate", fields, sourceFile, 1),
|
|
84
|
+
out_of_scope_backlog: requireArray(errors, "Product / Architecture Source", "out_of_scope_backlog", fields, sourceFile, 1)
|
|
85
|
+
};
|
|
86
|
+
throwCompileErrors(errors);
|
|
87
|
+
return scope;
|
|
88
|
+
}
|
|
89
|
+
export function parsePlanItems(content, sourceFile) {
|
|
90
|
+
const items = {};
|
|
91
|
+
const errors = [];
|
|
92
|
+
for (const definition of parseHeadingDefinitions(content, { kind: "PI", sourceFile, allowedFields: PLAN_FIELDS })) {
|
|
93
|
+
const fields = definition.fields;
|
|
94
|
+
items[definition.id] = {
|
|
95
|
+
requirement: definition.title,
|
|
96
|
+
source_file: definition.source_file,
|
|
97
|
+
source_start_line: definition.source_start_line,
|
|
98
|
+
source_end_line: definition.source_end_line,
|
|
99
|
+
delivery_scope: requireEnum(errors, definition.id, "delivery_scope", fields, PLAN_DELIVERY_SCOPES, sourceFile, definition.source_start_line),
|
|
100
|
+
capability_target: requireText(errors, definition.id, "capability_target", fields, sourceFile, definition.source_start_line),
|
|
101
|
+
representative_samples: requireArray(errors, definition.id, "representative_samples", fields, sourceFile, definition.source_start_line),
|
|
102
|
+
full_population_boundary: requireText(errors, definition.id, "full_population_boundary", fields, sourceFile, definition.source_start_line),
|
|
103
|
+
non_required_population: requireArray(errors, definition.id, "non_required_population", fields, sourceFile, definition.source_start_line),
|
|
104
|
+
owner_surfaces: optionalArray(fields, "owner_surfaces"),
|
|
105
|
+
forbidden_surfaces: optionalArray(fields, "forbidden_surfaces"),
|
|
106
|
+
implementation_paths: optionalArray(fields, "implementation_paths"),
|
|
107
|
+
required_tests: optionalArray(fields, "required_tests"),
|
|
108
|
+
status: "not_started",
|
|
109
|
+
related_acs: optionalArray(fields, "related_acs").map((item) => item.toUpperCase()),
|
|
110
|
+
required_proof_layers: []
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
throwCompileErrors(errors);
|
|
114
|
+
return items;
|
|
115
|
+
}
|
|
116
|
+
export function parseAcceptanceCriteria(content, sourceFile) {
|
|
117
|
+
const items = {};
|
|
118
|
+
const errors = [];
|
|
119
|
+
for (const definition of parseHeadingDefinitions(content, { kind: "AC", sourceFile, allowedFields: ACCEPTANCE_FIELDS })) {
|
|
120
|
+
const fields = definition.fields;
|
|
121
|
+
const layers = optionalArray(fields, "required_proof_layers").map(normalizeLayer).filter(Boolean);
|
|
122
|
+
items[definition.id] = {
|
|
123
|
+
scope: definition.title,
|
|
124
|
+
source_file: definition.source_file,
|
|
125
|
+
source_start_line: definition.source_start_line,
|
|
126
|
+
source_end_line: definition.source_end_line,
|
|
127
|
+
acceptance_scope: requireEnum(errors, definition.id, "acceptance_scope", fields, ACCEPTANCE_SCOPES, sourceFile, definition.source_start_line),
|
|
128
|
+
ac_validates: requireArray(errors, definition.id, "ac_validates", fields, sourceFile, definition.source_start_line),
|
|
129
|
+
ac_does_not_validate: requireArray(errors, definition.id, "ac_does_not_validate", fields, sourceFile, definition.source_start_line),
|
|
130
|
+
sample_boundary: requireText(errors, definition.id, "sample_boundary", fields, sourceFile, definition.source_start_line),
|
|
131
|
+
full_population_required: requireBoolean(errors, definition.id, "full_population_required", fields, sourceFile, definition.source_start_line),
|
|
132
|
+
related_plan_items: optionalArray(fields, "related_plan_items").map((item) => item.toUpperCase()),
|
|
133
|
+
required_proof_layers: layers.length > 0 ? layers : DEFAULT_LAYERS,
|
|
134
|
+
status: "not_run"
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
throwCompileErrors(errors);
|
|
138
|
+
return items;
|
|
139
|
+
}
|
|
140
|
+
function normalizeLayer(value) {
|
|
141
|
+
return value.trim().toLowerCase().replace(/[- ]+/g, "_");
|
|
142
|
+
}
|
|
143
|
+
function requireEnum(errors, label, name, fields, allowed, sourceFile, fallbackLine) {
|
|
144
|
+
const value = fieldText(fields, name);
|
|
145
|
+
const line = fieldLine(fields, name) ?? fallbackLine;
|
|
146
|
+
if (!value) {
|
|
147
|
+
errors.push(`${label} missing ${name} at ${sourceFile}:${line}`);
|
|
148
|
+
return "";
|
|
149
|
+
}
|
|
150
|
+
if (!allowed.has(value)) {
|
|
151
|
+
errors.push(`${label} invalid ${name}: ${value} at ${sourceFile}:${line}; allowed: ${[...allowed].join(", ")}`);
|
|
152
|
+
}
|
|
153
|
+
return value;
|
|
154
|
+
}
|
|
155
|
+
function requireText(errors, label, name, fields, sourceFile, fallbackLine) {
|
|
156
|
+
const value = fieldText(fields, name);
|
|
157
|
+
const line = fieldLine(fields, name) ?? fallbackLine;
|
|
158
|
+
if (!value) {
|
|
159
|
+
errors.push(`${label} missing ${name} at ${sourceFile}:${line}`);
|
|
160
|
+
}
|
|
161
|
+
return value;
|
|
162
|
+
}
|
|
163
|
+
function requireArray(errors, label, name, fields, sourceFile, fallbackLine) {
|
|
164
|
+
const line = fieldLine(fields, name) ?? fallbackLine;
|
|
165
|
+
if (!fields[name]) {
|
|
166
|
+
errors.push(`${label} missing ${name} at ${sourceFile}:${line}`);
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
return fieldArray(fields, name);
|
|
170
|
+
}
|
|
171
|
+
function requireBoolean(errors, label, name, fields, sourceFile, fallbackLine) {
|
|
172
|
+
const line = fieldLine(fields, name) ?? fallbackLine;
|
|
173
|
+
if (!fields[name]) {
|
|
174
|
+
errors.push(`${label} missing ${name} at ${sourceFile}:${line}`);
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
const value = fieldBoolean(fields, name);
|
|
178
|
+
if (value === null) {
|
|
179
|
+
errors.push(`${label} invalid ${name}: ${fieldText(fields, name)} at ${sourceFile}:${line}; must be true or false`);
|
|
180
|
+
}
|
|
181
|
+
return value;
|
|
182
|
+
}
|
|
183
|
+
function optionalArray(fields, name) {
|
|
184
|
+
return fields[name] ? fieldArray(fields, name) : [];
|
|
185
|
+
}
|
|
186
|
+
function throwCompileErrors(errors) {
|
|
187
|
+
if (errors.length > 0) {
|
|
188
|
+
throw new Error(`Superpowers source compile failed:\n- ${errors.join("\n- ")}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface ParsedField {
|
|
2
|
+
value: string | string[];
|
|
3
|
+
line: number;
|
|
4
|
+
}
|
|
5
|
+
export interface ParsedDefinition {
|
|
6
|
+
id: string;
|
|
7
|
+
title: string;
|
|
8
|
+
fields: Record<string, ParsedField>;
|
|
9
|
+
source_file: string;
|
|
10
|
+
source_start_line: number;
|
|
11
|
+
source_end_line: number;
|
|
12
|
+
}
|
|
13
|
+
export interface DefinitionParseOptions {
|
|
14
|
+
kind: "PI" | "AC";
|
|
15
|
+
sourceFile: string;
|
|
16
|
+
allowedFields: Set<string>;
|
|
17
|
+
}
|
|
18
|
+
export declare function parseHeadingDefinitions(content: string, options: DefinitionParseOptions): ParsedDefinition[];
|
|
19
|
+
export declare function parseDocumentFields(content: string, sourceFile: string, allowedFields: Set<string>): Record<string, ParsedField>;
|
|
20
|
+
export declare function fieldText(fields: Record<string, ParsedField>, name: string): string;
|
|
21
|
+
export declare function fieldArray(fields: Record<string, ParsedField>, name: string): string[];
|
|
22
|
+
export declare function fieldBoolean(fields: Record<string, ParsedField>, name: string): boolean | null;
|
|
23
|
+
export declare function fieldLine(fields: Record<string, ParsedField>, name: string): number | undefined;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
export function parseHeadingDefinitions(content, options) {
|
|
2
|
+
const lines = splitLines(content);
|
|
3
|
+
const errors = [];
|
|
4
|
+
rejectListStyleDefinitions(lines, options, errors);
|
|
5
|
+
const headings = findDefinitionHeadings(lines, options.kind);
|
|
6
|
+
const seen = new Map();
|
|
7
|
+
const definitions = [];
|
|
8
|
+
for (const heading of headings) {
|
|
9
|
+
const firstLine = seen.get(heading.id);
|
|
10
|
+
if (firstLine !== undefined) {
|
|
11
|
+
errors.push(`${heading.id} duplicate definition at ${options.sourceFile}:${firstLine} and ${options.sourceFile}:${heading.line}`);
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
seen.set(heading.id, heading.line);
|
|
15
|
+
const sourceEndLine = sectionEndLine(lines, heading.index, heading.level);
|
|
16
|
+
const section = lines.slice(heading.index, sourceEndLine);
|
|
17
|
+
definitions.push({
|
|
18
|
+
id: heading.id,
|
|
19
|
+
title: heading.title,
|
|
20
|
+
fields: parseFields(section, options.sourceFile, heading.line, options.allowedFields, errors),
|
|
21
|
+
source_file: options.sourceFile,
|
|
22
|
+
source_start_line: heading.line,
|
|
23
|
+
source_end_line: sourceEndLine
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
if (definitions.length === 0) {
|
|
27
|
+
errors.push(`${options.sourceFile} must define ${options.kind} items with Markdown headings like "## ${options.kind}-001: ..."`);
|
|
28
|
+
}
|
|
29
|
+
throwIfErrors(errors);
|
|
30
|
+
return definitions;
|
|
31
|
+
}
|
|
32
|
+
export function parseDocumentFields(content, sourceFile, allowedFields) {
|
|
33
|
+
const errors = [];
|
|
34
|
+
const fields = parseFields(splitLines(content), sourceFile, 1, allowedFields, errors);
|
|
35
|
+
throwIfErrors(errors);
|
|
36
|
+
return fields;
|
|
37
|
+
}
|
|
38
|
+
export function fieldText(fields, name) {
|
|
39
|
+
const field = fields[name];
|
|
40
|
+
if (!field) {
|
|
41
|
+
return "";
|
|
42
|
+
}
|
|
43
|
+
return Array.isArray(field.value) ? field.value.join("\n").trim() : field.value.trim();
|
|
44
|
+
}
|
|
45
|
+
export function fieldArray(fields, name) {
|
|
46
|
+
const field = fields[name];
|
|
47
|
+
if (!field) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
if (Array.isArray(field.value)) {
|
|
51
|
+
return field.value.flatMap(splitScalarArray).filter(Boolean);
|
|
52
|
+
}
|
|
53
|
+
return splitScalarArray(field.value);
|
|
54
|
+
}
|
|
55
|
+
export function fieldBoolean(fields, name) {
|
|
56
|
+
const value = fieldText(fields, name).toLowerCase();
|
|
57
|
+
if (value === "true") {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
if (value === "false") {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
export function fieldLine(fields, name) {
|
|
66
|
+
return fields[name]?.line;
|
|
67
|
+
}
|
|
68
|
+
function parseFields(lines, sourceFile, startLine, allowedFields, errors) {
|
|
69
|
+
const fields = {};
|
|
70
|
+
for (let index = 0; index < lines.length; index++) {
|
|
71
|
+
const line = lines[index];
|
|
72
|
+
const lineNumber = startLine + index;
|
|
73
|
+
const heading = /^(#{1,6})\s+(.+?)\s*$/.exec(line);
|
|
74
|
+
if (heading && allowedFields.has(heading[2].trim())) {
|
|
75
|
+
errors.push(`${sourceFile}:${lineNumber} field headings are not supported; use "${heading[2].trim()}: ..."`);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (/^\s*\|/.test(line) && containsKnownField(line, allowedFields)) {
|
|
79
|
+
errors.push(`${sourceFile}:${lineNumber} table fields are not supported; use key: value fields`);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const match = /^([a-z][a-z0-9_]*)\s*:\s*(.*)$/.exec(line);
|
|
83
|
+
if (!match) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const name = match[1];
|
|
87
|
+
if (!allowedFields.has(name)) {
|
|
88
|
+
if (name.includes("_")) {
|
|
89
|
+
errors.push(`${sourceFile}:${lineNumber} unknown field ${name}`);
|
|
90
|
+
}
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (fields[name]) {
|
|
94
|
+
errors.push(`${sourceFile}:${lineNumber} duplicate field ${name}`);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const parsed = parseFieldValue(lines, index, match[2], sourceFile, lineNumber, errors);
|
|
98
|
+
fields[name] = { value: parsed.value, line: lineNumber };
|
|
99
|
+
index = parsed.endIndex;
|
|
100
|
+
}
|
|
101
|
+
return fields;
|
|
102
|
+
}
|
|
103
|
+
function parseFieldValue(lines, index, rest, sourceFile, lineNumber, errors) {
|
|
104
|
+
const trimmed = rest.trim();
|
|
105
|
+
if (trimmed === "|") {
|
|
106
|
+
const block = [];
|
|
107
|
+
let cursor = index + 1;
|
|
108
|
+
for (; cursor < lines.length; cursor++) {
|
|
109
|
+
const next = lines[cursor];
|
|
110
|
+
if (next.trim() && isTopLevelFieldOrHeading(next)) {
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
block.push(next.replace(/^\s{0,2}/, ""));
|
|
114
|
+
}
|
|
115
|
+
if (block.length === 0) {
|
|
116
|
+
errors.push(`${sourceFile}:${lineNumber} block field must include indented text`);
|
|
117
|
+
}
|
|
118
|
+
return { value: block.join("\n").trim(), endIndex: cursor - 1 };
|
|
119
|
+
}
|
|
120
|
+
if (trimmed) {
|
|
121
|
+
return { value: cleanValue(trimmed), endIndex: index };
|
|
122
|
+
}
|
|
123
|
+
const values = [];
|
|
124
|
+
let cursor = index + 1;
|
|
125
|
+
for (; cursor < lines.length; cursor++) {
|
|
126
|
+
const next = lines[cursor];
|
|
127
|
+
if (!next.trim()) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (isTopLevelFieldOrHeading(next)) {
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
const listItem = /^\s*[-*+]\s+(.+?)\s*$/.exec(next);
|
|
134
|
+
if (!listItem) {
|
|
135
|
+
errors.push(`${sourceFile}:${cursor + 1} field lists must use indented "- item" entries or key: | blocks`);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
values.push(cleanValue(listItem[1]));
|
|
139
|
+
}
|
|
140
|
+
return { value: values, endIndex: cursor - 1 };
|
|
141
|
+
}
|
|
142
|
+
function isTopLevelFieldOrHeading(line) {
|
|
143
|
+
if (/^(#{1,6})\s+/.test(line)) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
return /^([a-z][a-z0-9_]*)\s*:/.test(line);
|
|
147
|
+
}
|
|
148
|
+
function rejectListStyleDefinitions(lines, options, errors) {
|
|
149
|
+
const idPattern = options.kind === "PI" ? "PI" : "AC";
|
|
150
|
+
const listPattern = new RegExp(`^\\s*[-*+]\\s+(${idPattern}-\\d{3,})\\b\\s*[:.-]?`, "i");
|
|
151
|
+
for (let index = 0; index < lines.length; index++) {
|
|
152
|
+
const match = listPattern.exec(lines[index]);
|
|
153
|
+
if (!match) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const block = listItemBlock(lines, index);
|
|
157
|
+
if (!block.some((line) => containsKnownField(line, options.allowedFields))) {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
const id = match[1].toUpperCase();
|
|
161
|
+
errors.push(`${id} list-style definition is not allowed at ${options.sourceFile}:${index + 1}; use "## ${id}: ..."`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function listItemBlock(lines, start) {
|
|
165
|
+
const block = [lines[start]];
|
|
166
|
+
for (let index = start + 1; index < lines.length; index++) {
|
|
167
|
+
const line = lines[index];
|
|
168
|
+
if (line.trim() && !/^\s/.test(line)) {
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
block.push(line);
|
|
172
|
+
}
|
|
173
|
+
return block;
|
|
174
|
+
}
|
|
175
|
+
function findDefinitionHeadings(lines, kind) {
|
|
176
|
+
const pattern = new RegExp(`^(#{1,6})\\s+(${kind}-\\d{3,})\\b(?:\\s*[:.-]\\s*|\\s+)?(.*?)\\s*$`, "i");
|
|
177
|
+
return lines.flatMap((line, index) => {
|
|
178
|
+
const match = pattern.exec(line);
|
|
179
|
+
if (!match) {
|
|
180
|
+
return [];
|
|
181
|
+
}
|
|
182
|
+
return [{
|
|
183
|
+
id: match[2].toUpperCase(),
|
|
184
|
+
level: match[1].length,
|
|
185
|
+
title: cleanValue(match[3]) || match[2].toUpperCase(),
|
|
186
|
+
index,
|
|
187
|
+
line: index + 1
|
|
188
|
+
}];
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
function sectionEndLine(lines, headingIndex, headingLevel) {
|
|
192
|
+
for (let index = headingIndex + 1; index < lines.length; index++) {
|
|
193
|
+
const match = /^(#{1,6})\s+/.exec(lines[index]);
|
|
194
|
+
if (match && match[1].length <= headingLevel) {
|
|
195
|
+
return index;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return lines.length;
|
|
199
|
+
}
|
|
200
|
+
function containsKnownField(line, allowedFields) {
|
|
201
|
+
for (const field of allowedFields) {
|
|
202
|
+
if (new RegExp(`\\b${field}\\b\\s*:`, "i").test(line) || new RegExp(`\\b${field}\\b`, "i").test(line)) {
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
function splitScalarArray(value) {
|
|
209
|
+
return value
|
|
210
|
+
.split(/[,;\n]/)
|
|
211
|
+
.map((item) => cleanValue(item))
|
|
212
|
+
.filter(Boolean);
|
|
213
|
+
}
|
|
214
|
+
function splitLines(content) {
|
|
215
|
+
return content.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
216
|
+
}
|
|
217
|
+
function cleanValue(value) {
|
|
218
|
+
return value.replace(/^[-#*\s]+/, "").trim();
|
|
219
|
+
}
|
|
220
|
+
function throwIfErrors(errors) {
|
|
221
|
+
if (errors.length > 0) {
|
|
222
|
+
throw new Error(`Superpowers source compile failed:\n- ${errors.join("\n- ")}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -122,6 +122,9 @@ export interface SuperpowersProgressState {
|
|
|
122
122
|
}
|
|
123
123
|
export interface SuperpowersPlanItem {
|
|
124
124
|
requirement: string;
|
|
125
|
+
source_file: string;
|
|
126
|
+
source_start_line: number;
|
|
127
|
+
source_end_line: number;
|
|
125
128
|
delivery_scope: SuperpowersPlanDeliveryScope | "";
|
|
126
129
|
capability_target: string;
|
|
127
130
|
representative_samples: string[];
|
|
@@ -137,6 +140,9 @@ export interface SuperpowersPlanItem {
|
|
|
137
140
|
}
|
|
138
141
|
export interface SuperpowersAcceptanceCriterion {
|
|
139
142
|
scope: string;
|
|
143
|
+
source_file: string;
|
|
144
|
+
source_start_line: number;
|
|
145
|
+
source_end_line: number;
|
|
140
146
|
acceptance_scope: SuperpowersAcceptanceScope | "";
|
|
141
147
|
ac_validates: string[];
|
|
142
148
|
ac_does_not_validate: string[];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type SuperpowersTaskState } from "./superpowers-task-state-schema.js";
|
|
2
2
|
import type { ValidatorReport } from "./validators.js";
|
|
3
3
|
export declare function validateSuperpowersState(projectRoot: string, args?: string[]): Promise<ValidatorReport>;
|
|
4
|
+
export declare function completionConditionErrors(state: SuperpowersTaskState): string[];
|
|
4
5
|
export declare function allCompletionConditionsSatisfied(state: SuperpowersTaskState): boolean;
|
|
@@ -221,6 +221,9 @@ function validateFinalCompletion(state, errors) {
|
|
|
221
221
|
errors.push("product_goal_complete=true but Context Delta coverage is unresolved");
|
|
222
222
|
}
|
|
223
223
|
}
|
|
224
|
+
validateFullPopulationEvidence(state, errors);
|
|
225
|
+
}
|
|
226
|
+
function validateFullPopulationEvidence(state, errors) {
|
|
224
227
|
if (fullPopulationRequired(state)) {
|
|
225
228
|
const sampleOnlyEvidence = state.evidence.filter((evidence) => evidence.does_not_prove.some((claim) => /\b(full[-_ ]?population|all[-_ ]?provider|all[-_ ]?interface|all[-_ ]?platform)\b/i.test(claim)));
|
|
226
229
|
if (sampleOnlyEvidence.length > 0) {
|
|
@@ -230,11 +233,11 @@ function validateFinalCompletion(state, errors) {
|
|
|
230
233
|
}
|
|
231
234
|
}
|
|
232
235
|
}
|
|
233
|
-
export function
|
|
236
|
+
export function completionConditionErrors(state) {
|
|
234
237
|
const errors = [];
|
|
235
238
|
validateShape(state, errors);
|
|
236
239
|
if (!hasUsableShape(state)) {
|
|
237
|
-
return
|
|
240
|
+
return errors;
|
|
238
241
|
}
|
|
239
242
|
validateDeliveryContract(state, errors);
|
|
240
243
|
validateScopeConflicts(state, errors);
|
|
@@ -242,11 +245,21 @@ export function allCompletionConditionsSatisfied(state) {
|
|
|
242
245
|
validateEvidenceRecords(state, errors);
|
|
243
246
|
validateProofLayers(state, errors);
|
|
244
247
|
validateAuditor(state, errors);
|
|
248
|
+
validateFullPopulationEvidence(state, errors);
|
|
245
249
|
const planItems = Object.values(state.graph.plan_items);
|
|
246
250
|
const acceptanceCriteria = Object.values(state.graph.acceptance_criteria);
|
|
247
251
|
const proofLayers = Object.values(state.graph.proof_layers);
|
|
248
252
|
const allPlansComplete = planItems.every((item) => item.status === "complete" || item.status === "out_of_scope_NA");
|
|
249
253
|
const allAcsComplete = acceptanceCriteria.every((ac) => ac.status === "complete" || ac.status === "out_of_scope_NA");
|
|
250
254
|
const allLayersSatisfied = proofLayers.every((layer) => !layer.required || layer.status === "satisfied");
|
|
251
|
-
|
|
255
|
+
if (planItems.length === 0 || acceptanceCriteria.length === 0 || proofLayers.length === 0) {
|
|
256
|
+
errors.push("completion conditions require a compiled task graph with plan items, ACs and proof layers");
|
|
257
|
+
}
|
|
258
|
+
if (!allPlansComplete || !allAcsComplete || !allLayersSatisfied) {
|
|
259
|
+
errors.push("completion conditions require all required plan items, ACs and proof layers to be complete");
|
|
260
|
+
}
|
|
261
|
+
return errors;
|
|
262
|
+
}
|
|
263
|
+
export function allCompletionConditionsSatisfied(state) {
|
|
264
|
+
return completionConditionErrors(state).length === 0;
|
|
252
265
|
}
|