@dezkareid/osddt 1.11.3 → 1.11.5

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,29 @@ 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
+ if (basename === featureName) {
748
+ return worktreePath;
749
+ }
742
750
  }
751
+ return undefined;
743
752
  }
744
753
  function printCheckResult(result) {
745
754
  const icon = result.passed ? '✓' : '✗';
@@ -831,8 +840,51 @@ function cloneBareRepository(cwd, repositoryUrl) {
831
840
  console.log('');
832
841
  return barePath;
833
842
  }
834
- async function setupWorktreeEnvironment(cwd, worktreeRepository) {
843
+ function detectDefaultBranch(barePath) {
844
+ const output = execSync('git remote show origin', { cwd: barePath, encoding: 'utf-8' });
845
+ const match = output.match(/HEAD branch:\s*(\S+)/);
846
+ if (match)
847
+ return match[1];
848
+ // fallback: try main then master
849
+ try {
850
+ execSync('git rev-parse --verify main', { cwd: barePath, stdio: 'ignore' });
851
+ return 'main';
852
+ }
853
+ catch {
854
+ return 'master';
855
+ }
856
+ }
857
+ function addDefaultBranchWorktree(barePath, branch) {
858
+ const worktreePath = path.join(barePath, branch);
859
+ execSync(`git worktree add "${worktreePath}" ${branch}`, { cwd: barePath, stdio: 'inherit' });
860
+ }
861
+ async function detectPackageManager(worktreePath) {
862
+ if (await fs.pathExists(path.join(worktreePath, 'pnpm-lock.yaml')))
863
+ return 'pnpm';
864
+ if (await fs.pathExists(path.join(worktreePath, 'yarn.lock')))
865
+ return 'yarn';
866
+ if (await fs.pathExists(path.join(worktreePath, 'package-lock.json')))
867
+ return 'npm';
868
+ return 'npm';
869
+ }
870
+ async function writeAgentPointerFiles(cwd, agents, branch) {
871
+ if (agents.includes('claude')) {
872
+ const filePath = path.join(cwd, 'CLAUDE.md');
873
+ await fs.writeFile(filePath, `@.bare/${branch}/CLAUDE.md\n`, 'utf-8');
874
+ console.log(` Created: ${filePath}`);
875
+ }
876
+ if (agents.includes('gemini')) {
877
+ const filePath = path.join(cwd, 'GEMINI.md');
878
+ await fs.writeFile(filePath, `@.bare/${branch}/GEMINI.md\n`, 'utf-8');
879
+ console.log(` Created: ${filePath}`);
880
+ }
881
+ }
882
+ async function setupWorktreeEnvironment(cwd, worktreeRepository, agents) {
835
883
  const barePath = cloneBareRepository(cwd, worktreeRepository);
884
+ const branch = detectDefaultBranch(barePath);
885
+ addDefaultBranchWorktree(barePath, branch);
886
+ const worktreePath = path.join(barePath, branch);
887
+ const packageManager = await detectPackageManager(worktreePath);
836
888
  console.log('Checking environment for git worktree support...\n');
837
889
  const allPassed = await runWorktreeChecks(barePath);
838
890
  console.log('');
@@ -840,9 +892,9 @@ async function setupWorktreeEnvironment(cwd, worktreeRepository) {
840
892
  console.log('Some checks failed. Resolve the issues above before using the worktree workflow.');
841
893
  process.exit(1);
842
894
  }
843
- await initStateFile(barePath);
895
+ await writeAgentPointerFiles(cwd, agents, branch);
844
896
  console.log('');
845
- return barePath;
897
+ return { barePath, packageManager };
846
898
  }
847
899
  async function runSetup(cwd, rawAgents, rawRepoType, rawWorktreeRepository) {
848
900
  const agents = rawAgents !== undefined ? parseAgents(rawAgents) : await askAgents();
@@ -855,8 +907,9 @@ async function runSetup(cwd, rawAgents, rawRepoType, rawWorktreeRepository) {
855
907
  if (rawWorktreeRepository === undefined)
856
908
  console.log('');
857
909
  let barePath;
910
+ let packageManager;
858
911
  if (worktreeRepository) {
859
- barePath = await setupWorktreeEnvironment(cwd, worktreeRepository);
912
+ ({ barePath, packageManager } = await setupWorktreeEnvironment(cwd, worktreeRepository, agents));
860
913
  }
861
914
  const npxCommand = await resolveNpxCommand(cwd);
862
915
  console.log('Setting up OSDDT command files...\n');
@@ -866,6 +919,8 @@ async function runSetup(cwd, rawAgents, rawRepoType, rawWorktreeRepository) {
866
919
  config['worktree-repository'] = worktreeRepository;
867
920
  if (barePath)
868
921
  config['bare-path'] = barePath;
922
+ if (packageManager)
923
+ config['packageManager'] = packageManager;
869
924
  await writeConfig(cwd, config);
870
925
  console.log('\nSetup complete!');
871
926
  console.log('Commands created: osddt.spec, osddt.plan, osddt.tasks, osddt.implement');
@@ -917,18 +972,6 @@ function todayPrefix() {
917
972
  const dd = String(now.getDate()).padStart(2, '0');
918
973
  return `${yyyy}-${mm}-${dd}`;
919
974
  }
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
975
  async function runDone(featureName, cwd, worktree) {
933
976
  const src = path.join(cwd, 'working-on', featureName);
934
977
  const destName = `${todayPrefix()}-${featureName}`;
@@ -942,28 +985,19 @@ async function runDone(featureName, cwd, worktree) {
942
985
  console.log(`Moved: working-on/${featureName} → done/${destName}`);
943
986
  if (!worktree)
944
987
  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.`);
988
+ const barePath = await resolveBarePath(process.cwd());
989
+ const worktreePath = findWorktreeByFeature(barePath, featureName);
990
+ if (!worktreePath) {
991
+ console.error(`Warning: No worktree found for "${featureName}". Skipping worktree cleanup.`);
949
992
  return;
950
993
  }
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}`);
994
+ if (await fs.pathExists(worktreePath)) {
995
+ execSync(`git worktree remove "${worktreePath}" --force`, { cwd: barePath, stdio: 'inherit' });
996
+ console.log(`Removed worktree: ${worktreePath}`);
960
997
  }
961
998
  else {
962
999
  console.log(`Worktree path not found on filesystem, skipping git worktree remove.`);
963
1000
  }
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
1001
  }
968
1002
  function doneCommand() {
969
1003
  const cmd = new Command('done');
@@ -971,7 +1005,7 @@ function doneCommand() {
971
1005
  .description('Move a feature from working-on/<feature-name> to done/<feature-name>')
972
1006
  .argument('<feature-name>', 'name of the feature to mark as done')
973
1007
  .option('-d, --dir <directory>', 'project directory', process.cwd())
974
- .option('--worktree', 'also remove the git worktree and clean up the state file')
1008
+ .option('--worktree', 'also remove the git worktree')
975
1009
  .action(async (featureName, options) => {
976
1010
  const targetDir = path.resolve(options.dir);
977
1011
  await runDone(featureName, targetDir, options.worktree ?? false);
@@ -1110,20 +1144,6 @@ function contextCommand() {
1110
1144
  return cmd;
1111
1145
  }
1112
1146
 
1113
- function repoName(repoRoot) {
1114
- return path.basename(repoRoot);
1115
- }
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
1147
  function branchExists(branch, cwd) {
1128
1148
  try {
1129
1149
  execSync(`git rev-parse --verify ${branch}`, { cwd, stdio: 'ignore' });
@@ -1151,15 +1171,6 @@ async function prompt(question) {
1151
1171
  });
1152
1172
  });
1153
1173
  }
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
1174
  async function createWorktree(branch, worktreePath, repoRoot) {
1164
1175
  if (await fs.pathExists(worktreePath)) {
1165
1176
  console.log(`\nDirectory already exists at: ${worktreePath}`);
@@ -1185,9 +1196,17 @@ async function createWorktree(branch, worktreePath, repoRoot) {
1185
1196
  execSync(`git worktree add "${worktreePath}" -b ${branch}`, { cwd: repoRoot, stdio: 'inherit' });
1186
1197
  }
1187
1198
  }
1199
+ function runInstall(worktreePath, packageManager) {
1200
+ if (!packageManager) {
1201
+ console.log(`\n ℹ No packageManager configured in .osddtrc — skipping install. Run it manually inside ${worktreePath}`);
1202
+ return;
1203
+ }
1204
+ console.log(`\n Running ${packageManager} install...`);
1205
+ execSync(`${packageManager} install`, { cwd: worktreePath, stdio: 'inherit' });
1206
+ }
1188
1207
  async function runStartWorktree(featureName, options) {
1189
1208
  const cwd = process.cwd();
1190
- const repoRoot = await resolveRepoRoot$1(cwd);
1209
+ const repoRoot = await resolveBarePath(cwd);
1191
1210
  const branch = `feat/${featureName}`;
1192
1211
  // Read .osddtrc
1193
1212
  const rcPath = path.join(cwd, '.osddtrc');
@@ -1195,30 +1214,29 @@ async function runStartWorktree(featureName, options) {
1195
1214
  if (await fs.pathExists(rcPath)) {
1196
1215
  rc = await fs.readJson(rcPath);
1197
1216
  }
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}`);
1217
+ // Check for existing worktree via git worktree list
1218
+ const existingPath = findWorktreeByFeature(repoRoot, featureName);
1219
+ if (existingPath) {
1220
+ const workingDir = path.join(existingPath, 'working-on', featureName);
1221
+ console.log(`\nWorktree for "${featureName}" already exists at: ${existingPath}`);
1207
1222
  const answer = await prompt('Resume or Abort? [R/a] ');
1208
1223
  if (answer.toLowerCase() === 'a') {
1209
1224
  console.log('Aborted.');
1210
1225
  process.exit(0);
1211
1226
  }
1212
1227
  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}`);
1228
+ console.log(` Branch: ${branch}`);
1229
+ console.log(` Worktree path: ${existingPath}`);
1230
+ console.log(` Working dir: ${workingDir}`);
1216
1231
  return;
1217
1232
  }
1233
+ // Resolve worktree path — inside .bare as <barePath>/<featureName>
1234
+ const base = rc['worktreeBase'] ?? repoRoot;
1235
+ const worktreePath = path.join(base, featureName);
1218
1236
  await createWorktree(branch, worktreePath, repoRoot);
1219
1237
  // Resolve working dir
1220
1238
  let projectPath;
1221
- if (rc.repoType === 'monorepo') {
1239
+ if (rc['repoType'] === 'monorepo') {
1222
1240
  const pkg = options.dir ?? await prompt('Package path (e.g. packages/my-package): ');
1223
1241
  projectPath = path.join(worktreePath, pkg);
1224
1242
  }
@@ -1227,9 +1245,7 @@ async function runStartWorktree(featureName, options) {
1227
1245
  }
1228
1246
  const workingDir = path.join(projectPath, 'working-on', featureName);
1229
1247
  await fs.ensureDir(workingDir);
1230
- // Write state file entry
1231
- entries.push({ featureName, branch, worktreePath, workingDir, repoRoot });
1232
- await writeStateFile(stateFile, entries);
1248
+ runInstall(worktreePath, rc['packageManager']);
1233
1249
  console.log(`\nWorktree feature started:`);
1234
1250
  console.log(` Branch: ${branch}`);
1235
1251
  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.5",
4
4
  "description": "Package for Spec-Driven Development workflow",
5
5
  "keywords": [
6
6
  "spec-driven",