nightralph 0.0.52 → 0.0.54

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 (46) hide show
  1. package/README.md +145 -16
  2. package/dist/display.js +80 -62
  3. package/dist/display.js.map +2 -2
  4. package/dist/index.js +2099 -314
  5. package/dist/index.js.map +4 -4
  6. package/dist/learnings.js +356 -0
  7. package/dist/learnings.js.map +7 -0
  8. package/dist/meta.json +234 -14
  9. package/dist/orchestrator.js +528 -142
  10. package/dist/orchestrator.js.map +3 -3
  11. package/dist/preflight.js +34 -0
  12. package/dist/preflight.js.map +7 -0
  13. package/dist/progress.js +5 -4
  14. package/dist/progress.js.map +2 -2
  15. package/dist/retry.js +83 -0
  16. package/dist/retry.js.map +7 -0
  17. package/dist/review.js +662 -0
  18. package/dist/review.js.map +7 -0
  19. package/dist/rules.js +54 -0
  20. package/dist/rules.js.map +7 -0
  21. package/dist/src/display.d.ts +35 -33
  22. package/dist/src/display.d.ts.map +1 -1
  23. package/dist/src/index.d.ts.map +1 -1
  24. package/dist/src/learnings.d.ts +68 -0
  25. package/dist/src/learnings.d.ts.map +1 -0
  26. package/dist/src/orchestrator.d.ts +24 -11
  27. package/dist/src/orchestrator.d.ts.map +1 -1
  28. package/dist/src/preflight.d.ts +6 -0
  29. package/dist/src/preflight.d.ts.map +1 -0
  30. package/dist/src/progress.d.ts +1 -1
  31. package/dist/src/progress.d.ts.map +1 -1
  32. package/dist/src/retry.d.ts +26 -0
  33. package/dist/src/retry.d.ts.map +1 -0
  34. package/dist/src/review.d.ts +127 -0
  35. package/dist/src/review.d.ts.map +1 -0
  36. package/dist/src/rules.d.ts +10 -0
  37. package/dist/src/rules.d.ts.map +1 -0
  38. package/dist/src/testcmd.d.ts +1 -0
  39. package/dist/src/testcmd.d.ts.map +1 -1
  40. package/dist/src/worktree.d.ts +30 -1
  41. package/dist/src/worktree.d.ts.map +1 -1
  42. package/dist/testcmd.js +13 -0
  43. package/dist/testcmd.js.map +2 -2
  44. package/dist/worktree.js +236 -32
  45. package/dist/worktree.js.map +2 -2
  46. package/package.json +5 -4
@@ -0,0 +1,127 @@
1
+ import { type EffortLevel } from './retry.js';
2
+ import type { AgentResult } from './orchestrator.js';
3
+ export declare const FINDINGS_FILE = "findings.md";
4
+ export declare const FINDINGS_PATH: string;
5
+ export declare const NO_FINDINGS = "No findings.";
6
+ export declare const REVIEW_EXCLUDE = ".scratch";
7
+ export declare const REVIEW_MARKER = "---- REVIEW INSTRUCTIONS ----";
8
+ export declare const FIX_MARKER = "---- REVIEW FINDINGS ----";
9
+ export declare function reviewExcludeFor(repoRoot: string, reviewPath: string): string;
10
+ export declare function reviewDiffCommand(from: string, to: string, exclude?: string): string;
11
+ export declare function renderReviewPrompt(opts: {
12
+ specBody: string;
13
+ from: string;
14
+ to: string;
15
+ exclude?: string;
16
+ }): string;
17
+ export type FixFailure = 'install' | 'exit' | 'no-commits' | 'tests' | 'merge';
18
+ export declare const FIX_FAILURE_TEXT: Record<FixFailure, string>;
19
+ export declare function renderFixPrompt(opts: {
20
+ specBody: string;
21
+ findings: ReadonlyArray<Finding>;
22
+ testCmd: string;
23
+ installCmd?: string;
24
+ previous?: {
25
+ reason: FixFailure;
26
+ notes: string | null;
27
+ };
28
+ }): string;
29
+ export declare const REVIEW_STATUSES: readonly ["clean", "fixed", "unresolved", "review-failed"];
30
+ export type ReviewStatus = typeof REVIEW_STATUSES[number];
31
+ export type Finding = {
32
+ summary: string;
33
+ detail: string;
34
+ checked: boolean;
35
+ };
36
+ export type FixRecord = {
37
+ attempts: number;
38
+ maxAttempts: number;
39
+ effort?: string;
40
+ exitCode: number | null;
41
+ result: string;
42
+ notes: string | null;
43
+ };
44
+ export type ReviewEntryInput = {
45
+ from: string;
46
+ to: string;
47
+ status: ReviewStatus;
48
+ now: Date;
49
+ findings: Finding[];
50
+ fix?: FixRecord;
51
+ };
52
+ export type ReviewEntry = {
53
+ from: string;
54
+ to: string;
55
+ status: ReviewStatus;
56
+ findings: Finding[];
57
+ body: string;
58
+ };
59
+ export type ReviewOutcome = {
60
+ status: ReviewStatus | 'skipped';
61
+ findings: number;
62
+ fixed: number;
63
+ };
64
+ export declare function parseFindings(text: string): Finding[] | null;
65
+ export declare function markFixed(findings: ReadonlyArray<Finding>, completedItems: ReadonlyArray<string>): Finding[];
66
+ export declare function renderReviewEntry(input: ReviewEntryInput): string;
67
+ export declare function parseReviewEntries(text: string): ReviewEntry[];
68
+ export declare function selectReviewBase(entries: ReadonlyArray<ReviewEntry>, opts: {
69
+ isAncestor: (sha: string) => boolean;
70
+ forkPoint: string | null;
71
+ launchCommit: string;
72
+ }): string;
73
+ export declare function renderReviewSummary(outcome: ReviewOutcome, reviewPath: string): string | null;
74
+ export type SpawnAgentFn = (opts: {
75
+ prompt: string;
76
+ agentCmd: string;
77
+ model: string | null;
78
+ timeout: number;
79
+ logPath: string;
80
+ cwd?: string;
81
+ label?: string;
82
+ maxTurns?: number;
83
+ effort?: EffortLevel;
84
+ onLine?: (line: string) => void;
85
+ }) => Promise<AgentResult>;
86
+ export type ReviewDeps = {
87
+ spawnAgent: SpawnAgentFn;
88
+ registry?: Set<number>;
89
+ };
90
+ export type ReviewPassOpts = {
91
+ repoRoot: string;
92
+ repoName: string;
93
+ baseBranch: string;
94
+ reviewPath: string;
95
+ forkedFrom?: string;
96
+ launchCommit: string;
97
+ specBody: string;
98
+ agentCmd: string;
99
+ model: string | null;
100
+ timeout: number;
101
+ logsDir: string;
102
+ testCmd: string;
103
+ installCmd?: string;
104
+ maxTurns?: number;
105
+ effort?: EffortLevel;
106
+ retries: number;
107
+ retryDelay: number;
108
+ log: (msg: string) => void;
109
+ now?: () => Date;
110
+ };
111
+ export declare function runReviewer(opts: ReviewPassOpts, deps: ReviewDeps, range: {
112
+ from: string;
113
+ to: string;
114
+ }): Promise<Finding[] | null>;
115
+ export type FixResult = {
116
+ merged: boolean;
117
+ findings: Finding[];
118
+ attempts: number;
119
+ effort?: EffortLevel;
120
+ exitCode: number | null;
121
+ result: string;
122
+ notes: string | null;
123
+ };
124
+ export declare function runFixer(opts: ReviewPassOpts, deps: ReviewDeps, findings: ReadonlyArray<Finding>): Promise<FixResult>;
125
+ export declare function readReviewEntries(path: string): ReviewEntry[];
126
+ export declare function runReviewPass(opts: ReviewPassOpts, deps: ReviewDeps): Promise<ReviewOutcome>;
127
+ //# sourceMappingURL=review.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review.d.ts","sourceRoot":"","sources":["../../src/review.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ export declare function readDocsRule(subject: string): string;
2
+ export declare function installRule(installCmd?: string): string[];
3
+ export declare function testGateRule(testCmd: string, subject: string): string;
4
+ export declare const LINT_RULE: string;
5
+ export declare function integrityRule(unit: string): string;
6
+ export declare const CONFINEMENT_RULES: string[];
7
+ export declare function commitRule(unit: string): string;
8
+ export declare function notesRule(unit: string, subject: string): string;
9
+ export declare function agentsMdRule(subject: string): string;
10
+ //# sourceMappingURL=rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../src/rules.ts"],"names":[],"mappings":"AAMA,wBAAgB,YAAY,CAAE,OAAO,EAAC,MAAM,GAAE,MAAM,CAOnD;AAID,wBAAgB,WAAW,CAAE,UAAU,CAAC,EAAC,MAAM,GAAE,MAAM,EAAE,CAUxD;AAED,wBAAgB,YAAY,CAAE,OAAO,EAAC,MAAM,EAAE,OAAO,EAAC,MAAM,GAAE,MAAM,CAenE;AAED,eAAO,MAAM,SAAS,QACQ,CAAA;AAE9B,wBAAgB,aAAa,CAAE,IAAI,EAAC,MAAM,GAAE,MAAM,CAQjD;AAED,eAAO,MAAM,iBAAiB,EAAC,MAAM,EAIpC,CAAA;AAED,wBAAgB,UAAU,CAAE,IAAI,EAAC,MAAM,GAAE,MAAM,CAI9C;AAED,wBAAgB,SAAS,CAAE,IAAI,EAAC,MAAM,EAAE,OAAO,EAAC,MAAM,GAAE,MAAM,CAU7D;AAED,wBAAgB,YAAY,CAAE,OAAO,EAAC,MAAM,GAAE,MAAM,CAenD"}
@@ -3,6 +3,7 @@ export type TestResult = {
3
3
  output: string;
4
4
  };
5
5
  export declare function detectTestCmd(projectDir: string): string | null;
6
+ export declare function detectInstallCmd(projectDir: string): string | null;
6
7
  export declare function runTestCmd(opts: {
7
8
  cmd: string;
8
9
  cwd: string;
@@ -1 +1 @@
1
- {"version":3,"file":"testcmd.d.ts","sourceRoot":"","sources":["../../src/testcmd.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,UAAU,GAAG;IACrB,QAAQ,EAAC,MAAM,CAAA;IACf,MAAM,EAAC,MAAM,CAAA;CAChB,CAAA;AAID,wBAAgB,aAAa,CAAE,UAAU,EAAC,MAAM,GAAE,MAAM,GAAC,IAAI,CAa5D;AAED,wBAAgB,UAAU,CAAE,IAAI,EAAC;IAC7B,GAAG,EAAC,MAAM,CAAA;IACV,GAAG,EAAC,MAAM,CAAA;IACV,OAAO,EAAC,MAAM,CAAA;IACd,QAAQ,CAAC,EAAC,GAAG,CAAC,MAAM,CAAC,CAAA;CACxB,GAAE,OAAO,CAAC,UAAU,CAAC,CAwDrB"}
1
+ {"version":3,"file":"testcmd.d.ts","sourceRoot":"","sources":["../../src/testcmd.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,UAAU,GAAG;IACrB,QAAQ,EAAC,MAAM,CAAA;IACf,MAAM,EAAC,MAAM,CAAA;CAChB,CAAA;AAID,wBAAgB,aAAa,CAAE,UAAU,EAAC,MAAM,GAAE,MAAM,GAAC,IAAI,CAa5D;AAOD,wBAAgB,gBAAgB,CAAE,UAAU,EAAC,MAAM,GAAE,MAAM,GAAC,IAAI,CAW/D;AAED,wBAAgB,UAAU,CAAE,IAAI,EAAC;IAC7B,GAAG,EAAC,MAAM,CAAA;IACV,GAAG,EAAC,MAAM,CAAA;IACV,OAAO,EAAC,MAAM,CAAA;IACd,QAAQ,CAAC,EAAC,GAAG,CAAC,MAAM,CAAC,CAAA;CACxB,GAAE,OAAO,CAAC,UAAU,CAAC,CAwDrB"}
@@ -1,7 +1,22 @@
1
1
  import type { Ticket } from './orchestrator.js';
2
2
  export declare function getRepoRoot(): string;
3
3
  export declare function getRepoName(): string;
4
- export declare function getCurrentBranch(): string;
4
+ export declare function getCurrentBranch(cwd?: string): string;
5
+ export declare function localBranchExists(repoRoot: string, name: string): boolean;
6
+ export declare function getDefaultBranch(repoRoot: string): string | null;
7
+ export declare function integrationBranchName(features: ReadonlyArray<string>): string;
8
+ export declare function planIntegrationBranch(current: string, defaultBranch: string | null, features: ReadonlyArray<string>): string | null;
9
+ export type IntegrationPlan = {
10
+ branch: string;
11
+ created: boolean;
12
+ resumed: boolean;
13
+ behind: number;
14
+ fastForwarded: boolean;
15
+ launchBranch: string;
16
+ forkedFrom: string | null;
17
+ };
18
+ export declare function prepareIntegrationBranch(repoRoot: string, features: ReadonlyArray<string>): IntegrationPlan;
19
+ export declare function describeIntegrationBranch(repoRoot: string, features: ReadonlyArray<string>): string;
5
20
  export declare function buildBranchName(repoName: string, ticket: Ticket): string;
6
21
  export declare function createWorktree(opts: {
7
22
  repoRoot: string;
@@ -12,10 +27,24 @@ export declare function createWorktree(opts: {
12
27
  worktreePath: string;
13
28
  branchName: string;
14
29
  }>;
30
+ export declare function createNamedWorktree(opts: {
31
+ repoRoot: string;
32
+ repoName: string;
33
+ baseBranch: string;
34
+ name: string;
35
+ }): Promise<{
36
+ worktreePath: string;
37
+ branchName: string;
38
+ }>;
15
39
  export declare function removeWorktree(worktreePath: string, opts?: {
16
40
  force?: boolean;
17
41
  }): Promise<void>;
42
+ export declare function deleteBranch(repoRoot: string, name: string): Promise<void>;
18
43
  export declare function hasNewCommits(repoRoot: string, base: string, branch: string): boolean;
44
+ export declare function headCommit(cwd: string): string;
45
+ export declare function mergeBase(cwd: string, a: string, b: string): string | null;
46
+ export declare function isAncestor(cwd: string, ancestor: string, of: string): boolean;
47
+ export declare function hasChangesOutside(cwd: string, from: string, to: string, excludeDir: string): boolean;
19
48
  export declare function isDirty(cwd: string): boolean;
20
49
  export declare function commitDirty(cwd: string, message?: string): Promise<boolean>;
21
50
  export declare function listWorktrees(repoRoot: string): Promise<Array<string>>;
@@ -1 +1 @@
1
- {"version":3,"file":"worktree.d.ts","sourceRoot":"","sources":["../../src/worktree.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAI/C,wBAAgB,WAAW,IAAI,MAAM,CAOpC;AAED,wBAAgB,WAAW,IAAI,MAAM,CAOpC;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAOzC;AAkBD,wBAAgB,eAAe,CAC3B,QAAQ,EAAC,MAAM,EACf,MAAM,EAAC,MAAM,GACf,MAAM,CAMP;AAwED,wBAAgB,cAAc,CAC1B,IAAI,EAAC;IACD,QAAQ,EAAC,MAAM,CAAA;IACf,QAAQ,EAAC,MAAM,CAAA;IACf,UAAU,EAAC,MAAM,CAAA;IACjB,MAAM,EAAC,MAAM,CAAA;CAChB,GACH,OAAO,CAAC;IAAE,YAAY,EAAC,MAAM,CAAC;IAAC,UAAU,EAAC,MAAM,CAAA;CAAE,CAAC,CAIpD;AAeD,wBAAgB,cAAc,CAC1B,YAAY,EAAC,MAAM,EACnB,IAAI,CAAC,EAAC;IAAE,KAAK,CAAC,EAAC,OAAO,CAAA;CAAE,GAC1B,OAAO,CAAC,IAAI,CAAC,CAMd;AAED,wBAAgB,aAAa,CACzB,QAAQ,EAAC,MAAM,EACf,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,MAAM,GACf,OAAO,CAOR;AAED,wBAAgB,OAAO,CAAE,GAAG,EAAC,MAAM,GAAE,OAAO,CAO3C;AAED,wBAAsB,WAAW,CAC7B,GAAG,EAAC,MAAM,EACV,OAAO,CAAC,EAAC,MAAM,GACjB,OAAO,CAAC,OAAO,CAAC,CAsBjB;AAED,wBAAsB,aAAa,CAC/B,QAAQ,EAAC,MAAM,GACjB,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAWvB"}
1
+ {"version":3,"file":"worktree.d.ts","sourceRoot":"","sources":["../../src/worktree.ts"],"names":[],"mappings":"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"}
package/dist/testcmd.js CHANGED
@@ -16,6 +16,18 @@ function detectTestCmd(projectDir) {
16
16
  }
17
17
  }
18
18
  __name(detectTestCmd, "detectTestCmd");
19
+ function detectInstallCmd(projectDir) {
20
+ const has = /* @__PURE__ */ __name((name) => existsSync(join(projectDir, name)), "has");
21
+ if (!has("package.json")) return null;
22
+ if (has("pnpm-lock.yaml")) {
23
+ return "pnpm install --prefer-offline --frozen-lockfile";
24
+ }
25
+ if (has("package-lock.json")) {
26
+ return "npm ci --prefer-offline --no-audit --no-fund";
27
+ }
28
+ return "npm install --prefer-offline --no-audit --no-fund --no-package-lock";
29
+ }
30
+ __name(detectInstallCmd, "detectInstallCmd");
19
31
  function runTestCmd(opts) {
20
32
  return new Promise((resolve) => {
21
33
  let settled = false;
@@ -70,6 +82,7 @@ function runTestCmd(opts) {
70
82
  }
71
83
  __name(runTestCmd, "runTestCmd");
72
84
  export {
85
+ detectInstallCmd,
73
86
  detectTestCmd,
74
87
  runTestCmd
75
88
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/testcmd.ts"],
4
- "sourcesContent": ["import { spawn } from 'node:child_process'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\nexport type TestResult = {\n exitCode:number\n output:string\n}\n\n// Returns the command to run the project's tests, or null\n// when the project does not declare one.\nexport function detectTestCmd (projectDir:string):string|null {\n const pkgPath = join(projectDir, 'package.json')\n if (!existsSync(pkgPath)) return null\n try {\n const pkg = JSON.parse(\n readFileSync(pkgPath, 'utf8')\n ) as { scripts?:Record<string, string> }\n return pkg.scripts?.test ?\n 'npm test' :\n null\n } catch {\n return null\n }\n}\n\nexport function runTestCmd (opts:{\n cmd:string\n cwd:string\n timeout:number\n registry?:Set<number>\n}):Promise<TestResult> {\n return new Promise(resolve => {\n let settled = false\n const child = spawn('sh', ['-c', opts.cmd], {\n cwd:opts.cwd,\n stdio:['ignore', 'pipe', 'pipe'],\n detached:true,\n })\n\n if (child.pid !== undefined && opts.registry) {\n opts.registry.add(child.pid)\n }\n\n const chunks:string[] = []\n child.stdout.on('data', d => chunks.push(String(d)))\n child.stderr.on('data', d => chunks.push(String(d)))\n\n const timer = setTimeout(() => {\n if (child.pid) {\n try {\n process.kill(-child.pid, 'SIGKILL')\n } catch {\n child.kill('SIGKILL')\n }\n } else {\n child.kill('SIGKILL')\n }\n }, opts.timeout * 1000)\n\n child.on('error', err => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n if (child.pid !== undefined && opts.registry) {\n opts.registry.delete(child.pid)\n }\n chunks.push(String(err))\n resolve({\n exitCode:1,\n output:chunks.join(''),\n })\n })\n\n child.on('close', code => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n if (child.pid !== undefined && opts.registry) {\n opts.registry.delete(child.pid)\n }\n resolve({\n exitCode:code ?? 1,\n output:chunks.join(''),\n })\n })\n })\n}\n"],
5
- "mappings": ";;AAAA,SAAS,aAAa;AACtB,SAAS,YAAY,oBAAoB;AACzC,SAAS,YAAY;AASd,SAAS,cAAe,YAA+B;AAC1D,QAAM,UAAU,KAAK,YAAY,cAAc;AAC/C,MAAI,CAAC,WAAW,OAAO,EAAG,QAAO;AACjC,MAAI;AACA,UAAM,MAAM,KAAK;AAAA,MACb,aAAa,SAAS,MAAM;AAAA,IAChC;AACA,WAAO,IAAI,SAAS,OAChB,aACA;AAAA,EACR,QAAQ;AACJ,WAAO;AAAA,EACX;AACJ;AAbgB;AAeT,SAAS,WAAY,MAKL;AACnB,SAAO,IAAI,QAAQ,aAAW;AAC1B,QAAI,UAAU;AACd,UAAM,QAAQ,MAAM,MAAM,CAAC,MAAM,KAAK,GAAG,GAAG;AAAA,MACxC,KAAI,KAAK;AAAA,MACT,OAAM,CAAC,UAAU,QAAQ,MAAM;AAAA,MAC/B,UAAS;AAAA,IACb,CAAC;AAED,QAAI,MAAM,QAAQ,UAAa,KAAK,UAAU;AAC1C,WAAK,SAAS,IAAI,MAAM,GAAG;AAAA,IAC/B;AAEA,UAAM,SAAkB,CAAC;AACzB,UAAM,OAAO,GAAG,QAAQ,OAAK,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC;AACnD,UAAM,OAAO,GAAG,QAAQ,OAAK,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC;AAEnD,UAAM,QAAQ,WAAW,MAAM;AAC3B,UAAI,MAAM,KAAK;AACX,YAAI;AACA,kBAAQ,KAAK,CAAC,MAAM,KAAK,SAAS;AAAA,QACtC,QAAQ;AACJ,gBAAM,KAAK,SAAS;AAAA,QACxB;AAAA,MACJ,OAAO;AACH,cAAM,KAAK,SAAS;AAAA,MACxB;AAAA,IACJ,GAAG,KAAK,UAAU,GAAI;AAEtB,UAAM,GAAG,SAAS,SAAO;AACrB,UAAI,QAAS;AACb,gBAAU;AACV,mBAAa,KAAK;AAClB,UAAI,MAAM,QAAQ,UAAa,KAAK,UAAU;AAC1C,aAAK,SAAS,OAAO,MAAM,GAAG;AAAA,MAClC;AACA,aAAO,KAAK,OAAO,GAAG,CAAC;AACvB,cAAQ;AAAA,QACJ,UAAS;AAAA,QACT,QAAO,OAAO,KAAK,EAAE;AAAA,MACzB,CAAC;AAAA,IACL,CAAC;AAED,UAAM,GAAG,SAAS,UAAQ;AACtB,UAAI,QAAS;AACb,gBAAU;AACV,mBAAa,KAAK;AAClB,UAAI,MAAM,QAAQ,UAAa,KAAK,UAAU;AAC1C,aAAK,SAAS,OAAO,MAAM,GAAG;AAAA,MAClC;AACA,cAAQ;AAAA,QACJ,UAAS,QAAQ;AAAA,QACjB,QAAO,OAAO,KAAK,EAAE;AAAA,MACzB,CAAC;AAAA,IACL,CAAC;AAAA,EACL,CAAC;AACL;AA7DgB;",
4
+ "sourcesContent": ["import { spawn } from 'node:child_process'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\nexport type TestResult = {\n exitCode:number\n output:string\n}\n\n// Returns the command to run the project's tests, or null\n// when the project does not declare one.\nexport function detectTestCmd (projectDir:string):string|null {\n const pkgPath = join(projectDir, 'package.json')\n if (!existsSync(pkgPath)) return null\n try {\n const pkg = JSON.parse(\n readFileSync(pkgPath, 'utf8')\n ) as { scripts?:Record<string, string> }\n return pkg.scripts?.test ?\n 'npm test' :\n null\n } catch {\n return null\n }\n}\n\n// Returns the command that installs dependencies in a fresh\n// worktree, or null when the project has no package.json.\n// Follows the project's lockfile, so the worktree gets the\n// same dependency tree as the main checkout, and never\n// writes a lockfile a ticket branch would then commit.\nexport function detectInstallCmd (projectDir:string):string|null {\n const has = (name:string) => existsSync(join(projectDir, name))\n if (!has('package.json')) return null\n if (has('pnpm-lock.yaml')) {\n return 'pnpm install --prefer-offline --frozen-lockfile'\n }\n if (has('package-lock.json')) {\n return 'npm ci --prefer-offline --no-audit --no-fund'\n }\n return 'npm install --prefer-offline --no-audit ' +\n '--no-fund --no-package-lock'\n}\n\nexport function runTestCmd (opts:{\n cmd:string\n cwd:string\n timeout:number\n registry?:Set<number>\n}):Promise<TestResult> {\n return new Promise(resolve => {\n let settled = false\n const child = spawn('sh', ['-c', opts.cmd], {\n cwd:opts.cwd,\n stdio:['ignore', 'pipe', 'pipe'],\n detached:true,\n })\n\n if (child.pid !== undefined && opts.registry) {\n opts.registry.add(child.pid)\n }\n\n const chunks:string[] = []\n child.stdout.on('data', d => chunks.push(String(d)))\n child.stderr.on('data', d => chunks.push(String(d)))\n\n const timer = setTimeout(() => {\n if (child.pid) {\n try {\n process.kill(-child.pid, 'SIGKILL')\n } catch {\n child.kill('SIGKILL')\n }\n } else {\n child.kill('SIGKILL')\n }\n }, opts.timeout * 1000)\n\n child.on('error', err => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n if (child.pid !== undefined && opts.registry) {\n opts.registry.delete(child.pid)\n }\n chunks.push(String(err))\n resolve({\n exitCode:1,\n output:chunks.join(''),\n })\n })\n\n child.on('close', code => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n if (child.pid !== undefined && opts.registry) {\n opts.registry.delete(child.pid)\n }\n resolve({\n exitCode:code ?? 1,\n output:chunks.join(''),\n })\n })\n })\n}\n"],
5
+ "mappings": ";;AAAA,SAAS,aAAa;AACtB,SAAS,YAAY,oBAAoB;AACzC,SAAS,YAAY;AASd,SAAS,cAAe,YAA+B;AAC1D,QAAM,UAAU,KAAK,YAAY,cAAc;AAC/C,MAAI,CAAC,WAAW,OAAO,EAAG,QAAO;AACjC,MAAI;AACA,UAAM,MAAM,KAAK;AAAA,MACb,aAAa,SAAS,MAAM;AAAA,IAChC;AACA,WAAO,IAAI,SAAS,OAChB,aACA;AAAA,EACR,QAAQ;AACJ,WAAO;AAAA,EACX;AACJ;AAbgB;AAoBT,SAAS,iBAAkB,YAA+B;AAC7D,QAAM,MAAM,wBAAC,SAAgB,WAAW,KAAK,YAAY,IAAI,CAAC,GAAlD;AACZ,MAAI,CAAC,IAAI,cAAc,EAAG,QAAO;AACjC,MAAI,IAAI,gBAAgB,GAAG;AACvB,WAAO;AAAA,EACX;AACA,MAAI,IAAI,mBAAmB,GAAG;AAC1B,WAAO;AAAA,EACX;AACA,SAAO;AAEX;AAXgB;AAaT,SAAS,WAAY,MAKL;AACnB,SAAO,IAAI,QAAQ,aAAW;AAC1B,QAAI,UAAU;AACd,UAAM,QAAQ,MAAM,MAAM,CAAC,MAAM,KAAK,GAAG,GAAG;AAAA,MACxC,KAAI,KAAK;AAAA,MACT,OAAM,CAAC,UAAU,QAAQ,MAAM;AAAA,MAC/B,UAAS;AAAA,IACb,CAAC;AAED,QAAI,MAAM,QAAQ,UAAa,KAAK,UAAU;AAC1C,WAAK,SAAS,IAAI,MAAM,GAAG;AAAA,IAC/B;AAEA,UAAM,SAAkB,CAAC;AACzB,UAAM,OAAO,GAAG,QAAQ,OAAK,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC;AACnD,UAAM,OAAO,GAAG,QAAQ,OAAK,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC;AAEnD,UAAM,QAAQ,WAAW,MAAM;AAC3B,UAAI,MAAM,KAAK;AACX,YAAI;AACA,kBAAQ,KAAK,CAAC,MAAM,KAAK,SAAS;AAAA,QACtC,QAAQ;AACJ,gBAAM,KAAK,SAAS;AAAA,QACxB;AAAA,MACJ,OAAO;AACH,cAAM,KAAK,SAAS;AAAA,MACxB;AAAA,IACJ,GAAG,KAAK,UAAU,GAAI;AAEtB,UAAM,GAAG,SAAS,SAAO;AACrB,UAAI,QAAS;AACb,gBAAU;AACV,mBAAa,KAAK;AAClB,UAAI,MAAM,QAAQ,UAAa,KAAK,UAAU;AAC1C,aAAK,SAAS,OAAO,MAAM,GAAG;AAAA,MAClC;AACA,aAAO,KAAK,OAAO,GAAG,CAAC;AACvB,cAAQ;AAAA,QACJ,UAAS;AAAA,QACT,QAAO,OAAO,KAAK,EAAE;AAAA,MACzB,CAAC;AAAA,IACL,CAAC;AAED,UAAM,GAAG,SAAS,UAAQ;AACtB,UAAI,QAAS;AACb,gBAAU;AACV,mBAAa,KAAK;AAClB,UAAI,MAAM,QAAQ,UAAa,KAAK,UAAU;AAC1C,aAAK,SAAS,OAAO,MAAM,GAAG;AAAA,MAClC;AACA,cAAQ;AAAA,QACJ,UAAS,QAAQ;AAAA,QACjB,QAAO,OAAO,KAAK,EAAE;AAAA,MACzB,CAAC;AAAA,IACL,CAAC;AAAA,EACL,CAAC;AACL;AA7DgB;",
6
6
  "names": []
7
7
  }
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