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.
Files changed (46) hide show
  1. package/README.md +9 -3
  2. package/assets/README.md +9 -3
  3. package/assets/README.zh-CN.md +7 -1
  4. package/assets/protected-harness-baseline.json +18 -0
  5. package/assets/skills/composite-long-task-workflow/SKILL.md +19 -1
  6. package/assets/skills/composite-long-task-workflow/assets/execution-binding.template.md +10 -0
  7. package/assets/skills/composite-long-task-workflow/assets/goal-objective.template.md +6 -15
  8. package/assets/skills/composite-long-task-workflow/references/composite-long-task-workflow-protocol.md +26 -14
  9. package/dist/commands/composite-long-task.js +45 -2
  10. package/dist/lib/superpowers-task-ac010.d.ts +6 -0
  11. package/dist/lib/superpowers-task-ac010.js +26 -0
  12. package/dist/lib/superpowers-task-assertion-normalizers.js +4 -0
  13. package/dist/lib/superpowers-task-assertions.js +18 -4
  14. package/dist/lib/superpowers-task-attempt.d.ts +4 -0
  15. package/dist/lib/superpowers-task-attempt.js +102 -0
  16. package/dist/lib/superpowers-task-command-specs.d.ts +3 -0
  17. package/dist/lib/superpowers-task-command-specs.js +52 -0
  18. package/dist/lib/superpowers-task-compile.d.ts +4 -1
  19. package/dist/lib/superpowers-task-compile.js +7 -1
  20. package/dist/lib/superpowers-task-contradictions.d.ts +6 -0
  21. package/dist/lib/superpowers-task-contradictions.js +126 -0
  22. package/dist/lib/superpowers-task-current-evidence.d.ts +3 -0
  23. package/dist/lib/superpowers-task-current-evidence.js +154 -0
  24. package/dist/lib/superpowers-task-derive.js +14 -0
  25. package/dist/lib/superpowers-task-evidence-kernel.d.ts +12 -0
  26. package/dist/lib/superpowers-task-evidence-kernel.js +351 -0
  27. package/dist/lib/superpowers-task-evidence-records.d.ts +2 -0
  28. package/dist/lib/superpowers-task-evidence-records.js +55 -0
  29. package/dist/lib/superpowers-task-evidence.d.ts +10 -0
  30. package/dist/lib/superpowers-task-evidence.js +141 -0
  31. package/dist/lib/superpowers-task-gates.js +36 -24
  32. package/dist/lib/superpowers-task-harness-drift.d.ts +11 -0
  33. package/dist/lib/superpowers-task-harness-drift.js +86 -0
  34. package/dist/lib/superpowers-task-protected-baseline.d.ts +10 -0
  35. package/dist/lib/superpowers-task-protected-baseline.js +47 -0
  36. package/dist/lib/superpowers-task-state-schema.d.ts +99 -3
  37. package/dist/lib/superpowers-task-state-schema.js +23 -1
  38. package/dist/lib/superpowers-task-state-shape.d.ts +3 -0
  39. package/dist/lib/superpowers-task-state-shape.js +50 -0
  40. package/dist/lib/superpowers-task-state.js +7 -36
  41. package/dist/lib/superpowers-task-status.js +11 -1
  42. package/dist/lib/superpowers-task-under-specified.d.ts +7 -0
  43. package/dist/lib/superpowers-task-under-specified.js +61 -0
  44. package/dist/lib/superpowers-task-validator.js +5 -27
  45. package/package.json +1 -1
  46. 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, recomputeStatuses, saveSuperpowersState } from "./superpowers-task-state.js";
5
- import { completionConditionErrors, validateSuperpowersState } from "./superpowers-task-validator.js";
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
- recomputeStatuses(state);
28
- state.final.audit_task_complete = true;
29
- state.meta.audit_task_complete = true;
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 completionErrors = completionConditionErrors(latest);
37
- const errors = [...new Set([...report.errors, ...acceptanceReport.errors, ...completionErrors])];
38
- const complete = errors.length === 0;
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
- ? ["all_required_acs_complete", "validator_passed", "assertion_evidence_passed", "negative_evidence_scan_passed", "auditor_no_blocker"]
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
- "derive",
56
- "verification_before_completion_expected",
57
- "validate_state",
58
- "validate_derived",
59
- "validate_plan_acceptance",
60
- "auditor_blocker_scan",
61
- "stale_overclaim_scope_scan",
62
- "ac_evidence_assertion_gate",
63
- "negative_evidence_scan_gate",
64
- "compute_completion"
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 (/source hash mismatch|source_changed_requires_recompile|scope_conflict_requires_decision|auditor blocker|Context Delta coverage is unresolved/i.test(text)) {
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: ["meta", "sources", "context", "delivery", "graph", "slices", "evidence", "gates", "progress", "blockers", "final"],
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,3 @@
1
+ import { type SuperpowersTaskState } from "./superpowers-task-state-schema.js";
2
+ export declare function validateShape(state: SuperpowersTaskState, errors: string[]): void;
3
+ export declare function hasUsableShape(state: SuperpowersTaskState): boolean;
@@ -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, normalizeAssertionResult, normalizeNegativeEvidenceScan } from "./superpowers-task-assertions.js";
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(["not_started", "not_run", "in_progress", "partial", "blocked", "invalidated", "complete", "out_of_scope_NA"]);
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[];