pi-crew 0.9.15 → 0.9.17

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 (432) hide show
  1. package/CHANGELOG.md +145 -0
  2. package/README.md +19 -0
  3. package/dist/build-meta.json +21790 -0
  4. package/dist/index.mjs +83070 -0
  5. package/dist/index.mjs.map +7 -0
  6. package/docs/A +358 -0
  7. package/docs/M-A +357 -0
  8. package/docs/REVIEW-FINDINGS-2026-06 +357 -0
  9. package/docs/Y +357 -0
  10. package/docs/a +357 -0
  11. package/docs/aA +358 -0
  12. package/docs/archive/README.md +91 -0
  13. package/docs/patterns/command-agent-skill.md +1 -1
  14. package/docs/skills/REFERENCE.md +0 -17
  15. package/docs//303/242mmaAAA/303/242 +357 -0
  16. package/index.bundle.ts +25 -0
  17. package/index.ts +95 -4
  18. package/package.json +15 -4
  19. package/skills/iterative-audit/SKILL.md +0 -1
  20. package/skills/widget-rendering/SKILL.md +17 -0
  21. package/src/adapters/claude-adapter.ts +1 -3
  22. package/src/adapters/export-util.ts +16 -16
  23. package/src/adapters/index.ts +5 -5
  24. package/src/agents/agent-config.ts +20 -14
  25. package/src/agents/agent-serializer.ts +1 -1
  26. package/src/agents/discover-agents.ts +83 -39
  27. package/src/benchmark/benchmark-runner.ts +51 -51
  28. package/src/benchmark/feedback-loop.ts +1 -1
  29. package/src/config/config.ts +166 -590
  30. package/src/config/defaults.ts +28 -2
  31. package/src/config/drift-detector.ts +11 -14
  32. package/src/config/markers.ts +201 -203
  33. package/src/config/resilient-parser.ts +14 -6
  34. package/src/config/role-tools.ts +3 -3
  35. package/src/config/suggestions.ts +5 -12
  36. package/src/config/types.ts +9 -25
  37. package/src/errors.ts +151 -157
  38. package/src/extension/action-suggestions.ts +54 -9
  39. package/src/extension/async-notifier.ts +43 -13
  40. package/src/extension/autonomous-policy.ts +77 -37
  41. package/src/extension/command-completions.ts +9 -8
  42. package/src/extension/context-status-injection.ts +14 -12
  43. package/src/extension/crew-autocomplete.ts +8 -16
  44. package/src/extension/crew-cleanup.ts +2 -1
  45. package/src/extension/crew-shortcuts.ts +9 -6
  46. package/src/extension/cross-extension-rpc.ts +136 -42
  47. package/src/extension/help.ts +1 -1
  48. package/src/extension/import-index.ts +10 -5
  49. package/src/extension/knowledge-injection.ts +88 -15
  50. package/src/extension/management.ts +89 -349
  51. package/src/extension/message-renderers.ts +3 -4
  52. package/src/extension/notification-router.ts +22 -5
  53. package/src/extension/notification-sink.ts +6 -3
  54. package/src/extension/pi-api.ts +17 -8
  55. package/src/extension/plan-orchestrate.ts +8 -27
  56. package/src/extension/project-init.ts +21 -6
  57. package/src/extension/register.ts +329 -975
  58. package/src/extension/registration/artifact-cleanup.ts +9 -4
  59. package/src/extension/registration/command-utils.ts +5 -1
  60. package/src/extension/registration/commands.ts +806 -314
  61. package/src/extension/registration/compaction-guard.ts +15 -15
  62. package/src/extension/registration/lifecycle.ts +259 -0
  63. package/src/extension/registration/observability.ts +293 -0
  64. package/src/extension/registration/subagent-helpers.ts +29 -7
  65. package/src/extension/registration/subagent-tools.ts +253 -44
  66. package/src/extension/registration/team-tool.ts +19 -84
  67. package/src/extension/registration/ui.ts +168 -0
  68. package/src/extension/registration/viewers.ts +64 -24
  69. package/src/extension/result-watcher.ts +18 -7
  70. package/src/extension/run-bundle-schema.ts +21 -6
  71. package/src/extension/run-export.ts +15 -6
  72. package/src/extension/run-import.ts +64 -29
  73. package/src/extension/run-index.ts +28 -9
  74. package/src/extension/run-maintenance.ts +32 -12
  75. package/src/extension/session-summary.ts +1 -1
  76. package/src/extension/team-manager-command.ts +64 -7
  77. package/src/extension/team-onboard.ts +151 -150
  78. package/src/extension/team-recommendation.ts +121 -21
  79. package/src/extension/team-tool/anchor.ts +31 -64
  80. package/src/extension/team-tool/api.ts +929 -141
  81. package/src/extension/team-tool/auto-summarize.ts +14 -26
  82. package/src/extension/team-tool/cache-control.ts +1 -5
  83. package/src/extension/team-tool/cancel.ts +138 -38
  84. package/src/extension/team-tool/chain-dispatch.ts +5 -13
  85. package/src/extension/team-tool/chain-executor.ts +29 -51
  86. package/src/extension/team-tool/config-patch.ts +1 -1
  87. package/src/extension/team-tool/context.ts +40 -20
  88. package/src/extension/team-tool/destructive-gate.ts +10 -5
  89. package/src/extension/team-tool/doctor.ts +170 -51
  90. package/src/extension/team-tool/explain.ts +228 -214
  91. package/src/extension/team-tool/failure-patterns.ts +3 -9
  92. package/src/extension/team-tool/goal-wrap.ts +71 -29
  93. package/src/extension/team-tool/goal.ts +185 -35
  94. package/src/extension/team-tool/handle-schedule.ts +34 -15
  95. package/src/extension/team-tool/handle-settings.ts +94 -56
  96. package/src/extension/team-tool/health-monitor.ts +27 -108
  97. package/src/extension/team-tool/inspect.ts +42 -9
  98. package/src/extension/team-tool/intent-policy.ts +4 -1
  99. package/src/extension/team-tool/lifecycle-actions.ts +244 -48
  100. package/src/extension/team-tool/orchestrate.ts +7 -18
  101. package/src/extension/team-tool/parallel-dispatch.ts +51 -29
  102. package/src/extension/team-tool/plan.ts +29 -5
  103. package/src/extension/team-tool/respond.ts +49 -16
  104. package/src/extension/team-tool/run-not-found.ts +3 -8
  105. package/src/extension/team-tool/run.ts +80 -317
  106. package/src/extension/team-tool/status.ts +135 -43
  107. package/src/extension/team-tool/workflow-manage.ts +92 -25
  108. package/src/extension/team-tool-types.ts +8 -1
  109. package/src/extension/team-tool.ts +142 -551
  110. package/src/extension/validate-resources.ts +39 -8
  111. package/src/hooks/registry.ts +66 -17
  112. package/src/hooks/types.ts +1 -1
  113. package/src/i18n.ts +25 -11
  114. package/src/observability/correlation.ts +17 -3
  115. package/src/observability/event-bus.ts +51 -56
  116. package/src/observability/event-to-metric.ts +117 -15
  117. package/src/observability/exporters/otlp-exporter.ts +56 -17
  118. package/src/observability/exporters/prometheus-exporter.ts +1 -1
  119. package/src/observability/metric-registry.ts +16 -4
  120. package/src/observability/metric-retention.ts +8 -2
  121. package/src/observability/metric-sink.ts +11 -3
  122. package/src/observability/metrics-primitives.ts +42 -9
  123. package/src/plugins/plugin-define.ts +2 -2
  124. package/src/plugins/plugin-registry.ts +25 -25
  125. package/src/plugins/plugins/index.ts +1 -1
  126. package/src/plugins/plugins/nextjs.ts +16 -16
  127. package/src/plugins/plugins/vite.ts +7 -11
  128. package/src/plugins/plugins/vitest.ts +6 -11
  129. package/src/runtime/adaptive-plan.ts +225 -40
  130. package/src/runtime/agent-control.ts +58 -15
  131. package/src/runtime/agent-memory.ts +15 -9
  132. package/src/runtime/agent-observability.ts +10 -2
  133. package/src/runtime/anchor-manager.ts +27 -25
  134. package/src/runtime/async-marker.ts +7 -1
  135. package/src/runtime/async-runner.ts +27 -23
  136. package/src/runtime/auto-summarize.ts +7 -13
  137. package/src/runtime/background-runner.ts +189 -251
  138. package/src/runtime/batch-barrier.ts +18 -16
  139. package/src/runtime/cancellation-token.ts +16 -6
  140. package/src/runtime/cancellation.ts +46 -8
  141. package/src/runtime/capability-inventory.ts +6 -4
  142. package/src/runtime/chain-parser.ts +44 -11
  143. package/src/runtime/chain-runner.ts +63 -69
  144. package/src/runtime/checkpoint.ts +12 -56
  145. package/src/runtime/child-pi.ts +343 -78
  146. package/src/runtime/coalesce-tasks.ts +268 -0
  147. package/src/runtime/code-summary.ts +71 -26
  148. package/src/runtime/compact-stages/index.ts +15 -5
  149. package/src/runtime/compact-stages/tail-capture-stage.ts +7 -2
  150. package/src/runtime/compact-stages/truncation-stage.ts +4 -1
  151. package/src/runtime/compaction-summary.ts +17 -10
  152. package/src/runtime/completion-guard.ts +33 -16
  153. package/src/runtime/crash-classification.ts +28 -7
  154. package/src/runtime/crash-recovery.ts +177 -37
  155. package/src/runtime/crew-agent-records.ts +193 -40
  156. package/src/runtime/crew-hooks.ts +9 -24
  157. package/src/runtime/cross-extension-rpc.ts +25 -23
  158. package/src/runtime/custom-tools/irc-tool.ts +43 -15
  159. package/src/runtime/custom-tools/submit-result-tool.ts +16 -5
  160. package/src/runtime/delivery-coordinator.ts +34 -7
  161. package/src/runtime/delta-conflict.ts +9 -21
  162. package/src/runtime/deterministic-ast.ts +1 -1
  163. package/src/runtime/diagnostic-export.ts +53 -20
  164. package/src/runtime/direct-run.ts +12 -2
  165. package/src/runtime/dwf-state-store.ts +1 -1
  166. package/src/runtime/dynamic-workflow-context.ts +186 -58
  167. package/src/runtime/dynamic-workflow-runner.ts +42 -20
  168. package/src/runtime/effectiveness.ts +16 -8
  169. package/src/runtime/errors/crew-errors.ts +7 -11
  170. package/src/runtime/event-stream-bridge.ts +9 -3
  171. package/src/runtime/foreground-control.ts +54 -14
  172. package/src/runtime/foreground-watchdog.ts +9 -6
  173. package/src/runtime/goal-achievement.ts +27 -10
  174. package/src/runtime/goal-evaluator.ts +54 -19
  175. package/src/runtime/goal-loop-runner.ts +280 -99
  176. package/src/runtime/goal-state-store.ts +27 -17
  177. package/src/runtime/green-contract.ts +11 -2
  178. package/src/runtime/group-join.ts +64 -31
  179. package/src/runtime/handoff-manager.ts +37 -42
  180. package/src/runtime/heartbeat-gradient.ts +10 -2
  181. package/src/runtime/heartbeat-watcher.ts +32 -6
  182. package/src/runtime/hidden-handoff.ts +12 -30
  183. package/src/runtime/important-line-classifier.ts +12 -2
  184. package/src/runtime/iteration-hooks.ts +25 -15
  185. package/src/runtime/live-agent-control.ts +55 -7
  186. package/src/runtime/live-agent-manager.ts +130 -27
  187. package/src/runtime/live-control-realtime.ts +23 -3
  188. package/src/runtime/live-irc.ts +4 -1
  189. package/src/runtime/live-session-health.ts +12 -4
  190. package/src/runtime/live-session-runtime.ts +379 -117
  191. package/src/runtime/manifest-cache.ts +28 -12
  192. package/src/runtime/mcp-proxy.ts +8 -16
  193. package/src/runtime/metric-parser.ts +1 -5
  194. package/src/runtime/model-fallback.ts +55 -18
  195. package/src/runtime/model-resolver.ts +2 -6
  196. package/src/runtime/model-scope.ts +19 -3
  197. package/src/runtime/notebook-helpers.ts +60 -62
  198. package/src/runtime/orphan-worker-registry.ts +37 -26
  199. package/src/runtime/output-validator.ts +18 -3
  200. package/src/runtime/overflow-recovery.ts +5 -4
  201. package/src/runtime/parallel-research.ts +29 -5
  202. package/src/runtime/parallel-utils.ts +9 -11
  203. package/src/runtime/path-overlap.ts +150 -0
  204. package/src/runtime/peer-dep.ts +12 -17
  205. package/src/runtime/per-write-validator.ts +1 -3
  206. package/src/runtime/phase-tracker.ts +342 -330
  207. package/src/runtime/pi-args.ts +26 -8
  208. package/src/runtime/pi-json-output.ts +1 -1
  209. package/src/runtime/pi-spawn.ts +30 -19
  210. package/src/runtime/pipeline-runner.ts +56 -55
  211. package/src/runtime/plan-templates.ts +5 -6
  212. package/src/runtime/policy-engine.ts +43 -7
  213. package/src/runtime/post-checks.ts +12 -4
  214. package/src/runtime/post-exit-stdio-guard.ts +2 -2
  215. package/src/runtime/process-lifecycle.ts +20 -10
  216. package/src/runtime/process-status.ts +16 -5
  217. package/src/runtime/progress-event-coalescer.ts +2 -1
  218. package/src/runtime/progress-tracker.ts +103 -103
  219. package/src/runtime/prose-compressor.ts +9 -11
  220. package/src/runtime/recovery-recipes.ts +118 -24
  221. package/src/runtime/replace.ts +25 -10
  222. package/src/runtime/resilient-edit.ts +10 -25
  223. package/src/runtime/result-extractor.ts +1 -2
  224. package/src/runtime/retry-executor.ts +20 -4
  225. package/src/runtime/retry-runner.ts +26 -50
  226. package/src/runtime/role-permission.ts +6 -1
  227. package/src/runtime/run-coalesced-task-group.ts +256 -0
  228. package/src/runtime/run-drift.ts +14 -15
  229. package/src/runtime/run-tracker.ts +6 -28
  230. package/src/runtime/runtime-policy.ts +1 -6
  231. package/src/runtime/runtime-resolver.ts +80 -15
  232. package/src/runtime/scheduler.ts +47 -18
  233. package/src/runtime/semaphore.ts +5 -7
  234. package/src/runtime/sensitive-paths.ts +2 -1
  235. package/src/runtime/session-usage.ts +1 -1
  236. package/src/runtime/settings-store.ts +16 -12
  237. package/src/runtime/sidechain-output.ts +6 -2
  238. package/src/runtime/single-agent-compose.ts +1 -4
  239. package/src/runtime/skill-effectiveness.ts +29 -97
  240. package/src/runtime/skill-instructions.ts +40 -83
  241. package/src/runtime/stale-reconciler.ts +73 -197
  242. package/src/runtime/stream-preview.ts +1 -1
  243. package/src/runtime/streaming-output.ts +1 -1
  244. package/src/runtime/subagent-manager.ts +86 -169
  245. package/src/runtime/subprocess-tool-registry.ts +4 -1
  246. package/src/runtime/supervisor-contact.ts +10 -5
  247. package/src/runtime/task-display.ts +19 -5
  248. package/src/runtime/task-graph-scheduler.ts +95 -21
  249. package/src/runtime/task-graph.ts +5 -11
  250. package/src/runtime/task-health.ts +54 -47
  251. package/src/runtime/task-id.ts +11 -18
  252. package/src/runtime/task-output-context.ts +265 -81
  253. package/src/runtime/task-packet.ts +12 -20
  254. package/src/runtime/task-quality.ts +6 -14
  255. package/src/runtime/task-runner/context-retrieval.ts +4 -13
  256. package/src/runtime/task-runner/live-executor.ts +114 -36
  257. package/src/runtime/task-runner/output-splitter.ts +152 -0
  258. package/src/runtime/task-runner/progress.ts +50 -12
  259. package/src/runtime/task-runner/prompt-builder.ts +31 -7
  260. package/src/runtime/task-runner/prompt-pipeline.ts +31 -7
  261. package/src/runtime/task-runner/result-utils.ts +3 -1
  262. package/src/runtime/task-runner/retrieval-orchestrator.ts +310 -0
  263. package/src/runtime/task-runner/run-projection.ts +27 -8
  264. package/src/runtime/task-runner/state-helpers.ts +29 -8
  265. package/src/runtime/task-runner/tail-read.ts +2 -6
  266. package/src/runtime/task-runner.ts +180 -326
  267. package/src/runtime/team-runner-artifacts.ts +13 -0
  268. package/src/runtime/team-runner.ts +456 -974
  269. package/src/runtime/tool-output-pruner.ts +6 -9
  270. package/src/runtime/tool-progress.ts +11 -21
  271. package/src/runtime/verification-gates.ts +33 -24
  272. package/src/runtime/verification-integrity.ts +1 -4
  273. package/src/runtime/verification-worktree.ts +62 -12
  274. package/src/runtime/worker-heartbeat.ts +5 -1
  275. package/src/runtime/worker-startup.ts +29 -5
  276. package/src/runtime/workflow-state.ts +17 -9
  277. package/src/runtime/workspace-lock.ts +30 -35
  278. package/src/runtime/workspace-tree.ts +20 -32
  279. package/src/runtime/yield-handler.ts +43 -9
  280. package/src/runtime/zombie-scanner.ts +5 -4
  281. package/src/schema/config-schema.ts +266 -172
  282. package/src/schema/team-tool-schema.ts +29 -74
  283. package/src/schema/validation-types.ts +25 -17
  284. package/src/skills/discover-skills.ts +35 -10
  285. package/src/skills/skill-templates.ts +109 -27
  286. package/src/skills/validate.ts +26 -8
  287. package/src/state/active-run-registry.ts +87 -28
  288. package/src/state/artifact-store.ts +9 -5
  289. package/src/state/atomic-write-v2.ts +85 -63
  290. package/src/state/atomic-write.ts +105 -26
  291. package/src/state/blob-store.ts +47 -20
  292. package/src/state/contracts.ts +20 -5
  293. package/src/state/crew-init.ts +5 -22
  294. package/src/state/decision-ledger.ts +19 -73
  295. package/src/state/event-log-rotation.ts +41 -15
  296. package/src/state/event-log.ts +168 -53
  297. package/src/state/event-reconstructor.ts +11 -1
  298. package/src/state/gitignore-manager.ts +2 -8
  299. package/src/state/health-store.ts +57 -57
  300. package/src/state/hook-instinct-bridge.ts +1 -1
  301. package/src/state/hook-integrations.ts +1 -1
  302. package/src/state/instinct-store.ts +17 -5
  303. package/src/state/jsonl-writer.ts +1 -1
  304. package/src/state/locks.ts +23 -9
  305. package/src/state/mailbox.ts +151 -28
  306. package/src/state/observation-store.ts +20 -14
  307. package/src/state/run-cache.ts +142 -135
  308. package/src/state/run-graph.ts +6 -15
  309. package/src/state/run-metrics.ts +5 -18
  310. package/src/state/schedule.ts +15 -9
  311. package/src/state/state-store.ts +129 -45
  312. package/src/state/task-claims.ts +12 -2
  313. package/src/state/tiered-eval.ts +52 -43
  314. package/src/state/types-eval.ts +2 -2
  315. package/src/state/types.ts +24 -20
  316. package/src/state/usage.ts +20 -4
  317. package/src/state/worker-atomic-writer.ts +6 -3
  318. package/src/subagents/index.ts +2 -2
  319. package/src/teams/discover-teams.ts +21 -6
  320. package/src/tools/safe-bash-extension.ts +5 -6
  321. package/src/tools/safe-bash.ts +10 -11
  322. package/src/types/new-api-types.ts +6 -10
  323. package/src/ui/agent-management-overlay.ts +52 -40
  324. package/src/ui/card-colors.ts +13 -4
  325. package/src/ui/crew-footer.ts +8 -7
  326. package/src/ui/crew-select-list.ts +1 -1
  327. package/src/ui/dashboard-panes/agents-pane.ts +41 -25
  328. package/src/ui/dashboard-panes/cancellation-pane.ts +1 -1
  329. package/src/ui/dashboard-panes/capability-pane.ts +32 -15
  330. package/src/ui/dashboard-panes/health-pane.ts +2 -1
  331. package/src/ui/dashboard-panes/metrics-pane.ts +4 -1
  332. package/src/ui/dashboard-panes/progress-pane.ts +12 -9
  333. package/src/ui/heartbeat-aggregator.ts +15 -4
  334. package/src/ui/keybinding-map.ts +40 -7
  335. package/src/ui/live-conversation-overlay.ts +36 -20
  336. package/src/ui/live-duration.ts +1 -4
  337. package/src/ui/live-run-sidebar.ts +88 -25
  338. package/src/ui/loaders.ts +2 -8
  339. package/src/ui/mascot.ts +20 -36
  340. package/src/ui/overlays/agent-picker-overlay.ts +11 -3
  341. package/src/ui/overlays/confirm-overlay.ts +4 -2
  342. package/src/ui/overlays/help-overlay.ts +25 -14
  343. package/src/ui/overlays/mailbox-compose-overlay.ts +48 -11
  344. package/src/ui/overlays/mailbox-compose-preview.ts +20 -5
  345. package/src/ui/overlays/mailbox-detail-overlay.ts +27 -6
  346. package/src/ui/pi-ui-compat.ts +7 -7
  347. package/src/ui/powerbar-publisher.ts +120 -42
  348. package/src/ui/render-diff.ts +10 -3
  349. package/src/ui/render-scheduler.ts +12 -4
  350. package/src/ui/run-action-dispatcher.ts +81 -18
  351. package/src/ui/run-dashboard.ts +172 -76
  352. package/src/ui/run-event-bus.ts +59 -37
  353. package/src/ui/run-snapshot-cache.ts +276 -86
  354. package/src/ui/settings-overlay.ts +361 -74
  355. package/src/ui/status-colors.ts +12 -1
  356. package/src/ui/syntax-highlight.ts +1 -1
  357. package/src/ui/theme-adapter.ts +16 -14
  358. package/src/ui/theme-discovery.ts +13 -2
  359. package/src/ui/tool-progress-formatter.ts +7 -7
  360. package/src/ui/tool-render.ts +128 -56
  361. package/src/ui/tool-renderers/brief-mode.ts +45 -27
  362. package/src/ui/tool-renderers/index.ts +109 -43
  363. package/src/ui/transcript-cache.ts +32 -6
  364. package/src/ui/transcript-entries.ts +25 -23
  365. package/src/ui/transcript-viewer.ts +78 -29
  366. package/src/ui/widget/index.ts +99 -40
  367. package/src/ui/widget/widget-formatters.ts +13 -6
  368. package/src/ui/widget/widget-model.ts +19 -8
  369. package/src/ui/widget/widget-renderer.ts +23 -13
  370. package/src/ui/widget/widget-types.ts +1 -1
  371. package/src/utils/bm25-search.ts +199 -199
  372. package/src/utils/conflict-detect.ts +22 -21
  373. package/src/utils/env-filter.ts +14 -7
  374. package/src/utils/file-coalescer.ts +5 -1
  375. package/src/utils/fingerprint.ts +3 -6
  376. package/src/utils/frontmatter.ts +3 -1
  377. package/src/utils/fs-watch.ts +2 -6
  378. package/src/utils/gh-protocol.ts +119 -44
  379. package/src/utils/git.ts +13 -15
  380. package/src/utils/guards.ts +2 -5
  381. package/src/utils/ids.ts +9 -2
  382. package/src/utils/incremental-reader.ts +14 -3
  383. package/src/utils/internal-error.ts +2 -1
  384. package/src/utils/names.ts +12 -3
  385. package/src/utils/paths.ts +49 -5
  386. package/src/utils/project-detector.ts +2 -2
  387. package/src/utils/redaction.ts +29 -19
  388. package/src/utils/resolve-shell.ts +9 -7
  389. package/src/utils/run-watcher-registry.ts +19 -31
  390. package/src/utils/safe-paths.ts +45 -24
  391. package/src/utils/scan-cache.ts +9 -2
  392. package/src/utils/session-utils.ts +2 -4
  393. package/src/utils/sleep.ts +12 -5
  394. package/src/utils/sse-parser.ts +5 -17
  395. package/src/utils/visual.ts +52 -32
  396. package/src/workflows/discover-workflows.ts +41 -109
  397. package/src/workflows/intermediate-store.ts +5 -21
  398. package/src/workflows/preflight-validator.ts +6 -33
  399. package/src/workflows/topology-analyzer.ts +5 -21
  400. package/src/workflows/workflow-config.ts +7 -6
  401. package/src/worktree/branch-freshness.ts +66 -8
  402. package/src/worktree/cleanup.ts +130 -20
  403. package/src/worktree/worktree-manager.ts +212 -69
  404. package/skills/artifact-analysis-loop/SKILL.md +0 -303
  405. package/skills/detection-pipeline-design/SKILL.md +0 -286
  406. package/skills/hunting-investigation-loop/SKILL.md +0 -402
  407. package/skills/incident-playbook-construction/SKILL.md +0 -384
  408. package/skills/security-review/SKILL.md +0 -561
  409. package/skills/threat-hypothesis-framework/SKILL.md +0 -176
  410. package/skills/ui-render-performance/SKILL.md +0 -58
  411. /package/docs/{followup-review-round3-2026-05-12.md → archive/followup-review-round3-2026-05-12.md} +0 -0
  412. /package/docs/{followup-review-round4-2026-05-13.md → archive/followup-review-round4-2026-05-13.md} +0 -0
  413. /package/docs/{pi-crew-bugs.md → archive/pi-crew-bugs.md} +0 -0
  414. /package/docs/{pi-crew-test-final.md → archive/pi-crew-test-final.md} +0 -0
  415. /package/docs/{pi-crew-test-results.md → archive/pi-crew-test-results.md} +0 -0
  416. /package/docs/{pi-crew-test-round2.md → archive/pi-crew-test-round2.md} +0 -0
  417. /package/docs/{pi-crew-test-round4.md → archive/pi-crew-test-round4.md} +0 -0
  418. /package/docs/{pi-crew-test-round5.md → archive/pi-crew-test-round5.md} +0 -0
  419. /package/docs/{pi-crew-test-round6.md → archive/pi-crew-test-round6.md} +0 -0
  420. /package/docs/{pi-crew-v0.5.10-audit-fix-plan.md → archive/pi-crew-v0.5.10-audit-fix-plan.md} +0 -0
  421. /package/docs/{pi-crew-v0.5.11-audit-fix-plan.md → archive/pi-crew-v0.5.11-audit-fix-plan.md} +0 -0
  422. /package/docs/{pi-crew-v0.5.12-audit-fix-plan.md → archive/pi-crew-v0.5.12-audit-fix-plan.md} +0 -0
  423. /package/docs/{pi-crew-v0.5.13-audit-fix-plan.md → archive/pi-crew-v0.5.13-audit-fix-plan.md} +0 -0
  424. /package/docs/{pi-crew-v0.5.14-audit-fix-plan.md → archive/pi-crew-v0.5.14-audit-fix-plan.md} +0 -0
  425. /package/docs/{pi-crew-v0.5.16-audit-fix-plan.md → archive/pi-crew-v0.5.16-audit-fix-plan.md} +0 -0
  426. /package/docs/{pi-crew-v0.5.17-audit-fix-plan.md → archive/pi-crew-v0.5.17-audit-fix-plan.md} +0 -0
  427. /package/docs/{pi-crew-v0.5.5-audit-fix-plan.md → archive/pi-crew-v0.5.5-audit-fix-plan.md} +0 -0
  428. /package/docs/{pi-crew-v0.5.9-audit-fix-plan.md → archive/pi-crew-v0.5.9-audit-fix-plan.md} +0 -0
  429. /package/docs/{pi-mono-opportunities.md → archive/pi-mono-opportunities.md} +0 -0
  430. /package/docs/{pi-mono-review.md → archive/pi-mono-review.md} +0 -0
  431. /package/docs/{pi-subagent4-comparison.md → archive/pi-subagent4-comparison.md} +0 -0
  432. /package/docs/{pi-subagents3-deep-analysis.md → archive/pi-subagents3-deep-analysis.md} +0 -0
@@ -2,13 +2,13 @@ import * as crypto from "node:crypto";
2
2
  import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
4
  import { DEFAULT_CACHE, DEFAULT_PATHS } from "../config/defaults.ts";
5
- import type { TeamRunManifest } from "./types.ts";
6
- import { atomicWriteJson, renameWithRetry, isSymlinkSafePath } from "./atomic-write.ts";
5
+ import { logInternalError } from "../utils/internal-error.ts";
7
6
  import { userCrewRoot } from "../utils/paths.ts";
8
7
  import { isSafePathId } from "../utils/safe-paths.ts";
9
8
  import { sharedScanCache } from "../utils/scan-cache.ts";
10
9
  import { sleepSync } from "../utils/sleep.ts";
11
- import { logInternalError } from "../utils/internal-error.ts";
10
+ import { atomicWriteJson, isSymlinkSafePath, renameWithRetry } from "./atomic-write.ts";
11
+ import type { TeamRunManifest } from "./types.ts";
12
12
 
13
13
  /** Magic bytes prefix for binary registry to prevent deserialization of hostile files. */
14
14
  const BINARY_MAGIC = Buffer.from("PICREW2BIN", "utf-8");
@@ -37,8 +37,6 @@ function registryLockPath(): string {
37
37
  return `${registryPath()}.lock`;
38
38
  }
39
39
 
40
-
41
-
42
40
  function lockCreatedAt(raw: string): number | undefined {
43
41
  try {
44
42
  const parsed = JSON.parse(raw) as { createdAt?: unknown };
@@ -73,7 +71,13 @@ function withRegistryLock<T>(fn: () => T): T {
73
71
  try {
74
72
  const fd = fs.openSync(filePath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL, 0o644);
75
73
  try {
76
- fs.writeSync(fd, JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() }));
74
+ fs.writeSync(
75
+ fd,
76
+ JSON.stringify({
77
+ pid: process.pid,
78
+ createdAt: new Date().toISOString(),
79
+ }),
80
+ );
77
81
  } finally {
78
82
  fs.closeSync(fd);
79
83
  }
@@ -81,7 +85,8 @@ function withRegistryLock<T>(fn: () => T): T {
81
85
  } catch (error) {
82
86
  const code = (error as NodeJS.ErrnoException).code;
83
87
  if (code !== "EEXIST") throw error;
84
- if (!removeStaleRegistryLock(filePath, staleMs) && Date.now() > deadline) throw new Error("Active-run registry is locked by another operation.");
88
+ if (!removeStaleRegistryLock(filePath, staleMs) && Date.now() > deadline)
89
+ throw new Error("Active-run registry is locked by another operation.");
85
90
  sleepSync(Math.min(250, 25 * 2 ** attempt));
86
91
  attempt += 1;
87
92
  }
@@ -139,7 +144,9 @@ export function readActiveRunRegistry(maxEntries = DEFAULT_CACHE.manifestMaxEntr
139
144
  return [];
140
145
  }
141
146
  }
142
- const entries = Array.isArray(parsed) ? parsed.map(normalizeEntry).filter((entry): entry is ActiveRunRegistryEntry => entry !== undefined) : [];
147
+ const entries = Array.isArray(parsed)
148
+ ? parsed.map(normalizeEntry).filter((entry): entry is ActiveRunRegistryEntry => entry !== undefined)
149
+ : [];
143
150
  const byId = new Map<string, ActiveRunRegistryEntry>();
144
151
  for (const entry of entries.sort((a, b) => (b.updatedAt ?? "").localeCompare(a.updatedAt ?? ""))) {
145
152
  if (!byId.has(entry.runId)) byId.set(entry.runId, entry);
@@ -152,7 +159,8 @@ export function readActiveRunRegistry(maxEntries = DEFAULT_CACHE.manifestMaxEntr
152
159
  * Writes to temp file first, then renames. Includes version field for forward compatibility.
153
160
  */
154
161
  function atomicWriteBinary(filePath: string, entries: ActiveRunRegistryEntry[]): void {
155
- if (!isSymlinkSafePath(filePath)) throw new Error(`Refusing to write binary registry: target is a symlink or inside untrusted directory: ${filePath}`);
162
+ if (!isSymlinkSafePath(filePath))
163
+ throw new Error(`Refusing to write binary registry: target is a symlink or inside untrusted directory: ${filePath}`);
156
164
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
157
165
  const tempPath = `${filePath}.${crypto.randomUUID()}.tmp`;
158
166
  const O_NOFOLLOW = typeof fs.constants.O_NOFOLLOW === "number" ? fs.constants.O_NOFOLLOW : 0;
@@ -175,7 +183,11 @@ function atomicWriteBinary(filePath: string, entries: ActiveRunRegistryEntry[]):
175
183
  fs.closeSync(fd);
176
184
  renameWithRetry(tempPath, filePath);
177
185
  } catch (error) {
178
- try { fs.rmSync(tempPath, { force: true }); } catch { /* best-effort */ }
186
+ try {
187
+ fs.rmSync(tempPath, { force: true });
188
+ } catch {
189
+ /* best-effort */
190
+ }
179
191
  throw error;
180
192
  }
181
193
  }
@@ -214,26 +226,52 @@ function writeEntries(entries: ActiveRunRegistryEntry[], skipTerminalRunIds?: Se
214
226
  // instead of deleting both. This preserves whichever registry was successfully written.
215
227
  if (jsonRenamed && !binRenamed) {
216
228
  // JSON succeeded, binary failed — try to recover binary from temp
217
- try { renameWithRetry(tempBin, registryBinaryPath()); binRenamed = true; } catch { /* recovery failed */ }
229
+ try {
230
+ renameWithRetry(tempBin, registryBinaryPath());
231
+ binRenamed = true;
232
+ } catch {
233
+ /* recovery failed */
234
+ }
218
235
  } else if (binRenamed && !jsonRenamed) {
219
236
  // Binary succeeded, JSON failed — try to recover JSON from temp
220
- try { renameWithRetry(tempJson, registryPath()); jsonRenamed = true; } catch { /* recovery failed */ }
237
+ try {
238
+ renameWithRetry(tempJson, registryPath());
239
+ jsonRenamed = true;
240
+ } catch {
241
+ /* recovery failed */
242
+ }
221
243
  }
222
244
  // FIX Issue 2: If recovery failed, do NOT delete the final files — they may contain valid
223
245
  // data from a prior write cycle. Leave temp files for manual recovery and throw error.
224
246
  if (!binRenamed || !jsonRenamed) {
225
247
  // Attempt to clean up temp files only (not final files)
226
- try { fs.rmSync(tempJson, { force: true }); } catch { /* best-effort */ }
227
- try { fs.rmSync(tempBin, { force: true }); } catch { /* best-effort */ }
248
+ try {
249
+ fs.rmSync(tempJson, { force: true });
250
+ } catch {
251
+ /* best-effort */
252
+ }
253
+ try {
254
+ fs.rmSync(tempBin, { force: true });
255
+ } catch {
256
+ /* best-effort */
257
+ }
228
258
  throw error;
229
259
  }
230
260
  // FIX Issue 1: Ensure temp file cleanup executes even when recovery rename fails.
231
261
  // If recovery throws, the error propagates before reaching the cleanup below.
232
262
  // Wrapping in nested try-catch guarantees cleanup runs regardless of recovery outcome.
233
- try { fs.rmSync(tempJson, { force: true }); } catch { /* best-effort */ }
234
- try { fs.rmSync(tempBin, { force: true }); } catch { /* best-effort */ }
263
+ try {
264
+ fs.rmSync(tempJson, { force: true });
265
+ } catch {
266
+ /* best-effort */
267
+ }
268
+ try {
269
+ fs.rmSync(tempBin, { force: true });
270
+ } catch {
271
+ /* best-effort */
272
+ }
235
273
  throw error;
236
- }
274
+ }
237
275
  }
238
276
 
239
277
  const TERMINAL_STATUSES = new Set(["completed", "failed", "cancelled", "blocked"]);
@@ -246,7 +284,10 @@ const TERMINAL_STATUSES = new Set(["completed", "failed", "cancelled", "blocked"
246
284
  * Status filtering is best-effort at read time. The terminal runIds set is returned so callers
247
285
  * can pass them to writeEntries to skip entries that transitioned to terminal since filtering.
248
286
  */
249
- function filterAliveEntries(entries: ActiveRunRegistryEntry[]): { alive: ActiveRunRegistryEntry[]; terminalRunIds: Set<string> } {
287
+ function filterAliveEntries(entries: ActiveRunRegistryEntry[]): {
288
+ alive: ActiveRunRegistryEntry[];
289
+ terminalRunIds: Set<string>;
290
+ } {
250
291
  const terminalRunIds = new Set<string>();
251
292
  const alive = entries.filter((entry) => {
252
293
  try {
@@ -259,7 +300,11 @@ function filterAliveEntries(entries: ActiveRunRegistryEntry[]): { alive: ActiveR
259
300
  return false;
260
301
  }
261
302
  try {
262
- const raw = JSON.parse(fs.readFileSync(entry.manifestPath, "utf-8")) as { status?: string; async?: { pid?: number }; updatedAt?: string };
303
+ const raw = JSON.parse(fs.readFileSync(entry.manifestPath, "utf-8")) as {
304
+ status?: string;
305
+ async?: { pid?: number };
306
+ updatedAt?: string;
307
+ };
263
308
  if (TERMINAL_STATUSES.has(raw.status ?? "")) {
264
309
  terminalRunIds.add(entry.runId);
265
310
  return false;
@@ -268,7 +313,9 @@ function filterAliveEntries(entries: ActiveRunRegistryEntry[]): { alive: ActiveR
268
313
  // EPERM means process exists but we can't signal it (different security context).
269
314
  // Also check manifest age to guard against PID reuse.
270
315
  if (raw.async?.pid) {
271
- try { process.kill(raw.async.pid, 0); } catch (err) {
316
+ try {
317
+ process.kill(raw.async.pid, 0);
318
+ } catch (err) {
272
319
  // Only treat "process does not exist" as dead (ESRCH/ENOENT).
273
320
  // EPERM means process is alive but protected; treat as alive.
274
321
  const code = (err as NodeJS.ErrnoException).code;
@@ -277,13 +324,13 @@ function filterAliveEntries(entries: ActiveRunRegistryEntry[]): { alive: ActiveR
277
324
  }
278
325
  // FIX Issue 2: Async runs older than 30 min are stale even if PID is alive.
279
326
  // This guards against PID reuse after the original process exits.
280
- const updatedAt = typeof raw.updatedAt === 'string' ? Date.parse(raw.updatedAt) : NaN;
327
+ const updatedAt = typeof raw.updatedAt === "string" ? Date.parse(raw.updatedAt) : NaN;
281
328
  if (Number.isFinite(updatedAt) && Date.now() - updatedAt > 30 * 60 * 1000) return false;
282
329
  }
283
330
  // 2.19 — Stale non-async run: live-session/scaffold runs older than 30 min
284
331
  // Without this, test runs that crash/leak would stay in the registry forever.
285
332
  if (!raw.async) {
286
- const updatedAt = typeof raw.updatedAt === 'string' ? Date.parse(raw.updatedAt) : NaN;
333
+ const updatedAt = typeof raw.updatedAt === "string" ? Date.parse(raw.updatedAt) : NaN;
287
334
  if (Number.isFinite(updatedAt) && Date.now() - updatedAt > 30 * 60 * 1000) return false;
288
335
  }
289
336
  } catch {
@@ -355,17 +402,29 @@ export function activeRunEntries(): ActiveRunRegistryEntry[] {
355
402
  if (!isSymlinkSafePath(entry.stateRoot)) continue;
356
403
  // FIX Issue 4: Use cached manifest content directly when available.
357
404
  // sharedScanCache.readAndCache already read the file; avoid redundant re-read.
358
- const manifest = (cached?.raw !== undefined
359
- ? cached.raw
360
- : JSON.parse(fs.readFileSync(entry.manifestPath, "utf-8"))) as { status?: unknown; updatedAt?: string; async?: { pid?: number } };
361
- if (manifest.status !== "queued" && manifest.status !== "planning" && manifest.status !== "running" && manifest.status !== "blocked") continue;
405
+ const manifest = (cached?.raw !== undefined ? cached.raw : JSON.parse(fs.readFileSync(entry.manifestPath, "utf-8"))) as {
406
+ status?: unknown;
407
+ updatedAt?: string;
408
+ async?: { pid?: number };
409
+ };
410
+ if (
411
+ manifest.status !== "queued" &&
412
+ manifest.status !== "planning" &&
413
+ manifest.status !== "running" &&
414
+ manifest.status !== "blocked"
415
+ )
416
+ continue;
362
417
  // PID liveness check: async runs with dead PID are stale — don't surface them
363
418
  if (manifest.async?.pid) {
364
- try { process.kill(manifest.async.pid, 0); } catch { continue; }
419
+ try {
420
+ process.kill(manifest.async.pid, 0);
421
+ } catch {
422
+ continue;
423
+ }
365
424
  }
366
425
  // Stale non-async run: live-session/scaffold runs older than 30 min
367
426
  if (!manifest.async) {
368
- const updatedAt = typeof manifest.updatedAt === 'string' ? Date.parse(manifest.updatedAt) : NaN;
427
+ const updatedAt = typeof manifest.updatedAt === "string" ? Date.parse(manifest.updatedAt) : NaN;
369
428
  if (Number.isFinite(updatedAt) && Date.now() - updatedAt > 30 * 60 * 1000) continue;
370
429
  }
371
430
  entries.push(entry);
@@ -1,10 +1,10 @@
1
+ import { createHash } from "node:crypto";
1
2
  import * as fs from "node:fs";
2
3
  import * as path from "node:path";
3
- import { createHash } from "node:crypto";
4
- import type { ArtifactDescriptor } from "./types.ts";
5
- import { atomicWriteFile } from "./atomic-write.ts";
6
- import { resolveRealContainedPath } from "../utils/safe-paths.ts";
7
4
  import { redactSecretString, redactSecrets } from "../utils/redaction.ts";
5
+ import { resolveRealContainedPath } from "../utils/safe-paths.ts";
6
+ import { atomicWriteFile } from "./atomic-write.ts";
7
+ import type { ArtifactDescriptor } from "./types.ts";
8
8
 
9
9
  function hashContent(content: string): string {
10
10
  return createHash("sha256").update(content).digest("hex");
@@ -107,7 +107,11 @@ function resolveInside(baseDir: string, relativePath: string): string {
107
107
  }
108
108
 
109
109
  const normalizedRelativePath = relativePath.replaceAll("\\", "/").replace(/^\.\/+/, "");
110
- if (!normalizedRelativePath || normalizedRelativePath.split("/").some((segment) => segment === "..") || path.isAbsolute(normalizedRelativePath)) {
110
+ if (
111
+ !normalizedRelativePath ||
112
+ normalizedRelativePath.split("/").some((segment) => segment === "..") ||
113
+ path.isAbsolute(normalizedRelativePath)
114
+ ) {
111
115
  throw new Error(`Invalid artifact path: ${relativePath}`);
112
116
  }
113
117
  // Use resolveRealContainedPath to resolve symlinks before checking containment,
@@ -13,74 +13,96 @@ import * as path from "node:path";
13
13
  * - UUID-based tmp file to prevent collisions under concurrent writes
14
14
  */
15
15
  export class AtomicWriter {
16
- private initializedDirs = new Set<string>();
17
- private baseDir: string;
16
+ private initializedDirs = new Set<string>();
17
+ private baseDir: string;
18
18
 
19
- constructor(baseDir: string) {
20
- this.baseDir = baseDir;
21
- }
19
+ constructor(baseDir: string) {
20
+ this.baseDir = baseDir;
21
+ }
22
22
 
23
- writeSync(targetPath: string, content: string): void {
24
- this.ensureParentDir(targetPath);
25
- const tmpPath = this.tmpPath(targetPath);
26
- const fd = fs.openSync(tmpPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL, 0o600);
27
- try {
28
- fs.writeSync(fd, content, undefined, "utf8");
29
- try { fs.fsyncSync(fd); } catch { /* best-effort */ }
30
- } finally {
31
- fs.closeSync(fd);
32
- }
33
- try {
34
- fs.renameSync(tmpPath, targetPath);
35
- } catch (err) {
36
- try { fs.unlinkSync(tmpPath); } catch { /* best-effort cleanup */ }
37
- throw err;
38
- }
39
- }
23
+ writeSync(targetPath: string, content: string): void {
24
+ this.ensureParentDir(targetPath);
25
+ const tmpPath = this.tmpPath(targetPath);
26
+ const fd = fs.openSync(tmpPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL, 0o600);
27
+ try {
28
+ fs.writeSync(fd, content, undefined, "utf8");
29
+ try {
30
+ fs.fsyncSync(fd);
31
+ } catch {
32
+ /* best-effort */
33
+ }
34
+ } finally {
35
+ fs.closeSync(fd);
36
+ }
37
+ try {
38
+ fs.renameSync(tmpPath, targetPath);
39
+ } catch (err) {
40
+ try {
41
+ fs.unlinkSync(tmpPath);
42
+ } catch {
43
+ /* best-effort cleanup */
44
+ }
45
+ throw err;
46
+ }
47
+ }
40
48
 
41
- async writeAsync(targetPath: string, content: string): Promise<void> {
42
- await fs.promises.mkdir(path.dirname(targetPath), { recursive: true });
43
- const tmpPath = this.tmpPath(targetPath);
44
- const fd = await fs.promises.open(tmpPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL, 0o600);
45
- try {
46
- await fd.writeFile(content, "utf8");
47
- try { await fd.sync(); } catch { /* best-effort */ }
48
- } finally {
49
- await fd.close();
50
- }
51
- try {
52
- await fs.promises.rename(tmpPath, targetPath);
53
- } catch (err) {
54
- try { await fs.promises.unlink(tmpPath); } catch { /* best-effort cleanup */ }
55
- throw err;
56
- }
57
- }
49
+ async writeAsync(targetPath: string, content: string): Promise<void> {
50
+ await fs.promises.mkdir(path.dirname(targetPath), { recursive: true });
51
+ const tmpPath = this.tmpPath(targetPath);
52
+ const fd = await fs.promises.open(tmpPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL, 0o600);
53
+ try {
54
+ await fd.writeFile(content, "utf8");
55
+ try {
56
+ await fd.sync();
57
+ } catch {
58
+ /* best-effort */
59
+ }
60
+ } finally {
61
+ await fd.close();
62
+ }
63
+ try {
64
+ await fs.promises.rename(tmpPath, targetPath);
65
+ } catch (err) {
66
+ try {
67
+ await fs.promises.unlink(tmpPath);
68
+ } catch {
69
+ /* best-effort cleanup */
70
+ }
71
+ throw err;
72
+ }
73
+ }
58
74
 
59
- writeJsonSync<T>(targetPath: string, value: T): void {
60
- this.writeSync(targetPath, JSON.stringify(value, null, 2) + "\n");
61
- }
75
+ writeJsonSync<T>(targetPath: string, value: T): void {
76
+ this.writeSync(targetPath, JSON.stringify(value, null, 2) + "\n");
77
+ }
62
78
 
63
- async writeJsonAsync<T>(targetPath: string, value: T): Promise<void> {
64
- await this.writeAsync(targetPath, JSON.stringify(value, null, 2) + "\n");
65
- }
79
+ async writeJsonAsync<T>(targetPath: string, value: T): Promise<void> {
80
+ await this.writeAsync(targetPath, JSON.stringify(value, null, 2) + "\n");
81
+ }
66
82
 
67
- private tmpPath(targetPath: string): string {
68
- const uuid = crypto.randomUUID();
69
- return `${targetPath}.${uuid}.tmp`;
70
- }
83
+ private tmpPath(targetPath: string): string {
84
+ const uuid = crypto.randomUUID();
85
+ return `${targetPath}.${uuid}.tmp`;
86
+ }
71
87
 
72
- private ensureParentDir(targetPath: string): void {
73
- const dir = path.dirname(targetPath);
74
- fs.mkdirSync(dir, { recursive: true });
75
- this.ensureGitignore(dir);
76
- }
88
+ private ensureParentDir(targetPath: string): void {
89
+ const dir = path.dirname(targetPath);
90
+ fs.mkdirSync(dir, { recursive: true });
91
+ this.ensureGitignore(dir);
92
+ }
77
93
 
78
- private ensureGitignore(dir: string): void {
79
- if (this.initializedDirs.has(dir)) return;
80
- this.initializedDirs.add(dir);
81
- const gitignorePath = path.join(dir, ".gitignore");
82
- try { fs.accessSync(gitignorePath); } catch {
83
- try { fs.writeFileSync(gitignorePath, "*\n", "utf8"); } catch { /* best-effort */ }
84
- }
85
- }
86
- }
94
+ private ensureGitignore(dir: string): void {
95
+ if (this.initializedDirs.has(dir)) return;
96
+ this.initializedDirs.add(dir);
97
+ const gitignorePath = path.join(dir, ".gitignore");
98
+ try {
99
+ fs.accessSync(gitignorePath);
100
+ } catch {
101
+ try {
102
+ fs.writeFileSync(gitignorePath, "*\n", "utf8");
103
+ } catch {
104
+ /* best-effort */
105
+ }
106
+ }
107
+ }
108
+ }