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.
- package/README.md +5 -5
- package/assets/README.md +8 -8
- package/assets/README.zh-CN.md +4 -4
- package/assets/protected-harness-baseline.json +6 -4
- package/assets/skills/composite-long-task-workflow/SKILL.md +12 -5
- package/assets/skills/composite-long-task-workflow/assets/execution-binding.template.md +19 -0
- package/assets/skills/composite-long-task-workflow/assets/goal-objective.template.md +2 -2
- package/assets/skills/composite-long-task-workflow/references/composite-long-task-workflow-protocol.md +47 -26
- package/dist/commands/composite-long-task.js +21 -3
- package/dist/lib/package-source.js +11 -1
- package/dist/lib/superpowers-task-command-run-correlation.d.ts +8 -0
- package/dist/lib/superpowers-task-command-run-correlation.js +103 -0
- package/dist/lib/superpowers-task-completion-output.d.ts +91 -0
- package/dist/lib/superpowers-task-completion-output.js +374 -0
- package/dist/lib/superpowers-task-current-evidence.js +22 -0
- package/dist/lib/superpowers-task-derive.js +66 -8
- package/dist/lib/superpowers-task-evidence-kernel.d.ts +7 -0
- package/dist/lib/superpowers-task-evidence-kernel.js +67 -71
- package/dist/lib/superpowers-task-evidence.js +7 -1
- package/dist/lib/superpowers-task-final-card.d.ts +3 -0
- package/dist/lib/superpowers-task-final-card.js +35 -0
- package/dist/lib/superpowers-task-gates.d.ts +2 -2
- package/dist/lib/superpowers-task-gates.js +179 -27
- package/dist/lib/superpowers-task-harness-drift.js +6 -2
- package/dist/lib/superpowers-task-protected-baseline.js +21 -2
- package/dist/lib/superpowers-task-state-schema.d.ts +37 -0
- package/dist/lib/superpowers-task-state.js +10 -1
- package/dist/lib/superpowers-task-unregistered-evidence.d.ts +11 -0
- package/dist/lib/superpowers-task-unregistered-evidence.js +72 -0
- package/dist/lib/superpowers-task-validator.js +41 -0
- package/package.json +69 -69
|
@@ -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, scanGeneratedCompletionOutputSurfacesDetailed, triageFinalGateBlockers } 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);
|
|
@@ -24,25 +26,48 @@ export async function runEpochGate(workdir, epochId) {
|
|
|
24
26
|
return { passed: true, messages: ["epoch derived artifacts refreshed"] };
|
|
25
27
|
}
|
|
26
28
|
export async function runFinalGate(workdir) {
|
|
29
|
+
return runFinalGateOnce(workdir, { recoveryAttempted: false, recoveryAction: "" });
|
|
30
|
+
}
|
|
31
|
+
async function runFinalGateOnce(workdir, options) {
|
|
27
32
|
const state = await loadSuperpowersState(workdir);
|
|
33
|
+
const previousTransientFindings = previousTransientBookkeepingFindings(state);
|
|
28
34
|
const kernel = await evaluateTrustedEvidenceKernel(workdir, state);
|
|
29
35
|
applyTrustedEvidenceKernelResult(state, kernel);
|
|
30
36
|
state.gates.validator = { status: "not_run", kernel: "trusted_evidence_kernel" };
|
|
37
|
+
const candidateContract = resolveCompletionOutputStatus({
|
|
38
|
+
final_gate_ran: true,
|
|
39
|
+
product_goal_complete: kernel.product_goal_complete && kernel.errors.length === 0,
|
|
40
|
+
acceptance_target_status: kernel.product_goal_complete && kernel.errors.length === 0 ? "complete" : kernel.acceptance_target_status,
|
|
41
|
+
audit_task_complete: true,
|
|
42
|
+
validator_errors: kernel.errors,
|
|
43
|
+
rejection_reasons: kernel.product_goal_complete ? [] : kernel.errors.slice(0, 12)
|
|
44
|
+
});
|
|
45
|
+
const candidateState = candidateStateFromContract(candidateContract);
|
|
46
|
+
candidateContract.candidate_state = candidateState;
|
|
47
|
+
applyCompletionOutputContract(state, candidateContract);
|
|
48
|
+
state.final.completion_basis = candidateContract.product_goal_complete
|
|
49
|
+
? ["trusted_evidence_kernel", "current_attempt_evidence", "negative_evidence_scan_passed", "harness_drift_lock_passed"]
|
|
50
|
+
: [];
|
|
31
51
|
await saveSuperpowersState(workdir, state);
|
|
32
52
|
await deriveSuperpowersArtifacts(workdir);
|
|
33
53
|
const report = await validateSuperpowersState(workdir, [workdir]);
|
|
34
54
|
const acceptanceReport = await validatePlanAcceptance(workdir, [workdir]);
|
|
35
55
|
const latest = await loadSuperpowersState(workdir);
|
|
36
|
-
|
|
37
|
-
|
|
56
|
+
let errors = [...new Set([...kernel.errors, ...report.errors, ...acceptanceReport.errors])];
|
|
57
|
+
let complete = kernel.product_goal_complete && errors.length === 0;
|
|
38
58
|
const acceptanceStatus = complete ? "complete" : acceptanceStatusForErrors(errors, kernel);
|
|
39
59
|
const nextRequiredActions = complete ? [] : nextActionsForErrors(errors);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
60
|
+
let contract = resolveCompletionOutputStatus({
|
|
61
|
+
final_gate_ran: true,
|
|
62
|
+
product_goal_complete: complete,
|
|
63
|
+
acceptance_target_status: acceptanceStatus,
|
|
64
|
+
audit_task_complete: true,
|
|
65
|
+
validator_errors: report.errors,
|
|
66
|
+
acceptance_validator_errors: acceptanceReport.errors,
|
|
67
|
+
rejection_reasons: complete ? [] : nextRequiredActions
|
|
68
|
+
});
|
|
69
|
+
contract.candidate_state = candidateState;
|
|
70
|
+
applyCompletionOutputContract(latest, contract);
|
|
46
71
|
latest.final.completion_basis = complete
|
|
47
72
|
? ["trusted_evidence_kernel", "current_attempt_evidence", "negative_evidence_scan_passed", "harness_drift_lock_passed"]
|
|
48
73
|
: [];
|
|
@@ -51,31 +76,126 @@ export async function runFinalGate(workdir) {
|
|
|
51
76
|
latest.gates.final_gate = {
|
|
52
77
|
status: complete ? "pass" : acceptanceStatus,
|
|
53
78
|
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
|
-
],
|
|
79
|
+
order: kernel.kernel_order,
|
|
70
80
|
errors,
|
|
71
81
|
stale_evidence_ids: kernel.stale_evidence_ids,
|
|
82
|
+
ignored_unregistered_evidence: kernel.ignored_unregistered_evidence,
|
|
83
|
+
invalidated_evidence_ids: kernel.invalidated_evidence_ids,
|
|
84
|
+
ac_findings: kernel.ac_findings,
|
|
85
|
+
pi_findings: kernel.pi_findings,
|
|
72
86
|
harness_task_final_verdict: kernel.harness_task_final_verdict,
|
|
73
|
-
next_required_actions: nextRequiredActions
|
|
87
|
+
next_required_actions: nextRequiredActions,
|
|
88
|
+
completion_output_status: contract.completion_output_status,
|
|
89
|
+
final_answer_allowed: contract.final_answer_allowed,
|
|
90
|
+
required_user_visible_status: contract.required_user_visible_status,
|
|
91
|
+
exit_code: contract.exit_code,
|
|
92
|
+
blocked_reasons: contract.blocked_reasons,
|
|
93
|
+
rejection_reasons: contract.rejection_reasons,
|
|
94
|
+
generated_output_mismatch: contract.generated_output_mismatch,
|
|
95
|
+
candidate_state: candidateState,
|
|
96
|
+
previous_bookkeeping_snapshot: previousTransientFindings
|
|
74
97
|
};
|
|
75
98
|
await saveSuperpowersState(workdir, latest);
|
|
76
99
|
await deriveSuperpowersArtifacts(workdir);
|
|
77
|
-
await
|
|
78
|
-
|
|
100
|
+
const outputFindings = await scanGeneratedCompletionOutputSurfacesDetailed(workdir, contract);
|
|
101
|
+
let triage = triageFinalGateBlockers({
|
|
102
|
+
errors,
|
|
103
|
+
output_findings: outputFindings,
|
|
104
|
+
previous_transient_findings: previousTransientFindings,
|
|
105
|
+
candidate_state: candidateState,
|
|
106
|
+
recovery_attempted: options.recoveryAttempted || previousTransientFindings.length > 0,
|
|
107
|
+
recovery_action: options.recoveryAction || (previousTransientFindings.length > 0 ? "cleared previous transient bookkeeping" : "")
|
|
108
|
+
});
|
|
109
|
+
if (outputFindings.length > 0 && triage.self_recoverable && !options.recoveryAttempted) {
|
|
110
|
+
await deriveSuperpowersArtifacts(workdir);
|
|
111
|
+
return runFinalGateOnce(workdir, { recoveryAttempted: true, recoveryAction: "regenerated_derived_outputs" });
|
|
112
|
+
}
|
|
113
|
+
if (outputFindings.length > 0) {
|
|
114
|
+
errors = [...new Set([...errors, ...completionPhraseFindingMessages(outputFindings)])];
|
|
115
|
+
complete = false;
|
|
116
|
+
triage = triageFinalGateBlockers({
|
|
117
|
+
errors,
|
|
118
|
+
output_findings: outputFindings,
|
|
119
|
+
previous_transient_findings: previousTransientFindings,
|
|
120
|
+
candidate_state: candidateState,
|
|
121
|
+
recovery_attempted: options.recoveryAttempted,
|
|
122
|
+
recovery_action: options.recoveryAction
|
|
123
|
+
});
|
|
124
|
+
contract = resolveCompletionOutputStatus({
|
|
125
|
+
final_gate_ran: true,
|
|
126
|
+
product_goal_complete: false,
|
|
127
|
+
acceptance_target_status: acceptanceStatusForErrors(errors, kernel),
|
|
128
|
+
audit_task_complete: true,
|
|
129
|
+
validator_errors: errors,
|
|
130
|
+
generated_output_mismatch: true
|
|
131
|
+
});
|
|
132
|
+
contract.candidate_state = candidateState;
|
|
133
|
+
contract.blocker_triage = triage;
|
|
134
|
+
const blockedLatest = await loadSuperpowersState(workdir);
|
|
135
|
+
applyCompletionOutputContract(blockedLatest, contract);
|
|
136
|
+
blockedLatest.final.completion_basis = [];
|
|
137
|
+
blockedLatest.final.next_required_actions = nextActionsForErrors(errors);
|
|
138
|
+
blockedLatest.gates.validator = { status: "blocked", errors };
|
|
139
|
+
blockedLatest.gates.final_gate = {
|
|
140
|
+
...(isRecord(blockedLatest.gates.final_gate) ? blockedLatest.gates.final_gate : {}),
|
|
141
|
+
status: contract.completion_output_status,
|
|
142
|
+
errors,
|
|
143
|
+
next_required_actions: blockedLatest.final.next_required_actions,
|
|
144
|
+
completion_output_status: contract.completion_output_status,
|
|
145
|
+
final_answer_allowed: contract.final_answer_allowed,
|
|
146
|
+
required_user_visible_status: contract.required_user_visible_status,
|
|
147
|
+
exit_code: contract.exit_code,
|
|
148
|
+
blocked_reasons: contract.blocked_reasons,
|
|
149
|
+
rejection_reasons: contract.rejection_reasons,
|
|
150
|
+
generated_output_mismatch: contract.generated_output_mismatch,
|
|
151
|
+
false_completion_phrase_findings: outputFindings,
|
|
152
|
+
candidate_state: candidateState,
|
|
153
|
+
blocker_triage: triage,
|
|
154
|
+
previous_bookkeeping_snapshot: previousTransientFindings
|
|
155
|
+
};
|
|
156
|
+
blockedLatest.final.false_completion_phrase_findings = outputFindings;
|
|
157
|
+
await saveSuperpowersState(workdir, blockedLatest);
|
|
158
|
+
await deriveSuperpowersArtifacts(workdir);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
if (triage.category === "environment_blocked" || triage.category === "contract_blocked" || triage.category === "harness_drift_blocked") {
|
|
162
|
+
contract = resolveCompletionOutputStatus({
|
|
163
|
+
final_gate_ran: true,
|
|
164
|
+
product_goal_complete: false,
|
|
165
|
+
acceptance_target_status: "blocked",
|
|
166
|
+
audit_task_complete: true,
|
|
167
|
+
validator_errors: errors,
|
|
168
|
+
blocked_reasons: [triage.category],
|
|
169
|
+
rejection_reasons: []
|
|
170
|
+
});
|
|
171
|
+
contract.candidate_state = candidateState;
|
|
172
|
+
}
|
|
173
|
+
contract.blocker_triage = triage;
|
|
174
|
+
const triagedLatest = await loadSuperpowersState(workdir);
|
|
175
|
+
applyCompletionOutputContract(triagedLatest, contract);
|
|
176
|
+
triagedLatest.final.next_required_actions = contract.completion_output_status === "accept" ? [] : nextActionsForErrors(errors);
|
|
177
|
+
triagedLatest.gates.final_gate = {
|
|
178
|
+
...(isRecord(triagedLatest.gates.final_gate) ? triagedLatest.gates.final_gate : {}),
|
|
179
|
+
completion_output_status: contract.completion_output_status,
|
|
180
|
+
final_answer_allowed: contract.final_answer_allowed,
|
|
181
|
+
required_user_visible_status: contract.required_user_visible_status,
|
|
182
|
+
exit_code: contract.exit_code,
|
|
183
|
+
blocked_reasons: contract.blocked_reasons,
|
|
184
|
+
rejection_reasons: contract.rejection_reasons,
|
|
185
|
+
generated_output_mismatch: contract.generated_output_mismatch,
|
|
186
|
+
candidate_state: candidateState,
|
|
187
|
+
blocker_triage: triage,
|
|
188
|
+
previous_bookkeeping_snapshot: previousTransientFindings
|
|
189
|
+
};
|
|
190
|
+
await saveSuperpowersState(workdir, triagedLatest);
|
|
191
|
+
await deriveSuperpowersArtifacts(workdir);
|
|
192
|
+
}
|
|
193
|
+
await appendSuperpowersEvent(workdir, "final_gate", {
|
|
194
|
+
product_goal_complete: contract.product_goal_complete,
|
|
195
|
+
completion_output_status: contract.completion_output_status,
|
|
196
|
+
blocker_triage: contract.blocker_triage
|
|
197
|
+
});
|
|
198
|
+
return { ...contract, blocker_triage: contract.blocker_triage ?? triage, errors };
|
|
79
199
|
}
|
|
80
200
|
function acceptanceStatusForErrors(errors, kernel) {
|
|
81
201
|
const text = errors.join("\n");
|
|
@@ -113,3 +233,35 @@ function nextActionsForErrors(errors) {
|
|
|
113
233
|
return error;
|
|
114
234
|
});
|
|
115
235
|
}
|
|
236
|
+
function candidateStateFromContract(contract) {
|
|
237
|
+
return {
|
|
238
|
+
final_gate_ran: contract.final_gate_ran,
|
|
239
|
+
product_goal_complete: contract.product_goal_complete,
|
|
240
|
+
acceptance_target_status: contract.acceptance_target_status,
|
|
241
|
+
completion_output_status: contract.completion_output_status,
|
|
242
|
+
generated_output_mismatch: false,
|
|
243
|
+
source: "trusted_evidence_kernel"
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function previousTransientBookkeepingFindings(state) {
|
|
247
|
+
const findings = [];
|
|
248
|
+
const finalRecord = state.final;
|
|
249
|
+
const metaRecord = state.meta;
|
|
250
|
+
const gate = isRecord(state.gates?.final_gate) ? state.gates.final_gate : {};
|
|
251
|
+
collectTransient(findings, "final", finalRecord);
|
|
252
|
+
collectTransient(findings, "meta", metaRecord);
|
|
253
|
+
collectTransient(findings, "gates.final_gate", gate);
|
|
254
|
+
return [...new Set(findings)];
|
|
255
|
+
}
|
|
256
|
+
function collectTransient(findings, label, record) {
|
|
257
|
+
const status = typeof record.completion_output_status === "string" ? record.completion_output_status : "";
|
|
258
|
+
if (status === "blocked" || status === "reject") {
|
|
259
|
+
findings.push(`${label}.completion_output_status=${status}`);
|
|
260
|
+
}
|
|
261
|
+
if (record.generated_output_mismatch === true) {
|
|
262
|
+
findings.push(`${label}.generated_output_mismatch=true`);
|
|
263
|
+
}
|
|
264
|
+
if (Array.isArray(record.false_completion_phrase_findings) && record.false_completion_phrase_findings.length > 0) {
|
|
265
|
+
findings.push(`${label}.false_completion_phrase_findings=${record.false_completion_phrase_findings.length}`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
@@ -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
|
-
/
|
|
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
|
-
|
|
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
|
-
|
|
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,25 @@ 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";
|
|
80
|
+
export type FinalGateBlockerCategory = "none" | "product_evidence_failed" | "missing_current_evidence" | "stale_or_contradictory_evidence" | "generated_output_mismatch" | "self_recoverable_generated_output_mismatch" | "transient_state_bookkeeping" | "environment_blocked" | "contract_blocked" | "harness_drift_blocked";
|
|
81
|
+
export interface FinalGateCandidateStateRecord {
|
|
82
|
+
final_gate_ran: boolean;
|
|
83
|
+
product_goal_complete: boolean;
|
|
84
|
+
acceptance_target_status: string;
|
|
85
|
+
completion_output_status: CompletionOutputStatus;
|
|
86
|
+
generated_output_mismatch: boolean;
|
|
87
|
+
source: string;
|
|
88
|
+
}
|
|
89
|
+
export interface FinalGateBlockerTriageRecord {
|
|
90
|
+
category: FinalGateBlockerCategory;
|
|
91
|
+
self_recoverable: boolean;
|
|
92
|
+
recovery_attempted: boolean;
|
|
93
|
+
recovery_action: string;
|
|
94
|
+
next_action: string;
|
|
95
|
+
details: string[];
|
|
96
|
+
blocker_count: number;
|
|
97
|
+
}
|
|
79
98
|
export interface SuperpowersTaskState {
|
|
80
99
|
meta: {
|
|
81
100
|
task_id: string;
|
|
@@ -87,6 +106,7 @@ export interface SuperpowersTaskState {
|
|
|
87
106
|
product_goal_complete: boolean;
|
|
88
107
|
acceptance_target_status: string;
|
|
89
108
|
audit_task_complete: boolean;
|
|
109
|
+
completion_output_status?: CompletionOutputStatus;
|
|
90
110
|
};
|
|
91
111
|
sources: Record<string, SuperpowersSourceRecord>;
|
|
92
112
|
context: {
|
|
@@ -116,6 +136,17 @@ export interface SuperpowersTaskState {
|
|
|
116
136
|
product_goal_complete: boolean;
|
|
117
137
|
acceptance_target_status: string;
|
|
118
138
|
audit_task_complete: boolean;
|
|
139
|
+
completion_output_status?: CompletionOutputStatus;
|
|
140
|
+
final_answer_allowed?: boolean;
|
|
141
|
+
required_user_visible_status?: "accepted" | "rejected" | "blocked";
|
|
142
|
+
final_answer?: CompletionOutputStatus;
|
|
143
|
+
exit_code?: 0 | 1 | 2;
|
|
144
|
+
blocked_reasons?: string[];
|
|
145
|
+
rejection_reasons?: string[];
|
|
146
|
+
generated_output_mismatch?: boolean;
|
|
147
|
+
false_completion_phrase_findings?: unknown[];
|
|
148
|
+
blocker_triage?: FinalGateBlockerTriageRecord;
|
|
149
|
+
candidate_state?: FinalGateCandidateStateRecord;
|
|
119
150
|
completion_basis: string[];
|
|
120
151
|
next_required_actions?: string[];
|
|
121
152
|
};
|
|
@@ -162,6 +193,7 @@ export interface CommandRunRecord {
|
|
|
162
193
|
command_line: string;
|
|
163
194
|
exit_code: number;
|
|
164
195
|
started_at: string;
|
|
196
|
+
completed_at?: string;
|
|
165
197
|
ended_at: string;
|
|
166
198
|
artifact_paths: string[];
|
|
167
199
|
}
|
|
@@ -336,11 +368,16 @@ export interface SuperpowersEvidenceRecord {
|
|
|
336
368
|
schema_version?: "evidence-record-v1" | "evidence-record-v2" | string;
|
|
337
369
|
evidence_id: string;
|
|
338
370
|
task_attempt_id?: string;
|
|
371
|
+
generated_at?: string;
|
|
339
372
|
source_bundle_hash?: string;
|
|
340
373
|
product_source_hash?: string;
|
|
341
374
|
technical_plan_hash?: string;
|
|
342
375
|
acceptance_checklist_hash?: string;
|
|
343
376
|
git_head?: string;
|
|
377
|
+
git_status_short?: string;
|
|
378
|
+
tracked_diff_hash?: string;
|
|
379
|
+
relevant_untracked_hash?: string;
|
|
380
|
+
covers_dirty_worktree?: boolean;
|
|
344
381
|
worktree_fingerprint?: string;
|
|
345
382
|
command_spec_id?: string;
|
|
346
383
|
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
|
+
}>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { listFiles, readText } from "./fs.js";
|
|
3
|
+
import { isRecord } from "./superpowers-task-state-schema.js";
|
|
4
|
+
export async function scanUnregisteredAssertionEvidence(workdir, state) {
|
|
5
|
+
const registeredPaths = new Set();
|
|
6
|
+
for (const evidence of state.evidence ?? []) {
|
|
7
|
+
for (const candidate of [evidence.artifact_path, ...(evidence.artifact_paths ?? []), ...(evidence.assertion_result?.artifacts ?? [])]) {
|
|
8
|
+
if (candidate) {
|
|
9
|
+
registeredPaths.add(slash(candidate));
|
|
10
|
+
registeredPaths.add(slash(path.join(workdir, candidate)));
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const ignored = [];
|
|
15
|
+
for (const file of await listFiles(workdir)) {
|
|
16
|
+
if (!/\.json$/i.test(file)) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const relative = slash(path.relative(workdir, file));
|
|
20
|
+
if (registeredPaths.has(relative) || registeredPaths.has(slash(file))) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const parsed = await readJson(file);
|
|
24
|
+
const assertion = assertionRecord(parsed);
|
|
25
|
+
if (!assertion) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const status = String(assertion.status ?? "");
|
|
29
|
+
if (status !== "passed") {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
ignored.push({
|
|
33
|
+
path: relative,
|
|
34
|
+
status,
|
|
35
|
+
target_ac_ids: asStringArray(assertion.target_ac_ids),
|
|
36
|
+
target_proof_layers: asStringArray(assertion.target_proof_layers)
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
ignored,
|
|
41
|
+
errors: ignored.map((item) => `ignored_unregistered_evidence: unregistered assertion JSON ${item.path} status=passed is not proof`)
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
async function readJson(file) {
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(await readText(file));
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function assertionRecord(value) {
|
|
53
|
+
if (!isRecord(value)) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
if (looksLikeAssertion(value)) {
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
if (isRecord(value.assertion_result) && looksLikeAssertion(value.assertion_result)) {
|
|
60
|
+
return value.assertion_result;
|
|
61
|
+
}
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
function looksLikeAssertion(value) {
|
|
65
|
+
return /assertion-result-v\d+/i.test(String(value.schema_version ?? "")) || (typeof value.status === "string" && Array.isArray(value.target_ac_ids));
|
|
66
|
+
}
|
|
67
|
+
function asStringArray(value) {
|
|
68
|
+
return Array.isArray(value) ? value.map(String).filter(Boolean) : [];
|
|
69
|
+
}
|
|
70
|
+
function slash(value) {
|
|
71
|
+
return value.replace(/\\/g, "/");
|
|
72
|
+
}
|
|
@@ -12,6 +12,7 @@ import { loadSuperpowersState, sha256 } from "./superpowers-task-state.js";
|
|
|
12
12
|
import { validateCanonicalStatuses } from "./superpowers-task-status.js";
|
|
13
13
|
import { isRecord } from "./superpowers-task-state-schema.js";
|
|
14
14
|
import { evaluateProofLayerAssertions, isUiBrowserLayer, proofLayerName } from "./superpowers-task-assertions.js";
|
|
15
|
+
import { completionOutputContractFromState, completionPhraseFindingMessages, scanGeneratedCompletionOutputSurfaces } from "./superpowers-task-completion-output.js";
|
|
15
16
|
export async function validateSuperpowersState(projectRoot, args = []) {
|
|
16
17
|
const info = [];
|
|
17
18
|
const warnings = [];
|
|
@@ -47,12 +48,52 @@ export async function validateSuperpowersState(projectRoot, args = []) {
|
|
|
47
48
|
validateAuditor(state, errors);
|
|
48
49
|
validateFinalCompletion(state, errors);
|
|
49
50
|
errors.push(...(await derivedMatchesState(targetDir, state)));
|
|
51
|
+
await validateCompletionOutputConsistency(targetDir, state, errors, info);
|
|
50
52
|
info.push(`checked superpowers task state ${repoRelative(projectRoot, targetDir)} plan_items=${Object.keys(state.graph?.plan_items ?? {}).length} acs=${Object.keys(state.graph?.acceptance_criteria ?? {}).length} evidence=${state.evidence?.length ?? 0}`);
|
|
51
53
|
if (errors.length === 0) {
|
|
52
54
|
info.push("Superpowers task state validation passed");
|
|
53
55
|
}
|
|
54
56
|
return { info, warnings, hygiene, errors };
|
|
55
57
|
}
|
|
58
|
+
async function validateCompletionOutputConsistency(workdir, state, errors, info) {
|
|
59
|
+
const contract = completionOutputContractFromState(state);
|
|
60
|
+
const finalRecord = state.final;
|
|
61
|
+
const gate = isRecord(state.gates?.final_gate) ? state.gates.final_gate : {};
|
|
62
|
+
const storedStatus = finalRecord.completion_output_status ?? (typeof gate.completion_output_status === "string" ? gate.completion_output_status : undefined);
|
|
63
|
+
if (storedStatus && storedStatus !== contract.completion_output_status) {
|
|
64
|
+
errors.push(`completion_output_status mismatch: expected ${contract.completion_output_status}, found ${storedStatus}`);
|
|
65
|
+
}
|
|
66
|
+
if (contract.completion_output_status === "accept" && state.final.product_goal_complete !== true) {
|
|
67
|
+
errors.push("completion_output_status=accept but product_goal_complete is not true");
|
|
68
|
+
}
|
|
69
|
+
if (finalRecord.final_answer_allowed !== undefined && finalRecord.final_answer_allowed !== contract.final_answer_allowed) {
|
|
70
|
+
errors.push(`final_answer_allowed mismatch: expected ${contract.final_answer_allowed}, found ${finalRecord.final_answer_allowed}`);
|
|
71
|
+
}
|
|
72
|
+
if (finalRecord.required_user_visible_status && finalRecord.required_user_visible_status !== contract.required_user_visible_status) {
|
|
73
|
+
errors.push(`required_user_visible_status mismatch: expected ${contract.required_user_visible_status}, found ${finalRecord.required_user_visible_status}`);
|
|
74
|
+
}
|
|
75
|
+
if (finalRecord.exit_code !== undefined && finalRecord.exit_code !== contract.exit_code) {
|
|
76
|
+
errors.push(`completion output exit_code mismatch: expected ${contract.exit_code}, found ${finalRecord.exit_code}`);
|
|
77
|
+
}
|
|
78
|
+
if (contract.blocker_triage) {
|
|
79
|
+
info.push(`blocker_triage category=${contract.blocker_triage.category} self_recoverable=${contract.blocker_triage.self_recoverable} next_action=${contract.blocker_triage.next_action}`);
|
|
80
|
+
}
|
|
81
|
+
errors.push(...completionPhraseFindingMessages(await scanGeneratedCompletionOutputSurfaces(workdir, contract)));
|
|
82
|
+
await validateMarkdownCompletionStatus(workdir, contract.completion_output_status, errors);
|
|
83
|
+
}
|
|
84
|
+
async function validateMarkdownCompletionStatus(workdir, expectedStatus, errors) {
|
|
85
|
+
for (const relative of ["derived/final-summary.md", "derived/final-card.md", "derived/local-audit.md"]) {
|
|
86
|
+
const file = path.join(workdir, ...relative.split("/"));
|
|
87
|
+
if (!(await pathExists(file))) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const text = await readText(file);
|
|
91
|
+
const match = /^completion_output_status:\s*(\S+)\s*$/im.exec(text);
|
|
92
|
+
if (match && match[1] !== expectedStatus) {
|
|
93
|
+
errors.push(`${relative} completion_output_status mismatch: expected ${expectedStatus}, found ${match[1]}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
56
97
|
async function validateSourceHashes(workdir, state, errors) {
|
|
57
98
|
for (const [key, source] of Object.entries(state.sources ?? {})) {
|
|
58
99
|
const file = path.join(workdir, source.path);
|