claude-tempo 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CLAUDE.md CHANGED
@@ -65,9 +65,13 @@ src/
65
65
  │ ├── quality-gate.ts # Define quality gates for tasks (conductor only)
66
66
  │ ├── evaluate-gate.ts # Mark gate criteria as passed/failed (conductor only)
67
67
  │ ├── gates.ts # List quality gates and their status (conductor only)
68
+ │ ├── worktree.ts # Manage git worktrees for player isolation (conductor only)
68
69
  │ └── helpers.ts # Zod/MCP tool registration wrapper
69
70
  ├── utils/
70
- └── validation.ts # Shared validation constants (name/message/path limits, encore defaults) and helpers
71
+ ├── validation.ts # Shared validation constants (name/message/path limits, encore defaults) and helpers
72
+ │ ├── worktree.ts # Git worktree create/remove helpers (cross-platform)
73
+ │ ├── safe-path.ts # Path safety utilities
74
+ │ └── duration.ts # Duration parsing helpers
71
75
  ├── types.ts # Shared type definitions
72
76
  ├── channel.ts # Claude channel notification helper
73
77
  ├── git-info.ts # Git repository detection helper
@@ -120,6 +124,7 @@ npm test
120
124
  - **Schedule**: A one-shot or recurring message delivery configured via the `schedule` tool. Backed by a durable `claudeSchedulerWorkflow` — survives restarts. Supports delay (`delay`), fixed time (`at`), recurring interval (`every`), and cron expressions (`cron`) with optional IANA timezone (`timezone`). Cron schedules use `croner` for expression parsing and next-fire computation. Managed via `schedule`, `unschedule`, and `schedules` tools.
121
125
  - **Lineup**: A YAML file defining an ensemble configuration — which players to recruit, their types, working directories, and optional startup messages. Load via `load_lineup` to bootstrap a full ensemble in one step; save via `save_lineup` to snapshot a running ensemble's state for later reuse.
122
126
  - **Quality Gate**: A named checklist of criteria a conductor tracks to verify a task is complete. Created via `quality_gate` (conductor only), evaluated via `evaluate_gate`, and listed via `gates`. Each criterion has a `pending` → `passed` | `failed` status; the gate's aggregate status is derived automatically (all passed → `passed`, any failed → `failed`, else `open`). Gates are stored in the conductor workflow and survive `continueAsNew`.
127
+ - **Worktree**: A git worktree provisioned by the conductor for a player, giving them an isolated checkout on a separate branch. Managed via the `worktree` tool (conductor only): `create` provisions the worktree and notifies the player, `remove` cleans up after the task, `list` shows all active worktrees. Worktree assignments are stored in the conductor workflow (`WorktreeEntry` records: player, path, branch, gitRoot, createdAt, createdBy).
123
128
  - **Wire protocol**: All Temporal signal, query, update, and workflow names are documented in [`docs/WIRE-PROTOCOL.md`](docs/WIRE-PROTOCOL.md). These names are stable as of v0.10 — renaming or removing any is a breaking change requiring a major version bump.
124
129
 
125
130
  ## Dashboard
package/README.md CHANGED
@@ -133,6 +133,7 @@ These tools are available inside Claude Code sessions connected to claude-tempo:
133
133
  | `broadcast` | Send a message to all active players. Optional `type` filter limits to a specific player type. |
134
134
  | `encore` | Revive a stale player session — restarts the process and reconnects to the existing workflow with context restored. |
135
135
  | `recall` | Read your own message history. Shows received messages by default; pass `includeSent: true` for the full timeline. |
136
+ | `worktree` | Manage git worktrees for player isolation. Actions: `create`, `remove`, `list`. Conductor only. |
136
137
  | `quality_gate` | Define or replace a quality gate for a task — a named checklist of criteria that must pass. Conductor only. |
137
138
  | `evaluate_gate` | Mark one or more criteria on a quality gate as passed or failed. Conductor only. |
138
139
  | `gates` | List quality gates and their status. Filter by task name or status (`open`, `passed`, `failed`). Conductor only. |
@@ -28,12 +28,6 @@ function loadLineup(filePath) {
28
28
  if (!/^[a-zA-Z0-9_-]+$/.test(p.name)) {
29
29
  throw new Error(`Invalid lineup: players[${i}].name "${p.name}" contains invalid characters`);
30
30
  }
31
- if (p.isolation != null && p.isolation !== 'worktree') {
32
- throw new Error(`Invalid lineup: players[${i}].isolation must be "worktree" if specified`);
33
- }
34
- if (p.branch != null && (typeof p.branch !== 'string' || !p.branch)) {
35
- throw new Error(`Invalid lineup: players[${i}].branch must be a non-empty string`);
36
- }
37
31
  }
38
32
  // Validate schedules if present
39
33
  if (doc.schedules != null) {
@@ -76,8 +70,6 @@ function loadLineup(filePath) {
76
70
  ...(p.agent != null && { agent: p.agent }),
77
71
  ...(p.instructions != null && { instructions: p.instructions }),
78
72
  ...(Array.isArray(p.allowedTools) && { allowedTools: p.allowedTools.map(String) }),
79
- ...(p.isolation != null && { isolation: p.isolation }),
80
- ...(p.branch != null && { branch: p.branch }),
81
73
  })),
82
74
  schedules: doc.schedules,
83
75
  };
@@ -14,8 +14,6 @@ export interface EnsembleLineup {
14
14
  agent?: string;
15
15
  instructions?: string;
16
16
  allowedTools?: string[];
17
- isolation?: 'worktree';
18
- branch?: string;
19
17
  /** Transient: resolved agent definition name (set by loadAndResolveLineup). */
20
18
  _agentDefinition?: string;
21
19
  /** Transient: resolved absolute path to .md file (set by loadAndResolveLineup). */
package/dist/server.js CHANGED
@@ -67,6 +67,7 @@ const encore_1 = require("./tools/encore");
67
67
  const quality_gate_1 = require("./tools/quality-gate");
68
68
  const evaluate_gate_1 = require("./tools/evaluate-gate");
69
69
  const gates_1 = require("./tools/gates");
70
+ const worktree_1 = require("./tools/worktree");
70
71
  const channel_1 = require("./channel");
71
72
  const agent_types_2 = require("./ensemble/agent-types");
72
73
  const log = (...args) => console.error('[claude-tempo]', ...args);
@@ -288,6 +289,7 @@ async function main() {
288
289
  (0, quality_gate_1.registerQualityGateTool)(mcpServer, handle, getPlayerId);
289
290
  (0, evaluate_gate_1.registerEvaluateGateTool)(mcpServer, handle, getPlayerId);
290
291
  (0, gates_1.registerGatesTool)(mcpServer, handle);
292
+ (0, worktree_1.registerWorktreeTool)(mcpServer, client, config, handle, getPlayerId);
291
293
  }
292
294
  const MAESTRO_ACK = '\n\n[IMPORTANT: This message is from a human (Maestro). Immediately cue the sender back with a brief acknowledgment and your planned next step before doing the work.]';
293
295
  // Start message poller — push messages into Claude Code via channel notifications.
@@ -15,7 +15,6 @@ const duration_1 = require("../utils/duration");
15
15
  const safe_path_1 = require("../utils/safe-path");
16
16
  const helpers_1 = require("./helpers");
17
17
  const validation_1 = require("../utils/validation");
18
- const worktree_1 = require("../utils/worktree");
19
18
  const log = (...args) => console.error('[claude-tempo:load-lineup]', ...args);
20
19
  function sleep(ms) {
21
20
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -80,8 +79,7 @@ function registerLoadLineupTool(server, client, config, getPlayerId, ownAgentTyp
80
79
  // Recruit players sequentially
81
80
  for (const player of lineup.players) {
82
81
  const playerName = player.name;
83
- let workDir = player.workDir || process.cwd();
84
- let worktreePath;
82
+ const workDir = player.workDir || process.cwd();
85
83
  const agentType = player.agent === 'copilot' ? 'copilot' : 'claude';
86
84
  const isCustomAgent = player.agent && player.agent !== 'default' && player.agent !== 'copilot';
87
85
  const systemPrompt = player._agentDefinition ? undefined : (isCustomAgent ? player.agent : undefined);
@@ -106,31 +104,6 @@ function registerLoadLineupTool(server, client, config, getPlayerId, ownAgentTyp
106
104
  }
107
105
  continue;
108
106
  }
109
- // Create worktree if isolation is requested
110
- if (player.isolation === 'worktree') {
111
- try {
112
- // Determine git root: use the player's workDir as the git root,
113
- // or fall back to cwd (which should be a git repo).
114
- const gitRoot = workDir;
115
- const result = (0, worktree_1.createWorktree)({
116
- gitRoot,
117
- ensemble: config.ensemble,
118
- playerName,
119
- branch: player.branch,
120
- });
121
- worktreePath = result.path;
122
- workDir = result.path;
123
- log(`Worktree for "${playerName}": ${result.path} (branch: ${result.branch}, created: ${result.created})`);
124
- // Install dependencies — blocking but failure is non-fatal
125
- if (result.created) {
126
- (0, worktree_1.installDependencies)(result.path);
127
- }
128
- }
129
- catch (err) {
130
- failed.push(`${playerName}: worktree creation failed — ${err}`);
131
- continue;
132
- }
133
- }
134
107
  // Record existing workflows to detect the new one
135
108
  const existingIds = new Set();
136
109
  const listQuery = `WorkflowType = "claudeSessionWorkflow" AND ExecutionStatus = "Running"`;
@@ -221,16 +194,6 @@ function registerLoadLineupTool(server, client, config, getPlayerId, ownAgentTyp
221
194
  failed.push(`${playerName}: spawned but did not register within 15s`);
222
195
  continue;
223
196
  }
224
- // Record worktree path in session metadata
225
- if (worktreePath && newWorkflowId) {
226
- try {
227
- const newHandle = client.workflow.getHandle(newWorkflowId);
228
- await newHandle.signal('updateMetadata', { worktreePath });
229
- }
230
- catch (err) {
231
- log(`Failed to set worktreePath metadata for "${playerName}":`, err);
232
- }
233
- }
234
197
  // Send initial instructions if provided
235
198
  if (player.instructions) {
236
199
  try {
@@ -0,0 +1,4 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { Client, WorkflowHandle } from '@temporalio/client';
3
+ import { Config } from '../config';
4
+ export declare function registerWorktreeTool(server: McpServer, client: Client, config: Config, handle: WorkflowHandle, getPlayerId: () => string): void;
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.registerWorktreeTool = registerWorktreeTool;
37
+ const zod_1 = require("zod");
38
+ const resolve_1 = require("./resolve");
39
+ const signals_1 = require("../workflows/signals");
40
+ const helpers_1 = require("./helpers");
41
+ const worktree_1 = require("../utils/worktree");
42
+ const validation_1 = require("../utils/validation");
43
+ function registerWorktreeTool(server, client, config, handle, getPlayerId) {
44
+ (0, helpers_1.defineTool)(server, 'worktree', 'Manage git worktrees for player isolation. Conductor only. Actions: create (provision worktree for a player), remove (clean up), list (show active worktrees).', {
45
+ action: zod_1.z.enum(['create', 'remove', 'list']).describe('Action to perform'),
46
+ player: zod_1.z.string().max(validation_1.PLAYER_NAME_MAX).optional().describe('Player name (required for create/remove)'),
47
+ branch: zod_1.z.string().optional().describe('Git branch for the worktree (defaults to {ensemble}/{player-name})'),
48
+ }, async (args) => {
49
+ const { action, player, branch } = args;
50
+ try {
51
+ switch (action) {
52
+ case 'create': {
53
+ if (!player) {
54
+ return {
55
+ content: [{ type: 'text', text: '`player` is required for create action.' }],
56
+ isError: true,
57
+ };
58
+ }
59
+ // Verify player exists
60
+ const targetHandle = await (0, resolve_1.resolveSession)(client, config.ensemble, player);
61
+ if (!targetHandle) {
62
+ return {
63
+ content: [{ type: 'text', text: `No active session found for "${player}".` }],
64
+ isError: true,
65
+ };
66
+ }
67
+ // Check target is on same host (cross-machine worktrees not supported)
68
+ const targetMeta = await targetHandle.query('getMetadata');
69
+ const { hostname } = await Promise.resolve().then(() => __importStar(require('os'))).then((os) => ({ hostname: os.hostname() }));
70
+ if (targetMeta.hostname && targetMeta.hostname !== hostname) {
71
+ return {
72
+ content: [{ type: 'text', text: `Cannot create worktree for "${player}" — they are on host "${targetMeta.hostname}" but worktrees must be created locally.` }],
73
+ isError: true,
74
+ };
75
+ }
76
+ const gitRoot = process.cwd();
77
+ const result = (0, worktree_1.createWorktree)({
78
+ gitRoot,
79
+ ensemble: config.ensemble,
80
+ playerName: player,
81
+ branch,
82
+ });
83
+ if (result.created) {
84
+ (0, worktree_1.installDependencies)(result.path);
85
+ }
86
+ // Record in conductor's worktree state
87
+ const entry = {
88
+ player,
89
+ path: result.path,
90
+ branch: result.branch,
91
+ gitRoot,
92
+ createdAt: new Date().toISOString(),
93
+ createdBy: getPlayerId(),
94
+ };
95
+ await handle.signal('setWorktree', entry);
96
+ // Auto-cue the player with worktree info
97
+ const cueMessage = [
98
+ `\u{1f33f} **Worktree ready** for your task:`,
99
+ `- **Path**: \`${result.path}\``,
100
+ `- **Branch**: \`${result.branch}\``,
101
+ '',
102
+ `Run \`cd ${result.path}\` to switch to your isolated workspace.`,
103
+ `All your changes will be on branch \`${result.branch}\`.`,
104
+ `When done, commit and push \u2014 the conductor will handle cleanup.`,
105
+ ].join('\n');
106
+ await handle.executeUpdate(signals_1.submitOutboxUpdate, {
107
+ args: [{
108
+ type: 'cue',
109
+ targetPlayerId: player,
110
+ message: cueMessage,
111
+ }],
112
+ });
113
+ return {
114
+ content: [{
115
+ type: 'text',
116
+ text: `Worktree created for **${player}**:\n- Path: \`${result.path}\`\n- Branch: \`${result.branch}\`\n- Created: ${result.created ? 'new' : 'reused existing'}\n\nPlayer has been notified.`,
117
+ }],
118
+ };
119
+ }
120
+ case 'remove': {
121
+ if (!player) {
122
+ return {
123
+ content: [{ type: 'text', text: '`player` is required for remove action.' }],
124
+ isError: true,
125
+ };
126
+ }
127
+ // Look up worktree entry from conductor state
128
+ const entries = await handle.query('worktrees');
129
+ const entry = entries.find((w) => w.player === player);
130
+ if (!entry) {
131
+ return {
132
+ content: [{ type: 'text', text: `No worktree found for player "${player}".` }],
133
+ isError: true,
134
+ };
135
+ }
136
+ // Remove from disk
137
+ (0, worktree_1.removeWorktree)(entry.path);
138
+ // Remove from conductor state
139
+ await handle.signal('removeWorktree', player);
140
+ // Auto-cue the player
141
+ try {
142
+ await handle.executeUpdate(signals_1.submitOutboxUpdate, {
143
+ args: [{
144
+ type: 'cue',
145
+ targetPlayerId: player,
146
+ message: `Worktree removed. You're back in the shared repository.`,
147
+ }],
148
+ });
149
+ }
150
+ catch {
151
+ // Player may no longer be active — non-fatal
152
+ }
153
+ return {
154
+ content: [{
155
+ type: 'text',
156
+ text: `Worktree for **${player}** removed (branch: \`${entry.branch}\`).`,
157
+ }],
158
+ };
159
+ }
160
+ case 'list': {
161
+ const entries = await handle.query('worktrees');
162
+ if (entries.length === 0) {
163
+ return {
164
+ content: [{ type: 'text', text: 'No active worktrees.' }],
165
+ };
166
+ }
167
+ const lines = entries.map((w) => `- **${w.player}**: \`${w.path}\` (branch: \`${w.branch}\`, created: ${w.createdAt} by ${w.createdBy})`);
168
+ return {
169
+ content: [{
170
+ type: 'text',
171
+ text: `${entries.length} active worktree${entries.length === 1 ? '' : 's'}:\n${lines.join('\n')}`,
172
+ }],
173
+ };
174
+ }
175
+ }
176
+ }
177
+ catch (err) {
178
+ return {
179
+ content: [{ type: 'text', text: `Worktree operation failed: ${err}` }],
180
+ isError: true,
181
+ };
182
+ }
183
+ });
184
+ }
package/dist/types.d.ts CHANGED
@@ -46,6 +46,8 @@ export interface SessionInput {
46
46
  disableStaleDetection?: boolean;
47
47
  /** Restored from continue-as-new (conductor only) */
48
48
  qualityGates?: QualityGate[];
49
+ /** Restored from continue-as-new (conductor only) */
50
+ worktrees?: WorktreeEntry[];
49
51
  /** Temporal config passed through for outbox activities (non-secret fields only). */
50
52
  temporalConfig?: {
51
53
  temporalAddress: string;
@@ -154,6 +156,20 @@ export interface QualityGate {
154
156
  /** Derived: all passed → passed, any failed → failed, else open. */
155
157
  status: 'open' | 'passed' | 'failed';
156
158
  }
159
+ export interface WorktreeEntry {
160
+ /** Player name assigned to this worktree. */
161
+ player: string;
162
+ /** Absolute path to worktree directory. */
163
+ path: string;
164
+ /** Git branch for this worktree. */
165
+ branch: string;
166
+ /** Original git root (for git worktree remove). */
167
+ gitRoot: string;
168
+ /** ISO timestamp of creation. */
169
+ createdAt: string;
170
+ /** Player ID of creator. */
171
+ createdBy: string;
172
+ }
157
173
  export interface ScheduleEntry {
158
174
  /** Unique name for this schedule (used as key for add/replace/remove). */
159
175
  name: string;
@@ -25,6 +25,7 @@ async function claudeSessionWorkflow(input) {
25
25
  (0, workflow_1.patched)('v0.10-initial');
26
26
  (0, workflow_1.patched)('v0.11-check-and-set-status');
27
27
  (0, workflow_1.patched)('v0.13-quality-gates');
28
+ (0, workflow_1.patched)('v0.14-worktrees');
28
29
  // Ensure search attributes are always current — critical when reconnecting
29
30
  // via WorkflowIdConflictPolicy.USE_EXISTING, which skips the attributes
30
31
  // passed to client.workflow.start().
@@ -169,6 +170,7 @@ async function claudeSessionWorkflow(input) {
169
170
  const commandHistory = input.commandHistory ?? [];
170
171
  const reportHistory = input.reportHistory ?? [];
171
172
  const qualityGates = input.qualityGates ?? [];
173
+ const worktrees = input.worktrees ?? [];
172
174
  // ── Conductor-specific Handlers ──
173
175
  if (input.metadata.isConductor) {
174
176
  (0, workflow_1.setHandler)(signals_1.commandSignal, (cmd) => {
@@ -258,6 +260,23 @@ async function claudeSessionWorkflow(input) {
258
260
  gate.status = deriveGateStatus(gate);
259
261
  });
260
262
  (0, workflow_1.setHandler)(signals_1.qualityGatesQuery, () => qualityGates);
263
+ // ── Worktree Handlers ──
264
+ (0, workflow_1.setHandler)(signals_1.setWorktreeSignal, (entry) => {
265
+ const existing = worktrees.findIndex((w) => w.player === entry.player);
266
+ if (existing >= 0) {
267
+ worktrees[existing] = entry;
268
+ }
269
+ else {
270
+ worktrees.push(entry);
271
+ }
272
+ });
273
+ (0, workflow_1.setHandler)(signals_1.removeWorktreeSignal, (playerName) => {
274
+ const idx = worktrees.findIndex((w) => w.player === playerName);
275
+ if (idx >= 0) {
276
+ worktrees.splice(idx, 1);
277
+ }
278
+ });
279
+ (0, workflow_1.setHandler)(signals_1.worktreesQuery, () => worktrees);
261
280
  }
262
281
  // ── Main Loop ──
263
282
  const hasPendingOutbox = () => outbox.some((e) => e.status === 'pending');
@@ -416,7 +435,7 @@ async function claudeSessionWorkflow(input) {
416
435
  messages: messages.filter((m) => !m.delivered),
417
436
  sentMessages: sentMessages.slice(-50),
418
437
  outbox: outbox.filter((e) => e.status === 'pending' || e.status === 'processing'),
419
- ...(input.metadata.isConductor ? { commandHistory, reportHistory, qualityGates } : {}),
438
+ ...(input.metadata.isConductor ? { commandHistory, reportHistory, qualityGates, worktrees } : {}),
420
439
  });
421
440
  }
422
441
  }
@@ -1,5 +1,5 @@
1
- import type { SessionMetadata, Message, SentMessage, HistoryEntry, OutboxEntry, OutboxEntryInput, QualityGate } from '../types';
2
- export type { SessionMetadata, SessionInput, SessionStatus, Message, Command, PlayerReport, SentMessage, HistoryEntry, OutboxEntry, OutboxEntryInput, OutboxEntryStatus, CueOutboxEntry, RecruitOutboxEntry, ReportOutboxEntry, StopOutboxEntry, EncoreOutboxEntry, QualityGate, QualityGateCriterion, } from '../types';
1
+ import type { SessionMetadata, Message, SentMessage, HistoryEntry, OutboxEntry, OutboxEntryInput, QualityGate, WorktreeEntry } from '../types';
2
+ export type { SessionMetadata, SessionInput, SessionStatus, Message, Command, PlayerReport, SentMessage, HistoryEntry, OutboxEntry, OutboxEntryInput, OutboxEntryStatus, CueOutboxEntry, RecruitOutboxEntry, ReportOutboxEntry, StopOutboxEntry, EncoreOutboxEntry, QualityGate, QualityGateCriterion, WorktreeEntry, } from '../types';
3
3
  export declare const receiveMessageSignal: import("@temporalio/workflow").SignalDefinition<[{
4
4
  from: string;
5
5
  text: string;
@@ -61,3 +61,6 @@ export declare const evaluateGateCriteriaSignal: import("@temporalio/workflow").
61
61
  evaluatedBy: string;
62
62
  }], string>;
63
63
  export declare const qualityGatesQuery: import("@temporalio/workflow").QueryDefinition<QualityGate[], [], string>;
64
+ export declare const setWorktreeSignal: import("@temporalio/workflow").SignalDefinition<[WorktreeEntry], string>;
65
+ export declare const removeWorktreeSignal: import("@temporalio/workflow").SignalDefinition<[string], string>;
66
+ export declare const worktreesQuery: import("@temporalio/workflow").QueryDefinition<WorktreeEntry[], [], string>;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.qualityGatesQuery = exports.evaluateGateCriteriaSignal = exports.setQualityGateSignal = exports.outboxQuery = exports.submitOutboxUpdate = exports.checkAndSetStatusUpdate = exports.historyQuery = exports.playerReportSignal = exports.commandSignal = exports.allSentMessagesQuery = exports.allMessagesQuery = exports.pendingMessagesQuery = exports.getMetadataQuery = exports.getPartQuery = exports.updateMetadataSignal = exports.setNameSignal = exports.markDeliveredSignal = exports.setPartSignal = exports.recordSentMessageSignal = exports.receiveMessageSignal = void 0;
3
+ exports.worktreesQuery = exports.removeWorktreeSignal = exports.setWorktreeSignal = exports.qualityGatesQuery = exports.evaluateGateCriteriaSignal = exports.setQualityGateSignal = exports.outboxQuery = exports.submitOutboxUpdate = exports.checkAndSetStatusUpdate = exports.historyQuery = exports.playerReportSignal = exports.commandSignal = exports.allSentMessagesQuery = exports.allMessagesQuery = exports.pendingMessagesQuery = exports.getMetadataQuery = exports.getPartQuery = exports.updateMetadataSignal = exports.setNameSignal = exports.markDeliveredSignal = exports.setPartSignal = exports.recordSentMessageSignal = exports.receiveMessageSignal = void 0;
4
4
  const workflow_1 = require("@temporalio/workflow");
5
5
  // ── Player Signals ──
6
6
  exports.receiveMessageSignal = (0, workflow_1.defineSignal)('receiveMessage');
@@ -30,3 +30,7 @@ exports.outboxQuery = (0, workflow_1.defineQuery)('outbox');
30
30
  exports.setQualityGateSignal = (0, workflow_1.defineSignal)('setQualityGate');
31
31
  exports.evaluateGateCriteriaSignal = (0, workflow_1.defineSignal)('evaluateGateCriteria');
32
32
  exports.qualityGatesQuery = (0, workflow_1.defineQuery)('qualityGates');
33
+ // ── Worktree Signals + Query (conductor-only) ──
34
+ exports.setWorktreeSignal = (0, workflow_1.defineSignal)('setWorktree');
35
+ exports.removeWorktreeSignal = (0, workflow_1.defineSignal)('removeWorktree');
36
+ exports.worktreesQuery = (0, workflow_1.defineQuery)('worktrees');
@@ -53,7 +53,7 @@ You are a combination of Product Manager, Task Decomposition Expert, and Context
53
53
 
54
54
  ## Worktree Coordination
55
55
 
56
- Use git worktrees when two or more engineers need to work in the same repo on different branches simultaneously. Each worktree is an independent checkout — players can build, test, and commit without interfering with each other.
56
+ Use the `worktree` tool to give players isolated git checkouts when two or more engineers need to work in the same repo on different branches simultaneously. Each worktree is an independent checkout — players can build, test, and commit without interfering with each other.
57
57
 
58
58
  ### When to use
59
59
 
@@ -63,28 +63,16 @@ Use git worktrees when two or more engineers need to work in the same repo on di
63
63
 
64
64
  ### How to coordinate
65
65
 
66
- 1. **Create the worktree** (you or a player with shell access):
67
- ```
68
- git worktree add ../ct-{task} {branch}
69
- ```
70
- 2. **Install dependencies** in the new worktree:
71
- ```
72
- cd ../ct-{task} && npm install
73
- ```
74
- 3. **Recruit with `workDir`** pointing to the worktree:
75
- ```
76
- recruit({ name: "eng-33", workDir: "../ct-{task}", ... })
77
- ```
78
- 4. **Clean up** after the task: stop the player first, then remove the worktree:
79
- ```
80
- git worktree remove ../ct-{task}
81
- ```
82
-
83
- `recruit` already accepts `workDir` — no new tools are needed for manual worktree coordination.
66
+ 1. **Create**: `worktree({ action: "create", player: "eng-33" })` — provisions the worktree, installs dependencies, and notifies the player with the path and branch.
67
+ 2. **Work**: the player receives a cue with their worktree path and branch. They commit and push as normal.
68
+ 3. **Remove**: `worktree({ action: "remove", player: "eng-33" })` — cleans up the worktree and notifies the player. Stop the player session first on Windows (NTFS locks).
69
+ 4. **List**: `worktree({ action: "list" })` — shows all active worktree assignments.
70
+
71
+ By default, `create` names the branch `{ensemble}/{player-name}`. Pass `branch` to override.
84
72
 
85
73
  ### Platform notes
86
74
 
87
- - **Windows**: Use short sibling paths (e.g. `../ct-feat33`) to avoid MAX_PATH limits. Always stop players before removing worktrees — NTFS file locks will block cleanup while a session is active.
75
+ - **Windows**: Worktrees are placed in short sibling directories (e.g. `../ct-feat33`) to avoid MAX_PATH limits. Stop the player session before calling `remove` — NTFS file locks will block cleanup while a session is active.
88
76
 
89
77
  ## Handling Context Pressure
90
78
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-tempo",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "MCP server for multi-session Claude Code coordination via Temporal",
5
5
  "keywords": [
6
6
  "mcp",