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.
Files changed (34) hide show
  1. package/README.md +10 -8
  2. package/assets/README.md +9 -7
  3. package/assets/README.zh-CN.md +10 -0
  4. package/assets/github/harness.yml +1 -1
  5. package/assets/skills/composite-long-task-workflow/SKILL.md +10 -2
  6. package/assets/skills/composite-long-task-workflow/assets/execution-binding.template.md +2 -0
  7. package/assets/skills/composite-long-task-workflow/assets/goal-objective.template.md +7 -3
  8. package/assets/skills/composite-long-task-workflow/references/composite-long-task-workflow-protocol.md +27 -11
  9. package/dist/lib/composite-long-task-renderer.js +18 -62
  10. package/dist/lib/superpowers-task-assertion-normalizers.d.ts +3 -0
  11. package/dist/lib/superpowers-task-assertion-normalizers.js +70 -0
  12. package/dist/lib/superpowers-task-assertions.d.ts +20 -0
  13. package/dist/lib/superpowers-task-assertions.js +243 -0
  14. package/dist/lib/superpowers-task-compile-diagnostics.d.ts +5 -0
  15. package/dist/lib/superpowers-task-compile-diagnostics.js +20 -0
  16. package/dist/lib/superpowers-task-compile-guards.d.ts +2 -0
  17. package/dist/lib/superpowers-task-compile-guards.js +66 -0
  18. package/dist/lib/superpowers-task-compile.js +25 -5
  19. package/dist/lib/superpowers-task-conformance.d.ts +2 -0
  20. package/dist/lib/superpowers-task-conformance.js +24 -0
  21. package/dist/lib/superpowers-task-delivery.js +30 -18
  22. package/dist/lib/superpowers-task-derive.d.ts +1 -0
  23. package/dist/lib/superpowers-task-derive.js +121 -4
  24. package/dist/lib/superpowers-task-fields.d.ts +22 -0
  25. package/dist/lib/superpowers-task-fields.js +276 -0
  26. package/dist/lib/superpowers-task-gates.js +57 -3
  27. package/dist/lib/superpowers-task-source-compile.js +128 -92
  28. package/dist/lib/superpowers-task-source-parser.js +12 -18
  29. package/dist/lib/superpowers-task-state-schema.d.ts +120 -1
  30. package/dist/lib/superpowers-task-state.js +33 -8
  31. package/dist/lib/superpowers-task-status.d.ts +2 -0
  32. package/dist/lib/superpowers-task-status.js +14 -0
  33. package/dist/lib/superpowers-task-validator.js +18 -9
  34. 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: String(progressValue.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.graph.proof_layers[layerId]?.status === "satisfied")) {
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,2 @@
1
+ import type { SuperpowersTaskState } from "./superpowers-task-state-schema.js";
2
+ export declare function validateCanonicalStatuses(state: SuperpowersTaskState, errors: string[]): void;
@@ -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
- if (proofLayer.endsWith(".runtime") && /\b(mock|unit|viewmodel)\b/i.test(evidence.type)) {
164
- errors.push(`${label} runtime proof cannot be mock/unit/viewmodel only`);
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 (proofLayer.endsWith(".ui_browser") && !/\b(browser|ui_browser|screenshot)\b/i.test(evidence.type)) {
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.80",
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": ">=20"
59
- },
60
- "dependencies": {
61
- "@google/design.md": "^0.2.0",
62
- "impeccable": "^2.3.2",
63
- "yaml": "^2.9.0"
64
- },
65
- "devDependencies": {
66
- "@types/node": "^24.0.0",
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
+ }