project-tiny-context-harness 0.2.83 → 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 (30) 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 +5 -3
  5. package/assets/skills/composite-long-task-workflow/SKILL.md +11 -5
  6. package/assets/skills/composite-long-task-workflow/assets/execution-binding.template.md +14 -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 +39 -25
  9. package/dist/commands/composite-long-task.js +15 -3
  10. package/dist/lib/superpowers-task-command-run-correlation.d.ts +8 -0
  11. package/dist/lib/superpowers-task-command-run-correlation.js +103 -0
  12. package/dist/lib/superpowers-task-completion-output.d.ts +52 -0
  13. package/dist/lib/superpowers-task-completion-output.js +228 -0
  14. package/dist/lib/superpowers-task-current-evidence.js +22 -0
  15. package/dist/lib/superpowers-task-derive.js +55 -8
  16. package/dist/lib/superpowers-task-evidence-kernel.d.ts +7 -0
  17. package/dist/lib/superpowers-task-evidence-kernel.js +67 -71
  18. package/dist/lib/superpowers-task-evidence.js +7 -1
  19. package/dist/lib/superpowers-task-final-card.d.ts +3 -0
  20. package/dist/lib/superpowers-task-final-card.js +24 -0
  21. package/dist/lib/superpowers-task-gates.d.ts +2 -2
  22. package/dist/lib/superpowers-task-gates.js +67 -27
  23. package/dist/lib/superpowers-task-harness-drift.js +6 -2
  24. package/dist/lib/superpowers-task-protected-baseline.js +21 -2
  25. package/dist/lib/superpowers-task-state-schema.d.ts +17 -0
  26. package/dist/lib/superpowers-task-state.js +10 -1
  27. package/dist/lib/superpowers-task-unregistered-evidence.d.ts +11 -0
  28. package/dist/lib/superpowers-task-unregistered-evidence.js +72 -0
  29. package/dist/lib/superpowers-task-validator.js +38 -0
  30. package/package.json +69 -69
@@ -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,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
  }>;
@@ -2,7 +2,9 @@ import { appendSuperpowersEvent } from "./superpowers-task-events.js";
2
2
  import { deriveSuperpowersArtifacts } from "./superpowers-task-derive.js";
3
3
  import { validatePlanAcceptance } from "./plan-acceptance-validator.js";
4
4
  import { loadSuperpowersState, saveSuperpowersState } from "./superpowers-task-state.js";
5
+ import { applyCompletionOutputContract, completionPhraseFindingMessages, resolveCompletionOutputStatus, scanGeneratedCompletionOutputSurfaces } from "./superpowers-task-completion-output.js";
5
6
  import { applyTrustedEvidenceKernelResult, evaluateTrustedEvidenceKernel } from "./superpowers-task-evidence-kernel.js";
7
+ import { isRecord } from "./superpowers-task-state-schema.js";
6
8
  import { validateSuperpowersState } from "./superpowers-task-validator.js";
7
9
  export async function runSliceGate(workdir, sliceId) {
8
10
  const state = await loadSuperpowersState(workdir);
@@ -33,16 +35,20 @@ export async function runFinalGate(workdir) {
33
35
  const report = await validateSuperpowersState(workdir, [workdir]);
34
36
  const acceptanceReport = await validatePlanAcceptance(workdir, [workdir]);
35
37
  const latest = await loadSuperpowersState(workdir);
36
- const errors = [...new Set([...kernel.errors, ...report.errors, ...acceptanceReport.errors])];
37
- const complete = kernel.product_goal_complete && errors.length === 0;
38
+ let errors = [...new Set([...kernel.errors, ...report.errors, ...acceptanceReport.errors])];
39
+ let complete = kernel.product_goal_complete && errors.length === 0;
38
40
  const acceptanceStatus = complete ? "complete" : acceptanceStatusForErrors(errors, kernel);
39
41
  const nextRequiredActions = complete ? [] : nextActionsForErrors(errors);
40
- latest.final.product_goal_complete = complete;
41
- latest.meta.product_goal_complete = complete;
42
- latest.final.acceptance_target_status = acceptanceStatus;
43
- latest.meta.acceptance_target_status = latest.final.acceptance_target_status;
44
- latest.final.audit_task_complete = true;
45
- latest.meta.audit_task_complete = true;
42
+ let contract = resolveCompletionOutputStatus({
43
+ final_gate_ran: true,
44
+ product_goal_complete: complete,
45
+ acceptance_target_status: acceptanceStatus,
46
+ audit_task_complete: true,
47
+ validator_errors: report.errors,
48
+ acceptance_validator_errors: acceptanceReport.errors,
49
+ rejection_reasons: complete ? [] : nextRequiredActions
50
+ });
51
+ applyCompletionOutputContract(latest, contract);
46
52
  latest.final.completion_basis = complete
47
53
  ? ["trusted_evidence_kernel", "current_attempt_evidence", "negative_evidence_scan_passed", "harness_drift_lock_passed"]
48
54
  : [];
@@ -51,31 +57,65 @@ export async function runFinalGate(workdir) {
51
57
  latest.gates.final_gate = {
52
58
  status: complete ? "pass" : acceptanceStatus,
53
59
  kernel: "trusted_evidence_kernel",
54
- order: [
55
- "load_three_inputs",
56
- "recompute_source_hashes",
57
- "load_task_state",
58
- "load_current_attempt",
59
- "load_command_run_records",
60
- "load_registered_evidence_records",
61
- "discard_stale_evidence",
62
- "contradiction_scan",
63
- "recompute_every_ac",
64
- "recompute_every_pi",
65
- "recompute_acceptance_target_status",
66
- "recompute_product_goal_complete",
67
- "regenerate_derived",
68
- "append_event"
69
- ],
60
+ order: kernel.kernel_order,
70
61
  errors,
71
62
  stale_evidence_ids: kernel.stale_evidence_ids,
63
+ ignored_unregistered_evidence: kernel.ignored_unregistered_evidence,
64
+ invalidated_evidence_ids: kernel.invalidated_evidence_ids,
65
+ ac_findings: kernel.ac_findings,
66
+ pi_findings: kernel.pi_findings,
72
67
  harness_task_final_verdict: kernel.harness_task_final_verdict,
73
- next_required_actions: nextRequiredActions
68
+ next_required_actions: nextRequiredActions,
69
+ completion_output_status: contract.completion_output_status,
70
+ final_answer_allowed: contract.final_answer_allowed,
71
+ required_user_visible_status: contract.required_user_visible_status,
72
+ exit_code: contract.exit_code,
73
+ blocked_reasons: contract.blocked_reasons,
74
+ rejection_reasons: contract.rejection_reasons,
75
+ generated_output_mismatch: contract.generated_output_mismatch
74
76
  };
75
77
  await saveSuperpowersState(workdir, latest);
76
78
  await deriveSuperpowersArtifacts(workdir);
77
- await appendSuperpowersEvent(workdir, "final_gate", { product_goal_complete: complete });
78
- return { product_goal_complete: complete, errors };
79
+ const outputFindings = await scanGeneratedCompletionOutputSurfaces(workdir, contract);
80
+ if (outputFindings.length > 0) {
81
+ errors = [...new Set([...errors, ...completionPhraseFindingMessages(outputFindings)])];
82
+ complete = false;
83
+ contract = resolveCompletionOutputStatus({
84
+ final_gate_ran: true,
85
+ product_goal_complete: false,
86
+ acceptance_target_status: acceptanceStatusForErrors(errors, kernel),
87
+ audit_task_complete: true,
88
+ validator_errors: errors,
89
+ generated_output_mismatch: true
90
+ });
91
+ const blockedLatest = await loadSuperpowersState(workdir);
92
+ applyCompletionOutputContract(blockedLatest, contract);
93
+ blockedLatest.final.completion_basis = [];
94
+ blockedLatest.final.next_required_actions = nextActionsForErrors(errors);
95
+ blockedLatest.gates.validator = { status: "blocked", errors };
96
+ blockedLatest.gates.final_gate = {
97
+ ...(isRecord(blockedLatest.gates.final_gate) ? blockedLatest.gates.final_gate : {}),
98
+ status: contract.completion_output_status,
99
+ errors,
100
+ next_required_actions: blockedLatest.final.next_required_actions,
101
+ completion_output_status: contract.completion_output_status,
102
+ final_answer_allowed: contract.final_answer_allowed,
103
+ required_user_visible_status: contract.required_user_visible_status,
104
+ exit_code: contract.exit_code,
105
+ blocked_reasons: contract.blocked_reasons,
106
+ rejection_reasons: contract.rejection_reasons,
107
+ generated_output_mismatch: contract.generated_output_mismatch,
108
+ false_completion_phrase_findings: outputFindings
109
+ };
110
+ blockedLatest.final.false_completion_phrase_findings = outputFindings;
111
+ await saveSuperpowersState(workdir, blockedLatest);
112
+ await deriveSuperpowersArtifacts(workdir);
113
+ }
114
+ await appendSuperpowersEvent(workdir, "final_gate", {
115
+ product_goal_complete: contract.product_goal_complete,
116
+ completion_output_status: contract.completion_output_status
117
+ });
118
+ return { ...contract, errors };
79
119
  }
80
120
  function acceptanceStatusForErrors(errors, kernel) {
81
121
  const text = errors.join("\n");
@@ -4,7 +4,8 @@ const HARNESS_PATH_PATTERNS = [
4
4
  /(^|\/)playwright\.config\./i,
5
5
  /(^|\/).*\.spec\.[cm]?[jt]sx?$/i,
6
6
  /(^|\/).*\.test\.[cm]?[jt]sx?$/i,
7
- /superpowers-task-(assertions|evidence|evidence-kernel|current-evidence|command-specs|ac010|gates|validator|derive|state|state-schema|state-shape|under-specified|protected-baseline|harness-drift)\.ts$/i,
7
+ /(^|\/)tests\/ty-context\/fixtures\/composite-long-task\//i,
8
+ /superpowers-task-(assertions|evidence|evidence-kernel|current-evidence|command-specs|command-run-correlation|unregistered-evidence|completion-output|final-card|ac010|gates|validator|derive|state|state-schema|state-shape|under-specified|protected-baseline|harness-drift)\.ts$/i,
8
9
  /(^|\/)(\.codex\/ty-context-managed|packages\/ty-context\/assets)\/skills\/composite-long-task-workflow\//i,
9
10
  /composite-long-task-workflow-protocol\.md$/i,
10
11
  /(^|\/)Makefile$/i,
@@ -79,7 +80,10 @@ function harnessTaskFixtureVerdict(state, changedFiles) {
79
80
  };
80
81
  }
81
82
  function currentAttempt(state) {
82
- return (state.attempts ?? []).find((item) => item.task_attempt_id === state.current_attempt_id) ?? (state.attempts ?? []).at(-1);
83
+ if (!state.current_attempt_id) {
84
+ return undefined;
85
+ }
86
+ return (state.attempts ?? []).find((item) => item.task_attempt_id === state.current_attempt_id);
83
87
  }
84
88
  function changedFilesFor(attempt) {
85
89
  return [...new Set((attempt?.changed_files ?? []).map((file) => file.replace(/\\/g, "/")).filter(Boolean))];
@@ -5,15 +5,31 @@ export const PROTECTED_BASELINE_PATHS = [
5
5
  ".codex/ty-context-managed/protected-harness-baseline.json",
6
6
  "packages/ty-context/source-mappings.yaml",
7
7
  "packages/ty-context/src/lib/superpowers-task-gates.ts",
8
+ "packages/ty-context/src/lib/superpowers-task-completion-output.ts",
9
+ "packages/ty-context/src/lib/superpowers-task-final-card.ts",
8
10
  "packages/ty-context/src/lib/superpowers-task-validator.ts",
9
11
  "packages/ty-context/src/lib/superpowers-task-derive.ts",
10
12
  "packages/ty-context/src/lib/superpowers-task-evidence.ts",
11
13
  "packages/ty-context/src/lib/superpowers-task-evidence-kernel.ts",
14
+ "packages/ty-context/src/lib/superpowers-task-current-evidence.ts",
15
+ "packages/ty-context/src/lib/superpowers-task-command-run-correlation.ts",
16
+ "packages/ty-context/src/lib/superpowers-task-unregistered-evidence.ts",
17
+ "packages/ty-context/src/lib/superpowers-task-ac010.ts",
18
+ "packages/ty-context/src/lib/superpowers-task-harness-drift.ts",
19
+ "packages/ty-context/src/lib/superpowers-task-protected-baseline.ts",
12
20
  "packages/ty-context/src/lib/superpowers-task-state-schema.ts",
13
21
  ".codex/ty-context-managed/skills/composite-long-task-workflow/SKILL.md",
14
22
  ".codex/ty-context-managed/skills/composite-long-task-workflow/references/composite-long-task-workflow-protocol.md",
23
+ ".codex/ty-context-managed/skills/composite-long-task-workflow/assets/goal-objective.template.md",
24
+ ".codex/ty-context-managed/skills/composite-long-task-workflow/assets/execution-binding.template.md",
15
25
  "packages/ty-context/assets/skills/composite-long-task-workflow/SKILL.md",
16
- "packages/ty-context/assets/skills/composite-long-task-workflow/references/composite-long-task-workflow-protocol.md"
26
+ "packages/ty-context/assets/skills/composite-long-task-workflow/references/composite-long-task-workflow-protocol.md",
27
+ "packages/ty-context/assets/skills/composite-long-task-workflow/assets/goal-objective.template.md",
28
+ "packages/ty-context/assets/skills/composite-long-task-workflow/assets/execution-binding.template.md",
29
+ "tests/ty-context/composite-long-task-completion-output-gate.test.mjs",
30
+ "tests/ty-context/fixtures/composite-long-task/completion-output-gate/expected-outcomes.json",
31
+ "tests/ty-context/composite-long-task-false-completion-regression.test.mjs",
32
+ "tests/ty-context/fixtures/composite-long-task/false-completion-regression/manifest.json"
17
33
  ];
18
34
  export function evaluateProtectedBaseline(state) {
19
35
  const attempt = currentAttempt(state);
@@ -43,5 +59,8 @@ function protectedBaselineReason(state) {
43
59
  return isRecord(baseline) && typeof baseline.reason === "string" && baseline.reason.trim().length > 0;
44
60
  }
45
61
  function currentAttempt(state) {
46
- return (state.attempts ?? []).find((item) => item.task_attempt_id === state.current_attempt_id) ?? (state.attempts ?? []).at(-1);
62
+ if (!state.current_attempt_id) {
63
+ return undefined;
64
+ }
65
+ return (state.attempts ?? []).find((item) => item.task_attempt_id === state.current_attempt_id);
47
66
  }
@@ -76,6 +76,7 @@ export type SuperpowersProductDeliveryScope = "system_capability_build" | "repre
76
76
  export type SuperpowersPlanDeliveryScope = "system_capability_build" | "representative_sample_validation" | "full_population_operation" | "out_of_scope_backlog";
77
77
  export type SuperpowersAcceptanceScope = "system_capability_build" | "representative_sample_validation" | "full_population_operation" | "full_population_not_required";
78
78
  export type SuperpowersScopeFitDecision = "fit_for_three_inputs" | "selected_from_split" | "blocked_for_decision" | "";
79
+ export type CompletionOutputStatus = "accept" | "reject" | "blocked";
79
80
  export interface SuperpowersTaskState {
80
81
  meta: {
81
82
  task_id: string;
@@ -87,6 +88,7 @@ export interface SuperpowersTaskState {
87
88
  product_goal_complete: boolean;
88
89
  acceptance_target_status: string;
89
90
  audit_task_complete: boolean;
91
+ completion_output_status?: CompletionOutputStatus;
90
92
  };
91
93
  sources: Record<string, SuperpowersSourceRecord>;
92
94
  context: {
@@ -116,6 +118,15 @@ export interface SuperpowersTaskState {
116
118
  product_goal_complete: boolean;
117
119
  acceptance_target_status: string;
118
120
  audit_task_complete: boolean;
121
+ completion_output_status?: CompletionOutputStatus;
122
+ final_answer_allowed?: boolean;
123
+ required_user_visible_status?: "accepted" | "rejected" | "blocked";
124
+ final_answer?: CompletionOutputStatus;
125
+ exit_code?: 0 | 1 | 2;
126
+ blocked_reasons?: string[];
127
+ rejection_reasons?: string[];
128
+ generated_output_mismatch?: boolean;
129
+ false_completion_phrase_findings?: unknown[];
119
130
  completion_basis: string[];
120
131
  next_required_actions?: string[];
121
132
  };
@@ -162,6 +173,7 @@ export interface CommandRunRecord {
162
173
  command_line: string;
163
174
  exit_code: number;
164
175
  started_at: string;
176
+ completed_at?: string;
165
177
  ended_at: string;
166
178
  artifact_paths: string[];
167
179
  }
@@ -336,11 +348,16 @@ export interface SuperpowersEvidenceRecord {
336
348
  schema_version?: "evidence-record-v1" | "evidence-record-v2" | string;
337
349
  evidence_id: string;
338
350
  task_attempt_id?: string;
351
+ generated_at?: string;
339
352
  source_bundle_hash?: string;
340
353
  product_source_hash?: string;
341
354
  technical_plan_hash?: string;
342
355
  acceptance_checklist_hash?: string;
343
356
  git_head?: string;
357
+ git_status_short?: string;
358
+ tracked_diff_hash?: string;
359
+ relevant_untracked_hash?: string;
360
+ covers_dirty_worktree?: boolean;
344
361
  worktree_fingerprint?: string;
345
362
  command_spec_id?: string;
346
363
  command_run_id?: string;
@@ -41,7 +41,8 @@ export async function initializeSuperpowersTask(workdir, options = {}) {
41
41
  goal_type: options.goalType ?? "implementation",
42
42
  product_goal_complete: false,
43
43
  acceptance_target_status: "not_run",
44
- audit_task_complete: false
44
+ audit_task_complete: false,
45
+ completion_output_status: "blocked"
45
46
  },
46
47
  sources: await sourceRecords(workdir),
47
48
  context: {
@@ -88,6 +89,14 @@ export async function initializeSuperpowersTask(workdir, options = {}) {
88
89
  product_goal_complete: false,
89
90
  acceptance_target_status: "not_run",
90
91
  audit_task_complete: false,
92
+ completion_output_status: "blocked",
93
+ final_answer_allowed: false,
94
+ required_user_visible_status: "blocked",
95
+ final_answer: "blocked",
96
+ exit_code: 2,
97
+ blocked_reasons: ["final_gate_not_run"],
98
+ rejection_reasons: [],
99
+ generated_output_mismatch: false,
91
100
  completion_basis: [],
92
101
  next_required_actions: []
93
102
  }
@@ -0,0 +1,11 @@
1
+ import { type SuperpowersTaskState } from "./superpowers-task-state-schema.js";
2
+ export interface IgnoredUnregisteredEvidence {
3
+ path: string;
4
+ status: string;
5
+ target_ac_ids: string[];
6
+ target_proof_layers: string[];
7
+ }
8
+ export declare function scanUnregisteredAssertionEvidence(workdir: string, state: SuperpowersTaskState): Promise<{
9
+ ignored: IgnoredUnregisteredEvidence[];
10
+ errors: string[];
11
+ }>;