@dezkareid/osddt 1.11.5 → 1.11.7

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.
@@ -1,9 +1,4 @@
1
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
- }
2
+ import { type WorktreeEntry } from '../utils/worktree.js';
3
+ export type { WorktreeEntry };
9
4
  export declare function startWorktreeCommand(): Command;
package/dist/index.js CHANGED
@@ -126,7 +126,27 @@ const COMMAND_DEFINITIONS = [
126
126
  {
127
127
  name: 'osddt.continue',
128
128
  description: 'Detect the current workflow phase and prompt the next command to run',
129
- body: ({ args, npxCommand }) => `${getRepoPreamble(npxCommand)}${RESOLVE_FEATURE_NAME}
129
+ body: ({ args, npxCommand }) => `${getRepoPreamble(npxCommand)}### Resolving the Feature Name and Working Directory
130
+
131
+ Use the following logic to determine the working directory:
132
+
133
+ **If \`worktree-repository\` is present in \`.osddtrc\` (worktree mode):**
134
+
135
+ 1. If arguments were provided, derive the feature name from them:
136
+ - If the argument looks like a branch name (no spaces, kebab-case or slash-separated), use the last segment (after the last \`/\`, or the full value if no \`/\` is present).
137
+ - Otherwise convert it to a feature name following the Feature Name Constraints.
138
+ 2. Run \`${npxCommand} worktree-info\` (pass \`<feature-name>\` as argument if one was derived, otherwise run without arguments):
139
+ - exit code **0**: parse the JSON and use the returned \`workingDir\` as the working directory.
140
+ - exit code **1**: no matching worktree found — fall back to the standard resolution below.
141
+
142
+ **If \`worktree-repository\` is absent in \`.osddtrc\` (standard mode), or after a worktree-info fallback:**
143
+
144
+ 1. If arguments were provided, derive the feature name (same rules as above).
145
+ 2. If **no arguments were provided**:
146
+ - List all folders under \`<project-path>/working-on/\`.
147
+ - If there is **only one folder**, use it automatically and inform the user.
148
+ - If there are **multiple folders**, present the list to the user and ask them to pick one.
149
+ - If there are **no folders**, inform the user that no in-progress features were found and stop.
130
150
 
131
151
  ## Instructions
132
152
 
@@ -767,6 +787,25 @@ async function runWorktreeChecks(barePath) {
767
787
  }
768
788
  return results.every(r => r.passed);
769
789
  }
790
+ function listFeatureWorktrees(barePath, mainBranch) {
791
+ const output = execSync('git worktree list --porcelain', { cwd: barePath, encoding: 'utf-8' });
792
+ const blocks = output.trim().split(/\n\n+/);
793
+ const entries = [];
794
+ for (const block of blocks) {
795
+ const pathMatch = block.match(/^worktree (.+)$/m);
796
+ const branchMatch = block.match(/^branch (.+)$/m);
797
+ if (!pathMatch)
798
+ continue;
799
+ const worktreePath = pathMatch[1].trim();
800
+ const featureName = path.basename(worktreePath);
801
+ if (featureName === mainBranch)
802
+ continue;
803
+ const branch = branchMatch ? branchMatch[1].trim().replace(/^refs\/heads\//, '') : featureName;
804
+ const workingDir = path.join(worktreePath, 'working-on', featureName);
805
+ entries.push({ featureName, branch, worktreePath, workingDir });
806
+ }
807
+ return entries;
808
+ }
770
809
 
771
810
  const CANONICAL_PACKAGE_NAME = '@dezkareid/osddt';
772
811
  const NPX_COMMAND = 'npx osddt';
@@ -894,7 +933,7 @@ async function setupWorktreeEnvironment(cwd, worktreeRepository, agents) {
894
933
  }
895
934
  await writeAgentPointerFiles(cwd, agents, branch);
896
935
  console.log('');
897
- return { barePath, packageManager };
936
+ return { barePath, packageManager, mainBranch: branch };
898
937
  }
899
938
  async function runSetup(cwd, rawAgents, rawRepoType, rawWorktreeRepository) {
900
939
  const agents = rawAgents !== undefined ? parseAgents(rawAgents) : await askAgents();
@@ -906,21 +945,15 @@ async function runSetup(cwd, rawAgents, rawRepoType, rawWorktreeRepository) {
906
945
  const worktreeRepository = rawWorktreeRepository !== undefined ? rawWorktreeRepository : (await askWorktreeUrl()) || undefined;
907
946
  if (rawWorktreeRepository === undefined)
908
947
  console.log('');
909
- let barePath;
910
- let packageManager;
948
+ let worktreeConfig = {};
911
949
  if (worktreeRepository) {
912
- ({ barePath, packageManager } = await setupWorktreeEnvironment(cwd, worktreeRepository, agents));
950
+ const { barePath, packageManager, mainBranch } = await setupWorktreeEnvironment(cwd, worktreeRepository, agents);
951
+ worktreeConfig = { 'worktree-repository': worktreeRepository, 'bare-path': barePath, packageManager, mainBranch };
913
952
  }
914
953
  const npxCommand = await resolveNpxCommand(cwd);
915
954
  console.log('Setting up OSDDT command files...\n');
916
955
  await writeAgentFiles(cwd, agents, npxCommand);
917
- const config = { repoType, agents };
918
- if (worktreeRepository)
919
- config['worktree-repository'] = worktreeRepository;
920
- if (barePath)
921
- config['bare-path'] = barePath;
922
- if (packageManager)
923
- config['packageManager'] = packageManager;
956
+ const config = { repoType, agents, ...worktreeConfig };
924
957
  await writeConfig(cwd, config);
925
958
  console.log('\nSetup complete!');
926
959
  console.log('Commands created: osddt.spec, osddt.plan, osddt.tasks, osddt.implement');
@@ -973,9 +1006,12 @@ function todayPrefix() {
973
1006
  return `${yyyy}-${mm}-${dd}`;
974
1007
  }
975
1008
  async function runDone(featureName, cwd, worktree) {
976
- const src = path.join(cwd, 'working-on', featureName);
1009
+ const barePath = worktree ? await resolveBarePath(process.cwd()) : undefined;
1010
+ const worktreePath = worktree ? findWorktreeByFeature(barePath, featureName) : undefined;
1011
+ const projectDir = worktreePath ?? cwd;
1012
+ const src = path.join(projectDir, 'working-on', featureName);
977
1013
  const destName = `${todayPrefix()}-${featureName}`;
978
- const dest = path.join(cwd, 'done', destName);
1014
+ const dest = path.join(projectDir, 'done', destName);
979
1015
  if (!(await fs.pathExists(src))) {
980
1016
  console.error(`Error: working-on/${featureName} does not exist.`);
981
1017
  process.exit(1);
@@ -985,8 +1021,6 @@ async function runDone(featureName, cwd, worktree) {
985
1021
  console.log(`Moved: working-on/${featureName} → done/${destName}`);
986
1022
  if (!worktree)
987
1023
  return;
988
- const barePath = await resolveBarePath(process.cwd());
989
- const worktreePath = findWorktreeByFeature(barePath, featureName);
990
1024
  if (!worktreePath) {
991
1025
  console.error(`Warning: No worktree found for "${featureName}". Skipping worktree cleanup.`);
992
1026
  return;
@@ -1162,7 +1196,7 @@ function remoteBranchExists(branch, cwd) {
1162
1196
  return false;
1163
1197
  }
1164
1198
  }
1165
- async function prompt(question) {
1199
+ async function prompt$1(question) {
1166
1200
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1167
1201
  return new Promise((resolve) => {
1168
1202
  rl.question(question, (answer) => {
@@ -1171,10 +1205,10 @@ async function prompt(question) {
1171
1205
  });
1172
1206
  });
1173
1207
  }
1174
- async function createWorktree(branch, worktreePath, repoRoot) {
1208
+ async function createWorktree(branch, worktreePath, repoRoot, mainBranch) {
1175
1209
  if (await fs.pathExists(worktreePath)) {
1176
1210
  console.log(`\nDirectory already exists at: ${worktreePath}`);
1177
- const answer = await prompt('Resume or Abort? [R/a] ');
1211
+ const answer = await prompt$1('Resume or Abort? [R/a] ');
1178
1212
  if (answer.toLowerCase() === 'a') {
1179
1213
  console.log('Aborted.');
1180
1214
  process.exit(0);
@@ -1185,7 +1219,7 @@ async function createWorktree(branch, worktreePath, repoRoot) {
1185
1219
  const remoteExists = !localExists && remoteBranchExists(branch, repoRoot);
1186
1220
  if (localExists || remoteExists) {
1187
1221
  console.log(`\nBranch "${branch}" already exists ${localExists ? 'locally' : 'on remote'}.`);
1188
- const answer = await prompt('Resume or Abort? [R/a] ');
1222
+ const answer = await prompt$1('Resume or Abort? [R/a] ');
1189
1223
  if (answer.toLowerCase() === 'a') {
1190
1224
  console.log('Aborted.');
1191
1225
  process.exit(0);
@@ -1193,7 +1227,7 @@ async function createWorktree(branch, worktreePath, repoRoot) {
1193
1227
  execSync(`git worktree add "${worktreePath}" ${branch}`, { cwd: repoRoot, stdio: 'inherit' });
1194
1228
  }
1195
1229
  else {
1196
- execSync(`git worktree add "${worktreePath}" -b ${branch}`, { cwd: repoRoot, stdio: 'inherit' });
1230
+ execSync(`git worktree add "${worktreePath}" -b ${branch} ${mainBranch}`, { cwd: repoRoot, stdio: 'inherit' });
1197
1231
  }
1198
1232
  }
1199
1233
  function runInstall(worktreePath, packageManager) {
@@ -1219,7 +1253,7 @@ async function runStartWorktree(featureName, options) {
1219
1253
  if (existingPath) {
1220
1254
  const workingDir = path.join(existingPath, 'working-on', featureName);
1221
1255
  console.log(`\nWorktree for "${featureName}" already exists at: ${existingPath}`);
1222
- const answer = await prompt('Resume or Abort? [R/a] ');
1256
+ const answer = await prompt$1('Resume or Abort? [R/a] ');
1223
1257
  if (answer.toLowerCase() === 'a') {
1224
1258
  console.log('Aborted.');
1225
1259
  process.exit(0);
@@ -1233,11 +1267,12 @@ async function runStartWorktree(featureName, options) {
1233
1267
  // Resolve worktree path — inside .bare as <barePath>/<featureName>
1234
1268
  const base = rc['worktreeBase'] ?? repoRoot;
1235
1269
  const worktreePath = path.join(base, featureName);
1236
- await createWorktree(branch, worktreePath, repoRoot);
1270
+ const mainBranch = rc['mainBranch'] ?? 'main';
1271
+ await createWorktree(branch, worktreePath, repoRoot, mainBranch);
1237
1272
  // Resolve working dir
1238
1273
  let projectPath;
1239
1274
  if (rc['repoType'] === 'monorepo') {
1240
- const pkg = options.dir ?? await prompt('Package path (e.g. packages/my-package): ');
1275
+ const pkg = options.dir ?? await prompt$1('Package path (e.g. packages/my-package): ');
1241
1276
  projectPath = path.join(worktreePath, pkg);
1242
1277
  }
1243
1278
  else {
@@ -1263,38 +1298,66 @@ function startWorktreeCommand() {
1263
1298
  return cmd;
1264
1299
  }
1265
1300
 
1266
- async function resolveRepoRoot(cwd) {
1301
+ async function prompt(question) {
1302
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1303
+ return new Promise((resolve) => {
1304
+ rl.question(question, (answer) => {
1305
+ rl.close();
1306
+ resolve(answer.trim());
1307
+ });
1308
+ });
1309
+ }
1310
+ async function selectWorktree(entries) {
1311
+ console.log('\nMultiple feature worktrees found:');
1312
+ entries.forEach((e, i) => {
1313
+ console.log(` ${i + 1}) ${e.featureName} (${e.branch})`);
1314
+ });
1315
+ const answer = await prompt(`Select a feature [1-${entries.length}]: `);
1316
+ const index = parseInt(answer, 10) - 1;
1317
+ if (index < 0 || index >= entries.length || isNaN(index)) {
1318
+ console.error('Invalid selection.');
1319
+ process.exit(1);
1320
+ }
1321
+ return entries[index];
1322
+ }
1323
+ async function runWorktreeInfo(featureName) {
1324
+ const cwd = process.cwd();
1325
+ const barePath = await resolveBarePath(cwd);
1267
1326
  const rcPath = path.join(cwd, '.osddtrc');
1327
+ let mainBranch = 'main';
1268
1328
  if (await fs.pathExists(rcPath)) {
1269
1329
  const rc = await fs.readJson(rcPath);
1270
- if (rc['bare-path'])
1271
- return rc['bare-path'];
1330
+ if (rc.mainBranch)
1331
+ mainBranch = rc.mainBranch;
1272
1332
  }
1273
- return execSync('git rev-parse --show-toplevel', { cwd, encoding: 'utf-8' }).trim();
1274
- }
1275
- function stateFilePath(repoRoot) {
1276
- return path.join(path.dirname(repoRoot), '.osddt-worktrees');
1277
- }
1278
- async function runWorktreeInfo(featureName) {
1279
- const repoRoot = await resolveRepoRoot(process.cwd());
1280
- const stateFile = stateFilePath(repoRoot);
1281
- if (!(await fs.pathExists(stateFile))) {
1282
- console.error(`No .osddt-worktrees file found at: ${stateFile}`);
1283
- process.exit(1);
1333
+ const entries = listFeatureWorktrees(barePath, mainBranch);
1334
+ let entry;
1335
+ if (featureName) {
1336
+ entry = entries.find(e => e.featureName === featureName);
1337
+ if (!entry) {
1338
+ console.error(`No worktree found for feature: ${featureName}`);
1339
+ process.exit(1);
1340
+ }
1284
1341
  }
1285
- const entries = await fs.readJson(stateFile);
1286
- const entry = entries.find(e => e.featureName === featureName);
1287
- if (!entry) {
1288
- console.error(`No worktree entry found for feature: ${featureName}`);
1289
- process.exit(1);
1342
+ else {
1343
+ if (entries.length === 0) {
1344
+ console.error('No feature worktrees found.');
1345
+ process.exit(1);
1346
+ }
1347
+ else if (entries.length === 1) {
1348
+ entry = entries[0];
1349
+ }
1350
+ else {
1351
+ entry = await selectWorktree(entries);
1352
+ }
1290
1353
  }
1291
1354
  console.log(JSON.stringify({ worktreePath: entry.worktreePath, workingDir: entry.workingDir, branch: entry.branch }));
1292
1355
  }
1293
1356
  function worktreeInfoCommand() {
1294
1357
  const cmd = new Command('worktree-info');
1295
1358
  cmd
1296
- .description('Look up worktree paths for a feature from the state file')
1297
- .argument('<feature-name>', 'feature name to look up')
1359
+ .description('Look up worktree paths for a feature from git worktree list')
1360
+ .argument('[feature-name]', 'feature name to look up (optional if only one worktree exists)')
1298
1361
  .action(async (featureName) => {
1299
1362
  await runWorktreeInfo(featureName);
1300
1363
  });
@@ -1,3 +1,9 @@
1
+ export interface WorktreeEntry {
2
+ featureName: string;
3
+ branch: string;
4
+ worktreePath: string;
5
+ workingDir: string;
6
+ }
1
7
  export interface CheckResult {
2
8
  label: string;
3
9
  passed: boolean;
@@ -9,3 +15,4 @@ export declare function resolveBarePath(cwd: string): Promise<string>;
9
15
  export declare function findWorktreeByFeature(barePath: string, featureName: string): string | undefined;
10
16
  export declare function printCheckResult(result: CheckResult): void;
11
17
  export declare function runWorktreeChecks(barePath: string): Promise<boolean>;
18
+ export declare function listFeatureWorktrees(barePath: string, mainBranch: string): WorktreeEntry[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dezkareid/osddt",
3
- "version": "1.11.5",
3
+ "version": "1.11.7",
4
4
  "description": "Package for Spec-Driven Development workflow",
5
5
  "keywords": [
6
6
  "spec-driven",