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.
- package/bin/mstro.js +119 -40
- package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -1
- package/dist/server/cli/headless/claude-invoker.js +3 -0
- package/dist/server/cli/headless/claude-invoker.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/services/plan/composer.d.ts +1 -1
- package/dist/server/services/plan/composer.d.ts.map +1 -1
- package/dist/server/services/plan/composer.js +116 -31
- package/dist/server/services/plan/composer.js.map +1 -1
- package/dist/server/services/plan/config-installer.d.ts +25 -0
- package/dist/server/services/plan/config-installer.d.ts.map +1 -0
- package/dist/server/services/plan/config-installer.js +182 -0
- package/dist/server/services/plan/config-installer.js.map +1 -0
- package/dist/server/services/plan/dependency-resolver.d.ts +1 -1
- package/dist/server/services/plan/dependency-resolver.d.ts.map +1 -1
- package/dist/server/services/plan/dependency-resolver.js +4 -1
- package/dist/server/services/plan/dependency-resolver.js.map +1 -1
- package/dist/server/services/plan/executor.d.ts +38 -74
- package/dist/server/services/plan/executor.d.ts.map +1 -1
- package/dist/server/services/plan/executor.js +271 -459
- package/dist/server/services/plan/executor.js.map +1 -1
- package/dist/server/services/plan/front-matter.d.ts +18 -0
- package/dist/server/services/plan/front-matter.d.ts.map +1 -0
- package/dist/server/services/plan/front-matter.js +44 -0
- package/dist/server/services/plan/front-matter.js.map +1 -0
- package/dist/server/services/plan/output-manager.d.ts +22 -0
- package/dist/server/services/plan/output-manager.d.ts.map +1 -0
- package/dist/server/services/plan/output-manager.js +97 -0
- package/dist/server/services/plan/output-manager.js.map +1 -0
- package/dist/server/services/plan/parser.d.ts +18 -2
- package/dist/server/services/plan/parser.d.ts.map +1 -1
- package/dist/server/services/plan/parser.js +359 -25
- package/dist/server/services/plan/parser.js.map +1 -1
- package/dist/server/services/plan/prompt-builder.d.ts +17 -0
- package/dist/server/services/plan/prompt-builder.d.ts.map +1 -0
- package/dist/server/services/plan/prompt-builder.js +137 -0
- package/dist/server/services/plan/prompt-builder.js.map +1 -0
- package/dist/server/services/plan/review-gate.d.ts +26 -0
- package/dist/server/services/plan/review-gate.d.ts.map +1 -0
- package/dist/server/services/plan/review-gate.js +191 -0
- package/dist/server/services/plan/review-gate.js.map +1 -0
- package/dist/server/services/plan/state-reconciler.d.ts +1 -1
- package/dist/server/services/plan/state-reconciler.d.ts.map +1 -1
- package/dist/server/services/plan/state-reconciler.js +59 -7
- package/dist/server/services/plan/state-reconciler.js.map +1 -1
- package/dist/server/services/plan/types.d.ts +66 -0
- package/dist/server/services/plan/types.d.ts.map +1 -1
- package/dist/server/services/platform.d.ts.map +1 -1
- package/dist/server/services/platform.js +11 -0
- package/dist/server/services/platform.js.map +1 -1
- package/dist/server/services/websocket/handler.d.ts.map +1 -1
- package/dist/server/services/websocket/handler.js +14 -0
- package/dist/server/services/websocket/handler.js.map +1 -1
- package/dist/server/services/websocket/plan-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/plan-handlers.js +518 -40
- package/dist/server/services/websocket/plan-handlers.js.map +1 -1
- package/dist/server/services/websocket/types.d.ts +2 -2
- package/dist/server/services/websocket/types.d.ts.map +1 -1
- package/package.json +1 -2
- package/server/cli/headless/claude-invoker.ts +4 -0
- package/server/cli/headless/types.ts +4 -1
- package/server/services/plan/composer.ts +138 -34
- package/server/services/plan/config-installer.ts +187 -0
- package/server/services/plan/dependency-resolver.ts +4 -1
- package/server/services/plan/executor.ts +278 -487
- package/server/services/plan/front-matter.ts +48 -0
- package/server/services/plan/output-manager.ts +113 -0
- package/server/services/plan/parser.ts +389 -27
- package/server/services/plan/prompt-builder.ts +161 -0
- package/server/services/plan/review-gate.ts +210 -0
- package/server/services/plan/state-reconciler.ts +68 -7
- package/server/services/plan/types.ts +99 -1
- package/server/services/platform.ts +11 -0
- package/server/services/websocket/handler.ts +14 -0
- package/server/services/websocket/plan-handlers.ts +629 -44
- 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 => {
|