project-tiny-context-harness 0.2.80 → 0.2.82
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 -8
- package/assets/README.md +9 -7
- package/assets/README.zh-CN.md +10 -0
- package/assets/github/harness.yml +1 -1
- package/assets/skills/composite-long-task-workflow/SKILL.md +10 -2
- package/assets/skills/composite-long-task-workflow/assets/execution-binding.template.md +2 -0
- package/assets/skills/composite-long-task-workflow/assets/goal-objective.template.md +7 -3
- package/assets/skills/composite-long-task-workflow/references/composite-long-task-workflow-protocol.md +27 -11
- package/dist/lib/composite-long-task-renderer.js +18 -62
- package/dist/lib/superpowers-task-assertion-normalizers.d.ts +3 -0
- package/dist/lib/superpowers-task-assertion-normalizers.js +70 -0
- package/dist/lib/superpowers-task-assertions.d.ts +20 -0
- package/dist/lib/superpowers-task-assertions.js +243 -0
- package/dist/lib/superpowers-task-compile-diagnostics.d.ts +5 -0
- package/dist/lib/superpowers-task-compile-diagnostics.js +20 -0
- package/dist/lib/superpowers-task-compile-guards.d.ts +2 -0
- package/dist/lib/superpowers-task-compile-guards.js +66 -0
- package/dist/lib/superpowers-task-compile.js +25 -5
- package/dist/lib/superpowers-task-conformance.d.ts +2 -0
- package/dist/lib/superpowers-task-conformance.js +24 -0
- package/dist/lib/superpowers-task-delivery.js +30 -18
- package/dist/lib/superpowers-task-derive.d.ts +1 -0
- package/dist/lib/superpowers-task-derive.js +121 -4
- package/dist/lib/superpowers-task-fields.d.ts +22 -0
- package/dist/lib/superpowers-task-fields.js +276 -0
- package/dist/lib/superpowers-task-gates.js +57 -3
- package/dist/lib/superpowers-task-source-compile.js +128 -92
- package/dist/lib/superpowers-task-source-parser.js +12 -18
- package/dist/lib/superpowers-task-state-schema.d.ts +120 -1
- package/dist/lib/superpowers-task-state.js +33 -8
- package/dist/lib/superpowers-task-status.d.ts +2 -0
- package/dist/lib/superpowers-task-status.js +14 -0
- package/dist/lib/superpowers-task-validator.js +18 -9
- package/package.json +69 -69
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { ensureDir, pathExists, readText, writeTextIfChanged } from "./fs.js";
|
|
4
|
+
import { normalizeProofLayerId } from "./superpowers-task-fields.js";
|
|
4
5
|
import { appendSuperpowersEvent } from "./superpowers-task-events.js";
|
|
6
|
+
import { evaluateProofLayerAssertions, isMachineVerifiableLayer, normalizeAssertionResult, normalizeNegativeEvidenceScan } from "./superpowers-task-assertions.js";
|
|
5
7
|
import { SUPERPOWERS_TASK_STATE_JSON_SCHEMA, SUPERPOWERS_TASK_STATE_SCHEMA_VERSION, asStringArray, isRecord } from "./superpowers-task-state-schema.js";
|
|
6
8
|
const SOURCE_FILES = {
|
|
7
9
|
product_architecture_source: {
|
|
@@ -17,6 +19,7 @@ const SOURCE_FILES = {
|
|
|
17
19
|
authority: "acs_completion_semantics_proof_layers"
|
|
18
20
|
}
|
|
19
21
|
};
|
|
22
|
+
const SLICE_PROGRESS_TYPES = new Set(["functional_gap_closed", "proof_gap_closed", "blocker_resolved", "invalid_evidence_removed"]);
|
|
20
23
|
export async function initializeSuperpowersTask(workdir, options = {}) {
|
|
21
24
|
await ensureDir(path.join(workdir, "derived"));
|
|
22
25
|
await writeTextIfChanged(path.join(workdir, "task-state.schema.json"), `${stableJson(SUPERPOWERS_TASK_STATE_JSON_SCHEMA)}\n`);
|
|
@@ -52,7 +55,15 @@ export async function initializeSuperpowersTask(workdir, options = {}) {
|
|
|
52
55
|
full_population_required: null,
|
|
53
56
|
representative_samples_validate: [],
|
|
54
57
|
representative_samples_do_not_validate: [],
|
|
55
|
-
out_of_scope_backlog: []
|
|
58
|
+
out_of_scope_backlog: [],
|
|
59
|
+
scope_fit_decision: "",
|
|
60
|
+
selected_scope_fit_slice: "",
|
|
61
|
+
owner_boundary: "",
|
|
62
|
+
primary_capability_path: "",
|
|
63
|
+
non_completing_outcomes: [],
|
|
64
|
+
assertion_policy: "",
|
|
65
|
+
source_authority: "",
|
|
66
|
+
product_goal: ""
|
|
56
67
|
},
|
|
57
68
|
scope_conflicts: []
|
|
58
69
|
},
|
|
@@ -71,7 +82,8 @@ export async function initializeSuperpowersTask(workdir, options = {}) {
|
|
|
71
82
|
product_goal_complete: false,
|
|
72
83
|
acceptance_target_status: "not_run",
|
|
73
84
|
audit_task_complete: false,
|
|
74
|
-
completion_basis: []
|
|
85
|
+
completion_basis: [],
|
|
86
|
+
next_required_actions: []
|
|
75
87
|
}
|
|
76
88
|
};
|
|
77
89
|
await saveSuperpowersState(workdir, state);
|
|
@@ -96,6 +108,10 @@ export async function applySliceDelta(workdir, deltaFile) {
|
|
|
96
108
|
if (!progressValue || !String(progressValue.type ?? "").trim() || asStringArray(progressValue.closed_items).length === 0) {
|
|
97
109
|
throw new Error("slice_delta must include progress_value with type and closed_items");
|
|
98
110
|
}
|
|
111
|
+
const progressType = String(progressValue.type);
|
|
112
|
+
if (!SLICE_PROGRESS_TYPES.has(progressType)) {
|
|
113
|
+
throw new Error(`slice_delta progress_value.type must be one of ${[...SLICE_PROGRESS_TYPES].join(", ")}; got ${progressType || "(missing)"}`);
|
|
114
|
+
}
|
|
99
115
|
const evidenceRecords = readEvidenceRecords(delta.evidence_records);
|
|
100
116
|
for (const evidence of evidenceRecords) {
|
|
101
117
|
const existingIndex = state.evidence.findIndex((item) => item.evidence_id === evidence.evidence_id);
|
|
@@ -114,12 +130,12 @@ export async function applySliceDelta(workdir, deltaFile) {
|
|
|
114
130
|
missing_layer_classes: asStringArray(delta.missing_layer_classes),
|
|
115
131
|
code_changes: asStringArray(delta.code_changes),
|
|
116
132
|
evidence_records: evidenceRecords.map((item) => item.evidence_id),
|
|
117
|
-
closed_layers: asStringArray(delta.closed_layers),
|
|
118
|
-
remaining_layers: asStringArray(delta.remaining_layers),
|
|
133
|
+
closed_layers: asStringArray(delta.closed_layers).map(normalizeProofLayerId),
|
|
134
|
+
remaining_layers: asStringArray(delta.remaining_layers).map(normalizeProofLayerId),
|
|
119
135
|
blockers: Array.isArray(delta.blockers) ? delta.blockers : [],
|
|
120
136
|
cleanup_assertions: asStringArray(delta.cleanup_assertions),
|
|
121
137
|
progress_value: {
|
|
122
|
-
type:
|
|
138
|
+
type: progressType,
|
|
123
139
|
closed_items: asStringArray(progressValue.closed_items),
|
|
124
140
|
why_it_reduces_rework: String(progressValue.why_it_reduces_rework ?? "")
|
|
125
141
|
}
|
|
@@ -147,7 +163,7 @@ export function recomputeStatuses(state) {
|
|
|
147
163
|
if (layerIds.length === 0) {
|
|
148
164
|
ac.status = "not_run";
|
|
149
165
|
}
|
|
150
|
-
else if (layerIds.every((layerId) => state
|
|
166
|
+
else if (layerIds.every((layerId) => layerSatisfiedForCompletion(state, layerId))) {
|
|
151
167
|
ac.status = "complete";
|
|
152
168
|
}
|
|
153
169
|
else if (layerIds.some((layerId) => state.graph.proof_layers[layerId]?.status === "satisfied")) {
|
|
@@ -169,6 +185,12 @@ export function recomputeStatuses(state) {
|
|
|
169
185
|
}
|
|
170
186
|
}
|
|
171
187
|
}
|
|
188
|
+
function layerSatisfiedForCompletion(state, layerId) {
|
|
189
|
+
if (state.graph.proof_layers[layerId]?.status !== "satisfied") {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
return !isMachineVerifiableLayer(layerId) || evaluateProofLayerAssertions(state, layerId).assertion_status === "passed";
|
|
193
|
+
}
|
|
172
194
|
export function emptyProgressState() {
|
|
173
195
|
return {
|
|
174
196
|
system_capability_progress: { status: "not_started" },
|
|
@@ -208,9 +230,10 @@ function readEvidenceRecords(value) {
|
|
|
208
230
|
}
|
|
209
231
|
: { created_at: "", valid_for: "", stale_after: null },
|
|
210
232
|
command: item.command === undefined ? undefined : String(item.command),
|
|
233
|
+
command_exit_code: item.command_exit_code === undefined ? undefined : Number(item.command_exit_code),
|
|
211
234
|
artifact_paths: asStringArray(item.artifact_paths),
|
|
212
|
-
proves: asStringArray(item.proves),
|
|
213
|
-
does_not_prove: asStringArray(item.does_not_prove),
|
|
235
|
+
proves: asStringArray(item.proves).map(normalizeProofLayerId),
|
|
236
|
+
does_not_prove: asStringArray(item.does_not_prove).map((value) => (value.includes(".") ? normalizeProofLayerId(value) : value)),
|
|
214
237
|
redaction: isRecord(item.redaction)
|
|
215
238
|
? { checked: item.redaction.checked === true, contains_secret: item.redaction.contains_secret === true }
|
|
216
239
|
: { checked: false, contains_secret: false },
|
|
@@ -220,6 +243,8 @@ function readEvidenceRecords(value) {
|
|
|
220
243
|
reproduction_steps: String(item.reviewability.reproduction_steps ?? "")
|
|
221
244
|
}
|
|
222
245
|
: { external_reviewer_can_reproduce: false, reproduction_steps: "" },
|
|
246
|
+
assertion_result: normalizeAssertionResult(item.assertion_result),
|
|
247
|
+
negative_evidence_scan: normalizeNegativeEvidenceScan(item.negative_evidence_scan),
|
|
223
248
|
sibling_substitution_used: item.sibling_substitution_used === true,
|
|
224
249
|
sibling_substitution_approval_source: item.sibling_substitution_approval_source === undefined ? undefined : String(item.sibling_substitution_approval_source)
|
|
225
250
|
}));
|
|
@@ -0,0 +1,14 @@
|
|
|
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"]);
|
|
3
|
+
export function validateCanonicalStatuses(state, errors) {
|
|
4
|
+
for (const [planId, item] of Object.entries(state.graph?.plan_items ?? {})) {
|
|
5
|
+
if (!CANONICAL_PLAN_STATUSES.has(item.status)) {
|
|
6
|
+
errors.push(`plan item ${planId} has non-canonical status: ${item.status}`);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
for (const [acId, ac] of Object.entries(state.graph?.acceptance_criteria ?? {})) {
|
|
10
|
+
if (!CANONICAL_AC_STATUSES.has(ac.status)) {
|
|
11
|
+
errors.push(`AC ${acId} has non-canonical status: ${ac.status}`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -3,9 +3,12 @@ import { pathExists, readText } from "./fs.js";
|
|
|
3
3
|
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
|
+
import { validatePlanCompletionConformance } from "./superpowers-task-conformance.js";
|
|
6
7
|
import { fullPopulationRequired, validateDeliveryContract, validateScopeConflicts } from "./superpowers-task-delivery.js";
|
|
7
8
|
import { loadSuperpowersState, sha256 } from "./superpowers-task-state.js";
|
|
9
|
+
import { validateCanonicalStatuses } from "./superpowers-task-status.js";
|
|
8
10
|
import { isRecord } from "./superpowers-task-state-schema.js";
|
|
11
|
+
import { evaluateProofLayerAssertions, isUiBrowserLayer, proofLayerName } from "./superpowers-task-assertions.js";
|
|
9
12
|
export async function validateSuperpowersState(projectRoot, args = []) {
|
|
10
13
|
const info = [];
|
|
11
14
|
const warnings = [];
|
|
@@ -21,12 +24,7 @@ export async function validateSuperpowersState(projectRoot, args = []) {
|
|
|
21
24
|
state = await loadSuperpowersState(targetDir);
|
|
22
25
|
}
|
|
23
26
|
catch (error) {
|
|
24
|
-
return {
|
|
25
|
-
info,
|
|
26
|
-
warnings,
|
|
27
|
-
hygiene,
|
|
28
|
-
errors: [`${repoRelative(projectRoot, statePath)} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`]
|
|
29
|
-
};
|
|
27
|
+
return { info, warnings, hygiene, errors: [`${repoRelative(projectRoot, statePath)} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`] };
|
|
30
28
|
}
|
|
31
29
|
validateShape(state, errors);
|
|
32
30
|
if (!hasUsableShape(state)) {
|
|
@@ -35,7 +33,9 @@ export async function validateSuperpowersState(projectRoot, args = []) {
|
|
|
35
33
|
}
|
|
36
34
|
await validateSourceHashes(targetDir, state, errors);
|
|
37
35
|
validateDeliveryContract(state, errors);
|
|
36
|
+
validateCanonicalStatuses(state, errors);
|
|
38
37
|
validateGraphReferences(state, errors);
|
|
38
|
+
validatePlanCompletionConformance(state, errors);
|
|
39
39
|
validateScopeConflicts(state, errors);
|
|
40
40
|
validateEvidenceRecords(state, errors);
|
|
41
41
|
validateProofLayers(state, errors);
|
|
@@ -160,10 +160,11 @@ function validateEvidenceRecords(state, errors) {
|
|
|
160
160
|
errors.push(`${label} uses sibling substitution without approval`);
|
|
161
161
|
}
|
|
162
162
|
for (const proofLayer of evidence.proves ?? []) {
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
const layerName = proofLayerName(proofLayer);
|
|
164
|
+
if (layerName === "worker_runtime" && /\b(mock|unit|viewmodel)\b/i.test(evidence.type)) {
|
|
165
|
+
errors.push(`${label} worker_runtime proof cannot be mock/unit/viewmodel only`);
|
|
165
166
|
}
|
|
166
|
-
if (
|
|
167
|
+
if (layerName === "ui_browser" && !/(browser|ui_browser|screenshot|playwright)/i.test(evidence.type)) {
|
|
167
168
|
errors.push(`${label} UI proof must use browser owner surface evidence`);
|
|
168
169
|
}
|
|
169
170
|
}
|
|
@@ -185,6 +186,13 @@ function validateProofLayers(state, errors) {
|
|
|
185
186
|
errors.push(`proof layer ${layerId} references ${evidenceId} but that evidence does not prove it`);
|
|
186
187
|
}
|
|
187
188
|
}
|
|
189
|
+
if (layer.status === "satisfied") {
|
|
190
|
+
const evaluation = evaluateProofLayerAssertions(state, layerId);
|
|
191
|
+
errors.push(...evaluation.blocking_assertion_failures, ...evaluation.negative_evidence_findings);
|
|
192
|
+
if (isUiBrowserLayer(layerId) && evaluation.assertion_status !== "passed") {
|
|
193
|
+
errors.push(`proof layer ${layerId} ui_browser proof not machine-backed`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
188
196
|
}
|
|
189
197
|
}
|
|
190
198
|
function validateAuditor(state, errors) {
|
|
@@ -242,6 +250,7 @@ export function completionConditionErrors(state) {
|
|
|
242
250
|
validateDeliveryContract(state, errors);
|
|
243
251
|
validateScopeConflicts(state, errors);
|
|
244
252
|
validateGraphReferences(state, errors);
|
|
253
|
+
validatePlanCompletionConformance(state, errors);
|
|
245
254
|
validateEvidenceRecords(state, errors);
|
|
246
255
|
validateProofLayers(state, errors);
|
|
247
256
|
validateAuditor(state, errors);
|
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": ">=
|
|
59
|
-
},
|
|
60
|
-
"dependencies": {
|
|
61
|
-
"@google/design.md": "^0.
|
|
62
|
-
"impeccable": "^
|
|
63
|
-
"yaml": "^2.9.0"
|
|
64
|
-
},
|
|
65
|
-
"devDependencies": {
|
|
66
|
-
"@types/node": "^
|
|
67
|
-
"typescript": "^5.5.0"
|
|
68
|
-
}
|
|
69
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "project-tiny-context-harness",
|
|
3
|
+
"version": "0.2.82",
|
|
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
|
+
}
|