project-tiny-context-harness 0.2.82 → 0.2.84

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 (55) hide show
  1. package/README.md +10 -4
  2. package/assets/README.md +13 -7
  3. package/assets/README.zh-CN.md +8 -2
  4. package/assets/protected-harness-baseline.json +20 -0
  5. package/assets/skills/composite-long-task-workflow/SKILL.md +27 -3
  6. package/assets/skills/composite-long-task-workflow/assets/execution-binding.template.md +24 -0
  7. package/assets/skills/composite-long-task-workflow/assets/goal-objective.template.md +6 -15
  8. package/assets/skills/composite-long-task-workflow/references/composite-long-task-workflow-protocol.md +51 -25
  9. package/dist/commands/composite-long-task.js +60 -5
  10. package/dist/lib/superpowers-task-ac010.d.ts +6 -0
  11. package/dist/lib/superpowers-task-ac010.js +26 -0
  12. package/dist/lib/superpowers-task-assertion-normalizers.js +4 -0
  13. package/dist/lib/superpowers-task-assertions.js +18 -4
  14. package/dist/lib/superpowers-task-attempt.d.ts +4 -0
  15. package/dist/lib/superpowers-task-attempt.js +102 -0
  16. package/dist/lib/superpowers-task-command-run-correlation.d.ts +8 -0
  17. package/dist/lib/superpowers-task-command-run-correlation.js +103 -0
  18. package/dist/lib/superpowers-task-command-specs.d.ts +3 -0
  19. package/dist/lib/superpowers-task-command-specs.js +52 -0
  20. package/dist/lib/superpowers-task-compile.d.ts +4 -1
  21. package/dist/lib/superpowers-task-compile.js +7 -1
  22. package/dist/lib/superpowers-task-completion-output.d.ts +52 -0
  23. package/dist/lib/superpowers-task-completion-output.js +228 -0
  24. package/dist/lib/superpowers-task-contradictions.d.ts +6 -0
  25. package/dist/lib/superpowers-task-contradictions.js +126 -0
  26. package/dist/lib/superpowers-task-current-evidence.d.ts +3 -0
  27. package/dist/lib/superpowers-task-current-evidence.js +176 -0
  28. package/dist/lib/superpowers-task-derive.js +69 -8
  29. package/dist/lib/superpowers-task-evidence-kernel.d.ts +19 -0
  30. package/dist/lib/superpowers-task-evidence-kernel.js +347 -0
  31. package/dist/lib/superpowers-task-evidence-records.d.ts +2 -0
  32. package/dist/lib/superpowers-task-evidence-records.js +55 -0
  33. package/dist/lib/superpowers-task-evidence.d.ts +10 -0
  34. package/dist/lib/superpowers-task-evidence.js +147 -0
  35. package/dist/lib/superpowers-task-final-card.d.ts +3 -0
  36. package/dist/lib/superpowers-task-final-card.js +24 -0
  37. package/dist/lib/superpowers-task-gates.d.ts +2 -2
  38. package/dist/lib/superpowers-task-gates.js +87 -35
  39. package/dist/lib/superpowers-task-harness-drift.d.ts +11 -0
  40. package/dist/lib/superpowers-task-harness-drift.js +90 -0
  41. package/dist/lib/superpowers-task-protected-baseline.d.ts +10 -0
  42. package/dist/lib/superpowers-task-protected-baseline.js +66 -0
  43. package/dist/lib/superpowers-task-state-schema.d.ts +116 -3
  44. package/dist/lib/superpowers-task-state-schema.js +23 -1
  45. package/dist/lib/superpowers-task-state-shape.d.ts +3 -0
  46. package/dist/lib/superpowers-task-state-shape.js +50 -0
  47. package/dist/lib/superpowers-task-state.js +17 -37
  48. package/dist/lib/superpowers-task-status.js +11 -1
  49. package/dist/lib/superpowers-task-under-specified.d.ts +7 -0
  50. package/dist/lib/superpowers-task-under-specified.js +61 -0
  51. package/dist/lib/superpowers-task-unregistered-evidence.d.ts +11 -0
  52. package/dist/lib/superpowers-task-unregistered-evidence.js +72 -0
  53. package/dist/lib/superpowers-task-validator.js +43 -27
  54. package/package.json +69 -69
  55. package/source-mappings.yaml +3 -0
@@ -0,0 +1,347 @@
1
+ import { deriveRequiredCommandSpecs } from "./superpowers-task-command-specs.js";
2
+ import { validateCommandRunsForSpec, validateRequiredCommandCorrelation } from "./superpowers-task-command-run-correlation.js";
3
+ import { evaluateAc010Bootstrap } from "./superpowers-task-ac010.js";
4
+ import { evaluateCurrentAttemptArtifact } from "./superpowers-task-current-evidence.js";
5
+ import { scanSuperpowersContradictions } from "./superpowers-task-contradictions.js";
6
+ import { detectHarnessDrift } from "./superpowers-task-harness-drift.js";
7
+ import { evaluateProtectedBaseline } from "./superpowers-task-protected-baseline.js";
8
+ import { validateScopeConflicts } from "./superpowers-task-delivery.js";
9
+ import { evaluateProofLayerAssertions, isMachineVerifiableLayer } from "./superpowers-task-assertions.js";
10
+ import { scanUnregisteredAssertionEvidence } from "./superpowers-task-unregistered-evidence.js";
11
+ import { findUnderSpecifiedAcs } from "./superpowers-task-under-specified.js";
12
+ import { loadSuperpowersState, sourceRecords } from "./superpowers-task-state.js";
13
+ export const TRUSTED_EVIDENCE_KERNEL_ORDER = [
14
+ "load_three_inputs",
15
+ "recompute_source_hashes",
16
+ "load_task_state",
17
+ "resolve_current_attempt",
18
+ "load_required_command_specs",
19
+ "load_command_run_records",
20
+ "load_registered_evidence_records",
21
+ "discard_stale_evidence",
22
+ "scan_unregistered_assertion_json",
23
+ "scan_contradictions",
24
+ "run_ac010_bootstrap_prevention",
25
+ "run_under_specified_ac_checks",
26
+ "run_harness_drift_lock",
27
+ "run_protected_baseline_guard",
28
+ "validate_scope_conflicts",
29
+ "recompute_every_ac",
30
+ "recompute_every_pi",
31
+ "recompute_acceptance_target_status",
32
+ "recompute_product_goal_complete",
33
+ "regenerate_derived",
34
+ "append_final_gate_event"
35
+ ];
36
+ export async function evaluateTrustedEvidenceKernel(workdir, providedState) {
37
+ const state = providedState ?? (await loadSuperpowersState(workdir));
38
+ const errors = [];
39
+ const acStatuses = {};
40
+ const acFindings = {};
41
+ const staleEvidenceIds = new Set();
42
+ const invalidatedEvidenceIds = new Set();
43
+ const attempt = currentAttempt(state);
44
+ const currentSources = await sourceRecords(workdir);
45
+ if (!attempt) {
46
+ errors.push("missing current attempt: final-gate requires current_attempt_id with ExecutionAttempt");
47
+ }
48
+ else {
49
+ validateAttemptAgainstSources(state, attempt, currentSources, errors);
50
+ }
51
+ const expectedSpecs = deriveRequiredCommandSpecs(state);
52
+ const commandCorrelation = validateRequiredCommandCorrelation(state, attempt, expectedSpecs);
53
+ errors.push(...commandCorrelation.errors);
54
+ commandCorrelation.invalidated_evidence_ids.forEach((id) => invalidatedEvidenceIds.add(id));
55
+ const underSpecified = new Map(findUnderSpecifiedAcs(state).map((item) => [item.ac_id, item.reasons]));
56
+ for (const reasons of underSpecified.values()) {
57
+ errors.push(...reasons);
58
+ }
59
+ const contradictionScan = await scanSuperpowersContradictions(workdir, state);
60
+ errors.push(...contradictionScan.errors);
61
+ const drift = detectHarnessDrift(state);
62
+ errors.push(...drift.errors);
63
+ const baseline = evaluateProtectedBaseline(state);
64
+ errors.push(...baseline.errors);
65
+ validateScopeConflicts(state, errors);
66
+ const unregistered = await scanUnregisteredAssertionEvidence(workdir, state);
67
+ errors.push(...unregistered.errors);
68
+ const evidenceById = new Map((state.evidence ?? []).map((evidence) => [evidence.evidence_id, evidence]));
69
+ for (const [acId, ac] of Object.entries(state.graph?.acceptance_criteria ?? {})) {
70
+ const acErrors = [];
71
+ if (underSpecified.has(acId)) {
72
+ acStatuses[acId] = "under_specified";
73
+ acFindings[acId] = underSpecified.get(acId) ?? [];
74
+ continue;
75
+ }
76
+ const requiredLayers = ac.required_proof_layers ?? [];
77
+ const spec = specForAc(state, expectedSpecs, acId);
78
+ if ((ac.machine_blocking === true || ac.assertion_result_required === true) && !spec) {
79
+ acErrors.push(`${acId} missing required_command_spec`);
80
+ }
81
+ if (spec) {
82
+ acErrors.push(...validateCommandRunsForSpec(state, attempt, spec));
83
+ }
84
+ for (const layerName of requiredLayers) {
85
+ const layerId = `${acId}.${layerName}`;
86
+ const layer = state.graph.proof_layers?.[layerId];
87
+ if (!layer || layer.status !== "satisfied") {
88
+ acErrors.push(`${layerId} missing current satisfied proof layer`);
89
+ continue;
90
+ }
91
+ if (isMachineVerifiableLayer(layerId)) {
92
+ const evaluation = evaluateProofLayerAssertions(state, layerId);
93
+ acErrors.push(...evaluation.blocking_assertion_failures, ...evaluation.negative_evidence_findings);
94
+ for (const evidenceId of layer.evidence_ids ?? []) {
95
+ const evidence = evidenceById.get(evidenceId);
96
+ if (!evidence) {
97
+ acErrors.push(`${layerId} references unregistered evidence ${evidenceId}`);
98
+ continue;
99
+ }
100
+ acErrors.push(...validateEvidenceAgainstSpec(state, evidence, spec, layerId));
101
+ acErrors.push(...(await evaluateCurrentAttemptArtifact(workdir, evidence, layerId)));
102
+ if (invalidatedEvidenceIds.has(evidence.evidence_id)) {
103
+ acErrors.push(`${layerId} evidence ${evidence.evidence_id} invalidated by newer failed command`);
104
+ }
105
+ if (isStaleEvidenceError(acErrors)) {
106
+ staleEvidenceIds.add(evidence.evidence_id);
107
+ }
108
+ }
109
+ }
110
+ }
111
+ errors.push(...acErrors);
112
+ acStatuses[acId] = statusForAcErrors(acErrors, requiredLayers.length);
113
+ acFindings[acId] = acErrors;
114
+ }
115
+ const ac010 = evaluateAc010Bootstrap(state, acStatuses);
116
+ for (const acId of ac010.invalidated_ac_ids) {
117
+ acStatuses[acId] = "invalidated";
118
+ }
119
+ errors.push(...ac010.errors);
120
+ const piStatuses = recomputePlanStatuses(state, acStatuses);
121
+ const piFindings = planFindings(state, acStatuses, piStatuses);
122
+ const allAcsComplete = Object.keys(state.graph?.acceptance_criteria ?? {}).length > 0 &&
123
+ Object.values(acStatuses).every((status) => status === "complete" || status === "out_of_scope_NA");
124
+ const allPisComplete = Object.keys(state.graph?.plan_items ?? {}).length > 0 &&
125
+ Object.values(piStatuses).every((status) => status === "complete" || status === "out_of_scope_NA");
126
+ const uniqueErrors = unique(errors);
127
+ const productComplete = uniqueErrors.length === 0 && allAcsComplete && allPisComplete && drift.product_goal_complete !== false && baseline.product_goal_complete !== false;
128
+ return {
129
+ product_goal_complete: productComplete,
130
+ acceptance_target_status: productComplete ? "complete" : statusForGlobalErrors(uniqueErrors, acStatuses),
131
+ errors: uniqueErrors,
132
+ ac_statuses: acStatuses,
133
+ pi_statuses: piStatuses,
134
+ stale_evidence_ids: [...staleEvidenceIds],
135
+ ignored_unregistered_evidence: unregistered.ignored,
136
+ invalidated_evidence_ids: [...invalidatedEvidenceIds],
137
+ kernel_order: [...TRUSTED_EVIDENCE_KERNEL_ORDER],
138
+ ac_findings: acFindings,
139
+ pi_findings: piFindings,
140
+ harness_task_final_verdict: drift.harness_task_final_verdict
141
+ };
142
+ }
143
+ export function applyTrustedEvidenceKernelResult(state, result) {
144
+ for (const [acId, status] of Object.entries(result.ac_statuses)) {
145
+ const ac = state.graph.acceptance_criteria[acId];
146
+ if (ac) {
147
+ ac.status = status;
148
+ }
149
+ }
150
+ for (const [piId, status] of Object.entries(result.pi_statuses)) {
151
+ const pi = state.graph.plan_items[piId];
152
+ if (pi) {
153
+ pi.status = status;
154
+ }
155
+ }
156
+ state.final.product_goal_complete = result.product_goal_complete;
157
+ state.meta.product_goal_complete = result.product_goal_complete;
158
+ state.final.acceptance_target_status = result.acceptance_target_status;
159
+ state.meta.acceptance_target_status = result.acceptance_target_status;
160
+ state.final.audit_task_complete = true;
161
+ state.meta.audit_task_complete = true;
162
+ state.final.completion_basis = result.product_goal_complete
163
+ ? ["trusted_evidence_kernel", "current_attempt_evidence", "negative_evidence_scan_passed", "harness_drift_lock_passed"]
164
+ : [];
165
+ state.final.next_required_actions = result.product_goal_complete ? [] : result.errors.slice(0, 12);
166
+ state.gates.final_gate = {
167
+ status: result.product_goal_complete ? "pass" : result.acceptance_target_status,
168
+ kernel: "trusted_evidence_kernel",
169
+ order: result.kernel_order,
170
+ errors: result.errors,
171
+ stale_evidence_ids: result.stale_evidence_ids,
172
+ ignored_unregistered_evidence: result.ignored_unregistered_evidence,
173
+ invalidated_evidence_ids: result.invalidated_evidence_ids,
174
+ ac_findings: result.ac_findings,
175
+ pi_findings: result.pi_findings,
176
+ harness_task_final_verdict: result.harness_task_final_verdict,
177
+ next_required_actions: state.final.next_required_actions
178
+ };
179
+ }
180
+ function validateAttemptAgainstSources(state, attempt, currentSources, errors) {
181
+ const productHash = currentSources.product_architecture_source?.sha256 ?? "";
182
+ const planHash = currentSources.technical_realization_plan?.sha256 ?? "";
183
+ const checklistHash = currentSources.acceptance_checklist?.sha256 ?? "";
184
+ for (const [key, source] of Object.entries(currentSources)) {
185
+ const expected = state.sources[key]?.sha256;
186
+ if (expected && source.sha256 !== expected) {
187
+ errors.push(`source hash mismatch for ${key}: expected ${expected}, actual ${source.sha256}; recompile graph before final-gate`);
188
+ }
189
+ }
190
+ if (attempt.product_source_hash !== productHash) {
191
+ errors.push("source hash mismatch: current attempt product_source_hash does not match product-architecture-source.md");
192
+ }
193
+ if (attempt.technical_plan_hash !== planHash) {
194
+ errors.push("source hash mismatch: current attempt technical_plan_hash does not match technical-realization-plan.md");
195
+ }
196
+ if (attempt.acceptance_checklist_hash !== checklistHash) {
197
+ errors.push("source hash mismatch: current attempt acceptance_checklist_hash does not match acceptance-checklist.md");
198
+ }
199
+ for (const field of [
200
+ "task_attempt_id",
201
+ "source_bundle_hash",
202
+ "product_source_hash",
203
+ "technical_plan_hash",
204
+ "acceptance_checklist_hash",
205
+ "git_head",
206
+ "git_status_short",
207
+ "tracked_diff_hash",
208
+ "relevant_untracked_hash",
209
+ "worktree_fingerprint",
210
+ "started_at",
211
+ "required_command_specs_hash",
212
+ "mode"
213
+ ]) {
214
+ if (!attempt[field]) {
215
+ errors.push(`current attempt missing required field ${field}`);
216
+ }
217
+ }
218
+ }
219
+ function validateEvidenceAgainstSpec(state, evidence, spec, layerId) {
220
+ if (!spec) {
221
+ return [];
222
+ }
223
+ const errors = [];
224
+ const assertion = evidence.assertion_result;
225
+ const label = `proof layer ${layerId} evidence ${evidence.evidence_id}`;
226
+ const expectedPiIds = state.graph.acceptance_criteria[spec.ac_id]?.related_plan_items ?? [];
227
+ const targetPiIds = evidence.target_pi_ids ?? assertion?.target_pi_ids ?? [];
228
+ for (const piId of expectedPiIds) {
229
+ if (!targetPiIds.includes(piId)) {
230
+ errors.push(`${label} target_pi_ids ${targetPiIds.join(", ") || "(none)"} do not include ${piId}`);
231
+ }
232
+ }
233
+ if (assertion) {
234
+ const assertionIds = new Set(assertion.positive_assertions.map((item) => item.id));
235
+ for (const id of spec.positive_assertions ?? []) {
236
+ if (!assertionIds.has(id)) {
237
+ errors.push(`${label} missing positive assertion ${id}`);
238
+ }
239
+ }
240
+ const negativeIds = new Set(assertion.negative_assertions.map((item) => item.id));
241
+ for (const id of spec.negative_assertions ?? []) {
242
+ if (!negativeIds.has(id)) {
243
+ errors.push(`${label} missing negative assertion ${id}`);
244
+ }
245
+ }
246
+ const requiredTests = new Set(assertion.required_test_ids ?? []);
247
+ for (const testId of spec.required_test_ids ?? []) {
248
+ if (!requiredTests.has(testId)) {
249
+ errors.push(`${label} assertion_result.required_test_ids missing ${testId}`);
250
+ }
251
+ }
252
+ const invalidChecks = [
253
+ ...((assertion.invalid_completion_signals ?? []).map((item) => `${item.id} ${item.forbidden_text ?? ""}`)),
254
+ ...((assertion.negative_evidence_scan?.invalid_completion_signals_checked ?? []).map((item) => String(item)))
255
+ ].join("\n");
256
+ for (const signal of spec.invalid_completion_signals ?? []) {
257
+ if (!invalidChecks.includes(signal)) {
258
+ errors.push(`${label} invalid_completion_signals did not check ${signal}`);
259
+ }
260
+ }
261
+ }
262
+ return errors;
263
+ }
264
+ function specForAc(state, expectedSpecs, acId) {
265
+ const actual = (state.required_command_specs ?? []).find((spec) => spec.ac_id === acId);
266
+ if (actual) {
267
+ return actual;
268
+ }
269
+ return expectedSpecs.find((spec) => spec.ac_id === acId);
270
+ }
271
+ function statusForAcErrors(errors, requiredLayerCount) {
272
+ if (requiredLayerCount === 0) {
273
+ return "not_run";
274
+ }
275
+ const text = errors.join("\n");
276
+ if (!text) {
277
+ return "complete";
278
+ }
279
+ if (/under_specified/i.test(text)) {
280
+ return "under_specified";
281
+ }
282
+ if (/stale|failed|invalid|contradiction|negative evidence|forbidden|bootstrap/i.test(text)) {
283
+ return "invalidated";
284
+ }
285
+ if (/blocked|harness_drift|protected_baseline|source hash mismatch/i.test(text)) {
286
+ return "blocked";
287
+ }
288
+ return "partial";
289
+ }
290
+ function recomputePlanStatuses(state, acStatuses) {
291
+ const statuses = {};
292
+ for (const [planId, item] of Object.entries(state.graph?.plan_items ?? {})) {
293
+ const relatedStatuses = (item.related_acs ?? []).map((acId) => acStatuses[acId] ?? state.graph.acceptance_criteria[acId]?.status ?? "not_run");
294
+ if (relatedStatuses.length > 0 && relatedStatuses.every((status) => status === "complete" || status === "out_of_scope_NA")) {
295
+ statuses[planId] = "complete";
296
+ }
297
+ else if (relatedStatuses.some((status) => status === "under_specified" || status === "blocked")) {
298
+ statuses[planId] = "blocked";
299
+ }
300
+ else if (relatedStatuses.some((status) => status === "invalidated")) {
301
+ statuses[planId] = "invalidated";
302
+ }
303
+ else if (relatedStatuses.some((status) => status === "partial")) {
304
+ statuses[planId] = "partial";
305
+ }
306
+ else {
307
+ statuses[planId] = "not_started";
308
+ }
309
+ }
310
+ return statuses;
311
+ }
312
+ function planFindings(state, acStatuses, piStatuses) {
313
+ const findings = {};
314
+ for (const [planId, item] of Object.entries(state.graph?.plan_items ?? {})) {
315
+ const related = item.related_acs ?? [];
316
+ findings[planId] = related
317
+ .filter((acId) => acStatuses[acId] && acStatuses[acId] !== "complete" && acStatuses[acId] !== "out_of_scope_NA")
318
+ .map((acId) => `${acId} status=${acStatuses[acId]}`)
319
+ .concat(piStatuses[planId] ? [`${planId} status=${piStatuses[planId]}`] : []);
320
+ }
321
+ return findings;
322
+ }
323
+ function statusForGlobalErrors(errors, acStatuses) {
324
+ const text = errors.join("\n");
325
+ if (Object.values(acStatuses).includes("under_specified") || /under_specified/i.test(text)) {
326
+ return "under_specified";
327
+ }
328
+ if (/harness_drift|protected_baseline|source hash mismatch|missing current attempt|required_command_specs_hash|harness_task_missing|scope_conflict_requires_decision/i.test(text)) {
329
+ return "blocked";
330
+ }
331
+ if (/stale|failed|invalid|contradiction|negative evidence|forbidden|bootstrap/i.test(text)) {
332
+ return "invalidated";
333
+ }
334
+ return "partial";
335
+ }
336
+ function currentAttempt(state) {
337
+ if (!state.current_attempt_id) {
338
+ return undefined;
339
+ }
340
+ return (state.attempts ?? []).find((item) => item.task_attempt_id === state.current_attempt_id);
341
+ }
342
+ function isStaleEvidenceError(errors) {
343
+ return errors.some((error) => /stale evidence|source_bundle_hash mismatch|artifact_sha256 mismatch|artifact_mtime/i.test(error));
344
+ }
345
+ function unique(values) {
346
+ return [...new Set(values.filter(Boolean))];
347
+ }
@@ -0,0 +1,2 @@
1
+ import { type SuperpowersEvidenceRecord } from "./superpowers-task-state-schema.js";
2
+ export declare function readEvidenceRecords(value: unknown): SuperpowersEvidenceRecord[];
@@ -0,0 +1,55 @@
1
+ import { normalizeAssertionResult, normalizeNegativeEvidenceScan } from "./superpowers-task-assertion-normalizers.js";
2
+ import { normalizeProofLayerId } from "./superpowers-task-fields.js";
3
+ import { asStringArray, isRecord } from "./superpowers-task-state-schema.js";
4
+ export function readEvidenceRecords(value) {
5
+ if (!Array.isArray(value)) {
6
+ return [];
7
+ }
8
+ return value.filter(isRecord).map((item) => ({
9
+ evidence_id: String(item.evidence_id ?? item.evidenceId ?? ""),
10
+ schema_version: item.schema_version === undefined ? undefined : String(item.schema_version),
11
+ task_attempt_id: item.task_attempt_id === undefined ? undefined : String(item.task_attempt_id),
12
+ source_bundle_hash: item.source_bundle_hash === undefined ? undefined : String(item.source_bundle_hash),
13
+ product_source_hash: item.product_source_hash === undefined ? undefined : String(item.product_source_hash),
14
+ technical_plan_hash: item.technical_plan_hash === undefined ? undefined : String(item.technical_plan_hash),
15
+ acceptance_checklist_hash: item.acceptance_checklist_hash === undefined ? undefined : String(item.acceptance_checklist_hash),
16
+ git_head: item.git_head === undefined ? undefined : String(item.git_head),
17
+ worktree_fingerprint: item.worktree_fingerprint === undefined ? undefined : String(item.worktree_fingerprint),
18
+ command_spec_id: item.command_spec_id === undefined ? undefined : String(item.command_spec_id),
19
+ command_run_id: item.command_run_id === undefined ? undefined : String(item.command_run_id),
20
+ command_line: item.command_line === undefined ? undefined : String(item.command_line),
21
+ artifact_path: item.artifact_path === undefined ? undefined : String(item.artifact_path),
22
+ artifact_sha256: item.artifact_sha256 === undefined ? undefined : String(item.artifact_sha256),
23
+ artifact_mtime: item.artifact_mtime === undefined ? undefined : String(item.artifact_mtime),
24
+ target_ac_ids: asStringArray(item.target_ac_ids),
25
+ target_pi_ids: asStringArray(item.target_pi_ids),
26
+ target_proof_layers: asStringArray(item.target_proof_layers).map(normalizeProofLayerId),
27
+ slice_id: String(item.slice_id ?? item.sliceId ?? ""),
28
+ type: String(item.type ?? ""),
29
+ freshness: isRecord(item.freshness)
30
+ ? {
31
+ created_at: String(item.freshness.created_at ?? ""),
32
+ valid_for: String(item.freshness.valid_for ?? ""),
33
+ stale_after: item.freshness.stale_after === null ? null : item.freshness.stale_after === undefined ? null : String(item.freshness.stale_after)
34
+ }
35
+ : { created_at: "", valid_for: "", stale_after: null },
36
+ command: item.command === undefined ? undefined : String(item.command),
37
+ command_exit_code: item.command_exit_code === undefined ? undefined : Number(item.command_exit_code),
38
+ artifact_paths: asStringArray(item.artifact_paths),
39
+ proves: asStringArray(item.proves).map(normalizeProofLayerId),
40
+ does_not_prove: asStringArray(item.does_not_prove).map((claim) => (claim.includes(".") ? normalizeProofLayerId(claim) : claim)),
41
+ redaction: isRecord(item.redaction)
42
+ ? { checked: item.redaction.checked === true, contains_secret: item.redaction.contains_secret === true }
43
+ : { checked: false, contains_secret: false },
44
+ reviewability: isRecord(item.reviewability)
45
+ ? {
46
+ external_reviewer_can_reproduce: item.reviewability.external_reviewer_can_reproduce === true,
47
+ reproduction_steps: String(item.reviewability.reproduction_steps ?? "")
48
+ }
49
+ : { external_reviewer_can_reproduce: false, reproduction_steps: "" },
50
+ assertion_result: normalizeAssertionResult(item.assertion_result),
51
+ negative_evidence_scan: normalizeNegativeEvidenceScan(item.negative_evidence_scan),
52
+ sibling_substitution_used: item.sibling_substitution_used === true,
53
+ sibling_substitution_approval_source: item.sibling_substitution_approval_source === undefined ? undefined : String(item.sibling_substitution_approval_source)
54
+ }));
55
+ }
@@ -0,0 +1,10 @@
1
+ import { type CommandRunRecord, type SuperpowersEvidenceRecord } from "./superpowers-task-state-schema.js";
2
+ export declare function runSuperpowersAssertion(workdir: string, options: {
3
+ acId: string;
4
+ proofLayer: string;
5
+ commandArgs: string[];
6
+ }): Promise<CommandRunRecord>;
7
+ export declare function recordSuperpowersEvidence(workdir: string, options: {
8
+ artifactPath: string;
9
+ commandRunId: string;
10
+ }): Promise<SuperpowersEvidenceRecord>;
@@ -0,0 +1,147 @@
1
+ import { createHash } from "node:crypto";
2
+ import { spawn } from "node:child_process";
3
+ import { promises as fs } from "node:fs";
4
+ import path from "node:path";
5
+ import { pathExists, readText } from "./fs.js";
6
+ import { appendSuperpowersEvent } from "./superpowers-task-events.js";
7
+ import { computeSourceBundleHash } from "./superpowers-task-attempt.js";
8
+ import { normalizeAssertionResult, normalizeNegativeEvidenceScan } from "./superpowers-task-assertions.js";
9
+ import { normalizeProofLayerId, normalizeProofLayerName } from "./superpowers-task-fields.js";
10
+ import { loadSuperpowersState, saveSuperpowersState, sha256 } from "./superpowers-task-state.js";
11
+ import { asStringArray, isRecord } from "./superpowers-task-state-schema.js";
12
+ export async function runSuperpowersAssertion(workdir, options) {
13
+ if (!options.acId || !options.proofLayer) {
14
+ throw new Error("run-assertion requires --ac <AC-ID> and --proof-layer <layer>");
15
+ }
16
+ if (options.commandArgs.length === 0) {
17
+ throw new Error("run-assertion requires a command after --");
18
+ }
19
+ const state = await loadSuperpowersState(workdir);
20
+ const proofLayer = normalizeProofLayerName(options.proofLayer);
21
+ const spec = (state.required_command_specs ?? []).find((item) => item.ac_id === options.acId && item.proof_layers.map(normalizeProofLayerName).includes(proofLayer));
22
+ if (!spec) {
23
+ throw new Error(`no required command spec for ${options.acId}.${proofLayer}`);
24
+ }
25
+ const startedAt = new Date().toISOString();
26
+ const exitCode = await runCommand(options.commandArgs);
27
+ const endedAt = new Date().toISOString();
28
+ const commandLine = options.commandArgs.join(" ");
29
+ const commandRun = {
30
+ command_run_id: `CR-${compactDate(startedAt)}-${sha256(commandLine).slice(0, 8)}`,
31
+ task_attempt_id: state.current_attempt_id,
32
+ command_spec_id: spec.command_spec_id,
33
+ ac_id: options.acId,
34
+ proof_layer: proofLayer,
35
+ command_line: commandLine,
36
+ exit_code: exitCode,
37
+ started_at: startedAt,
38
+ completed_at: endedAt,
39
+ ended_at: endedAt,
40
+ artifact_paths: []
41
+ };
42
+ state.command_runs = [...(state.command_runs ?? []), commandRun];
43
+ await saveSuperpowersState(workdir, state);
44
+ await appendSuperpowersEvent(workdir, "assertion_command_run", {
45
+ command_run_id: commandRun.command_run_id,
46
+ command_spec_id: commandRun.command_spec_id,
47
+ exit_code: commandRun.exit_code
48
+ });
49
+ return commandRun;
50
+ }
51
+ export async function recordSuperpowersEvidence(workdir, options) {
52
+ const state = await loadSuperpowersState(workdir);
53
+ const commandRun = (state.command_runs ?? []).find((item) => item.command_run_id === options.commandRunId);
54
+ if (!commandRun) {
55
+ throw new Error(`command run not found: ${options.commandRunId}`);
56
+ }
57
+ const artifactPath = path.resolve(options.artifactPath);
58
+ if (!(await pathExists(artifactPath))) {
59
+ throw new Error(`evidence artifact not found: ${options.artifactPath}`);
60
+ }
61
+ const artifactText = await readText(artifactPath);
62
+ const artifact = JSON.parse(artifactText);
63
+ const artifactRecord = isRecord(artifact) ? artifact : {};
64
+ const assertion = normalizeAssertionResult(artifactRecord.assertion_result ?? artifactRecord);
65
+ if (!assertion) {
66
+ throw new Error(`evidence artifact is missing assertion_result: ${options.artifactPath}`);
67
+ }
68
+ const stats = await fs.stat(artifactPath);
69
+ const attempt = (state.attempts ?? []).find((item) => item.task_attempt_id === commandRun.task_attempt_id);
70
+ const commandSpec = (state.required_command_specs ?? []).find((item) => item.command_spec_id === commandRun.command_spec_id);
71
+ const layerId = normalizeProofLayerId(`${commandRun.ac_id}.${commandRun.proof_layer}`);
72
+ const relativeArtifactPath = slash(path.relative(workdir, artifactPath));
73
+ const targetPiIds = commandSpec ? (state.graph.acceptance_criteria[commandSpec.ac_id]?.related_plan_items ?? []) : [];
74
+ const negativeScan = normalizeNegativeEvidenceScan(artifactRecord.negative_evidence_scan);
75
+ if (assertion.schema_version === "assertion-result-v2") {
76
+ assertion.target_pi_ids = assertion.target_pi_ids ?? targetPiIds;
77
+ assertion.invalid_completion_signals = assertion.invalid_completion_signals ?? [];
78
+ assertion.required_test_ids = assertion.required_test_ids ?? commandSpec?.required_test_ids ?? [];
79
+ assertion.negative_evidence_scan = assertion.negative_evidence_scan ?? negativeScan;
80
+ }
81
+ const evidence = {
82
+ schema_version: "evidence-record-v2",
83
+ evidence_id: `EV2-${compactDate(new Date().toISOString())}-${sha256(options.commandRunId + artifactText).slice(0, 8)}`,
84
+ task_attempt_id: commandRun.task_attempt_id,
85
+ generated_at: new Date().toISOString(),
86
+ source_bundle_hash: attempt?.source_bundle_hash ?? computeSourceBundleHash(state),
87
+ product_source_hash: attempt?.product_source_hash ?? state.sources.product_architecture_source?.sha256 ?? "",
88
+ technical_plan_hash: attempt?.technical_plan_hash ?? state.sources.technical_realization_plan?.sha256 ?? "",
89
+ acceptance_checklist_hash: attempt?.acceptance_checklist_hash ?? state.sources.acceptance_checklist?.sha256 ?? "",
90
+ git_head: attempt?.git_head ?? "",
91
+ git_status_short: attempt?.git_status_short ?? "",
92
+ tracked_diff_hash: attempt?.tracked_diff_hash ?? "",
93
+ relevant_untracked_hash: attempt?.relevant_untracked_hash ?? "",
94
+ covers_dirty_worktree: Boolean(attempt?.git_status_short?.trim()),
95
+ worktree_fingerprint: attempt?.worktree_fingerprint ?? "",
96
+ command_spec_id: commandRun.command_spec_id,
97
+ command_run_id: commandRun.command_run_id,
98
+ command_line: commandRun.command_line,
99
+ command_exit_code: commandRun.exit_code,
100
+ artifact_path: relativeArtifactPath,
101
+ artifact_sha256: createHash("sha256").update(artifactText).digest("hex"),
102
+ artifact_mtime: stats.mtime.toISOString(),
103
+ target_ac_ids: [commandRun.ac_id],
104
+ target_pi_ids: targetPiIds,
105
+ target_proof_layers: [layerId],
106
+ slice_id: String(artifactRecord.slice_id ?? "attempt-evidence"),
107
+ type: String(artifactRecord.type ?? `${commandRun.proof_layer}_assertion`),
108
+ freshness: { created_at: commandRun.completed_at ?? commandRun.ended_at, valid_for: "current_attempt", stale_after: null },
109
+ command: commandRun.command_line,
110
+ artifact_paths: [relativeArtifactPath],
111
+ proves: [layerId],
112
+ does_not_prove: asStringArray(artifactRecord.does_not_prove).length > 0 ? asStringArray(artifactRecord.does_not_prove) : ["unrelated proof layer"],
113
+ redaction: isRecord(artifactRecord.redaction)
114
+ ? { checked: artifactRecord.redaction.checked === true, contains_secret: artifactRecord.redaction.contains_secret === true }
115
+ : { checked: true, contains_secret: false },
116
+ reviewability: isRecord(artifactRecord.reviewability)
117
+ ? {
118
+ external_reviewer_can_reproduce: artifactRecord.reviewability.external_reviewer_can_reproduce === true,
119
+ reproduction_steps: String(artifactRecord.reviewability.reproduction_steps ?? commandRun.command_line)
120
+ }
121
+ : { external_reviewer_can_reproduce: true, reproduction_steps: commandRun.command_line },
122
+ assertion_result: assertion,
123
+ negative_evidence_scan: negativeScan
124
+ };
125
+ state.evidence = [...(state.evidence ?? []), evidence];
126
+ const proofLayer = state.graph.proof_layers[layerId];
127
+ if (proofLayer && commandRun.exit_code === 0 && assertion.status === "passed") {
128
+ proofLayer.status = "satisfied";
129
+ proofLayer.evidence_ids = [...new Set([...(proofLayer.evidence_ids ?? []), evidence.evidence_id])];
130
+ }
131
+ await saveSuperpowersState(workdir, state);
132
+ await appendSuperpowersEvent(workdir, "evidence_recorded", { evidence_id: evidence.evidence_id, command_run_id: commandRun.command_run_id });
133
+ return evidence;
134
+ }
135
+ function runCommand(args) {
136
+ return new Promise((resolve) => {
137
+ const child = spawn(args[0], args.slice(1), { cwd: process.cwd(), stdio: "ignore", windowsHide: true });
138
+ child.on("error", () => resolve(1));
139
+ child.on("exit", (code) => resolve(code ?? 1));
140
+ });
141
+ }
142
+ function compactDate(value) {
143
+ return value.replace(/[-:.TZ]/g, "").slice(0, 17);
144
+ }
145
+ function slash(value) {
146
+ return value.split(path.sep).join("/");
147
+ }
@@ -0,0 +1,3 @@
1
+ import { type CompletionOutputContract } from "./superpowers-task-completion-output.js";
2
+ import { type SuperpowersTaskState } from "./superpowers-task-state-schema.js";
3
+ export declare function renderFinalCard(contract: CompletionOutputContract, state: SuperpowersTaskState): string;
@@ -0,0 +1,24 @@
1
+ export function renderFinalCard(contract, state) {
2
+ const reasons = contract.completion_output_status === "blocked" ? contract.blocked_reasons : contract.rejection_reasons;
3
+ const reasonBlock = reasons.length > 0 ? reasons.map((reason) => `- ${reason}`).join("\n") : "- none";
4
+ const gate = contract.completion_output_status === "accept" ? "Final answer: accept" : `Final answer: ${contract.completion_output_status}`;
5
+ const auditLine = contract.completion_output_status === "accept"
6
+ ? "Final-gate accepted the current attempt."
7
+ : "Audit workflow completed; acceptance target not complete.";
8
+ return `# Final Card
9
+
10
+ completion_output_status: ${contract.completion_output_status}
11
+ ${gate}
12
+ required_user_visible_status: ${contract.required_user_visible_status}
13
+ final_answer_allowed: ${contract.final_answer_allowed}
14
+ exit_code: ${contract.exit_code}
15
+ product_goal_complete: ${contract.product_goal_complete}
16
+ acceptance_target_status: ${contract.acceptance_target_status}
17
+ audit_task_complete: ${state.final.audit_task_complete}
18
+
19
+ ${auditLine}
20
+
21
+ Reasons:
22
+ ${reasonBlock}
23
+ `;
24
+ }
@@ -1,3 +1,4 @@
1
+ import { type CompletionOutputContract } from "./superpowers-task-completion-output.js";
1
2
  export declare function runSliceGate(workdir: string, sliceId: string): Promise<{
2
3
  passed: boolean;
3
4
  messages: string[];
@@ -6,7 +7,6 @@ export declare function runEpochGate(workdir: string, epochId: string): Promise<
6
7
  passed: boolean;
7
8
  messages: string[];
8
9
  }>;
9
- export declare function runFinalGate(workdir: string): Promise<{
10
- product_goal_complete: boolean;
10
+ export declare function runFinalGate(workdir: string): Promise<CompletionOutputContract & {
11
11
  errors: string[];
12
12
  }>;