mstro-app 0.4.20 → 0.4.22

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 (177) hide show
  1. package/README.md +66 -0
  2. package/dist/server/cli/headless/claude-invoker-process.js +1 -1
  3. package/dist/server/cli/headless/claude-invoker-process.js.map +1 -1
  4. package/dist/server/cli/headless/headless-logger.js +1 -1
  5. package/dist/server/cli/headless/headless-logger.js.map +1 -1
  6. package/dist/server/cli/headless/mcp-config.d.ts +1 -1
  7. package/dist/server/cli/headless/mcp-config.d.ts.map +1 -1
  8. package/dist/server/cli/headless/mcp-config.js +4 -1
  9. package/dist/server/cli/headless/mcp-config.js.map +1 -1
  10. package/dist/server/cli/headless/runner.d.ts.map +1 -1
  11. package/dist/server/cli/headless/runner.js +1 -0
  12. package/dist/server/cli/headless/runner.js.map +1 -1
  13. package/dist/server/cli/headless/types.d.ts +4 -1
  14. package/dist/server/cli/headless/types.d.ts.map +1 -1
  15. package/dist/server/index.js +9 -1
  16. package/dist/server/index.js.map +1 -1
  17. package/dist/server/mcp/bouncer-integration.d.ts +2 -2
  18. package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
  19. package/dist/server/mcp/bouncer-integration.js +20 -20
  20. package/dist/server/mcp/bouncer-integration.js.map +1 -1
  21. package/dist/server/mcp/security-analysis.d.ts +6 -0
  22. package/dist/server/mcp/security-analysis.d.ts.map +1 -1
  23. package/dist/server/mcp/security-analysis.js +16 -1
  24. package/dist/server/mcp/security-analysis.js.map +1 -1
  25. package/dist/server/mcp/security-patterns.d.ts +8 -0
  26. package/dist/server/mcp/security-patterns.d.ts.map +1 -1
  27. package/dist/server/mcp/security-patterns.js +47 -2
  28. package/dist/server/mcp/security-patterns.js.map +1 -1
  29. package/dist/server/services/deploy/ai-broker.d.ts +63 -0
  30. package/dist/server/services/deploy/ai-broker.d.ts.map +1 -0
  31. package/dist/server/services/deploy/ai-broker.js +360 -0
  32. package/dist/server/services/deploy/ai-broker.js.map +1 -0
  33. package/dist/server/services/deploy/board-execution-handler.d.ts +114 -0
  34. package/dist/server/services/deploy/board-execution-handler.d.ts.map +1 -0
  35. package/dist/server/services/deploy/board-execution-handler.js +621 -0
  36. package/dist/server/services/deploy/board-execution-handler.js.map +1 -0
  37. package/dist/server/services/deploy/credentials.d.ts +35 -0
  38. package/dist/server/services/deploy/credentials.d.ts.map +1 -0
  39. package/dist/server/services/deploy/credentials.js +177 -0
  40. package/dist/server/services/deploy/credentials.js.map +1 -0
  41. package/dist/server/services/deploy/deploy-ai-service.d.ts +107 -0
  42. package/dist/server/services/deploy/deploy-ai-service.d.ts.map +1 -0
  43. package/dist/server/services/deploy/deploy-ai-service.js +294 -0
  44. package/dist/server/services/deploy/deploy-ai-service.js.map +1 -0
  45. package/dist/server/services/deploy/headless-session-handler.d.ts +94 -0
  46. package/dist/server/services/deploy/headless-session-handler.d.ts.map +1 -0
  47. package/dist/server/services/deploy/headless-session-handler.js +274 -0
  48. package/dist/server/services/deploy/headless-session-handler.js.map +1 -0
  49. package/dist/server/services/pathUtils.d.ts.map +1 -1
  50. package/dist/server/services/pathUtils.js +33 -1
  51. package/dist/server/services/pathUtils.js.map +1 -1
  52. package/dist/server/services/plan/agent-loader.d.ts +10 -0
  53. package/dist/server/services/plan/agent-loader.d.ts.map +1 -0
  54. package/dist/server/services/plan/agent-loader.js +65 -0
  55. package/dist/server/services/plan/agent-loader.js.map +1 -0
  56. package/dist/server/services/plan/composer.d.ts.map +1 -1
  57. package/dist/server/services/plan/composer.js +5 -1
  58. package/dist/server/services/plan/composer.js.map +1 -1
  59. package/dist/server/services/plan/dependency-resolver.d.ts +1 -1
  60. package/dist/server/services/plan/dependency-resolver.js +2 -2
  61. package/dist/server/services/plan/dependency-resolver.js.map +1 -1
  62. package/dist/server/services/plan/executor.d.ts +7 -3
  63. package/dist/server/services/plan/executor.d.ts.map +1 -1
  64. package/dist/server/services/plan/executor.js +27 -14
  65. package/dist/server/services/plan/executor.js.map +1 -1
  66. package/dist/server/services/plan/front-matter.d.ts +5 -0
  67. package/dist/server/services/plan/front-matter.d.ts.map +1 -1
  68. package/dist/server/services/plan/front-matter.js +19 -0
  69. package/dist/server/services/plan/front-matter.js.map +1 -1
  70. package/dist/server/services/plan/issue-prompt-builder.d.ts +1 -1
  71. package/dist/server/services/plan/issue-prompt-builder.d.ts.map +1 -1
  72. package/dist/server/services/plan/issue-prompt-builder.js +1 -1
  73. package/dist/server/services/plan/issue-retry.d.ts +25 -0
  74. package/dist/server/services/plan/issue-retry.d.ts.map +1 -0
  75. package/dist/server/services/plan/issue-retry.js +216 -0
  76. package/dist/server/services/plan/issue-retry.js.map +1 -0
  77. package/dist/server/services/plan/output-manager.d.ts +2 -2
  78. package/dist/server/services/plan/output-manager.js +2 -2
  79. package/dist/server/services/plan/parser-core.d.ts +1 -1
  80. package/dist/server/services/plan/parser-core.js +1 -1
  81. package/dist/server/services/plan/parser-core.js.map +1 -1
  82. package/dist/server/services/plan/parser-migration.d.ts +2 -2
  83. package/dist/server/services/plan/parser-migration.d.ts.map +1 -1
  84. package/dist/server/services/plan/parser-migration.js +5 -5
  85. package/dist/server/services/plan/parser-migration.js.map +1 -1
  86. package/dist/server/services/plan/parser.d.ts.map +1 -1
  87. package/dist/server/services/plan/parser.js +4 -7
  88. package/dist/server/services/plan/parser.js.map +1 -1
  89. package/dist/server/services/plan/prompt-builder.d.ts +1 -1
  90. package/dist/server/services/plan/prompt-builder.d.ts.map +1 -1
  91. package/dist/server/services/plan/review-gate.d.ts +4 -0
  92. package/dist/server/services/plan/review-gate.d.ts.map +1 -1
  93. package/dist/server/services/plan/review-gate.js +90 -35
  94. package/dist/server/services/plan/review-gate.js.map +1 -1
  95. package/dist/server/services/plan/state-reconciler.d.ts.map +1 -1
  96. package/dist/server/services/plan/state-reconciler.js +21 -11
  97. package/dist/server/services/plan/state-reconciler.js.map +1 -1
  98. package/dist/server/services/plan/types.d.ts +2 -2
  99. package/dist/server/services/plan/types.d.ts.map +1 -1
  100. package/dist/server/services/plan/watcher.js +1 -1
  101. package/dist/server/services/sentry.d.ts.map +1 -1
  102. package/dist/server/services/sentry.js +8 -4
  103. package/dist/server/services/sentry.js.map +1 -1
  104. package/dist/server/services/websocket/deploy-handlers.d.ts +14 -0
  105. package/dist/server/services/websocket/deploy-handlers.d.ts.map +1 -0
  106. package/dist/server/services/websocket/deploy-handlers.js +409 -0
  107. package/dist/server/services/websocket/deploy-handlers.js.map +1 -0
  108. package/dist/server/services/websocket/handler.d.ts.map +1 -1
  109. package/dist/server/services/websocket/handler.js +12 -0
  110. package/dist/server/services/websocket/handler.js.map +1 -1
  111. package/dist/server/services/websocket/handlers/deploy-handlers.d.ts +11 -0
  112. package/dist/server/services/websocket/handlers/deploy-handlers.d.ts.map +1 -0
  113. package/dist/server/services/websocket/handlers/deploy-handlers.js +180 -0
  114. package/dist/server/services/websocket/handlers/deploy-handlers.js.map +1 -0
  115. package/dist/server/services/websocket/plan-board-handlers.d.ts.map +1 -1
  116. package/dist/server/services/websocket/plan-board-handlers.js +54 -1
  117. package/dist/server/services/websocket/plan-board-handlers.js.map +1 -1
  118. package/dist/server/services/websocket/plan-helpers.d.ts +1 -1
  119. package/dist/server/services/websocket/plan-helpers.d.ts.map +1 -1
  120. package/dist/server/services/websocket/plan-helpers.js +3 -4
  121. package/dist/server/services/websocket/plan-helpers.js.map +1 -1
  122. package/dist/server/services/websocket/plan-issue-handlers.d.ts.map +1 -1
  123. package/dist/server/services/websocket/plan-issue-handlers.js +5 -1
  124. package/dist/server/services/websocket/plan-issue-handlers.js.map +1 -1
  125. package/dist/server/services/websocket/plan-sprint-handlers.d.ts.map +1 -1
  126. package/dist/server/services/websocket/plan-sprint-handlers.js +3 -11
  127. package/dist/server/services/websocket/plan-sprint-handlers.js.map +1 -1
  128. package/dist/server/services/websocket/settings-handlers.d.ts.map +1 -1
  129. package/dist/server/services/websocket/settings-handlers.js +17 -21
  130. package/dist/server/services/websocket/settings-handlers.js.map +1 -1
  131. package/dist/server/services/websocket/types.d.ts +264 -2
  132. package/dist/server/services/websocket/types.d.ts.map +1 -1
  133. package/package.json +1 -1
  134. package/server/cli/headless/claude-invoker-process.ts +1 -1
  135. package/server/cli/headless/headless-logger.ts +1 -1
  136. package/server/cli/headless/mcp-config.ts +4 -1
  137. package/server/cli/headless/runner.ts +1 -0
  138. package/server/cli/headless/types.ts +4 -1
  139. package/server/index.ts +9 -1
  140. package/server/mcp/bouncer-integration.ts +19 -17
  141. package/server/mcp/security-analysis.ts +19 -0
  142. package/server/mcp/security-patterns.ts +53 -2
  143. package/server/services/deploy/ai-broker.ts +512 -0
  144. package/server/services/deploy/board-execution-handler.ts +847 -0
  145. package/server/services/deploy/credentials.ts +200 -0
  146. package/server/services/deploy/deploy-ai-service.ts +401 -0
  147. package/server/services/deploy/headless-session-handler.ts +415 -0
  148. package/server/services/pathUtils.ts +35 -1
  149. package/server/services/plan/agent-loader.ts +73 -0
  150. package/server/services/plan/agents/review-code.md +28 -0
  151. package/server/services/plan/agents/review-custom.md +27 -0
  152. package/server/services/plan/agents/review-quality.md +42 -0
  153. package/server/services/plan/composer.ts +5 -1
  154. package/server/services/plan/dependency-resolver.ts +2 -2
  155. package/server/services/plan/executor.ts +27 -15
  156. package/server/services/plan/front-matter.ts +23 -0
  157. package/server/services/plan/issue-prompt-builder.ts +2 -2
  158. package/server/services/plan/issue-retry.ts +297 -0
  159. package/server/services/plan/output-manager.ts +2 -2
  160. package/server/services/plan/parser-core.ts +2 -2
  161. package/server/services/plan/parser-migration.ts +5 -5
  162. package/server/services/plan/parser.ts +4 -5
  163. package/server/services/plan/prompt-builder.ts +1 -1
  164. package/server/services/plan/review-gate.ts +105 -34
  165. package/server/services/plan/state-reconciler.ts +21 -11
  166. package/server/services/plan/types.ts +3 -3
  167. package/server/services/plan/watcher.ts +1 -1
  168. package/server/services/sentry.ts +8 -4
  169. package/server/services/websocket/deploy-handlers.ts +544 -0
  170. package/server/services/websocket/handler.ts +11 -1
  171. package/server/services/websocket/handlers/deploy-handlers.ts +230 -0
  172. package/server/services/websocket/plan-board-handlers.ts +53 -1
  173. package/server/services/websocket/plan-helpers.ts +3 -4
  174. package/server/services/websocket/plan-issue-handlers.ts +6 -1
  175. package/server/services/websocket/plan-sprint-handlers.ts +3 -9
  176. package/server/services/websocket/settings-handlers.ts +18 -22
  177. package/server/services/websocket/types.ts +333 -2
@@ -13,6 +13,7 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from
13
13
  import { join } from 'node:path';
14
14
  import { runWithFileLogger } from '../../cli/headless/headless-logger.js';
15
15
  import { HeadlessRunner } from '../../cli/headless/index.js';
16
+ import { loadAgentPrompt } from './agent-loader.js';
16
17
  import type { Issue, ReviewCheck, ReviewResult } from './types.js';
17
18
 
18
19
  /** Max review attempts per issue per sprint before giving up */
@@ -33,6 +34,10 @@ export interface ReviewIssueOptions {
33
34
  logDir?: string;
34
35
  /** Custom board-level review criteria — replaces default review instructions when set */
35
36
  reviewCriteria?: string;
37
+ /** Board directory for agent prompt override resolution (e.g., .mstro/pm/boards/BOARD-001) */
38
+ boardDir?: string | null;
39
+ /** Extra environment variables for spawned Claude processes (e.g. API keys) */
40
+ extraEnv?: Record<string, string>;
36
41
  }
37
42
 
38
43
  /**
@@ -40,12 +45,12 @@ export interface ReviewIssueOptions {
40
45
  * Returns auto-pass on infrastructure failures to avoid blocking execution.
41
46
  */
42
47
  export async function reviewIssue(options: ReviewIssueOptions): Promise<ReviewResult> {
43
- const { workingDir, issue, pmDir, outputPath, onOutput, logDir, reviewCriteria } = options;
48
+ const { workingDir, issue, pmDir, outputPath, onOutput, logDir, reviewCriteria, boardDir } = options;
44
49
  const isCodeTask = issue.filesToModify.length > 0;
45
50
  const issueType: ReviewResult['issueType'] = isCodeTask ? 'code' : 'non-code';
46
51
 
47
52
  try {
48
- const prompt = buildReviewPrompt(issue, pmDir, outputPath, isCodeTask, reviewCriteria);
53
+ const prompt = buildReviewPrompt(issue, pmDir, outputPath, isCodeTask, reviewCriteria, boardDir);
49
54
 
50
55
  const runner = new HeadlessRunner({
51
56
  workingDir,
@@ -53,8 +58,9 @@ export async function reviewIssue(options: ReviewIssueOptions): Promise<ReviewRe
53
58
  stallWarningMs: REVIEW_STALL_WARNING_MS,
54
59
  stallKillMs: REVIEW_STALL_KILL_MS,
55
60
  stallHardCapMs: REVIEW_STALL_HARD_CAP_MS,
56
- verbose: false,
61
+ verbose: true,
57
62
  outputCallback: onOutput ? (text: string) => onOutput(`Review: ${text}`) : undefined,
63
+ extraEnv: options.extraEnv,
58
64
  });
59
65
 
60
66
  const result = await runWithFileLogger('pm-review', () => runner.run(), logDir);
@@ -175,74 +181,139 @@ export function autoPassResult(issueId: string, issueType: ReviewResult['issueTy
175
181
 
176
182
  // ── Private helpers ─────────────────────────────────────────
177
183
 
178
- function buildReviewPrompt(issue: Issue, pmDir: string, outputPath: string, isCodeTask: boolean, reviewCriteria?: string): string {
184
+ function buildReviewPrompt(
185
+ issue: Issue,
186
+ pmDir: string,
187
+ outputPath: string,
188
+ isCodeTask: boolean,
189
+ reviewCriteria?: string,
190
+ boardDir?: string | null,
191
+ ): string {
179
192
  const criteria = issue.acceptanceCriteria
180
193
  .map(c => `- [${c.checked ? 'x' : ' '}] ${c.text}`)
181
194
  .join('\n');
195
+ const criteriaStr = criteria || 'No specific criteria defined.';
196
+ const filesModified = issue.filesToModify.map(f => `- ${f}`).join('\n');
197
+ const issueSpecPath = join(pmDir, issue.path);
182
198
 
183
- // When custom review criteria are set, use a generic review prompt
184
- // that applies the user's criteria instead of assuming code review.
199
+ // When custom review criteria are set, use the review-custom agent.
185
200
  if (reviewCriteria) {
186
- return `You are a reviewer. Review the work done for issue ${issue.id}: ${issue.title}.
187
- ${isCodeTask ? `\n## Files Modified\n${issue.filesToModify.map(f => `- ${f}`).join('\n')}` : `\n## Output File\n${outputPath}\n\n## Issue Spec\n${join(pmDir, issue.path)}`}
188
-
189
- ## Acceptance Criteria
190
- ${criteria || 'No specific criteria defined.'}
191
-
192
- ## Review Criteria
193
- ${reviewCriteria}
201
+ const contextSection = isCodeTask
202
+ ? `\n## Files Modified\n${filesModified}`
203
+ : `\n## Output File\n${outputPath}\n\n## Issue Spec\n${issueSpecPath}`;
204
+ const readInstruction = isCodeTask
205
+ ? 'Read each modified file listed above'
206
+ : 'Read the output file and issue spec at the paths above';
207
+
208
+ return loadAgentPrompt('review-custom', {
209
+ issue_id: issue.id,
210
+ issue_title: issue.title,
211
+ context_section: contextSection,
212
+ acceptance_criteria: criteriaStr,
213
+ review_criteria: reviewCriteria,
214
+ read_instruction: readInstruction,
215
+ }, boardDir) ?? buildCustomFallback(issue, contextSection, criteriaStr, reviewCriteria, readInstruction);
216
+ }
194
217
 
195
- ## Instructions
196
- 1. ${isCodeTask ? 'Read each modified file listed above' : 'Read the output file and issue spec at the paths above'}
197
- 2. Check if all acceptance criteria are met
198
- 3. Evaluate against the review criteria above
218
+ if (isCodeTask) {
219
+ return loadAgentPrompt('review-code', {
220
+ issue_id: issue.id,
221
+ issue_title: issue.title,
222
+ files_modified: filesModified,
223
+ acceptance_criteria: criteriaStr,
224
+ output_path: outputPath,
225
+ }, boardDir) ?? buildCodeFallback(issue, filesModified, criteriaStr, outputPath);
226
+ }
199
227
 
200
- Output EXACTLY one JSON object on its own line (no markdown fencing):
201
- {"passed": true, "checks": [{"name": "criteria_met", "passed": true, "details": "..."}]}
228
+ return loadAgentPrompt('review-quality', {
229
+ issue_id: issue.id,
230
+ issue_title: issue.title,
231
+ output_path: outputPath,
232
+ issue_spec_path: issueSpecPath,
233
+ acceptance_criteria: criteriaStr,
234
+ }, boardDir) ?? buildQualityFallback(issue, outputPath, issueSpecPath, criteriaStr);
235
+ }
202
236
 
203
- Include checks for: criteria_met, review_criteria.`;
204
- }
237
+ // ── Hardcoded fallbacks (used when agent files are missing) ──────────
205
238
 
206
- if (isCodeTask) {
207
- return `You are a code reviewer. Review the work done for issue ${issue.id}: ${issue.title}.
239
+ function buildCodeFallback(issue: Issue, filesModified: string, criteria: string, outputPath: string): string {
240
+ return `You are a reviewer. Review the work done for issue ${issue.id}: ${issue.title}.
208
241
 
209
242
  ## Files Modified
210
- ${issue.filesToModify.map(f => `- ${f}`).join('\n')}
243
+ ${filesModified}
211
244
 
212
245
  ## Acceptance Criteria
213
- ${criteria || 'No specific criteria defined.'}
246
+ ${criteria}
214
247
 
215
248
  ## Instructions
216
249
  1. Read each modified file listed above
217
- 2. Check if all acceptance criteria are met by the code changes
218
- 3. Look for obvious bugs, security vulnerabilities, or code quality issues
250
+ 2. Check if all acceptance criteria are met by the changes
251
+ 3. Evaluate the quality of the changes:
252
+ - For source code files: look for obvious bugs, security vulnerabilities, or code quality issues
253
+ - For content files (markdown, docs, config, copy): check for accuracy, completeness, and appropriate structure
219
254
  4. Check if the output artifact exists at: ${outputPath}
220
255
 
221
256
  Output EXACTLY one JSON object on its own line (no markdown fencing):
222
257
  {"passed": true, "checks": [{"name": "criteria_met", "passed": true, "details": "..."}]}
223
258
 
224
259
  Include checks for: criteria_met, code_quality, no_obvious_bugs.`;
225
- }
260
+ }
226
261
 
262
+ function buildQualityFallback(issue: Issue, outputPath: string, issueSpecPath: string, criteria: string): string {
227
263
  return `You are a quality reviewer. Review the work done for issue ${issue.id}: ${issue.title}.
228
264
 
229
265
  ## Output File
230
266
  ${outputPath}
231
267
 
232
268
  ## Issue Spec
233
- ${join(pmDir, issue.path)}
269
+ ${issueSpecPath}
234
270
 
235
271
  ## Acceptance Criteria
236
- ${criteria || 'No specific criteria defined.'}
272
+ ${criteria}
237
273
 
238
274
  ## Instructions
239
275
  1. Read the output file at the path above
240
- 2. Read the full issue spec
241
- 3. Check if all acceptance criteria are met
242
- 4. Check for completeness and quality of the output
276
+ 2. Read the full issue spec to understand the original requirements and intent
277
+ 3. Evaluate the output against ALL of the following dimensions:
278
+
279
+ ### Acceptance Criteria
280
+ - Are all acceptance criteria met? Check each one individually.
281
+
282
+ ### Content Quality
283
+ - Is the content accurate, well-reasoned, and free of factual errors?
284
+ - Is it written clearly with appropriate structure and organization?
285
+ - Does it have sufficient depth and detail for its purpose?
286
+ - Is the tone and style appropriate for the intended audience?
287
+
288
+ ### Completeness
289
+ - Does the output fully address what was requested in the issue spec?
290
+ - Are there obvious gaps, missing sections, or incomplete thoughts?
291
+ - If the issue requested specific deliverables (e.g., a plan, analysis, document), are all deliverables present?
243
292
 
244
293
  Output EXACTLY one JSON object on its own line (no markdown fencing):
245
294
  {"passed": true, "checks": [{"name": "criteria_met", "passed": true, "details": "..."}]}
246
295
 
247
296
  Include checks for: criteria_met, output_quality, completeness.`;
248
297
  }
298
+
299
+ function buildCustomFallback(issue: Issue, contextSection: string, criteria: string, reviewCriteria: string, readInstruction: string): string {
300
+ return `You are a reviewer. Review the work done for issue ${issue.id}: ${issue.title}.
301
+ ${contextSection}
302
+
303
+ ## Acceptance Criteria
304
+ ${criteria}
305
+
306
+ ## Review Criteria
307
+ ${reviewCriteria}
308
+
309
+ ## Instructions
310
+ 1. ${readInstruction}
311
+ 2. Check if all acceptance criteria are met — evaluate each criterion individually
312
+ 3. Evaluate thoroughly against the review criteria above
313
+ 4. Consider the overall quality of the work: does it fully address the issue's intent, is it well-structured, and is it ready to ship?
314
+
315
+ Output EXACTLY one JSON object on its own line (no markdown fencing):
316
+ {"passed": true, "checks": [{"name": "criteria_met", "passed": true, "details": "..."}]}
317
+
318
+ Include checks for: criteria_met, review_criteria.`;
319
+ }
@@ -111,27 +111,36 @@ function buildStateMarkdown(
111
111
 
112
112
  /**
113
113
  * Derive epic status from its children's actual statuses.
114
- * All children done/cancelled → done (auto-complete the epic).
114
+ * - All children done/cancelled → done
115
+ * - Any child in_progress/in_review → in_progress
116
+ * - Otherwise → null (no change)
115
117
  */
116
- function deriveEpicDone(epic: Issue, issueByPath: Map<string, Issue>): boolean {
117
- if (epic.children.length === 0) return false;
118
- if (epic.status === 'done' || epic.status === 'cancelled') return false;
118
+ function deriveEpicStatus(epic: Issue, issueByPath: Map<string, Issue>): string | null {
119
+ if (epic.children.length === 0) return null;
120
+ if (epic.status === 'done' || epic.status === 'cancelled') return null;
119
121
 
120
- return epic.children.every(childPath => {
121
- const child = issueByPath.get(childPath);
122
- return child && (child.status === 'done' || child.status === 'cancelled');
123
- });
122
+ const childStatuses = epic.children.map(cp => issueByPath.get(cp)?.status).filter(Boolean) as string[];
123
+ if (childStatuses.length === 0) return null;
124
+
125
+ const allFinished = childStatuses.every(s => s === 'done' || s === 'cancelled');
126
+ if (allFinished) return 'done';
127
+
128
+ const anyStarted = childStatuses.some(s => s === 'in_progress' || s === 'in_review');
129
+ if (anyStarted && epic.status !== 'in_progress') return 'in_progress';
130
+
131
+ return null;
124
132
  }
125
133
 
126
134
  function reconcileEpicStatuses(pmDir: string, issues: Issue[], issueByPath: Map<string, Issue>): void {
127
135
  const epics = issues.filter(i => i.type === 'epic');
128
136
  for (const epic of epics) {
129
- if (!deriveEpicDone(epic, issueByPath)) continue;
137
+ const derived = deriveEpicStatus(epic, issueByPath);
138
+ if (!derived) continue;
130
139
 
131
140
  const epicPath = join(pmDir, epic.path);
132
141
  try {
133
142
  let content = readFileSync(epicPath, 'utf-8');
134
- content = replaceFrontMatterField(content, 'status', 'done');
143
+ content = replaceFrontMatterField(content, 'status', derived);
135
144
  writeFileSync(epicPath, content, 'utf-8');
136
145
  } catch {
137
146
  // Epic file may be missing or unwritable
@@ -206,7 +215,8 @@ export function tryCompleteParentEpic(workingDir: string, updatedIssue: Issue):
206
215
  if (!epic) return null;
207
216
 
208
217
  const issueByPath = new Map(issues.map(i => [i.path, i]));
209
- if (!deriveEpicDone(epic, issueByPath)) return null;
218
+ const derived = deriveEpicStatus(epic, issueByPath);
219
+ if (derived !== 'done') return null;
210
220
 
211
221
  const epicFullPath = join(pmDir, epic.path);
212
222
  try {
@@ -4,7 +4,7 @@
4
4
  /**
5
5
  * Plan Types — Project Plan Spec (PPS) data structures
6
6
  *
7
- * These types represent the parsed contents of .pm/ directory files.
7
+ * These types represent the parsed contents of .mstro/pm/ directory files.
8
8
  */
9
9
 
10
10
  // ============================================================================
@@ -25,7 +25,7 @@ export interface ProjectConfig {
25
25
 
26
26
  export interface WorkflowStatus {
27
27
  status: string;
28
- category: 'unstarted' | 'started' | 'completed' | 'cancelled';
28
+ category: 'ready' | 'unstarted' | 'started' | 'completed' | 'cancelled';
29
29
  description: string;
30
30
  }
31
31
 
@@ -98,7 +98,7 @@ export interface Issue {
98
98
  outputFile: string | null;
99
99
  // Full markdown body
100
100
  body: string;
101
- // File path relative to .pm/
101
+ // File path relative to .mstro/pm/
102
102
  path: string;
103
103
  }
104
104
 
@@ -2,7 +2,7 @@
2
2
  // Licensed under the MIT License. See LICENSE file for details.
3
3
 
4
4
  /**
5
- * Plan Watcher — Watches .pm/ (or legacy .plan/) directory for changes and broadcasts updates.
5
+ * Plan Watcher — Watches .mstro/pm/ directory for changes and broadcasts updates.
6
6
  *
7
7
  * Uses fs.watch with debouncing to batch rapid changes.
8
8
  */
@@ -6,9 +6,12 @@ import { homedir } from 'node:os'
6
6
  import { join } from 'node:path'
7
7
  import * as Sentry from '@sentry/node'
8
8
 
9
- // Hardcoded DSN for production - this is safe to expose (can only send, not read)
10
- // Override with SENTRY_DSN env var for development/testing
11
- const SENTRY_DSN = process.env.SENTRY_DSN || 'https://2a8d2493e3ee5a7beec30f4518a5e24c@o4510824844820480.ingest.us.sentry.io/4510824923594752'
9
+ // Sentry DSN lives on the platform server. The CLI sends envelopes
10
+ // to the server's /sentry-tunnel endpoint which proxies to Sentry.
11
+ // A placeholder DSN is needed so the Sentry SDK initializes its
12
+ // transport — the real DSN is injected server-side before forwarding.
13
+ const SENTRY_TUNNEL_DSN = 'https://tunnel@sentry.io/0'
14
+ const PLATFORM_URL = process.env.PLATFORM_URL || 'https://api.mstro.app'
12
15
 
13
16
  const CONFIG_FILE = join(homedir(), '.mstro', 'config.json')
14
17
 
@@ -51,7 +54,8 @@ export function initSentry(): void {
51
54
  initialized = true
52
55
 
53
56
  Sentry.init({
54
- dsn: SENTRY_DSN,
57
+ dsn: SENTRY_TUNNEL_DSN,
58
+ tunnel: `${PLATFORM_URL}/sentry-tunnel`,
55
59
  environment: process.env.NODE_ENV || 'development',
56
60
  release: `mstro-cli@${process.env.npm_package_version || '0.0.0'}`,
57
61
  tracesSampleRate: 0.1,