Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"taskflow-core": "workspace:*",
"taskflow-hosts": "workspace:*",
"typebox": "^1.3.14",
"typescript": "^7.0.2"
"typescript": "^7.0.2",
"yaml": "^2.9.0"
}
}
1,295 changes: 620 additions & 675 deletions packages/claude-taskflow/plugin/skills/taskflow/SKILL.md

Large diffs are not rendered by default.

1,295 changes: 620 additions & 675 deletions packages/codex-taskflow/plugin/skills/taskflow/SKILL.md

Large diffs are not rendered by default.

1,295 changes: 620 additions & 675 deletions packages/grok-taskflow/plugin/skills/taskflow/SKILL.md

Large diffs are not rendered by default.

1,295 changes: 620 additions & 675 deletions packages/hermes-taskflow/plugin/skills/taskflow/SKILL.md

Large diffs are not rendered by default.

1,295 changes: 620 additions & 675 deletions packages/opencode-taskflow/plugin/skills/taskflow/SKILL.md

Large diffs are not rendered by default.

1,384 changes: 660 additions & 724 deletions packages/pi-taskflow/skills/taskflow/SKILL.md

Large diffs are not rendered by default.

50 changes: 50 additions & 0 deletions packages/pi-taskflow/skills/taskflow/commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<!-- GENERATED FILE — do not edit. Source: skills-src/taskflow/commands.pi.md (npm run build:skills) -->

# Taskflow commands

This sidecar covers human/operator use of Taskflow in Pi. Load it when you need to inspect saved flows or runs, continue a run, or use the `/tf` control surface.

## Saved flows

- `/tf list` — list saved flows.
- `/tf show <name>` — show a saved flow definition.
- `/tf run <name> [args]` — run a saved flow with optional arguments.
- `/tf:<name> [args]` — run a saved flow through its shortcut.

The equivalent tool operation uses `action: "run"` with `name` and optional `args`. A saved flow's shortcut is available after the flow is registered.

## Check and inspect a flow

- `/tf verify <name>` — run zero-token structural checks.
- `/tf plan <name> [args]` — bind arguments, inspect projected phase order and dynamic bindings, and estimate the static agent-call bound without executing subagents.
- `/tf compile <name> [lr|td]` — render the flow and its verification report.
- `/tf ir <name>` — inspect the content-addressed FlowIR representation.

Use these controls before a consequential run; they do not execute provider-backed phases.

## Inspect runs

- `/tf runs` — list recent runs.
- `/tf peek <runId>` — list stored phase statuses and output sizes.
- `/tf peek <runId> <phaseId>` — inspect one stored phase output.
- Add `--json` for parsed JSON, `--item <n>` for one fan-out item, or `--limit <chars>` to bound displayed output.
- `/tf provenance <runId>` — inspect observed upstream reads.
- `/tf trace <runId> [--json]` — inspect the recorded event trace when one exists.
- `/tf replay <runId> [options]` — perform an offline what-if replay without model calls.

## Continue or recompute

- `/tf resume <runId>` — fork a failed or paused run and continue its unfinished work.
- `/tf why-stale <runId> [phaseId]` — inspect the stale frontier from a changed phase.
- `/tf recompute <runId> <phaseId> [--apply]` — preview the stale frontier, or apply the recompute with `--apply`.
- `/tf reconcile-workspace --ack` — acknowledge the current state of a dirty resolve-only workspace after inspection or repair.

Resume preserves the original run. Recompute is for changed inputs and is dry-run by default.

## Background and setup

- A background run returns a `runId`; use `/tf runs` to monitor it.
- `/tf version` — show package, build, schema, and host identity.
- `/tf init` — interactively configure model roles.

These commands operate on Taskflow's control surface; use the main skill for flow authoring guidance.
101 changes: 89 additions & 12 deletions packages/pi-taskflow/test/skills-build.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,35 @@ import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import { fileURLToPath } from "node:url";
import path from "node:path";
import { parse as parseYaml } from "yaml";

const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "..");

function parseSkillFrontmatter(name: string, text: string): string {
const match = text.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
assert.ok(match, `${name} SKILL.md must have leading YAML frontmatter`);

let frontmatter: unknown;
assert.doesNotThrow(
() => {
frontmatter = parseYaml(match[1]);
},
`${name} SKILL.md frontmatter must parse as YAML`,
);
assert.ok(
frontmatter !== null && typeof frontmatter === "object" && !Array.isArray(frontmatter),
`${name} SKILL.md frontmatter must be a YAML mapping`,
);
const metadata = frontmatter as { name?: unknown; description?: unknown };
assert.equal(metadata.name, "taskflow", `${name} SKILL.md frontmatter name`);
assert.equal(typeof metadata.description, "string", `${name} SKILL.md frontmatter description type`);
assert.ok(
typeof metadata.description === "string" && metadata.description.trim().length > 0,
`${name} SKILL.md frontmatter description must be non-empty`,
);
return metadata.description as string;
}

test("skills: generated skill files are in sync with skills-src (build-skills --check)", () => {
try {
execFileSync(process.execPath, [path.join(root, "scripts", "build-skills.mjs"), "--check"], {
Expand Down Expand Up @@ -45,8 +71,9 @@ test("release discovery metadata advertises the complete MCP surface", async ()
});

test("skills: host-conditional filtering removed the other host's content", async () => {
const { readFileSync } = await import("node:fs");
const { existsSync, readFileSync } = await import("node:fs");
const piSkill = readFileSync(path.join(root, "packages", "pi-taskflow", "skills", "taskflow", "SKILL.md"), "utf8");
const piCommands = readFileSync(path.join(root, "packages", "pi-taskflow", "skills", "taskflow", "commands.md"), "utf8");
const cxSkill = readFileSync(
path.join(root, "packages", "codex-taskflow", "plugin", "skills", "taskflow", "SKILL.md"),
"utf8",
Expand Down Expand Up @@ -78,14 +105,61 @@ test("skills: host-conditional filtering removed the other host's content", asyn
] as const) {
assert.ok(!/<!--\s*\/?host:/.test(text), `${name} SKILL.md must not contain host markers`);
}
// Pi teaches its 20 actions; the MCP hosts must not (they're unreachable via MCP).
assert.match(piSkill, /Actions \(all 20\)/);
assert.doesNotMatch(cxSkill, /Actions \(all 20\)/);
assert.doesNotMatch(clSkill, /Actions \(all 20\)/);
assert.doesNotMatch(ocSkill, /Actions \(all 20\)/);
assert.doesNotMatch(gkSkill, /Actions \(all 20\)/);
assert.doesNotMatch(hmSkill, /Actions \(all 20\)/);
assert.doesNotMatch(cxSkill, /action: "recompute"/);
// The Pi command sidecar is generated only for Pi and is not part of the main skill.
assert.match(piCommands, /\/tf list/);
assert.match(piCommands, /\/tf resume/);
assert.match(piCommands, /\/tf verify <name>/);
assert.doesNotMatch(piCommands, /\/tf verify(?:\s+—|\s*$)/m);
assert.doesNotMatch(piSkill, /\/tf(?: |:)/);
for (const host of ["codex", "claude", "opencode", "grok", "hermes"]) {
const dir = host === "codex" ? "codex-taskflow/plugin" : `${host}-taskflow/plugin`;
assert.equal(existsSync(path.join(root, "packages", dir, "skills", "taskflow", "commands.md")), false, `${host} must not receive commands.md`);
}
const generatedSkills = [
["pi", piSkill],
["codex", cxSkill],
["claude", clSkill],
["opencode", ocSkill],
["grok", gkSkill],
["hermes", hmSkill],
] as const;
for (const [name, text] of generatedSkills) {
if (name === "pi") {
assert.match(text, /\| `commands\.md` \|/);
} else {
assert.doesNotMatch(text, /\| `commands\.md` \|/, `${name} SKILL.md must not reference commands.md`);
}
const description = parseSkillFrontmatter(name, text);
assert.match(description, /delegate or orchestrate bounded work with isolated subagents/, `${name} activation description must use bounded delegation`);
assert.match(description, /cheaper or specialized agents/, `${name} activation description must name a concrete delegation benefit`);
assert.doesNotMatch(description, /Orchestrate multi-phase subagent workflows/);
assert.doesNotMatch(description, /Use whenever a request spans a whole project or many items/);
assert.doesNotMatch(description, /Prefer this over ad-hoc parallel work when the task has multiple phases/);
}
// The accepted main-skill structure is exactly nine ordered top-level sections.
const headings = [...piSkill.matchAll(/^## (\d+\. [^\n]+)/gm)].map((match) => match[1]);
assert.deepEqual(headings, [
"1. Decide whether Taskflow helps",
"2. Choose the smallest useful shape",
"3. Quick-start examples",
"4. Proven task patterns",
"5. Adapt the pattern safely",
"6. Preflight → verify → plan → run",
"7. When execution fails",
"8. Advanced shapes",
"9. Need more detail?",
]);
assert.match(piSkill, /\"name\": \"example-flow\"/);
assert.match(piSkill, /\{steps\.produce\.output\}/);
assert.match(piSkill, /\"from\": \[\"inspect-a\", \"inspect-b\"\]/);
assert.match(piSkill, /\{previous\.output\}/);
assert.match(piSkill, /per-call `timeout`/);
assert.match(piSkill, /retry\.max: 0.*automatically retry/s);
assert.match(piSkill, /user \| project \| both/);
assert.match(piSkill, /unbounded.*static call estimate/s);
assert.match(piSkill, /strictInterpolation: true/);
assert.doesNotMatch(piSkill, /readSeek_|colgrep|hypa_|lens_|context-mode|packages_/);
assert.doesNotMatch(piSkill, /Actions \(all 20\)/);
// The MCP hosts teach the MCP tools; pi must not.
assert.match(cxSkill, /taskflow_verify/);
assert.match(clSkill, /taskflow_verify/);
Expand All @@ -104,9 +178,12 @@ test("skills: host-conditional filtering removed the other host's content", asyn
assert.doesNotMatch(ocSkill, /codex exec|claude -p|grok -p|hermes chat/);
assert.doesNotMatch(gkSkill, /codex exec|claude -p|opencode run|hermes chat/);
assert.doesNotMatch(hmSkill, /codex exec|claude -p|opencode run|grok -p/);
// All hosts share the same core: flow design ladder + common-mistakes section.
// All hosts share the accepted nine-section body and its DAG semantics.
for (const text of [piSkill, cxSkill, clSkill, ocSkill, gkSkill, hmSkill]) {
assert.match(text, /Flow design ladder/);
assert.match(text, /Referencing `\{steps\.X\}` without `dependsOn/);
assert.match(text, /## 1\. Decide whether Taskflow helps/);
assert.match(text, /## 9\. Need more detail\?/);
assert.match(text, /Array order is not a dependency/);
assert.match(text, /\{steps\.produce\.output\}/);
assert.match(text, /\{previous\.output\}/);
}
});
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 12 additions & 9 deletions scripts/build-skills.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
// entry.grok.md — grok frontmatter + MCP tool table preamble
// entry.hermes.md — hermes frontmatter + MCP tool table preamble
// core.md — the shared body (host-conditional blocks allowed)
// patterns.md, advanced.md, configuration.md — shared companions
// patterns.md, advanced.md, configuration.md, library.md — shared companions
// commands.pi.md — Pi-only operator command sidecar
//
// Host-conditional blocks use HTML comment markers on their own lines; the
// host field is a comma-list, kept when it contains the build target:
Expand All @@ -17,12 +18,12 @@
// Marker lines are always stripped. Nesting is not supported (build error).
//
// Outputs (generated, committed; drift-guarded by skills-build.test.ts):
// packages/pi-taskflow/skills/taskflow/{SKILL.md,patterns.md,advanced.md,configuration.md}
// packages/codex-taskflow/plugin/skills/taskflow/{…same four…}
// packages/claude-taskflow/plugin/skills/taskflow/{…same four…}
// packages/opencode-taskflow/plugin/skills/taskflow/{…same four…}
// packages/grok-taskflow/plugin/skills/taskflow/{…same four…}
// packages/hermes-taskflow/plugin/skills/taskflow/{…same four…}
// packages/pi-taskflow/skills/taskflow/{SKILL.md,patterns.md,advanced.md,configuration.md,library.md,commands.md}
// packages/codex-taskflow/plugin/skills/taskflow/{…same shared four…}
// packages/claude-taskflow/plugin/skills/taskflow/{…same shared four…}
// packages/opencode-taskflow/plugin/skills/taskflow/{…same shared four…}
// packages/grok-taskflow/plugin/skills/taskflow/{…same shared four…}
// packages/hermes-taskflow/plugin/skills/taskflow/{…same shared four…}
//
// Usage: node scripts/build-skills.mjs [--check]
// --check: exit 1 if any generated file differs from what's on disk.
Expand All @@ -37,6 +38,7 @@ const srcDir = join(root, "skills-src", "taskflow");

const HOSTS = ["pi", "codex", "claude", "opencode", "grok", "hermes"];
const COMPANIONS = ["patterns.md", "advanced.md", "configuration.md", "library.md"];
const HOST_COMPANIONS = { pi: ["commands.pi.md"] };
const OUT_DIRS = {
pi: join(root, "packages", "pi-taskflow", "skills", "taskflow"),
codex: join(root, "packages", "codex-taskflow", "plugin", "skills", "taskflow"),
Expand Down Expand Up @@ -111,9 +113,10 @@ export function buildAll() {
filterForHost(core, host, "core.md").trim() +
"\n";
files.push({ path: join(OUT_DIRS[host], "SKILL.md"), content: skill });
for (const c of COMPANIONS) {
for (const c of [...COMPANIONS, ...(HOST_COMPANIONS[host] ?? [])]) {
const outputName = c.endsWith(`.${host}.md`) ? c.slice(0, -`.${host}.md`.length) + ".md" : c;
const body = GENERATED_BANNER(c) + "\n" + filterForHost(read(c), host, c).trim() + "\n";
files.push({ path: join(OUT_DIRS[host], c), content: body });
files.push({ path: join(OUT_DIRS[host], outputName), content: body });
}
}
return files;
Expand Down
48 changes: 48 additions & 0 deletions skills-src/taskflow/commands.pi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Taskflow commands

This sidecar covers human/operator use of Taskflow in Pi. Load it when you need to inspect saved flows or runs, continue a run, or use the `/tf` control surface.

## Saved flows

- `/tf list` — list saved flows.
- `/tf show <name>` — show a saved flow definition.
- `/tf run <name> [args]` — run a saved flow with optional arguments.
- `/tf:<name> [args]` — run a saved flow through its shortcut.

The equivalent tool operation uses `action: "run"` with `name` and optional `args`. A saved flow's shortcut is available after the flow is registered.

## Check and inspect a flow

- `/tf verify <name>` — run zero-token structural checks.
- `/tf plan <name> [args]` — bind arguments, inspect projected phase order and dynamic bindings, and estimate the static agent-call bound without executing subagents.
- `/tf compile <name> [lr|td]` — render the flow and its verification report.
- `/tf ir <name>` — inspect the content-addressed FlowIR representation.

Use these controls before a consequential run; they do not execute provider-backed phases.

## Inspect runs

- `/tf runs` — list recent runs.
- `/tf peek <runId>` — list stored phase statuses and output sizes.
- `/tf peek <runId> <phaseId>` — inspect one stored phase output.
- Add `--json` for parsed JSON, `--item <n>` for one fan-out item, or `--limit <chars>` to bound displayed output.
- `/tf provenance <runId>` — inspect observed upstream reads.
- `/tf trace <runId> [--json]` — inspect the recorded event trace when one exists.
- `/tf replay <runId> [options]` — perform an offline what-if replay without model calls.

## Continue or recompute

- `/tf resume <runId>` — fork a failed or paused run and continue its unfinished work.
- `/tf why-stale <runId> [phaseId]` — inspect the stale frontier from a changed phase.
- `/tf recompute <runId> <phaseId> [--apply]` — preview the stale frontier, or apply the recompute with `--apply`.
- `/tf reconcile-workspace --ack` — acknowledge the current state of a dirty resolve-only workspace after inspection or repair.

Resume preserves the original run. Recompute is for changed inputs and is dry-run by default.

## Background and setup

- A background run returns a `runId`; use `/tf runs` to monitor it.
- `/tf version` — show package, build, schema, and host identity.
- `/tf init` — interactively configure model roles.

These commands operate on Taskflow's control surface; use the main skill for flow authoring guidance.
Loading