mstro-app 0.4.2 → 0.4.3

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 (77) hide show
  1. package/bin/mstro.js +119 -40
  2. package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -1
  3. package/dist/server/cli/headless/claude-invoker.js +3 -0
  4. package/dist/server/cli/headless/claude-invoker.js.map +1 -1
  5. package/dist/server/cli/headless/types.d.ts +4 -1
  6. package/dist/server/cli/headless/types.d.ts.map +1 -1
  7. package/dist/server/services/plan/composer.d.ts +1 -1
  8. package/dist/server/services/plan/composer.d.ts.map +1 -1
  9. package/dist/server/services/plan/composer.js +116 -31
  10. package/dist/server/services/plan/composer.js.map +1 -1
  11. package/dist/server/services/plan/config-installer.d.ts +25 -0
  12. package/dist/server/services/plan/config-installer.d.ts.map +1 -0
  13. package/dist/server/services/plan/config-installer.js +182 -0
  14. package/dist/server/services/plan/config-installer.js.map +1 -0
  15. package/dist/server/services/plan/dependency-resolver.d.ts +1 -1
  16. package/dist/server/services/plan/dependency-resolver.d.ts.map +1 -1
  17. package/dist/server/services/plan/dependency-resolver.js +4 -1
  18. package/dist/server/services/plan/dependency-resolver.js.map +1 -1
  19. package/dist/server/services/plan/executor.d.ts +38 -74
  20. package/dist/server/services/plan/executor.d.ts.map +1 -1
  21. package/dist/server/services/plan/executor.js +271 -459
  22. package/dist/server/services/plan/executor.js.map +1 -1
  23. package/dist/server/services/plan/front-matter.d.ts +18 -0
  24. package/dist/server/services/plan/front-matter.d.ts.map +1 -0
  25. package/dist/server/services/plan/front-matter.js +44 -0
  26. package/dist/server/services/plan/front-matter.js.map +1 -0
  27. package/dist/server/services/plan/output-manager.d.ts +22 -0
  28. package/dist/server/services/plan/output-manager.d.ts.map +1 -0
  29. package/dist/server/services/plan/output-manager.js +97 -0
  30. package/dist/server/services/plan/output-manager.js.map +1 -0
  31. package/dist/server/services/plan/parser.d.ts +18 -2
  32. package/dist/server/services/plan/parser.d.ts.map +1 -1
  33. package/dist/server/services/plan/parser.js +359 -25
  34. package/dist/server/services/plan/parser.js.map +1 -1
  35. package/dist/server/services/plan/prompt-builder.d.ts +17 -0
  36. package/dist/server/services/plan/prompt-builder.d.ts.map +1 -0
  37. package/dist/server/services/plan/prompt-builder.js +137 -0
  38. package/dist/server/services/plan/prompt-builder.js.map +1 -0
  39. package/dist/server/services/plan/review-gate.d.ts +26 -0
  40. package/dist/server/services/plan/review-gate.d.ts.map +1 -0
  41. package/dist/server/services/plan/review-gate.js +191 -0
  42. package/dist/server/services/plan/review-gate.js.map +1 -0
  43. package/dist/server/services/plan/state-reconciler.d.ts +1 -1
  44. package/dist/server/services/plan/state-reconciler.d.ts.map +1 -1
  45. package/dist/server/services/plan/state-reconciler.js +59 -7
  46. package/dist/server/services/plan/state-reconciler.js.map +1 -1
  47. package/dist/server/services/plan/types.d.ts +66 -0
  48. package/dist/server/services/plan/types.d.ts.map +1 -1
  49. package/dist/server/services/platform.d.ts.map +1 -1
  50. package/dist/server/services/platform.js +11 -0
  51. package/dist/server/services/platform.js.map +1 -1
  52. package/dist/server/services/websocket/handler.d.ts.map +1 -1
  53. package/dist/server/services/websocket/handler.js +14 -0
  54. package/dist/server/services/websocket/handler.js.map +1 -1
  55. package/dist/server/services/websocket/plan-handlers.d.ts.map +1 -1
  56. package/dist/server/services/websocket/plan-handlers.js +518 -40
  57. package/dist/server/services/websocket/plan-handlers.js.map +1 -1
  58. package/dist/server/services/websocket/types.d.ts +2 -2
  59. package/dist/server/services/websocket/types.d.ts.map +1 -1
  60. package/package.json +1 -2
  61. package/server/cli/headless/claude-invoker.ts +4 -0
  62. package/server/cli/headless/types.ts +4 -1
  63. package/server/services/plan/composer.ts +138 -34
  64. package/server/services/plan/config-installer.ts +187 -0
  65. package/server/services/plan/dependency-resolver.ts +4 -1
  66. package/server/services/plan/executor.ts +278 -487
  67. package/server/services/plan/front-matter.ts +48 -0
  68. package/server/services/plan/output-manager.ts +113 -0
  69. package/server/services/plan/parser.ts +389 -27
  70. package/server/services/plan/prompt-builder.ts +161 -0
  71. package/server/services/plan/review-gate.ts +210 -0
  72. package/server/services/plan/state-reconciler.ts +68 -7
  73. package/server/services/plan/types.ts +99 -1
  74. package/server/services/platform.ts +11 -0
  75. package/server/services/websocket/handler.ts +14 -0
  76. package/server/services/websocket/plan-handlers.ts +629 -44
  77. package/server/services/websocket/types.ts +29 -2
@@ -0,0 +1,187 @@
1
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ /**
5
+ * Config Installer — Manages temporary config file modifications for Agent Teams.
6
+ *
7
+ * Installs teammate permissions in .claude/settings.json and bouncer MCP config
8
+ * in .mcp.json before wave execution, then restores originals afterward.
9
+ */
10
+
11
+ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
12
+ import { join } from 'node:path';
13
+ import { generateMcpConfig } from '../../cli/headless/mcp-config.js';
14
+
15
+ /** Tools that teammates may need during execution */
16
+ const REQUIRED_PERMISSIONS = [
17
+ 'Bash',
18
+ 'Read',
19
+ 'Edit',
20
+ 'Write',
21
+ 'Glob',
22
+ 'Grep',
23
+ 'WebFetch',
24
+ 'WebSearch',
25
+ 'Agent',
26
+ ];
27
+
28
+ /** Restore a file from a .pm-backup, handling the __NONE__ sentinel for files that didn't exist. */
29
+ function restoreFromBackup(backupPath: string, targetPath: string): void {
30
+ try {
31
+ if (!existsSync(backupPath)) return;
32
+ const backup = readFileSync(backupPath, 'utf-8');
33
+ if (backup === '__NONE__') {
34
+ if (existsSync(targetPath)) unlinkSync(targetPath);
35
+ } else {
36
+ writeFileSync(targetPath, backup);
37
+ }
38
+ unlinkSync(backupPath);
39
+ } catch { /* best effort */ }
40
+ }
41
+
42
+ export class ConfigInstaller {
43
+ private savedClaudeSettings: string | null = null;
44
+ private claudeSettingsInstalled = false;
45
+ private savedMcpJson: string | null = null;
46
+ private mcpJsonInstalled = false;
47
+
48
+ constructor(private workingDir: string) {
49
+ // Recover from prior crash: if backup files exist, restore them
50
+ this.recoverFromCrash();
51
+ }
52
+
53
+ /** Restore .claude/settings.json and .mcp.json from backups left by a previous crash. */
54
+ private recoverFromCrash(): void {
55
+ restoreFromBackup(
56
+ join(this.workingDir, '.claude', 'settings.json.pm-backup'),
57
+ join(this.workingDir, '.claude', 'settings.json'),
58
+ );
59
+ restoreFromBackup(
60
+ join(this.workingDir, '.mcp.json.pm-backup'),
61
+ join(this.workingDir, '.mcp.json'),
62
+ );
63
+ }
64
+
65
+ /**
66
+ * Pre-approve tools in .claude/settings.json so Agent Teams
67
+ * teammates can work without interactive permission prompts.
68
+ */
69
+ installTeammatePermissions(): void {
70
+ const claudeDir = join(this.workingDir, '.claude');
71
+ const settingsPath = join(claudeDir, 'settings.json');
72
+
73
+ if (!existsSync(claudeDir)) {
74
+ mkdirSync(claudeDir, { recursive: true });
75
+ }
76
+
77
+ const backupPath = join(claudeDir, 'settings.json.pm-backup');
78
+ try {
79
+ if (existsSync(settingsPath)) {
80
+ this.savedClaudeSettings = readFileSync(settingsPath, 'utf-8');
81
+ writeFileSync(backupPath, this.savedClaudeSettings);
82
+ const existing = JSON.parse(this.savedClaudeSettings);
83
+
84
+ if (!existing.permissions) existing.permissions = {};
85
+ if (!existing.permissions.allow) existing.permissions.allow = [];
86
+
87
+ for (const tool of REQUIRED_PERMISSIONS) {
88
+ if (!existing.permissions.allow.includes(tool)) {
89
+ existing.permissions.allow.push(tool);
90
+ }
91
+ }
92
+
93
+ writeFileSync(settingsPath, JSON.stringify(existing, null, 2));
94
+ } else {
95
+ this.savedClaudeSettings = null;
96
+ writeFileSync(backupPath, '__NONE__');
97
+ writeFileSync(settingsPath, JSON.stringify({
98
+ permissions: { allow: REQUIRED_PERMISSIONS },
99
+ }, null, 2));
100
+ }
101
+ this.claudeSettingsInstalled = true;
102
+ } catch {
103
+ // Non-fatal — teammates may hit permission prompts
104
+ }
105
+ }
106
+
107
+ /** Restore original .claude/settings.json after wave execution. */
108
+ uninstallTeammatePermissions(): void {
109
+ if (!this.claudeSettingsInstalled) return;
110
+ const settingsPath = join(this.workingDir, '.claude', 'settings.json');
111
+ const backupPath = join(this.workingDir, '.claude', 'settings.json.pm-backup');
112
+
113
+ try {
114
+ if (this.savedClaudeSettings !== null) {
115
+ writeFileSync(settingsPath, this.savedClaudeSettings);
116
+ } else {
117
+ unlinkSync(settingsPath);
118
+ }
119
+ } catch {
120
+ // Best effort
121
+ }
122
+
123
+ // Remove backup — successful restore means crash recovery is no longer needed
124
+ try { if (existsSync(backupPath)) unlinkSync(backupPath); } catch { /* ok */ }
125
+
126
+ this.savedClaudeSettings = null;
127
+ this.claudeSettingsInstalled = false;
128
+ }
129
+
130
+ /**
131
+ * Write .mcp.json so Agent Teams teammates auto-discover the bouncer MCP server.
132
+ * Also generates ~/.mstro/mcp-config.json for the team lead (--mcp-config).
133
+ */
134
+ installBouncerForSubagents(): void {
135
+ const mcpJsonPath = join(this.workingDir, '.mcp.json');
136
+
137
+ const backupPath = join(this.workingDir, '.mcp.json.pm-backup');
138
+ try {
139
+ const generatedPath = generateMcpConfig(this.workingDir);
140
+ if (!generatedPath) return;
141
+
142
+ const mcpConfig = readFileSync(generatedPath, 'utf-8');
143
+
144
+ if (existsSync(mcpJsonPath)) {
145
+ this.savedMcpJson = readFileSync(mcpJsonPath, 'utf-8');
146
+ writeFileSync(backupPath, this.savedMcpJson);
147
+
148
+ const existing = JSON.parse(this.savedMcpJson);
149
+ const generated = JSON.parse(mcpConfig);
150
+ existing.mcpServers = {
151
+ ...existing.mcpServers,
152
+ 'mstro-bouncer': generated.mcpServers['mstro-bouncer'],
153
+ };
154
+ writeFileSync(mcpJsonPath, JSON.stringify(existing, null, 2));
155
+ } else {
156
+ writeFileSync(backupPath, '__NONE__');
157
+ writeFileSync(mcpJsonPath, mcpConfig);
158
+ }
159
+
160
+ this.mcpJsonInstalled = true;
161
+ } catch {
162
+ // Non-fatal: parent has MCP via --mcp-config, teammates fall back to PreToolUse hooks
163
+ }
164
+ }
165
+
166
+ /** Restore or remove .mcp.json after execution. */
167
+ uninstallBouncerForSubagents(): void {
168
+ if (!this.mcpJsonInstalled) return;
169
+ const mcpJsonPath = join(this.workingDir, '.mcp.json');
170
+ const backupPath = join(this.workingDir, '.mcp.json.pm-backup');
171
+
172
+ try {
173
+ if (this.savedMcpJson !== null) {
174
+ writeFileSync(mcpJsonPath, this.savedMcpJson);
175
+ } else {
176
+ unlinkSync(mcpJsonPath);
177
+ }
178
+ } catch {
179
+ // Best effort cleanup
180
+ }
181
+
182
+ try { if (existsSync(backupPath)) unlinkSync(backupPath); } catch { /* ok */ }
183
+
184
+ this.savedMcpJson = null;
185
+ this.mcpJsonInstalled = false;
186
+ }
187
+ }
@@ -84,7 +84,7 @@ function dfs(
84
84
  *
85
85
  * If epicScope is provided, only returns issues belonging to that epic.
86
86
  */
87
- export function resolveReadyToWork(issues: Issue[], epicScope?: string): Issue[] {
87
+ export function resolveReadyToWork(issues: Issue[], epicScope?: string, sprintScope?: string): Issue[] {
88
88
  const issueByPath = new Map<string, Issue>();
89
89
  for (const issue of issues) {
90
90
  issueByPath.set(issue.path, issue);
@@ -116,6 +116,9 @@ export function resolveReadyToWork(issues: Issue[], epicScope?: string): Issue[]
116
116
  // If scoped to an epic, only include that epic's children
117
117
  if (epicChildPaths && !epicChildPaths.has(issue.path)) return false;
118
118
 
119
+ // If scoped to a sprint, only include issues assigned to that sprint
120
+ if (sprintScope && issue.sprint !== sprintScope) return false;
121
+
119
122
  // Check all blockers are resolved
120
123
  if (issue.blockedBy.length > 0) {
121
124
  const allResolved = issue.blockedBy.every(bp => {