project-tiny-context-harness 0.2.82 → 0.2.84
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -4
- package/assets/README.md +13 -7
- package/assets/README.zh-CN.md +8 -2
- package/assets/protected-harness-baseline.json +20 -0
- package/assets/skills/composite-long-task-workflow/SKILL.md +27 -3
- package/assets/skills/composite-long-task-workflow/assets/execution-binding.template.md +24 -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 +51 -25
- package/dist/commands/composite-long-task.js +60 -5
- 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-run-correlation.d.ts +8 -0
- package/dist/lib/superpowers-task-command-run-correlation.js +103 -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-completion-output.d.ts +52 -0
- package/dist/lib/superpowers-task-completion-output.js +228 -0
- 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 +176 -0
- package/dist/lib/superpowers-task-derive.js +69 -8
- package/dist/lib/superpowers-task-evidence-kernel.d.ts +19 -0
- package/dist/lib/superpowers-task-evidence-kernel.js +347 -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 +147 -0
- package/dist/lib/superpowers-task-final-card.d.ts +3 -0
- package/dist/lib/superpowers-task-final-card.js +24 -0
- package/dist/lib/superpowers-task-gates.d.ts +2 -2
- package/dist/lib/superpowers-task-gates.js +87 -35
- package/dist/lib/superpowers-task-harness-drift.d.ts +11 -0
- package/dist/lib/superpowers-task-harness-drift.js +90 -0
- package/dist/lib/superpowers-task-protected-baseline.d.ts +10 -0
- package/dist/lib/superpowers-task-protected-baseline.js +66 -0
- package/dist/lib/superpowers-task-state-schema.d.ts +116 -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 +17 -37
- 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-unregistered-evidence.d.ts +11 -0
- package/dist/lib/superpowers-task-unregistered-evidence.js +72 -0
- package/dist/lib/superpowers-task-validator.js +43 -27
- package/package.json +69 -69
- package/source-mappings.yaml +3 -0
|
@@ -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: {
|
|
@@ -40,7 +41,8 @@ export async function initializeSuperpowersTask(workdir, options = {}) {
|
|
|
40
41
|
goal_type: options.goalType ?? "implementation",
|
|
41
42
|
product_goal_complete: false,
|
|
42
43
|
acceptance_target_status: "not_run",
|
|
43
|
-
audit_task_complete: false
|
|
44
|
+
audit_task_complete: false,
|
|
45
|
+
completion_output_status: "blocked"
|
|
44
46
|
},
|
|
45
47
|
sources: await sourceRecords(workdir),
|
|
46
48
|
context: {
|
|
@@ -73,6 +75,11 @@ export async function initializeSuperpowersTask(workdir, options = {}) {
|
|
|
73
75
|
proof_layers: {},
|
|
74
76
|
edges: []
|
|
75
77
|
},
|
|
78
|
+
attempts: [],
|
|
79
|
+
current_attempt_id: "",
|
|
80
|
+
required_command_specs: [],
|
|
81
|
+
command_runs: [],
|
|
82
|
+
negative_evidence_records: [],
|
|
76
83
|
slices: [],
|
|
77
84
|
evidence: [],
|
|
78
85
|
gates: {},
|
|
@@ -82,6 +89,14 @@ export async function initializeSuperpowersTask(workdir, options = {}) {
|
|
|
82
89
|
product_goal_complete: false,
|
|
83
90
|
acceptance_target_status: "not_run",
|
|
84
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,
|
|
85
100
|
completion_basis: [],
|
|
86
101
|
next_required_actions: []
|
|
87
102
|
}
|
|
@@ -214,41 +229,6 @@ export function sha256(value) {
|
|
|
214
229
|
export function stableJson(value) {
|
|
215
230
|
return JSON.stringify(sortJson(value), null, 2);
|
|
216
231
|
}
|
|
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
232
|
function sortJson(value) {
|
|
253
233
|
if (Array.isArray(value)) {
|
|
254
234
|
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[];
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { isMachineVerifiableLayer } from "./superpowers-task-assertions.js";
|
|
2
|
+
const GENERATED_ONLY_EVIDENCE = /\b(matrix|verdict|validator|final[-_ ]?card|final[-_ ]?summary|evidence[-_ ]?index|derived\/)/i;
|
|
3
|
+
const MACHINE_UI_PROOF = /\b(browser|ui_browser|e2e|smoke|trace|playwright|route|owner surface)\b/i;
|
|
4
|
+
const MANUAL_ONLY_TEST = /\b(查看页面|人工确认|manual confirm|manually inspect|visual check only)\b/i;
|
|
5
|
+
export function findUnderSpecifiedAcs(state) {
|
|
6
|
+
return Object.entries(state.graph?.acceptance_criteria ?? {})
|
|
7
|
+
.map(([acId, ac]) => ({ ac_id: acId, reasons: underSpecifiedReasons(acId, ac) }))
|
|
8
|
+
.filter((item) => item.reasons.length > 0);
|
|
9
|
+
}
|
|
10
|
+
export function underSpecifiedReasons(acId, ac) {
|
|
11
|
+
if (ac.machine_blocking !== true && ac.assertion_result_required !== true) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
const reasons = [];
|
|
15
|
+
if (!trimmed(ac.assertion_command)) {
|
|
16
|
+
reasons.push(`${acId} under_specified: assertion_command is required for machine_blocking AC`);
|
|
17
|
+
}
|
|
18
|
+
if ((ac.assertion_artifacts ?? []).length === 0) {
|
|
19
|
+
reasons.push(`${acId} under_specified: assertion_artifacts are required for machine_blocking AC`);
|
|
20
|
+
}
|
|
21
|
+
if ((ac.positive_assertions ?? []).length === 0) {
|
|
22
|
+
reasons.push(`${acId} under_specified: positive_assertions are required for machine_blocking AC`);
|
|
23
|
+
}
|
|
24
|
+
if ((ac.negative_assertions ?? []).length === 0) {
|
|
25
|
+
reasons.push(`${acId} under_specified: negative_assertions are required for machine_blocking AC`);
|
|
26
|
+
}
|
|
27
|
+
if ((ac.invalid_completion_signals ?? []).length === 0) {
|
|
28
|
+
reasons.push(`${acId} under_specified: invalid_completion_signals are required for machine_blocking AC`);
|
|
29
|
+
}
|
|
30
|
+
if (ac.assertion_result_required !== true) {
|
|
31
|
+
reasons.push(`${acId} under_specified: machine_blocking AC must require assertion_result`);
|
|
32
|
+
}
|
|
33
|
+
const requiredLayers = ac.required_proof_layers ?? [];
|
|
34
|
+
if (requiredLayers.some((layer) => isMachineVerifiableLayer(`${acId}.${layer}`)) && !trimmed(ac.assertion_command)) {
|
|
35
|
+
reasons.push(`${acId} under_specified: machine proof layers cannot produce assertion_result without assertion_command`);
|
|
36
|
+
}
|
|
37
|
+
if (requiredLayers.includes("ui_browser") && !MACHINE_UI_PROOF.test(acEvidenceText(ac))) {
|
|
38
|
+
reasons.push(`${acId} under_specified: ui_browser AC requires browser/e2e/smoke/trace-backed proof`);
|
|
39
|
+
}
|
|
40
|
+
const finalEvidence = ac.final_evidence_expected ?? [];
|
|
41
|
+
if (finalEvidence.length > 0 && finalEvidence.every((item) => GENERATED_ONLY_EVIDENCE.test(item))) {
|
|
42
|
+
reasons.push(`${acId} under_specified: final_evidence_expected only names generated summary artifacts`);
|
|
43
|
+
}
|
|
44
|
+
const testCases = ac.test_cases ?? [];
|
|
45
|
+
if (testCases.length > 0 && testCases.every((item) => MANUAL_ONLY_TEST.test(item))) {
|
|
46
|
+
reasons.push(`${acId} under_specified: test_cases are manual-only`);
|
|
47
|
+
}
|
|
48
|
+
return reasons;
|
|
49
|
+
}
|
|
50
|
+
function acEvidenceText(ac) {
|
|
51
|
+
return [
|
|
52
|
+
ac.assertion_command ?? "",
|
|
53
|
+
...(ac.assertion_artifacts ?? []),
|
|
54
|
+
...(ac.verification_method ?? []),
|
|
55
|
+
...(ac.test_cases ?? []),
|
|
56
|
+
...(ac.final_evidence_expected ?? [])
|
|
57
|
+
].join("\n");
|
|
58
|
+
}
|
|
59
|
+
function trimmed(value) {
|
|
60
|
+
return Boolean(value && value.trim());
|
|
61
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -4,11 +4,15 @@ import { findSensitiveEvidence } from "./plan-acceptance-evidence.js";
|
|
|
4
4
|
import { primitiveText, repoRelative, resolveInputDir } from "./plan-validator-common.js";
|
|
5
5
|
import { derivedMatchesState } from "./superpowers-task-derive.js";
|
|
6
6
|
import { validatePlanCompletionConformance } from "./superpowers-task-conformance.js";
|
|
7
|
+
import { scanSuperpowersContradictions } from "./superpowers-task-contradictions.js";
|
|
8
|
+
import { evaluateTrustedEvidenceKernel } from "./superpowers-task-evidence-kernel.js";
|
|
7
9
|
import { fullPopulationRequired, validateDeliveryContract, validateScopeConflicts } from "./superpowers-task-delivery.js";
|
|
10
|
+
import { hasUsableShape, validateShape } from "./superpowers-task-state-shape.js";
|
|
8
11
|
import { loadSuperpowersState, sha256 } from "./superpowers-task-state.js";
|
|
9
12
|
import { validateCanonicalStatuses } from "./superpowers-task-status.js";
|
|
10
13
|
import { isRecord } from "./superpowers-task-state-schema.js";
|
|
11
14
|
import { evaluateProofLayerAssertions, isUiBrowserLayer, proofLayerName } from "./superpowers-task-assertions.js";
|
|
15
|
+
import { completionOutputContractFromState, completionPhraseFindingMessages, scanGeneratedCompletionOutputSurfaces } from "./superpowers-task-completion-output.js";
|
|
12
16
|
export async function validateSuperpowersState(projectRoot, args = []) {
|
|
13
17
|
const info = [];
|
|
14
18
|
const warnings = [];
|
|
@@ -39,15 +43,54 @@ export async function validateSuperpowersState(projectRoot, args = []) {
|
|
|
39
43
|
validateScopeConflicts(state, errors);
|
|
40
44
|
validateEvidenceRecords(state, errors);
|
|
41
45
|
validateProofLayers(state, errors);
|
|
46
|
+
errors.push(...(await evaluateTrustedEvidenceKernel(targetDir, state)).errors);
|
|
47
|
+
errors.push(...(await scanSuperpowersContradictions(targetDir, state)).errors);
|
|
42
48
|
validateAuditor(state, errors);
|
|
43
49
|
validateFinalCompletion(state, errors);
|
|
44
50
|
errors.push(...(await derivedMatchesState(targetDir, state)));
|
|
51
|
+
await validateCompletionOutputConsistency(targetDir, state, errors);
|
|
45
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}`);
|
|
46
53
|
if (errors.length === 0) {
|
|
47
54
|
info.push("Superpowers task state validation passed");
|
|
48
55
|
}
|
|
49
56
|
return { info, warnings, hygiene, errors };
|
|
50
57
|
}
|
|
58
|
+
async function validateCompletionOutputConsistency(workdir, state, errors) {
|
|
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
|
+
errors.push(...completionPhraseFindingMessages(await scanGeneratedCompletionOutputSurfaces(workdir, contract)));
|
|
79
|
+
await validateMarkdownCompletionStatus(workdir, contract.completion_output_status, errors);
|
|
80
|
+
}
|
|
81
|
+
async function validateMarkdownCompletionStatus(workdir, expectedStatus, errors) {
|
|
82
|
+
for (const relative of ["derived/final-summary.md", "derived/final-card.md", "derived/local-audit.md"]) {
|
|
83
|
+
const file = path.join(workdir, ...relative.split("/"));
|
|
84
|
+
if (!(await pathExists(file))) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const text = await readText(file);
|
|
88
|
+
const match = /^completion_output_status:\s*(\S+)\s*$/im.exec(text);
|
|
89
|
+
if (match && match[1] !== expectedStatus) {
|
|
90
|
+
errors.push(`${relative} completion_output_status mismatch: expected ${expectedStatus}, found ${match[1]}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
51
94
|
async function validateSourceHashes(workdir, state, errors) {
|
|
52
95
|
for (const [key, source] of Object.entries(state.sources ?? {})) {
|
|
53
96
|
const file = path.join(workdir, source.path);
|
|
@@ -61,33 +104,6 @@ async function validateSourceHashes(workdir, state, errors) {
|
|
|
61
104
|
}
|
|
62
105
|
}
|
|
63
106
|
}
|
|
64
|
-
function validateShape(state, errors) {
|
|
65
|
-
if (state.meta?.schema_version !== "superpowers-task-state-v1") {
|
|
66
|
-
errors.push("task-state.json schema_version must be superpowers-task-state-v1");
|
|
67
|
-
}
|
|
68
|
-
for (const key of ["meta", "sources", "context", "delivery", "graph", "slices", "evidence", "gates", "progress", "blockers", "final"]) {
|
|
69
|
-
if (!(key in state)) {
|
|
70
|
-
errors.push(`task-state.json is missing section: ${key}`);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
function hasUsableShape(state) {
|
|
75
|
-
const candidate = state;
|
|
76
|
-
return (isRecord(candidate.meta) &&
|
|
77
|
-
isRecord(candidate.sources) &&
|
|
78
|
-
isRecord(candidate.context) &&
|
|
79
|
-
isRecord(candidate.delivery) &&
|
|
80
|
-
isRecord(candidate.graph) &&
|
|
81
|
-
isRecord(candidate.graph.plan_items) &&
|
|
82
|
-
isRecord(candidate.graph.acceptance_criteria) &&
|
|
83
|
-
isRecord(candidate.graph.proof_layers) &&
|
|
84
|
-
Array.isArray(candidate.slices) &&
|
|
85
|
-
Array.isArray(candidate.evidence) &&
|
|
86
|
-
isRecord(candidate.gates) &&
|
|
87
|
-
isRecord(candidate.progress) &&
|
|
88
|
-
Array.isArray(candidate.blockers) &&
|
|
89
|
-
isRecord(candidate.final));
|
|
90
|
-
}
|
|
91
107
|
function validateGraphReferences(state, errors) {
|
|
92
108
|
const planIds = new Set(Object.keys(state.graph?.plan_items ?? {}));
|
|
93
109
|
const acIds = new Set(Object.keys(state.graph?.acceptance_criteria ?? {}));
|
package/package.json
CHANGED
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "project-tiny-context-harness",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Minimal project memory and validation harness for AI coding agents.",
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"author": "Seven128",
|
|
7
|
-
"homepage": "https://github.com/Seven128/project-tiny-context-harness#readme",
|
|
8
|
-
"repository": {
|
|
9
|
-
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/Seven128/project-tiny-context-harness.git",
|
|
11
|
-
"directory": "packages/ty-context"
|
|
12
|
-
},
|
|
13
|
-
"bugs": {
|
|
14
|
-
"url": "https://github.com/Seven128/project-tiny-context-harness/issues"
|
|
15
|
-
},
|
|
16
|
-
"keywords": [
|
|
17
|
-
"ai-agents",
|
|
18
|
-
"coding-agent",
|
|
19
|
-
"codex",
|
|
20
|
-
"claude-code",
|
|
21
|
-
"cursor",
|
|
22
|
-
"gemini-cli",
|
|
23
|
-
"opencode",
|
|
24
|
-
"agent-context",
|
|
25
|
-
"context-engineering",
|
|
26
|
-
"context-management",
|
|
27
|
-
"agents-md",
|
|
28
|
-
"project-memory",
|
|
29
|
-
"agent-memory",
|
|
30
|
-
"ai-coding",
|
|
31
|
-
"multi-agent",
|
|
32
|
-
"llm",
|
|
33
|
-
"developer-tools",
|
|
34
|
-
"developer-productivity",
|
|
35
|
-
"cli",
|
|
36
|
-
"ty-context",
|
|
37
|
-
"workflow"
|
|
38
|
-
],
|
|
39
|
-
"type": "module",
|
|
40
|
-
"bin": {
|
|
41
|
-
"ty-context": "dist/cli.js"
|
|
42
|
-
},
|
|
43
|
-
"files": [
|
|
44
|
-
"README.md",
|
|
45
|
-
"dist",
|
|
46
|
-
"assets",
|
|
47
|
-
"migrations",
|
|
48
|
-
"source-mappings.yaml"
|
|
49
|
-
],
|
|
50
|
-
"scripts": {
|
|
51
|
-
"build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json",
|
|
52
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
53
|
-
"test:built": "node --test ../../tests/ty-context/*.test.mjs",
|
|
54
|
-
"test": "npm run build && node --test ../../tests/ty-context/*.test.mjs",
|
|
55
|
-
"prepack": "npm run build"
|
|
56
|
-
},
|
|
57
|
-
"engines": {
|
|
58
|
-
"node": ">=24"
|
|
59
|
-
},
|
|
60
|
-
"dependencies": {
|
|
61
|
-
"@google/design.md": "^0.3.0",
|
|
62
|
-
"impeccable": "^3.1.0",
|
|
63
|
-
"yaml": "^2.9.0"
|
|
64
|
-
},
|
|
65
|
-
"devDependencies": {
|
|
66
|
-
"@types/node": "^26.0.0",
|
|
67
|
-
"typescript": "^5.5.0"
|
|
68
|
-
}
|
|
69
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "project-tiny-context-harness",
|
|
3
|
+
"version": "0.2.84",
|
|
4
|
+
"description": "Minimal project memory and validation harness for AI coding agents.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Seven128",
|
|
7
|
+
"homepage": "https://github.com/Seven128/project-tiny-context-harness#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Seven128/project-tiny-context-harness.git",
|
|
11
|
+
"directory": "packages/ty-context"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/Seven128/project-tiny-context-harness/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"ai-agents",
|
|
18
|
+
"coding-agent",
|
|
19
|
+
"codex",
|
|
20
|
+
"claude-code",
|
|
21
|
+
"cursor",
|
|
22
|
+
"gemini-cli",
|
|
23
|
+
"opencode",
|
|
24
|
+
"agent-context",
|
|
25
|
+
"context-engineering",
|
|
26
|
+
"context-management",
|
|
27
|
+
"agents-md",
|
|
28
|
+
"project-memory",
|
|
29
|
+
"agent-memory",
|
|
30
|
+
"ai-coding",
|
|
31
|
+
"multi-agent",
|
|
32
|
+
"llm",
|
|
33
|
+
"developer-tools",
|
|
34
|
+
"developer-productivity",
|
|
35
|
+
"cli",
|
|
36
|
+
"ty-context",
|
|
37
|
+
"workflow"
|
|
38
|
+
],
|
|
39
|
+
"type": "module",
|
|
40
|
+
"bin": {
|
|
41
|
+
"ty-context": "dist/cli.js"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"README.md",
|
|
45
|
+
"dist",
|
|
46
|
+
"assets",
|
|
47
|
+
"migrations",
|
|
48
|
+
"source-mappings.yaml"
|
|
49
|
+
],
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json",
|
|
52
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
53
|
+
"test:built": "node --test ../../tests/ty-context/*.test.mjs",
|
|
54
|
+
"test": "npm run build && node --test ../../tests/ty-context/*.test.mjs",
|
|
55
|
+
"prepack": "npm run build"
|
|
56
|
+
},
|
|
57
|
+
"engines": {
|
|
58
|
+
"node": ">=24"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"@google/design.md": "^0.3.0",
|
|
62
|
+
"impeccable": "^3.1.0",
|
|
63
|
+
"yaml": "^2.9.0"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@types/node": "^26.0.0",
|
|
67
|
+
"typescript": "^5.5.0"
|
|
68
|
+
}
|
|
69
|
+
}
|
package/source-mappings.yaml
CHANGED
|
@@ -17,6 +17,9 @@ source_mappings:
|
|
|
17
17
|
- source: ".codex/ty-context-managed/make/ty-context.mk"
|
|
18
18
|
target: "packages/ty-context/assets/make/ty-context.mk"
|
|
19
19
|
mode: "copy-file"
|
|
20
|
+
- source: ".codex/ty-context-managed/protected-harness-baseline.json"
|
|
21
|
+
target: "packages/ty-context/assets/protected-harness-baseline.json"
|
|
22
|
+
mode: "copy-file"
|
|
20
23
|
- source: ".codex/ty-context-managed/minimal_tools"
|
|
21
24
|
target: "packages/ty-context/assets/tools"
|
|
22
25
|
mode: "copy-tree"
|