nightralph 0.0.34 → 0.0.36
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 +1 -1
- package/dist/docs-templates/issue-tracker.md +14 -6
- package/dist/index.js +58 -59
- package/dist/index.js.map +2 -2
- package/dist/meta.json +6 -6
- package/dist/orchestrator.js +53 -55
- package/dist/orchestrator.js.map +2 -2
- package/dist/skills/to-tickets/SKILL.md +2 -0
- package/dist/src/orchestrator.d.ts +6 -6
- package/dist/src/orchestrator.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,18 +6,26 @@ Issues and specs for this repo live as markdown files in `.scratch/`.
|
|
|
6
6
|
|
|
7
7
|
- One feature per directory: `.scratch/<feature-slug>/`
|
|
8
8
|
- The spec is `.scratch/<feature-slug>/spec.md`
|
|
9
|
-
- Implementation issues are one file per ticket at
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
-
|
|
9
|
+
- Implementation issues are one file per ticket at
|
|
10
|
+
`.scratch/<feature-slug>/issues/<NN>-<slug>.md`, numbered from `01`,
|
|
11
|
+
never a single combined tickets file
|
|
12
|
+
- Triage state is recorded as a `**Status:**` line near the top of each
|
|
13
|
+
issue file (see `triage-labels.md` for the role strings)
|
|
14
|
+
- Dependencies are recorded as a `**Blocked by:**` line near the top of
|
|
15
|
+
each issue file, listing the numbers of the tickets that must complete
|
|
16
|
+
first, or `None`
|
|
17
|
+
- Comments and conversation history append to the bottom of the file under
|
|
18
|
+
a `## Comments` heading
|
|
13
19
|
|
|
14
20
|
## When a skill says "publish to the issue tracker"
|
|
15
21
|
|
|
16
|
-
Create a new file under `.scratch/<feature-slug>/` (creating the directory
|
|
22
|
+
Create a new file under `.scratch/<feature-slug>/` (creating the directory
|
|
23
|
+
if needed).
|
|
17
24
|
|
|
18
25
|
## When a skill says "fetch the relevant ticket"
|
|
19
26
|
|
|
20
|
-
Read the file at the referenced path. The user will normally pass the path
|
|
27
|
+
Read the file at the referenced path. The user will normally pass the path
|
|
28
|
+
or the issue number directly.
|
|
21
29
|
|
|
22
30
|
## Tickets are executed within hours, not weeks
|
|
23
31
|
|
package/dist/index.js
CHANGED
|
@@ -1292,11 +1292,7 @@ function stripSpecSection(specBody, heading) {
|
|
|
1292
1292
|
return out.join("\n");
|
|
1293
1293
|
}
|
|
1294
1294
|
__name(stripSpecSection, "stripSpecSection");
|
|
1295
|
-
function renderPrompt(specBody, ticket, opts
|
|
1296
|
-
const testRules = opts.testCmd ? [
|
|
1297
|
-
"- Work test-first. For each checklist item, write a failing test at the seams named in the spec's Testing Decisions section, then write only enough code to make it pass.",
|
|
1298
|
-
`- Run \`${opts.testCmd}\` before committing. Do not commit with failing tests. The orchestrator runs the same command after you exit and rejects the ticket if it fails.`
|
|
1299
|
-
] : [];
|
|
1295
|
+
function renderPrompt(specBody, ticket, opts) {
|
|
1300
1296
|
return [
|
|
1301
1297
|
"---- SPEC CONTEXT ----",
|
|
1302
1298
|
specBody,
|
|
@@ -1306,12 +1302,13 @@ function renderPrompt(specBody, ticket, opts = {}) {
|
|
|
1306
1302
|
ticket.body,
|
|
1307
1303
|
"",
|
|
1308
1304
|
"---- AGENT INSTRUCTIONS ----",
|
|
1309
|
-
"You are an autonomous coding agent. Implement the ticket.",
|
|
1305
|
+
"You are an autonomous coding agent. Implement the ticket using /tdd.",
|
|
1310
1306
|
"",
|
|
1311
1307
|
"Follow these rules:",
|
|
1312
1308
|
"- Read the files directly relevant to the ticket before writing code.",
|
|
1313
1309
|
"- Do NOT research external APIs or services via web search. Use the spec and ticket body as your sole reference for API shapes.",
|
|
1314
|
-
|
|
1310
|
+
"- Use /tdd for every checklist item. For each item, write a failing test at the seams named in the spec's Testing Decisions section, then write only enough code to make it pass, then refactor.",
|
|
1311
|
+
`- Run \`${opts.testCmd}\` before committing. Do not commit with failing tests. The orchestrator runs the same command after you exit and rejects the ticket if it fails.`,
|
|
1315
1312
|
"- Do NOT read or modify files outside your current directory (the worktree).",
|
|
1316
1313
|
"- Do NOT run git push.",
|
|
1317
1314
|
"- Commit your work when finished.",
|
|
@@ -1816,7 +1813,7 @@ function spawnAgent(opts) {
|
|
|
1816
1813
|
});
|
|
1817
1814
|
}
|
|
1818
1815
|
__name(spawnAgent, "spawnAgent");
|
|
1819
|
-
function dryRun(tickets, specBody) {
|
|
1816
|
+
function dryRun(tickets, specBody, testCmd) {
|
|
1820
1817
|
listTickets(tickets);
|
|
1821
1818
|
console.log("\nSimulated wave order:");
|
|
1822
1819
|
for (const { wave, ready } of simulateWaves(tickets)) {
|
|
@@ -1838,7 +1835,11 @@ Prompt for first ready ticket (${firstReady[0].filename}):
|
|
|
1838
1835
|
`
|
|
1839
1836
|
);
|
|
1840
1837
|
console.log(
|
|
1841
|
-
renderPrompt(
|
|
1838
|
+
renderPrompt(
|
|
1839
|
+
specBody,
|
|
1840
|
+
firstReady[0],
|
|
1841
|
+
{ testCmd }
|
|
1842
|
+
)
|
|
1842
1843
|
);
|
|
1843
1844
|
}
|
|
1844
1845
|
}
|
|
@@ -1884,7 +1885,7 @@ Prompt for first ready ticket (${firstReady[0].filename}):
|
|
|
1884
1885
|
const strategyDesc = opts.conflictStrategy === "respawn" ? "respawn (re-run agent on conflict)" : "stop (stop merging on conflict)";
|
|
1885
1886
|
console.log(
|
|
1886
1887
|
`
|
|
1887
|
-
Test command: ${opts.testCmd
|
|
1888
|
+
Test command: ${opts.testCmd}`
|
|
1888
1889
|
);
|
|
1889
1890
|
console.log(
|
|
1890
1891
|
`Effort: ${opts.effort ?? "(provider default)"}`
|
|
@@ -1976,22 +1977,20 @@ async function respawnAndRetryMerge(opts) {
|
|
|
1976
1977
|
}
|
|
1977
1978
|
return false;
|
|
1978
1979
|
}
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
return false;
|
|
1994
|
-
}
|
|
1980
|
+
const passed = await runTestGate({
|
|
1981
|
+
testCmd: opts.testCmd,
|
|
1982
|
+
ticket: opts.ticket,
|
|
1983
|
+
worktreePath: opts.worktreePath,
|
|
1984
|
+
timeout: opts.timeout,
|
|
1985
|
+
logsDir: opts.logsDir,
|
|
1986
|
+
log,
|
|
1987
|
+
registry: runningAgents
|
|
1988
|
+
});
|
|
1989
|
+
if (!passed) {
|
|
1990
|
+
log(
|
|
1991
|
+
` ${opts.ticket.filename}: tests failed after conflict respawn; not merging`
|
|
1992
|
+
);
|
|
1993
|
+
return false;
|
|
1995
1994
|
}
|
|
1996
1995
|
const mergeResult = await mergeBranch(
|
|
1997
1996
|
opts.repoRoot,
|
|
@@ -2107,30 +2106,28 @@ async function runTicketAttempt(opts) {
|
|
|
2107
2106
|
branchName: worktree.branchName
|
|
2108
2107
|
};
|
|
2109
2108
|
}
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
};
|
|
2133
|
-
}
|
|
2109
|
+
const passed = await runTestGate({
|
|
2110
|
+
testCmd: opts.testCmd,
|
|
2111
|
+
ticket,
|
|
2112
|
+
worktreePath: worktree.worktreePath,
|
|
2113
|
+
timeout: opts.timeout,
|
|
2114
|
+
logsDir: opts.logsDir,
|
|
2115
|
+
attempt: opts.attempt,
|
|
2116
|
+
log: /* @__PURE__ */ __name((msg) => d.log(msg), "log"),
|
|
2117
|
+
registry: runningAgents
|
|
2118
|
+
});
|
|
2119
|
+
if (!passed) {
|
|
2120
|
+
await removeWorktreeQuietly(
|
|
2121
|
+
worktree.worktreePath,
|
|
2122
|
+
d
|
|
2123
|
+
);
|
|
2124
|
+
return {
|
|
2125
|
+
kind: "failed",
|
|
2126
|
+
reason: "tests",
|
|
2127
|
+
branchName: worktree.branchName,
|
|
2128
|
+
exitCode: 1,
|
|
2129
|
+
completedItems: agentResult.completedItems
|
|
2130
|
+
};
|
|
2134
2131
|
}
|
|
2135
2132
|
return {
|
|
2136
2133
|
kind: "ready",
|
|
@@ -2204,7 +2201,7 @@ function runProgress(tickets) {
|
|
|
2204
2201
|
};
|
|
2205
2202
|
}
|
|
2206
2203
|
__name(runProgress, "runProgress");
|
|
2207
|
-
async function runWaves(tickets, specBody, agentCmd, model, timeout, logsDir, display, maxTurns, effort, retries, retryDelay) {
|
|
2204
|
+
async function runWaves(tickets, specBody, agentCmd, model, timeout, logsDir, testCmd, display, maxTurns, effort, retries, retryDelay) {
|
|
2208
2205
|
mkdirSync2(logsDir, { recursive: true });
|
|
2209
2206
|
const d = display ?? createDisplay();
|
|
2210
2207
|
const maxAttempts = (retries ?? DEFAULT_RETRIES) + 1;
|
|
@@ -2241,7 +2238,11 @@ async function runWaves(tickets, specBody, agentCmd, model, timeout, logsDir, di
|
|
|
2241
2238
|
);
|
|
2242
2239
|
const results = await Promise.allSettled(
|
|
2243
2240
|
ready.map(async (t) => {
|
|
2244
|
-
const prompt = renderPrompt(
|
|
2241
|
+
const prompt = renderPrompt(
|
|
2242
|
+
specBody,
|
|
2243
|
+
t,
|
|
2244
|
+
{ testCmd }
|
|
2245
|
+
);
|
|
2245
2246
|
const label = t.filename.replace(
|
|
2246
2247
|
/\.md$/,
|
|
2247
2248
|
""
|
|
@@ -2368,9 +2369,6 @@ async function runWavesIsolated(opts) {
|
|
|
2368
2369
|
`nightralph: start ${opts.featureName}`,
|
|
2369
2370
|
d
|
|
2370
2371
|
);
|
|
2371
|
-
if (!opts.testCmd) {
|
|
2372
|
-
d.log(" Test gate disabled: no test command");
|
|
2373
|
-
}
|
|
2374
2372
|
let totalCompleted = 0;
|
|
2375
2373
|
let waveNum = 0;
|
|
2376
2374
|
const failedNums = /* @__PURE__ */ new Set();
|
|
@@ -2823,7 +2821,7 @@ yargs(hideBin(process.argv)).scriptName("nightralph").usage("$0 <command>").comm
|
|
|
2823
2821
|
default: 3600
|
|
2824
2822
|
}).option("test-cmd", {
|
|
2825
2823
|
type: "string",
|
|
2826
|
-
describe: 'Command run in each worktree after the agent exits; non-zero exit rejects the ticket. Defaults to "npm test"
|
|
2824
|
+
describe: 'Command run in each worktree after the agent exits; non-zero exit rejects the ticket. Defaults to "npm test".'
|
|
2827
2825
|
}).option("X", {
|
|
2828
2826
|
type: "boolean",
|
|
2829
2827
|
describe: "Stop on merge conflict instead of re-spawning",
|
|
@@ -2918,9 +2916,9 @@ async function runExecute(argv) {
|
|
|
2918
2916
|
}
|
|
2919
2917
|
const logsDir = join6(dirname3(dir), "logs");
|
|
2920
2918
|
const timeout = argv.timeout ?? 900;
|
|
2921
|
-
const testCmd = argv.testCmd
|
|
2919
|
+
const testCmd = argv.testCmd || detectTestCmd(
|
|
2922
2920
|
isGitRepo() ? getRepoRoot() : process.cwd()
|
|
2923
|
-
)
|
|
2921
|
+
) || "npm test";
|
|
2924
2922
|
const featureName = extractFeatureName(spec);
|
|
2925
2923
|
const conflictStrategy = argv.stopOnConflict ? "stop" : "respawn";
|
|
2926
2924
|
const progressPath = join6(
|
|
@@ -2942,7 +2940,7 @@ async function runExecute(argv) {
|
|
|
2942
2940
|
retryDelay: argv.retryDelay
|
|
2943
2941
|
});
|
|
2944
2942
|
} else {
|
|
2945
|
-
dryRun(tickets, specBody);
|
|
2943
|
+
dryRun(tickets, specBody, testCmd);
|
|
2946
2944
|
}
|
|
2947
2945
|
return;
|
|
2948
2946
|
}
|
|
@@ -2981,6 +2979,7 @@ async function runExecute(argv) {
|
|
|
2981
2979
|
argv.model ?? null,
|
|
2982
2980
|
timeout,
|
|
2983
2981
|
logsDir,
|
|
2982
|
+
testCmd,
|
|
2984
2983
|
display,
|
|
2985
2984
|
argv.maxTurns,
|
|
2986
2985
|
argv.effort,
|