supipowers 2.0.1 → 2.1.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 (80) hide show
  1. package/README.md +10 -6
  2. package/package.json +4 -2
  3. package/skills/harness/SKILL.md +1 -0
  4. package/src/bootstrap.ts +5 -133
  5. package/src/commands/clear.ts +6 -6
  6. package/src/commands/release.ts +3 -1
  7. package/src/commands/update.ts +1 -1
  8. package/src/config/defaults.ts +5 -5
  9. package/src/config/loader.ts +1 -0
  10. package/src/config/schema.ts +2 -6
  11. package/src/context/analyzer.ts +104 -35
  12. package/src/context-mode/knowledge/store.ts +381 -43
  13. package/src/context-mode/tools.ts +41 -3
  14. package/src/deps/registry.ts +1 -12
  15. package/src/fix-pr/assessment.ts +1 -0
  16. package/src/fix-pr/prompt-builder.ts +1 -0
  17. package/src/git/commit.ts +76 -18
  18. package/src/harness/command.ts +103 -6
  19. package/src/harness/default-agents/docs.md +39 -0
  20. package/src/harness/docs/config.ts +29 -0
  21. package/src/harness/docs/glob-match.ts +27 -0
  22. package/src/harness/docs/index-renderer.ts +82 -0
  23. package/src/harness/docs/provenance.ts +125 -0
  24. package/src/harness/docs/regen-decision.ts +167 -0
  25. package/src/harness/docs/representative-files.ts +175 -0
  26. package/src/harness/docs/source-hash.ts +106 -0
  27. package/src/harness/docs/validator.ts +233 -0
  28. package/src/harness/hooks/layer-context-inject.ts +35 -1
  29. package/src/harness/hooks/register.ts +24 -3
  30. package/src/harness/pipeline.ts +20 -5
  31. package/src/harness/pr-comment/baseline.ts +105 -0
  32. package/src/harness/pr-comment/ci-env.ts +120 -0
  33. package/src/harness/pr-comment/gh-poster.ts +227 -0
  34. package/src/harness/pr-comment/handler.ts +198 -0
  35. package/src/harness/pr-comment/render.ts +297 -0
  36. package/src/harness/pr-comment/status.ts +95 -0
  37. package/src/harness/pr-comment/types.ts +73 -0
  38. package/src/harness/pr-comment/workflow-summary.ts +47 -0
  39. package/src/harness/project-paths.ts +95 -0
  40. package/src/harness/stages/design.ts +1 -0
  41. package/src/harness/stages/discover.ts +1 -13
  42. package/src/harness/stages/docs.ts +708 -0
  43. package/src/harness/stages/implement-apply.ts +877 -0
  44. package/src/harness/stages/implement.ts +64 -51
  45. package/src/harness/stages/plan.ts +25 -16
  46. package/src/harness/stages/validate.ts +370 -0
  47. package/src/harness/storage.ts +142 -0
  48. package/src/harness/tools.ts +130 -0
  49. package/src/mempalace/bridge.ts +207 -41
  50. package/src/mempalace/config.ts +10 -4
  51. package/src/mempalace/format.ts +122 -6
  52. package/src/mempalace/hooks.ts +204 -56
  53. package/src/mempalace/installer-helper.ts +18 -4
  54. package/src/mempalace/python/mempalace_bridge.py +128 -3
  55. package/src/mempalace/runtime.ts +55 -18
  56. package/src/mempalace/schema.ts +151 -30
  57. package/src/mempalace/session-summary.ts +5 -0
  58. package/src/mempalace/tool.ts +17 -4
  59. package/src/mempalace/upstream-limits.ts +69 -0
  60. package/src/planning/approval-flow.ts +25 -2
  61. package/src/planning/planning-ask-tool.ts +34 -4
  62. package/src/planning/system-prompt.ts +1 -1
  63. package/src/tool-catalog/active-tool-controller.ts +0 -22
  64. package/src/tool-catalog/active-tool-planner.ts +0 -26
  65. package/src/tool-catalog/tool-groups.ts +1 -9
  66. package/src/types.ts +87 -8
  67. package/src/ui-design/session.ts +114 -10
  68. package/src/utils/executable.ts +10 -1
  69. package/src/workspace/state-paths.ts +1 -1
  70. package/src/commands/mcp.ts +0 -814
  71. package/src/mcp/activation.ts +0 -77
  72. package/src/mcp/config.ts +0 -223
  73. package/src/mcp/docs.ts +0 -154
  74. package/src/mcp/gateway.ts +0 -103
  75. package/src/mcp/lifecycle.ts +0 -79
  76. package/src/mcp/manager-tool.ts +0 -104
  77. package/src/mcp/mcpc.ts +0 -113
  78. package/src/mcp/registry.ts +0 -98
  79. package/src/mcp/triggers.ts +0 -62
  80. 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, `mcpc`, and Playwright CLI.
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,19 +69,22 @@ 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 |
77
75
  | `/supi:generate` | Documentation drift detection |
78
76
  | `/supi:update` | Update supipowers to the latest version |
79
77
  | `/supi:agents` | Manage review agents |
78
+ | `/supi:ultraplan` | Multi-stage authoring pipeline (intake → scout → discover → research → synthesize → review → approve) |
79
+ | `/supi:harness` | Harness engineering pipeline and anti-slop guardrails |
80
+ | `/supi:memory` | Manage native MemPalace memory integration (`status`, `setup`) |
81
+ | `/supi:clear` | Clear metrics, cache, session knowledge, and memory |
80
82
 
81
- 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:mcp`, `/supi:model`, `/supi:context`, `/supi:optimize-context`, `/supi:commit`, `/supi:release`, `/supi:checks`, `/supi:agents`.
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`.
82
84
 
83
85
  ## How it works
84
86
 
85
- **Planning.** `/supi:plan` steers the AI through planning phases (scopedecomposeestimateverify), saves the result to `.omp/supipowers/plans/`, and presents an approval UI. On approval, tasks execute in the same session.
87
+ **Planning.** `/supi:plan` steers the AI through planning phases (ExploreClarifyBrainstormDesign & 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.
86
88
 
87
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.
88
90
 
@@ -123,7 +125,6 @@ Use `/supi:agents` to inspect the merged set that will actually run.
123
125
  | Release automation | ✅ | ❌ |
124
126
  | Commit workflow | ✅ | ❌ |
125
127
  | Context-window optimizations | ✅ | ❌ |
126
- | MCP server management through mcpc | ✅ | ❌ |
127
128
  | Git worktree workflow | ❌ | ✅ |
128
129
 
129
130
  ## Quality gates
@@ -157,6 +158,8 @@ Configuration uses built-in defaults plus two user-managed override layers:
157
158
 
158
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.
159
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
+
160
163
  ## Release channels
161
164
 
162
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`.
@@ -213,6 +216,7 @@ Supipowers ships runtime-loaded prompt skills that are also available to the age
213
216
  | `release` | `/supi:release` |
214
217
  | `context-mode` | Context window guidance |
215
218
  | `creating-supi-agents` | Agent creation guidance |
219
+ | `harness` | `/supi:harness` |
216
220
 
217
221
  ## Development
218
222
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supipowers",
3
- "version": "2.0.1",
3
+ "version": "2.1.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"
@@ -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";
@@ -60,7 +54,6 @@ const TUI_COMMANDS: Record<string, (platform: Platform, ctx: any, args?: string)
60
54
  "supi:review": (platform, ctx, args) => handleAiReview(platform, ctx, args),
61
55
  "supi:update": (platform, ctx) => handleUpdate(platform, ctx),
62
56
  "supi:doctor": (platform, ctx) => handleDoctor(platform, ctx),
63
- "supi:mcp": (platform, ctx) => handleMcp(platform, ctx),
64
57
  "supi:model": (platform, ctx) => handleModel(platform, ctx),
65
58
  "supi:context": (platform, ctx) => handleContext(platform, ctx),
66
59
  "supi:optimize-context": (platform, ctx, args) => handleOptimizeContext(platform, ctx, args),
@@ -74,8 +67,6 @@ const TUI_COMMANDS: Record<string, (platform: Platform, ctx: any, args?: string)
74
67
  "supi:memory": (platform, ctx, args) => handleMemory(platform, ctx, args),
75
68
  };
76
69
 
77
- let pendingTags: string[] = [];
78
-
79
70
  function getInstalledVersion(platform: Platform): string | null {
80
71
  const pkgPath = platform.paths.agent("extensions", "supipowers", "package.json");
81
72
  if (!existsSync(pkgPath)) return null;
@@ -99,7 +90,6 @@ export function bootstrap(platform: Platform): void {
99
90
  registerUpdateCommand(platform);
100
91
  registerFixPrCommand(platform);
101
92
  registerDoctorCommand(platform);
102
- registerMcpCommand(platform);
103
93
  registerModelCommand(platform);
104
94
  registerContextCommand(platform);
105
95
  registerOptimizeContextCommand(platform);
@@ -131,17 +121,6 @@ export function bootstrap(platform: Platform): void {
131
121
  // message submission, so no chat message appears and no "Working..." indicator
132
122
  platform.on("input", (event, ctx) => {
133
123
  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
124
  if (!text.startsWith("/")) return;
146
125
 
147
126
  const spaceIndex = text.indexOf(" ");
@@ -157,14 +136,7 @@ export function bootstrap(platform: Platform): void {
157
136
 
158
137
  // Context-mode integration
159
138
  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
- });
139
+ registerActiveToolController(platform, config);
168
140
  registerContextModeHooks(platform, config);
169
141
  registerMempalaceTool(platform, config);
170
142
  registerMempalaceHooks(platform, config);
@@ -174,9 +146,9 @@ export function bootstrap(platform: Platform): void {
174
146
  registerPlanningSystemPromptHook(platform);
175
147
  registerUiDesignSystemPromptHook(platform);
176
148
 
177
- // Register harness anti-slop hooks (gated by per-repo marker file at session start).
178
- // Idempotent safe to call once per process. The hooks themselves no-op on every
179
- // event when the marker is missing, so other repos see no behavior change.
149
+ // Register harness anti-slop hooks only for repos with a harness marker at extension boot.
150
+ // Registered handlers also check the marker per event, so removing the marker disables
151
+ // an already-started process without affecting other repos.
180
152
  registerHarnessHooks(platform);
181
153
 
182
154
 
@@ -196,23 +168,6 @@ export function bootstrap(platform: Platform): void {
196
168
  // OMP's StatusLine never clears hook statuses on /new, so extensions must do it.
197
169
  ctx.ui?.setStatus?.("supi-model", undefined);
198
170
 
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
171
  // Check for updates in the background
217
172
  const currentVersion = getInstalledVersion(platform);
218
173
  if (!currentVersion) return;
@@ -234,90 +189,7 @@ export function bootstrap(platform: Platform): void {
234
189
  });
235
190
 
236
191
  // Session shutdown
237
- platform.on("session_shutdown", async (_event, ctx) => {
192
+ platform.on("session_shutdown", async () => {
238
193
  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
194
  });
309
195
  }
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
- }
@@ -269,14 +269,14 @@ function parseArgs(args: string | undefined): ParsedArgs {
269
269
  return { scope, dryRun };
270
270
  }
271
271
 
272
- export function handleClear(
272
+ export async function handleClear(
273
273
  platform: Platform,
274
274
  ctx: PlatformContext,
275
275
  args?: string,
276
- ): void {
276
+ ): Promise<void> {
277
277
  if (!ctx.hasUI) return;
278
278
 
279
- void (async () => {
279
+ try {
280
280
  const { scope, dryRun } = parseArgs(args);
281
281
  const store = getMetricsStore();
282
282
  const sessionId = getSessionId();
@@ -419,16 +419,16 @@ export function handleClear(
419
419
  "error",
420
420
  );
421
421
  }
422
- })().catch((err) => {
422
+ } catch (err) {
423
423
  ctx.ui.notify(`Clear error: ${(err as Error).message}`, "error");
424
- });
424
+ }
425
425
  }
426
426
 
427
427
  export function registerClearCommand(platform: Platform): void {
428
428
  platform.registerCommand("supi:clear", {
429
429
  description: "Clear metrics, cache, current-session knowledge, and memory for the active session (or `all` for the project)",
430
430
  async handler(args: string | undefined, ctx: any) {
431
- handleClear(platform, ctx, args);
431
+ await handleClear(platform, ctx, args);
432
432
  },
433
433
  });
434
434
  }
@@ -51,6 +51,9 @@ import {
51
51
  import {
52
52
  checkDocDrift,
53
53
  buildFixPrompt,
54
+ loadState as loadDriftState,
55
+ saveState as saveDriftState,
56
+ getHeadCommit,
54
57
  } from "../docs/drift.js";
55
58
  import { runQualityGates } from "../quality/runner.js";
56
59
  import { REVIEW_GATE_REGISTRY } from "../quality/review-gates.js";
@@ -492,7 +495,6 @@ export async function handleRelease(platform: Platform, ctx: any, args?: string)
492
495
  progress.activate("doc-drift", "Fixing documentation");
493
496
  notifyInfo(ctx, "Updating documentation", driftResult.summary);
494
497
  const fixPrompt = buildFixPrompt(driftResult.findings);
495
- const { loadState: loadDriftState, saveState: saveDriftState, getHeadCommit } = await import("../docs/drift.js");
496
498
  const driftHead = await getHeadCommit(platform, repoRoot);
497
499
  const driftState = loadDriftState(platform.paths, repoRoot);
498
500
  saveDriftState(platform.paths, repoRoot, { ...driftState, lastCommit: driftHead, lastRunAt: new Date().toISOString() });
@@ -106,7 +106,7 @@ async function updateSupipowers(
106
106
  cpSync(binSource, join(extDir, "bin"), { recursive: true });
107
107
  }
108
108
  // skills/ must live inside the extension dir — src/commands/agents.ts
109
- // uses a static `import from "../../skills/..."` resolved relative to src/.
109
+ // uses a static skills markdown import resolved relative to src/.
110
110
  const skillsDirSource = join(downloadedRoot, "skills");
111
111
  if (existsSync(skillsDirSource)) {
112
112
  cpSync(skillsDirSource, join(extDir, "skills"), { recursive: true });
@@ -1,5 +1,6 @@
1
1
  // src/config/defaults.ts
2
2
  import type { SupipowersConfig } from "../types.js";
3
+ import { MEMPALACE_PACKAGE_VERSION } from "../mempalace/upstream-limits.js";
3
4
 
4
5
  export const DEFAULT_CONFIG: SupipowersConfig = {
5
6
  version: "1.0.0",
@@ -39,7 +40,7 @@ export const DEFAULT_CONFIG: SupipowersConfig = {
39
40
  lazyTools: {
40
41
  enabled: true,
41
42
  mode: "balanced",
42
- alwaysKeep: ["ctx_execute", "ctx_search", "ctx_open_cached", "mcpc_manager"],
43
+ alwaysKeep: ["ctx_execute", "ctx_search", "ctx_open_cached"],
43
44
  commandAllowlist: {},
44
45
  keywordTools: {},
45
46
  },
@@ -61,12 +62,9 @@ export const DEFAULT_CONFIG: SupipowersConfig = {
61
62
  focusChainCadence: 6,
62
63
  },
63
64
  },
64
- mcp: {
65
- closeSessionsOnExit: false,
66
- },
67
65
  mempalace: {
68
66
  enabled: true,
69
- packageVersion: "3.3.4",
67
+ packageVersion: MEMPALACE_PACKAGE_VERSION,
70
68
  managedVenvPath: "~/.omp/supipowers/mempalace-venv",
71
69
  palacePath: "~/.mempalace/palace",
72
70
  defaultWingStrategy: "repo-name",
@@ -87,6 +85,8 @@ export const DEFAULT_CONFIG: SupipowersConfig = {
87
85
  diaryChars: 8000,
88
86
  autoSearchTokens: 150,
89
87
  wakeUpInjectionEvery: 10,
88
+ autoSearchSimilarityFloor: 0.55,
89
+ autoSearchBm25Floor: 0.3,
90
90
  },
91
91
  timeouts: {
92
92
  setupMs: 120000,
@@ -349,6 +349,7 @@ function migrateConfig(config: Record<string, unknown>): Record<string, unknown>
349
349
  const legacyProfile = typeof migrated.defaultProfile === "string" ? migrated.defaultProfile : null;
350
350
  delete migrated.defaultProfile;
351
351
  delete migrated.orchestration;
352
+ delete migrated.mcp;
352
353
 
353
354
  const qa = asRecord(migrated.qa);
354
355
  const legacyTestCommand =
@@ -120,12 +120,6 @@ export const ConfigSchema = Type.Object(
120
120
  },
121
121
  { additionalProperties: false },
122
122
  ),
123
- mcp: Type.Object(
124
- {
125
- closeSessionsOnExit: Type.Boolean(),
126
- },
127
- { additionalProperties: false },
128
- ),
129
123
  mempalace: Type.Object(
130
124
  {
131
125
  enabled: Type.Boolean(),
@@ -158,6 +152,8 @@ export const ConfigSchema = Type.Object(
158
152
  diaryChars: Type.Integer({ minimum: 1 }),
159
153
  autoSearchTokens: Type.Integer({ minimum: 1 }),
160
154
  wakeUpInjectionEvery: Type.Integer({ minimum: 1 }),
155
+ autoSearchSimilarityFloor: Type.Number({ minimum: 0, maximum: 1 }),
156
+ autoSearchBm25Floor: Type.Number({ minimum: 0 }),
161
157
  },
162
158
  { additionalProperties: false },
163
159
  ),
@@ -59,41 +59,71 @@ export interface ParsedSkill {
59
59
  export function parseIndividualSkills(systemPrompt: string): ParsedSkill[] {
60
60
  if (!systemPrompt) return [];
61
61
 
62
- // OMP renders skills as markdown headings under "# Skills":
63
- // ## skill-name
64
- // Description text...
65
- // Find the Skills section bounded by the next h1 heading or end of text.
66
- const skillsSectionMatch = systemPrompt.match(
67
- /^# Skills\n[\s\S]*?\n(?=##\s)/m,
68
- );
69
- if (!skillsSectionMatch) return [];
70
-
71
- // Extract the region from first ## to the next # (h1) or end of text
72
- const sectionStart = skillsSectionMatch.index! + skillsSectionMatch[0].length;
73
- const afterSection = systemPrompt.slice(sectionStart);
74
- const nextH1 = afterSection.search(/^# [^#]/m);
75
- const skillsBody =
76
- skillsSectionMatch[0].slice(skillsSectionMatch[0].indexOf("\n## ") + 1) +
77
- (nextH1 === -1 ? afterSection : afterSection.slice(0, nextH1));
78
-
79
- // Split on ## headings
80
- const skills: ParsedSkill[] = [];
62
+ // Locate `# Skills` and bound the section by the next h1 heading or end of
63
+ // text. The legacy bound used `^##\s`, which let the section bleed past
64
+ // sibling h2 headings (e.g. `## MCP Server Instructions`) into unrelated
65
+ // content and misidentify them as skills.
66
+ const headerMatch = systemPrompt.match(/^# Skills\b[^\n]*\n/m);
67
+ if (!headerMatch) return [];
68
+ const bodyStart = headerMatch.index! + headerMatch[0].length;
69
+ const after = systemPrompt.slice(bodyStart);
70
+ const nextH1 = after.search(/^# [^#]/m);
71
+ const skillsBody = nextH1 === -1 ? after : after.slice(0, nextH1);
72
+
73
+ // Modern OMP (≥14.7) renders skills as a bullet list: "- name: description"
74
+ // with descriptions that may wrap across multiple lines.
75
+ const bulletRegex = /^- ([a-zA-Z0-9._-]+):/gm;
76
+ const bullets: { name: string; index: number }[] = [];
77
+ let bm: RegExpExecArray | null;
78
+ while ((bm = bulletRegex.exec(skillsBody)) !== null) {
79
+ bullets.push({ name: bm[1], index: bm.index });
80
+ }
81
+
82
+ if (bullets.length > 0) {
83
+ // Defensive upper bound for the last bullet: stop at any inline markdown
84
+ // heading inside the body. Real OMP prompts already terminate at an h1
85
+ // boundary, but synthetic / older prompts may not.
86
+ let bodyEnd = skillsBody.length;
87
+ const headingScan = /^#{1,6}\s/gm;
88
+ let hsm: RegExpExecArray | null;
89
+ while ((hsm = headingScan.exec(skillsBody)) !== null) {
90
+ if (hsm.index > bullets[bullets.length - 1].index) {
91
+ bodyEnd = hsm.index;
92
+ break;
93
+ }
94
+ }
95
+
96
+ const bulletSkills: ParsedSkill[] = [];
97
+ for (let i = 0; i < bullets.length; i++) {
98
+ const start = bullets[i].index;
99
+ const end = i + 1 < bullets.length ? bullets[i + 1].index : bodyEnd;
100
+ const content = skillsBody.slice(start, end).trimEnd();
101
+ bulletSkills.push({
102
+ name: bullets[i].name,
103
+ bytes: byteLength(content),
104
+ tokens: estimateTokens(content),
105
+ content,
106
+ });
107
+ }
108
+ return bulletSkills;
109
+ }
110
+
111
+ // Legacy / synthetic shape: "## name" h2 sub-headings under `# Skills`.
81
112
  const headingRegex = /^## (.+)$/gm;
82
113
  const headings: { name: string; index: number }[] = [];
83
- let match;
84
-
85
- while ((match = headingRegex.exec(skillsBody)) !== null) {
86
- headings.push({ name: match[1].trim(), index: match.index });
114
+ let hm: RegExpExecArray | null;
115
+ while ((hm = headingRegex.exec(skillsBody)) !== null) {
116
+ headings.push({ name: hm[1].trim(), index: hm.index });
87
117
  }
88
118
 
119
+ const skills: ParsedSkill[] = [];
89
120
  for (let i = 0; i < headings.length; i++) {
90
121
  const start = headings[i].index;
91
122
  const end = i + 1 < headings.length ? headings[i + 1].index : skillsBody.length;
92
123
  const content = skillsBody.slice(start, end).trimEnd();
93
- const bytes = byteLength(content);
94
124
  skills.push({
95
125
  name: headings[i].name,
96
- bytes,
126
+ bytes: byteLength(content),
97
127
  tokens: estimateTokens(content),
98
128
  content,
99
129
  });
@@ -218,26 +248,45 @@ function extractXmlSections(
218
248
  sections: PromptSection[],
219
249
  consumed: Set<number>,
220
250
  ): void {
221
- // Project section FIRST (so nested <file> tags inside <project> are consumed)
222
- const projMatch = text.match(/<project>([\s\S]*?)<\/project>/);
223
- if (projMatch) {
251
+ // Project section FIRST (so nested <file> tags inside the wrapper are consumed).
252
+ // Modern OMP uses `<|START_PROJECT|>...<|END_PROJECT|>` pipe markers; older OMP
253
+ // and synthetic test inputs use the legacy `<project>...</project>` XML form.
254
+ const projectPatterns: RegExp[] = [
255
+ /<\|START_PROJECT\|>[\s\S]*?<\|END_PROJECT\|>/,
256
+ /<project>[\s\S]*?<\/project>/,
257
+ ];
258
+ for (const pattern of projectPatterns) {
259
+ const projMatch = text.match(pattern);
260
+ if (!projMatch) continue;
224
261
  sections.push({
225
262
  label: "Project context",
226
263
  bytes: byteLength(projMatch[0]),
227
264
  content: projMatch[0],
228
265
  });
229
266
  markConsumed(consumed, projMatch.index!, projMatch.index! + projMatch[0].length);
267
+ break;
230
268
  }
231
269
 
232
- // Instructions section
233
- const instrMatch = text.match(/<instructions>([\s\S]*?)<\/instructions>/);
234
- if (instrMatch) {
270
+ // Environment envelope (OMP ≥14.9.3) — workstation, tool catalog, LSP guidance.
271
+ const envMatch = text.match(/<\|START_ENV\|>[\s\S]*?<\|END_ENV\|>/);
272
+ if (envMatch) {
235
273
  sections.push({
236
- label: "Extension instructions",
237
- bytes: byteLength(instrMatch[0]),
238
- content: instrMatch[0],
274
+ label: "Environment",
275
+ bytes: byteLength(envMatch[0]),
276
+ content: envMatch[0],
239
277
  });
240
- markConsumed(consumed, instrMatch.index!, instrMatch.index! + instrMatch[0].length);
278
+ markConsumed(consumed, envMatch.index!, envMatch.index! + envMatch[0].length);
279
+ }
280
+
281
+ // Contract envelope (OMP ≥14.9.3) — inviolable rules, yielding criteria.
282
+ const contractMatch = text.match(/<\|START_CONTRACT\|>[\s\S]*?<\|END_CONTRACT\|>/);
283
+ if (contractMatch) {
284
+ sections.push({
285
+ label: "Contract",
286
+ bytes: byteLength(contractMatch[0]),
287
+ content: contractMatch[0],
288
+ });
289
+ markConsumed(consumed, contractMatch.index!, contractMatch.index! + contractMatch[0].length);
241
290
  }
242
291
 
243
292
  // File sections — skip if already consumed (e.g., nested inside <project>)
@@ -318,6 +367,26 @@ function extractHeadingSections(
318
367
  }
319
368
  }
320
369
 
370
+ // Skills aggregate (OMP ≥14.7): markdown bullet list under `# Skills`.
371
+ // The legacy `<skills>` XML form is handled in extractXmlSections; this picks
372
+ // up the modern markdown shape rendered by OMP runtime. Bounded by the next
373
+ // h1/h2 heading so we don't swallow MCP instructions / Tools blocks.
374
+ const skillsHeading = text.match(/^# Skills\b[^\n]*\n/m);
375
+ if (skillsHeading && !consumed.has(skillsHeading.index!)) {
376
+ const start = skillsHeading.index!;
377
+ const afterHeading = text.slice(start + skillsHeading[0].length);
378
+ const nextHeading = afterHeading.search(/^#{1,2}\s/m);
379
+ const end = nextHeading === -1
380
+ ? text.length
381
+ : start + skillsHeading[0].length + nextHeading;
382
+ const content = text.slice(start, end);
383
+ const bulletCount = (content.match(/^- [a-zA-Z0-9._-]+:/gm) || []).length;
384
+ if (bulletCount > 0) {
385
+ sections.push({ label: `Skills (${bulletCount})`, bytes: byteLength(content), content });
386
+ markConsumed(consumed, start, end);
387
+ }
388
+ }
389
+
321
390
  // Also recognize bare memory:// blocks without a heading
322
391
  if (!sections.some((s) => s.label === "Memory")) {
323
392
  const memoryMatch = text.match(/memory:\/\/\S+/);