mstro-app 0.3.8 → 0.4.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 (109) hide show
  1. package/LICENSE +191 -21
  2. package/PRIVACY.md +286 -62
  3. package/README.md +81 -58
  4. package/bin/commands/status.js +1 -1
  5. package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -1
  6. package/dist/server/cli/headless/claude-invoker.js +22 -12
  7. package/dist/server/cli/headless/claude-invoker.js.map +1 -1
  8. package/dist/server/cli/headless/headless-logger.d.ts +10 -0
  9. package/dist/server/cli/headless/headless-logger.d.ts.map +1 -0
  10. package/dist/server/cli/headless/headless-logger.js +66 -0
  11. package/dist/server/cli/headless/headless-logger.js.map +1 -0
  12. package/dist/server/cli/headless/mcp-config.d.ts.map +1 -1
  13. package/dist/server/cli/headless/mcp-config.js +6 -5
  14. package/dist/server/cli/headless/mcp-config.js.map +1 -1
  15. package/dist/server/cli/headless/runner.d.ts.map +1 -1
  16. package/dist/server/cli/headless/runner.js +4 -0
  17. package/dist/server/cli/headless/runner.js.map +1 -1
  18. package/dist/server/cli/headless/stall-assessor.d.ts +21 -0
  19. package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
  20. package/dist/server/cli/headless/stall-assessor.js +100 -24
  21. package/dist/server/cli/headless/stall-assessor.js.map +1 -1
  22. package/dist/server/cli/headless/tool-watchdog.d.ts +0 -12
  23. package/dist/server/cli/headless/tool-watchdog.d.ts.map +1 -1
  24. package/dist/server/cli/headless/tool-watchdog.js +22 -9
  25. package/dist/server/cli/headless/tool-watchdog.js.map +1 -1
  26. package/dist/server/cli/headless/types.d.ts +8 -1
  27. package/dist/server/cli/headless/types.d.ts.map +1 -1
  28. package/dist/server/cli/improvisation-session-manager.d.ts +16 -0
  29. package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
  30. package/dist/server/cli/improvisation-session-manager.js +94 -11
  31. package/dist/server/cli/improvisation-session-manager.js.map +1 -1
  32. package/dist/server/mcp/bouncer-cli.d.ts +3 -0
  33. package/dist/server/mcp/bouncer-cli.d.ts.map +1 -0
  34. package/dist/server/mcp/bouncer-cli.js +54 -0
  35. package/dist/server/mcp/bouncer-cli.js.map +1 -0
  36. package/dist/server/services/plan/composer.d.ts +4 -0
  37. package/dist/server/services/plan/composer.d.ts.map +1 -0
  38. package/dist/server/services/plan/composer.js +181 -0
  39. package/dist/server/services/plan/composer.js.map +1 -0
  40. package/dist/server/services/plan/dependency-resolver.d.ts +28 -0
  41. package/dist/server/services/plan/dependency-resolver.d.ts.map +1 -0
  42. package/dist/server/services/plan/dependency-resolver.js +154 -0
  43. package/dist/server/services/plan/dependency-resolver.js.map +1 -0
  44. package/dist/server/services/plan/executor.d.ts +110 -0
  45. package/dist/server/services/plan/executor.d.ts.map +1 -0
  46. package/dist/server/services/plan/executor.js +641 -0
  47. package/dist/server/services/plan/executor.js.map +1 -0
  48. package/dist/server/services/plan/parser.d.ts +11 -0
  49. package/dist/server/services/plan/parser.d.ts.map +1 -0
  50. package/dist/server/services/plan/parser.js +445 -0
  51. package/dist/server/services/plan/parser.js.map +1 -0
  52. package/dist/server/services/plan/state-reconciler.d.ts +2 -0
  53. package/dist/server/services/plan/state-reconciler.d.ts.map +1 -0
  54. package/dist/server/services/plan/state-reconciler.js +145 -0
  55. package/dist/server/services/plan/state-reconciler.js.map +1 -0
  56. package/dist/server/services/plan/types.d.ts +121 -0
  57. package/dist/server/services/plan/types.d.ts.map +1 -0
  58. package/dist/server/services/plan/types.js +4 -0
  59. package/dist/server/services/plan/types.js.map +1 -0
  60. package/dist/server/services/plan/watcher.d.ts +14 -0
  61. package/dist/server/services/plan/watcher.d.ts.map +1 -0
  62. package/dist/server/services/plan/watcher.js +69 -0
  63. package/dist/server/services/plan/watcher.js.map +1 -0
  64. package/dist/server/services/websocket/file-explorer-handlers.js +20 -0
  65. package/dist/server/services/websocket/file-explorer-handlers.js.map +1 -1
  66. package/dist/server/services/websocket/handler.d.ts.map +1 -1
  67. package/dist/server/services/websocket/handler.js +21 -0
  68. package/dist/server/services/websocket/handler.js.map +1 -1
  69. package/dist/server/services/websocket/plan-handlers.d.ts +6 -0
  70. package/dist/server/services/websocket/plan-handlers.d.ts.map +1 -0
  71. package/dist/server/services/websocket/plan-handlers.js +494 -0
  72. package/dist/server/services/websocket/plan-handlers.js.map +1 -0
  73. package/dist/server/services/websocket/quality-handlers.d.ts.map +1 -1
  74. package/dist/server/services/websocket/quality-handlers.js +384 -12
  75. package/dist/server/services/websocket/quality-handlers.js.map +1 -1
  76. package/dist/server/services/websocket/quality-persistence.d.ts +45 -0
  77. package/dist/server/services/websocket/quality-persistence.d.ts.map +1 -0
  78. package/dist/server/services/websocket/quality-persistence.js +187 -0
  79. package/dist/server/services/websocket/quality-persistence.js.map +1 -0
  80. package/dist/server/services/websocket/quality-service.d.ts +12 -2
  81. package/dist/server/services/websocket/quality-service.d.ts.map +1 -1
  82. package/dist/server/services/websocket/quality-service.js +162 -18
  83. package/dist/server/services/websocket/quality-service.js.map +1 -1
  84. package/dist/server/services/websocket/types.d.ts +2 -2
  85. package/dist/server/services/websocket/types.d.ts.map +1 -1
  86. package/package.json +3 -3
  87. package/server/cli/headless/claude-invoker.ts +25 -12
  88. package/server/cli/headless/headless-logger.ts +78 -0
  89. package/server/cli/headless/mcp-config.ts +6 -5
  90. package/server/cli/headless/runner.ts +4 -0
  91. package/server/cli/headless/stall-assessor.ts +131 -24
  92. package/server/cli/headless/tool-watchdog.ts +10 -9
  93. package/server/cli/headless/types.ts +10 -1
  94. package/server/cli/improvisation-session-manager.ts +118 -11
  95. package/server/mcp/bouncer-cli.ts +73 -0
  96. package/server/services/plan/composer.ts +199 -0
  97. package/server/services/plan/dependency-resolver.ts +182 -0
  98. package/server/services/plan/executor.ts +700 -0
  99. package/server/services/plan/parser.ts +491 -0
  100. package/server/services/plan/state-reconciler.ts +174 -0
  101. package/server/services/plan/types.ts +166 -0
  102. package/server/services/plan/watcher.ts +73 -0
  103. package/server/services/websocket/file-explorer-handlers.ts +20 -0
  104. package/server/services/websocket/handler.ts +21 -0
  105. package/server/services/websocket/plan-handlers.ts +592 -0
  106. package/server/services/websocket/quality-handlers.ts +450 -12
  107. package/server/services/websocket/quality-persistence.ts +250 -0
  108. package/server/services/websocket/quality-service.ts +183 -18
  109. package/server/services/websocket/types.ts +48 -2
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
3
+ // Licensed under the MIT License. See LICENSE file for details.
4
+ import { reviewOperation } from './bouncer-integration.js';
5
+ function buildOperation(toolName, toolInput) {
6
+ const prefix = `${toolName}: `;
7
+ if (toolName === 'Bash' && toolInput.command)
8
+ return prefix + String(toolInput.command);
9
+ if (toolName === 'Edit' && toolInput.file_path)
10
+ return prefix + String(toolInput.file_path);
11
+ if (toolName === 'Write' && toolInput.file_path)
12
+ return prefix + String(toolInput.file_path);
13
+ return prefix + JSON.stringify(toolInput).slice(0, 500);
14
+ }
15
+ async function evaluate(rawInput) {
16
+ if (!rawInput.trim()) {
17
+ return { decision: 'allow', reason: 'Empty input' };
18
+ }
19
+ let parsed;
20
+ try {
21
+ parsed = JSON.parse(rawInput);
22
+ }
23
+ catch {
24
+ return { decision: 'allow', reason: 'Invalid JSON input' };
25
+ }
26
+ const toolName = parsed.tool_name || parsed.toolName || 'unknown';
27
+ const toolInput = parsed.input || parsed.toolInput || {};
28
+ const request = {
29
+ operation: buildOperation(toolName, toolInput),
30
+ context: {
31
+ purpose: 'Tool use request from Claude Code hook',
32
+ workingDirectory: process.cwd(),
33
+ toolName,
34
+ toolInput,
35
+ },
36
+ };
37
+ const result = await reviewOperation(request);
38
+ return {
39
+ decision: result.decision === 'deny' ? 'deny' : 'allow',
40
+ reason: result.reasoning,
41
+ };
42
+ }
43
+ async function main() {
44
+ let rawInput = '';
45
+ for await (const chunk of process.stdin) {
46
+ rawInput += chunk;
47
+ }
48
+ const result = await evaluate(rawInput);
49
+ console.log(JSON.stringify(result));
50
+ }
51
+ main().catch(() => {
52
+ console.log(JSON.stringify({ decision: 'allow', reason: 'Bouncer crash' }));
53
+ });
54
+ //# sourceMappingURL=bouncer-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bouncer-cli.js","sourceRoot":"","sources":["../../../server/mcp/bouncer-cli.ts"],"names":[],"mappings":";AACA,8DAA8D;AAC9D,gEAAgE;AAiBhE,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAE3D,SAAS,cAAc,CAAC,QAAgB,EAAE,SAAkC;IAC1E,MAAM,MAAM,GAAG,GAAG,QAAQ,IAAI,CAAC;IAC/B,IAAI,QAAQ,KAAK,MAAM,IAAI,SAAS,CAAC,OAAO;QAAE,OAAO,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACxF,IAAI,QAAQ,KAAK,MAAM,IAAI,SAAS,CAAC,SAAS;QAAE,OAAO,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IAC5F,IAAI,QAAQ,KAAK,OAAO,IAAI,SAAS,CAAC,SAAS;QAAE,OAAO,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7F,OAAO,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAC1D,CAAC;AAED,KAAK,UAAU,QAAQ,CAAC,QAAgB;IACtC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;QACrB,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;IACtD,CAAC;IAED,IAAI,MAAuH,CAAC;IAC5H,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,oBAAoB,EAAE,CAAC;IAC7D,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,QAAQ,IAAI,SAAS,CAAC;IAClE,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC;IAEzD,MAAM,OAAO,GAAyB;QACpC,SAAS,EAAE,cAAc,CAAC,QAAQ,EAAE,SAAS,CAAC;QAC9C,OAAO,EAAE;YACP,OAAO,EAAE,wCAAwC;YACjD,gBAAgB,EAAE,OAAO,CAAC,GAAG,EAAE;YAC/B,QAAQ;YACR,SAAS;SACV;KACF,CAAC;IAEF,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,OAAO,CAAC,CAAC;IAC9C,OAAO;QACL,QAAQ,EAAE,MAAM,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO;QACvD,MAAM,EAAE,MAAM,CAAC,SAAS;KACzB,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,IAAI,QAAQ,GAAG,EAAE,CAAC;IAClB,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QACxC,QAAQ,IAAI,KAAK,CAAC;IACpB,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACxC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;AACtC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE;IAChB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC;AAC9E,CAAC,CAAC,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { HandlerContext } from '../websocket/handler-context.js';
2
+ import type { WSContext } from '../websocket/types.js';
3
+ export declare function handlePlanPrompt(ctx: HandlerContext, ws: WSContext, userPrompt: string, workingDir: string): Promise<void>;
4
+ //# sourceMappingURL=composer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composer.d.ts","sourceRoot":"","sources":["../../../../server/services/plan/composer.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAkDvD,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,cAAc,EACnB,EAAE,EAAE,SAAS,EACb,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CA+Hf"}
@@ -0,0 +1,181 @@
1
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file for details.
3
+ /**
4
+ * Plan Composer — Handles natural language prompts for PPS creation/editing.
5
+ *
6
+ * When a planPrompt message arrives, this builds a context-enriched prompt
7
+ * against the .pm/ (or legacy .plan/) directory and spawns a scoped
8
+ * HeadlessRunner session to execute it.
9
+ */
10
+ import { existsSync, readFileSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+ import { runWithFileLogger } from '../../cli/headless/headless-logger.js';
13
+ import { HeadlessRunner } from '../../cli/headless/index.js';
14
+ import { getNextId, parsePlanDirectory, resolvePmDir } from './parser.js';
15
+ const PROMPT_TOOL_MESSAGES = {
16
+ Glob: 'Discovering project files...',
17
+ Read: 'Reading project structure...',
18
+ Grep: 'Searching codebase...',
19
+ Write: 'Creating project files...',
20
+ Edit: 'Updating project files...',
21
+ Bash: 'Running commands...',
22
+ };
23
+ function getPromptToolCompleteMessage(event) {
24
+ const input = event.completeInput;
25
+ if (!input)
26
+ return null;
27
+ if (event.toolName === 'Write' && input.file_path) {
28
+ const filename = String(input.file_path).split('/').pop() ?? '';
29
+ return `Created ${filename}`;
30
+ }
31
+ if (event.toolName === 'Edit' && input.file_path) {
32
+ const filename = String(input.file_path).split('/').pop() ?? '';
33
+ return `Updated ${filename}`;
34
+ }
35
+ if (event.toolName === 'Read' && input.file_path) {
36
+ return `Read ${String(input.file_path).split('/').slice(-2).join('/')}`;
37
+ }
38
+ return null;
39
+ }
40
+ function createPromptProgressTracker() {
41
+ const seenToolStarts = new Set();
42
+ return (event) => {
43
+ if (event.type === 'tool_start' && event.toolName) {
44
+ if (seenToolStarts.has(event.toolName))
45
+ return null;
46
+ seenToolStarts.add(event.toolName);
47
+ return PROMPT_TOOL_MESSAGES[event.toolName] ?? null;
48
+ }
49
+ if (event.type === 'tool_complete')
50
+ return getPromptToolCompleteMessage(event);
51
+ return null;
52
+ };
53
+ }
54
+ function readFileOrEmpty(path) {
55
+ try {
56
+ if (existsSync(path))
57
+ return readFileSync(path, 'utf-8');
58
+ }
59
+ catch { /* skip */ }
60
+ return '';
61
+ }
62
+ export async function handlePlanPrompt(ctx, ws, userPrompt, workingDir) {
63
+ const pmDir = resolvePmDir(workingDir) ?? join(workingDir, '.pm');
64
+ const stateContent = readFileOrEmpty(join(pmDir, 'STATE.md'));
65
+ const projectContent = readFileOrEmpty(join(pmDir, 'project.md'));
66
+ // Compute next available IDs
67
+ const fullState = parsePlanDirectory(workingDir);
68
+ let idInfo = '';
69
+ if (fullState) {
70
+ const nextIS = getNextId(fullState.issues, 'IS');
71
+ const nextBG = getNextId(fullState.issues, 'BG');
72
+ const nextEP = getNextId(fullState.issues, 'EP');
73
+ idInfo = `Next available IDs: ${nextIS}, ${nextBG}, ${nextEP}`;
74
+ }
75
+ // Read existing epic files to provide context
76
+ let epicContext = '';
77
+ if (fullState) {
78
+ const existingEpics = fullState.issues.filter((i) => i.type === 'epic');
79
+ if (existingEpics.length > 0) {
80
+ epicContext = `\nExisting epics:\n${existingEpics.map((e) => `- ${e.id}: ${e.title} (${e.path}, children: ${e.children.length})`).join('\n')}\n`;
81
+ }
82
+ }
83
+ const enrichedPrompt = `You are managing a project in the .pm/ directory format (Project Plan Spec).
84
+ The project's current state is:
85
+
86
+ <state>
87
+ ${stateContent || 'No STATE.md exists yet'}
88
+ </state>
89
+
90
+ <project>
91
+ ${projectContent || 'No project.md yet'}
92
+ </project>
93
+
94
+ ${idInfo}
95
+ ${epicContext}
96
+
97
+ Follow these rules:
98
+ - When creating .pm/ files, use YAML front matter + markdown body
99
+ - When modifying issues, preserve all existing YAML fields you don't change
100
+ - After any state change, update STATE.md to reflect the new status
101
+ - Use the next available ID for new entities
102
+ - Respond briefly describing what you did
103
+
104
+ Issue scoping rules (critical for execution quality):
105
+ - Each issue is executed by a single AI agent with its own context window
106
+ - Issues estimated at 1-3 story points execute well (focused, single concern)
107
+ - Issues at 5 story points are viable if scoped to one subsystem
108
+ - Issues at 8+ story points MUST be decomposed into smaller sub-issues
109
+ - Issues at 13+ story points MUST become an epic with child issues
110
+ - Each issue should touch one logical concern (one component, one service, one data flow)
111
+ - If an issue requires work across multiple subsystems, split it into one issue per subsystem with blocked_by edges between them
112
+ - Research/investigation issues should be separate from implementation issues
113
+
114
+ Epic creation rules (when user asks for a feature with sub-tasks or an epic):
115
+ - Create an EP-*.md file in .pm/backlog/ with type: epic and a children: [] field in front matter
116
+ - Create individual IS-*.md (or BG-*.md) files for each child issue
117
+ - Each child issue must have epic: backlog/EP-XXX.md in its front matter
118
+ - The epic's children field must list all child paths: [backlog/IS-001.md, backlog/IS-002.md, ...]
119
+ - Set blocked_by between child issues where there are natural dependencies
120
+ - Give each child issue clear acceptance criteria and files to modify when possible
121
+ - Set appropriate priorities (P0-P3) based on the issue's importance within the epic
122
+
123
+ User request: ${userPrompt}`;
124
+ try {
125
+ ctx.broadcastToAll({
126
+ type: 'planPromptProgress',
127
+ data: { message: 'Starting project planning...' },
128
+ });
129
+ const runner = new HeadlessRunner({
130
+ workingDir,
131
+ directPrompt: enrichedPrompt,
132
+ outputCallback: (text) => {
133
+ ctx.send(ws, {
134
+ type: 'planPromptStreaming',
135
+ data: { token: text },
136
+ });
137
+ },
138
+ toolUseCallback: (() => {
139
+ const getProgressMessage = createPromptProgressTracker();
140
+ return (event) => {
141
+ const message = getProgressMessage(event);
142
+ if (message) {
143
+ ctx.broadcastToAll({
144
+ type: 'planPromptProgress',
145
+ data: { message },
146
+ });
147
+ }
148
+ };
149
+ })(),
150
+ });
151
+ ctx.broadcastToAll({
152
+ type: 'planPromptProgress',
153
+ data: { message: 'Claude is planning your project...' },
154
+ });
155
+ const result = await runWithFileLogger('pm-compose', () => runner.run());
156
+ ctx.broadcastToAll({
157
+ type: 'planPromptProgress',
158
+ data: { message: 'Finalizing project plan...' },
159
+ });
160
+ ctx.send(ws, {
161
+ type: 'planPromptResponse',
162
+ data: {
163
+ response: result.completed ? 'Prompt executed successfully.' : (result.error || 'Unknown error'),
164
+ success: result.completed,
165
+ error: result.error || null,
166
+ },
167
+ });
168
+ // Re-parse and broadcast updated state
169
+ const updatedState = parsePlanDirectory(workingDir);
170
+ if (updatedState) {
171
+ ctx.broadcastToAll({ type: 'planStateUpdated', data: updatedState });
172
+ }
173
+ }
174
+ catch (error) {
175
+ ctx.send(ws, {
176
+ type: 'planError',
177
+ data: { error: error instanceof Error ? error.message : String(error) },
178
+ });
179
+ }
180
+ }
181
+ //# sourceMappingURL=composer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composer.js","sourceRoot":"","sources":["../../../../server/services/plan/composer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Dependency Resolver — Validates and computes the dependency DAG.
3
+ *
4
+ * Builds adjacency list from blocked_by/blocks fields, detects cycles,
5
+ * and computes the "ready to work" set.
6
+ */
7
+ import type { Issue } from './types.js';
8
+ /**
9
+ * Detect cycles in the dependency graph.
10
+ * Returns the first cycle found as an array of issue IDs, or null if acyclic.
11
+ */
12
+ export declare function detectCycles(issues: Issue[]): string[] | null;
13
+ /**
14
+ * Compute the set of issues that are ready to work on.
15
+ * An issue is ready if:
16
+ * - It's not an epic
17
+ * - Its status is backlog or todo (not started, done, or cancelled)
18
+ * - All its blocked_by items are done or cancelled
19
+ *
20
+ * If epicScope is provided, only returns issues belonging to that epic.
21
+ */
22
+ export declare function resolveReadyToWork(issues: Issue[], epicScope?: string): Issue[];
23
+ /**
24
+ * Compute the critical path through incomplete issues.
25
+ * Returns the longest chain of dependent issues.
26
+ */
27
+ export declare function computeCriticalPath(issues: Issue[]): Issue[];
28
+ //# sourceMappingURL=dependency-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dependency-resolver.d.ts","sourceRoot":"","sources":["../../../../server/services/plan/dependency-resolver.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,EAAE,GAAG,IAAI,CAsB7D;AAuCD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,KAAK,EAAE,CA+C/E;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,CA0C5D"}
@@ -0,0 +1,154 @@
1
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file for details.
3
+ /**
4
+ * Detect cycles in the dependency graph.
5
+ * Returns the first cycle found as an array of issue IDs, or null if acyclic.
6
+ */
7
+ export function detectCycles(issues) {
8
+ const issueByPath = new Map();
9
+ for (const issue of issues) {
10
+ issueByPath.set(issue.path, issue);
11
+ }
12
+ // DFS with coloring: 0=white, 1=gray, 2=black
13
+ const color = new Map();
14
+ const parent = new Map();
15
+ for (const issue of issues) {
16
+ color.set(issue.path, 0);
17
+ }
18
+ for (const issue of issues) {
19
+ if (color.get(issue.path) === 0) {
20
+ const cycle = dfs(issue.path, issueByPath, color, parent);
21
+ if (cycle)
22
+ return cycle;
23
+ }
24
+ }
25
+ return null;
26
+ }
27
+ function dfs(path, issueByPath, color, parent) {
28
+ color.set(path, 1); // Gray
29
+ const issue = issueByPath.get(path);
30
+ if (!issue) {
31
+ color.set(path, 2);
32
+ return null;
33
+ }
34
+ for (const dep of issue.blocks) {
35
+ if (!issueByPath.has(dep))
36
+ continue;
37
+ const depColor = color.get(dep);
38
+ if (depColor === 1) {
39
+ // Found cycle — reconstruct
40
+ const cycle = [dep, path];
41
+ let cur = path;
42
+ while (parent.has(cur) && parent.get(cur) !== dep) {
43
+ cur = parent.get(cur);
44
+ cycle.push(cur);
45
+ }
46
+ return cycle.map(p => issueByPath.get(p)?.id || p);
47
+ }
48
+ if (depColor === 0) {
49
+ parent.set(dep, path);
50
+ const cycle = dfs(dep, issueByPath, color, parent);
51
+ if (cycle)
52
+ return cycle;
53
+ }
54
+ }
55
+ color.set(path, 2); // Black
56
+ return null;
57
+ }
58
+ /**
59
+ * Compute the set of issues that are ready to work on.
60
+ * An issue is ready if:
61
+ * - It's not an epic
62
+ * - Its status is backlog or todo (not started, done, or cancelled)
63
+ * - All its blocked_by items are done or cancelled
64
+ *
65
+ * If epicScope is provided, only returns issues belonging to that epic.
66
+ */
67
+ export function resolveReadyToWork(issues, epicScope) {
68
+ const issueByPath = new Map();
69
+ for (const issue of issues) {
70
+ issueByPath.set(issue.path, issue);
71
+ }
72
+ const readyStatuses = new Set(['backlog', 'todo']);
73
+ const doneStatuses = new Set(['done', 'cancelled']);
74
+ const priorityOrder = { P0: 0, P1: 1, P2: 2, P3: 3 };
75
+ // Build set of child paths for epic scoping
76
+ let epicChildPaths = null;
77
+ if (epicScope) {
78
+ const epic = issueByPath.get(epicScope);
79
+ if (epic) {
80
+ epicChildPaths = new Set(epic.children);
81
+ // Also include issues that reference this epic via their epic field
82
+ for (const issue of issues) {
83
+ if (issue.epic === epicScope)
84
+ epicChildPaths.add(issue.path);
85
+ }
86
+ }
87
+ }
88
+ return issues
89
+ .filter(issue => {
90
+ if (issue.type === 'epic')
91
+ return false;
92
+ if (!readyStatuses.has(issue.status))
93
+ return false;
94
+ // If scoped to an epic, only include that epic's children
95
+ if (epicChildPaths && !epicChildPaths.has(issue.path))
96
+ return false;
97
+ // Check all blockers are resolved
98
+ if (issue.blockedBy.length > 0) {
99
+ const allResolved = issue.blockedBy.every(bp => {
100
+ const blocker = issueByPath.get(bp);
101
+ return blocker && doneStatuses.has(blocker.status);
102
+ });
103
+ if (!allResolved)
104
+ return false;
105
+ }
106
+ return true;
107
+ })
108
+ .sort((a, b) => {
109
+ // Sort by priority (P0 first)
110
+ return (priorityOrder[a.priority] ?? 9) - (priorityOrder[b.priority] ?? 9);
111
+ });
112
+ }
113
+ /**
114
+ * Compute the critical path through incomplete issues.
115
+ * Returns the longest chain of dependent issues.
116
+ */
117
+ export function computeCriticalPath(issues) {
118
+ const issueByPath = new Map();
119
+ for (const issue of issues) {
120
+ issueByPath.set(issue.path, issue);
121
+ }
122
+ const doneStatuses = new Set(['done', 'cancelled']);
123
+ const incompleteIssues = issues.filter(i => !doneStatuses.has(i.status) && i.type !== 'epic');
124
+ // Compute longest path using DFS with memoization
125
+ const longestFrom = new Map();
126
+ function getLongest(path) {
127
+ if (longestFrom.has(path))
128
+ return longestFrom.get(path);
129
+ const issue = issueByPath.get(path);
130
+ if (!issue || doneStatuses.has(issue.status)) {
131
+ longestFrom.set(path, []);
132
+ return [];
133
+ }
134
+ // Set sentinel before recursing to break cycles
135
+ longestFrom.set(path, [issue]);
136
+ let best = [];
137
+ for (const dep of issue.blocks) {
138
+ const sub = getLongest(dep);
139
+ if (sub.length > best.length)
140
+ best = sub;
141
+ }
142
+ const result = [issue, ...best];
143
+ longestFrom.set(path, result);
144
+ return result;
145
+ }
146
+ let criticalPath = [];
147
+ for (const issue of incompleteIssues) {
148
+ const path = getLongest(issue.path);
149
+ if (path.length > criticalPath.length)
150
+ criticalPath = path;
151
+ }
152
+ return criticalPath;
153
+ }
154
+ //# sourceMappingURL=dependency-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dependency-resolver.js","sourceRoot":"","sources":["../../../../server/services/plan/dependency-resolver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Plan Executor — Wave-based execution with Claude Code Agent Teams.
3
+ *
4
+ * Reads the dependency DAG from .pm/, picks ALL unblocked issues per wave,
5
+ * spawns a coordinator Claude session that uses Agent Teams to execute them
6
+ * in parallel, then reconciles state and repeats for newly-unblocked issues.
7
+ */
8
+ import { EventEmitter } from 'node:events';
9
+ export type ExecutionStatus = 'idle' | 'starting' | 'executing' | 'paused' | 'stopping' | 'complete' | 'error';
10
+ export interface ExecutionMetrics {
11
+ issuesCompleted: number;
12
+ issuesAttempted: number;
13
+ totalDuration: number;
14
+ currentIssueId: string | null;
15
+ /** IDs of issues being executed in the current wave */
16
+ currentWaveIds: string[];
17
+ }
18
+ export declare class PlanExecutor extends EventEmitter {
19
+ private status;
20
+ private workingDir;
21
+ private shouldStop;
22
+ private shouldPause;
23
+ private epicScope;
24
+ private metrics;
25
+ constructor(workingDir: string);
26
+ getStatus(): ExecutionStatus;
27
+ getMetrics(): ExecutionMetrics;
28
+ startEpic(epicPath: string): Promise<void>;
29
+ start(): Promise<void>;
30
+ pause(): void;
31
+ stop(): void;
32
+ resume(): Promise<void>;
33
+ private executeWave;
34
+ /**
35
+ * After a wave, check each issue's status on disk.
36
+ * `status: done` in issue front matter is the single completion signal.
37
+ * Output doc existence is NOT used as a proxy — code-focused issues
38
+ * (bug fixes, refactors) don't produce docs but are still valid completions.
39
+ */
40
+ private reconcileWaveResults;
41
+ private pickReadyIssues;
42
+ /**
43
+ * Build the team lead prompt for a wave of issues.
44
+ * Uses Agent Teams for true parallel execution as separate processes —
45
+ * each teammate gets its own context window and sends idle notifications
46
+ * when done. The team is created implicitly by the first Agent(team_name=...) call.
47
+ */
48
+ private buildCoordinatorPrompt;
49
+ /**
50
+ * Revert issues that stayed in_progress after a failed wave.
51
+ */
52
+ private revertIncompleteIssues;
53
+ /** Saved content of any pre-existing .claude/settings.json so we can restore it */
54
+ private savedClaudeSettings;
55
+ private claudeSettingsInstalled;
56
+ /**
57
+ * Pre-approve tools in project .claude/settings.json so Agent Teams
58
+ * teammates can work without interactive permission prompts.
59
+ * Teammates are separate processes that inherit the lead's permission
60
+ * settings. Without pre-approved tools, they hit interactive prompts
61
+ * that can't be answered in headless/background mode (known bug #25254).
62
+ */
63
+ private installTeammatePermissions;
64
+ /**
65
+ * Restore original .claude/settings.json after wave execution.
66
+ */
67
+ private uninstallTeammatePermissions;
68
+ /** Saved content of any pre-existing .mcp.json so we can restore it */
69
+ private savedMcpJson;
70
+ private mcpJsonInstalled;
71
+ /**
72
+ * Write .mcp.json in the working directory so Agent Teams teammates
73
+ * (separate processes) auto-discover the bouncer MCP server.
74
+ * This is essential — teammates don't inherit --mcp-config or
75
+ * --permission-prompt-tool from the team lead. .mcp.json project-level
76
+ * discovery + global PreToolUse hooks are the two bouncer paths for teammates.
77
+ *
78
+ * Also generates ~/.mstro/mcp-config.json for the team lead (--mcp-config).
79
+ */
80
+ private installBouncerForSubagents;
81
+ /**
82
+ * Restore or remove .mcp.json after execution.
83
+ */
84
+ private uninstallBouncerForSubagents;
85
+ /**
86
+ * Resolve the canonical output path for an issue in .pm/out/.
87
+ * This is the PM system's internal execution artifact — always under
88
+ * PM control. User-facing delivery to output_file happens via publishOutputs().
89
+ */
90
+ private resolveOutputPath;
91
+ /**
92
+ * List existing execution output docs in .pm/out/.
93
+ * Single canonical location — no split-brain lookup.
94
+ */
95
+ private listExistingDocs;
96
+ /**
97
+ * Copy confirmed-done outputs from .pm/out/ to user-specified output_file paths.
98
+ * Only copies for issues that completed successfully and have output_file set.
99
+ * Failures are non-fatal — the canonical artifact in .pm/out/ is always safe.
100
+ */
101
+ private publishOutputs;
102
+ private slugify;
103
+ private updateIssueFrontMatter;
104
+ /**
105
+ * Append a progress log entry after a wave completes.
106
+ * Re-reads issue files from disk to determine which actually completed.
107
+ */
108
+ private appendProgressEntry;
109
+ }
110
+ //# sourceMappingURL=executor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../../../server/services/plan/executor.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAW3C,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,GAAG,OAAO,CAAC;AAE/G,MAAM,WAAW,gBAAgB;IAC/B,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,uDAAuD;IACvD,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,qBAAa,YAAa,SAAQ,YAAY;IAC5C,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,OAAO,CAMb;gBAEU,UAAU,EAAE,MAAM;IAK9B,SAAS,IAAI,eAAe;IAI5B,UAAU,IAAI,gBAAgB;IAIxB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK1C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAyC5B,KAAK,IAAI,IAAI;IAIb,IAAI,IAAI,IAAI;IAIZ,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;YAQT,WAAW;IA8EzB;;;;;OAKG;IACH,OAAO,CAAC,oBAAoB;IA8B5B,OAAO,CAAC,eAAe;IAmBvB;;;;;OAKG;IACH,OAAO,CAAC,sBAAsB;IAuH9B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAgB9B,mFAAmF;IACnF,OAAO,CAAC,mBAAmB,CAAuB;IAClD,OAAO,CAAC,uBAAuB,CAAS;IAExC;;;;;;OAMG;IACH,OAAO,CAAC,0BAA0B;IAkDlC;;OAEG;IACH,OAAO,CAAC,4BAA4B;IAoBpC,uEAAuE;IACvE,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,gBAAgB,CAAS;IAEjC;;;;;;;;OAQG;IACH,OAAO,CAAC,0BAA0B;IAgClC;;OAEG;IACH,OAAO,CAAC,4BAA4B;IAsBpC;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAMzB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAexB;;;;OAIG;IACH,OAAO,CAAC,cAAc;IAyCtB,OAAO,CAAC,OAAO;IAQf,OAAO,CAAC,sBAAsB;IAa9B;;;OAGG;IACH,OAAO,CAAC,mBAAmB;CA6C5B"}