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.
- package/README.md +66 -0
- package/dist/server/cli/headless/claude-invoker-process.js +1 -1
- package/dist/server/cli/headless/claude-invoker-process.js.map +1 -1
- package/dist/server/cli/headless/headless-logger.js +1 -1
- package/dist/server/cli/headless/headless-logger.js.map +1 -1
- package/dist/server/cli/headless/mcp-config.d.ts +1 -1
- package/dist/server/cli/headless/mcp-config.d.ts.map +1 -1
- package/dist/server/cli/headless/mcp-config.js +4 -1
- package/dist/server/cli/headless/mcp-config.js.map +1 -1
- package/dist/server/cli/headless/runner.d.ts.map +1 -1
- package/dist/server/cli/headless/runner.js +1 -0
- package/dist/server/cli/headless/runner.js.map +1 -1
- package/dist/server/cli/headless/types.d.ts +4 -1
- package/dist/server/cli/headless/types.d.ts.map +1 -1
- package/dist/server/index.js +9 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/mcp/bouncer-integration.d.ts +2 -2
- package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
- package/dist/server/mcp/bouncer-integration.js +20 -20
- package/dist/server/mcp/bouncer-integration.js.map +1 -1
- package/dist/server/mcp/security-analysis.d.ts +6 -0
- package/dist/server/mcp/security-analysis.d.ts.map +1 -1
- package/dist/server/mcp/security-analysis.js +16 -1
- package/dist/server/mcp/security-analysis.js.map +1 -1
- package/dist/server/mcp/security-patterns.d.ts +8 -0
- package/dist/server/mcp/security-patterns.d.ts.map +1 -1
- package/dist/server/mcp/security-patterns.js +47 -2
- package/dist/server/mcp/security-patterns.js.map +1 -1
- package/dist/server/services/deploy/ai-broker.d.ts +63 -0
- package/dist/server/services/deploy/ai-broker.d.ts.map +1 -0
- package/dist/server/services/deploy/ai-broker.js +360 -0
- package/dist/server/services/deploy/ai-broker.js.map +1 -0
- package/dist/server/services/deploy/board-execution-handler.d.ts +114 -0
- package/dist/server/services/deploy/board-execution-handler.d.ts.map +1 -0
- package/dist/server/services/deploy/board-execution-handler.js +621 -0
- package/dist/server/services/deploy/board-execution-handler.js.map +1 -0
- package/dist/server/services/deploy/credentials.d.ts +35 -0
- package/dist/server/services/deploy/credentials.d.ts.map +1 -0
- package/dist/server/services/deploy/credentials.js +177 -0
- package/dist/server/services/deploy/credentials.js.map +1 -0
- package/dist/server/services/deploy/deploy-ai-service.d.ts +107 -0
- package/dist/server/services/deploy/deploy-ai-service.d.ts.map +1 -0
- package/dist/server/services/deploy/deploy-ai-service.js +294 -0
- package/dist/server/services/deploy/deploy-ai-service.js.map +1 -0
- package/dist/server/services/deploy/headless-session-handler.d.ts +94 -0
- package/dist/server/services/deploy/headless-session-handler.d.ts.map +1 -0
- package/dist/server/services/deploy/headless-session-handler.js +274 -0
- package/dist/server/services/deploy/headless-session-handler.js.map +1 -0
- package/dist/server/services/pathUtils.d.ts.map +1 -1
- package/dist/server/services/pathUtils.js +33 -1
- package/dist/server/services/pathUtils.js.map +1 -1
- package/dist/server/services/plan/agent-loader.d.ts +10 -0
- package/dist/server/services/plan/agent-loader.d.ts.map +1 -0
- package/dist/server/services/plan/agent-loader.js +65 -0
- package/dist/server/services/plan/agent-loader.js.map +1 -0
- package/dist/server/services/plan/composer.d.ts.map +1 -1
- package/dist/server/services/plan/composer.js +5 -1
- package/dist/server/services/plan/composer.js.map +1 -1
- package/dist/server/services/plan/dependency-resolver.d.ts +1 -1
- package/dist/server/services/plan/dependency-resolver.js +2 -2
- package/dist/server/services/plan/dependency-resolver.js.map +1 -1
- package/dist/server/services/plan/executor.d.ts +7 -3
- package/dist/server/services/plan/executor.d.ts.map +1 -1
- package/dist/server/services/plan/executor.js +27 -14
- package/dist/server/services/plan/executor.js.map +1 -1
- package/dist/server/services/plan/front-matter.d.ts +5 -0
- package/dist/server/services/plan/front-matter.d.ts.map +1 -1
- package/dist/server/services/plan/front-matter.js +19 -0
- package/dist/server/services/plan/front-matter.js.map +1 -1
- package/dist/server/services/plan/issue-prompt-builder.d.ts +1 -1
- package/dist/server/services/plan/issue-prompt-builder.d.ts.map +1 -1
- package/dist/server/services/plan/issue-prompt-builder.js +1 -1
- package/dist/server/services/plan/issue-retry.d.ts +25 -0
- package/dist/server/services/plan/issue-retry.d.ts.map +1 -0
- package/dist/server/services/plan/issue-retry.js +216 -0
- package/dist/server/services/plan/issue-retry.js.map +1 -0
- package/dist/server/services/plan/output-manager.d.ts +2 -2
- package/dist/server/services/plan/output-manager.js +2 -2
- package/dist/server/services/plan/parser-core.d.ts +1 -1
- package/dist/server/services/plan/parser-core.js +1 -1
- package/dist/server/services/plan/parser-core.js.map +1 -1
- package/dist/server/services/plan/parser-migration.d.ts +2 -2
- package/dist/server/services/plan/parser-migration.d.ts.map +1 -1
- package/dist/server/services/plan/parser-migration.js +5 -5
- package/dist/server/services/plan/parser-migration.js.map +1 -1
- package/dist/server/services/plan/parser.d.ts.map +1 -1
- package/dist/server/services/plan/parser.js +4 -7
- package/dist/server/services/plan/parser.js.map +1 -1
- package/dist/server/services/plan/prompt-builder.d.ts +1 -1
- package/dist/server/services/plan/prompt-builder.d.ts.map +1 -1
- package/dist/server/services/plan/review-gate.d.ts +4 -0
- package/dist/server/services/plan/review-gate.d.ts.map +1 -1
- package/dist/server/services/plan/review-gate.js +90 -35
- package/dist/server/services/plan/review-gate.js.map +1 -1
- package/dist/server/services/plan/state-reconciler.d.ts.map +1 -1
- package/dist/server/services/plan/state-reconciler.js +21 -11
- package/dist/server/services/plan/state-reconciler.js.map +1 -1
- package/dist/server/services/plan/types.d.ts +2 -2
- package/dist/server/services/plan/types.d.ts.map +1 -1
- package/dist/server/services/plan/watcher.js +1 -1
- package/dist/server/services/sentry.d.ts.map +1 -1
- package/dist/server/services/sentry.js +8 -4
- package/dist/server/services/sentry.js.map +1 -1
- package/dist/server/services/websocket/deploy-handlers.d.ts +14 -0
- package/dist/server/services/websocket/deploy-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/deploy-handlers.js +409 -0
- package/dist/server/services/websocket/deploy-handlers.js.map +1 -0
- package/dist/server/services/websocket/handler.d.ts.map +1 -1
- package/dist/server/services/websocket/handler.js +12 -0
- package/dist/server/services/websocket/handler.js.map +1 -1
- package/dist/server/services/websocket/handlers/deploy-handlers.d.ts +11 -0
- package/dist/server/services/websocket/handlers/deploy-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/handlers/deploy-handlers.js +180 -0
- package/dist/server/services/websocket/handlers/deploy-handlers.js.map +1 -0
- package/dist/server/services/websocket/plan-board-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/plan-board-handlers.js +54 -1
- package/dist/server/services/websocket/plan-board-handlers.js.map +1 -1
- package/dist/server/services/websocket/plan-helpers.d.ts +1 -1
- package/dist/server/services/websocket/plan-helpers.d.ts.map +1 -1
- package/dist/server/services/websocket/plan-helpers.js +3 -4
- package/dist/server/services/websocket/plan-helpers.js.map +1 -1
- package/dist/server/services/websocket/plan-issue-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/plan-issue-handlers.js +5 -1
- package/dist/server/services/websocket/plan-issue-handlers.js.map +1 -1
- package/dist/server/services/websocket/plan-sprint-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/plan-sprint-handlers.js +3 -11
- package/dist/server/services/websocket/plan-sprint-handlers.js.map +1 -1
- package/dist/server/services/websocket/settings-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/settings-handlers.js +17 -21
- package/dist/server/services/websocket/settings-handlers.js.map +1 -1
- package/dist/server/services/websocket/types.d.ts +264 -2
- package/dist/server/services/websocket/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/server/cli/headless/claude-invoker-process.ts +1 -1
- package/server/cli/headless/headless-logger.ts +1 -1
- package/server/cli/headless/mcp-config.ts +4 -1
- package/server/cli/headless/runner.ts +1 -0
- package/server/cli/headless/types.ts +4 -1
- package/server/index.ts +9 -1
- package/server/mcp/bouncer-integration.ts +19 -17
- package/server/mcp/security-analysis.ts +19 -0
- package/server/mcp/security-patterns.ts +53 -2
- package/server/services/deploy/ai-broker.ts +512 -0
- package/server/services/deploy/board-execution-handler.ts +847 -0
- package/server/services/deploy/credentials.ts +200 -0
- package/server/services/deploy/deploy-ai-service.ts +401 -0
- package/server/services/deploy/headless-session-handler.ts +415 -0
- package/server/services/pathUtils.ts +35 -1
- package/server/services/plan/agent-loader.ts +73 -0
- package/server/services/plan/agents/review-code.md +28 -0
- package/server/services/plan/agents/review-custom.md +27 -0
- package/server/services/plan/agents/review-quality.md +42 -0
- package/server/services/plan/composer.ts +5 -1
- package/server/services/plan/dependency-resolver.ts +2 -2
- package/server/services/plan/executor.ts +27 -15
- package/server/services/plan/front-matter.ts +23 -0
- package/server/services/plan/issue-prompt-builder.ts +2 -2
- package/server/services/plan/issue-retry.ts +297 -0
- package/server/services/plan/output-manager.ts +2 -2
- package/server/services/plan/parser-core.ts +2 -2
- package/server/services/plan/parser-migration.ts +5 -5
- package/server/services/plan/parser.ts +4 -5
- package/server/services/plan/prompt-builder.ts +1 -1
- package/server/services/plan/review-gate.ts +105 -34
- package/server/services/plan/state-reconciler.ts +21 -11
- package/server/services/plan/types.ts +3 -3
- package/server/services/plan/watcher.ts +1 -1
- package/server/services/sentry.ts +8 -4
- package/server/services/websocket/deploy-handlers.ts +544 -0
- package/server/services/websocket/handler.ts +11 -1
- package/server/services/websocket/handlers/deploy-handlers.ts +230 -0
- package/server/services/websocket/plan-board-handlers.ts +53 -1
- package/server/services/websocket/plan-helpers.ts +3 -4
- package/server/services/websocket/plan-issue-handlers.ts +6 -1
- package/server/services/websocket/plan-sprint-handlers.ts +3 -9
- package/server/services/websocket/settings-handlers.ts +18 -22
- 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:
|
|
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(
|
|
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
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
201
|
-
|
|
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
|
-
|
|
204
|
-
}
|
|
237
|
+
// ── Hardcoded fallbacks (used when agent files are missing) ──────────
|
|
205
238
|
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
${
|
|
243
|
+
${filesModified}
|
|
211
244
|
|
|
212
245
|
## Acceptance Criteria
|
|
213
|
-
${criteria
|
|
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
|
|
218
|
-
3.
|
|
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
|
-
${
|
|
269
|
+
${issueSpecPath}
|
|
234
270
|
|
|
235
271
|
## Acceptance Criteria
|
|
236
|
-
${criteria
|
|
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.
|
|
242
|
-
|
|
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
|
|
114
|
+
* - All children done/cancelled → done
|
|
115
|
+
* - Any child in_progress/in_review → in_progress
|
|
116
|
+
* - Otherwise → null (no change)
|
|
115
117
|
*/
|
|
116
|
-
function
|
|
117
|
-
if (epic.children.length === 0) return
|
|
118
|
-
if (epic.status === 'done' || epic.status === 'cancelled') return
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
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',
|
|
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
|
-
|
|
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/
|
|
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
|
-
//
|
|
10
|
-
//
|
|
11
|
-
|
|
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:
|
|
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,
|