From 5ca2a5872457351ea344e8dce9412599fcd078ea Mon Sep 17 00:00:00 2001 From: Bacsystem Solutions EIRL Date: Fri, 24 Jul 2026 12:30:38 -0500 Subject: [PATCH 1/3] feat(bin): expose parallelWidth in parse-plan.js and plan-remainder.js output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit maxConcurrency (runDag's optional concurrency cap, since 0.6.15) had no way to reach the interactive commands: /cys:run-plan and /cys:flow never saw the plan's inferred parallelism before launching, so they couldn't decide whether capping was worth offering. computeParallelWidth(graph) already existed (0.6.18, used only in the workflow's own final summary) — both CLIs now report it in their JSON so the commands can read it before the run starts. --- bin/parse-plan.js | 4 ++-- bin/plan-remainder.js | 8 ++++++-- tests/parse-plan-cli.test.js | 9 +++++++++ tests/plan-remainder.test.js | 16 ++++++++++++++++ 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/bin/parse-plan.js b/bin/parse-plan.js index 0fa3d72..eea5d9e 100644 --- a/bin/parse-plan.js +++ b/bin/parse-plan.js @@ -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) { @@ -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)); diff --git a/bin/plan-remainder.js b/bin/plan-remainder.js index f40222a..9e57517 100644 --- a/bin/plan-remainder.js +++ b/bin/plan-remainder.js @@ -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) { @@ -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 +)); diff --git a/tests/parse-plan-cli.test.js b/tests/parse-plan-cli.test.js index 27888b4..7b003b2 100644 --- a/tests/parse-plan-cli.test.js +++ b/tests/parse-plan-cli.test.js @@ -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 diff --git a/tests/plan-remainder.test.js b/tests/plan-remainder.test.js index 23b9dde..1b2a27c 100644 --- a/tests/plan-remainder.test.js +++ b/tests/plan-remainder.test.js @@ -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' })); }); From e6665c4f60b575b6d409519ed061334c55566ada Mon Sep 17 00:00:00 2001 From: Bacsystem Solutions EIRL Date: Fri, 24 Jul 2026 12:46:07 -0500 Subject: [PATCH 2/3] feat(commands): offer maxConcurrency automatically for wide plans MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit runDag has supported an optional maxConcurrency cap since 0.6.15, but neither /cys:run-plan nor /cys:flow ever asked about it or passed it through — a user had to bypass the command and invoke the Workflow tool by hand to use it at all. Both commands now read parallelWidth from the parsed plan's JSON (previous commit) and, only when it exceeds 6, mention that the plan can run that many tasks at once and ask whether to cap it with maxConcurrency. Narrower plans are never asked — a low parallelWidth never benefits from capping. The chosen value (or its absence) now flows through to the Workflow launch args in both commands. Documented in both READMEs. --- README.es.md | 4 +++- README.md | 3 ++- commands/flow.md | 17 ++++++++++++----- commands/run-plan.md | 15 ++++++++++----- tests/skills.test.js | 15 +++++++++++++++ 5 files changed, 42 insertions(+), 12 deletions(-) diff --git a/README.es.md b/README.es.md index 2b1e925..26580bd 100644 --- a/README.es.md +++ b/README.es.md @@ -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` diff --git a/README.md b/README.md index c5e9635..cd334ea 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/commands/flow.md b/commands/flow.md index e11262f..d0f1b73 100644 --- a/commands/flow.md +++ b/commands/flow.md @@ -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 show-ref --verify --quiet @@ -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 diff --git a/commands/run-plan.md b/commands/run-plan.md index 6c4b835..f80ba85 100644 --- a/commands/run-plan.md +++ b/commands/run-plan.md @@ -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 @@ -107,10 +112,10 @@ REPO = `${CLAUDE_PLUGIN_ROOT}` 9. **Launch**: invoke the `Workflow` tool with: - `scriptPath`: `/workflows/parallel-plan-executor.js` - - `args`: if `allDone` was `true`, `{ tasks: [], graph: {}, planPath, repoPath, integrationBranch, executorPath: , finishOnly: true, openPr, pr }` (no `mergeAuthorization` — nothing merges in this mode). Otherwise, - `{ tasks, graph, planPath, repoPath, integrationBranch, executorPath: , openPr, pr, mergeAuthorization }` + - `args`: if `allDone` was `true`, `{ tasks: [], graph: {}, planPath, repoPath, integrationBranch, executorPath: , finishOnly: true, openPr, pr }` (no `mergeAuthorization`/`maxConcurrency` — nothing runs in this mode). Otherwise, + `{ tasks, graph, planPath, repoPath, integrationBranch, executorPath: , 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 diff --git a/tests/skills.test.js b/tests/skills.test.js index 303a67b..cfe43cc 100644 --- a/tests/skills.test.js +++ b/tests/skills.test.js @@ -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( From 0fa854aa65d909380f691b6ea75d5e36592e1a44 Mon Sep 17 00:00:00 2001 From: Bacsystem Solutions EIRL Date: Fri, 24 Jul 2026 12:51:26 -0500 Subject: [PATCH 3/3] chore(release): bump version to 0.6.24 --- .claude-plugin/plugin.json | 2 +- .cursor-plugin/plugin.json | 2 +- CHANGELOG.md | 19 +++++++++++++++++++ gemini-extension.json | 2 +- package.json | 2 +- 5 files changed, 23 insertions(+), 4 deletions(-) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 9d2c8f0..4f423e5 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -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", diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index 615f034..a9982e9 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -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", diff --git a/CHANGELOG.md b/CHANGELOG.md index 56286eb..08ce6d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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: diff --git a/gemini-extension.json b/gemini-extension.json index 285139d..3d7fc08 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -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" diff --git a/package.json b/package.json index d1a7c7f..724e369 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parallel-plan-executor", - "version": "0.6.23", + "version": "0.6.24", "author": "Christian Bacilio", "private": true, "type": "module",