Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cys",
"version": "0.6.23",
"version": "0.6.24",
"description": "Development methodology skills with parallel plan execution: design, plan, run, check, ship. Named after the author's twin daughters, Cielo y Sophia.",
"author": { "name": "Christian Bacilio" },
"repository": "https://github.com/bacsystem/parallel-plan-executor",
Expand Down
2 changes: 1 addition & 1 deletion .cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cys",
"version": "0.6.23",
"version": "0.6.24",
"description": "Development methodology skills for design, plan, check, and ship — parallel plan execution (cys:run) is Claude Code only for now.",
"author": { "name": "Christian Bacilio" },
"repository": "https://github.com/bacsystem/parallel-plan-executor",
Expand Down
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 0.6.24 — 2026-07-24

Added:

- `bin/parse-plan.js` and `bin/plan-remainder.js` now report
`parallelWidth` (via the existing `computeParallelWidth`, 0.6.18) in
their JSON output.
- `/cys:run-plan` and `/cys:flow` now offer to set `maxConcurrency`
automatically when the parsed plan's `parallelWidth` exceeds 6,
explaining that the plan can run that many tasks at once (worktrees +
subagents + merges in parallel). Narrower plans are never asked. The
chosen value flows through to the `Workflow` launch args in both
commands.

`runDag`'s `maxConcurrency` cap has existed since 0.6.15, but neither
command ever asked about it or passed it through — using it required
bypassing the command and invoking the `Workflow` tool by hand. Both
READMEs updated.

## 0.6.23 — 2026-07-22

Fixed:
Expand Down
4 changes: 3 additions & 1 deletion README.es.md
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,9 @@ node bin/parse-plan.js /ruta/a/tu-plan.md > /tmp/plan-graph.json
DAG. La tool `Workflow` de Claude Code ya encola las llamadas a `agent()` que exceden su
propio tope de `min(16, cores-2)`, así que esto sirve sobre todo para ir *más abajo* de
ese default — por ejemplo, para evitar muchos worktrees locales simultáneos en tu propia
máquina cuando un plan tiene una capa ancha de tareas independientes.
máquina cuando un plan tiene una capa ancha de tareas independientes. `/run-plan` y
`/cys:flow` te ofrecen configurarlo automáticamente cuando el ancho de paralelismo
inferido del plan supera 6 — no hace falta calcularlo a mano.

### El comando `/run-plan`

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,8 @@ node bin/parse-plan.js /path/to/your-plan.md > /tmp/plan-graph.json
Claude Code `Workflow` tool already queues excess `agent()` calls beyond its own
`min(16, cores-2)` cap, so this is mainly useful to go *lower* than that — e.g. to avoid
many simultaneous local git worktrees on your own machine for a plan with a wide layer of
independent tasks.
independent tasks. `/run-plan` and `/cys:flow` offer to set it for you when the parsed
plan's inferred parallel width exceeds 6 — you don't need to compute this by hand.

### The `/run-plan` slash command

Expand Down
4 changes: 2 additions & 2 deletions bin/parse-plan.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env node
import { readFileSync } from 'node:fs';
import { parsePlanWithDiagnostics } from '../src/plan-parser.js';
import { buildGraphWithDiagnostics } from '../src/graph-builder.js';
import { buildGraphWithDiagnostics, computeParallelWidth } from '../src/graph-builder.js';

const [, , planPath] = process.argv;
if (!planPath) {
Expand All @@ -19,4 +19,4 @@ for (const warning of warnings) {
console.error(`WARNING: ${warning}`);
}

console.log(JSON.stringify({ tasks, graph, warnings }, null, 2));
console.log(JSON.stringify({ tasks, graph, warnings, parallelWidth: computeParallelWidth(graph) }, null, 2));
8 changes: 6 additions & 2 deletions bin/plan-remainder.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { readFileSync, realpathSync } from 'node:fs';
import { resolve } from 'node:path';
import { parsePlanWithDiagnostics } from '../src/plan-parser.js';
import { buildGraphWithDiagnostics } from '../src/graph-builder.js';
import { buildGraphWithDiagnostics, computeParallelWidth } from '../src/graph-builder.js';

const [, , planPath, stateJsonPath] = process.argv;
if (!planPath || !stateJsonPath) {
Expand Down Expand Up @@ -63,4 +63,8 @@ const warnings = [...parseWarnings, ...graphWarnings];
for (const warning of warnings) {
console.error(`WARNING: ${warning}`);
}
console.log(JSON.stringify({ tasks: remainingTasks, graph: remainingGraph, warnings, allDone }, null, 2));
console.log(JSON.stringify(
{ tasks: remainingTasks, graph: remainingGraph, warnings, allDone, parallelWidth: computeParallelWidth(remainingGraph) },
null,
2
));
17 changes: 12 additions & 5 deletions commands/flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,18 @@ REPO = `${CLAUDE_PLUGIN_ROOT}`
"I authorize merging task-1 through task-N into feature/x"). Never
fabricate it; a bare "yes" is not enough — they name the branches.
Pass their words verbatim as `args.mergeAuthorization`.
- **Only if `parallelWidth` (from step 6's JSON) is greater than 6**: mention
that this plan can run up to that many tasks at once (worktrees +
subagents + merges all in parallel), and ask whether to cap it with
`maxConcurrency` — a positive integer, or leave unlimited (the
default). Don't ask for narrower plans; a low `parallelWidth` never
benefits from capping.

8. **Summarize and confirm**: plan path, repo, task count, parallelism
the graph shows, integration branch, PR settings, authorization text.
Re-check the working tree is still clean; if the integration branch
already exists, ask whether to continue on it or pick another name.
the graph shows, integration branch, PR settings, `maxConcurrency` if
set, authorization text. Re-check the working tree is still clean; if
the integration branch already exists, ask whether to continue on it
or pick another name.

9. **Create the integration branch if it doesn't exist**: run
`git -C <repo-path> show-ref --verify --quiet
Expand Down Expand Up @@ -107,8 +114,8 @@ REPO = `${CLAUDE_PLUGIN_ROOT}`
11. **Launch** the `Workflow` tool with:
- `scriptPath`: `REPO/workflows/parallel-plan-executor.js`
- `args`: `{ tasks, graph, planPath, repoPath, integrationBranch,
executorPath: REPO, openPr, pr, mergeAuthorization }` (omit the
optional ones not provided).
executorPath: REPO, openPr, pr, mergeAuthorization, maxConcurrency }`
(omit the optional ones not provided).

12. **After launching**: tell the user it runs in the background, that
they can ask "how's the workflow going?" or open `/workflows`, and
Expand Down
15 changes: 10 additions & 5 deletions commands/run-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,15 @@ REPO = `${CLAUDE_PLUGIN_ROOT}`
the branches themselves. Pass their words verbatim as `args.mergeAuthorization`. If
they decline to give one, proceed without it and mention that some merges may then
need authorizing individually mid-run.
- **Only if `parallelWidth` (from step 4's JSON) is greater than 6**: mention that
this plan can run up to that many tasks at once (worktrees + subagents + merges
all in parallel), and ask whether to cap it with `maxConcurrency` — a positive
integer, or leave unlimited (the default). Don't ask for narrower plans; a low
`parallelWidth` never benefits from capping.

6. **Summarize before launching**: plan path, repo, task count, integration branch,
openPr/PR settings, and confirm the authorization text with the user. This is a real
run against their repo — don't skip the confirmation.
openPr/PR settings, `maxConcurrency` if set, and confirm the authorization text with
the user. This is a real run against their repo — don't skip the confirmation.

7. **Create the integration branch if it doesn't exist** (skip if `allDone`
was `true` — the branch already has everything merged on it): run
Expand Down Expand Up @@ -107,10 +112,10 @@ REPO = `${CLAUDE_PLUGIN_ROOT}`

9. **Launch**: invoke the `Workflow` tool with:
- `scriptPath`: `<REPO>/workflows/parallel-plan-executor.js`
- `args`: if `allDone` was `true`, `{ tasks: [], graph: {}, planPath, repoPath, integrationBranch, executorPath: <REPO>, finishOnly: true, openPr, pr }` (no `mergeAuthorization` — nothing merges in this mode). Otherwise,
`{ tasks, graph, planPath, repoPath, integrationBranch, executorPath: <REPO>, openPr, pr, mergeAuthorization }`
- `args`: if `allDone` was `true`, `{ tasks: [], graph: {}, planPath, repoPath, integrationBranch, executorPath: <REPO>, finishOnly: true, openPr, pr }` (no `mergeAuthorization`/`maxConcurrency` — nothing runs in this mode). Otherwise,
`{ tasks, graph, planPath, repoPath, integrationBranch, executorPath: <REPO>, openPr, pr, mergeAuthorization, maxConcurrency }`
(executorPath is REPO — the workflow invokes REPO/bin scripts by exact path;
omit `openPr`/`pr`/`mergeAuthorization` if not provided)
omit `openPr`/`pr`/`mergeAuthorization`/`maxConcurrency` if not provided)

10. **After launching**: tell the user it's running in the background, mention they can
ask "how's the workflow going?" any time or open `/workflows`, and that you'll report
Expand Down
2 changes: 1 addition & 1 deletion gemini-extension.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cys",
"version": "0.6.23",
"version": "0.6.24",
"description": "Development methodology skills for design, plan, check, and ship — parallel plan execution (cys:run) is Claude Code only for now.",
"repository": "https://github.com/bacsystem/parallel-plan-executor",
"license": "MIT"
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "parallel-plan-executor",
"version": "0.6.23",
"version": "0.6.24",
"author": "Christian Bacilio",
"private": true,
"type": "module",
Expand Down
9 changes: 9 additions & 0 deletions tests/parse-plan-cli.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ test('CLI exits non-zero with a usage message when no path is given', () => {
assert.throws(() => execFileSync('node', [cliPath], { encoding: 'utf8' }));
});

test('el JSON incluye parallelWidth, para que los comandos decidan si vale la pena ofrecer maxConcurrency', () => {
const output = execFileSync('node', [cliPath, fixturePath], { encoding: 'utf8' });
const parsed = JSON.parse(output);

// fixtures/sample-plan.md: tasks 1 y 2 son independientes (capa 0, ancho 2), la tarea
// 3 depende de ambas (capa 1, ancho 1) — el ancho máximo del plan es 2.
assert.equal(parsed.parallelWidth, 2);
});

test('el comando publicado en examples/README.md imprime el grafo que ese README promete', () => {
// examples/README.md muestra `node bin/parse-plan.js examples/hello-parallel/plan.md`
// como el primer contacto de un dev nuevo con cys — si el CLI cambia de forma
Expand Down
16 changes: 16 additions & 0 deletions tests/plan-remainder.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,22 @@ test('allDone es false mientras quede algo pendiente o fallido', () => {
assert.equal(result.allDone, false);
});

test('el JSON incluye parallelWidth del grafo remanente, para que los comandos decidan si vale la pena ofrecer maxConcurrency al resumir', () => {
const { dir, planPath } = makeFixtures();
const statePath = writeState(dir, planPath, {
1: { status: 'done' },
2: { status: 'failed' },
3: { status: 'pending' },
});

const stdout = execFileSync('node', [cli, planPath, statePath], { encoding: 'utf8' });
const result = JSON.parse(stdout);

// Remanente: { 2: [], 3: [2] } — 2 no tiene dependencias pendientes, 3 depende de 2:
// ancho máximo 1 (nunca corren al mismo tiempo).
assert.equal(result.parallelWidth, 1);
});

test('falla ruidosamente sin args', () => {
assert.throws(() => execFileSync('node', [cli], { encoding: 'utf8', stdio: 'pipe' }));
});
15 changes: 15 additions & 0 deletions tests/skills.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,21 @@ test('los comandos detectan .cys/state.json de una corrida interrumpida (Fase 4b
);
});

test('los comandos ofrecen maxConcurrency cuando el plan es ancho, y lo pasan en el launch (hallazgo: runDag lo soporta desde 0.6.15 pero ningún comando lo preguntaba ni lo pasaba)', () => {
const flow = readFileSync(path.join(root, 'commands', 'flow.md'), 'utf8');
const runPlan = readFileSync(path.join(root, 'commands', 'run-plan.md'), 'utf8');
for (const [name, content] of [['flow.md', flow], ['run-plan.md', runPlan]]) {
assert.ok(
content.includes('parallelWidth') && content.includes('maxConcurrency'),
`commands/${name}: debe leer parallelWidth del JSON parseado y decidir si ofrecer maxConcurrency`
);
assert.ok(
content.includes('mergeAuthorization, maxConcurrency') || content.includes('mergeAuthorization,\n maxConcurrency'),
`commands/${name}: maxConcurrency debe llegar hasta los args del launch, no quedar solo preguntado`
);
}
});

test('run-plan.md maneja allDone lanzando con finishOnly en vez de fallar por tasks vacío (final review, hallazgo Important #2)', () => {
const runPlan = readFileSync(path.join(root, 'commands', 'run-plan.md'), 'utf8');
assert.ok(
Expand Down
Loading