salmon-loop 0.2.3

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 (655) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +144 -0
  3. package/README.zh-CN.md +144 -0
  4. package/dist/cli/argv/headless-detection.js +60 -0
  5. package/dist/cli/argv/print-mode.js +60 -0
  6. package/dist/cli/authorization/allowlist.js +908 -0
  7. package/dist/cli/authorization/non-interactive.js +166 -0
  8. package/dist/cli/authorization/provider.js +416 -0
  9. package/dist/cli/chat-interface.js +83 -0
  10. package/dist/cli/chat.js +492 -0
  11. package/dist/cli/cli-runtime-context.js +12 -0
  12. package/dist/cli/commander-error-adapter.js +35 -0
  13. package/dist/cli/commander-error-meta.js +13 -0
  14. package/dist/cli/commands/allowlist.js +270 -0
  15. package/dist/cli/commands/chat.js +120 -0
  16. package/dist/cli/commands/config.js +250 -0
  17. package/dist/cli/commands/context.js +57 -0
  18. package/dist/cli/commands/dispatcher.js +53 -0
  19. package/dist/cli/commands/exit.js +9 -0
  20. package/dist/cli/commands/llm-output.js +135 -0
  21. package/dist/cli/commands/log-mode.js +143 -0
  22. package/dist/cli/commands/mode.js +136 -0
  23. package/dist/cli/commands/new.js +18 -0
  24. package/dist/cli/commands/parallel.js +256 -0
  25. package/dist/cli/commands/queue.js +130 -0
  26. package/dist/cli/commands/registry.js +85 -0
  27. package/dist/cli/commands/restore.js +26 -0
  28. package/dist/cli/commands/run/assistant-message.js +14 -0
  29. package/dist/cli/commands/run/config-resolution.js +37 -0
  30. package/dist/cli/commands/run/early-errors.js +108 -0
  31. package/dist/cli/commands/run/execute.js +73 -0
  32. package/dist/cli/commands/run/extensions-resolution.js +22 -0
  33. package/dist/cli/commands/run/handler.js +434 -0
  34. package/dist/cli/commands/run/headless-error-writer.js +182 -0
  35. package/dist/cli/commands/run/instruction-guard.js +24 -0
  36. package/dist/cli/commands/run/loop-params.js +46 -0
  37. package/dist/cli/commands/run/mode.js +8 -0
  38. package/dist/cli/commands/run/parse-options.js +67 -0
  39. package/dist/cli/commands/run/persist-session.js +35 -0
  40. package/dist/cli/commands/run/preflight.js +156 -0
  41. package/dist/cli/commands/run/reporter-factory.js +52 -0
  42. package/dist/cli/commands/run/runtime-llm.js +56 -0
  43. package/dist/cli/commands/run/runtime-options.js +30 -0
  44. package/dist/cli/commands/run/session.js +19 -0
  45. package/dist/cli/commands/run/structured-output.js +106 -0
  46. package/dist/cli/commands/run/types.js +2 -0
  47. package/dist/cli/commands/run/validate-options.js +28 -0
  48. package/dist/cli/commands/run/verbose.js +36 -0
  49. package/dist/cli/commands/run.js +2 -0
  50. package/dist/cli/commands/serve.js +323 -0
  51. package/dist/cli/commands/session.js +77 -0
  52. package/dist/cli/commands/snapshot-interactive.js +165 -0
  53. package/dist/cli/commands/snapshot.js +159 -0
  54. package/dist/cli/commands/status.js +17 -0
  55. package/dist/cli/commands/subagent.js +178 -0
  56. package/dist/cli/commands/subcommand-suggestions.js +63 -0
  57. package/dist/cli/commands/tool-names.js +155 -0
  58. package/dist/cli/commands/types.js +2 -0
  59. package/dist/cli/commands/utils.js +42 -0
  60. package/dist/cli/config.js +16 -0
  61. package/dist/cli/crash-reporter.js +5 -0
  62. package/dist/cli/headless/anthropic-stream-normalized-encoder.js +164 -0
  63. package/dist/cli/headless/anthropic-stream-protocol.js +62 -0
  64. package/dist/cli/headless/json-protocol.js +124 -0
  65. package/dist/cli/headless/native-stream-normalized-encoder.js +206 -0
  66. package/dist/cli/headless/openai-responses-canonical-applier.js +94 -0
  67. package/dist/cli/headless/openai-responses-state.js +294 -0
  68. package/dist/cli/headless/openai-stream-encoder.js +152 -0
  69. package/dist/cli/headless/stdout-writer.js +9 -0
  70. package/dist/cli/headless/stream-json-protocol.js +136 -0
  71. package/dist/cli/index.js +8 -0
  72. package/dist/cli/locales/en.js +409 -0
  73. package/dist/cli/locales/index.js +7 -0
  74. package/dist/cli/program-bootstrap.js +14 -0
  75. package/dist/cli/program-commands.js +106 -0
  76. package/dist/cli/program-options.js +15 -0
  77. package/dist/cli/program-output-mode.js +11 -0
  78. package/dist/cli/program-parse.js +24 -0
  79. package/dist/cli/reporters/anthropic-stream.js +77 -0
  80. package/dist/cli/reporters/base.js +2 -0
  81. package/dist/cli/reporters/json.js +69 -0
  82. package/dist/cli/reporters/openai-stream.js +72 -0
  83. package/dist/cli/reporters/standard.js +226 -0
  84. package/dist/cli/reporters/stderr-log-reporter.js +71 -0
  85. package/dist/cli/reporters/stream-json.js +111 -0
  86. package/dist/cli/run-cli.js +25 -0
  87. package/dist/cli/slash/runtime.js +240 -0
  88. package/dist/cli/ui/App.js +273 -0
  89. package/dist/cli/ui/authorization/bus.js +35 -0
  90. package/dist/cli/ui/components/CommandInput.js +200 -0
  91. package/dist/cli/ui/components/CommandSuggestionList.js +20 -0
  92. package/dist/cli/ui/components/Markdown.js +423 -0
  93. package/dist/cli/ui/components/MessageList.js +34 -0
  94. package/dist/cli/ui/components/StatusBannerLine.js +7 -0
  95. package/dist/cli/ui/components/TodoDrawer.js +60 -0
  96. package/dist/cli/ui/components/WelcomeMessage.js +14 -0
  97. package/dist/cli/ui/components/animations/StretchingThinking.js +51 -0
  98. package/dist/cli/ui/components/animations/ThinkingWave.js +15 -0
  99. package/dist/cli/ui/components/animations/TypeIndicator.js +30 -0
  100. package/dist/cli/ui/components/layout/SplitPane.js +11 -0
  101. package/dist/cli/ui/components/messageList/MessageItem.js +27 -0
  102. package/dist/cli/ui/components/messageList/QueuePreviewList.js +11 -0
  103. package/dist/cli/ui/components/messageList/items/EmphasisMessageItem.js +20 -0
  104. package/dist/cli/ui/components/messageList/items/InterruptMessageItem.js +10 -0
  105. package/dist/cli/ui/components/messageList/items/LightweightMessageItem.js +12 -0
  106. package/dist/cli/ui/components/messageList/items/StandardMessageItem.js +23 -0
  107. package/dist/cli/ui/components/messageList/items/WelcomeMessageItem.js +7 -0
  108. package/dist/cli/ui/components/messageList/messageListLayout.js +27 -0
  109. package/dist/cli/ui/components/messageList/streaming.js +51 -0
  110. package/dist/cli/ui/components/messageList/types.js +2 -0
  111. package/dist/cli/ui/components/messageList/utils.js +7 -0
  112. package/dist/cli/ui/components/sidebar/FileContext.js +8 -0
  113. package/dist/cli/ui/components/sidebar/MissionControl.js +8 -0
  114. package/dist/cli/ui/config.js +59 -0
  115. package/dist/cli/ui/hooks/useCommandLifecycle.js +110 -0
  116. package/dist/cli/ui/hooks/useCommandSuggestions.js +87 -0
  117. package/dist/cli/ui/hooks/useInputHistory.js +57 -0
  118. package/dist/cli/ui/hooks/useLoopEvents.js +382 -0
  119. package/dist/cli/ui/hooks/useLoopState.js +73 -0
  120. package/dist/cli/ui/hooks/useOnionExit.js +31 -0
  121. package/dist/cli/ui/hooks/useTerminalDimensions.js +34 -0
  122. package/dist/cli/ui/index.js +136 -0
  123. package/dist/cli/ui/selection/bus.js +35 -0
  124. package/dist/cli/ui/status/formatStatusBanner.js +8 -0
  125. package/dist/cli/ui/store/context.js +17 -0
  126. package/dist/cli/ui/store/reducer.js +264 -0
  127. package/dist/cli/ui/store/types.js +81 -0
  128. package/dist/cli/ui/styles/theme.js +295 -0
  129. package/dist/cli/ui/types.js +2 -0
  130. package/dist/cli/ui/utils/sanitizer.js +122 -0
  131. package/dist/cli/ui/utils/transcript.js +28 -0
  132. package/dist/cli/utils/asyncQueue.js +125 -0
  133. package/dist/cli/utils/audit-scope.js +10 -0
  134. package/dist/cli/utils/detectors/index.js +38 -0
  135. package/dist/cli/utils/llm-output.js +34 -0
  136. package/dist/cli/utils/outcome-reporter.js +17 -0
  137. package/dist/cli/utils/safe-fs.js +184 -0
  138. package/dist/cli/utils/verify-resolver.js +34 -0
  139. package/dist/cli/utils/worktree-prepare-resolver.js +18 -0
  140. package/dist/core/adapters/fs/atomic-file-writer.js +129 -0
  141. package/dist/core/adapters/fs/file-adapter.js +95 -0
  142. package/dist/core/adapters/fs/filesystem.js +31 -0
  143. package/dist/core/adapters/fs/index.js +5 -0
  144. package/dist/core/adapters/fs/node-fs.js +7 -0
  145. package/dist/core/adapters/fs/readonly-filesystem.js +23 -0
  146. package/dist/core/adapters/git/git-adapter.js +704 -0
  147. package/dist/core/adapters/git/git-runner.js +119 -0
  148. package/dist/core/adapters/git/lock-manager.js +314 -0
  149. package/dist/core/adapters/git/types.js +2 -0
  150. package/dist/core/adapters/path/index.js +2 -0
  151. package/dist/core/adapters/path/path-adapter.js +23 -0
  152. package/dist/core/ast/guard.js +116 -0
  153. package/dist/core/ast/index.js +4 -0
  154. package/dist/core/ast/parser.js +284 -0
  155. package/dist/core/ast/validator.js +46 -0
  156. package/dist/core/backends/salmon-loop/task-executor.js +68 -0
  157. package/dist/core/checkpoint-domain/manifest-store.js +379 -0
  158. package/dist/core/checkpoint-domain/service.js +84 -0
  159. package/dist/core/checkpoint-domain/types.js +2 -0
  160. package/dist/core/config/defaults.js +50 -0
  161. package/dist/core/config/errors.js +11 -0
  162. package/dist/core/config/file-format.js +108 -0
  163. package/dist/core/config/index.js +7 -0
  164. package/dist/core/config/limits.js +77 -0
  165. package/dist/core/config/load.js +34 -0
  166. package/dist/core/config/normalize.js +35 -0
  167. package/dist/core/config/paths.js +20 -0
  168. package/dist/core/config/redact.js +16 -0
  169. package/dist/core/config/resolve-env.js +43 -0
  170. package/dist/core/config/resolve-llm.js +130 -0
  171. package/dist/core/config/resolve.js +68 -0
  172. package/dist/core/config/resolvers/ast-validation.js +8 -0
  173. package/dist/core/config/resolvers/context.js +21 -0
  174. package/dist/core/config/resolvers/observability.js +45 -0
  175. package/dist/core/config/resolvers/output.js +8 -0
  176. package/dist/core/config/resolvers/permission-mode.js +6 -0
  177. package/dist/core/config/resolvers/security.js +14 -0
  178. package/dist/core/config/resolvers/server.js +36 -0
  179. package/dist/core/config/resolvers/tool-authorization.js +39 -0
  180. package/dist/core/config/resolvers/ui.js +26 -0
  181. package/dist/core/config/types/config-file.js +2 -0
  182. package/dist/core/config/types/primitives.js +9 -0
  183. package/dist/core/config/types/resolved.js +2 -0
  184. package/dist/core/config/types.js +4 -0
  185. package/dist/core/config/validate.js +852 -0
  186. package/dist/core/context/assembly/default-prompt-assembler.js +7 -0
  187. package/dist/core/context/assembly/prompt-assembler.js +2 -0
  188. package/dist/core/context/ast/import-extractor.js +28 -0
  189. package/dist/core/context/ast/module-resolver.js +61 -0
  190. package/dist/core/context/ast/source-outline.js +25 -0
  191. package/dist/core/context/audit-constants.js +23 -0
  192. package/dist/core/context/audit.js +54 -0
  193. package/dist/core/context/budget/dynamic-adjuster.js +149 -0
  194. package/dist/core/context/budget/example-integration.js +49 -0
  195. package/dist/core/context/budget/integration.js +93 -0
  196. package/dist/core/context/builder.js +289 -0
  197. package/dist/core/context/cache/errors.js +16 -0
  198. package/dist/core/context/cache/incremental-updater.js +131 -0
  199. package/dist/core/context/cache/index.js +25 -0
  200. package/dist/core/context/cache/path-resolver.js +127 -0
  201. package/dist/core/context/cache/prompt-caching.js +207 -0
  202. package/dist/core/context/cache/store-factory.js +63 -0
  203. package/dist/core/context/cache/store.js +193 -0
  204. package/dist/core/context/cache/types.js +15 -0
  205. package/dist/core/context/compression/js-like-comments.js +139 -0
  206. package/dist/core/context/compression/smart-compress.js +61 -0
  207. package/dist/core/context/compression/whitespace.js +26 -0
  208. package/dist/core/context/dependencies.js +102 -0
  209. package/dist/core/context/effectiveness/index.js +25 -0
  210. package/dist/core/context/effectiveness/tracker.js +253 -0
  211. package/dist/core/context/effectiveness/types.js +15 -0
  212. package/dist/core/context/formatters/index.js +7 -0
  213. package/dist/core/context/formatters/json-converter.js +662 -0
  214. package/dist/core/context/formatters/types.js +6 -0
  215. package/dist/core/context/formatters/xml-context.js +296 -0
  216. package/dist/core/context/gatherers/architecture-gatherer.js +75 -0
  217. package/dist/core/context/gatherers/artifact-gatherer.js +53 -0
  218. package/dist/core/context/gatherers/ast-gatherer.js +370 -0
  219. package/dist/core/context/gatherers/ghost-dependency-gatherer.js +46 -0
  220. package/dist/core/context/gatherers/git-diff-gatherer.js +91 -0
  221. package/dist/core/context/gatherers/git-history-gatherer.js +57 -0
  222. package/dist/core/context/gatherers/knowledge-gatherer.js +101 -0
  223. package/dist/core/context/gatherers/metadata-gatherer.js +59 -0
  224. package/dist/core/context/gatherers/primary-text-gatherer.js +36 -0
  225. package/dist/core/context/gatherers/ripgrep-gatherer.js +104 -0
  226. package/dist/core/context/hash.js +52 -0
  227. package/dist/core/context/index.js +3 -0
  228. package/dist/core/context/keywords.js +179 -0
  229. package/dist/core/context/policies/budget-policy.js +36 -0
  230. package/dist/core/context/policies/pack-until-full.js +419 -0
  231. package/dist/core/context/scoring/relevance.js +191 -0
  232. package/dist/core/context/service-deps.js +32 -0
  233. package/dist/core/context/service-helpers.js +32 -0
  234. package/dist/core/context/service.js +265 -0
  235. package/dist/core/context/steps/context-budget.js +157 -0
  236. package/dist/core/context/steps/context-gather.js +71 -0
  237. package/dist/core/context/steps/context-primary.js +19 -0
  238. package/dist/core/context/steps/context-promotion.js +78 -0
  239. package/dist/core/context/steps/context-targets.js +85 -0
  240. package/dist/core/context/steps/types.js +2 -0
  241. package/dist/core/context/summarization/index.js +27 -0
  242. package/dist/core/context/summarization/prompts.js +80 -0
  243. package/dist/core/context/summarization/summarizer.js +377 -0
  244. package/dist/core/context/summarization/types.js +29 -0
  245. package/dist/core/context/targeting/churn-policy.js +27 -0
  246. package/dist/core/context/targeting/target-resolver.js +491 -0
  247. package/dist/core/context/token/adaptive-budget.js +364 -0
  248. package/dist/core/context/token/cache.js +163 -0
  249. package/dist/core/context/token/counter.js +190 -0
  250. package/dist/core/context/token/encoding-registry.js +173 -0
  251. package/dist/core/context/token/index.js +31 -0
  252. package/dist/core/context/token/token-budget.js +213 -0
  253. package/dist/core/context/token/types.js +10 -0
  254. package/dist/core/context/truncation/index.js +23 -0
  255. package/dist/core/context/truncation/semantic-truncator.js +103 -0
  256. package/dist/core/context/truncation/strategies/error-stack.js +94 -0
  257. package/dist/core/context/truncation/strategies/generic.js +48 -0
  258. package/dist/core/context/truncation/strategies/git-diff.js +99 -0
  259. package/dist/core/context/truncation/strategies/index.js +10 -0
  260. package/dist/core/context/truncation/strategies/json.js +142 -0
  261. package/dist/core/context/truncation/strategies/log.js +131 -0
  262. package/dist/core/context/truncation/strategies/test-result.js +140 -0
  263. package/dist/core/context/truncation/type-detector.js +133 -0
  264. package/dist/core/context/truncation/types.js +16 -0
  265. package/dist/core/context/types.js +2 -0
  266. package/dist/core/extensions/index.js +118 -0
  267. package/dist/core/extensions/load.js +36 -0
  268. package/dist/core/extensions/merge.js +29 -0
  269. package/dist/core/extensions/paths.js +40 -0
  270. package/dist/core/extensions/redact.js +37 -0
  271. package/dist/core/extensions/schemas.js +70 -0
  272. package/dist/core/extensions/types.js +2 -0
  273. package/dist/core/facades/cli-authorization-allowlist.js +3 -0
  274. package/dist/core/facades/cli-authorization-non-interactive.js +3 -0
  275. package/dist/core/facades/cli-authorization-provider.js +2 -0
  276. package/dist/core/facades/cli-chat.js +11 -0
  277. package/dist/core/facades/cli-command-allowlist.js +3 -0
  278. package/dist/core/facades/cli-command-chat.js +8 -0
  279. package/dist/core/facades/cli-command-checkpoint.js +3 -0
  280. package/dist/core/facades/cli-command-config.js +10 -0
  281. package/dist/core/facades/cli-command-dispatcher.js +2 -0
  282. package/dist/core/facades/cli-command-parallel.js +8 -0
  283. package/dist/core/facades/cli-command-session.js +2 -0
  284. package/dist/core/facades/cli-command-tool-names.js +6 -0
  285. package/dist/core/facades/cli-context.js +8 -0
  286. package/dist/core/facades/cli-headless.js +3 -0
  287. package/dist/core/facades/cli-observability.js +3 -0
  288. package/dist/core/facades/cli-program-bootstrap.js +2 -0
  289. package/dist/core/facades/cli-reporters.js +5 -0
  290. package/dist/core/facades/cli-run-execute.js +3 -0
  291. package/dist/core/facades/cli-run-handler.js +7 -0
  292. package/dist/core/facades/cli-run-headless-error-writer.js +2 -0
  293. package/dist/core/facades/cli-run-loop-params.js +2 -0
  294. package/dist/core/facades/cli-run-persist-session.js +2 -0
  295. package/dist/core/facades/cli-run-runtime-llm.js +5 -0
  296. package/dist/core/facades/cli-serve.js +21 -0
  297. package/dist/core/facades/cli-slash-runtime.js +9 -0
  298. package/dist/core/facades/cli-subagent.js +2 -0
  299. package/dist/core/facades/cli-ui.js +5 -0
  300. package/dist/core/facades/cli-utils-llm-output.js +3 -0
  301. package/dist/core/facades/cli-utils-path.js +2 -0
  302. package/dist/core/facades/cli-utils-worktree.js +2 -0
  303. package/dist/core/failure/diagnostics.js +221 -0
  304. package/dist/core/feedback/index.js +28 -0
  305. package/dist/core/feedback/parsers.js +59 -0
  306. package/dist/core/feedback/patterns.js +26 -0
  307. package/dist/core/feedback/types.js +2 -0
  308. package/dist/core/grizzco/domain/grizzco-types.js +41 -0
  309. package/dist/core/grizzco/dsl/DecisionEngine.js +149 -0
  310. package/dist/core/grizzco/dsl/MicroTaskRunner.js +39 -0
  311. package/dist/core/grizzco/dsl/llm-strategy.js +80 -0
  312. package/dist/core/grizzco/dsl/strategies.js +69 -0
  313. package/dist/core/grizzco/dsl/types.js +2 -0
  314. package/dist/core/grizzco/engine/observability/event-adapter.js +41 -0
  315. package/dist/core/grizzco/engine/observability/index.js +3 -0
  316. package/dist/core/grizzco/engine/observability/loop-telemetry.js +51 -0
  317. package/dist/core/grizzco/engine/outcome/index.js +2 -0
  318. package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +167 -0
  319. package/dist/core/grizzco/engine/pipeline/pipeline.js +335 -0
  320. package/dist/core/grizzco/engine/pipeline/types.js +2 -0
  321. package/dist/core/grizzco/engine/transaction/attempt-failure.js +242 -0
  322. package/dist/core/grizzco/engine/transaction/authorization-summary.js +44 -0
  323. package/dist/core/grizzco/engine/transaction/index.js +3 -0
  324. package/dist/core/grizzco/engine/transaction/report-mapper.js +50 -0
  325. package/dist/core/grizzco/engine/transaction/retry-policy.js +19 -0
  326. package/dist/core/grizzco/engine/transaction/runner-builder.js +45 -0
  327. package/dist/core/grizzco/engine/transaction/session.js +58 -0
  328. package/dist/core/grizzco/engine/transaction/transaction-runner.js +193 -0
  329. package/dist/core/grizzco/engine/transaction/types.js +2 -0
  330. package/dist/core/grizzco/execution/Executor.js +58 -0
  331. package/dist/core/grizzco/execution/RejectionManager.js +71 -0
  332. package/dist/core/grizzco/execution/WorkerFactory.js +31 -0
  333. package/dist/core/grizzco/flows/SalmonLoopFlow.js +102 -0
  334. package/dist/core/grizzco/runtime/apply-back-runtime.js +136 -0
  335. package/dist/core/grizzco/runtime/apply-back-utils.js +13 -0
  336. package/dist/core/grizzco/runtime/host/host-runner.js +99 -0
  337. package/dist/core/grizzco/runtime/host/index.js +2 -0
  338. package/dist/core/grizzco/runtime/host/types.js +2 -0
  339. package/dist/core/grizzco/services/CachedService.js +42 -0
  340. package/dist/core/grizzco/services/implementations/default/GitConfigService.js +38 -0
  341. package/dist/core/grizzco/services/implementations/mock/MockLockService.js +11 -0
  342. package/dist/core/grizzco/services/implementations/mock/MockUserQuotaService.js +11 -0
  343. package/dist/core/grizzco/services/registry.js +30 -0
  344. package/dist/core/grizzco/services/types.js +2 -0
  345. package/dist/core/grizzco/steps/answer.js +75 -0
  346. package/dist/core/grizzco/steps/apply-back.js +46 -0
  347. package/dist/core/grizzco/steps/apply.js +136 -0
  348. package/dist/core/grizzco/steps/ast-validate.js +37 -0
  349. package/dist/core/grizzco/steps/audit.js +311 -0
  350. package/dist/core/grizzco/steps/context.js +74 -0
  351. package/dist/core/grizzco/steps/display-answer.js +6 -0
  352. package/dist/core/grizzco/steps/display-report.js +158 -0
  353. package/dist/core/grizzco/steps/display-research.js +6 -0
  354. package/dist/core/grizzco/steps/displayReview.js +6 -0
  355. package/dist/core/grizzco/steps/explore.js +245 -0
  356. package/dist/core/grizzco/steps/extractIssues.js +27 -0
  357. package/dist/core/grizzco/steps/generateFixPlan.js +13 -0
  358. package/dist/core/grizzco/steps/generateReview.js +71 -0
  359. package/dist/core/grizzco/steps/patch.js +220 -0
  360. package/dist/core/grizzco/steps/plan.js +191 -0
  361. package/dist/core/grizzco/steps/preflight.js +93 -0
  362. package/dist/core/grizzco/steps/prepare-deps.js +49 -0
  363. package/dist/core/grizzco/steps/read-only-shrink.js +4 -0
  364. package/dist/core/grizzco/steps/research.js +188 -0
  365. package/dist/core/grizzco/steps/rollback.js +138 -0
  366. package/dist/core/grizzco/steps/shrink.js +64 -0
  367. package/dist/core/grizzco/steps/validate.js +40 -0
  368. package/dist/core/grizzco/steps/verify.js +136 -0
  369. package/dist/core/grizzco/validation/AstValidationService.js +133 -0
  370. package/dist/core/grizzco/validation/ContextValidator.js +17 -0
  371. package/dist/core/grizzco/validation/ast-validation-policy.js +11 -0
  372. package/dist/core/grizzco/workers/direct-write-worker.js +44 -0
  373. package/dist/core/grizzco/workers/git-apply-worker.js +75 -0
  374. package/dist/core/grizzco/workers/i-merge-worker.js +2 -0
  375. package/dist/core/grizzco/workers/mm-three-way-worker.js +117 -0
  376. package/dist/core/grizzco/workers/no-op-worker.js +18 -0
  377. package/dist/core/grizzco/workers/overwrite-binary-worker.js +29 -0
  378. package/dist/core/grizzco/workers/strata-sync-worker.js +69 -0
  379. package/dist/core/grizzco/workers/three-way-merge-worker.js +84 -0
  380. package/dist/core/grizzco/workers/three-way-staged-worker.js +93 -0
  381. package/dist/core/grizzco/workers/union-merge-worker.js +71 -0
  382. package/dist/core/history/input-history.js +55 -0
  383. package/dist/core/intent/chat-intent.js +250 -0
  384. package/dist/core/interaction/events/bus.js +52 -0
  385. package/dist/core/interaction/model/events.js +2 -0
  386. package/dist/core/interaction/model/index.js +3 -0
  387. package/dist/core/interaction/model/task-state.js +9 -0
  388. package/dist/core/interaction/model/transition-policy.js +50 -0
  389. package/dist/core/interaction/model/types.js +2 -0
  390. package/dist/core/interaction/orchestration/facade.js +190 -0
  391. package/dist/core/interaction/orchestration/index.js +2 -0
  392. package/dist/core/interaction/orchestration/store.js +32 -0
  393. package/dist/core/interaction/sync/task-sync-engine.js +57 -0
  394. package/dist/core/interaction/turn-stop-reason.js +27 -0
  395. package/dist/core/language-support/index.js +3 -0
  396. package/dist/core/language-support/orchestrator.js +37 -0
  397. package/dist/core/language-support/strategies/extension-candidate-strategy.js +27 -0
  398. package/dist/core/language-support/strategies/index.js +3 -0
  399. package/dist/core/language-support/strategies/language-query-strategy.js +26 -0
  400. package/dist/core/llm/ai-sdk/chat-executor.js +88 -0
  401. package/dist/core/llm/ai-sdk/langfuse-headers.js +28 -0
  402. package/dist/core/llm/ai-sdk/message-mapper.js +240 -0
  403. package/dist/core/llm/ai-sdk/observation-context.js +16 -0
  404. package/dist/core/llm/ai-sdk/provider-factory.js +29 -0
  405. package/dist/core/llm/ai-sdk/request-params.js +18 -0
  406. package/dist/core/llm/ai-sdk/request-runtime.js +168 -0
  407. package/dist/core/llm/ai-sdk/result-mapper.js +31 -0
  408. package/dist/core/llm/ai-sdk/retry-classifier.js +82 -0
  409. package/dist/core/llm/ai-sdk/retry-executor.js +38 -0
  410. package/dist/core/llm/ai-sdk.js +92 -0
  411. package/dist/core/llm/audit.js +2 -0
  412. package/dist/core/llm/base-url.js +18 -0
  413. package/dist/core/llm/contracts/repair.js +68 -0
  414. package/dist/core/llm/errors.js +172 -0
  415. package/dist/core/llm/factory.js +21 -0
  416. package/dist/core/llm/http/index.js +2 -0
  417. package/dist/core/llm/index.js +6 -0
  418. package/dist/core/llm/message-composition.js +25 -0
  419. package/dist/core/llm/openai.js +69 -0
  420. package/dist/core/llm/output-policy.js +192 -0
  421. package/dist/core/llm/phase-router.js +55 -0
  422. package/dist/core/llm/redact.js +37 -0
  423. package/dist/core/llm/registry.js +81 -0
  424. package/dist/core/llm/retry-utils.js +114 -0
  425. package/dist/core/llm/stream-utils.js +87 -0
  426. package/dist/core/llm/utils.js +82 -0
  427. package/dist/core/observability/audit-file.js +199 -0
  428. package/dist/core/observability/audit-trail.js +125 -0
  429. package/dist/core/observability/authorization-decisions.js +54 -0
  430. package/dist/core/observability/debug-artifacts.js +61 -0
  431. package/dist/core/observability/error-envelope.js +63 -0
  432. package/dist/core/observability/error-mapping.js +271 -0
  433. package/dist/core/observability/ignored-error.js +6 -0
  434. package/dist/core/observability/logger.js +457 -0
  435. package/dist/core/observability/loop-event-reporter.js +46 -0
  436. package/dist/core/observability/monitor.js +240 -0
  437. package/dist/core/observability/run-outcome-reporter.js +15 -0
  438. package/dist/core/observability/token-usage.js +36 -0
  439. package/dist/core/observability/ui-log-sanitize.js +35 -0
  440. package/dist/core/patch/aggregator.js +93 -0
  441. package/dist/core/patch/diff.js +298 -0
  442. package/dist/core/permission-gate/default-gate.js +115 -0
  443. package/dist/core/permission-gate/gate.js +2 -0
  444. package/dist/core/permission-gate/types.js +2 -0
  445. package/dist/core/plan/index.js +2 -0
  446. package/dist/core/plan/manager.js +123 -0
  447. package/dist/core/plan/markdown-editor.js +238 -0
  448. package/dist/core/plan/storage.js +75 -0
  449. package/dist/core/plan/types.js +2 -0
  450. package/dist/core/plugin/interface.js +2 -0
  451. package/dist/core/plugin/loader.js +130 -0
  452. package/dist/core/plugin/registry.js +90 -0
  453. package/dist/core/plugin/validator.js +98 -0
  454. package/dist/core/prompts/registry.js +189 -0
  455. package/dist/core/prompts/runtime.js +69 -0
  456. package/dist/core/prompts/schema.js +2 -0
  457. package/dist/core/prompts/templates/phases/explore_user.hbs +26 -0
  458. package/dist/core/prompts/templates/phases/patch_user.hbs +57 -0
  459. package/dist/core/prompts/templates/phases/plan_user.hbs +33 -0
  460. package/dist/core/prompts/templates/system/_context_json_legend.hbs +21 -0
  461. package/dist/core/prompts/templates/system/_tool_defs.hbs +60 -0
  462. package/dist/core/prompts/templates/system/explore_system.hbs +26 -0
  463. package/dist/core/prompts/templates/system/main_system.hbs +18 -0
  464. package/dist/core/prompts/templates/system/patch_system.hbs +10 -0
  465. package/dist/core/prompts/templates/system/plan_system.hbs +1 -0
  466. package/dist/core/prompts/templates/system/reflection.hbs +39 -0
  467. package/dist/core/protocols/a2a/agent-card.js +30 -0
  468. package/dist/core/protocols/a2a/mapper.js +14 -0
  469. package/dist/core/protocols/a2a/sdk/auth-middleware.js +31 -0
  470. package/dist/core/protocols/a2a/sdk/executor.js +301 -0
  471. package/dist/core/protocols/a2a/sdk/server.js +24 -0
  472. package/dist/core/protocols/a2a/task-projection.js +45 -0
  473. package/dist/core/protocols/acp/acp-command-runner.js +204 -0
  474. package/dist/core/protocols/acp/acp-filesystem.js +43 -0
  475. package/dist/core/protocols/acp/checkpoint-meta.js +2 -0
  476. package/dist/core/protocols/acp/formal-agent.js +1201 -0
  477. package/dist/core/protocols/acp/handlers.js +51 -0
  478. package/dist/core/protocols/acp/permission-provider.js +122 -0
  479. package/dist/core/protocols/acp/stdio-server.js +116 -0
  480. package/dist/core/reflection/engine.js +55 -0
  481. package/dist/core/reflection/types.js +2 -0
  482. package/dist/core/runtime/agent-server-runtime.js +88 -0
  483. package/dist/core/runtime/bun-runtime.js +26 -0
  484. package/dist/core/runtime/command-runner-context.js +16 -0
  485. package/dist/core/runtime/exit-codes.js +11 -0
  486. package/dist/core/runtime/fastify-fetch-bridge.js +51 -0
  487. package/dist/core/runtime/fastify-server-bundle.js +26 -0
  488. package/dist/core/runtime/initialize.js +132 -0
  489. package/dist/core/runtime/loop-finalize.js +71 -0
  490. package/dist/core/runtime/loop-run-lifecycle.js +73 -0
  491. package/dist/core/runtime/loop-run-reporter.js +19 -0
  492. package/dist/core/runtime/loop-runtime-config.js +26 -0
  493. package/dist/core/runtime/loop-session-runner.js +30 -0
  494. package/dist/core/runtime/loop.js +84 -0
  495. package/dist/core/runtime/paths.js +84 -0
  496. package/dist/core/runtime/process-runner.js +16 -0
  497. package/dist/core/runtime/process-types.js +2 -0
  498. package/dist/core/runtime/semaphore.js +41 -0
  499. package/dist/core/runtime/sidecar-fastify-plugin.js +35 -0
  500. package/dist/core/runtime/sidecar-paths.js +47 -0
  501. package/dist/core/runtime/sidecar-route-catalog.js +103 -0
  502. package/dist/core/runtime/spawn-command.js +392 -0
  503. package/dist/core/runtime/spawn-interactive.js +71 -0
  504. package/dist/core/security/redaction.js +160 -0
  505. package/dist/core/session/compression.js +323 -0
  506. package/dist/core/session/flow.js +85 -0
  507. package/dist/core/session/manager.js +313 -0
  508. package/dist/core/session/pruning-strategy.js +153 -0
  509. package/dist/core/session/session-context-builder.js +122 -0
  510. package/dist/core/session/summary-sync.js +82 -0
  511. package/dist/core/session/token-tracker.js +82 -0
  512. package/dist/core/session/types.js +2 -0
  513. package/dist/core/skills/bridge.js +33 -0
  514. package/dist/core/skills/index.js +8 -0
  515. package/dist/core/skills/loader.js +80 -0
  516. package/dist/core/skills/parser.js +66 -0
  517. package/dist/core/skills/runtime/MicroTaskRunner.js +102 -0
  518. package/dist/core/skills/runtime/SkillRunner.js +108 -0
  519. package/dist/core/skills/strategy.js +29 -0
  520. package/dist/core/skills/types.js +2 -0
  521. package/dist/core/slash/index.js +6 -0
  522. package/dist/core/slash/parser.js +33 -0
  523. package/dist/core/slash/registry.js +78 -0
  524. package/dist/core/slash/router.js +76 -0
  525. package/dist/core/slash/steps/slash-decide.js +19 -0
  526. package/dist/core/slash/steps/slash-execute.js +73 -0
  527. package/dist/core/slash/steps/types.js +2 -0
  528. package/dist/core/slash/strategy.js +33 -0
  529. package/dist/core/slash/types.js +2 -0
  530. package/dist/core/strata/checkpoint/manager.js +492 -0
  531. package/dist/core/strata/checkpoint/snapshot-audit.js +88 -0
  532. package/dist/core/strata/checkpoint/snapshot-create.js +79 -0
  533. package/dist/core/strata/checkpoint/snapshot-write-tree.js +72 -0
  534. package/dist/core/strata/engine/shadow-merge-engine.js +394 -0
  535. package/dist/core/strata/index.js +15 -0
  536. package/dist/core/strata/interaction/content-guardian.js +59 -0
  537. package/dist/core/strata/interaction/file-system-provider.js +89 -0
  538. package/dist/core/strata/layers/file-state-resolver.js +157 -0
  539. package/dist/core/strata/layers/immutable-git-layer.js +42 -0
  540. package/dist/core/strata/layers/shadow-driver/copy-backend.js +114 -0
  541. package/dist/core/strata/layers/shadow-driver/env.js +29 -0
  542. package/dist/core/strata/layers/shadow-driver/error-classifier.js +41 -0
  543. package/dist/core/strata/layers/shadow-driver/index.js +17 -0
  544. package/dist/core/strata/layers/shadow-driver/readonly-lock.js +221 -0
  545. package/dist/core/strata/layers/shadow-driver/shadow-driver.js +234 -0
  546. package/dist/core/strata/layers/shadow-driver/strategy.js +86 -0
  547. package/dist/core/strata/layers/sidecar-layer.js +96 -0
  548. package/dist/core/strata/layers/worktree.js +240 -0
  549. package/dist/core/strata/runtime/environment.js +377 -0
  550. package/dist/core/strata/runtime/synchronizer.js +819 -0
  551. package/dist/core/strata/types.js +46 -0
  552. package/dist/core/streaming/canonical/canonical-responses-event-emitter.js +326 -0
  553. package/dist/core/streaming/canonical/function-call-item-id.js +13 -0
  554. package/dist/core/streaming/canonical/parts-from-llm-stream-chunk.js +54 -0
  555. package/dist/core/streaming/canonical/responses-event-emitter.js +127 -0
  556. package/dist/core/streaming/canonical/responses-events.js +2 -0
  557. package/dist/core/streaming/normalized-events.js +9 -0
  558. package/dist/core/streaming/normalized-from-text.js +47 -0
  559. package/dist/core/streaming/stream-assembler.js +347 -0
  560. package/dist/core/structured-output/index.js +3 -0
  561. package/dist/core/structured-output/json-extract.js +70 -0
  562. package/dist/core/structured-output/json-schema-validator.js +90 -0
  563. package/dist/core/structured-output/types.js +2 -0
  564. package/dist/core/sub-agent/artifacts/store.js +141 -0
  565. package/dist/core/sub-agent/artifacts/types.js +2 -0
  566. package/dist/core/sub-agent/controller.js +69 -0
  567. package/dist/core/sub-agent/core/loop.js +79 -0
  568. package/dist/core/sub-agent/core/manager.js +246 -0
  569. package/dist/core/sub-agent/registry-defaults.js +52 -0
  570. package/dist/core/sub-agent/registry.js +35 -0
  571. package/dist/core/sub-agent/tools/task-spawn.js +29 -0
  572. package/dist/core/sub-agent/types.js +23 -0
  573. package/dist/core/target-runtime/command-resolver.js +42 -0
  574. package/dist/core/target-runtime/index.js +3 -0
  575. package/dist/core/target-runtime/profile.js +73 -0
  576. package/dist/core/testgen/detector.js +17 -0
  577. package/dist/core/testgen/index.js +38 -0
  578. package/dist/core/testgen/templates.js +46 -0
  579. package/dist/core/tools/audit.js +140 -0
  580. package/dist/core/tools/authorization/types.js +2 -0
  581. package/dist/core/tools/budget.js +118 -0
  582. package/dist/core/tools/builtin/artifact.js +29 -0
  583. package/dist/core/tools/builtin/ast-grep.js +107 -0
  584. package/dist/core/tools/builtin/ast.js +62 -0
  585. package/dist/core/tools/builtin/code-search/backends/powershell.js +84 -0
  586. package/dist/core/tools/builtin/code-search/backends/rg.js +85 -0
  587. package/dist/core/tools/builtin/code-search/executor.js +87 -0
  588. package/dist/core/tools/builtin/code-search/parse/plain-grep.js +59 -0
  589. package/dist/core/tools/builtin/code-search/parse/rg-json.js +31 -0
  590. package/dist/core/tools/builtin/code-search/spec.js +82 -0
  591. package/dist/core/tools/builtin/fs.js +243 -0
  592. package/dist/core/tools/builtin/git.js +118 -0
  593. package/dist/core/tools/builtin/index.js +80 -0
  594. package/dist/core/tools/builtin/interaction.js +120 -0
  595. package/dist/core/tools/builtin/knowledge.js +98 -0
  596. package/dist/core/tools/builtin/plan.js +148 -0
  597. package/dist/core/tools/builtin/proposal.js +207 -0
  598. package/dist/core/tools/builtin/shell.js +71 -0
  599. package/dist/core/tools/builtin/verify.js +41 -0
  600. package/dist/core/tools/capability/executor.js +84 -0
  601. package/dist/core/tools/capability/runner.js +50 -0
  602. package/dist/core/tools/capability/types.js +2 -0
  603. package/dist/core/tools/dispatcher.js +80 -0
  604. package/dist/core/tools/headless-payload.js +37 -0
  605. package/dist/core/tools/loader.js +100 -0
  606. package/dist/core/tools/mapper.js +142 -0
  607. package/dist/core/tools/mcp/client.js +308 -0
  608. package/dist/core/tools/mcp/loader.js +110 -0
  609. package/dist/core/tools/mcp/schema.js +54 -0
  610. package/dist/core/tools/mcp/streamable-http.js +101 -0
  611. package/dist/core/tools/mcp/types.js +26 -0
  612. package/dist/core/tools/parallel/isolation.js +25 -0
  613. package/dist/core/tools/parallel/lock-manager.js +124 -0
  614. package/dist/core/tools/parallel/persistence.js +126 -0
  615. package/dist/core/tools/parallel/plan-builder.js +66 -0
  616. package/dist/core/tools/parallel/plan.js +2 -0
  617. package/dist/core/tools/parallel/refs.js +7 -0
  618. package/dist/core/tools/parallel/resolve-args.js +50 -0
  619. package/dist/core/tools/parallel/resource-helpers.js +35 -0
  620. package/dist/core/tools/parallel/resources.js +2 -0
  621. package/dist/core/tools/parallel/scheduler.js +372 -0
  622. package/dist/core/tools/parser.js +89 -0
  623. package/dist/core/tools/permissions/permission-rules.js +503 -0
  624. package/dist/core/tools/plugins/loader.js +102 -0
  625. package/dist/core/tools/policy.js +87 -0
  626. package/dist/core/tools/registry.js +29 -0
  627. package/dist/core/tools/router.js +514 -0
  628. package/dist/core/tools/sanitize.js +78 -0
  629. package/dist/core/tools/schema-utils.js +71 -0
  630. package/dist/core/tools/session.js +1105 -0
  631. package/dist/core/tools/streaming/ToolCallAccumulator.js +64 -0
  632. package/dist/core/tools/types.js +2 -0
  633. package/dist/core/types/authorization.js +2 -0
  634. package/dist/core/types/context.js +2 -0
  635. package/dist/core/types/errors.js +29 -0
  636. package/dist/core/types/execution.js +65 -0
  637. package/dist/core/types/index.js +9 -0
  638. package/dist/core/types/llm.js +9 -0
  639. package/dist/core/types/loop.js +2 -0
  640. package/dist/core/types/planning.js +2 -0
  641. package/dist/core/types/runtime.js +2 -0
  642. package/dist/core/types/usage.js +2 -0
  643. package/dist/core/ui/kaomoji.js +5 -0
  644. package/dist/core/utils/path.js +116 -0
  645. package/dist/core/utils/platform-shell.js +10 -0
  646. package/dist/core/utils/sanitizer.js +107 -0
  647. package/dist/core/verification/runner.js +265 -0
  648. package/dist/integrations/langfuse/litellm-langfuse-outcome-reporter.js +272 -0
  649. package/dist/integrations/langfuse/outcome-proxy.js +68 -0
  650. package/dist/interfaces/cli/task-runner.js +11 -0
  651. package/dist/languages/typescript/index.js +178 -0
  652. package/dist/locales/en.js +679 -0
  653. package/dist/locales/index.js +11 -0
  654. package/dist/utils/eol.js +35 -0
  655. package/package.json +153 -0
@@ -0,0 +1,908 @@
1
+ import * as crypto from 'crypto';
2
+ import * as os from 'os';
3
+ import * as path from 'path';
4
+ import { LIMITS, getLogger, } from '../../core/facades/cli-authorization-allowlist.js';
5
+ import { text } from '../locales/index.js';
6
+ import { copyFile, mkdirp, openFile, readdir, readFileUtf8, realpath, rename, stat, unlink, writeFileUtf8, } from '../utils/safe-fs.js';
7
+ const createEmptyAllowlist = () => ({ version: 1, tools: {} });
8
+ const CACHE_VERSION = 3;
9
+ const allowlistLoadStats = {
10
+ repo: { total: 0, success: 0, failure: 0 },
11
+ user: { total: 0, success: 0, failure: 0 },
12
+ };
13
+ const allowlistLoadToolCounts = {
14
+ repo: new Map(),
15
+ user: new Map(),
16
+ };
17
+ const allowlistLoadToolStats = {
18
+ repo: new Map(),
19
+ user: new Map(),
20
+ };
21
+ const allowlistLoadPathCounts = {
22
+ repo: new Map(),
23
+ user: new Map(),
24
+ };
25
+ const allowlistLoadSummaryState = {
26
+ repo: { lastLoggedAt: 0, lastLoggedTotal: 0, lastLoggedFailure: 0 },
27
+ user: { lastLoggedAt: 0, lastLoggedTotal: 0, lastLoggedFailure: 0 },
28
+ };
29
+ const DEFAULT_ALLOWLIST_SUMMARY_CONFIG = {
30
+ every: 100,
31
+ minIntervalMs: 10 * 60 * 1000,
32
+ failureMinIntervalMs: 60 * 1000,
33
+ maxToolStats: 1000,
34
+ maxPathStats: 2000,
35
+ };
36
+ let allowlistSummaryConfig = { ...DEFAULT_ALLOWLIST_SUMMARY_CONFIG };
37
+ const MAX_REALPATH_ASCENT = 20;
38
+ const TEMP_ARTIFACT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
39
+ const cleanedAllowlistDirs = new Set();
40
+ const DEFAULT_ALLOWLIST_MATCHING_CONFIG = {
41
+ denySideEffects: 'any',
42
+ allowSideEffects: 'all',
43
+ };
44
+ let allowlistMatchingConfig = { ...DEFAULT_ALLOWLIST_MATCHING_CONFIG };
45
+ function normalizeConfigNumber(value, fallback, min) {
46
+ if (typeof value !== 'number' || Number.isNaN(value) || !Number.isFinite(value)) {
47
+ return fallback;
48
+ }
49
+ if (value < min)
50
+ return fallback;
51
+ return Math.floor(value);
52
+ }
53
+ function applyAllowlistSummaryConfig(config) {
54
+ const summary = config?.allowlist?.summary;
55
+ allowlistSummaryConfig = {
56
+ every: normalizeConfigNumber(summary?.every, DEFAULT_ALLOWLIST_SUMMARY_CONFIG.every, 1),
57
+ minIntervalMs: normalizeConfigNumber(summary?.minIntervalMs, DEFAULT_ALLOWLIST_SUMMARY_CONFIG.minIntervalMs, 0),
58
+ failureMinIntervalMs: normalizeConfigNumber(summary?.failureMinIntervalMs, DEFAULT_ALLOWLIST_SUMMARY_CONFIG.failureMinIntervalMs, 0),
59
+ maxToolStats: normalizeConfigNumber(summary?.maxToolStats, DEFAULT_ALLOWLIST_SUMMARY_CONFIG.maxToolStats, 1),
60
+ maxPathStats: normalizeConfigNumber(summary?.maxPathStats, DEFAULT_ALLOWLIST_SUMMARY_CONFIG.maxPathStats, 1),
61
+ };
62
+ }
63
+ function applyAllowlistMatchingConfig(config) {
64
+ const matching = config?.allowlist?.matching;
65
+ const denySideEffects = matching?.denySideEffects === 'all' || matching?.denySideEffects === 'any'
66
+ ? matching.denySideEffects
67
+ : DEFAULT_ALLOWLIST_MATCHING_CONFIG.denySideEffects;
68
+ const allowSideEffects = matching?.allowSideEffects === 'all' || matching?.allowSideEffects === 'any'
69
+ ? matching.allowSideEffects
70
+ : DEFAULT_ALLOWLIST_MATCHING_CONFIG.allowSideEffects;
71
+ allowlistMatchingConfig = {
72
+ denySideEffects,
73
+ allowSideEffects,
74
+ };
75
+ }
76
+ async function cleanupAllowlistArtifacts(targetPath, rootContext, scope) {
77
+ const dir = path.dirname(targetPath);
78
+ if (cleanedAllowlistDirs.has(dir))
79
+ return;
80
+ cleanedAllowlistDirs.add(dir);
81
+ try {
82
+ const entries = await readdir(dir, rootContext);
83
+ const now = Date.now();
84
+ const base = path.basename(targetPath);
85
+ let cleaned = 0;
86
+ for (const entry of entries) {
87
+ if (!entry.startsWith(`${base}.`))
88
+ continue;
89
+ if (!entry.endsWith('.tmp') && !entry.endsWith('.bak'))
90
+ continue;
91
+ const fullPath = path.join(dir, entry);
92
+ try {
93
+ const fileStat = await stat(fullPath, rootContext);
94
+ if (now - fileStat.mtimeMs < TEMP_ARTIFACT_MAX_AGE_MS)
95
+ continue;
96
+ await unlink(fullPath, rootContext);
97
+ cleaned += 1;
98
+ }
99
+ catch {
100
+ // Best effort cleanup
101
+ }
102
+ }
103
+ if (cleaned > 0) {
104
+ getLogger().audit('ALLOWLIST_TEMP_ARTIFACTS_CLEANED', { path: dir, count: cleaned }, { source: 'allowlist', severity: 'low', scope });
105
+ }
106
+ }
107
+ catch {
108
+ // Best effort cleanup
109
+ }
110
+ }
111
+ function updateCountMap(map, key, limit) {
112
+ const next = (map.get(key) ?? 0) + 1;
113
+ map.set(key, next);
114
+ map.delete(key);
115
+ map.set(key, next);
116
+ if (map.size > limit) {
117
+ const oldest = map.keys().next().value;
118
+ if (oldest !== undefined) {
119
+ map.delete(oldest);
120
+ }
121
+ }
122
+ return next;
123
+ }
124
+ function updateToolStatsMap(map, key, outcome) {
125
+ const current = map.get(key) ?? { total: 0, success: 0, failure: 0 };
126
+ current.total += 1;
127
+ if (outcome === 'success') {
128
+ current.success += 1;
129
+ }
130
+ else {
131
+ current.failure += 1;
132
+ }
133
+ map.set(key, current);
134
+ map.delete(key);
135
+ map.set(key, current);
136
+ if (map.size > allowlistSummaryConfig.maxToolStats) {
137
+ const oldest = map.keys().next().value;
138
+ if (oldest !== undefined) {
139
+ map.delete(oldest);
140
+ }
141
+ }
142
+ return current;
143
+ }
144
+ function shouldLogAllowlistSummary(scope, outcome) {
145
+ const state = allowlistLoadSummaryState[scope];
146
+ const stats = allowlistLoadStats[scope];
147
+ const { every, minIntervalMs, failureMinIntervalMs } = allowlistSummaryConfig;
148
+ const now = Date.now();
149
+ const totalDelta = stats.total - state.lastLoggedTotal;
150
+ const failureDelta = stats.failure - state.lastLoggedFailure;
151
+ if (totalDelta >= every)
152
+ return true;
153
+ if (now - state.lastLoggedAt >= minIntervalMs)
154
+ return true;
155
+ if (outcome === 'failure' &&
156
+ failureDelta > 0 &&
157
+ now - state.lastLoggedAt >= failureMinIntervalMs) {
158
+ return true;
159
+ }
160
+ return false;
161
+ }
162
+ function recordAllowlistLoadSummary(params) {
163
+ const { scope, outcome, source, error, toolName, path } = params;
164
+ const stats = allowlistLoadStats[scope];
165
+ stats.total += 1;
166
+ if (outcome === 'success') {
167
+ stats.success += 1;
168
+ }
169
+ else {
170
+ stats.failure += 1;
171
+ }
172
+ if (!shouldLogAllowlistSummary(scope, outcome)) {
173
+ if (toolName) {
174
+ updateCountMap(allowlistLoadToolCounts[scope], toolName, allowlistSummaryConfig.maxToolStats);
175
+ updateToolStatsMap(allowlistLoadToolStats[scope], toolName, outcome);
176
+ }
177
+ if (path) {
178
+ updateCountMap(allowlistLoadPathCounts[scope], path, allowlistSummaryConfig.maxPathStats);
179
+ }
180
+ return;
181
+ }
182
+ const summaryState = allowlistLoadSummaryState[scope];
183
+ if (toolName) {
184
+ updateCountMap(allowlistLoadToolCounts[scope], toolName, allowlistSummaryConfig.maxToolStats);
185
+ }
186
+ if (path) {
187
+ updateCountMap(allowlistLoadPathCounts[scope], path, allowlistSummaryConfig.maxPathStats);
188
+ }
189
+ const toolStats = toolName
190
+ ? updateToolStatsMap(allowlistLoadToolStats[scope], toolName, outcome)
191
+ : undefined;
192
+ const toolFailureRate = toolStats
193
+ ? Math.round((toolStats.failure / toolStats.total) * 10000) / 100
194
+ : undefined;
195
+ summaryState.lastLoggedAt = Date.now();
196
+ summaryState.lastLoggedTotal = stats.total;
197
+ summaryState.lastLoggedFailure = stats.failure;
198
+ getLogger().audit('ALLOWLIST_LOAD_SUMMARY', {
199
+ scope,
200
+ total: stats.total,
201
+ success: stats.success,
202
+ failure: stats.failure,
203
+ lastOutcome: outcome,
204
+ lastSource: source,
205
+ lastError: error,
206
+ lastToolName: toolName,
207
+ lastPath: path,
208
+ toolCount: toolName ? allowlistLoadToolCounts[scope].get(toolName) : undefined,
209
+ pathCount: path ? allowlistLoadPathCounts[scope].get(path) : undefined,
210
+ toolFailureRatePct: toolFailureRate,
211
+ }, { source: 'allowlist', severity: 'low', scope });
212
+ }
213
+ function expandHome(filePath) {
214
+ if (!filePath.startsWith('~'))
215
+ return filePath;
216
+ if (filePath === '~')
217
+ return os.homedir();
218
+ if (filePath.startsWith('~/') || filePath.startsWith('~\\')) {
219
+ return path.join(os.homedir(), filePath.slice(2));
220
+ }
221
+ return filePath;
222
+ }
223
+ function resolveAllowlistPath(filePath, repoRoot, scope) {
224
+ const expanded = expandHome(filePath);
225
+ if (expanded.startsWith(path.sep))
226
+ return expanded;
227
+ const baseRoot = scope === 'user' ? os.homedir() : repoRoot;
228
+ return path.resolve(baseRoot, expanded);
229
+ }
230
+ function resolveAllowlistScopeRoot(repoRoot, scope) {
231
+ return scope === 'user'
232
+ ? path.join(os.homedir(), '.salmonloop')
233
+ : path.join(repoRoot, '.salmonloop');
234
+ }
235
+ async function resolveRealTargetForMissingPath(targetPath) {
236
+ let current = targetPath;
237
+ let depth = 0;
238
+ while (depth < MAX_REALPATH_ASCENT) {
239
+ try {
240
+ const realParent = await realpath(current);
241
+ const relative = path.relative(current, targetPath);
242
+ return path.join(realParent, relative);
243
+ }
244
+ catch (error) {
245
+ if (error && typeof error === 'object' && 'code' in error && error.code !== 'ENOENT')
246
+ return null;
247
+ }
248
+ const next = path.dirname(current);
249
+ if (next === current)
250
+ return null;
251
+ current = next;
252
+ depth += 1;
253
+ }
254
+ return null;
255
+ }
256
+ function logBlockedAllowlistPath(filePath, scope) {
257
+ getLogger().warn(text.cli.authPathBlocked(filePath, scope));
258
+ getLogger().audit('ALLOWLIST_PATH_BLOCKED', { path: filePath, scope }, { source: 'allowlist', severity: 'high', scope });
259
+ }
260
+ async function ensureAllowlistPath(filePath, repoRoot, scope) {
261
+ const resolved = resolveAllowlistPath(filePath, repoRoot, scope);
262
+ const scopeRoot = resolveAllowlistScopeRoot(repoRoot, scope);
263
+ const normalizedResolved = path.resolve(resolved);
264
+ const normalizedRoot = path.resolve(scopeRoot);
265
+ if (normalizedResolved === normalizedRoot)
266
+ return null;
267
+ if (!normalizedResolved.startsWith(normalizedRoot + path.sep))
268
+ return null;
269
+ let realRoot;
270
+ try {
271
+ realRoot = await realpath(normalizedRoot, normalizedRoot);
272
+ }
273
+ catch {
274
+ realRoot = normalizedRoot;
275
+ }
276
+ let realTarget;
277
+ try {
278
+ realTarget = await realpath(normalizedResolved, normalizedRoot);
279
+ }
280
+ catch (error) {
281
+ if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
282
+ const resolvedTarget = await resolveRealTargetForMissingPath(normalizedResolved);
283
+ if (!resolvedTarget)
284
+ return null;
285
+ realTarget = resolvedTarget;
286
+ }
287
+ else {
288
+ return null;
289
+ }
290
+ }
291
+ const realRootNormalized = path.resolve(realRoot);
292
+ const realTargetNormalized = path.resolve(realTarget);
293
+ if (realTargetNormalized === realRootNormalized)
294
+ return null;
295
+ if (!realTargetNormalized.startsWith(realRootNormalized + path.sep))
296
+ return null;
297
+ return normalizedResolved;
298
+ }
299
+ function hashCacheKey(value) {
300
+ return crypto.createHash('sha256').update(value).digest('hex');
301
+ }
302
+ function getCachePath(resolvedPath, repoRoot) {
303
+ const cacheName = `allowlist-cache-${hashCacheKey(resolvedPath)}.json`;
304
+ if (resolvedPath.includes(path.join(repoRoot, '.salmonloop'))) {
305
+ return path.resolve(repoRoot, '.salmonloop', 'state', cacheName);
306
+ }
307
+ if (resolvedPath.startsWith(os.homedir())) {
308
+ return path.join(os.homedir(), '.salmonloop', cacheName);
309
+ }
310
+ return path.resolve(repoRoot, '.salmonloop', 'state', cacheName);
311
+ }
312
+ async function readAllowlistCache(cachePath, rootContext) {
313
+ try {
314
+ const raw = await readFileUtf8(cachePath, rootContext);
315
+ const parsed = JSON.parse(raw);
316
+ return parsed;
317
+ }
318
+ catch (error) {
319
+ if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT')
320
+ return null;
321
+ return null;
322
+ }
323
+ }
324
+ async function saveAllowlistCache(cachePath, sourcePath, sourceMtimeMs, sourceSize, sourceHash, data, rootContext) {
325
+ const payload = {
326
+ version: CACHE_VERSION,
327
+ sourcePath,
328
+ sourceMtimeMs,
329
+ sourceSize,
330
+ sourceHash,
331
+ data,
332
+ };
333
+ await mkdirp(path.dirname(cachePath), rootContext);
334
+ await writeFileUtf8(cachePath, JSON.stringify(payload, null, 2), rootContext);
335
+ }
336
+ async function saveAllowlistCacheWithAudit(cachePath, sourcePath, sourceMtimeMs, sourceSize, sourceHash, data, rootContext, scope) {
337
+ try {
338
+ await saveAllowlistCache(cachePath, sourcePath, sourceMtimeMs, sourceSize, sourceHash, data, rootContext);
339
+ }
340
+ catch (error) {
341
+ const msg = error instanceof Error ? error.message : String(error);
342
+ getLogger().audit('ALLOWLIST_CACHE_WRITE_FAILED', { path: cachePath, sourcePath, error: msg }, { source: 'allowlist', severity: 'high', scope });
343
+ throw error;
344
+ }
345
+ }
346
+ function hashAllowlistSource(raw) {
347
+ return crypto.createHash('sha256').update(raw).digest('hex');
348
+ }
349
+ function getCacheInvalidationReason(cached, sourcePath, sourceMtimeMs, sourceSize, sourceHash) {
350
+ if (cached.version !== CACHE_VERSION)
351
+ return 'version_mismatch';
352
+ if (cached.sourcePath !== sourcePath)
353
+ return 'source_path_mismatch';
354
+ if (cached.sourceMtimeMs !== sourceMtimeMs)
355
+ return 'mtime_mismatch';
356
+ if (cached.sourceSize !== sourceSize)
357
+ return 'size_mismatch';
358
+ if (sourceHash && cached.sourceHash !== sourceHash)
359
+ return 'hash_mismatch';
360
+ if (!cached.data?.tools)
361
+ return 'missing_tools';
362
+ return null;
363
+ }
364
+ async function resolveAllowlistPathOrLog(filePath, repoRoot, scope) {
365
+ const resolved = await ensureAllowlistPath(filePath, repoRoot, scope);
366
+ if (!resolved) {
367
+ logBlockedAllowlistPath(filePath, scope);
368
+ }
369
+ return resolved;
370
+ }
371
+ async function loadAllowlist(filePath, repoRoot, scope, toolName) {
372
+ const resolved = await resolveAllowlistPathOrLog(filePath, repoRoot, scope);
373
+ if (!resolved) {
374
+ recordAllowlistLoadSummary({
375
+ scope,
376
+ outcome: 'failure',
377
+ source: 'blocked',
378
+ toolName,
379
+ path: filePath,
380
+ });
381
+ return createEmptyAllowlist();
382
+ }
383
+ return loadAllowlistResolved(resolved, repoRoot, scope, toolName);
384
+ }
385
+ async function loadAllowlistResolved(resolved, repoRoot, scope, toolName) {
386
+ const scopeRoot = resolveAllowlistScopeRoot(repoRoot, scope);
387
+ const cachePath = getCachePath(resolved, repoRoot);
388
+ try {
389
+ const sourceStat = await stat(resolved, scopeRoot);
390
+ const sourceMtimeMs = sourceStat.mtimeMs;
391
+ const sourceSize = sourceStat.size;
392
+ const scopeResolved = scope;
393
+ const cached = await readAllowlistCache(cachePath, scopeRoot);
394
+ if (cached) {
395
+ const reason = getCacheInvalidationReason(cached, resolved, sourceMtimeMs, sourceSize, undefined);
396
+ if (!reason && cached.data?.tools) {
397
+ recordAllowlistLoadSummary({
398
+ scope: scopeResolved,
399
+ outcome: 'success',
400
+ source: 'cache',
401
+ toolName,
402
+ path: resolved,
403
+ });
404
+ getLogger().audit('ALLOWLIST_CACHE_HIT', {
405
+ path: resolved,
406
+ hash: cached.sourceHash,
407
+ mtimeMs: sourceMtimeMs,
408
+ size: sourceSize,
409
+ }, { source: 'allowlist', severity: 'low', scope: scopeResolved });
410
+ return cached.data;
411
+ }
412
+ if (reason) {
413
+ getLogger().info(text.cli.authCacheInvalidated(reason, resolved));
414
+ getLogger().audit('ALLOWLIST_CACHE_INVALIDATED', {
415
+ path: resolved,
416
+ reason,
417
+ cachedPath: cached.sourcePath,
418
+ cachedHash: cached.sourceHash,
419
+ cachedMtimeMs: cached.sourceMtimeMs,
420
+ cachedSize: cached.sourceSize,
421
+ }, { source: 'allowlist', severity: 'low', scope: scopeResolved });
422
+ }
423
+ }
424
+ else {
425
+ getLogger().audit('ALLOWLIST_CACHE_MISS', { path: resolved, cachePath }, { source: 'allowlist', severity: 'low', scope: scopeResolved });
426
+ }
427
+ const raw = await readFileUtf8(resolved, scopeRoot);
428
+ const sourceHash = hashAllowlistSource(raw);
429
+ let parsed;
430
+ try {
431
+ parsed = JSON.parse(raw);
432
+ }
433
+ catch (error) {
434
+ const msg = error instanceof Error ? error.message : String(error);
435
+ recordAllowlistLoadSummary({
436
+ scope: scopeResolved,
437
+ outcome: 'failure',
438
+ source: 'parse',
439
+ error: msg,
440
+ toolName,
441
+ path: resolved,
442
+ });
443
+ getLogger().audit('ALLOWLIST_PARSE_FAILED', { path: resolved, error: msg }, { source: 'allowlist', severity: 'medium', scope: scopeResolved });
444
+ return createEmptyAllowlist();
445
+ }
446
+ if (parsed && parsed.version === 1 && parsed.tools && typeof parsed.tools === 'object') {
447
+ const allowlist = { version: 1, tools: parsed.tools };
448
+ await saveAllowlistCacheWithAudit(cachePath, resolved, sourceMtimeMs, sourceSize, sourceHash, allowlist, scopeRoot, scopeResolved);
449
+ recordAllowlistLoadSummary({
450
+ scope: scopeResolved,
451
+ outcome: 'success',
452
+ source: 'file',
453
+ toolName,
454
+ path: resolved,
455
+ });
456
+ return allowlist;
457
+ }
458
+ recordAllowlistLoadSummary({
459
+ scope: scopeResolved,
460
+ outcome: 'success',
461
+ source: 'empty',
462
+ toolName,
463
+ path: resolved,
464
+ });
465
+ return createEmptyAllowlist();
466
+ }
467
+ catch (error) {
468
+ if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
469
+ recordAllowlistLoadSummary({
470
+ scope,
471
+ outcome: 'success',
472
+ source: 'missing',
473
+ toolName,
474
+ path: resolved,
475
+ });
476
+ return createEmptyAllowlist();
477
+ }
478
+ const msg = error instanceof Error ? error.message : String(error);
479
+ recordAllowlistLoadSummary({
480
+ scope,
481
+ outcome: 'failure',
482
+ source: 'read',
483
+ error: msg,
484
+ toolName,
485
+ path: resolved,
486
+ });
487
+ return createEmptyAllowlist();
488
+ }
489
+ }
490
+ async function saveAllowlistResolved(resolved, allowlist, repoRoot, scope) {
491
+ const scopeRoot = resolveAllowlistScopeRoot(repoRoot, scope);
492
+ try {
493
+ await mkdirp(path.dirname(resolved), scopeRoot);
494
+ await writeFileAtomic(resolved, JSON.stringify(allowlist, null, 2), scopeRoot, scope);
495
+ }
496
+ catch (error) {
497
+ const msg = error instanceof Error ? error.message : String(error);
498
+ getLogger().audit('ALLOWLIST_WRITE_FAILED', { path: resolved, error: msg }, { source: 'allowlist', severity: 'high', scope });
499
+ throw error;
500
+ }
501
+ }
502
+ function ruleMatches(rule, ctx, sideEffectMode) {
503
+ if (rule.phase && rule.phase !== ctx.phase)
504
+ return false;
505
+ if (rule.sideEffects && rule.sideEffects.length > 0) {
506
+ if (!ctx.sideEffects || ctx.sideEffects.length === 0)
507
+ return false;
508
+ if (sideEffectMode === 'any') {
509
+ const matches = rule.sideEffects.some((effect) => ctx.sideEffects?.includes(effect));
510
+ if (!matches)
511
+ return false;
512
+ }
513
+ else {
514
+ for (const effect of rule.sideEffects) {
515
+ if (!ctx.sideEffects.includes(effect))
516
+ return false;
517
+ }
518
+ }
519
+ }
520
+ if (rule.argsHash && rule.argsHash !== ctx.argsHash)
521
+ return false;
522
+ return true;
523
+ }
524
+ function matchEntry(entry, ctx) {
525
+ if (entry.rules && entry.rules.length > 0) {
526
+ for (const rule of entry.rules) {
527
+ if (ruleMatches(rule, ctx, allowlistMatchingConfig.denySideEffects) && rule.mode === 'deny')
528
+ return 'deny';
529
+ }
530
+ for (const rule of entry.rules) {
531
+ if (ruleMatches(rule, ctx, allowlistMatchingConfig.allowSideEffects) &&
532
+ rule.mode === 'allow') {
533
+ return 'allow';
534
+ }
535
+ }
536
+ }
537
+ if (entry.phases && entry.phases[ctx.phase])
538
+ return entry.phases[ctx.phase];
539
+ if (entry.mode)
540
+ return entry.mode;
541
+ return null;
542
+ }
543
+ function isAllowed(allowlist, ctx) {
544
+ const entry = allowlist.tools[ctx.toolName];
545
+ if (!entry)
546
+ return null;
547
+ return matchEntry(entry, ctx);
548
+ }
549
+ const allowlistLocks = new Map();
550
+ const allowlistLockOwners = new Map();
551
+ const allowlistLockPrefix = `allowlist-${process.pid}-`;
552
+ async function withAllowlistLock(key, fn) {
553
+ const previous = allowlistLocks.get(key) ?? Promise.resolve();
554
+ let release;
555
+ const next = new Promise((resolve) => {
556
+ release = resolve;
557
+ });
558
+ allowlistLocks.set(key, previous.then(() => next));
559
+ await previous;
560
+ try {
561
+ return await fn();
562
+ }
563
+ finally {
564
+ release?.();
565
+ if (allowlistLocks.get(key) === next) {
566
+ allowlistLocks.delete(key);
567
+ }
568
+ }
569
+ }
570
+ function getAllowlistLockPath(resolvedPath, repoRoot) {
571
+ const lockName = `allowlist-${hashCacheKey(resolvedPath)}.lock`;
572
+ if (resolvedPath.includes(path.join(repoRoot, '.salmonloop'))) {
573
+ return path.resolve(repoRoot, '.salmonloop', 'state', 'locks', lockName);
574
+ }
575
+ if (resolvedPath.startsWith(os.homedir())) {
576
+ return path.join(os.homedir(), '.salmonloop', 'locks', lockName);
577
+ }
578
+ return path.resolve(repoRoot, '.salmonloop', 'state', 'locks', lockName);
579
+ }
580
+ function createAllowlistLockOwner() {
581
+ const salt = crypto.randomBytes(8).toString('hex');
582
+ return `${allowlistLockPrefix}${salt}`;
583
+ }
584
+ async function acquireAllowlistFileLock(lockPath, rootContext, scope) {
585
+ const start = Date.now();
586
+ let retryCount = 0;
587
+ const owner = createAllowlistLockOwner();
588
+ const verifyDelayMs = 25;
589
+ while (Date.now() - start < LIMITS.lockWaitTimeoutMs) {
590
+ try {
591
+ const handle = await openFile(lockPath, 'wx', rootContext);
592
+ await handle.writeFile(JSON.stringify({ pid: process.pid, timestamp: Date.now(), owner }), 'utf8');
593
+ await handle.close();
594
+ try {
595
+ const raw = await readFileUtf8(lockPath, rootContext);
596
+ const metadata = JSON.parse(raw);
597
+ if (metadata.owner !== owner) {
598
+ await new Promise((resolve) => setTimeout(resolve, verifyDelayMs));
599
+ const retryRaw = await readFileUtf8(lockPath, rootContext);
600
+ const retryMetadata = JSON.parse(retryRaw);
601
+ if (retryMetadata.owner !== owner) {
602
+ getLogger().audit('ALLOWLIST_LOCK_VERIFICATION_FAILED', { path: lockPath, owner, pid: process.pid }, { source: 'allowlist', severity: 'medium', scope });
603
+ await unlink(lockPath, rootContext).catch(() => undefined);
604
+ retryCount += 1;
605
+ const delay = Math.min(LIMITS.retry.io.initialDelayMs * Math.pow(1.5, retryCount), LIMITS.retry.io.maxDelayMs);
606
+ await new Promise((resolve) => setTimeout(resolve, delay));
607
+ continue;
608
+ }
609
+ }
610
+ }
611
+ catch {
612
+ getLogger().audit('ALLOWLIST_LOCK_VERIFICATION_FAILED', { path: lockPath, owner, pid: process.pid }, { source: 'allowlist', severity: 'medium', scope });
613
+ await unlink(lockPath, rootContext).catch(() => undefined);
614
+ retryCount += 1;
615
+ const delay = Math.min(LIMITS.retry.io.initialDelayMs * Math.pow(1.5, retryCount), LIMITS.retry.io.maxDelayMs);
616
+ await new Promise((resolve) => setTimeout(resolve, delay));
617
+ continue;
618
+ }
619
+ allowlistLockOwners.set(lockPath, owner);
620
+ getLogger().audit('ALLOWLIST_LOCK_ACQUIRED', { path: lockPath, owner, pid: process.pid, waitedMs: Date.now() - start }, { source: 'allowlist', severity: 'low', scope });
621
+ return;
622
+ }
623
+ catch (error) {
624
+ if (error && typeof error === 'object' && 'code' in error && error.code === 'EEXIST') {
625
+ try {
626
+ const raw = await readFileUtf8(lockPath, rootContext);
627
+ const metadata = JSON.parse(raw);
628
+ const age = Date.now() - (metadata.timestamp ?? 0);
629
+ let isAlive = true;
630
+ if (metadata.pid) {
631
+ try {
632
+ process.kill(metadata.pid, 0);
633
+ }
634
+ catch {
635
+ isAlive = false;
636
+ }
637
+ }
638
+ if (!isAlive || age > LIMITS.lockStaleThresholdMs) {
639
+ await unlink(lockPath, rootContext).catch(() => undefined);
640
+ getLogger().audit('ALLOWLIST_LOCK_STALE_REMOVED', { path: lockPath, owner: metadata.owner, pid: metadata.pid, ageMs: age }, { source: 'allowlist', severity: 'medium', scope });
641
+ continue;
642
+ }
643
+ }
644
+ catch {
645
+ // If lock contents are unreadable, treat it as stale and retry.
646
+ await unlink(lockPath, rootContext).catch(() => undefined);
647
+ getLogger().audit('ALLOWLIST_LOCK_STALE_REMOVED', { path: lockPath, owner: 'unknown', pid: undefined, ageMs: undefined }, { source: 'allowlist', severity: 'medium', scope });
648
+ continue;
649
+ }
650
+ retryCount += 1;
651
+ const delay = Math.min(LIMITS.retry.io.initialDelayMs * Math.pow(1.5, retryCount), LIMITS.retry.io.maxDelayMs);
652
+ await new Promise((resolve) => setTimeout(resolve, delay));
653
+ continue;
654
+ }
655
+ if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
656
+ await mkdirp(path.dirname(lockPath), rootContext);
657
+ continue;
658
+ }
659
+ throw error;
660
+ }
661
+ }
662
+ getLogger().audit('ALLOWLIST_LOCK_TIMEOUT', { path: lockPath }, { source: 'allowlist', severity: 'high', scope });
663
+ throw new Error(text.cli.authLockTimeout(lockPath));
664
+ }
665
+ async function releaseAllowlistFileLock(lockPath, rootContext, scope) {
666
+ const owner = allowlistLockOwners.get(lockPath);
667
+ if (!owner)
668
+ return;
669
+ try {
670
+ const raw = await readFileUtf8(lockPath, rootContext);
671
+ const metadata = JSON.parse(raw);
672
+ if (metadata.owner !== owner)
673
+ return;
674
+ }
675
+ catch {
676
+ // If we cannot read it, still attempt to remove to avoid deadlocks.
677
+ }
678
+ try {
679
+ await unlink(lockPath, rootContext);
680
+ getLogger().audit('ALLOWLIST_LOCK_RELEASED', { path: lockPath, owner, pid: process.pid }, { source: 'allowlist', severity: 'low', scope });
681
+ }
682
+ catch (error) {
683
+ if (error && typeof error === 'object' && 'code' in error && error.code !== 'ENOENT')
684
+ throw error;
685
+ }
686
+ finally {
687
+ allowlistLockOwners.delete(lockPath);
688
+ }
689
+ }
690
+ async function withAllowlistFileLock(resolvedPath, repoRoot, scope, fn) {
691
+ const lockPath = getAllowlistLockPath(resolvedPath, repoRoot);
692
+ const rootContext = resolveAllowlistScopeRoot(repoRoot, scope);
693
+ await acquireAllowlistFileLock(lockPath, rootContext, scope);
694
+ try {
695
+ return await fn();
696
+ }
697
+ finally {
698
+ await releaseAllowlistFileLock(lockPath, rootContext, scope);
699
+ }
700
+ }
701
+ async function writeFileAtomic(targetPath, content, rootContext, scope) {
702
+ await cleanupAllowlistArtifacts(targetPath, rootContext, scope);
703
+ const tempPath = `${targetPath}.${process.pid}.${Date.now()}.tmp`;
704
+ const backupPath = `${targetPath}.${process.pid}.${Date.now()}.bak`;
705
+ let backupCreated = false;
706
+ await writeFileUtf8(tempPath, content, rootContext);
707
+ try {
708
+ await rename(tempPath, targetPath, rootContext);
709
+ }
710
+ catch (error) {
711
+ const msg = error instanceof Error ? error.message : String(error);
712
+ getLogger().audit('ALLOWLIST_ATOMIC_WRITE_FALLBACK', { path: targetPath, error: msg }, { source: 'allowlist', severity: 'medium', scope });
713
+ try {
714
+ try {
715
+ await copyFile(targetPath, backupPath, rootContext);
716
+ backupCreated = true;
717
+ }
718
+ catch (copyError) {
719
+ if (copyError &&
720
+ typeof copyError === 'object' &&
721
+ 'code' in copyError &&
722
+ copyError.code !== 'ENOENT') {
723
+ getLogger().audit('ALLOWLIST_ATOMIC_WRITE_BACKUP_FAILED', {
724
+ path: targetPath,
725
+ error: copyError instanceof Error ? copyError.message : String(copyError),
726
+ }, { source: 'allowlist', severity: 'medium', scope });
727
+ }
728
+ }
729
+ await writeFileUtf8(targetPath, content, rootContext);
730
+ }
731
+ catch (writeError) {
732
+ if (backupCreated) {
733
+ try {
734
+ await copyFile(backupPath, targetPath, rootContext);
735
+ }
736
+ catch {
737
+ getLogger().audit('ALLOWLIST_ATOMIC_RESTORE_FAILED', { path: targetPath }, { source: 'allowlist', severity: 'high', scope });
738
+ }
739
+ }
740
+ throw writeError;
741
+ }
742
+ finally {
743
+ await unlink(tempPath, rootContext).catch(() => undefined);
744
+ if (backupCreated) {
745
+ await unlink(backupPath, rootContext).catch(() => undefined);
746
+ }
747
+ }
748
+ }
749
+ }
750
+ export async function loadAllowlistDecision(params) {
751
+ const { config, repoRoot, toolName, phase, sideEffects, argsHash } = params;
752
+ applyAllowlistSummaryConfig(config);
753
+ applyAllowlistMatchingConfig(config);
754
+ const repoFile = config.allowlist?.repoFile;
755
+ const userFile = config.allowlist?.userFile;
756
+ const ctx = { toolName, phase, sideEffects, argsHash };
757
+ const userDecision = userFile
758
+ ? isAllowed(await loadAllowlist(userFile, repoRoot, 'user', toolName), ctx)
759
+ : null;
760
+ if (userDecision === 'deny')
761
+ return 'deny';
762
+ const repoDecision = repoFile
763
+ ? isAllowed(await loadAllowlist(repoFile, repoRoot, 'repo', toolName), ctx)
764
+ : null;
765
+ if (repoDecision === 'deny')
766
+ return 'deny';
767
+ if (userDecision === 'allow')
768
+ return 'allow';
769
+ if (repoDecision === 'allow')
770
+ return 'allow';
771
+ return null;
772
+ }
773
+ export async function persistAllowlistDecision(params) {
774
+ const { config, repoRoot, toolName, phase, scope, mode = 'allow', sideEffects, argsHash, } = params;
775
+ const targetFile = scope === 'repo' ? config.allowlist?.repoFile : config.allowlist?.userFile;
776
+ if (!targetFile)
777
+ return;
778
+ const resolved = await resolveAllowlistPathOrLog(targetFile, repoRoot, scope);
779
+ if (!resolved)
780
+ return;
781
+ await withAllowlistLock(resolved, async () => withAllowlistFileLock(resolved, repoRoot, scope, async () => {
782
+ const scopeRoot = resolveAllowlistScopeRoot(repoRoot, scope);
783
+ const allowlist = await loadAllowlistResolved(resolved, repoRoot, scope);
784
+ const entry = allowlist.tools[toolName] || { phases: {}, rules: [] };
785
+ const rules = entry.rules || [];
786
+ rules.push({
787
+ mode,
788
+ phase,
789
+ sideEffects: sideEffects && sideEffects.length > 0 ? sideEffects : undefined,
790
+ argsHash,
791
+ });
792
+ allowlist.tools[toolName] = { ...entry, rules };
793
+ await saveAllowlistResolved(resolved, allowlist, repoRoot, scope);
794
+ const cachePath = getCachePath(resolved, repoRoot);
795
+ const sourceStat = await stat(resolved, scopeRoot);
796
+ const raw = await readFileUtf8(resolved, scopeRoot);
797
+ await saveAllowlistCacheWithAudit(cachePath, resolved, sourceStat.mtimeMs, sourceStat.size, hashAllowlistSource(raw), allowlist, scopeRoot, scope);
798
+ getLogger().audit('ALLOWLIST_RULE_PERSISTED', { path: resolved, toolName, scope, mode, phase, sideEffects, argsHash }, { source: 'allowlist', severity: 'medium', scope });
799
+ }));
800
+ }
801
+ export async function listAllowlist(params) {
802
+ const { config, repoRoot, scope } = params;
803
+ applyAllowlistSummaryConfig(config);
804
+ const targetFile = scope === 'repo' ? config.allowlist?.repoFile : config.allowlist?.userFile;
805
+ if (!targetFile)
806
+ return createEmptyAllowlist();
807
+ return loadAllowlist(targetFile, repoRoot, scope);
808
+ }
809
+ export async function removeAllowlistRule(params) {
810
+ const { config, repoRoot, scope, toolName, phase, argsHash, sideEffects } = params;
811
+ const targetFile = scope === 'repo' ? config.allowlist?.repoFile : config.allowlist?.userFile;
812
+ if (!targetFile)
813
+ return false;
814
+ const resolved = await resolveAllowlistPathOrLog(targetFile, repoRoot, scope);
815
+ if (!resolved)
816
+ return false;
817
+ return withAllowlistLock(resolved, async () => withAllowlistFileLock(resolved, repoRoot, scope, async () => {
818
+ const scopeRoot = resolveAllowlistScopeRoot(repoRoot, scope);
819
+ const allowlist = await loadAllowlistResolved(resolved, repoRoot, scope);
820
+ const entry = allowlist.tools[toolName];
821
+ if (!entry)
822
+ return false;
823
+ if (!phase && !argsHash && (!sideEffects || sideEffects.length === 0)) {
824
+ delete allowlist.tools[toolName];
825
+ }
826
+ else {
827
+ const rules = (entry.rules || []).filter((rule) => {
828
+ if (phase && rule.phase !== phase)
829
+ return true;
830
+ if (argsHash && rule.argsHash !== argsHash)
831
+ return true;
832
+ if (sideEffects && sideEffects.length > 0) {
833
+ if (!rule.sideEffects || rule.sideEffects.length === 0)
834
+ return true;
835
+ for (const effect of sideEffects) {
836
+ if (!rule.sideEffects.includes(effect))
837
+ return true;
838
+ }
839
+ }
840
+ return false;
841
+ });
842
+ const hasPhases = entry.phases && Object.keys(entry.phases).length > 0;
843
+ if (rules.length === 0 && !entry.mode && !hasPhases) {
844
+ delete allowlist.tools[toolName];
845
+ }
846
+ else {
847
+ allowlist.tools[toolName] = { ...entry, rules };
848
+ }
849
+ }
850
+ const removedAll = !phase && !argsHash && (!sideEffects || sideEffects.length === 0);
851
+ await saveAllowlistResolved(resolved, allowlist, repoRoot, scope);
852
+ const cachePath = getCachePath(resolved, repoRoot);
853
+ const sourceStat = await stat(resolved, scopeRoot);
854
+ const raw = await readFileUtf8(resolved, scopeRoot);
855
+ await saveAllowlistCacheWithAudit(cachePath, resolved, sourceStat.mtimeMs, sourceStat.size, hashAllowlistSource(raw), allowlist, scopeRoot, scope);
856
+ getLogger().audit('ALLOWLIST_RULE_REMOVED', { path: resolved, toolName, scope, phase, sideEffects, argsHash, removedAll }, { source: 'allowlist', severity: 'medium', scope });
857
+ return true;
858
+ }));
859
+ }
860
+ export async function clearAllowlist(params) {
861
+ const { config, repoRoot, scope } = params;
862
+ const targetFile = scope === 'repo' ? config.allowlist?.repoFile : config.allowlist?.userFile;
863
+ if (!targetFile)
864
+ return;
865
+ const resolved = await resolveAllowlistPathOrLog(targetFile, repoRoot, scope);
866
+ if (!resolved)
867
+ return;
868
+ await withAllowlistLock(resolved, async () => withAllowlistFileLock(resolved, repoRoot, scope, async () => {
869
+ const scopeRoot = resolveAllowlistScopeRoot(repoRoot, scope);
870
+ const empty = createEmptyAllowlist();
871
+ await saveAllowlistResolved(resolved, empty, repoRoot, scope);
872
+ const sourceStat = await stat(resolved, scopeRoot);
873
+ const raw = await readFileUtf8(resolved, scopeRoot);
874
+ await saveAllowlistCacheWithAudit(getCachePath(resolved, repoRoot), resolved, sourceStat.mtimeMs, sourceStat.size, hashAllowlistSource(raw), {
875
+ ...empty,
876
+ }, scopeRoot, scope);
877
+ getLogger().audit('ALLOWLIST_CLEARED', { path: resolved, scope }, { source: 'allowlist', severity: 'medium', scope });
878
+ }));
879
+ }
880
+ export async function clearAllowlistCache(params) {
881
+ const { config, repoRoot } = params;
882
+ const repoResolved = config.allowlist?.repoFile
883
+ ? await resolveAllowlistPathOrLog(config.allowlist.repoFile, repoRoot, 'repo')
884
+ : null;
885
+ const userResolved = config.allowlist?.userFile
886
+ ? await resolveAllowlistPathOrLog(config.allowlist.userFile, repoRoot, 'user')
887
+ : null;
888
+ const repoRootContext = resolveAllowlistScopeRoot(repoRoot, 'repo');
889
+ const userRootContext = resolveAllowlistScopeRoot(repoRoot, 'user');
890
+ const targets = [
891
+ repoResolved
892
+ ? { path: getCachePath(repoResolved, repoRoot), rootContext: repoRootContext }
893
+ : null,
894
+ userResolved
895
+ ? { path: getCachePath(userResolved, repoRoot), rootContext: userRootContext }
896
+ : null,
897
+ ].filter(Boolean);
898
+ await Promise.all(targets.map(async ({ path: filePath, rootContext }) => {
899
+ try {
900
+ await unlink(filePath, rootContext);
901
+ }
902
+ catch (error) {
903
+ if (error && typeof error === 'object' && 'code' in error && error.code !== 'ENOENT')
904
+ throw error;
905
+ }
906
+ }));
907
+ }
908
+ //# sourceMappingURL=allowlist.js.map