nightralph 0.0.37 → 0.0.40
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 -7
- package/dist/index.js +135 -26
- package/dist/index.js.map +3 -3
- package/dist/meta.json +21 -11
- package/dist/orchestrator.js +68 -7
- package/dist/orchestrator.js.map +2 -2
- package/dist/setup.js +16 -0
- package/dist/setup.js.map +2 -2
- package/dist/src/orchestrator.d.ts +1 -0
- package/dist/src/orchestrator.d.ts.map +1 -1
- package/dist/src/setup.d.ts.map +1 -1
- package/dist/src/worktree.d.ts +1 -0
- package/dist/src/worktree.d.ts.map +1 -1
- package/dist/worktree.js +37 -5
- package/dist/worktree.js.map +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -39,6 +39,7 @@ so they can't drift as long as you install the skills with this package.
|
|
|
39
39
|
+ [`pi` + OpenRouter model](#pi--openrouter-model)
|
|
40
40
|
+ [`codex` + Luna](#codex--luna)
|
|
41
41
|
+ [`codex` + Terra](#codex--terra)
|
|
42
|
+
+ [Mercury](#mercury)
|
|
42
43
|
|
|
43
44
|
<!-- tocstop -->
|
|
44
45
|
|
|
@@ -300,7 +301,7 @@ keep the machine awake, but the display can still go dark.
|
|
|
300
301
|
#### Claude Opus 4.6
|
|
301
302
|
|
|
302
303
|
```sh
|
|
303
|
-
caffeinate -i npx nightralph claude -m claude-opus-4-6 --effort
|
|
304
|
+
caffeinate -i npx nightralph claude -m claude-opus-4-6 --effort low
|
|
304
305
|
```
|
|
305
306
|
|
|
306
307
|
#### Claude Sonnet 5
|
|
@@ -312,13 +313,9 @@ caffeinate -i npx nightralph claude -m claude-sonnet-5 --effort high
|
|
|
312
313
|
#### `pi` + OpenRouter model
|
|
313
314
|
|
|
314
315
|
```sh
|
|
315
|
-
caffeinate -i npx nightralph pi -m openrouter/z-ai/glm-5.3-flash --
|
|
316
|
+
caffeinate -i npx nightralph pi -m openrouter/z-ai/glm-5.3-flash --effort high
|
|
316
317
|
```
|
|
317
318
|
|
|
318
|
-
##### pi `--thinking` levels
|
|
319
|
-
|
|
320
|
-
`off`, `minimal`, `low`, `medium`,`high`, and `xhigh`
|
|
321
|
-
|
|
322
319
|
#### `codex` + Luna
|
|
323
320
|
|
|
324
321
|
```sh
|
|
@@ -328,5 +325,11 @@ caffeinate -i npx nightralph codex -m gpt-5.6-luna --effort high
|
|
|
328
325
|
#### `codex` + Terra
|
|
329
326
|
|
|
330
327
|
```sh
|
|
331
|
-
caffeinate -i npx nightralph codex -m gpt-5.6-
|
|
328
|
+
caffeinate -i npx nightralph codex -m gpt-5.6-terra --effort medium
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
#### Mercury
|
|
332
|
+
|
|
333
|
+
```sh
|
|
334
|
+
caffeinate -i npx nightralph pi -m openrouter/inception/mercury-2.5 --effort high
|
|
332
335
|
```
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
|
|
|
4
4
|
|
|
5
5
|
// src/index.ts
|
|
6
6
|
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
|
|
7
|
-
import { dirname as
|
|
7
|
+
import { dirname as dirname4, join as join6 } from "node:path";
|
|
8
8
|
import yargs from "yargs";
|
|
9
9
|
import { hideBin } from "yargs/helpers";
|
|
10
10
|
import { select as select2 } from "@inquirer/prompts";
|
|
@@ -61,6 +61,17 @@ function writeManagedSection(filePath, content) {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
__name(writeManagedSection, "writeManagedSection");
|
|
64
|
+
function ensureGitAttribute(filePath, line) {
|
|
65
|
+
if (!existsSync(filePath)) {
|
|
66
|
+
writeFileSync(filePath, line + "\n");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const existing = readFileSync(filePath, "utf8");
|
|
70
|
+
if (existing.split("\n").includes(line)) return;
|
|
71
|
+
const sep = existing.endsWith("\n") ? "" : "\n";
|
|
72
|
+
writeFileSync(filePath, existing + sep + line + "\n");
|
|
73
|
+
}
|
|
74
|
+
__name(ensureGitAttribute, "ensureGitAttribute");
|
|
64
75
|
function checkGhAuth() {
|
|
65
76
|
try {
|
|
66
77
|
execSync("gh auth status", { stdio: "ignore" });
|
|
@@ -171,6 +182,11 @@ function setup(opts) {
|
|
|
171
182
|
const agentsPath = join(projectDir, "AGENTS.md");
|
|
172
183
|
writeManagedSection(agentsPath, managedContent);
|
|
173
184
|
console.log("\n AGENTS.md (updated)");
|
|
185
|
+
ensureGitAttribute(
|
|
186
|
+
join(projectDir, ".gitattributes"),
|
|
187
|
+
"AGENTS.md merge=union"
|
|
188
|
+
);
|
|
189
|
+
console.log(" .gitattributes (AGENTS.md merge=union)");
|
|
174
190
|
console.log(
|
|
175
191
|
`
|
|
176
192
|
Source: ${upstream.repo} @ ${upstream.commit}`
|
|
@@ -179,7 +195,7 @@ Source: ${upstream.repo} @ ${upstream.commit}`
|
|
|
179
195
|
__name(setup, "setup");
|
|
180
196
|
|
|
181
197
|
// src/orchestrator.ts
|
|
182
|
-
import { spawn as spawn2 } from "node:child_process";
|
|
198
|
+
import { spawn as spawn2, execFile as execFile4 } from "node:child_process";
|
|
183
199
|
import {
|
|
184
200
|
createWriteStream,
|
|
185
201
|
mkdirSync as mkdirSync2,
|
|
@@ -189,13 +205,14 @@ import {
|
|
|
189
205
|
readdirSync as readdirSync2
|
|
190
206
|
} from "node:fs";
|
|
191
207
|
import { tmpdir } from "node:os";
|
|
192
|
-
import { join as join4, dirname, basename as basename2 } from "node:path";
|
|
208
|
+
import { join as join4, dirname as dirname2, basename as basename2 } from "node:path";
|
|
193
209
|
import { createInterface } from "node:readline";
|
|
210
|
+
import { promisify as promisify4 } from "node:util";
|
|
194
211
|
|
|
195
212
|
// src/worktree.ts
|
|
196
213
|
import { execFile, execFileSync } from "node:child_process";
|
|
197
214
|
import { promisify } from "node:util";
|
|
198
|
-
import { basename, join as join2 } from "node:path";
|
|
215
|
+
import { basename, dirname, join as join2, resolve } from "node:path";
|
|
199
216
|
import { existsSync as existsSync2 } from "node:fs";
|
|
200
217
|
var execFileAsync = promisify(execFile);
|
|
201
218
|
function getRepoRoot() {
|
|
@@ -208,7 +225,12 @@ function getRepoRoot() {
|
|
|
208
225
|
}
|
|
209
226
|
__name(getRepoRoot, "getRepoRoot");
|
|
210
227
|
function getRepoName() {
|
|
211
|
-
|
|
228
|
+
const commonDir = execFileSync(
|
|
229
|
+
"git",
|
|
230
|
+
["rev-parse", "--git-common-dir"],
|
|
231
|
+
{ encoding: "utf8" }
|
|
232
|
+
).trim();
|
|
233
|
+
return basename(dirname(resolve(commonDir)));
|
|
212
234
|
}
|
|
213
235
|
__name(getRepoName, "getRepoName");
|
|
214
236
|
function getCurrentBranch() {
|
|
@@ -220,6 +242,24 @@ function getCurrentBranch() {
|
|
|
220
242
|
return stdout.trim();
|
|
221
243
|
}
|
|
222
244
|
__name(getCurrentBranch, "getCurrentBranch");
|
|
245
|
+
function branchExistsSync(repoRoot, branchName) {
|
|
246
|
+
try {
|
|
247
|
+
execFileSync(
|
|
248
|
+
"git",
|
|
249
|
+
[
|
|
250
|
+
"show-ref",
|
|
251
|
+
"--verify",
|
|
252
|
+
"--quiet",
|
|
253
|
+
`refs/heads/${branchName}`
|
|
254
|
+
],
|
|
255
|
+
{ cwd: repoRoot }
|
|
256
|
+
);
|
|
257
|
+
return true;
|
|
258
|
+
} catch {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
__name(branchExistsSync, "branchExistsSync");
|
|
223
263
|
async function branchExists(repoRoot, branchName) {
|
|
224
264
|
try {
|
|
225
265
|
await execFileAsync(
|
|
@@ -233,11 +273,19 @@ async function branchExists(repoRoot, branchName) {
|
|
|
233
273
|
}
|
|
234
274
|
}
|
|
235
275
|
__name(branchExists, "branchExists");
|
|
276
|
+
function buildBranchName(repoRoot, repoName, ticket) {
|
|
277
|
+
const num = String(ticket.num).padStart(2, "0");
|
|
278
|
+
const sep = branchExistsSync(repoRoot, repoName) ? "--" : "/";
|
|
279
|
+
return `${repoName}${sep}${num}-${ticket.slug}`;
|
|
280
|
+
}
|
|
281
|
+
__name(buildBranchName, "buildBranchName");
|
|
236
282
|
async function createWorktreeNow(opts) {
|
|
237
283
|
const { repoRoot, repoName, baseBranch, ticket } = opts;
|
|
238
|
-
const branchName =
|
|
239
|
-
|
|
240
|
-
|
|
284
|
+
const branchName = buildBranchName(
|
|
285
|
+
repoRoot,
|
|
286
|
+
repoName,
|
|
287
|
+
ticket
|
|
288
|
+
);
|
|
241
289
|
const worktreePath = join2(
|
|
242
290
|
repoRoot,
|
|
243
291
|
"..",
|
|
@@ -1026,7 +1074,7 @@ function detectTestCmd(projectDir) {
|
|
|
1026
1074
|
}
|
|
1027
1075
|
__name(detectTestCmd, "detectTestCmd");
|
|
1028
1076
|
function runTestCmd(opts) {
|
|
1029
|
-
return new Promise((
|
|
1077
|
+
return new Promise((resolve2) => {
|
|
1030
1078
|
let settled = false;
|
|
1031
1079
|
const child = spawn("sh", ["-c", opts.cmd], {
|
|
1032
1080
|
cwd: opts.cwd,
|
|
@@ -1058,7 +1106,7 @@ function runTestCmd(opts) {
|
|
|
1058
1106
|
opts.registry.delete(child.pid);
|
|
1059
1107
|
}
|
|
1060
1108
|
chunks.push(String(err));
|
|
1061
|
-
|
|
1109
|
+
resolve2({
|
|
1062
1110
|
exitCode: 1,
|
|
1063
1111
|
output: chunks.join("")
|
|
1064
1112
|
});
|
|
@@ -1070,7 +1118,7 @@ function runTestCmd(opts) {
|
|
|
1070
1118
|
if (child.pid !== void 0 && opts.registry) {
|
|
1071
1119
|
opts.registry.delete(child.pid);
|
|
1072
1120
|
}
|
|
1073
|
-
|
|
1121
|
+
resolve2({
|
|
1074
1122
|
exitCode: code ?? 1,
|
|
1075
1123
|
output: chunks.join("")
|
|
1076
1124
|
});
|
|
@@ -1080,6 +1128,7 @@ function runTestCmd(opts) {
|
|
|
1080
1128
|
__name(runTestCmd, "runTestCmd");
|
|
1081
1129
|
|
|
1082
1130
|
// src/orchestrator.ts
|
|
1131
|
+
var execFileAsync4 = promisify4(execFile4);
|
|
1083
1132
|
function formatErrorMessage(error) {
|
|
1084
1133
|
return error instanceof Error ? error.message : String(error);
|
|
1085
1134
|
}
|
|
@@ -1094,6 +1143,48 @@ async function removeWorktreeQuietly(worktreePath, d) {
|
|
|
1094
1143
|
}
|
|
1095
1144
|
}
|
|
1096
1145
|
__name(removeWorktreeQuietly, "removeWorktreeQuietly");
|
|
1146
|
+
async function commitTicketFiles(repoRoot, tickets, d) {
|
|
1147
|
+
const paths = tickets.map((t) => t.filepath);
|
|
1148
|
+
if (paths.length === 0) return;
|
|
1149
|
+
try {
|
|
1150
|
+
await execFileAsync4(
|
|
1151
|
+
"git",
|
|
1152
|
+
["add", "--force", ...paths],
|
|
1153
|
+
{ cwd: repoRoot }
|
|
1154
|
+
);
|
|
1155
|
+
try {
|
|
1156
|
+
await execFileAsync4(
|
|
1157
|
+
"git",
|
|
1158
|
+
[
|
|
1159
|
+
"diff",
|
|
1160
|
+
"--cached",
|
|
1161
|
+
"--quiet",
|
|
1162
|
+
"--",
|
|
1163
|
+
...paths
|
|
1164
|
+
],
|
|
1165
|
+
{ cwd: repoRoot }
|
|
1166
|
+
);
|
|
1167
|
+
return;
|
|
1168
|
+
} catch {
|
|
1169
|
+
}
|
|
1170
|
+
await execFileAsync4(
|
|
1171
|
+
"git",
|
|
1172
|
+
[
|
|
1173
|
+
"commit",
|
|
1174
|
+
"-m",
|
|
1175
|
+
"nightralph: update ticket checklists",
|
|
1176
|
+
"--",
|
|
1177
|
+
...paths
|
|
1178
|
+
],
|
|
1179
|
+
{ cwd: repoRoot }
|
|
1180
|
+
);
|
|
1181
|
+
} catch (err) {
|
|
1182
|
+
d.log(
|
|
1183
|
+
" Failed to commit ticket updates: " + formatErrorMessage(err)
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
__name(commitTicketFiles, "commitTicketFiles");
|
|
1097
1188
|
var TICKET_RE = /^(\d+)-(.+)\.md$/;
|
|
1098
1189
|
function parseTicketFilename(filename) {
|
|
1099
1190
|
const m = filename.match(TICKET_RE);
|
|
@@ -1305,14 +1396,18 @@ function renderPrompt(specBody, ticket, opts) {
|
|
|
1305
1396
|
"You are an autonomous coding agent. Implement the ticket using /tdd.",
|
|
1306
1397
|
"",
|
|
1307
1398
|
"Follow these rules:",
|
|
1308
|
-
"- Read the files directly relevant to the ticket before writing code.",
|
|
1399
|
+
"- Read the files directly relevant to the ticket before writing code. Also read any AGENTS.md in or above the directories you will touch, plus CONTEXT.md and the relevant docs/adr/ entries if they exist. If they do not exist, proceed silently.",
|
|
1400
|
+
"- Work ONLY on this ticket. Do not fix unrelated problems you notice.",
|
|
1309
1401
|
"- Do NOT research external APIs or services via web search. Use the spec and ticket body as your sole reference for API shapes.",
|
|
1310
1402
|
"- 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
|
|
1403
|
+
`- While iterating, run only the tests relevant to what you changed. Run \`${opts.testCmd}\` in full once, as the final gate before your last commit. Do not commit with failing tests. The orchestrator runs the same command after you exit and rejects the ticket if it fails.`,
|
|
1404
|
+
"- If the project has a lint script, run it and fix what it reports.",
|
|
1405
|
+
"- INTEGRITY: never delete, skip, weaken, or write tautological tests to force a green result. Only report a checklist item done when its real tests genuinely pass. If an item cannot be completed honestly, leave it unchecked and say what blocked you.",
|
|
1312
1406
|
"- Do NOT read or modify files outside your current directory (the worktree).",
|
|
1313
1407
|
"- Do NOT run git push.",
|
|
1314
|
-
"- Commit
|
|
1408
|
+
"- Commit after each checklist item, and make sure everything is committed when finished.",
|
|
1315
1409
|
"- After completing each checklist item in the ticket, print a line: [x] <item text> (matching the checklist text exactly).",
|
|
1410
|
+
"- Before your final commit, record reusable learnings in the nearest AGENTS.md (in or above the directories you modified; prefer that over the repo root). Good additions: module conventions, gotchas, files that must change together, how to test that area, environment requirements. Do NOT add ticket-specific details or temporary debugging notes. Only write when you have knowledge that would help future work in that directory.",
|
|
1316
1411
|
""
|
|
1317
1412
|
].join("\n");
|
|
1318
1413
|
}
|
|
@@ -1333,8 +1428,9 @@ function renderMergePrompt(specBody, ticket) {
|
|
|
1333
1428
|
"",
|
|
1334
1429
|
"Your task:",
|
|
1335
1430
|
"1. Find and resolve all conflict markers (<<<<<<< / ======= / >>>>>>>).",
|
|
1336
|
-
"2.
|
|
1337
|
-
"3.
|
|
1431
|
+
"2. When AGENTS.md or other docs conflict, keep both sides' additions rather than picking one.",
|
|
1432
|
+
"3. Make sure the code compiles and tests pass.",
|
|
1433
|
+
"4. Commit the resolved files.",
|
|
1338
1434
|
"",
|
|
1339
1435
|
"Do NOT re-implement the ticket from scratch.",
|
|
1340
1436
|
"Do NOT read or modify files outside your current directory (the worktree).",
|
|
@@ -1748,7 +1844,7 @@ function spawnAgent(opts) {
|
|
|
1748
1844
|
proc.stdin.write(opts.prompt);
|
|
1749
1845
|
}
|
|
1750
1846
|
proc.stdin.end();
|
|
1751
|
-
return new Promise((
|
|
1847
|
+
return new Promise((resolve2) => {
|
|
1752
1848
|
let settled = false;
|
|
1753
1849
|
let graceTimer;
|
|
1754
1850
|
function finish(exitCode) {
|
|
@@ -1774,7 +1870,7 @@ function spawnAgent(opts) {
|
|
|
1774
1870
|
rlErr.close();
|
|
1775
1871
|
logStream.end();
|
|
1776
1872
|
if (promptFile) cleanupPromptFile(promptFile);
|
|
1777
|
-
|
|
1873
|
+
resolve2({ exitCode, completedItems, timedOut });
|
|
1778
1874
|
}
|
|
1779
1875
|
__name(finish, "finish");
|
|
1780
1876
|
proc.on("exit", (code) => {
|
|
@@ -1804,7 +1900,7 @@ function spawnAgent(opts) {
|
|
|
1804
1900
|
"Agent process error:",
|
|
1805
1901
|
formatErrorMessage(err)
|
|
1806
1902
|
);
|
|
1807
|
-
|
|
1903
|
+
resolve2({
|
|
1808
1904
|
exitCode: 1,
|
|
1809
1905
|
completedItems,
|
|
1810
1906
|
timedOut
|
|
@@ -1855,7 +1951,11 @@ function dryRunIsolated(opts) {
|
|
|
1855
1951
|
);
|
|
1856
1952
|
for (const t of ready) {
|
|
1857
1953
|
const num = String(t.num).padStart(2, "0");
|
|
1858
|
-
const branchName =
|
|
1954
|
+
const branchName = buildBranchName(
|
|
1955
|
+
opts.repoRoot,
|
|
1956
|
+
opts.repoName,
|
|
1957
|
+
t
|
|
1958
|
+
);
|
|
1859
1959
|
const worktreePath = `../${opts.repoName}.${num}-${t.slug}`;
|
|
1860
1960
|
console.log(` worktree: ${worktreePath}`);
|
|
1861
1961
|
console.log(` branch: ${branchName}`);
|
|
@@ -2022,7 +2122,7 @@ function retryDelayFor(base, attempt) {
|
|
|
2022
2122
|
}
|
|
2023
2123
|
__name(retryDelayFor, "retryDelayFor");
|
|
2024
2124
|
function sleep(ms) {
|
|
2025
|
-
return new Promise((
|
|
2125
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
2026
2126
|
}
|
|
2027
2127
|
__name(sleep, "sleep");
|
|
2028
2128
|
function attemptLogPath(logsDir, ticket, attempt, ext) {
|
|
@@ -2355,7 +2455,7 @@ async function runWavesIsolated(opts) {
|
|
|
2355
2455
|
);
|
|
2356
2456
|
}
|
|
2357
2457
|
const progressPath = join4(
|
|
2358
|
-
|
|
2458
|
+
dirname2(opts.logsDir),
|
|
2359
2459
|
"progress.md"
|
|
2360
2460
|
);
|
|
2361
2461
|
const progress = mergeProgress(
|
|
@@ -2517,6 +2617,13 @@ async function runWavesIsolated(opts) {
|
|
|
2517
2617
|
});
|
|
2518
2618
|
}
|
|
2519
2619
|
if (successfulBranches.length > 0) {
|
|
2620
|
+
await commitTicketFiles(
|
|
2621
|
+
repoRoot,
|
|
2622
|
+
successfulBranches.map(
|
|
2623
|
+
(sb) => ticketsByNum.get(sb.ticketNum)
|
|
2624
|
+
),
|
|
2625
|
+
d
|
|
2626
|
+
);
|
|
2520
2627
|
const mergeResult = await mergeWave({
|
|
2521
2628
|
repoRoot,
|
|
2522
2629
|
baseBranch,
|
|
@@ -2683,11 +2790,11 @@ __name(runWavesIsolated, "runWavesIsolated");
|
|
|
2683
2790
|
|
|
2684
2791
|
// src/resolve.ts
|
|
2685
2792
|
import { existsSync as existsSync4, readdirSync as readdirSync3 } from "node:fs";
|
|
2686
|
-
import { dirname as
|
|
2793
|
+
import { dirname as dirname3, join as join5 } from "node:path";
|
|
2687
2794
|
import { fileURLToPath } from "node:url";
|
|
2688
2795
|
import { select } from "@inquirer/prompts";
|
|
2689
2796
|
function getScriptDir(metaUrl) {
|
|
2690
|
-
return
|
|
2797
|
+
return dirname3(fileURLToPath(metaUrl));
|
|
2691
2798
|
}
|
|
2692
2799
|
__name(getScriptDir, "getScriptDir");
|
|
2693
2800
|
function findFeatureDirs(cwd) {
|
|
@@ -2914,7 +3021,7 @@ async function runExecute(argv) {
|
|
|
2914
3021
|
(b) => knownNums.has(b)
|
|
2915
3022
|
);
|
|
2916
3023
|
}
|
|
2917
|
-
const logsDir = join6(
|
|
3024
|
+
const logsDir = join6(dirname4(dir), "logs");
|
|
2918
3025
|
const timeout = argv.timeout ?? 900;
|
|
2919
3026
|
const testCmd = argv.testCmd || detectTestCmd(
|
|
2920
3027
|
isGitRepo() ? getRepoRoot() : process.cwd()
|
|
@@ -2922,15 +3029,17 @@ async function runExecute(argv) {
|
|
|
2922
3029
|
const featureName = extractFeatureName(spec);
|
|
2923
3030
|
const conflictStrategy = argv.stopOnConflict ? "stop" : "respawn";
|
|
2924
3031
|
const progressPath = join6(
|
|
2925
|
-
|
|
3032
|
+
dirname4(dir),
|
|
2926
3033
|
"progress.md"
|
|
2927
3034
|
);
|
|
2928
3035
|
if (argv.dryRun) {
|
|
2929
3036
|
if (isGitRepo()) {
|
|
3037
|
+
const repoRoot = getRepoRoot();
|
|
2930
3038
|
const repoName = getRepoName();
|
|
2931
3039
|
dryRunIsolated({
|
|
2932
3040
|
tickets,
|
|
2933
3041
|
specBody,
|
|
3042
|
+
repoRoot,
|
|
2934
3043
|
repoName,
|
|
2935
3044
|
conflictStrategy,
|
|
2936
3045
|
progressPath,
|