nightralph 0.0.53 → 0.0.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +181 -19
  2. package/dist/blockers.js +149 -0
  3. package/dist/blockers.js.map +7 -0
  4. package/dist/display.js +12 -4
  5. package/dist/display.js.map +2 -2
  6. package/dist/docs-templates/issue-tracker.md +9 -1
  7. package/dist/index.js +2735 -415
  8. package/dist/index.js.map +4 -4
  9. package/dist/learnings.js +360 -0
  10. package/dist/learnings.js.map +7 -0
  11. package/dist/meta.json +289 -16
  12. package/dist/orchestrator.js +753 -188
  13. package/dist/orchestrator.js.map +3 -3
  14. package/dist/preflight.js +34 -0
  15. package/dist/preflight.js.map +7 -0
  16. package/dist/progress.js +5 -4
  17. package/dist/progress.js.map +2 -2
  18. package/dist/resolve.js +98 -9
  19. package/dist/resolve.js.map +2 -2
  20. package/dist/retry.js +83 -0
  21. package/dist/retry.js.map +7 -0
  22. package/dist/review.js +669 -0
  23. package/dist/review.js.map +7 -0
  24. package/dist/rules.js +67 -0
  25. package/dist/rules.js.map +7 -0
  26. package/dist/skills/to-tickets/SKILL.md +5 -5
  27. package/dist/src/blockers.d.ts +45 -0
  28. package/dist/src/blockers.d.ts.map +1 -0
  29. package/dist/src/display.d.ts +9 -0
  30. package/dist/src/display.d.ts.map +1 -1
  31. package/dist/src/index.d.ts.map +1 -1
  32. package/dist/src/learnings.d.ts +70 -0
  33. package/dist/src/learnings.d.ts.map +1 -0
  34. package/dist/src/orchestrator.d.ts +85 -28
  35. package/dist/src/orchestrator.d.ts.map +1 -1
  36. package/dist/src/preflight.d.ts +6 -0
  37. package/dist/src/preflight.d.ts.map +1 -0
  38. package/dist/src/progress.d.ts +1 -1
  39. package/dist/src/progress.d.ts.map +1 -1
  40. package/dist/src/resolve.d.ts +24 -0
  41. package/dist/src/resolve.d.ts.map +1 -1
  42. package/dist/src/retry.d.ts +26 -0
  43. package/dist/src/retry.d.ts.map +1 -0
  44. package/dist/src/review.d.ts +131 -0
  45. package/dist/src/review.d.ts.map +1 -0
  46. package/dist/src/rules.d.ts +11 -0
  47. package/dist/src/rules.d.ts.map +1 -0
  48. package/dist/src/stats.d.ts +43 -0
  49. package/dist/src/stats.d.ts.map +1 -0
  50. package/dist/src/testcmd.d.ts +1 -0
  51. package/dist/src/testcmd.d.ts.map +1 -1
  52. package/dist/src/worktree.d.ts +30 -1
  53. package/dist/src/worktree.d.ts.map +1 -1
  54. package/dist/stats.js +111 -0
  55. package/dist/stats.js.map +7 -0
  56. package/dist/testcmd.js +13 -0
  57. package/dist/testcmd.js.map +2 -2
  58. package/dist/worktree.js +236 -32
  59. package/dist/worktree.js.map +2 -2
  60. package/package.json +3 -2
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/review.ts"],
4
+ "sourcesContent": ["import { existsSync, readFileSync, writeFileSync } from 'node:fs'\nimport { basename, dirname, join, relative } from 'node:path'\nimport {\n NOTES_PATH,\n formatDate,\n collectNotes,\n ensureNotesIgnored,\n appendEntry,\n} from './learnings.js'\nimport { runTestCmd } from './testcmd.js'\nimport { commitProgress } from './progress.js'\nimport { runWithRetries, type EffortLevel } from './retry.js'\nimport {\n createNamedWorktree,\n deleteBranch,\n removeWorktree,\n commitDirty,\n hasNewCommits,\n isDirty,\n hasChangesOutside,\n headCommit,\n isAncestor,\n mergeBase,\n} from './worktree.js'\nimport { mergeBranch } from './merge.js'\nimport type { AgentResult } from './orchestrator.js'\nimport {\n readDocsRule,\n installRule,\n subagentRule,\n testGateRule,\n LINT_RULE,\n integrityRule,\n CONFINEMENT_RULES,\n commitRule,\n notesRule,\n agentsMdRule,\n} from './rules.js'\n\nexport const FINDINGS_FILE = 'findings.md'\nexport const FINDINGS_PATH = `${dirname(NOTES_PATH)}/${FINDINGS_FILE}`\nexport const NO_FINDINGS = 'No findings.'\n// Bookkeeping lives here (tickets, progress, learnings, the\n// review log), so the review never looks at it.\nexport const REVIEW_EXCLUDE = '.scratch'\n\nexport const REVIEW_MARKER = '---- REVIEW INSTRUCTIONS ----'\nexport const FIX_MARKER = '---- REVIEW FINDINGS ----'\n\n// The bookkeeping directory for a review log: the nearest ancestor\n// of reviewPath named .scratch, relative to the repo root, so a run\n// launched from a subdirectory excludes the right path. Falls back\n// to REVIEW_EXCLUDE when reviewPath is not under a .scratch.\nexport function reviewExcludeFor (\n repoRoot:string,\n reviewPath:string\n):string {\n let dir = dirname(reviewPath)\n while (dirname(dir) !== dir) {\n if (basename(dir) === '.scratch') {\n const rel = relative(repoRoot, dir)\n if (rel && !rel.startsWith('..')) return rel\n break\n }\n dir = dirname(dir)\n }\n return REVIEW_EXCLUDE\n}\n\nexport function reviewDiffCommand (\n from:string,\n to:string,\n exclude:string = REVIEW_EXCLUDE\n):string {\n return `git diff ${from}..${to} -- . ':(exclude)${exclude}'`\n}\n\nexport function renderReviewPrompt (opts:{\n specBody:string\n from:string\n to:string\n exclude?:string\n preferSubagent?:string\n}):string {\n return [\n '---- SPEC CONTEXT ----',\n opts.specBody,\n '',\n REVIEW_MARKER,\n 'You are reviewing work that autonomous agents merged ' +\n 'into this branch, one ticket at a time. Review the ' +\n 'combined change as a whole.',\n '',\n 'The change to review:',\n '',\n ` ${reviewDiffCommand(opts.from, opts.to, opts.exclude)}`,\n '',\n 'Follow these rules:',\n '- Read any AGENTS.md in or above the directories the ' +\n 'change touches, plus CONTEXT.md and the relevant ' +\n 'docs/adr/ entries if they exist. Treat their ' +\n 'contracts and the spec above as the standard the ' +\n 'code must meet.',\n '- Look for correctness bugs, missing or weak tests, ' +\n 'and code that breaks a documented contract. Ignore ' +\n 'style preferences.',\n '- You may run the tests or the type checker to confirm ' +\n 'a suspicion.',\n ...subagentRule(opts.preferSubagent, true),\n '- Do NOT edit, stage, or commit code. Anything you ' +\n 'change here is discarded.',\n ...CONFINEMENT_RULES,\n '',\n `Write your findings to \\`${FINDINGS_PATH}\\` (create the ` +\n 'directory), most severe first, in exactly this form:',\n '',\n '- [ ] <one-line summary of the finding>',\n ' <file:line, and a concrete input or state that goes ' +\n 'wrong>',\n '',\n 'If you find nothing worth fixing, write the single line: ' +\n NO_FINDINGS,\n 'Never git add or commit that file; the orchestrator ' +\n 'collects it.',\n '',\n ].join('\\n')\n}\n\nexport type FixFailure =\n 'install'|'exit'|'no-commits'|'tests'|'merge'\n\nexport const FIX_FAILURE_TEXT:Record<FixFailure, string> = {\n install:'dependency install failed',\n exit:'the agent exited with an error',\n 'no-commits':'the agent made no commits',\n tests:'the test command failed after the agent finished',\n merge:'the fix branch did not merge cleanly',\n}\n\nfunction findingLines (findings:ReadonlyArray<Finding>):string[] {\n return findings.flatMap(f => [\n `- [ ] ${f.summary}`,\n ...(f.detail ?\n f.detail.split('\\n').map(l => ` ${l}`) :\n []),\n ])\n}\n\nfunction previousBlock (\n previous?:{ reason:FixFailure; notes:string|null }\n):string[] {\n if (!previous) return []\n return [\n '---- PREVIOUS FIX ATTEMPT ----',\n `It failed: ${FIX_FAILURE_TEXT[previous.reason]}.`,\n previous.notes ?? 'It left no notes.',\n '',\n ]\n}\n\nexport function renderFixPrompt (opts:{\n specBody:string\n findings:ReadonlyArray<Finding>\n testCmd:string\n installCmd?:string\n preferSubagent?:string\n previous?:{ reason:FixFailure; notes:string|null }\n}):string {\n return [\n '---- SPEC CONTEXT ----',\n opts.specBody,\n '',\n FIX_MARKER,\n ...findingLines(opts.findings),\n '',\n ...previousBlock(opts.previous),\n '---- AGENT INSTRUCTIONS ----',\n 'You are an autonomous coding agent. Fix the review ' +\n 'findings above using /tdd.',\n '',\n 'Follow these rules:',\n readDocsRule('findings'),\n '- Fix ONLY the listed findings. Do not fix unrelated ' +\n 'problems you notice.',\n ...installRule(opts.installCmd),\n ...subagentRule(opts.preferSubagent),\n '- Use /tdd for every finding: write a failing test that ' +\n 'shows the problem, then write only enough code to ' +\n 'make it pass, then refactor.',\n testGateRule(opts.testCmd, 'fix'),\n LINT_RULE,\n integrityRule('finding'),\n ...CONFINEMENT_RULES,\n commitRule('finding'),\n notesRule('finding', 'fix'),\n '- After fixing each finding, print a line: ' +\n '[x] <finding summary> (matching the summary ' +\n 'text exactly).',\n '- If a finding is wrong or cannot be fixed honestly, ' +\n 'leave it unchecked and explain why in your notes ' +\n 'file.',\n agentsMdRule('fix'),\n '',\n ].join('\\n')\n}\n\nconst SENTINEL_PREFIX = '<!-- nightralph:review'\nconst SENTINEL_RE = /^<!-- nightralph:review (.*) -->$/\nconst SHA_RE = /^[0-9a-f]{4,64}$/\nconst FIX_HEADING = '### Fix'\nconst FINDING_RE = /^ ?[-*+]\\s+\\[([ xX])\\]\\s+(.+)$/\n\nexport const REVIEW_STATUSES = [\n 'clean', 'fixed', 'unresolved', 'review-failed',\n] as const\n\nexport type ReviewStatus = typeof REVIEW_STATUSES[number]\n\nexport type Finding = {\n summary:string\n detail:string\n checked:boolean\n}\n\nexport type FixRecord = {\n attempts:number\n maxAttempts:number\n effort?:string\n exitCode:number|null\n result:string\n notes:string|null\n}\n\nexport type ReviewEntryInput = {\n from:string\n to:string\n status:ReviewStatus\n now:Date\n findings:Finding[]\n fix?:FixRecord\n}\n\nexport type ReviewEntry = {\n from:string\n to:string\n status:ReviewStatus\n findings:Finding[]\n body:string\n}\n\nexport type ReviewOutcome = {\n status:ReviewStatus|'skipped'\n findings:number\n fixed:number\n}\n\nfunction isReviewStatus (value:string):value is ReviewStatus {\n return (REVIEW_STATUSES as readonly string[]).includes(value)\n}\n\nfunction collapse (s:string):string {\n return s.replace(/\\s+/g, ' ').trim()\n}\n\nfunction normalize (s:string):string {\n return collapse(s).toLowerCase()\n}\n\nfunction sanitize (text:string):string {\n return text.replace(\n new RegExp(SENTINEL_PREFIX, 'g'),\n '<!-- nightralph-review'\n )\n}\n\nfunction splitLines (text:string):string[] {\n return text.split('\\n').map(line => {\n return line.endsWith('\\r') ? line.slice(0, -1) : line\n })\n}\n\n// The reviewer's checklist: `- [ ] summary` lines, each followed\n// by its detail lines. [] for an explicit \"No findings.\", null\n// when the text is neither, which fails the review attempt.\nexport function parseFindings (text:string):Finding[]|null {\n const findings:Finding[] = []\n const details:string[][] = []\n for (const line of splitLines(text)) {\n const m = line.match(FINDING_RE)\n if (m) {\n findings.push({\n summary:collapse(m[2]),\n detail:'',\n checked:m[1] !== ' ',\n })\n details.push([])\n } else if (findings.length > 0 && line.trim()) {\n details[details.length - 1].push(line.trim())\n }\n }\n if (findings.length > 0) {\n return findings.map((f, i) => {\n return { ...f, detail:details[i].join('\\n') }\n })\n }\n const clean = splitLines(text).some(line => {\n return normalize(line) === NO_FINDINGS.toLowerCase()\n })\n return clean ? [] : null\n}\n\n// Checks off the findings the fixer echoed as `[x] <summary>`,\n// compared loosely like ticket checklist items.\nexport function markFixed (\n findings:ReadonlyArray<Finding>,\n completedItems:ReadonlyArray<string>\n):Finding[] {\n const done = new Set(completedItems.map(normalize))\n return findings.map(f => {\n return { ...f, checked:done.has(normalize(f.summary)) }\n })\n}\n\nfunction headline (input:ReviewEntryInput):string {\n const n = input.findings.length\n const fixed = input.findings.filter(f => f.checked).length\n switch (input.status) {\n case 'clean': return 'no findings'\n case 'fixed': return `${fixed} of ${n} fixed`\n case 'unresolved':\n return `${n} finding${n === 1 ? '' : 's'}, fix failed`\n case 'review-failed': return 'review failed'\n default: {\n const _exhaustive:never = input.status\n return _exhaustive\n }\n }\n}\n\nfunction renderFindings (input:ReviewEntryInput):string[] {\n if (input.status === 'review-failed') {\n return ['The reviewer left no usable findings file.']\n }\n if (input.findings.length === 0) return [NO_FINDINGS]\n return input.findings.flatMap(f => {\n const box = f.checked ? '[x]' : '[ ]'\n const detail = f.detail ?\n f.detail.split('\\n').map(l => ` ${sanitize(l)}`) :\n []\n return [`- ${box} ${sanitize(collapse(f.summary))}`, ...detail]\n })\n}\n\nfunction renderFix (fix?:FixRecord):string[] {\n if (!fix) return []\n return [\n '',\n FIX_HEADING,\n `- attempts: ${fix.attempts} of ${fix.maxAttempts}`,\n ...(fix.effort ? [`- effort: ${fix.effort}`] : []),\n `- agent exit code: ${fix.exitCode ?? 'none'}`,\n `- result: ${sanitize(fix.result)}`,\n '',\n fix.notes ? sanitize(fix.notes) : 'The fixer left no notes.',\n ]\n}\n\nexport function renderReviewEntry (input:ReviewEntryInput):string {\n return [\n `${SENTINEL_PREFIX} from=${input.from} to=${input.to} ` +\n `status=${input.status} -->`,\n `## ${formatDate(input.now)} -- review ` +\n `${input.from.slice(0, 7)}..${input.to.slice(0, 7)} ` +\n `-- ${headline(input)}`,\n ...renderFindings(input),\n ...renderFix(input.fix),\n '---',\n '',\n ].join('\\n')\n}\n\nfunction parseEntry (\n sentinel:string,\n bodyLines:string[]\n):ReviewEntry|null {\n const attrs:Record<string, string> = {}\n for (const part of sentinel.split(/\\s+/).filter(Boolean)) {\n const idx = part.indexOf('=')\n if (idx > 0) attrs[part.slice(0, idx)] = part.slice(idx + 1)\n }\n const { from, to, status } = attrs\n if (!from || !SHA_RE.test(from)) return null\n if (!to || !SHA_RE.test(to)) return null\n if (!status || !isReviewStatus(status)) return null\n\n const lines = [...bodyLines]\n while (lines.length > 0 && !lines[lines.length - 1].trim()) {\n lines.pop()\n }\n if (lines[lines.length - 1] === '---') lines.pop()\n const fixAt = lines.indexOf(FIX_HEADING)\n const findingLines = fixAt === -1 ? lines : lines.slice(0, fixAt)\n return {\n from,\n to,\n status,\n findings:parseFindings(findingLines.join('\\n')) ?? [],\n body:lines.join('\\n').trim(),\n }\n}\n\n// Malformed entries are dropped, not thrown; text before the\n// first sentinel is ignored.\nexport function parseReviewEntries (text:string):ReviewEntry[] {\n const entries:ReviewEntry[] = []\n let sentinel:string|null = null\n let body:string[] = []\n const flush = ():void => {\n if (sentinel === null) return\n const entry = parseEntry(sentinel, body)\n if (entry) entries.push(entry)\n }\n for (const line of splitLines(text)) {\n const m = line.match(SENTINEL_RE)\n if (m) {\n flush()\n sentinel = m[1]\n body = []\n } else if (sentinel !== null) {\n body.push(line)\n }\n }\n flush()\n return entries\n}\n\n// Where the review range starts: the last reviewed commit while\n// it is still on this branch, else the fork point from the\n// default branch, else the commit the run started from.\nexport function selectReviewBase (\n entries:ReadonlyArray<ReviewEntry>,\n opts:{\n isAncestor:(sha:string) => boolean\n forkPoint:string|null\n launchCommit:string\n }\n):string {\n const last = [...entries].reverse().find(e => {\n return e.status !== 'review-failed'\n })\n if (last && opts.isAncestor(last.to)) return last.to\n return opts.forkPoint ?? opts.launchCommit\n}\n\n// One line for the finish summary; null when no review ran.\nexport function renderReviewSummary (\n outcome:ReviewOutcome,\n reviewPath:string\n):string|null {\n const see = ` -- see ${reviewPath}`\n const plural = outcome.findings === 1 ? '' : 's'\n switch (outcome.status) {\n case 'skipped': return null\n case 'clean': return `Review: no findings${see}`\n case 'fixed':\n return `Review: ${outcome.findings} finding${plural}, ` +\n `${outcome.fixed} fixed${see}`\n case 'unresolved':\n return `Review: ${outcome.findings} finding${plural}, ` +\n `fix failed${see}`\n case 'review-failed':\n return `Review: the reviewer failed${see}`\n default: {\n const _exhaustive:never = outcome.status\n return _exhaustive\n }\n }\n}\n\nexport type SpawnAgentFn = (opts:{\n prompt:string\n agentCmd:string\n model:string|null\n timeout:number\n logPath:string\n cwd?:string\n label?:string\n maxTurns?:number\n effort?:EffortLevel\n // claude only: model for subagents; unset = off\n preferSubagent?:string\n onLine?:(line:string) => void\n}) => Promise<AgentResult>\n\nexport type ReviewDeps = {\n spawnAgent:SpawnAgentFn\n // running child pids, so SIGINT also kills installs and tests\n registry?:Set<number>\n}\n\nexport type ReviewPassOpts = {\n repoRoot:string\n repoName:string\n // the branch the run merged into; the fix merges here too\n baseBranch:string\n reviewPath:string\n // the default branch, when baseBranch is an integration branch\n forkedFrom?:string\n launchCommit:string\n specBody:string\n agentCmd:string\n model:string|null\n timeout:number\n logsDir:string\n testCmd:string\n installCmd?:string\n maxTurns?:number\n effort?:EffortLevel\n // claude only: model for subagents; unset = off\n preferSubagent?:string\n retries:number\n retryDelay:number\n log:(msg:string) => void\n now?:() => Date\n}\n\n// Same naming as ticket logs: the first attempt keeps the plain\n// name, later attempts get their own file.\nfunction logPath (\n logsDir:string,\n stem:string,\n attempt:number,\n ext:string\n):string {\n const suffix = attempt > 1 ? `.attempt${attempt}` : ''\n return join(logsDir, `${stem}${suffix}${ext}`)\n}\n\n// Runs a shell step (install or tests) in a worktree, writing its\n// output to `path`; true only on exit 0.\nasync function runStep (\n opts:ReviewPassOpts,\n deps:ReviewDeps,\n cmd:string,\n cwd:string,\n path:string\n):Promise<boolean> {\n const result = await runTestCmd({\n cmd,\n cwd,\n timeout:opts.timeout,\n registry:deps.registry,\n })\n writeFileSync(path, result.output)\n return result.exitCode === 0\n}\n\nasync function discardWorktree (\n opts:ReviewPassOpts,\n wt:{ worktreePath:string; branchName:string }\n):Promise<void> {\n try {\n await removeWorktree(wt.worktreePath, { force:true })\n await deleteBranch(opts.repoRoot, wt.branchName)\n } catch (err) {\n opts.log(\n ` Failed to remove ${wt.worktreePath}: ` +\n (err instanceof Error ? err.message : String(err))\n )\n }\n}\n\n// Asks an agent to review from..to and returns its findings:\n// [] when clean, null when no attempt produced a usable findings\n// file. The worktree and anything the reviewer changed in it are\n// always discarded.\nexport async function runReviewer (\n opts:ReviewPassOpts,\n deps:ReviewDeps,\n range:{ from:string; to:string }\n):Promise<Finding[]|null> {\n return runWithRetries({\n maxAttempts:opts.retries + 1,\n retryDelay:opts.retryDelay,\n agentCmd:opts.agentCmd,\n effort:opts.effort,\n label:'review',\n log:opts.log,\n }, async (attempt, effort) => {\n const wt = await createNamedWorktree({\n repoRoot:opts.repoRoot,\n repoName:opts.repoName,\n baseBranch:opts.baseBranch,\n name:'review',\n })\n try {\n if (opts.installCmd && !(await runStep(\n opts, deps, opts.installCmd, wt.worktreePath,\n logPath(opts.logsDir, 'review', attempt, '.install.log')\n ))) {\n opts.log(' review: install failed')\n return { result:null, stop:false }\n }\n ensureNotesIgnored(wt.worktreePath)\n await deps.spawnAgent({\n prompt:renderReviewPrompt({\n specBody:opts.specBody,\n ...range,\n exclude:reviewExcludeFor(\n opts.repoRoot, opts.reviewPath\n ),\n preferSubagent:opts.preferSubagent,\n }),\n agentCmd:opts.agentCmd,\n model:opts.model,\n timeout:opts.timeout,\n logPath:logPath(opts.logsDir, 'review', attempt, '.log'),\n cwd:wt.worktreePath,\n label:'review',\n maxTurns:opts.maxTurns,\n effort,\n preferSubagent:opts.preferSubagent,\n onLine:opts.log,\n })\n const text = collectNotes(wt.worktreePath, FINDINGS_FILE)\n const findings = text === null ? null : parseFindings(text)\n if (findings === null) {\n opts.log(' review: no usable findings file')\n }\n return { result:findings, stop:findings !== null }\n } finally {\n await discardWorktree(opts, wt)\n }\n })\n}\n\nexport type FixResult = {\n merged:boolean\n // checked per the fixer's [x] lines when merged; all\n // unchecked otherwise\n findings:Finding[]\n attempts:number\n effort?:EffortLevel\n exitCode:number|null\n result:string\n notes:string|null\n}\n\n// Asks an agent to fix the findings in a fresh worktree off the\n// branch tip. Its work merges only after a green test gate;\n// every failure is retried like a ticket.\nexport async function runFixer (\n opts:ReviewPassOpts,\n deps:ReviewDeps,\n findings:ReadonlyArray<Finding>\n):Promise<FixResult> {\n const unchecked = findings.map(f => ({ ...f, checked:false }))\n let previous:{ reason:FixFailure; notes:string|null }|undefined\n\n return runWithRetries<FixResult>({\n maxAttempts:opts.retries + 1,\n retryDelay:opts.retryDelay,\n agentCmd:opts.agentCmd,\n effort:opts.effort,\n label:'review fix',\n log:opts.log,\n }, async (attempt, effort) => {\n const fail = (\n reason:FixFailure,\n exitCode:number|null,\n notes:string|null\n ) => {\n previous = { reason, notes }\n opts.log(` review fix: ${FIX_FAILURE_TEXT[reason]}`)\n return {\n result:{\n merged:false,\n findings:unchecked,\n attempts:attempt,\n effort,\n exitCode,\n result:FIX_FAILURE_TEXT[reason],\n notes,\n },\n stop:false,\n }\n }\n\n const wt = await createNamedWorktree({\n repoRoot:opts.repoRoot,\n repoName:opts.repoName,\n baseBranch:opts.baseBranch,\n name:'review-fix',\n })\n try {\n if (opts.installCmd && !(await runStep(\n opts, deps, opts.installCmd, wt.worktreePath,\n logPath(\n opts.logsDir, 'review-fix', attempt,\n '.install.log'\n )\n ))) {\n return fail('install', null, null)\n }\n ensureNotesIgnored(wt.worktreePath)\n const agent = await deps.spawnAgent({\n prompt:renderFixPrompt({\n specBody:opts.specBody,\n findings,\n testCmd:opts.testCmd,\n installCmd:opts.installCmd,\n previous,\n preferSubagent:opts.preferSubagent,\n }),\n agentCmd:opts.agentCmd,\n model:opts.model,\n timeout:opts.timeout,\n logPath:logPath(\n opts.logsDir, 'review-fix', attempt, '.log'\n ),\n cwd:wt.worktreePath,\n label:'review-fix',\n maxTurns:opts.maxTurns,\n effort,\n preferSubagent:opts.preferSubagent,\n onLine:opts.log,\n })\n // Before any auto-commit, so notes never get committed\n const notes = collectNotes(wt.worktreePath)\n\n if (!agent.timedOut && agent.exitCode !== 0) {\n return fail('exit', agent.exitCode, notes)\n }\n if (isDirty(wt.worktreePath)) {\n await commitDirty(\n wt.worktreePath,\n 'nightralph: auto-commit review fix ' +\n 'remaining changes'\n )\n }\n if (!hasNewCommits(\n opts.repoRoot, opts.baseBranch, wt.branchName\n )) {\n return fail('no-commits', agent.exitCode, notes)\n }\n if (opts.testCmd === '') {\n opts.log(' review fix: no test gate')\n } else if (!(await runStep(\n opts, deps, opts.testCmd, wt.worktreePath,\n logPath(\n opts.logsDir, 'review-fix', attempt, '.test.log'\n )\n ))) {\n return fail('tests', agent.exitCode, notes)\n }\n const merged = await mergeBranch(\n opts.repoRoot, wt.branchName\n )\n if (!merged.success) {\n return fail(\n 'merge',\n agent.exitCode,\n [notes, merged.stderr].filter(Boolean)\n .join('\\n\\n') || null\n )\n }\n opts.log(' review fix: merged')\n return {\n result:{\n merged:true,\n findings:markFixed(findings, agent.completedItems),\n attempts:attempt,\n effort,\n exitCode:agent.exitCode,\n result:'merged',\n notes,\n },\n stop:true,\n }\n } finally {\n await discardWorktree(opts, wt)\n }\n })\n}\n\nexport function readReviewEntries (path:string):ReviewEntry[] {\n if (!existsSync(path)) return []\n return parseReviewEntries(readFileSync(path, 'utf8'))\n}\n\n// Reviews everything since the last reviewed commit, runs one fix\n// pass when there are findings, and appends and commits one entry\n// to the review log. Skips entirely when the range only touches\n// bookkeeping under REVIEW_EXCLUDE.\nexport async function runReviewPass (\n opts:ReviewPassOpts,\n deps:ReviewDeps\n):Promise<ReviewOutcome> {\n const now = opts.now ?? (() => new Date())\n const to = headCommit(opts.repoRoot)\n const forkPoint = opts.forkedFrom ?\n mergeBase(opts.repoRoot, opts.forkedFrom, to) :\n null\n const from = selectReviewBase(readReviewEntries(opts.reviewPath), {\n isAncestor:sha => isAncestor(opts.repoRoot, sha, to),\n forkPoint,\n launchCommit:opts.launchCommit,\n })\n const exclude = reviewExcludeFor(opts.repoRoot, opts.reviewPath)\n if (!hasChangesOutside(opts.repoRoot, from, to, exclude)) {\n opts.log('\\nReview: nothing new to review')\n return { status:'skipped', findings:0, fixed:0 }\n }\n\n opts.log(`\\nReviewing ${from.slice(0, 7)}..${to.slice(0, 7)}`)\n const findings = await runReviewer(opts, deps, { from, to })\n\n let entry:ReviewEntryInput\n if (findings === null) {\n entry = {\n from,\n to,\n status:'review-failed',\n now:now(),\n findings:[]\n }\n } else if (findings.length === 0) {\n entry = { from, to, status:'clean', now:now(), findings:[] }\n } else {\n opts.log(\n `Review: ${findings.length} finding` +\n `${findings.length === 1 ? '' : 's'}`\n )\n const fix = await runFixer(opts, deps, findings)\n entry = {\n from,\n // A merged fix counts as reviewed\n to:fix.merged ? headCommit(opts.repoRoot) : to,\n status:fix.merged ? 'fixed' : 'unresolved',\n now:now(),\n findings:fix.findings,\n fix:{\n attempts:fix.attempts,\n maxAttempts:opts.retries + 1,\n effort:fix.effort,\n exitCode:fix.exitCode,\n result:fix.result,\n notes:fix.notes,\n },\n }\n }\n\n appendEntry(opts.reviewPath, renderReviewEntry(entry))\n await commitProgress(\n opts.repoRoot, opts.reviewPath, 'nightralph: record review'\n )\n return {\n status:entry.status,\n findings:entry.findings.length,\n fixed:entry.findings.filter(f => f.checked).length,\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["findingLines"]
7
+ }
package/dist/rules.js ADDED
@@ -0,0 +1,67 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+ import { NOTES_PATH } from "./learnings.js";
4
+ function readDocsRule(subject) {
5
+ return `- Read the files directly relevant to the ${subject} before writing code. Also read any AGENTS.md in or above the directories you will touch, plus CONTEXT.md and the relevant docs/adr/ entries if they exist. If they do not exist, proceed silently.`;
6
+ }
7
+ __name(readDocsRule, "readDocsRule");
8
+ function installRule(installCmd) {
9
+ if (!installCmd) return [];
10
+ return [
11
+ `- Dependencies are already installed with \`${installCmd}\`. Do NOT reinstall them with any other command; to reinstall, run that same command. To add a dependency, use the package manager that command uses, so its lockfile stays in sync.`
12
+ ];
13
+ }
14
+ __name(installRule, "installRule");
15
+ function subagentRule(preferSubagent, readOnly = false) {
16
+ if (!preferSubagent) return [];
17
+ return [
18
+ `- Hand read-only work to subagents: exploring the codebase, searching code, and experiments or test runs whose output would flood your context. Subagents default to the ${preferSubagent} model.`,
19
+ "- A subagent starts with no context. Brief each one with the objective, what you already know, its boundaries, and the reply you expect (file:line references, or pass/fail with the relevant output).",
20
+ ...readOnly ? [] : [
21
+ "- Subagents never edit files or run git commands that write. You make every edit and every commit."
22
+ ],
23
+ "- Do not delegate a lookup that takes a single tool call."
24
+ ];
25
+ }
26
+ __name(subagentRule, "subagentRule");
27
+ function testGateRule(testCmd, subject) {
28
+ if (testCmd === "") {
29
+ return `- Before each commit, run the tests relevant to what you changed. Do not commit with failing tests. The orchestrator runs no test command for this ${subject}.`;
30
+ }
31
+ return `- While iterating, run only the tests relevant to what you changed. Run \`${testCmd}\` in full once, as the final gate before your last commit. Do not commit with failing tests. The orchestrator runs the same command after you exit and rejects the ${subject} if it fails.`;
32
+ }
33
+ __name(testGateRule, "testGateRule");
34
+ const LINT_RULE = "- If the project has a lint script, run it and fix what it reports.";
35
+ function integrityRule(unit) {
36
+ return `- INTEGRITY: never delete, skip, weaken, or write tautological tests to force a green result. Only report a ${unit} done when its real tests genuinely pass. If an item cannot be completed honestly, leave it unchecked and record what blocked you in your notes file.`;
37
+ }
38
+ __name(integrityRule, "integrityRule");
39
+ const CONFINEMENT_RULES = [
40
+ "- Do NOT read or modify files outside your current directory (the worktree).",
41
+ "- Do NOT run git push."
42
+ ];
43
+ function commitRule(unit) {
44
+ return `- Commit after each ${unit}, and make sure everything is committed when finished, except your notes file.`;
45
+ }
46
+ __name(commitRule, "commitRule");
47
+ function notesRule(unit, subject) {
48
+ return `- Keep notes for this attempt in \`${NOTES_PATH}\` (create the directory). Update it after each ${unit} and as soon as you hit a blocker, not only at the end. Record what you implemented, files changed, how you read any vague acceptance criteria, what blocked you, and anything the next attempt at this ${subject} should know. Never commit, git add, or delete this file; the orchestrator collects it.`;
49
+ }
50
+ __name(notesRule, "notesRule");
51
+ function agentsMdRule(subject) {
52
+ return `- Before your final commit, record reusable learnings in the nearest AGENTS.md (in or above the directories you modified; prefer that over the repo root). Good additions: module conventions, gotchas, files that must change together, how to test that area, environment requirements. Do NOT add ${subject}-specific details or temporary debugging notes. Only write when you have knowledge that would help future work in that directory. Reusable patterns go in AGENTS.md; anything specific to this ${subject} goes in your notes file.`;
53
+ }
54
+ __name(agentsMdRule, "agentsMdRule");
55
+ export {
56
+ CONFINEMENT_RULES,
57
+ LINT_RULE,
58
+ agentsMdRule,
59
+ commitRule,
60
+ installRule,
61
+ integrityRule,
62
+ notesRule,
63
+ readDocsRule,
64
+ subagentRule,
65
+ testGateRule
66
+ };
67
+ //# sourceMappingURL=rules.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/rules.ts"],
4
+ "sourcesContent": ["import { NOTES_PATH } from './learnings.js'\n\n// Rule lines shared by the agent prompts. `unit` is what the agent\n// commits one at a time (\"checklist item\", \"finding\"); `subject` is\n// what the orchestrator accepts or rejects (\"ticket\", \"fix\").\n\nexport function readDocsRule (subject:string):string {\n return '- Read the files directly relevant to the ' +\n `${subject} before writing code. Also read any ` +\n 'AGENTS.md in or above the directories you ' +\n 'will touch, plus CONTEXT.md and the ' +\n 'relevant docs/adr/ entries if they exist. ' +\n 'If they do not exist, proceed silently.'\n}\n\n// The rule for agents in a worktree the orchestrator has\n// already installed dependencies into. Empty without one.\nexport function installRule (installCmd?:string):string[] {\n if (!installCmd) return []\n return [\n '- Dependencies are already installed with ' +\n `\\`${installCmd}\\`. Do NOT reinstall them ` +\n 'with any other command; to reinstall, run ' +\n 'that same command. To add a dependency, use ' +\n 'the package manager that command uses, so ' +\n 'its lockfile stays in sync.',\n ]\n}\n\n// The delegation rule for agents whose subagents default to a\n// cheaper model (CLAUDE_CODE_SUBAGENT_MODEL). Empty without one.\n// `readOnly` drops the edit/commit line, for the reviewer, which\n// never edits.\nexport function subagentRule (\n preferSubagent?:string,\n readOnly = false\n):string[] {\n if (!preferSubagent) return []\n return [\n '- Hand read-only work to subagents: exploring the ' +\n 'codebase, searching code, and experiments or test ' +\n 'runs whose output would flood your context. ' +\n `Subagents default to the ${preferSubagent} model.`,\n '- A subagent starts with no context. Brief each one ' +\n 'with the objective, what you already know, its ' +\n 'boundaries, and the reply you expect (file:line ' +\n 'references, or pass/fail with the relevant output).',\n ...(readOnly ? [] : [\n '- Subagents never edit files or run git commands ' +\n 'that write. You make every edit and every commit.',\n ]),\n '- Do not delegate a lookup that takes a single tool call.',\n ]\n}\n\nexport function testGateRule (testCmd:string, subject:string):string {\n if (testCmd === '') {\n return '- Before each commit, run the tests relevant ' +\n 'to what you changed. Do not commit with failing ' +\n 'tests. The orchestrator runs no test command ' +\n `for this ${subject}.`\n }\n return '- While iterating, run only the tests ' +\n 'relevant to what you changed. Run ' +\n `\\`${testCmd}\\` in full once, as the ` +\n 'final gate before your last commit. Do ' +\n 'not commit with failing tests. The ' +\n 'orchestrator runs the same command after ' +\n `you exit and rejects the ${subject} if it ` +\n 'fails.'\n}\n\nexport const LINT_RULE = '- If the project has a lint script, run it ' +\n 'and fix what it reports.'\n\nexport function integrityRule (unit:string):string {\n return '- INTEGRITY: never delete, skip, weaken, or ' +\n 'write tautological tests to force a green ' +\n `result. Only report a ${unit} done ` +\n 'when its real tests genuinely pass. If an ' +\n 'item cannot be completed honestly, leave ' +\n 'it unchecked and record what blocked you ' +\n 'in your notes file.'\n}\n\nexport const CONFINEMENT_RULES:string[] = [\n '- Do NOT read or modify files outside ' +\n 'your current directory (the worktree).',\n '- Do NOT run git push.',\n]\n\nexport function commitRule (unit:string):string {\n return `- Commit after each ${unit}, and make ` +\n 'sure everything is committed when finished, ' +\n 'except your notes file.'\n}\n\nexport function notesRule (unit:string, subject:string):string {\n return `- Keep notes for this attempt in \\`${NOTES_PATH}\\` ` +\n '(create the directory). Update it after each ' +\n `${unit} and as soon as you hit a ` +\n 'blocker, not only at the end. Record what you ' +\n 'implemented, files changed, how you read any ' +\n 'vague acceptance criteria, what blocked you, and ' +\n `anything the next attempt at this ${subject} should ` +\n 'know. Never commit, git add, or delete this ' +\n 'file; the orchestrator collects it.'\n}\n\nexport function agentsMdRule (subject:string):string {\n return '- Before your final commit, record reusable ' +\n 'learnings in the nearest AGENTS.md (in or ' +\n 'above the directories you modified; ' +\n 'prefer that over the repo root). Good ' +\n 'additions: module conventions, gotchas, ' +\n 'files that must change together, how to ' +\n 'test that area, environment requirements. ' +\n `Do NOT add ${subject}-specific details or ` +\n 'temporary debugging notes. Only write ' +\n 'when you have knowledge that would help ' +\n 'future work in that directory. Reusable ' +\n 'patterns go in AGENTS.md; anything ' +\n `specific to this ${subject} goes in your ` +\n 'notes file.'\n}\n"],
5
+ "mappings": ";;AAAA,SAAS,kBAAkB;AAMpB,SAAS,aAAc,SAAuB;AACjD,SAAO,6CACA,OAAO;AAKlB;AAPgB;AAWT,SAAS,YAAa,YAA6B;AACtD,MAAI,CAAC,WAAY,QAAO,CAAC;AACzB,SAAO;AAAA,IACH,+CACS,UAAU;AAAA,EAKvB;AACJ;AAVgB;AAgBT,SAAS,aACZ,gBACA,WAAW,OACJ;AACP,MAAI,CAAC,eAAgB,QAAO,CAAC;AAC7B,SAAO;AAAA,IACH,4KAGgC,cAAc;AAAA,IAC9C;AAAA,IAIA,GAAI,WAAW,CAAC,IAAI;AAAA,MAChB;AAAA,IAEJ;AAAA,IACA;AAAA,EACJ;AACJ;AApBgB;AAsBT,SAAS,aAAc,SAAgB,SAAuB;AACjE,MAAI,YAAY,IAAI;AAChB,WAAO,sJAGS,OAAO;AAAA,EAC3B;AACA,SAAO,6EAEE,OAAO,uKAIgB,OAAO;AAE3C;AAfgB;AAiBT,MAAM,YAAY;AAGlB,SAAS,cAAe,MAAoB;AAC/C,SAAO,+GAEsB,IAAI;AAKrC;AARgB;AAUT,MAAM,oBAA6B;AAAA,EACtC;AAAA,EAEA;AACJ;AAEO,SAAS,WAAY,MAAoB;AAC5C,SAAO,uBAAuB,IAAI;AAGtC;AAJgB;AAMT,SAAS,UAAW,MAAa,SAAuB;AAC3D,SAAO,sCAAsC,UAAU,mDAEhD,IAAI,2MAI8B,OAAO;AAGpD;AAVgB;AAYT,SAAS,aAAc,SAAuB;AACjD,SAAO,wSAOW,OAAO,kMAKD,OAAO;AAEnC;AAfgB;",
6
+ "names": []
7
+ }
@@ -44,7 +44,7 @@ Give each ticket its **blocking edges**: the other tickets that must complete be
44
44
  Present the proposed breakdown as a numbered list. For each ticket, show:
45
45
 
46
46
  - **Title**: short descriptive name
47
- - **Blocked by**: which other tickets (if any) must complete first
47
+ - **Blocked by**: which other tickets (if any) must complete first, by number, or `<feature> <NN>` for a ticket in another feature
48
48
  - **What it delivers**: the end-to-end behaviour this ticket makes work
49
49
 
50
50
  Ask the user:
@@ -59,10 +59,10 @@ Iterate until the user approves the breakdown.
59
59
 
60
60
  Publish the approved tickets. **How** depends on the tracker `npx nightralph init` configured; the tickets are the same either way, only the shape of the blocking edges changes:
61
61
 
62
- - **Local files** → write one file per ticket under `.scratch/<feature-slug>/issues/<NN>-<slug>.md`, numbered from `01` in dependency order (blockers first). Each file's "Blocked by" lists the numbers/titles it depends on. Use the per-ticket file template below: one ticket per file, never a single combined file.
62
+ - **Local files** -> write one file per ticket under `.scratch/<feature-slug>/issues/<NN>-<slug>.md`, numbered from `01` in dependency order (blockers first). Each file's "Blocked by" lists the numbers of the tickets it depends on. A blocker in another feature is written `<feature> <NN>`, where `<feature>` is that feature's `.scratch/<feature>/` directory name (a path such as `rollout/billing` for a nested feature), repeated before each of its numbers (`newsletter-audit 15, newsletter-audit 16`). List every number: `03-05` means 3 and 5, not 3 through 5, and a feature name before it applies to both. A note in parentheses is ignored. The list may wrap onto the next lines; it ends at a blank line, the next `**Field:**` line, a heading or a checklist item. Use the per-ticket file template below: one ticket per file, never a single combined file.
63
63
  - Set every local ticket to `**Status:** ready-for-agent` so nightralph can
64
64
  run it immediately. Do not publish local tickets as `needs-triage`.
65
- - **A real issue tracker (GitHub, Linear, …)** → publish one issue per ticket in dependency order (blockers first) so each ticket's blocking edges can reference real identifiers. Use the platform's native blocking / sub-issue relationship where it has one; otherwise set each ticket's "Blocked by" to the blocking issues. Apply the `ready-for-agent` triage label unless instructed otherwise; the tickets are agent-grabbable by construction.
65
+ - **A real issue tracker (GitHub, Linear, …)** -> publish one issue per ticket in dependency order (blockers first) so each ticket's blocking edges can reference real identifiers. Use the platform's native blocking / sub-issue relationship where it has one; otherwise set each ticket's "Blocked by" to the blocking issues. Apply the `ready-for-agent` triage label unless instructed otherwise; the tickets are agent-grabbable by construction.
66
66
 
67
67
  Work the **frontier**: any ticket whose blockers are all done. For a purely linear chain that means top to bottom.
68
68
 
@@ -74,7 +74,7 @@ Do NOT close or modify any parent issue.
74
74
 
75
75
  **What to build:** the end-to-end behaviour this ticket makes work, from the user's perspective, not a layer-by-layer implementation list.
76
76
 
77
- **Blocked by:** the numbers/titles of the tickets that gate this one, or "None (can start immediately)".
77
+ **Blocked by:** the numbers of the tickets that gate this one, and `<feature> <NN>` for one in another feature, each optionally followed by a note in parentheses; or "None (can start immediately)".
78
78
 
79
79
  **Status:** ready-for-agent
80
80
 
@@ -100,7 +100,7 @@ The end-to-end behaviour this ticket makes work, from the user's perspective, no
100
100
 
101
101
  ## Blocked by
102
102
 
103
- - A reference to each blocking ticket, or "None (can start immediately)".
103
+ - The tracker identifier of each blocking ticket, or "None (can start immediately)".
104
104
 
105
105
  </issue-template>
106
106
 
@@ -0,0 +1,45 @@
1
+ export type RawRef = {
2
+ feature?: string;
3
+ num: number;
4
+ };
5
+ export type BlockerParse = {
6
+ refs: RawRef[];
7
+ typos: string[];
8
+ };
9
+ /**
10
+ * Reads the **Blocked by:** field of a ticket body. A number
11
+ * belongs to the feature named by the word directly before it
12
+ * when that word is in `knownFeatures` (the names of
13
+ * `.scratch/<name>/issues` dirs, `<name>` possibly nested, as in
14
+ * `rollout/billing`); otherwise to the ticket's own feature.
15
+ * Feature-shaped words that name no feature are returned as
16
+ * `typos`.
17
+ */
18
+ export declare function parseBlockerField(body: string, knownFeatures: ReadonlySet<string>): BlockerParse;
19
+ export type BlockerWhere = 'run' | 'done' | 'pending' | 'missing';
20
+ export type BlockerRef = {
21
+ feature: string;
22
+ num: number;
23
+ where: BlockerWhere;
24
+ status?: string;
25
+ };
26
+ /**
27
+ * Classifies raw refs. `selected` maps each feature in the run to
28
+ * its ticket numbers; `lookup` returns the status of a ticket in a
29
+ * feature outside the run, or undefined when there is none.
30
+ * De-duplicates by (feature, num) after the own feature is filled
31
+ * in.
32
+ */
33
+ export declare function resolveBlockers(raw: ReadonlyArray<RawRef>, ownFeature: string, selected: ReadonlyMap<string, ReadonlySet<number>>, lookup: (feature: string, num: number) => string | undefined): BlockerRef[];
34
+ /**
35
+ * Refs for a bare scan, before the run is known: every ref is
36
+ * `run`, so one that matches no ticket in the list is never
37
+ * satisfied.
38
+ */
39
+ export declare function provisionalRefs(raw: ReadonlyArray<RawRef>, ownFeature: string): BlockerRef[];
40
+ export type BlockerWarning = {
41
+ ticket: string;
42
+ kind: 'missing' | 'typo';
43
+ text: string;
44
+ };
45
+ //# sourceMappingURL=blockers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blockers.d.ts","sourceRoot":"","sources":["../../src/blockers.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,MAAM,GAAG;IACjB,OAAO,CAAC,EAAC,MAAM,CAAC;IAChB,GAAG,EAAC,MAAM,CAAC;CACd,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACvB,IAAI,EAAC,MAAM,EAAE,CAAC;IACd,KAAK,EAAC,MAAM,EAAE,CAAC;CAClB,CAAA;AA8FD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC7B,IAAI,EAAC,MAAM,EACX,aAAa,EAAC,WAAW,CAAC,MAAM,CAAC,GACnC,YAAY,CAkCb;AAED,MAAM,MAAM,YAAY,GAAG,KAAK,GAAC,MAAM,GAAC,SAAS,GAAC,SAAS,CAAA;AAE3D,MAAM,MAAM,UAAU,GAAG;IACrB,OAAO,EAAC,MAAM,CAAC;IACf,GAAG,EAAC,MAAM,CAAC;IAKX,KAAK,EAAC,YAAY,CAAC;IACnB,MAAM,CAAC,EAAC,MAAM,CAAC;CAClB,CAAA;AAQD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC3B,GAAG,EAAC,aAAa,CAAC,MAAM,CAAC,EACzB,UAAU,EAAC,MAAM,EACjB,QAAQ,EAAC,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC,EACjD,MAAM,EAAC,CAAC,OAAO,EAAC,MAAM,EAAE,GAAG,EAAC,MAAM,KAAK,MAAM,GAAC,SAAS,GACzD,UAAU,EAAE,CA8Bb;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC3B,GAAG,EAAC,aAAa,CAAC,MAAM,CAAC,EACzB,UAAU,EAAC,MAAM,GACnB,UAAU,EAAE,CAWb;AAED,MAAM,MAAM,cAAc,GAAG;IACzB,MAAM,EAAC,MAAM,CAAC;IACd,IAAI,EAAC,SAAS,GAAC,MAAM,CAAC;IACtB,IAAI,EAAC,MAAM,CAAC;CACf,CAAA"}
@@ -1,3 +1,4 @@
1
+ import { type SubagentStats } from './stats.js';
1
2
  export type TicketStatus = 'pending' | 'in-progress' | 'awaiting-merge' | 'done' | 'failed' | 'blocked';
2
3
  export type DisplayTicket = {
3
4
  readonly num: number;
@@ -5,6 +6,7 @@ export type DisplayTicket = {
5
6
  readonly feature?: string;
6
7
  status: TicketStatus;
7
8
  blockedBy?: ReadonlyArray<string>;
9
+ stats?: SubagentStats;
8
10
  };
9
11
  export type TicketRef = {
10
12
  readonly num: number;
@@ -41,6 +43,11 @@ export type Display = {
41
43
  * of the upstream tickets that failed or need a human.
42
44
  */
43
45
  setBlocked(num: number, feature: string | undefined, blockedBy: ReadonlyArray<string>): void;
46
+ /**
47
+ * Subagent stats for a ticket's row: the running total of
48
+ * its attempts so far.
49
+ */
50
+ setTicketStats(num: number, feature: string | undefined, stats: SubagentStats): void;
44
51
  log(message: string): void;
45
52
  cleanup(): void;
46
53
  };
@@ -94,6 +101,7 @@ export declare class StatusDisplay implements Display {
94
101
  startWave(waveNum: number, totalWaves: number, tickets: ReadonlyArray<TicketRef>, _progress?: RunProgress): void;
95
102
  setTicketStatus(num: number, status: TicketStatus, feature?: string): void;
96
103
  setBlocked(num: number, feature: string | undefined, blockedBy: ReadonlyArray<string>): void;
104
+ setTicketStats(num: number, feature: string | undefined, stats: SubagentStats): void;
97
105
  log(message: string): void;
98
106
  cleanup(): void;
99
107
  private render;
@@ -103,6 +111,7 @@ export declare class PlainDisplay implements Display {
103
111
  startWave(waveNum: number, _totalWaves: number, tickets: ReadonlyArray<TicketRef>, progress?: RunProgress): void;
104
112
  setTicketStatus(_num: number, _status: TicketStatus, _feature?: string): void;
105
113
  setBlocked(_num: number, _feature: string | undefined, _blockedBy: ReadonlyArray<string>): void;
114
+ setTicketStats(_num: number, _feature: string | undefined, _stats: SubagentStats): void;
106
115
  log(message: string): void;
107
116
  cleanup(): void;
108
117
  }
@@ -1 +1 @@
1
- {"version":3,"file":"display.d.ts","sourceRoot":"","sources":["../../src/display.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"display.d.ts","sourceRoot":"","sources":["../../src/display.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAyBA,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAgB,MAAM,cAAc,CAAA;AAG9E,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AA6BA,OAAO,EACH,eAAe,EACf,gBAAgB,EAKnB,MAAM,cAAc,CAAA;AAarB,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,CAAA"}
@@ -0,0 +1,70 @@
1
+ export declare const MAX_OWN_ENTRIES = 4;
2
+ export declare const MAX_ENTRY_CHARS = 4000;
3
+ export declare const LOG_TAIL_LINES = 40;
4
+ export declare const NOTES_PATH = ".nightralph/notes.md";
5
+ export declare const TRUNCATED_MARKER = "[truncated]";
6
+ export declare const LEARNING_OUTCOMES: readonly ["done", "exit", "tests", "install", "no-commits", "error", "merge"];
7
+ export type LearningOutcome = typeof LEARNING_OUTCOMES[number];
8
+ export type LearningEntry = {
9
+ key: string;
10
+ attempt: number;
11
+ outcome: LearningOutcome;
12
+ timedOut: boolean;
13
+ body: string;
14
+ };
15
+ type EntryBase = {
16
+ key: string;
17
+ label: string;
18
+ attempt: number;
19
+ now: Date;
20
+ };
21
+ export type AgentRunRecord = {
22
+ exitCode: number;
23
+ completedItems: string[];
24
+ timedOut: boolean;
25
+ notes: string | null;
26
+ subagents?: number;
27
+ costUsd?: number;
28
+ };
29
+ type AgentFields = {
30
+ run: AgentRunRecord;
31
+ effort?: string;
32
+ };
33
+ type LogTail = {
34
+ logName: string;
35
+ logTail: string;
36
+ };
37
+ export type EntryInput = EntryBase & ((AgentFields & {
38
+ outcome: 'done' | 'exit' | 'no-commits';
39
+ }) | (AgentFields & LogTail & {
40
+ outcome: 'tests';
41
+ }) | (LogTail & {
42
+ outcome: 'install';
43
+ }) | {
44
+ outcome: 'error';
45
+ error: string;
46
+ } | {
47
+ outcome: 'merge';
48
+ stderr: string;
49
+ });
50
+ export type HistorySelection = {
51
+ own: LearningEntry[];
52
+ blockers: LearningEntry[];
53
+ };
54
+ export declare function formatDate(date: Date): string;
55
+ export declare function isLearningOutcome(value: string): value is LearningOutcome;
56
+ export declare function renderEntry(input: EntryInput): string;
57
+ export declare function parseEntries(text: string): LearningEntry[];
58
+ export declare function selectHistory(entries: LearningEntry[], opts: {
59
+ key: string;
60
+ blockerKeys: string[];
61
+ }): HistorySelection;
62
+ export declare function renderHistory(selection: HistorySelection): string;
63
+ export declare function tailLines(text: string, n: number): string;
64
+ export declare function learningsPathFor(progressPath: string): string;
65
+ export declare function readEntries(path: string): LearningEntry[];
66
+ export declare function appendEntry(path: string, text: string): void;
67
+ export declare function collectNotes(worktreePath: string, file?: string): string | null;
68
+ export declare function ensureNotesIgnored(worktreePath: string): void;
69
+ export {};
70
+ //# sourceMappingURL=learnings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"learnings.d.ts","sourceRoot":"","sources":["../../src/learnings.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,9 @@
1
1
  import { type MergeConflictStrategy } from './merge.js';
2
2
  import { type Display } from './display.js';
3
+ import { type SubagentStats } from './stats.js';
4
+ import { type EffortLevel } from './retry.js';
5
+ export { EFFORT_LADDERS, ALL_EFFORT_LEVELS, escalateEffort, retryDelayFor, type EffortLevel, type ProviderName, } from './retry.js';
6
+ import { type BlockerRef, type BlockerWarning } from './blockers.js';
3
7
  export type Ticket = {
4
8
  num: number;
5
9
  slug: string;
@@ -7,7 +11,7 @@ export type Ticket = {
7
11
  filepath: string;
8
12
  body: string;
9
13
  status: string;
10
- blockers: number[];
14
+ blockers: BlockerRef[];
11
15
  feature: string;
12
16
  };
13
17
  export declare function formatErrorMessage(error: unknown): string;
@@ -17,20 +21,38 @@ export declare function parseTicketFilename(filename: string): {
17
21
  slug: string;
18
22
  } | null;
19
23
  export declare function parseStatus(body: string): string;
20
- export declare function parseBlockers(body: string): number[];
21
- export declare function scanTickets(dir: string, feature?: string): Ticket[];
24
+ /**
25
+ * Reads every ticket file in `dir`. Blocker refs are provisional
26
+ * (every ref `where:'run'`) until `loadSelection` resolves them
27
+ * against the run; a word in `knownFeatures` before a number names
28
+ * that number's feature.
29
+ */
30
+ export declare function scanTickets(dir: string, feature?: string, knownFeatures?: ReadonlySet<string>): Ticket[];
22
31
  export declare function ticketKey(feature: string, num: number): string;
32
+ /**
33
+ * Whether one blocker ref lets its dependent run. `run` refs are
34
+ * decided by the live status of the ticket in this run; refs
35
+ * outside the run were classified at load time.
36
+ */
37
+ export declare function blockerSatisfied(ref: BlockerRef, doneKeys: ReadonlySet<string>): boolean;
38
+ /**
39
+ * Display name of a blocker: `<feature>/<NN>`, plus why it is dead
40
+ * when it is outside the run.
41
+ */
42
+ export declare function blockerLabel(ref: BlockerRef): string;
23
43
  export declare function findReadyTickets(tickets: ReadonlyArray<Ticket>): Ticket[];
24
44
  /**
25
- * Tickets that cannot run this session because a ticket they
26
- * depend on (directly or through a chain, within their own
27
- * feature) is dead: in `failedKeys`, or in a status that is
28
- * neither done nor ready-for-agent, e.g. ready-for-human.
29
- * Returns ticketKey -> dead upstream ticketKeys. Only
30
- * ready-for-agent tickets that have not failed are keys. A
31
- * blocker number with no matching ticket is not dead.
45
+ * Tickets that cannot run this session because a blocker they
46
+ * depend on, directly or through a chain of in-run refs (across
47
+ * features), is dead. Dead means a `pending` or `missing` ref, or
48
+ * an in-run ticket that is in `failedKeys` or whose status is
49
+ * neither done nor ready-for-agent, e.g. ready-for-human. A `done`
50
+ * ref, or a done in-run ticket, ends the walk. An in-run ref with
51
+ * no matching ticket (bare scans only) is not dead.
52
+ * Returns ticketKey -> dead upstream refs. Only ready-for-agent
53
+ * tickets that have not failed are keys.
32
54
  */
33
- export declare function findBlockedTickets(tickets: ReadonlyArray<Ticket>, failedKeys: ReadonlySet<string>): Map<string, Array<string>>;
55
+ export declare function findBlockedTickets(tickets: ReadonlyArray<Ticket>, failedKeys: ReadonlySet<string>): Map<string, BlockerRef[]>;
34
56
  export declare const COMPLETED_RE: RegExp;
35
57
  export declare const TOOL_USE_RE: RegExp;
36
58
  export declare function checkItems(ticket: Ticket, completedItems: string[]): void;
@@ -39,18 +61,15 @@ export declare function stripSpecSection(specBody: string, heading: string): str
39
61
  export declare function lookupSpec(specBodies: Map<string, string> | undefined, fallback: string, feature: string): string;
40
62
  export declare function renderPrompt(specBody: string, ticket: Ticket, opts: {
41
63
  testCmd: string;
64
+ installCmd?: string;
65
+ history?: string;
66
+ preferSubagent?: string;
67
+ }): string;
68
+ export declare function renderMergePrompt(specBody: string, ticket: Ticket, opts?: {
69
+ installCmd?: string;
70
+ preferSubagent?: string;
42
71
  }): string;
43
- export declare function renderMergePrompt(specBody: string, ticket: Ticket): string;
44
72
  export declare function getProviderArgs(cmd: string): string[];
45
- export declare const EFFORT_LADDERS: {
46
- readonly pi: readonly ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
47
- readonly claude: readonly ["low", "medium", "high", "xhigh", "max"];
48
- readonly codex: readonly ["low", "medium", "high"];
49
- };
50
- export type ProviderName = keyof typeof EFFORT_LADDERS;
51
- export type EffortLevel = typeof EFFORT_LADDERS[ProviderName][number];
52
- export declare const ALL_EFFORT_LEVELS: EffortLevel[];
53
- export declare function escalateEffort(provider: ProviderName, level?: EffortLevel): EffortLevel;
54
73
  export declare function formatStreamLine(line: string): string | null;
55
74
  export type PiStreamFormatter = {
56
75
  process: (line: string) => string[];
@@ -61,6 +80,8 @@ export type AgentResult = {
61
80
  exitCode: number;
62
81
  completedItems: string[];
63
82
  timedOut: boolean;
83
+ subagents?: number;
84
+ costUsd?: number;
64
85
  };
65
86
  export declare function killProcessGroup(pid: number | undefined): void;
66
87
  export declare function killRunningAgents(): number;
@@ -75,8 +96,10 @@ export declare function spawnAgent(opts: {
75
96
  maxTurns?: number;
76
97
  effort?: EffortLevel;
77
98
  onLine?: (line: string) => void;
99
+ preferSubagent?: string;
100
+ onStats?: (stats: SubagentStats) => void;
78
101
  }): Promise<AgentResult>;
79
- export declare function dryRun(tickets: Array<Ticket>, specBody: string, testCmd: string): void;
102
+ export declare function dryRun(tickets: Array<Ticket>, specBody: string, testCmd: string, preferSubagent?: string): void;
80
103
  export declare function dryRunIsolated(opts: {
81
104
  tickets: Array<Ticket>;
82
105
  specBody: string;
@@ -84,26 +107,52 @@ export declare function dryRunIsolated(opts: {
84
107
  conflictStrategy: MergeConflictStrategy;
85
108
  progressPath: string;
86
109
  testCmd: string;
110
+ installCmd?: string;
87
111
  effort?: EffortLevel;
88
112
  retries?: number;
89
113
  retryDelay?: number;
90
114
  concurrency?: number;
115
+ preferSubagent?: string;
91
116
  }): void;
92
117
  export declare const DEFAULT_RETRIES = 3;
93
118
  export declare const DEFAULT_RETRY_DELAY = 30;
94
119
  export declare const DEFAULT_CONCURRENCY = 5;
95
120
  export declare function runPool<T, R>(items: T[], limit: number, fn: (item: T) => Promise<R>): Promise<PromiseSettledResult<R>[]>;
96
- export declare function retryDelayFor(base: number, attempt: number): number;
121
+ /**
122
+ * The ticket's own earlier attempts and its direct blockers' last
123
+ * successful runs, rendered for the prompt. A blocker in another
124
+ * feature is read from that feature's learnings.md, under the same
125
+ * .scratch dir as this ticket's feature. Throws when a log cannot
126
+ * be read.
127
+ */
128
+ export declare function historyFor(learningsPath: string, ticket: Ticket): string;
97
129
  export declare const HUMAN_STATUS = "ready-for-human";
98
130
  /**
99
131
  * The text logged when a run ends. The first line is the
100
- * count summary. When tickets marked ready-for-human are
101
- * still open, further lines name them and say that an
102
- * agent cannot finish them, so a run that exits with open
132
+ * count summary. Further paragraphs (`explainOpen`) name
133
+ * open ready-for-human tickets and say that an agent cannot
134
+ * finish them, list tickets waiting on blockers outside the
135
+ * run and blocker refs that match no ticket, and repeat the
136
+ * typo `warnings` from loading, so a run that exits with open
103
137
  * tickets never looks like it silently gave up.
104
138
  */
105
- export declare function renderFinishSummary(tickets: ReadonlyArray<Ticket>, totalCompleted: number, failedKeys: ReadonlySet<string>): string;
106
- export declare function runWaves(tickets: Ticket[], specBody: string, agentCmd: string, model: string | null, timeout: number, logsDir: string, testCmd: string, display?: Display, maxTurns?: number, effort?: EffortLevel, retries?: number, retryDelay?: number, concurrency?: number): Promise<{
139
+ export declare function renderFinishSummary(tickets: ReadonlyArray<Ticket>, totalCompleted: number, failedKeys: ReadonlySet<string>, warnings?: ReadonlyArray<BlockerWarning>): string;
140
+ /**
141
+ * The zero-ready stop rule: nothing can start, yet a
142
+ * ready-for-agent ticket is waiting on a blocker. A run with no
143
+ * ready-for-agent ticket left (all done, or only human work) is
144
+ * not a stop: it still gets its finish summary and review.
145
+ */
146
+ export declare function nothingReady(tickets: ReadonlyArray<Ticket>): boolean;
147
+ /**
148
+ * Why nothing can run: the same paragraphs as the finish summary
149
+ * (human work, tickets outside the run, refs that match nothing,
150
+ * typos), plus tickets waiting on an in-run ticket in another
151
+ * status (e.g. needs-triage) and tickets that wait on each other
152
+ * in a cycle.
153
+ */
154
+ export declare function renderNothingReady(tickets: ReadonlyArray<Ticket>, warnings: ReadonlyArray<BlockerWarning>): string;
155
+ export declare function runWaves(tickets: Ticket[], specBody: string, agentCmd: string, model: string | null, timeout: number, logsDir: string, testCmd: string, display?: Display, maxTurns?: number, effort?: EffortLevel, retries?: number, retryDelay?: number, concurrency?: number, warnings?: ReadonlyArray<BlockerWarning>, preferSubagent?: string): Promise<{
107
156
  allDone: boolean;
108
157
  }>;
109
158
  export declare function runWavesIsolated(opts: {
@@ -118,13 +167,21 @@ export declare function runWavesIsolated(opts: {
118
167
  conflictStrategy: MergeConflictStrategy;
119
168
  display?: Display;
120
169
  testCmd: string;
170
+ installCmd?: string;
121
171
  maxTurns?: number;
122
172
  effort?: EffortLevel;
123
173
  retries?: number;
124
174
  retryDelay?: number;
125
175
  concurrency?: number;
176
+ preferSubagent?: string;
126
177
  progressPaths?: Map<string, string>;
178
+ warnings?: ReadonlyArray<BlockerWarning>;
179
+ review?: {
180
+ reviewPath: string;
181
+ forkedFrom?: string;
182
+ };
127
183
  }): Promise<{
128
184
  allDone: boolean;
185
+ branch: string;
129
186
  }>;
130
187
  //# sourceMappingURL=orchestrator.d.ts.map