edsger 0.54.0 → 0.55.0

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 (142) hide show
  1. package/README.md +20 -0
  2. package/dist/api/financing.d.ts +47 -0
  3. package/dist/api/financing.js +37 -0
  4. package/dist/api/issues/approval-checker.d.ts +11 -9
  5. package/dist/api/issues/approval-checker.js +30 -41
  6. package/dist/api/issues/status-updater.d.ts +47 -20
  7. package/dist/api/issues/status-updater.js +114 -46
  8. package/dist/api/issues/update-issue.d.ts +5 -0
  9. package/dist/api/issues/update-issue.js +6 -0
  10. package/dist/commands/agent-workflow/processor.js +5 -1
  11. package/dist/commands/checklists/index.d.ts +5 -2
  12. package/dist/commands/checklists/index.js +73 -12
  13. package/dist/commands/checklists/tools.d.ts +14 -7
  14. package/dist/commands/checklists/tools.js +15 -208
  15. package/dist/commands/financing-deck/index.d.ts +8 -0
  16. package/dist/commands/financing-deck/index.js +66 -0
  17. package/dist/commands/find-architecture/index.d.ts +13 -0
  18. package/dist/commands/find-architecture/index.js +41 -0
  19. package/dist/commands/issue-analysis/index.d.ts +5 -0
  20. package/dist/commands/issue-analysis/index.js +9 -0
  21. package/dist/commands/sync-github-issues/index.d.ts +11 -0
  22. package/dist/commands/sync-github-issues/index.js +42 -0
  23. package/dist/commands/sync-sentry-issues/index.d.ts +14 -0
  24. package/dist/commands/sync-sentry-issues/index.js +73 -0
  25. package/dist/commands/technical-design/index.d.ts +5 -0
  26. package/dist/commands/technical-design/index.js +9 -0
  27. package/dist/commands/test-cases-analysis/index.d.ts +5 -0
  28. package/dist/commands/test-cases-analysis/index.js +9 -0
  29. package/dist/commands/user-stories-analysis/index.d.ts +5 -0
  30. package/dist/commands/user-stories-analysis/index.js +9 -0
  31. package/dist/commands/workflow/executors/phase-executor.js +6 -4
  32. package/dist/commands/workflow/phase-orchestrator.js +0 -1
  33. package/dist/config/issue-status.d.ts +18 -45
  34. package/dist/config/issue-status.js +21 -107
  35. package/dist/index.js +176 -4
  36. package/dist/phases/app-store-generation/agent.js +2 -1
  37. package/dist/phases/app-store-generation/index.js +11 -3
  38. package/dist/phases/branch-planning/index.js +0 -1
  39. package/dist/phases/bug-fixing/analyzer.js +0 -1
  40. package/dist/phases/bug-fixing/mcp-server.d.ts +18 -1
  41. package/dist/phases/bug-fixing/mcp-server.js +19 -76
  42. package/dist/phases/chat-processor/product-tools.d.ts +5 -8
  43. package/dist/phases/chat-processor/product-tools.js +6 -512
  44. package/dist/phases/chat-processor/tools.d.ts +5 -9
  45. package/dist/phases/chat-processor/tools.js +6 -704
  46. package/dist/phases/code-implementation/index.js +0 -1
  47. package/dist/phases/code-implementation-verification/agent.js +6 -1
  48. package/dist/phases/code-refine/index.js +0 -1
  49. package/dist/phases/code-refine/refine-iteration.js +2 -1
  50. package/dist/phases/code-review/index.js +0 -1
  51. package/dist/phases/code-testing/analyzer.js +0 -1
  52. package/dist/phases/financing-deck/agent.d.ts +1 -0
  53. package/dist/phases/financing-deck/agent.js +96 -0
  54. package/dist/phases/financing-deck/context.d.ts +13 -0
  55. package/dist/phases/financing-deck/context.js +69 -0
  56. package/dist/phases/financing-deck/index.d.ts +15 -0
  57. package/dist/phases/financing-deck/index.js +89 -0
  58. package/dist/phases/financing-deck/prompts.d.ts +2 -0
  59. package/dist/phases/financing-deck/prompts.js +94 -0
  60. package/dist/phases/find-architecture/index.d.ts +44 -0
  61. package/dist/phases/find-architecture/index.js +248 -0
  62. package/dist/phases/find-architecture/prompts.d.ts +31 -0
  63. package/dist/phases/find-architecture/prompts.js +128 -0
  64. package/dist/phases/find-architecture/state.d.ts +21 -0
  65. package/dist/phases/find-architecture/state.js +17 -0
  66. package/dist/phases/find-architecture/types.d.ts +55 -0
  67. package/dist/phases/find-architecture/types.js +69 -0
  68. package/dist/phases/find-bugs/index.js +13 -4
  69. package/dist/phases/find-features/index.js +10 -5
  70. package/dist/phases/find-smells/index.js +10 -3
  71. package/dist/phases/functional-testing/analyzer.js +27 -17
  72. package/dist/phases/functional-testing/http-fallback.d.ts +1 -1
  73. package/dist/phases/functional-testing/http-fallback.js +32 -16
  74. package/dist/phases/functional-testing/mcp-server.d.ts +9 -1
  75. package/dist/phases/functional-testing/mcp-server.js +13 -132
  76. package/dist/phases/growth-analysis/agent.js +2 -2
  77. package/dist/phases/growth-analysis/index.js +9 -3
  78. package/dist/phases/intelligence-analysis/agent.js +2 -2
  79. package/dist/phases/intelligence-analysis/index.js +9 -2
  80. package/dist/phases/issue-analysis/agent.d.ts +9 -1
  81. package/dist/phases/issue-analysis/agent.js +68 -27
  82. package/dist/phases/issue-analysis/context.d.ts +5 -9
  83. package/dist/phases/issue-analysis/context.js +31 -76
  84. package/dist/phases/issue-analysis/index.js +32 -84
  85. package/dist/phases/issue-analysis/outcome.d.ts +3 -33
  86. package/dist/phases/issue-analysis/outcome.js +15 -253
  87. package/dist/phases/issue-analysis/prompts.d.ts +3 -5
  88. package/dist/phases/issue-analysis/prompts.js +45 -158
  89. package/dist/phases/issue-analysis-verification/agent.d.ts +4 -4
  90. package/dist/phases/issue-analysis-verification/agent.js +5 -5
  91. package/dist/phases/issue-analysis-verification/index.d.ts +4 -2
  92. package/dist/phases/issue-analysis-verification/index.js +9 -22
  93. package/dist/phases/issue-analysis-verification/prompts.d.ts +1 -2
  94. package/dist/phases/issue-analysis-verification/prompts.js +21 -46
  95. package/dist/phases/output-contracts.js +66 -78
  96. package/dist/phases/pr-execution/index.js +2 -2
  97. package/dist/phases/pr-resolve/index.js +2 -8
  98. package/dist/phases/pr-splitting/index.js +2 -2
  99. package/dist/phases/release-sync/index.js +52 -43
  100. package/dist/phases/run-sheet/index.js +2 -1
  101. package/dist/phases/smoke-test/agent.js +2 -1
  102. package/dist/phases/smoke-test/index.js +4 -1
  103. package/dist/phases/sync-github-issues/index.d.ts +41 -0
  104. package/dist/phases/sync-github-issues/index.js +187 -0
  105. package/dist/phases/sync-github-issues/state.d.ts +26 -0
  106. package/dist/phases/sync-github-issues/state.js +18 -0
  107. package/dist/phases/sync-github-issues/types.d.ts +35 -0
  108. package/dist/phases/sync-github-issues/types.js +6 -0
  109. package/dist/phases/sync-sentry-issues/index.d.ts +29 -0
  110. package/dist/phases/sync-sentry-issues/index.js +153 -0
  111. package/dist/phases/sync-sentry-issues/sentry-client.d.ts +66 -0
  112. package/dist/phases/sync-sentry-issues/sentry-client.js +221 -0
  113. package/dist/phases/sync-sentry-issues/state.d.ts +23 -0
  114. package/dist/phases/sync-sentry-issues/state.js +18 -0
  115. package/dist/phases/sync-sentry-issues/types.d.ts +46 -0
  116. package/dist/phases/sync-sentry-issues/types.js +6 -0
  117. package/dist/phases/sync-shared/mcp.d.ts +81 -0
  118. package/dist/phases/sync-shared/mcp.js +111 -0
  119. package/dist/phases/technical-design/index.js +0 -1
  120. package/dist/phases/test-cases-analysis/agent.js +2 -1
  121. package/dist/phases/test-cases-analysis/index.js +0 -1
  122. package/dist/phases/user-stories-analysis/agent.js +2 -1
  123. package/dist/phases/user-stories-analysis/index.js +0 -1
  124. package/dist/services/coaching/coaching-agent.js +29 -4
  125. package/dist/services/feedbacks.d.ts +1 -1
  126. package/dist/skills/phase/issue-analysis/SKILL.md +48 -92
  127. package/dist/skills/phase/issue-analysis-verification/SKILL.md +46 -31
  128. package/dist/tools/bootstrap.d.ts +45 -0
  129. package/dist/tools/bootstrap.js +50 -0
  130. package/dist/types/external-sources.d.ts +22 -0
  131. package/dist/types/external-sources.js +23 -0
  132. package/dist/types/index.d.ts +5 -10
  133. package/dist/types/issues.d.ts +2 -0
  134. package/dist/types/llm-responses.d.ts +1 -14
  135. package/dist/utils/formatters.js +1 -7
  136. package/dist/utils/issue-phase-cli.d.ts +26 -0
  137. package/dist/utils/issue-phase-cli.js +44 -0
  138. package/dist/workspace/workspace-manager.d.ts +10 -0
  139. package/dist/workspace/workspace-manager.js +22 -1
  140. package/package.json +6 -2
  141. package/vitest.config.ts +4 -0
  142. package/.env.local +0 -12
package/README.md CHANGED
@@ -229,6 +229,26 @@ npx edsger --review --staged
229
229
  - MCP server for advanced issue operations (analysis, design, implementation, testing)
230
230
  - Playwright for functional testing (when using `--test` flag)
231
231
 
232
+ ## Releasing
233
+
234
+ Published from this directory using
235
+ [semantic-release](https://github.com/semantic-release/semantic-release),
236
+ driven by [Conventional Commit](https://www.conventionalcommits.org/)
237
+ messages on `main` (`fix:` → patch, `feat:` → minor, `feat!:` /
238
+ `BREAKING CHANGE:` → major).
239
+
240
+ ```bash
241
+ cd packages/edsger
242
+ npm run release:preview # dry-run, see the next version + notes
243
+ npm run release # publish to npm + push the git tag
244
+ ```
245
+
246
+ > **Important:** the `edsger-contract` and `edsger-tools` versions
247
+ > referenced in `package.json` must already be on the npm registry
248
+ > before this CLI can be installed. Always run the Changesets flow for
249
+ > those two packages first — see [`RELEASING.md`](../../RELEASING.md)
250
+ > in the repo root for the full sequence.
251
+
232
252
  ## License
233
253
 
234
254
  ISC
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Thin MCP wrappers for the Financing tab. Mirror the shape of the
3
+ * `growth.ts` and `intelligence.ts` siblings.
4
+ */
5
+ export type FinancingStage = 'pre_seed' | 'seed' | 'series_a' | 'series_b' | 'series_c' | 'growth';
6
+ export interface FinancingDeckSlide {
7
+ slide_id: string;
8
+ title: string;
9
+ body: string;
10
+ ai_rationale?: string;
11
+ }
12
+ export interface FinancingRedFlag {
13
+ metric: string;
14
+ severity: 'low' | 'medium' | 'high';
15
+ explanation: string;
16
+ fix: string;
17
+ }
18
+ export interface FinancingContext {
19
+ product: {
20
+ id: string;
21
+ name: string;
22
+ description: string | null;
23
+ created_at: string | null;
24
+ } | null;
25
+ profile: Record<string, unknown> | null;
26
+ metrics: Record<string, unknown> | null;
27
+ target_stage: FinancingStage | null;
28
+ }
29
+ /**
30
+ * Fetch the founder-supplied financing profile + metrics. The agent is
31
+ * expected to WebSearch current industry benchmarks itself rather than
32
+ * relying on a stale server-side table.
33
+ */
34
+ export declare function getFinancingContext(productId: string, targetStage: FinancingStage, verbose?: boolean): Promise<FinancingContext>;
35
+ /**
36
+ * Persist a generated pitch deck draft to financing_ai_analyses.
37
+ */
38
+ export declare function saveFinancingAnalysis(payload: {
39
+ product_id: string;
40
+ target_stage: FinancingStage;
41
+ deck_slides: FinancingDeckSlide[];
42
+ stage_reasoning?: string;
43
+ red_flags?: FinancingRedFlag[];
44
+ source_summary?: Record<string, unknown>;
45
+ }, verbose?: boolean): Promise<{
46
+ analysis_id: string;
47
+ }>;
@@ -0,0 +1,37 @@
1
+ import { logError, logInfo } from '../utils/logger.js';
2
+ import { callMcpEndpoint } from './mcp-client.js';
3
+ /**
4
+ * Fetch the founder-supplied financing profile + metrics. The agent is
5
+ * expected to WebSearch current industry benchmarks itself rather than
6
+ * relying on a stale server-side table.
7
+ */
8
+ export async function getFinancingContext(productId, targetStage, verbose) {
9
+ if (verbose) {
10
+ logInfo(`Fetching financing context for product=${productId} stage=${targetStage}`);
11
+ }
12
+ try {
13
+ const result = (await callMcpEndpoint('financing/get_context', {
14
+ product_id: productId,
15
+ target_stage: targetStage,
16
+ }));
17
+ const text = result.content?.[0]?.text || '{}';
18
+ return JSON.parse(text);
19
+ }
20
+ catch (error) {
21
+ if (verbose) {
22
+ logError(`Failed to fetch financing context: ${error instanceof Error ? error.message : String(error)}`);
23
+ }
24
+ throw error;
25
+ }
26
+ }
27
+ /**
28
+ * Persist a generated pitch deck draft to financing_ai_analyses.
29
+ */
30
+ export async function saveFinancingAnalysis(payload, verbose) {
31
+ if (verbose) {
32
+ logInfo(`Saving financing analysis: ${payload.deck_slides.length} slides, ${(payload.red_flags ?? []).length} red flags`);
33
+ }
34
+ const result = (await callMcpEndpoint('financing/save_analysis', payload));
35
+ const text = result.content?.[0]?.text || '{}';
36
+ return JSON.parse(text);
37
+ }
@@ -1,6 +1,11 @@
1
1
  /**
2
- * Approval workflow integration for issue phases
3
- * Checks if current issue status has been approved before executing next phase
2
+ * Approval workflow integration for issue phases.
3
+ *
4
+ * Under the 2D model, approvals are gated on the PHASE the worker is
5
+ * about to enter (e.g. "approve before technical_design runs"), not on
6
+ * the issue's lifecycle status. The phase-executor passes the target
7
+ * phase name; we look up the approval config for that phase and block
8
+ * execution if pending approval is required.
4
9
  */
5
10
  interface ApprovalCheckResult {
6
11
  canProceed: boolean;
@@ -9,12 +14,9 @@ interface ApprovalCheckResult {
9
14
  message?: string;
10
15
  }
11
16
  /**
12
- * Check if current issue status has been approved before executing next phase
13
- * This should be called BEFORE executing a phase, not after
14
- *
15
- * @param issueId - Issue ID
16
- * @param verbose - Verbose logging
17
- * @returns Promise<ApprovalCheckResult> - Whether the phase can proceed
17
+ * Check if a phase requires approval before the worker enters it.
18
+ * Called by phase-executor with the snake_case phase name (e.g.
19
+ * 'technical_design').
18
20
  */
19
- export declare function checkApprovalBeforePhaseExecution(issueId: string, verbose?: boolean): Promise<ApprovalCheckResult>;
21
+ export declare function checkApprovalBeforePhaseExecution(issueId: string, phase: string, verbose?: boolean): Promise<ApprovalCheckResult>;
20
22
  export {};
@@ -1,32 +1,31 @@
1
1
  /**
2
- * Approval workflow integration for issue phases
3
- * Checks if current issue status has been approved before executing next phase
2
+ * Approval workflow integration for issue phases.
3
+ *
4
+ * Under the 2D model, approvals are gated on the PHASE the worker is
5
+ * about to enter (e.g. "approve before technical_design runs"), not on
6
+ * the issue's lifecycle status. The phase-executor passes the target
7
+ * phase name; we look up the approval config for that phase and block
8
+ * execution if pending approval is required.
4
9
  */
5
10
  import { logError, logInfo, logWarning } from '../../utils/logger.js';
6
11
  import { callMcpEndpoint } from '../mcp-client.js';
7
12
  import { getIssue } from './get-issue.js';
8
13
  /**
9
- * Check if current issue status has been approved before executing next phase
10
- * This should be called BEFORE executing a phase, not after
11
- *
12
- * @param issueId - Issue ID
13
- * @param verbose - Verbose logging
14
- * @returns Promise<ApprovalCheckResult> - Whether the phase can proceed
14
+ * Check if a phase requires approval before the worker enters it.
15
+ * Called by phase-executor with the snake_case phase name (e.g.
16
+ * 'technical_design').
15
17
  */
16
- export async function checkApprovalBeforePhaseExecution(issueId, verbose = false) {
18
+ export async function checkApprovalBeforePhaseExecution(issueId, phase, verbose = false) {
17
19
  try {
18
- // 1. Get current issue to check its status and product_id
19
20
  const issue = await getIssue(issueId, verbose);
20
- const currentStatus = issue.status;
21
21
  const productId = issue.product_id;
22
22
  if (verbose) {
23
- logInfo(`🔍 Checking approval for current status: ${currentStatus}`);
23
+ logInfo(`🔍 Checking approval for phase: ${phase}`);
24
24
  logInfo(`📦 Product ID: ${productId}`);
25
25
  }
26
- // 2. Check if current status requires approval
27
26
  const requiresApprovalResult = (await callMcpEndpoint('approvals/requires_approval', {
28
27
  product_id: productId,
29
- issue_status: currentStatus,
28
+ issue_status: phase,
30
29
  }));
31
30
  if (verbose) {
32
31
  logInfo(`📋 MCP requires_approval response: ${JSON.stringify(requiresApprovalResult)}`);
@@ -34,26 +33,24 @@ export async function checkApprovalBeforePhaseExecution(issueId, verbose = false
34
33
  const requiresApproval = requiresApprovalResult?.requires_approval === true;
35
34
  if (!requiresApproval) {
36
35
  if (verbose) {
37
- logInfo(`✅ Current status ${currentStatus} does not require approval. Proceeding.`);
36
+ logInfo(`✅ Phase ${phase} does not require approval. Proceeding.`);
38
37
  }
39
38
  return {
40
39
  canProceed: true,
41
40
  requiresApproval: false,
42
41
  };
43
42
  }
44
- // 3. Status requires approval - check if already approved
45
43
  if (verbose) {
46
- logInfo(`🔒 Current status ${currentStatus} requires approval`);
44
+ logInfo(`🔒 Phase ${phase} requires approval`);
47
45
  }
48
46
  const existingApprovals = (await callMcpEndpoint('approvals/issue_approvals', {
49
47
  issue_id: issueId,
50
48
  }));
51
- // Check if there's an approved approval for the current status
52
- const approvedApproval = existingApprovals?.approvals?.find((approval) => approval.requested_status === currentStatus &&
49
+ const approvedApproval = existingApprovals?.approvals?.find((approval) => approval.requested_status === phase &&
53
50
  approval.approval_status === 'approved');
54
51
  if (approvedApproval) {
55
52
  if (verbose) {
56
- logInfo(`✅ Found approved approval for ${currentStatus}. Proceeding.`);
53
+ logInfo(`✅ Found approved approval for ${phase}. Proceeding.`);
57
54
  }
58
55
  return {
59
56
  canProceed: true,
@@ -61,39 +58,36 @@ export async function checkApprovalBeforePhaseExecution(issueId, verbose = false
61
58
  approvalId: approvedApproval.id,
62
59
  };
63
60
  }
64
- // Check if there's already a pending approval for current status
65
- const pendingApproval = existingApprovals?.approvals?.find((approval) => approval.requested_status === currentStatus &&
61
+ const pendingApproval = existingApprovals?.approvals?.find((approval) => approval.requested_status === phase &&
66
62
  approval.approval_status === 'pending');
67
63
  if (pendingApproval) {
68
64
  if (verbose) {
69
- logWarning(`⏳ Approval already pending for ${currentStatus}. Waiting for review.`);
65
+ logWarning(`⏳ Approval already pending for ${phase}. Waiting.`);
70
66
  }
71
67
  return {
72
68
  canProceed: false,
73
69
  requiresApproval: true,
74
70
  approvalId: pendingApproval.id,
75
- message: `Waiting for approval of current status: ${currentStatus}`,
71
+ message: `Waiting for approval of phase: ${phase}`,
76
72
  };
77
73
  }
78
- // No pending or approved approval - need to create one
79
74
  if (verbose) {
80
- logInfo(`🔔 No approval exists for ${currentStatus}. Creating approval request...`);
75
+ logInfo(`🔔 No approval exists for ${phase}. Creating approval request...`);
81
76
  }
82
- // Create approval request
83
- const approvalId = await createApprovalRequestWithEmail(issueId, productId, currentStatus, verbose);
77
+ const approvalId = await createApprovalRequestWithEmail(issueId, productId, phase, verbose);
84
78
  if (approvalId) {
85
79
  return {
86
80
  canProceed: false,
87
81
  requiresApproval: true,
88
82
  approvalId,
89
- message: `Approval request created for status: ${currentStatus}`,
83
+ message: `Approval request created for phase: ${phase}`,
90
84
  };
91
85
  }
92
86
  logError('Failed to create approval request');
93
87
  return {
94
88
  canProceed: false,
95
89
  requiresApproval: true,
96
- message: `Failed to create approval request for status: ${currentStatus}`,
90
+ message: `Failed to create approval request for phase: ${phase}`,
97
91
  };
98
92
  }
99
93
  catch (error) {
@@ -108,22 +102,18 @@ export async function checkApprovalBeforePhaseExecution(issueId, verbose = false
108
102
  }
109
103
  }
110
104
  /**
111
- * Create an approval request (emails are sent automatically by the MCP handler)
112
- * This is for the CURRENT status, not the next status
105
+ * Create an approval request for a phase. The approval is asking:
106
+ * "Can we proceed into this phase?"
113
107
  */
114
- async function createApprovalRequestWithEmail(issueId, productId, currentStatus, verbose = false) {
108
+ async function createApprovalRequestWithEmail(issueId, productId, phase, verbose = false) {
115
109
  try {
116
110
  if (verbose) {
117
- logInfo(`Creating approval request for current status: ${currentStatus}`);
111
+ logInfo(`Creating approval request for phase: ${phase}`);
118
112
  }
119
- // Create approval request via MCP endpoint
120
- // Note: We're requesting approval for the current status
121
- // The approval is asking: "Can we proceed from this status?"
122
- // Email notifications are sent automatically by the MCP handler
123
113
  const result = (await callMcpEndpoint('approvals/create', {
124
114
  issue_id: issueId,
125
- requested_status: currentStatus,
126
- previous_status: null, // We're approving current status, not transitioning
115
+ requested_status: phase,
116
+ previous_status: null,
127
117
  }));
128
118
  const approvalId = result?.approval_id;
129
119
  if (!approvalId) {
@@ -132,7 +122,6 @@ async function createApprovalRequestWithEmail(issueId, productId, currentStatus,
132
122
  }
133
123
  if (verbose) {
134
124
  logInfo(`✅ Approval request created: ${approvalId}`);
135
- // Log email results if available
136
125
  if (result?.emails_sent) {
137
126
  const sentCount = result.emails_sent.filter((e) => e.status === 'sent').length;
138
127
  const failedCount = result.emails_sent.filter((e) => e.status !== 'sent').length;
@@ -1,6 +1,25 @@
1
1
  /**
2
- * Issue status updater for workflow pipeline
3
- * Updates issue status at each phase of the development workflow
2
+ * Worker-side phase progress writer.
3
+ *
4
+ * Under the 2D state model, phase progress lives in the workflow JSONB
5
+ * column, not in issues.status. The lifecycle column changes only at:
6
+ * - claim time (ready_for_ai → assigned_to_ai, via claim_next_ready_issue)
7
+ * - first phase entry (assigned_to_ai → in_progress, automatic in enter_phase)
8
+ * - terminal completion (handled by orchestrator after all phases done)
9
+ *
10
+ * For each phase transition, we call the enter_phase RPC which atomically
11
+ * closes any prior active phase and sets the target phase to running or
12
+ * verifying. For non-active terminal states (completed/failed/skipped on a
13
+ * specific phase, e.g. functional-testing setting itself to completed/failed
14
+ * when the test result arrives), we call set_phase_state.
15
+ *
16
+ * Public exports:
17
+ * enterPhase(issueId, phase, state) — primary write path
18
+ * setPhaseState(issueId, phase, state) — terminal phase outcomes
19
+ * updateIssueStatus({issueId, status}) — direct lifecycle changes
20
+ * updateIssueStatusForPhase(issueId, phaseName, verbose)
21
+ * Stable signature for the orchestrator's 9 call sites; internally
22
+ * dispatches to enterPhase or setPhaseState based on the phase name.
4
23
  */
5
24
  import type { IssueStatus } from '../../types/index.js';
6
25
  interface StatusUpdateOptions {
@@ -9,33 +28,41 @@ interface StatusUpdateOptions {
9
28
  readonly verbose?: boolean;
10
29
  }
11
30
  /**
12
- * Check if moving from currentStatus to newStatus is forward progression
13
- *
14
- * Special cases for archived status:
15
- * - Any status → archived: Always allowed (archiving from any state)
16
- * - Archived → backlog: Always allowed (unarchiving restores to backlog)
31
+ * Forward-progression check for the legacy status enum. Retained for
32
+ * lifecycle-only updates; phase progress no longer flows through here.
17
33
  */
18
34
  export declare function isForwardProgression(currentStatus: IssueStatus, newStatus: IssueStatus): boolean;
19
35
  /**
20
- * Update issue status via MCP endpoint
36
+ * Direct lifecycle status update. Phase-driven callers should use
37
+ * `enterPhase` / `setPhaseState` instead — this is for true lifecycle
38
+ * writes only (e.g. shipping, archiving, failing terminally).
21
39
  */
22
40
  export declare function updateIssueStatus({ issueId, status, verbose, }: StatusUpdateOptions): Promise<boolean>;
23
41
  /**
24
- * Map pipeline phase to issue status
42
+ * Atomic phase transition. Closes any prior running/verifying phase and
43
+ * marks the target phase as running or verifying. Bumps issues.status to
44
+ * in_progress automatically when needed. Use this for active phase entry.
45
+ *
46
+ * Throws on RPC failure so callers can react (retry, escalate, abort).
47
+ * Old boolean-returning behavior is preserved by updateIssueStatusForPhase
48
+ * which wraps this call.
25
49
  */
26
- export declare const getStatusForPhase: (phase: string) => IssueStatus | null;
50
+ export declare function enterPhase(issueId: string, phase: string, state: 'running' | 'verifying', verbose?: boolean): Promise<void>;
27
51
  /**
28
- * Update issue status based on pipeline phase
29
- *
30
- * @param issueId - The issue ID to update
31
- * @param phase - The pipeline phase name
32
- * @param verbose - Whether to log verbose output
33
- * @returns Promise<boolean> - true if update succeeded, false if skipped or failed
52
+ * Atomic phase state update typically used to mark a phase as completed,
53
+ * failed, or skipped without affecting other phases or issues.status.
54
+ * For example, functional-testing/analyzer uses this to record
55
+ * `functional_testing completed` (testing passed) or
56
+ * `functional_testing failed` (testing failed).
57
+ */
58
+ export declare function setPhaseState(issueId: string, phase: string, state: 'pending' | 'running' | 'verifying' | 'completed' | 'failed' | 'skipped', verbose?: boolean): Promise<void>;
59
+ /**
60
+ * Update issue progress for a pipeline phase.
34
61
  *
35
- * BREAKING CHANGE: This function was changed from synchronous to asynchronous in PR #87
36
- * to support regression prevention checks. All callers must now use await/then to handle
37
- * the Promise return type. The function now validates against status regression and will
38
- * skip updates that would move an issue backward in the development workflow.
62
+ * Signature is preserved (issueId, phase, verbose) so all 9+ orchestrator
63
+ * call sites continue to work without modification. The internal
64
+ * implementation has been rewritten to call enter_phase, which writes to
65
+ * workflow JSONB instead of issues.status.
39
66
  */
40
67
  export declare const updateIssueStatusForPhase: (issueId: string, phase: string, verbose?: boolean) => Promise<boolean>;
41
68
  export {};
@@ -1,41 +1,83 @@
1
1
  /**
2
- * Issue status updater for workflow pipeline
3
- * Updates issue status at each phase of the development workflow
2
+ * Worker-side phase progress writer.
3
+ *
4
+ * Under the 2D state model, phase progress lives in the workflow JSONB
5
+ * column, not in issues.status. The lifecycle column changes only at:
6
+ * - claim time (ready_for_ai → assigned_to_ai, via claim_next_ready_issue)
7
+ * - first phase entry (assigned_to_ai → in_progress, automatic in enter_phase)
8
+ * - terminal completion (handled by orchestrator after all phases done)
9
+ *
10
+ * For each phase transition, we call the enter_phase RPC which atomically
11
+ * closes any prior active phase and sets the target phase to running or
12
+ * verifying. For non-active terminal states (completed/failed/skipped on a
13
+ * specific phase, e.g. functional-testing setting itself to completed/failed
14
+ * when the test result arrives), we call set_phase_state.
15
+ *
16
+ * Public exports:
17
+ * enterPhase(issueId, phase, state) — primary write path
18
+ * setPhaseState(issueId, phase, state) — terminal phase outcomes
19
+ * updateIssueStatus({issueId, status}) — direct lifecycle changes
20
+ * updateIssueStatusForPhase(issueId, phaseName, verbose)
21
+ * Stable signature for the orchestrator's 9 call sites; internally
22
+ * dispatches to enterPhase or setPhaseState based on the phase name.
4
23
  */
5
- import { PHASE_STATUS_MAP, STATUS_PROGRESSION_ORDER, } from '../../config/issue-status.js';
24
+ import { STATUS_PROGRESSION_ORDER } from '../../config/issue-status.js';
6
25
  import { logError, logInfo } from '../../utils/logger.js';
7
26
  import { callMcpEndpoint } from '../mcp-client.js';
8
27
  import { getIssue } from './get-issue.js';
28
+ const PHASE_NAME_TO_WORKFLOW = {
29
+ // Active phase transitions (call enter_phase)
30
+ 'issue-analysis': { kind: 'enter', phase: 'issue_analysis', state: 'running' },
31
+ 'issue-analysis-verification': { kind: 'enter', phase: 'issue_analysis', state: 'verifying' },
32
+ 'user-stories-analysis': { kind: 'enter', phase: 'user_stories_analysis', state: 'running' },
33
+ 'user-stories-analysis-verification': { kind: 'enter', phase: 'user_stories_analysis', state: 'verifying' },
34
+ 'test-cases-analysis': { kind: 'enter', phase: 'test_cases_analysis', state: 'running' },
35
+ 'test-cases-analysis-verification': { kind: 'enter', phase: 'test_cases_analysis', state: 'verifying' },
36
+ 'technical-design': { kind: 'enter', phase: 'technical_design', state: 'running' },
37
+ 'technical-design-verification': { kind: 'enter', phase: 'technical_design', state: 'verifying' },
38
+ 'branch-planning': { kind: 'enter', phase: 'branch_planning', state: 'running' },
39
+ 'branch-planning-verification': { kind: 'enter', phase: 'branch_planning', state: 'verifying' },
40
+ 'code-implementation': { kind: 'enter', phase: 'code_implementation', state: 'running' },
41
+ 'code-implementation-verification': { kind: 'enter', phase: 'code_implementation', state: 'verifying' },
42
+ 'pr-splitting': { kind: 'enter', phase: 'pr_splitting', state: 'running' },
43
+ 'pr-splitting-verification': { kind: 'enter', phase: 'pr_splitting', state: 'verifying' },
44
+ 'pr-execution': { kind: 'enter', phase: 'pr_execution', state: 'running' },
45
+ 'functional-testing': { kind: 'enter', phase: 'functional_testing', state: 'running' },
46
+ 'code-review': { kind: 'enter', phase: 'code_review', state: 'running' },
47
+ 'ready-for-review': { kind: 'enter', phase: 'code_review', state: 'verifying' },
48
+ 'code-refine': { kind: 'enter', phase: 'code_refine', state: 'running' },
49
+ 'code-refine-verification': { kind: 'enter', phase: 'code_refine', state: 'verifying' },
50
+ 'bug-fixing': { kind: 'enter', phase: 'bug_fixing', state: 'running' },
51
+ // Terminal phase outcomes (call set_phase_state)
52
+ 'testing-passed': { kind: 'terminal', phase: 'functional_testing', state: 'completed' },
53
+ 'testing-failed': { kind: 'terminal', phase: 'functional_testing', state: 'failed' },
54
+ 'testing-in-progress': { kind: 'enter', phase: 'functional_testing', state: 'running' },
55
+ };
9
56
  /**
10
- * Check if moving from currentStatus to newStatus is forward progression
11
- *
12
- * Special cases for archived status:
13
- * - Any status → archived: Always allowed (archiving from any state)
14
- * - Archived → backlog: Always allowed (unarchiving restores to backlog)
57
+ * Forward-progression check for the legacy status enum. Retained for
58
+ * lifecycle-only updates; phase progress no longer flows through here.
15
59
  */
16
60
  export function isForwardProgression(currentStatus, newStatus) {
17
- // Any status can transition to archived
18
61
  if (newStatus === 'archived') {
19
62
  return true;
20
63
  }
21
- // Archived can only transition back to backlog (unarchive)
22
64
  if (currentStatus === 'archived') {
23
65
  return newStatus === 'backlog';
24
66
  }
25
67
  const currentIndex = STATUS_PROGRESSION_ORDER.indexOf(currentStatus);
26
68
  const newIndex = STATUS_PROGRESSION_ORDER.indexOf(newStatus);
27
- // Allow moving forward or staying at same level (for retries, etc.)
28
69
  return newIndex >= currentIndex;
29
70
  }
30
71
  /**
31
- * Update issue status via MCP endpoint
72
+ * Direct lifecycle status update. Phase-driven callers should use
73
+ * `enterPhase` / `setPhaseState` instead — this is for true lifecycle
74
+ * writes only (e.g. shipping, archiving, failing terminally).
32
75
  */
33
76
  export async function updateIssueStatus({ issueId, status, verbose = false, }) {
34
77
  try {
35
78
  if (verbose) {
36
79
  logInfo(`Updating issue ${issueId} status to: ${status}`);
37
80
  }
38
- // Get current issue status to check for regression
39
81
  let issue;
40
82
  let currentStatus;
41
83
  try {
@@ -45,14 +87,11 @@ export async function updateIssueStatus({ issueId, status, verbose = false, }) {
45
87
  catch (error) {
46
88
  const errorMessage = error instanceof Error ? error.message : String(error);
47
89
  logError(`Failed to get current issue status for ${issueId}: ${errorMessage}`);
48
- // If we can't get the current status, we can't safely check for regression
49
- // so we'll skip the update to prevent potential regression
50
90
  if (verbose) {
51
91
  logInfo('⚠️ Skipping status update due to inability to verify current status');
52
92
  }
53
93
  return false;
54
94
  }
55
- // Check if this would be a regression
56
95
  if (!isForwardProgression(currentStatus, status)) {
57
96
  const message = `⚠️ Skipping status regression: ${currentStatus} → ${status}. Only forward progression allowed.`;
58
97
  if (verbose) {
@@ -60,18 +99,13 @@ export async function updateIssueStatus({ issueId, status, verbose = false, }) {
60
99
  }
61
100
  return false;
62
101
  }
63
- // Only proceed if status actually changed
64
102
  if (currentStatus === status) {
65
103
  if (verbose) {
66
104
  logInfo(`Status already at ${status}, no update needed`);
67
105
  }
68
106
  return true;
69
107
  }
70
- // Update status
71
- await callMcpEndpoint('issues/update', {
72
- issue_id: issueId,
73
- status,
74
- });
108
+ await callMcpEndpoint('issues/update', { issue_id: issueId, status });
75
109
  if (verbose) {
76
110
  logInfo(`✅ Issue status updated successfully from ${currentStatus} to: ${status}`);
77
111
  }
@@ -86,37 +120,71 @@ export async function updateIssueStatus({ issueId, status, verbose = false, }) {
86
120
  }
87
121
  }
88
122
  /**
89
- * Map pipeline phase to issue status
123
+ * Atomic phase transition. Closes any prior running/verifying phase and
124
+ * marks the target phase as running or verifying. Bumps issues.status to
125
+ * in_progress automatically when needed. Use this for active phase entry.
126
+ *
127
+ * Throws on RPC failure so callers can react (retry, escalate, abort).
128
+ * Old boolean-returning behavior is preserved by updateIssueStatusForPhase
129
+ * which wraps this call.
90
130
  */
91
- export const getStatusForPhase = (phase) => {
92
- // Use the externalized phase-to-status mapping
93
- return PHASE_STATUS_MAP[phase] ?? null;
94
- };
131
+ export async function enterPhase(issueId, phase, state, verbose = false) {
132
+ await callMcpEndpoint('issues/enter_phase', {
133
+ issue_id: issueId,
134
+ phase,
135
+ state,
136
+ });
137
+ if (verbose) {
138
+ logInfo(`✅ entered phase ${phase} (${state})`);
139
+ }
140
+ }
95
141
  /**
96
- * Update issue status based on pipeline phase
97
- *
98
- * @param issueId - The issue ID to update
99
- * @param phase - The pipeline phase name
100
- * @param verbose - Whether to log verbose output
101
- * @returns Promise<boolean> - true if update succeeded, false if skipped or failed
142
+ * Atomic phase state update typically used to mark a phase as completed,
143
+ * failed, or skipped without affecting other phases or issues.status.
144
+ * For example, functional-testing/analyzer uses this to record
145
+ * `functional_testing completed` (testing passed) or
146
+ * `functional_testing failed` (testing failed).
147
+ */
148
+ export async function setPhaseState(issueId, phase, state, verbose = false) {
149
+ await callMcpEndpoint('issues/set_phase_state', {
150
+ issue_id: issueId,
151
+ phase,
152
+ state,
153
+ });
154
+ if (verbose) {
155
+ logInfo(`✅ set phase ${phase} → ${state}`);
156
+ }
157
+ }
158
+ /**
159
+ * Update issue progress for a pipeline phase.
102
160
  *
103
- * BREAKING CHANGE: This function was changed from synchronous to asynchronous in PR #87
104
- * to support regression prevention checks. All callers must now use await/then to handle
105
- * the Promise return type. The function now validates against status regression and will
106
- * skip updates that would move an issue backward in the development workflow.
161
+ * Signature is preserved (issueId, phase, verbose) so all 9+ orchestrator
162
+ * call sites continue to work without modification. The internal
163
+ * implementation has been rewritten to call enter_phase, which writes to
164
+ * workflow JSONB instead of issues.status.
107
165
  */
108
166
  export const updateIssueStatusForPhase = async (issueId, phase, verbose) => {
109
- const status = getStatusForPhase(phase);
110
- if (!status) {
111
- const message = `⚠️ Unknown phase '${phase}' - skipping status update to prevent regression`;
167
+ const mapping = PHASE_NAME_TO_WORKFLOW[phase];
168
+ if (!mapping) {
112
169
  if (verbose) {
113
- logInfo(message);
170
+ logInfo(`⚠️ Unknown phase '${phase}' — skipping status update`);
171
+ }
172
+ return false;
173
+ }
174
+ try {
175
+ if (mapping.kind === 'enter') {
176
+ await enterPhase(issueId, mapping.phase, mapping.state, verbose);
177
+ }
178
+ else {
179
+ await setPhaseState(issueId, mapping.phase, mapping.state, verbose);
180
+ }
181
+ return true;
182
+ }
183
+ catch (error) {
184
+ const message = error instanceof Error ? error.message : String(error);
185
+ if (verbose) {
186
+ logError(`Failed to ${mapping.kind === 'enter' ? 'enter' : 'set'} phase ${mapping.phase} (${mapping.state}): ${message}`);
114
187
  }
115
188
  return false;
116
189
  }
117
- return updateIssueStatus({
118
- issueId,
119
- status,
120
- verbose,
121
- });
122
190
  };
@@ -4,6 +4,7 @@ import { type IssueWorkflow } from '../../types/pipeline.js';
4
4
  */
5
5
  export declare function updateIssue(issueId: string, updates: {
6
6
  technical_design?: string;
7
+ execution_plan?: string;
7
8
  status?: string;
8
9
  execution_mode?: string;
9
10
  workflow?: IssueWorkflow;
@@ -12,6 +13,10 @@ export declare function updateIssue(issueId: string, updates: {
12
13
  * Update technical design for an issue
13
14
  */
14
15
  export declare function updateTechnicalDesign(issueId: string, technicalDesign: string, verbose?: boolean): Promise<boolean>;
16
+ /**
17
+ * Update execution plan for an issue
18
+ */
19
+ export declare function updateExecutionPlan(issueId: string, executionPlan: string, verbose?: boolean): Promise<boolean>;
15
20
  /**
16
21
  * Mark a workflow phase as completed
17
22
  * Fetches current workflow, updates the phase status, and saves back