nightralph 0.0.53 → 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.
- package/README.md +82 -9
- package/dist/index.js +1947 -230
- package/dist/index.js.map +4 -4
- package/dist/learnings.js +356 -0
- package/dist/learnings.js.map +7 -0
- package/dist/meta.json +232 -12
- package/dist/orchestrator.js +442 -109
- package/dist/orchestrator.js.map +3 -3
- package/dist/preflight.js +34 -0
- package/dist/preflight.js.map +7 -0
- package/dist/progress.js +5 -4
- package/dist/progress.js.map +2 -2
- package/dist/retry.js +83 -0
- package/dist/retry.js.map +7 -0
- package/dist/review.js +662 -0
- package/dist/review.js.map +7 -0
- package/dist/rules.js +54 -0
- package/dist/rules.js.map +7 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/learnings.d.ts +68 -0
- package/dist/src/learnings.d.ts.map +1 -0
- package/dist/src/orchestrator.d.ts +14 -11
- package/dist/src/orchestrator.d.ts.map +1 -1
- package/dist/src/preflight.d.ts +6 -0
- package/dist/src/preflight.d.ts.map +1 -0
- package/dist/src/progress.d.ts +1 -1
- package/dist/src/progress.d.ts.map +1 -1
- package/dist/src/retry.d.ts +26 -0
- package/dist/src/retry.d.ts.map +1 -0
- package/dist/src/review.d.ts +127 -0
- package/dist/src/review.d.ts.map +1 -0
- package/dist/src/rules.d.ts +10 -0
- package/dist/src/rules.d.ts.map +1 -0
- package/dist/src/testcmd.d.ts +1 -0
- package/dist/src/testcmd.d.ts.map +1 -1
- package/dist/src/worktree.d.ts +30 -1
- package/dist/src/worktree.d.ts.map +1 -1
- package/dist/testcmd.js +13 -0
- package/dist/testcmd.js.map +2 -2
- package/dist/worktree.js +236 -32
- package/dist/worktree.js.map +2 -2
- package/package.json +4 -3
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
import { accessSync, constants, statSync } from "node:fs";
|
|
4
|
+
import { delimiter, join, resolve } from "node:path";
|
|
5
|
+
function isExecutableFile(file) {
|
|
6
|
+
try {
|
|
7
|
+
if (!statSync(file).isFile()) return false;
|
|
8
|
+
accessSync(file, constants.X_OK);
|
|
9
|
+
return true;
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
__name(isExecutableFile, "isExecutableFile");
|
|
15
|
+
function resolveExecutable(cmd, opts = {}) {
|
|
16
|
+
if (cmd === "") return null;
|
|
17
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
18
|
+
if (cmd.includes("/")) {
|
|
19
|
+
const full = resolve(cwd, cmd);
|
|
20
|
+
return isExecutableFile(full) ? full : null;
|
|
21
|
+
}
|
|
22
|
+
const path = opts.path ?? process.env.PATH ?? "";
|
|
23
|
+
for (const dir of path.split(delimiter)) {
|
|
24
|
+
if (dir === "") continue;
|
|
25
|
+
const candidate = join(dir, cmd);
|
|
26
|
+
if (isExecutableFile(candidate)) return candidate;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
__name(resolveExecutable, "resolveExecutable");
|
|
31
|
+
export {
|
|
32
|
+
resolveExecutable
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=preflight.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/preflight.ts"],
|
|
4
|
+
"sourcesContent": ["import { accessSync, constants, statSync } from 'node:fs'\nimport { delimiter, join, resolve } from 'node:path'\n\nexport type ResolveExecutableOpts = {\n // PATH to search for a bare name; default process.env.PATH\n path?:string\n // directory a relative path is resolved against; default\n // process.cwd()\n cwd?:string\n}\n\nfunction isExecutableFile (file:string):boolean {\n try {\n if (!statSync(file).isFile()) return false\n accessSync(file, constants.X_OK)\n return true\n } catch {\n return false\n }\n}\n\n// Where a command would run from, or null when it would not run.\n// A name with a slash is a path, resolved against cwd. A bare name\n// is looked up in each PATH entry in order. Never throws.\nexport function resolveExecutable (\n cmd:string,\n opts:ResolveExecutableOpts = {}\n):string|null {\n if (cmd === '') return null\n const cwd = opts.cwd ?? process.cwd()\n if (cmd.includes('/')) {\n const full = resolve(cwd, cmd)\n return isExecutableFile(full) ? full : null\n }\n const path = opts.path ?? process.env.PATH ?? ''\n for (const dir of path.split(delimiter)) {\n if (dir === '') continue\n const candidate = join(dir, cmd)\n if (isExecutableFile(candidate)) return candidate\n }\n return null\n}\n"],
|
|
5
|
+
"mappings": ";;AAAA,SAAS,YAAY,WAAW,gBAAgB;AAChD,SAAS,WAAW,MAAM,eAAe;AAUzC,SAAS,iBAAkB,MAAqB;AAC5C,MAAI;AACA,QAAI,CAAC,SAAS,IAAI,EAAE,OAAO,EAAG,QAAO;AACrC,eAAW,MAAM,UAAU,IAAI;AAC/B,WAAO;AAAA,EACX,QAAQ;AACJ,WAAO;AAAA,EACX;AACJ;AARS;AAaF,SAAS,kBACZ,KACA,OAA6B,CAAC,GACpB;AACV,MAAI,QAAQ,GAAI,QAAO;AACvB,QAAM,MAAM,KAAK,OAAO,QAAQ,IAAI;AACpC,MAAI,IAAI,SAAS,GAAG,GAAG;AACnB,UAAM,OAAO,QAAQ,KAAK,GAAG;AAC7B,WAAO,iBAAiB,IAAI,IAAI,OAAO;AAAA,EAC3C;AACA,QAAM,OAAO,KAAK,QAAQ,QAAQ,IAAI,QAAQ;AAC9C,aAAW,OAAO,KAAK,MAAM,SAAS,GAAG;AACrC,QAAI,QAAQ,GAAI;AAChB,UAAM,YAAY,KAAK,KAAK,GAAG;AAC/B,QAAI,iBAAiB,SAAS,EAAG,QAAO;AAAA,EAC5C;AACA,SAAO;AACX;AAjBgB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/progress.js
CHANGED
|
@@ -172,16 +172,17 @@ function writeProgress(progressPath, data) {
|
|
|
172
172
|
);
|
|
173
173
|
}
|
|
174
174
|
__name(writeProgress, "writeProgress");
|
|
175
|
-
async function commitProgress(repoRoot,
|
|
175
|
+
async function commitProgress(repoRoot, paths, message) {
|
|
176
|
+
const list = typeof paths === "string" ? [paths] : paths;
|
|
176
177
|
await execFileAsync(
|
|
177
178
|
"git",
|
|
178
|
-
["add", "--force",
|
|
179
|
+
["add", "--force", "--", ...list],
|
|
179
180
|
{ cwd: repoRoot }
|
|
180
181
|
);
|
|
181
182
|
try {
|
|
182
183
|
await execFileAsync(
|
|
183
184
|
"git",
|
|
184
|
-
["diff", "--cached", "--quiet", "--",
|
|
185
|
+
["diff", "--cached", "--quiet", "--", ...list],
|
|
185
186
|
{ cwd: repoRoot }
|
|
186
187
|
);
|
|
187
188
|
return false;
|
|
@@ -189,7 +190,7 @@ async function commitProgress(repoRoot, progressPath, message) {
|
|
|
189
190
|
}
|
|
190
191
|
await execFileAsync(
|
|
191
192
|
"git",
|
|
192
|
-
["commit", "-m", message, "--",
|
|
193
|
+
["commit", "-m", message, "--", ...list],
|
|
193
194
|
{ cwd: repoRoot }
|
|
194
195
|
);
|
|
195
196
|
return true;
|
package/dist/progress.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/progress.ts"],
|
|
4
|
-
"sourcesContent": ["import { execFile } from 'node:child_process'\nimport { readFileSync, writeFileSync } from 'node:fs'\nimport { promisify } from 'node:util'\nimport type { Ticket } from './orchestrator.js'\n\nconst TICKET_FILENAME_RE = /^(\\d+)-(.+)\\.md$/\n\nconst execFileAsync = promisify(execFile)\n\nexport type TicketProgress = {\n num:number\n slug:string\n filename:string\n done:boolean\n branch:string|null\n exitCode:number|null\n completedAt:string|null\n}\n\nexport type ProgressData = {\n feature:string\n startedAt:string\n tickets:Array<TicketProgress>\n}\n\nexport function generateProgress (\n feature:string,\n tickets:ReadonlyArray<Ticket>\n):ProgressData {\n return {\n feature,\n startedAt:new Date().toISOString(),\n tickets:tickets.map((ticket) => ({\n num:ticket.num,\n slug:ticket.slug,\n filename:ticket.filename,\n done:false,\n branch:null,\n exitCode:null,\n completedAt:null,\n })),\n }\n}\n\nexport function generateMultiFeatureProgress (\n tickets:ReadonlyArray<Ticket>\n):Map<string, ProgressData> {\n const byFeature = new Map<string, Ticket[]>()\n for (const t of tickets) {\n const key = t.feature || ''\n const arr = byFeature.get(key) ?? []\n arr.push(t)\n byFeature.set(key, arr)\n }\n const result = new Map<string, ProgressData>()\n for (const [feat, ft] of byFeature) {\n result.set(feat, generateProgress(feat, ft))\n }\n return result\n}\n\nexport type ProgressTracker = {\n dataFor:(feature:string) => ProgressData\n pathFor:(feature:string) => string\n}\n\nexport function createProgressTracker (opts:{\n progressPaths:Map<string, string>\n dataMap:Map<string, ProgressData>\n fallbackPath:string\n fallbackData:ProgressData\n}):ProgressTracker {\n return {\n dataFor (feature:string):ProgressData {\n return opts.dataMap.get(feature) ??\n opts.fallbackData\n },\n pathFor (feature:string):string {\n return opts.progressPaths.get(feature) ??\n opts.fallbackPath\n },\n }\n}\n\nexport function isSpecComplete (\n progressPath:string\n):boolean {\n const data = readProgress(progressPath)\n if (!data) return false\n if (data.tickets.length === 0) return false\n return data.tickets.every(t => t.done)\n}\n\nexport function readProgress (\n progressPath:string\n):ProgressData|null {\n let content:string\n try {\n content = readFileSync(progressPath, 'utf8')\n } catch {\n return null\n }\n\n const featureMatch = content.match(/^# Progress: (.*)$/m)\n const startedMatch = content.match(/^Started: (.*)$/m)\n\n const tickets:Array<TicketProgress> = []\n let current:TicketProgress|null = null\n\n for (const line of content.split('\\n')) {\n const ticketMatch = line.match(/^- \\[( |x)\\] (.+)$/)\n if (ticketMatch) {\n if (current) tickets.push(current)\n const filename = ticketMatch[2]\n const parsed = filename.match(TICKET_FILENAME_RE)\n current = {\n num:parsed ? Number(parsed[1]) : NaN,\n slug:parsed ? parsed[2] : '',\n filename,\n done:ticketMatch[1] === 'x',\n branch:null,\n exitCode:null,\n completedAt:null,\n }\n continue\n }\n if (!current) continue\n\n const branchMatch = line.match(/^ {2}branch: (.*)$/)\n if (branchMatch) {\n current.branch = branchMatch[1]\n continue\n }\n const exitMatch = line.match(/^ {2}exit: (-?\\d+)$/)\n if (exitMatch) {\n current.exitCode = Number(exitMatch[1])\n continue\n }\n const completedMatch = line.match(/^ {2}completed: (.*)$/)\n if (completedMatch) {\n current.completedAt = completedMatch[1]\n continue\n }\n }\n if (current) tickets.push(current)\n\n return {\n feature:featureMatch ? featureMatch[1] : '',\n startedAt:startedMatch ? startedMatch[1] : '',\n tickets,\n }\n}\n\nexport function mergeProgress (\n fresh:ProgressData,\n existing:ProgressData|null\n):ProgressData {\n if (!existing) return fresh\n\n const existingByFilename = new Map(\n existing.tickets.map((t) => [t.filename, t])\n )\n\n return {\n ...fresh,\n tickets:fresh.tickets.map((ticket) => {\n const prior = existingByFilename.get(ticket.filename)\n return (prior && prior.done) ? { ...prior } : ticket\n }),\n }\n}\n\nexport function renderProgress (data:ProgressData):string {\n const lines:Array<string> = [\n `# Progress: ${data.feature}`,\n '',\n `Started: ${data.startedAt}`,\n '',\n ]\n\n for (const ticket of data.tickets) {\n const checkbox = ticket.done ? '[x]' : '[ ]'\n lines.push(`- ${checkbox} ${ticket.filename}`)\n\n if (ticket.done || ticket.exitCode !== null) {\n if (ticket.branch) {\n lines.push(\n ` branch: ${ticket.branch}`\n )\n }\n if (ticket.exitCode !== null) {\n lines.push(\n ` exit: ${ticket.exitCode}`\n )\n }\n if (ticket.completedAt) {\n lines.push(\n ` completed: ${ticket.completedAt}`\n )\n }\n }\n }\n\n return lines.join('\\n') + '\\n'\n}\n\nexport function checkTicket (\n data:ProgressData,\n ticketNum:number,\n result:{ branch:string; exitCode:number; done?:boolean }\n):void {\n const ticket = data.tickets.find(\n (t) => t.num === ticketNum\n )\n if (!ticket) return\n\n ticket.done = result.done ?? true\n ticket.branch = result.branch\n ticket.exitCode = result.exitCode\n if (ticket.done) {\n ticket.completedAt = new Date().toISOString()\n }\n}\n\nexport function writeProgress (\n progressPath:string,\n data:ProgressData\n):void {\n writeFileSync(\n progressPath,\n renderProgress(data)\n )\n}\n\nexport async function commitProgress (\n repoRoot:string,\n
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { execFile } from 'node:child_process'\nimport { readFileSync, writeFileSync } from 'node:fs'\nimport { promisify } from 'node:util'\nimport type { Ticket } from './orchestrator.js'\n\nconst TICKET_FILENAME_RE = /^(\\d+)-(.+)\\.md$/\n\nconst execFileAsync = promisify(execFile)\n\nexport type TicketProgress = {\n num:number\n slug:string\n filename:string\n done:boolean\n branch:string|null\n exitCode:number|null\n completedAt:string|null\n}\n\nexport type ProgressData = {\n feature:string\n startedAt:string\n tickets:Array<TicketProgress>\n}\n\nexport function generateProgress (\n feature:string,\n tickets:ReadonlyArray<Ticket>\n):ProgressData {\n return {\n feature,\n startedAt:new Date().toISOString(),\n tickets:tickets.map((ticket) => ({\n num:ticket.num,\n slug:ticket.slug,\n filename:ticket.filename,\n done:false,\n branch:null,\n exitCode:null,\n completedAt:null,\n })),\n }\n}\n\nexport function generateMultiFeatureProgress (\n tickets:ReadonlyArray<Ticket>\n):Map<string, ProgressData> {\n const byFeature = new Map<string, Ticket[]>()\n for (const t of tickets) {\n const key = t.feature || ''\n const arr = byFeature.get(key) ?? []\n arr.push(t)\n byFeature.set(key, arr)\n }\n const result = new Map<string, ProgressData>()\n for (const [feat, ft] of byFeature) {\n result.set(feat, generateProgress(feat, ft))\n }\n return result\n}\n\nexport type ProgressTracker = {\n dataFor:(feature:string) => ProgressData\n pathFor:(feature:string) => string\n}\n\nexport function createProgressTracker (opts:{\n progressPaths:Map<string, string>\n dataMap:Map<string, ProgressData>\n fallbackPath:string\n fallbackData:ProgressData\n}):ProgressTracker {\n return {\n dataFor (feature:string):ProgressData {\n return opts.dataMap.get(feature) ??\n opts.fallbackData\n },\n pathFor (feature:string):string {\n return opts.progressPaths.get(feature) ??\n opts.fallbackPath\n },\n }\n}\n\nexport function isSpecComplete (\n progressPath:string\n):boolean {\n const data = readProgress(progressPath)\n if (!data) return false\n if (data.tickets.length === 0) return false\n return data.tickets.every(t => t.done)\n}\n\nexport function readProgress (\n progressPath:string\n):ProgressData|null {\n let content:string\n try {\n content = readFileSync(progressPath, 'utf8')\n } catch {\n return null\n }\n\n const featureMatch = content.match(/^# Progress: (.*)$/m)\n const startedMatch = content.match(/^Started: (.*)$/m)\n\n const tickets:Array<TicketProgress> = []\n let current:TicketProgress|null = null\n\n for (const line of content.split('\\n')) {\n const ticketMatch = line.match(/^- \\[( |x)\\] (.+)$/)\n if (ticketMatch) {\n if (current) tickets.push(current)\n const filename = ticketMatch[2]\n const parsed = filename.match(TICKET_FILENAME_RE)\n current = {\n num:parsed ? Number(parsed[1]) : NaN,\n slug:parsed ? parsed[2] : '',\n filename,\n done:ticketMatch[1] === 'x',\n branch:null,\n exitCode:null,\n completedAt:null,\n }\n continue\n }\n if (!current) continue\n\n const branchMatch = line.match(/^ {2}branch: (.*)$/)\n if (branchMatch) {\n current.branch = branchMatch[1]\n continue\n }\n const exitMatch = line.match(/^ {2}exit: (-?\\d+)$/)\n if (exitMatch) {\n current.exitCode = Number(exitMatch[1])\n continue\n }\n const completedMatch = line.match(/^ {2}completed: (.*)$/)\n if (completedMatch) {\n current.completedAt = completedMatch[1]\n continue\n }\n }\n if (current) tickets.push(current)\n\n return {\n feature:featureMatch ? featureMatch[1] : '',\n startedAt:startedMatch ? startedMatch[1] : '',\n tickets,\n }\n}\n\nexport function mergeProgress (\n fresh:ProgressData,\n existing:ProgressData|null\n):ProgressData {\n if (!existing) return fresh\n\n const existingByFilename = new Map(\n existing.tickets.map((t) => [t.filename, t])\n )\n\n return {\n ...fresh,\n tickets:fresh.tickets.map((ticket) => {\n const prior = existingByFilename.get(ticket.filename)\n return (prior && prior.done) ? { ...prior } : ticket\n }),\n }\n}\n\nexport function renderProgress (data:ProgressData):string {\n const lines:Array<string> = [\n `# Progress: ${data.feature}`,\n '',\n `Started: ${data.startedAt}`,\n '',\n ]\n\n for (const ticket of data.tickets) {\n const checkbox = ticket.done ? '[x]' : '[ ]'\n lines.push(`- ${checkbox} ${ticket.filename}`)\n\n if (ticket.done || ticket.exitCode !== null) {\n if (ticket.branch) {\n lines.push(\n ` branch: ${ticket.branch}`\n )\n }\n if (ticket.exitCode !== null) {\n lines.push(\n ` exit: ${ticket.exitCode}`\n )\n }\n if (ticket.completedAt) {\n lines.push(\n ` completed: ${ticket.completedAt}`\n )\n }\n }\n }\n\n return lines.join('\\n') + '\\n'\n}\n\nexport function checkTicket (\n data:ProgressData,\n ticketNum:number,\n result:{ branch:string; exitCode:number; done?:boolean }\n):void {\n const ticket = data.tickets.find(\n (t) => t.num === ticketNum\n )\n if (!ticket) return\n\n ticket.done = result.done ?? true\n ticket.branch = result.branch\n ticket.exitCode = result.exitCode\n if (ticket.done) {\n ticket.completedAt = new Date().toISOString()\n }\n}\n\nexport function writeProgress (\n progressPath:string,\n data:ProgressData\n):void {\n writeFileSync(\n progressPath,\n renderProgress(data)\n )\n}\n\nexport async function commitProgress (\n repoRoot:string,\n paths:string|string[],\n message:string\n):Promise<boolean> {\n const list = typeof paths === 'string' ?\n [paths] :\n paths\n await execFileAsync(\n 'git',\n ['add', '--force', '--', ...list],\n { cwd:repoRoot }\n )\n\n try {\n await execFileAsync(\n 'git',\n ['diff', '--cached', '--quiet', '--', ...list],\n { cwd:repoRoot }\n )\n return false\n } catch {\n // non-zero exit means the staged file differs from HEAD\n }\n\n await execFileAsync(\n 'git',\n ['commit', '-m', message, '--', ...list],\n { cwd:repoRoot }\n )\n return true\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/retry.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
import { basename } from "node:path";
|
|
4
|
+
const EFFORT_LADDERS = {
|
|
5
|
+
pi: [
|
|
6
|
+
"off",
|
|
7
|
+
"minimal",
|
|
8
|
+
"low",
|
|
9
|
+
"medium",
|
|
10
|
+
"high",
|
|
11
|
+
"xhigh",
|
|
12
|
+
"max"
|
|
13
|
+
],
|
|
14
|
+
claude: [
|
|
15
|
+
"low",
|
|
16
|
+
"medium",
|
|
17
|
+
"high",
|
|
18
|
+
"xhigh",
|
|
19
|
+
"max"
|
|
20
|
+
],
|
|
21
|
+
codex: ["low", "medium", "high"]
|
|
22
|
+
};
|
|
23
|
+
const ALL_EFFORT_LEVELS = [
|
|
24
|
+
"off",
|
|
25
|
+
"minimal",
|
|
26
|
+
"low",
|
|
27
|
+
"medium",
|
|
28
|
+
"high",
|
|
29
|
+
"xhigh",
|
|
30
|
+
"max"
|
|
31
|
+
];
|
|
32
|
+
function escalateEffort(provider, level) {
|
|
33
|
+
if (!level) return "high";
|
|
34
|
+
const ladder = EFFORT_LADDERS[provider];
|
|
35
|
+
const i = ladder.indexOf(level);
|
|
36
|
+
if (i === -1) return level;
|
|
37
|
+
return ladder[Math.min(i + 1, ladder.length - 1)];
|
|
38
|
+
}
|
|
39
|
+
__name(escalateEffort, "escalateEffort");
|
|
40
|
+
function retryDelayFor(base, attempt) {
|
|
41
|
+
return base * 2 ** (attempt - 2);
|
|
42
|
+
}
|
|
43
|
+
__name(retryDelayFor, "retryDelayFor");
|
|
44
|
+
function sleep(ms) {
|
|
45
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
46
|
+
}
|
|
47
|
+
__name(sleep, "sleep");
|
|
48
|
+
async function runWithRetries(opts, run) {
|
|
49
|
+
const maxAttempts = Math.max(1, opts.maxAttempts);
|
|
50
|
+
const providerName = basename(opts.agentCmd);
|
|
51
|
+
const isKnown = providerName in EFFORT_LADDERS;
|
|
52
|
+
const wait = opts.wait ?? sleep;
|
|
53
|
+
let effort = opts.effort;
|
|
54
|
+
for (let attempt = 1; ; attempt++) {
|
|
55
|
+
if (attempt === 1) {
|
|
56
|
+
opts.log(` Starting ${opts.label}`);
|
|
57
|
+
} else {
|
|
58
|
+
if (isKnown) {
|
|
59
|
+
effort = escalateEffort(
|
|
60
|
+
providerName,
|
|
61
|
+
effort
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
const delay = retryDelayFor(opts.retryDelay, attempt);
|
|
65
|
+
opts.log(
|
|
66
|
+
` ${opts.label}: retrying (attempt ${attempt}/${maxAttempts}` + (isKnown && effort ? `, effort ${effort}` : "") + (delay > 0 ? `, waiting ${delay}s` : "") + ")"
|
|
67
|
+
);
|
|
68
|
+
if (delay > 0) await wait(delay * 1e3);
|
|
69
|
+
}
|
|
70
|
+
const step = await run(attempt, effort);
|
|
71
|
+
if (step.stop || attempt >= maxAttempts) return step.result;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
__name(runWithRetries, "runWithRetries");
|
|
75
|
+
export {
|
|
76
|
+
ALL_EFFORT_LEVELS,
|
|
77
|
+
EFFORT_LADDERS,
|
|
78
|
+
escalateEffort,
|
|
79
|
+
retryDelayFor,
|
|
80
|
+
runWithRetries,
|
|
81
|
+
sleep
|
|
82
|
+
};
|
|
83
|
+
//# sourceMappingURL=retry.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/retry.ts"],
|
|
4
|
+
"sourcesContent": ["import { basename } from 'node:path'\n\n// Per-provider effort ladders.\nexport const EFFORT_LADDERS = {\n pi:['off', 'minimal', 'low', 'medium',\n 'high', 'xhigh', 'max'],\n claude:['low', 'medium', 'high',\n 'xhigh', 'max'],\n codex:['low', 'medium', 'high'],\n} as const\n\nexport type ProviderName =\n keyof typeof EFFORT_LADDERS\n\nexport type EffortLevel =\n typeof EFFORT_LADDERS[ProviderName][number]\n\nexport const ALL_EFFORT_LEVELS:EffortLevel[] = [\n 'off', 'minimal', 'low', 'medium',\n 'high', 'xhigh', 'max',\n]\n\n// Step up the effort ladder for a given provider.\n// Without a starting level, the first retry jumps to high.\n// Otherwise, step one rung up and stop at the provider's max.\nexport function escalateEffort (\n provider:ProviderName,\n level?:EffortLevel\n):EffortLevel {\n if (!level) return 'high'\n const ladder = EFFORT_LADDERS[provider]\n const i = (ladder as readonly string[])\n .indexOf(level)\n if (i === -1) return level\n return ladder[\n Math.min(i + 1, ladder.length - 1)\n ] as EffortLevel\n}\n\nexport function retryDelayFor (base:number, attempt:number):number {\n return base * 2 ** (attempt - 2)\n}\n\nexport function sleep (ms:number):Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n\nexport type AttemptStep<T> = { result:T; stop:boolean }\n\nexport type RetryOpts = {\n maxAttempts:number\n // seconds before the first retry; doubles each further retry\n retryDelay:number\n agentCmd:string\n // starting effort level; unset = provider's default\n effort?:EffortLevel\n // names the work in log lines, e.g. a ticket filename\n label:string\n log:(msg:string) => void\n // test seam; defaults to sleep\n wait?:(ms:number) => Promise<void>\n}\n\n// Runs `run` until a step says stop or the attempt cap is hit,\n// escalating effort for known providers and backing off\n// exponentially between attempts. Returns the last step's result.\nexport async function runWithRetries<T> (\n opts:RetryOpts,\n run:(\n attempt:number,\n effort:EffortLevel|undefined\n ) => Promise<AttemptStep<T>>\n):Promise<T> {\n const maxAttempts = Math.max(1, opts.maxAttempts)\n const providerName = basename(opts.agentCmd)\n const isKnown = providerName in EFFORT_LADDERS\n const wait = opts.wait ?? sleep\n let effort = opts.effort\n\n for (let attempt = 1; ; attempt++) {\n if (attempt === 1) {\n opts.log(` Starting ${opts.label}`)\n } else {\n if (isKnown) {\n effort = escalateEffort(\n providerName as ProviderName,\n effort\n )\n }\n const delay = retryDelayFor(opts.retryDelay, attempt)\n opts.log(\n ` ${opts.label}: retrying ` +\n `(attempt ${attempt}/${maxAttempts}` +\n (isKnown && effort ?\n `, effort ${effort}` : '') +\n (delay > 0 ?\n `, waiting ${delay}s` : '') +\n ')'\n )\n if (delay > 0) await wait(delay * 1000)\n }\n const step = await run(attempt, effort)\n if (step.stop || attempt >= maxAttempts) return step.result\n }\n}\n"],
|
|
5
|
+
"mappings": ";;AAAA,SAAS,gBAAgB;AAGlB,MAAM,iBAAiB;AAAA,EAC1B,IAAG;AAAA,IAAC;AAAA,IAAO;AAAA,IAAW;AAAA,IAAO;AAAA,IACzB;AAAA,IAAQ;AAAA,IAAS;AAAA,EAAK;AAAA,EAC1B,QAAO;AAAA,IAAC;AAAA,IAAO;AAAA,IAAU;AAAA,IACrB;AAAA,IAAS;AAAA,EAAK;AAAA,EAClB,OAAM,CAAC,OAAO,UAAU,MAAM;AAClC;AAQO,MAAM,oBAAkC;AAAA,EAC3C;AAAA,EAAO;AAAA,EAAW;AAAA,EAAO;AAAA,EACzB;AAAA,EAAQ;AAAA,EAAS;AACrB;AAKO,SAAS,eACZ,UACA,OACU;AACV,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,SAAS,eAAe,QAAQ;AACtC,QAAM,IAAK,OACN,QAAQ,KAAK;AAClB,MAAI,MAAM,GAAI,QAAO;AACrB,SAAO,OACH,KAAK,IAAI,IAAI,GAAG,OAAO,SAAS,CAAC,CACrC;AACJ;AAZgB;AAcT,SAAS,cAAe,MAAa,SAAuB;AAC/D,SAAO,OAAO,MAAM,UAAU;AAClC;AAFgB;AAIT,SAAS,MAAO,IAAyB;AAC5C,SAAO,IAAI,QAAQ,aAAW,WAAW,SAAS,EAAE,CAAC;AACzD;AAFgB;AAuBhB,eAAsB,eAClB,MACA,KAIS;AACT,QAAM,cAAc,KAAK,IAAI,GAAG,KAAK,WAAW;AAChD,QAAM,eAAe,SAAS,KAAK,QAAQ;AAC3C,QAAM,UAAU,gBAAgB;AAChC,QAAM,OAAO,KAAK,QAAQ;AAC1B,MAAI,SAAS,KAAK;AAElB,WAAS,UAAU,KAAK,WAAW;AAC/B,QAAI,YAAY,GAAG;AACf,WAAK,IAAI,cAAc,KAAK,KAAK,EAAE;AAAA,IACvC,OAAO;AACH,UAAI,SAAS;AACT,iBAAS;AAAA,UACL;AAAA,UACA;AAAA,QACJ;AAAA,MACJ;AACA,YAAM,QAAQ,cAAc,KAAK,YAAY,OAAO;AACpD,WAAK;AAAA,QACD,KAAK,KAAK,KAAK,uBACH,OAAO,IAAI,WAAW,MACjC,WAAW,SACR,YAAY,MAAM,KAAK,OAC1B,QAAQ,IACL,aAAa,KAAK,MAAM,MAC5B;AAAA,MACJ;AACA,UAAI,QAAQ,EAAG,OAAM,KAAK,QAAQ,GAAI;AAAA,IAC1C;AACA,UAAM,OAAO,MAAM,IAAI,SAAS,MAAM;AACtC,QAAI,KAAK,QAAQ,WAAW,YAAa,QAAO,KAAK;AAAA,EACzD;AACJ;AAtCsB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|