nightralph 0.0.8 → 0.0.10
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.
- package/README.md +10 -1
- package/dist/demo-display.js +298 -0
- package/dist/display.js +280 -0
- package/dist/display.js.map +7 -0
- package/dist/index.js +6 -2
- package/dist/index.js.map +2 -2
- package/dist/meta.json +49 -8
- package/dist/orchestrator.js +158 -58
- package/dist/orchestrator.js.map +2 -2
- package/dist/src/display.d.ts +69 -0
- package/dist/src/display.d.ts.map +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/orchestrator.d.ts +11 -2
- package/dist/src/orchestrator.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["#!/usr/bin/env node\n\nimport { existsSync, readFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport yargs from 'yargs'\nimport { hideBin } from 'yargs/helpers'\nimport { select } from '@inquirer/prompts'\nimport { setup, checkGhAuth, type TrackerKind } from './setup.js'\nimport {\n scanTickets,\n dryRun,\n dryRunIsolated,\n runWaves,\n runWavesIsolated,\n} from './orchestrator.js'\nimport {\n findFeatureDirs,\n resolveIssuesDir,\n getScriptDir,\n} from './resolve.js'\nimport { getRepoRoot, getRepoName } from './worktree.js'\n\nexport { findFeatureDirs, resolveIssuesDir }\n\nconst scriptDir = getScriptDir(import.meta.url)\n\nyargs(hideBin(process.argv))\n .scriptName('nightralph')\n .usage('$0 <command>')\n .command(\n 'setup',\n 'Install bundled skills and doc templates',\n y => y.option('claude', {\n type:'boolean',\n describe:'Also create .claude/skills/ symlinks',\n default:false,\n }),\n async argv => {\n const tracker = await select<TrackerKind>({\n message:'Issue tracker format',\n choices:[\n {\n name:'Local Markdown',\n value:'local' as const,\n description:'Issues as .md files in ' +\n '.scratch/',\n },\n {\n name:'GitHub Issues',\n value:'github' as const,\n description:'Issues via gh CLI',\n },\n ],\n })\n\n if (tracker === 'github') {\n if (!checkGhAuth()) {\n console.error(\n 'GitHub Issues requires the gh CLI, ' +\n 'authenticated.\\n' +\n 'Install: https://cli.github.com\\n' +\n 'Then run: gh auth login'\n )\n process.exit(2)\n }\n }\n\n setup({\n bundledSkillsDir:join(scriptDir, 'skills'),\n docsTemplatesDir:join(\n scriptDir, 'docs-templates'\n ),\n projectDir:process.cwd(),\n claude:argv.claude,\n tracker,\n })\n }\n )\n .command(\n '$0 <provider>',\n 'Execute tickets with the given provider',\n y => y\n .positional('provider', {\n type:'string',\n describe:'Agent CLI to spawn ' +\n '(e.g. claude, codex, pi)',\n demandOption:true,\n })\n .option('m', {\n alias:'model',\n type:'string',\n describe:'Model passed to the agent',\n })\n .option('spec', {\n type:'string',\n describe:'Feature name ' +\n '(resolves .scratch/<name>/)',\n })\n .option('dry-run', {\n type:'boolean',\n describe:'Show wave order without running',\n default:false,\n })\n .option('timeout', {\n type:'number',\n describe:'Kill agent after N seconds',\n default:900,\n })\n .option('X', {\n type:'boolean',\n describe:'Stop on merge conflict instead of ' +\n 're-spawning',\n default:false,\n }),\n async argv => {\n await runExecute({\n provider:argv.provider as string,\n model:argv.model as string|undefined,\n spec:argv.spec as string|undefined,\n dryRun:argv.dryRun as boolean|undefined,\n timeout:argv.timeout as\n number|undefined,\n stopOnConflict:argv.X as\n boolean|undefined,\n })\n }\n )\n .strict()\n .demandCommand(1, 'Specify a command or provider')\n .help()\n .parse()\n\nfunction isGitRepo ():boolean {\n try {\n getRepoRoot()\n return true\n } catch {\n return false\n }\n}\n\nfunction extractFeatureName (spec:string):string {\n const parts = spec.split(\n /[\\\\/]/\n )\n const scratchIndex = parts.indexOf('.scratch')\n if (scratchIndex >= 0 && scratchIndex < parts.length - 1) {\n return parts[scratchIndex + 1]\n }\n return parts[parts.length - 2] ??\n parts[parts.length - 1] ?? 'feature'\n}\n\nasync function runExecute (argv:{\n provider:string\n model?:string\n spec?:string\n dryRun?:boolean\n timeout?:number\n stopOnConflict?:boolean\n}):Promise<void> {\n const { dir, spec } = await resolveIssuesDir(\n argv.spec\n )\n\n if (!existsSync(dir)) {\n console.error(`Directory not found: ${dir}`)\n process.exit(2)\n }\n if (!existsSync(spec)) {\n console.error(`Spec file not found: ${spec}`)\n process.exit(2)\n }\n\n const specBody = readFileSync(spec, 'utf8')\n const tickets = scanTickets(dir)\n\n if (tickets.length === 0) {\n console.log('No ticket files found. Exiting.')\n return\n }\n\n console.log(`Found ${tickets.length} tickets`)\n\n const knownNums = new Set(tickets.map(t => t.num))\n for (const t of tickets) {\n const unknown = t.blockers.filter(\n b => !knownNums.has(b)\n )\n if (unknown.length > 0) {\n console.warn(\n `${t.filename}: blockers ` +\n `[${unknown.join(', ')}] ` +\n 'not found in directory'\n )\n }\n t.blockers = t.blockers.filter(\n b => knownNums.has(b)\n )\n }\n\n const logsDir = join(dirname(dir), 'logs')\n const timeout = argv.timeout ?? 300\n const featureName = extractFeatureName(spec)\n const conflictStrategy = argv.stopOnConflict ?\n 'stop' :\n 'respawn'\n const progressPath = join(\n dirname(dir), 'progress.md'\n )\n\n if (argv.dryRun) {\n if (isGitRepo()) {\n const repoName = getRepoName()\n dryRunIsolated({\n tickets,\n specBody,\n repoName,\n conflictStrategy,\n progressPath,\n })\n } else {\n dryRun(tickets, specBody)\n }\n return\n }\n\n let result:{ allDone:boolean }\n\n if (isGitRepo()) {\n result = await runWavesIsolated({\n tickets,\n specBody,\n agentCmd:argv.provider,\n model:argv.model ?? null,\n timeout,\n logsDir,\n featureName,\n conflictStrategy,\n })\n } else {\n result = await runWaves(\n tickets, specBody,\n argv.provider, argv.model ?? null,\n timeout,\n logsDir,\n )\n }\n\n if (!result.allDone) {\n process.exit(1)\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;AAEA,SAAS,YAAY,oBAAoB;AACzC,SAAS,SAAS,YAAY;AAC9B,OAAO,WAAW;AAClB,SAAS,eAAe;AACxB,SAAS,cAAc;AACvB,SAAS,OAAO,mBAAqC;AACrD;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACG;AACP;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,OACG;AACP,SAAS,aAAa,mBAAmB;AAIzC,MAAM,YAAY,aAAa,YAAY,GAAG;AAE9C,MAAM,QAAQ,QAAQ,IAAI,CAAC,EACtB,WAAW,YAAY,EACvB,MAAM,cAAc,EACpB;AAAA,EACG;AAAA,EACA;AAAA,EACA,OAAK,EAAE,OAAO,UAAU;AAAA,IACpB,MAAK;AAAA,IACL,UAAS;AAAA,IACT,SAAQ;AAAA,EACZ,CAAC;AAAA,EACD,OAAM,SAAQ;AACV,UAAM,UAAU,MAAM,OAAoB;AAAA,MACtC,SAAQ;AAAA,MACR,SAAQ;AAAA,QACJ;AAAA,UACI,MAAK;AAAA,UACL,OAAM;AAAA,UACN,aAAY;AAAA,QAEhB;AAAA,QACA;AAAA,UACI,MAAK;AAAA,UACL,OAAM;AAAA,UACN,aAAY;AAAA,QAChB;AAAA,MACJ;AAAA,IACJ,CAAC;AAED,QAAI,YAAY,UAAU;AACtB,UAAI,CAAC,YAAY,GAAG;AAChB,gBAAQ;AAAA,UACJ;AAAA,QAIJ;AACA,gBAAQ,KAAK,CAAC;AAAA,MAClB;AAAA,IACJ;AAEA,UAAM;AAAA,MACF,kBAAiB,KAAK,WAAW,QAAQ;AAAA,MACzC,kBAAiB;AAAA,QACb;AAAA,QAAW;AAAA,MACf;AAAA,MACA,YAAW,QAAQ,IAAI;AAAA,MACvB,QAAO,KAAK;AAAA,MACZ;AAAA,IACJ,CAAC;AAAA,EACL;AACJ,EACC;AAAA,EACG;AAAA,EACA;AAAA,EACA,OAAK,EACA,WAAW,YAAY;AAAA,IACpB,MAAK;AAAA,IACL,UAAS;AAAA,IAET,cAAa;AAAA,EACjB,CAAC,EACA,OAAO,KAAK;AAAA,IACT,OAAM;AAAA,IACN,MAAK;AAAA,IACL,UAAS;AAAA,EACb,CAAC,EACA,OAAO,QAAQ;AAAA,IACZ,MAAK;AAAA,IACL,UAAS;AAAA,EAEb,CAAC,EACA,OAAO,WAAW;AAAA,IACf,MAAK;AAAA,IACL,UAAS;AAAA,IACT,SAAQ;AAAA,EACZ,CAAC,EACA,OAAO,WAAW;AAAA,IACf,MAAK;AAAA,IACL,UAAS;AAAA,IACT,SAAQ;AAAA,EACZ,CAAC,EACA,OAAO,KAAK;AAAA,IACT,MAAK;AAAA,IACL,UAAS;AAAA,IAET,SAAQ;AAAA,EACZ,CAAC;AAAA,EACL,OAAM,SAAQ;AACV,UAAM,WAAW;AAAA,MACb,UAAS,KAAK;AAAA,MACd,OAAM,KAAK;AAAA,MACX,MAAK,KAAK;AAAA,MACV,QAAO,KAAK;AAAA,MACZ,SAAQ,KAAK;AAAA,MAEb,gBAAe,KAAK;AAAA,IAExB,CAAC;AAAA,EACL;AACJ,EACC,OAAO,EACP,cAAc,GAAG,+BAA+B,EAChD,KAAK,EACL,MAAM;AAEX,SAAS,YAAqB;AAC1B,MAAI;AACA,gBAAY;AACZ,WAAO;AAAA,EACX,QAAQ;AACJ,WAAO;AAAA,EACX;AACJ;AAPS;AAST,SAAS,mBAAoB,MAAoB;AAC7C,QAAM,QAAQ,KAAK;AAAA,IACf;AAAA,EACJ;AACA,QAAM,eAAe,MAAM,QAAQ,UAAU;AAC7C,MAAI,gBAAgB,KAAK,eAAe,MAAM,SAAS,GAAG;AACtD,WAAO,MAAM,eAAe,CAAC;AAAA,EACjC;AACA,SAAO,MAAM,MAAM,SAAS,CAAC,KACzB,MAAM,MAAM,SAAS,CAAC,KAAK;AACnC;AAVS;AAYT,eAAe,WAAY,MAOV;AACb,QAAM,EAAE,KAAK,KAAK,IAAI,MAAM;AAAA,IACxB,KAAK;AAAA,EACT;AAEA,MAAI,CAAC,WAAW,GAAG,GAAG;AAClB,YAAQ,MAAM,wBAAwB,GAAG,EAAE;AAC3C,YAAQ,KAAK,CAAC;AAAA,EAClB;AACA,MAAI,CAAC,WAAW,IAAI,GAAG;AACnB,YAAQ,MAAM,wBAAwB,IAAI,EAAE;AAC5C,YAAQ,KAAK,CAAC;AAAA,EAClB;AAEA,QAAM,WAAW,aAAa,MAAM,MAAM;AAC1C,QAAM,UAAU,YAAY,GAAG;AAE/B,MAAI,QAAQ,WAAW,GAAG;AACtB,YAAQ,IAAI,iCAAiC;AAC7C;AAAA,EACJ;AAEA,UAAQ,IAAI,SAAS,QAAQ,MAAM,UAAU;AAE7C,QAAM,YAAY,IAAI,IAAI,QAAQ,IAAI,OAAK,EAAE,GAAG,CAAC;AACjD,aAAW,KAAK,SAAS;AACrB,UAAM,UAAU,EAAE,SAAS;AAAA,MACvB,OAAK,CAAC,UAAU,IAAI,CAAC;AAAA,IACzB;AACA,QAAI,QAAQ,SAAS,GAAG;AACpB,cAAQ;AAAA,QACJ,GAAG,EAAE,QAAQ,eACT,QAAQ,KAAK,IAAI,CAAC;AAAA,MAE1B;AAAA,IACJ;AACA,MAAE,WAAW,EAAE,SAAS;AAAA,MACpB,OAAK,UAAU,IAAI,CAAC;AAAA,IACxB;AAAA,EACJ;AAEA,QAAM,UAAU,KAAK,QAAQ,GAAG,GAAG,MAAM;AACzC,QAAM,UAAU,KAAK,WAAW;AAChC,QAAM,cAAc,mBAAmB,IAAI;AAC3C,QAAM,mBAAmB,KAAK,iBAC1B,SACA;AACJ,QAAM,eAAe;AAAA,IACjB,QAAQ,GAAG;AAAA,IAAG;AAAA,EAClB;AAEA,MAAI,KAAK,QAAQ;AACb,QAAI,UAAU,GAAG;AACb,YAAM,WAAW,YAAY;AAC7B,qBAAe;AAAA,QACX;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACJ,CAAC;AAAA,IACL,OAAO;AACH,aAAO,SAAS,QAAQ;AAAA,IAC5B;AACA;AAAA,EACJ;AAEA,MAAI;AAEJ,MAAI,UAAU,GAAG;AACb,aAAS,MAAM,iBAAiB;AAAA,MAC5B;AAAA,MACA;AAAA,MACA,UAAS,KAAK;AAAA,MACd,OAAM,KAAK,SAAS;AAAA,MACpB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACJ,CAAC;AAAA,EACL,OAAO;AACH,aAAS,MAAM;AAAA,MACX;AAAA,MAAS;AAAA,MACT,KAAK;AAAA,MAAU,KAAK,SAAS;AAAA,MAC7B;AAAA,MACA;AAAA,IACJ;AAAA,EACJ;AAEA,MAAI,CAAC,OAAO,SAAS;AACjB,YAAQ,KAAK,CAAC;AAAA,EAClB;AACJ;
|
|
4
|
+
"sourcesContent": ["#!/usr/bin/env node\n\nimport { existsSync, readFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport yargs from 'yargs'\nimport { hideBin } from 'yargs/helpers'\nimport { select } from '@inquirer/prompts'\nimport { setup, checkGhAuth, type TrackerKind } from './setup.js'\nimport {\n scanTickets,\n dryRun,\n dryRunIsolated,\n runWaves,\n runWavesIsolated,\n} from './orchestrator.js'\nimport { createDisplay } from './display.js'\nimport {\n findFeatureDirs,\n resolveIssuesDir,\n getScriptDir,\n} from './resolve.js'\nimport { getRepoRoot, getRepoName } from './worktree.js'\n\nexport { findFeatureDirs, resolveIssuesDir }\n\nconst scriptDir = getScriptDir(import.meta.url)\n\nyargs(hideBin(process.argv))\n .scriptName('nightralph')\n .usage('$0 <command>')\n .command(\n 'setup',\n 'Install bundled skills and doc templates',\n y => y.option('claude', {\n type:'boolean',\n describe:'Also create .claude/skills/ symlinks',\n default:false,\n }),\n async argv => {\n const tracker = await select<TrackerKind>({\n message:'Issue tracker format',\n choices:[\n {\n name:'Local Markdown',\n value:'local' as const,\n description:'Issues as .md files in ' +\n '.scratch/',\n },\n {\n name:'GitHub Issues',\n value:'github' as const,\n description:'Issues via gh CLI',\n },\n ],\n })\n\n if (tracker === 'github') {\n if (!checkGhAuth()) {\n console.error(\n 'GitHub Issues requires the gh CLI, ' +\n 'authenticated.\\n' +\n 'Install: https://cli.github.com\\n' +\n 'Then run: gh auth login'\n )\n process.exit(2)\n }\n }\n\n setup({\n bundledSkillsDir:join(scriptDir, 'skills'),\n docsTemplatesDir:join(\n scriptDir, 'docs-templates'\n ),\n projectDir:process.cwd(),\n claude:argv.claude,\n tracker,\n })\n }\n )\n .command(\n '$0 <provider>',\n 'Execute tickets with the given provider',\n y => y\n .positional('provider', {\n type:'string',\n describe:'Agent CLI to spawn ' +\n '(e.g. claude, codex, pi)',\n demandOption:true,\n })\n .option('m', {\n alias:'model',\n type:'string',\n describe:'Model passed to the agent',\n })\n .option('spec', {\n type:'string',\n describe:'Feature name ' +\n '(resolves .scratch/<name>/)',\n })\n .option('dry-run', {\n type:'boolean',\n describe:'Show wave order without running',\n default:false,\n })\n .option('timeout', {\n type:'number',\n describe:'Kill agent after N seconds',\n default:900,\n })\n .option('X', {\n type:'boolean',\n describe:'Stop on merge conflict instead of ' +\n 're-spawning',\n default:false,\n }),\n async argv => {\n await runExecute({\n provider:argv.provider as string,\n model:argv.model as string|undefined,\n spec:argv.spec as string|undefined,\n dryRun:argv.dryRun as boolean|undefined,\n timeout:argv.timeout as\n number|undefined,\n stopOnConflict:argv.X as\n boolean|undefined,\n })\n }\n )\n .strict()\n .demandCommand(1, 'Specify a command or provider')\n .help()\n .parse()\n\nfunction isGitRepo ():boolean {\n try {\n getRepoRoot()\n return true\n } catch {\n return false\n }\n}\n\nfunction extractFeatureName (spec:string):string {\n const parts = spec.split(\n /[\\\\/]/\n )\n const scratchIndex = parts.indexOf('.scratch')\n if (scratchIndex >= 0 && scratchIndex < parts.length - 1) {\n return parts[scratchIndex + 1]\n }\n return parts[parts.length - 2] ??\n parts[parts.length - 1] ?? 'feature'\n}\n\nasync function runExecute (argv:{\n provider:string\n model?:string\n spec?:string\n dryRun?:boolean\n timeout?:number\n stopOnConflict?:boolean\n}):Promise<void> {\n const { dir, spec } = await resolveIssuesDir(\n argv.spec\n )\n\n if (!existsSync(dir)) {\n console.error(`Directory not found: ${dir}`)\n process.exit(2)\n }\n if (!existsSync(spec)) {\n console.error(`Spec file not found: ${spec}`)\n process.exit(2)\n }\n\n const specBody = readFileSync(spec, 'utf8')\n const tickets = scanTickets(dir)\n\n if (tickets.length === 0) {\n console.log('No ticket files found. Exiting.')\n return\n }\n\n console.log(`Found ${tickets.length} tickets`)\n\n const knownNums = new Set(tickets.map(t => t.num))\n for (const t of tickets) {\n const unknown = t.blockers.filter(\n b => !knownNums.has(b)\n )\n if (unknown.length > 0) {\n console.warn(\n `${t.filename}: blockers ` +\n `[${unknown.join(', ')}] ` +\n 'not found in directory'\n )\n }\n t.blockers = t.blockers.filter(\n b => knownNums.has(b)\n )\n }\n\n const logsDir = join(dirname(dir), 'logs')\n const timeout = argv.timeout ?? 300\n const featureName = extractFeatureName(spec)\n const conflictStrategy = argv.stopOnConflict ?\n 'stop' :\n 'respawn'\n const progressPath = join(\n dirname(dir), 'progress.md'\n )\n\n if (argv.dryRun) {\n if (isGitRepo()) {\n const repoName = getRepoName()\n dryRunIsolated({\n tickets,\n specBody,\n repoName,\n conflictStrategy,\n progressPath,\n })\n } else {\n dryRun(tickets, specBody)\n }\n return\n }\n\n const display = createDisplay()\n let result:{ allDone:boolean }\n\n if (isGitRepo()) {\n result = await runWavesIsolated({\n tickets,\n specBody,\n agentCmd:argv.provider,\n model:argv.model ?? null,\n timeout,\n logsDir,\n featureName,\n conflictStrategy,\n display,\n })\n } else {\n result = await runWaves(\n tickets, specBody,\n argv.provider, argv.model ?? null,\n timeout,\n logsDir,\n display,\n )\n }\n\n if (!result.allDone) {\n process.exit(1)\n }\n}\n"],
|
|
5
|
+
"mappings": ";;;AAEA,SAAS,YAAY,oBAAoB;AACzC,SAAS,SAAS,YAAY;AAC9B,OAAO,WAAW;AAClB,SAAS,eAAe;AACxB,SAAS,cAAc;AACvB,SAAS,OAAO,mBAAqC;AACrD;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACG;AACP,SAAS,qBAAqB;AAC9B;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,OACG;AACP,SAAS,aAAa,mBAAmB;AAIzC,MAAM,YAAY,aAAa,YAAY,GAAG;AAE9C,MAAM,QAAQ,QAAQ,IAAI,CAAC,EACtB,WAAW,YAAY,EACvB,MAAM,cAAc,EACpB;AAAA,EACG;AAAA,EACA;AAAA,EACA,OAAK,EAAE,OAAO,UAAU;AAAA,IACpB,MAAK;AAAA,IACL,UAAS;AAAA,IACT,SAAQ;AAAA,EACZ,CAAC;AAAA,EACD,OAAM,SAAQ;AACV,UAAM,UAAU,MAAM,OAAoB;AAAA,MACtC,SAAQ;AAAA,MACR,SAAQ;AAAA,QACJ;AAAA,UACI,MAAK;AAAA,UACL,OAAM;AAAA,UACN,aAAY;AAAA,QAEhB;AAAA,QACA;AAAA,UACI,MAAK;AAAA,UACL,OAAM;AAAA,UACN,aAAY;AAAA,QAChB;AAAA,MACJ;AAAA,IACJ,CAAC;AAED,QAAI,YAAY,UAAU;AACtB,UAAI,CAAC,YAAY,GAAG;AAChB,gBAAQ;AAAA,UACJ;AAAA,QAIJ;AACA,gBAAQ,KAAK,CAAC;AAAA,MAClB;AAAA,IACJ;AAEA,UAAM;AAAA,MACF,kBAAiB,KAAK,WAAW,QAAQ;AAAA,MACzC,kBAAiB;AAAA,QACb;AAAA,QAAW;AAAA,MACf;AAAA,MACA,YAAW,QAAQ,IAAI;AAAA,MACvB,QAAO,KAAK;AAAA,MACZ;AAAA,IACJ,CAAC;AAAA,EACL;AACJ,EACC;AAAA,EACG;AAAA,EACA;AAAA,EACA,OAAK,EACA,WAAW,YAAY;AAAA,IACpB,MAAK;AAAA,IACL,UAAS;AAAA,IAET,cAAa;AAAA,EACjB,CAAC,EACA,OAAO,KAAK;AAAA,IACT,OAAM;AAAA,IACN,MAAK;AAAA,IACL,UAAS;AAAA,EACb,CAAC,EACA,OAAO,QAAQ;AAAA,IACZ,MAAK;AAAA,IACL,UAAS;AAAA,EAEb,CAAC,EACA,OAAO,WAAW;AAAA,IACf,MAAK;AAAA,IACL,UAAS;AAAA,IACT,SAAQ;AAAA,EACZ,CAAC,EACA,OAAO,WAAW;AAAA,IACf,MAAK;AAAA,IACL,UAAS;AAAA,IACT,SAAQ;AAAA,EACZ,CAAC,EACA,OAAO,KAAK;AAAA,IACT,MAAK;AAAA,IACL,UAAS;AAAA,IAET,SAAQ;AAAA,EACZ,CAAC;AAAA,EACL,OAAM,SAAQ;AACV,UAAM,WAAW;AAAA,MACb,UAAS,KAAK;AAAA,MACd,OAAM,KAAK;AAAA,MACX,MAAK,KAAK;AAAA,MACV,QAAO,KAAK;AAAA,MACZ,SAAQ,KAAK;AAAA,MAEb,gBAAe,KAAK;AAAA,IAExB,CAAC;AAAA,EACL;AACJ,EACC,OAAO,EACP,cAAc,GAAG,+BAA+B,EAChD,KAAK,EACL,MAAM;AAEX,SAAS,YAAqB;AAC1B,MAAI;AACA,gBAAY;AACZ,WAAO;AAAA,EACX,QAAQ;AACJ,WAAO;AAAA,EACX;AACJ;AAPS;AAST,SAAS,mBAAoB,MAAoB;AAC7C,QAAM,QAAQ,KAAK;AAAA,IACf;AAAA,EACJ;AACA,QAAM,eAAe,MAAM,QAAQ,UAAU;AAC7C,MAAI,gBAAgB,KAAK,eAAe,MAAM,SAAS,GAAG;AACtD,WAAO,MAAM,eAAe,CAAC;AAAA,EACjC;AACA,SAAO,MAAM,MAAM,SAAS,CAAC,KACzB,MAAM,MAAM,SAAS,CAAC,KAAK;AACnC;AAVS;AAYT,eAAe,WAAY,MAOV;AACb,QAAM,EAAE,KAAK,KAAK,IAAI,MAAM;AAAA,IACxB,KAAK;AAAA,EACT;AAEA,MAAI,CAAC,WAAW,GAAG,GAAG;AAClB,YAAQ,MAAM,wBAAwB,GAAG,EAAE;AAC3C,YAAQ,KAAK,CAAC;AAAA,EAClB;AACA,MAAI,CAAC,WAAW,IAAI,GAAG;AACnB,YAAQ,MAAM,wBAAwB,IAAI,EAAE;AAC5C,YAAQ,KAAK,CAAC;AAAA,EAClB;AAEA,QAAM,WAAW,aAAa,MAAM,MAAM;AAC1C,QAAM,UAAU,YAAY,GAAG;AAE/B,MAAI,QAAQ,WAAW,GAAG;AACtB,YAAQ,IAAI,iCAAiC;AAC7C;AAAA,EACJ;AAEA,UAAQ,IAAI,SAAS,QAAQ,MAAM,UAAU;AAE7C,QAAM,YAAY,IAAI,IAAI,QAAQ,IAAI,OAAK,EAAE,GAAG,CAAC;AACjD,aAAW,KAAK,SAAS;AACrB,UAAM,UAAU,EAAE,SAAS;AAAA,MACvB,OAAK,CAAC,UAAU,IAAI,CAAC;AAAA,IACzB;AACA,QAAI,QAAQ,SAAS,GAAG;AACpB,cAAQ;AAAA,QACJ,GAAG,EAAE,QAAQ,eACT,QAAQ,KAAK,IAAI,CAAC;AAAA,MAE1B;AAAA,IACJ;AACA,MAAE,WAAW,EAAE,SAAS;AAAA,MACpB,OAAK,UAAU,IAAI,CAAC;AAAA,IACxB;AAAA,EACJ;AAEA,QAAM,UAAU,KAAK,QAAQ,GAAG,GAAG,MAAM;AACzC,QAAM,UAAU,KAAK,WAAW;AAChC,QAAM,cAAc,mBAAmB,IAAI;AAC3C,QAAM,mBAAmB,KAAK,iBAC1B,SACA;AACJ,QAAM,eAAe;AAAA,IACjB,QAAQ,GAAG;AAAA,IAAG;AAAA,EAClB;AAEA,MAAI,KAAK,QAAQ;AACb,QAAI,UAAU,GAAG;AACb,YAAM,WAAW,YAAY;AAC7B,qBAAe;AAAA,QACX;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACJ,CAAC;AAAA,IACL,OAAO;AACH,aAAO,SAAS,QAAQ;AAAA,IAC5B;AACA;AAAA,EACJ;AAEA,QAAM,UAAU,cAAc;AAC9B,MAAI;AAEJ,MAAI,UAAU,GAAG;AACb,aAAS,MAAM,iBAAiB;AAAA,MAC5B;AAAA,MACA;AAAA,MACA,UAAS,KAAK;AAAA,MACd,OAAM,KAAK,SAAS;AAAA,MACpB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACJ,CAAC;AAAA,EACL,OAAO;AACH,aAAS,MAAM;AAAA,MACX;AAAA,MAAS;AAAA,MACT,KAAK;AAAA,MAAU,KAAK,SAAS;AAAA,MAC7B;AAAA,MACA;AAAA,MACA;AAAA,IACJ;AAAA,EACJ;AAEA,MAAI,CAAC,OAAO,SAAS;AACjB,YAAQ,KAAK,CAAC;AAAA,EAClB;AACJ;AAtGe;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/meta.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"inputs": {
|
|
3
3
|
"src/index.ts": {
|
|
4
|
-
"bytes":
|
|
4
|
+
"bytes": 7134,
|
|
5
5
|
"imports": [],
|
|
6
6
|
"format": "esm"
|
|
7
7
|
},
|
|
8
8
|
"src/orchestrator.ts": {
|
|
9
|
-
"bytes":
|
|
9
|
+
"bytes": 33865,
|
|
10
10
|
"imports": [],
|
|
11
11
|
"format": "esm"
|
|
12
12
|
},
|
|
@@ -34,6 +34,11 @@
|
|
|
34
34
|
"bytes": 6529,
|
|
35
35
|
"imports": [],
|
|
36
36
|
"format": "esm"
|
|
37
|
+
},
|
|
38
|
+
"src/display.ts": {
|
|
39
|
+
"bytes": 10792,
|
|
40
|
+
"imports": [],
|
|
41
|
+
"format": "esm"
|
|
37
42
|
}
|
|
38
43
|
},
|
|
39
44
|
"outputs": {
|
|
@@ -41,7 +46,7 @@
|
|
|
41
46
|
"imports": [],
|
|
42
47
|
"exports": [],
|
|
43
48
|
"inputs": {},
|
|
44
|
-
"bytes":
|
|
49
|
+
"bytes": 11126
|
|
45
50
|
},
|
|
46
51
|
"dist/index.js": {
|
|
47
52
|
"imports": [
|
|
@@ -80,6 +85,11 @@
|
|
|
80
85
|
"kind": "import-statement",
|
|
81
86
|
"external": true
|
|
82
87
|
},
|
|
88
|
+
{
|
|
89
|
+
"path": "./display.js",
|
|
90
|
+
"kind": "import-statement",
|
|
91
|
+
"external": true
|
|
92
|
+
},
|
|
83
93
|
{
|
|
84
94
|
"path": "./resolve.js",
|
|
85
95
|
"kind": "import-statement",
|
|
@@ -98,16 +108,16 @@
|
|
|
98
108
|
"entryPoint": "src/index.ts",
|
|
99
109
|
"inputs": {
|
|
100
110
|
"src/index.ts": {
|
|
101
|
-
"bytesInOutput":
|
|
111
|
+
"bytesInOutput": 5338
|
|
102
112
|
}
|
|
103
113
|
},
|
|
104
|
-
"bytes":
|
|
114
|
+
"bytes": 5571
|
|
105
115
|
},
|
|
106
116
|
"dist/orchestrator.js.map": {
|
|
107
117
|
"imports": [],
|
|
108
118
|
"exports": [],
|
|
109
119
|
"inputs": {},
|
|
110
|
-
"bytes":
|
|
120
|
+
"bytes": 52948
|
|
111
121
|
},
|
|
112
122
|
"dist/orchestrator.js": {
|
|
113
123
|
"imports": [
|
|
@@ -150,10 +160,17 @@
|
|
|
150
160
|
"path": "./merge.js",
|
|
151
161
|
"kind": "import-statement",
|
|
152
162
|
"external": true
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"path": "./display.js",
|
|
166
|
+
"kind": "import-statement",
|
|
167
|
+
"external": true
|
|
153
168
|
}
|
|
154
169
|
],
|
|
155
170
|
"exports": [
|
|
171
|
+
"COMPLETED_RE",
|
|
156
172
|
"TICKET_RE",
|
|
173
|
+
"checkItems",
|
|
157
174
|
"createPiStreamFormatter",
|
|
158
175
|
"dryRun",
|
|
159
176
|
"dryRunIsolated",
|
|
@@ -174,10 +191,10 @@
|
|
|
174
191
|
"entryPoint": "src/orchestrator.ts",
|
|
175
192
|
"inputs": {
|
|
176
193
|
"src/orchestrator.ts": {
|
|
177
|
-
"bytesInOutput":
|
|
194
|
+
"bytesInOutput": 26158
|
|
178
195
|
}
|
|
179
196
|
},
|
|
180
|
-
"bytes":
|
|
197
|
+
"bytes": 26662
|
|
181
198
|
},
|
|
182
199
|
"dist/setup.js.map": {
|
|
183
200
|
"imports": [],
|
|
@@ -381,6 +398,30 @@
|
|
|
381
398
|
}
|
|
382
399
|
},
|
|
383
400
|
"bytes": 4616
|
|
401
|
+
},
|
|
402
|
+
"dist/display.js.map": {
|
|
403
|
+
"imports": [],
|
|
404
|
+
"exports": [],
|
|
405
|
+
"inputs": {},
|
|
406
|
+
"bytes": 16702
|
|
407
|
+
},
|
|
408
|
+
"dist/display.js": {
|
|
409
|
+
"imports": [],
|
|
410
|
+
"exports": [
|
|
411
|
+
"PlainDisplay",
|
|
412
|
+
"StatusDisplay",
|
|
413
|
+
"ansi",
|
|
414
|
+
"color",
|
|
415
|
+
"createDisplay",
|
|
416
|
+
"renderDot"
|
|
417
|
+
],
|
|
418
|
+
"entryPoint": "src/display.ts",
|
|
419
|
+
"inputs": {
|
|
420
|
+
"src/display.ts": {
|
|
421
|
+
"bytesInOutput": 7277
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
"bytes": 7533
|
|
384
425
|
}
|
|
385
426
|
}
|
|
386
427
|
}
|
package/dist/orchestrator.js
CHANGED
|
@@ -35,6 +35,9 @@ import {
|
|
|
35
35
|
mergeWave,
|
|
36
36
|
updateWorktreeFromBase
|
|
37
37
|
} from "./merge.js";
|
|
38
|
+
import {
|
|
39
|
+
createDisplay
|
|
40
|
+
} from "./display.js";
|
|
38
41
|
function formatErrorMessage(error) {
|
|
39
42
|
return error instanceof Error ? error.message : String(error);
|
|
40
43
|
}
|
|
@@ -97,6 +100,27 @@ function findReadyTickets(tickets) {
|
|
|
97
100
|
});
|
|
98
101
|
}
|
|
99
102
|
__name(findReadyTickets, "findReadyTickets");
|
|
103
|
+
const COMPLETED_RE = /^\[x\]\s+(.+)$/i;
|
|
104
|
+
function checkItems(ticket, completedItems) {
|
|
105
|
+
if (completedItems.length === 0) return;
|
|
106
|
+
let body = ticket.body;
|
|
107
|
+
for (const item of completedItems) {
|
|
108
|
+
const escaped = item.replace(
|
|
109
|
+
/[.*+?^${}()|[\]\\]/g,
|
|
110
|
+
"\\$&"
|
|
111
|
+
);
|
|
112
|
+
body = body.replace(
|
|
113
|
+
new RegExp(
|
|
114
|
+
`- \\[ \\] ${escaped}`,
|
|
115
|
+
"i"
|
|
116
|
+
),
|
|
117
|
+
`- [x] ${item}`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
writeFileSync(ticket.filepath, body);
|
|
121
|
+
ticket.body = body;
|
|
122
|
+
}
|
|
123
|
+
__name(checkItems, "checkItems");
|
|
100
124
|
function markDone(ticket) {
|
|
101
125
|
const updated = ticket.body.replace(
|
|
102
126
|
/\*\*Status:\*\*\s*.+/i,
|
|
@@ -124,6 +148,7 @@ function renderPrompt(specBody, ticket) {
|
|
|
124
148
|
"- Do NOT research external APIs or services via web search. Use the spec and ticket body as your sole reference for API shapes.",
|
|
125
149
|
"- Start writing code as soon as you understand the immediate context. You can read more files as needed while implementing.",
|
|
126
150
|
"- Commit your work when finished.",
|
|
151
|
+
"- After completing each checklist item in the ticket, print a line: [x] <item text> (matching the checklist text exactly).",
|
|
127
152
|
""
|
|
128
153
|
].join("\n");
|
|
129
154
|
}
|
|
@@ -336,26 +361,55 @@ function spawnAgent(opts) {
|
|
|
336
361
|
});
|
|
337
362
|
proc.stdout.pipe(logStream);
|
|
338
363
|
const piFormatter = isPi ? createPiStreamFormatter() : null;
|
|
364
|
+
const completedItems = [];
|
|
365
|
+
function collectCompleted(text) {
|
|
366
|
+
const match = text.match(COMPLETED_RE);
|
|
367
|
+
if (match) completedItems.push(match[1]);
|
|
368
|
+
}
|
|
369
|
+
__name(collectCompleted, "collectCompleted");
|
|
339
370
|
const rl = createInterface({ input: proc.stdout });
|
|
340
371
|
rl.on("line", (line) => {
|
|
341
372
|
if (isClaude) {
|
|
342
373
|
const formatted = formatStreamLine(line);
|
|
343
374
|
if (formatted !== null) {
|
|
344
|
-
|
|
375
|
+
collectCompleted(formatted);
|
|
376
|
+
const output = `${prefix}${formatted}`;
|
|
377
|
+
if (opts.onLine) {
|
|
378
|
+
opts.onLine(output);
|
|
379
|
+
} else {
|
|
380
|
+
console.log(output);
|
|
381
|
+
}
|
|
345
382
|
}
|
|
346
383
|
} else if (piFormatter) {
|
|
347
384
|
const formatted = piFormatter(line);
|
|
348
385
|
if (formatted !== null) {
|
|
349
|
-
|
|
386
|
+
collectCompleted(formatted);
|
|
387
|
+
const output = `${prefix}${formatted}`;
|
|
388
|
+
if (opts.onLine) {
|
|
389
|
+
opts.onLine(output);
|
|
390
|
+
} else {
|
|
391
|
+
console.log(output);
|
|
392
|
+
}
|
|
350
393
|
}
|
|
351
394
|
} else {
|
|
352
|
-
|
|
395
|
+
collectCompleted(line);
|
|
396
|
+
const output = `${prefix}${line}`;
|
|
397
|
+
if (opts.onLine) {
|
|
398
|
+
opts.onLine(output);
|
|
399
|
+
} else {
|
|
400
|
+
console.log(output);
|
|
401
|
+
}
|
|
353
402
|
}
|
|
354
403
|
});
|
|
355
404
|
proc.stderr.pipe(logStream);
|
|
356
405
|
const rlErr = createInterface({ input: proc.stderr });
|
|
357
406
|
rlErr.on("line", (line) => {
|
|
358
|
-
|
|
407
|
+
const output = `${prefix}${line}`;
|
|
408
|
+
if (opts.onLine) {
|
|
409
|
+
opts.onLine(output);
|
|
410
|
+
} else {
|
|
411
|
+
console.error(output);
|
|
412
|
+
}
|
|
359
413
|
});
|
|
360
414
|
const timeout = setTimeout(() => {
|
|
361
415
|
if (!proc.killed) {
|
|
@@ -376,7 +430,10 @@ function spawnAgent(opts) {
|
|
|
376
430
|
rlErr.close();
|
|
377
431
|
logStream.end();
|
|
378
432
|
if (promptFile) cleanupPromptFile(promptFile);
|
|
379
|
-
resolve(
|
|
433
|
+
resolve({
|
|
434
|
+
exitCode: code ?? 1,
|
|
435
|
+
completedItems
|
|
436
|
+
});
|
|
380
437
|
});
|
|
381
438
|
proc.on("error", (err) => {
|
|
382
439
|
clearTimeout(timeout);
|
|
@@ -388,7 +445,7 @@ function spawnAgent(opts) {
|
|
|
388
445
|
"Agent process error:",
|
|
389
446
|
formatErrorMessage(err)
|
|
390
447
|
);
|
|
391
|
-
resolve(1);
|
|
448
|
+
resolve({ exitCode: 1, completedItems });
|
|
392
449
|
});
|
|
393
450
|
});
|
|
394
451
|
}
|
|
@@ -467,9 +524,12 @@ async function respawnAndRetryMerge(opts) {
|
|
|
467
524
|
opts.worktreePath,
|
|
468
525
|
opts.baseBranch
|
|
469
526
|
);
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
527
|
+
const msg1 = ` ${opts.ticket.filename}: respawning agent to resolve merge conflict`;
|
|
528
|
+
if (opts.onLine) {
|
|
529
|
+
opts.onLine(msg1);
|
|
530
|
+
} else {
|
|
531
|
+
console.log(msg1);
|
|
532
|
+
}
|
|
473
533
|
const prompt = renderPrompt(opts.specBody, opts.ticket);
|
|
474
534
|
const logPath = join(
|
|
475
535
|
opts.logsDir,
|
|
@@ -479,19 +539,23 @@ async function respawnAndRetryMerge(opts) {
|
|
|
479
539
|
/\.md$/,
|
|
480
540
|
""
|
|
481
541
|
);
|
|
482
|
-
const
|
|
542
|
+
const result = await spawnAgent({
|
|
483
543
|
prompt,
|
|
484
544
|
agentCmd: opts.agentCmd,
|
|
485
545
|
model: opts.model,
|
|
486
546
|
timeout: opts.timeout,
|
|
487
547
|
logPath,
|
|
488
548
|
cwd: opts.worktreePath,
|
|
489
|
-
label
|
|
549
|
+
label,
|
|
550
|
+
onLine: opts.onLine
|
|
490
551
|
});
|
|
491
|
-
if (exitCode !== 0) {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
552
|
+
if (result.exitCode !== 0) {
|
|
553
|
+
const msg2 = ` ${opts.ticket.filename}: respawned agent exited ${result.exitCode}`;
|
|
554
|
+
if (opts.onLine) {
|
|
555
|
+
opts.onLine(msg2);
|
|
556
|
+
} else {
|
|
557
|
+
console.log(msg2);
|
|
558
|
+
}
|
|
495
559
|
return false;
|
|
496
560
|
}
|
|
497
561
|
const merged = await mergeBranch(
|
|
@@ -499,24 +563,34 @@ async function respawnAndRetryMerge(opts) {
|
|
|
499
563
|
opts.branch
|
|
500
564
|
);
|
|
501
565
|
if (!merged) {
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
566
|
+
const msg3 = ` ${opts.ticket.filename}: merge still conflicted after respawn`;
|
|
567
|
+
if (opts.onLine) {
|
|
568
|
+
opts.onLine(msg3);
|
|
569
|
+
} else {
|
|
570
|
+
console.log(msg3);
|
|
571
|
+
}
|
|
505
572
|
}
|
|
506
573
|
return merged;
|
|
507
574
|
}
|
|
508
575
|
__name(respawnAndRetryMerge, "respawnAndRetryMerge");
|
|
509
|
-
async function runWaves(tickets, specBody, agentCmd, model, timeout, logsDir) {
|
|
576
|
+
async function runWaves(tickets, specBody, agentCmd, model, timeout, logsDir, display) {
|
|
510
577
|
mkdirSync(logsDir, { recursive: true });
|
|
578
|
+
const d = display ?? createDisplay();
|
|
579
|
+
let totalWaves = 0;
|
|
580
|
+
for (const _ of simulateWaves(tickets)) totalWaves++;
|
|
511
581
|
let totalCompleted = 0;
|
|
512
582
|
let waveNum = 0;
|
|
513
583
|
while (true) {
|
|
514
584
|
const ready = findReadyTickets(tickets);
|
|
515
585
|
if (ready.length === 0) break;
|
|
516
586
|
waveNum++;
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
587
|
+
d.startWave(
|
|
588
|
+
waveNum,
|
|
589
|
+
totalWaves,
|
|
590
|
+
ready.map((t) => ({
|
|
591
|
+
num: t.num,
|
|
592
|
+
filename: t.filename
|
|
593
|
+
}))
|
|
520
594
|
);
|
|
521
595
|
const results = await Promise.allSettled(
|
|
522
596
|
ready.map(async (t) => {
|
|
@@ -529,37 +603,42 @@ Wave ${waveNum}: ` + ready.map((t) => t.filename).join(", ")
|
|
|
529
603
|
/\.md$/,
|
|
530
604
|
""
|
|
531
605
|
);
|
|
532
|
-
|
|
533
|
-
|
|
606
|
+
d.setTicketStatus(t.num, "in-progress");
|
|
607
|
+
d.log(` Starting ${t.filename}`);
|
|
608
|
+
const result = await spawnAgent({
|
|
534
609
|
prompt,
|
|
535
610
|
agentCmd,
|
|
536
611
|
model,
|
|
537
612
|
timeout,
|
|
538
613
|
logPath,
|
|
539
|
-
label
|
|
614
|
+
label,
|
|
615
|
+
onLine: /* @__PURE__ */ __name((line) => d.log(line), "onLine")
|
|
540
616
|
});
|
|
541
|
-
return { ticket: t,
|
|
617
|
+
return { ticket: t, result };
|
|
542
618
|
})
|
|
543
619
|
);
|
|
544
620
|
let waveCompleted = 0;
|
|
545
621
|
for (const r of results) {
|
|
546
622
|
if (r.status === "rejected") continue;
|
|
547
|
-
const { ticket,
|
|
548
|
-
if (exitCode !== 0) {
|
|
549
|
-
|
|
550
|
-
|
|
623
|
+
const { ticket, result } = r.value;
|
|
624
|
+
if (result.exitCode !== 0) {
|
|
625
|
+
d.setTicketStatus(ticket.num, "failed");
|
|
626
|
+
d.log(
|
|
627
|
+
` ${ticket.filename}: agent exited ${result.exitCode}`
|
|
551
628
|
);
|
|
552
629
|
continue;
|
|
553
630
|
}
|
|
631
|
+
checkItems(ticket, result.completedItems);
|
|
554
632
|
markDone(ticket);
|
|
555
633
|
waveCompleted++;
|
|
556
634
|
totalCompleted++;
|
|
557
|
-
|
|
635
|
+
d.setTicketStatus(ticket.num, "done");
|
|
636
|
+
d.log(
|
|
558
637
|
` ${ticket.filename}: done`
|
|
559
638
|
);
|
|
560
639
|
}
|
|
561
640
|
if (waveCompleted === 0) {
|
|
562
|
-
|
|
641
|
+
d.log(
|
|
563
642
|
"\nNo tickets completed this wave. Stopping."
|
|
564
643
|
);
|
|
565
644
|
break;
|
|
@@ -568,15 +647,19 @@ Wave ${waveNum}: ` + ready.map((t) => t.filename).join(", ")
|
|
|
568
647
|
const remaining = tickets.filter(
|
|
569
648
|
(t) => t.status === "ready-for-agent"
|
|
570
649
|
);
|
|
571
|
-
|
|
650
|
+
d.log(
|
|
572
651
|
`
|
|
573
652
|
Finished: ${totalCompleted} completed` + (remaining.length > 0 ? `, ${remaining.length} remaining` : "")
|
|
574
653
|
);
|
|
654
|
+
d.cleanup();
|
|
575
655
|
return { allDone: remaining.length === 0 };
|
|
576
656
|
}
|
|
577
657
|
__name(runWaves, "runWaves");
|
|
578
658
|
async function runWavesIsolated(opts) {
|
|
579
659
|
mkdirSync(opts.logsDir, { recursive: true });
|
|
660
|
+
const d = opts.display ?? createDisplay();
|
|
661
|
+
let totalWaves = 0;
|
|
662
|
+
for (const _ of simulateWaves(opts.tickets)) totalWaves++;
|
|
580
663
|
const repoRoot = getRepoRoot();
|
|
581
664
|
const repoName = getRepoName();
|
|
582
665
|
const baseBranch = getCurrentBranch();
|
|
@@ -600,9 +683,13 @@ async function runWavesIsolated(opts) {
|
|
|
600
683
|
const ready = findReadyTickets(opts.tickets);
|
|
601
684
|
if (ready.length === 0) break;
|
|
602
685
|
waveNum++;
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
686
|
+
d.startWave(
|
|
687
|
+
waveNum,
|
|
688
|
+
totalWaves,
|
|
689
|
+
ready.map((t) => ({
|
|
690
|
+
num: t.num,
|
|
691
|
+
filename: t.filename
|
|
692
|
+
}))
|
|
606
693
|
);
|
|
607
694
|
const worktreeSettled = await Promise.allSettled(
|
|
608
695
|
ready.map((t) => createWorktree({
|
|
@@ -618,7 +705,8 @@ Wave ${waveNum}: ` + ready.map((t) => t.filename).join(", ")
|
|
|
618
705
|
const settled = worktreeSettled[i];
|
|
619
706
|
const ticket = ready[i];
|
|
620
707
|
if (settled.status === "rejected") {
|
|
621
|
-
|
|
708
|
+
d.setTicketStatus(ticket.num, "failed");
|
|
709
|
+
d.log(
|
|
622
710
|
` ${ticket.filename}: failed to create worktree: ${formatErrorMessage(settled.reason)}`
|
|
623
711
|
);
|
|
624
712
|
continue;
|
|
@@ -637,7 +725,8 @@ Wave ${waveNum}: ` + ready.map((t) => t.filename).join(", ")
|
|
|
637
725
|
/\.md$/,
|
|
638
726
|
""
|
|
639
727
|
);
|
|
640
|
-
|
|
728
|
+
d.setTicketStatus(t.num, "in-progress");
|
|
729
|
+
d.log(` Starting ${t.filename}`);
|
|
641
730
|
return spawnAgent({
|
|
642
731
|
prompt,
|
|
643
732
|
agentCmd: opts.agentCmd,
|
|
@@ -645,7 +734,8 @@ Wave ${waveNum}: ` + ready.map((t) => t.filename).join(", ")
|
|
|
645
734
|
timeout: opts.timeout,
|
|
646
735
|
logPath,
|
|
647
736
|
cwd: worktrees[i].worktreePath,
|
|
648
|
-
label
|
|
737
|
+
label,
|
|
738
|
+
onLine: /* @__PURE__ */ __name((line) => d.log(line), "onLine")
|
|
649
739
|
});
|
|
650
740
|
})
|
|
651
741
|
);
|
|
@@ -656,16 +746,18 @@ Wave ${waveNum}: ` + ready.map((t) => t.filename).join(", ")
|
|
|
656
746
|
for (let i = 0; i < results.length; i++) {
|
|
657
747
|
const r = results[i];
|
|
658
748
|
if (r.status === "rejected") continue;
|
|
659
|
-
const
|
|
749
|
+
const agentResult = r.value;
|
|
660
750
|
const ticket = activeTickets[i];
|
|
661
751
|
const worktree = worktrees[i];
|
|
662
|
-
if (exitCode !== 0) {
|
|
663
|
-
|
|
664
|
-
|
|
752
|
+
if (agentResult.exitCode !== 0) {
|
|
753
|
+
d.setTicketStatus(ticket.num, "failed");
|
|
754
|
+
d.log(
|
|
755
|
+
` ${ticket.filename}: agent exited ${agentResult.exitCode}`
|
|
665
756
|
);
|
|
757
|
+
checkItems(ticket, agentResult.completedItems);
|
|
666
758
|
checkTicket(progress, ticket.num, {
|
|
667
759
|
branch: worktree.branchName,
|
|
668
|
-
exitCode,
|
|
760
|
+
exitCode: agentResult.exitCode,
|
|
669
761
|
done: false
|
|
670
762
|
});
|
|
671
763
|
writeProgress(progressPath, progress);
|
|
@@ -677,7 +769,7 @@ Wave ${waveNum}: ` + ready.map((t) => t.filename).join(", ")
|
|
|
677
769
|
try {
|
|
678
770
|
await removeWorktree(worktree.worktreePath);
|
|
679
771
|
} catch (err) {
|
|
680
|
-
|
|
772
|
+
d.log(
|
|
681
773
|
` Failed to remove worktree ${worktree.worktreePath}: ` + formatErrorMessage(err)
|
|
682
774
|
);
|
|
683
775
|
}
|
|
@@ -689,7 +781,7 @@ Wave ${waveNum}: ` + ready.map((t) => t.filename).join(", ")
|
|
|
689
781
|
`nightralph: auto-commit ${ticket.filename} remaining changes`
|
|
690
782
|
);
|
|
691
783
|
if (committed) {
|
|
692
|
-
|
|
784
|
+
d.log(
|
|
693
785
|
` ${ticket.filename}: auto-committed remaining changes`
|
|
694
786
|
);
|
|
695
787
|
}
|
|
@@ -699,12 +791,14 @@ Wave ${waveNum}: ` + ready.map((t) => t.filename).join(", ")
|
|
|
699
791
|
baseBranch,
|
|
700
792
|
worktree.branchName
|
|
701
793
|
)) {
|
|
702
|
-
|
|
794
|
+
d.setTicketStatus(ticket.num, "failed");
|
|
795
|
+
d.log(
|
|
703
796
|
` ${ticket.filename}: agent exited 0 but made no changes; leaving ticket ready-for-agent`
|
|
704
797
|
);
|
|
705
798
|
continue;
|
|
706
799
|
}
|
|
707
|
-
|
|
800
|
+
checkItems(ticket, agentResult.completedItems);
|
|
801
|
+
d.log(
|
|
708
802
|
` ${ticket.filename}: agent finished, awaiting merge`
|
|
709
803
|
);
|
|
710
804
|
ticketsByNum.set(ticket.num, ticket);
|
|
@@ -734,7 +828,8 @@ Wave ${waveNum}: ` + ready.map((t) => t.filename).join(", ")
|
|
|
734
828
|
markDone(ticket);
|
|
735
829
|
waveCompleted++;
|
|
736
830
|
totalCompleted++;
|
|
737
|
-
|
|
831
|
+
d.setTicketStatus(ticket.num, "done");
|
|
832
|
+
d.log(
|
|
738
833
|
` ${ticket.filename}: done`
|
|
739
834
|
);
|
|
740
835
|
checkTicket(progress, ticket.num, {
|
|
@@ -754,7 +849,7 @@ Wave ${waveNum}: ` + ready.map((t) => t.filename).join(", ")
|
|
|
754
849
|
try {
|
|
755
850
|
await removeWorktree(worktreePath);
|
|
756
851
|
} catch (err) {
|
|
757
|
-
|
|
852
|
+
d.log(
|
|
758
853
|
` Failed to remove worktree ${worktreePath}: ` + formatErrorMessage(err)
|
|
759
854
|
);
|
|
760
855
|
}
|
|
@@ -773,13 +868,15 @@ Wave ${waveNum}: ` + ready.map((t) => t.filename).join(", ")
|
|
|
773
868
|
agentCmd: opts.agentCmd,
|
|
774
869
|
model: opts.model,
|
|
775
870
|
timeout: opts.timeout,
|
|
776
|
-
logsDir: opts.logsDir
|
|
871
|
+
logsDir: opts.logsDir,
|
|
872
|
+
onLine: /* @__PURE__ */ __name((msg) => d.log(msg), "onLine")
|
|
777
873
|
}) : false;
|
|
778
874
|
if (recovered) {
|
|
779
875
|
markDone(ticket);
|
|
780
876
|
waveCompleted++;
|
|
781
877
|
totalCompleted++;
|
|
782
|
-
|
|
878
|
+
d.setTicketStatus(ticket.num, "done");
|
|
879
|
+
d.log(
|
|
783
880
|
` ${ticket.filename}: done (resolved via respawn)`
|
|
784
881
|
);
|
|
785
882
|
checkTicket(progress, ticket.num, {
|
|
@@ -796,20 +893,20 @@ Wave ${waveNum}: ` + ready.map((t) => t.filename).join(", ")
|
|
|
796
893
|
try {
|
|
797
894
|
await removeWorktree(worktreePath);
|
|
798
895
|
} catch (err) {
|
|
799
|
-
|
|
896
|
+
d.log(
|
|
800
897
|
` Failed to remove worktree ${worktreePath}: ` + formatErrorMessage(err)
|
|
801
898
|
);
|
|
802
899
|
}
|
|
803
900
|
}
|
|
804
901
|
} else {
|
|
805
|
-
|
|
902
|
+
d.log(
|
|
806
903
|
` ${ticket.filename}: merge failed, staying ready-for-agent`
|
|
807
904
|
);
|
|
808
905
|
}
|
|
809
906
|
}
|
|
810
907
|
}
|
|
811
908
|
if (mergeResult.stoppedAt) {
|
|
812
|
-
|
|
909
|
+
d.log(
|
|
813
910
|
`
|
|
814
911
|
Merge stopped at ${mergeResult.stoppedAt.branch}`
|
|
815
912
|
);
|
|
@@ -819,7 +916,7 @@ Merge stopped at ${mergeResult.stoppedAt.branch}`
|
|
|
819
916
|
try {
|
|
820
917
|
await removeWorktree(sb.worktreePath);
|
|
821
918
|
} catch (err) {
|
|
822
|
-
|
|
919
|
+
d.log(
|
|
823
920
|
` Failed to remove worktree ${sb.worktreePath}: ` + formatErrorMessage(err)
|
|
824
921
|
);
|
|
825
922
|
}
|
|
@@ -828,7 +925,7 @@ Merge stopped at ${mergeResult.stoppedAt.branch}`
|
|
|
828
925
|
}
|
|
829
926
|
}
|
|
830
927
|
if (waveCompleted === 0) {
|
|
831
|
-
|
|
928
|
+
d.log(
|
|
832
929
|
"\nNo tickets completed this wave. Stopping."
|
|
833
930
|
);
|
|
834
931
|
break;
|
|
@@ -837,15 +934,18 @@ Merge stopped at ${mergeResult.stoppedAt.branch}`
|
|
|
837
934
|
const remaining = opts.tickets.filter(
|
|
838
935
|
(t) => t.status === "ready-for-agent"
|
|
839
936
|
);
|
|
840
|
-
|
|
937
|
+
d.log(
|
|
841
938
|
`
|
|
842
939
|
Finished: ${totalCompleted} completed` + (remaining.length > 0 ? `, ${remaining.length} remaining` : "")
|
|
843
940
|
);
|
|
941
|
+
d.cleanup();
|
|
844
942
|
return { allDone: remaining.length === 0 };
|
|
845
943
|
}
|
|
846
944
|
__name(runWavesIsolated, "runWavesIsolated");
|
|
847
945
|
export {
|
|
946
|
+
COMPLETED_RE,
|
|
848
947
|
TICKET_RE,
|
|
948
|
+
checkItems,
|
|
849
949
|
createPiStreamFormatter,
|
|
850
950
|
dryRun,
|
|
851
951
|
dryRunIsolated,
|