edsger 0.29.3 → 0.30.1

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 (131) hide show
  1. package/.claude/settings.local.json +3 -23
  2. package/dist/api/chat.d.ts +18 -0
  3. package/dist/api/chat.js +41 -0
  4. package/dist/api/features/status-updater.d.ts +4 -0
  5. package/dist/api/features/status-updater.js +10 -0
  6. package/dist/commands/agent-workflow/chat-worker.js +32 -7
  7. package/dist/config/__tests__/feature-status.test.js +2 -2
  8. package/dist/config/feature-status.js +2 -0
  9. package/dist/index.js +0 -0
  10. package/dist/phases/autonomous/index.js +9 -56
  11. package/dist/phases/chat-processor/index.d.ts +6 -0
  12. package/dist/phases/chat-processor/index.js +164 -0
  13. package/dist/phases/chat-processor/product-context.d.ts +36 -0
  14. package/dist/phases/chat-processor/product-context.js +104 -0
  15. package/dist/phases/chat-processor/product-prompts.d.ts +4 -0
  16. package/dist/phases/chat-processor/product-prompts.js +55 -0
  17. package/dist/phases/chat-processor/product-tools.d.ts +11 -0
  18. package/dist/phases/chat-processor/product-tools.js +236 -0
  19. package/dist/phases/code-implementation/branch-pr-creator.js +2 -58
  20. package/dist/phases/code-implementation/index.js +9 -67
  21. package/dist/phases/code-refine/index.js +6 -37
  22. package/dist/phases/code-review/index.js +1 -0
  23. package/dist/phases/pr-execution/index.js +1 -0
  24. package/dist/phases/pr-execution/pr-executor.d.ts +2 -1
  25. package/dist/phases/pr-execution/pr-executor.js +9 -38
  26. package/dist/phases/pull-request/creator.js +9 -35
  27. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.d.ts +4 -0
  28. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.js +133 -0
  29. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.d.ts +4 -0
  30. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.js +336 -0
  31. package/dist/services/lifecycle-agent/index.d.ts +24 -0
  32. package/dist/services/lifecycle-agent/index.js +25 -0
  33. package/dist/services/lifecycle-agent/phase-criteria.d.ts +57 -0
  34. package/dist/services/lifecycle-agent/phase-criteria.js +335 -0
  35. package/dist/services/lifecycle-agent/transition-rules.d.ts +60 -0
  36. package/dist/services/lifecycle-agent/transition-rules.js +184 -0
  37. package/dist/services/lifecycle-agent/types.d.ts +190 -0
  38. package/dist/services/lifecycle-agent/types.js +12 -0
  39. package/dist/types/index.d.ts +1 -1
  40. package/dist/utils/git-branch-manager.d.ts +2 -0
  41. package/dist/utils/git-branch-manager.js +8 -35
  42. package/dist/utils/git-push.d.ts +43 -0
  43. package/dist/utils/git-push.js +125 -0
  44. package/package.json +1 -1
  45. package/.env.local +0 -12
  46. package/dist/api/features/__tests__/regression-prevention.test.d.ts +0 -5
  47. package/dist/api/features/__tests__/regression-prevention.test.js +0 -338
  48. package/dist/api/features/__tests__/status-updater.integration.test.d.ts +0 -5
  49. package/dist/api/features/__tests__/status-updater.integration.test.js +0 -497
  50. package/dist/commands/workflow/pipeline-runner.d.ts +0 -17
  51. package/dist/commands/workflow/pipeline-runner.js +0 -393
  52. package/dist/commands/workflow/runner.d.ts +0 -26
  53. package/dist/commands/workflow/runner.js +0 -119
  54. package/dist/commands/workflow/workflow-runner.d.ts +0 -26
  55. package/dist/commands/workflow/workflow-runner.js +0 -119
  56. package/dist/phases/code-implementation/analyzer-helpers.d.ts +0 -28
  57. package/dist/phases/code-implementation/analyzer-helpers.js +0 -177
  58. package/dist/phases/code-implementation/analyzer.d.ts +0 -32
  59. package/dist/phases/code-implementation/analyzer.js +0 -629
  60. package/dist/phases/code-implementation/context-fetcher.d.ts +0 -17
  61. package/dist/phases/code-implementation/context-fetcher.js +0 -86
  62. package/dist/phases/code-implementation/mcp-server.d.ts +0 -1
  63. package/dist/phases/code-implementation/mcp-server.js +0 -93
  64. package/dist/phases/code-implementation/prompts-improvement.d.ts +0 -5
  65. package/dist/phases/code-implementation/prompts-improvement.js +0 -108
  66. package/dist/phases/code-implementation-verification/verifier.d.ts +0 -31
  67. package/dist/phases/code-implementation-verification/verifier.js +0 -196
  68. package/dist/phases/code-refine/analyzer.d.ts +0 -41
  69. package/dist/phases/code-refine/analyzer.js +0 -561
  70. package/dist/phases/code-refine/context-fetcher.d.ts +0 -94
  71. package/dist/phases/code-refine/context-fetcher.js +0 -423
  72. package/dist/phases/code-refine-verification/analysis/llm-analyzer.d.ts +0 -22
  73. package/dist/phases/code-refine-verification/analysis/llm-analyzer.js +0 -134
  74. package/dist/phases/code-refine-verification/verifier.d.ts +0 -47
  75. package/dist/phases/code-refine-verification/verifier.js +0 -597
  76. package/dist/phases/code-review/analyzer.d.ts +0 -29
  77. package/dist/phases/code-review/analyzer.js +0 -363
  78. package/dist/phases/code-review/context-fetcher.d.ts +0 -92
  79. package/dist/phases/code-review/context-fetcher.js +0 -296
  80. package/dist/phases/feature-analysis/analyzer-helpers.d.ts +0 -10
  81. package/dist/phases/feature-analysis/analyzer-helpers.js +0 -47
  82. package/dist/phases/feature-analysis/analyzer.d.ts +0 -11
  83. package/dist/phases/feature-analysis/analyzer.js +0 -208
  84. package/dist/phases/feature-analysis/context-fetcher.d.ts +0 -26
  85. package/dist/phases/feature-analysis/context-fetcher.js +0 -134
  86. package/dist/phases/feature-analysis/http-fallback.d.ts +0 -20
  87. package/dist/phases/feature-analysis/http-fallback.js +0 -95
  88. package/dist/phases/feature-analysis/mcp-server.d.ts +0 -1
  89. package/dist/phases/feature-analysis/mcp-server.js +0 -144
  90. package/dist/phases/feature-analysis/prompts-improvement.d.ts +0 -8
  91. package/dist/phases/feature-analysis/prompts-improvement.js +0 -109
  92. package/dist/phases/feature-analysis-verification/verifier.d.ts +0 -37
  93. package/dist/phases/feature-analysis-verification/verifier.js +0 -147
  94. package/dist/phases/technical-design/analyzer-helpers.d.ts +0 -25
  95. package/dist/phases/technical-design/analyzer-helpers.js +0 -39
  96. package/dist/phases/technical-design/analyzer.d.ts +0 -21
  97. package/dist/phases/technical-design/analyzer.js +0 -461
  98. package/dist/phases/technical-design/context-fetcher.d.ts +0 -12
  99. package/dist/phases/technical-design/context-fetcher.js +0 -39
  100. package/dist/phases/technical-design/http-fallback.d.ts +0 -17
  101. package/dist/phases/technical-design/http-fallback.js +0 -151
  102. package/dist/phases/technical-design/mcp-server.d.ts +0 -1
  103. package/dist/phases/technical-design/mcp-server.js +0 -157
  104. package/dist/phases/technical-design/prompts-improvement.d.ts +0 -5
  105. package/dist/phases/technical-design/prompts-improvement.js +0 -93
  106. package/dist/phases/technical-design-verification/verifier.d.ts +0 -53
  107. package/dist/phases/technical-design-verification/verifier.js +0 -170
  108. package/dist/services/feature-branches.d.ts +0 -77
  109. package/dist/services/feature-branches.js +0 -205
  110. package/dist/workflow-runner/config/phase-configs.d.ts +0 -5
  111. package/dist/workflow-runner/config/phase-configs.js +0 -120
  112. package/dist/workflow-runner/core/feature-filter.d.ts +0 -16
  113. package/dist/workflow-runner/core/feature-filter.js +0 -46
  114. package/dist/workflow-runner/core/index.d.ts +0 -8
  115. package/dist/workflow-runner/core/index.js +0 -12
  116. package/dist/workflow-runner/core/pipeline-evaluator.d.ts +0 -24
  117. package/dist/workflow-runner/core/pipeline-evaluator.js +0 -32
  118. package/dist/workflow-runner/core/state-manager.d.ts +0 -24
  119. package/dist/workflow-runner/core/state-manager.js +0 -42
  120. package/dist/workflow-runner/core/workflow-logger.d.ts +0 -20
  121. package/dist/workflow-runner/core/workflow-logger.js +0 -65
  122. package/dist/workflow-runner/executors/phase-executor.d.ts +0 -8
  123. package/dist/workflow-runner/executors/phase-executor.js +0 -248
  124. package/dist/workflow-runner/feature-workflow-runner.d.ts +0 -26
  125. package/dist/workflow-runner/feature-workflow-runner.js +0 -119
  126. package/dist/workflow-runner/index.d.ts +0 -2
  127. package/dist/workflow-runner/index.js +0 -2
  128. package/dist/workflow-runner/pipeline-runner.d.ts +0 -17
  129. package/dist/workflow-runner/pipeline-runner.js +0 -393
  130. package/dist/workflow-runner/workflow-processor.d.ts +0 -54
  131. package/dist/workflow-runner/workflow-processor.js +0 -170
@@ -1,28 +1,8 @@
1
1
  {
2
2
  "permissions": {
3
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": []
4
+ "Bash(npx tsc:*)",
5
+ "Bash(npm run:*)"
6
+ ]
27
7
  }
28
8
  }
@@ -33,6 +33,24 @@ export declare function claimPendingMessages(channelId: string, workerId: string
33
33
  export declare function markMessageProcessed(messageId: string, verbose?: boolean): Promise<void>;
34
34
  export declare function markChannelRead(channelId: string, lastReadMessageId?: string, verbose?: boolean): Promise<void>;
35
35
  export declare function getUnreadCount(channelId: string, verbose?: boolean): Promise<number>;
36
+ /**
37
+ * Get or create the group chat channel for a product.
38
+ * Every product has one group channel.
39
+ */
40
+ export declare function getProductChannel(productId: string, verbose?: boolean): Promise<ChatChannel>;
41
+ /**
42
+ * Send a system message to a product's group channel.
43
+ * Creates the channel if it doesn't exist.
44
+ */
45
+ export declare function sendProductSystemMessage(productId: string, content: string, metadata?: Record<string, unknown>, verbose?: boolean): Promise<ChatMessage>;
46
+ /**
47
+ * Send an AI message to a product's group channel.
48
+ * Creates the channel if it doesn't exist.
49
+ */
50
+ export declare function sendProductAiMessage(productId: string, content: string, metadata?: Record<string, unknown>, options?: {
51
+ messageType?: ChatMessageType;
52
+ parentMessageId?: string;
53
+ }, verbose?: boolean): Promise<ChatMessage>;
36
54
  /**
37
55
  * Get or create the group chat channel for a feature.
38
56
  * This is the most common operation — every feature has one group channel.
package/dist/api/chat.js CHANGED
@@ -135,6 +135,47 @@ export async function getUnreadCount(channelId, verbose) {
135
135
  return result.unread_count;
136
136
  }
137
137
  // ============================================================
138
+ // Convenience: Product Channel
139
+ // ============================================================
140
+ /**
141
+ * Get or create the group chat channel for a product.
142
+ * Every product has one group channel.
143
+ */
144
+ export async function getProductChannel(productId, verbose) {
145
+ const { channel } = await getOrCreateChannel('product', productId, 'group', undefined, verbose);
146
+ return channel;
147
+ }
148
+ /**
149
+ * Send a system message to a product's group channel.
150
+ * Creates the channel if it doesn't exist.
151
+ */
152
+ export async function sendProductSystemMessage(productId, content, metadata = {}, verbose) {
153
+ try {
154
+ const channel = await getProductChannel(productId, verbose);
155
+ return await sendSystemMessage(channel.id, content, metadata, verbose);
156
+ }
157
+ catch (error) {
158
+ const msg = error instanceof Error ? error.message : String(error);
159
+ logError(`Failed to send system message for product ${productId}: ${msg}`);
160
+ throw error;
161
+ }
162
+ }
163
+ /**
164
+ * Send an AI message to a product's group channel.
165
+ * Creates the channel if it doesn't exist.
166
+ */
167
+ export async function sendProductAiMessage(productId, content, metadata = {}, options = {}, verbose) {
168
+ try {
169
+ const channel = await getProductChannel(productId, verbose);
170
+ return await sendAiMessage(channel.id, content, metadata, options, verbose);
171
+ }
172
+ catch (error) {
173
+ const msg = error instanceof Error ? error.message : String(error);
174
+ logError(`Failed to send AI message for product ${productId}: ${msg}`);
175
+ throw error;
176
+ }
177
+ }
178
+ // ============================================================
138
179
  // Convenience: Feature Channel
139
180
  // ============================================================
140
181
  /**
@@ -10,6 +10,10 @@ interface StatusUpdateOptions {
10
10
  }
11
11
  /**
12
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)
13
17
  */
14
18
  export declare function isForwardProgression(currentStatus: FeatureStatus, newStatus: FeatureStatus): boolean;
15
19
  /**
@@ -8,8 +8,18 @@ import { getFeature } from './get-feature.js';
8
8
  import { STATUS_PROGRESSION_ORDER, PHASE_STATUS_MAP, } from '../../config/feature-status.js';
9
9
  /**
10
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)
11
15
  */
12
16
  export function isForwardProgression(currentStatus, newStatus) {
17
+ // Any status can transition to archived
18
+ if (newStatus === 'archived')
19
+ return true;
20
+ // Archived can only transition back to backlog (unarchive)
21
+ if (currentStatus === 'archived')
22
+ return newStatus === 'backlog';
13
23
  const currentIndex = STATUS_PROGRESSION_ORDER.indexOf(currentStatus);
14
24
  const newIndex = STATUS_PROGRESSION_ORDER.indexOf(newStatus);
15
25
  // Allow moving forward or staying at same level (for retries, etc.)
@@ -15,7 +15,7 @@
15
15
  */
16
16
  import { randomUUID } from 'node:crypto';
17
17
  import { getFeatureChannel, claimPendingMessages, listChannels, sendSystemMessage, } from '../../api/chat.js';
18
- import { processHumanMessages, processPhaseCompletion, } from '../../phases/chat-processor/index.js';
18
+ import { processHumanMessages, processProductHumanMessages, processPhaseCompletion, } from '../../phases/chat-processor/index.js';
19
19
  function sendMessage(msg) {
20
20
  if (process.send) {
21
21
  process.send(msg);
@@ -35,6 +35,8 @@ let pollTimer = null;
35
35
  const WORKER_ID = `chat-worker-${process.pid}-${randomUUID().slice(0, 8)}`;
36
36
  // Track active feature channels (featureId -> channelId)
37
37
  const activeChannels = new Map();
38
+ // Track active product channels (productId -> channelId)
39
+ const activeProductChannels = new Map();
38
40
  // Track feature repo paths (featureId -> repoPath) for setting cwd on AI agent
39
41
  const featureRepoPaths = new Map();
40
42
  // Poll interval in ms
@@ -53,20 +55,33 @@ async function pollForMessages() {
53
55
  if (pollCount % CHANNEL_REFRESH_INTERVAL === 0) {
54
56
  await refreshChannels();
55
57
  }
58
+ // Poll feature channels
56
59
  for (const [featureId, channelId] of activeChannels) {
57
60
  try {
58
- // Atomically claim messages — other workers won't see these
59
61
  const claimed = await claimPendingMessages(channelId, WORKER_ID);
60
62
  if (claimed.length > 0) {
61
63
  log('info', `Claimed ${claimed.length} message(s) for feature ${featureId} (worker: ${WORKER_ID})`);
62
- // Batch all claimed messages into a single AI session
63
64
  const repoPath = featureRepoPaths.get(featureId);
64
65
  await processHumanMessages(claimed, featureId, config, verbose, repoPath);
65
66
  }
66
67
  }
67
68
  catch (error) {
68
69
  const msg = error instanceof Error ? error.message : String(error);
69
- log('error', `Error polling channel ${channelId}: ${msg}`);
70
+ log('error', `Error polling feature channel ${channelId}: ${msg}`);
71
+ }
72
+ }
73
+ // Poll product channels
74
+ for (const [productId, channelId] of activeProductChannels) {
75
+ try {
76
+ const claimed = await claimPendingMessages(channelId, WORKER_ID);
77
+ if (claimed.length > 0) {
78
+ log('info', `Claimed ${claimed.length} message(s) for product ${productId} (worker: ${WORKER_ID})`);
79
+ await processProductHumanMessages(claimed, productId, config, verbose);
80
+ }
81
+ }
82
+ catch (error) {
83
+ const msg = error instanceof Error ? error.message : String(error);
84
+ log('error', `Error polling product channel ${channelId}: ${msg}`);
70
85
  }
71
86
  }
72
87
  }
@@ -161,16 +176,26 @@ async function handleFeatureDone(msg) {
161
176
  */
162
177
  async function refreshChannels() {
163
178
  try {
164
- const channels = await listChannels('feature');
179
+ // Fetch feature and product channels in parallel
180
+ const [featureChannels, productChannels] = await Promise.all([
181
+ listChannels('feature'),
182
+ listChannels('product'),
183
+ ]);
165
184
  let added = 0;
166
- for (const channel of channels) {
185
+ for (const channel of featureChannels) {
167
186
  if (channel.channel_ref_id && !activeChannels.has(channel.channel_ref_id)) {
168
187
  activeChannels.set(channel.channel_ref_id, channel.id);
169
188
  added++;
170
189
  }
171
190
  }
191
+ for (const channel of productChannels) {
192
+ if (channel.channel_ref_id && !activeProductChannels.has(channel.channel_ref_id)) {
193
+ activeProductChannels.set(channel.channel_ref_id, channel.id);
194
+ added++;
195
+ }
196
+ }
172
197
  if (added > 0) {
173
- log('info', `Discovered ${added} new channel(s) (total: ${activeChannels.size})`);
198
+ log('info', `Discovered ${added} new channel(s) (features: ${activeChannels.size}, products: ${activeProductChannels.size})`);
174
199
  }
175
200
  }
176
201
  catch (error) {
@@ -6,9 +6,9 @@ import assert from 'node:assert';
6
6
  import { STATUS_PROGRESSION_ORDER, PHASE_STATUS_MAP, } from '../feature-status.js';
7
7
  describe('Feature Status Configuration', () => {
8
8
  describe('STATUS_PROGRESSION_ORDER', () => {
9
- it('should start with backlog and end with shipped', () => {
9
+ it('should start with backlog and end with archived', () => {
10
10
  assert.strictEqual(STATUS_PROGRESSION_ORDER[0], 'backlog', 'First status should be backlog');
11
- assert.strictEqual(STATUS_PROGRESSION_ORDER[STATUS_PROGRESSION_ORDER.length - 1], 'shipped', 'Last status should be shipped');
11
+ assert.strictEqual(STATUS_PROGRESSION_ORDER[STATUS_PROGRESSION_ORDER.length - 1], 'archived', 'Last status should be archived');
12
12
  });
13
13
  it('should be readonly', () => {
14
14
  // This test ensures the configuration is properly typed as readonly
@@ -49,6 +49,7 @@ export const STATUS_PROGRESSION_ORDER = [
49
49
  'testing_failed',
50
50
  'ready_for_review',
51
51
  'shipped',
52
+ 'archived',
52
53
  ];
53
54
  /**
54
55
  * Phase to status mapping
@@ -119,6 +120,7 @@ export const HUMAN_SELECTABLE_STATUSES = [
119
120
  'functional_testing',
120
121
  'ready_for_review',
121
122
  'shipped',
123
+ 'archived',
122
124
  ];
123
125
  /**
124
126
  * Check if a status can be manually selected by a human user
package/dist/index.js CHANGED
File without changes
@@ -14,6 +14,7 @@ import { createBranches, getCurrentBranch, updateBranch, } from '../../services/
14
14
  import { prepareCustomBranchGitEnvironmentAsync, resetUncommittedChanges, } from '../../utils/git-branch-manager.js';
15
15
  import { createBranchPullRequest, } from '../code-implementation/branch-pr-creator.js';
16
16
  import { getGitHubConfig } from '../../api/github.js';
17
+ import { gitPush } from '../../utils/git-push.js';
17
18
  import { logFeaturePhaseEvent } from '../../services/audit-logs.js';
18
19
  import { createAutonomousSystemPrompt, createAutonomousUserPrompt, } from './prompts.js';
19
20
  /**
@@ -35,59 +36,6 @@ async function* prompt(userPrompt) {
35
36
  yield userMessage(userPrompt);
36
37
  await new Promise((res) => setTimeout(res, 10000));
37
38
  }
38
- /**
39
- * Push branch to remote repository
40
- */
41
- async function pushToRemote(branchName, verbose) {
42
- try {
43
- const { execSync } = await import('child_process');
44
- if (verbose) {
45
- logInfo(`📤 Pushing branch ${branchName} to remote...`);
46
- }
47
- try {
48
- execSync(`git push -u origin ${branchName}`, {
49
- encoding: 'utf-8',
50
- stdio: verbose ? 'inherit' : 'pipe',
51
- });
52
- return { success: true };
53
- }
54
- catch {
55
- try {
56
- execSync(`git push origin ${branchName}`, {
57
- encoding: 'utf-8',
58
- stdio: verbose ? 'inherit' : 'pipe',
59
- });
60
- return { success: true };
61
- }
62
- catch {
63
- if (verbose) {
64
- logInfo(`⚠️ Push rejected, attempting force push with lease...`);
65
- }
66
- try {
67
- execSync(`git push --force-with-lease origin ${branchName}`, {
68
- encoding: 'utf-8',
69
- stdio: verbose ? 'inherit' : 'pipe',
70
- });
71
- return { success: true };
72
- }
73
- catch (forceError) {
74
- return {
75
- success: false,
76
- error: forceError instanceof Error
77
- ? forceError.message
78
- : String(forceError),
79
- };
80
- }
81
- }
82
- }
83
- }
84
- catch (error) {
85
- return {
86
- success: false,
87
- error: error instanceof Error ? error.message : String(error),
88
- };
89
- }
90
- }
91
39
  /**
92
40
  * Run a single autonomous iteration using Claude Code SDK
93
41
  */
@@ -265,14 +213,19 @@ export async function runAutonomousDevelopment(options, config, _checklistContex
265
213
  logInfo(`⏱️ Iteration ${totalIterations} took ${(iterationDuration / 1000).toFixed(0)}s`);
266
214
  if (iterationResult.success) {
267
215
  logInfo(`✅ Iteration ${totalIterations}: ${iterationResult.summary || 'completed'}`);
268
- // Push to remote
269
- const pushResult = await pushToRemote(devBranchName, verbose);
216
+ // Get GitHub config for push authentication and PR creation
217
+ const githubConfig = await getGitHubConfig(featureId, verbose);
218
+ // Push to remote with authentication
219
+ const pushResult = gitPush({
220
+ branchName: devBranchName,
221
+ token: githubConfig.configured ? githubConfig.token : undefined,
222
+ verbose,
223
+ });
270
224
  if (!pushResult.success && verbose) {
271
225
  logError(`⚠️ Failed to push: ${pushResult.error}`);
272
226
  }
273
227
  // Create PR after first successful iteration
274
228
  if (!prUrl) {
275
- const githubConfig = await getGitHubConfig(featureId, verbose);
276
229
  if (githubConfig.configured &&
277
230
  githubConfig.token &&
278
231
  githubConfig.owner &&
@@ -27,6 +27,12 @@ export declare function clearChannelSession(channelId: string): void;
27
27
  * Returns the session ID (callers should track this if needed).
28
28
  */
29
29
  export declare function processHumanMessages(messages: ChatMessage[], featureId: string, config: EdsgerConfig, verbose?: boolean, repoPath?: string): Promise<string | undefined>;
30
+ /**
31
+ * Process one or more human messages from a product channel.
32
+ * Same session resumption pattern as feature chat, but uses
33
+ * product context, product prompt, and product tools.
34
+ */
35
+ export declare function processProductHumanMessages(messages: ChatMessage[], productId: string, config: EdsgerConfig, verbose?: boolean): Promise<string | undefined>;
30
36
  /**
31
37
  * Process a phase completion event: analyze output and suggest next steps.
32
38
  * Resumes the same channel session so AI has full conversation context.
@@ -15,8 +15,11 @@ import { DEFAULT_MODEL } from '../../constants.js';
15
15
  import { logInfo, logError } from '../../utils/logger.js';
16
16
  import { sendAiMessage, markMessageProcessed, sendSystemMessage, getFeatureChannel, listChatMessages, } from '../../api/chat.js';
17
17
  import { buildChatContext, formatContextForAI } from './context.js';
18
+ import { buildProductChatContext, formatProductContextForAI, } from './product-context.js';
18
19
  import { CHAT_RESPONSE_PROMPT, NEXT_STEP_ADVISOR_PROMPT, buildNextStepAdvisorMessage, } from './prompts.js';
20
+ import { PRODUCT_CHAT_RESPONSE_PROMPT } from './product-prompts.js';
19
21
  import { createChatMcpServer } from './tools.js';
22
+ import { createProductChatMcpServer } from './product-tools.js';
20
23
  // ============================================================
21
24
  // Session Management
22
25
  // ============================================================
@@ -153,6 +156,109 @@ export async function processHumanMessages(messages, featureId, config, verbose,
153
156
  return undefined;
154
157
  }
155
158
  }
159
+ /**
160
+ * Process one or more human messages from a product channel.
161
+ * Same session resumption pattern as feature chat, but uses
162
+ * product context, product prompt, and product tools.
163
+ */
164
+ export async function processProductHumanMessages(messages, productId, config, verbose) {
165
+ if (messages.length === 0)
166
+ return undefined;
167
+ const channelId = messages[0].channel_id;
168
+ const existingSessionId = channelSessions.get(channelId);
169
+ if (verbose) {
170
+ logInfo(`Processing ${messages.length} product message(s) for channel ${channelId}` +
171
+ (existingSessionId ? ` (resuming session ${existingSessionId})` : ' (new session)'));
172
+ }
173
+ try {
174
+ // Build the user prompt
175
+ const messageParts = messages.map((m, i) => {
176
+ if (messages.length === 1) {
177
+ return m.content;
178
+ }
179
+ return `[Message ${i + 1} from ${m.sender_name}]: ${m.content}`;
180
+ });
181
+ const userPrompt = messageParts.join('\n\n');
182
+ let fullPrompt;
183
+ if (!existingSessionId) {
184
+ const context = await buildProductChatContext(productId, channelId, verbose);
185
+ const contextStr = formatProductContextForAI(context);
186
+ // Load recent chat history
187
+ const recentMessages = await listChatMessages(channelId, { limit: 30 }, verbose);
188
+ const currentIds = new Set(messages.map((m) => m.id));
189
+ const historyMessages = recentMessages.filter((m) => !currentIds.has(m.id));
190
+ let historySection = '';
191
+ if (historyMessages.length > 0) {
192
+ const historyLines = historyMessages.map((m) => {
193
+ const role = m.sender_type === 'ai' ? 'AI' : m.sender_type === 'system' ? 'System' : (m.sender_name || 'User');
194
+ return `[${role}]: ${m.content}`;
195
+ });
196
+ historySection = [
197
+ `## Previous Chat History`,
198
+ `The following is the recent conversation history in this channel. Continue the conversation naturally.`,
199
+ '',
200
+ ...historyLines,
201
+ '',
202
+ ].join('\n');
203
+ }
204
+ fullPrompt = [
205
+ `## Current Product Context`,
206
+ contextStr,
207
+ '',
208
+ `## Channel ID: ${channelId}`,
209
+ `## Product ID: ${productId}`,
210
+ '',
211
+ historySection,
212
+ `## Human Message`,
213
+ userPrompt,
214
+ '',
215
+ `Respond to this message. Use tools if you need to take actions. Always respond in the chat using the send_chat_message tool.`,
216
+ ].join('\n');
217
+ }
218
+ else {
219
+ fullPrompt = userPrompt;
220
+ }
221
+ // Run the agent with product tools (no cwd — product chat doesn't operate on a repo)
222
+ const result = await runProductChatAgent(PRODUCT_CHAT_RESPONSE_PROMPT, fullPrompt, config, existingSessionId, verbose);
223
+ if (result.sessionId) {
224
+ channelSessions.set(channelId, result.sessionId);
225
+ }
226
+ const sentViaTool = result.toolsUsed.has('mcp__edsger-product-chat__send_chat_message');
227
+ if (result.text && !sentViaTool) {
228
+ await sendAiMessage(channelId, result.text, {
229
+ in_response_to: messages[messages.length - 1].id,
230
+ });
231
+ }
232
+ for (const message of messages) {
233
+ await markMessageProcessed(message.id, verbose);
234
+ }
235
+ if (verbose) {
236
+ logInfo(`All ${messages.length} product message(s) processed successfully`);
237
+ }
238
+ return result.sessionId;
239
+ }
240
+ catch (error) {
241
+ const msg = error instanceof Error ? error.message : String(error);
242
+ logError(`Failed to process product messages: ${msg}`);
243
+ if (existingSessionId) {
244
+ logInfo(`Clearing session ${existingSessionId} and retrying without resume`);
245
+ channelSessions.delete(channelId);
246
+ return processProductHumanMessages(messages, productId, config, verbose);
247
+ }
248
+ try {
249
+ await sendAiMessage(channelId, `Sorry, I encountered an error processing your message: ${msg}`, {
250
+ error: true,
251
+ });
252
+ }
253
+ catch {
254
+ // Ignore error sending error message
255
+ }
256
+ for (const message of messages) {
257
+ await markMessageProcessed(message.id, verbose);
258
+ }
259
+ return undefined;
260
+ }
261
+ }
156
262
  /**
157
263
  * Process a phase completion event: analyze output and suggest next steps.
158
264
  * Resumes the same channel session so AI has full conversation context.
@@ -309,3 +415,61 @@ async function runChatAgent(systemPrompt, userPrompt, config, resumeSessionId, v
309
415
  toolsUsed: toolUsed,
310
416
  };
311
417
  }
418
+ /**
419
+ * Run the Claude Agent SDK with product chat MCP tools.
420
+ * Same as runChatAgent but uses product-level tools instead of feature-level tools.
421
+ * No cwd parameter since product chat doesn't operate on a specific repo.
422
+ */
423
+ async function runProductChatAgent(systemPrompt, userPrompt, config, resumeSessionId, verbose) {
424
+ let lastAssistantResponse = '';
425
+ const toolUsed = new Set();
426
+ let sessionId;
427
+ const productChatMcpServer = createProductChatMcpServer();
428
+ for await (const message of query({
429
+ prompt: prompt(userPrompt),
430
+ options: {
431
+ systemPrompt: {
432
+ type: 'preset',
433
+ preset: 'claude_code',
434
+ append: systemPrompt,
435
+ },
436
+ model: DEFAULT_MODEL,
437
+ maxTurns: 20,
438
+ permissionMode: 'bypassPermissions',
439
+ mcpServers: {
440
+ 'edsger-product-chat': productChatMcpServer,
441
+ },
442
+ ...(resumeSessionId ? { resume: resumeSessionId } : {}),
443
+ },
444
+ })) {
445
+ if (message.session_id && !sessionId) {
446
+ sessionId = message.session_id;
447
+ }
448
+ if (message.type === 'assistant' && message.message?.content) {
449
+ for (const content of message.message.content) {
450
+ if (content.type === 'text') {
451
+ lastAssistantResponse += content.text + '\n';
452
+ if (verbose) {
453
+ console.log(`🤖 ${content.text}`);
454
+ }
455
+ }
456
+ else if (content.type === 'tool_use') {
457
+ toolUsed.add(content.name);
458
+ if (verbose) {
459
+ console.log(`🔧 Tool: ${content.name}`);
460
+ }
461
+ }
462
+ }
463
+ }
464
+ if (message.type === 'result') {
465
+ if (verbose) {
466
+ logInfo(`Product chat agent completed: ${message.subtype}`);
467
+ }
468
+ }
469
+ }
470
+ return {
471
+ text: lastAssistantResponse.trim(),
472
+ sessionId,
473
+ toolsUsed: toolUsed,
474
+ };
475
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Build context for the product chat AI processor.
3
+ * Fetches product state, features summary, and recent chat history.
4
+ */
5
+ import type { ChatMessage } from '../../types/index.js';
6
+ export interface ProductChatContext {
7
+ productId: string;
8
+ productName: string;
9
+ productDescription: string;
10
+ featuresSummary: {
11
+ total: number;
12
+ byStatus: Record<string, number>;
13
+ features: Array<{
14
+ id: string;
15
+ name: string;
16
+ status: string;
17
+ description: string;
18
+ }>;
19
+ };
20
+ teamMembers: Array<{
21
+ id: string;
22
+ name: string;
23
+ email: string;
24
+ role: string;
25
+ }>;
26
+ recentChatMessages: ChatMessage[];
27
+ channelId: string;
28
+ }
29
+ /**
30
+ * Build the full context for the product chat AI to process a message.
31
+ */
32
+ export declare function buildProductChatContext(productId: string, channelId: string, verbose?: boolean): Promise<ProductChatContext>;
33
+ /**
34
+ * Format product context into a string for the AI system prompt.
35
+ */
36
+ export declare function formatProductContextForAI(context: ProductChatContext): string;