project-tiny-context-harness 0.2.83 → 0.2.85

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 (31) hide show
  1. package/README.md +5 -5
  2. package/assets/README.md +8 -8
  3. package/assets/README.zh-CN.md +4 -4
  4. package/assets/protected-harness-baseline.json +6 -4
  5. package/assets/skills/composite-long-task-workflow/SKILL.md +12 -5
  6. package/assets/skills/composite-long-task-workflow/assets/execution-binding.template.md +19 -0
  7. package/assets/skills/composite-long-task-workflow/assets/goal-objective.template.md +2 -2
  8. package/assets/skills/composite-long-task-workflow/references/composite-long-task-workflow-protocol.md +47 -26
  9. package/dist/commands/composite-long-task.js +21 -3
  10. package/dist/lib/package-source.js +11 -1
  11. package/dist/lib/superpowers-task-command-run-correlation.d.ts +8 -0
  12. package/dist/lib/superpowers-task-command-run-correlation.js +103 -0
  13. package/dist/lib/superpowers-task-completion-output.d.ts +91 -0
  14. package/dist/lib/superpowers-task-completion-output.js +374 -0
  15. package/dist/lib/superpowers-task-current-evidence.js +22 -0
  16. package/dist/lib/superpowers-task-derive.js +66 -8
  17. package/dist/lib/superpowers-task-evidence-kernel.d.ts +7 -0
  18. package/dist/lib/superpowers-task-evidence-kernel.js +67 -71
  19. package/dist/lib/superpowers-task-evidence.js +7 -1
  20. package/dist/lib/superpowers-task-final-card.d.ts +3 -0
  21. package/dist/lib/superpowers-task-final-card.js +35 -0
  22. package/dist/lib/superpowers-task-gates.d.ts +2 -2
  23. package/dist/lib/superpowers-task-gates.js +179 -27
  24. package/dist/lib/superpowers-task-harness-drift.js +6 -2
  25. package/dist/lib/superpowers-task-protected-baseline.js +21 -2
  26. package/dist/lib/superpowers-task-state-schema.d.ts +37 -0
  27. package/dist/lib/superpowers-task-state.js +10 -1
  28. package/dist/lib/superpowers-task-unregistered-evidence.d.ts +11 -0
  29. package/dist/lib/superpowers-task-unregistered-evidence.js +72 -0
  30. package/dist/lib/superpowers-task-validator.js +41 -0
  31. package/package.json +69 -69
@@ -2,9 +2,12 @@ import path from "node:path";
2
2
  import { ensureDir, pathExists, readText, writeTextIfChanged } from "./fs.js";
3
3
  import { stableJson, loadSuperpowersState } from "./superpowers-task-state.js";
4
4
  import { evaluateProofLayerAssertions } from "./superpowers-task-assertions.js";
5
+ import { completionOutputContractFromState } from "./superpowers-task-completion-output.js";
6
+ import { renderFinalCard } from "./superpowers-task-final-card.js";
5
7
  export async function deriveSuperpowersArtifacts(workdir) {
6
8
  const state = await loadSuperpowersState(workdir);
7
9
  const derived = deriveObjects(state);
10
+ const contract = completionOutputContractFromState(state);
8
11
  const derivedDir = path.join(workdir, "derived");
9
12
  await ensureDir(derivedDir);
10
13
  const files = [];
@@ -14,11 +17,12 @@ export async function deriveSuperpowersArtifacts(workdir) {
14
17
  await writeDerived(files, path.join(derivedDir, "evidence-index.json"), stableJson(deriveEvidenceIndex(state)));
15
18
  await writeDerived(files, path.join(derivedDir, "plan-conformance-matrix.md"), matrixMarkdown(derived.matrix));
16
19
  await writeDerived(files, path.join(derivedDir, "final-acceptance-verdict.md"), verdictMarkdown(derived.verdict));
17
- await writeDerived(files, path.join(derivedDir, "local-audit.md"), localAuditMarkdown(state));
20
+ await writeDerived(files, path.join(derivedDir, "local-audit.md"), localAuditMarkdown(state, contract));
18
21
  await writeDerived(files, path.join(derivedDir, "progress-ledger.md"), progressMarkdown(derived.progress));
19
22
  await writeDerived(files, path.join(derivedDir, "evidence-index.md"), evidenceMarkdown(state));
20
23
  await writeDerived(files, path.join(derivedDir, "context-alignment.md"), contextMarkdown(state));
21
- await writeDerived(files, path.join(derivedDir, "final-summary.md"), finalSummaryMarkdown(state, derived.verdict));
24
+ await writeDerived(files, path.join(derivedDir, "final-summary.md"), finalSummaryMarkdown(state, derived.verdict, contract));
25
+ await writeDerived(files, path.join(derivedDir, "final-card.md"), renderFinalCard(contract, state));
22
26
  return { matrix: derived.matrix, verdict: derived.verdict, files };
23
27
  }
24
28
  export function deriveObjects(state) {
@@ -140,9 +144,28 @@ export function deriveObjects(state) {
140
144
  .slice(0, 5)
141
145
  .map(([layerId]) => layerId)
142
146
  };
147
+ const contract = completionOutputContractFromState(state);
148
+ const completionMetadata = {
149
+ completion_output_status: contract.completion_output_status,
150
+ final_answer_allowed: contract.final_answer_allowed,
151
+ required_user_visible_status: contract.required_user_visible_status,
152
+ product_goal_complete: contract.product_goal_complete,
153
+ acceptance_target_status: contract.acceptance_target_status,
154
+ audit_task_complete: contract.audit_task_complete
155
+ };
143
156
  return {
144
- matrix: { overall_status: allComplete ? "complete" : "partial", items: matrixRows },
145
- verdict: { overall_status: allComplete ? "complete" : "partial", acceptance_items: verdictRows },
157
+ matrix: {
158
+ diagnostic_scope: "plan_conformance_rows_only",
159
+ overall_status: allComplete ? "complete" : "partial",
160
+ ...completionMetadata,
161
+ items: matrixRows
162
+ },
163
+ verdict: {
164
+ diagnostic_scope: "acceptance_evidence_rows_only",
165
+ overall_status: allComplete ? "complete" : "partial",
166
+ ...completionMetadata,
167
+ acceptance_items: verdictRows
168
+ },
146
169
  progress
147
170
  };
148
171
  }
@@ -289,12 +312,19 @@ missing_layer_count: ${missing}
289
312
  ${rows.map((row) => `- ${row.ac_id}: ${row.status}`).join("\n")}
290
313
  `;
291
314
  }
292
- function localAuditMarkdown(state) {
315
+ function localAuditMarkdown(state, contract) {
316
+ const auditLine = contract.completion_output_status === "accept"
317
+ ? "Final-gate accepted the current attempt."
318
+ : "Audit workflow completed; acceptance target not complete.";
293
319
  return `# Local Audit
294
320
 
295
321
  audit_task_complete: ${state.final.audit_task_complete}
296
322
  acceptance_target_status: ${state.final.acceptance_target_status}
297
323
  product_goal_complete: ${state.final.product_goal_complete}
324
+ completion_output_status: ${contract.completion_output_status}
325
+ final_answer_allowed: ${contract.final_answer_allowed}
326
+
327
+ ${auditLine}
298
328
  `;
299
329
  }
300
330
  function progressMarkdown(progress) {
@@ -326,10 +356,38 @@ Product Context Delta: ${state.context.product_context_delta}
326
356
  Technical Context Delta: ${state.context.technical_context_delta}
327
357
  `;
328
358
  }
329
- function finalSummaryMarkdown(state, verdict) {
359
+ function finalSummaryMarkdown(state, verdict, contract) {
360
+ const reasons = contract.completion_output_status === "blocked" ? contract.blocked_reasons : contract.rejection_reasons;
361
+ const reasonBlock = reasons.length > 0 ? reasons.map((reason) => `- ${reason}`).join("\n") : "- none";
362
+ const triage = contract.blocker_triage;
363
+ const triageBlock = triage
364
+ ? `blocker_triage_category: ${triage.category}
365
+ blocker_triage_self_recoverable: ${triage.self_recoverable}
366
+ blocker_triage_recovery_attempted: ${triage.recovery_attempted}
367
+ blocker_triage_next_action: ${triage.next_action}`
368
+ : `blocker_triage_category: none
369
+ blocker_triage_self_recoverable: false
370
+ blocker_triage_recovery_attempted: false
371
+ blocker_triage_next_action: no blocker remains`;
372
+ const auditLine = contract.completion_output_status === "accept"
373
+ ? "Final-gate accepted the current attempt."
374
+ : "Audit workflow completed; acceptance target not complete.";
330
375
  return `# Final Summary
331
376
 
332
- overall_status: ${verdict.overall_status}
333
- product_goal_complete: ${state.final.product_goal_complete}
377
+ diagnostic_overall_status: ${verdict.overall_status}
378
+ product_goal_complete: ${contract.product_goal_complete}
379
+ acceptance_target_status: ${contract.acceptance_target_status}
380
+ completion_output_status: ${contract.completion_output_status}
381
+ final_answer_allowed: ${contract.final_answer_allowed}
382
+ required_user_visible_status: ${contract.required_user_visible_status}
383
+ exit_code: ${contract.exit_code}
384
+ ${triageBlock}
385
+
386
+ Final answer: ${contract.final_answer}
387
+
388
+ ${auditLine}
389
+
390
+ Reasons:
391
+ ${reasonBlock}
334
392
  `;
335
393
  }
@@ -1,3 +1,4 @@
1
+ import { type IgnoredUnregisteredEvidence } from "./superpowers-task-unregistered-evidence.js";
1
2
  import { type SuperpowersTaskState } from "./superpowers-task-state-schema.js";
2
3
  export interface TrustedEvidenceKernelResult {
3
4
  product_goal_complete: boolean;
@@ -6,7 +7,13 @@ export interface TrustedEvidenceKernelResult {
6
7
  ac_statuses: Record<string, string>;
7
8
  pi_statuses: Record<string, string>;
8
9
  stale_evidence_ids: string[];
10
+ ignored_unregistered_evidence: IgnoredUnregisteredEvidence[];
11
+ invalidated_evidence_ids: string[];
12
+ kernel_order: string[];
13
+ ac_findings: Record<string, string[]>;
14
+ pi_findings: Record<string, string[]>;
9
15
  harness_task_final_verdict?: "passed" | "failed";
10
16
  }
17
+ export declare const TRUSTED_EVIDENCE_KERNEL_ORDER: readonly ["load_three_inputs", "recompute_source_hashes", "load_task_state", "resolve_current_attempt", "load_required_command_specs", "load_command_run_records", "load_registered_evidence_records", "discard_stale_evidence", "scan_unregistered_assertion_json", "scan_contradictions", "run_ac010_bootstrap_prevention", "run_under_specified_ac_checks", "run_harness_drift_lock", "run_protected_baseline_guard", "validate_scope_conflicts", "recompute_every_ac", "recompute_every_pi", "recompute_acceptance_target_status", "recompute_product_goal_complete", "regenerate_derived", "append_final_gate_event"];
11
18
  export declare function evaluateTrustedEvidenceKernel(workdir: string, providedState?: SuperpowersTaskState): Promise<TrustedEvidenceKernelResult>;
12
19
  export declare function applyTrustedEvidenceKernelResult(state: SuperpowersTaskState, result: TrustedEvidenceKernelResult): void;
@@ -1,17 +1,45 @@
1
- import { deriveRequiredCommandSpecs, requiredCommandSpecsHash } from "./superpowers-task-command-specs.js";
1
+ import { deriveRequiredCommandSpecs } from "./superpowers-task-command-specs.js";
2
+ import { validateCommandRunsForSpec, validateRequiredCommandCorrelation } from "./superpowers-task-command-run-correlation.js";
2
3
  import { evaluateAc010Bootstrap } from "./superpowers-task-ac010.js";
3
4
  import { evaluateCurrentAttemptArtifact } from "./superpowers-task-current-evidence.js";
4
5
  import { scanSuperpowersContradictions } from "./superpowers-task-contradictions.js";
5
6
  import { detectHarnessDrift } from "./superpowers-task-harness-drift.js";
6
7
  import { evaluateProtectedBaseline } from "./superpowers-task-protected-baseline.js";
8
+ import { validateScopeConflicts } from "./superpowers-task-delivery.js";
7
9
  import { evaluateProofLayerAssertions, isMachineVerifiableLayer } from "./superpowers-task-assertions.js";
10
+ import { scanUnregisteredAssertionEvidence } from "./superpowers-task-unregistered-evidence.js";
8
11
  import { findUnderSpecifiedAcs } from "./superpowers-task-under-specified.js";
9
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
+ ];
10
36
  export async function evaluateTrustedEvidenceKernel(workdir, providedState) {
11
37
  const state = providedState ?? (await loadSuperpowersState(workdir));
12
38
  const errors = [];
13
39
  const acStatuses = {};
40
+ const acFindings = {};
14
41
  const staleEvidenceIds = new Set();
42
+ const invalidatedEvidenceIds = new Set();
15
43
  const attempt = currentAttempt(state);
16
44
  const currentSources = await sourceRecords(workdir);
17
45
  if (!attempt) {
@@ -21,7 +49,9 @@ export async function evaluateTrustedEvidenceKernel(workdir, providedState) {
21
49
  validateAttemptAgainstSources(state, attempt, currentSources, errors);
22
50
  }
23
51
  const expectedSpecs = deriveRequiredCommandSpecs(state);
24
- validateRequiredSpecs(state, attempt, expectedSpecs, errors);
52
+ const commandCorrelation = validateRequiredCommandCorrelation(state, attempt, expectedSpecs);
53
+ errors.push(...commandCorrelation.errors);
54
+ commandCorrelation.invalidated_evidence_ids.forEach((id) => invalidatedEvidenceIds.add(id));
25
55
  const underSpecified = new Map(findUnderSpecifiedAcs(state).map((item) => [item.ac_id, item.reasons]));
26
56
  for (const reasons of underSpecified.values()) {
27
57
  errors.push(...reasons);
@@ -32,11 +62,15 @@ export async function evaluateTrustedEvidenceKernel(workdir, providedState) {
32
62
  errors.push(...drift.errors);
33
63
  const baseline = evaluateProtectedBaseline(state);
34
64
  errors.push(...baseline.errors);
65
+ validateScopeConflicts(state, errors);
66
+ const unregistered = await scanUnregisteredAssertionEvidence(workdir, state);
67
+ errors.push(...unregistered.errors);
35
68
  const evidenceById = new Map((state.evidence ?? []).map((evidence) => [evidence.evidence_id, evidence]));
36
69
  for (const [acId, ac] of Object.entries(state.graph?.acceptance_criteria ?? {})) {
37
70
  const acErrors = [];
38
71
  if (underSpecified.has(acId)) {
39
72
  acStatuses[acId] = "under_specified";
73
+ acFindings[acId] = underSpecified.get(acId) ?? [];
40
74
  continue;
41
75
  }
42
76
  const requiredLayers = ac.required_proof_layers ?? [];
@@ -65,6 +99,9 @@ export async function evaluateTrustedEvidenceKernel(workdir, providedState) {
65
99
  }
66
100
  acErrors.push(...validateEvidenceAgainstSpec(state, evidence, spec, layerId));
67
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
+ }
68
105
  if (isStaleEvidenceError(acErrors)) {
69
106
  staleEvidenceIds.add(evidence.evidence_id);
70
107
  }
@@ -73,6 +110,7 @@ export async function evaluateTrustedEvidenceKernel(workdir, providedState) {
73
110
  }
74
111
  errors.push(...acErrors);
75
112
  acStatuses[acId] = statusForAcErrors(acErrors, requiredLayers.length);
113
+ acFindings[acId] = acErrors;
76
114
  }
77
115
  const ac010 = evaluateAc010Bootstrap(state, acStatuses);
78
116
  for (const acId of ac010.invalidated_ac_ids) {
@@ -80,6 +118,7 @@ export async function evaluateTrustedEvidenceKernel(workdir, providedState) {
80
118
  }
81
119
  errors.push(...ac010.errors);
82
120
  const piStatuses = recomputePlanStatuses(state, acStatuses);
121
+ const piFindings = planFindings(state, acStatuses, piStatuses);
83
122
  const allAcsComplete = Object.keys(state.graph?.acceptance_criteria ?? {}).length > 0 &&
84
123
  Object.values(acStatuses).every((status) => status === "complete" || status === "out_of_scope_NA");
85
124
  const allPisComplete = Object.keys(state.graph?.plan_items ?? {}).length > 0 &&
@@ -93,6 +132,11 @@ export async function evaluateTrustedEvidenceKernel(workdir, providedState) {
93
132
  ac_statuses: acStatuses,
94
133
  pi_statuses: piStatuses,
95
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,
96
140
  harness_task_final_verdict: drift.harness_task_final_verdict
97
141
  };
98
142
  }
@@ -122,24 +166,13 @@ export function applyTrustedEvidenceKernelResult(state, result) {
122
166
  state.gates.final_gate = {
123
167
  status: result.product_goal_complete ? "pass" : result.acceptance_target_status,
124
168
  kernel: "trusted_evidence_kernel",
125
- order: [
126
- "load_three_inputs",
127
- "recompute_source_hashes",
128
- "load_task_state",
129
- "load_current_attempt",
130
- "load_command_run_records",
131
- "load_registered_evidence_records",
132
- "discard_stale_evidence",
133
- "contradiction_scan",
134
- "recompute_every_ac",
135
- "recompute_every_pi",
136
- "recompute_acceptance_target_status",
137
- "recompute_product_goal_complete",
138
- "regenerate_derived",
139
- "append_event"
140
- ],
169
+ order: result.kernel_order,
141
170
  errors: result.errors,
142
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,
143
176
  harness_task_final_verdict: result.harness_task_final_verdict,
144
177
  next_required_actions: state.final.next_required_actions
145
178
  };
@@ -183,57 +216,6 @@ function validateAttemptAgainstSources(state, attempt, currentSources, errors) {
183
216
  }
184
217
  }
185
218
  }
186
- function validateRequiredSpecs(state, attempt, expectedSpecs, errors) {
187
- const expectedByAc = new Map(expectedSpecs.map((spec) => [spec.ac_id, spec]));
188
- for (const [acId, ac] of Object.entries(state.graph?.acceptance_criteria ?? {})) {
189
- if (ac.machine_blocking !== true && ac.assertion_result_required !== true) {
190
- continue;
191
- }
192
- const expected = expectedByAc.get(acId);
193
- const actual = (state.required_command_specs ?? []).find((spec) => spec.ac_id === acId);
194
- if (!expected || !actual) {
195
- errors.push(`${acId} missing required_command_spec`);
196
- continue;
197
- }
198
- if (actual.command_spec_id !== expected.command_spec_id) {
199
- errors.push(`${acId} command_spec_id mismatch; required command specs must be recompiled from Acceptance Checklist`);
200
- }
201
- }
202
- if (attempt) {
203
- const specsHash = requiredCommandSpecsHash(state.required_command_specs ?? []);
204
- if (attempt.required_command_specs_hash !== specsHash) {
205
- errors.push("required_command_specs_hash mismatch for current attempt");
206
- }
207
- }
208
- }
209
- function validateCommandRunsForSpec(state, attempt, spec) {
210
- const errors = [];
211
- for (const proofLayer of spec.proof_layers.filter((layer) => isMachineVerifiableLayer(`${spec.ac_id}.${layer}`))) {
212
- const run = (state.command_runs ?? []).find((item) => item.task_attempt_id === state.current_attempt_id &&
213
- item.command_spec_id === spec.command_spec_id &&
214
- item.ac_id === spec.ac_id &&
215
- item.proof_layer === proofLayer);
216
- if (!run) {
217
- errors.push(`${spec.ac_id}.${proofLayer} missing current attempt command-run record for command_spec_id ${spec.command_spec_id}`);
218
- continue;
219
- }
220
- errors.push(...validateCommandRun(run, attempt));
221
- }
222
- return errors;
223
- }
224
- function validateCommandRun(run, attempt) {
225
- const errors = [];
226
- if (attempt && run.task_attempt_id !== attempt.task_attempt_id) {
227
- errors.push(`${run.command_run_id} stale command run from ${run.task_attempt_id}; expected ${attempt.task_attempt_id}`);
228
- }
229
- if (run.exit_code !== 0) {
230
- errors.push(`${run.command_run_id} command_exit_code=${run.exit_code}; expected 0`);
231
- }
232
- if (!run.command_line.trim()) {
233
- errors.push(`${run.command_run_id} missing command_line`);
234
- }
235
- return errors;
236
- }
237
219
  function validateEvidenceAgainstSpec(state, evidence, spec, layerId) {
238
220
  if (!spec) {
239
221
  return [];
@@ -327,12 +309,23 @@ function recomputePlanStatuses(state, acStatuses) {
327
309
  }
328
310
  return statuses;
329
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
+ }
330
323
  function statusForGlobalErrors(errors, acStatuses) {
331
324
  const text = errors.join("\n");
332
325
  if (Object.values(acStatuses).includes("under_specified") || /under_specified/i.test(text)) {
333
326
  return "under_specified";
334
327
  }
335
- if (/harness_drift|protected_baseline|source hash mismatch|missing current attempt|required_command_specs_hash|harness_task_missing/i.test(text)) {
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)) {
336
329
  return "blocked";
337
330
  }
338
331
  if (/stale|failed|invalid|contradiction|negative evidence|forbidden|bootstrap/i.test(text)) {
@@ -341,7 +334,10 @@ function statusForGlobalErrors(errors, acStatuses) {
341
334
  return "partial";
342
335
  }
343
336
  function currentAttempt(state) {
344
- return (state.attempts ?? []).find((item) => item.task_attempt_id === state.current_attempt_id) ?? (state.attempts ?? []).at(-1);
337
+ if (!state.current_attempt_id) {
338
+ return undefined;
339
+ }
340
+ return (state.attempts ?? []).find((item) => item.task_attempt_id === state.current_attempt_id);
345
341
  }
346
342
  function isStaleEvidenceError(errors) {
347
343
  return errors.some((error) => /stale evidence|source_bundle_hash mismatch|artifact_sha256 mismatch|artifact_mtime/i.test(error));
@@ -35,6 +35,7 @@ export async function runSuperpowersAssertion(workdir, options) {
35
35
  command_line: commandLine,
36
36
  exit_code: exitCode,
37
37
  started_at: startedAt,
38
+ completed_at: endedAt,
38
39
  ended_at: endedAt,
39
40
  artifact_paths: []
40
41
  };
@@ -81,11 +82,16 @@ export async function recordSuperpowersEvidence(workdir, options) {
81
82
  schema_version: "evidence-record-v2",
82
83
  evidence_id: `EV2-${compactDate(new Date().toISOString())}-${sha256(options.commandRunId + artifactText).slice(0, 8)}`,
83
84
  task_attempt_id: commandRun.task_attempt_id,
85
+ generated_at: new Date().toISOString(),
84
86
  source_bundle_hash: attempt?.source_bundle_hash ?? computeSourceBundleHash(state),
85
87
  product_source_hash: attempt?.product_source_hash ?? state.sources.product_architecture_source?.sha256 ?? "",
86
88
  technical_plan_hash: attempt?.technical_plan_hash ?? state.sources.technical_realization_plan?.sha256 ?? "",
87
89
  acceptance_checklist_hash: attempt?.acceptance_checklist_hash ?? state.sources.acceptance_checklist?.sha256 ?? "",
88
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()),
89
95
  worktree_fingerprint: attempt?.worktree_fingerprint ?? "",
90
96
  command_spec_id: commandRun.command_spec_id,
91
97
  command_run_id: commandRun.command_run_id,
@@ -99,7 +105,7 @@ export async function recordSuperpowersEvidence(workdir, options) {
99
105
  target_proof_layers: [layerId],
100
106
  slice_id: String(artifactRecord.slice_id ?? "attempt-evidence"),
101
107
  type: String(artifactRecord.type ?? `${commandRun.proof_layer}_assertion`),
102
- freshness: { created_at: commandRun.ended_at, valid_for: "current_attempt", stale_after: null },
108
+ freshness: { created_at: commandRun.completed_at ?? commandRun.ended_at, valid_for: "current_attempt", stale_after: null },
103
109
  command: commandRun.command_line,
104
110
  artifact_paths: [relativeArtifactPath],
105
111
  proves: [layerId],
@@ -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,35 @@
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 triage = contract.blocker_triage;
5
+ const triageBlock = triage
6
+ ? `blocker_triage_category: ${triage.category}
7
+ blocker_triage_self_recoverable: ${triage.self_recoverable}
8
+ blocker_triage_recovery_attempted: ${triage.recovery_attempted}
9
+ blocker_triage_next_action: ${triage.next_action}`
10
+ : `blocker_triage_category: none
11
+ blocker_triage_self_recoverable: false
12
+ blocker_triage_recovery_attempted: false
13
+ blocker_triage_next_action: no blocker remains`;
14
+ const gate = contract.completion_output_status === "accept" ? "Final answer: accept" : `Final answer: ${contract.completion_output_status}`;
15
+ const auditLine = contract.completion_output_status === "accept"
16
+ ? "Final-gate accepted the current attempt."
17
+ : "Audit workflow completed; acceptance target not complete.";
18
+ return `# Final Card
19
+
20
+ completion_output_status: ${contract.completion_output_status}
21
+ ${gate}
22
+ required_user_visible_status: ${contract.required_user_visible_status}
23
+ final_answer_allowed: ${contract.final_answer_allowed}
24
+ exit_code: ${contract.exit_code}
25
+ product_goal_complete: ${contract.product_goal_complete}
26
+ acceptance_target_status: ${contract.acceptance_target_status}
27
+ audit_task_complete: ${state.final.audit_task_complete}
28
+ ${triageBlock}
29
+
30
+ ${auditLine}
31
+
32
+ Reasons:
33
+ ${reasonBlock}
34
+ `;
35
+ }
@@ -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
  }>;