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.
Files changed (135) hide show
  1. package/.claude/commands/dev.md +2 -2
  2. package/.claude/commands/plan.md +2 -2
  3. package/.claude/commands/premerge.md +2 -2
  4. package/.claude/commands/review.md +5 -2
  5. package/.claude/commands/ship.md +6 -5
  6. package/.claude/commands/status.md +4 -4
  7. package/.claude/rules/greptile-review-process.md +4 -4
  8. package/.cline/workflows/dev.md +2 -2
  9. package/.cline/workflows/plan.md +2 -2
  10. package/.cline/workflows/premerge.md +2 -2
  11. package/.cline/workflows/review.md +5 -2
  12. package/.cline/workflows/ship.md +6 -5
  13. package/.cline/workflows/status.md +4 -4
  14. package/.codex/skills/dev/SKILL.md +2 -2
  15. package/.codex/skills/plan/SKILL.md +2 -2
  16. package/.codex/skills/premerge/SKILL.md +2 -2
  17. package/.codex/skills/review/SKILL.md +5 -2
  18. package/.codex/skills/ship/SKILL.md +6 -5
  19. package/.codex/skills/status/SKILL.md +4 -4
  20. package/.cursor/commands/dev.md +2 -2
  21. package/.cursor/commands/plan.md +2 -2
  22. package/.cursor/commands/premerge.md +2 -2
  23. package/.cursor/commands/review.md +5 -2
  24. package/.cursor/commands/ship.md +6 -5
  25. package/.cursor/commands/status.md +4 -4
  26. package/.github/prompts/dev.prompt.md +2 -2
  27. package/.github/prompts/plan.prompt.md +2 -2
  28. package/.github/prompts/premerge.prompt.md +2 -2
  29. package/.github/prompts/review.prompt.md +5 -2
  30. package/.github/prompts/ship.prompt.md +6 -5
  31. package/.github/prompts/status.prompt.md +4 -4
  32. package/.github/workflows/beads-to-github.yml +44 -11
  33. package/.github/workflows/github-to-beads.yml +11 -8
  34. package/.kilocode/workflows/dev.md +2 -2
  35. package/.kilocode/workflows/plan.md +2 -2
  36. package/.kilocode/workflows/premerge.md +2 -2
  37. package/.kilocode/workflows/review.md +5 -2
  38. package/.kilocode/workflows/ship.md +6 -5
  39. package/.kilocode/workflows/status.md +4 -4
  40. package/.opencode/commands/dev.md +2 -2
  41. package/.opencode/commands/plan.md +2 -2
  42. package/.opencode/commands/premerge.md +2 -2
  43. package/.opencode/commands/review.md +5 -2
  44. package/.opencode/commands/ship.md +6 -5
  45. package/.opencode/commands/status.md +4 -4
  46. package/.roo/commands/dev.md +2 -2
  47. package/.roo/commands/plan.md +2 -2
  48. package/.roo/commands/premerge.md +2 -2
  49. package/.roo/commands/review.md +5 -2
  50. package/.roo/commands/ship.md +6 -5
  51. package/.roo/commands/status.md +4 -4
  52. package/AGENTS.md +10 -9
  53. package/CLAUDE.md +12 -0
  54. package/README.md +12 -6
  55. package/bin/forge.js +24 -8
  56. package/docs/BEADS_GITHUB_SYNC.md +32 -2
  57. package/docs/EXAMPLES.md +22 -22
  58. package/docs/ROADMAP.md +3 -3
  59. package/docs/TOOLCHAIN.md +150 -160
  60. package/lib/agents/codex.plugin.json +3 -0
  61. package/lib/agents-config.js +18 -12
  62. package/lib/beads-bootstrap.js +225 -0
  63. package/lib/beads-health-check.js +55 -10
  64. package/lib/beads-setup.js +104 -28
  65. package/lib/beads-sync-scaffold.js +11 -6
  66. package/lib/codex-skills.js +54 -1
  67. package/lib/commands/_issue.js +11 -1
  68. package/lib/commands/issues.js +49 -0
  69. package/lib/commands/plan.js +5 -2
  70. package/lib/commands/recommend.js +22 -1
  71. package/lib/commands/setup.js +247 -27
  72. package/lib/commands/ship.js +188 -5
  73. package/lib/commands/status.js +187 -19
  74. package/lib/commands/team.js +11 -1
  75. package/lib/commands/test.js +125 -25
  76. package/lib/commands/validate.js +232 -9
  77. package/lib/commands/worktree.js +27 -54
  78. package/lib/dep-guard/keyword-ripple.js +184 -0
  79. package/lib/detect-worktree.js +9 -10
  80. package/lib/forge-issues.js +326 -0
  81. package/lib/issue-sync/authority.js +100 -0
  82. package/lib/issue-sync/github-pull.js +184 -0
  83. package/lib/issue-sync/import-primitives.js +98 -0
  84. package/lib/issue-sync/legacy-link-bridge.js +436 -0
  85. package/lib/issue-sync/link-store.js +292 -0
  86. package/lib/issue-sync/project-github.js +123 -0
  87. package/lib/issue-sync/reconcile.js +195 -0
  88. package/lib/issue-sync/schema.js +126 -0
  89. package/lib/lefthook-check.js +5 -2
  90. package/lib/project-memory.js +564 -0
  91. package/lib/runtime-health.js +100 -12
  92. package/lib/setup-action-log.js +2 -0
  93. package/lib/setup-summary-renderer.js +15 -11
  94. package/lib/smart-status/conflicts.js +205 -0
  95. package/lib/smart-status/scoring.js +177 -0
  96. package/lib/status/beads-snapshot.js +102 -0
  97. package/lib/status/presenter.js +65 -0
  98. package/lib/workflow/enforce-stage.js +14 -8
  99. package/lib/workflow/state-manager.js +349 -0
  100. package/package.json +12 -4
  101. package/scripts/beads-context.sh +124 -5
  102. package/scripts/beads-context.test.js +21 -4
  103. package/scripts/beads-migrate-to-dolt.sh +7 -0
  104. package/scripts/beads-upgrade-smoke.sh +263 -0
  105. package/scripts/behavioral-judge.sh +115 -11
  106. package/scripts/benchmark.js +349 -63
  107. package/scripts/dep-guard-analyze.js +52 -17
  108. package/scripts/dep-guard-keyword-ripple.js +29 -0
  109. package/scripts/dep-guard-render-review.js +86 -0
  110. package/scripts/dep-guard.sh +45 -222
  111. package/scripts/forge-team/lib/hooks.sh +1 -1
  112. package/scripts/forge-team/lib/sync-github.sh +160 -28
  113. package/scripts/forge-team/lib/verify.sh +1 -1
  114. package/scripts/forge-team/lib/workload.sh +56 -27
  115. package/scripts/forge-team/tests/sync-github.test.sh +195 -58
  116. package/scripts/forge-team/tests/workload.test.sh +35 -4
  117. package/scripts/github-beads-sync/index.mjs +122 -98
  118. package/scripts/github-beads-sync/mapping.mjs +54 -0
  119. package/scripts/github-beads-sync/reverse-sync-cli.mjs +2 -2
  120. package/scripts/github-beads-sync/reverse-sync.mjs +31 -7
  121. package/scripts/github-beads-sync/run-bd.mjs +4 -2
  122. package/scripts/lib/beads-migrate-to-dolt.mjs +503 -0
  123. package/scripts/preflight.sh +181 -0
  124. package/scripts/smart-status-score.js +31 -0
  125. package/scripts/smart-status-sessions.js +51 -0
  126. package/scripts/smart-status.sh +84 -330
  127. package/scripts/sync-agentic-workflow.js +48 -0
  128. package/scripts/sync-utils.sh +39 -0
  129. package/scripts/test-ci-shard.js +244 -0
  130. package/scripts/test-dashboard.js +188 -52
  131. package/scripts/test-full-suite.js +186 -0
  132. package/scripts/test-profile.js +278 -0
  133. package/scripts/test.js +335 -38
  134. package/scripts/validate.js +143 -0
  135. package/scripts/validate.sh +18 -1
@@ -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
- execFileSync('gh', ['--version'], getGhCheckOptions()); // NOSONAR S4036 - hardcoded CLI command, no user input, developer tool context
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
- execFileSync('git', ['rev-parse', '--git-dir'], getExecOptions()); // NOSONAR S4036 - hardcoded CLI command, no user input, developer tool context
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
- execFileSync('git', ['remote', 'get-url', 'origin'], getExecOptions()); // NOSONAR S4036 - hardcoded CLI command, no user input, developer tool context
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 = execFileSync('gh', ['pr', 'create', '--title', title, '--body', body], getExecOptions()); // NOSONAR S4036 - hardcoded CLI command, no user input, developer tool context
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
  };
@@ -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 extractWorkflowStateFromComments(comments = '') {
345
- const matches = String(comments).match(/^WorkflowState:\s*(\{.*\})$/gm);
346
- if (!matches || matches.length === 0) {
347
- return null;
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
- const latest = matches.at(-1).replace(/^WorkflowState:\s*/, '');
351
- return readWorkflowState(latest);
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 readWorkflowStateFromBeads(issueId, options = {}) {
389
+ function readIssueDetails(issueId, projectRoot = process.cwd()) {
355
390
  if (!issueId) {
356
391
  return null;
357
392
  }
358
393
 
359
- const comments = options.comments || secureExecFileSync('bd', ['comments', 'list', issueId], {
360
- encoding: 'utf8',
361
- stdio: ['pipe', 'pipe', 'pipe'],
362
- }).trim();
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
- if (!comments) {
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 extractWorkflowStateFromComments(comments);
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
- const workflowState = inputs.workflowState
374
- ? readWorkflowState(inputs.workflowState)
375
- : readWorkflowStateFromBeads(inputs.issueId, { comments: inputs.bdComments });
376
- return { workflowState, fallbackReason: null };
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, _projectRoot) => {
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
  };
@@ -34,4 +34,14 @@ function handleTeam(args) {
34
34
  }
35
35
  }
36
36
 
37
- module.exports = { handleTeam };
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
+ };