project-tiny-context-harness 0.2.79 → 0.2.81

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 (39) hide show
  1. package/README.md +17 -15
  2. package/assets/README.md +17 -15
  3. package/assets/README.zh-CN.md +19 -9
  4. package/assets/agents/AGENTS_CORE.md +42 -42
  5. package/assets/github/harness.yml +1 -1
  6. package/assets/skills/composite-long-task-workflow/SKILL.md +218 -0
  7. package/assets/skills/composite-long-task-workflow/assets/execution-binding.template.md +33 -0
  8. package/assets/skills/composite-long-task-workflow/assets/goal-objective.template.md +26 -0
  9. package/assets/skills/composite-long-task-workflow/references/composite-long-task-workflow-protocol.md +644 -0
  10. package/assets/skills/normal-long-task/SKILL.md +12 -12
  11. package/dist/commands/composite-long-task.d.ts +6 -0
  12. package/dist/commands/composite-long-task.js +103 -0
  13. package/dist/commands/index.js +5 -3
  14. package/dist/commands/superpowers.js +6 -87
  15. package/dist/lib/composite-long-task-renderer.d.ts +12 -0
  16. package/dist/lib/composite-long-task-renderer.js +109 -0
  17. package/dist/lib/superpowers-task-assertion-normalizers.d.ts +3 -0
  18. package/dist/lib/superpowers-task-assertion-normalizers.js +67 -0
  19. package/dist/lib/superpowers-task-assertions.d.ts +20 -0
  20. package/dist/lib/superpowers-task-assertions.js +242 -0
  21. package/dist/lib/superpowers-task-compile-diagnostics.d.ts +5 -0
  22. package/dist/lib/superpowers-task-compile-diagnostics.js +20 -0
  23. package/dist/lib/superpowers-task-compile-guards.d.ts +2 -0
  24. package/dist/lib/superpowers-task-compile-guards.js +66 -0
  25. package/dist/lib/superpowers-task-compile.js +26 -6
  26. package/dist/lib/superpowers-task-conformance.d.ts +2 -0
  27. package/dist/lib/superpowers-task-conformance.js +24 -0
  28. package/dist/lib/superpowers-task-derive.js +61 -5
  29. package/dist/lib/superpowers-task-gates.js +62 -6
  30. package/dist/lib/superpowers-task-source-compile.js +94 -14
  31. package/dist/lib/superpowers-task-source-parser.js +20 -18
  32. package/dist/lib/superpowers-task-state-schema.d.ts +61 -1
  33. package/dist/lib/superpowers-task-state.js +19 -3
  34. package/dist/lib/superpowers-task-status.d.ts +2 -0
  35. package/dist/lib/superpowers-task-status.js +14 -0
  36. package/dist/lib/superpowers-task-validator.d.ts +1 -0
  37. package/dist/lib/superpowers-task-validator.js +30 -4
  38. package/package.json +5 -5
  39. package/assets/skills/superpowers-long-task/SKILL.md +0 -612
@@ -19,7 +19,7 @@ Convert a given ordinary long-task source into a strict, project-context-aware a
19
19
  For this exact plan, what must be true before a future executor can honestly say the task is complete?
20
20
  ```
21
21
 
22
- This Skill defines the acceptance standard. It is not an execution framework. For a dedicated prompt that binds the finished checklist to the external Superpowers workflow, directly invoke `/superpowers-long-task` after this Skill has produced or verified the full checklist.
22
+ This Skill defines the acceptance standard. It is not an execution framework. For the composite workflow that binds the finished checklist to Superpowers-backed execution, directly invoke `/composite-long-task-workflow` after this Skill has produced or verified the full checklist.
23
23
 
24
24
  ## Required Outputs
25
25
 
@@ -59,7 +59,7 @@ Use this Skill through explicit invocation:
59
59
  /normal-long-task
60
60
  ```
61
61
 
62
- Do not rely on broad automatic keyword routing. If the user needs the Superpowers-specific long-task adapter, use `/superpowers-long-task` after this Skill has produced or verified the full checklist.
62
+ Do not rely on broad automatic keyword routing. If the user needs the Superpowers-backed composite long-task workflow, use `/composite-long-task-workflow` after this Skill has produced or verified the full checklist.
63
63
 
64
64
  ## Use Cases
65
65
 
@@ -264,11 +264,11 @@ If only locally unsatisfiable hard blockers remain, pause for the user or extern
264
264
 
265
265
  ## Autonomous Progress Protocol
266
266
 
267
- The generated target-mode prompt must require maximum safe autonomous progress within current platform, repository, tool and user-authorized permission boundaries. Do not ask the user for work the executor can safely discover, run, inspect or verify itself.
268
-
269
- Before treating account, credential or app access as blocked, open the relevant app, browser page, CLI tool or system setting and try existing app sessions, browser cookies, CLI auth, OS credential helpers or other user-authorized local state. If the existing session is absent, expired, permission-denied or requires login/MFA/approval, then pause with the minimal user action list; do not ask the user to send passwords, tokens, cookies or full sensitive fields.
270
-
271
- The generated target-mode prompt must inherit current repository/global `AGENTS.md` or agent-instruction permission policy. Authorized `sudo` / `gsudo` / administrator elevation is not a user blocker; the executor must try it before pausing. Pause only if elevation is unavailable, fails, or requires user/system authorization.
267
+ The generated target-mode prompt must require maximum safe autonomous progress within current platform, repository, tool and user-authorized permission boundaries. Do not ask the user for work the executor can safely discover, run, inspect or verify itself.
268
+
269
+ Before treating account, credential or app access as blocked, open the relevant app, browser page, CLI tool or system setting and try existing app sessions, browser cookies, CLI auth, OS credential helpers or other user-authorized local state. If the existing session is absent, expired, permission-denied or requires login/MFA/approval, then pause with the minimal user action list; do not ask the user to send passwords, tokens, cookies or full sensitive fields.
270
+
271
+ The generated target-mode prompt must inherit current repository/global `AGENTS.md` or agent-instruction permission policy. Authorized `sudo` / `gsudo` / administrator elevation is not a user blocker; the executor must try it before pausing. Pause only if elevation is unavailable, fails, or requires user/system authorization.
272
272
 
273
273
  Pause only for locally unsatisfiable hard blockers such as missing accounts, credentials, production access, paid services, legal/security approval, user-only browser sessions or sensitive fields the executor cannot access safely.
274
274
 
@@ -445,11 +445,11 @@ Hard requirements:
445
445
  - The prompt must identify the full checklist path immediately after the plan path and say it is the complete acceptance standard. Chinese prompts must include this exact sentence: `该文件是完整验收标准,验收以这个为准。完成前必须逐项检查,不满足则继续实现。` English prompts must say the full checklist is the complete acceptance standard, acceptance is judged against it, and every item must be checked before completion.
446
446
  - The prompt must identify a local audit path, normally `tmp/ty-context/plan-acceptance/<plan-slug>-local-audit.md`, and require the future executor to read it before resuming, keep it current during execution, and use it only as target-mode acceptance progress state, not Context or proof.
447
447
  - If the prompt uses a compact checklist summary, say the full checklist owns details and acceptance authority; the compact summary owns direction, priority and recovery navigation; overlap with the full checklist is allowed; conflicts are resolved in favor of the full checklist.
448
- - The prompt must require local audit status to start from `unknown / not_run`; only fresh required evidence can mark an AC complete. If any fresh browser / API / runtime / data / test contradiction appears, downgrade the affected AC and overall status and record invalidating evidence.
449
- - The prompt must state that UI-facing acceptance requires a real page path and matching user-visible state; component / viewmodel / mock / unit test evidence is auxiliary unless the full checklist explicitly says otherwise.
450
- - The prompt must require maximum safe autonomous progress within current platform, repository, tool and user-authorized permission boundaries. Do not ask the user for work the executor can safely discover, run, inspect or verify itself. If pausing for a locally unsatisfiable hard blocker, provide the minimum user action list: exact page/system/command/owner, field or value location, redaction guidance, values not to send and what the executor will do next.
451
- - The prompt must say existing local sessions, browser state, CLI auth and OS credential helpers are self-service resources: open the relevant app/page/tool/settings first, use existing local auth if present, and pause only after access is actually absent, expired, permission-denied or requires login/MFA/approval.
452
- - The prompt must inherit current repository/global `AGENTS.md` or agent-instruction permission policy. Authorized `sudo` / `gsudo` / administrator elevation is not a user blocker; the executor must try it before pausing. Pause only if elevation is unavailable, fails, or requires user/system authorization.
448
+ - The prompt must require local audit status to start from `unknown / not_run`; only fresh required evidence can mark an AC complete. If any fresh browser / API / runtime / data / test contradiction appears, downgrade the affected AC and overall status and record invalidating evidence.
449
+ - The prompt must state that UI-facing acceptance requires a real page path and matching user-visible state; component / viewmodel / mock / unit test evidence is auxiliary unless the full checklist explicitly says otherwise.
450
+ - The prompt must require maximum safe autonomous progress within current platform, repository, tool and user-authorized permission boundaries. Do not ask the user for work the executor can safely discover, run, inspect or verify itself. If pausing for a locally unsatisfiable hard blocker, provide the minimum user action list: exact page/system/command/owner, field or value location, redaction guidance, values not to send and what the executor will do next.
451
+ - The prompt must say existing local sessions, browser state, CLI auth and OS credential helpers are self-service resources: open the relevant app/page/tool/settings first, use existing local auth if present, and pause only after access is actually absent, expired, permission-denied or requires login/MFA/approval.
452
+ - The prompt must inherit current repository/global `AGENTS.md` or agent-instruction permission policy. Authorized `sudo` / `gsudo` / administrator elevation is not a user blocker; the executor must try it before pausing. Pause only if elevation is unavailable, fails, or requires user/system authorization.
453
453
  - The prompt must preserve hard-blocker semantics: if only locally unsatisfiable hard blockers remain, pause for the user or external owner instead of marking the goal complete.
454
454
  - The prompt must say that local audit is not Context, not product-quality proof, not a global task manager, and not a replacement for project tests, CI, review, human acceptance, Task Contract or workflow-contract `plan.md`.
455
455
  - Do not include explanatory preface inside the prompt.
@@ -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,109 @@
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 = await renderExecutionBinding(resolvedWorkdir, protocolSha256);
30
+ const executionBindingPath = path.join(resolvedWorkdir, "execution-binding.md");
31
+ await writeTextIfChanged(executionBindingPath, executionBinding);
32
+ const goalObjective = await 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
+ async function renderExecutionBinding(workdir, protocolSha256) {
69
+ return renderTemplateAsset(["skills", "composite-long-task-workflow", "assets", "execution-binding.template.md"], {
70
+ workdir: path.normalize(workdir),
71
+ protocol_sha256: protocolSha256
72
+ });
73
+ }
74
+ async function renderGoalObjective(workdir) {
75
+ return renderTemplateAsset(["skills", "composite-long-task-workflow", "assets", "goal-objective.template.md"], {
76
+ workdir
77
+ });
78
+ }
79
+ async function packageVersion() {
80
+ const packageJson = JSON.parse(await readText(path.join(packageRoot(), "package.json")));
81
+ return packageJson.version ?? "unknown";
82
+ }
83
+ function workdirForPrompt(workdir) {
84
+ const cwd = process.cwd();
85
+ const relative = path.relative(cwd, workdir);
86
+ if (relative && !relative.startsWith("..") && !path.isAbsolute(relative)) {
87
+ return relative.replace(/\\/g, "/");
88
+ }
89
+ return workdir.replace(/\\/g, "/");
90
+ }
91
+ function normalizeNewlines(value) {
92
+ return value.replace(/\r\n/g, "\n");
93
+ }
94
+ function ensureTrailingNewline(value) {
95
+ return `${value.trimEnd()}\n`;
96
+ }
97
+ async function renderTemplateAsset(segments, values) {
98
+ let rendered = normalizeNewlines(await readText(packageAssetPath(...segments)));
99
+ for (const [key, value] of Object.entries(values)) {
100
+ rendered = rendered.replaceAll(`{{${key}}}`, value);
101
+ }
102
+ return ensureTrailingNewline(rendered);
103
+ }
104
+ function sha256(value) {
105
+ return createHash("sha256").update(value).digest("hex");
106
+ }
107
+ export const COMPOSITE_LONG_TASK_WORKFLOW_SKILL_NAME = "composite-long-task-workflow";
108
+ export const COMPOSITE_LONG_TASK_PUBLIC_COMMAND = "composite-long-task";
109
+ export const COMPOSITE_LONG_TASK_PACKAGE = CANONICAL_CORE_PACKAGE;
@@ -0,0 +1,3 @@
1
+ import { type AssertionResult, type NegativeEvidenceScan } from "./superpowers-task-state-schema.js";
2
+ export declare function normalizeAssertionResult(value: unknown): AssertionResult | undefined;
3
+ export declare function normalizeNegativeEvidenceScan(value: unknown): NegativeEvidenceScan | undefined;
@@ -0,0 +1,67 @@
1
+ import { isRecord } from "./superpowers-task-state-schema.js";
2
+ export function normalizeAssertionResult(value) {
3
+ if (!isRecord(value)) {
4
+ return undefined;
5
+ }
6
+ return {
7
+ schema_version: String(value.schema_version ?? ""),
8
+ status: String(value.status ?? ""),
9
+ runner: String(value.runner ?? ""),
10
+ exit_code: numberValue(value.exit_code),
11
+ target_ac_ids: stringArray(value.target_ac_ids),
12
+ target_proof_layers: stringArray(value.target_proof_layers),
13
+ owner_surface: value.owner_surface === undefined ? undefined : String(value.owner_surface),
14
+ route: value.route === undefined ? undefined : String(value.route),
15
+ action: value.action === undefined ? undefined : String(value.action),
16
+ positive_assertions: checkArray(value.positive_assertions),
17
+ negative_assertions: checkArray(value.negative_assertions),
18
+ artifacts: stringArray(value.artifacts)
19
+ };
20
+ }
21
+ export function normalizeNegativeEvidenceScan(value) {
22
+ if (!isRecord(value)) {
23
+ return undefined;
24
+ }
25
+ return {
26
+ schema_version: String(value.schema_version ?? ""),
27
+ status: String(value.status ?? ""),
28
+ target_ac_ids: stringArray(value.target_ac_ids),
29
+ owner_surface: value.owner_surface === undefined ? undefined : String(value.owner_surface),
30
+ route: value.route === undefined ? undefined : String(value.route),
31
+ forbidden_findings: findingArray(value.forbidden_findings),
32
+ required_findings: checkArray(value.required_findings),
33
+ artifacts: stringArray(value.artifacts)
34
+ };
35
+ }
36
+ function checkArray(value) {
37
+ if (!Array.isArray(value)) {
38
+ return [];
39
+ }
40
+ return value.filter(isRecord).map((item) => ({
41
+ id: String(item.id ?? ""),
42
+ status: String(item.status ?? ""),
43
+ actual: item.actual === undefined ? undefined : String(item.actual),
44
+ expected: item.expected === undefined ? undefined : String(item.expected),
45
+ forbidden_text: item.forbidden_text === undefined ? undefined : String(item.forbidden_text)
46
+ }));
47
+ }
48
+ function findingArray(value) {
49
+ if (!Array.isArray(value)) {
50
+ return [];
51
+ }
52
+ return value.filter(isRecord).map((item) => ({
53
+ id: String(item.id ?? ""),
54
+ status: String(item.status ?? ""),
55
+ forbidden_text: item.forbidden_text === undefined ? undefined : String(item.forbidden_text),
56
+ actual: item.actual === undefined ? undefined : String(item.actual)
57
+ }));
58
+ }
59
+ function stringArray(value) {
60
+ if (!Array.isArray(value)) {
61
+ return [];
62
+ }
63
+ return value.map((item) => String(item)).filter(Boolean);
64
+ }
65
+ function numberValue(value) {
66
+ return typeof value === "number" && Number.isFinite(value) ? value : Number.NaN;
67
+ }
@@ -0,0 +1,20 @@
1
+ import { type SuperpowersEvidenceRecord, type SuperpowersTaskState } from "./superpowers-task-state-schema.js";
2
+ export { normalizeAssertionResult, normalizeNegativeEvidenceScan } from "./superpowers-task-assertion-normalizers.js";
3
+ export type AssertionStatus = "passed" | "failed" | "missing" | "stale" | "not_applicable";
4
+ export interface ProofLayerAssertionEvaluation {
5
+ assertion_status: AssertionStatus;
6
+ blocking_assertion_failures: string[];
7
+ negative_evidence_findings: string[];
8
+ }
9
+ export declare const MACHINE_VERIFIABLE_PROOF_LAYERS: Set<string>;
10
+ export declare const UI_BROWSER_ASSERTION_TYPES: Set<string>;
11
+ export declare function proofLayerName(layerId: string): string;
12
+ export declare function proofLayerAcId(layerId: string): string;
13
+ export declare function isMachineVerifiableLayer(layerId: string): boolean;
14
+ export declare function isUiBrowserLayer(layerId: string): boolean;
15
+ export declare function evaluateAcEvidence(state: SuperpowersTaskState, acId: string): ProofLayerAssertionEvaluation;
16
+ export declare function evaluateProofLayer(state: SuperpowersTaskState, layerId: string): ProofLayerAssertionEvaluation;
17
+ export declare function evaluateProofLayerAssertions(state: SuperpowersTaskState, layerId: string): ProofLayerAssertionEvaluation;
18
+ export declare function assertionFailuresForState(state: SuperpowersTaskState): string[];
19
+ export declare function evaluateAssertionEvidence(evidence: SuperpowersEvidenceRecord, layerId: string): string[];
20
+ export declare function evaluateNegativeEvidence(evidence: SuperpowersEvidenceRecord, layerId: string): string[];