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.
- package/README.md +181 -19
- package/dist/blockers.js +149 -0
- package/dist/blockers.js.map +7 -0
- package/dist/display.js +12 -4
- package/dist/display.js.map +2 -2
- package/dist/docs-templates/issue-tracker.md +9 -1
- package/dist/index.js +2735 -415
- package/dist/index.js.map +4 -4
- package/dist/learnings.js +360 -0
- package/dist/learnings.js.map +7 -0
- package/dist/meta.json +289 -16
- package/dist/orchestrator.js +753 -188
- 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/resolve.js +98 -9
- package/dist/resolve.js.map +2 -2
- package/dist/retry.js +83 -0
- package/dist/retry.js.map +7 -0
- package/dist/review.js +669 -0
- package/dist/review.js.map +7 -0
- package/dist/rules.js +67 -0
- package/dist/rules.js.map +7 -0
- package/dist/skills/to-tickets/SKILL.md +5 -5
- package/dist/src/blockers.d.ts +45 -0
- package/dist/src/blockers.d.ts.map +1 -0
- package/dist/src/display.d.ts +9 -0
- package/dist/src/display.d.ts.map +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/learnings.d.ts +70 -0
- package/dist/src/learnings.d.ts.map +1 -0
- package/dist/src/orchestrator.d.ts +85 -28
- 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/resolve.d.ts +24 -0
- package/dist/src/resolve.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 +131 -0
- package/dist/src/review.d.ts.map +1 -0
- package/dist/src/rules.d.ts +11 -0
- package/dist/src/rules.d.ts.map +1 -0
- package/dist/src/stats.d.ts +43 -0
- package/dist/src/stats.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/stats.js +111 -0
- package/dist/stats.js.map +7 -0
- 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 +3 -2
|
@@ -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/resolve.js
CHANGED
|
@@ -5,7 +5,16 @@ import { dirname, join } from "node:path";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { checkbox, Separator } from "@inquirer/prompts";
|
|
7
7
|
import { isSpecComplete } from "./progress.js";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
scanTickets,
|
|
10
|
+
findReadyTickets,
|
|
11
|
+
blockerLabel
|
|
12
|
+
} from "./orchestrator.js";
|
|
13
|
+
import { ticketLabel } from "./display.js";
|
|
14
|
+
import {
|
|
15
|
+
parseBlockerField,
|
|
16
|
+
resolveBlockers
|
|
17
|
+
} from "./blockers.js";
|
|
9
18
|
function getScriptDir(metaUrl) {
|
|
10
19
|
return dirname(fileURLToPath(metaUrl));
|
|
11
20
|
}
|
|
@@ -47,6 +56,86 @@ function findFeatureDirs(cwd) {
|
|
|
47
56
|
return results;
|
|
48
57
|
}
|
|
49
58
|
__name(findFeatureDirs, "findFeatureDirs");
|
|
59
|
+
function knownFeatureNames(scratchDir) {
|
|
60
|
+
const names = /* @__PURE__ */ new Set();
|
|
61
|
+
const walk = /* @__PURE__ */ __name((rel) => {
|
|
62
|
+
const dir = rel === "" ? scratchDir : join(scratchDir, rel);
|
|
63
|
+
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
|
64
|
+
if (!e.isDirectory() || e.name === "issues") continue;
|
|
65
|
+
const name = rel === "" ? e.name : `${rel}/${e.name}`;
|
|
66
|
+
if (existsSync(join(scratchDir, name, "issues"))) {
|
|
67
|
+
names.add(name);
|
|
68
|
+
}
|
|
69
|
+
walk(name);
|
|
70
|
+
}
|
|
71
|
+
}, "walk");
|
|
72
|
+
if (existsSync(scratchDir)) walk("");
|
|
73
|
+
return names;
|
|
74
|
+
}
|
|
75
|
+
__name(knownFeatureNames, "knownFeatureNames");
|
|
76
|
+
function loadSelection(scratchDir, names) {
|
|
77
|
+
const known = knownFeatureNames(scratchDir);
|
|
78
|
+
const issuesDir = /* @__PURE__ */ __name((name) => join(scratchDir, name, "issues"), "issuesDir");
|
|
79
|
+
const tickets = [];
|
|
80
|
+
const selected = /* @__PURE__ */ new Map();
|
|
81
|
+
for (const name of new Set(names)) {
|
|
82
|
+
const dir = issuesDir(name);
|
|
83
|
+
const found = existsSync(dir) ? scanTickets(dir, name, known) : [];
|
|
84
|
+
selected.set(name, new Set(found.map((t) => t.num)));
|
|
85
|
+
tickets.push(...found);
|
|
86
|
+
}
|
|
87
|
+
const outside = /* @__PURE__ */ new Map();
|
|
88
|
+
const lookup = /* @__PURE__ */ __name((feature, num) => {
|
|
89
|
+
if (!known.has(feature)) return void 0;
|
|
90
|
+
let statuses = outside.get(feature);
|
|
91
|
+
if (!statuses) {
|
|
92
|
+
statuses = new Map(
|
|
93
|
+
scanTickets(issuesDir(feature), feature).map((t) => [t.num, t.status])
|
|
94
|
+
);
|
|
95
|
+
outside.set(feature, statuses);
|
|
96
|
+
}
|
|
97
|
+
return statuses.get(num);
|
|
98
|
+
}, "lookup");
|
|
99
|
+
const warnings = [];
|
|
100
|
+
for (const t of tickets) {
|
|
101
|
+
const parsed = parseBlockerField(t.body, known);
|
|
102
|
+
t.blockers = resolveBlockers(
|
|
103
|
+
parsed.refs,
|
|
104
|
+
t.feature,
|
|
105
|
+
selected,
|
|
106
|
+
lookup
|
|
107
|
+
);
|
|
108
|
+
const ticket = ticketLabel(t);
|
|
109
|
+
for (const word of parsed.typos) {
|
|
110
|
+
warnings.push({ ticket, kind: "typo", text: word });
|
|
111
|
+
}
|
|
112
|
+
for (const ref of t.blockers) {
|
|
113
|
+
if (ref.where !== "missing") continue;
|
|
114
|
+
warnings.push({
|
|
115
|
+
ticket,
|
|
116
|
+
kind: "missing",
|
|
117
|
+
text: blockerLabel(ref)
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return { tickets, warnings };
|
|
122
|
+
}
|
|
123
|
+
__name(loadSelection, "loadSelection");
|
|
124
|
+
function featureCounts(scratchDir, name) {
|
|
125
|
+
const { tickets, warnings } = loadSelection(scratchDir, [name]);
|
|
126
|
+
return {
|
|
127
|
+
ready: findReadyTickets(tickets).length,
|
|
128
|
+
total: tickets.length,
|
|
129
|
+
badBlockers: warnings.filter((w) => w.kind === "missing").length
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
__name(featureCounts, "featureCounts");
|
|
133
|
+
function featureChoiceLabel(scratchDir, name) {
|
|
134
|
+
const c = featureCounts(scratchDir, name);
|
|
135
|
+
const bad = c.badBlockers === 0 ? "" : `, ${c.badBlockers} bad blocker` + (c.badBlockers === 1 ? "" : "s");
|
|
136
|
+
return `${name} (${c.ready}/${c.total} ready${bad})`;
|
|
137
|
+
}
|
|
138
|
+
__name(featureChoiceLabel, "featureChoiceLabel");
|
|
50
139
|
const ALL_VALUE = "__all__";
|
|
51
140
|
async function resolveIssuesDir(specName, cwd = process.cwd()) {
|
|
52
141
|
if (specName) {
|
|
@@ -69,14 +158,11 @@ async function resolveIssuesDir(specName, cwd = process.cwd()) {
|
|
|
69
158
|
console.log(`Using feature: ${f.name}`);
|
|
70
159
|
return [{ dir: f.dir, spec: f.spec, name: f.name }];
|
|
71
160
|
}
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
value: f.name
|
|
78
|
-
};
|
|
79
|
-
});
|
|
161
|
+
const scratchDir = join(cwd, ".scratch");
|
|
162
|
+
const featureChoices = features.map((f) => ({
|
|
163
|
+
name: featureChoiceLabel(scratchDir, f.name),
|
|
164
|
+
value: f.name
|
|
165
|
+
}));
|
|
80
166
|
while (true) {
|
|
81
167
|
const selected = await checkbox({
|
|
82
168
|
message: "Which features?",
|
|
@@ -101,8 +187,11 @@ async function resolveIssuesDir(specName, cwd = process.cwd()) {
|
|
|
101
187
|
}
|
|
102
188
|
__name(resolveIssuesDir, "resolveIssuesDir");
|
|
103
189
|
export {
|
|
190
|
+
featureChoiceLabel,
|
|
191
|
+
featureCounts,
|
|
104
192
|
findFeatureDirs,
|
|
105
193
|
getScriptDir,
|
|
194
|
+
loadSelection,
|
|
106
195
|
resolveIssuesDir
|
|
107
196
|
};
|
|
108
197
|
//# sourceMappingURL=resolve.js.map
|
package/dist/resolve.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/resolve.ts"],
|
|
4
|
-
"sourcesContent": ["import { existsSync, readdirSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { checkbox, Separator } from '@inquirer/prompts'\nimport { isSpecComplete } from './progress.js'\nimport {
|
|
5
|
-
"mappings": ";;AAAA,SAAS,YAAY,mBAAmB;AACxC,SAAS,SAAS,YAAY;AAC9B,SAAS,qBAAqB;AAC9B,SAAS,UAAU,iBAAiB;AACpC,SAAS,sBAAsB;AAC/B,SAAS,
|
|
4
|
+
"sourcesContent": ["import { existsSync, readdirSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { checkbox, Separator } from '@inquirer/prompts'\nimport { isSpecComplete } from './progress.js'\nimport {\n scanTickets,\n findReadyTickets,\n blockerLabel,\n type Ticket,\n} from './orchestrator.js'\nimport { ticketLabel } from './display.js'\nimport {\n parseBlockerField,\n resolveBlockers,\n type BlockerWarning,\n} from './blockers.js'\n\nexport type { BlockerWarning }\n\nexport type FeatureSelection = {\n dir:string;\n spec:string;\n name:string;\n}\n\nexport function getScriptDir (metaUrl:string):string {\n return dirname(fileURLToPath(metaUrl))\n}\n\nexport function findFeatureDirs (cwd:string):{\n name:string;\n dir:string;\n spec:string\n}[] {\n const scratchDir = join(cwd, '.scratch')\n if (!existsSync(scratchDir)) return []\n const entries = readdirSync(\n scratchDir, { withFileTypes:true }\n )\n const results:{ name:string; dir:string; spec:string }[] = []\n for (const entry of entries) {\n if (!entry.isDirectory()) continue\n const issuesDir = join(\n scratchDir, entry.name, 'issues'\n )\n const specFile = join(\n scratchDir, entry.name, 'spec.md'\n )\n if (!existsSync(issuesDir)) continue\n const progressFile = join(\n scratchDir, entry.name, 'progress.md'\n )\n if (isSpecComplete(progressFile)) continue\n results.push({\n name:entry.name,\n dir:issuesDir,\n spec:specFile,\n })\n }\n results.sort((a, b) => a.name.localeCompare(b.name))\n return results\n}\n\nexport type LoadedSelection = {\n tickets:Ticket[];\n warnings:BlockerWarning[];\n}\n\n// Names of the features under scratchDir: dirs with an issues\n// subdir, complete or not, at any depth. A nested feature's name\n// is its path, e.g. `rollout/billing`, the same name `--spec`\n// takes.\nfunction knownFeatureNames (scratchDir:string):Set<string> {\n const names = new Set<string>()\n const walk = (rel:string) => {\n const dir = rel === '' ? scratchDir : join(scratchDir, rel)\n for (const e of readdirSync(dir, { withFileTypes:true })) {\n if (!e.isDirectory() || e.name === 'issues') continue\n const name = rel === '' ? e.name : `${rel}/${e.name}`\n if (existsSync(join(scratchDir, name, 'issues'))) {\n names.add(name)\n }\n walk(name)\n }\n }\n if (existsSync(scratchDir)) walk('')\n return names\n}\n\n/**\n * Tickets of the named features with every blocker resolved, and\n * the problems found on the way. An unselected feature is scanned\n * only when a ref points into it, once.\n */\nexport function loadSelection (\n scratchDir:string,\n names:ReadonlyArray<string>\n):LoadedSelection {\n const known = knownFeatureNames(scratchDir)\n const issuesDir = (name:string) => join(scratchDir, name, 'issues')\n\n const tickets:Ticket[] = []\n const selected = new Map<string, Set<number>>()\n for (const name of new Set(names)) {\n const dir = issuesDir(name)\n const found = existsSync(dir) ?\n scanTickets(dir, name, known) :\n []\n selected.set(name, new Set(found.map(t => t.num)))\n tickets.push(...found)\n }\n\n const outside = new Map<string, Map<number, string>>()\n const lookup = (feature:string, num:number) => {\n if (!known.has(feature)) return undefined\n let statuses = outside.get(feature)\n if (!statuses) {\n statuses = new Map(\n scanTickets(issuesDir(feature), feature)\n .map(t => [t.num, t.status])\n )\n outside.set(feature, statuses)\n }\n return statuses.get(num)\n }\n\n const warnings:BlockerWarning[] = []\n for (const t of tickets) {\n const parsed = parseBlockerField(t.body, known)\n t.blockers = resolveBlockers(\n parsed.refs, t.feature, selected, lookup\n )\n const ticket = ticketLabel(t)\n for (const word of parsed.typos) {\n warnings.push({ ticket, kind:'typo', text:word })\n }\n for (const ref of t.blockers) {\n if (ref.where !== 'missing') continue\n warnings.push({\n ticket, kind:'missing', text:blockerLabel(ref),\n })\n }\n }\n return { tickets, warnings }\n}\n\nexport type FeatureCounts = {\n ready:number;\n total:number;\n badBlockers:number; // refs that match no ticket\n}\n\n/**\n * Counts for one feature as if it ran alone: the same\n * loadSelection a `--spec <name>` run uses.\n */\nexport function featureCounts (\n scratchDir:string,\n name:string\n):FeatureCounts {\n const { tickets, warnings } = loadSelection(scratchDir, [name])\n return {\n ready:findReadyTickets(tickets).length,\n total:tickets.length,\n badBlockers:warnings.filter(w => w.kind === 'missing').length,\n }\n}\n\nexport function featureChoiceLabel (\n scratchDir:string,\n name:string\n):string {\n const c = featureCounts(scratchDir, name)\n const bad = c.badBlockers === 0 ?\n '' :\n `, ${c.badBlockers} bad blocker` +\n (c.badBlockers === 1 ? '' : 's')\n return `${name} (${c.ready}/${c.total} ready${bad})`\n}\n\nconst ALL_VALUE = '__all__'\n\nexport async function resolveIssuesDir (\n specName:string|undefined,\n cwd:string = process.cwd(),\n):Promise<FeatureSelection[]> {\n if (specName) {\n const featureDir = join(cwd, '.scratch', specName)\n return [{\n dir:join(featureDir, 'issues'),\n spec:join(featureDir, 'spec.md'),\n name:specName,\n }]\n }\n\n const features = findFeatureDirs(cwd)\n\n if (features.length === 0) {\n console.error(\n 'No .scratch/<feature>/issues/ directories ' +\n 'found.\\nProvide --spec <feature-name>.'\n )\n process.exit(2)\n }\n\n if (features.length === 1) {\n const f = features[0]\n console.log(`Using feature: ${f.name}`)\n return [{ dir:f.dir, spec:f.spec, name:f.name }]\n }\n\n const scratchDir = join(cwd, '.scratch')\n const featureChoices = features.map(f => ({\n name:featureChoiceLabel(scratchDir, f.name),\n value:f.name,\n }))\n\n while (true) {\n const selected = await checkbox({\n message:'Which features?',\n choices:[\n { name:'All', value:ALL_VALUE },\n new Separator(),\n ...featureChoices,\n ],\n })\n\n if (selected.length === 0) {\n console.log('Select at least one feature')\n continue\n }\n\n const names = selected.includes(ALL_VALUE) ?\n features.map(f => f.name) :\n selected\n\n return names.map(name => {\n const f = features.find(\n ff => ff.name === name\n )!\n return { dir:f.dir, spec:f.spec, name:f.name }\n })\n }\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
|
+
}
|