@sentry/warden 0.21.0 → 0.23.0

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 (326) hide show
  1. package/.oxlintrc.json +169 -0
  2. package/CHANGELOG.md +390 -0
  3. package/README.md +4 -4
  4. package/dist/cli/args.d.ts +6 -2
  5. package/dist/cli/args.d.ts.map +1 -1
  6. package/dist/cli/args.js +175 -207
  7. package/dist/cli/args.js.map +1 -1
  8. package/dist/cli/commands/add.d.ts.map +1 -1
  9. package/dist/cli/commands/add.js +16 -5
  10. package/dist/cli/commands/add.js.map +1 -1
  11. package/dist/cli/commands/build.d.ts +13 -0
  12. package/dist/cli/commands/build.d.ts.map +1 -0
  13. package/dist/cli/commands/build.js +384 -0
  14. package/dist/cli/commands/build.js.map +1 -0
  15. package/dist/cli/commands/init.d.ts.map +1 -1
  16. package/dist/cli/commands/init.js +10 -2
  17. package/dist/cli/commands/init.js.map +1 -1
  18. package/dist/cli/commands/runs.d.ts.map +1 -1
  19. package/dist/cli/commands/runs.js +79 -15
  20. package/dist/cli/commands/runs.js.map +1 -1
  21. package/dist/cli/commands/sync.d.ts.map +1 -1
  22. package/dist/cli/commands/sync.js +4 -1
  23. package/dist/cli/commands/sync.js.map +1 -1
  24. package/dist/cli/context.d.ts.map +1 -1
  25. package/dist/cli/context.js +8 -1
  26. package/dist/cli/context.js.map +1 -1
  27. package/dist/cli/files.d.ts.map +1 -1
  28. package/dist/cli/files.js +27 -9
  29. package/dist/cli/files.js.map +1 -1
  30. package/dist/cli/help.d.ts +4 -0
  31. package/dist/cli/help.d.ts.map +1 -0
  32. package/dist/cli/help.js +448 -0
  33. package/dist/cli/help.js.map +1 -0
  34. package/dist/cli/index.js +2 -13
  35. package/dist/cli/index.js.map +1 -1
  36. package/dist/cli/input.d.ts +12 -0
  37. package/dist/cli/input.d.ts.map +1 -1
  38. package/dist/cli/input.js +60 -0
  39. package/dist/cli/input.js.map +1 -1
  40. package/dist/cli/log-cleanup.d.ts.map +1 -1
  41. package/dist/cli/log-cleanup.js +4 -0
  42. package/dist/cli/log-cleanup.js.map +1 -1
  43. package/dist/cli/main.d.ts +39 -2
  44. package/dist/cli/main.d.ts.map +1 -1
  45. package/dist/cli/main.js +482 -100
  46. package/dist/cli/main.js.map +1 -1
  47. package/dist/cli/output/box.js +1 -1
  48. package/dist/cli/output/formatters.d.ts +10 -2
  49. package/dist/cli/output/formatters.d.ts.map +1 -1
  50. package/dist/cli/output/formatters.js +32 -9
  51. package/dist/cli/output/formatters.js.map +1 -1
  52. package/dist/cli/output/index.d.ts +2 -2
  53. package/dist/cli/output/index.d.ts.map +1 -1
  54. package/dist/cli/output/index.js +2 -2
  55. package/dist/cli/output/index.js.map +1 -1
  56. package/dist/cli/output/ink-runner.d.ts +3 -1
  57. package/dist/cli/output/ink-runner.d.ts.map +1 -1
  58. package/dist/cli/output/ink-runner.js +79 -25
  59. package/dist/cli/output/ink-runner.js.map +1 -1
  60. package/dist/cli/output/jsonl-schema-gen.d.ts.map +1 -1
  61. package/dist/cli/output/jsonl-schema-gen.js +4 -2
  62. package/dist/cli/output/jsonl-schema-gen.js.map +1 -1
  63. package/dist/cli/output/jsonl.d.ts +147 -4
  64. package/dist/cli/output/jsonl.d.ts.map +1 -1
  65. package/dist/cli/output/jsonl.js +183 -7
  66. package/dist/cli/output/jsonl.js.map +1 -1
  67. package/dist/cli/output/live-status.d.ts +48 -0
  68. package/dist/cli/output/live-status.d.ts.map +1 -0
  69. package/dist/cli/output/live-status.js +173 -0
  70. package/dist/cli/output/live-status.js.map +1 -0
  71. package/dist/cli/output/reporter.d.ts +4 -0
  72. package/dist/cli/output/reporter.d.ts.map +1 -1
  73. package/dist/cli/output/reporter.js +20 -3
  74. package/dist/cli/output/reporter.js.map +1 -1
  75. package/dist/cli/output/tasks.d.ts +11 -5
  76. package/dist/cli/output/tasks.d.ts.map +1 -1
  77. package/dist/cli/output/tasks.js +126 -68
  78. package/dist/cli/output/tasks.js.map +1 -1
  79. package/dist/cli/signals.d.ts +15 -0
  80. package/dist/cli/signals.d.ts.map +1 -0
  81. package/dist/cli/signals.js +26 -0
  82. package/dist/cli/signals.js.map +1 -0
  83. package/dist/config/loader.d.ts +48 -7
  84. package/dist/config/loader.d.ts.map +1 -1
  85. package/dist/config/loader.js +228 -32
  86. package/dist/config/loader.js.map +1 -1
  87. package/dist/config/schema.d.ts +55 -0
  88. package/dist/config/schema.d.ts.map +1 -1
  89. package/dist/config/schema.js +34 -2
  90. package/dist/config/schema.js.map +1 -1
  91. package/dist/diff/context.d.ts +9 -2
  92. package/dist/diff/context.d.ts.map +1 -1
  93. package/dist/diff/context.js +65 -17
  94. package/dist/diff/context.js.map +1 -1
  95. package/dist/evals/runner.d.ts.map +1 -1
  96. package/dist/evals/runner.js +16 -0
  97. package/dist/evals/runner.js.map +1 -1
  98. package/dist/event/context.d.ts.map +1 -1
  99. package/dist/event/context.js +1 -0
  100. package/dist/event/context.js.map +1 -1
  101. package/dist/event/schedule-context.d.ts.map +1 -1
  102. package/dist/event/schedule-context.js +1 -0
  103. package/dist/event/schedule-context.js.map +1 -1
  104. package/dist/index.d.ts +6 -6
  105. package/dist/index.d.ts.map +1 -1
  106. package/dist/index.js +3 -3
  107. package/dist/index.js.map +1 -1
  108. package/dist/output/dedup.d.ts +8 -11
  109. package/dist/output/dedup.d.ts.map +1 -1
  110. package/dist/output/dedup.js +39 -37
  111. package/dist/output/dedup.js.map +1 -1
  112. package/dist/output/github-checks.d.ts +1 -0
  113. package/dist/output/github-checks.d.ts.map +1 -1
  114. package/dist/output/github-checks.js +9 -7
  115. package/dist/output/github-checks.js.map +1 -1
  116. package/dist/sdk/analyze.d.ts.map +1 -1
  117. package/dist/sdk/analyze.js +166 -276
  118. package/dist/sdk/analyze.js.map +1 -1
  119. package/dist/sdk/circuit-breaker.d.ts +26 -0
  120. package/dist/sdk/circuit-breaker.d.ts.map +1 -0
  121. package/dist/sdk/circuit-breaker.js +53 -0
  122. package/dist/sdk/circuit-breaker.js.map +1 -0
  123. package/dist/sdk/errors.d.ts +5 -0
  124. package/dist/sdk/errors.d.ts.map +1 -1
  125. package/dist/sdk/errors.js +27 -0
  126. package/dist/sdk/errors.js.map +1 -1
  127. package/dist/sdk/extract.d.ts +18 -9
  128. package/dist/sdk/extract.d.ts.map +1 -1
  129. package/dist/sdk/extract.js +95 -87
  130. package/dist/sdk/extract.js.map +1 -1
  131. package/dist/sdk/fix-quality.d.ts +5 -0
  132. package/dist/sdk/fix-quality.d.ts.map +1 -1
  133. package/dist/sdk/fix-quality.js +42 -23
  134. package/dist/sdk/fix-quality.js.map +1 -1
  135. package/dist/sdk/haiku.d.ts +6 -0
  136. package/dist/sdk/haiku.d.ts.map +1 -1
  137. package/dist/sdk/haiku.js +75 -49
  138. package/dist/sdk/haiku.js.map +1 -1
  139. package/dist/sdk/json-output.d.ts +31 -0
  140. package/dist/sdk/json-output.d.ts.map +1 -0
  141. package/dist/sdk/json-output.js +98 -0
  142. package/dist/sdk/json-output.js.map +1 -0
  143. package/dist/sdk/model-pricing.json +56 -16
  144. package/dist/sdk/post-process.d.ts +29 -0
  145. package/dist/sdk/post-process.d.ts.map +1 -0
  146. package/dist/sdk/post-process.js +65 -0
  147. package/dist/sdk/post-process.js.map +1 -0
  148. package/dist/sdk/prepare.d.ts.map +1 -1
  149. package/dist/sdk/prepare.js +4 -1
  150. package/dist/sdk/prepare.js.map +1 -1
  151. package/dist/sdk/pricing.d.ts +18 -0
  152. package/dist/sdk/pricing.d.ts.map +1 -1
  153. package/dist/sdk/pricing.js +108 -3
  154. package/dist/sdk/pricing.js.map +1 -1
  155. package/dist/sdk/prompt-sections.d.ts +56 -0
  156. package/dist/sdk/prompt-sections.d.ts.map +1 -0
  157. package/dist/sdk/prompt-sections.js +118 -0
  158. package/dist/sdk/prompt-sections.js.map +1 -0
  159. package/dist/sdk/prompt.d.ts +2 -17
  160. package/dist/sdk/prompt.d.ts.map +1 -1
  161. package/dist/sdk/prompt.js +15 -37
  162. package/dist/sdk/prompt.js.map +1 -1
  163. package/dist/sdk/report-files.d.ts +15 -0
  164. package/dist/sdk/report-files.d.ts.map +1 -0
  165. package/dist/sdk/report-files.js +20 -0
  166. package/dist/sdk/report-files.js.map +1 -0
  167. package/dist/sdk/runner.d.ts +9 -1
  168. package/dist/sdk/runner.d.ts.map +1 -1
  169. package/dist/sdk/runner.js +6 -0
  170. package/dist/sdk/runner.js.map +1 -1
  171. package/dist/sdk/runtimes/claude.d.ts +3 -0
  172. package/dist/sdk/runtimes/claude.d.ts.map +1 -0
  173. package/dist/sdk/runtimes/claude.js +376 -0
  174. package/dist/sdk/runtimes/claude.js.map +1 -0
  175. package/dist/sdk/runtimes/index.d.ts +12 -0
  176. package/dist/sdk/runtimes/index.d.ts.map +1 -0
  177. package/dist/sdk/runtimes/index.js +22 -0
  178. package/dist/sdk/runtimes/index.js.map +1 -0
  179. package/dist/sdk/runtimes/types.d.ts +116 -0
  180. package/dist/sdk/runtimes/types.d.ts.map +1 -0
  181. package/dist/sdk/runtimes/types.js +17 -0
  182. package/dist/sdk/runtimes/types.js.map +1 -0
  183. package/dist/sdk/types.d.ts +42 -2
  184. package/dist/sdk/types.d.ts.map +1 -1
  185. package/dist/sdk/types.js.map +1 -1
  186. package/dist/sdk/usage.d.ts +21 -6
  187. package/dist/sdk/usage.d.ts.map +1 -1
  188. package/dist/sdk/usage.js +36 -23
  189. package/dist/sdk/usage.js.map +1 -1
  190. package/dist/sdk/verify.d.ts +25 -0
  191. package/dist/sdk/verify.d.ts.map +1 -0
  192. package/dist/sdk/verify.js +226 -0
  193. package/dist/sdk/verify.js.map +1 -0
  194. package/dist/skill-builder/agentic.d.ts +33 -0
  195. package/dist/skill-builder/agentic.d.ts.map +1 -0
  196. package/dist/skill-builder/agentic.js +240 -0
  197. package/dist/skill-builder/agentic.js.map +1 -0
  198. package/dist/skill-builder/authoring-provider.d.ts +5 -0
  199. package/dist/skill-builder/authoring-provider.d.ts.map +1 -0
  200. package/dist/skill-builder/authoring-provider.js +70 -0
  201. package/dist/skill-builder/authoring-provider.js.map +1 -0
  202. package/dist/skill-builder/definition.d.ts +46 -0
  203. package/dist/skill-builder/definition.d.ts.map +1 -0
  204. package/dist/skill-builder/definition.js +171 -0
  205. package/dist/skill-builder/definition.js.map +1 -0
  206. package/dist/skill-builder/outline-contract.d.ts +113 -0
  207. package/dist/skill-builder/outline-contract.d.ts.map +1 -0
  208. package/dist/skill-builder/outline-contract.js +73 -0
  209. package/dist/skill-builder/outline-contract.js.map +1 -0
  210. package/dist/skill-builder/outline-state.d.ts +158 -0
  211. package/dist/skill-builder/outline-state.d.ts.map +1 -0
  212. package/dist/skill-builder/outline-state.js +87 -0
  213. package/dist/skill-builder/outline-state.js.map +1 -0
  214. package/dist/skill-builder/outline.d.ts +36 -0
  215. package/dist/skill-builder/outline.d.ts.map +1 -0
  216. package/dist/skill-builder/outline.js +345 -0
  217. package/dist/skill-builder/outline.js.map +1 -0
  218. package/dist/skill-builder/skill-contract.d.ts +97 -0
  219. package/dist/skill-builder/skill-contract.d.ts.map +1 -0
  220. package/dist/skill-builder/skill-contract.js +58 -0
  221. package/dist/skill-builder/skill-contract.js.map +1 -0
  222. package/dist/skill-builder/skill-prompts.d.ts +61 -0
  223. package/dist/skill-builder/skill-prompts.d.ts.map +1 -0
  224. package/dist/skill-builder/skill-prompts.js +285 -0
  225. package/dist/skill-builder/skill-prompts.js.map +1 -0
  226. package/dist/skill-builder/skill.d.ts +23 -0
  227. package/dist/skill-builder/skill.d.ts.map +1 -0
  228. package/dist/skill-builder/skill.js +563 -0
  229. package/dist/skill-builder/skill.js.map +1 -0
  230. package/dist/skills/index.d.ts +2 -2
  231. package/dist/skills/index.d.ts.map +1 -1
  232. package/dist/skills/index.js +1 -1
  233. package/dist/skills/index.js.map +1 -1
  234. package/dist/skills/loader.d.ts +20 -6
  235. package/dist/skills/loader.d.ts.map +1 -1
  236. package/dist/skills/loader.js +88 -141
  237. package/dist/skills/loader.js.map +1 -1
  238. package/dist/skills/remote.d.ts +11 -7
  239. package/dist/skills/remote.d.ts.map +1 -1
  240. package/dist/skills/remote.js +62 -162
  241. package/dist/skills/remote.js.map +1 -1
  242. package/dist/triggers/matcher.d.ts.map +1 -1
  243. package/dist/triggers/matcher.js +28 -15
  244. package/dist/triggers/matcher.js.map +1 -1
  245. package/dist/types/index.d.ts +41 -0
  246. package/dist/types/index.d.ts.map +1 -1
  247. package/dist/types/index.js +24 -0
  248. package/dist/types/index.js.map +1 -1
  249. package/dist/utils/index.d.ts +1 -0
  250. package/dist/utils/index.d.ts.map +1 -1
  251. package/dist/utils/index.js +1 -0
  252. package/dist/utils/index.js.map +1 -1
  253. package/dist/utils/path.d.ts +17 -0
  254. package/dist/utils/path.d.ts.map +1 -0
  255. package/dist/utils/path.js +36 -0
  256. package/dist/utils/path.js.map +1 -0
  257. package/package.json +8 -8
  258. package/policies/README.md +22 -0
  259. package/policies/code-comments.md +20 -0
  260. package/policies/policy-template.md +14 -0
  261. package/skills/warden/SKILL.md +5 -5
  262. package/skills/warden/SOURCES.md +39 -0
  263. package/skills/warden/SPEC.md +112 -0
  264. package/skills/warden/references/cli-reference.md +11 -4
  265. package/skills/warden/references/config-schema.md +38 -13
  266. package/skills/warden/references/configuration.md +29 -8
  267. package/skills/warden-sweep/SKILL.md +43 -365
  268. package/skills/warden-sweep/SOURCES.md +43 -0
  269. package/skills/warden-sweep/SPEC.md +126 -0
  270. package/skills/warden-sweep/references/issue-phase.md +24 -0
  271. package/skills/warden-sweep/references/organize-phase.md +31 -0
  272. package/skills/warden-sweep/references/patch-phase.md +142 -0
  273. package/skills/warden-sweep/references/resume-and-artifacts.md +51 -0
  274. package/skills/warden-sweep/references/scan-phase.md +46 -0
  275. package/skills/warden-sweep/references/script-interfaces.md +127 -0
  276. package/skills/warden-sweep/references/verify-phase.md +69 -0
  277. package/src/builtin-skills/security-review/SKILL.md +81 -0
  278. package/src/builtin-skills/security-review/SPEC.md +77 -0
  279. package/src/builtin-skills/security-review/references/github-workflows.md +186 -0
  280. package/src/builtin-skills/security-review/references/javascript-typescript.md +69 -0
  281. package/src/builtin-skills/security-review/references/python.md +70 -0
  282. package/src/internal-skills/skill-writer/EVAL.md +124 -0
  283. package/src/internal-skills/skill-writer/SKILL.md +159 -0
  284. package/src/internal-skills/skill-writer/SOURCES.md +94 -0
  285. package/src/internal-skills/skill-writer/SPEC.md +153 -0
  286. package/src/internal-skills/skill-writer/references/artifact-layouts/argument-driven-skill-layout.md +32 -0
  287. package/src/internal-skills/skill-writer/references/artifact-layouts/asset-template-skill-layout.md +30 -0
  288. package/src/internal-skills/skill-writer/references/artifact-layouts/inline-skill-layout.md +28 -0
  289. package/src/internal-skills/skill-writer/references/artifact-layouts/reference-backed-skill-layout.md +35 -0
  290. package/src/internal-skills/skill-writer/references/artifact-layouts/script-backed-skill-layout.md +31 -0
  291. package/src/internal-skills/skill-writer/references/authoring-path.md +91 -0
  292. package/src/internal-skills/skill-writer/references/claude-code/argument-substitutions.md +20 -0
  293. package/src/internal-skills/skill-writer/references/claude-code/dynamic-context.md +16 -0
  294. package/src/internal-skills/skill-writer/references/claude-code/frontmatter-and-invocation.md +33 -0
  295. package/src/internal-skills/skill-writer/references/claude-code/hook-backed-skills.md +28 -0
  296. package/src/internal-skills/skill-writer/references/claude-code/subagent-fork-skills.md +22 -0
  297. package/src/internal-skills/skill-writer/references/description-optimization.md +28 -0
  298. package/src/internal-skills/skill-writer/references/design-principles.md +74 -0
  299. package/src/internal-skills/skill-writer/references/evaluation-path.md +73 -0
  300. package/src/internal-skills/skill-writer/references/examples/documentation-skill.md +70 -0
  301. package/src/internal-skills/skill-writer/references/examples/evaluator-loop-skill.md +38 -0
  302. package/src/internal-skills/skill-writer/references/examples/hook-backed-skill.md +39 -0
  303. package/src/internal-skills/skill-writer/references/examples/router-skill.md +41 -0
  304. package/src/internal-skills/skill-writer/references/examples/security-review-skill.md +69 -0
  305. package/src/internal-skills/skill-writer/references/examples/subagent-fork-skill.md +39 -0
  306. package/src/internal-skills/skill-writer/references/examples/workflow-process-skill.md +65 -0
  307. package/src/internal-skills/skill-writer/references/execution-shapes.md +68 -0
  308. package/src/internal-skills/skill-writer/references/iteration-evidence.md +82 -0
  309. package/src/internal-skills/skill-writer/references/iteration-path.md +40 -0
  310. package/src/internal-skills/skill-writer/references/mode-selection.md +67 -0
  311. package/src/internal-skills/skill-writer/references/output-contracts.md +68 -0
  312. package/src/internal-skills/skill-writer/references/reference-architecture.md +71 -0
  313. package/src/internal-skills/skill-writer/references/registration-validation.md +67 -0
  314. package/src/internal-skills/skill-writer/references/source-discovery.md +60 -0
  315. package/src/internal-skills/skill-writer/references/spec-template.md +118 -0
  316. package/src/internal-skills/skill-writer/references/structure-troubleshooting.md +111 -0
  317. package/src/internal-skills/skill-writer/references/synthesis-path.md +119 -0
  318. package/src/internal-skills/skill-writer/references/workflow-mechanics/evaluator-loops.md +25 -0
  319. package/src/internal-skills/skill-writer/references/workflow-mechanics/orchestrator-workers.md +25 -0
  320. package/src/internal-skills/skill-writer/references/workflow-mechanics/parallel-workflows.md +21 -0
  321. package/src/internal-skills/skill-writer/references/workflow-mechanics/plan-validate-execute.md +26 -0
  322. package/src/internal-skills/skill-writer/references/workflow-mechanics/prompt-chaining.md +24 -0
  323. package/src/internal-skills/skill-writer/references/workflow-mechanics/routing-workflows.md +28 -0
  324. package/src/internal-skills/skill-writer/references/workflow-mechanics/validation-loops.md +26 -0
  325. package/src/internal-skills/skill-writer/scripts/quick_validate.py +158 -0
  326. package/src/internal-skills/skill-writer/scripts/quick_validate_test.py +79 -0
package/dist/cli/main.js CHANGED
@@ -1,18 +1,21 @@
1
- import { existsSync, readFileSync } from 'node:fs';
1
+ import { existsSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
2
2
  import { dirname, join, resolve } from 'node:path';
3
3
  import { config as dotenvConfig } from 'dotenv';
4
4
  import { Sentry, flushSentry, setGlobalAttributes, emitRunMetric, getTraceId } from '../sentry.js';
5
- import { loadWardenConfig, resolveSkillConfigs } from '../config/loader.js';
5
+ import { emptyToUndefined, loadWardenConfig, resolveSkillConfigs } from '../config/loader.js';
6
6
  import { verifyAuth } from '../sdk/runner.js';
7
- import { resolveSkillAsync } from '../skills/loader.js';
7
+ import { mapExtractionErrorCode } from '../sdk/errors.js';
8
+ import { mergeAuxiliaryUsage } from '../sdk/usage.js';
9
+ import { resolveSkillAsync, SkillLoaderError } from '../skills/loader.js';
8
10
  import { matchTrigger, filterContextByPaths, shouldFail, countFindingsAtOrAbove } from '../triggers/matcher.js';
9
11
  import { filterFindings } from '../types/index.js';
10
12
  import { DEFAULT_CONCURRENCY, getAnthropicApiKey } from '../utils/index.js';
11
- import { parseCliArgs, showHelp, showVersion, classifyTargets } from './args.js';
13
+ import { parseCliArgs, showVersion, classifyTargets } from './args.js';
14
+ import { showHelp } from './help.js';
12
15
  import { buildLocalEventContext, buildFileEventContext } from './context.js';
13
- import { getRepoRoot, getHeadSha, refExists, hasUncommittedChanges } from './git.js';
16
+ import { getRepoRoot, getHeadSha, refExists, getDefaultBranch } from './git.js';
14
17
  import { renderTerminalReport, filterReports } from './terminal.js';
15
- import { Reporter, detectOutputMode, parseVerbosity, Verbosity, runSkillTasks, runSkillTasksWithInk, pluralize, MODEL_DEFAULT_SENTINEL, writeJsonlContent, renderJsonlString, renderJsonlSkillLine, renderJsonlSummaryLine, initJsonlFile, appendJsonlLine, getRepoLogPath, generateRunId, } from './output/index.js';
18
+ import { Reporter, detectOutputMode, parseVerbosity, Verbosity, runSkillTasks, runSkillTasksWithInk, pluralize, MODEL_DEFAULT_SENTINEL, writeJsonlContent, renderJsonlString, renderJsonlChunkLine, renderJsonlChunkRecords, initJsonlFile, appendJsonlLine, getRepoLogPath, generateRunId, } from './output/index.js';
16
19
  import { cleanupArtifacts } from './log-cleanup.js';
17
20
  import { collectFixableFindings, applyAllFixes, runInteractiveFixFlow, renderFixSummary, } from './fix.js';
18
21
  import { UserAbortError } from './input.js';
@@ -21,6 +24,8 @@ import { runAdd } from './commands/add.js';
21
24
  import { runSetupApp } from './commands/setup-app.js';
22
25
  import { runSync } from './commands/sync.js';
23
26
  import { runRuns } from './commands/runs.js';
27
+ import { runBuild, runImprove } from './commands/build.js';
28
+ import { generatedSkillDefinitionRootExists, resolveGeneratedSkillTarget, } from '../skill-builder/definition.js';
24
29
  /**
25
30
  * Global abort controller for graceful shutdown on SIGINT.
26
31
  * Used to cancel in-progress SDK queries.
@@ -52,22 +57,31 @@ function loadEnvFiles(dir) {
52
57
  dotenvConfig({ path: envLocalPath, override: true, quiet: true });
53
58
  }
54
59
  }
55
- /**
56
- * Create a Reporter instance from CLI options.
57
- */
58
60
  function createReporter(options) {
59
61
  const detected = detectOutputMode(options.color);
60
62
  const outputMode = options.log ? { ...detected, isTTY: false } : detected;
61
63
  const verbosity = parseVerbosity(options.quiet, options.verbose, options.debug);
62
64
  return new Reporter(outputMode, verbosity);
63
65
  }
64
- /**
65
- * Resolve the config file path based on CLI options and repo root.
66
- */
66
+ /** Resolve the directory Warden should treat as the invocation root. */
67
+ export function resolveInvocationCwd(baseCwd, cliCwd) {
68
+ return cliCwd ? resolve(baseCwd, cliCwd) : baseCwd;
69
+ }
67
70
  function resolveConfigPath(options, repoPath) {
68
71
  const cwd = process.cwd();
69
72
  return options.config ? resolve(cwd, options.config) : resolve(repoPath, 'warden.toml');
70
73
  }
74
+ function resolveLocalReviewBase(configDefaultBranch, repoPath) {
75
+ const defaultBranch = configDefaultBranch ?? getDefaultBranch(repoPath);
76
+ if (defaultBranch.includes('/')) {
77
+ return { defaultBranch, base: defaultBranch };
78
+ }
79
+ const remoteTrackingRef = `origin/${defaultBranch}`;
80
+ return {
81
+ defaultBranch,
82
+ base: refExists(remoteTrackingRef, repoPath) ? remoteTrackingRef : defaultBranch,
83
+ };
84
+ }
71
85
  /**
72
86
  * Emit a minimal JSONL log (summary-only, 0 findings) for early-exit paths.
73
87
  *
@@ -146,17 +160,139 @@ function initializeRunLog(args) {
146
160
  }
147
161
  }
148
162
  const paths = [];
163
+ const seenPaths = new Set();
164
+ const addPath = (path) => {
165
+ const key = resolve(process.cwd(), path);
166
+ if (seenPaths.has(key))
167
+ return;
168
+ seenPaths.add(key);
169
+ paths.push(path);
170
+ };
149
171
  if (primaryLogWritten)
150
- paths.push(primaryLogPath);
172
+ addPath(primaryLogPath);
151
173
  if (resolvedOutputPath)
152
- paths.push(resolvedOutputPath);
153
- return { paths, primaryLogPath, primaryLogWritten, outputPath: resolvedOutputPath, startTime, baseRun };
174
+ addPath(resolvedOutputPath);
175
+ return { paths, primaryLogPath, primaryLogWritten, outputPath: resolvedOutputPath, startTime, baseRun, chunks: [] };
154
176
  }
155
- function appendSkillToRunLog(log, report) {
177
+ function appendChunkToRunLog(log, skillName, chunk) {
156
178
  if (log.paths.length === 0)
157
179
  return;
158
- const run = { ...log.baseRun, durationMs: Date.now() - log.startTime };
159
- const line = renderJsonlSkillLine(report, run);
180
+ const auxiliaryUsage = chunk.auxiliaryUsage?.reduce((acc, entry) => mergeAuxiliaryUsage(acc, { [entry.agent]: entry.usage }), undefined);
181
+ const error = chunk.failed
182
+ ? {
183
+ code: chunk.failureCode ?? 'unknown',
184
+ message: chunk.failureMessage ?? 'unknown error',
185
+ timestamp: new Date().toISOString(),
186
+ }
187
+ : chunk.extractionFailed
188
+ ? {
189
+ code: mapExtractionErrorCode(chunk.extractionError),
190
+ message: chunk.extractionError ?? 'unknown extraction error',
191
+ timestamp: new Date().toISOString(),
192
+ }
193
+ : undefined;
194
+ const record = {
195
+ schemaVersion: 1,
196
+ run: { ...log.baseRun, durationMs: Date.now() - log.startTime },
197
+ skill: skillName,
198
+ model: chunk.model,
199
+ chunk: {
200
+ file: chunk.filename,
201
+ index: chunk.index,
202
+ total: chunk.total,
203
+ lineRange: chunk.lineRange,
204
+ },
205
+ status: error ? 'error' : 'ok',
206
+ findings: chunk.findings,
207
+ usage: chunk.usage,
208
+ durationMs: chunk.durationMs,
209
+ auxiliaryUsage,
210
+ error,
211
+ };
212
+ let line;
213
+ try {
214
+ line = renderJsonlChunkLine(record);
215
+ }
216
+ catch {
217
+ return;
218
+ }
219
+ log.chunks.push(record);
220
+ for (const p of log.paths) {
221
+ try {
222
+ appendJsonlLine(p, line);
223
+ }
224
+ catch { /* best-effort */ }
225
+ }
226
+ }
227
+ function buildReportChunkRecord(log, report, runDurationMs, index = 1, total = 1, error) {
228
+ const reportError = report.error ?? error;
229
+ return {
230
+ schemaVersion: 1,
231
+ run: { ...log.baseRun, durationMs: runDurationMs },
232
+ skill: report.skill,
233
+ model: report.model,
234
+ chunk: {
235
+ file: report.skippedFiles?.[0]?.filename ?? '',
236
+ index,
237
+ total,
238
+ lineRange: '',
239
+ },
240
+ status: reportError ? 'error' : report.skippedFiles?.length ? 'skipped' : 'ok',
241
+ findings: report.findings,
242
+ usage: report.usage,
243
+ durationMs: report.durationMs ?? runDurationMs,
244
+ auxiliaryUsage: report.auxiliaryUsage,
245
+ error: reportError,
246
+ skippedFiles: report.skippedFiles,
247
+ };
248
+ }
249
+ function buildRunErrorChunkRecord(log, runDurationMs, error) {
250
+ return {
251
+ schemaVersion: 1,
252
+ run: { ...log.baseRun, durationMs: runDurationMs },
253
+ skill: 'run',
254
+ chunk: {
255
+ file: '',
256
+ index: 1,
257
+ total: 1,
258
+ lineRange: '',
259
+ },
260
+ status: 'error',
261
+ findings: [],
262
+ durationMs: runDurationMs,
263
+ error,
264
+ };
265
+ }
266
+ function hasReportRecord(log, report) {
267
+ return log.chunks.some((chunk) => {
268
+ if (chunk.skill !== report.skill)
269
+ return false;
270
+ if (report.error) {
271
+ return chunk.error?.code === report.error.code && chunk.error.message === report.error.message;
272
+ }
273
+ if (report.skippedFiles?.length) {
274
+ return (chunk.skippedFiles?.length ?? 0) > 0;
275
+ }
276
+ return true;
277
+ });
278
+ }
279
+ function shouldStreamReportRecord(log, report) {
280
+ if (hasReportRecord(log, report))
281
+ return false;
282
+ return Boolean(report.error || report.skippedFiles?.length || !log.chunks.some((chunk) => chunk.skill === report.skill));
283
+ }
284
+ function appendReportToRunLog(log, report) {
285
+ if (!shouldStreamReportRecord(log, report))
286
+ return;
287
+ const record = buildReportChunkRecord(log, report, Date.now() - log.startTime);
288
+ let line;
289
+ try {
290
+ line = renderJsonlChunkLine(record);
291
+ }
292
+ catch {
293
+ return;
294
+ }
295
+ log.chunks.push(record);
160
296
  for (const p of log.paths) {
161
297
  try {
162
298
  appendJsonlLine(p, line);
@@ -164,26 +300,198 @@ function appendSkillToRunLog(log, report) {
164
300
  catch { /* best-effort */ }
165
301
  }
166
302
  }
303
+ function lineRangeIncludes(lineRange, line) {
304
+ if (!lineRange)
305
+ return false;
306
+ const [startText, endText] = lineRange.split('-');
307
+ const start = Number(startText);
308
+ const end = endText ? Number(endText) : start;
309
+ return Number.isFinite(start) && Number.isFinite(end) && line >= start && line <= end;
310
+ }
311
+ function findChunkForFinding(chunks, skill, finding) {
312
+ const sameSkill = chunks.filter((chunk) => chunk.skill === skill && chunk.chunk.file);
313
+ const location = finding.location;
314
+ if (!location)
315
+ return sameSkill[0];
316
+ return sameSkill.find((chunk) => chunk.chunk.file === location.path && lineRangeIncludes(chunk.chunk.lineRange, location.startLine)) ?? sameSkill.find((chunk) => chunk.chunk.file === location.path) ?? sameSkill[0];
317
+ }
318
+ function buildFinalChunkRecords(log, reports, totalDurationMs, error) {
319
+ const finalRun = { ...log.baseRun, durationMs: totalDurationMs };
320
+ if (log.chunks.length === 0) {
321
+ if (reports.length === 0 && error) {
322
+ return [buildRunErrorChunkRecord(log, totalDurationMs, error)];
323
+ }
324
+ return reports.map((report) => buildReportChunkRecord(log, report, totalDurationMs, undefined, undefined, error));
325
+ }
326
+ const findingsByChunk = new Map();
327
+ for (const report of reports) {
328
+ for (const finding of report.findings) {
329
+ const chunk = findChunkForFinding(log.chunks, report.skill, finding);
330
+ if (!chunk)
331
+ continue;
332
+ const findings = findingsByChunk.get(chunk) ?? [];
333
+ findings.push(finding);
334
+ findingsByChunk.set(chunk, findings);
335
+ }
336
+ }
337
+ const chunkRecords = log.chunks.map((chunk) => ({
338
+ ...chunk,
339
+ run: { ...finalRun },
340
+ findings: findingsByChunk.get(chunk) ?? [],
341
+ }));
342
+ const finalLog = { ...log, chunks: chunkRecords };
343
+ const missingReports = reports.filter((report) => shouldStreamReportRecord(finalLog, report));
344
+ return [
345
+ ...chunkRecords,
346
+ ...missingReports.map((report) => buildReportChunkRecord(log, report, totalDurationMs, undefined, undefined, error)),
347
+ ];
348
+ }
167
349
  /**
168
- * Append the run-final summary record. Returns the set of paths that
350
+ * Rewrite the run log with final chunk records. Returns the set of paths that
169
351
  * accepted the write, so the caller can decide whether to claim
170
- * "wrote JSONL output to X" (only true when the summary actually landed).
352
+ * "wrote JSONL output to X" (only true when the final log actually landed).
171
353
  */
172
354
  function finalizeRunLog(log, reports, totalDurationMs, error) {
173
355
  const wrote = new Set();
174
356
  if (log.paths.length === 0)
175
357
  return wrote;
176
- const run = { ...log.baseRun, durationMs: totalDurationMs };
177
- const line = renderJsonlSummaryLine(reports, run, error);
358
+ let content;
359
+ try {
360
+ const records = buildFinalChunkRecords(log, reports, totalDurationMs, error);
361
+ content = renderJsonlChunkRecords(records);
362
+ }
363
+ catch {
364
+ return wrote;
365
+ }
178
366
  for (const p of log.paths) {
367
+ const targetPath = resolve(process.cwd(), p);
368
+ const tempPath = `${targetPath}.${process.pid}.${Date.now()}.tmp`;
369
+ const tempDonePath = `${tempPath}.done`;
179
370
  try {
180
- appendJsonlLine(p, line);
371
+ writeJsonlContent(tempPath, content);
372
+ writeFileSync(tempDonePath, '');
373
+ renameSync(tempPath, targetPath);
374
+ renameSync(tempDonePath, `${targetPath}.done`);
181
375
  wrote.add(p);
182
376
  }
183
- catch { /* best-effort */ }
377
+ catch {
378
+ try {
379
+ unlinkSync(tempPath);
380
+ }
381
+ catch { /* ignore */ }
382
+ try {
383
+ unlinkSync(tempDonePath);
384
+ }
385
+ catch { /* ignore */ }
386
+ // best-effort
387
+ }
184
388
  }
185
389
  return wrote;
186
390
  }
391
+ /** Apply per-skill runner overrides on top of the shared execution defaults. */
392
+ export function mergeSkillRunnerOptions(base, overrides) {
393
+ const merged = { ...base };
394
+ if (overrides.model !== undefined)
395
+ merged.model = overrides.model;
396
+ if (overrides.maxTurns !== undefined)
397
+ merged.maxTurns = overrides.maxTurns;
398
+ if (overrides.runtime !== undefined)
399
+ merged.runtime = overrides.runtime;
400
+ if (overrides.auxiliaryModel !== undefined)
401
+ merged.auxiliaryModel = overrides.auxiliaryModel;
402
+ if (overrides.synthesisModel !== undefined)
403
+ merged.synthesisModel = overrides.synthesisModel;
404
+ if (overrides.auxiliaryMaxRetries !== undefined) {
405
+ merged.auxiliaryMaxRetries = overrides.auxiliaryMaxRetries;
406
+ }
407
+ if (overrides.verifyFindings !== undefined)
408
+ merged.verifyFindings = overrides.verifyFindings;
409
+ return merged;
410
+ }
411
+ function renderSkillRunHeader(args) {
412
+ const { reporter, skill, repoPath, runtimeName, model } = args;
413
+ const source = skill.rootDir && repoPath && skill.rootDir.startsWith(repoPath)
414
+ ? skill.rootDir.slice(repoPath.length + 1)
415
+ : skill.rootDir;
416
+ reporter.blank();
417
+ reporter.text(` Skill ${skill.name}`);
418
+ if (source) {
419
+ reporter.text(` Source ${source}`);
420
+ }
421
+ reporter.text(` Model ${model ?? 'default'} [${runtimeName}]`);
422
+ reporter.blank();
423
+ }
424
+ async function createDirectSkillTask(args) {
425
+ const { spec, repoPath, options, reporter, renderHeader } = args;
426
+ let skill;
427
+ try {
428
+ skill = await resolveSkillAsync(spec.skill, repoPath, {
429
+ remote: spec.remote,
430
+ offline: options.offline,
431
+ });
432
+ }
433
+ catch (error) {
434
+ if (error instanceof SkillLoaderError &&
435
+ repoPath &&
436
+ generatedSkillDefinitionRootExists(resolveGeneratedSkillTarget(repoPath, spec.skill).rootDir)) {
437
+ throw new Error(`Generated skill ${spec.skill} is missing generated artifacts. Run "warden build ${spec.skill}" first.`);
438
+ }
439
+ throw error;
440
+ }
441
+ if (renderHeader && !options.json) {
442
+ renderSkillRunHeader({
443
+ reporter,
444
+ skill,
445
+ repoPath,
446
+ runtimeName: spec.runnerOptions.runtime ?? 'claude',
447
+ model: spec.runnerOptions.model,
448
+ });
449
+ }
450
+ return {
451
+ name: spec.name,
452
+ displayName: spec.displayName,
453
+ failOn: spec.failOn,
454
+ minConfidence: spec.minConfidence,
455
+ resolveSkill: async () => skill,
456
+ context: spec.context,
457
+ runnerOptions: spec.runnerOptions,
458
+ };
459
+ }
460
+ /** Expand configured skills into runnable direct tasks. */
461
+ export async function createSkillTasks(args) {
462
+ const tasks = [];
463
+ const singleDirectSkill = args.specs.length === 1;
464
+ for (const spec of args.specs) {
465
+ tasks.push(await createDirectSkillTask({
466
+ spec,
467
+ repoPath: args.repoPath,
468
+ options: args.options,
469
+ reporter: args.reporter,
470
+ renderHeader: singleDirectSkill,
471
+ }));
472
+ }
473
+ return tasks;
474
+ }
475
+ /** Resolve the default analysis model from config, CLI overrides, or environment. */
476
+ export function resolveCliDefaultModel(config, cliModel) {
477
+ return (emptyToUndefined(config?.defaults?.agent?.model) ??
478
+ emptyToUndefined(config?.defaults?.model) ??
479
+ emptyToUndefined(cliModel) ??
480
+ emptyToUndefined(process.env['WARDEN_MODEL']));
481
+ }
482
+ /** Resolve the default auxiliary model used for helper and repair passes. */
483
+ export function resolveCliDefaultAuxiliaryModel(config) {
484
+ return emptyToUndefined(config?.defaults?.auxiliary?.model);
485
+ }
486
+ /** Resolve the default synthesis model, falling back to the auxiliary lane when unset. */
487
+ export function resolveCliDefaultSynthesisModel(config) {
488
+ return (emptyToUndefined(config?.defaults?.synthesis?.model) ??
489
+ resolveCliDefaultAuxiliaryModel(config));
490
+ }
491
+ /** Resolve the model label recorded in JSONL output, including the default sentinel. */
492
+ export function resolveCliLogModel(config, cliModel) {
493
+ return resolveCliDefaultModel(config, cliModel) ?? MODEL_DEFAULT_SENTINEL;
494
+ }
187
495
  /**
188
496
  * Process skill task results into reports and check for failures.
189
497
  * Exported for testing; callers inside main.ts use it directly.
@@ -216,9 +524,6 @@ export function processTaskResults(results, reportOn, minConfidence) {
216
524
  const filteredReports = filterReports(reports, reportOn, minConfidence);
217
525
  return { reports, filteredReports, hasFailure, failureReasons };
218
526
  }
219
- /**
220
- * Output results and handle fixes. Returns exit code.
221
- */
222
527
  async function outputResultsAndHandleFixes(processed, options, reporter, runLog, totalDuration, failFastAborted) {
223
528
  const { reports, filteredReports, hasFailure, failureReasons } = processed;
224
529
  const traceId = runLog.baseRun.traceId;
@@ -247,8 +552,7 @@ async function outputResultsAndHandleFixes(processed, options, reporter, runLog,
247
552
  // Prefer reading the on-disk log (per-skill durationMs is a snapshot).
248
553
  // Only read it back if finalize actually landed the summary there;
249
554
  // a half-written file should fall through to the in-memory render.
250
- // The fallback uses the run total for every record — structurally
251
- // valid but not byte-identical to the file.
555
+ // The fallback renders the same chunk-record shape in memory.
252
556
  let jsonlContent;
253
557
  if (finalizedPaths.has(runLog.primaryLogPath)) {
254
558
  try {
@@ -257,14 +561,13 @@ async function outputResultsAndHandleFixes(processed, options, reporter, runLog,
257
561
  catch { /* fall through */ }
258
562
  }
259
563
  if (!jsonlContent) {
260
- jsonlContent = renderJsonlString(reports, totalDuration, {
261
- runId: runLog.baseRun.runId,
262
- traceId,
263
- timestamp: new Date(runLog.baseRun.timestamp),
264
- model: runLog.baseRun.model,
265
- headSha: runLog.baseRun.headSha,
266
- cwd: runLog.baseRun.cwd,
267
- });
564
+ try {
565
+ jsonlContent = renderJsonlChunkRecords(buildFinalChunkRecords(runLog, reports, totalDuration));
566
+ }
567
+ catch (err) {
568
+ reporter.error(`Failed to render JSONL output: ${err instanceof Error ? err.message : String(err)}`);
569
+ return 1;
570
+ }
268
571
  }
269
572
  process.stdout.write(jsonlContent);
270
573
  }
@@ -310,12 +613,8 @@ async function outputResultsAndHandleFixes(processed, options, reporter, runLog,
310
613
  }
311
614
  return 0;
312
615
  }
313
- /**
314
- * Run skills on a context and output results.
315
- * If skillName is provided, runs only that skill.
316
- * Otherwise, runs skills from matched triggers in warden.toml.
317
- */
318
- async function runSkills(context, options, reporter) {
616
+ /** Run one or more skills against an already constructed review context. */
617
+ export async function runSkills(context, options, reporter) {
319
618
  const cwd = process.cwd();
320
619
  const startTime = Date.now();
321
620
  // Get API key (optional - SDK can use Claude Code subscription auth)
@@ -357,6 +656,9 @@ async function runSkills(context, options, reporter) {
357
656
  const config = configPath && existsSync(configPath)
358
657
  ? loadWardenConfig(dirname(configPath))
359
658
  : null;
659
+ const defaultModel = resolveCliDefaultModel(config, options.model);
660
+ const defaultAuxiliaryModel = resolveCliDefaultAuxiliaryModel(config);
661
+ const defaultSynthesisModel = resolveCliDefaultSynthesisModel(config);
360
662
  // Determine which triggers/skills to run
361
663
  let skillsToRun;
362
664
  if (options.skill) {
@@ -369,7 +671,20 @@ async function runSkills(context, options, reporter) {
369
671
  const fallbackFilters = defaultIgnorePaths?.length
370
672
  ? { ignorePaths: defaultIgnorePaths }
371
673
  : {};
372
- skillsToRun = [{ skill: options.skill, remote: match?.remote, filters: match?.filters ?? fallbackFilters }];
674
+ skillsToRun = [{
675
+ skill: options.skill,
676
+ remote: match?.remote,
677
+ filters: match?.filters ?? fallbackFilters,
678
+ model: match?.model ?? defaultModel,
679
+ maxTurns: match?.maxTurns ?? config?.defaults?.agent?.maxTurns ?? config?.defaults?.maxTurns,
680
+ runtime: match?.runtime ?? config?.defaults?.runtime ?? 'claude',
681
+ auxiliaryModel: match?.auxiliaryModel ?? defaultAuxiliaryModel,
682
+ synthesisModel: match?.synthesisModel ?? defaultSynthesisModel,
683
+ auxiliaryMaxRetries: match?.auxiliaryMaxRetries ??
684
+ config?.defaults?.auxiliary?.maxRetries ??
685
+ config?.defaults?.auxiliaryMaxRetries,
686
+ verifyFindings: match?.verifyFindings ?? (config?.defaults?.verification?.enabled !== false),
687
+ }];
373
688
  }
374
689
  else if (config) {
375
690
  // Get skills from matched triggers, preserving remote property and filters
@@ -384,7 +699,18 @@ async function runSkills(context, options, reporter) {
384
699
  seen.add(t.skill);
385
700
  return true;
386
701
  })
387
- .map((t) => ({ skill: t.skill, remote: t.remote, filters: t.filters }));
702
+ .map((t) => ({
703
+ skill: t.skill,
704
+ remote: t.remote,
705
+ filters: t.filters,
706
+ model: t.model,
707
+ maxTurns: t.maxTurns,
708
+ runtime: t.runtime,
709
+ auxiliaryModel: t.auxiliaryModel,
710
+ synthesisModel: t.synthesisModel,
711
+ auxiliaryMaxRetries: t.auxiliaryMaxRetries,
712
+ verifyFindings: t.verifyFindings,
713
+ }));
388
714
  }
389
715
  else {
390
716
  skillsToRun = [];
@@ -402,30 +728,54 @@ async function runSkills(context, options, reporter) {
402
728
  return 0;
403
729
  }
404
730
  // Build skill tasks
405
- // Model precedence: defaults.model > CLI flag > WARDEN_MODEL env var > SDK default
731
+ // Model precedence: defaults.agent.model > defaults.model > CLI flag > WARDEN_MODEL env var > SDK default
406
732
  // sdkModel is undefined when no model is explicitly configured (lets SDK use its default).
407
733
  // logModel records what was used for JSONL logs (sentinel when no explicit model).
408
- const sdkModel = config?.defaults?.model ?? options.model ?? process.env['WARDEN_MODEL'];
409
- const logModel = sdkModel ?? MODEL_DEFAULT_SENTINEL;
734
+ const sdkModel = defaultModel;
735
+ const logModel = resolveCliLogModel(config, options.model);
410
736
  const runnerOptions = {
411
737
  apiKey,
412
738
  model: sdkModel,
739
+ runtime: config?.defaults?.runtime ?? 'claude',
740
+ auxiliaryModel: defaultAuxiliaryModel,
741
+ synthesisModel: defaultSynthesisModel,
413
742
  abortController,
414
- maxTurns: config?.defaults?.maxTurns,
743
+ maxTurns: config?.defaults?.agent?.maxTurns ?? config?.defaults?.maxTurns,
415
744
  batchDelayMs: config?.defaults?.batchDelayMs,
416
745
  maxContextFiles: config?.defaults?.chunking?.maxContextFiles,
417
- auxiliaryMaxRetries: config?.defaults?.auxiliaryMaxRetries,
746
+ auxiliaryMaxRetries: config?.defaults?.auxiliary?.maxRetries ??
747
+ config?.defaults?.auxiliaryMaxRetries,
748
+ verifyFindings: config?.defaults?.verification?.enabled !== false,
418
749
  };
419
- const tasks = skillsToRun.map(({ skill, remote, filters }) => ({
750
+ const specs = skillsToRun.map(({ skill, remote, filters, ...skillOptions }) => ({
420
751
  name: skill,
752
+ skill,
753
+ remote,
421
754
  failOn: options.failOn,
422
- resolveSkill: () => resolveSkillAsync(skill, repoPath, {
423
- remote,
424
- offline: options.offline,
425
- }),
426
755
  context: filterContextByPaths(context, filters),
427
- runnerOptions,
756
+ runnerOptions: mergeSkillRunnerOptions(runnerOptions, skillOptions),
428
757
  }));
758
+ let tasks;
759
+ const concurrency = options.parallel ?? DEFAULT_CONCURRENCY;
760
+ try {
761
+ tasks = await createSkillTasks({
762
+ specs,
763
+ repoPath,
764
+ options,
765
+ parallel: concurrency,
766
+ reporter,
767
+ });
768
+ }
769
+ catch (error) {
770
+ const message = error instanceof Error ? error.message : String(error);
771
+ reporter.error(message);
772
+ emitEmptyRunLog(repoPath ?? cwd, options, {
773
+ code: 'unknown',
774
+ message,
775
+ timestamp: new Date().toISOString(),
776
+ });
777
+ return 1;
778
+ }
429
779
  // Open the run's JSONL log before launching skills so `warden runs
430
780
  // follow <runId>` works from a second terminal while the run is live.
431
781
  const runId = generateRunId();
@@ -450,14 +800,14 @@ async function runSkills(context, options, reporter) {
450
800
  startTime,
451
801
  });
452
802
  // Run skills with Ink UI (TTY) or simple console output (non-TTY)
453
- const concurrency = options.parallel ?? DEFAULT_CONCURRENCY;
454
803
  failFastController = options.failFast ? new AbortController() : undefined;
455
804
  const taskOptions = {
456
805
  mode: reporter.mode,
457
806
  verbosity: reporter.verbosity,
458
807
  concurrency,
459
808
  failFastController,
460
- onSkillComplete: (report) => appendSkillToRunLog(runLog, report),
809
+ onChunkComplete: (skillName, chunk) => appendChunkToRunLog(runLog, skillName, chunk),
810
+ onSkillComplete: (report) => appendReportToRunLog(runLog, report),
461
811
  };
462
812
  const results = reporter.mode.isTTY
463
813
  ? await runSkillTasksWithInk(tasks, taskOptions)
@@ -468,9 +818,6 @@ async function runSkills(context, options, reporter) {
468
818
  const processed = processTaskResults(results, options.reportOn, effectiveMinConfidence);
469
819
  return outputResultsAndHandleFixes(processed, options, reporter, runLog, totalDuration, failFastController?.signal.aborted);
470
820
  }
471
- /**
472
- * Run in file mode: analyze specific files.
473
- */
474
821
  async function runFileMode(filePatterns, options, reporter) {
475
822
  const cwd = process.cwd();
476
823
  // Build context from files
@@ -513,9 +860,6 @@ function parseGitRef(ref) {
513
860
  }
514
861
  return { base: ref, head: 'HEAD' };
515
862
  }
516
- /**
517
- * Run in git ref mode: analyze changes from a git ref.
518
- */
519
863
  async function runGitRefMode(gitRef, options, reporter) {
520
864
  const cwd = process.cwd();
521
865
  let repoPath;
@@ -565,9 +909,6 @@ async function runGitRefMode(gitRef, options, reporter) {
565
909
  reporter.contextFiles(pullRequest.files);
566
910
  return runSkills(context, options, reporter);
567
911
  }
568
- /**
569
- * Run in config mode: use warden.toml triggers.
570
- */
571
912
  async function runConfigMode(options, reporter) {
572
913
  const cwd = process.cwd();
573
914
  let repoPath;
@@ -589,13 +930,18 @@ async function runConfigMode(options, reporter) {
589
930
  }
590
931
  // Load config
591
932
  const config = loadWardenConfig(dirname(configPath));
592
- // Build context from local git default to HEAD so only uncommitted changes are analyzed
593
- const statusMessage = options.staged ? 'Analyzing staged changes...' : 'Analyzing uncommitted changes...';
933
+ // Build context from local git. By default, mirror PR-style analysis:
934
+ // compare the configured/default branch merge base to HEAD.
935
+ const localReviewBase = resolveLocalReviewBase(config.defaults?.defaultBranch, repoPath);
936
+ const statusMessage = options.staged
937
+ ? 'Analyzing staged changes...'
938
+ : `Analyzing changes from ${localReviewBase.base} to HEAD...`;
594
939
  reporter.startContext(statusMessage);
595
940
  const context = buildLocalEventContext({
596
- base: 'HEAD',
941
+ base: options.staged ? 'HEAD' : localReviewBase.base,
942
+ head: options.staged ? undefined : 'HEAD',
597
943
  cwd: repoPath,
598
- defaultBranch: config.defaults?.defaultBranch,
944
+ defaultBranch: localReviewBase.defaultBranch,
599
945
  staged: options.staged,
600
946
  });
601
947
  const pullRequest = context.pullRequest;
@@ -610,10 +956,7 @@ async function runConfigMode(options, reporter) {
610
956
  reporter.renderEmptyState('No staged changes found');
611
957
  }
612
958
  else {
613
- const tip = !hasUncommittedChanges(repoPath)
614
- ? 'Specify a git ref: warden HEAD~3 --skill <name>'
615
- : undefined;
616
- reporter.renderEmptyState('No uncommitted changes found', tip);
959
+ reporter.renderEmptyState(`No changes found from ${pullRequest.baseBranch} to HEAD`, 'Use --staged to analyze staged changes');
617
960
  }
618
961
  reporter.blank();
619
962
  }
@@ -659,7 +1002,6 @@ async function runConfigMode(options, reporter) {
659
1002
  if (!apiKey) {
660
1003
  reporter.debug('No API key found. Using Claude Code subscription auth.');
661
1004
  }
662
- // Pre-flight: verify auth will work before starting analysis
663
1005
  try {
664
1006
  verifyAuth({ apiKey });
665
1007
  }
@@ -675,31 +1017,54 @@ async function runConfigMode(options, reporter) {
675
1017
  }
676
1018
  // Build trigger tasks
677
1019
  const effectiveMinConfidence = options.minConfidence ?? config.defaults?.minConfidence ?? 'medium';
678
- const tasks = triggersToRun.map((trigger) => ({
1020
+ const specs = triggersToRun.map((trigger) => ({
679
1021
  name: trigger.name,
680
1022
  displayName: trigger.skill,
1023
+ skill: trigger.skill,
1024
+ remote: trigger.remote,
681
1025
  failOn: trigger.failOn ?? options.failOn,
682
1026
  minConfidence: trigger.minConfidence ?? effectiveMinConfidence,
683
- resolveSkill: () => resolveSkillAsync(trigger.skill, repoPath, {
684
- remote: trigger.remote,
685
- offline: options.offline,
686
- }),
687
1027
  context: filterContextByPaths(context, trigger.filters),
688
1028
  runnerOptions: {
689
1029
  apiKey,
690
1030
  model: trigger.model,
1031
+ runtime: trigger.runtime,
1032
+ auxiliaryModel: trigger.auxiliaryModel,
1033
+ synthesisModel: trigger.synthesisModel,
691
1034
  abortController,
692
1035
  maxTurns: trigger.maxTurns,
693
1036
  maxContextFiles: config.defaults?.chunking?.maxContextFiles,
694
- auxiliaryMaxRetries: config.defaults?.auxiliaryMaxRetries,
1037
+ auxiliaryMaxRetries: trigger.auxiliaryMaxRetries,
1038
+ verifyFindings: trigger.verifyFindings,
695
1039
  },
696
1040
  }));
1041
+ let tasks;
1042
+ const concurrency = options.parallel ?? config.runner?.concurrency ?? DEFAULT_CONCURRENCY;
1043
+ try {
1044
+ tasks = await createSkillTasks({
1045
+ specs,
1046
+ repoPath,
1047
+ options,
1048
+ parallel: concurrency,
1049
+ reporter,
1050
+ });
1051
+ }
1052
+ catch (error) {
1053
+ const message = error instanceof Error ? error.message : String(error);
1054
+ reporter.error(message);
1055
+ emitEmptyRunLog(repoPath, options, {
1056
+ code: 'unknown',
1057
+ message,
1058
+ timestamp: new Date().toISOString(),
1059
+ });
1060
+ return 1;
1061
+ }
697
1062
  // Initialize the run's JSONL log up front so a second terminal can
698
1063
  // `warden runs follow <runId>` while skills are still running.
699
1064
  // Skill records are appended on each completion; the trailing summary
700
1065
  // is appended in `outputResultsAndHandleFixes`.
701
1066
  // Run-level model is the default (ignoring per-trigger overrides); per-skill models are on each report.
702
- const defaultModel = config.defaults?.model ?? options.model ?? process.env['WARDEN_MODEL'] ?? MODEL_DEFAULT_SENTINEL;
1067
+ const defaultModel = resolveCliLogModel(config, options.model);
703
1068
  const runId = generateRunId();
704
1069
  const timestamp = new Date();
705
1070
  const traceId = getTraceId();
@@ -722,14 +1087,14 @@ async function runConfigMode(options, reporter) {
722
1087
  startTime,
723
1088
  });
724
1089
  // Run triggers with Ink UI (TTY) or simple console output (non-TTY)
725
- const concurrency = options.parallel ?? config.runner?.concurrency ?? DEFAULT_CONCURRENCY;
726
1090
  failFastController = options.failFast ? new AbortController() : undefined;
727
1091
  const taskOptions = {
728
1092
  mode: reporter.mode,
729
1093
  verbosity: reporter.verbosity,
730
1094
  concurrency,
731
1095
  failFastController,
732
- onSkillComplete: (report) => appendSkillToRunLog(runLog, report),
1096
+ onChunkComplete: (skillName, chunk) => appendChunkToRunLog(runLog, skillName, chunk),
1097
+ onSkillComplete: (report) => appendReportToRunLog(runLog, report),
733
1098
  };
734
1099
  const results = reporter.mode.isTTY
735
1100
  ? await runSkillTasksWithInk(tasks, taskOptions)
@@ -739,10 +1104,6 @@ async function runConfigMode(options, reporter) {
739
1104
  const processed = processTaskResults(results, options.reportOn, effectiveMinConfidence);
740
1105
  return outputResultsAndHandleFixes(processed, options, reporter, runLog, totalDuration, failFastController?.signal.aborted);
741
1106
  }
742
- /**
743
- * Run in direct skill mode: run a specific skill on uncommitted changes.
744
- * Used when --skill is specified without targets.
745
- */
746
1107
  async function runDirectSkillMode(options, reporter) {
747
1108
  const cwd = process.cwd();
748
1109
  let repoPath;
@@ -757,13 +1118,18 @@ async function runDirectSkillMode(options, reporter) {
757
1118
  // Load config to get defaultBranch if available
758
1119
  const configPath = resolveConfigPath(options, repoPath);
759
1120
  const config = existsSync(configPath) ? loadWardenConfig(dirname(configPath)) : null;
760
- // Build context from local git - compare against HEAD for true uncommitted changes
761
- const statusMessage = options.staged ? 'Analyzing staged changes...' : 'Analyzing uncommitted changes...';
1121
+ // Build context from local git. By default, mirror PR-style analysis:
1122
+ // compare the configured/default branch merge base to HEAD.
1123
+ const localReviewBase = resolveLocalReviewBase(config?.defaults?.defaultBranch, repoPath);
1124
+ const statusMessage = options.staged
1125
+ ? 'Analyzing staged changes...'
1126
+ : `Analyzing changes from ${localReviewBase.base} to HEAD...`;
762
1127
  reporter.startContext(statusMessage);
763
1128
  const context = buildLocalEventContext({
764
- base: 'HEAD',
1129
+ base: options.staged ? 'HEAD' : localReviewBase.base,
1130
+ head: options.staged ? undefined : 'HEAD',
765
1131
  cwd: repoPath,
766
- defaultBranch: config?.defaults?.defaultBranch,
1132
+ defaultBranch: localReviewBase.defaultBranch,
767
1133
  staged: options.staged,
768
1134
  });
769
1135
  const pullRequest = context.pullRequest;
@@ -778,8 +1144,7 @@ async function runDirectSkillMode(options, reporter) {
778
1144
  reporter.renderEmptyState('No staged changes found');
779
1145
  }
780
1146
  else {
781
- const tip = 'Specify a git ref to analyze committed changes: warden main --skill <name>';
782
- reporter.renderEmptyState('No uncommitted changes found', tip);
1147
+ reporter.renderEmptyState(`No changes found from ${pullRequest.baseBranch} to HEAD`, 'Use --staged to analyze staged changes');
783
1148
  }
784
1149
  reporter.blank();
785
1150
  }
@@ -794,7 +1159,7 @@ async function runCommand(options, reporter) {
794
1159
  if (options.staged && targets.length > 0) {
795
1160
  reporter.warning('--staged is ignored when targets are specified');
796
1161
  }
797
- // No targets with --skill → run skill directly on uncommitted changes
1162
+ // No targets with --skill → run skill directly on current branch changes
798
1163
  if (targets.length === 0 && options.skill) {
799
1164
  return runDirectSkillMode(options, reporter);
800
1165
  }
@@ -824,16 +1189,31 @@ async function runCommand(options, reporter) {
824
1189
  // File mode
825
1190
  return runFileMode(filePatterns, options, reporter);
826
1191
  }
1192
+ /** Parse CLI input, dispatch the selected command, and perform shutdown cleanup. */
827
1193
  export async function main() {
828
- const { command, options, setupAppOptions, runsOptions } = parseCliArgs();
1194
+ const { command, options, helpTarget, setupAppOptions, runsOptions } = parseCliArgs();
829
1195
  if (command === 'help') {
830
- showHelp();
1196
+ showHelp(helpTarget);
831
1197
  process.exit(0);
832
1198
  }
833
1199
  if (command === 'version') {
834
1200
  showVersion();
835
1201
  process.exit(0);
836
1202
  }
1203
+ const reporter = createReporter(options);
1204
+ const originalCwd = process.cwd();
1205
+ const invocationCwd = resolveInvocationCwd(originalCwd, options.cwd);
1206
+ if (invocationCwd !== originalCwd) {
1207
+ try {
1208
+ process.chdir(invocationCwd);
1209
+ }
1210
+ catch (error) {
1211
+ const message = error instanceof Error ? error.message : String(error);
1212
+ reporter.error(`Unable to change to ${invocationCwd}: ${message}`);
1213
+ process.exit(1);
1214
+ return;
1215
+ }
1216
+ }
837
1217
  // Load environment variables from .env files at CLI entry point.
838
1218
  // Try repo root first, fall back to cwd if not in a git repo.
839
1219
  const cwd = process.cwd();
@@ -845,8 +1225,6 @@ export async function main() {
845
1225
  // Not in a git repo - use cwd
846
1226
  }
847
1227
  loadEnvFiles(envDir);
848
- // Create reporter based on options
849
- const reporter = createReporter(options);
850
1228
  // Show header (unless JSON output or quiet)
851
1229
  if (!options.json) {
852
1230
  reporter.header();
@@ -876,6 +1254,10 @@ export async function main() {
876
1254
  process.exit(1);
877
1255
  }
878
1256
  return runRuns(runsOptions, options, reporter);
1257
+ case 'build':
1258
+ return runBuild(options, reporter, { abortController, interrupted });
1259
+ case 'improve':
1260
+ return runImprove(options, reporter, { abortController, interrupted });
879
1261
  default:
880
1262
  return runCommand(options, reporter);
881
1263
  }