project-tiny-context-harness 0.2.82 → 0.2.83
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 +9 -3
- package/assets/README.md +9 -3
- package/assets/README.zh-CN.md +7 -1
- package/assets/protected-harness-baseline.json +18 -0
- package/assets/skills/composite-long-task-workflow/SKILL.md +19 -1
- package/assets/skills/composite-long-task-workflow/assets/execution-binding.template.md +10 -0
- package/assets/skills/composite-long-task-workflow/assets/goal-objective.template.md +6 -15
- package/assets/skills/composite-long-task-workflow/references/composite-long-task-workflow-protocol.md +26 -14
- package/dist/commands/composite-long-task.js +45 -2
- package/dist/lib/superpowers-task-ac010.d.ts +6 -0
- package/dist/lib/superpowers-task-ac010.js +26 -0
- package/dist/lib/superpowers-task-assertion-normalizers.js +4 -0
- package/dist/lib/superpowers-task-assertions.js +18 -4
- package/dist/lib/superpowers-task-attempt.d.ts +4 -0
- package/dist/lib/superpowers-task-attempt.js +102 -0
- package/dist/lib/superpowers-task-command-specs.d.ts +3 -0
- package/dist/lib/superpowers-task-command-specs.js +52 -0
- package/dist/lib/superpowers-task-compile.d.ts +4 -1
- package/dist/lib/superpowers-task-compile.js +7 -1
- package/dist/lib/superpowers-task-contradictions.d.ts +6 -0
- package/dist/lib/superpowers-task-contradictions.js +126 -0
- package/dist/lib/superpowers-task-current-evidence.d.ts +3 -0
- package/dist/lib/superpowers-task-current-evidence.js +154 -0
- package/dist/lib/superpowers-task-derive.js +14 -0
- package/dist/lib/superpowers-task-evidence-kernel.d.ts +12 -0
- package/dist/lib/superpowers-task-evidence-kernel.js +351 -0
- package/dist/lib/superpowers-task-evidence-records.d.ts +2 -0
- package/dist/lib/superpowers-task-evidence-records.js +55 -0
- package/dist/lib/superpowers-task-evidence.d.ts +10 -0
- package/dist/lib/superpowers-task-evidence.js +141 -0
- package/dist/lib/superpowers-task-gates.js +36 -24
- package/dist/lib/superpowers-task-harness-drift.d.ts +11 -0
- package/dist/lib/superpowers-task-harness-drift.js +86 -0
- package/dist/lib/superpowers-task-protected-baseline.d.ts +10 -0
- package/dist/lib/superpowers-task-protected-baseline.js +47 -0
- package/dist/lib/superpowers-task-state-schema.d.ts +99 -3
- package/dist/lib/superpowers-task-state-schema.js +23 -1
- package/dist/lib/superpowers-task-state-shape.d.ts +3 -0
- package/dist/lib/superpowers-task-state-shape.js +50 -0
- package/dist/lib/superpowers-task-state.js +7 -36
- package/dist/lib/superpowers-task-status.js +11 -1
- package/dist/lib/superpowers-task-under-specified.d.ts +7 -0
- package/dist/lib/superpowers-task-under-specified.js +61 -0
- package/dist/lib/superpowers-task-validator.js +5 -27
- package/package.json +1 -1
- package/source-mappings.yaml +3 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
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
|
-
import { loadSuperpowersState,
|
|
5
|
-
import {
|
|
4
|
+
import { loadSuperpowersState, saveSuperpowersState } from "./superpowers-task-state.js";
|
|
5
|
+
import { applyTrustedEvidenceKernelResult, evaluateTrustedEvidenceKernel } from "./superpowers-task-evidence-kernel.js";
|
|
6
|
+
import { validateSuperpowersState } from "./superpowers-task-validator.js";
|
|
6
7
|
export async function runSliceGate(workdir, sliceId) {
|
|
7
8
|
const state = await loadSuperpowersState(workdir);
|
|
8
9
|
const slice = state.slices.find((item) => item.slice_id === sliceId);
|
|
@@ -24,19 +25,17 @@ export async function runEpochGate(workdir, epochId) {
|
|
|
24
25
|
}
|
|
25
26
|
export async function runFinalGate(workdir) {
|
|
26
27
|
const state = await loadSuperpowersState(workdir);
|
|
27
|
-
|
|
28
|
-
state
|
|
29
|
-
state.
|
|
30
|
-
state.gates.validator = { status: "not_run" };
|
|
28
|
+
const kernel = await evaluateTrustedEvidenceKernel(workdir, state);
|
|
29
|
+
applyTrustedEvidenceKernelResult(state, kernel);
|
|
30
|
+
state.gates.validator = { status: "not_run", kernel: "trusted_evidence_kernel" };
|
|
31
31
|
await saveSuperpowersState(workdir, state);
|
|
32
32
|
await deriveSuperpowersArtifacts(workdir);
|
|
33
33
|
const report = await validateSuperpowersState(workdir, [workdir]);
|
|
34
34
|
const acceptanceReport = await validatePlanAcceptance(workdir, [workdir]);
|
|
35
35
|
const latest = await loadSuperpowersState(workdir);
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
const acceptanceStatus = complete ? "complete" : acceptanceStatusForErrors(errors);
|
|
36
|
+
const errors = [...new Set([...kernel.errors, ...report.errors, ...acceptanceReport.errors])];
|
|
37
|
+
const complete = kernel.product_goal_complete && errors.length === 0;
|
|
38
|
+
const acceptanceStatus = complete ? "complete" : acceptanceStatusForErrors(errors, kernel);
|
|
40
39
|
const nextRequiredActions = complete ? [] : nextActionsForErrors(errors);
|
|
41
40
|
latest.final.product_goal_complete = complete;
|
|
42
41
|
latest.meta.product_goal_complete = complete;
|
|
@@ -45,25 +44,32 @@ export async function runFinalGate(workdir) {
|
|
|
45
44
|
latest.final.audit_task_complete = true;
|
|
46
45
|
latest.meta.audit_task_complete = true;
|
|
47
46
|
latest.final.completion_basis = complete
|
|
48
|
-
? ["
|
|
47
|
+
? ["trusted_evidence_kernel", "current_attempt_evidence", "negative_evidence_scan_passed", "harness_drift_lock_passed"]
|
|
49
48
|
: [];
|
|
50
49
|
latest.final.next_required_actions = nextRequiredActions;
|
|
51
50
|
latest.gates.validator = { status: errors.length === 0 ? "pass" : "blocked", errors };
|
|
52
51
|
latest.gates.final_gate = {
|
|
53
52
|
status: complete ? "pass" : acceptanceStatus,
|
|
53
|
+
kernel: "trusted_evidence_kernel",
|
|
54
54
|
order: [
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
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"
|
|
65
69
|
],
|
|
66
70
|
errors,
|
|
71
|
+
stale_evidence_ids: kernel.stale_evidence_ids,
|
|
72
|
+
harness_task_final_verdict: kernel.harness_task_final_verdict,
|
|
67
73
|
next_required_actions: nextRequiredActions
|
|
68
74
|
};
|
|
69
75
|
await saveSuperpowersState(workdir, latest);
|
|
@@ -71,12 +77,15 @@ export async function runFinalGate(workdir) {
|
|
|
71
77
|
await appendSuperpowersEvent(workdir, "final_gate", { product_goal_complete: complete });
|
|
72
78
|
return { product_goal_complete: complete, errors };
|
|
73
79
|
}
|
|
74
|
-
function acceptanceStatusForErrors(errors) {
|
|
80
|
+
function acceptanceStatusForErrors(errors, kernel) {
|
|
75
81
|
const text = errors.join("\n");
|
|
76
|
-
if (
|
|
82
|
+
if (kernel?.acceptance_target_status === "under_specified" || /under_specified/i.test(text)) {
|
|
83
|
+
return "under_specified";
|
|
84
|
+
}
|
|
85
|
+
if (/source hash mismatch|source_changed_requires_recompile|scope_conflict_requires_decision|auditor blocker|Context Delta coverage is unresolved|harness_drift|protected_baseline|harness_task_missing|required_command_specs_hash/i.test(text)) {
|
|
77
86
|
return "blocked";
|
|
78
87
|
}
|
|
79
|
-
if (/invalid evidence|forbidden shortcut|negative evidence|stale evidence|raw secret|contains_secret|sibling substitution|assertion_result\.status=failed|assertion exit_code=|command_exit_code=|forbidden text/i.test(text)) {
|
|
88
|
+
if (/invalid evidence|forbidden shortcut|negative evidence|stale evidence|raw secret|contains_secret|sibling substitution|assertion_result\.status=failed|assertion exit_code=|command_exit_code=|forbidden text|current contradiction|workflow_gate_bug_prevented|playwright_last_run_failed|owner_dom_forbidden_state|failed_test_result_artifact/i.test(text)) {
|
|
80
89
|
return "invalidated";
|
|
81
90
|
}
|
|
82
91
|
return "partial";
|
|
@@ -95,6 +104,9 @@ function nextActionsForErrors(errors) {
|
|
|
95
104
|
if (/negative evidence|forbidden/i.test(error)) {
|
|
96
105
|
return `${error}; rerun the negative evidence scan and fix the contradictory owner-surface state`;
|
|
97
106
|
}
|
|
107
|
+
if (/current contradiction|playwright_last_run_failed|owner_dom_forbidden_state|failed_test_result_artifact/i.test(error)) {
|
|
108
|
+
return `${error}; rerun the required current assertion command after fixing the current failure`;
|
|
109
|
+
}
|
|
98
110
|
if (/derived\/.*does not match/i.test(error)) {
|
|
99
111
|
return `${error}; rerun ty-context composite-long-task derive <workdir>`;
|
|
100
112
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type SuperpowersTaskState } from "./superpowers-task-state-schema.js";
|
|
2
|
+
export interface HarnessDriftResult {
|
|
3
|
+
harness_drift_detected: boolean;
|
|
4
|
+
acceptance_target_status: "complete" | "partial" | "blocked";
|
|
5
|
+
product_goal_complete: boolean;
|
|
6
|
+
harness_task_final_verdict?: "passed" | "failed";
|
|
7
|
+
changed_files: string[];
|
|
8
|
+
errors: string[];
|
|
9
|
+
}
|
|
10
|
+
export declare function detectHarnessDrift(state: SuperpowersTaskState): HarnessDriftResult;
|
|
11
|
+
export declare function isHarnessPath(file: string): boolean;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { isRecord } from "./superpowers-task-state-schema.js";
|
|
2
|
+
const HARNESS_PATH_PATTERNS = [
|
|
3
|
+
/(^|\/)tests\/.*\.(spec|test)\.[cm]?[jt]sx?$/i,
|
|
4
|
+
/(^|\/)playwright\.config\./i,
|
|
5
|
+
/(^|\/).*\.spec\.[cm]?[jt]sx?$/i,
|
|
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,
|
|
8
|
+
/(^|\/)(\.codex\/ty-context-managed|packages\/ty-context\/assets)\/skills\/composite-long-task-workflow\//i,
|
|
9
|
+
/composite-long-task-workflow-protocol\.md$/i,
|
|
10
|
+
/(^|\/)Makefile$/i,
|
|
11
|
+
/(^|\/)package\.json$/i
|
|
12
|
+
];
|
|
13
|
+
const REQUIRED_NEGATIVE_FIXTURES = [
|
|
14
|
+
"stale_evidence",
|
|
15
|
+
"historical_complete",
|
|
16
|
+
"derived_contradiction",
|
|
17
|
+
"ac010_summary_only",
|
|
18
|
+
"target_mismatch",
|
|
19
|
+
"api_only_for_ui",
|
|
20
|
+
"negative_evidence_after_pass",
|
|
21
|
+
"source_hash_mismatch",
|
|
22
|
+
"dirty_worktree_mismatch",
|
|
23
|
+
"missing_assertion_result",
|
|
24
|
+
"test_weakening"
|
|
25
|
+
];
|
|
26
|
+
export function detectHarnessDrift(state) {
|
|
27
|
+
const attempt = currentAttempt(state);
|
|
28
|
+
const changedFiles = changedFilesFor(attempt);
|
|
29
|
+
const harnessFiles = changedFiles.filter(isHarnessPath);
|
|
30
|
+
const mode = attempt?.mode ?? "product_task";
|
|
31
|
+
if (mode === "product_task" && harnessFiles.length > 0) {
|
|
32
|
+
return {
|
|
33
|
+
harness_drift_detected: true,
|
|
34
|
+
acceptance_target_status: "blocked",
|
|
35
|
+
product_goal_complete: false,
|
|
36
|
+
changed_files: harnessFiles,
|
|
37
|
+
errors: [
|
|
38
|
+
`harness_drift_detected: ${harnessFiles.join(", ")}`,
|
|
39
|
+
"本轮修改了验收工具链或测试本身,不能用被修改后的验收证明同一轮产品完成。请拆成独立 harness_task。"
|
|
40
|
+
]
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
if (mode === "harness_task") {
|
|
44
|
+
return harnessTaskFixtureVerdict(state, changedFiles);
|
|
45
|
+
}
|
|
46
|
+
return { harness_drift_detected: false, acceptance_target_status: "complete", product_goal_complete: true, changed_files: [], errors: [] };
|
|
47
|
+
}
|
|
48
|
+
export function isHarnessPath(file) {
|
|
49
|
+
const normalized = file.replace(/\\/g, "/");
|
|
50
|
+
return HARNESS_PATH_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
51
|
+
}
|
|
52
|
+
function harnessTaskFixtureVerdict(state, changedFiles) {
|
|
53
|
+
const fixtureState = isRecord(state.gates?.harness_task_fixtures) ? state.gates.harness_task_fixtures : undefined;
|
|
54
|
+
const errors = [];
|
|
55
|
+
if (!fixtureState) {
|
|
56
|
+
errors.push("harness_task_missing_adversarial_fixtures: harness_task must declare adversarial fixture outcomes");
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
for (const fixture of REQUIRED_NEGATIVE_FIXTURES) {
|
|
60
|
+
if (fixtureState[fixture] !== false) {
|
|
61
|
+
errors.push(`harness_task_missing_adversarial_fixtures: ${fixture} must have expected product_goal_complete=false`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (fixtureState.happy_path !== true) {
|
|
65
|
+
errors.push("harness_task_missing_happy_path_fixture: happy_path must have expected product_goal_complete=true");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const fixtureChanges = changedFiles.filter((file) => /(^|\/)tests\/ty-context\/.*fixture/i.test(file.replace(/\\/g, "/")));
|
|
69
|
+
if (fixtureChanges.length > 0) {
|
|
70
|
+
errors.push(`fixture_changed_requires_review: ${fixtureChanges.join(", ")}`);
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
harness_drift_detected: false,
|
|
74
|
+
acceptance_target_status: errors.length > 0 ? "blocked" : "complete",
|
|
75
|
+
product_goal_complete: false,
|
|
76
|
+
harness_task_final_verdict: errors.length > 0 ? "failed" : "passed",
|
|
77
|
+
changed_files: changedFiles,
|
|
78
|
+
errors
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function currentAttempt(state) {
|
|
82
|
+
return (state.attempts ?? []).find((item) => item.task_attempt_id === state.current_attempt_id) ?? (state.attempts ?? []).at(-1);
|
|
83
|
+
}
|
|
84
|
+
function changedFilesFor(attempt) {
|
|
85
|
+
return [...new Set((attempt?.changed_files ?? []).map((file) => file.replace(/\\/g, "/")).filter(Boolean))];
|
|
86
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type SuperpowersTaskState } from "./superpowers-task-state-schema.js";
|
|
2
|
+
export interface ProtectedBaselineResult {
|
|
3
|
+
protected_baseline_changed: boolean;
|
|
4
|
+
product_goal_complete: boolean;
|
|
5
|
+
changed_files: string[];
|
|
6
|
+
errors: string[];
|
|
7
|
+
}
|
|
8
|
+
export declare const PROTECTED_BASELINE_PATHS: string[];
|
|
9
|
+
export declare function evaluateProtectedBaseline(state: SuperpowersTaskState): ProtectedBaselineResult;
|
|
10
|
+
export declare function isProtectedBaselinePath(file: string): boolean;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { isHarnessPath } from "./superpowers-task-harness-drift.js";
|
|
2
|
+
import { isRecord } from "./superpowers-task-state-schema.js";
|
|
3
|
+
export const PROTECTED_BASELINE_PATHS = [
|
|
4
|
+
"packages/ty-context/assets/protected-harness-baseline.json",
|
|
5
|
+
".codex/ty-context-managed/protected-harness-baseline.json",
|
|
6
|
+
"packages/ty-context/source-mappings.yaml",
|
|
7
|
+
"packages/ty-context/src/lib/superpowers-task-gates.ts",
|
|
8
|
+
"packages/ty-context/src/lib/superpowers-task-validator.ts",
|
|
9
|
+
"packages/ty-context/src/lib/superpowers-task-derive.ts",
|
|
10
|
+
"packages/ty-context/src/lib/superpowers-task-evidence.ts",
|
|
11
|
+
"packages/ty-context/src/lib/superpowers-task-evidence-kernel.ts",
|
|
12
|
+
"packages/ty-context/src/lib/superpowers-task-state-schema.ts",
|
|
13
|
+
".codex/ty-context-managed/skills/composite-long-task-workflow/SKILL.md",
|
|
14
|
+
".codex/ty-context-managed/skills/composite-long-task-workflow/references/composite-long-task-workflow-protocol.md",
|
|
15
|
+
"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"
|
|
17
|
+
];
|
|
18
|
+
export function evaluateProtectedBaseline(state) {
|
|
19
|
+
const attempt = currentAttempt(state);
|
|
20
|
+
const changedFiles = [...new Set((attempt?.changed_files ?? []).map((file) => file.replace(/\\/g, "/")).filter(Boolean))];
|
|
21
|
+
const protectedChanges = changedFiles.filter(isProtectedBaselinePath);
|
|
22
|
+
const mode = attempt?.mode ?? "product_task";
|
|
23
|
+
const errors = [];
|
|
24
|
+
if (mode === "product_task" && protectedChanges.length > 0) {
|
|
25
|
+
errors.push(`protected_baseline_changed: product_task cannot change protected harness baseline paths: ${protectedChanges.join(", ")}`);
|
|
26
|
+
}
|
|
27
|
+
if (mode === "harness_task" && protectedChanges.length > 0 && !protectedBaselineReason(state)) {
|
|
28
|
+
errors.push("protected_baseline_reason_required: harness_task baseline changes must record gates.protected_baseline.reason");
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
protected_baseline_changed: protectedChanges.length > 0,
|
|
32
|
+
product_goal_complete: errors.length === 0,
|
|
33
|
+
changed_files: protectedChanges,
|
|
34
|
+
errors
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function isProtectedBaselinePath(file) {
|
|
38
|
+
const normalized = file.replace(/\\/g, "/");
|
|
39
|
+
return PROTECTED_BASELINE_PATHS.includes(normalized) || isHarnessPath(normalized);
|
|
40
|
+
}
|
|
41
|
+
function protectedBaselineReason(state) {
|
|
42
|
+
const baseline = state.gates?.protected_baseline;
|
|
43
|
+
return isRecord(baseline) && typeof baseline.reason === "string" && baseline.reason.trim().length > 0;
|
|
44
|
+
}
|
|
45
|
+
function currentAttempt(state) {
|
|
46
|
+
return (state.attempts ?? []).find((item) => item.task_attempt_id === state.current_attempt_id) ?? (state.attempts ?? []).at(-1);
|
|
47
|
+
}
|
|
@@ -4,7 +4,7 @@ export declare const SUPERPOWERS_TASK_STATE_JSON_SCHEMA: {
|
|
|
4
4
|
readonly $id: "https://project-tiny-context-harness.local/superpowers-task-state.schema.json";
|
|
5
5
|
readonly title: "Superpowers Long-Task State";
|
|
6
6
|
readonly type: "object";
|
|
7
|
-
readonly required: readonly ["meta", "sources", "context", "delivery", "graph", "slices", "evidence", "gates", "progress", "blockers", "final"];
|
|
7
|
+
readonly required: readonly ["meta", "sources", "context", "delivery", "graph", "attempts", "current_attempt_id", "required_command_specs", "command_runs", "negative_evidence_records", "slices", "evidence", "gates", "progress", "blockers", "final"];
|
|
8
8
|
readonly properties: {
|
|
9
9
|
readonly meta: {
|
|
10
10
|
readonly type: "object";
|
|
@@ -33,6 +33,21 @@ export declare const SUPERPOWERS_TASK_STATE_JSON_SCHEMA: {
|
|
|
33
33
|
readonly graph: {
|
|
34
34
|
readonly type: "object";
|
|
35
35
|
};
|
|
36
|
+
readonly attempts: {
|
|
37
|
+
readonly type: "array";
|
|
38
|
+
};
|
|
39
|
+
readonly current_attempt_id: {
|
|
40
|
+
readonly type: "string";
|
|
41
|
+
};
|
|
42
|
+
readonly required_command_specs: {
|
|
43
|
+
readonly type: "array";
|
|
44
|
+
};
|
|
45
|
+
readonly command_runs: {
|
|
46
|
+
readonly type: "array";
|
|
47
|
+
};
|
|
48
|
+
readonly negative_evidence_records: {
|
|
49
|
+
readonly type: "array";
|
|
50
|
+
};
|
|
36
51
|
readonly slices: {
|
|
37
52
|
readonly type: "array";
|
|
38
53
|
};
|
|
@@ -55,7 +70,8 @@ export declare const SUPERPOWERS_TASK_STATE_JSON_SCHEMA: {
|
|
|
55
70
|
};
|
|
56
71
|
export type SuperpowersProofLayerStatus = "missing" | "satisfied" | "invalidated" | "blocked";
|
|
57
72
|
export type SuperpowersPlanItemStatus = "not_started" | "in_progress" | "complete" | "partial" | "blocked" | "invalidated" | "out_of_scope_NA";
|
|
58
|
-
export type SuperpowersAcceptanceStatus = "not_run" | "complete" | "partial" | "blocked" | "invalidated" | "out_of_scope_NA";
|
|
73
|
+
export type SuperpowersAcceptanceStatus = "not_run" | "complete" | "partial" | "blocked" | "invalidated" | "under_specified" | "out_of_scope_NA";
|
|
74
|
+
export type SuperpowersAttemptMode = "product_task" | "harness_task";
|
|
59
75
|
export type SuperpowersProductDeliveryScope = "system_capability_build" | "representative_sample_validation" | "full_population_operation" | "mixed_scope_requires_boundary";
|
|
60
76
|
export type SuperpowersPlanDeliveryScope = "system_capability_build" | "representative_sample_validation" | "full_population_operation" | "out_of_scope_backlog";
|
|
61
77
|
export type SuperpowersAcceptanceScope = "system_capability_build" | "representative_sample_validation" | "full_population_operation" | "full_population_not_required";
|
|
@@ -86,6 +102,11 @@ export interface SuperpowersTaskState {
|
|
|
86
102
|
proof_layers: Record<string, SuperpowersProofLayer>;
|
|
87
103
|
edges: SuperpowersGraphEdge[];
|
|
88
104
|
};
|
|
105
|
+
attempts: ExecutionAttempt[];
|
|
106
|
+
current_attempt_id: string;
|
|
107
|
+
required_command_specs: RequiredCommandSpec[];
|
|
108
|
+
command_runs: CommandRunRecord[];
|
|
109
|
+
negative_evidence_records: NegativeEvidenceRecord[];
|
|
89
110
|
slices: SuperpowersSliceRecord[];
|
|
90
111
|
evidence: SuperpowersEvidenceRecord[];
|
|
91
112
|
gates: Record<string, unknown>;
|
|
@@ -99,6 +120,60 @@ export interface SuperpowersTaskState {
|
|
|
99
120
|
next_required_actions?: string[];
|
|
100
121
|
};
|
|
101
122
|
}
|
|
123
|
+
export interface ExecutionAttempt {
|
|
124
|
+
task_attempt_id: string;
|
|
125
|
+
source_bundle_hash: string;
|
|
126
|
+
product_source_hash: string;
|
|
127
|
+
technical_plan_hash: string;
|
|
128
|
+
acceptance_checklist_hash: string;
|
|
129
|
+
git_head: string;
|
|
130
|
+
git_status_short: string;
|
|
131
|
+
tracked_diff_hash: string;
|
|
132
|
+
relevant_untracked_hash: string;
|
|
133
|
+
untracked_relevant_hash?: string;
|
|
134
|
+
worktree_fingerprint: string;
|
|
135
|
+
started_at: string;
|
|
136
|
+
ended_at: string | null;
|
|
137
|
+
finalized_at?: string | null;
|
|
138
|
+
required_command_specs_hash: string;
|
|
139
|
+
mode: SuperpowersAttemptMode;
|
|
140
|
+
changed_files?: string[];
|
|
141
|
+
}
|
|
142
|
+
export interface RequiredCommandSpec {
|
|
143
|
+
command_spec_id: string;
|
|
144
|
+
ac_id: string;
|
|
145
|
+
proof_layers: string[];
|
|
146
|
+
command: string;
|
|
147
|
+
assertion_artifacts: string[];
|
|
148
|
+
required_test_ids: string[];
|
|
149
|
+
machine_blocking: boolean;
|
|
150
|
+
assertion_result_required: boolean;
|
|
151
|
+
positive_assertions: string[];
|
|
152
|
+
negative_assertions: string[];
|
|
153
|
+
invalid_completion_signals: string[];
|
|
154
|
+
final_evidence_expected: string[];
|
|
155
|
+
}
|
|
156
|
+
export interface CommandRunRecord {
|
|
157
|
+
command_run_id: string;
|
|
158
|
+
task_attempt_id: string;
|
|
159
|
+
command_spec_id: string;
|
|
160
|
+
ac_id: string;
|
|
161
|
+
proof_layer: string;
|
|
162
|
+
command_line: string;
|
|
163
|
+
exit_code: number;
|
|
164
|
+
started_at: string;
|
|
165
|
+
ended_at: string;
|
|
166
|
+
artifact_paths: string[];
|
|
167
|
+
}
|
|
168
|
+
export interface NegativeEvidenceRecord {
|
|
169
|
+
negative_evidence_id: string;
|
|
170
|
+
task_attempt_id: string;
|
|
171
|
+
target_ac_ids: string[];
|
|
172
|
+
target_proof_layers: string[];
|
|
173
|
+
artifact_path: string;
|
|
174
|
+
artifact_sha256?: string;
|
|
175
|
+
status: "passed" | "failed" | "blocked" | "stale";
|
|
176
|
+
}
|
|
102
177
|
export interface SuperpowersSourceRecord {
|
|
103
178
|
path: string;
|
|
104
179
|
sha256: string;
|
|
@@ -258,7 +333,24 @@ export interface SuperpowersSliceRecord {
|
|
|
258
333
|
};
|
|
259
334
|
}
|
|
260
335
|
export interface SuperpowersEvidenceRecord {
|
|
336
|
+
schema_version?: "evidence-record-v1" | "evidence-record-v2" | string;
|
|
261
337
|
evidence_id: string;
|
|
338
|
+
task_attempt_id?: string;
|
|
339
|
+
source_bundle_hash?: string;
|
|
340
|
+
product_source_hash?: string;
|
|
341
|
+
technical_plan_hash?: string;
|
|
342
|
+
acceptance_checklist_hash?: string;
|
|
343
|
+
git_head?: string;
|
|
344
|
+
worktree_fingerprint?: string;
|
|
345
|
+
command_spec_id?: string;
|
|
346
|
+
command_run_id?: string;
|
|
347
|
+
command_line?: string;
|
|
348
|
+
artifact_path?: string;
|
|
349
|
+
artifact_sha256?: string;
|
|
350
|
+
artifact_mtime?: string;
|
|
351
|
+
target_ac_ids?: string[];
|
|
352
|
+
target_pi_ids?: string[];
|
|
353
|
+
target_proof_layers?: string[];
|
|
262
354
|
slice_id: string;
|
|
263
355
|
type: string;
|
|
264
356
|
freshness: {
|
|
@@ -285,17 +377,21 @@ export interface SuperpowersEvidenceRecord {
|
|
|
285
377
|
sibling_substitution_approval_source?: string;
|
|
286
378
|
}
|
|
287
379
|
export interface AssertionResult {
|
|
288
|
-
schema_version: "assertion-result-v1";
|
|
380
|
+
schema_version: "assertion-result-v1" | "assertion-result-v2";
|
|
289
381
|
status: "passed" | "failed" | "blocked" | "stale";
|
|
290
382
|
runner: string;
|
|
291
383
|
exit_code: number;
|
|
292
384
|
target_ac_ids: string[];
|
|
385
|
+
target_pi_ids?: string[];
|
|
293
386
|
target_proof_layers: string[];
|
|
294
387
|
owner_surface?: string;
|
|
295
388
|
route?: string;
|
|
296
389
|
action?: string;
|
|
297
390
|
positive_assertions: AssertionCheck[];
|
|
298
391
|
negative_assertions: AssertionCheck[];
|
|
392
|
+
invalid_completion_signals?: AssertionCheck[];
|
|
393
|
+
negative_evidence_scan?: NegativeEvidenceScan;
|
|
394
|
+
required_test_ids?: string[];
|
|
299
395
|
artifacts?: string[];
|
|
300
396
|
}
|
|
301
397
|
export interface AssertionCheck {
|
|
@@ -4,7 +4,24 @@ export const SUPERPOWERS_TASK_STATE_JSON_SCHEMA = {
|
|
|
4
4
|
$id: "https://project-tiny-context-harness.local/superpowers-task-state.schema.json",
|
|
5
5
|
title: "Superpowers Long-Task State",
|
|
6
6
|
type: "object",
|
|
7
|
-
required: [
|
|
7
|
+
required: [
|
|
8
|
+
"meta",
|
|
9
|
+
"sources",
|
|
10
|
+
"context",
|
|
11
|
+
"delivery",
|
|
12
|
+
"graph",
|
|
13
|
+
"attempts",
|
|
14
|
+
"current_attempt_id",
|
|
15
|
+
"required_command_specs",
|
|
16
|
+
"command_runs",
|
|
17
|
+
"negative_evidence_records",
|
|
18
|
+
"slices",
|
|
19
|
+
"evidence",
|
|
20
|
+
"gates",
|
|
21
|
+
"progress",
|
|
22
|
+
"blockers",
|
|
23
|
+
"final"
|
|
24
|
+
],
|
|
8
25
|
properties: {
|
|
9
26
|
meta: {
|
|
10
27
|
type: "object",
|
|
@@ -19,6 +36,11 @@ export const SUPERPOWERS_TASK_STATE_JSON_SCHEMA = {
|
|
|
19
36
|
context: { type: "object" },
|
|
20
37
|
delivery: { type: "object" },
|
|
21
38
|
graph: { type: "object" },
|
|
39
|
+
attempts: { type: "array" },
|
|
40
|
+
current_attempt_id: { type: "string" },
|
|
41
|
+
required_command_specs: { type: "array" },
|
|
42
|
+
command_runs: { type: "array" },
|
|
43
|
+
negative_evidence_records: { type: "array" },
|
|
22
44
|
slices: { type: "array" },
|
|
23
45
|
evidence: { type: "array" },
|
|
24
46
|
gates: { type: "object" },
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { isRecord } from "./superpowers-task-state-schema.js";
|
|
2
|
+
export function validateShape(state, errors) {
|
|
3
|
+
if (state.meta?.schema_version !== "superpowers-task-state-v1") {
|
|
4
|
+
errors.push("task-state.json schema_version must be superpowers-task-state-v1");
|
|
5
|
+
}
|
|
6
|
+
for (const key of [
|
|
7
|
+
"meta",
|
|
8
|
+
"sources",
|
|
9
|
+
"context",
|
|
10
|
+
"delivery",
|
|
11
|
+
"graph",
|
|
12
|
+
"attempts",
|
|
13
|
+
"current_attempt_id",
|
|
14
|
+
"required_command_specs",
|
|
15
|
+
"command_runs",
|
|
16
|
+
"negative_evidence_records",
|
|
17
|
+
"slices",
|
|
18
|
+
"evidence",
|
|
19
|
+
"gates",
|
|
20
|
+
"progress",
|
|
21
|
+
"blockers",
|
|
22
|
+
"final"
|
|
23
|
+
]) {
|
|
24
|
+
if (!(key in state)) {
|
|
25
|
+
errors.push(`task-state.json is missing section: ${key}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function hasUsableShape(state) {
|
|
30
|
+
const candidate = state;
|
|
31
|
+
return (isRecord(candidate.meta) &&
|
|
32
|
+
isRecord(candidate.sources) &&
|
|
33
|
+
isRecord(candidate.context) &&
|
|
34
|
+
isRecord(candidate.delivery) &&
|
|
35
|
+
isRecord(candidate.graph) &&
|
|
36
|
+
isRecord(candidate.graph.plan_items) &&
|
|
37
|
+
isRecord(candidate.graph.acceptance_criteria) &&
|
|
38
|
+
isRecord(candidate.graph.proof_layers) &&
|
|
39
|
+
Array.isArray(candidate.attempts) &&
|
|
40
|
+
typeof candidate.current_attempt_id === "string" &&
|
|
41
|
+
Array.isArray(candidate.required_command_specs) &&
|
|
42
|
+
Array.isArray(candidate.command_runs) &&
|
|
43
|
+
Array.isArray(candidate.negative_evidence_records) &&
|
|
44
|
+
Array.isArray(candidate.slices) &&
|
|
45
|
+
Array.isArray(candidate.evidence) &&
|
|
46
|
+
isRecord(candidate.gates) &&
|
|
47
|
+
isRecord(candidate.progress) &&
|
|
48
|
+
Array.isArray(candidate.blockers) &&
|
|
49
|
+
isRecord(candidate.final));
|
|
50
|
+
}
|
|
@@ -3,7 +3,8 @@ import path from "node:path";
|
|
|
3
3
|
import { ensureDir, pathExists, readText, writeTextIfChanged } from "./fs.js";
|
|
4
4
|
import { normalizeProofLayerId } from "./superpowers-task-fields.js";
|
|
5
5
|
import { appendSuperpowersEvent } from "./superpowers-task-events.js";
|
|
6
|
-
import { evaluateProofLayerAssertions, isMachineVerifiableLayer
|
|
6
|
+
import { evaluateProofLayerAssertions, isMachineVerifiableLayer } from "./superpowers-task-assertions.js";
|
|
7
|
+
import { readEvidenceRecords } from "./superpowers-task-evidence-records.js";
|
|
7
8
|
import { SUPERPOWERS_TASK_STATE_JSON_SCHEMA, SUPERPOWERS_TASK_STATE_SCHEMA_VERSION, asStringArray, isRecord } from "./superpowers-task-state-schema.js";
|
|
8
9
|
const SOURCE_FILES = {
|
|
9
10
|
product_architecture_source: {
|
|
@@ -73,6 +74,11 @@ export async function initializeSuperpowersTask(workdir, options = {}) {
|
|
|
73
74
|
proof_layers: {},
|
|
74
75
|
edges: []
|
|
75
76
|
},
|
|
77
|
+
attempts: [],
|
|
78
|
+
current_attempt_id: "",
|
|
79
|
+
required_command_specs: [],
|
|
80
|
+
command_runs: [],
|
|
81
|
+
negative_evidence_records: [],
|
|
76
82
|
slices: [],
|
|
77
83
|
evidence: [],
|
|
78
84
|
gates: {},
|
|
@@ -214,41 +220,6 @@ export function sha256(value) {
|
|
|
214
220
|
export function stableJson(value) {
|
|
215
221
|
return JSON.stringify(sortJson(value), null, 2);
|
|
216
222
|
}
|
|
217
|
-
function readEvidenceRecords(value) {
|
|
218
|
-
if (!Array.isArray(value)) {
|
|
219
|
-
return [];
|
|
220
|
-
}
|
|
221
|
-
return value.filter(isRecord).map((item) => ({
|
|
222
|
-
evidence_id: String(item.evidence_id ?? item.evidenceId ?? ""),
|
|
223
|
-
slice_id: String(item.slice_id ?? item.sliceId ?? ""),
|
|
224
|
-
type: String(item.type ?? ""),
|
|
225
|
-
freshness: isRecord(item.freshness)
|
|
226
|
-
? {
|
|
227
|
-
created_at: String(item.freshness.created_at ?? ""),
|
|
228
|
-
valid_for: String(item.freshness.valid_for ?? ""),
|
|
229
|
-
stale_after: item.freshness.stale_after === null ? null : item.freshness.stale_after === undefined ? null : String(item.freshness.stale_after)
|
|
230
|
-
}
|
|
231
|
-
: { created_at: "", valid_for: "", stale_after: null },
|
|
232
|
-
command: item.command === undefined ? undefined : String(item.command),
|
|
233
|
-
command_exit_code: item.command_exit_code === undefined ? undefined : Number(item.command_exit_code),
|
|
234
|
-
artifact_paths: asStringArray(item.artifact_paths),
|
|
235
|
-
proves: asStringArray(item.proves).map(normalizeProofLayerId),
|
|
236
|
-
does_not_prove: asStringArray(item.does_not_prove).map((value) => (value.includes(".") ? normalizeProofLayerId(value) : value)),
|
|
237
|
-
redaction: isRecord(item.redaction)
|
|
238
|
-
? { checked: item.redaction.checked === true, contains_secret: item.redaction.contains_secret === true }
|
|
239
|
-
: { checked: false, contains_secret: false },
|
|
240
|
-
reviewability: isRecord(item.reviewability)
|
|
241
|
-
? {
|
|
242
|
-
external_reviewer_can_reproduce: item.reviewability.external_reviewer_can_reproduce === true,
|
|
243
|
-
reproduction_steps: String(item.reviewability.reproduction_steps ?? "")
|
|
244
|
-
}
|
|
245
|
-
: { external_reviewer_can_reproduce: false, reproduction_steps: "" },
|
|
246
|
-
assertion_result: normalizeAssertionResult(item.assertion_result),
|
|
247
|
-
negative_evidence_scan: normalizeNegativeEvidenceScan(item.negative_evidence_scan),
|
|
248
|
-
sibling_substitution_used: item.sibling_substitution_used === true,
|
|
249
|
-
sibling_substitution_approval_source: item.sibling_substitution_approval_source === undefined ? undefined : String(item.sibling_substitution_approval_source)
|
|
250
|
-
}));
|
|
251
|
-
}
|
|
252
223
|
function sortJson(value) {
|
|
253
224
|
if (Array.isArray(value)) {
|
|
254
225
|
return value.map(sortJson);
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
const CANONICAL_PLAN_STATUSES = new Set(["not_started", "in_progress", "partial", "blocked", "invalidated", "complete", "out_of_scope_NA"]);
|
|
2
|
-
const CANONICAL_AC_STATUSES = new Set([
|
|
2
|
+
const CANONICAL_AC_STATUSES = new Set([
|
|
3
|
+
"not_started",
|
|
4
|
+
"not_run",
|
|
5
|
+
"in_progress",
|
|
6
|
+
"partial",
|
|
7
|
+
"blocked",
|
|
8
|
+
"invalidated",
|
|
9
|
+
"under_specified",
|
|
10
|
+
"complete",
|
|
11
|
+
"out_of_scope_NA"
|
|
12
|
+
]);
|
|
3
13
|
export function validateCanonicalStatuses(state, errors) {
|
|
4
14
|
for (const [planId, item] of Object.entries(state.graph?.plan_items ?? {})) {
|
|
5
15
|
if (!CANONICAL_PLAN_STATUSES.has(item.status)) {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SuperpowersAcceptanceCriterion, SuperpowersTaskState } from "./superpowers-task-state-schema.js";
|
|
2
|
+
export interface UnderSpecifiedAc {
|
|
3
|
+
ac_id: string;
|
|
4
|
+
reasons: string[];
|
|
5
|
+
}
|
|
6
|
+
export declare function findUnderSpecifiedAcs(state: SuperpowersTaskState): UnderSpecifiedAc[];
|
|
7
|
+
export declare function underSpecifiedReasons(acId: string, ac: SuperpowersAcceptanceCriterion): string[];
|