supipowers 2.0.2 → 2.2.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.
- package/README.md +5 -6
- package/package.json +4 -2
- package/skills/harness/SKILL.md +1 -0
- package/src/bootstrap.ts +8 -133
- package/src/commands/optimize-context.ts +153 -16
- package/src/commands/runbook.ts +511 -0
- package/src/config/defaults.ts +5 -5
- package/src/config/loader.ts +1 -0
- package/src/config/schema.ts +2 -6
- package/src/context/rule-renderer.ts +274 -2
- package/src/context/runbook-extension-template.ts +193 -0
- package/src/context/startup-check.ts +197 -2
- package/src/context/startup-optimizer.ts +133 -10
- package/src/context-mode/knowledge/store.ts +381 -43
- package/src/context-mode/tools.ts +41 -3
- package/src/deps/registry.ts +1 -12
- package/src/fix-pr/assessment.ts +1 -0
- package/src/fix-pr/prompt-builder.ts +1 -0
- package/src/git/commit.ts +76 -18
- package/src/harness/command.ts +201 -12
- package/src/harness/default-agents/docs.md +39 -0
- package/src/harness/docs/config.ts +29 -0
- package/src/harness/docs/glob-match.ts +27 -0
- package/src/harness/docs/index-renderer.ts +82 -0
- package/src/harness/docs/provenance.ts +125 -0
- package/src/harness/docs/regen-decision.ts +167 -0
- package/src/harness/docs/representative-files.ts +175 -0
- package/src/harness/docs/source-hash.ts +106 -0
- package/src/harness/docs/validator.ts +233 -0
- package/src/harness/git-verification.ts +515 -0
- package/src/harness/git-verify-qa.ts +406 -0
- package/src/harness/hooks/layer-context-inject.ts +35 -1
- package/src/harness/hooks/register.ts +24 -3
- package/src/harness/pipeline.ts +37 -13
- package/src/harness/pr-comment/baseline.ts +105 -0
- package/src/harness/pr-comment/ci-env.ts +120 -0
- package/src/harness/pr-comment/gh-poster.ts +227 -0
- package/src/harness/pr-comment/handler.ts +198 -0
- package/src/harness/pr-comment/render.ts +297 -0
- package/src/harness/pr-comment/status.ts +95 -0
- package/src/harness/pr-comment/types.ts +73 -0
- package/src/harness/pr-comment/workflow-summary.ts +47 -0
- package/src/harness/project-paths.ts +95 -0
- package/src/harness/stages/design.ts +1 -0
- package/src/harness/stages/discover.ts +1 -13
- package/src/harness/stages/docs.ts +708 -0
- package/src/harness/stages/implement-apply.ts +934 -0
- package/src/harness/stages/implement.ts +64 -51
- package/src/harness/stages/plan.ts +25 -16
- package/src/harness/stages/validate.ts +478 -0
- package/src/harness/storage.ts +142 -0
- package/src/harness/tools.ts +130 -0
- package/src/mempalace/bridge.ts +207 -41
- package/src/mempalace/config.ts +10 -4
- package/src/mempalace/format.ts +122 -6
- package/src/mempalace/hooks.ts +204 -56
- package/src/mempalace/installer-helper.ts +18 -4
- package/src/mempalace/python/mempalace_bridge.py +128 -3
- package/src/mempalace/runtime.ts +53 -16
- package/src/mempalace/schema.ts +151 -30
- package/src/mempalace/session-summary.ts +5 -0
- package/src/mempalace/tool.ts +17 -4
- package/src/mempalace/upstream-limits.ts +69 -0
- package/src/planning/approval-flow.ts +25 -2
- package/src/planning/planning-ask-tool.ts +34 -4
- package/src/planning/system-prompt.ts +1 -1
- package/src/tool-catalog/active-tool-controller.ts +0 -22
- package/src/tool-catalog/active-tool-planner.ts +0 -26
- package/src/tool-catalog/tool-groups.ts +1 -9
- package/src/types.ts +127 -8
- package/src/ui-design/session.ts +114 -8
- package/src/utils/executable.ts +10 -1
- package/src/workspace/state-paths.ts +1 -1
- package/src/commands/mcp.ts +0 -814
- package/src/mcp/activation.ts +0 -77
- package/src/mcp/config.ts +0 -223
- package/src/mcp/docs.ts +0 -154
- package/src/mcp/gateway.ts +0 -103
- package/src/mcp/lifecycle.ts +0 -79
- package/src/mcp/manager-tool.ts +0 -104
- package/src/mcp/mcpc.ts +0 -113
- package/src/mcp/registry.ts +0 -98
- package/src/mcp/triggers.ts +0 -62
- package/src/mcp/types.ts +0 -95
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ Run the interactive installer:
|
|
|
23
23
|
bunx supipowers
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
The installer detects Pi (`~/.pi`) and OMP (`~/.omp`) — when both are present it offers a multiselect to install to one or both. It registers the extension, removes legacy external context-mode MCP registrations from `agent/mcp.json` and cleans up the old `settings/mcp.json` if present, and can install missing optional tooling such as LSP servers
|
|
26
|
+
The installer detects Pi (`~/.pi`) and OMP (`~/.omp`) — when both are present it offers a multiselect to install to one or both. It registers the extension, removes legacy external context-mode MCP registrations from `agent/mcp.json` and cleans up the old `settings/mcp.json` if present, and can install missing optional tooling such as LSP servers and Playwright CLI.
|
|
27
27
|
|
|
28
28
|
> [!TIP]
|
|
29
29
|
> Run `/supi:update` at any time to upgrade to the latest version, or `/supi:doctor` to check your setup.
|
|
@@ -42,7 +42,6 @@ The installer scans for these and offers to install missing tooling where it can
|
|
|
42
42
|
|
|
43
43
|
| Dependency | What it enables |
|
|
44
44
|
| ------------------------------------- | --------------------------------------------------------------------- |
|
|
45
|
-
| [mcpc](https://github.com/apify/mcpc) | MCP server management via `/supi:mcp` |
|
|
46
45
|
| `typescript-language-server` | TypeScript/JS diagnostics and references in review gates |
|
|
47
46
|
| `pyright` | Python type checking |
|
|
48
47
|
| `rust-analyzer` | Rust language server |
|
|
@@ -70,7 +69,6 @@ The installer scans for these and offers to install missing tooling where it can
|
|
|
70
69
|
| `/supi:model` | Configure model assignments per action (plan, review, qa…) |
|
|
71
70
|
| `/supi:context` | Show current context window usage and system prompt breakdown |
|
|
72
71
|
| `/supi:optimize-context` | Analyze loaded prompt/context usage and suggest reductions |
|
|
73
|
-
| `/supi:mcp` | Manage MCP servers (connect, disconnect, migrate) |
|
|
74
72
|
| `/supi:config` | Interactive settings TUI |
|
|
75
73
|
| `/supi:status` | Show project plans and configuration summary |
|
|
76
74
|
| `/supi:doctor` | Diagnose extension health and missing dependencies |
|
|
@@ -82,11 +80,11 @@ The installer scans for these and offers to install missing tooling where it can
|
|
|
82
80
|
| `/supi:memory` | Manage native MemPalace memory integration (`status`, `setup`) |
|
|
83
81
|
| `/supi:clear` | Clear metrics, cache, session knowledge, and memory |
|
|
84
82
|
|
|
85
|
-
Most commands steer the AI session. These are TUI-only — they open native dialogs without triggering the AI: `/supi`, `/supi:config`, `/supi:status`, `/supi:review`, `/supi:update`, `/supi:doctor`, `/supi:
|
|
83
|
+
Most commands steer the AI session. These are TUI-only — they open native dialogs without triggering the AI: `/supi`, `/supi:config`, `/supi:status`, `/supi:review`, `/supi:update`, `/supi:doctor`, `/supi:model`, `/supi:context`, `/supi:optimize-context`, `/supi:commit`, `/supi:release`, `/supi:checks`, `/supi:agents`, `/supi:ultraplan`, `/supi:harness`, `/supi:memory`, `/supi:clear`.
|
|
86
84
|
|
|
87
85
|
## How it works
|
|
88
86
|
|
|
89
|
-
**Planning.** `/supi:plan` steers the AI through planning phases (
|
|
87
|
+
**Planning.** `/supi:plan` steers the AI through planning phases (Explore → Clarify → Brainstorm → Design & Save → Review Loop → User Gate → Plan), saves the result to `.omp/supipowers/plans/`, and presents an approval UI. On approval, tasks execute in the same session.
|
|
90
88
|
|
|
91
89
|
**Quality gates.** `/supi:checks` runs deterministic quality gates. Six gates are available: `lsp-diagnostics`, `lint`, `typecheck`, `format`, `test-suite`, and `build`. Each gate can be enabled independently via `/supi:config` or the shared repository config at `.omp/supipowers/config.json`. In monorepos, `/supi:checks` defaults to `All`, which runs the root target plus every workspace target sequentially; use `--target <package>` to narrow the run or `--target all` to request the batch mode explicitly. Gates report issues with severity levels.
|
|
92
90
|
|
|
@@ -127,7 +125,6 @@ Use `/supi:agents` to inspect the merged set that will actually run.
|
|
|
127
125
|
| Release automation | ✅ | ❌ |
|
|
128
126
|
| Commit workflow | ✅ | ❌ |
|
|
129
127
|
| Context-window optimizations | ✅ | ❌ |
|
|
130
|
-
| MCP server management through mcpc | ✅ | ❌ |
|
|
131
128
|
| Git worktree workflow | ❌ | ✅ |
|
|
132
129
|
|
|
133
130
|
## Quality gates
|
|
@@ -161,6 +158,8 @@ Configuration uses built-in defaults plus two user-managed override layers:
|
|
|
161
158
|
|
|
162
159
|
`/supi:config` exposes only `Global` and `Repository`. In monorepos, the repository config is shared across every workspace; there are no per-workspace Supipowers config files for general settings.
|
|
163
160
|
|
|
161
|
+
MemPalace hook timeouts are configured under `mempalace.timeouts`. Keep `hookMs` at or above `6000` when `mempalace.hooks.autoSearchOnPrompt` is enabled; MemPalace search can now pause before retrying a transient index lookup. The built-in default is `10000`.
|
|
162
|
+
|
|
164
163
|
## Release channels
|
|
165
164
|
|
|
166
165
|
Three built-in channels are available: `github` (GitHub Release via `gh` CLI), `gitlab` (GitLab Release via `glab` CLI), and `gitea` (Gitea Release via `tea` CLI). Channels are selected per-project in `release.channels`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "supipowers",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Workflow extension for OMP coding agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -9,9 +9,11 @@
|
|
|
9
9
|
"test:watch": "bun test --timeout 20000 --watch tests/",
|
|
10
10
|
"test:evals": "bun test --timeout 20000 tests/evals/",
|
|
11
11
|
"build": "tsc -p tsconfig.build.json",
|
|
12
|
+
"ci": "bun run typecheck && bun run test",
|
|
12
13
|
"install:visual-server": "npm --prefix src/visual/scripts ci --ignore-scripts --no-audit --no-fund",
|
|
13
14
|
"postinstall": "bun run install:visual-server",
|
|
14
|
-
"prepare": "git config core.hooksPath hooks || true"
|
|
15
|
+
"prepare": "git config core.hooksPath hooks || true",
|
|
16
|
+
"dev-install": "bun run bin/dev-install.ts"
|
|
15
17
|
},
|
|
16
18
|
"engines": {
|
|
17
19
|
"bun": ">=1.3.10"
|
package/skills/harness/SKILL.md
CHANGED
|
@@ -78,6 +78,7 @@ Scorecard has lenient + strict scores (0–100). Strict counts `wontfix` items a
|
|
|
78
78
|
- `/supi:harness resolve <id>` — mark an entry resolved.
|
|
79
79
|
- `/supi:harness backlog` — list every open entry.
|
|
80
80
|
- `/supi:harness score` — recompute and display the score.
|
|
81
|
+
- `/supi:harness pr-comment [--dry-run] [--pr=N] [--repo=owner/repo] [--session=<id>] [--mode=every-push|on-status-change]` — render (or post) the sticky PR comment for the latest validate report. See `docs/supipowers/harness/pr-comment.md`.
|
|
81
82
|
|
|
82
83
|
## Conventions you MUST follow
|
|
83
84
|
|
package/src/bootstrap.ts
CHANGED
|
@@ -13,9 +13,7 @@ import { registerQaCommand } from "./commands/qa.js";
|
|
|
13
13
|
import { registerReleaseCommand, handleRelease } from "./commands/release.js";
|
|
14
14
|
import { registerUpdateCommand, handleUpdate } from "./commands/update.js";
|
|
15
15
|
import { registerDoctorCommand, handleDoctor } from "./commands/doctor.js";
|
|
16
|
-
import { registerMcpCommand, handleMcp, handleMcpCli, parseCliArgs } from "./commands/mcp.js";
|
|
17
16
|
import { registerModelCommand, handleModel } from "./commands/model.js";
|
|
18
|
-
import { executeManagerAction } from "./mcp/manager-tool.js";
|
|
19
17
|
import { registerFixPrCommand } from "./commands/fix-pr.js";
|
|
20
18
|
import { registerContextCommand, handleContext } from "./commands/context.js";
|
|
21
19
|
import { registerOptimizeContextCommand, handleOptimizeContext } from "./commands/optimize-context.js";
|
|
@@ -30,10 +28,6 @@ import { registerHarnessPipelineTools } from "./harness/tools.js";
|
|
|
30
28
|
import { registerHarnessHooks } from "./harness/hooks/register.js";
|
|
31
29
|
import { loadConfig } from "./config/loader.js";
|
|
32
30
|
import { registerContextModeHooks } from "./context-mode/hooks.js";
|
|
33
|
-
import { loadMcpRegistry } from "./mcp/config.js";
|
|
34
|
-
import { McpcClient } from "./mcp/mcpc.js";
|
|
35
|
-
import { parseTags } from "./mcp/activation.js";
|
|
36
|
-
import { initializeMcpServers, shutdownMcpServers } from "./mcp/lifecycle.js";
|
|
37
31
|
import { registerPlanApprovalHook } from "./planning/approval-flow.js";
|
|
38
32
|
import { registerPlanningSystemPromptHook } from "./planning/system-prompt.js";
|
|
39
33
|
import { registerPlanningAskTool, registerPlanningAskToolGuard } from "./planning/planning-ask-tool.js";
|
|
@@ -49,6 +43,7 @@ import { registerUltraPlanAuthoringTool } from "./ultraplan/authoring-tool.js";
|
|
|
49
43
|
import { registerUltraPlanAuthoringPipelineTools } from "./ultraplan/authoring/authoring-tools.js";
|
|
50
44
|
import { registerActiveToolController } from "./tool-catalog/active-tool-controller.js";
|
|
51
45
|
import { registerMempalaceHooks } from "./mempalace/hooks.js";
|
|
46
|
+
import { registerRunbookCommand, handleRunbook } from "./commands/runbook.js";
|
|
52
47
|
import { registerMempalaceTool } from "./mempalace/tool.js";
|
|
53
48
|
|
|
54
49
|
// TUI-only commands — intercepted at the input level to prevent
|
|
@@ -60,7 +55,6 @@ const TUI_COMMANDS: Record<string, (platform: Platform, ctx: any, args?: string)
|
|
|
60
55
|
"supi:review": (platform, ctx, args) => handleAiReview(platform, ctx, args),
|
|
61
56
|
"supi:update": (platform, ctx) => handleUpdate(platform, ctx),
|
|
62
57
|
"supi:doctor": (platform, ctx) => handleDoctor(platform, ctx),
|
|
63
|
-
"supi:mcp": (platform, ctx) => handleMcp(platform, ctx),
|
|
64
58
|
"supi:model": (platform, ctx) => handleModel(platform, ctx),
|
|
65
59
|
"supi:context": (platform, ctx) => handleContext(platform, ctx),
|
|
66
60
|
"supi:optimize-context": (platform, ctx, args) => handleOptimizeContext(platform, ctx, args),
|
|
@@ -72,10 +66,9 @@ const TUI_COMMANDS: Record<string, (platform: Platform, ctx: any, args?: string)
|
|
|
72
66
|
"supi:ultraplan": (platform, ctx, args) => handleUltraplan(platform, ctx, args),
|
|
73
67
|
"supi:harness": (platform, ctx, args) => { void handleHarness(platform, ctx, args); },
|
|
74
68
|
"supi:memory": (platform, ctx, args) => handleMemory(platform, ctx, args),
|
|
69
|
+
"runbook": (platform, ctx, args) => handleRunbook(platform, ctx, args),
|
|
75
70
|
};
|
|
76
71
|
|
|
77
|
-
let pendingTags: string[] = [];
|
|
78
|
-
|
|
79
72
|
function getInstalledVersion(platform: Platform): string | null {
|
|
80
73
|
const pkgPath = platform.paths.agent("extensions", "supipowers", "package.json");
|
|
81
74
|
if (!existsSync(pkgPath)) return null;
|
|
@@ -99,7 +92,6 @@ export function bootstrap(platform: Platform): void {
|
|
|
99
92
|
registerUpdateCommand(platform);
|
|
100
93
|
registerFixPrCommand(platform);
|
|
101
94
|
registerDoctorCommand(platform);
|
|
102
|
-
registerMcpCommand(platform);
|
|
103
95
|
registerModelCommand(platform);
|
|
104
96
|
registerContextCommand(platform);
|
|
105
97
|
registerOptimizeContextCommand(platform);
|
|
@@ -111,6 +103,7 @@ export function bootstrap(platform: Platform): void {
|
|
|
111
103
|
registerUltraplanCommand(platform);
|
|
112
104
|
registerHarnessCommand(platform);
|
|
113
105
|
registerMemoryCommand(platform);
|
|
106
|
+
registerRunbookCommand(platform);
|
|
114
107
|
|
|
115
108
|
|
|
116
109
|
registerUltraPlanRuntimeTools(platform);
|
|
@@ -131,17 +124,6 @@ export function bootstrap(platform: Platform): void {
|
|
|
131
124
|
// message submission, so no chat message appears and no "Working..." indicator
|
|
132
125
|
platform.on("input", (event, ctx) => {
|
|
133
126
|
const text = event.text.trim();
|
|
134
|
-
|
|
135
|
-
// Scan for $tags
|
|
136
|
-
const registry = loadMcpRegistry(platform.paths, ctx.cwd);
|
|
137
|
-
const registeredNames = new Set(Object.keys(registry.servers));
|
|
138
|
-
if (registeredNames.size > 0) {
|
|
139
|
-
const tags = parseTags(event.text, registeredNames);
|
|
140
|
-
if (tags.length > 0) {
|
|
141
|
-
pendingTags = tags;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
127
|
if (!text.startsWith("/")) return;
|
|
146
128
|
|
|
147
129
|
const spaceIndex = text.indexOf(" ");
|
|
@@ -157,14 +139,7 @@ export function bootstrap(platform: Platform): void {
|
|
|
157
139
|
|
|
158
140
|
// Context-mode integration
|
|
159
141
|
const config = loadConfig(platform.paths, process.cwd());
|
|
160
|
-
registerActiveToolController(platform, config
|
|
161
|
-
loadMcpRegistryForCwd: (cwd: string) => loadMcpRegistry(platform.paths, cwd),
|
|
162
|
-
consumePendingTags: () => {
|
|
163
|
-
const tags = pendingTags;
|
|
164
|
-
pendingTags = [];
|
|
165
|
-
return tags;
|
|
166
|
-
},
|
|
167
|
-
});
|
|
142
|
+
registerActiveToolController(platform, config);
|
|
168
143
|
registerContextModeHooks(platform, config);
|
|
169
144
|
registerMempalaceTool(platform, config);
|
|
170
145
|
registerMempalaceHooks(platform, config);
|
|
@@ -174,9 +149,9 @@ export function bootstrap(platform: Platform): void {
|
|
|
174
149
|
registerPlanningSystemPromptHook(platform);
|
|
175
150
|
registerUiDesignSystemPromptHook(platform);
|
|
176
151
|
|
|
177
|
-
// Register harness anti-slop hooks
|
|
178
|
-
//
|
|
179
|
-
//
|
|
152
|
+
// Register harness anti-slop hooks only for repos with a harness marker at extension boot.
|
|
153
|
+
// Registered handlers also check the marker per event, so removing the marker disables
|
|
154
|
+
// an already-started process without affecting other repos.
|
|
180
155
|
registerHarnessHooks(platform);
|
|
181
156
|
|
|
182
157
|
|
|
@@ -196,23 +171,6 @@ export function bootstrap(platform: Platform): void {
|
|
|
196
171
|
// OMP's StatusLine never clears hook statuses on /new, so extensions must do it.
|
|
197
172
|
ctx.ui?.setStatus?.("supi-model", undefined);
|
|
198
173
|
|
|
199
|
-
// MCP: always register mcpc_manager tool (agent needs it even with zero servers)
|
|
200
|
-
if (platform.registerTool) {
|
|
201
|
-
registerMcpcManagerTool(platform, ctx);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// MCP server initialization (only if servers configured)
|
|
205
|
-
const mcpRegistry = loadMcpRegistry(platform.paths, ctx.cwd);
|
|
206
|
-
if (Object.keys(mcpRegistry.servers).length > 0) {
|
|
207
|
-
const mcpClient = new McpcClient((cmd, args, opts) => platform.exec(cmd, args, opts));
|
|
208
|
-
const installed = await mcpClient.checkInstalled();
|
|
209
|
-
if (!installed.installed) {
|
|
210
|
-
ctx.ui.notify("mcpc not installed — MCP servers won't connect. Run /supi:upgrade to install.", "warning");
|
|
211
|
-
} else {
|
|
212
|
-
await initializeMcpServers(mcpRegistry, mcpClient);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
174
|
// Check for updates in the background
|
|
217
175
|
const currentVersion = getInstalledVersion(platform);
|
|
218
176
|
if (!currentVersion) return;
|
|
@@ -234,90 +192,7 @@ export function bootstrap(platform: Platform): void {
|
|
|
234
192
|
});
|
|
235
193
|
|
|
236
194
|
// Session shutdown
|
|
237
|
-
platform.on("session_shutdown", async (
|
|
195
|
+
platform.on("session_shutdown", async () => {
|
|
238
196
|
await stopActiveUiDesignSession();
|
|
239
|
-
|
|
240
|
-
const mcpConfig = loadConfig(platform.paths, ctx.cwd ?? process.cwd());
|
|
241
|
-
if (!mcpConfig.mcp?.closeSessionsOnExit) return;
|
|
242
|
-
|
|
243
|
-
const registry = loadMcpRegistry(platform.paths, ctx.cwd ?? process.cwd());
|
|
244
|
-
const mcpClient = new McpcClient((cmd, args, opts) => platform.exec(cmd, args, opts));
|
|
245
|
-
const names = Object.keys(registry.servers).filter((n) => registry.servers[n].enabled);
|
|
246
|
-
await shutdownMcpServers(names, mcpClient, true);
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function registerMcpcManagerTool(platform: Platform, ctx: any): void {
|
|
251
|
-
platform.registerTool!({
|
|
252
|
-
name: "mcpc_manager",
|
|
253
|
-
label: "MCP Server Manager",
|
|
254
|
-
description: "Add, remove, enable, disable, or refresh MCP servers managed by supipowers. Use this when the user asks to install, set up, or manage MCP servers.",
|
|
255
|
-
promptSnippet: "mcpc_manager — manage MCP servers (add, remove, enable, disable, refresh, login, logout, list, info)",
|
|
256
|
-
promptGuidelines: [
|
|
257
|
-
"Use when the user asks to install, add, or set up an MCP server",
|
|
258
|
-
"Use when the user asks to remove, disable, or manage an MCP server",
|
|
259
|
-
"Do NOT use for calling MCP tools — use the mcpc_<name> gateway tools instead",
|
|
260
|
-
],
|
|
261
|
-
parameters: {
|
|
262
|
-
type: "object",
|
|
263
|
-
properties: {
|
|
264
|
-
action: { type: "string", enum: ["add", "remove", "enable", "disable", "refresh", "login", "logout", "set-activation", "set-taggable", "list", "info"], description: "Action to perform" },
|
|
265
|
-
name: { type: "string", description: "Server name (required for all except list/refresh-all)" },
|
|
266
|
-
url: { type: "string", description: "Server URL (required for add)" },
|
|
267
|
-
transport: { type: "string", enum: ["http", "stdio"], description: "Transport type" },
|
|
268
|
-
docsUrl: { type: "string", description: "Documentation URL for richer README generation" },
|
|
269
|
-
activation: { type: "string", enum: ["always", "contextual", "disabled"], description: "Activation mode" },
|
|
270
|
-
taggable: { type: "boolean", description: "Whether $name tag activates this server" },
|
|
271
|
-
},
|
|
272
|
-
required: ["action"],
|
|
273
|
-
},
|
|
274
|
-
async execute(_toolCallId: string, params: any, _signal: any, _onUpdate: any, toolCtx: any) {
|
|
275
|
-
// First validate via routeManagerAction
|
|
276
|
-
const result = await executeManagerAction(params, {
|
|
277
|
-
hasUI: toolCtx.hasUI ?? true,
|
|
278
|
-
ui: toolCtx.ui ?? {},
|
|
279
|
-
cwd: toolCtx.cwd ?? ctx.cwd,
|
|
280
|
-
}, {
|
|
281
|
-
addServer: () => {},
|
|
282
|
-
removeServer: () => {},
|
|
283
|
-
updateServer: () => {},
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
if (result.error) {
|
|
287
|
-
throw new Error(result.content[0]?.text ?? "Manager action failed");
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// For actual operations, delegate to handleMcpCli
|
|
291
|
-
const cliArgs = buildCliArgsFromParams(params);
|
|
292
|
-
await handleMcpCli(platform, {
|
|
293
|
-
cwd: toolCtx.cwd ?? ctx.cwd,
|
|
294
|
-
hasUI: toolCtx.hasUI ?? true,
|
|
295
|
-
ui: {
|
|
296
|
-
notify: (msg: string) => {
|
|
297
|
-
// Collect notifications — they'll be in the tool result
|
|
298
|
-
},
|
|
299
|
-
...toolCtx.ui,
|
|
300
|
-
},
|
|
301
|
-
}, cliArgs);
|
|
302
|
-
|
|
303
|
-
return {
|
|
304
|
-
content: result.content,
|
|
305
|
-
details: { action: params.action, name: params.name },
|
|
306
|
-
};
|
|
307
|
-
},
|
|
308
197
|
});
|
|
309
198
|
}
|
|
310
|
-
|
|
311
|
-
function buildCliArgsFromParams(params: any): ReturnType<typeof parseCliArgs> {
|
|
312
|
-
return {
|
|
313
|
-
subcommand: params.action === "set-activation" ? "activation"
|
|
314
|
-
: params.action === "set-taggable" ? "tag"
|
|
315
|
-
: params.action,
|
|
316
|
-
name: params.name,
|
|
317
|
-
url: params.url,
|
|
318
|
-
transport: params.transport,
|
|
319
|
-
docsUrl: params.docsUrl,
|
|
320
|
-
activation: params.activation,
|
|
321
|
-
taggable: params.taggable,
|
|
322
|
-
};
|
|
323
|
-
}
|
|
@@ -18,9 +18,11 @@ import {
|
|
|
18
18
|
import type {
|
|
19
19
|
ManualOptimizationAction,
|
|
20
20
|
OptimizationPlan,
|
|
21
|
+
WriteCommandAction,
|
|
21
22
|
WriteRuleAction,
|
|
23
|
+
WriteExtensionAction,
|
|
22
24
|
} from "../context/startup-optimizer.js";
|
|
23
|
-
import { parseManagedRule, renderManagedRule } from "../context/rule-renderer.js";
|
|
25
|
+
import { parseManagedCommand, parseManagedExtension, parseManagedRule, renderManagedCommand, renderManagedExtension, renderManagedRule } from "../context/rule-renderer.js";
|
|
24
26
|
import { DEFAULT_TOKENIGNORE_ENTRIES, mergeManagedTokenignore } from "../context/tokenignore.js";
|
|
25
27
|
import {
|
|
26
28
|
parseStartupOptimizerManifest,
|
|
@@ -142,6 +144,20 @@ async function runCheck(
|
|
|
142
144
|
}
|
|
143
145
|
}
|
|
144
146
|
|
|
147
|
+
const commandFiles: Record<string, string | null> = {};
|
|
148
|
+
if (manifest) {
|
|
149
|
+
for (const command of manifest.commands) {
|
|
150
|
+
commandFiles[command.path] = readOptionalFile(path.join(ctx.cwd, command.path));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const extensionFiles: Record<string, string | null> = {};
|
|
155
|
+
if (manifest) {
|
|
156
|
+
for (const extension of manifest.extensions) {
|
|
157
|
+
extensionFiles[extension.path] = readOptionalFile(path.join(ctx.cwd, extension.path));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
145
161
|
const currentSkills: ParsedSkill[] = currentPrompt ? parseIndividualSkills(currentPrompt) : [];
|
|
146
162
|
const currentSections: PromptSection[] = currentPrompt ? parseSystemPrompt(currentPrompt) : [];
|
|
147
163
|
|
|
@@ -149,6 +165,8 @@ async function runCheck(
|
|
|
149
165
|
manifestPath,
|
|
150
166
|
manifestText,
|
|
151
167
|
ruleFiles,
|
|
168
|
+
commandFiles,
|
|
169
|
+
extensionFiles,
|
|
152
170
|
tokenignorePath,
|
|
153
171
|
tokenignoreText: readOptionalFile(tokenignorePath),
|
|
154
172
|
currentPrompt,
|
|
@@ -232,17 +250,23 @@ async function showDryRun(ctx: PlatformContext, plan: OptimizationPlan): Promise
|
|
|
232
250
|
|
|
233
251
|
function buildPlanPreview(plan: OptimizationPlan): string[] {
|
|
234
252
|
const writeRuleCount = plan.actions.filter((action) => action.kind === "write-rule").length;
|
|
235
|
-
const
|
|
253
|
+
const writeCommandCount = plan.actions.filter((action) => action.kind === "write-command").length;
|
|
254
|
+
const writeExtensionCount = plan.actions.filter((action) => action.kind === "write-extension").length;
|
|
255
|
+
const manualCount = plan.actions.filter((action) => action.kind !== "write-rule" && action.kind !== "write-command" && action.kind !== "write-extension").length;
|
|
236
256
|
const lines = [
|
|
237
257
|
`Source set: ${plan.sourceSetHash.slice(0, 12)}`,
|
|
238
258
|
`Current: ~${formatTokens(Math.ceil(plan.beforeBytes / 4))} tokens | Estimated after planned removals: ~${formatTokens(Math.ceil(plan.estimatedAfterBytes / 4))} tokens`,
|
|
239
|
-
`Actions: ${writeRuleCount} write-rule, ${manualCount} manual`,
|
|
259
|
+
`Actions: ${writeRuleCount} write-rule, ${writeCommandCount} write-command, ${writeExtensionCount} write-extension, ${manualCount} manual`,
|
|
240
260
|
"",
|
|
241
261
|
];
|
|
242
262
|
|
|
243
263
|
for (const action of plan.actions) {
|
|
244
264
|
if (action.kind === "write-rule") {
|
|
245
265
|
lines.push(`write-rule ${action.mode}: ${action.targetPath}`);
|
|
266
|
+
} else if (action.kind === "write-command") {
|
|
267
|
+
lines.push(`write-command: /${action.commandName} -> ${action.targetPath}`);
|
|
268
|
+
} else if (action.kind === "write-extension") {
|
|
269
|
+
lines.push(`write-extension: ${action.extensionName} -> ${action.targetPath}`);
|
|
246
270
|
} else if (action.kind === "manual-disable") {
|
|
247
271
|
lines.push(`manual-disable ${action.sourceName}: ${action.reason}`);
|
|
248
272
|
} else {
|
|
@@ -268,9 +292,14 @@ async function applyOptimizationPlan(
|
|
|
268
292
|
}
|
|
269
293
|
|
|
270
294
|
const writeRules = plan.actions.filter((action): action is WriteRuleAction => action.kind === "write-rule");
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
295
|
+
const writeCommands = plan.actions.filter((action): action is WriteCommandAction => action.kind === "write-command");
|
|
296
|
+
const writeExtensions = plan.actions.filter((action): action is WriteExtensionAction => action.kind === "write-extension");
|
|
297
|
+
const rulePreflight = preflightRuleWrites(ctx.cwd, writeRules);
|
|
298
|
+
const commandPreflight = preflightCommandWrites(ctx.cwd, writeCommands);
|
|
299
|
+
const extensionPreflight = preflightExtensionWrites(ctx.cwd, writeExtensions);
|
|
300
|
+
const preflightConflicts = [...rulePreflight.conflicts, ...commandPreflight.conflicts, ...extensionPreflight.conflicts];
|
|
301
|
+
if (preflightConflicts.length > 0) {
|
|
302
|
+
ctx.ui.notify(`Apply blocked: ${preflightConflicts.join("; ")}`, "error");
|
|
274
303
|
return;
|
|
275
304
|
}
|
|
276
305
|
|
|
@@ -279,7 +308,7 @@ async function applyOptimizationPlan(
|
|
|
279
308
|
return;
|
|
280
309
|
}
|
|
281
310
|
|
|
282
|
-
const summary = buildApplySummary(plan,
|
|
311
|
+
const summary = buildApplySummary(plan, [...rulePreflight.updates, ...commandPreflight.updates, ...extensionPreflight.updates]);
|
|
283
312
|
const accepted = await confirmApply(ctx, summary);
|
|
284
313
|
if (!accepted) {
|
|
285
314
|
ctx.ui.notify("Apply cancelled.", "info");
|
|
@@ -299,6 +328,18 @@ async function applyOptimizationPlan(
|
|
|
299
328
|
fs.writeFileSync(target, renderManagedRule(action));
|
|
300
329
|
}
|
|
301
330
|
|
|
331
|
+
for (const action of writeCommands) {
|
|
332
|
+
const target = absoluteRulePath(ctx.cwd, action.targetPath);
|
|
333
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
334
|
+
fs.writeFileSync(target, renderManagedCommand(action));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
for (const action of writeExtensions) {
|
|
338
|
+
const target = absoluteRulePath(ctx.cwd, action.targetPath);
|
|
339
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
340
|
+
fs.writeFileSync(target, renderManagedExtension(action));
|
|
341
|
+
}
|
|
342
|
+
|
|
302
343
|
fs.mkdirSync(path.dirname(tokenignorePath), { recursive: true });
|
|
303
344
|
fs.writeFileSync(tokenignorePath, tokenignore.content);
|
|
304
345
|
|
|
@@ -315,7 +356,7 @@ async function applyOptimizationPlan(
|
|
|
315
356
|
// process can't see the just-written .omp/rules without a full restart.
|
|
316
357
|
// Be honest about this rather than silently calling reload() and lying.
|
|
317
358
|
ctx.ui.notify(
|
|
318
|
-
"Applied deterministic context migration. Restart OMP so
|
|
359
|
+
"Applied deterministic context migration. Restart OMP so new managed rules in .omp/rules, commands in .omp/commands, and the runbook extension in .omp/extensions are picked up by discovery, then disable the original sources and run /supi:optimize-context --check to validate runtime savings.",
|
|
319
360
|
"info",
|
|
320
361
|
);
|
|
321
362
|
}
|
|
@@ -358,11 +399,73 @@ function preflightRuleWrites(
|
|
|
358
399
|
conflicts.push(`${action.targetPath} is malformed: ${parsed.error}`);
|
|
359
400
|
continue;
|
|
360
401
|
}
|
|
361
|
-
if (
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
402
|
+
if (fs.readFileSync(target, "utf-8") !== renderManagedRule(action)) {
|
|
403
|
+
updates.push(action.targetPath);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return { conflicts, updates };
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function preflightCommandWrites(
|
|
411
|
+
cwd: string,
|
|
412
|
+
actions: WriteCommandAction[],
|
|
413
|
+
): { conflicts: string[]; updates: string[] } {
|
|
414
|
+
const conflicts: string[] = [];
|
|
415
|
+
const updates: string[] = [];
|
|
416
|
+
|
|
417
|
+
for (const action of actions) {
|
|
418
|
+
const target = absoluteRulePath(cwd, action.targetPath);
|
|
419
|
+
if (!fs.existsSync(target)) continue;
|
|
420
|
+
const stat = fs.statSync(target);
|
|
421
|
+
if (!stat.isFile()) {
|
|
422
|
+
conflicts.push(`${action.targetPath} exists and is not a file`);
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const parsed = parseManagedCommand(fs.readFileSync(target, "utf-8"));
|
|
427
|
+
if (parsed.status === "unmanaged") {
|
|
428
|
+
conflicts.push(`${action.targetPath} is unmanaged`);
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
if (parsed.status === "malformed") {
|
|
432
|
+
conflicts.push(`${action.targetPath} is malformed: ${parsed.error}`);
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
if (fs.readFileSync(target, "utf-8") !== renderManagedCommand(action)) {
|
|
436
|
+
updates.push(action.targetPath);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return { conflicts, updates };
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function preflightExtensionWrites(
|
|
444
|
+
cwd: string,
|
|
445
|
+
actions: WriteExtensionAction[],
|
|
446
|
+
): { conflicts: string[]; updates: string[] } {
|
|
447
|
+
const conflicts: string[] = [];
|
|
448
|
+
const updates: string[] = [];
|
|
449
|
+
|
|
450
|
+
for (const action of actions) {
|
|
451
|
+
const target = absoluteRulePath(cwd, action.targetPath);
|
|
452
|
+
if (!fs.existsSync(target)) continue;
|
|
453
|
+
const stat = fs.statSync(target);
|
|
454
|
+
if (!stat.isFile()) {
|
|
455
|
+
conflicts.push(`${action.targetPath} exists and is not a file`);
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const parsed = parseManagedExtension(fs.readFileSync(target, "utf-8"));
|
|
460
|
+
if (parsed.status === "unmanaged") {
|
|
461
|
+
conflicts.push(`${action.targetPath} is unmanaged`);
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
if (parsed.status === "malformed") {
|
|
465
|
+
conflicts.push(`${action.targetPath} is malformed: ${parsed.error}`);
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
if (fs.readFileSync(target, "utf-8") !== renderManagedExtension(action)) {
|
|
366
469
|
updates.push(action.targetPath);
|
|
367
470
|
}
|
|
368
471
|
}
|
|
@@ -370,11 +473,16 @@ function preflightRuleWrites(
|
|
|
370
473
|
return { conflicts, updates };
|
|
371
474
|
}
|
|
372
475
|
|
|
476
|
+
|
|
373
477
|
function buildApplySummary(plan: OptimizationPlan, updates: string[]): string {
|
|
374
478
|
const writeRuleCount = plan.actions.filter((action) => action.kind === "write-rule").length;
|
|
375
|
-
const
|
|
479
|
+
const writeCommandCount = plan.actions.filter((action) => action.kind === "write-command").length;
|
|
480
|
+
const writeExtensionCount = plan.actions.filter((action) => action.kind === "write-extension").length;
|
|
481
|
+
const manualCount = plan.actions.filter((action) => action.kind !== "write-rule" && action.kind !== "write-command" && action.kind !== "write-extension").length;
|
|
376
482
|
const lines = [
|
|
377
483
|
`Write ${writeRuleCount} managed rule file${writeRuleCount === 1 ? "" : "s"}.`,
|
|
484
|
+
`Write ${writeCommandCount} managed command file${writeCommandCount === 1 ? "" : "s"}.`,
|
|
485
|
+
`Write ${writeExtensionCount} managed extension file${writeExtensionCount === 1 ? "" : "s"}.`,
|
|
378
486
|
`Merge ${DEFAULT_TOKENIGNORE_ENTRIES.length} managed .tokenignore entries.`,
|
|
379
487
|
`Record ${manualCount} manual follow-up action${manualCount === 1 ? "" : "s"}.`,
|
|
380
488
|
`Estimated savings after planned removals: ~${formatTokens(Math.ceil(plan.estimatedSavedBytes / 4))} tokens.`,
|
|
@@ -382,7 +490,7 @@ function buildApplySummary(plan: OptimizationPlan, updates: string[]): string {
|
|
|
382
490
|
if (updates.length > 0) {
|
|
383
491
|
lines.push(`Managed update candidate${updates.length === 1 ? "" : "s"}: ${updates.join(", ")}`);
|
|
384
492
|
}
|
|
385
|
-
lines.push("Manifest is written last after managed rule and tokenignore writes succeed.");
|
|
493
|
+
lines.push("Manifest is written last after managed rule, command, extension, and tokenignore writes succeed.");
|
|
386
494
|
return lines.join("\n");
|
|
387
495
|
}
|
|
388
496
|
|
|
@@ -406,8 +514,35 @@ function buildManifest(plan: OptimizationPlan): StartupOptimizerManifest {
|
|
|
406
514
|
slug: action.slug,
|
|
407
515
|
sourceBytes: action.sourceBytes,
|
|
408
516
|
...(action.condition ? { condition: action.condition } : {}),
|
|
517
|
+
...(action.triggers ? { triggers: action.triggers } : {}),
|
|
518
|
+
...(action.scope ? { scope: action.scope } : {}),
|
|
409
519
|
...(action.description ? { description: action.description } : {}),
|
|
410
520
|
}));
|
|
521
|
+
const commands = plan.actions
|
|
522
|
+
.filter((action): action is WriteCommandAction => action.kind === "write-command")
|
|
523
|
+
.map((action) => ({
|
|
524
|
+
path: action.targetPath,
|
|
525
|
+
sourceId: action.sourceId,
|
|
526
|
+
sourceName: action.sourceName,
|
|
527
|
+
sourceHash: action.sourceHash,
|
|
528
|
+
slug: action.slug,
|
|
529
|
+
commandName: action.commandName,
|
|
530
|
+
sourceBytes: action.sourceBytes,
|
|
531
|
+
...(action.description ? { description: action.description } : {}),
|
|
532
|
+
}));
|
|
533
|
+
const extensions = plan.actions
|
|
534
|
+
.filter((action): action is WriteExtensionAction => action.kind === "write-extension")
|
|
535
|
+
.map((action) => ({
|
|
536
|
+
path: action.targetPath,
|
|
537
|
+
sourceId: action.sourceId,
|
|
538
|
+
sourceName: action.sourceName,
|
|
539
|
+
sourceHash: action.sourceHash,
|
|
540
|
+
slug: action.slug,
|
|
541
|
+
extensionName: action.extensionName,
|
|
542
|
+
sourceBytes: action.sourceBytes,
|
|
543
|
+
}));
|
|
544
|
+
|
|
545
|
+
|
|
411
546
|
|
|
412
547
|
return {
|
|
413
548
|
version: 1,
|
|
@@ -417,12 +552,14 @@ function buildManifest(plan: OptimizationPlan): StartupOptimizerManifest {
|
|
|
417
552
|
estimatedAfterBytes: plan.estimatedAfterBytes,
|
|
418
553
|
estimatedSavedBytes: plan.estimatedSavedBytes,
|
|
419
554
|
rules,
|
|
555
|
+
commands,
|
|
556
|
+
extensions,
|
|
420
557
|
tokenignore: {
|
|
421
558
|
path: ".omp/supipowers/.tokenignore",
|
|
422
559
|
entries: tokenignore.entries,
|
|
423
560
|
hash: tokenignore.hash,
|
|
424
561
|
},
|
|
425
|
-
manualActions: plan.actions.filter((action): action is ManualOptimizationAction => action.kind !== "write-rule"),
|
|
562
|
+
manualActions: plan.actions.filter((action): action is ManualOptimizationAction => action.kind !== "write-rule" && action.kind !== "write-command" && action.kind !== "write-extension"),
|
|
426
563
|
};
|
|
427
564
|
}
|
|
428
565
|
|