edsger 0.19.15 → 0.19.17

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 (95) hide show
  1. package/dist/phases/code-implementation/index.js +25 -1
  2. package/dist/phases/code-refine/index.js +25 -1
  3. package/dist/phases/code-review/index.js +25 -1
  4. package/dist/services/branches.d.ts +8 -4
  5. package/dist/services/branches.js +29 -4
  6. package/dist/utils/git-branch-manager.d.ts +2 -2
  7. package/dist/utils/git-branch-manager.js +2 -3
  8. package/package.json +1 -1
  9. package/.claude/settings.local.json +0 -28
  10. package/dist/api/features/__tests__/regression-prevention.test.d.ts +0 -5
  11. package/dist/api/features/__tests__/regression-prevention.test.js +0 -338
  12. package/dist/api/features/__tests__/status-updater.integration.test.d.ts +0 -5
  13. package/dist/api/features/__tests__/status-updater.integration.test.js +0 -497
  14. package/dist/commands/workflow/pipeline-runner.d.ts +0 -17
  15. package/dist/commands/workflow/pipeline-runner.js +0 -393
  16. package/dist/commands/workflow/runner.d.ts +0 -26
  17. package/dist/commands/workflow/runner.js +0 -119
  18. package/dist/commands/workflow/workflow-runner.d.ts +0 -26
  19. package/dist/commands/workflow/workflow-runner.js +0 -119
  20. package/dist/phases/code-implementation/analyzer-helpers.d.ts +0 -28
  21. package/dist/phases/code-implementation/analyzer-helpers.js +0 -177
  22. package/dist/phases/code-implementation/analyzer.d.ts +0 -32
  23. package/dist/phases/code-implementation/analyzer.js +0 -629
  24. package/dist/phases/code-implementation/context-fetcher.d.ts +0 -17
  25. package/dist/phases/code-implementation/context-fetcher.js +0 -86
  26. package/dist/phases/code-implementation/mcp-server.d.ts +0 -1
  27. package/dist/phases/code-implementation/mcp-server.js +0 -93
  28. package/dist/phases/code-implementation/prompts-improvement.d.ts +0 -5
  29. package/dist/phases/code-implementation/prompts-improvement.js +0 -108
  30. package/dist/phases/code-implementation-verification/verifier.d.ts +0 -31
  31. package/dist/phases/code-implementation-verification/verifier.js +0 -196
  32. package/dist/phases/code-refine/analyzer.d.ts +0 -41
  33. package/dist/phases/code-refine/analyzer.js +0 -561
  34. package/dist/phases/code-refine/context-fetcher.d.ts +0 -94
  35. package/dist/phases/code-refine/context-fetcher.js +0 -423
  36. package/dist/phases/code-refine-verification/analysis/llm-analyzer.d.ts +0 -22
  37. package/dist/phases/code-refine-verification/analysis/llm-analyzer.js +0 -134
  38. package/dist/phases/code-refine-verification/verifier.d.ts +0 -47
  39. package/dist/phases/code-refine-verification/verifier.js +0 -597
  40. package/dist/phases/code-review/analyzer.d.ts +0 -29
  41. package/dist/phases/code-review/analyzer.js +0 -363
  42. package/dist/phases/code-review/context-fetcher.d.ts +0 -92
  43. package/dist/phases/code-review/context-fetcher.js +0 -296
  44. package/dist/phases/feature-analysis/analyzer-helpers.d.ts +0 -10
  45. package/dist/phases/feature-analysis/analyzer-helpers.js +0 -47
  46. package/dist/phases/feature-analysis/analyzer.d.ts +0 -11
  47. package/dist/phases/feature-analysis/analyzer.js +0 -208
  48. package/dist/phases/feature-analysis/context-fetcher.d.ts +0 -26
  49. package/dist/phases/feature-analysis/context-fetcher.js +0 -134
  50. package/dist/phases/feature-analysis/http-fallback.d.ts +0 -20
  51. package/dist/phases/feature-analysis/http-fallback.js +0 -95
  52. package/dist/phases/feature-analysis/mcp-server.d.ts +0 -1
  53. package/dist/phases/feature-analysis/mcp-server.js +0 -144
  54. package/dist/phases/feature-analysis/prompts-improvement.d.ts +0 -8
  55. package/dist/phases/feature-analysis/prompts-improvement.js +0 -109
  56. package/dist/phases/feature-analysis-verification/verifier.d.ts +0 -37
  57. package/dist/phases/feature-analysis-verification/verifier.js +0 -147
  58. package/dist/phases/technical-design/analyzer-helpers.d.ts +0 -25
  59. package/dist/phases/technical-design/analyzer-helpers.js +0 -39
  60. package/dist/phases/technical-design/analyzer.d.ts +0 -21
  61. package/dist/phases/technical-design/analyzer.js +0 -461
  62. package/dist/phases/technical-design/context-fetcher.d.ts +0 -12
  63. package/dist/phases/technical-design/context-fetcher.js +0 -39
  64. package/dist/phases/technical-design/http-fallback.d.ts +0 -17
  65. package/dist/phases/technical-design/http-fallback.js +0 -151
  66. package/dist/phases/technical-design/mcp-server.d.ts +0 -1
  67. package/dist/phases/technical-design/mcp-server.js +0 -157
  68. package/dist/phases/technical-design/prompts-improvement.d.ts +0 -5
  69. package/dist/phases/technical-design/prompts-improvement.js +0 -93
  70. package/dist/phases/technical-design-verification/verifier.d.ts +0 -53
  71. package/dist/phases/technical-design-verification/verifier.js +0 -170
  72. package/dist/services/feature-branches.d.ts +0 -77
  73. package/dist/services/feature-branches.js +0 -205
  74. package/dist/workflow-runner/config/phase-configs.d.ts +0 -5
  75. package/dist/workflow-runner/config/phase-configs.js +0 -120
  76. package/dist/workflow-runner/core/feature-filter.d.ts +0 -16
  77. package/dist/workflow-runner/core/feature-filter.js +0 -46
  78. package/dist/workflow-runner/core/index.d.ts +0 -8
  79. package/dist/workflow-runner/core/index.js +0 -12
  80. package/dist/workflow-runner/core/pipeline-evaluator.d.ts +0 -24
  81. package/dist/workflow-runner/core/pipeline-evaluator.js +0 -32
  82. package/dist/workflow-runner/core/state-manager.d.ts +0 -24
  83. package/dist/workflow-runner/core/state-manager.js +0 -42
  84. package/dist/workflow-runner/core/workflow-logger.d.ts +0 -20
  85. package/dist/workflow-runner/core/workflow-logger.js +0 -65
  86. package/dist/workflow-runner/executors/phase-executor.d.ts +0 -8
  87. package/dist/workflow-runner/executors/phase-executor.js +0 -248
  88. package/dist/workflow-runner/feature-workflow-runner.d.ts +0 -26
  89. package/dist/workflow-runner/feature-workflow-runner.js +0 -119
  90. package/dist/workflow-runner/index.d.ts +0 -2
  91. package/dist/workflow-runner/index.js +0 -2
  92. package/dist/workflow-runner/pipeline-runner.d.ts +0 -17
  93. package/dist/workflow-runner/pipeline-runner.js +0 -393
  94. package/dist/workflow-runner/workflow-processor.d.ts +0 -54
  95. package/dist/workflow-runner/workflow-processor.js +0 -170
@@ -6,7 +6,7 @@ import { fetchCodeImplementationContext, formatContextForPrompt, } from './conte
6
6
  import { logFeaturePhaseEvent } from '../../services/audit-logs.js';
7
7
  import { buildImplementationResult, buildVerificationFailureResult, buildNoResultsError, } from './outcome.js';
8
8
  import { performVerificationCycle } from '../code-implementation-verification/index.js';
9
- import { prepareCustomBranchGitEnvironment, } from '../../utils/git-branch-manager.js';
9
+ import { prepareCustomBranchGitEnvironment, syncFeatBranchWithMain, } from '../../utils/git-branch-manager.js';
10
10
  import { getCurrentBranch, updateBranch, getBranches, getBaseBranchInfo, } from '../../services/branches.js';
11
11
  import { createBranchPullRequest, } from './branch-pr-creator.js';
12
12
  import { getGitHubConfig } from '../../api/github.js';
@@ -99,6 +99,30 @@ export const implementFeatureCode = async (options, config, checklistContext) =>
99
99
  if (verbose && currentBranch) {
100
100
  logInfo(`🔄 Using dev branch for development: ${devBranchName}`);
101
101
  }
102
+ // Sync feat branch to main before rebase to ensure it's up to date
103
+ // This prevents the PR (dev → feat) from showing extra commits
104
+ if (devBranchName.startsWith('dev/')) {
105
+ try {
106
+ const githubConfig = await getGitHubConfig(featureId, verbose);
107
+ if (githubConfig.configured &&
108
+ githubConfig.token &&
109
+ githubConfig.owner &&
110
+ githubConfig.repo) {
111
+ const { devBranchToFeatBranch } = await import('./branch-pr-creator.js');
112
+ const featBranchName = devBranchToFeatBranch(devBranchName);
113
+ if (verbose) {
114
+ logInfo(`📥 Syncing ${featBranchName} with main before rebase...`);
115
+ }
116
+ await syncFeatBranchWithMain(featBranchName, githubConfig.token, githubConfig.owner, githubConfig.repo, 'main', verbose);
117
+ }
118
+ }
119
+ catch (error) {
120
+ // Don't fail the phase if sync fails
121
+ if (verbose) {
122
+ logInfo(`⚠️ Could not sync feat branch: ${error instanceof Error ? error.message : String(error)}`);
123
+ }
124
+ }
125
+ }
102
126
  const cleanupGit = prepareCustomBranchGitEnvironment(devBranchName, actualBaseBranch, verbose);
103
127
  try {
104
128
  // Fetch all required context information via MCP endpoints
@@ -9,8 +9,9 @@ import { execSync } from 'child_process';
9
9
  import { fetchCodeRefineContext, } from './context.js';
10
10
  import { getFeedbacksForPhase, formatFeedbacksForContext, } from '../../services/feedbacks.js';
11
11
  import { createSystemPrompt, createCodeRefinePrompt } from './prompts.js';
12
- import { preparePhaseGitEnvironmentAsync, prepareCustomBranchGitEnvironmentAsync, hasUncommittedChanges, getUncommittedFiles, } from '../../utils/git-branch-manager.js';
12
+ import { preparePhaseGitEnvironmentAsync, prepareCustomBranchGitEnvironmentAsync, hasUncommittedChanges, getUncommittedFiles, syncFeatBranchWithMain, } from '../../utils/git-branch-manager.js';
13
13
  import { getFeature } from '../../api/features/get-feature.js';
14
+ import { getGitHubConfig } from '../../api/github.js';
14
15
  import { getBranches, getBaseBranchInfo, updateBranch, } from '../../services/branches.js';
15
16
  import { verifyAndResolveComments, } from '../code-refine-verification/index.js';
16
17
  // Maximum number of refine + verification iterations
@@ -141,6 +142,29 @@ export const refineCodeFromPRFeedback = async (options, config) => {
141
142
  }
142
143
  }
143
144
  }
145
+ // Sync feat branch to main before rebase to ensure it's up to date
146
+ // This prevents the PR (dev → feat) from showing extra commits
147
+ if (branchName.startsWith('dev/')) {
148
+ try {
149
+ const githubConfig = await getGitHubConfig(featureId, verbose);
150
+ if (githubConfig.configured &&
151
+ githubConfig.token &&
152
+ githubConfig.owner &&
153
+ githubConfig.repo) {
154
+ const featBranchName = branchName.replace(/^dev\//, 'feat/');
155
+ if (verbose) {
156
+ logInfo(`📥 Syncing ${featBranchName} with main before rebase...`);
157
+ }
158
+ await syncFeatBranchWithMain(featBranchName, githubConfig.token, githubConfig.owner, githubConfig.repo, 'main', verbose);
159
+ }
160
+ }
161
+ catch (error) {
162
+ // Don't fail the phase if sync fails
163
+ if (verbose) {
164
+ logInfo(`⚠️ Could not sync feat branch: ${error instanceof Error ? error.message : String(error)}`);
165
+ }
166
+ }
167
+ }
144
168
  // Prepare git environment: switch to the appropriate branch and rebase from correct base
145
169
  // Use async version with automatic conflict resolution
146
170
  const gitEnvResult = currentBranch
@@ -7,9 +7,10 @@ import { logInfo, logError } from '../../utils/logger.js';
7
7
  import { Octokit } from '@octokit/rest';
8
8
  import { fetchCodeReviewContext, formatContextForPrompt, } from './context.js';
9
9
  import { getFeedbacksForPhase, formatFeedbacksForContext, } from '../../services/feedbacks.js';
10
- import { preparePhaseGitEnvironmentAsync, prepareCustomBranchGitEnvironmentAsync, } from '../../utils/git-branch-manager.js';
10
+ import { preparePhaseGitEnvironmentAsync, prepareCustomBranchGitEnvironmentAsync, syncFeatBranchWithMain, } from '../../utils/git-branch-manager.js';
11
11
  import { getBranches, getBaseBranchInfo, updateBranch, } from '../../services/branches.js';
12
12
  import { getFeature } from '../../api/features/get-feature.js';
13
+ import { getGitHubConfig } from '../../api/github.js';
13
14
  function userMessage(content) {
14
15
  return {
15
16
  type: 'user',
@@ -194,6 +195,29 @@ export const reviewPullRequest = async (options, config) => {
194
195
  }
195
196
  }
196
197
  }
198
+ // Sync feat branch to main before rebase to ensure it's up to date
199
+ // This prevents the PR (dev → feat) from showing extra commits
200
+ if (branchName.startsWith('dev/')) {
201
+ try {
202
+ const githubConfig = await getGitHubConfig(featureId, verbose);
203
+ if (githubConfig.configured &&
204
+ githubConfig.token &&
205
+ githubConfig.owner &&
206
+ githubConfig.repo) {
207
+ const featBranchName = branchName.replace(/^dev\//, 'feat/');
208
+ if (verbose) {
209
+ logInfo(`📥 Syncing ${featBranchName} with main before rebase...`);
210
+ }
211
+ await syncFeatBranchWithMain(featBranchName, githubConfig.token, githubConfig.owner, githubConfig.repo, 'main', verbose);
212
+ }
213
+ }
214
+ catch (error) {
215
+ // Don't fail the phase if sync fails
216
+ if (verbose) {
217
+ logInfo(`⚠️ Could not sync feat branch: ${error instanceof Error ? error.message : String(error)}`);
218
+ }
219
+ }
220
+ }
197
221
  // Prepare git environment: switch to the appropriate branch and rebase from correct base
198
222
  // Use async version with automatic conflict resolution
199
223
  const gitEnvResult = currentBranch
@@ -12,7 +12,7 @@ export interface Branch {
12
12
  base_branch_id: string | null;
13
13
  pull_request_url: string | null;
14
14
  pull_request_number: number | null;
15
- status: 'pending' | 'in_progress' | 'ready_for_review' | 'reviewed' | 'refined' | 'merged' | 'closed';
15
+ status: 'pending' | 'in_progress' | 'ready_for_review' | 'reviewed' | 'refined' | 'merged' | 'completed' | 'closed';
16
16
  created_at: string;
17
17
  updated_at: string;
18
18
  }
@@ -56,20 +56,24 @@ export declare function hasMultipleBranches(options: PipelinePhaseOptions): Prom
56
56
  */
57
57
  export declare function getNextPendingBranch(options: PipelinePhaseOptions): Promise<Branch | null>;
58
58
  /**
59
- * Check if all branches are completed
59
+ * Check if all branches are completed (feat merged to main)
60
60
  */
61
61
  export declare function allBranchesCompleted(options: PipelinePhaseOptions): Promise<boolean>;
62
62
  /**
63
63
  * Get the base branch information for a branch.
64
64
  * Returns:
65
65
  * - If base_branch_id is null: use main (first branch in chain)
66
+ * - If base_branch_id is set and that branch is completed: use main
67
+ * (completed means feat merged to main)
66
68
  * - If base_branch_id is set and that branch is merged: use base branch's feat branch
67
69
  * (merged means dev merged to feat, not to main)
68
70
  * - If base_branch_id is set and that branch is not merged: use base branch's dev branch
69
71
  *
70
- * When base branch is merged (squash merge), we need to use `git rebase --onto` to
72
+ * When base branch is merged/completed (squash merge), we need to use `git rebase --onto` to
71
73
  * correctly rebase only the new commits. This function returns both the new base
72
- * (feat branch) and the original base (dev branch) for this purpose.
74
+ * and the original base for this purpose:
75
+ * - completed: baseBranch=main, originalBaseBranch=feat/xxx
76
+ * - merged: baseBranch=feat/xxx, originalBaseBranch=dev/xxx
73
77
  */
74
78
  export declare function getBaseBranchInfo(branch: Branch, allBranches: Branch[], mainBranch?: string): Promise<{
75
79
  baseBranch: string;
@@ -97,6 +97,8 @@ export function formatBranchesForContext(branches) {
97
97
  }
98
98
  const getStatusEmoji = (status) => {
99
99
  switch (status) {
100
+ case 'completed':
101
+ return '🏁';
100
102
  case 'merged':
101
103
  return '✅';
102
104
  case 'refined':
@@ -148,25 +150,29 @@ export async function getNextPendingBranch(options) {
148
150
  return branches.find((b) => b.status === 'pending') || null;
149
151
  }
150
152
  /**
151
- * Check if all branches are completed
153
+ * Check if all branches are completed (feat merged to main)
152
154
  */
153
155
  export async function allBranchesCompleted(options) {
154
156
  const branches = await getBranches(options);
155
157
  if (branches.length === 0)
156
158
  return true;
157
- return branches.every((b) => b.status === 'merged' || b.status === 'closed');
159
+ return branches.every((b) => b.status === 'completed' || b.status === 'merged' || b.status === 'closed');
158
160
  }
159
161
  /**
160
162
  * Get the base branch information for a branch.
161
163
  * Returns:
162
164
  * - If base_branch_id is null: use main (first branch in chain)
165
+ * - If base_branch_id is set and that branch is completed: use main
166
+ * (completed means feat merged to main)
163
167
  * - If base_branch_id is set and that branch is merged: use base branch's feat branch
164
168
  * (merged means dev merged to feat, not to main)
165
169
  * - If base_branch_id is set and that branch is not merged: use base branch's dev branch
166
170
  *
167
- * When base branch is merged (squash merge), we need to use `git rebase --onto` to
171
+ * When base branch is merged/completed (squash merge), we need to use `git rebase --onto` to
168
172
  * correctly rebase only the new commits. This function returns both the new base
169
- * (feat branch) and the original base (dev branch) for this purpose.
173
+ * and the original base for this purpose:
174
+ * - completed: baseBranch=main, originalBaseBranch=feat/xxx
175
+ * - merged: baseBranch=feat/xxx, originalBaseBranch=dev/xxx
170
176
  */
171
177
  export async function getBaseBranchInfo(branch, allBranches, mainBranch = 'main') {
172
178
  // No base branch - start from main (first branch in chain)
@@ -187,6 +193,25 @@ export async function getBaseBranchInfo(branch, allBranches, mainBranch = 'main'
187
193
  baseBranchMerged: true,
188
194
  };
189
195
  }
196
+ // Check if base branch is completed (feat merged to main)
197
+ if (baseBranch.status === 'completed') {
198
+ // Base branch's feat is merged to main - rebase directly from main
199
+ // Use feat branch as originalBaseBranch for --onto
200
+ if (!baseBranch.branch_name) {
201
+ return {
202
+ baseBranch: mainBranch,
203
+ needsRebase: false,
204
+ baseBranchMerged: true,
205
+ };
206
+ }
207
+ const featBranchName = baseBranch.branch_name.replace(/^dev\//, 'feat/');
208
+ return {
209
+ baseBranch: mainBranch,
210
+ originalBaseBranch: featBranchName, // Use feat branch for --onto
211
+ needsRebase: true,
212
+ baseBranchMerged: true,
213
+ };
214
+ }
190
215
  // Check if base branch is merged (dev merged to feat)
191
216
  if (baseBranch.status === 'merged') {
192
217
  // Base branch's dev is merged to feat - rebase from base branch's feat branch
@@ -136,14 +136,14 @@ export declare function prepareCustomBranchGitEnvironment(featureBranch: string,
136
136
  * This ensures the feat branch (PR base) is up to date with main,
137
137
  * preventing extra commits from appearing in PRs when dev branch is rebased.
138
138
  *
139
- * @param featureId - The feature ID (will be used to construct branch name "feat/{featureId}")
139
+ * @param featBranch - The feat branch name (e.g., "feat/abc123/1-database")
140
140
  * @param githubToken - GitHub personal access token or app token
141
141
  * @param owner - Repository owner
142
142
  * @param repo - Repository name
143
143
  * @param baseBranch - The base branch to sync from (default: "main")
144
144
  * @param verbose - Whether to log verbose output
145
145
  */
146
- export declare function syncFeatBranchWithMain(featureId: string, githubToken: string, owner: string, repo: string, baseBranch?: string, verbose?: boolean): Promise<boolean>;
146
+ export declare function syncFeatBranchWithMain(featBranch: string, githubToken: string, owner: string, repo: string, baseBranch?: string, verbose?: boolean): Promise<boolean>;
147
147
  /**
148
148
  * Options for async rebase with conflict resolution
149
149
  */
@@ -528,15 +528,14 @@ export function prepareCustomBranchGitEnvironment(featureBranch, baseBranch = 'm
528
528
  * This ensures the feat branch (PR base) is up to date with main,
529
529
  * preventing extra commits from appearing in PRs when dev branch is rebased.
530
530
  *
531
- * @param featureId - The feature ID (will be used to construct branch name "feat/{featureId}")
531
+ * @param featBranch - The feat branch name (e.g., "feat/abc123/1-database")
532
532
  * @param githubToken - GitHub personal access token or app token
533
533
  * @param owner - Repository owner
534
534
  * @param repo - Repository name
535
535
  * @param baseBranch - The base branch to sync from (default: "main")
536
536
  * @param verbose - Whether to log verbose output
537
537
  */
538
- export async function syncFeatBranchWithMain(featureId, githubToken, owner, repo, baseBranch = 'main', verbose) {
539
- const featBranch = `feat/${featureId}`;
538
+ export async function syncFeatBranchWithMain(featBranch, githubToken, owner, repo, baseBranch = 'main', verbose) {
540
539
  try {
541
540
  const octokit = new Octokit({ auth: githubToken });
542
541
  // Check if feat branch exists
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edsger",
3
- "version": "0.19.15",
3
+ "version": "0.19.17",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "edsger": "dist/index.js"
@@ -1,28 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Read(//Users/steven/development/edsger/**)",
5
- "Bash(npm run build)",
6
- "Bash(node:*)",
7
- "Bash(git add:*)",
8
- "Bash(git commit:*)",
9
- "Bash(ls:*)",
10
- "Bash(cat:*)",
11
- "Bash(npm run typecheck:*)",
12
- "Bash(git diff:*)",
13
- "WebSearch",
14
- "WebFetch(domain:supabase.com)",
15
- "Bash(npm install:*)",
16
- "Bash(grep:*)",
17
- "Bash(npx supabase gen types typescript --help:*)",
18
- "Bash(git -C /Users/steven/development/edsger status)",
19
- "Bash(git -C /Users/steven/development/edsger diff)",
20
- "Bash(git -C /Users/steven/development/edsger log --oneline -5)",
21
- "Bash(git -C /Users/steven/development/edsger add supabase/migrations/20251231000000_drop_unused_views.sql)",
22
- "Bash(git -C /Users/steven/development/edsger commit -m \"$\\(cat <<''EOF''\nchore: drop unused database views\n\nRemove test_report_summary and user_stories_with_context views that are defined but never used in the application.\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
23
- "Bash(git -C /Users/steven/development/edsger commit -m \"$\\(cat <<''EOF''\nchore: drop unused database views\n\nRemove test_report_summary and user_stories_with_context views\nthat are defined but never used in the application.\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")"
24
- ],
25
- "deny": [],
26
- "ask": []
27
- }
28
- }
@@ -1,5 +0,0 @@
1
- /**
2
- * Specialized tests for status regression prevention
3
- * Focuses on the core business requirement: preventing backward status movement
4
- */
5
- export {};
@@ -1,338 +0,0 @@
1
- /**
2
- * Specialized tests for status regression prevention
3
- * Focuses on the core business requirement: preventing backward status movement
4
- */
5
- import { describe, it } from 'node:test';
6
- import assert from 'node:assert';
7
- import { isForwardProgression } from '../status-updater.js';
8
- import { STATUS_PROGRESSION_ORDER } from '../../../config/feature-status.js';
9
- describe('Regression Prevention Tests', () => {
10
- describe('Core Business Logic: Prevent Backlog Regression', () => {
11
- it('should prevent any status from regressing to backlog', () => {
12
- const allStatuses = STATUS_PROGRESSION_ORDER.slice(1); // All except backlog itself
13
- for (const status of allStatuses) {
14
- const result = isForwardProgression(status, 'backlog');
15
- assert.strictEqual(result, false, `Status ${status} should NOT be allowed to regress to backlog`);
16
- }
17
- });
18
- it('should allow progression from backlog to any other status', () => {
19
- const targetStatuses = STATUS_PROGRESSION_ORDER.slice(1); // All except backlog
20
- for (const targetStatus of targetStatuses) {
21
- const result = isForwardProgression('backlog', targetStatus);
22
- assert.strictEqual(result, true, `Should allow progression from backlog to ${targetStatus}`);
23
- }
24
- });
25
- it('should prevent regression from any status to ready_for_dev', () => {
26
- // Only backlog should be allowed to progress to ready_for_dev
27
- const higherStatuses = STATUS_PROGRESSION_ORDER.slice(2); // All except backlog and ready_for_dev
28
- for (const status of higherStatuses) {
29
- const result = isForwardProgression(status, 'ready_for_dev');
30
- assert.strictEqual(result, false, `Status ${status} should NOT regress to ready_for_dev`);
31
- }
32
- });
33
- it('should prevent all forms of backward progression in critical workflow', () => {
34
- // Test critical workflow progression points
35
- const criticalProgression = [
36
- 'backlog',
37
- 'ready_for_dev',
38
- 'feature_analysis',
39
- 'technical_design',
40
- 'code_implementation',
41
- 'functional_testing',
42
- 'deployment',
43
- 'shipped',
44
- ];
45
- // Test every possible backward combination
46
- for (let i = 0; i < criticalProgression.length; i++) {
47
- for (let j = 0; j < i; j++) {
48
- const currentStatus = criticalProgression[i];
49
- const targetStatus = criticalProgression[j];
50
- const result = isForwardProgression(currentStatus, targetStatus);
51
- assert.strictEqual(result, false, `Should prevent regression from ${currentStatus} to ${targetStatus}`);
52
- }
53
- }
54
- });
55
- });
56
- describe('CLI Phase Protection', () => {
57
- it('should identify scenarios where CLI phases could cause regression', () => {
58
- // Simulate CLI phases that could potentially cause issues
59
- const problematicScenarios = [
60
- {
61
- currentStatus: 'code_implementation',
62
- phase: 'feature-analysis',
63
- expectedTargetStatus: 'feature_analysis',
64
- description: 'CLI re-running feature analysis after code implementation',
65
- },
66
- {
67
- currentStatus: 'deployment',
68
- phase: 'technical-design',
69
- expectedTargetStatus: 'technical_design',
70
- description: 'CLI running technical design after deployment',
71
- },
72
- {
73
- currentStatus: 'shipped',
74
- phase: 'code-implementation',
75
- expectedTargetStatus: 'code_implementation',
76
- description: 'CLI attempting code implementation on shipped feature',
77
- },
78
- {
79
- currentStatus: 'testing_passed',
80
- phase: 'feature-analysis',
81
- expectedTargetStatus: 'feature_analysis',
82
- description: 'CLI re-analyzing feature after tests passed',
83
- },
84
- ];
85
- for (const scenario of problematicScenarios) {
86
- const result = isForwardProgression(scenario.currentStatus, scenario.expectedTargetStatus);
87
- assert.strictEqual(result, false, `Regression prevention should block: ${scenario.description}`);
88
- }
89
- });
90
- it('should allow valid CLI phase progressions', () => {
91
- const validScenarios = [
92
- {
93
- currentStatus: 'backlog',
94
- phase: 'feature-analysis',
95
- expectedTargetStatus: 'feature_analysis',
96
- description: 'CLI starting feature analysis from backlog',
97
- },
98
- {
99
- currentStatus: 'feature_analysis',
100
- phase: 'technical-design',
101
- expectedTargetStatus: 'technical_design',
102
- description: 'CLI progressing to technical design',
103
- },
104
- {
105
- currentStatus: 'technical_design',
106
- phase: 'code-implementation',
107
- expectedTargetStatus: 'code_implementation',
108
- description: 'CLI progressing to code implementation',
109
- },
110
- {
111
- currentStatus: 'functional_testing',
112
- phase: 'deployment',
113
- expectedTargetStatus: 'deployment',
114
- description: 'CLI progressing to deployment after testing',
115
- },
116
- ];
117
- for (const scenario of validScenarios) {
118
- const result = isForwardProgression(scenario.currentStatus, scenario.expectedTargetStatus);
119
- assert.strictEqual(result, true, `Valid progression should be allowed: ${scenario.description}`);
120
- }
121
- });
122
- });
123
- describe('Edge Cases and Boundary Conditions', () => {
124
- it('should handle same-status transitions correctly', () => {
125
- // Allow staying at the same status (for retries, re-runs, etc.)
126
- for (const status of STATUS_PROGRESSION_ORDER) {
127
- const result = isForwardProgression(status, status);
128
- assert.strictEqual(result, true, `Should allow staying at status ${status}`);
129
- }
130
- });
131
- it('should handle first and last status edge cases', () => {
132
- const firstStatus = STATUS_PROGRESSION_ORDER[0]; // 'backlog'
133
- const lastStatus = STATUS_PROGRESSION_ORDER[STATUS_PROGRESSION_ORDER.length - 1]; // 'shipped'
134
- // From first to last should be allowed
135
- const forwardResult = isForwardProgression(firstStatus, lastStatus);
136
- assert.strictEqual(forwardResult, true, 'Should allow progression from first to last status');
137
- // From last to first should be prevented
138
- const backwardResult = isForwardProgression(lastStatus, firstStatus);
139
- assert.strictEqual(backwardResult, false, 'Should prevent regression from last to first status');
140
- });
141
- it('should validate progression order is strictly enforced', () => {
142
- // Test every adjacent pair in the progression
143
- for (let i = 0; i < STATUS_PROGRESSION_ORDER.length - 1; i++) {
144
- const currentStatus = STATUS_PROGRESSION_ORDER[i];
145
- const nextStatus = STATUS_PROGRESSION_ORDER[i + 1];
146
- // Forward should be allowed
147
- const forwardResult = isForwardProgression(currentStatus, nextStatus);
148
- assert.strictEqual(forwardResult, true, `Should allow progression from ${currentStatus} to ${nextStatus}`);
149
- // Backward should be prevented
150
- const backwardResult = isForwardProgression(nextStatus, currentStatus);
151
- assert.strictEqual(backwardResult, false, `Should prevent regression from ${nextStatus} to ${currentStatus}`);
152
- }
153
- });
154
- it('should handle testing workflow regression scenarios', () => {
155
- // Testing has special rules - test specific edge cases
156
- const testingScenarios = [
157
- {
158
- from: 'testing_passed',
159
- to: 'testing_in_progress',
160
- shouldAllow: false,
161
- description: 'Should prevent going back from passed to in-progress',
162
- },
163
- {
164
- from: 'testing_passed',
165
- to: 'testing_failed',
166
- shouldAllow: true,
167
- description: 'Should allow going from passed to failed (possible if retested)',
168
- },
169
- {
170
- from: 'testing_failed',
171
- to: 'testing_passed',
172
- shouldAllow: false,
173
- description: 'Should prevent going from failed to passed (need to restart testing)',
174
- },
175
- {
176
- from: 'testing_in_progress',
177
- to: 'testing_failed',
178
- shouldAllow: true,
179
- description: 'Should allow marking in-progress tests as failed',
180
- },
181
- {
182
- from: 'testing_in_progress',
183
- to: 'testing_passed',
184
- shouldAllow: true,
185
- description: 'Should allow marking in-progress tests as passed',
186
- },
187
- ];
188
- for (const scenario of testingScenarios) {
189
- const result = isForwardProgression(scenario.from, scenario.to);
190
- assert.strictEqual(result, scenario.shouldAllow, scenario.description);
191
- }
192
- });
193
- it('should prevent skipping multiple phases backwards', () => {
194
- // Test skipping multiple phases in regression
195
- const skipBackwardTests = [
196
- {
197
- from: 'shipped',
198
- to: 'feature_analysis',
199
- skipCount: 'many phases',
200
- },
201
- {
202
- from: 'code_implementation',
203
- to: 'backlog',
204
- skipCount: 'many phases',
205
- },
206
- {
207
- from: 'deployment',
208
- to: 'ready_for_dev',
209
- skipCount: 'many phases',
210
- },
211
- ];
212
- for (const test of skipBackwardTests) {
213
- const result = isForwardProgression(test.from, test.to);
214
- assert.strictEqual(result, false, `Should prevent skipping ${test.skipCount} backwards from ${test.from} to ${test.to}`);
215
- }
216
- });
217
- it('should allow skipping multiple phases forward', () => {
218
- // Test skipping phases in forward direction (should be allowed)
219
- const skipForwardTests = [
220
- {
221
- from: 'backlog',
222
- to: 'code_implementation',
223
- description: 'Skip from backlog to code implementation',
224
- },
225
- {
226
- from: 'feature_analysis',
227
- to: 'deployment',
228
- description: 'Skip from analysis to deployment',
229
- },
230
- {
231
- from: 'technical_design',
232
- to: 'shipped',
233
- description: 'Skip from design to shipped',
234
- },
235
- ];
236
- for (const test of skipForwardTests) {
237
- const result = isForwardProgression(test.from, test.to);
238
- assert.strictEqual(result, true, `Should allow forward skip: ${test.description}`);
239
- }
240
- });
241
- });
242
- describe('Real-world Workflow Scenarios', () => {
243
- it('should handle hotfix scenario correctly', () => {
244
- // Hotfix: shipped feature needs bug fixing
245
- // Should allow progression to bug_fixing from shipped
246
- const result = isForwardProgression('shipped', 'bug_fixing');
247
- // Since bug_fixing comes before shipped in progression, this should be false
248
- assert.strictEqual(result, false, 'Should prevent regression from shipped to bug_fixing (hotfixes need new feature entries)');
249
- });
250
- it('should handle feature refinement scenarios', () => {
251
- // Feature refinement: going from code_implementation to code_refine
252
- const result1 = isForwardProgression('code_implementation', 'code_refine');
253
- assert.strictEqual(result1, true, 'Should allow progression from implementation to refinement');
254
- // But not the other way around
255
- const result2 = isForwardProgression('code_refine', 'code_implementation');
256
- assert.strictEqual(result2, false, 'Should prevent regression from refinement to implementation');
257
- });
258
- it('should handle review cycle scenarios', () => {
259
- // Review cycles: code_review -> pull_request -> code_review (if rejected)
260
- const reviewToPrep = isForwardProgression('code_review', 'pull_request');
261
- assert.strictEqual(reviewToPrep, true, 'Should allow progression from review to pull request');
262
- // But not back from pull_request to code_review
263
- const prepToReview = isForwardProgression('pull_request', 'code_review');
264
- assert.strictEqual(prepToReview, false, 'Should prevent regression from pull request to code review');
265
- });
266
- it('should handle deployment rollback scenarios', () => {
267
- // Deployment rollback: deployment -> functional_testing (to re-test)
268
- const deployToTest = isForwardProgression('deployment', 'functional_testing');
269
- assert.strictEqual(deployToTest, false, 'Should prevent regression from deployment to testing (rollbacks need new process)');
270
- });
271
- it('should validate complete workflow progression path', () => {
272
- // Simulate a complete feature lifecycle
273
- const completeWorkflow = [
274
- 'backlog',
275
- 'ready_for_dev',
276
- 'feature_analysis',
277
- 'feature_analysis_verification',
278
- 'technical_design',
279
- 'technical_design_verification',
280
- 'code_implementation',
281
- 'code_refine',
282
- 'code_review',
283
- 'pull_request',
284
- 'functional_testing',
285
- 'testing_in_progress',
286
- 'testing_passed',
287
- 'deployment',
288
- 'shipped',
289
- ];
290
- // Test each step in the workflow
291
- for (let i = 0; i < completeWorkflow.length - 1; i++) {
292
- const currentStatus = completeWorkflow[i];
293
- const nextStatus = completeWorkflow[i + 1];
294
- const result = isForwardProgression(currentStatus, nextStatus);
295
- assert.strictEqual(result, true, `Workflow step ${i + 1}: Should allow progression from ${currentStatus} to ${nextStatus}`);
296
- }
297
- // Test that you can't go backwards at any point in the workflow
298
- for (let i = 1; i < completeWorkflow.length; i++) {
299
- const currentStatus = completeWorkflow[i];
300
- const previousStatus = completeWorkflow[i - 1];
301
- const result = isForwardProgression(currentStatus, previousStatus);
302
- assert.strictEqual(result, false, `Workflow regression check ${i}: Should prevent regression from ${currentStatus} to ${previousStatus}`);
303
- }
304
- });
305
- });
306
- describe('Status Progression Order Validation', () => {
307
- it('should ensure progression order matches business workflow', () => {
308
- // Verify that the progression order makes business sense
309
- const criticalStatusPositions = {
310
- backlog: STATUS_PROGRESSION_ORDER.indexOf('backlog'),
311
- ready_for_dev: STATUS_PROGRESSION_ORDER.indexOf('ready_for_dev'),
312
- feature_analysis: STATUS_PROGRESSION_ORDER.indexOf('feature_analysis'),
313
- technical_design: STATUS_PROGRESSION_ORDER.indexOf('technical_design'),
314
- code_implementation: STATUS_PROGRESSION_ORDER.indexOf('code_implementation'),
315
- functional_testing: STATUS_PROGRESSION_ORDER.indexOf('functional_testing'),
316
- deployment: STATUS_PROGRESSION_ORDER.indexOf('deployment'),
317
- shipped: STATUS_PROGRESSION_ORDER.indexOf('shipped'),
318
- };
319
- // Verify logical ordering
320
- assert.ok(criticalStatusPositions.backlog < criticalStatusPositions.ready_for_dev, 'Backlog should come before ready_for_dev');
321
- assert.ok(criticalStatusPositions.ready_for_dev < criticalStatusPositions.feature_analysis, 'Ready for dev should come before feature analysis');
322
- assert.ok(criticalStatusPositions.feature_analysis < criticalStatusPositions.technical_design, 'Feature analysis should come before technical design');
323
- assert.ok(criticalStatusPositions.technical_design < criticalStatusPositions.code_implementation, 'Technical design should come before code implementation');
324
- assert.ok(criticalStatusPositions.code_implementation < criticalStatusPositions.functional_testing, 'Code implementation should come before functional testing');
325
- assert.ok(criticalStatusPositions.functional_testing < criticalStatusPositions.deployment, 'Functional testing should come before deployment');
326
- assert.ok(criticalStatusPositions.deployment < criticalStatusPositions.shipped, 'Deployment should come before shipped');
327
- });
328
- it('should prevent CLI from causing any backward movement', () => {
329
- // This test specifically addresses the original issue:
330
- // "After running CLI, in some phases, it update feature status to 'Backlog'"
331
- // No matter what the current status is, CLI should NEVER be able to set it to backlog
332
- for (const currentStatus of STATUS_PROGRESSION_ORDER.slice(1)) {
333
- const result = isForwardProgression(currentStatus, 'backlog');
334
- assert.strictEqual(result, false, `CLI should NEVER be able to regress ${currentStatus} to backlog`);
335
- }
336
- });
337
- });
338
- });