nightralph 0.0.53 → 0.0.56

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.
Files changed (60) hide show
  1. package/README.md +181 -19
  2. package/dist/blockers.js +149 -0
  3. package/dist/blockers.js.map +7 -0
  4. package/dist/display.js +12 -4
  5. package/dist/display.js.map +2 -2
  6. package/dist/docs-templates/issue-tracker.md +9 -1
  7. package/dist/index.js +2735 -415
  8. package/dist/index.js.map +4 -4
  9. package/dist/learnings.js +360 -0
  10. package/dist/learnings.js.map +7 -0
  11. package/dist/meta.json +289 -16
  12. package/dist/orchestrator.js +753 -188
  13. package/dist/orchestrator.js.map +3 -3
  14. package/dist/preflight.js +34 -0
  15. package/dist/preflight.js.map +7 -0
  16. package/dist/progress.js +5 -4
  17. package/dist/progress.js.map +2 -2
  18. package/dist/resolve.js +98 -9
  19. package/dist/resolve.js.map +2 -2
  20. package/dist/retry.js +83 -0
  21. package/dist/retry.js.map +7 -0
  22. package/dist/review.js +669 -0
  23. package/dist/review.js.map +7 -0
  24. package/dist/rules.js +67 -0
  25. package/dist/rules.js.map +7 -0
  26. package/dist/skills/to-tickets/SKILL.md +5 -5
  27. package/dist/src/blockers.d.ts +45 -0
  28. package/dist/src/blockers.d.ts.map +1 -0
  29. package/dist/src/display.d.ts +9 -0
  30. package/dist/src/display.d.ts.map +1 -1
  31. package/dist/src/index.d.ts.map +1 -1
  32. package/dist/src/learnings.d.ts +70 -0
  33. package/dist/src/learnings.d.ts.map +1 -0
  34. package/dist/src/orchestrator.d.ts +85 -28
  35. package/dist/src/orchestrator.d.ts.map +1 -1
  36. package/dist/src/preflight.d.ts +6 -0
  37. package/dist/src/preflight.d.ts.map +1 -0
  38. package/dist/src/progress.d.ts +1 -1
  39. package/dist/src/progress.d.ts.map +1 -1
  40. package/dist/src/resolve.d.ts +24 -0
  41. package/dist/src/resolve.d.ts.map +1 -1
  42. package/dist/src/retry.d.ts +26 -0
  43. package/dist/src/retry.d.ts.map +1 -0
  44. package/dist/src/review.d.ts +131 -0
  45. package/dist/src/review.d.ts.map +1 -0
  46. package/dist/src/rules.d.ts +11 -0
  47. package/dist/src/rules.d.ts.map +1 -0
  48. package/dist/src/stats.d.ts +43 -0
  49. package/dist/src/stats.d.ts.map +1 -0
  50. package/dist/src/testcmd.d.ts +1 -0
  51. package/dist/src/testcmd.d.ts.map +1 -1
  52. package/dist/src/worktree.d.ts +30 -1
  53. package/dist/src/worktree.d.ts.map +1 -1
  54. package/dist/stats.js +111 -0
  55. package/dist/stats.js.map +7 -0
  56. package/dist/testcmd.js +13 -0
  57. package/dist/testcmd.js.map +2 -2
  58. package/dist/worktree.js +236 -32
  59. package/dist/worktree.js.map +2 -2
  60. package/package.json +3 -2
package/dist/worktree.js CHANGED
@@ -23,15 +23,138 @@ function getRepoName() {
23
23
  return basename(dirname(resolve(commonDir)));
24
24
  }
25
25
  __name(getRepoName, "getRepoName");
26
- function getCurrentBranch() {
26
+ function getCurrentBranch(cwd) {
27
27
  const stdout = execFileSync(
28
28
  "git",
29
29
  ["rev-parse", "--abbrev-ref", "HEAD"],
30
- { encoding: "utf8" }
30
+ { cwd, encoding: "utf8" }
31
31
  );
32
32
  return stdout.trim();
33
33
  }
34
34
  __name(getCurrentBranch, "getCurrentBranch");
35
+ function localBranchExists(repoRoot, name) {
36
+ try {
37
+ execFileSync(
38
+ "git",
39
+ ["show-ref", "--verify", "--quiet", `refs/heads/${name}`],
40
+ { cwd: repoRoot, stdio: "ignore" }
41
+ );
42
+ return true;
43
+ } catch {
44
+ return false;
45
+ }
46
+ }
47
+ __name(localBranchExists, "localBranchExists");
48
+ function getDefaultBranch(repoRoot) {
49
+ try {
50
+ const ref = execFileSync(
51
+ "git",
52
+ [
53
+ "symbolic-ref",
54
+ "--quiet",
55
+ "--short",
56
+ "refs/remotes/origin/HEAD"
57
+ ],
58
+ {
59
+ cwd: repoRoot,
60
+ encoding: "utf8",
61
+ stdio: ["ignore", "pipe", "ignore"]
62
+ }
63
+ ).trim();
64
+ if (ref) return ref.replace(/^origin\//, "");
65
+ } catch {
66
+ }
67
+ for (const name of ["main", "master"]) {
68
+ if (localBranchExists(repoRoot, name)) return name;
69
+ }
70
+ return null;
71
+ }
72
+ __name(getDefaultBranch, "getDefaultBranch");
73
+ function integrationBranchName(features) {
74
+ const distinct = [...new Set(features.filter(Boolean))];
75
+ return distinct.length === 1 ? `nightralph--${distinct[0]}` : "nightralph--all";
76
+ }
77
+ __name(integrationBranchName, "integrationBranchName");
78
+ function planIntegrationBranch(current, defaultBranch, features) {
79
+ if (!defaultBranch || current === "HEAD") return null;
80
+ if (current !== defaultBranch) return null;
81
+ return integrationBranchName(features);
82
+ }
83
+ __name(planIntegrationBranch, "planIntegrationBranch");
84
+ function gitText(cwd, args) {
85
+ return execFileSync("git", args, { cwd, encoding: "utf8" }).trim();
86
+ }
87
+ __name(gitText, "gitText");
88
+ function prepareIntegrationBranch(repoRoot, features) {
89
+ const current = getCurrentBranch(repoRoot);
90
+ const defaultBranch = getDefaultBranch(repoRoot);
91
+ const name = planIntegrationBranch(
92
+ current,
93
+ defaultBranch,
94
+ features
95
+ );
96
+ if (!name) {
97
+ const onIntegration = defaultBranch !== null && current === integrationBranchName(features);
98
+ return {
99
+ branch: current,
100
+ created: false,
101
+ resumed: false,
102
+ behind: 0,
103
+ fastForwarded: false,
104
+ launchBranch: current,
105
+ forkedFrom: onIntegration ? defaultBranch : null
106
+ };
107
+ }
108
+ if (isDirty(repoRoot)) {
109
+ throw new Error(
110
+ "Working tree is dirty; commit or stash changes before running"
111
+ );
112
+ }
113
+ if (localBranchExists(repoRoot, name)) {
114
+ const contained = isAncestor(repoRoot, name, current);
115
+ gitText(repoRoot, ["checkout", "--quiet", name]);
116
+ let behind = Number(gitText(
117
+ repoRoot,
118
+ ["rev-list", "--count", `${name}..${current}`]
119
+ ));
120
+ const fastForwarded = contained && behind > 0;
121
+ if (fastForwarded) {
122
+ gitText(repoRoot, ["merge", "--ff-only", "--quiet", current]);
123
+ behind = 0;
124
+ }
125
+ return {
126
+ branch: name,
127
+ created: false,
128
+ resumed: true,
129
+ behind,
130
+ fastForwarded,
131
+ launchBranch: current,
132
+ forkedFrom: defaultBranch
133
+ };
134
+ }
135
+ gitText(repoRoot, ["checkout", "--quiet", "-b", name]);
136
+ return {
137
+ branch: name,
138
+ created: true,
139
+ resumed: false,
140
+ behind: 0,
141
+ fastForwarded: false,
142
+ launchBranch: current,
143
+ forkedFrom: defaultBranch
144
+ };
145
+ }
146
+ __name(prepareIntegrationBranch, "prepareIntegrationBranch");
147
+ function describeIntegrationBranch(repoRoot, features) {
148
+ const current = getCurrentBranch(repoRoot);
149
+ const name = planIntegrationBranch(
150
+ current,
151
+ getDefaultBranch(repoRoot),
152
+ features
153
+ );
154
+ if (!name) return `Branch: merges into ${current}`;
155
+ return localBranchExists(repoRoot, name) ? `Branch: would resume ${name}` : `Branch: would create ${name} from ${current}`;
156
+ }
157
+ __name(describeIntegrationBranch, "describeIntegrationBranch");
35
158
  async function branchExists(repoRoot, branchName) {
36
159
  try {
37
160
  await execFileAsync(
@@ -51,16 +174,20 @@ function buildBranchName(repoName, ticket) {
51
174
  return `${repoName}--${prefix}${num}-${ticket.slug}`;
52
175
  }
53
176
  __name(buildBranchName, "buildBranchName");
54
- async function createWorktreeNow(opts) {
55
- const { repoRoot, repoName, baseBranch, ticket } = opts;
56
- const branchName = buildBranchName(repoName, ticket);
57
- const num = String(ticket.num).padStart(2, "0");
58
- const prefix = ticket.feature ? `${ticket.feature}-` : "";
59
- const worktreePath = join(
60
- repoRoot,
61
- "..",
62
- `${repoName}.${prefix}${num}-${ticket.slug}`
63
- );
177
+ async function removeWorktreeNow(worktreePath, opts) {
178
+ const args = [
179
+ "-C",
180
+ worktreePath,
181
+ "worktree",
182
+ "remove"
183
+ ];
184
+ if (opts?.force) args.push("--force");
185
+ args.push(worktreePath);
186
+ await execFileAsync("git", args);
187
+ }
188
+ __name(removeWorktreeNow, "removeWorktreeNow");
189
+ async function addWorktreeNow(opts) {
190
+ const { repoRoot, baseBranch, branchName, worktreePath } = opts;
64
191
  if (existsSync(worktreePath)) {
65
192
  try {
66
193
  await execFileAsync(
@@ -100,36 +227,56 @@ async function createWorktreeNow(opts) {
100
227
  );
101
228
  return { worktreePath, branchName };
102
229
  }
103
- __name(createWorktreeNow, "createWorktreeNow");
230
+ __name(addWorktreeNow, "addWorktreeNow");
104
231
  let worktreeQueue = Promise.resolve();
105
- function createWorktree(opts) {
106
- const result = worktreeQueue.then(() => createWorktreeNow(opts));
232
+ function enqueue(fn) {
233
+ const result = worktreeQueue.then(fn);
107
234
  worktreeQueue = result.catch(() => {
108
235
  });
109
236
  return result;
110
237
  }
238
+ __name(enqueue, "enqueue");
239
+ function createWorktree(opts) {
240
+ const { repoRoot, repoName, ticket } = opts;
241
+ const num = String(ticket.num).padStart(2, "0");
242
+ const prefix = ticket.feature ? `${ticket.feature}-` : "";
243
+ return enqueue(() => addWorktreeNow({
244
+ repoRoot,
245
+ baseBranch: opts.baseBranch,
246
+ branchName: buildBranchName(repoName, ticket),
247
+ worktreePath: join(
248
+ repoRoot,
249
+ "..",
250
+ `${repoName}.${prefix}${num}-${ticket.slug}`
251
+ )
252
+ }));
253
+ }
111
254
  __name(createWorktree, "createWorktree");
112
- async function removeWorktreeNow(worktreePath, opts) {
113
- const args = [
114
- "-C",
115
- worktreePath,
116
- "worktree",
117
- "remove"
118
- ];
119
- if (opts?.force) args.push("--force");
120
- args.push(worktreePath);
121
- await execFileAsync("git", args);
255
+ function createNamedWorktree(opts) {
256
+ const { repoRoot, repoName, name } = opts;
257
+ return enqueue(() => addWorktreeNow({
258
+ repoRoot,
259
+ baseBranch: opts.baseBranch,
260
+ branchName: `${repoName}--${name}`,
261
+ worktreePath: join(repoRoot, "..", `${repoName}.${name}`)
262
+ }));
122
263
  }
123
- __name(removeWorktreeNow, "removeWorktreeNow");
264
+ __name(createNamedWorktree, "createNamedWorktree");
124
265
  function removeWorktree(worktreePath, opts) {
125
- const result = worktreeQueue.then(
126
- () => removeWorktreeNow(worktreePath, opts)
127
- );
128
- worktreeQueue = result.catch(() => {
129
- });
130
- return result;
266
+ return enqueue(() => removeWorktreeNow(worktreePath, opts));
131
267
  }
132
268
  __name(removeWorktree, "removeWorktree");
269
+ function deleteBranch(repoRoot, name) {
270
+ return enqueue(async () => {
271
+ if (!await branchExists(repoRoot, name)) return;
272
+ await execFileAsync(
273
+ "git",
274
+ ["branch", "-D", name],
275
+ { cwd: repoRoot }
276
+ );
277
+ });
278
+ }
279
+ __name(deleteBranch, "deleteBranch");
133
280
  function hasNewCommits(repoRoot, base, branch) {
134
281
  const stdout = execFileSync(
135
282
  "git",
@@ -139,6 +286,51 @@ function hasNewCommits(repoRoot, base, branch) {
139
286
  return Number(stdout.trim()) > 0;
140
287
  }
141
288
  __name(hasNewCommits, "hasNewCommits");
289
+ function headCommit(cwd) {
290
+ return gitText(cwd, ["rev-parse", "HEAD"]);
291
+ }
292
+ __name(headCommit, "headCommit");
293
+ function mergeBase(cwd, a, b) {
294
+ try {
295
+ return gitText(cwd, ["merge-base", a, b]) || null;
296
+ } catch {
297
+ return null;
298
+ }
299
+ }
300
+ __name(mergeBase, "mergeBase");
301
+ function isAncestor(cwd, ancestor, of) {
302
+ try {
303
+ execFileSync(
304
+ "git",
305
+ ["merge-base", "--is-ancestor", ancestor, of],
306
+ { cwd, stdio: "ignore" }
307
+ );
308
+ return true;
309
+ } catch {
310
+ return false;
311
+ }
312
+ }
313
+ __name(isAncestor, "isAncestor");
314
+ function hasChangesOutside(cwd, from, to, excludeDir) {
315
+ try {
316
+ execFileSync(
317
+ "git",
318
+ [
319
+ "diff",
320
+ "--quiet",
321
+ `${from}..${to}`,
322
+ "--",
323
+ ".",
324
+ `:(exclude)${excludeDir}`
325
+ ],
326
+ { cwd, stdio: "ignore" }
327
+ );
328
+ return false;
329
+ } catch {
330
+ return true;
331
+ }
332
+ }
333
+ __name(hasChangesOutside, "hasChangesOutside");
142
334
  function isDirty(cwd) {
143
335
  const stdout = execFileSync(
144
336
  "git",
@@ -183,13 +375,25 @@ __name(listWorktrees, "listWorktrees");
183
375
  export {
184
376
  buildBranchName,
185
377
  commitDirty,
378
+ createNamedWorktree,
186
379
  createWorktree,
380
+ deleteBranch,
381
+ describeIntegrationBranch,
187
382
  getCurrentBranch,
383
+ getDefaultBranch,
188
384
  getRepoName,
189
385
  getRepoRoot,
386
+ hasChangesOutside,
190
387
  hasNewCommits,
388
+ headCommit,
389
+ integrationBranchName,
390
+ isAncestor,
191
391
  isDirty,
192
392
  listWorktrees,
393
+ localBranchExists,
394
+ mergeBase,
395
+ planIntegrationBranch,
396
+ prepareIntegrationBranch,
193
397
  removeWorktree
194
398
  };
195
399
  //# sourceMappingURL=worktree.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/worktree.ts"],
4
- "sourcesContent": ["import { execFile, execFileSync } from 'node:child_process'\nimport { promisify } from 'node:util'\nimport { basename, dirname, join, resolve } from 'node:path'\nimport { existsSync } from 'node:fs'\nimport type { Ticket } from './orchestrator.js'\n\nconst execFileAsync = promisify(execFile)\n\nexport function getRepoRoot ():string {\n const stdout = execFileSync(\n 'git',\n ['rev-parse', '--show-toplevel'],\n { encoding:'utf8' }\n )\n return stdout.trim()\n}\n\nexport function getRepoName ():string {\n const commonDir = execFileSync(\n 'git',\n ['rev-parse', '--git-common-dir'],\n { encoding:'utf8' }\n ).trim()\n return basename(dirname(resolve(commonDir)))\n}\n\nexport function getCurrentBranch ():string {\n const stdout = execFileSync(\n 'git',\n ['rev-parse', '--abbrev-ref', 'HEAD'],\n { encoding:'utf8' }\n )\n return stdout.trim()\n}\n\nasync function branchExists (\n repoRoot:string,\n branchName:string\n):Promise<boolean> {\n try {\n await execFileAsync(\n 'git',\n ['show-ref', '--verify', '--quiet', `refs/heads/${branchName}`],\n { cwd:repoRoot }\n )\n return true\n } catch {\n return false\n }\n}\n\nexport function buildBranchName (\n repoName:string,\n ticket:Ticket\n):string {\n const num = String(ticket.num).padStart(2, '0')\n const prefix = ticket.feature ?\n `${ticket.feature}-` :\n ''\n return `${repoName}--${prefix}${num}-${ticket.slug}`\n}\n\nasync function createWorktreeNow (\n opts:{\n repoRoot:string\n repoName:string\n baseBranch:string\n ticket:Ticket\n }\n):Promise<{ worktreePath:string; branchName:string }> {\n const { repoRoot, repoName, baseBranch, ticket } = opts\n const branchName = buildBranchName(repoName, ticket)\n const num = String(ticket.num).padStart(2, '0')\n const prefix = ticket.feature ?\n `${ticket.feature}-` :\n ''\n const worktreePath = join(\n repoRoot,\n '..',\n `${repoName}.${prefix}${num}-${ticket.slug}`\n )\n\n if (existsSync(worktreePath)) {\n try {\n await execFileAsync(\n 'git',\n ['worktree', 'remove', '--force', worktreePath],\n { cwd:repoRoot }\n )\n } catch {\n // directory may not be a registered worktree anymore;\n // prune stale entries below and let worktree add fail\n // loudly if the path is still unusable\n }\n }\n\n try {\n await execFileAsync(\n 'git',\n ['worktree', 'prune'],\n { cwd:repoRoot }\n )\n } catch {\n // best-effort cleanup of stale worktree metadata\n }\n\n if (await branchExists(repoRoot, branchName)) {\n await execFileAsync(\n 'git',\n ['branch', '-D', branchName],\n { cwd:repoRoot }\n )\n }\n\n await execFileAsync(\n 'git',\n [\n 'worktree',\n 'add',\n '-b',\n branchName,\n worktreePath,\n baseBranch,\n ],\n { cwd:repoRoot }\n )\n\n return { worktreePath, branchName }\n}\n\nlet worktreeQueue:Promise<unknown> = Promise.resolve()\n\nexport function createWorktree (\n opts:{\n repoRoot:string\n repoName:string\n baseBranch:string\n ticket:Ticket\n }\n):Promise<{ worktreePath:string; branchName:string }> {\n const result = worktreeQueue.then(() => createWorktreeNow(opts))\n worktreeQueue = result.catch(() => {})\n return result\n}\n\nasync function removeWorktreeNow (\n worktreePath:string,\n opts?:{ force?:boolean }\n):Promise<void> {\n const args = [\n '-C', worktreePath,\n 'worktree', 'remove',\n ]\n if (opts?.force) args.push('--force')\n args.push(worktreePath)\n await execFileAsync('git', args)\n}\n\nexport function removeWorktree (\n worktreePath:string,\n opts?:{ force?:boolean }\n):Promise<void> {\n const result = worktreeQueue.then(\n () => removeWorktreeNow(worktreePath, opts)\n )\n worktreeQueue = result.catch(() => {})\n return result\n}\n\nexport function hasNewCommits (\n repoRoot:string,\n base:string,\n branch:string\n):boolean {\n const stdout = execFileSync(\n 'git',\n ['rev-list', '--count', `${base}..${branch}`],\n { cwd:repoRoot, encoding:'utf8' }\n )\n return Number(stdout.trim()) > 0\n}\n\nexport function isDirty (cwd:string):boolean {\n const stdout = execFileSync(\n 'git',\n ['status', '--porcelain'],\n { cwd, encoding:'utf8' }\n )\n return stdout.trim().length > 0\n}\n\nexport async function commitDirty (\n cwd:string,\n message?:string\n):Promise<boolean> {\n if (!isDirty(cwd)) return false\n await execFileAsync(\n 'git',\n ['add', '-A'],\n { cwd }\n )\n try {\n await execFileAsync(\n 'git',\n [\n 'commit', '-m',\n message ??\n 'nightralph: auto-commit ' +\n 'remaining changes',\n ],\n { cwd }\n )\n return true\n } catch {\n return false\n }\n}\n\nexport async function listWorktrees (\n repoRoot:string\n):Promise<Array<string>> {\n const { stdout } = await execFileAsync(\n 'git',\n ['worktree', 'list', '--porcelain'],\n { cwd:repoRoot }\n )\n return stdout\n .trim()\n .split('\\n')\n .filter(line => line.startsWith('worktree '))\n .map(line => line.substring('worktree '.length))\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { execFile, execFileSync } from 'node:child_process'\nimport { promisify } from 'node:util'\nimport { basename, dirname, join, resolve } from 'node:path'\nimport { existsSync } from 'node:fs'\nimport type { Ticket } from './orchestrator.js'\n\nconst execFileAsync = promisify(execFile)\n\nexport function getRepoRoot ():string {\n const stdout = execFileSync(\n 'git',\n ['rev-parse', '--show-toplevel'],\n { encoding:'utf8' }\n )\n return stdout.trim()\n}\n\nexport function getRepoName ():string {\n const commonDir = execFileSync(\n 'git',\n ['rev-parse', '--git-common-dir'],\n { encoding:'utf8' }\n ).trim()\n return basename(dirname(resolve(commonDir)))\n}\n\nexport function getCurrentBranch (cwd?:string):string {\n const stdout = execFileSync(\n 'git',\n ['rev-parse', '--abbrev-ref', 'HEAD'],\n { cwd, encoding:'utf8' }\n )\n return stdout.trim()\n}\n\nexport function localBranchExists (\n repoRoot:string,\n name:string\n):boolean {\n try {\n execFileSync(\n 'git',\n ['show-ref', '--verify', '--quiet', `refs/heads/${name}`],\n { cwd:repoRoot, stdio:'ignore' }\n )\n return true\n } catch {\n return false\n }\n}\n\n// The repo's default branch: what origin/HEAD points at, else a\n// local main or master, else null.\nexport function getDefaultBranch (repoRoot:string):string|null {\n try {\n const ref = execFileSync(\n 'git',\n [\n 'symbolic-ref', '--quiet', '--short',\n 'refs/remotes/origin/HEAD',\n ],\n {\n cwd:repoRoot,\n encoding:'utf8',\n stdio:['ignore', 'pipe', 'ignore'],\n }\n ).trim()\n if (ref) return ref.replace(/^origin\\//, '')\n } catch {\n // no origin/HEAD\n }\n for (const name of ['main', 'master']) {\n if (localBranchExists(repoRoot, name)) return name\n }\n return null\n}\n\n// Uses `--` rather than `/` for the same reason as\n// buildBranchName: refs/heads/X/Y cannot coexist with\n// refs/heads/X.\nexport function integrationBranchName (\n features:ReadonlyArray<string>\n):string {\n const distinct = [...new Set(features.filter(Boolean))]\n return distinct.length === 1 ?\n `nightralph--${distinct[0]}` :\n 'nightralph--all'\n}\n\n// The integration branch to use, or null to merge into the\n// current branch as before. Only a run launched from the default\n// branch gets one.\nexport function planIntegrationBranch (\n current:string,\n defaultBranch:string|null,\n features:ReadonlyArray<string>\n):string|null {\n if (!defaultBranch || current === 'HEAD') return null\n if (current !== defaultBranch) return null\n return integrationBranchName(features)\n}\n\nexport type IntegrationPlan = {\n // where this run's work lands\n branch:string\n created:boolean\n resumed:boolean\n // commits on the launch branch that `branch` lacks\n behind:number\n // true when a resumed branch that was already contained in the\n // launch branch was moved up to it\n fastForwarded:boolean\n launchBranch:string\n // the default branch when `branch` is an integration branch,\n // else null; the review falls back to its merge-base\n forkedFrom:string|null\n}\n\nfunction gitText (cwd:string, args:string[]):string {\n return execFileSync('git', args, { cwd, encoding:'utf8' }).trim()\n}\n\n// Enters the integration branch when the run was launched from\n// the default branch, creating it or resuming it. Callers must\n// scan tickets after this, since ticket statuses live on the\n// branch it checks out.\nexport function prepareIntegrationBranch (\n repoRoot:string,\n features:ReadonlyArray<string>\n):IntegrationPlan {\n const current = getCurrentBranch(repoRoot)\n const defaultBranch = getDefaultBranch(repoRoot)\n const name = planIntegrationBranch(\n current, defaultBranch, features\n )\n if (!name) {\n const onIntegration = defaultBranch !== null &&\n current === integrationBranchName(features)\n return {\n branch:current,\n created:false,\n resumed:false,\n behind:0,\n fastForwarded:false,\n launchBranch:current,\n forkedFrom:onIntegration ? defaultBranch : null,\n }\n }\n if (isDirty(repoRoot)) {\n throw new Error(\n 'Working tree is dirty; commit or stash ' +\n 'changes before running'\n )\n }\n if (localBranchExists(repoRoot, name)) {\n // Merged into the launch branch already: nothing of its own\n // to lose, so bring it up to date\n const contained = isAncestor(repoRoot, name, current)\n gitText(repoRoot, ['checkout', '--quiet', name])\n let behind = Number(gitText(\n repoRoot, ['rev-list', '--count', `${name}..${current}`]\n ))\n const fastForwarded = contained && behind > 0\n if (fastForwarded) {\n gitText(repoRoot, ['merge', '--ff-only', '--quiet', current])\n behind = 0\n }\n return {\n branch:name,\n created:false,\n resumed:true,\n behind,\n fastForwarded,\n launchBranch:current,\n forkedFrom:defaultBranch,\n }\n }\n gitText(repoRoot, ['checkout', '--quiet', '-b', name])\n return {\n branch:name,\n created:true,\n resumed:false,\n behind:0,\n fastForwarded:false,\n launchBranch:current,\n forkedFrom:defaultBranch,\n }\n}\n\n// What prepareIntegrationBranch would do, for --dry-run.\nexport function describeIntegrationBranch (\n repoRoot:string,\n features:ReadonlyArray<string>\n):string {\n const current = getCurrentBranch(repoRoot)\n const name = planIntegrationBranch(\n current, getDefaultBranch(repoRoot), features\n )\n if (!name) return `Branch: merges into ${current}`\n return localBranchExists(repoRoot, name) ?\n `Branch: would resume ${name}` :\n `Branch: would create ${name} from ${current}`\n}\n\nasync function branchExists (\n repoRoot:string,\n branchName:string\n):Promise<boolean> {\n try {\n await execFileAsync(\n 'git',\n ['show-ref', '--verify', '--quiet', `refs/heads/${branchName}`],\n { cwd:repoRoot }\n )\n return true\n } catch {\n return false\n }\n}\n\nexport function buildBranchName (\n repoName:string,\n ticket:Ticket\n):string {\n const num = String(ticket.num).padStart(2, '0')\n const prefix = ticket.feature ?\n `${ticket.feature}-` :\n ''\n return `${repoName}--${prefix}${num}-${ticket.slug}`\n}\n\nasync function removeWorktreeNow (\n worktreePath:string,\n opts?:{ force?:boolean }\n):Promise<void> {\n const args = [\n '-C', worktreePath,\n 'worktree', 'remove',\n ]\n if (opts?.force) args.push('--force')\n args.push(worktreePath)\n await execFileAsync('git', args)\n}\n\n// Creates a worktree on a fresh branch, first clearing any stale\n// worktree at the path and any leftover branch of the same name,\n// so re-running after a failed run is safe.\nasync function addWorktreeNow (opts:{\n repoRoot:string\n baseBranch:string\n branchName:string\n worktreePath:string\n}):Promise<{ worktreePath:string; branchName:string }> {\n const { repoRoot, baseBranch, branchName, worktreePath } = opts\n\n if (existsSync(worktreePath)) {\n try {\n await execFileAsync(\n 'git',\n ['worktree', 'remove', '--force', worktreePath],\n { cwd:repoRoot }\n )\n } catch {\n // directory may not be a registered worktree anymore;\n // prune stale entries below and let worktree add fail\n // loudly if the path is still unusable\n }\n }\n\n try {\n await execFileAsync(\n 'git',\n ['worktree', 'prune'],\n { cwd:repoRoot }\n )\n } catch {\n // best-effort cleanup of stale worktree metadata\n }\n\n if (await branchExists(repoRoot, branchName)) {\n await execFileAsync(\n 'git',\n ['branch', '-D', branchName],\n { cwd:repoRoot }\n )\n }\n\n await execFileAsync(\n 'git',\n [\n 'worktree',\n 'add',\n '-b',\n branchName,\n worktreePath,\n baseBranch,\n ],\n { cwd:repoRoot }\n )\n\n return { worktreePath, branchName }\n}\n\nlet worktreeQueue:Promise<unknown> = Promise.resolve()\n\n// Serializes every worktree and branch mutation module-wide, so\n// concurrent calls cannot race on shared git refs or on\n// .git/worktrees metadata.\nfunction enqueue<T> (fn:() => Promise<T>):Promise<T> {\n const result = worktreeQueue.then(fn)\n worktreeQueue = result.catch(() => {})\n return result\n}\n\nexport function createWorktree (\n opts:{\n repoRoot:string\n repoName:string\n baseBranch:string\n ticket:Ticket\n }\n):Promise<{ worktreePath:string; branchName:string }> {\n const { repoRoot, repoName, ticket } = opts\n const num = String(ticket.num).padStart(2, '0')\n const prefix = ticket.feature ?\n `${ticket.feature}-` :\n ''\n return enqueue(() => addWorktreeNow({\n repoRoot,\n baseBranch:opts.baseBranch,\n branchName:buildBranchName(repoName, ticket),\n worktreePath:join(\n repoRoot,\n '..',\n `${repoName}.${prefix}${num}-${ticket.slug}`\n ),\n }))\n}\n\n// A worktree that belongs to no ticket, e.g. the review pass:\n// `<repo>.<name>` on branch `<repo>--<name>`. Ticket branches\n// always carry a number, so the names cannot collide.\nexport function createNamedWorktree (\n opts:{\n repoRoot:string\n repoName:string\n baseBranch:string\n name:string\n }\n):Promise<{ worktreePath:string; branchName:string }> {\n const { repoRoot, repoName, name } = opts\n return enqueue(() => addWorktreeNow({\n repoRoot,\n baseBranch:opts.baseBranch,\n branchName:`${repoName}--${name}`,\n worktreePath:join(repoRoot, '..', `${repoName}.${name}`),\n }))\n}\n\nexport function removeWorktree (\n worktreePath:string,\n opts?:{ force?:boolean }\n):Promise<void> {\n return enqueue(() => removeWorktreeNow(worktreePath, opts))\n}\n\n// Deletes a local branch; a missing branch is not an error.\nexport function deleteBranch (\n repoRoot:string,\n name:string\n):Promise<void> {\n return enqueue(async () => {\n if (!(await branchExists(repoRoot, name))) return\n await execFileAsync(\n 'git', ['branch', '-D', name], { cwd:repoRoot }\n )\n })\n}\n\nexport function hasNewCommits (\n repoRoot:string,\n base:string,\n branch:string\n):boolean {\n const stdout = execFileSync(\n 'git',\n ['rev-list', '--count', `${base}..${branch}`],\n { cwd:repoRoot, encoding:'utf8' }\n )\n return Number(stdout.trim()) > 0\n}\n\nexport function headCommit (cwd:string):string {\n return gitText(cwd, ['rev-parse', 'HEAD'])\n}\n\nexport function mergeBase (\n cwd:string,\n a:string,\n b:string\n):string|null {\n try {\n return gitText(cwd, ['merge-base', a, b]) || null\n } catch {\n return null\n }\n}\n\n// False when `ancestor` is not an ancestor of `of`, or is not a\n// commit this repo knows.\nexport function isAncestor (\n cwd:string,\n ancestor:string,\n of:string\n):boolean {\n try {\n execFileSync(\n 'git',\n ['merge-base', '--is-ancestor', ancestor, of],\n { cwd, stdio:'ignore' }\n )\n return true\n } catch {\n return false\n }\n}\n\n// True when from..to changes any path outside `excludeDir`.\nexport function hasChangesOutside (\n cwd:string,\n from:string,\n to:string,\n excludeDir:string\n):boolean {\n try {\n execFileSync(\n 'git',\n [\n 'diff', '--quiet', `${from}..${to}`,\n '--', '.', `:(exclude)${excludeDir}`,\n ],\n { cwd, stdio:'ignore' }\n )\n return false\n } catch {\n return true\n }\n}\n\nexport function isDirty (cwd:string):boolean {\n const stdout = execFileSync(\n 'git',\n ['status', '--porcelain'],\n { cwd, encoding:'utf8' }\n )\n return stdout.trim().length > 0\n}\n\nexport async function commitDirty (\n cwd:string,\n message?:string\n):Promise<boolean> {\n if (!isDirty(cwd)) return false\n await execFileAsync(\n 'git',\n ['add', '-A'],\n { cwd }\n )\n try {\n await execFileAsync(\n 'git',\n [\n 'commit', '-m',\n message ??\n 'nightralph: auto-commit ' +\n 'remaining changes',\n ],\n { cwd }\n )\n return true\n } catch {\n return false\n }\n}\n\nexport async function listWorktrees (\n repoRoot:string\n):Promise<Array<string>> {\n const { stdout } = await execFileAsync(\n 'git',\n ['worktree', 'list', '--porcelain'],\n { cwd:repoRoot }\n )\n return stdout\n .trim()\n .split('\\n')\n .filter(line => line.startsWith('worktree '))\n .map(line => line.substring('worktree '.length))\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nightralph",
3
- "version": "0.0.53",
3
+ "version": "0.0.56",
4
4
  "type": "module",
5
5
  "description": "Orchestrator that runs a single-agent session per issue.",
6
6
  "bin": {
@@ -17,11 +17,12 @@
17
17
  "lint": "eslint \"./**/*.{ts,js}\"",
18
18
  "start": "npm run build && node dist/index.js",
19
19
  "build": "mkdir -p ./dist && rm -rf ./dist/* && npm run build-esm && npm run copy",
20
- "build-esm": "esbuild src/index.ts --bundle --format=esm --platform=node --packages=external --metafile=dist/meta.json --keep-names --tsconfig=tsconfig.build.json --outfile=./dist/index.js --sourcemap && esbuild src/orchestrator.ts src/setup.ts src/resolve.ts src/worktree.ts src/progress.ts src/merge.ts src/display.ts src/testcmd.ts --format=esm --keep-names --tsconfig=tsconfig.build.json --outdir=./dist --sourcemap && tsc --emitDeclarationOnly --project tsconfig.build.json",
20
+ "build-esm": "esbuild src/index.ts --bundle --format=esm --platform=node --packages=external --metafile=dist/meta.json --keep-names --tsconfig=tsconfig.build.json --outfile=./dist/index.js --sourcemap && esbuild src/orchestrator.ts src/setup.ts src/resolve.ts src/worktree.ts src/progress.ts src/merge.ts src/display.ts src/testcmd.ts src/preflight.ts src/learnings.ts src/retry.ts src/rules.ts src/review.ts src/blockers.ts src/stats.ts --format=esm --keep-names --tsconfig=tsconfig.build.json --outdir=./dist --sourcemap && tsc --emitDeclarationOnly --project tsconfig.build.json",
21
21
  "copy": "mkdir -p ./dist/skills ./dist/docs-templates && cp -r ./src/skills/* ./dist/skills && cp -r ./src/docs-templates/* ./dist/docs-templates",
22
22
  "clone-skills": "bash scripts/clone-skills.sh",
23
23
  "toc": "markdown-toc --maxdepth 4 -i README.md",
24
24
  "preversion": "npm run lint",
25
+ "prepublishOnly": "npm run build",
25
26
  "version": "npm run toc && git add README.md",
26
27
  "postversion": "git push --follow-tags && npm publish",
27
28
  "demo": "npm run build && node test/demo-display.mjs",