@dezkareid/osddt 1.11.4 → 1.11.6

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
@@ -744,9 +744,7 @@ function findWorktreeByFeature(barePath, featureName) {
744
744
  continue;
745
745
  const worktreePath = match[1].trim();
746
746
  const basename = path.basename(worktreePath);
747
- const segments = basename.split('-');
748
- const suffixMatch = segments.length > 1 && basename.slice(basename.indexOf('-') + 1) === featureName;
749
- if (basename === featureName || suffixMatch) {
747
+ if (basename === featureName) {
750
748
  return worktreePath;
751
749
  }
752
750
  }
@@ -769,6 +767,25 @@ async function runWorktreeChecks(barePath) {
769
767
  }
770
768
  return results.every(r => r.passed);
771
769
  }
770
+ function listFeatureWorktrees(barePath, mainBranch) {
771
+ const output = execSync('git worktree list --porcelain', { cwd: barePath, encoding: 'utf-8' });
772
+ const blocks = output.trim().split(/\n\n+/);
773
+ const entries = [];
774
+ for (const block of blocks) {
775
+ const pathMatch = block.match(/^worktree (.+)$/m);
776
+ const branchMatch = block.match(/^branch (.+)$/m);
777
+ if (!pathMatch)
778
+ continue;
779
+ const worktreePath = pathMatch[1].trim();
780
+ const featureName = path.basename(worktreePath);
781
+ if (featureName === mainBranch)
782
+ continue;
783
+ const branch = branchMatch ? branchMatch[1].trim().replace(/^refs\/heads\//, '') : featureName;
784
+ const workingDir = path.join(worktreePath, 'working-on', featureName);
785
+ entries.push({ featureName, branch, worktreePath, workingDir });
786
+ }
787
+ return entries;
788
+ }
772
789
 
773
790
  const CANONICAL_PACKAGE_NAME = '@dezkareid/osddt';
774
791
  const NPX_COMMAND = 'npx osddt';
@@ -896,7 +913,7 @@ async function setupWorktreeEnvironment(cwd, worktreeRepository, agents) {
896
913
  }
897
914
  await writeAgentPointerFiles(cwd, agents, branch);
898
915
  console.log('');
899
- return { barePath, packageManager };
916
+ return { barePath, packageManager, mainBranch: branch };
900
917
  }
901
918
  async function runSetup(cwd, rawAgents, rawRepoType, rawWorktreeRepository) {
902
919
  const agents = rawAgents !== undefined ? parseAgents(rawAgents) : await askAgents();
@@ -908,21 +925,15 @@ async function runSetup(cwd, rawAgents, rawRepoType, rawWorktreeRepository) {
908
925
  const worktreeRepository = rawWorktreeRepository !== undefined ? rawWorktreeRepository : (await askWorktreeUrl()) || undefined;
909
926
  if (rawWorktreeRepository === undefined)
910
927
  console.log('');
911
- let barePath;
912
- let packageManager;
928
+ let worktreeConfig = {};
913
929
  if (worktreeRepository) {
914
- ({ barePath, packageManager } = await setupWorktreeEnvironment(cwd, worktreeRepository, agents));
930
+ const { barePath, packageManager, mainBranch } = await setupWorktreeEnvironment(cwd, worktreeRepository, agents);
931
+ worktreeConfig = { 'worktree-repository': worktreeRepository, 'bare-path': barePath, packageManager, mainBranch };
915
932
  }
916
933
  const npxCommand = await resolveNpxCommand(cwd);
917
934
  console.log('Setting up OSDDT command files...\n');
918
935
  await writeAgentFiles(cwd, agents, npxCommand);
919
- const config = { repoType, agents };
920
- if (worktreeRepository)
921
- config['worktree-repository'] = worktreeRepository;
922
- if (barePath)
923
- config['bare-path'] = barePath;
924
- if (packageManager)
925
- config['packageManager'] = packageManager;
936
+ const config = { repoType, agents, ...worktreeConfig };
926
937
  await writeConfig(cwd, config);
927
938
  console.log('\nSetup complete!');
928
939
  console.log('Commands created: osddt.spec, osddt.plan, osddt.tasks, osddt.implement');
@@ -975,9 +986,12 @@ function todayPrefix() {
975
986
  return `${yyyy}-${mm}-${dd}`;
976
987
  }
977
988
  async function runDone(featureName, cwd, worktree) {
978
- const src = path.join(cwd, 'working-on', featureName);
989
+ const barePath = worktree ? await resolveBarePath(process.cwd()) : undefined;
990
+ const worktreePath = worktree ? findWorktreeByFeature(barePath, featureName) : undefined;
991
+ const projectDir = worktreePath ?? cwd;
992
+ const src = path.join(projectDir, 'working-on', featureName);
979
993
  const destName = `${todayPrefix()}-${featureName}`;
980
- const dest = path.join(cwd, 'done', destName);
994
+ const dest = path.join(projectDir, 'done', destName);
981
995
  if (!(await fs.pathExists(src))) {
982
996
  console.error(`Error: working-on/${featureName} does not exist.`);
983
997
  process.exit(1);
@@ -987,8 +1001,6 @@ async function runDone(featureName, cwd, worktree) {
987
1001
  console.log(`Moved: working-on/${featureName} → done/${destName}`);
988
1002
  if (!worktree)
989
1003
  return;
990
- const barePath = await resolveBarePath(process.cwd());
991
- const worktreePath = findWorktreeByFeature(barePath, featureName);
992
1004
  if (!worktreePath) {
993
1005
  console.error(`Warning: No worktree found for "${featureName}". Skipping worktree cleanup.`);
994
1006
  return;
@@ -1146,9 +1158,6 @@ function contextCommand() {
1146
1158
  return cmd;
1147
1159
  }
1148
1160
 
1149
- function repoName(repoRoot) {
1150
- return path.basename(repoRoot);
1151
- }
1152
1161
  function branchExists(branch, cwd) {
1153
1162
  try {
1154
1163
  execSync(`git rev-parse --verify ${branch}`, { cwd, stdio: 'ignore' });
@@ -1167,7 +1176,7 @@ function remoteBranchExists(branch, cwd) {
1167
1176
  return false;
1168
1177
  }
1169
1178
  }
1170
- async function prompt(question) {
1179
+ async function prompt$1(question) {
1171
1180
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1172
1181
  return new Promise((resolve) => {
1173
1182
  rl.question(question, (answer) => {
@@ -1176,10 +1185,10 @@ async function prompt(question) {
1176
1185
  });
1177
1186
  });
1178
1187
  }
1179
- async function createWorktree(branch, worktreePath, repoRoot) {
1188
+ async function createWorktree(branch, worktreePath, repoRoot, mainBranch) {
1180
1189
  if (await fs.pathExists(worktreePath)) {
1181
1190
  console.log(`\nDirectory already exists at: ${worktreePath}`);
1182
- const answer = await prompt('Resume or Abort? [R/a] ');
1191
+ const answer = await prompt$1('Resume or Abort? [R/a] ');
1183
1192
  if (answer.toLowerCase() === 'a') {
1184
1193
  console.log('Aborted.');
1185
1194
  process.exit(0);
@@ -1190,7 +1199,7 @@ async function createWorktree(branch, worktreePath, repoRoot) {
1190
1199
  const remoteExists = !localExists && remoteBranchExists(branch, repoRoot);
1191
1200
  if (localExists || remoteExists) {
1192
1201
  console.log(`\nBranch "${branch}" already exists ${localExists ? 'locally' : 'on remote'}.`);
1193
- const answer = await prompt('Resume or Abort? [R/a] ');
1202
+ const answer = await prompt$1('Resume or Abort? [R/a] ');
1194
1203
  if (answer.toLowerCase() === 'a') {
1195
1204
  console.log('Aborted.');
1196
1205
  process.exit(0);
@@ -1198,7 +1207,7 @@ async function createWorktree(branch, worktreePath, repoRoot) {
1198
1207
  execSync(`git worktree add "${worktreePath}" ${branch}`, { cwd: repoRoot, stdio: 'inherit' });
1199
1208
  }
1200
1209
  else {
1201
- execSync(`git worktree add "${worktreePath}" -b ${branch}`, { cwd: repoRoot, stdio: 'inherit' });
1210
+ execSync(`git worktree add "${worktreePath}" -b ${branch} ${mainBranch}`, { cwd: repoRoot, stdio: 'inherit' });
1202
1211
  }
1203
1212
  }
1204
1213
  function runInstall(worktreePath, packageManager) {
@@ -1224,7 +1233,7 @@ async function runStartWorktree(featureName, options) {
1224
1233
  if (existingPath) {
1225
1234
  const workingDir = path.join(existingPath, 'working-on', featureName);
1226
1235
  console.log(`\nWorktree for "${featureName}" already exists at: ${existingPath}`);
1227
- const answer = await prompt('Resume or Abort? [R/a] ');
1236
+ const answer = await prompt$1('Resume or Abort? [R/a] ');
1228
1237
  if (answer.toLowerCase() === 'a') {
1229
1238
  console.log('Aborted.');
1230
1239
  process.exit(0);
@@ -1235,14 +1244,15 @@ async function runStartWorktree(featureName, options) {
1235
1244
  console.log(` Working dir: ${workingDir}`);
1236
1245
  return;
1237
1246
  }
1238
- // Resolve worktree path — sibling of repoRoot (i.e. sibling of .bare)
1239
- const base = rc['worktreeBase'] ?? path.dirname(repoRoot);
1240
- const worktreePath = path.join(base, `${repoName(repoRoot)}-${featureName}`);
1241
- await createWorktree(branch, worktreePath, repoRoot);
1247
+ // Resolve worktree path — inside .bare as <barePath>/<featureName>
1248
+ const base = rc['worktreeBase'] ?? repoRoot;
1249
+ const worktreePath = path.join(base, featureName);
1250
+ const mainBranch = rc['mainBranch'] ?? 'main';
1251
+ await createWorktree(branch, worktreePath, repoRoot, mainBranch);
1242
1252
  // Resolve working dir
1243
1253
  let projectPath;
1244
1254
  if (rc['repoType'] === 'monorepo') {
1245
- const pkg = options.dir ?? await prompt('Package path (e.g. packages/my-package): ');
1255
+ const pkg = options.dir ?? await prompt$1('Package path (e.g. packages/my-package): ');
1246
1256
  projectPath = path.join(worktreePath, pkg);
1247
1257
  }
1248
1258
  else {
@@ -1268,38 +1278,66 @@ function startWorktreeCommand() {
1268
1278
  return cmd;
1269
1279
  }
1270
1280
 
1271
- async function resolveRepoRoot(cwd) {
1281
+ async function prompt(question) {
1282
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1283
+ return new Promise((resolve) => {
1284
+ rl.question(question, (answer) => {
1285
+ rl.close();
1286
+ resolve(answer.trim());
1287
+ });
1288
+ });
1289
+ }
1290
+ async function selectWorktree(entries) {
1291
+ console.log('\nMultiple feature worktrees found:');
1292
+ entries.forEach((e, i) => {
1293
+ console.log(` ${i + 1}) ${e.featureName} (${e.branch})`);
1294
+ });
1295
+ const answer = await prompt(`Select a feature [1-${entries.length}]: `);
1296
+ const index = parseInt(answer, 10) - 1;
1297
+ if (index < 0 || index >= entries.length || isNaN(index)) {
1298
+ console.error('Invalid selection.');
1299
+ process.exit(1);
1300
+ }
1301
+ return entries[index];
1302
+ }
1303
+ async function runWorktreeInfo(featureName) {
1304
+ const cwd = process.cwd();
1305
+ const barePath = await resolveBarePath(cwd);
1272
1306
  const rcPath = path.join(cwd, '.osddtrc');
1307
+ let mainBranch = 'main';
1273
1308
  if (await fs.pathExists(rcPath)) {
1274
1309
  const rc = await fs.readJson(rcPath);
1275
- if (rc['bare-path'])
1276
- return rc['bare-path'];
1310
+ if (rc.mainBranch)
1311
+ mainBranch = rc.mainBranch;
1277
1312
  }
1278
- return execSync('git rev-parse --show-toplevel', { cwd, encoding: 'utf-8' }).trim();
1279
- }
1280
- function stateFilePath(repoRoot) {
1281
- return path.join(path.dirname(repoRoot), '.osddt-worktrees');
1282
- }
1283
- async function runWorktreeInfo(featureName) {
1284
- const repoRoot = await resolveRepoRoot(process.cwd());
1285
- const stateFile = stateFilePath(repoRoot);
1286
- if (!(await fs.pathExists(stateFile))) {
1287
- console.error(`No .osddt-worktrees file found at: ${stateFile}`);
1288
- process.exit(1);
1313
+ const entries = listFeatureWorktrees(barePath, mainBranch);
1314
+ let entry;
1315
+ if (featureName) {
1316
+ entry = entries.find(e => e.featureName === featureName);
1317
+ if (!entry) {
1318
+ console.error(`No worktree found for feature: ${featureName}`);
1319
+ process.exit(1);
1320
+ }
1289
1321
  }
1290
- const entries = await fs.readJson(stateFile);
1291
- const entry = entries.find(e => e.featureName === featureName);
1292
- if (!entry) {
1293
- console.error(`No worktree entry found for feature: ${featureName}`);
1294
- process.exit(1);
1322
+ else {
1323
+ if (entries.length === 0) {
1324
+ console.error('No feature worktrees found.');
1325
+ process.exit(1);
1326
+ }
1327
+ else if (entries.length === 1) {
1328
+ entry = entries[0];
1329
+ }
1330
+ else {
1331
+ entry = await selectWorktree(entries);
1332
+ }
1295
1333
  }
1296
1334
  console.log(JSON.stringify({ worktreePath: entry.worktreePath, workingDir: entry.workingDir, branch: entry.branch }));
1297
1335
  }
1298
1336
  function worktreeInfoCommand() {
1299
1337
  const cmd = new Command('worktree-info');
1300
1338
  cmd
1301
- .description('Look up worktree paths for a feature from the state file')
1302
- .argument('<feature-name>', 'feature name to look up')
1339
+ .description('Look up worktree paths for a feature from git worktree list')
1340
+ .argument('[feature-name]', 'feature name to look up (optional if only one worktree exists)')
1303
1341
  .action(async (featureName) => {
1304
1342
  await runWorktreeInfo(featureName);
1305
1343
  });
@@ -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.4",
3
+ "version": "1.11.6",
4
4
  "description": "Package for Spec-Driven Development workflow",
5
5
  "keywords": [
6
6
  "spec-driven",