project-tiny-context-harness 0.2.75 → 0.2.76

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.
@@ -0,0 +1,223 @@
1
+ import { createHash } from "node:crypto";
2
+ import path from "node:path";
3
+ import { ensureDir, pathExists, readText, writeTextIfChanged } from "./fs.js";
4
+ import { appendSuperpowersEvent } from "./superpowers-task-events.js";
5
+ import { SUPERPOWERS_TASK_STATE_JSON_SCHEMA, SUPERPOWERS_TASK_STATE_SCHEMA_VERSION, asStringArray, isRecord } from "./superpowers-task-state-schema.js";
6
+ const SOURCE_FILES = {
7
+ product_architecture_source: {
8
+ path: "product-architecture-source.md",
9
+ authority: "intent_scope_boundaries"
10
+ },
11
+ technical_realization_plan: {
12
+ path: "technical-realization-plan.md",
13
+ authority: "plan_items_execution_blueprint_conformance"
14
+ },
15
+ acceptance_checklist: {
16
+ path: "acceptance-checklist.md",
17
+ authority: "acs_completion_semantics_proof_layers"
18
+ }
19
+ };
20
+ export async function initializeSuperpowersTask(workdir, options = {}) {
21
+ await ensureDir(path.join(workdir, "derived"));
22
+ await writeTextIfChanged(path.join(workdir, "task-state.schema.json"), `${stableJson(SUPERPOWERS_TASK_STATE_JSON_SCHEMA)}\n`);
23
+ for (const source of Object.values(SOURCE_FILES)) {
24
+ const target = path.join(workdir, source.path);
25
+ if (!(await pathExists(target))) {
26
+ await writeTextIfChanged(target, `# ${source.path.replace(/\.md$/, "").replace(/-/g, " ")}\n`);
27
+ }
28
+ }
29
+ const now = new Date().toISOString();
30
+ const state = {
31
+ meta: {
32
+ task_id: options.taskId ?? `SP-${compactDate(now)}-001`,
33
+ plan_slug: options.planSlug ?? path.basename(workdir),
34
+ created_at: now,
35
+ updated_at: now,
36
+ schema_version: SUPERPOWERS_TASK_STATE_SCHEMA_VERSION,
37
+ goal_type: options.goalType ?? "implementation",
38
+ product_goal_complete: false,
39
+ acceptance_target_status: "not_run",
40
+ audit_task_complete: false
41
+ },
42
+ sources: await sourceRecords(workdir),
43
+ context: {
44
+ product_context_delta: "none",
45
+ technical_context_delta: "none",
46
+ source_to_context_coverage: [],
47
+ context_to_implementation_binding: []
48
+ },
49
+ graph: {
50
+ plan_items: {},
51
+ acceptance_criteria: {},
52
+ proof_layers: {},
53
+ edges: []
54
+ },
55
+ slices: [],
56
+ evidence: [],
57
+ gates: {},
58
+ progress: {},
59
+ blockers: [],
60
+ final: {
61
+ product_goal_complete: false,
62
+ acceptance_target_status: "not_run",
63
+ audit_task_complete: false,
64
+ completion_basis: []
65
+ }
66
+ };
67
+ await saveSuperpowersState(workdir, state);
68
+ await appendSuperpowersEvent(workdir, "task_initialized", { task_id: state.meta.task_id });
69
+ return state;
70
+ }
71
+ export async function loadSuperpowersState(workdir) {
72
+ return JSON.parse(await readText(path.join(workdir, "task-state.json")));
73
+ }
74
+ export async function saveSuperpowersState(workdir, state) {
75
+ state.meta.updated_at = new Date().toISOString();
76
+ await writeTextIfChanged(path.join(workdir, "task-state.json"), `${stableJson(state)}\n`);
77
+ }
78
+ export async function applySliceDelta(workdir, deltaFile) {
79
+ const state = await loadSuperpowersState(workdir);
80
+ const delta = JSON.parse(await readText(deltaFile));
81
+ const sliceId = String(delta.slice_id ?? "");
82
+ const progressValue = isRecord(delta.progress_value) ? delta.progress_value : undefined;
83
+ if (!sliceId) {
84
+ throw new Error("slice_delta must include slice_id");
85
+ }
86
+ if (!progressValue || !String(progressValue.type ?? "").trim() || asStringArray(progressValue.closed_items).length === 0) {
87
+ throw new Error("slice_delta must include progress_value with type and closed_items");
88
+ }
89
+ const evidenceRecords = readEvidenceRecords(delta.evidence_records);
90
+ for (const evidence of evidenceRecords) {
91
+ const existingIndex = state.evidence.findIndex((item) => item.evidence_id === evidence.evidence_id);
92
+ if (existingIndex >= 0) {
93
+ state.evidence[existingIndex] = evidence;
94
+ }
95
+ else {
96
+ state.evidence.push(evidence);
97
+ }
98
+ }
99
+ const slice = {
100
+ slice_id: sliceId,
101
+ slice_goal: String(delta.slice_goal ?? ""),
102
+ touched_plan_items: asStringArray(delta.touched_plan_items),
103
+ touched_acs: asStringArray(delta.touched_acs),
104
+ missing_layer_classes: asStringArray(delta.missing_layer_classes),
105
+ code_changes: asStringArray(delta.code_changes),
106
+ evidence_records: evidenceRecords.map((item) => item.evidence_id),
107
+ closed_layers: asStringArray(delta.closed_layers),
108
+ remaining_layers: asStringArray(delta.remaining_layers),
109
+ blockers: Array.isArray(delta.blockers) ? delta.blockers : [],
110
+ cleanup_assertions: asStringArray(delta.cleanup_assertions),
111
+ progress_value: {
112
+ type: String(progressValue.type),
113
+ closed_items: asStringArray(progressValue.closed_items),
114
+ why_it_reduces_rework: String(progressValue.why_it_reduces_rework ?? "")
115
+ }
116
+ };
117
+ state.slices.push(slice);
118
+ for (const layerId of slice.closed_layers) {
119
+ const layer = state.graph.proof_layers[layerId];
120
+ if (!layer) {
121
+ continue;
122
+ }
123
+ layer.status = "satisfied";
124
+ layer.evidence_ids = unique([...layer.evidence_ids, ...evidenceRecords.filter((item) => item.proves.includes(layerId)).map((item) => item.evidence_id)]);
125
+ }
126
+ recomputeStatuses(state);
127
+ await saveSuperpowersState(workdir, state);
128
+ await appendSuperpowersEvent(workdir, "slice_delta_applied", { slice_id: sliceId });
129
+ return state;
130
+ }
131
+ export async function refreshSourceHashes(workdir, state) {
132
+ state.sources = await sourceRecords(workdir);
133
+ }
134
+ export function recomputeStatuses(state) {
135
+ for (const [acId, ac] of Object.entries(state.graph.acceptance_criteria)) {
136
+ const layerIds = ac.required_proof_layers.map((layer) => `${acId}.${layer}`);
137
+ if (layerIds.length === 0) {
138
+ ac.status = "not_run";
139
+ }
140
+ else if (layerIds.every((layerId) => state.graph.proof_layers[layerId]?.status === "satisfied")) {
141
+ ac.status = "complete";
142
+ }
143
+ else if (layerIds.some((layerId) => state.graph.proof_layers[layerId]?.status === "satisfied")) {
144
+ ac.status = "partial";
145
+ }
146
+ else {
147
+ ac.status = "not_run";
148
+ }
149
+ }
150
+ for (const item of Object.values(state.graph.plan_items)) {
151
+ if (item.related_acs.length > 0 && item.related_acs.every((acId) => state.graph.acceptance_criteria[acId]?.status === "complete")) {
152
+ item.status = "complete";
153
+ }
154
+ else if (item.related_acs.some((acId) => state.graph.acceptance_criteria[acId]?.status === "partial")) {
155
+ item.status = "partial";
156
+ }
157
+ else if (item.status === "complete") {
158
+ item.status = "not_started";
159
+ }
160
+ }
161
+ }
162
+ export async function sourceRecords(workdir) {
163
+ const sources = {};
164
+ for (const [key, source] of Object.entries(SOURCE_FILES)) {
165
+ const file = path.join(workdir, source.path);
166
+ const content = (await pathExists(file)) ? await readText(file) : "";
167
+ sources[key] = { path: source.path, sha256: sha256(content), authority: source.authority };
168
+ }
169
+ return sources;
170
+ }
171
+ export function sha256(value) {
172
+ return createHash("sha256").update(value).digest("hex");
173
+ }
174
+ export function stableJson(value) {
175
+ return JSON.stringify(sortJson(value), null, 2);
176
+ }
177
+ function readEvidenceRecords(value) {
178
+ if (!Array.isArray(value)) {
179
+ return [];
180
+ }
181
+ return value.filter(isRecord).map((item) => ({
182
+ evidence_id: String(item.evidence_id ?? item.evidenceId ?? ""),
183
+ slice_id: String(item.slice_id ?? item.sliceId ?? ""),
184
+ type: String(item.type ?? ""),
185
+ freshness: isRecord(item.freshness)
186
+ ? {
187
+ created_at: String(item.freshness.created_at ?? ""),
188
+ valid_for: String(item.freshness.valid_for ?? ""),
189
+ stale_after: item.freshness.stale_after === null ? null : item.freshness.stale_after === undefined ? null : String(item.freshness.stale_after)
190
+ }
191
+ : { created_at: "", valid_for: "", stale_after: null },
192
+ command: item.command === undefined ? undefined : String(item.command),
193
+ artifact_paths: asStringArray(item.artifact_paths),
194
+ proves: asStringArray(item.proves),
195
+ does_not_prove: asStringArray(item.does_not_prove),
196
+ redaction: isRecord(item.redaction)
197
+ ? { checked: item.redaction.checked === true, contains_secret: item.redaction.contains_secret === true }
198
+ : { checked: false, contains_secret: false },
199
+ reviewability: isRecord(item.reviewability)
200
+ ? {
201
+ external_reviewer_can_reproduce: item.reviewability.external_reviewer_can_reproduce === true,
202
+ reproduction_steps: String(item.reviewability.reproduction_steps ?? "")
203
+ }
204
+ : { external_reviewer_can_reproduce: false, reproduction_steps: "" },
205
+ sibling_substitution_used: item.sibling_substitution_used === true,
206
+ sibling_substitution_approval_source: item.sibling_substitution_approval_source === undefined ? undefined : String(item.sibling_substitution_approval_source)
207
+ }));
208
+ }
209
+ function sortJson(value) {
210
+ if (Array.isArray(value)) {
211
+ return value.map(sortJson);
212
+ }
213
+ if (!isRecord(value)) {
214
+ return value;
215
+ }
216
+ return Object.fromEntries(Object.keys(value).sort().map((key) => [key, sortJson(value[key])]));
217
+ }
218
+ function unique(values) {
219
+ return [...new Set(values.filter(Boolean))];
220
+ }
221
+ function compactDate(value) {
222
+ return value.slice(0, 10).replace(/-/g, "");
223
+ }
@@ -0,0 +1,4 @@
1
+ import { type SuperpowersTaskState } from "./superpowers-task-state-schema.js";
2
+ import type { ValidatorReport } from "./validators.js";
3
+ export declare function validateSuperpowersState(projectRoot: string, args?: string[]): Promise<ValidatorReport>;
4
+ export declare function allCompletionConditionsSatisfied(state: SuperpowersTaskState): boolean;
@@ -0,0 +1,238 @@
1
+ import path from "node:path";
2
+ import { pathExists, readText } from "./fs.js";
3
+ import { findSensitiveEvidence } from "./plan-acceptance-evidence.js";
4
+ import { primitiveText, repoRelative, resolveInputDir } from "./plan-validator-common.js";
5
+ import { derivedMatchesState } from "./superpowers-task-derive.js";
6
+ import { loadSuperpowersState, sha256 } from "./superpowers-task-state.js";
7
+ import { isRecord } from "./superpowers-task-state-schema.js";
8
+ export async function validateSuperpowersState(projectRoot, args = []) {
9
+ const info = [];
10
+ const warnings = [];
11
+ const hygiene = [];
12
+ const errors = [];
13
+ const targetDir = await resolveInputDir(projectRoot, args[0], "tmp/ty-context/plan-acceptance");
14
+ const statePath = path.join(targetDir, "task-state.json");
15
+ if (!(await pathExists(statePath))) {
16
+ return { info, warnings, hygiene, errors: [`superpowers task state is missing: ${repoRelative(projectRoot, statePath)}`] };
17
+ }
18
+ let state;
19
+ try {
20
+ state = await loadSuperpowersState(targetDir);
21
+ }
22
+ catch (error) {
23
+ return {
24
+ info,
25
+ warnings,
26
+ hygiene,
27
+ errors: [`${repoRelative(projectRoot, statePath)} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`]
28
+ };
29
+ }
30
+ validateShape(state, errors);
31
+ if (!hasUsableShape(state)) {
32
+ info.push("checked superpowers task state with unusable or incomplete shape");
33
+ return { info, warnings, hygiene, errors };
34
+ }
35
+ await validateSourceHashes(targetDir, state, errors);
36
+ validateGraphReferences(state, errors);
37
+ validateEvidenceRecords(state, errors);
38
+ validateProofLayers(state, errors);
39
+ validateAuditor(state, errors);
40
+ validateFinalCompletion(state, errors);
41
+ errors.push(...(await derivedMatchesState(targetDir, state)));
42
+ info.push(`checked superpowers task state ${repoRelative(projectRoot, targetDir)} plan_items=${Object.keys(state.graph?.plan_items ?? {}).length} acs=${Object.keys(state.graph?.acceptance_criteria ?? {}).length} evidence=${state.evidence?.length ?? 0}`);
43
+ if (errors.length === 0) {
44
+ info.push("Superpowers task state validation passed");
45
+ }
46
+ return { info, warnings, hygiene, errors };
47
+ }
48
+ async function validateSourceHashes(workdir, state, errors) {
49
+ for (const [key, source] of Object.entries(state.sources ?? {})) {
50
+ const file = path.join(workdir, source.path);
51
+ if (!(await pathExists(file))) {
52
+ errors.push(`source file is missing for ${key}: ${source.path}`);
53
+ continue;
54
+ }
55
+ const actual = sha256(await readText(file));
56
+ if (actual !== source.sha256) {
57
+ errors.push(`source hash mismatch for ${key}: expected ${source.sha256}, actual ${actual}; recompile graph before continuing`);
58
+ }
59
+ }
60
+ }
61
+ function validateShape(state, errors) {
62
+ if (state.meta?.schema_version !== "superpowers-task-state-v1") {
63
+ errors.push("task-state.json schema_version must be superpowers-task-state-v1");
64
+ }
65
+ for (const key of ["meta", "sources", "context", "graph", "slices", "evidence", "gates", "progress", "blockers", "final"]) {
66
+ if (!(key in state)) {
67
+ errors.push(`task-state.json is missing section: ${key}`);
68
+ }
69
+ }
70
+ }
71
+ function hasUsableShape(state) {
72
+ const candidate = state;
73
+ return (isRecord(candidate.meta) &&
74
+ isRecord(candidate.sources) &&
75
+ isRecord(candidate.context) &&
76
+ isRecord(candidate.graph) &&
77
+ isRecord(candidate.graph.plan_items) &&
78
+ isRecord(candidate.graph.acceptance_criteria) &&
79
+ isRecord(candidate.graph.proof_layers) &&
80
+ Array.isArray(candidate.slices) &&
81
+ Array.isArray(candidate.evidence) &&
82
+ isRecord(candidate.gates) &&
83
+ isRecord(candidate.progress) &&
84
+ Array.isArray(candidate.blockers) &&
85
+ isRecord(candidate.final));
86
+ }
87
+ function validateGraphReferences(state, errors) {
88
+ const planIds = new Set(Object.keys(state.graph?.plan_items ?? {}));
89
+ const acIds = new Set(Object.keys(state.graph?.acceptance_criteria ?? {}));
90
+ for (const [planId, item] of Object.entries(state.graph?.plan_items ?? {})) {
91
+ for (const acId of item.related_acs ?? []) {
92
+ if (!acIds.has(acId)) {
93
+ errors.push(`plan item ${planId} references unknown AC: ${acId}`);
94
+ }
95
+ }
96
+ for (const layerId of item.required_proof_layers ?? []) {
97
+ if (!state.graph.proof_layers[layerId]) {
98
+ errors.push(`plan item ${planId} references unknown proof layer: ${layerId}`);
99
+ }
100
+ }
101
+ }
102
+ for (const [acId, ac] of Object.entries(state.graph?.acceptance_criteria ?? {})) {
103
+ for (const planId of ac.related_plan_items ?? []) {
104
+ if (!planIds.has(planId)) {
105
+ errors.push(`AC ${acId} references unknown plan item: ${planId}`);
106
+ }
107
+ }
108
+ for (const layer of ac.required_proof_layers ?? []) {
109
+ const layerId = `${acId}.${layer}`;
110
+ if (!state.graph.proof_layers[layerId]) {
111
+ errors.push(`AC ${acId} references unknown proof layer: ${layerId}`);
112
+ }
113
+ }
114
+ }
115
+ }
116
+ function validateEvidenceRecords(state, errors) {
117
+ const ids = new Set();
118
+ for (const [index, evidence] of (state.evidence ?? []).entries()) {
119
+ const label = `evidence ${evidence.evidence_id || index + 1}`;
120
+ if (!evidence.evidence_id) {
121
+ errors.push(`${label} is missing evidence_id`);
122
+ }
123
+ else if (ids.has(evidence.evidence_id)) {
124
+ errors.push(`${label} duplicates evidence_id`);
125
+ }
126
+ ids.add(evidence.evidence_id);
127
+ if (!evidence.slice_id) {
128
+ errors.push(`${label} is missing slice_id`);
129
+ }
130
+ if ((evidence.proves ?? []).length === 0) {
131
+ errors.push(`${label} is missing proves`);
132
+ }
133
+ if ((evidence.does_not_prove ?? []).length === 0) {
134
+ errors.push(`${label} is missing does_not_prove`);
135
+ }
136
+ if (!evidence.freshness?.created_at || !evidence.freshness.valid_for) {
137
+ errors.push(`${label} is missing freshness`);
138
+ }
139
+ if (evidence.freshness?.stale_after && Date.parse(evidence.freshness.stale_after) < Date.now()) {
140
+ errors.push(`${label} is stale evidence: ${evidence.freshness.stale_after}`);
141
+ }
142
+ if (evidence.redaction?.checked !== true) {
143
+ errors.push(`${label} redaction.checked must be true`);
144
+ }
145
+ if (evidence.redaction?.contains_secret === true) {
146
+ errors.push(`${label} redaction contains_secret is true`);
147
+ }
148
+ if (evidence.reviewability?.external_reviewer_can_reproduce !== true || !evidence.reviewability.reproduction_steps) {
149
+ errors.push(`${label} is not reviewable by an external reviewer`);
150
+ }
151
+ const sensitive = findSensitiveEvidence(primitiveText(evidence));
152
+ if (sensitive) {
153
+ errors.push(`${label} contains raw secret/token/cookie material: ${sensitive}`);
154
+ }
155
+ if (evidence.sibling_substitution_used === true && !evidence.sibling_substitution_approval_source) {
156
+ errors.push(`${label} uses sibling substitution without approval`);
157
+ }
158
+ for (const proofLayer of evidence.proves ?? []) {
159
+ if (proofLayer.endsWith(".runtime") && /\b(mock|unit|viewmodel)\b/i.test(evidence.type)) {
160
+ errors.push(`${label} runtime proof cannot be mock/unit/viewmodel only`);
161
+ }
162
+ if (proofLayer.endsWith(".ui_browser") && !/\b(browser|ui_browser|screenshot)\b/i.test(evidence.type)) {
163
+ errors.push(`${label} UI proof must use browser owner surface evidence`);
164
+ }
165
+ }
166
+ }
167
+ }
168
+ function validateProofLayers(state, errors) {
169
+ const evidenceById = new Map((state.evidence ?? []).map((item) => [item.evidence_id, item]));
170
+ for (const [layerId, layer] of Object.entries(state.graph?.proof_layers ?? {})) {
171
+ if (layer.status === "satisfied" && layer.evidence_ids.length === 0) {
172
+ errors.push(`proof layer ${layerId} is satisfied but has no evidence_ids`);
173
+ }
174
+ for (const evidenceId of layer.evidence_ids ?? []) {
175
+ const evidence = evidenceById.get(evidenceId);
176
+ if (!evidence) {
177
+ errors.push(`proof layer ${layerId} references unknown evidence_id: ${evidenceId}`);
178
+ continue;
179
+ }
180
+ if (!evidence.proves.includes(layerId)) {
181
+ errors.push(`proof layer ${layerId} references ${evidenceId} but that evidence does not prove it`);
182
+ }
183
+ }
184
+ }
185
+ }
186
+ function validateAuditor(state, errors) {
187
+ const auditor = state.gates?.auditor;
188
+ if (!isRecord(auditor)) {
189
+ return;
190
+ }
191
+ const status = String(auditor.auditor_status ?? "").toLowerCase();
192
+ const findings = Array.isArray(auditor.findings) ? auditor.findings : [];
193
+ if (status === "blocking_gap" || findings.some((finding) => isRecord(finding) && String(finding.severity ?? "").toLowerCase() === "blocking")) {
194
+ errors.push(`auditor blocker remains: ${findings.map((finding) => (isRecord(finding) ? finding.id : "")).filter(Boolean).join(", ") || status}`);
195
+ }
196
+ }
197
+ function validateFinalCompletion(state, errors) {
198
+ const finalComplete = state.final?.product_goal_complete === true || state.meta?.product_goal_complete === true;
199
+ if (!finalComplete) {
200
+ return;
201
+ }
202
+ const planEntries = Object.entries(state.graph.plan_items);
203
+ const acEntries = Object.entries(state.graph.acceptance_criteria);
204
+ const layerEntries = Object.entries(state.graph.proof_layers);
205
+ if (planEntries.length === 0 || acEntries.length === 0 || layerEntries.length === 0) {
206
+ errors.push("product_goal_complete=true but task graph is empty or uncompiled");
207
+ }
208
+ const incompleteAcs = acEntries.filter(([, ac]) => ac.status !== "complete" && ac.status !== "out_of_scope_NA");
209
+ const incompletePlans = planEntries.filter(([, item]) => item.status !== "complete" && item.status !== "out_of_scope_NA");
210
+ const incompleteLayers = layerEntries.filter(([, layer]) => layer.required && layer.status !== "satisfied");
211
+ if (incompleteAcs.length > 0 || incompletePlans.length > 0 || incompleteLayers.length > 0) {
212
+ errors.push("product_goal_complete=true but required plan items, ACs or proof layers are incomplete");
213
+ }
214
+ if (state.context.product_context_delta === "required" || state.context.technical_context_delta === "required") {
215
+ const unresolvedCoverage = (state.context.source_to_context_coverage ?? []).filter((row) => /\b(new_context_required|needs_user_decision|under_scoped)\b/i.test(primitiveText(row)));
216
+ if (unresolvedCoverage.length > 0) {
217
+ errors.push("product_goal_complete=true but Context Delta coverage is unresolved");
218
+ }
219
+ }
220
+ }
221
+ export function allCompletionConditionsSatisfied(state) {
222
+ const errors = [];
223
+ validateShape(state, errors);
224
+ if (!hasUsableShape(state)) {
225
+ return false;
226
+ }
227
+ validateGraphReferences(state, errors);
228
+ validateEvidenceRecords(state, errors);
229
+ validateProofLayers(state, errors);
230
+ validateAuditor(state, errors);
231
+ const planItems = Object.values(state.graph.plan_items);
232
+ const acceptanceCriteria = Object.values(state.graph.acceptance_criteria);
233
+ const proofLayers = Object.values(state.graph.proof_layers);
234
+ const allPlansComplete = planItems.every((item) => item.status === "complete" || item.status === "out_of_scope_NA");
235
+ const allAcsComplete = acceptanceCriteria.every((ac) => ac.status === "complete" || ac.status === "out_of_scope_NA");
236
+ const allLayersSatisfied = proofLayers.every((layer) => !layer.required || layer.status === "satisfied");
237
+ return errors.length === 0 && planItems.length > 0 && acceptanceCriteria.length > 0 && proofLayers.length > 0 && allPlansComplete && allAcsComplete && allLayersSatisfied;
238
+ }
@@ -5,13 +5,15 @@ import { listFiles, pathExists, readText } from "./fs.js";
5
5
  import { runModularityCheck } from "./modularity.js";
6
6
  import { validatePlanAcceptance } from "./plan-acceptance-validator.js";
7
7
  import { validatePlanContract } from "./plan-contract-validator.js";
8
+ import { validateSuperpowersState } from "./superpowers-task-validator.js";
8
9
  import { unsupportedSchemaMessage } from "./schema-guard.js";
9
10
  const VALIDATORS = {
10
11
  "validate-context": validateContext,
11
12
  "validate-code-modularity": validateCodeModularity,
12
13
  "validate-harness": validateHarness,
13
14
  "validate-plan-contract": validatePlanContract,
14
- "validate-plan-acceptance": validatePlanAcceptance
15
+ "validate-plan-acceptance": validatePlanAcceptance,
16
+ "validate-superpowers-state": validateSuperpowersState
15
17
  };
16
18
  const GLOBAL_REQUIRED_SECTIONS = [
17
19
  ...sectionSpecs([
@@ -77,7 +79,7 @@ export async function runValidator(projectRoot, gate, args = []) {
77
79
  return {
78
80
  info: [],
79
81
  errors: [
80
- `unknown validator: ${gate}. Minimal Context Harness supports validate-context, validate-code-modularity, validate-harness, validate-plan-contract and validate-plan-acceptance only.`
82
+ `unknown validator: ${gate}. Minimal Context Harness supports validate-context, validate-code-modularity, validate-harness, validate-plan-contract, validate-plan-acceptance and validate-superpowers-state only.`
81
83
  ]
82
84
  };
83
85
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-tiny-context-harness",
3
- "version": "0.2.75",
3
+ "version": "0.2.76",
4
4
  "description": "Minimal project memory and validation harness for AI coding agents.",
5
5
  "license": "MIT",
6
6
  "author": "Seven128",