@dezkareid/osddt 1.10.1 → 1.11.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.
package/AGENTS.md CHANGED
@@ -82,7 +82,6 @@ Always prefer use exact versions for dependencies. Do not use `^` or `~`.
82
82
 
83
83
  #### Linting & Formatting
84
84
  - **ESLint**: `9.39.2`
85
- - **Prettier**: `3.8.1`
86
85
 
87
86
  #### Type Definitions
88
87
  - **@types/node**: `25.0.10`
@@ -112,7 +111,8 @@ osddt/
112
111
  │ │ ├── claude.ts # Formats COMMAND_DEFINITIONS as Markdown (.claude/commands/osddt.<name>.md)
113
112
  │ │ └── gemini.ts # Formats COMMAND_DEFINITIONS as TOML (.gemini/commands/osddt.<name>.toml)
114
113
  │ └── utils/
115
- └── prompt.ts # Inquirer prompts: askAgents() (checkbox) and askRepoType() (select)
114
+ ├── prompt.ts # Inquirer prompts: askAgents() (checkbox), askRepoType() (select), askWorktreeUrl() (input)
115
+ │ └── worktree.ts # Shared worktree helpers: git version check, not-a-worktree check, writable check, state file init
116
116
  ├── dist/ # Compiled output (single ESM bundle, gitignored)
117
117
  ├── rollup.config.js # Bundles src/index.ts → dist/index.js (ESM, shebang injected)
118
118
  ├── tsconfig.json # TypeScript config
@@ -126,7 +126,7 @@ osddt/
126
126
  #### Key relationships
127
127
 
128
128
  - **`shared.ts` is the single source of truth** for all command template content. Both `claude.ts` and `gemini.ts` import `COMMAND_DEFINITIONS` from it and only differ in file format (Markdown vs TOML) and argument placeholder (`$ARGUMENTS` vs `{{args}}`).
129
- - **`setup.ts`** orchestrates setup: reads `--agents`/`--repo-type` flags when provided (non-interactive), otherwise calls `askAgents()` → `askRepoType()` → writes selected agent files → writes `.osddtrc`.
129
+ - **`setup.ts`** orchestrates setup: reads `--agents`/`--repo-type`/`--worktree-repository` flags when provided (non-interactive), otherwise calls `askAgents()` → `askRepoType()` → `askWorktreeUrl()` → if URL provided, runs worktree environment checks via `worktree.ts` → writes selected agent files → writes `.osddtrc`.
130
130
  - **`meta-info.ts`** is referenced inside the generated templates so agents can fetch live branch/date at invocation time (not baked in at build time).
131
131
  - **Test files** (`.spec.ts`) live next to the source file they cover. Template tests are pure (no mocks); command tests mock `fs-extra` and `child_process`.
132
132
 
@@ -147,18 +147,28 @@ The selected agents are saved in `.osddtrc` alongside `repoType`. When `osddt up
147
147
 
148
148
  #### Available commands
149
149
 
150
- | Command | Context | Description |
151
- | -------------------------------------------------------------------- | ------------- | ------------------------------------------------------------- |
152
- | `osddt setup` | Local dev | Generate agent command files for Claude and Gemini |
153
- | `osddt setup --agents <list> --repo-type <type>` | Local dev | Non-interactive setup (for CI/scripted environments) |
154
- | `npx @dezkareid/osddt setup` | External | Generate agent command files for Claude and Gemini |
155
- | `npx @dezkareid/osddt setup --agents <list> --repo-type <type>` | External | Non-interactive setup (for CI/scripted environments) |
156
- | `osddt meta-info` | Local dev | Output current branch and date as JSON |
157
- | `npx @dezkareid/osddt meta-info` | External | Output current branch and date as JSON |
158
- | `osddt done <feature-name> --dir <project-path>` | Local dev | Move `working-on/<feature>` to `done/<feature>` |
159
- | `npx @dezkareid/osddt done <feature-name> --dir <project-path>` | External | Move `working-on/<feature>` to `done/<feature>` |
160
- | `osddt update` | Local dev | Regenerate agent command files from the existing `.osddtrc` |
161
- | `npx @dezkareid/osddt update` | External | Regenerate agent command files from the existing `.osddtrc` |
150
+ | Command | Context | Description |
151
+ | ---------------------------------------------------------------------------------------------- | ------------- | ------------------------------------------------------------- |
152
+ | `osddt setup` | Local dev | Generate agent command files for Claude and Gemini |
153
+ | `osddt setup --agents <list> --repo-type <type>` | Local dev | Non-interactive setup (for CI/scripted environments) |
154
+ | `osddt setup --worktree-repository <url>` | Local dev | Setup with worktree workflow enabled |
155
+ | `npx @dezkareid/osddt setup` | External | Generate agent command files for Claude and Gemini |
156
+ | `npx @dezkareid/osddt setup --agents <list> --repo-type <type>` | External | Non-interactive setup (for CI/scripted environments) |
157
+ | `npx @dezkareid/osddt setup --worktree-repository <url>` | External | Setup with worktree workflow enabled |
158
+ | `osddt meta-info` | Local dev | Output current branch and date as JSON |
159
+ | `npx @dezkareid/osddt meta-info` | External | Output current branch and date as JSON |
160
+ | `osddt done <feature-name> --dir <project-path>` | Local dev | Move `working-on/<feature>` to `done/<feature>` |
161
+ | `npx @dezkareid/osddt done <feature-name> --dir <project-path>` | External | Move `working-on/<feature>` to `done/<feature>` |
162
+ | `osddt done <feature-name> --dir <project-path> --worktree` | Local dev | Archive feature, remove git worktree, and clean state file |
163
+ | `npx @dezkareid/osddt done <feature-name> --dir <project-path> --worktree` | External | Archive feature, remove git worktree, and clean state file |
164
+ | `osddt update` | Local dev | Regenerate agent command files from the existing `.osddtrc` |
165
+ | `npx @dezkareid/osddt update` | External | Regenerate agent command files from the existing `.osddtrc` |
166
+ | `osddt start-worktree <feature-name>` | Local dev | Create a git worktree for a feature and scaffold working-on/ |
167
+ | `npx @dezkareid/osddt start-worktree <feature-name>` | External | Create a git worktree for a feature and scaffold working-on/ |
168
+ | `osddt start-worktree <feature-name> --dir <package-path>` | Local dev | Same, specifying the package path in a monorepo |
169
+ | `npx @dezkareid/osddt start-worktree <feature-name> --dir <package-path>` | External | Same, specifying the package path in a monorepo |
170
+ | `osddt worktree-info <feature-name>` | Local dev | Look up worktree paths for a feature (JSON output) |
171
+ | `npx @dezkareid/osddt worktree-info <feature-name>` | External | Look up worktree paths for a feature (JSON output) |
162
172
 
163
173
  #### `osddt setup` options
164
174
 
@@ -166,26 +176,27 @@ The selected agents are saved in `.osddtrc` alongside `repoType`. When `osddt up
166
176
  | ---- | ------ | ----------- |
167
177
  | `--agents <list>` | `claude`, `gemini` (comma-separated) | Skip the agents prompt and use the provided value(s) |
168
178
  | `--repo-type <type>` | `single`, `monorepo` | Skip the repo type prompt and use the provided value |
179
+ | `--worktree-repository <url>` | any git URL | Enable worktree workflow; saves URL as `"worktree-repository"` in `.osddtrc` |
169
180
  | `-d, --dir <directory>` | any path | Target directory (defaults to current working directory) |
170
181
 
171
- Both flags are optional. Providing neither runs the fully interactive mode. Providing both skips all prompts.
182
+ All flags are optional. Providing neither runs the fully interactive mode. When `--worktree-repository` is provided, environment checks run and `.osddt-worktrees` is initialised.
172
183
 
173
184
  ### Command Templates
174
185
 
175
186
  Templates are generated by `npx @dezkareid/osddt setup` and placed in each agent's commands directory. Each template corresponds to a step in the spec-driven workflow.
176
187
 
177
- | Template | Description |
178
- | ------------------ | ------------------------------------------------------------------ |
179
- | `osddt.continue` | Detect the current workflow phase and prompt the next command |
180
- | `osddt.research` | Research a topic and write a research file to inform the spec |
181
- | `osddt.start` | Start a new feature by creating a branch and working-on folder |
182
- | `osddt.spec` | Analyze requirements and write a feature specification |
183
- | `osddt.clarify` | Resolve open questions in the spec and record decisions (optional) |
184
- | `osddt.plan` | Create a technical implementation plan from a specification |
185
- | `osddt.tasks` | Generate actionable tasks from an implementation plan |
186
- | `osddt.implement` | Execute tasks from the task list one by one |
187
- | `osddt.fast` | Bootstrap all planning artifacts (spec, plan, tasks) in one shot |
188
- | `osddt.done` | Resolve project path, verify tasks, and move the feature to done |
188
+ | Template | Description |
189
+ | ---------------- | ---------------------------------------------------------------------------------------- |
190
+ | `osddt.continue` | Detect the current workflow phase and prompt the next command |
191
+ | `osddt.research` | Research a topic and write a research file to inform the spec |
192
+ | `osddt.start` | Start a new feature uses standard or worktree workflow based on `.osddtrc` |
193
+ | `osddt.spec` | Analyze requirements and write a feature specification |
194
+ | `osddt.clarify` | Resolve open questions in the spec and record decisions (optional) |
195
+ | `osddt.plan` | Create a technical implementation plan from a specification |
196
+ | `osddt.tasks` | Generate actionable tasks from an implementation plan |
197
+ | `osddt.implement`| Execute tasks from the task list one by one |
198
+ | `osddt.fast` | Bootstrap all planning artifacts (spec, plan, tasks) in one shot |
199
+ | `osddt.done` | Resolve project path, verify tasks, and move the feature to done |
189
200
 
190
201
  #### Template Workflow
191
202
 
@@ -249,6 +260,33 @@ Templates are generated by `npx @dezkareid/osddt setup` and placed in each agent
249
260
  /osddt.done
250
261
  ```
251
262
 
263
+ ##### Example D — Parallel feature workflow (git worktree)
264
+
265
+ Enable worktree mode once during setup by providing the repository URL:
266
+
267
+ ```bash
268
+ npx @dezkareid/osddt setup --agents claude --repo-type single --worktree-repository https://github.com/org/repo.git
269
+ ```
270
+
271
+ From that point on, `/osddt.start` automatically uses the worktree workflow:
272
+
273
+ ```
274
+ /osddt.start add-payment-gateway
275
+ /osddt.spec
276
+ /osddt.plan use Stripe SDK, REST endpoints
277
+ /osddt.tasks
278
+ /osddt.implement
279
+ /osddt.done
280
+ ```
281
+
282
+ The worktree is created as a sibling of the repo root (e.g. `../my-repo-add-payment-gateway`). Active worktrees are tracked in `.osddt-worktrees` (sibling to the repo root) — a JSON array with entries containing `featureName`, `branch`, `worktreePath`, `workingDir`, and `repoRoot`. `osddt.continue` and `osddt.done` read this file to resolve the correct paths. When `osddt.done` runs, it archives the planning folder **and** removes the worktree automatically.
283
+
284
+ You can customise the worktree base directory via `worktreeBase` in `.osddtrc`:
285
+
286
+ ```json
287
+ { "repoType": "single", "worktree-repository": "https://github.com/org/repo.git", "worktreeBase": "/Users/me/worktrees" }
288
+ ```
289
+
252
290
  ---
253
291
 
254
292
  - Use **`osddt.research`** when you want to explore the codebase and gather findings before writing the spec. It creates the `working-on/<feature-name>/` folder and writes `osddt.research.md`.
@@ -276,13 +314,16 @@ Templates are generated by `npx @dezkareid/osddt setup` and placed in each agent
276
314
 
277
315
  - **Input**: Either a human-readable feature description (e.g. `"Add user authentication"`) or an existing branch name (e.g. `feat/add-user-auth`).
278
316
  - **Branch name resolution**: input is used as-is if it looks like a branch name; otherwise a name is derived (lowercased, hyphens, prefixed with `feat/`), subject to the 30-character feature name limit.
279
- - **Actions performed by the agent**:
317
+ - **Workflow mode**: determined by reading `.osddtrc`. If `"worktree-repository"` is present, the worktree workflow is used; otherwise the standard branch workflow is used. The user is never prompted to choose.
318
+ - **Standard workflow** (no `"worktree-repository"` in `.osddtrc`):
280
319
  1. Checks for an existing branch — offers **Resume** (`git checkout`) or **Abort** if found, otherwise runs `git checkout -b <branch-name>`.
281
- 2. Reads `.osddtrc` to resolve the project path (single vs monorepo).
282
- 3. Checks for an existing `working-on/<feature-name>/` folder — offers **Resume** or **Abort** if found, otherwise creates it.
283
- 4. Reports the branch and working directory, then shows a context-aware next step:
284
- - If input was a **human-readable description**: informs the user their description will be used as the starting point for the spec and suggests running `/osddt.spec` (with an optional-context note).
285
- - If input was a **branch name** (or no arguments): prompts the user to run `/osddt.spec` with an optional-context note.
320
+ 2. Checks for an existing `working-on/<feature-name>/` folder offers **Resume** or **Abort** if found, otherwise creates it.
321
+ 3. Reports the branch and working directory.
322
+ - **Worktree workflow** (`"worktree-repository"` present in `.osddtrc`):
323
+ 1. Derives the branch and feature name.
324
+ 2. Runs `npx @dezkareid/osddt start-worktree <feature-name> [--dir <package-path>]`.
325
+ 3. Navigates into the created worktree directory to locate the project root.
326
+ 4. Reports the branch, worktree path, project root, and working directory.
286
327
 
287
328
  #### osddt.research behaviour
288
329
 
@@ -311,7 +352,9 @@ Templates are generated by `npx @dezkareid/osddt setup` and placed in each agent
311
352
  1. Reads `.osddtrc` to resolve the project path (single vs monorepo). For monorepos, asks the user which package.
312
353
  2. Lists all folders under `working-on/`. If there is only one, uses it automatically; if there are multiple, asks the user to pick one.
313
354
  3. Confirms all tasks in `osddt.tasks.md` are checked off (`- [x]`).
314
- 4. Runs `npx @dezkareid/osddt done <feature-name> --dir <project-path>` to move the folder.
355
+ 4. Runs `npx @dezkareid/osddt worktree-info <feature-name>` to check if this is a worktree feature.
356
+ - If found: runs `npx @dezkareid/osddt done <feature-name> --dir <project-path> --worktree` (archives folder, removes worktree, cleans state file).
357
+ - If not found: runs `npx @dezkareid/osddt done <feature-name> --dir <project-path>` (existing behaviour).
315
358
 
316
359
  Note: the `osddt done` CLI command does **not** read `.osddtrc` — project path resolution is the agent's responsibility, handled in step 1 above.
317
360
 
package/README.md CHANGED
@@ -3,13 +3,18 @@ Other spec driven development tool but for monorepo
3
3
 
4
4
  ## CLI Commands
5
5
 
6
- | Command | Description |
7
- | -------------------------------------------------------------------- | ------------------------------------------------------------- |
8
- | `@dezkareid/osddt setup` | Generate agent command files for Claude and Gemini |
9
- | `@dezkareid/osddt setup --agents <list> --repo-type <type>` | Non-interactive setup (for CI/scripted environments) |
10
- | `@dezkareid/osddt meta-info` | Output current branch and date as JSON |
11
- | `@dezkareid/osddt done <feature-name> --dir <project-path>` | Move `working-on/<feature>` to `done/<feature>` |
12
- | `@dezkareid/osddt update` | Regenerate agent command files from the existing `.osddtrc` |
6
+ | Command | Description |
7
+ | -------------------------------------------------------------------------------- | ------------------------------------------------------------- |
8
+ | `@dezkareid/osddt setup` | Generate agent command files for Claude and Gemini |
9
+ | `@dezkareid/osddt setup --agents <list> --repo-type <type>` | Non-interactive setup (for CI/scripted environments) |
10
+ | `@dezkareid/osddt setup --worktree-repository <url>` | Setup with worktree workflow enabled |
11
+ | `@dezkareid/osddt meta-info` | Output current branch and date as JSON |
12
+ | `@dezkareid/osddt done <feature-name> --dir <project-path>` | Move `working-on/<feature>` to `done/<feature>` |
13
+ | `@dezkareid/osddt done <feature-name> --dir <project-path> --worktree` | Archive feature, remove git worktree, and clean state file |
14
+ | `@dezkareid/osddt update` | Regenerate agent command files from the existing `.osddtrc` |
15
+ | `@dezkareid/osddt start-worktree <feature-name>` | Create a git worktree for a feature and scaffold working-on/ |
16
+ | `@dezkareid/osddt start-worktree <feature-name> --dir <package-path>` | Same, specifying the package path in a monorepo |
17
+ | `@dezkareid/osddt worktree-info <feature-name>` | Look up worktree paths for a feature (JSON output) |
13
18
 
14
19
  ### `osddt setup` options
15
20
 
@@ -17,11 +22,20 @@ Other spec driven development tool but for monorepo
17
22
  | ---- | ------ | ----------- |
18
23
  | `--agents <list>` | `claude`, `gemini` (comma-separated) | Skip the agents prompt and use the provided value(s) |
19
24
  | `--repo-type <type>` | `single`, `monorepo` | Skip the repo type prompt and use the provided value |
25
+ | `--worktree-repository <url>` | any git URL | Enable worktree workflow and save the repository URL |
20
26
  | `-d, --dir <directory>` | any path | Target directory (defaults to current working directory) |
21
27
 
22
- Both flags are optional. Providing neither runs the fully interactive mode. Providing both skips all prompts.
28
+ All flags are optional. Providing neither runs the fully interactive mode. Providing `--agents` and `--repo-type` together skips all standard prompts. Adding `--worktree-repository` also runs environment checks and initialises the worktree state file.
23
29
 
24
- The selected agents are saved in `.osddtrc` alongside `repoType` so that `osddt update` can regenerate the correct files without prompting.
30
+ The selected agents and config are saved in `.osddtrc` so that `osddt update` can regenerate the correct files without prompting.
31
+
32
+ ```json
33
+ // Standard mode
34
+ { "repoType": "single", "agents": ["claude"] }
35
+
36
+ // Worktree mode — the "worktree-repository" field enables the worktree workflow
37
+ { "repoType": "single", "agents": ["claude"], "worktree-repository": "https://github.com/org/repo.git" }
38
+ ```
25
39
 
26
40
  ```bash
27
41
  # Interactive (default)
@@ -106,6 +120,41 @@ flowchart LR
106
120
 
107
121
  > Review the `## Assumptions` section of `osddt.plan.md` before implementing. Run `/osddt.clarify` if any Open Questions in the spec need resolving first.
108
122
 
123
+ #### Parallel feature workflow (git worktree)
124
+
125
+ Enable worktree mode by providing the repository URL during setup:
126
+
127
+ ```bash
128
+ npx @dezkareid/osddt setup --agents claude --repo-type single --worktree-repository https://github.com/org/repo.git
129
+ ```
130
+
131
+ This saves `"worktree-repository"` in `.osddtrc`, runs environment checks, and initialises the `.osddt-worktrees` state file. From that point on, `/osddt.start` automatically uses the worktree workflow:
132
+
133
+ ```
134
+ /osddt.start add-payment-gateway
135
+ /osddt.spec
136
+ /osddt.plan use Stripe SDK, REST endpoints
137
+ /osddt.tasks
138
+ /osddt.implement
139
+ /osddt.done
140
+ ```
141
+
142
+ The worktree is created as a sibling of your repo (e.g. `../my-repo-add-payment-gateway`). The `working-on/` planning folder lives inside the worktree alongside the code. When `osddt.done` runs, it archives the planning folder **and** removes the worktree automatically.
143
+
144
+ You can customise the worktree base directory by adding `worktreeBase` to `.osddtrc`:
145
+
146
+ ```json
147
+ {
148
+ "repoType": "single",
149
+ "worktree-repository": "https://github.com/org/repo.git",
150
+ "worktreeBase": "/Users/me/worktrees"
151
+ }
152
+ ```
153
+
154
+ Active worktrees are tracked in `.osddt-worktrees` (sibling to the repo root), a JSON array with entries containing `featureName`, `branch`, `worktreePath`, `workingDir`, and `repoRoot`. This file is the single source of truth used by `osddt.continue` and `osddt.done` to resolve the correct paths.
155
+
156
+ > **Migration note**: If you previously used `osddt setup-worktree` or `/osddt.start-worktree`, those commands have been removed. Re-run `osddt setup --worktree-repository <url>` to configure the new unified workflow. Manually delete any `osddt.start-worktree.*` files from your agent command directories — `osddt update` will not regenerate them.
157
+
109
158
  #### Resuming after closing a session
110
159
 
111
160
  ```
@@ -118,18 +167,18 @@ flowchart LR
118
167
  /osddt.done
119
168
  ```
120
169
 
121
- | Template | Description |
122
- | ------------------ | ------------------------------------------------------------------ |
123
- | `osddt.continue` | Detect the current workflow phase and prompt the next command |
124
- | `osddt.research` | Research a topic and write a research file to inform the spec |
125
- | `osddt.start` | Start a new feature by creating a branch and working-on folder |
126
- | `osddt.spec` | Analyze requirements and write a feature specification |
127
- | `osddt.clarify` | Resolve open questions in the spec and record decisions (optional) |
128
- | `osddt.plan` | Create a technical implementation plan from a specification |
129
- | `osddt.tasks` | Generate actionable tasks from an implementation plan |
130
- | `osddt.implement` | Execute tasks from the task list one by one |
131
- | `osddt.fast` | Bootstrap all planning artifacts (spec, plan, tasks) in one shot |
132
- | `osddt.done` | Resolve project path, verify tasks, and move the feature to done |
170
+ | Template | Description |
171
+ | ---------------- | ------------------------------------------------------------------ |
172
+ | `osddt.continue` | Detect the current workflow phase and prompt the next command |
173
+ | `osddt.research` | Research a topic and write a research file to inform the spec |
174
+ | `osddt.start` | Start a new feature uses standard or worktree workflow based on `.osddtrc` |
175
+ | `osddt.spec` | Analyze requirements and write a feature specification |
176
+ | `osddt.clarify` | Resolve open questions in the spec and record decisions (optional) |
177
+ | `osddt.plan` | Create a technical implementation plan from a specification |
178
+ | `osddt.tasks` | Generate actionable tasks from an implementation plan |
179
+ | `osddt.implement`| Execute tasks from the task list one by one |
180
+ | `osddt.fast` | Bootstrap all planning artifacts (spec, plan, tasks) in one shot |
181
+ | `osddt.done` | Resolve project path, verify tasks, and move the feature to done |
133
182
 
134
183
  Generated files are placed in:
135
184
 
@@ -0,0 +1,9 @@
1
+ import { Command } from 'commander';
2
+ export interface WorktreeEntry {
3
+ featureName: string;
4
+ branch: string;
5
+ worktreePath: string;
6
+ workingDir: string;
7
+ repoRoot: string;
8
+ }
9
+ export declare function startWorktreeCommand(): Command;
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function worktreeInfoCommand(): Command;
package/dist/index.js CHANGED
@@ -5,7 +5,9 @@ import path from 'path';
5
5
  import { fileURLToPath } from 'url';
6
6
  import select from '@inquirer/select';
7
7
  import checkbox from '@inquirer/checkbox';
8
+ import input from '@inquirer/input';
8
9
  import { execSync } from 'child_process';
10
+ import readline from 'readline';
9
11
 
10
12
  function getRepoPreamble(npxCommand) {
11
13
  return `## Context
@@ -18,15 +20,22 @@ ${npxCommand} meta-info
18
20
 
19
21
  ## Repository Configuration
20
22
 
21
- Before proceeding, read the \`.osddtrc\` file in the root of the repository to determine the project path.
23
+ Before proceeding, read the \`.osddtrc\` file in the root of the repository to determine the project path and workflow mode.
22
24
 
23
25
  \`\`\`json
24
- // .osddtrc example
25
- { "repoType": "monorepo" | "single" }
26
+ // standard mode
27
+ { "repoType": "monorepo" | "single", "agents": ["claude"] }
28
+
29
+ // worktree mode — "worktree-repository" presence determines the workflow
30
+ { "repoType": "monorepo" | "single", "agents": ["claude"], "worktree-repository": "https://github.com/org/repo.git" }
26
31
  \`\`\`
27
32
 
28
33
  - If \`repoType\` is \`"single"\`: the project path is the repository root.
29
34
  - If \`repoType\` is \`"monorepo"\`: ask the user which package to work on (e.g. \`packages/my-package\`), then use \`<repo-root>/<package>\` as the project path.
35
+ - If \`"worktree-repository"\` is **present**: once the feature name is known, run \`${npxCommand} worktree-info <feature-name>\` to resolve the working directory:
36
+ - exit code **0**: parse the JSON and use the returned \`workingDir\` as the working directory.
37
+ - exit code **1**: the feature is not yet in a worktree — proceed as standard.
38
+ - If \`"worktree-repository"\` is **absent**: use the standard project path from \`.osddtrc\`.
30
39
 
31
40
  ## Working Directory
32
41
 
@@ -159,7 +168,9 @@ ${FEATURE_NAME_RULES}
159
168
 
160
169
  Once the feature name is determined:
161
170
 
162
- 3. ${WORKING_DIR_STEP}
171
+ 3. Resolve the working directory:
172
+ - If \`"worktree-repository"\` is present in \`.osddtrc\`: run \`${npxCommand} worktree-info <feature-name>\` and use the returned \`workingDir\` if it exits with code 0; otherwise fall through to the standard path.
173
+ - Otherwise: ${WORKING_DIR_STEP}
163
174
 
164
175
  4. Research the topic thoroughly:
165
176
  - Explore the existing codebase for relevant patterns, conventions, and prior art
@@ -201,7 +212,37 @@ Apply the constraints below to the feature name (the segment after the last \`/\
201
212
 
202
213
  ${FEATURE_NAME_RULES}
203
214
 
204
- Once the branch name is determined:
215
+ Once the branch name is determined, choose the workflow based on \`.osddtrc\`:
216
+
217
+ ---
218
+
219
+ ### If \`worktree-repository\` is **present** — Worktree workflow
220
+
221
+ 3. Run the following command to create the git worktree, scaffold the working directory, and register the feature in the state file:
222
+
223
+ \`\`\`
224
+ ${npxCommand} start-worktree <feature-name>
225
+ \`\`\`
226
+
227
+ For monorepos, pass the package path:
228
+
229
+ \`\`\`
230
+ ${npxCommand} start-worktree <feature-name> --dir <package-path>
231
+ \`\`\`
232
+
233
+ 4. Parse the command output to extract \`worktreePath\` and \`workingDir\`.
234
+
235
+ 5. Navigate into the worktree directory to locate the project:
236
+ - Enter \`<worktreePath>\` — this is the isolated git worktree for this feature.
237
+ - If \`repoType\` is \`"single"\`: the project root is \`<worktreePath>\`.
238
+ - If \`repoType\` is \`"monorepo"\`: the project root is \`<worktreePath>/<package-path>\`.
239
+ - The planning files will live under \`<workingDir>\` (i.e. \`<project-root>/working-on/<feature-name>/\`).
240
+
241
+ 6. Report the branch name, worktree path, project root, and working directory.
242
+
243
+ ---
244
+
245
+ ### If \`worktree-repository\` is **absent** — Standard workflow
205
246
 
206
247
  3. Check whether the branch already exists locally or remotely:
207
248
  - If it **does not exist**, create and switch to it:
@@ -218,6 +259,8 @@ Where \`<feature-name>\` is the last segment of the branch name (after the last
218
259
 
219
260
  5. Report the branch name and working directory that were created or resumed.
220
261
 
262
+ ---
263
+
221
264
  ${getCustomContextStep(npxCommand, 'start')}## Arguments
222
265
 
223
266
  ${args}
@@ -436,6 +479,26 @@ ${FEATURE_NAME_RULES}
436
479
 
437
480
  ### Step 2 — Create branch and working directory
438
481
 
482
+ Choose the workflow based on \`.osddtrc\`:
483
+
484
+ #### If \`worktree-repository\` is **present** — Worktree workflow
485
+
486
+ 3. Run the following command to create the git worktree, scaffold the working directory, and register the feature in the state file:
487
+
488
+ \`\`\`
489
+ ${npxCommand} start-worktree <feature-name>
490
+ \`\`\`
491
+
492
+ For monorepos, pass the package path:
493
+
494
+ \`\`\`
495
+ ${npxCommand} start-worktree <feature-name> --dir <package-path>
496
+ \`\`\`
497
+
498
+ 4. Parse the command output to extract \`worktreePath\` and \`workingDir\`. Navigate into \`<worktreePath>\` to locate the project root.
499
+
500
+ #### If \`worktree-repository\` is **absent** — Standard workflow
501
+
439
502
  3. Check whether the branch already exists locally or remotely:
440
503
  - If it **does not exist**, create and switch to it:
441
504
  \`\`\`
@@ -502,16 +565,53 @@ ${args}
502
565
  body: ({ npxCommand }) => `## Instructions
503
566
 
504
567
  1. Confirm all tasks in \`osddt.tasks.md\` are checked off (\`- [x]\`)
505
- 2. Run the following command to move the feature folder from \`working-on\` to \`done\`:
568
+ 2. Run the following command to check whether this feature uses a git worktree:
506
569
 
507
570
  \`\`\`
508
- ${npxCommand} done <feature-name> --dir <project-path>
571
+ ${npxCommand} worktree-info <feature-name>
509
572
  \`\`\`
510
573
 
511
- The command will automatically prefix the destination folder name with today's date in \`YYYY-MM-DD\` format.
574
+ 3. Based on the result:
575
+
576
+ **If it exits with code 1 (standard feature):** use the project path from \`.osddtrc\`, then run:
577
+ \`\`\`
578
+ ${npxCommand} done <feature-name> --dir <project-path>
579
+ \`\`\`
580
+ Skip to step 8.
581
+
582
+ **If it exits with code 0 (worktree feature):** parse the JSON to get \`worktreePath\` and \`branch\`, derive \`<project-path>\` from \`workingDir\`, then continue below.
583
+
584
+ 4. Check for uncommitted changes inside the worktree:
585
+
586
+ \`\`\`
587
+ git -C <worktreePath> status --porcelain
588
+ \`\`\`
589
+
590
+ 5. If there are **uncommitted changes**:
591
+ 1. Run \`git -C <worktreePath> diff\` to inspect them.
592
+ 2. Derive a concise commit message in **conventional commit** format (e.g. \`feat: add payment gateway integration\`) based on the diff.
593
+ 3. Present the proposed message to the user: _"Use this commit message, or provide your own?"_
594
+ 4. Once confirmed, commit:
595
+ \`\`\`
596
+ git -C <worktreePath> add -A
597
+ git -C <worktreePath> commit -m "<confirmed-message>"
598
+ \`\`\`
599
+
600
+ 6. Push the branch to remote (covers both first push and subsequent pushes):
601
+
602
+ \`\`\`
603
+ git -C <worktreePath> push --set-upstream origin <branch>
604
+ \`\`\`
605
+
606
+ 7. Run the done command with the \`--worktree\` flag:
607
+ \`\`\`
608
+ ${npxCommand} done <feature-name> --dir <project-path> --worktree
609
+ \`\`\`
610
+
611
+ 8. The command will automatically prefix the destination folder name with today's date in \`YYYY-MM-DD\` format.
512
612
  For example, \`working-on/feature-a\` will be moved to \`done/YYYY-MM-DD-feature-a\`.
513
613
 
514
- 3. Report the result of the command, including the full destination path
614
+ 9. Report the result of the command, including the full destination path
515
615
 
516
616
  ${getCustomContextStep(npxCommand, 'done')}`,
517
617
  },
@@ -527,7 +627,7 @@ ${body}`;
527
627
  }
528
628
  function getClaudeTemplates(cwd, npxCommand) {
529
629
  const dir = path.join(cwd, CLAUDE_COMMANDS_DIR);
530
- return COMMAND_DEFINITIONS.map((cmd) => ({
630
+ return COMMAND_DEFINITIONS.map(cmd => ({
531
631
  filePath: path.join(dir, `${cmd.name}.md`),
532
632
  content: formatClaudeCommand(cmd.description, cmd.body({ args: '$ARGUMENTS', npxCommand })),
533
633
  }));
@@ -543,7 +643,7 @@ ${body}"""
543
643
  }
544
644
  function getGeminiTemplates(cwd, npxCommand) {
545
645
  const dir = path.join(cwd, GEMINI_COMMANDS_DIR);
546
- return COMMAND_DEFINITIONS.map((cmd) => ({
646
+ return COMMAND_DEFINITIONS.map(cmd => ({
547
647
  filePath: path.join(dir, `${cmd.name}.toml`),
548
648
  content: formatGeminiCommand(cmd.description, cmd.body({ args: '{{args}}', npxCommand })),
549
649
  }));
@@ -566,6 +666,12 @@ async function askRepoType() {
566
666
  ],
567
667
  });
568
668
  }
669
+ async function askWorktreeUrl() {
670
+ return input({
671
+ message: 'Remote repository URL for worktree workflow (leave blank to skip):',
672
+ default: '',
673
+ });
674
+ }
569
675
  async function askAgents() {
570
676
  return checkbox({
571
677
  message: 'Which AI assistant tools do you want to set up?',
@@ -590,6 +696,102 @@ async function askAgents() {
590
696
  });
591
697
  }
592
698
 
699
+ function checkGitVersion() {
700
+ try {
701
+ const output = execSync('git --version', { encoding: 'utf-8' }).trim();
702
+ const match = output.match(/git version (\d+)\.(\d+)/);
703
+ if (!match) {
704
+ return { label: 'Git version >= 2.5', passed: false, detail: `Could not parse git version: ${output}` };
705
+ }
706
+ const major = parseInt(match[1], 10);
707
+ const minor = parseInt(match[2], 10);
708
+ const ok = major > 2 || (major === 2 && minor >= 5);
709
+ return {
710
+ label: 'Git version >= 2.5',
711
+ passed: ok,
712
+ detail: ok ? output : `Found ${output} — git worktree requires >= 2.5`,
713
+ };
714
+ }
715
+ catch {
716
+ return { label: 'Git version >= 2.5', passed: false, detail: 'git not found or not executable' };
717
+ }
718
+ }
719
+ function checkNotAWorktree(cwd) {
720
+ try {
721
+ const gitCommonDir = execSync('git rev-parse --git-common-dir', { cwd, encoding: 'utf-8' }).trim();
722
+ const gitDir = execSync('git rev-parse --git-dir', { cwd, encoding: 'utf-8' }).trim();
723
+ const isWorktree = gitDir !== gitCommonDir && gitDir !== '.git';
724
+ return {
725
+ label: 'Current directory is not a worktree',
726
+ passed: !isWorktree,
727
+ detail: isWorktree
728
+ ? `This directory is itself a worktree (git-dir: ${gitDir}). Run setup from the main repository.`
729
+ : 'OK',
730
+ };
731
+ }
732
+ catch {
733
+ return { label: 'Current directory is not a worktree', passed: false, detail: 'Not inside a git repository' };
734
+ }
735
+ }
736
+ async function checkTargetWritable(cwd) {
737
+ let targetBase;
738
+ try {
739
+ const repoRoot = execSync('git rev-parse --show-toplevel', { cwd, encoding: 'utf-8' }).trim();
740
+ const rcPath = path.join(repoRoot, '.osddtrc');
741
+ if (await fs.pathExists(rcPath)) {
742
+ const rc = await fs.readJson(rcPath);
743
+ targetBase = rc.worktreeBase ?? path.dirname(repoRoot);
744
+ }
745
+ else {
746
+ targetBase = path.dirname(repoRoot);
747
+ }
748
+ }
749
+ catch {
750
+ return { label: 'Worktree target directory is writable', passed: false, detail: 'Could not resolve repo root' };
751
+ }
752
+ try {
753
+ await fs.access(targetBase, fs.constants.W_OK);
754
+ return { label: 'Worktree target directory is writable', passed: true, detail: `${targetBase} is writable` };
755
+ }
756
+ catch {
757
+ return { label: 'Worktree target directory is writable', passed: false, detail: `${targetBase} is not writable` };
758
+ }
759
+ }
760
+ async function initStateFile(cwd) {
761
+ try {
762
+ const repoRoot = execSync('git rev-parse --show-toplevel', { cwd, encoding: 'utf-8' }).trim();
763
+ const stateFile = path.join(path.dirname(repoRoot), '.osddt-worktrees');
764
+ if (!(await fs.pathExists(stateFile))) {
765
+ await fs.writeJson(stateFile, [], { spaces: 2 });
766
+ console.log(` ✓ Initialized worktree state file: ${stateFile}`);
767
+ }
768
+ else {
769
+ console.log(` ✓ Worktree state file already exists: ${stateFile}`);
770
+ }
771
+ }
772
+ catch {
773
+ console.log(' ✗ Could not initialize worktree state file');
774
+ }
775
+ }
776
+ function printCheckResult(result) {
777
+ const icon = result.passed ? '✓' : '✗';
778
+ console.log(` ${icon} ${result.label}`);
779
+ if (!result.passed) {
780
+ console.log(` → ${result.detail}`);
781
+ }
782
+ }
783
+ async function runWorktreeChecks(cwd) {
784
+ const results = [
785
+ checkGitVersion(),
786
+ checkNotAWorktree(cwd),
787
+ await checkTargetWritable(cwd),
788
+ ];
789
+ for (const result of results) {
790
+ printCheckResult(result);
791
+ }
792
+ return results.every(r => r.passed);
793
+ }
794
+
593
795
  const CANONICAL_PACKAGE_NAME = '@dezkareid/osddt';
594
796
  const NPX_COMMAND = 'npx osddt';
595
797
  const NPX_COMMAND_FALLBACK = `npx ${CANONICAL_PACKAGE_NAME}`;
@@ -608,12 +810,12 @@ async function resolveNpxCommand(cwd) {
608
810
  const VALID_AGENTS = ['claude', 'gemini'];
609
811
  const VALID_REPO_TYPES = ['single', 'monorepo'];
610
812
  function parseAgents(raw) {
611
- const values = raw.split(',').map((s) => s.trim());
813
+ const values = raw.split(',').map(s => s.trim());
612
814
  if (values.length === 0) {
613
815
  console.error('Error: --agents requires at least one value.');
614
816
  process.exit(1);
615
817
  }
616
- const invalid = values.filter((v) => !VALID_AGENTS.includes(v));
818
+ const invalid = values.filter(v => !VALID_AGENTS.includes(v));
617
819
  if (invalid.length > 0) {
618
820
  console.error(`Error: Invalid agent(s): ${invalid.join(', ')}. Valid values: ${VALID_AGENTS.join(', ')}.`);
619
821
  process.exit(1);
@@ -637,15 +839,7 @@ async function writeConfig(cwd, config) {
637
839
  await fs.writeJson(configPath, config, { spaces: 2 });
638
840
  console.log(`\nSaved config: ${configPath}`);
639
841
  }
640
- async function runSetup(cwd, rawAgents, rawRepoType) {
641
- const agents = rawAgents !== undefined ? parseAgents(rawAgents) : await askAgents();
642
- if (rawAgents === undefined)
643
- console.log('');
644
- const repoType = rawRepoType !== undefined ? parseRepoType(rawRepoType) : await askRepoType();
645
- if (rawRepoType === undefined)
646
- console.log('');
647
- const npxCommand = await resolveNpxCommand(cwd);
648
- console.log('Setting up OSDDT command files...\n');
842
+ async function writeAgentFiles(cwd, agents, npxCommand) {
649
843
  if (agents.includes('claude')) {
650
844
  const claudeFiles = getClaudeTemplates(cwd, npxCommand);
651
845
  console.log('Claude Code commands (.claude/commands/):');
@@ -662,7 +856,38 @@ async function runSetup(cwd, rawAgents, rawRepoType) {
662
856
  }
663
857
  console.log('');
664
858
  }
665
- await writeConfig(cwd, { repoType, agents });
859
+ }
860
+ async function setupWorktreeEnvironment(cwd, _worktreeRepository) {
861
+ console.log('Checking environment for git worktree support...\n');
862
+ const allPassed = await runWorktreeChecks(cwd);
863
+ console.log('');
864
+ if (!allPassed) {
865
+ console.log('Some checks failed. Resolve the issues above before using the worktree workflow.');
866
+ process.exit(1);
867
+ }
868
+ await initStateFile(cwd);
869
+ console.log('');
870
+ }
871
+ async function runSetup(cwd, rawAgents, rawRepoType, rawWorktreeRepository) {
872
+ const agents = rawAgents !== undefined ? parseAgents(rawAgents) : await askAgents();
873
+ if (rawAgents === undefined)
874
+ console.log('');
875
+ const repoType = rawRepoType !== undefined ? parseRepoType(rawRepoType) : await askRepoType();
876
+ if (rawRepoType === undefined)
877
+ console.log('');
878
+ const worktreeRepository = rawWorktreeRepository !== undefined ? rawWorktreeRepository : (await askWorktreeUrl()) || undefined;
879
+ if (rawWorktreeRepository === undefined)
880
+ console.log('');
881
+ if (worktreeRepository) {
882
+ await setupWorktreeEnvironment(cwd);
883
+ }
884
+ const npxCommand = await resolveNpxCommand(cwd);
885
+ console.log('Setting up OSDDT command files...\n');
886
+ await writeAgentFiles(cwd, agents, npxCommand);
887
+ const config = { repoType, agents };
888
+ if (worktreeRepository)
889
+ config['worktree-repository'] = worktreeRepository;
890
+ await writeConfig(cwd, config);
666
891
  console.log('\nSetup complete!');
667
892
  console.log('Commands created: osddt.spec, osddt.plan, osddt.tasks, osddt.implement');
668
893
  }
@@ -673,9 +898,10 @@ function setupCommand() {
673
898
  .option('-d, --dir <directory>', 'target directory', process.cwd())
674
899
  .option('--agents <list>', 'comma-separated agents to set up (claude, gemini)')
675
900
  .option('--repo-type <type>', 'repository type (single, monorepo)')
901
+ .option('--worktree-repository <url>', 'remote repository URL to enable worktree workflow')
676
902
  .action(async (options) => {
677
903
  const targetDir = path.resolve(options.dir);
678
- await runSetup(targetDir, options.agents, options.repoType);
904
+ await runSetup(targetDir, options.agents, options.repoType, options.worktreeRepository);
679
905
  });
680
906
  return cmd;
681
907
  }
@@ -712,7 +938,13 @@ function todayPrefix() {
712
938
  const dd = String(now.getDate()).padStart(2, '0');
713
939
  return `${yyyy}-${mm}-${dd}`;
714
940
  }
715
- async function runDone(featureName, cwd) {
941
+ function resolveRepoRoot$2(cwd) {
942
+ return execSync('git rev-parse --show-toplevel', { cwd, encoding: 'utf-8' }).trim();
943
+ }
944
+ function stateFilePath$2(repoRoot) {
945
+ return path.join(path.dirname(repoRoot), '.osddt-worktrees');
946
+ }
947
+ async function runDone(featureName, cwd, worktree) {
716
948
  const src = path.join(cwd, 'working-on', featureName);
717
949
  const destName = `${todayPrefix()}-${featureName}`;
718
950
  const dest = path.join(cwd, 'done', destName);
@@ -723,6 +955,30 @@ async function runDone(featureName, cwd) {
723
955
  await fs.ensureDir(path.dirname(dest));
724
956
  await fs.move(src, dest);
725
957
  console.log(`Moved: working-on/${featureName} → done/${destName}`);
958
+ if (!worktree)
959
+ return;
960
+ const repoRoot = resolveRepoRoot$2(process.cwd());
961
+ const stateFile = stateFilePath$2(repoRoot);
962
+ if (!(await fs.pathExists(stateFile))) {
963
+ console.error(`Warning: .osddt-worktrees not found at ${stateFile}. Skipping worktree cleanup.`);
964
+ return;
965
+ }
966
+ const entries = await fs.readJson(stateFile);
967
+ const entry = entries.find(e => e.featureName === featureName);
968
+ if (!entry) {
969
+ console.error(`Warning: No worktree entry found for "${featureName}". Skipping worktree cleanup.`);
970
+ return;
971
+ }
972
+ if (await fs.pathExists(entry.worktreePath)) {
973
+ execSync(`git worktree remove "${entry.worktreePath}" --force`, { cwd: repoRoot, stdio: 'inherit' });
974
+ console.log(`Removed worktree: ${entry.worktreePath}`);
975
+ }
976
+ else {
977
+ console.log(`Worktree path not found on filesystem, skipping git worktree remove.`);
978
+ }
979
+ const updated = entries.filter(e => e.featureName !== featureName);
980
+ await fs.writeJson(stateFile, updated, { spaces: 2 });
981
+ console.log(`Removed state entry for "${featureName}" from .osddt-worktrees`);
726
982
  }
727
983
  function doneCommand() {
728
984
  const cmd = new Command('done');
@@ -730,9 +986,10 @@ function doneCommand() {
730
986
  .description('Move a feature from working-on/<feature-name> to done/<feature-name>')
731
987
  .argument('<feature-name>', 'name of the feature to mark as done')
732
988
  .option('-d, --dir <directory>', 'project directory', process.cwd())
989
+ .option('--worktree', 'also remove the git worktree and clean up the state file')
733
990
  .action(async (featureName, options) => {
734
991
  const targetDir = path.resolve(options.dir);
735
- await runDone(featureName, targetDir);
992
+ await runDone(featureName, targetDir, options.worktree ?? false);
736
993
  });
737
994
  return cmd;
738
995
  }
@@ -745,7 +1002,7 @@ async function hasOsddtCommandFile(dir, pattern) {
745
1002
  if (!(await fs.pathExists(dir)))
746
1003
  return false;
747
1004
  const entries = await fs.readdir(dir);
748
- return entries.some((f) => pattern.test(f));
1005
+ return entries.some(f => pattern.test(f));
749
1006
  }
750
1007
  async function inferAgents(cwd) {
751
1008
  const detected = [];
@@ -868,6 +1125,169 @@ function contextCommand() {
868
1125
  return cmd;
869
1126
  }
870
1127
 
1128
+ function resolveRepoRoot$1(cwd) {
1129
+ return execSync('git rev-parse --show-toplevel', { cwd, encoding: 'utf-8' }).trim();
1130
+ }
1131
+ function repoName(repoRoot) {
1132
+ return path.basename(repoRoot);
1133
+ }
1134
+ function stateFilePath$1(repoRoot) {
1135
+ return path.join(path.dirname(repoRoot), '.osddt-worktrees');
1136
+ }
1137
+ async function readStateFile(stateFile) {
1138
+ if (!(await fs.pathExists(stateFile)))
1139
+ return [];
1140
+ return fs.readJson(stateFile);
1141
+ }
1142
+ async function writeStateFile(stateFile, entries) {
1143
+ await fs.writeJson(stateFile, entries, { spaces: 2 });
1144
+ }
1145
+ function branchExists(branch, cwd) {
1146
+ try {
1147
+ execSync(`git rev-parse --verify ${branch}`, { cwd, stdio: 'ignore' });
1148
+ return true;
1149
+ }
1150
+ catch {
1151
+ return false;
1152
+ }
1153
+ }
1154
+ function remoteBranchExists(branch, cwd) {
1155
+ try {
1156
+ execSync(`git ls-remote --exit-code --heads origin ${branch}`, { cwd, stdio: 'ignore' });
1157
+ return true;
1158
+ }
1159
+ catch {
1160
+ return false;
1161
+ }
1162
+ }
1163
+ async function prompt(question) {
1164
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1165
+ return new Promise((resolve) => {
1166
+ rl.question(question, (answer) => {
1167
+ rl.close();
1168
+ resolve(answer.trim());
1169
+ });
1170
+ });
1171
+ }
1172
+ async function createWorktree(branch, worktreePath, repoRoot) {
1173
+ if (await fs.pathExists(worktreePath)) {
1174
+ console.log(`\nDirectory already exists at: ${worktreePath}`);
1175
+ const answer = await prompt('Resume or Abort? [R/a] ');
1176
+ if (answer.toLowerCase() === 'a') {
1177
+ console.log('Aborted.');
1178
+ process.exit(0);
1179
+ }
1180
+ return;
1181
+ }
1182
+ const localExists = branchExists(branch, repoRoot);
1183
+ const remoteExists = !localExists && remoteBranchExists(branch, repoRoot);
1184
+ if (localExists || remoteExists) {
1185
+ console.log(`\nBranch "${branch}" already exists ${localExists ? 'locally' : 'on remote'}.`);
1186
+ const answer = await prompt('Resume or Abort? [R/a] ');
1187
+ if (answer.toLowerCase() === 'a') {
1188
+ console.log('Aborted.');
1189
+ process.exit(0);
1190
+ }
1191
+ execSync(`git worktree add "${worktreePath}" ${branch}`, { cwd: repoRoot, stdio: 'inherit' });
1192
+ }
1193
+ else {
1194
+ execSync(`git worktree add "${worktreePath}" -b ${branch}`, { cwd: repoRoot, stdio: 'inherit' });
1195
+ }
1196
+ }
1197
+ async function runStartWorktree(featureName, options) {
1198
+ const cwd = process.cwd();
1199
+ const repoRoot = resolveRepoRoot$1(cwd);
1200
+ const branch = `feat/${featureName}`;
1201
+ // Read .osddtrc
1202
+ const rcPath = path.join(repoRoot, '.osddtrc');
1203
+ let rc = { repoType: 'single' };
1204
+ if (await fs.pathExists(rcPath)) {
1205
+ rc = await fs.readJson(rcPath);
1206
+ }
1207
+ // Resolve worktree path
1208
+ const base = rc.worktreeBase ?? path.dirname(repoRoot);
1209
+ const worktreePath = path.join(base, `${repoName(repoRoot)}-${featureName}`);
1210
+ // Check state file for existing entry
1211
+ const stateFile = stateFilePath$1(repoRoot);
1212
+ const entries = await readStateFile(stateFile);
1213
+ const existing = entries.find(e => e.featureName === featureName);
1214
+ if (existing) {
1215
+ console.log(`\nWorktree for "${featureName}" already exists at: ${existing.worktreePath}`);
1216
+ const answer = await prompt('Resume or Abort? [R/a] ');
1217
+ if (answer.toLowerCase() === 'a') {
1218
+ console.log('Aborted.');
1219
+ process.exit(0);
1220
+ }
1221
+ console.log(`\nResuming existing worktree.`);
1222
+ console.log(` Branch: ${existing.branch}`);
1223
+ console.log(` Worktree path: ${existing.worktreePath}`);
1224
+ console.log(` Working dir: ${existing.workingDir}`);
1225
+ return;
1226
+ }
1227
+ await createWorktree(branch, worktreePath, repoRoot);
1228
+ // Resolve working dir
1229
+ let projectPath;
1230
+ if (rc.repoType === 'monorepo') {
1231
+ const pkg = options.dir ?? await prompt('Package path (e.g. packages/my-package): ');
1232
+ projectPath = path.join(worktreePath, pkg);
1233
+ }
1234
+ else {
1235
+ projectPath = worktreePath;
1236
+ }
1237
+ const workingDir = path.join(projectPath, 'working-on', featureName);
1238
+ await fs.ensureDir(workingDir);
1239
+ // Write state file entry
1240
+ entries.push({ featureName, branch, worktreePath, workingDir, repoRoot });
1241
+ await writeStateFile(stateFile, entries);
1242
+ console.log(`\nWorktree feature started:`);
1243
+ console.log(` Branch: ${branch}`);
1244
+ console.log(` Worktree path: ${worktreePath}`);
1245
+ console.log(` Working dir: ${workingDir}`);
1246
+ }
1247
+ function startWorktreeCommand() {
1248
+ const cmd = new Command('start-worktree');
1249
+ cmd
1250
+ .description('Start a new feature using a git worktree')
1251
+ .argument('<feature-name>', 'feature name (kebab-case, max 30 chars)')
1252
+ .option('-d, --dir <package>', 'package path within monorepo (skips prompt)')
1253
+ .action(async (featureName, options) => {
1254
+ await runStartWorktree(featureName, options);
1255
+ });
1256
+ return cmd;
1257
+ }
1258
+
1259
+ function resolveRepoRoot(cwd) {
1260
+ return execSync('git rev-parse --show-toplevel', { cwd, encoding: 'utf-8' }).trim();
1261
+ }
1262
+ function stateFilePath(repoRoot) {
1263
+ return path.join(path.dirname(repoRoot), '.osddt-worktrees');
1264
+ }
1265
+ async function runWorktreeInfo(featureName) {
1266
+ const repoRoot = resolveRepoRoot(process.cwd());
1267
+ const stateFile = stateFilePath(repoRoot);
1268
+ if (!(await fs.pathExists(stateFile))) {
1269
+ console.error(`No .osddt-worktrees file found at: ${stateFile}`);
1270
+ process.exit(1);
1271
+ }
1272
+ const entries = await fs.readJson(stateFile);
1273
+ const entry = entries.find(e => e.featureName === featureName);
1274
+ if (!entry) {
1275
+ console.error(`No worktree entry found for feature: ${featureName}`);
1276
+ process.exit(1);
1277
+ }
1278
+ console.log(JSON.stringify({ worktreePath: entry.worktreePath, workingDir: entry.workingDir, branch: entry.branch }));
1279
+ }
1280
+ function worktreeInfoCommand() {
1281
+ const cmd = new Command('worktree-info');
1282
+ cmd
1283
+ .description('Look up worktree paths for a feature from the state file')
1284
+ .argument('<feature-name>', 'feature name to look up')
1285
+ .action(async (featureName) => {
1286
+ await runWorktreeInfo(featureName);
1287
+ });
1288
+ return cmd;
1289
+ }
1290
+
871
1291
  const __filename$1 = fileURLToPath(import.meta.url);
872
1292
  const __dirname$1 = path.dirname(__filename$1);
873
1293
  const pkgPath = path.join(__dirname$1, '..', 'package.json');
@@ -882,4 +1302,6 @@ program.addCommand(metaInfoCommand());
882
1302
  program.addCommand(doneCommand());
883
1303
  program.addCommand(updateCommand());
884
1304
  program.addCommand(contextCommand());
1305
+ program.addCommand(startWorktreeCommand());
1306
+ program.addCommand(worktreeInfoCommand());
885
1307
  program.parse(process.argv);
@@ -1,4 +1,5 @@
1
1
  export type RepoType = 'monorepo' | 'single';
2
2
  export type AgentType = 'claude' | 'gemini';
3
3
  export declare function askRepoType(): Promise<RepoType>;
4
+ export declare function askWorktreeUrl(): Promise<string>;
4
5
  export declare function askAgents(): Promise<AgentType[]>;
@@ -0,0 +1,11 @@
1
+ export interface CheckResult {
2
+ label: string;
3
+ passed: boolean;
4
+ detail: string;
5
+ }
6
+ export declare function checkGitVersion(): CheckResult;
7
+ export declare function checkNotAWorktree(cwd: string): CheckResult;
8
+ export declare function checkTargetWritable(cwd: string): Promise<CheckResult>;
9
+ export declare function initStateFile(cwd: string): Promise<void>;
10
+ export declare function printCheckResult(result: CheckResult): void;
11
+ export declare function runWorktreeChecks(cwd: string): Promise<boolean>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dezkareid/osddt",
3
- "version": "1.10.1",
3
+ "version": "1.11.1",
4
4
  "description": "Package for Spec-Driven Development workflow",
5
5
  "keywords": [
6
6
  "spec-driven",
@@ -34,6 +34,7 @@
34
34
  "main": "./dist/index.js",
35
35
  "dependencies": {
36
36
  "@inquirer/checkbox": "5.0.7",
37
+ "@inquirer/input": "5.0.7",
37
38
  "@inquirer/select": "5.0.7",
38
39
  "commander": "12.0.0",
39
40
  "fs-extra": "11.2.0",
@@ -42,13 +43,12 @@
42
43
  "devDependencies": {
43
44
  "@commitlint/cli": "20.5.0",
44
45
  "@commitlint/config-conventional": "20.5.0",
45
- "@dezkareid/eslint-config-ts-base": "1.0.0",
46
+ "@dezkareid/eslint-plugin-web": "1.0.0",
46
47
  "@rollup/plugin-typescript": "12.1.4",
47
48
  "@types/fs-extra": "11.0.4",
48
49
  "@types/node": "25.0.10",
49
50
  "eslint": "9.39.2",
50
51
  "husky": "9.1.7",
51
- "prettier": "3.8.1",
52
52
  "rollup": "4.56.0",
53
53
  "typescript": "5.9.3",
54
54
  "vitest": "4.0.18"
@@ -59,7 +59,7 @@
59
59
  "test": "vitest run",
60
60
  "test:watch": "vitest",
61
61
  "lint": "eslint src",
62
- "format": "prettier --write src",
62
+ "lint:fix": "eslint src --fix",
63
63
  "setup-local": "pnpm run build && npx osddt setup --agents claude --repo-type single",
64
64
  "update-local": "pnpm run build && npx osddt update"
65
65
  }
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};