@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.
- package/dist/commands/start-worktree.d.ts +2 -7
- package/dist/index.js +108 -45
- package/dist/utils/worktree.d.ts +7 -0
- package/package.json +1 -1
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
|
|
3
|
-
|
|
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)}
|
|
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
|
|
910
|
-
let packageManager;
|
|
948
|
+
let worktreeConfig = {};
|
|
911
949
|
if (worktreeRepository) {
|
|
912
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
1271
|
-
|
|
1330
|
+
if (rc.mainBranch)
|
|
1331
|
+
mainBranch = rc.mainBranch;
|
|
1272
1332
|
}
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
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
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
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
|
|
1297
|
-
.argument('
|
|
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
|
});
|
package/dist/utils/worktree.d.ts
CHANGED
|
@@ -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[];
|