project-tiny-context-harness 0.2.79 → 0.2.80

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.
@@ -0,0 +1,6 @@
1
+ export declare function compositeLongTask(args: string[]): Promise<void>;
2
+ export declare function runCompositeLongTaskCommand(args: string[], options: {
3
+ commandName: string;
4
+ label: string;
5
+ showHelp: boolean;
6
+ }): Promise<void>;
@@ -0,0 +1,103 @@
1
+ import path from "node:path";
2
+ import { applySliceDelta, initializeSuperpowersTask } from "../lib/superpowers-task-state.js";
3
+ import { compileSuperpowersTask } from "../lib/superpowers-task-compile.js";
4
+ import { deriveSuperpowersArtifacts } from "../lib/superpowers-task-derive.js";
5
+ import { runEpochGate, runFinalGate, runSliceGate } from "../lib/superpowers-task-gates.js";
6
+ import { nextSuperpowersSlices } from "../lib/superpowers-task-next-slices.js";
7
+ import { renderCompositeLongTaskGoal } from "../lib/composite-long-task-renderer.js";
8
+ export async function compositeLongTask(args) {
9
+ await runCompositeLongTaskCommand(args, {
10
+ commandName: "ty-context composite-long-task",
11
+ label: "composite long-task",
12
+ showHelp: true
13
+ });
14
+ }
15
+ export async function runCompositeLongTaskCommand(args, options) {
16
+ const subcommand = args[0] ?? "help";
17
+ const workdirArg = args[1];
18
+ if (!workdirArg || subcommand === "help") {
19
+ help(options.commandName, options.showHelp);
20
+ return;
21
+ }
22
+ const workdir = path.resolve(process.cwd(), workdirArg);
23
+ if (subcommand === "init") {
24
+ await initializeSuperpowersTask(workdir, { planSlug: path.basename(workdir) });
25
+ console.log(`initialized ${options.label} state at ${workdirArg}/task-state.json`);
26
+ return;
27
+ }
28
+ if (subcommand === "compile") {
29
+ const state = await compileSuperpowersTask(workdir);
30
+ console.log(`compiled ${options.label} graph plan_items=${Object.keys(state.graph.plan_items).length} acs=${Object.keys(state.graph.acceptance_criteria).length}`);
31
+ return;
32
+ }
33
+ if (subcommand === "apply-slice-delta") {
34
+ const delta = args[2];
35
+ if (!delta) {
36
+ throw new Error("apply-slice-delta requires <slice-delta.json>");
37
+ }
38
+ await applySliceDelta(workdir, path.resolve(process.cwd(), delta));
39
+ const result = await deriveSuperpowersArtifacts(workdir);
40
+ console.log(`applied ${options.label} slice delta and derived files=${result.files.length}`);
41
+ return;
42
+ }
43
+ if (subcommand === "derive") {
44
+ const result = await deriveSuperpowersArtifacts(workdir);
45
+ console.log(`derived ${options.label} artifacts files=${result.files.length}`);
46
+ return;
47
+ }
48
+ if (subcommand === "slice-gate") {
49
+ const sliceId = optionValue(args, "--slice") ?? "";
50
+ const result = await runSliceGate(workdir, sliceId);
51
+ console.log(result.passed ? `slice gate passed ${sliceId}` : `slice gate blocked ${result.messages.join("; ")}`);
52
+ if (!result.passed) {
53
+ process.exitCode = 1;
54
+ }
55
+ return;
56
+ }
57
+ if (subcommand === "epoch-gate") {
58
+ const epochId = optionValue(args, "--epoch") ?? "";
59
+ const result = await runEpochGate(workdir, epochId);
60
+ console.log(result.passed ? `epoch gate passed ${epochId}` : `epoch gate blocked ${result.messages.join("; ")}`);
61
+ return;
62
+ }
63
+ if (subcommand === "final-gate") {
64
+ const result = await runFinalGate(workdir);
65
+ console.log(`final gate product_goal_complete=${result.product_goal_complete}`);
66
+ if (!result.product_goal_complete) {
67
+ process.exitCode = 1;
68
+ for (const error of result.errors) {
69
+ console.error(`error: ${error}`);
70
+ }
71
+ }
72
+ return;
73
+ }
74
+ if (subcommand === "next-slices") {
75
+ const limit = Number.parseInt(optionValue(args, "--limit") ?? "5", 10);
76
+ const slices = await nextSuperpowersSlices(workdir, Number.isFinite(limit) ? limit : 5);
77
+ console.log(`Next ${Math.min(Number.isFinite(limit) ? limit : 5, 5)} high-value clusters:`);
78
+ console.log(slices.join("\n"));
79
+ return;
80
+ }
81
+ if (subcommand === "render-goal") {
82
+ const result = await renderCompositeLongTaskGoal(workdir);
83
+ console.log(`rendered ${options.label} goal artifacts: ${path.basename(result.goalObjectivePath)} ${path.basename(result.protocolPath)} ${path.basename(result.executionBindingPath)} length=${result.goalObjectiveLength}`);
84
+ return;
85
+ }
86
+ help(options.commandName, options.showHelp);
87
+ }
88
+ function help(commandName, showRenderGoal) {
89
+ const renderGoal = showRenderGoal ? "\n render-goal <workdir> Render workflow-protocol.md, execution-binding.md and goal-objective.txt" : "";
90
+ console.log(`${commandName} commands:
91
+ init <workdir> Initialize task-state.json and events.ndjson
92
+ compile <workdir> Compile sources into task graph
93
+ apply-slice-delta <workdir> <delta> Apply structured slice delta, evidence and derived views
94
+ derive <workdir> Generate derived/** views
95
+ slice-gate <workdir> --slice <id> Validate one slice has real progress
96
+ epoch-gate <workdir> --epoch <id> Refresh shared epoch evidence views
97
+ final-gate <workdir> Compute product_goal_complete
98
+ next-slices <workdir> --limit 5 Recommend next proof clusters${renderGoal}`);
99
+ }
100
+ function optionValue(args, name) {
101
+ const index = args.indexOf(name);
102
+ return index >= 0 ? args[index + 1] : undefined;
103
+ }
@@ -1,4 +1,5 @@
1
1
  import { checkModularity } from "./check-modularity.js";
2
+ import { compositeLongTask } from "./composite-long-task.js";
2
3
  import { doctor } from "./doctor.js";
3
4
  import { exportContext } from "./export-context.js";
4
5
  import { init } from "./init.js";
@@ -22,6 +23,7 @@ export const commands = {
22
23
  "validate-plan-contract": (args) => validate(["validate-plan-contract", ...args]),
23
24
  "validate-plan-acceptance": (args) => validate(["validate-plan-acceptance", ...args]),
24
25
  "validate-superpowers-state": (args) => validate(["validate-superpowers-state", ...args]),
26
+ "composite-long-task": compositeLongTask,
25
27
  superpowers,
26
28
  package: packageSource
27
29
  };
@@ -47,8 +49,8 @@ export function help() {
47
49
  validate-plan-acceptance <dir>
48
50
  Validate plan-conformance matrix and final verdict consistency
49
51
  validate-superpowers-state <dir>
50
- Validate canonical Superpowers task-state.json
51
- superpowers <subcommand>
52
- Manage explicit Superpowers long-task state workdirs
52
+ Validate canonical Superpowers-backed task-state.json
53
+ composite-long-task <subcommand>
54
+ Manage explicit composite long-task workflow workdirs
53
55
  package <subcommand> Maintain package canonical source`);
54
56
  }
@@ -1,89 +1,8 @@
1
- import path from "node:path";
2
- import { applySliceDelta, initializeSuperpowersTask } from "../lib/superpowers-task-state.js";
3
- import { compileSuperpowersTask } from "../lib/superpowers-task-compile.js";
4
- import { deriveSuperpowersArtifacts } from "../lib/superpowers-task-derive.js";
5
- import { runEpochGate, runFinalGate, runSliceGate } from "../lib/superpowers-task-gates.js";
6
- import { nextSuperpowersSlices } from "../lib/superpowers-task-next-slices.js";
1
+ import { runCompositeLongTaskCommand } from "./composite-long-task.js";
7
2
  export async function superpowers(args) {
8
- const subcommand = args[0] ?? "help";
9
- const workdirArg = args[1];
10
- if (!workdirArg || subcommand === "help") {
11
- help();
12
- return;
13
- }
14
- const workdir = path.resolve(process.cwd(), workdirArg);
15
- if (subcommand === "init") {
16
- await initializeSuperpowersTask(workdir, { planSlug: path.basename(workdir) });
17
- console.log(`initialized superpowers task state at ${workdirArg}/task-state.json`);
18
- return;
19
- }
20
- if (subcommand === "compile") {
21
- const state = await compileSuperpowersTask(workdir);
22
- console.log(`compiled superpowers task graph plan_items=${Object.keys(state.graph.plan_items).length} acs=${Object.keys(state.graph.acceptance_criteria).length}`);
23
- return;
24
- }
25
- if (subcommand === "apply-slice-delta") {
26
- const delta = args[2];
27
- if (!delta) {
28
- throw new Error("apply-slice-delta requires <slice-delta.json>");
29
- }
30
- await applySliceDelta(workdir, path.resolve(process.cwd(), delta));
31
- const result = await deriveSuperpowersArtifacts(workdir);
32
- console.log(`applied superpowers slice delta and derived files=${result.files.length}`);
33
- return;
34
- }
35
- if (subcommand === "derive") {
36
- const result = await deriveSuperpowersArtifacts(workdir);
37
- console.log(`derived superpowers artifacts files=${result.files.length}`);
38
- return;
39
- }
40
- if (subcommand === "slice-gate") {
41
- const sliceId = optionValue(args, "--slice") ?? "";
42
- const result = await runSliceGate(workdir, sliceId);
43
- console.log(result.passed ? `slice gate passed ${sliceId}` : `slice gate blocked ${result.messages.join("; ")}`);
44
- if (!result.passed) {
45
- process.exitCode = 1;
46
- }
47
- return;
48
- }
49
- if (subcommand === "epoch-gate") {
50
- const epochId = optionValue(args, "--epoch") ?? "";
51
- const result = await runEpochGate(workdir, epochId);
52
- console.log(result.passed ? `epoch gate passed ${epochId}` : `epoch gate blocked ${result.messages.join("; ")}`);
53
- return;
54
- }
55
- if (subcommand === "final-gate") {
56
- const result = await runFinalGate(workdir);
57
- console.log(`final gate product_goal_complete=${result.product_goal_complete}`);
58
- if (!result.product_goal_complete) {
59
- process.exitCode = 1;
60
- for (const error of result.errors) {
61
- console.error(`error: ${error}`);
62
- }
63
- }
64
- return;
65
- }
66
- if (subcommand === "next-slices") {
67
- const limit = Number.parseInt(optionValue(args, "--limit") ?? "5", 10);
68
- const slices = await nextSuperpowersSlices(workdir, Number.isFinite(limit) ? limit : 5);
69
- console.log(`Next ${Math.min(Number.isFinite(limit) ? limit : 5, 5)} high-value clusters:`);
70
- console.log(slices.join("\n"));
71
- return;
72
- }
73
- help();
74
- }
75
- function help() {
76
- console.log(`ty-context superpowers commands:
77
- init <workdir> Initialize task-state.json and events.ndjson
78
- compile <workdir> Compile sources into task graph
79
- apply-slice-delta <workdir> <delta> Apply structured slice delta, evidence and derived views
80
- derive <workdir> Generate derived/** views
81
- slice-gate <workdir> --slice <id> Validate one slice has real progress
82
- epoch-gate <workdir> --epoch <id> Refresh shared epoch evidence views
83
- final-gate <workdir> Compute product_goal_complete
84
- next-slices <workdir> --limit 5 Recommend next proof clusters`);
85
- }
86
- function optionValue(args, name) {
87
- const index = args.indexOf(name);
88
- return index >= 0 ? args[index + 1] : undefined;
3
+ await runCompositeLongTaskCommand(args, {
4
+ commandName: "ty-context superpowers",
5
+ label: "superpowers task",
6
+ showHelp: false
7
+ });
89
8
  }
@@ -0,0 +1,12 @@
1
+ export interface CompositeLongTaskGoalRenderResult {
2
+ workdir: string;
3
+ protocolPath: string;
4
+ protocolSha256: string;
5
+ executionBindingPath: string;
6
+ goalObjectivePath: string;
7
+ goalObjectiveLength: number;
8
+ }
9
+ export declare function renderCompositeLongTaskGoal(workdir: string): Promise<CompositeLongTaskGoalRenderResult>;
10
+ export declare const COMPOSITE_LONG_TASK_WORKFLOW_SKILL_NAME = "composite-long-task-workflow";
11
+ export declare const COMPOSITE_LONG_TASK_PUBLIC_COMMAND = "composite-long-task";
12
+ export declare const COMPOSITE_LONG_TASK_PACKAGE = "project-tiny-context-harness";
@@ -0,0 +1,153 @@
1
+ import { createHash } from "node:crypto";
2
+ import path from "node:path";
3
+ import { CANONICAL_CORE_PACKAGE } from "./constants.js";
4
+ import { pathExists, readText, writeTextIfChanged } from "./fs.js";
5
+ import { packageAssetPath, packageRoot } from "./paths.js";
6
+ const SOURCE_ASSET = "packages/ty-context/assets/skills/composite-long-task-workflow/references/composite-long-task-workflow-protocol.md";
7
+ const REQUIRED_SOURCE_FILES = [
8
+ "product-architecture-source.md",
9
+ "technical-realization-plan.md",
10
+ "acceptance-checklist.md"
11
+ ];
12
+ export async function renderCompositeLongTaskGoal(workdir) {
13
+ const resolvedWorkdir = path.resolve(workdir);
14
+ await assertReadyWorkdir(resolvedWorkdir);
15
+ const protocolBody = ensureTrailingNewline(normalizeNewlines(await readText(packageAssetPath("skills", "composite-long-task-workflow", "references", "composite-long-task-workflow-protocol.md"))));
16
+ const protocolSha256 = sha256(protocolBody);
17
+ const protocolVersion = await packageVersion();
18
+ const protocolSnapshot = [
19
+ "protocol_name: composite-long-task-workflow",
20
+ `protocol_version: ${protocolVersion}`,
21
+ `protocol_sha256: ${protocolSha256}`,
22
+ `generated_at: ${new Date().toISOString()}`,
23
+ `source_asset: ${SOURCE_ASSET}`,
24
+ "---",
25
+ protocolBody.trimEnd()
26
+ ].join("\n") + "\n";
27
+ const protocolPath = path.join(resolvedWorkdir, "workflow-protocol.md");
28
+ await writeTextIfChanged(protocolPath, protocolSnapshot);
29
+ const executionBinding = renderExecutionBinding(resolvedWorkdir, protocolSha256);
30
+ const executionBindingPath = path.join(resolvedWorkdir, "execution-binding.md");
31
+ await writeTextIfChanged(executionBindingPath, executionBinding);
32
+ const goalObjective = renderGoalObjective(workdirForPrompt(resolvedWorkdir));
33
+ if (goalObjective.length > 3850) {
34
+ throw new Error(`goal-objective.txt exceeds 3850 characters (${goalObjective.length})`);
35
+ }
36
+ if (/^\/goal\s+read\s+\S+\s*\.?$/i.test(goalObjective.trim())) {
37
+ throw new Error("goal-objective.txt must not be a single read-file pointer");
38
+ }
39
+ const goalObjectivePath = path.join(resolvedWorkdir, "goal-objective.txt");
40
+ await writeTextIfChanged(goalObjectivePath, goalObjective);
41
+ return {
42
+ workdir: resolvedWorkdir,
43
+ protocolPath,
44
+ protocolSha256,
45
+ executionBindingPath,
46
+ goalObjectivePath,
47
+ goalObjectiveLength: goalObjective.length
48
+ };
49
+ }
50
+ async function assertReadyWorkdir(workdir) {
51
+ for (const sourceFile of REQUIRED_SOURCE_FILES) {
52
+ const sourcePath = path.join(workdir, sourceFile);
53
+ if (!(await pathExists(sourcePath))) {
54
+ throw new Error(`render-goal requires ${sourceFile}; provide the three input files first`);
55
+ }
56
+ }
57
+ const statePath = path.join(workdir, "task-state.json");
58
+ if (!(await pathExists(statePath))) {
59
+ throw new Error("render-goal requires task-state.json; run ty-context composite-long-task init/compile before render-goal");
60
+ }
61
+ const state = JSON.parse(await readText(statePath));
62
+ const planItemCount = Object.keys(state.graph?.plan_items ?? {}).length;
63
+ const acceptanceCriteriaCount = Object.keys(state.graph?.acceptance_criteria ?? {}).length;
64
+ if (planItemCount === 0 || acceptanceCriteriaCount === 0) {
65
+ throw new Error("render-goal requires compiled task-state.json; run ty-context composite-long-task compile before render-goal");
66
+ }
67
+ }
68
+ function renderExecutionBinding(workdir, protocolSha256) {
69
+ return `# Composite Long-Task Execution Binding
70
+
71
+ workdir: ${path.normalize(workdir)}
72
+ protocol: workflow-protocol.md
73
+ protocol_sha256: ${protocolSha256}
74
+ goal_objective: goal-objective.txt
75
+
76
+ authorities:
77
+ product_architecture_source: product-architecture-source.md
78
+ technical_realization_plan: technical-realization-plan.md
79
+ acceptance_checklist: acceptance-checklist.md
80
+
81
+ canonical_state:
82
+ task_state: task-state.json
83
+ events: events.ndjson
84
+ derived_dir: derived/
85
+
86
+ required_commands:
87
+ init: ty-context composite-long-task init <workdir>
88
+ compile: ty-context composite-long-task compile <workdir>
89
+ derive: ty-context composite-long-task derive <workdir>
90
+ apply_slice_delta: ty-context composite-long-task apply-slice-delta <workdir> <slice-delta.json>
91
+ slice_gate: ty-context composite-long-task slice-gate <workdir> --slice <id>
92
+ epoch_gate: ty-context composite-long-task epoch-gate <workdir> --epoch <id>
93
+ state_validator: ty-context validate-superpowers-state <workdir>
94
+ acceptance_validator: ty-context validate-plan-acceptance <workdir>
95
+ final_gate: ty-context composite-long-task final-gate <workdir>
96
+
97
+ completion_gate:
98
+ product_goal_complete_source: final_gate
99
+ cannot_hand_set_product_goal_complete: true
100
+ `;
101
+ }
102
+ function renderGoalObjective(workdir) {
103
+ return `/goal Execute the composite long-task workflow in ${workdir}.
104
+
105
+ First read and obey:
106
+ - workflow-protocol.md
107
+ - execution-binding.md
108
+ - product-architecture-source.md
109
+ - technical-realization-plan.md
110
+ - acceptance-checklist.md
111
+ - task-state.json and generated derived/** views
112
+
113
+ Persistent contract:
114
+ Product / Architecture Source owns intent, scope and boundaries. Technical Realization Plan owns PI implementation and plan conformance. Acceptance Checklist owns AC completion semantics and proof layers. task-state.json is the only execution state source; events.ndjson is append-only; derived/** is generated and must not be hand-edited as authority.
115
+
116
+ Use workflow-protocol.md to combine Tiny Context gates with official Superpowers execution. It is not business Context and must not be registered in project_context/context.toml. Do not redefine, duplicate or fork Superpowers mechanics. Prefer superpowers:subagent-driven-development when subagents are available, otherwise use superpowers:executing-plans. Use TDD for behavior gaps and superpowers:verification-before-completion before completion claims.
117
+
118
+ Work in slices. Each slice must update state through slice-delta.json, canonical evidence records, derive, and slice-gate. Run epoch-gate for shared provider/browser/runtime/security proof environments. Preserve Context Delta, plan conformance, acceptance proof layers, redaction, reviewability and sample/full-population boundaries.
119
+
120
+ Forbidden shortcuts:
121
+ Tests alone do not prove plan conformance. Superpowers review does not override Tiny Context gates. Sample evidence does not prove full population unless AC allows. Manual edits under derived/** are not authority. Local audit cannot mark final completion. Do not claim full implementation while Context Delta is required but Context is not updated, or while Source-to-Context Coverage / Context-to-Implementation Binding has unresolved required gaps.
122
+
123
+ Completion:
124
+ Do not hand-set product_goal_complete. Only complete after derive, verification-before-completion, validate-superpowers-state, validate-plan-acceptance, auditor/stale-overclaim checks when applicable, and final-gate compute product_goal_complete=true. If audit_task_complete is true but acceptance_target_status is not complete, report "Audit workflow completed; acceptance target not complete." and continue or stop with blockers; do not say Goal achieved.
125
+
126
+ Blocked:
127
+ Maximize safe autonomous progress using repo tools, local app/browser sessions, CLI auth and authorized elevation. Stop only for locally unsatisfiable blockers such as MFA, missing permission, external approval or unavailable credentials, and return the minimal user action list plus next agent step.
128
+ `;
129
+ }
130
+ async function packageVersion() {
131
+ const packageJson = JSON.parse(await readText(path.join(packageRoot(), "package.json")));
132
+ return packageJson.version ?? "unknown";
133
+ }
134
+ function workdirForPrompt(workdir) {
135
+ const cwd = process.cwd();
136
+ const relative = path.relative(cwd, workdir);
137
+ if (relative && !relative.startsWith("..") && !path.isAbsolute(relative)) {
138
+ return relative.replace(/\\/g, "/");
139
+ }
140
+ return workdir.replace(/\\/g, "/");
141
+ }
142
+ function normalizeNewlines(value) {
143
+ return value.replace(/\r\n/g, "\n");
144
+ }
145
+ function ensureTrailingNewline(value) {
146
+ return `${value.trimEnd()}\n`;
147
+ }
148
+ function sha256(value) {
149
+ return createHash("sha256").update(value).digest("hex");
150
+ }
151
+ export const COMPOSITE_LONG_TASK_WORKFLOW_SKILL_NAME = "composite-long-task-workflow";
152
+ export const COMPOSITE_LONG_TASK_PUBLIC_COMMAND = "composite-long-task";
153
+ export const COMPOSITE_LONG_TASK_PACKAGE = CANONICAL_CORE_PACKAGE;
@@ -50,7 +50,7 @@ export function computeScopeConflicts(state) {
50
50
  const conflicts = [];
51
51
  const product = state.delivery?.product_architecture_scope;
52
52
  const productScope = product?.delivery_scope ?? "";
53
- const productRequiresFullPopulation = productScope === "full_population_operation" || product?.full_population_required === true;
53
+ const productRequiresFullPopulation = productScope === "full_population_operation";
54
54
  const productIsCapabilityOnly = productScope === "system_capability_build" ||
55
55
  productScope === "representative_sample_validation" ||
56
56
  product?.full_population_required === false;
@@ -117,7 +117,7 @@ async function assertDerivedJson(workdir, basename, expected, errors) {
117
117
  }
118
118
  const actual = JSON.parse(await readText(file));
119
119
  if (stableJson(actual) !== stableJson(expected)) {
120
- errors.push(`derived/${basename}.json does not match task-state.json; rerun ty-context superpowers derive`);
120
+ errors.push(`derived/${basename}.json does not match task-state.json; rerun ty-context composite-long-task derive`);
121
121
  }
122
122
  }
123
123
  function evidenceForLayers(state, layerIds) {
@@ -1,7 +1,7 @@
1
1
  import { appendSuperpowersEvent } from "./superpowers-task-events.js";
2
2
  import { deriveSuperpowersArtifacts } from "./superpowers-task-derive.js";
3
3
  import { loadSuperpowersState, recomputeStatuses, saveSuperpowersState } from "./superpowers-task-state.js";
4
- import { allCompletionConditionsSatisfied, validateSuperpowersState } from "./superpowers-task-validator.js";
4
+ import { completionConditionErrors, validateSuperpowersState } from "./superpowers-task-validator.js";
5
5
  export async function runSliceGate(workdir, sliceId) {
6
6
  const state = await loadSuperpowersState(workdir);
7
7
  const slice = state.slices.find((item) => item.slice_id === sliceId);
@@ -31,7 +31,9 @@ export async function runFinalGate(workdir) {
31
31
  await deriveSuperpowersArtifacts(workdir);
32
32
  const report = await validateSuperpowersState(workdir, [workdir]);
33
33
  const latest = await loadSuperpowersState(workdir);
34
- const complete = report.errors.length === 0 && allCompletionConditionsSatisfied(latest);
34
+ const completionErrors = completionConditionErrors(latest);
35
+ const errors = [...new Set([...report.errors, ...completionErrors])];
36
+ const complete = errors.length === 0;
35
37
  latest.final.product_goal_complete = complete;
36
38
  latest.meta.product_goal_complete = complete;
37
39
  latest.final.acceptance_target_status = complete ? "complete" : "partial";
@@ -39,9 +41,9 @@ export async function runFinalGate(workdir) {
39
41
  latest.final.audit_task_complete = true;
40
42
  latest.meta.audit_task_complete = true;
41
43
  latest.final.completion_basis = complete ? ["all_required_acs_complete", "validator_passed", "auditor_no_blocker"] : [];
42
- latest.gates.validator = { status: report.errors.length === 0 ? "pass" : "blocked", errors: report.errors };
44
+ latest.gates.validator = { status: errors.length === 0 ? "pass" : "blocked", errors };
43
45
  await saveSuperpowersState(workdir, latest);
44
46
  await deriveSuperpowersArtifacts(workdir);
45
47
  await appendSuperpowersEvent(workdir, "final_gate", { product_goal_complete: complete });
46
- return { product_goal_complete: complete, errors: report.errors };
48
+ return { product_goal_complete: complete, errors };
47
49
  }
@@ -8,7 +8,15 @@ const PRODUCT_FIELDS = new Set([
8
8
  "full_population_required",
9
9
  "representative_samples_validate",
10
10
  "representative_samples_do_not_validate",
11
- "out_of_scope_backlog"
11
+ "out_of_scope_backlog",
12
+ "source_authority",
13
+ "product_goal",
14
+ "surface_ia_lock",
15
+ "decision_lock",
16
+ "context_delta",
17
+ "source_to_context_coverage",
18
+ "acceptance_semantics",
19
+ "impact"
12
20
  ]);
13
21
  const PLAN_FIELDS = new Set([
14
22
  "delivery_scope",
@@ -20,16 +28,50 @@ const PLAN_FIELDS = new Set([
20
28
  "forbidden_surfaces",
21
29
  "implementation_paths",
22
30
  "required_tests",
23
- "related_acs"
31
+ "related_acs",
32
+ "requirement_ref",
33
+ "decision_id",
34
+ "proof_layer_ids",
35
+ "api_schema_changes",
36
+ "state_machine",
37
+ "data_flow",
38
+ "worker_runtime_behavior",
39
+ "ui_ia_changes",
40
+ "migration_plan",
41
+ "evidence_artifacts",
42
+ "explicit_no_test_scope",
43
+ "non_completing_shortcuts",
44
+ "substitution_policy",
45
+ "drift_severity",
46
+ "partial_conditions",
47
+ "blockers",
48
+ "context_fact_refs"
24
49
  ]);
25
50
  const ACCEPTANCE_FIELDS = new Set([
51
+ "checklist_source",
26
52
  "acceptance_scope",
27
53
  "ac_validates",
28
54
  "ac_does_not_validate",
29
55
  "sample_boundary",
30
56
  "full_population_required",
31
57
  "related_plan_items",
32
- "required_proof_layers"
58
+ "required_proof_layers",
59
+ "ac_type",
60
+ "proof_chain",
61
+ "verification_method",
62
+ "fail_conditions",
63
+ "invalid_evidence",
64
+ "substitution_policy",
65
+ "missing_layer_downgrade",
66
+ "auditor_expectation",
67
+ "out_of_scope_na_approval_source",
68
+ "required_test_ids",
69
+ "explicit_no_test_scope",
70
+ "hard_blockers",
71
+ "validates_explanation",
72
+ "does_not_validate_explanation",
73
+ "final_evidence_expected",
74
+ "test_cases"
33
75
  ]);
34
76
  export function parseProductArchitectureScope(content, sourceFile) {
35
77
  const fields = parseDocumentFields(content, sourceFile, PRODUCT_FIELDS);
@@ -107,7 +107,7 @@ function parseFieldValue(lines, index, rest, sourceFile, lineNumber, errors) {
107
107
  let cursor = index + 1;
108
108
  for (; cursor < lines.length; cursor++) {
109
109
  const next = lines[cursor];
110
- if (next.trim() && !/^\s/.test(next)) {
110
+ if (next.trim() && isTopLevelFieldOrHeading(next)) {
111
111
  break;
112
112
  }
113
113
  block.push(next.replace(/^\s{0,2}/, ""));
@@ -127,7 +127,7 @@ function parseFieldValue(lines, index, rest, sourceFile, lineNumber, errors) {
127
127
  if (!next.trim()) {
128
128
  continue;
129
129
  }
130
- if (!/^\s/.test(next)) {
130
+ if (isTopLevelFieldOrHeading(next)) {
131
131
  break;
132
132
  }
133
133
  const listItem = /^\s*[-*+]\s+(.+?)\s*$/.exec(next);
@@ -139,6 +139,12 @@ function parseFieldValue(lines, index, rest, sourceFile, lineNumber, errors) {
139
139
  }
140
140
  return { value: values, endIndex: cursor - 1 };
141
141
  }
142
+ function isTopLevelFieldOrHeading(line) {
143
+ if (/^(#{1,6})\s+/.test(line)) {
144
+ return true;
145
+ }
146
+ return /^([a-z][a-z0-9_]*)\s*:/.test(line);
147
+ }
142
148
  function rejectListStyleDefinitions(lines, options, errors) {
143
149
  const idPattern = options.kind === "PI" ? "PI" : "AC";
144
150
  const listPattern = new RegExp(`^\\s*[-*+]\\s+(${idPattern}-\\d{3,})\\b\\s*[:.-]?`, "i");
@@ -1,4 +1,5 @@
1
1
  import { type SuperpowersTaskState } from "./superpowers-task-state-schema.js";
2
2
  import type { ValidatorReport } from "./validators.js";
3
3
  export declare function validateSuperpowersState(projectRoot: string, args?: string[]): Promise<ValidatorReport>;
4
+ export declare function completionConditionErrors(state: SuperpowersTaskState): string[];
4
5
  export declare function allCompletionConditionsSatisfied(state: SuperpowersTaskState): boolean;
@@ -221,6 +221,9 @@ function validateFinalCompletion(state, errors) {
221
221
  errors.push("product_goal_complete=true but Context Delta coverage is unresolved");
222
222
  }
223
223
  }
224
+ validateFullPopulationEvidence(state, errors);
225
+ }
226
+ function validateFullPopulationEvidence(state, errors) {
224
227
  if (fullPopulationRequired(state)) {
225
228
  const sampleOnlyEvidence = state.evidence.filter((evidence) => evidence.does_not_prove.some((claim) => /\b(full[-_ ]?population|all[-_ ]?provider|all[-_ ]?interface|all[-_ ]?platform)\b/i.test(claim)));
226
229
  if (sampleOnlyEvidence.length > 0) {
@@ -230,11 +233,11 @@ function validateFinalCompletion(state, errors) {
230
233
  }
231
234
  }
232
235
  }
233
- export function allCompletionConditionsSatisfied(state) {
236
+ export function completionConditionErrors(state) {
234
237
  const errors = [];
235
238
  validateShape(state, errors);
236
239
  if (!hasUsableShape(state)) {
237
- return false;
240
+ return errors;
238
241
  }
239
242
  validateDeliveryContract(state, errors);
240
243
  validateScopeConflicts(state, errors);
@@ -242,11 +245,21 @@ export function allCompletionConditionsSatisfied(state) {
242
245
  validateEvidenceRecords(state, errors);
243
246
  validateProofLayers(state, errors);
244
247
  validateAuditor(state, errors);
248
+ validateFullPopulationEvidence(state, errors);
245
249
  const planItems = Object.values(state.graph.plan_items);
246
250
  const acceptanceCriteria = Object.values(state.graph.acceptance_criteria);
247
251
  const proofLayers = Object.values(state.graph.proof_layers);
248
252
  const allPlansComplete = planItems.every((item) => item.status === "complete" || item.status === "out_of_scope_NA");
249
253
  const allAcsComplete = acceptanceCriteria.every((ac) => ac.status === "complete" || ac.status === "out_of_scope_NA");
250
254
  const allLayersSatisfied = proofLayers.every((layer) => !layer.required || layer.status === "satisfied");
251
- return errors.length === 0 && planItems.length > 0 && acceptanceCriteria.length > 0 && proofLayers.length > 0 && allPlansComplete && allAcsComplete && allLayersSatisfied;
255
+ if (planItems.length === 0 || acceptanceCriteria.length === 0 || proofLayers.length === 0) {
256
+ errors.push("completion conditions require a compiled task graph with plan items, ACs and proof layers");
257
+ }
258
+ if (!allPlansComplete || !allAcsComplete || !allLayersSatisfied) {
259
+ errors.push("completion conditions require all required plan items, ACs and proof layers to be complete");
260
+ }
261
+ return errors;
262
+ }
263
+ export function allCompletionConditionsSatisfied(state) {
264
+ return completionConditionErrors(state).length === 0;
252
265
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-tiny-context-harness",
3
- "version": "0.2.79",
3
+ "version": "0.2.80",
4
4
  "description": "Minimal project memory and validation harness for AI coding agents.",
5
5
  "license": "MIT",
6
6
  "author": "Seven128",