@elhu/pit 0.1.1

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 (192) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +380 -0
  3. package/dist/adapters/claude-code.d.ts +70 -0
  4. package/dist/adapters/claude-code.d.ts.map +1 -0
  5. package/dist/adapters/claude-code.js +166 -0
  6. package/dist/adapters/claude-code.js.map +1 -0
  7. package/dist/adapters/index.d.ts +16 -0
  8. package/dist/adapters/index.d.ts.map +1 -0
  9. package/dist/adapters/index.js +49 -0
  10. package/dist/adapters/index.js.map +1 -0
  11. package/dist/adapters/opencode.d.ts +53 -0
  12. package/dist/adapters/opencode.d.ts.map +1 -0
  13. package/dist/adapters/opencode.js +120 -0
  14. package/dist/adapters/opencode.js.map +1 -0
  15. package/dist/adapters/process-utils.d.ts +29 -0
  16. package/dist/adapters/process-utils.d.ts.map +1 -0
  17. package/dist/adapters/process-utils.js +96 -0
  18. package/dist/adapters/process-utils.js.map +1 -0
  19. package/dist/adapters/types.d.ts +41 -0
  20. package/dist/adapters/types.d.ts.map +1 -0
  21. package/dist/adapters/types.js +6 -0
  22. package/dist/adapters/types.js.map +1 -0
  23. package/dist/assets.generated.d.ts +13 -0
  24. package/dist/assets.generated.d.ts.map +1 -0
  25. package/dist/assets.generated.js +162 -0
  26. package/dist/assets.generated.js.map +1 -0
  27. package/dist/beads.d.ts +85 -0
  28. package/dist/beads.d.ts.map +1 -0
  29. package/dist/beads.js +120 -0
  30. package/dist/beads.js.map +1 -0
  31. package/dist/cli.d.ts +3 -0
  32. package/dist/cli.d.ts.map +1 -0
  33. package/dist/cli.js +39 -0
  34. package/dist/cli.js.map +1 -0
  35. package/dist/commands/add.d.ts +10 -0
  36. package/dist/commands/add.d.ts.map +1 -0
  37. package/dist/commands/add.js +58 -0
  38. package/dist/commands/add.js.map +1 -0
  39. package/dist/commands/cleanup.d.ts +13 -0
  40. package/dist/commands/cleanup.d.ts.map +1 -0
  41. package/dist/commands/cleanup.js +174 -0
  42. package/dist/commands/cleanup.js.map +1 -0
  43. package/dist/commands/daemon.d.ts +3 -0
  44. package/dist/commands/daemon.d.ts.map +1 -0
  45. package/dist/commands/daemon.js +162 -0
  46. package/dist/commands/daemon.js.map +1 -0
  47. package/dist/commands/init.d.ts +20 -0
  48. package/dist/commands/init.d.ts.map +1 -0
  49. package/dist/commands/init.js +125 -0
  50. package/dist/commands/init.js.map +1 -0
  51. package/dist/commands/install-keybinding.d.ts +61 -0
  52. package/dist/commands/install-keybinding.d.ts.map +1 -0
  53. package/dist/commands/install-keybinding.js +138 -0
  54. package/dist/commands/install-keybinding.js.map +1 -0
  55. package/dist/commands/install-status.d.ts +35 -0
  56. package/dist/commands/install-status.d.ts.map +1 -0
  57. package/dist/commands/install-status.js +115 -0
  58. package/dist/commands/install-status.js.map +1 -0
  59. package/dist/commands/log.d.ts +7 -0
  60. package/dist/commands/log.d.ts.map +1 -0
  61. package/dist/commands/log.js +60 -0
  62. package/dist/commands/log.js.map +1 -0
  63. package/dist/commands/pause.d.ts +12 -0
  64. package/dist/commands/pause.d.ts.map +1 -0
  65. package/dist/commands/pause.js +47 -0
  66. package/dist/commands/pause.js.map +1 -0
  67. package/dist/commands/resume.d.ts +12 -0
  68. package/dist/commands/resume.d.ts.map +1 -0
  69. package/dist/commands/resume.js +59 -0
  70. package/dist/commands/resume.js.map +1 -0
  71. package/dist/commands/shared.d.ts +7 -0
  72. package/dist/commands/shared.d.ts.map +1 -0
  73. package/dist/commands/shared.js +56 -0
  74. package/dist/commands/shared.js.map +1 -0
  75. package/dist/commands/start.d.ts +12 -0
  76. package/dist/commands/start.d.ts.map +1 -0
  77. package/dist/commands/start.js +274 -0
  78. package/dist/commands/start.js.map +1 -0
  79. package/dist/commands/status.d.ts +24 -0
  80. package/dist/commands/status.d.ts.map +1 -0
  81. package/dist/commands/status.js +101 -0
  82. package/dist/commands/status.js.map +1 -0
  83. package/dist/commands/stop.d.ts +11 -0
  84. package/dist/commands/stop.d.ts.map +1 -0
  85. package/dist/commands/stop.js +52 -0
  86. package/dist/commands/stop.js.map +1 -0
  87. package/dist/commands/teardown.d.ts +15 -0
  88. package/dist/commands/teardown.d.ts.map +1 -0
  89. package/dist/commands/teardown.js +72 -0
  90. package/dist/commands/teardown.js.map +1 -0
  91. package/dist/config.d.ts +58 -0
  92. package/dist/config.d.ts.map +1 -0
  93. package/dist/config.js +129 -0
  94. package/dist/config.js.map +1 -0
  95. package/dist/daemon/client.d.ts +38 -0
  96. package/dist/daemon/client.d.ts.map +1 -0
  97. package/dist/daemon/client.js +254 -0
  98. package/dist/daemon/client.js.map +1 -0
  99. package/dist/daemon/context.d.ts +63 -0
  100. package/dist/daemon/context.d.ts.map +1 -0
  101. package/dist/daemon/context.js +14 -0
  102. package/dist/daemon/context.js.map +1 -0
  103. package/dist/daemon/handlers.d.ts +79 -0
  104. package/dist/daemon/handlers.d.ts.map +1 -0
  105. package/dist/daemon/handlers.js +1260 -0
  106. package/dist/daemon/handlers.js.map +1 -0
  107. package/dist/daemon/index.d.ts +6 -0
  108. package/dist/daemon/index.d.ts.map +1 -0
  109. package/dist/daemon/index.js +7 -0
  110. package/dist/daemon/index.js.map +1 -0
  111. package/dist/daemon/lifecycle.d.ts +56 -0
  112. package/dist/daemon/lifecycle.d.ts.map +1 -0
  113. package/dist/daemon/lifecycle.js +341 -0
  114. package/dist/daemon/lifecycle.js.map +1 -0
  115. package/dist/daemon/protocol.d.ts +174 -0
  116. package/dist/daemon/protocol.d.ts.map +1 -0
  117. package/dist/daemon/protocol.js +3 -0
  118. package/dist/daemon/protocol.js.map +1 -0
  119. package/dist/daemon/recovery.d.ts +37 -0
  120. package/dist/daemon/recovery.d.ts.map +1 -0
  121. package/dist/daemon/recovery.js +197 -0
  122. package/dist/daemon/recovery.js.map +1 -0
  123. package/dist/daemon/server.d.ts +31 -0
  124. package/dist/daemon/server.d.ts.map +1 -0
  125. package/dist/daemon/server.js +294 -0
  126. package/dist/daemon/server.js.map +1 -0
  127. package/dist/daemon/socket.d.ts +18 -0
  128. package/dist/daemon/socket.d.ts.map +1 -0
  129. package/dist/daemon/socket.js +36 -0
  130. package/dist/daemon/socket.js.map +1 -0
  131. package/dist/daemon/state.d.ts +60 -0
  132. package/dist/daemon/state.d.ts.map +1 -0
  133. package/dist/daemon/state.js +156 -0
  134. package/dist/daemon/state.js.map +1 -0
  135. package/dist/daemon/systemd.d.ts +19 -0
  136. package/dist/daemon/systemd.d.ts.map +1 -0
  137. package/dist/daemon/systemd.js +131 -0
  138. package/dist/daemon/systemd.js.map +1 -0
  139. package/dist/hooks/claude-code-hook.d.ts +32 -0
  140. package/dist/hooks/claude-code-hook.d.ts.map +1 -0
  141. package/dist/hooks/claude-code-hook.js +112 -0
  142. package/dist/hooks/claude-code-hook.js.map +1 -0
  143. package/dist/instructions-template.d.ts +9 -0
  144. package/dist/instructions-template.d.ts.map +1 -0
  145. package/dist/instructions-template.js +123 -0
  146. package/dist/instructions-template.js.map +1 -0
  147. package/dist/logger.d.ts +25 -0
  148. package/dist/logger.d.ts.map +1 -0
  149. package/dist/logger.js +44 -0
  150. package/dist/logger.js.map +1 -0
  151. package/dist/loop.d.ts +88 -0
  152. package/dist/loop.d.ts.map +1 -0
  153. package/dist/loop.js +161 -0
  154. package/dist/loop.js.map +1 -0
  155. package/dist/orchestrator-instructions-template.d.ts +13 -0
  156. package/dist/orchestrator-instructions-template.d.ts.map +1 -0
  157. package/dist/orchestrator-instructions-template.js +147 -0
  158. package/dist/orchestrator-instructions-template.js.map +1 -0
  159. package/dist/output.d.ts +12 -0
  160. package/dist/output.d.ts.map +1 -0
  161. package/dist/output.js +25 -0
  162. package/dist/output.js.map +1 -0
  163. package/dist/plugin/pit.js +57 -0
  164. package/dist/session.d.ts +55 -0
  165. package/dist/session.d.ts.map +1 -0
  166. package/dist/session.js +135 -0
  167. package/dist/session.js.map +1 -0
  168. package/dist/setup.d.ts +92 -0
  169. package/dist/setup.d.ts.map +1 -0
  170. package/dist/setup.js +382 -0
  171. package/dist/setup.js.map +1 -0
  172. package/dist/shell-quote.d.ts +16 -0
  173. package/dist/shell-quote.d.ts.map +1 -0
  174. package/dist/shell-quote.js +18 -0
  175. package/dist/shell-quote.js.map +1 -0
  176. package/dist/signals.d.ts +17 -0
  177. package/dist/signals.d.ts.map +1 -0
  178. package/dist/signals.js +26 -0
  179. package/dist/signals.js.map +1 -0
  180. package/dist/state-machine.d.ts +74 -0
  181. package/dist/state-machine.d.ts.map +1 -0
  182. package/dist/state-machine.js +153 -0
  183. package/dist/state-machine.js.map +1 -0
  184. package/dist/tmux.d.ts +101 -0
  185. package/dist/tmux.d.ts.map +1 -0
  186. package/dist/tmux.js +208 -0
  187. package/dist/tmux.js.map +1 -0
  188. package/dist/worktree.d.ts +33 -0
  189. package/dist/worktree.d.ts.map +1 -0
  190. package/dist/worktree.js +116 -0
  191. package/dist/worktree.js.map +1 -0
  192. package/package.json +66 -0
package/dist/loop.js ADDED
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Loop runner for pit.
3
+ *
4
+ * startLoop() starts the autonomous ticket loop for a single epic:
5
+ * - Creates the state machine and immediately advances it to RUNNING
6
+ * - On state changes: updates the tmux status bar
7
+ * - Writes all transitions to the session log
8
+ *
9
+ * Returns a LoopHandle that lets the caller stop the loop and query its state.
10
+ */
11
+ import { EpicStateMachine, InvalidTransitionError, } from "./state-machine.js";
12
+ import { setUserOption } from "./tmux.js";
13
+ // ---------------------------------------------------------------------------
14
+ // startLoop
15
+ // ---------------------------------------------------------------------------
16
+ /**
17
+ * Start the autonomous ticket loop for one epic.
18
+ *
19
+ * Precondition: the agent TUI is running and the initial prompt has already
20
+ * been sent by setupEpic(). The state machine starts in SETUP and this
21
+ * function immediately advances it to RUNNING.
22
+ *
23
+ * @returns A LoopHandle for stopping the loop and querying state.
24
+ */
25
+ export function startLoop(options) {
26
+ const { epic, logger, initialState, initialPauseReason } = options;
27
+ const machine = new EpicStateMachine({
28
+ epic,
29
+ onTransition: handleTransition,
30
+ initialState,
31
+ initialPauseReason,
32
+ });
33
+ let stopped = false;
34
+ // --------------------------------------------------------------------------
35
+ // Transition handler — fires synchronously inside the mutex for each state change
36
+ // --------------------------------------------------------------------------
37
+ function handleTransition(transition) {
38
+ const { from, to, reason, pauseReason: transitionPauseReason, timestamp } = transition;
39
+ // Log every transition
40
+ logger.info(`[${epic}] ${from} -> ${to} (${reason})`, {
41
+ epic,
42
+ from,
43
+ to,
44
+ reason,
45
+ pauseReason: transitionPauseReason,
46
+ timestamp,
47
+ });
48
+ // Fire the optional status bar updater (fire-and-forget)
49
+ if (options.onStatusBarUpdate) {
50
+ const result = options.onStatusBarUpdate(epic, transition);
51
+ if (result && typeof result.catch === "function") {
52
+ result.catch(_swallow);
53
+ }
54
+ }
55
+ }
56
+ // --------------------------------------------------------------------------
57
+ // Stop helper
58
+ // --------------------------------------------------------------------------
59
+ function doStop() {
60
+ if (stopped)
61
+ return;
62
+ stopped = true;
63
+ // Transition to DONE (fire-and-forget)
64
+ machine.transition("DONE", "MANUAL_STOP").catch((err) => {
65
+ // Swallow InvalidTransitionError (may already be DONE)
66
+ if (!(err instanceof InvalidTransitionError)) {
67
+ logger.error(`[${epic}] failed to transition to DONE on stop`, { err: String(err) });
68
+ }
69
+ });
70
+ }
71
+ // --------------------------------------------------------------------------
72
+ // Start: advance SETUP → RUNNING
73
+ // --------------------------------------------------------------------------
74
+ // Immediately transition to RUNNING (setup already complete from setupEpic)
75
+ machine.transition("RUNNING", "TUI_READY").catch((err) => {
76
+ logger.error(`[${epic}] failed to transition to RUNNING`, {
77
+ epic,
78
+ err: String(err),
79
+ });
80
+ });
81
+ // --------------------------------------------------------------------------
82
+ // LoopHandle
83
+ // --------------------------------------------------------------------------
84
+ return {
85
+ get state() {
86
+ return machine.state;
87
+ },
88
+ get pauseReason() {
89
+ return machine.pauseReason;
90
+ },
91
+ machine,
92
+ stop() {
93
+ doStop();
94
+ },
95
+ };
96
+ }
97
+ /**
98
+ * Map a raw pauseReason string (or null) to a short category keyword.
99
+ */
100
+ export function pauseReasonToCategory(reason) {
101
+ if (reason === null)
102
+ return "unknown";
103
+ if (reason.includes("idle") || reason === "UNRECOGNIZED_IDLE")
104
+ return "idle";
105
+ if (reason.startsWith("TIMEOUT:"))
106
+ return "timeout";
107
+ if (reason.includes("permission"))
108
+ return "permission";
109
+ if (reason.includes("crashed"))
110
+ return "crashed";
111
+ if (reason === "MANUAL_PAUSE" || reason.includes("manual"))
112
+ return "manual";
113
+ if (reason === "DAEMON_SHUTDOWN")
114
+ return "shutdown";
115
+ if (reason.includes("NEEDS_HUMAN_INPUT"))
116
+ return "input";
117
+ return "unknown";
118
+ }
119
+ /**
120
+ * Build a tmux status bar string from a map of epic → StatusBarEpic.
121
+ *
122
+ * Format:
123
+ * RUNNING/CLEARING: "[auth: running 5/12]" or "[auth: running]" (no progress)
124
+ * PAUSED: "[pay: PAUSED! timeout]"
125
+ * DONE: "[ui: done 8/8]"
126
+ * SETUP: "[auth: setup]"
127
+ */
128
+ export function formatStatusBar(epics) {
129
+ if (epics.size === 0)
130
+ return "";
131
+ return Array.from(epics.entries())
132
+ .map(([epic, { state, progress, pauseReason }]) => {
133
+ if (state === "PAUSED") {
134
+ const category = pauseReasonToCategory(pauseReason);
135
+ return `[${epic}: PAUSED! ${category}]`;
136
+ }
137
+ const label = state.toLowerCase();
138
+ if (state === "SETUP") {
139
+ return `[${epic}: ${label}]`;
140
+ }
141
+ // RUNNING, CLEARING, DONE — show progress if available
142
+ if (progress !== null) {
143
+ return `[${epic}: ${label} ${progress.done}/${progress.total}]`;
144
+ }
145
+ return `[${epic}: ${label}]`;
146
+ })
147
+ .join(" ");
148
+ }
149
+ /**
150
+ * Update the tmux status bar with the current epic states.
151
+ * Fire-and-forget — errors are swallowed.
152
+ */
153
+ export async function updateStatusBar(tmuxSession, epics) {
154
+ const value = formatStatusBar(epics);
155
+ await setUserOption(tmuxSession, "pit-status", value).catch(_swallow);
156
+ }
157
+ /** Silently swallow a rejected promise — used to suppress fire-and-forget errors. */
158
+ function _swallow(_err) {
159
+ // intentionally empty
160
+ }
161
+ //# sourceMappingURL=loop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loop.js","sourceRoot":"","sources":["../src/loop.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Orchestrator instructions template.
3
+ *
4
+ * Injected into a target project's AGENTS.md by `pit init` so the
5
+ * orchestrating LLM knows how to drive pit.
6
+ *
7
+ * Delimited by sentinel comments so `pit init` can find and replace
8
+ * the section idempotently.
9
+ */
10
+ export declare const ORCHESTRATOR_SECTION_START = "<!-- pit:orchestrator:start -->";
11
+ export declare const ORCHESTRATOR_SECTION_END = "<!-- pit:orchestrator:end -->";
12
+ export declare const ORCHESTRATOR_INSTRUCTIONS = "<!-- pit:orchestrator:start -->\n## Parallel Agent Orchestration with pit\n\nYou have access to `pit`, a tmux-based CLI that manages parallel AI coding agent sessions.\nEach agent works in its own git worktree and tmux window, autonomously looping through\ntickets in a beads epic.\n\n### Prerequisites\n\nBefore using pit, ensure the project has epics and tickets set up with beads (`bd`).\n\n### Workflow\n\n1. **Start agents**: `pit start --epics <epic-id>` (comma-separated for multiple)\n - Creates one git worktree + tmux window + agent TUI per epic\n - Each agent begins working through tickets autonomously\n2. **Monitor**: `pit status` \u2014 returns JSON with state of all epics\n3. **Read agent output**: `pit log <epic>` \u2014 captures the agent's terminal output\n4. **Pause an agent**: `pit pause <epic>` \u2014 pauses the agent's autonomous loop\n5. **Resume with guidance**: `pit resume <epic> --message \"your instructions\"`\n \u2014 resumes a paused agent, injecting your message into its context\n\n### CLI Reference\n\n| Command | Description |\n|---|---|\n| `pit start --epics <ids>` | Start agent sessions for given epic IDs |\n| `pit status` | Show status of all running epics (JSON) |\n| `pit log <epic>` | Capture agent's recent terminal output |\n| `pit log <epic> --lines 100` | Capture more lines of output |\n| `pit pause <epic>` | Pause a running epic's autonomous loop |\n| `pit resume <epic> --message \"...\"` | Resume a paused epic with guidance |\n| `pit teardown [<epic-id>]` | Tear down all epics (or a single epic), removes worktrees by default |\n| `pit teardown --keep-worktrees` | Tear down without removing git worktrees |\n| `pit teardown --force` | Tear down all epics even if some are still running |\n\n### Start Options\n\n| Flag | Default | Description |\n|---|---|---|\n| `--epics <ids>` | (required) | Comma-separated beads epic IDs |\n| `--agent <type>` | `auto` | Agent type: `auto`, `Claude`, or `claude-code` |\n| `--base-branch <branch>` | `main` | Base branch for worktrees |\n| `--tmux-session <name>` | `pit` | tmux session name |\n| `--worktree-dir <path>` | `.worktrees` | Directory for git worktrees |\n| `--prompt-template <path>` | (built-in) | Path to a custom prompt template file |\n| `--instructions-template <path>` | (built-in) | Path to a custom agent instructions template file |\n| `--ticket-timeout <minutes>` | (none) | Auto-pause agents when a ticket exceeds this duration |\n| `--model <value>` | (none) | Model to pass to the agent CLI (passed through as `--model <value>`) |\n| `--epic-model <mapping>` | (none) | Per-epic model override, repeatable: `--epic-model <epic>=<model>` |\n\n> **Model resolution order:** `--epic-model <epic>=<model>` overrides `--model` / `.pit.json` model, which overrides the agent default. The model string is passed through to the agent CLI as-is.\n\n### Custom Templates\n\nUse `--prompt-template` and `--instructions-template` to override the built-in agent\nprompt and instructions with your own files. Templates support variable substitution:\n\n- `{{EPIC_ID}}` \u2014 replaced with the epic ID being worked on\n- `{{EPIC_NAME}}` \u2014 replaced with the epic name\n\n### Configuration\n\npit reads `.pit.json` from the project root. CLI flags override file values; missing\nfields fall back to defaults.\n\n```json\n{\n \"agent\": \"auto\",\n \"model\": null,\n \"worktreeDir\": \".worktrees\",\n \"baseBranch\": \"main\",\n \"tmuxSession\": \"pit\",\n \"clearDelay\": 2000,\n \"initDelay\": 5000,\n \"ticketTimeout\": null\n}\n```\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `agent` | string | `\"auto\"` | Agent type: `auto`, `opencode`, or `claude-code` |\n| `model` | string|null | `null` | Model to pass to the agent CLI; null = agent default |\n| `worktreeDir` | string | `\".worktrees\"` | Directory for git worktrees |\n| `baseBranch` | string | `\"main\"` | Base branch for worktrees |\n| `tmuxSession` | string | `\"pit\"` | tmux session name |\n| `clearDelay` | number | `2000` | Ms to wait before clearing agent context after ticket completion |\n| `initDelay` | number | `5000` | Ms to wait before starting the agent after setup |\n| `ticketTimeout` | number|null | `null` | Minutes before a ticket auto-pauses the agent (null = disabled) |\n\n### Output Format\n\nAll pit commands output JSON by default (for LLM consumption). Add `--pretty` for\nhuman-readable output. Parse the JSON to understand the current state:\n\n```json\n// pit status example\n{\n \"epics\": {\n \"auth\": { \"state\": \"running\", \"progress\": \"3/7\" },\n \"payments\": { \"state\": \"paused\", \"progress\": \"1/4\" }\n }\n}\n```\n\n### When to Intervene\n\n- **`pit status` shows an epic as \"paused\"**: An agent hit `NEEDS_HUMAN_INPUT`.\n Read `pit log <epic>` to understand what the agent needs, then\n `pit resume <epic> --message \"your answer\"`.\n- **Pause reason starts with `TIMEOUT:`**: The agent was auto-paused because the\n ticket exceeded the configured `ticketTimeout`. Read `pit log <epic>` to assess\n progress, then `pit resume <epic> --message \"your guidance\"` or let it continue\n with `pit resume <epic>`.\n- **An agent seems stuck**: Use `pit log <epic>` to check progress.\n `pit pause <epic>` if needed, then resume with instructions.\n- **Epic completes**: The epic state becomes `\"done\"`. Merge the worktree branch\n back to the base branch.\n\n### Session Reliability\n\npit automatically manages long-running sessions reliably. Once started, agent sessions\ncontinue running independently until completion, regardless of how long they take.\nNo special flags or configuration needed.\n\n### Important Notes\n\n- Agents work independently \u2014 they do not communicate with each other.\n- Each agent works in its own git worktree, so there are no file conflicts during work.\n- Merging worktree branches back is a manual step (done by you after epic completion).\n- The tmux session is named `pit` by default. You can attach to it to observe agents directly.\n- `pit teardown` removes worktrees by default. Use `--keep-worktrees` if you need to inspect the worktree after teardown.\n- `pit teardown` (no epic specified) will fail if any epic is in `running` state, to prevent accidental destruction of active sessions. Use `pit teardown --force` to bypass this guard when you deliberately want to tear everything down.\n<!-- pit:orchestrator:end -->\n";
13
+ //# sourceMappingURL=orchestrator-instructions-template.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestrator-instructions-template.d.ts","sourceRoot":"","sources":["../src/orchestrator-instructions-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,eAAO,MAAM,0BAA0B,oCAAoC,CAAC;AAC5E,eAAO,MAAM,wBAAwB,kCAAkC,CAAC;AAExE,eAAO,MAAM,yBAAyB,ozMAsIrC,CAAC"}
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Orchestrator instructions template.
3
+ *
4
+ * Injected into a target project's AGENTS.md by `pit init` so the
5
+ * orchestrating LLM knows how to drive pit.
6
+ *
7
+ * Delimited by sentinel comments so `pit init` can find and replace
8
+ * the section idempotently.
9
+ */
10
+ export const ORCHESTRATOR_SECTION_START = "<!-- pit:orchestrator:start -->";
11
+ export const ORCHESTRATOR_SECTION_END = "<!-- pit:orchestrator:end -->";
12
+ export const ORCHESTRATOR_INSTRUCTIONS = `${ORCHESTRATOR_SECTION_START}
13
+ ## Parallel Agent Orchestration with pit
14
+
15
+ You have access to \`pit\`, a tmux-based CLI that manages parallel AI coding agent sessions.
16
+ Each agent works in its own git worktree and tmux window, autonomously looping through
17
+ tickets in a beads epic.
18
+
19
+ ### Prerequisites
20
+
21
+ Before using pit, ensure the project has epics and tickets set up with beads (\`bd\`).
22
+
23
+ ### Workflow
24
+
25
+ 1. **Start agents**: \`pit start --epics <epic-id>\` (comma-separated for multiple)
26
+ - Creates one git worktree + tmux window + agent TUI per epic
27
+ - Each agent begins working through tickets autonomously
28
+ 2. **Monitor**: \`pit status\` — returns JSON with state of all epics
29
+ 3. **Read agent output**: \`pit log <epic>\` — captures the agent's terminal output
30
+ 4. **Pause an agent**: \`pit pause <epic>\` — pauses the agent's autonomous loop
31
+ 5. **Resume with guidance**: \`pit resume <epic> --message "your instructions"\`
32
+ — resumes a paused agent, injecting your message into its context
33
+
34
+ ### CLI Reference
35
+
36
+ | Command | Description |
37
+ |---|---|
38
+ | \`pit start --epics <ids>\` | Start agent sessions for given epic IDs |
39
+ | \`pit status\` | Show status of all running epics (JSON) |
40
+ | \`pit log <epic>\` | Capture agent's recent terminal output |
41
+ | \`pit log <epic> --lines 100\` | Capture more lines of output |
42
+ | \`pit pause <epic>\` | Pause a running epic's autonomous loop |
43
+ | \`pit resume <epic> --message "..."\` | Resume a paused epic with guidance |
44
+ | \`pit teardown [<epic-id>]\` | Tear down all epics (or a single epic), removes worktrees by default |
45
+ | \`pit teardown --keep-worktrees\` | Tear down without removing git worktrees |
46
+ | \`pit teardown --force\` | Tear down all epics even if some are still running |
47
+
48
+ ### Start Options
49
+
50
+ | Flag | Default | Description |
51
+ |---|---|---|
52
+ | \`--epics <ids>\` | (required) | Comma-separated beads epic IDs |
53
+ | \`--agent <type>\` | \`auto\` | Agent type: \`auto\`, \`Claude\`, or \`claude-code\` |
54
+ | \`--base-branch <branch>\` | \`main\` | Base branch for worktrees |
55
+ | \`--tmux-session <name>\` | \`pit\` | tmux session name |
56
+ | \`--worktree-dir <path>\` | \`.worktrees\` | Directory for git worktrees |
57
+ | \`--prompt-template <path>\` | (built-in) | Path to a custom prompt template file |
58
+ | \`--instructions-template <path>\` | (built-in) | Path to a custom agent instructions template file |
59
+ | \`--ticket-timeout <minutes>\` | (none) | Auto-pause agents when a ticket exceeds this duration |
60
+ | \`--model <value>\` | (none) | Model to pass to the agent CLI (passed through as \`--model <value>\`) |
61
+ | \`--epic-model <mapping>\` | (none) | Per-epic model override, repeatable: \`--epic-model <epic>=<model>\` |
62
+
63
+ > **Model resolution order:** \`--epic-model <epic>=<model>\` overrides \`--model\` / \`.pit.json\` model, which overrides the agent default. The model string is passed through to the agent CLI as-is.
64
+
65
+ ### Custom Templates
66
+
67
+ Use \`--prompt-template\` and \`--instructions-template\` to override the built-in agent
68
+ prompt and instructions with your own files. Templates support variable substitution:
69
+
70
+ - \`{{EPIC_ID}}\` — replaced with the epic ID being worked on
71
+ - \`{{EPIC_NAME}}\` — replaced with the epic name
72
+
73
+ ### Configuration
74
+
75
+ pit reads \`.pit.json\` from the project root. CLI flags override file values; missing
76
+ fields fall back to defaults.
77
+
78
+ \`\`\`json
79
+ {
80
+ "agent": "auto",
81
+ "model": null,
82
+ "worktreeDir": ".worktrees",
83
+ "baseBranch": "main",
84
+ "tmuxSession": "pit",
85
+ "clearDelay": 2000,
86
+ "initDelay": 5000,
87
+ "ticketTimeout": null
88
+ }
89
+ \`\`\`
90
+
91
+ | Field | Type | Default | Description |
92
+ |---|---|---|---|
93
+ | \`agent\` | string | \`"auto"\` | Agent type: \`auto\`, \`opencode\`, or \`claude-code\` |
94
+ | \`model\` | string|null | \`null\` | Model to pass to the agent CLI; null = agent default |
95
+ | \`worktreeDir\` | string | \`".worktrees"\` | Directory for git worktrees |
96
+ | \`baseBranch\` | string | \`"main"\` | Base branch for worktrees |
97
+ | \`tmuxSession\` | string | \`"pit"\` | tmux session name |
98
+ | \`clearDelay\` | number | \`2000\` | Ms to wait before clearing agent context after ticket completion |
99
+ | \`initDelay\` | number | \`5000\` | Ms to wait before starting the agent after setup |
100
+ | \`ticketTimeout\` | number|null | \`null\` | Minutes before a ticket auto-pauses the agent (null = disabled) |
101
+
102
+ ### Output Format
103
+
104
+ All pit commands output JSON by default (for LLM consumption). Add \`--pretty\` for
105
+ human-readable output. Parse the JSON to understand the current state:
106
+
107
+ \`\`\`json
108
+ // pit status example
109
+ {
110
+ "epics": {
111
+ "auth": { "state": "running", "progress": "3/7" },
112
+ "payments": { "state": "paused", "progress": "1/4" }
113
+ }
114
+ }
115
+ \`\`\`
116
+
117
+ ### When to Intervene
118
+
119
+ - **\`pit status\` shows an epic as "paused"**: An agent hit \`NEEDS_HUMAN_INPUT\`.
120
+ Read \`pit log <epic>\` to understand what the agent needs, then
121
+ \`pit resume <epic> --message "your answer"\`.
122
+ - **Pause reason starts with \`TIMEOUT:\`**: The agent was auto-paused because the
123
+ ticket exceeded the configured \`ticketTimeout\`. Read \`pit log <epic>\` to assess
124
+ progress, then \`pit resume <epic> --message "your guidance"\` or let it continue
125
+ with \`pit resume <epic>\`.
126
+ - **An agent seems stuck**: Use \`pit log <epic>\` to check progress.
127
+ \`pit pause <epic>\` if needed, then resume with instructions.
128
+ - **Epic completes**: The epic state becomes \`"done"\`. Merge the worktree branch
129
+ back to the base branch.
130
+
131
+ ### Session Reliability
132
+
133
+ pit automatically manages long-running sessions reliably. Once started, agent sessions
134
+ continue running independently until completion, regardless of how long they take.
135
+ No special flags or configuration needed.
136
+
137
+ ### Important Notes
138
+
139
+ - Agents work independently — they do not communicate with each other.
140
+ - Each agent works in its own git worktree, so there are no file conflicts during work.
141
+ - Merging worktree branches back is a manual step (done by you after epic completion).
142
+ - The tmux session is named \`pit\` by default. You can attach to it to observe agents directly.
143
+ - \`pit teardown\` removes worktrees by default. Use \`--keep-worktrees\` if you need to inspect the worktree after teardown.
144
+ - \`pit teardown\` (no epic specified) will fail if any epic is in \`running\` state, to prevent accidental destruction of active sessions. Use \`pit teardown --force\` to bypass this guard when you deliberately want to tear everything down.
145
+ ${ORCHESTRATOR_SECTION_END}
146
+ `;
147
+ //# sourceMappingURL=orchestrator-instructions-template.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestrator-instructions-template.js","sourceRoot":"","sources":["../src/orchestrator-instructions-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,CAAC,MAAM,0BAA0B,GAAG,iCAAiC,CAAC;AAC5E,MAAM,CAAC,MAAM,wBAAwB,GAAG,+BAA+B,CAAC;AAExE,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqIpE,wBAAwB;CACzB,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Output formatting utilities for pit CLI.
3
+ * JSON output is the default (for LLM consumption).
4
+ * --pretty enables human-readable output.
5
+ */
6
+ export declare function formatOutput(data: unknown, pretty: boolean): string;
7
+ export declare function printOutput(data: unknown, pretty: boolean): void;
8
+ export declare function printError(error: {
9
+ error: string;
10
+ [key: string]: unknown;
11
+ }, pretty: boolean): void;
12
+ //# sourceMappingURL=output.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,CAMnE;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAEhE;AAED,wBAAgB,UAAU,CACxB,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,EAChD,MAAM,EAAE,OAAO,GACd,IAAI,CAMN"}
package/dist/output.js ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Output formatting utilities for pit CLI.
3
+ * JSON output is the default (for LLM consumption).
4
+ * --pretty enables human-readable output.
5
+ */
6
+ export function formatOutput(data, pretty) {
7
+ if (pretty) {
8
+ if (typeof data === "string")
9
+ return data;
10
+ return JSON.stringify(data, null, 2);
11
+ }
12
+ return JSON.stringify(data);
13
+ }
14
+ export function printOutput(data, pretty) {
15
+ process.stdout.write(formatOutput(data, pretty) + "\n");
16
+ }
17
+ export function printError(error, pretty) {
18
+ if (pretty) {
19
+ process.stderr.write(`Error: ${error.error}\n`);
20
+ }
21
+ else {
22
+ process.stderr.write(JSON.stringify(error) + "\n");
23
+ }
24
+ }
25
+ //# sourceMappingURL=output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,UAAU,YAAY,CAAC,IAAa,EAAE,MAAe;IACzD,IAAI,MAAM,EAAE,CAAC;QACX,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAC1C,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAa,EAAE,MAAe;IACxD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,UAAU,CACxB,KAAgD,EAChD,MAAe;IAEf,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC;IAClD,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;IACrD,CAAC;AACH,CAAC"}
@@ -0,0 +1,57 @@
1
+ // src/plugin/pit.ts
2
+ import * as net from "node:net";
3
+ import { randomUUID } from "node:crypto";
4
+ async function sendToDaemon(socketPath, method, params) {
5
+ return new Promise((resolve) => {
6
+ const socket = net.createConnection(socketPath);
7
+ const request = {
8
+ id: randomUUID(),
9
+ method,
10
+ params
11
+ };
12
+ socket.on("connect", () => {
13
+ socket.write(JSON.stringify(request) + "\n", () => {
14
+ socket.destroy();
15
+ resolve();
16
+ });
17
+ });
18
+ socket.on("error", (err) => {
19
+ console.error(`[pit plugin] Socket error (${method}):`, err.message);
20
+ socket.destroy();
21
+ resolve();
22
+ });
23
+ });
24
+ }
25
+ var PitPlugin = async () => {
26
+ const epic = process.env.PIT_EPIC;
27
+ const socketPath = process.env.PIT_SOCKET_PATH;
28
+ if (!epic || !socketPath) {
29
+ console.error("[pit plugin] PIT_EPIC and PIT_SOCKET_PATH env vars required. Plugin inactive.");
30
+ return {};
31
+ }
32
+ return {
33
+ event: async ({ event }) => {
34
+ try {
35
+ if (event.type === "session.idle") {
36
+ await sendToDaemon(socketPath, "agent-idle", { epicId: epic });
37
+ }
38
+ } catch (err) {
39
+ console.error("[pit plugin] Error handling session.idle:", err);
40
+ }
41
+ },
42
+ "permission.ask": async ({ input }) => {
43
+ try {
44
+ await sendToDaemon(socketPath, "agent-permission", {
45
+ epicId: epic,
46
+ tool: input.type ?? "unknown",
47
+ input: JSON.stringify(input)
48
+ });
49
+ } catch (err) {
50
+ console.error("[pit plugin] Error handling permission.ask:", err);
51
+ }
52
+ }
53
+ };
54
+ };
55
+ export {
56
+ PitPlugin
57
+ };
@@ -0,0 +1,55 @@
1
+ export interface LockData {
2
+ sessionId: string;
3
+ pid: number;
4
+ tmuxSession: string;
5
+ epics: string[];
6
+ createdAt: string;
7
+ createdSession: boolean;
8
+ adapter?: string;
9
+ }
10
+ export type LockStatus = {
11
+ status: "none";
12
+ } | {
13
+ status: "alive";
14
+ data: LockData;
15
+ } | {
16
+ status: "stale";
17
+ data: LockData;
18
+ };
19
+ /**
20
+ * Generates a deterministic 12-character hex session ID from the resolved
21
+ * project path and tmux session name.
22
+ *
23
+ * Normalization:
24
+ * 1. Resolve symlinks via `fs.realpathSync` (falls back to `path.resolve`)
25
+ * 2. Strip any trailing slash
26
+ */
27
+ export declare function generateSessionId(projectPath: string, tmuxSession: string): string;
28
+ /**
29
+ * Returns the session-level directory: `/tmp/pit/<sessionId>`
30
+ */
31
+ export declare function sessionDir(sessionId: string): string;
32
+ /**
33
+ * Atomically writes `data` as JSON to `${projectRoot}/.pit.lock`.
34
+ */
35
+ export declare function writeLock(projectRoot: string, data: LockData): Promise<void>;
36
+ /**
37
+ * Reads and parses `.pit.lock` from `projectRoot`.
38
+ * Returns null if the file is missing or malformed.
39
+ * Provides backwards compatibility by defaulting createdSession to false for old locks.
40
+ */
41
+ export declare function readLock(projectRoot: string): Promise<LockData | null>;
42
+ /**
43
+ * Removes `.pit.lock` from `projectRoot`.
44
+ * Ignores ENOENT (idempotent).
45
+ */
46
+ export declare function removeLock(projectRoot: string): Promise<void>;
47
+ /**
48
+ * Checks the lock status for `projectRoot`.
49
+ *
50
+ * - "none" — no lock file present
51
+ * - "alive" — lock file exists and the PID is still running
52
+ * - "stale" — lock file exists but the PID is dead (ESRCH)
53
+ */
54
+ export declare function checkLock(projectRoot: string): Promise<LockStatus>;
55
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,UAAU,GAClB;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GACnC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC;AAMxC;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAclF;AAMD;;GAEG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEpD;AAQD;;GAEG;AACH,wBAAsB,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAGlF;AAED;;;;GAIG;AACH,wBAAsB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAoC5E;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAUnE;AAED;;;;;;GAMG;AACH,wBAAsB,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAexE"}
@@ -0,0 +1,135 @@
1
+ import * as crypto from "node:crypto";
2
+ import * as fs from "node:fs";
3
+ import * as fsp from "node:fs/promises";
4
+ import * as path from "node:path";
5
+ import { atomicWrite } from "./signals.js";
6
+ // ---------------------------------------------------------------------------
7
+ // Session ID
8
+ // ---------------------------------------------------------------------------
9
+ /**
10
+ * Generates a deterministic 12-character hex session ID from the resolved
11
+ * project path and tmux session name.
12
+ *
13
+ * Normalization:
14
+ * 1. Resolve symlinks via `fs.realpathSync` (falls back to `path.resolve`)
15
+ * 2. Strip any trailing slash
16
+ */
17
+ export function generateSessionId(projectPath, tmuxSession) {
18
+ let resolved;
19
+ try {
20
+ resolved = fs.realpathSync(path.resolve(projectPath));
21
+ }
22
+ catch {
23
+ resolved = path.resolve(projectPath);
24
+ }
25
+ // Strip trailing slash
26
+ resolved = resolved.replace(/\/$/, "");
27
+ return crypto
28
+ .createHash("sha256")
29
+ .update(resolved + "|" + tmuxSession)
30
+ .digest("hex")
31
+ .slice(0, 12);
32
+ }
33
+ // ---------------------------------------------------------------------------
34
+ // Path helpers
35
+ // ---------------------------------------------------------------------------
36
+ /**
37
+ * Returns the session-level directory: `/tmp/pit/<sessionId>`
38
+ */
39
+ export function sessionDir(sessionId) {
40
+ return `/tmp/pit/${sessionId}`;
41
+ }
42
+ // ---------------------------------------------------------------------------
43
+ // Lock file CRUD
44
+ // ---------------------------------------------------------------------------
45
+ const LOCK_FILE = ".pit.lock";
46
+ /**
47
+ * Atomically writes `data` as JSON to `${projectRoot}/.pit.lock`.
48
+ */
49
+ export async function writeLock(projectRoot, data) {
50
+ const filePath = path.join(projectRoot, LOCK_FILE);
51
+ await atomicWrite(filePath, JSON.stringify(data));
52
+ }
53
+ /**
54
+ * Reads and parses `.pit.lock` from `projectRoot`.
55
+ * Returns null if the file is missing or malformed.
56
+ * Provides backwards compatibility by defaulting createdSession to false for old locks.
57
+ */
58
+ export async function readLock(projectRoot) {
59
+ const filePath = path.join(projectRoot, LOCK_FILE);
60
+ let raw;
61
+ try {
62
+ raw = await fsp.readFile(filePath, "utf8");
63
+ }
64
+ catch (err) {
65
+ if (err.code === "ENOENT") {
66
+ return null;
67
+ }
68
+ return null;
69
+ }
70
+ try {
71
+ const parsed = JSON.parse(raw);
72
+ // Validate required fields
73
+ if (typeof parsed.sessionId !== "string" ||
74
+ typeof parsed.pid !== "number" ||
75
+ typeof parsed.tmuxSession !== "string" ||
76
+ !Array.isArray(parsed.epics) ||
77
+ typeof parsed.createdAt !== "string") {
78
+ return null;
79
+ }
80
+ // Backwards compatibility: default createdSession to false if missing
81
+ return {
82
+ sessionId: parsed.sessionId,
83
+ pid: parsed.pid,
84
+ tmuxSession: parsed.tmuxSession,
85
+ epics: parsed.epics,
86
+ createdAt: parsed.createdAt,
87
+ createdSession: parsed.createdSession ?? false,
88
+ ...(parsed.adapter !== undefined ? { adapter: parsed.adapter } : {}),
89
+ };
90
+ }
91
+ catch {
92
+ return null;
93
+ }
94
+ }
95
+ /**
96
+ * Removes `.pit.lock` from `projectRoot`.
97
+ * Ignores ENOENT (idempotent).
98
+ */
99
+ export async function removeLock(projectRoot) {
100
+ const filePath = path.join(projectRoot, LOCK_FILE);
101
+ try {
102
+ await fsp.unlink(filePath);
103
+ }
104
+ catch (err) {
105
+ if (err.code === "ENOENT") {
106
+ return;
107
+ }
108
+ throw err;
109
+ }
110
+ }
111
+ /**
112
+ * Checks the lock status for `projectRoot`.
113
+ *
114
+ * - "none" — no lock file present
115
+ * - "alive" — lock file exists and the PID is still running
116
+ * - "stale" — lock file exists but the PID is dead (ESRCH)
117
+ */
118
+ export async function checkLock(projectRoot) {
119
+ const data = await readLock(projectRoot);
120
+ if (data === null) {
121
+ return { status: "none" };
122
+ }
123
+ try {
124
+ process.kill(data.pid, 0);
125
+ return { status: "alive", data };
126
+ }
127
+ catch (err) {
128
+ if (err.code === "ESRCH") {
129
+ return { status: "stale", data };
130
+ }
131
+ // EPERM means process exists but we lack permission to signal it — treat as alive
132
+ return { status: "alive", data };
133
+ }
134
+ }
135
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}