@dezkareid/osddt 1.11.3 → 1.11.4

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,3 +1,9 @@
1
1
  import { Command } from 'commander';
2
+ import { type AgentType } from '../utils/prompt.js';
2
3
  export declare function resolveNpxCommand(cwd: string): Promise<string>;
4
+ export declare function cloneBareRepository(cwd: string, repositoryUrl: string): string;
5
+ export declare function detectDefaultBranch(barePath: string): string;
6
+ export declare function addDefaultBranchWorktree(barePath: string, branch: string): void;
7
+ export declare function detectPackageManager(worktreePath: string): Promise<string>;
8
+ export declare function writeAgentPointerFiles(cwd: string, agents: AgentType[], branch: string): Promise<void>;
3
9
  export declare function setupCommand(): Command;
package/dist/index.js CHANGED
@@ -726,20 +726,31 @@ async function checkTargetWritable(barePath) {
726
726
  return { label: 'Worktree target directory is writable', passed: false, detail: `${targetBase} is not writable` };
727
727
  }
728
728
  }
729
- async function initStateFile(barePath) {
730
- try {
731
- const stateFile = path.join(path.dirname(barePath), '.osddt-worktrees');
732
- if (!(await fs.pathExists(stateFile))) {
733
- await fs.writeJson(stateFile, [], { spaces: 2 });
734
- console.log(` ✓ Initialized worktree state file: ${stateFile}`);
735
- }
736
- else {
737
- console.log(` ✓ Worktree state file already exists: ${stateFile}`);
738
- }
729
+ async function resolveBarePath(cwd) {
730
+ const rcPath = path.join(cwd, '.osddtrc');
731
+ if (await fs.pathExists(rcPath)) {
732
+ const rc = await fs.readJson(rcPath);
733
+ if (rc['bare-path'])
734
+ return rc['bare-path'];
739
735
  }
740
- catch {
741
- console.log(' ✗ Could not initialize worktree state file');
736
+ return execSync('git rev-parse --show-toplevel', { cwd, encoding: 'utf-8' }).trim();
737
+ }
738
+ function findWorktreeByFeature(barePath, featureName) {
739
+ const output = execSync('git worktree list --porcelain', { cwd: barePath, encoding: 'utf-8' });
740
+ const blocks = output.trim().split(/\n\n+/);
741
+ for (const block of blocks) {
742
+ const match = block.match(/^worktree (.+)$/m);
743
+ if (!match)
744
+ continue;
745
+ const worktreePath = match[1].trim();
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) {
750
+ return worktreePath;
751
+ }
742
752
  }
753
+ return undefined;
743
754
  }
744
755
  function printCheckResult(result) {
745
756
  const icon = result.passed ? '✓' : '✗';
@@ -831,8 +842,51 @@ function cloneBareRepository(cwd, repositoryUrl) {
831
842
  console.log('');
832
843
  return barePath;
833
844
  }
834
- async function setupWorktreeEnvironment(cwd, worktreeRepository) {
845
+ function detectDefaultBranch(barePath) {
846
+ const output = execSync('git remote show origin', { cwd: barePath, encoding: 'utf-8' });
847
+ const match = output.match(/HEAD branch:\s*(\S+)/);
848
+ if (match)
849
+ return match[1];
850
+ // fallback: try main then master
851
+ try {
852
+ execSync('git rev-parse --verify main', { cwd: barePath, stdio: 'ignore' });
853
+ return 'main';
854
+ }
855
+ catch {
856
+ return 'master';
857
+ }
858
+ }
859
+ function addDefaultBranchWorktree(barePath, branch) {
860
+ const worktreePath = path.join(barePath, branch);
861
+ execSync(`git worktree add "${worktreePath}" ${branch}`, { cwd: barePath, stdio: 'inherit' });
862
+ }
863
+ async function detectPackageManager(worktreePath) {
864
+ if (await fs.pathExists(path.join(worktreePath, 'pnpm-lock.yaml')))
865
+ return 'pnpm';
866
+ if (await fs.pathExists(path.join(worktreePath, 'yarn.lock')))
867
+ return 'yarn';
868
+ if (await fs.pathExists(path.join(worktreePath, 'package-lock.json')))
869
+ return 'npm';
870
+ return 'npm';
871
+ }
872
+ async function writeAgentPointerFiles(cwd, agents, branch) {
873
+ if (agents.includes('claude')) {
874
+ const filePath = path.join(cwd, 'CLAUDE.md');
875
+ await fs.writeFile(filePath, `@.bare/${branch}/CLAUDE.md\n`, 'utf-8');
876
+ console.log(` Created: ${filePath}`);
877
+ }
878
+ if (agents.includes('gemini')) {
879
+ const filePath = path.join(cwd, 'GEMINI.md');
880
+ await fs.writeFile(filePath, `@.bare/${branch}/GEMINI.md\n`, 'utf-8');
881
+ console.log(` Created: ${filePath}`);
882
+ }
883
+ }
884
+ async function setupWorktreeEnvironment(cwd, worktreeRepository, agents) {
835
885
  const barePath = cloneBareRepository(cwd, worktreeRepository);
886
+ const branch = detectDefaultBranch(barePath);
887
+ addDefaultBranchWorktree(barePath, branch);
888
+ const worktreePath = path.join(barePath, branch);
889
+ const packageManager = await detectPackageManager(worktreePath);
836
890
  console.log('Checking environment for git worktree support...\n');
837
891
  const allPassed = await runWorktreeChecks(barePath);
838
892
  console.log('');
@@ -840,9 +894,9 @@ async function setupWorktreeEnvironment(cwd, worktreeRepository) {
840
894
  console.log('Some checks failed. Resolve the issues above before using the worktree workflow.');
841
895
  process.exit(1);
842
896
  }
843
- await initStateFile(barePath);
897
+ await writeAgentPointerFiles(cwd, agents, branch);
844
898
  console.log('');
845
- return barePath;
899
+ return { barePath, packageManager };
846
900
  }
847
901
  async function runSetup(cwd, rawAgents, rawRepoType, rawWorktreeRepository) {
848
902
  const agents = rawAgents !== undefined ? parseAgents(rawAgents) : await askAgents();
@@ -855,8 +909,9 @@ async function runSetup(cwd, rawAgents, rawRepoType, rawWorktreeRepository) {
855
909
  if (rawWorktreeRepository === undefined)
856
910
  console.log('');
857
911
  let barePath;
912
+ let packageManager;
858
913
  if (worktreeRepository) {
859
- barePath = await setupWorktreeEnvironment(cwd, worktreeRepository);
914
+ ({ barePath, packageManager } = await setupWorktreeEnvironment(cwd, worktreeRepository, agents));
860
915
  }
861
916
  const npxCommand = await resolveNpxCommand(cwd);
862
917
  console.log('Setting up OSDDT command files...\n');
@@ -866,6 +921,8 @@ async function runSetup(cwd, rawAgents, rawRepoType, rawWorktreeRepository) {
866
921
  config['worktree-repository'] = worktreeRepository;
867
922
  if (barePath)
868
923
  config['bare-path'] = barePath;
924
+ if (packageManager)
925
+ config['packageManager'] = packageManager;
869
926
  await writeConfig(cwd, config);
870
927
  console.log('\nSetup complete!');
871
928
  console.log('Commands created: osddt.spec, osddt.plan, osddt.tasks, osddt.implement');
@@ -917,18 +974,6 @@ function todayPrefix() {
917
974
  const dd = String(now.getDate()).padStart(2, '0');
918
975
  return `${yyyy}-${mm}-${dd}`;
919
976
  }
920
- async function resolveRepoRoot$2(cwd) {
921
- const rcPath = path.join(cwd, '.osddtrc');
922
- if (await fs.pathExists(rcPath)) {
923
- const rc = await fs.readJson(rcPath);
924
- if (rc['bare-path'])
925
- return rc['bare-path'];
926
- }
927
- return execSync('git rev-parse --show-toplevel', { cwd, encoding: 'utf-8' }).trim();
928
- }
929
- function stateFilePath$2(repoRoot) {
930
- return path.join(path.dirname(repoRoot), '.osddt-worktrees');
931
- }
932
977
  async function runDone(featureName, cwd, worktree) {
933
978
  const src = path.join(cwd, 'working-on', featureName);
934
979
  const destName = `${todayPrefix()}-${featureName}`;
@@ -942,28 +987,19 @@ async function runDone(featureName, cwd, worktree) {
942
987
  console.log(`Moved: working-on/${featureName} → done/${destName}`);
943
988
  if (!worktree)
944
989
  return;
945
- const repoRoot = await resolveRepoRoot$2(process.cwd());
946
- const stateFile = stateFilePath$2(repoRoot);
947
- if (!(await fs.pathExists(stateFile))) {
948
- console.error(`Warning: .osddt-worktrees not found at ${stateFile}. Skipping worktree cleanup.`);
990
+ const barePath = await resolveBarePath(process.cwd());
991
+ const worktreePath = findWorktreeByFeature(barePath, featureName);
992
+ if (!worktreePath) {
993
+ console.error(`Warning: No worktree found for "${featureName}". Skipping worktree cleanup.`);
949
994
  return;
950
995
  }
951
- const entries = await fs.readJson(stateFile);
952
- const entry = entries.find(e => e.featureName === featureName);
953
- if (!entry) {
954
- console.error(`Warning: No worktree entry found for "${featureName}". Skipping worktree cleanup.`);
955
- return;
956
- }
957
- if (await fs.pathExists(entry.worktreePath)) {
958
- execSync(`git worktree remove "${entry.worktreePath}" --force`, { cwd: repoRoot, stdio: 'inherit' });
959
- console.log(`Removed worktree: ${entry.worktreePath}`);
996
+ if (await fs.pathExists(worktreePath)) {
997
+ execSync(`git worktree remove "${worktreePath}" --force`, { cwd: barePath, stdio: 'inherit' });
998
+ console.log(`Removed worktree: ${worktreePath}`);
960
999
  }
961
1000
  else {
962
1001
  console.log(`Worktree path not found on filesystem, skipping git worktree remove.`);
963
1002
  }
964
- const updated = entries.filter(e => e.featureName !== featureName);
965
- await fs.writeJson(stateFile, updated, { spaces: 2 });
966
- console.log(`Removed state entry for "${featureName}" from .osddt-worktrees`);
967
1003
  }
968
1004
  function doneCommand() {
969
1005
  const cmd = new Command('done');
@@ -971,7 +1007,7 @@ function doneCommand() {
971
1007
  .description('Move a feature from working-on/<feature-name> to done/<feature-name>')
972
1008
  .argument('<feature-name>', 'name of the feature to mark as done')
973
1009
  .option('-d, --dir <directory>', 'project directory', process.cwd())
974
- .option('--worktree', 'also remove the git worktree and clean up the state file')
1010
+ .option('--worktree', 'also remove the git worktree')
975
1011
  .action(async (featureName, options) => {
976
1012
  const targetDir = path.resolve(options.dir);
977
1013
  await runDone(featureName, targetDir, options.worktree ?? false);
@@ -1113,17 +1149,6 @@ function contextCommand() {
1113
1149
  function repoName(repoRoot) {
1114
1150
  return path.basename(repoRoot);
1115
1151
  }
1116
- function stateFilePath$1(repoRoot) {
1117
- return path.join(path.dirname(repoRoot), '.osddt-worktrees');
1118
- }
1119
- async function readStateFile(stateFile) {
1120
- if (!(await fs.pathExists(stateFile)))
1121
- return [];
1122
- return fs.readJson(stateFile);
1123
- }
1124
- async function writeStateFile(stateFile, entries) {
1125
- await fs.writeJson(stateFile, entries, { spaces: 2 });
1126
- }
1127
1152
  function branchExists(branch, cwd) {
1128
1153
  try {
1129
1154
  execSync(`git rev-parse --verify ${branch}`, { cwd, stdio: 'ignore' });
@@ -1151,15 +1176,6 @@ async function prompt(question) {
1151
1176
  });
1152
1177
  });
1153
1178
  }
1154
- async function resolveRepoRoot$1(cwd) {
1155
- const rcPath = path.join(cwd, '.osddtrc');
1156
- if (await fs.pathExists(rcPath)) {
1157
- const rc = await fs.readJson(rcPath);
1158
- if (rc['bare-path'])
1159
- return rc['bare-path'];
1160
- }
1161
- return execSync('git rev-parse --show-toplevel', { cwd, encoding: 'utf-8' }).trim();
1162
- }
1163
1179
  async function createWorktree(branch, worktreePath, repoRoot) {
1164
1180
  if (await fs.pathExists(worktreePath)) {
1165
1181
  console.log(`\nDirectory already exists at: ${worktreePath}`);
@@ -1185,9 +1201,17 @@ async function createWorktree(branch, worktreePath, repoRoot) {
1185
1201
  execSync(`git worktree add "${worktreePath}" -b ${branch}`, { cwd: repoRoot, stdio: 'inherit' });
1186
1202
  }
1187
1203
  }
1204
+ function runInstall(worktreePath, packageManager) {
1205
+ if (!packageManager) {
1206
+ console.log(`\n ℹ No packageManager configured in .osddtrc — skipping install. Run it manually inside ${worktreePath}`);
1207
+ return;
1208
+ }
1209
+ console.log(`\n Running ${packageManager} install...`);
1210
+ execSync(`${packageManager} install`, { cwd: worktreePath, stdio: 'inherit' });
1211
+ }
1188
1212
  async function runStartWorktree(featureName, options) {
1189
1213
  const cwd = process.cwd();
1190
- const repoRoot = await resolveRepoRoot$1(cwd);
1214
+ const repoRoot = await resolveBarePath(cwd);
1191
1215
  const branch = `feat/${featureName}`;
1192
1216
  // Read .osddtrc
1193
1217
  const rcPath = path.join(cwd, '.osddtrc');
@@ -1195,30 +1219,29 @@ async function runStartWorktree(featureName, options) {
1195
1219
  if (await fs.pathExists(rcPath)) {
1196
1220
  rc = await fs.readJson(rcPath);
1197
1221
  }
1198
- // Resolve worktree path base is parent of repoRoot (i.e. cwd when using .bare)
1199
- const base = rc.worktreeBase ?? path.dirname(repoRoot);
1200
- const worktreePath = path.join(base, `${repoName(repoRoot)}-${featureName}`);
1201
- // Check state file for existing entry
1202
- const stateFile = stateFilePath$1(repoRoot);
1203
- const entries = await readStateFile(stateFile);
1204
- const existing = entries.find(e => e.featureName === featureName);
1205
- if (existing) {
1206
- console.log(`\nWorktree for "${featureName}" already exists at: ${existing.worktreePath}`);
1222
+ // Check for existing worktree via git worktree list
1223
+ const existingPath = findWorktreeByFeature(repoRoot, featureName);
1224
+ if (existingPath) {
1225
+ const workingDir = path.join(existingPath, 'working-on', featureName);
1226
+ console.log(`\nWorktree for "${featureName}" already exists at: ${existingPath}`);
1207
1227
  const answer = await prompt('Resume or Abort? [R/a] ');
1208
1228
  if (answer.toLowerCase() === 'a') {
1209
1229
  console.log('Aborted.');
1210
1230
  process.exit(0);
1211
1231
  }
1212
1232
  console.log(`\nResuming existing worktree.`);
1213
- console.log(` Branch: ${existing.branch}`);
1214
- console.log(` Worktree path: ${existing.worktreePath}`);
1215
- console.log(` Working dir: ${existing.workingDir}`);
1233
+ console.log(` Branch: ${branch}`);
1234
+ console.log(` Worktree path: ${existingPath}`);
1235
+ console.log(` Working dir: ${workingDir}`);
1216
1236
  return;
1217
1237
  }
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}`);
1218
1241
  await createWorktree(branch, worktreePath, repoRoot);
1219
1242
  // Resolve working dir
1220
1243
  let projectPath;
1221
- if (rc.repoType === 'monorepo') {
1244
+ if (rc['repoType'] === 'monorepo') {
1222
1245
  const pkg = options.dir ?? await prompt('Package path (e.g. packages/my-package): ');
1223
1246
  projectPath = path.join(worktreePath, pkg);
1224
1247
  }
@@ -1227,9 +1250,7 @@ async function runStartWorktree(featureName, options) {
1227
1250
  }
1228
1251
  const workingDir = path.join(projectPath, 'working-on', featureName);
1229
1252
  await fs.ensureDir(workingDir);
1230
- // Write state file entry
1231
- entries.push({ featureName, branch, worktreePath, workingDir, repoRoot });
1232
- await writeStateFile(stateFile, entries);
1253
+ runInstall(worktreePath, rc['packageManager']);
1233
1254
  console.log(`\nWorktree feature started:`);
1234
1255
  console.log(` Branch: ${branch}`);
1235
1256
  console.log(` Worktree path: ${worktreePath}`);
@@ -5,6 +5,7 @@ export interface CheckResult {
5
5
  }
6
6
  export declare function checkGitVersion(): CheckResult;
7
7
  export declare function checkTargetWritable(barePath: string): Promise<CheckResult>;
8
- export declare function initStateFile(barePath: string): Promise<void>;
8
+ export declare function resolveBarePath(cwd: string): Promise<string>;
9
+ export declare function findWorktreeByFeature(barePath: string, featureName: string): string | undefined;
9
10
  export declare function printCheckResult(result: CheckResult): void;
10
11
  export declare function runWorktreeChecks(barePath: string): Promise<boolean>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dezkareid/osddt",
3
- "version": "1.11.3",
3
+ "version": "1.11.4",
4
4
  "description": "Package for Spec-Driven Development workflow",
5
5
  "keywords": [
6
6
  "spec-driven",