jettypod 4.4.53 → 4.4.55

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.
@@ -0,0 +1,281 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Global Guardrails Hook
4
+ *
5
+ * Enforces actions that are ALWAYS allowed and ALWAYS blocked regardless of
6
+ * skill or context. Runs before any contextual/skill-specific validators.
7
+ *
8
+ * Claude Code PreToolUse Hook
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ // Read hook input from stdin
15
+ let input = '';
16
+ process.stdin.on('data', chunk => input += chunk);
17
+ process.stdin.on('end', () => {
18
+ try {
19
+ const hookInput = JSON.parse(input);
20
+ const result = evaluateRequest(hookInput);
21
+
22
+ if (result.allowed) {
23
+ allow();
24
+ } else {
25
+ deny(result.message, result.hint);
26
+ }
27
+ } catch (err) {
28
+ // On parse error, allow (fail open)
29
+ console.error('Hook error:', err.message);
30
+ allow();
31
+ }
32
+ });
33
+
34
+ /**
35
+ * Evaluate the request and return allow/deny decision
36
+ */
37
+ function evaluateRequest(hookInput) {
38
+ const { tool_name, tool_input, cwd, active_worktree_path, current_branch } = hookInput;
39
+
40
+ // ALWAYS ALLOW: Read operations
41
+ if (tool_name === 'Read' || tool_name === 'Glob' || tool_name === 'Grep') {
42
+ return { allowed: true };
43
+ }
44
+
45
+ // Handle Bash commands
46
+ if (tool_name === 'Bash') {
47
+ return evaluateBashCommand(tool_input.command || '', current_branch, cwd);
48
+ }
49
+
50
+ // Handle Write/Edit operations
51
+ if (tool_name === 'Write' || tool_name === 'Edit') {
52
+ return evaluateWriteOperation(tool_input.file_path || '', active_worktree_path, cwd);
53
+ }
54
+
55
+ // Default: allow unknown tools
56
+ return { allowed: true };
57
+ }
58
+
59
+ /**
60
+ * Evaluate Bash command against global rules
61
+ */
62
+ function evaluateBashCommand(command, currentBranch, cwd) {
63
+ // BLOCKED: Force push
64
+ if (/git\s+push\s+.*--force/.test(command) || /git\s+push\s+-f\b/.test(command)) {
65
+ return {
66
+ allowed: false,
67
+ message: 'Force push is blocked',
68
+ hint: 'Force pushing can destroy history. Use regular push or create a PR.'
69
+ };
70
+ }
71
+
72
+ // BLOCKED: Direct commit to main
73
+ if (/git\s+commit\b/.test(command) && currentBranch === 'main') {
74
+ return {
75
+ allowed: false,
76
+ message: 'Direct commits to main are blocked',
77
+ hint: 'Use jettypod work start to create a feature branch first.'
78
+ };
79
+ }
80
+
81
+ // BLOCKED: Manual worktree creation
82
+ if (/git\s+worktree\s+add\b/.test(command)) {
83
+ return {
84
+ allowed: false,
85
+ message: 'Manual worktree creation is blocked',
86
+ hint: 'Use jettypod work start to create worktrees.'
87
+ };
88
+ }
89
+
90
+ // BLOCKED: Manual branch creation
91
+ if (/git\s+checkout\s+-b\b/.test(command) || /git\s+branch\s+(?!-d|-D)/.test(command)) {
92
+ return {
93
+ allowed: false,
94
+ message: 'Manual branch creation is blocked',
95
+ hint: 'Use jettypod work start to create branches.'
96
+ };
97
+ }
98
+
99
+ // BLOCKED: Direct SQL mutation to work.db
100
+ if (/sqlite3\s+.*work\.db/.test(command)) {
101
+ const sqlCommand = command.toLowerCase();
102
+ // Allow SELECT, block mutations
103
+ if (/\b(update|insert|delete|drop|alter|create)\b/i.test(sqlCommand)) {
104
+ return {
105
+ allowed: false,
106
+ message: 'Direct database mutations are blocked',
107
+ hint: 'Use jettypod CLI commands to modify work items.'
108
+ };
109
+ }
110
+ }
111
+
112
+ // BLOCKED: Merge from inside worktree (causes CWD corruption)
113
+ if (/jettypod\s+(work|tests)\s+merge/.test(command)) {
114
+ if (cwd && /\.jettypod-work\//.test(cwd)) {
115
+ return {
116
+ allowed: false,
117
+ message: 'Cannot merge from inside a worktree. CWD would be deleted.',
118
+ hint: 'Run: cd /Users/erikspangenberg/jettypod-source && jettypod work merge <id>'
119
+ };
120
+ }
121
+ }
122
+
123
+ // ALLOWED: Git read-only commands
124
+ if (/git\s+(status|log|diff|show|branch\s*$|remote|fetch)\b/.test(command)) {
125
+ return { allowed: true };
126
+ }
127
+
128
+ // ALLOWED: Jettypod read-only commands
129
+ if (/jettypod\s+(backlog|status|work\s+status|impact)\b/.test(command)) {
130
+ return { allowed: true };
131
+ }
132
+
133
+ // Default: allow other commands
134
+ return { allowed: true };
135
+ }
136
+
137
+ /**
138
+ * Find the jettypod database path
139
+ * @param {string} cwd - Starting directory
140
+ * @returns {string|null} Database path or null
141
+ */
142
+ function findDatabasePath(cwd) {
143
+ let dir = cwd;
144
+ while (dir !== path.dirname(dir)) {
145
+ const dbPath = path.join(dir, '.jettypod', 'work.db');
146
+ if (fs.existsSync(dbPath)) {
147
+ return dbPath;
148
+ }
149
+ dir = path.dirname(dir);
150
+ }
151
+ return null;
152
+ }
153
+
154
+ /**
155
+ * Get the active worktree path from database
156
+ * @param {string} cwd - Current working directory
157
+ * @returns {string|null} Active worktree path or null
158
+ */
159
+ function getActiveWorktreePathFromDB(cwd) {
160
+ const dbPath = findDatabasePath(cwd);
161
+ if (!dbPath) {
162
+ return null;
163
+ }
164
+
165
+ try {
166
+ // Try better-sqlite3 first
167
+ const sqlite3 = require('better-sqlite3');
168
+ const db = sqlite3(dbPath, { readonly: true });
169
+ const row = db.prepare(
170
+ `SELECT worktree_path FROM worktrees WHERE status = 'active' LIMIT 1`
171
+ ).get();
172
+ db.close();
173
+ return row ? row.worktree_path : null;
174
+ } catch (err) {
175
+ // Fall back to CLI
176
+ const { spawnSync } = require('child_process');
177
+ const result = spawnSync('sqlite3', [
178
+ dbPath,
179
+ `SELECT worktree_path FROM worktrees WHERE status = 'active' LIMIT 1`
180
+ ], { encoding: 'utf-8' });
181
+
182
+ if (result.error || result.status !== 0) {
183
+ return null;
184
+ }
185
+ return result.stdout.trim() || null;
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Evaluate Write/Edit operation against global rules
191
+ */
192
+ function evaluateWriteOperation(filePath, inputWorktreePath, cwd) {
193
+ // Query database for active worktree, fall back to input if provided
194
+ const activeWorktreePath = getActiveWorktreePathFromDB(cwd) || inputWorktreePath;
195
+
196
+ // Normalize paths
197
+ const normalizedPath = path.resolve(cwd || '.', filePath);
198
+ const normalizedWorktree = activeWorktreePath ? path.resolve(cwd || '.', activeWorktreePath) : null;
199
+
200
+ // BLOCKED: Protected files (skills, hooks)
201
+ const protectedPatterns = [
202
+ /\.claude\/skills\//i,
203
+ /claude-hooks\//i,
204
+ /\.jettypod\/hooks\//i
205
+ ];
206
+
207
+ for (const pattern of protectedPatterns) {
208
+ if (pattern.test(normalizedPath) || pattern.test(filePath)) {
209
+ return {
210
+ allowed: false,
211
+ message: 'Protected file - cannot modify',
212
+ hint: 'Skill and hook files are protected. Modify them through proper channels.'
213
+ };
214
+ }
215
+ }
216
+
217
+ // Check if path is in a worktree
218
+ const isInWorktree = /\.jettypod-work\//.test(filePath) || /\.jettypod-work\//.test(normalizedPath);
219
+
220
+ if (isInWorktree) {
221
+ // If we have an active worktree, check if this write is to it
222
+ if (activeWorktreePath) {
223
+ // SECURITY: Only use normalized path comparison to prevent traversal attacks
224
+ // The filePath.includes() check was vulnerable to "../" escapes
225
+ const isActiveWorktree = normalizedPath.startsWith(normalizedWorktree + path.sep) ||
226
+ normalizedPath === normalizedWorktree;
227
+
228
+ if (isActiveWorktree) {
229
+ return { allowed: true };
230
+ } else {
231
+ return {
232
+ allowed: false,
233
+ message: 'Not your active worktree',
234
+ hint: 'You can only write to your currently active worktree.'
235
+ };
236
+ }
237
+ }
238
+ // No active worktree tracked but path looks like worktree - block
239
+ return {
240
+ allowed: false,
241
+ message: 'Not your active worktree',
242
+ hint: 'You can only write to your currently active worktree.'
243
+ };
244
+ }
245
+
246
+ // BLOCKED: Write to main repo (not in worktree)
247
+ return {
248
+ allowed: false,
249
+ message: 'Cannot write to main repository',
250
+ hint: 'Use jettypod work start to create a worktree first.'
251
+ };
252
+ }
253
+
254
+ /**
255
+ * Allow the action
256
+ */
257
+ function allow() {
258
+ console.log(JSON.stringify({
259
+ hookSpecificOutput: {
260
+ hookEventName: "PreToolUse",
261
+ permissionDecision: "allow"
262
+ }
263
+ }));
264
+ process.exit(0);
265
+ }
266
+
267
+ /**
268
+ * Deny the action with explanation
269
+ */
270
+ function deny(message, hint) {
271
+ const reason = `❌ ${message}\n\n💡 Hint: ${hint || 'Check your action.'}`;
272
+
273
+ console.log(JSON.stringify({
274
+ hookSpecificOutput: {
275
+ hookEventName: "PreToolUse",
276
+ permissionDecision: "deny",
277
+ permissionDecisionReason: reason
278
+ }
279
+ }));
280
+ process.exit(0);
281
+ }
package/jettypod.js CHANGED
@@ -785,6 +785,14 @@ async function initializeProject() {
785
785
  fs.chmodSync(enforceHookDest, 0o755);
786
786
  }
787
787
 
788
+ // Install global-guardrails hook
789
+ const guardrailsHookSource = path.join(__dirname, 'claude-hooks', 'global-guardrails.js');
790
+ const guardrailsHookDest = path.join('.jettypod', 'hooks', 'global-guardrails.js');
791
+ if (fs.existsSync(guardrailsHookSource)) {
792
+ fs.copyFileSync(guardrailsHookSource, guardrailsHookDest);
793
+ fs.chmodSync(guardrailsHookDest, 0o755);
794
+ }
795
+
788
796
  // Create Claude Code settings
789
797
  if (!fs.existsSync('.claude')) {
790
798
  fs.mkdirSync('.claude', { recursive: true });
@@ -800,15 +808,24 @@ async function initializeProject() {
800
808
  PreToolUse: [
801
809
  {
802
810
  matcher: 'Edit',
803
- hooks: [{ type: 'command', command: '.jettypod/hooks/protect-claude-md.js' }]
811
+ hooks: [
812
+ { type: 'command', command: '.jettypod/hooks/global-guardrails.js' },
813
+ { type: 'command', command: '.jettypod/hooks/protect-claude-md.js' }
814
+ ]
804
815
  },
805
816
  {
806
817
  matcher: 'Write',
807
- hooks: [{ type: 'command', command: '.jettypod/hooks/protect-claude-md.js' }]
818
+ hooks: [
819
+ { type: 'command', command: '.jettypod/hooks/global-guardrails.js' },
820
+ { type: 'command', command: '.jettypod/hooks/protect-claude-md.js' }
821
+ ]
808
822
  },
809
823
  {
810
824
  matcher: 'Bash',
811
- hooks: [{ type: 'command', command: '.jettypod/hooks/enforce-skill-activation.js' }]
825
+ hooks: [
826
+ { type: 'command', command: '.jettypod/hooks/global-guardrails.js' },
827
+ { type: 'command', command: '.jettypod/hooks/enforce-skill-activation.js' }
828
+ ]
812
829
  }
813
830
  ]
814
831
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jettypod",
3
- "version": "4.4.53",
3
+ "version": "4.4.55",
4
4
  "description": "AI-powered development workflow manager with TDD, BDD, and automatic test generation",
5
5
  "main": "jettypod.js",
6
6
  "bin": {
@@ -19,7 +19,7 @@ When this skill is activated, you are helping discover the best approach for a f
19
19
  |---------|----------|------|-------|
20
20
  | `work implement <feature-id>` | Transition feature to implementation phase | After chores created (Step 8C) | Feature Planning |
21
21
  | `work tests start <feature-id>` | Create worktree for test authoring | After transition (Step 8D) | Feature Planning |
22
- | `work tests merge <feature-id>` | Merge tests to main, cleanup worktree | After tests validated (Step 8D) | Feature Planning |
22
+ | `cd <main-repo> && work tests merge <feature-id>` | Merge tests to main, cleanup worktree | After tests validated (Step 8D) | Feature Planning |
23
23
  | `work start <chore-id>` | Start implementing a specific chore | After tests merged (Step 8E) | Speed Mode |
24
24
 
25
25
  **CRITICAL:** All commands are run by **Claude**, not the user. The distinction is:
@@ -653,7 +653,8 @@ Write your BDD files to:
653
653
  <worktree>/features/email-login.feature
654
654
  <worktree>/features/step_definitions/email-login.steps.js
655
655
 
656
- When done, run: jettypod work tests merge 42
656
+ When done, cd to main repo and merge:
657
+ cd /path/to/main/repo && jettypod work tests merge 42
657
658
  ```
658
659
 
659
660
  **🛑 STOP AND CHECK:** Verify worktree was created successfully. If you see an error, investigate before continuing.
@@ -735,10 +736,16 @@ sqlite3 .jettypod/work.db "UPDATE work_items SET scenario_file = 'features/${FEA
735
736
 
736
737
  **Sub-step 5: Merge tests to main**
737
738
 
739
+ **⚠️ CRITICAL: The merge will delete the worktree.** Your shell is currently inside it. Chain commands to ensure shell is in main repo BEFORE deletion:
740
+
738
741
  ```bash
739
- jettypod work tests merge ${FEATURE_ID}
742
+ # CRITICAL: cd to main repo AND merge in SAME command
743
+ # This ensures shell is in main repo BEFORE worktree deletion
744
+ cd <main-repo-path> && jettypod work tests merge ${FEATURE_ID}
740
745
  ```
741
746
 
747
+ If you run the merge while your shell is in the worktree, subsequent commands will fail with "No such file or directory".
748
+
742
749
  This will:
743
750
  - Commit changes in the worktree
744
751
  - Merge to main
@@ -913,6 +920,6 @@ Before completing feature planning, ensure:
913
920
  - [ ] **Test worktree created** with `work tests start` (Step 8D)
914
921
  - [ ] **BDD files written** in worktree (Step 8D)
915
922
  - [ ] **BDD infrastructure validated** with dry-run (Step 8D)
916
- - [ ] **Tests merged to main** with `work tests merge` (Step 8D)
923
+ - [ ] **Tests merged to main** with `cd <main-repo> && work tests merge` (Step 8D)
917
924
  - [ ] First chore started with `work start [chore-id]` (Step 8E)
918
925
  - [ ] **Speed-mode skill invoked using Skill tool** (Step 8E)
@@ -827,7 +827,7 @@ Repeat for each confirmed chore. **Do NOT use `--mode` flag** - chores inherit m
827
827
  **3. Release merge lock:**
828
828
 
829
829
  ```bash
830
- jettypod work merge --release-lock
830
+ cd <main-repo> && jettypod work merge --release-lock
831
831
  ```
832
832
 
833
833
  **🔄 WORKFLOW COMPLETE: Speed mode finished**
@@ -926,12 +926,12 @@ Before ending speed-mode skill, ensure:
926
926
 
927
927
  ## Command Reference
928
928
 
929
- **Complete chores (run from main repo, not worktree):**
929
+ **Complete chores (CRITICAL: cd to main repo BEFORE merge - worktree will be deleted):**
930
930
  ```bash
931
- jettypod work merge <id> # Merge chore by ID (recommended)
932
- jettypod work merge # Merge current chore (requires being on feature branch)
933
- jettypod work merge --with-transition # Hold lock for transition phase
934
- jettypod work merge --release-lock # Release held lock
931
+ cd <main-repo> && jettypod work merge <id> # Merge chore by ID (recommended)
932
+ cd <main-repo> && jettypod work merge # Merge current chore
933
+ cd <main-repo> && jettypod work merge --with-transition # Hold lock for transition
934
+ cd <main-repo> && jettypod work merge --release-lock # Release held lock
935
935
  ```
936
936
 
937
937
  **Start chores:**
@@ -942,7 +942,8 @@ jettypod work start <chore-id> # Create worktree and start chore
942
942
  **Create test worktree (for writing BDD scenarios):**
943
943
  ```bash
944
944
  jettypod work tests <feature-id> # Create worktree for writing tests
945
- jettypod work tests merge <feature-id> # Merge tests back to main
945
+ # CRITICAL: cd to main repo BEFORE merge (worktree will be deleted)
946
+ cd <main-repo> && jettypod work tests merge <feature-id>
946
947
  ```
947
948
 
948
949
  **Set feature mode (BEFORE creating chores):**
@@ -743,10 +743,10 @@ Before ending stable-mode skill, ensure:
743
743
 
744
744
  ## Command Reference
745
745
 
746
- **Complete chores (run from main repo, not worktree):**
746
+ **Complete chores (CRITICAL: cd to main repo BEFORE merge - worktree will be deleted):**
747
747
  ```bash
748
- jettypod work merge <id> # Merge chore by ID (recommended)
749
- jettypod work merge # Merge current chore (requires being on feature branch)
748
+ cd <main-repo> && jettypod work merge <id> # Merge chore by ID (recommended)
749
+ cd <main-repo> && jettypod work merge # Merge current chore
750
750
  ```
751
751
 
752
752
  **Start chores:**
@@ -776,7 +776,7 @@ jettypod work set-mode <feature-id> production # Set feature to production mode
776
776
  - Parallel worktrees branch from main independently
777
777
 
778
778
  **Process:**
779
- 1. Complete chore → `jettypod work merge`
779
+ 1. Complete chore → `cd <main-repo> && jettypod work merge` (CRITICAL: cd first!)
780
780
  2. Start next chore → `jettypod work start <next-id>`
781
781
  3. Repeat
782
782
 
@@ -1,120 +0,0 @@
1
- # Command Whitelist Matrix: Feature Planning
2
-
3
- This document defines what commands/actions are allowed at each step of the feature-planning skill. Hooks should enforce these rules and redirect the agent when violations occur.
4
-
5
- ## Matrix
6
-
7
- | Step | Description | Allowed Commands | Blocked Commands | Redirect Message |
8
- |------|-------------|------------------|------------------|------------------|
9
- | **1** | Get feature context | `work show`, `workflow start`, `backlog` | Any file writes, `work create`, `work start`, `work implement` | "You're in Step 1 - get context first with `work show <id>`" |
10
- | **2** | Check epic decisions | `decisions --epic=X` | File writes, `work create`, `work start` | "Check for epic decisions before suggesting approaches" |
11
- | **3** | Suggest 3 UX approaches | Read-only (Read, Glob, Grep) | File writes, `work create`, `work start` | "Wait for user to pick an approach - no changes yet" |
12
- | **4** | Optional prototyping | Write to `prototypes/` only | Writes to `features/`, `src/`, `work create`, `work start` | "Prototypes go in prototypes/ - no production code yet" |
13
- | **5** | Choose winner | `workflow checkpoint` | File writes, `work create`, `work start` | "Wait for user to confirm winner" |
14
- | **6A** | Define integration contract | Read-only | File writes | "Define how users reach the feature first" |
15
- | **6B** | Propose BDD scenarios | Read-only, `workflow checkpoint` | **Write to `features/`**, `work create`, `work start` | "**BDD files are written in Step 8D in a worktree, not now**" |
16
- | **7** | Propose chores | Read-only (analyze codebase) | `work create`, `work start`, file writes | "Propose chores to user first - don't create yet" |
17
- | **8B** | Create chores | `work create chore` | `work start`, writes to `features/` | "Create all chores before transitioning" |
18
- | **8C** | Execute transition | `work implement`, `workflow checkpoint` | `work start` | "Transition the feature before starting chores" |
19
- | **8D** | Write tests in worktree | `work tests start`, `work tests merge`, Write to **worktree path only**, `cucumber-js --dry-run` | Write to main repo paths, `work start` | "Write tests in the worktree, not main repo" |
20
- | **8E** | Start first chore | `work start`, `workflow complete` | - | "Start the chore, then invoke speed-mode" |
21
-
22
- ## Key Enforcement Points
23
-
24
- ### 0. Allowlist-First Enforcement
25
-
26
- **Principle:** Each step defines what IS allowed, not what's blocked. Anything not explicitly allowed is rejected.
27
-
28
- This is simpler to reason about, safer by default, and guides the agent toward correct behavior rather than away from incorrect behavior.
29
-
30
- **Enforcement logic:**
31
- 1. Check if command/action is in the step's allowlist → allow
32
- 2. Check if it matches a global bypass pattern (see below) → block with specific message
33
- 3. Otherwise → block with generic "not allowed at this step" + list what IS allowed
34
-
35
- ### 1. Common Bypass Patterns (Global Blocks)
36
-
37
- Agents sometimes try shortcuts that bypass CLI commands entirely. Catch the common ones:
38
-
39
- - **Direct SQL:** `sqlite3` commands, raw SQL (`INSERT`, `UPDATE`, `DELETE`) in bash
40
- - **Inline Node execution:** `node -e` with database operations
41
-
42
- **Redirect Message:** "Use CLI commands to modify work items, not direct SQL. Run `jettypod help` to see available commands."
43
-
44
- We don't need to catch every possible bypass - these cover ~95% of cases. The redirect message does the real work.
45
-
46
- ### 2. Step 6B is the Critical Trap
47
-
48
- The agent often tries to write `.feature` files in Step 6B after proposing scenarios. This must be blocked.
49
-
50
- **Rule:** Block all writes to `features/**` until Step 8D, and only then to the worktree path.
51
-
52
- ### 3. Worktree Path Validation
53
-
54
- In Step 8D, writes are only allowed to the active worktree path (`.jettypod-work/tests-*`), not anywhere in the main repo.
55
-
56
- **Rule:** If a write targets a path that doesn't start with the active worktree path, block it.
57
-
58
- ### 4. Order Enforcement
59
-
60
- The following commands have strict ordering:
61
- 1. `work create chore` - Only in Step 8B (after user confirms chores)
62
- 2. `work implement` - Only in Step 8C (after chores created)
63
- 3. `work tests start` - Only in Step 8D (after implement)
64
- 4. `work tests merge` - Only in Step 8D (after tests written)
65
- 5. `work start` - Only in Step 8E (after tests merged)
66
-
67
- **Rule:** Each command should validate the previous step completed.
68
-
69
- ## Context Required for Enforcement
70
-
71
- Hooks need access to:
72
- - **Current skill:** `feature-planning`
73
- - **Current step:** 1-8E (from workflow checkpoint)
74
- - **Feature ID:** The work item being planned
75
- - **Worktree path:** For Step 8D validation (from `worktrees` table)
76
-
77
- ## Example Hook Logic
78
-
79
- ```javascript
80
- // Pseudocode for pre-command hook
81
- function validateCommand(command, context) {
82
- const { skill, step, featureId, worktreePath } = context;
83
-
84
- if (skill !== 'feature-planning') return { allowed: true };
85
-
86
- // Step 6B: Block writes to features/
87
- if (step === '6B' && command.type === 'write' && command.path.includes('features/')) {
88
- return {
89
- allowed: false,
90
- message: "BDD files are written in Step 8D in a worktree, not now. You're proposing scenarios - wait for user confirmation."
91
- };
92
- }
93
-
94
- // Step 8D: Only allow writes to worktree
95
- if (step === '8D' && command.type === 'write') {
96
- if (!command.path.startsWith(worktreePath)) {
97
- return {
98
- allowed: false,
99
- message: `Write tests in the worktree (${worktreePath}), not the main repo.`
100
- };
101
- }
102
- }
103
-
104
- // Block work start until Step 8E
105
- if (command.name === 'work start' && step !== '8E') {
106
- return {
107
- allowed: false,
108
- message: `Cannot start chores yet. Complete steps through 8D first (tests must be merged to main).`
109
- };
110
- }
111
-
112
- return { allowed: true };
113
- }
114
- ```
115
-
116
- ## Open Questions
117
-
118
- 1. **Step granularity:** Should we track sub-steps like 8B, 8C, 8D, 8E separately, or group them as "Step 8"?
119
- 2. **Read operations:** Should we restrict what files can be read at certain steps, or only writes?
120
- 3. **Skill transitions:** How do we handle the handoff to speed-mode at Step 8E?