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.
Files changed (27) hide show
  1. package/README.md +15 -11
  2. package/assets/README.md +16 -12
  3. package/assets/README.zh-CN.md +10 -6
  4. package/assets/agents/AGENTS_CORE.md +42 -42
  5. package/assets/skills/composite-long-task-workflow/SKILL.md +212 -0
  6. package/assets/skills/composite-long-task-workflow/assets/execution-binding.template.md +31 -0
  7. package/assets/skills/composite-long-task-workflow/assets/goal-objective.template.md +22 -0
  8. package/assets/skills/composite-long-task-workflow/references/composite-long-task-workflow-protocol.md +633 -0
  9. package/assets/skills/normal-long-task/SKILL.md +12 -12
  10. package/dist/commands/composite-long-task.d.ts +6 -0
  11. package/dist/commands/composite-long-task.js +103 -0
  12. package/dist/commands/index.js +5 -3
  13. package/dist/commands/superpowers.js +6 -87
  14. package/dist/lib/composite-long-task-renderer.d.ts +12 -0
  15. package/dist/lib/composite-long-task-renderer.js +153 -0
  16. package/dist/lib/superpowers-task-compile.js +5 -121
  17. package/dist/lib/superpowers-task-derive.js +1 -1
  18. package/dist/lib/superpowers-task-gates.js +6 -4
  19. package/dist/lib/superpowers-task-source-compile.d.ts +5 -0
  20. package/dist/lib/superpowers-task-source-compile.js +190 -0
  21. package/dist/lib/superpowers-task-source-parser.d.ts +23 -0
  22. package/dist/lib/superpowers-task-source-parser.js +224 -0
  23. package/dist/lib/superpowers-task-state-schema.d.ts +6 -0
  24. package/dist/lib/superpowers-task-validator.d.ts +1 -0
  25. package/dist/lib/superpowers-task-validator.js +16 -3
  26. package/package.json +1 -1
  27. 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 allCompletionConditionsSatisfied(state) {
236
+ export function completionConditionErrors(state) {
234
237
  const errors = [];
235
238
  validateShape(state, errors);
236
239
  if (!hasUsableShape(state)) {
237
- return false;
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
- return errors.length === 0 && planItems.length > 0 && acceptanceCriteria.length > 0 && proofLayers.length > 0 && allPlansComplete && allAcsComplete && allLayersSatisfied;
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-tiny-context-harness",
3
- "version": "0.2.78",
3
+ "version": "0.2.80",
4
4
  "description": "Minimal project memory and validation harness for AI coding agents.",
5
5
  "license": "MIT",
6
6
  "author": "Seven128",