forge-workflow 0.0.8 → 0.0.10
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/.claude/commands/dev.md +2 -2
- package/.claude/commands/plan.md +2 -2
- package/.claude/commands/premerge.md +2 -2
- package/.claude/commands/review.md +5 -2
- package/.claude/commands/ship.md +6 -5
- package/.claude/commands/status.md +4 -4
- package/.claude/rules/greptile-review-process.md +4 -4
- package/.cline/workflows/dev.md +2 -2
- package/.cline/workflows/plan.md +2 -2
- package/.cline/workflows/premerge.md +2 -2
- package/.cline/workflows/review.md +5 -2
- package/.cline/workflows/ship.md +6 -5
- package/.cline/workflows/status.md +4 -4
- package/.codex/skills/dev/SKILL.md +2 -2
- package/.codex/skills/plan/SKILL.md +2 -2
- package/.codex/skills/premerge/SKILL.md +2 -2
- package/.codex/skills/review/SKILL.md +5 -2
- package/.codex/skills/ship/SKILL.md +6 -5
- package/.codex/skills/status/SKILL.md +4 -4
- package/.cursor/commands/dev.md +2 -2
- package/.cursor/commands/plan.md +2 -2
- package/.cursor/commands/premerge.md +2 -2
- package/.cursor/commands/review.md +5 -2
- package/.cursor/commands/ship.md +6 -5
- package/.cursor/commands/status.md +4 -4
- package/.github/prompts/dev.prompt.md +2 -2
- package/.github/prompts/plan.prompt.md +2 -2
- package/.github/prompts/premerge.prompt.md +2 -2
- package/.github/prompts/review.prompt.md +5 -2
- package/.github/prompts/ship.prompt.md +6 -5
- package/.github/prompts/status.prompt.md +4 -4
- package/.github/workflows/beads-to-github.yml +44 -11
- package/.github/workflows/github-to-beads.yml +11 -8
- package/.kilocode/workflows/dev.md +2 -2
- package/.kilocode/workflows/plan.md +2 -2
- package/.kilocode/workflows/premerge.md +2 -2
- package/.kilocode/workflows/review.md +5 -2
- package/.kilocode/workflows/ship.md +6 -5
- package/.kilocode/workflows/status.md +4 -4
- package/.opencode/commands/dev.md +2 -2
- package/.opencode/commands/plan.md +2 -2
- package/.opencode/commands/premerge.md +2 -2
- package/.opencode/commands/review.md +5 -2
- package/.opencode/commands/ship.md +6 -5
- package/.opencode/commands/status.md +4 -4
- package/.roo/commands/dev.md +2 -2
- package/.roo/commands/plan.md +2 -2
- package/.roo/commands/premerge.md +2 -2
- package/.roo/commands/review.md +5 -2
- package/.roo/commands/ship.md +6 -5
- package/.roo/commands/status.md +4 -4
- package/AGENTS.md +10 -9
- package/CLAUDE.md +12 -0
- package/README.md +12 -6
- package/bin/forge.js +24 -8
- package/docs/BEADS_GITHUB_SYNC.md +32 -2
- package/docs/EXAMPLES.md +22 -22
- package/docs/ROADMAP.md +3 -3
- package/docs/TOOLCHAIN.md +150 -160
- package/lib/agents/codex.plugin.json +3 -0
- package/lib/agents-config.js +18 -12
- package/lib/beads-bootstrap.js +225 -0
- package/lib/beads-health-check.js +55 -10
- package/lib/beads-setup.js +104 -28
- package/lib/beads-sync-scaffold.js +11 -6
- package/lib/codex-skills.js +54 -1
- package/lib/commands/_issue.js +11 -1
- package/lib/commands/issues.js +49 -0
- package/lib/commands/plan.js +5 -2
- package/lib/commands/recommend.js +22 -1
- package/lib/commands/setup.js +247 -27
- package/lib/commands/ship.js +188 -5
- package/lib/commands/status.js +187 -19
- package/lib/commands/team.js +11 -1
- package/lib/commands/test.js +125 -25
- package/lib/commands/validate.js +232 -9
- package/lib/commands/worktree.js +27 -54
- package/lib/dep-guard/keyword-ripple.js +184 -0
- package/lib/detect-worktree.js +9 -10
- package/lib/forge-issues.js +326 -0
- package/lib/issue-sync/authority.js +100 -0
- package/lib/issue-sync/github-pull.js +184 -0
- package/lib/issue-sync/import-primitives.js +98 -0
- package/lib/issue-sync/legacy-link-bridge.js +436 -0
- package/lib/issue-sync/link-store.js +292 -0
- package/lib/issue-sync/project-github.js +123 -0
- package/lib/issue-sync/reconcile.js +195 -0
- package/lib/issue-sync/schema.js +126 -0
- package/lib/lefthook-check.js +5 -2
- package/lib/project-memory.js +564 -0
- package/lib/runtime-health.js +100 -12
- package/lib/setup-action-log.js +2 -0
- package/lib/setup-summary-renderer.js +15 -11
- package/lib/smart-status/conflicts.js +205 -0
- package/lib/smart-status/scoring.js +177 -0
- package/lib/status/beads-snapshot.js +102 -0
- package/lib/status/presenter.js +65 -0
- package/lib/workflow/enforce-stage.js +14 -8
- package/lib/workflow/state-manager.js +349 -0
- package/package.json +12 -4
- package/scripts/beads-context.sh +124 -5
- package/scripts/beads-context.test.js +21 -4
- package/scripts/beads-migrate-to-dolt.sh +7 -0
- package/scripts/beads-upgrade-smoke.sh +263 -0
- package/scripts/behavioral-judge.sh +115 -11
- package/scripts/benchmark.js +349 -63
- package/scripts/dep-guard-analyze.js +52 -17
- package/scripts/dep-guard-keyword-ripple.js +29 -0
- package/scripts/dep-guard-render-review.js +86 -0
- package/scripts/dep-guard.sh +45 -222
- package/scripts/forge-team/lib/hooks.sh +1 -1
- package/scripts/forge-team/lib/sync-github.sh +160 -28
- package/scripts/forge-team/lib/verify.sh +1 -1
- package/scripts/forge-team/lib/workload.sh +56 -27
- package/scripts/forge-team/tests/sync-github.test.sh +195 -58
- package/scripts/forge-team/tests/workload.test.sh +35 -4
- package/scripts/github-beads-sync/index.mjs +122 -98
- package/scripts/github-beads-sync/mapping.mjs +54 -0
- package/scripts/github-beads-sync/reverse-sync-cli.mjs +2 -2
- package/scripts/github-beads-sync/reverse-sync.mjs +31 -7
- package/scripts/github-beads-sync/run-bd.mjs +4 -2
- package/scripts/lib/beads-migrate-to-dolt.mjs +503 -0
- package/scripts/preflight.sh +181 -0
- package/scripts/smart-status-score.js +31 -0
- package/scripts/smart-status-sessions.js +51 -0
- package/scripts/smart-status.sh +84 -330
- package/scripts/sync-agentic-workflow.js +48 -0
- package/scripts/sync-utils.sh +39 -0
- package/scripts/test-ci-shard.js +244 -0
- package/scripts/test-dashboard.js +188 -52
- package/scripts/test-full-suite.js +186 -0
- package/scripts/test-profile.js +278 -0
- package/scripts/test.js +335 -38
- package/scripts/validate.js +143 -0
- package/scripts/validate.sh +18 -1
package/lib/commands/ship.js
CHANGED
|
@@ -29,6 +29,178 @@ function isCommandNotFound(error) {
|
|
|
29
29
|
return error.message.includes('ENOENT') || error.message.includes('not found');
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
function getGitExecOptions(cwd = process.cwd()) {
|
|
33
|
+
return { ...getExecOptions(), cwd };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getQuietGitExecOptions(cwd = process.cwd()) {
|
|
37
|
+
return { ...getGitExecOptions(cwd), stdio: 'pipe' };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function resolveRemoteHeadTarget(exec = execFileSync, cwd = process.cwd(), remoteName) {
|
|
41
|
+
try {
|
|
42
|
+
const symbolicRef = exec('git', ['symbolic-ref', `refs/remotes/${remoteName}/HEAD`], getQuietGitExecOptions(cwd)).trim();
|
|
43
|
+
if (!symbolicRef) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
exec('git', ['rev-parse', '--verify', symbolicRef], getQuietGitExecOptions(cwd));
|
|
47
|
+
return symbolicRef;
|
|
48
|
+
} catch (_error) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function remoteHasTrackingBase(exec = execFileSync, cwd = process.cwd(), remoteName) {
|
|
54
|
+
if (resolveRemoteHeadTarget(exec, cwd, remoteName)) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
for (const candidate of ['main', 'master']) {
|
|
59
|
+
try {
|
|
60
|
+
exec('git', ['rev-parse', '--verify', `refs/remotes/${remoteName}/${candidate}`], getQuietGitExecOptions(cwd));
|
|
61
|
+
return true;
|
|
62
|
+
} catch (_error) {
|
|
63
|
+
// Probe next candidate.
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function resolveBaseRemote(exec = execFileSync, cwd = process.cwd()) {
|
|
71
|
+
for (const candidate of ['upstream', 'origin']) {
|
|
72
|
+
try {
|
|
73
|
+
exec('git', ['remote', 'get-url', candidate], getQuietGitExecOptions(cwd));
|
|
74
|
+
if (remoteHasTrackingBase(exec, cwd, candidate)) {
|
|
75
|
+
return candidate;
|
|
76
|
+
}
|
|
77
|
+
} catch (_error) {
|
|
78
|
+
// Probe next candidate.
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return 'origin';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function resolveBaseBranch(exec = execFileSync, cwd = process.cwd(), remoteName = resolveBaseRemote(exec, cwd)) {
|
|
86
|
+
const symbolicRef = resolveRemoteHeadTarget(exec, cwd, remoteName);
|
|
87
|
+
if (symbolicRef) {
|
|
88
|
+
const match = new RegExp(`^refs/remotes/${remoteName}/(.+)$`).exec(symbolicRef);
|
|
89
|
+
if (match && match[1]) {
|
|
90
|
+
return match[1];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (const candidate of ['main', 'master']) {
|
|
95
|
+
try {
|
|
96
|
+
exec('git', ['rev-parse', '--verify', `refs/remotes/${remoteName}/${candidate}`], getQuietGitExecOptions(cwd));
|
|
97
|
+
return candidate;
|
|
98
|
+
} catch (_error) {
|
|
99
|
+
// Probe next candidate.
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return 'master';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function refreshBaseReference(exec = execFileSync, cwd = process.cwd(), remoteName, baseBranch) {
|
|
107
|
+
exec(
|
|
108
|
+
'git',
|
|
109
|
+
['fetch', '--quiet', '--no-tags', remoteName, `refs/heads/${baseBranch}:refs/remotes/${remoteName}/${baseBranch}`],
|
|
110
|
+
getQuietGitExecOptions(cwd),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function getBranchReadiness(options = {}) {
|
|
115
|
+
const exec = options.exec || execFileSync;
|
|
116
|
+
const cwd = options.cwd || process.cwd();
|
|
117
|
+
const baseRemote = options.baseRemote || resolveBaseRemote(exec, cwd);
|
|
118
|
+
const baseBranch = options.baseBranch || resolveBaseBranch(exec, cwd, baseRemote);
|
|
119
|
+
const baseRef = `${baseRemote}/${baseBranch}`;
|
|
120
|
+
const branchName = exec('git', ['rev-parse', '--abbrev-ref', 'HEAD'], getGitExecOptions(cwd)).trim();
|
|
121
|
+
|
|
122
|
+
if (!branchName || branchName === 'HEAD') {
|
|
123
|
+
return {
|
|
124
|
+
ready: false,
|
|
125
|
+
branchName: branchName || 'HEAD',
|
|
126
|
+
baseRemote,
|
|
127
|
+
baseBranch,
|
|
128
|
+
error: 'Current HEAD is detached. Check out a feature branch before running /ship.',
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
refreshBaseReference(exec, cwd, baseRemote, baseBranch);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
return {
|
|
136
|
+
ready: false,
|
|
137
|
+
branchName,
|
|
138
|
+
baseRemote,
|
|
139
|
+
baseBranch,
|
|
140
|
+
error: `Unable to refresh ${baseRef} before comparing branch readiness. Verify that remote '${baseRemote}' and branch '${baseBranch}' can be fetched. Git said: ${error.message}`,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let counts;
|
|
145
|
+
try {
|
|
146
|
+
counts = exec('git', ['rev-list', '--left-right', '--count', `${baseRef}...HEAD`], getGitExecOptions(cwd)).trim();
|
|
147
|
+
} catch (error) {
|
|
148
|
+
return {
|
|
149
|
+
ready: false,
|
|
150
|
+
branchName,
|
|
151
|
+
baseRemote,
|
|
152
|
+
baseBranch,
|
|
153
|
+
error: `Unable to compare the current branch against ${baseRef}. Verify that remote '${baseRemote}' and branch '${baseBranch}' exist. Git said: ${error.message}`,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const [behindRaw = '0', aheadRaw = '0'] = counts.split(/\s+/);
|
|
158
|
+
const behind = Number.parseInt(behindRaw, 10) || 0;
|
|
159
|
+
const ahead = Number.parseInt(aheadRaw, 10) || 0;
|
|
160
|
+
|
|
161
|
+
let hasDiff = false;
|
|
162
|
+
try {
|
|
163
|
+
exec('git', ['diff', '--quiet', `${baseRef}...HEAD`, '--'], getGitExecOptions(cwd));
|
|
164
|
+
} catch (error) {
|
|
165
|
+
if (error.status === 1) {
|
|
166
|
+
hasDiff = true;
|
|
167
|
+
} else {
|
|
168
|
+
return {
|
|
169
|
+
ready: false,
|
|
170
|
+
branchName,
|
|
171
|
+
baseRemote,
|
|
172
|
+
baseBranch,
|
|
173
|
+
ahead,
|
|
174
|
+
behind,
|
|
175
|
+
error: `Unable to inspect the tree diff against ${baseRef}. Git said: ${error.message}`,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (!hasDiff) {
|
|
181
|
+
return {
|
|
182
|
+
ready: false,
|
|
183
|
+
branchName,
|
|
184
|
+
baseRemote,
|
|
185
|
+
baseBranch,
|
|
186
|
+
ahead,
|
|
187
|
+
behind,
|
|
188
|
+
error:
|
|
189
|
+
`Current branch ${branchName} has no diff against ${baseRef}. ` +
|
|
190
|
+
'It is not PR-ready because all changes are already upstream or the branch collapsed onto the base during rebase.',
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
ready: true,
|
|
196
|
+
branchName,
|
|
197
|
+
baseRemote,
|
|
198
|
+
baseBranch,
|
|
199
|
+
ahead,
|
|
200
|
+
behind,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
32
204
|
/**
|
|
33
205
|
* Validate feature slug format
|
|
34
206
|
* Ensures slug matches expected pattern and doesn't contain path traversal
|
|
@@ -256,11 +428,11 @@ function validatePRTitle(title) {
|
|
|
256
428
|
}
|
|
257
429
|
|
|
258
430
|
async function createPR(options) { // NOSONAR S3776
|
|
259
|
-
const { title, body, dryRun = false } = options;
|
|
431
|
+
const { title, body, dryRun = false, exec = execFileSync, cwd = process.cwd() } = options;
|
|
260
432
|
const titleValidation = validatePRTitle(title);
|
|
261
433
|
if (!titleValidation.valid) return { success: false, error: titleValidation.error };
|
|
262
434
|
try {
|
|
263
|
-
|
|
435
|
+
exec('gh', ['--version'], { ...getGhCheckOptions(), cwd }); // NOSONAR S4036 - hardcoded CLI command, no user input, developer tool context
|
|
264
436
|
} catch (error) {
|
|
265
437
|
if (isCommandNotFound(error)) {
|
|
266
438
|
return { success: false, error: 'GitHub CLI (gh) not found. Install from: https://cli.github.com/' };
|
|
@@ -272,22 +444,30 @@ async function createPR(options) { // NOSONAR S3776
|
|
|
272
444
|
return { success: false, error: `GitHub CLI check failed: ${error.message}` };
|
|
273
445
|
}
|
|
274
446
|
try {
|
|
275
|
-
|
|
447
|
+
exec('git', ['rev-parse', '--git-dir'], getGitExecOptions(cwd)); // NOSONAR S4036 - hardcoded CLI command, no user input, developer tool context
|
|
276
448
|
} catch (error_) { // NOSONAR S2486 - intentional: not-a-git-repo is the expected failure signal
|
|
277
449
|
void error_;
|
|
278
450
|
return { success: false, error: 'Not in a git repository. Initialize with: git init' };
|
|
279
451
|
}
|
|
280
452
|
try {
|
|
281
|
-
|
|
453
|
+
exec('git', ['remote', 'get-url', 'origin'], getGitExecOptions(cwd)); // NOSONAR S4036 - hardcoded CLI command, no user input, developer tool context
|
|
282
454
|
} catch (error_) { // NOSONAR S2486 - intentional: no-remote is the expected failure signal
|
|
283
455
|
void error_;
|
|
284
456
|
return { success: false, error: 'No git remote configured. Add with: git remote add origin <url>' };
|
|
285
457
|
}
|
|
458
|
+
try {
|
|
459
|
+
const readiness = getBranchReadiness({ exec, cwd });
|
|
460
|
+
if (!readiness.ready) {
|
|
461
|
+
return { success: false, error: readiness.error };
|
|
462
|
+
}
|
|
463
|
+
} catch (error) {
|
|
464
|
+
return { success: false, error: `Failed to verify branch readiness: ${error.message}` };
|
|
465
|
+
}
|
|
286
466
|
if (dryRun) {
|
|
287
467
|
return { success: true, message: '[DRY RUN] Would create PR with title: ' + title, prUrl: 'https://github.com/owner/repo/pull/1' };
|
|
288
468
|
}
|
|
289
469
|
try {
|
|
290
|
-
const result =
|
|
470
|
+
const result = exec('gh', ['pr', 'create', '--title', title, '--body', body], getGitExecOptions(cwd)); // NOSONAR S4036 - hardcoded CLI command, no user input, developer tool context
|
|
291
471
|
const urlMatch = /https:\/\/github\.com\/[^\s]+/.exec(result);
|
|
292
472
|
const prUrl = urlMatch ? urlMatch[0] : null;
|
|
293
473
|
const numberMatch = /\/pull\/(\d+)/.exec(result);
|
|
@@ -394,4 +574,7 @@ module.exports = {
|
|
|
394
574
|
validatePRTitle,
|
|
395
575
|
createPR,
|
|
396
576
|
executeShip,
|
|
577
|
+
getBranchReadiness,
|
|
578
|
+
resolveBaseRemote,
|
|
579
|
+
resolveBaseBranch,
|
|
397
580
|
};
|
package/lib/commands/status.js
CHANGED
|
@@ -3,12 +3,20 @@
|
|
|
3
3
|
* Detects workflow stage (1-9) with confidence scoring
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const { secureExecFileSync } = require('../shell-utils');
|
|
7
6
|
const {
|
|
8
7
|
readWorkflowState,
|
|
9
8
|
getAllowedTransitionsForWorkflowState,
|
|
10
9
|
} = require('../workflow/state');
|
|
11
10
|
const { STAGE_IDS } = require('../workflow/stages');
|
|
11
|
+
const { detectWorktree } = require('../detect-worktree');
|
|
12
|
+
const { readBeadsSnapshot } = require('../status/beads-snapshot');
|
|
13
|
+
const { formatZeroArgStatus } = require('../status/presenter');
|
|
14
|
+
const {
|
|
15
|
+
loadState,
|
|
16
|
+
extractWorkflowStateFromComments,
|
|
17
|
+
readWorkflowStateFromBeads,
|
|
18
|
+
} = require('../workflow/state-manager');
|
|
19
|
+
const { secureExecFileSync } = require('../shell-utils.js');
|
|
12
20
|
|
|
13
21
|
const WORKFLOW_STAGES = {
|
|
14
22
|
1: { name: 'Fresh Start', nextCommand: 'research' },
|
|
@@ -338,42 +346,156 @@ function parseStatusInputs(args = [], flags = {}) {
|
|
|
338
346
|
issueId: flags.issueId || flags['--issue-id'] || getInlineValue('--issue-id') || getNextValue('--issue-id'),
|
|
339
347
|
workflowState: flags.workflowState || flags['--workflow-state'] || getInlineValue('--workflow-state') || getNextValue('--workflow-state'),
|
|
340
348
|
bdComments: flags.bdComments || flags['--bd-comments'] || getInlineValue('--bd-comments') || getNextValue('--bd-comments'),
|
|
349
|
+
projectRoot: flags.projectRoot || flags['--project-root'] || getInlineValue('--project-root') || getNextValue('--project-root') || null,
|
|
341
350
|
};
|
|
342
351
|
}
|
|
343
352
|
|
|
344
|
-
function
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
353
|
+
function runGitCommand(projectRoot, args) {
|
|
354
|
+
if (!projectRoot) {
|
|
355
|
+
return '';
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
try {
|
|
359
|
+
return secureExecFileSync('git', args, {
|
|
360
|
+
encoding: 'utf8',
|
|
361
|
+
cwd: projectRoot,
|
|
362
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
363
|
+
}).trim();
|
|
364
|
+
} catch (_error) {
|
|
365
|
+
return '';
|
|
348
366
|
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function detectRepoContext(projectRoot = process.cwd()) {
|
|
370
|
+
const worktree = detectWorktree(projectRoot);
|
|
371
|
+
const branch = worktree.branch || runGitCommand(projectRoot, ['branch', '--show-current']) || 'unknown';
|
|
372
|
+
const statusOutput = runGitCommand(projectRoot, ['status', '--short']);
|
|
373
|
+
const changedEntries = statusOutput ? statusOutput.split(/\r?\n/).filter(Boolean) : [];
|
|
374
|
+
const clean = changedEntries.length === 0;
|
|
375
|
+
const summary = clean ? 'clean' : `${changedEntries.length} uncommitted change${changedEntries.length === 1 ? '' : 's'}`;
|
|
349
376
|
|
|
350
|
-
|
|
351
|
-
|
|
377
|
+
return {
|
|
378
|
+
branch,
|
|
379
|
+
inWorktree: worktree.inWorktree === true,
|
|
380
|
+
worktreePath: worktree.currentWorktree || projectRoot,
|
|
381
|
+
mainWorktree: worktree.mainWorktree || projectRoot,
|
|
382
|
+
workingTree: {
|
|
383
|
+
clean,
|
|
384
|
+
summary,
|
|
385
|
+
},
|
|
386
|
+
};
|
|
352
387
|
}
|
|
353
388
|
|
|
354
|
-
function
|
|
389
|
+
function readIssueDetails(issueId, projectRoot = process.cwd()) {
|
|
355
390
|
if (!issueId) {
|
|
356
391
|
return null;
|
|
357
392
|
}
|
|
358
393
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
394
|
+
try {
|
|
395
|
+
const raw = secureExecFileSync('bd', ['show', issueId, '--json'], {
|
|
396
|
+
encoding: 'utf8',
|
|
397
|
+
cwd: projectRoot,
|
|
398
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
399
|
+
}).trim();
|
|
400
|
+
if (!raw) {
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
363
403
|
|
|
364
|
-
|
|
404
|
+
const parsed = JSON.parse(raw);
|
|
405
|
+
return Array.isArray(parsed) ? parsed[0] || null : parsed;
|
|
406
|
+
} catch (_error) {
|
|
365
407
|
return null;
|
|
366
408
|
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function extractDesignSlugs(design) {
|
|
412
|
+
if (typeof design !== 'string' || design.trim() === '') {
|
|
413
|
+
return [];
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const slugs = new Set();
|
|
417
|
+
const pattern = /docs[\\/]+plans[\\/]+\d{4}-\d{2}-\d{2}-([a-z0-9-]+?)-(?:design|tasks|decisions)\.md/g;
|
|
418
|
+
let match = pattern.exec(design);
|
|
419
|
+
while (match) {
|
|
420
|
+
slugs.add(match[1]);
|
|
421
|
+
match = pattern.exec(design);
|
|
422
|
+
}
|
|
367
423
|
|
|
368
|
-
return
|
|
424
|
+
return [...slugs];
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function designMatchesFeatureSlug(issue, featureSlug) {
|
|
428
|
+
if (!issue || !featureSlug) {
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return extractDesignSlugs(issue.design).includes(featureSlug);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function discoverCurrentIssue(snapshot, context, projectRoot = process.cwd()) {
|
|
436
|
+
const activeAssigned = snapshot.activeAssigned || [];
|
|
437
|
+
if (activeAssigned.length === 0) {
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const branchInfo = analyzeBranch(context.branch || 'master');
|
|
442
|
+
const hydratedIssues = new Map();
|
|
443
|
+
|
|
444
|
+
function hydrateIssue(issue) {
|
|
445
|
+
if (!issue || !issue.id) {
|
|
446
|
+
return issue;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (hydratedIssues.has(issue.id)) {
|
|
450
|
+
return hydratedIssues.get(issue.id);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const details = issue.design && issue.comments ? issue : readIssueDetails(issue.id, projectRoot);
|
|
454
|
+
const hydratedIssue = details ? { ...issue, ...details } : issue;
|
|
455
|
+
hydratedIssues.set(issue.id, hydratedIssue);
|
|
456
|
+
return hydratedIssue;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (branchInfo.featureSlug) {
|
|
460
|
+
const slugMatches = [];
|
|
461
|
+
|
|
462
|
+
for (const issue of activeAssigned) {
|
|
463
|
+
const candidate = issue.design ? issue : hydrateIssue(issue);
|
|
464
|
+
if (designMatchesFeatureSlug(candidate, branchInfo.featureSlug)) {
|
|
465
|
+
slugMatches.push(candidate);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (slugMatches.length === 1) {
|
|
470
|
+
return hydrateIssue(slugMatches[0]);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (slugMatches.length > 1) {
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (activeAssigned.length === 1) {
|
|
479
|
+
return hydrateIssue(activeAssigned[0]);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return null;
|
|
369
483
|
}
|
|
370
484
|
|
|
371
485
|
function resolveWorkflowState(inputs) {
|
|
372
486
|
try {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
487
|
+
if (inputs.workflowState) {
|
|
488
|
+
return { workflowState: readWorkflowState(inputs.workflowState), fallbackReason: null };
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const { state } = loadState(inputs.projectRoot, {
|
|
492
|
+
issueId: inputs.issueId,
|
|
493
|
+
issue: inputs.issue,
|
|
494
|
+
comments: inputs.bdComments,
|
|
495
|
+
preferBeads: inputs.preferBeads,
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
return { workflowState: state, fallbackReason: null };
|
|
377
499
|
} catch (error) {
|
|
378
500
|
return {
|
|
379
501
|
workflowState: null,
|
|
@@ -512,10 +634,53 @@ function formatStatus(result) {
|
|
|
512
634
|
module.exports = {
|
|
513
635
|
name: 'status',
|
|
514
636
|
description: 'Intelligent stage detection with confidence scoring',
|
|
515
|
-
handler: async (args, flags,
|
|
637
|
+
handler: async (args, flags, projectRoot) => {
|
|
516
638
|
const inputs = parseStatusInputs(args, flags);
|
|
639
|
+
const isZeroArgStatus = !inputs.issueId && !inputs.workflowState && !inputs.bdComments;
|
|
640
|
+
inputs.preferBeads = !isZeroArgStatus && Boolean(inputs.issueId || inputs.bdComments);
|
|
641
|
+
if (!inputs.projectRoot && projectRoot) {
|
|
642
|
+
inputs.projectRoot = projectRoot;
|
|
643
|
+
}
|
|
644
|
+
const effectiveProjectRoot = inputs.projectRoot || process.cwd();
|
|
645
|
+
let context = null;
|
|
646
|
+
let snapshot = null;
|
|
647
|
+
|
|
648
|
+
if (isZeroArgStatus) {
|
|
649
|
+
context = detectRepoContext(effectiveProjectRoot);
|
|
650
|
+
snapshot = readBeadsSnapshot(effectiveProjectRoot);
|
|
651
|
+
const discoveredIssue = discoverCurrentIssue(snapshot, context, effectiveProjectRoot);
|
|
652
|
+
if (discoveredIssue) {
|
|
653
|
+
inputs.issueId = discoveredIssue.id;
|
|
654
|
+
inputs.issue = discoveredIssue;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
517
658
|
const { workflowState, fallbackReason } = resolveWorkflowState(inputs);
|
|
518
659
|
|
|
660
|
+
if (isZeroArgStatus) {
|
|
661
|
+
if (workflowState) {
|
|
662
|
+
const result = buildAuthoritativeStatus(workflowState);
|
|
663
|
+
return {
|
|
664
|
+
success: true,
|
|
665
|
+
context,
|
|
666
|
+
snapshot,
|
|
667
|
+
issueId: inputs.issueId,
|
|
668
|
+
output: formatZeroArgStatus({ context, snapshot, workflowResult: result }),
|
|
669
|
+
...result,
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
return {
|
|
674
|
+
success: true,
|
|
675
|
+
context,
|
|
676
|
+
snapshot,
|
|
677
|
+
issueId: inputs.issueId,
|
|
678
|
+
missingWorkflowState: true,
|
|
679
|
+
output: formatZeroArgStatus({ context, snapshot }),
|
|
680
|
+
...(fallbackReason ? { fallbackReason } : {}),
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
|
|
519
684
|
if (workflowState) {
|
|
520
685
|
const result = buildAuthoritativeStatus(workflowState);
|
|
521
686
|
return { success: true, output: formatStatus(result), ...result };
|
|
@@ -534,6 +699,8 @@ module.exports = {
|
|
|
534
699
|
parseStatusInputs,
|
|
535
700
|
readWorkflowStateFromBeads,
|
|
536
701
|
detectStage,
|
|
702
|
+
detectRepoContext,
|
|
703
|
+
discoverCurrentIssue,
|
|
537
704
|
analyzeBranch,
|
|
538
705
|
analyzeFiles,
|
|
539
706
|
analyzePR,
|
|
@@ -541,4 +708,5 @@ module.exports = {
|
|
|
541
708
|
analyzeBeads,
|
|
542
709
|
calculateConfidence,
|
|
543
710
|
formatStatus,
|
|
711
|
+
extractDesignSlugs,
|
|
544
712
|
};
|
package/lib/commands/team.js
CHANGED
|
@@ -34,4 +34,14 @@ function handleTeam(args) {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
async function handler(args = []) {
|
|
38
|
+
handleTeam(args);
|
|
39
|
+
return { success: true };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = {
|
|
43
|
+
name: 'team',
|
|
44
|
+
description: 'Team coordination workflows and sync utilities',
|
|
45
|
+
handler,
|
|
46
|
+
handleTeam
|
|
47
|
+
};
|