nightralph 0.0.0
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/.claude/settings.local.json +5 -0
- package/AGENTS.md +60 -0
- package/CLAUDE.md +1 -0
- package/PROMPT.md +95 -0
- package/README.md +138 -0
- package/WIP.md +5 -0
- package/dist/docs-templates/domain.md +36 -0
- package/dist/docs-templates/issue-tracker-github.md +228 -0
- package/dist/docs-templates/issue-tracker.md +30 -0
- package/dist/docs-templates/triage-labels.md +15 -0
- package/dist/index.js +154 -0
- package/dist/index.js.map +7 -0
- package/dist/meta.json +207 -0
- package/dist/orchestrator.js +273 -0
- package/dist/orchestrator.js.map +7 -0
- package/dist/resolve.js +71 -0
- package/dist/resolve.js.map +7 -0
- package/dist/setup.js +151 -0
- package/dist/setup.js.map +7 -0
- package/dist/skills/domain-modeling/ADR-FORMAT.md +47 -0
- package/dist/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/dist/skills/domain-modeling/SKILL.md +74 -0
- package/dist/skills/domain-modeling/agents/openai.yaml +3 -0
- package/dist/skills/grill/SKILL.md +7 -0
- package/dist/skills/grill/agents/openai.yaml +5 -0
- package/dist/skills/grilling/SKILL.md +28 -0
- package/dist/skills/grilling/agents/openai.yaml +3 -0
- package/dist/skills/tdd/SKILL.md +38 -0
- package/dist/skills/tdd/agents/openai.yaml +3 -0
- package/dist/skills/tdd/mocking.md +59 -0
- package/dist/skills/tdd/tests.md +77 -0
- package/dist/skills/to-spec/SKILL.md +75 -0
- package/dist/skills/to-spec/agents/openai.yaml +5 -0
- package/dist/skills/to-tickets/SKILL.md +105 -0
- package/dist/skills/to-tickets/agents/openai.yaml +5 -0
- package/dist/skills/upstream.json +5 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/orchestrator.d.ts +31 -0
- package/dist/src/orchestrator.d.ts.map +1 -0
- package/dist/src/resolve.d.ts +10 -0
- package/dist/src/resolve.d.ts.map +1 -0
- package/dist/src/setup.d.ts +13 -0
- package/dist/src/setup.d.ts.map +1 -0
- package/dist/test/integration.test.js +848 -0
- package/dist/test/integration.test.js.map +7 -0
- package/dist/test/orchestrator.test.js +749 -0
- package/dist/test/orchestrator.test.js.map +7 -0
- package/dist/test/setup.test.js +574 -0
- package/dist/test/setup.test.js.map +7 -0
- package/docs/README.md +12 -0
- package/docs/agents/domain.md +36 -0
- package/docs/agents/issue-tracker.md +30 -0
- package/docs/agents/triage-labels.md +15 -0
- package/docs/implementation-plans/2026-08-27-issue-tracker-choice/phase-2-future-work.md +144 -0
- package/docs/implementation-plans/2026-08-27-issue-tracker-choice/phase_01.md +292 -0
- package/docs/implementation-plans/2026-08-27-issue-tracker-choice/phase_02.md +212 -0
- package/docs/implementation-plans/2026-08-27-issue-tracker-choice/test-requirements.md +203 -0
- package/docs/skills.md +58 -0
- package/docs/test-plans/2026-08-27-issue-tracker-choice.md +95 -0
- package/eslint.config.js +142 -0
- package/lib.es5.d.ts +19 -0
- package/package.json +36 -0
- package/progress.log +190 -0
- package/ralph_claude.sh +263 -0
- package/scripts/clone-skills.sh +52 -0
- package/specs/prd.json +172 -0
- package/src/docs-templates/domain.md +36 -0
- package/src/docs-templates/issue-tracker-github.md +228 -0
- package/src/docs-templates/issue-tracker.md +30 -0
- package/src/docs-templates/triage-labels.md +15 -0
- package/src/index.ts +184 -0
- package/src/orchestrator.ts +324 -0
- package/src/resolve.ts +73 -0
- package/src/setup.ts +157 -0
- package/src/skills/domain-modeling/ADR-FORMAT.md +47 -0
- package/src/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/src/skills/domain-modeling/SKILL.md +74 -0
- package/src/skills/domain-modeling/agents/openai.yaml +3 -0
- package/src/skills/grill/SKILL.md +7 -0
- package/src/skills/grill/agents/openai.yaml +5 -0
- package/src/skills/grilling/SKILL.md +28 -0
- package/src/skills/grilling/agents/openai.yaml +3 -0
- package/src/skills/tdd/SKILL.md +38 -0
- package/src/skills/tdd/agents/openai.yaml +3 -0
- package/src/skills/tdd/mocking.md +59 -0
- package/src/skills/tdd/tests.md +77 -0
- package/src/skills/to-spec/SKILL.md +75 -0
- package/src/skills/to-spec/agents/openai.yaml +5 -0
- package/src/skills/to-tickets/SKILL.md +105 -0
- package/src/skills/to-tickets/agents/openai.yaml +5 -0
- package/src/skills/upstream.json +5 -0
- package/tasks/prd-integration-test-coverage.md +243 -0
- package/test/integration.test.ts +636 -0
- package/test/mock-agent-echo-args.js +4 -0
- package/test/mock-agent-fail.js +3 -0
- package/test/mock-agent-pass.js +3 -0
- package/test/orchestrator.test.ts +609 -0
- package/test/setup.test.ts +417 -0
- package/tsconfig.build.json +7 -0
- package/tsconfig.json +31 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
4
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import yargs from "yargs";
|
|
7
|
+
import { hideBin } from "yargs/helpers";
|
|
8
|
+
import { select } from "@inquirer/prompts";
|
|
9
|
+
import { setup, checkGhAuth } from "./setup.js";
|
|
10
|
+
import {
|
|
11
|
+
scanTickets,
|
|
12
|
+
dryRun,
|
|
13
|
+
runWaves
|
|
14
|
+
} from "./orchestrator.js";
|
|
15
|
+
import {
|
|
16
|
+
findFeatureDirs,
|
|
17
|
+
resolveIssuesDir
|
|
18
|
+
} from "./resolve.js";
|
|
19
|
+
const scriptDir = dirname(process.argv[1]);
|
|
20
|
+
yargs(hideBin(process.argv)).scriptName("nightralph").usage("$0 <command>").command(
|
|
21
|
+
"setup",
|
|
22
|
+
"Install bundled skills and doc templates",
|
|
23
|
+
(y) => y.option("claude", {
|
|
24
|
+
type: "boolean",
|
|
25
|
+
describe: "Also create .claude/skills/ symlinks",
|
|
26
|
+
default: false
|
|
27
|
+
}),
|
|
28
|
+
async (argv) => {
|
|
29
|
+
const tracker = await select({
|
|
30
|
+
message: "Issue tracker format",
|
|
31
|
+
choices: [
|
|
32
|
+
{
|
|
33
|
+
name: "Local Markdown",
|
|
34
|
+
value: "local",
|
|
35
|
+
description: "Issues as .md files in .scratch/"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "GitHub Issues",
|
|
39
|
+
value: "github",
|
|
40
|
+
description: "Issues via gh CLI"
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
});
|
|
44
|
+
if (tracker === "github") {
|
|
45
|
+
if (!checkGhAuth()) {
|
|
46
|
+
console.error(
|
|
47
|
+
"GitHub Issues requires the gh CLI, authenticated.\nInstall: https://cli.github.com\nThen run: gh auth login"
|
|
48
|
+
);
|
|
49
|
+
process.exit(2);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
setup({
|
|
53
|
+
bundledSkillsDir: join(scriptDir, "skills"),
|
|
54
|
+
docsTemplatesDir: join(
|
|
55
|
+
scriptDir,
|
|
56
|
+
"docs-templates"
|
|
57
|
+
),
|
|
58
|
+
projectDir: process.cwd(),
|
|
59
|
+
claude: argv.claude,
|
|
60
|
+
tracker
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
).command(
|
|
64
|
+
"$0 <provider>",
|
|
65
|
+
"Execute tickets with the given provider",
|
|
66
|
+
(y) => y.positional("provider", {
|
|
67
|
+
type: "string",
|
|
68
|
+
describe: "Agent CLI to spawn (e.g. claude, codex)",
|
|
69
|
+
demandOption: true
|
|
70
|
+
}).option("m", {
|
|
71
|
+
alias: "model",
|
|
72
|
+
type: "string",
|
|
73
|
+
describe: "Model passed to the agent"
|
|
74
|
+
}).option("dir", {
|
|
75
|
+
type: "string",
|
|
76
|
+
describe: "Path to the issues directory"
|
|
77
|
+
}).option("spec", {
|
|
78
|
+
type: "string",
|
|
79
|
+
describe: "Path to the spec file"
|
|
80
|
+
}).option("dry-run", {
|
|
81
|
+
type: "boolean",
|
|
82
|
+
describe: "Show wave order without running",
|
|
83
|
+
default: false
|
|
84
|
+
}).option("timeout-secs", {
|
|
85
|
+
type: "number",
|
|
86
|
+
describe: "Kill agent after N seconds",
|
|
87
|
+
default: 300
|
|
88
|
+
}),
|
|
89
|
+
async (argv) => {
|
|
90
|
+
await runExecute({
|
|
91
|
+
provider: argv.provider,
|
|
92
|
+
model: argv.model,
|
|
93
|
+
dir: argv.dir,
|
|
94
|
+
spec: argv.spec,
|
|
95
|
+
dryRun: argv.dryRun,
|
|
96
|
+
timeoutSecs: argv.timeoutSecs
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
).strict().demandCommand(1, "Specify a command or provider").help().parse();
|
|
100
|
+
async function runExecute(argv) {
|
|
101
|
+
const { dir, spec } = await resolveIssuesDir(
|
|
102
|
+
argv.dir,
|
|
103
|
+
argv.spec
|
|
104
|
+
);
|
|
105
|
+
if (!existsSync(dir)) {
|
|
106
|
+
console.error(`Directory not found: ${dir}`);
|
|
107
|
+
process.exit(2);
|
|
108
|
+
}
|
|
109
|
+
if (!existsSync(spec)) {
|
|
110
|
+
console.error(`Spec file not found: ${spec}`);
|
|
111
|
+
process.exit(2);
|
|
112
|
+
}
|
|
113
|
+
const specBody = readFileSync(spec, "utf8");
|
|
114
|
+
const tickets = scanTickets(dir);
|
|
115
|
+
if (tickets.length === 0) {
|
|
116
|
+
console.log("No ticket files found. Exiting.");
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
console.log(`Found ${tickets.length} tickets`);
|
|
120
|
+
const knownNums = new Set(tickets.map((t) => t.num));
|
|
121
|
+
for (const t of tickets) {
|
|
122
|
+
const unknown = t.blockers.filter(
|
|
123
|
+
(b) => !knownNums.has(b)
|
|
124
|
+
);
|
|
125
|
+
if (unknown.length > 0) {
|
|
126
|
+
console.warn(
|
|
127
|
+
`${t.filename}: blockers [${unknown.join(", ")}] not found in directory`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
t.blockers = t.blockers.filter(
|
|
131
|
+
(b) => knownNums.has(b)
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
if (argv.dryRun) {
|
|
135
|
+
dryRun(tickets, specBody);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const logsDir = join(dirname(dir), "logs");
|
|
139
|
+
const timeoutSecs = argv.timeoutSecs ?? 300;
|
|
140
|
+
await runWaves(
|
|
141
|
+
tickets,
|
|
142
|
+
specBody,
|
|
143
|
+
argv.provider,
|
|
144
|
+
argv.model ?? null,
|
|
145
|
+
timeoutSecs,
|
|
146
|
+
logsDir
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
__name(runExecute, "runExecute");
|
|
150
|
+
export {
|
|
151
|
+
findFeatureDirs,
|
|
152
|
+
resolveIssuesDir
|
|
153
|
+
};
|
|
154
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 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 runWaves,\n} from './orchestrator.js'\nimport {\n findFeatureDirs,\n resolveIssuesDir,\n} from './resolve.js'\n\nexport { findFeatureDirs, resolveIssuesDir }\n\nconst scriptDir = dirname(process.argv[1])\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 (e.g. claude, codex)',\n demandOption:true,\n })\n .option('m', {\n alias:'model',\n type:'string',\n describe:'Model passed to the agent',\n })\n .option('dir', {\n type:'string',\n describe:'Path to the issues directory',\n })\n .option('spec', {\n type:'string',\n describe:'Path to the spec file',\n })\n .option('dry-run', {\n type:'boolean',\n describe:'Show wave order without running',\n default:false,\n })\n .option('timeout-secs', {\n type:'number',\n describe:'Kill agent after N seconds',\n default:300,\n }),\n async argv => {\n await runExecute({\n provider:argv.provider as string,\n model:argv.model as string|undefined,\n dir:argv.dir as string|undefined,\n spec:argv.spec as string|undefined,\n dryRun:argv.dryRun as boolean|undefined,\n timeoutSecs:argv.timeoutSecs as\n number|undefined,\n })\n }\n )\n .strict()\n .demandCommand(1, 'Specify a command or provider')\n .help()\n .parse()\n\nasync function runExecute (argv:{\n provider:string\n model?:string\n dir?:string\n spec?:string\n dryRun?:boolean\n timeoutSecs?:number\n}):Promise<void> {\n const { dir, spec } = await resolveIssuesDir(\n argv.dir, 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 if (argv.dryRun) {\n dryRun(tickets, specBody)\n return\n }\n\n const logsDir = join(dirname(dir), 'logs')\n const timeoutSecs = argv.timeoutSecs ?? 300\n await runWaves(\n tickets, specBody,\n argv.provider, argv.model ?? null, timeoutSecs,\n logsDir,\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/meta.json
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
{
|
|
2
|
+
"inputs": {
|
|
3
|
+
"src/index.ts": {
|
|
4
|
+
"bytes": 5325,
|
|
5
|
+
"imports": [],
|
|
6
|
+
"format": "esm"
|
|
7
|
+
},
|
|
8
|
+
"src/orchestrator.ts": {
|
|
9
|
+
"bytes": 8174,
|
|
10
|
+
"imports": [],
|
|
11
|
+
"format": "esm"
|
|
12
|
+
},
|
|
13
|
+
"src/setup.ts": {
|
|
14
|
+
"bytes": 4024,
|
|
15
|
+
"imports": [],
|
|
16
|
+
"format": "esm"
|
|
17
|
+
},
|
|
18
|
+
"src/resolve.ts": {
|
|
19
|
+
"bytes": 1978,
|
|
20
|
+
"imports": [],
|
|
21
|
+
"format": "esm"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"outputs": {
|
|
25
|
+
"dist/index.js.map": {
|
|
26
|
+
"imports": [],
|
|
27
|
+
"exports": [],
|
|
28
|
+
"inputs": {},
|
|
29
|
+
"bytes": 8282
|
|
30
|
+
},
|
|
31
|
+
"dist/index.js": {
|
|
32
|
+
"imports": [
|
|
33
|
+
{
|
|
34
|
+
"path": "node:fs",
|
|
35
|
+
"kind": "import-statement",
|
|
36
|
+
"external": true
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"path": "node:path",
|
|
40
|
+
"kind": "import-statement",
|
|
41
|
+
"external": true
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"path": "yargs",
|
|
45
|
+
"kind": "import-statement",
|
|
46
|
+
"external": true
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"path": "yargs/helpers",
|
|
50
|
+
"kind": "import-statement",
|
|
51
|
+
"external": true
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"path": "@inquirer/prompts",
|
|
55
|
+
"kind": "import-statement",
|
|
56
|
+
"external": true
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"path": "./setup.js",
|
|
60
|
+
"kind": "import-statement",
|
|
61
|
+
"external": true
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"path": "./orchestrator.js",
|
|
65
|
+
"kind": "import-statement",
|
|
66
|
+
"external": true
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"path": "./resolve.js",
|
|
70
|
+
"kind": "import-statement",
|
|
71
|
+
"external": true
|
|
72
|
+
}
|
|
73
|
+
],
|
|
74
|
+
"exports": [
|
|
75
|
+
"findFeatureDirs",
|
|
76
|
+
"resolveIssuesDir"
|
|
77
|
+
],
|
|
78
|
+
"entryPoint": "src/index.ts",
|
|
79
|
+
"inputs": {
|
|
80
|
+
"src/index.ts": {
|
|
81
|
+
"bytesInOutput": 3847
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"bytes": 4080
|
|
85
|
+
},
|
|
86
|
+
"dist/orchestrator.js.map": {
|
|
87
|
+
"imports": [],
|
|
88
|
+
"exports": [],
|
|
89
|
+
"inputs": {},
|
|
90
|
+
"bytes": 13697
|
|
91
|
+
},
|
|
92
|
+
"dist/orchestrator.js": {
|
|
93
|
+
"imports": [
|
|
94
|
+
{
|
|
95
|
+
"path": "node:child_process",
|
|
96
|
+
"kind": "import-statement",
|
|
97
|
+
"external": true
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"path": "node:fs",
|
|
101
|
+
"kind": "import-statement",
|
|
102
|
+
"external": true
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"path": "node:path",
|
|
106
|
+
"kind": "import-statement",
|
|
107
|
+
"external": true
|
|
108
|
+
}
|
|
109
|
+
],
|
|
110
|
+
"exports": [
|
|
111
|
+
"TICKET_RE",
|
|
112
|
+
"dryRun",
|
|
113
|
+
"findReadyTickets",
|
|
114
|
+
"formatErrorMessage",
|
|
115
|
+
"markDone",
|
|
116
|
+
"parseBlockers",
|
|
117
|
+
"parseStatus",
|
|
118
|
+
"parseTicketFilename",
|
|
119
|
+
"renderPrompt",
|
|
120
|
+
"runWaves",
|
|
121
|
+
"scanTickets",
|
|
122
|
+
"spawnAgent"
|
|
123
|
+
],
|
|
124
|
+
"entryPoint": "src/orchestrator.ts",
|
|
125
|
+
"inputs": {
|
|
126
|
+
"src/orchestrator.ts": {
|
|
127
|
+
"bytesInOutput": 6754
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
"bytes": 7124
|
|
131
|
+
},
|
|
132
|
+
"dist/setup.js.map": {
|
|
133
|
+
"imports": [],
|
|
134
|
+
"exports": [],
|
|
135
|
+
"inputs": {},
|
|
136
|
+
"bytes": 6493
|
|
137
|
+
},
|
|
138
|
+
"dist/setup.js": {
|
|
139
|
+
"imports": [
|
|
140
|
+
{
|
|
141
|
+
"path": "node:fs",
|
|
142
|
+
"kind": "import-statement",
|
|
143
|
+
"external": true
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"path": "node:path",
|
|
147
|
+
"kind": "import-statement",
|
|
148
|
+
"external": true
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"path": "node:child_process",
|
|
152
|
+
"kind": "import-statement",
|
|
153
|
+
"external": true
|
|
154
|
+
}
|
|
155
|
+
],
|
|
156
|
+
"exports": [
|
|
157
|
+
"DOC_TEMPLATES",
|
|
158
|
+
"SKILL_DIRS",
|
|
159
|
+
"checkGhAuth",
|
|
160
|
+
"setup"
|
|
161
|
+
],
|
|
162
|
+
"entryPoint": "src/setup.ts",
|
|
163
|
+
"inputs": {
|
|
164
|
+
"src/setup.ts": {
|
|
165
|
+
"bytesInOutput": 3417
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
"bytes": 3646
|
|
169
|
+
},
|
|
170
|
+
"dist/resolve.js.map": {
|
|
171
|
+
"imports": [],
|
|
172
|
+
"exports": [],
|
|
173
|
+
"inputs": {},
|
|
174
|
+
"bytes": 3349
|
|
175
|
+
},
|
|
176
|
+
"dist/resolve.js": {
|
|
177
|
+
"imports": [
|
|
178
|
+
{
|
|
179
|
+
"path": "node:fs",
|
|
180
|
+
"kind": "import-statement",
|
|
181
|
+
"external": true
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"path": "node:path",
|
|
185
|
+
"kind": "import-statement",
|
|
186
|
+
"external": true
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"path": "@inquirer/prompts",
|
|
190
|
+
"kind": "import-statement",
|
|
191
|
+
"external": true
|
|
192
|
+
}
|
|
193
|
+
],
|
|
194
|
+
"exports": [
|
|
195
|
+
"findFeatureDirs",
|
|
196
|
+
"resolveIssuesDir"
|
|
197
|
+
],
|
|
198
|
+
"entryPoint": "src/resolve.ts",
|
|
199
|
+
"inputs": {
|
|
200
|
+
"src/resolve.ts": {
|
|
201
|
+
"bytesInOutput": 1677
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
"bytes": 1892
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import {
|
|
5
|
+
createWriteStream,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
readdirSync
|
|
10
|
+
} from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
function formatErrorMessage(error) {
|
|
13
|
+
return error instanceof Error ? error.message : String(error);
|
|
14
|
+
}
|
|
15
|
+
__name(formatErrorMessage, "formatErrorMessage");
|
|
16
|
+
const TICKET_RE = /^(\d+)-(.+)\.md$/;
|
|
17
|
+
function parseTicketFilename(filename) {
|
|
18
|
+
const m = filename.match(TICKET_RE);
|
|
19
|
+
if (!m) return null;
|
|
20
|
+
return { num: Number(m[1]), slug: m[2] };
|
|
21
|
+
}
|
|
22
|
+
__name(parseTicketFilename, "parseTicketFilename");
|
|
23
|
+
function parseStatus(body) {
|
|
24
|
+
const m = body.match(/\*\*Status:\*\*\s*(.+)/i);
|
|
25
|
+
return m ? m[1].trim().toLowerCase() : "";
|
|
26
|
+
}
|
|
27
|
+
__name(parseStatus, "parseStatus");
|
|
28
|
+
function parseBlockers(body) {
|
|
29
|
+
const m = body.match(/\*\*Blocked by:\*\*\s*(.+)/i);
|
|
30
|
+
if (!m) return [];
|
|
31
|
+
const text = m[1];
|
|
32
|
+
if (/none/i.test(text)) return [];
|
|
33
|
+
const nums = [];
|
|
34
|
+
const re = /(\d+)/g;
|
|
35
|
+
let match;
|
|
36
|
+
while (match = re.exec(text)) {
|
|
37
|
+
nums.push(Number(match[1]));
|
|
38
|
+
}
|
|
39
|
+
return nums;
|
|
40
|
+
}
|
|
41
|
+
__name(parseBlockers, "parseBlockers");
|
|
42
|
+
function scanTickets(dir) {
|
|
43
|
+
const files = readdirSync(dir).filter((f) => TICKET_RE.test(f)).sort((a, b) => a.localeCompare(
|
|
44
|
+
b,
|
|
45
|
+
void 0,
|
|
46
|
+
{ numeric: true, sensitivity: "base" }
|
|
47
|
+
));
|
|
48
|
+
return files.map((filename) => {
|
|
49
|
+
const parsed = parseTicketFilename(filename);
|
|
50
|
+
const filepath = join(dir, filename);
|
|
51
|
+
const body = readFileSync(filepath, "utf8");
|
|
52
|
+
return {
|
|
53
|
+
num: parsed.num,
|
|
54
|
+
slug: parsed.slug,
|
|
55
|
+
filename,
|
|
56
|
+
filepath,
|
|
57
|
+
body,
|
|
58
|
+
status: parseStatus(body),
|
|
59
|
+
blockers: parseBlockers(body)
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
__name(scanTickets, "scanTickets");
|
|
64
|
+
function findReadyTickets(tickets) {
|
|
65
|
+
const doneNums = new Set(
|
|
66
|
+
tickets.filter((t) => t.status !== "ready-for-agent").map((t) => t.num)
|
|
67
|
+
);
|
|
68
|
+
return tickets.filter((t) => {
|
|
69
|
+
if (t.status !== "ready-for-agent") return false;
|
|
70
|
+
return t.blockers.every((b) => doneNums.has(b));
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
__name(findReadyTickets, "findReadyTickets");
|
|
74
|
+
function markDone(ticket) {
|
|
75
|
+
const updated = ticket.body.replace(
|
|
76
|
+
/\*\*Status:\*\*\s*.+/i,
|
|
77
|
+
"**Status:** done"
|
|
78
|
+
);
|
|
79
|
+
writeFileSync(ticket.filepath, updated);
|
|
80
|
+
ticket.status = "done";
|
|
81
|
+
ticket.body = updated;
|
|
82
|
+
}
|
|
83
|
+
__name(markDone, "markDone");
|
|
84
|
+
function renderPrompt(specBody, ticket) {
|
|
85
|
+
return [
|
|
86
|
+
"---- SPEC CONTEXT ----",
|
|
87
|
+
specBody,
|
|
88
|
+
"",
|
|
89
|
+
"---- TICKET ----",
|
|
90
|
+
ticket.filename,
|
|
91
|
+
ticket.body,
|
|
92
|
+
"",
|
|
93
|
+
"---- AGENT INSTRUCTIONS ----",
|
|
94
|
+
"You are an autonomous coding agent. Implement the ticket.",
|
|
95
|
+
""
|
|
96
|
+
].join("\n");
|
|
97
|
+
}
|
|
98
|
+
__name(renderPrompt, "renderPrompt");
|
|
99
|
+
function spawnAgent(opts) {
|
|
100
|
+
const args = [];
|
|
101
|
+
if (opts.model) {
|
|
102
|
+
args.push("--model", opts.model);
|
|
103
|
+
}
|
|
104
|
+
const logStream = createWriteStream(
|
|
105
|
+
opts.logPath,
|
|
106
|
+
{ flags: "w" }
|
|
107
|
+
);
|
|
108
|
+
const proc = spawn(opts.agentCmd, args, {
|
|
109
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
110
|
+
});
|
|
111
|
+
proc.stdout.pipe(logStream);
|
|
112
|
+
proc.stderr.pipe(logStream);
|
|
113
|
+
const timeout = setTimeout(() => {
|
|
114
|
+
if (!proc.killed) {
|
|
115
|
+
console.warn(
|
|
116
|
+
`Agent did not exit in ${opts.timeoutSecs}s; killing.`
|
|
117
|
+
);
|
|
118
|
+
proc.kill("SIGKILL");
|
|
119
|
+
}
|
|
120
|
+
}, opts.timeoutSecs * 1e3);
|
|
121
|
+
proc.stdin.write(opts.prompt);
|
|
122
|
+
proc.stdin.end();
|
|
123
|
+
return new Promise((resolve) => {
|
|
124
|
+
proc.on("close", (code) => {
|
|
125
|
+
clearTimeout(timeout);
|
|
126
|
+
logStream.end();
|
|
127
|
+
resolve(code ?? 1);
|
|
128
|
+
});
|
|
129
|
+
proc.on("error", (err) => {
|
|
130
|
+
clearTimeout(timeout);
|
|
131
|
+
logStream.end();
|
|
132
|
+
console.error(
|
|
133
|
+
"Agent process error:",
|
|
134
|
+
formatErrorMessage(err)
|
|
135
|
+
);
|
|
136
|
+
resolve(1);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
__name(spawnAgent, "spawnAgent");
|
|
141
|
+
function dryRun(tickets, specBody) {
|
|
142
|
+
console.log("\nTickets:");
|
|
143
|
+
for (const t of tickets) {
|
|
144
|
+
const blockerStr = t.blockers.length > 0 ? `blocked by [${t.blockers.join(", ")}]` : "no blockers";
|
|
145
|
+
console.log(
|
|
146
|
+
` ${t.filename} (${t.status}) ${blockerStr}`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
console.log("\nSimulated wave order:");
|
|
150
|
+
const sim = new Map(
|
|
151
|
+
tickets.map((t) => [t.num, t.status])
|
|
152
|
+
);
|
|
153
|
+
let wave = 1;
|
|
154
|
+
while (true) {
|
|
155
|
+
const doneNums = new Set(
|
|
156
|
+
[...sim.entries()].filter(([, s]) => s !== "ready-for-agent").map(([n]) => n)
|
|
157
|
+
);
|
|
158
|
+
const ready = tickets.filter((t) => {
|
|
159
|
+
if (sim.get(t.num) !== "ready-for-agent") {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
return t.blockers.every(
|
|
163
|
+
(b) => doneNums.has(b)
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
if (ready.length === 0) break;
|
|
167
|
+
console.log(
|
|
168
|
+
` Wave ${wave}: ` + ready.map((t) => t.filename).join(", ")
|
|
169
|
+
);
|
|
170
|
+
for (const t of ready) {
|
|
171
|
+
sim.set(t.num, "done");
|
|
172
|
+
}
|
|
173
|
+
wave++;
|
|
174
|
+
}
|
|
175
|
+
const stuck = tickets.filter(
|
|
176
|
+
(t) => sim.get(t.num) === "ready-for-agent"
|
|
177
|
+
);
|
|
178
|
+
if (stuck.length > 0) {
|
|
179
|
+
console.log(
|
|
180
|
+
"\nDeadlocked tickets: " + stuck.map((t) => t.filename).join(", ")
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
const firstReady = findReadyTickets(tickets);
|
|
184
|
+
if (firstReady.length > 0) {
|
|
185
|
+
console.log(
|
|
186
|
+
`
|
|
187
|
+
Prompt for first ready ticket (${firstReady[0].filename}):
|
|
188
|
+
`
|
|
189
|
+
);
|
|
190
|
+
console.log(
|
|
191
|
+
renderPrompt(specBody, firstReady[0])
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
__name(dryRun, "dryRun");
|
|
196
|
+
async function runWaves(tickets, specBody, agentCmd, model, timeoutSecs, logsDir) {
|
|
197
|
+
mkdirSync(logsDir, { recursive: true });
|
|
198
|
+
let totalCompleted = 0;
|
|
199
|
+
let waveNum = 0;
|
|
200
|
+
while (true) {
|
|
201
|
+
const ready = findReadyTickets(tickets);
|
|
202
|
+
if (ready.length === 0) break;
|
|
203
|
+
waveNum++;
|
|
204
|
+
console.log(
|
|
205
|
+
`
|
|
206
|
+
Wave ${waveNum}: ` + ready.map((t) => t.filename).join(", ")
|
|
207
|
+
);
|
|
208
|
+
const results = await Promise.allSettled(
|
|
209
|
+
ready.map(async (t) => {
|
|
210
|
+
const prompt = renderPrompt(specBody, t);
|
|
211
|
+
const logPath = join(
|
|
212
|
+
logsDir,
|
|
213
|
+
t.filename.replace(/\.md$/, ".log")
|
|
214
|
+
);
|
|
215
|
+
console.log(` Starting ${t.filename}`);
|
|
216
|
+
const code = await spawnAgent({
|
|
217
|
+
prompt,
|
|
218
|
+
agentCmd,
|
|
219
|
+
model,
|
|
220
|
+
timeoutSecs,
|
|
221
|
+
logPath
|
|
222
|
+
});
|
|
223
|
+
return { ticket: t, exitCode: code };
|
|
224
|
+
})
|
|
225
|
+
);
|
|
226
|
+
let waveCompleted = 0;
|
|
227
|
+
for (const r of results) {
|
|
228
|
+
if (r.status === "rejected") continue;
|
|
229
|
+
const { ticket, exitCode } = r.value;
|
|
230
|
+
if (exitCode === 0) {
|
|
231
|
+
markDone(ticket);
|
|
232
|
+
waveCompleted++;
|
|
233
|
+
totalCompleted++;
|
|
234
|
+
console.log(
|
|
235
|
+
` ${ticket.filename}: done`
|
|
236
|
+
);
|
|
237
|
+
} else {
|
|
238
|
+
console.log(
|
|
239
|
+
` ${ticket.filename}: agent exited ${exitCode}`
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (waveCompleted === 0) {
|
|
244
|
+
console.log(
|
|
245
|
+
"\nNo tickets completed this wave. Stopping."
|
|
246
|
+
);
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const remaining = tickets.filter(
|
|
251
|
+
(t) => t.status === "ready-for-agent"
|
|
252
|
+
);
|
|
253
|
+
console.log(
|
|
254
|
+
`
|
|
255
|
+
Finished: ${totalCompleted} completed` + (remaining.length > 0 ? `, ${remaining.length} remaining` : "")
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
__name(runWaves, "runWaves");
|
|
259
|
+
export {
|
|
260
|
+
TICKET_RE,
|
|
261
|
+
dryRun,
|
|
262
|
+
findReadyTickets,
|
|
263
|
+
formatErrorMessage,
|
|
264
|
+
markDone,
|
|
265
|
+
parseBlockers,
|
|
266
|
+
parseStatus,
|
|
267
|
+
parseTicketFilename,
|
|
268
|
+
renderPrompt,
|
|
269
|
+
runWaves,
|
|
270
|
+
scanTickets,
|
|
271
|
+
spawnAgent
|
|
272
|
+
};
|
|
273
|
+
//# sourceMappingURL=orchestrator.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/orchestrator.ts"],
|
|
4
|
+
"sourcesContent": ["import { spawn } from 'node:child_process'\nimport {\n createWriteStream,\n mkdirSync,\n readFileSync,\n writeFileSync,\n readdirSync,\n} from 'node:fs'\nimport { join } from 'node:path'\n\nexport type Ticket = {\n num:number\n slug:string\n filename:string\n filepath:string\n body:string\n status:string\n blockers:number[]\n}\n\nexport function formatErrorMessage (error:unknown):string {\n return error instanceof Error ?\n error.message :\n String(error)\n}\n\nexport const TICKET_RE = /^(\\d+)-(.+)\\.md$/\n\nexport function parseTicketFilename (\n filename:string\n):{ num:number; slug:string }|null {\n const m = filename.match(TICKET_RE)\n if (!m) return null\n return { num:Number(m[1]), slug:m[2] }\n}\n\nexport function parseStatus (body:string):string {\n const m = body.match(/\\*\\*Status:\\*\\*\\s*(.+)/i)\n return m ? m[1].trim().toLowerCase() : ''\n}\n\nexport function parseBlockers (body:string):number[] {\n const m = body.match(/\\*\\*Blocked by:\\*\\*\\s*(.+)/i)\n if (!m) return []\n const text = m[1]\n if (/none/i.test(text)) return []\n const nums:number[] = []\n const re = /(\\d+)/g\n let match\n while ((match = re.exec(text))) {\n nums.push(Number(match[1]))\n }\n return nums\n}\n\nexport function scanTickets (dir:string):Ticket[] {\n const files = readdirSync(dir)\n .filter(f => TICKET_RE.test(f))\n .sort((a, b) => a.localeCompare(\n b,\n undefined,\n { numeric:true, sensitivity:'base' }\n ))\n\n return files.map(filename => {\n const parsed = parseTicketFilename(filename)!\n const filepath = join(dir, filename)\n const body = readFileSync(filepath, 'utf8')\n return {\n num:parsed.num,\n slug:parsed.slug,\n filename,\n filepath,\n body,\n status:parseStatus(body),\n blockers:parseBlockers(body),\n }\n })\n}\n\nexport function findReadyTickets (\n tickets:ReadonlyArray<Ticket>\n):Ticket[] {\n const doneNums = new Set(\n tickets\n .filter(t => t.status !== 'ready-for-agent')\n .map(t => t.num)\n )\n return tickets.filter(t => {\n if (t.status !== 'ready-for-agent') return false\n return t.blockers.every(b => doneNums.has(b))\n })\n}\n\nexport function markDone (ticket:Ticket):void {\n const updated = ticket.body.replace(\n /\\*\\*Status:\\*\\*\\s*.+/i,\n '**Status:** done'\n )\n writeFileSync(ticket.filepath, updated)\n ticket.status = 'done'\n ticket.body = updated\n}\n\nexport function renderPrompt (\n specBody:string,\n ticket:Ticket\n):string {\n return [\n '---- SPEC CONTEXT ----',\n specBody,\n '',\n '---- TICKET ----',\n ticket.filename,\n ticket.body,\n '',\n '---- AGENT INSTRUCTIONS ----',\n 'You are an autonomous coding agent. ' +\n 'Implement the ticket.',\n '',\n ].join('\\n')\n}\n\nexport function spawnAgent (opts:{\n prompt:string\n agentCmd:string\n model:string|null\n timeoutSecs:number\n logPath:string\n}):Promise<number> {\n const args:string[] = []\n if (opts.model) {\n args.push('--model', opts.model)\n }\n\n const logStream = createWriteStream(\n opts.logPath,\n { flags:'w' }\n )\n const proc = spawn(opts.agentCmd, args, {\n stdio:['pipe', 'pipe', 'pipe'],\n })\n\n proc.stdout.pipe(logStream)\n proc.stderr.pipe(logStream)\n\n const timeout = setTimeout(() => {\n if (!proc.killed) {\n console.warn(\n 'Agent did not exit in ' +\n `${opts.timeoutSecs}s; killing.`\n )\n proc.kill('SIGKILL')\n }\n }, opts.timeoutSecs * 1000)\n\n proc.stdin.write(opts.prompt)\n proc.stdin.end()\n\n return new Promise<number>(resolve => {\n proc.on('close', code => {\n clearTimeout(timeout)\n logStream.end()\n resolve(code ?? 1)\n })\n proc.on('error', err => {\n clearTimeout(timeout)\n logStream.end()\n console.error(\n 'Agent process error:',\n formatErrorMessage(err)\n )\n resolve(1)\n })\n })\n}\n\nexport function dryRun (\n tickets:Ticket[],\n specBody:string\n):void {\n console.log('\\nTickets:')\n for (const t of tickets) {\n const blockerStr = t.blockers.length > 0 ?\n `blocked by [${t.blockers.join(', ')}]` :\n 'no blockers'\n console.log(\n ` ${t.filename} ` +\n `(${t.status}) ${blockerStr}`\n )\n }\n\n console.log('\\nSimulated wave order:')\n const sim = new Map(\n tickets.map(t => [t.num, t.status])\n )\n let wave = 1\n while (true) {\n const doneNums = new Set(\n [...sim.entries()]\n .filter(([, s]) => s !== 'ready-for-agent')\n .map(([n]) => n)\n )\n const ready = tickets.filter(t => {\n if (sim.get(t.num) !== 'ready-for-agent') {\n return false\n }\n return t.blockers.every(\n b => doneNums.has(b)\n )\n })\n if (ready.length === 0) break\n console.log(\n ` Wave ${wave}: ` +\n ready.map(t => t.filename).join(', ')\n )\n for (const t of ready) {\n sim.set(t.num, 'done')\n }\n wave++\n }\n\n const stuck = tickets.filter(\n t => sim.get(t.num) === 'ready-for-agent'\n )\n if (stuck.length > 0) {\n console.log(\n '\\nDeadlocked tickets: ' +\n stuck.map(t => t.filename).join(', ')\n )\n }\n\n const firstReady = findReadyTickets(tickets)\n if (firstReady.length > 0) {\n console.log(\n '\\nPrompt for first ready ticket ' +\n `(${firstReady[0].filename}):\\n`\n )\n console.log(\n renderPrompt(specBody, firstReady[0])\n )\n }\n}\n\nexport async function runWaves (\n tickets:Ticket[],\n specBody:string,\n agentCmd:string,\n model:string|null,\n timeoutSecs:number,\n logsDir:string,\n):Promise<void> {\n mkdirSync(logsDir, { recursive:true })\n\n let totalCompleted = 0\n let waveNum = 0\n\n while (true) {\n const ready = findReadyTickets(tickets)\n if (ready.length === 0) break\n\n waveNum++\n console.log(\n `\\nWave ${waveNum}: ` +\n ready.map(t => t.filename).join(', ')\n )\n\n const results = await Promise.allSettled(\n ready.map(async t => {\n const prompt = renderPrompt(specBody, t)\n const logPath = join(\n logsDir,\n t.filename.replace(/\\.md$/, '.log')\n )\n console.log(` Starting ${t.filename}`)\n const code = await spawnAgent({\n prompt,\n agentCmd,\n model,\n timeoutSecs,\n logPath,\n })\n return { ticket:t, exitCode:code }\n })\n )\n\n let waveCompleted = 0\n for (const r of results) {\n if (r.status === 'rejected') continue\n const { ticket, exitCode } = r.value\n if (exitCode === 0) {\n markDone(ticket)\n waveCompleted++\n totalCompleted++\n console.log(\n ` ${ticket.filename}: done`\n )\n } else {\n console.log(\n ` ${ticket.filename}: ` +\n `agent exited ${exitCode}`\n )\n }\n }\n\n if (waveCompleted === 0) {\n console.log(\n '\\nNo tickets completed this wave. ' +\n 'Stopping.'\n )\n break\n }\n }\n\n const remaining = tickets.filter(\n t => t.status === 'ready-for-agent'\n )\n console.log(\n `\\nFinished: ${totalCompleted} completed` +\n (remaining.length > 0 ?\n `, ${remaining.length} remaining` :\n '')\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|