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.
Files changed (84) hide show
  1. package/README.md +5 -6
  2. package/package.json +4 -2
  3. package/skills/harness/SKILL.md +1 -0
  4. package/src/bootstrap.ts +8 -133
  5. package/src/commands/optimize-context.ts +153 -16
  6. package/src/commands/runbook.ts +511 -0
  7. package/src/config/defaults.ts +5 -5
  8. package/src/config/loader.ts +1 -0
  9. package/src/config/schema.ts +2 -6
  10. package/src/context/rule-renderer.ts +274 -2
  11. package/src/context/runbook-extension-template.ts +193 -0
  12. package/src/context/startup-check.ts +197 -2
  13. package/src/context/startup-optimizer.ts +133 -10
  14. package/src/context-mode/knowledge/store.ts +381 -43
  15. package/src/context-mode/tools.ts +41 -3
  16. package/src/deps/registry.ts +1 -12
  17. package/src/fix-pr/assessment.ts +1 -0
  18. package/src/fix-pr/prompt-builder.ts +1 -0
  19. package/src/git/commit.ts +76 -18
  20. package/src/harness/command.ts +201 -12
  21. package/src/harness/default-agents/docs.md +39 -0
  22. package/src/harness/docs/config.ts +29 -0
  23. package/src/harness/docs/glob-match.ts +27 -0
  24. package/src/harness/docs/index-renderer.ts +82 -0
  25. package/src/harness/docs/provenance.ts +125 -0
  26. package/src/harness/docs/regen-decision.ts +167 -0
  27. package/src/harness/docs/representative-files.ts +175 -0
  28. package/src/harness/docs/source-hash.ts +106 -0
  29. package/src/harness/docs/validator.ts +233 -0
  30. package/src/harness/git-verification.ts +515 -0
  31. package/src/harness/git-verify-qa.ts +406 -0
  32. package/src/harness/hooks/layer-context-inject.ts +35 -1
  33. package/src/harness/hooks/register.ts +24 -3
  34. package/src/harness/pipeline.ts +37 -13
  35. package/src/harness/pr-comment/baseline.ts +105 -0
  36. package/src/harness/pr-comment/ci-env.ts +120 -0
  37. package/src/harness/pr-comment/gh-poster.ts +227 -0
  38. package/src/harness/pr-comment/handler.ts +198 -0
  39. package/src/harness/pr-comment/render.ts +297 -0
  40. package/src/harness/pr-comment/status.ts +95 -0
  41. package/src/harness/pr-comment/types.ts +73 -0
  42. package/src/harness/pr-comment/workflow-summary.ts +47 -0
  43. package/src/harness/project-paths.ts +95 -0
  44. package/src/harness/stages/design.ts +1 -0
  45. package/src/harness/stages/discover.ts +1 -13
  46. package/src/harness/stages/docs.ts +708 -0
  47. package/src/harness/stages/implement-apply.ts +934 -0
  48. package/src/harness/stages/implement.ts +64 -51
  49. package/src/harness/stages/plan.ts +25 -16
  50. package/src/harness/stages/validate.ts +478 -0
  51. package/src/harness/storage.ts +142 -0
  52. package/src/harness/tools.ts +130 -0
  53. package/src/mempalace/bridge.ts +207 -41
  54. package/src/mempalace/config.ts +10 -4
  55. package/src/mempalace/format.ts +122 -6
  56. package/src/mempalace/hooks.ts +204 -56
  57. package/src/mempalace/installer-helper.ts +18 -4
  58. package/src/mempalace/python/mempalace_bridge.py +128 -3
  59. package/src/mempalace/runtime.ts +53 -16
  60. package/src/mempalace/schema.ts +151 -30
  61. package/src/mempalace/session-summary.ts +5 -0
  62. package/src/mempalace/tool.ts +17 -4
  63. package/src/mempalace/upstream-limits.ts +69 -0
  64. package/src/planning/approval-flow.ts +25 -2
  65. package/src/planning/planning-ask-tool.ts +34 -4
  66. package/src/planning/system-prompt.ts +1 -1
  67. package/src/tool-catalog/active-tool-controller.ts +0 -22
  68. package/src/tool-catalog/active-tool-planner.ts +0 -26
  69. package/src/tool-catalog/tool-groups.ts +1 -9
  70. package/src/types.ts +127 -8
  71. package/src/ui-design/session.ts +114 -8
  72. package/src/utils/executable.ts +10 -1
  73. package/src/workspace/state-paths.ts +1 -1
  74. package/src/commands/mcp.ts +0 -814
  75. package/src/mcp/activation.ts +0 -77
  76. package/src/mcp/config.ts +0 -223
  77. package/src/mcp/docs.ts +0 -154
  78. package/src/mcp/gateway.ts +0 -103
  79. package/src/mcp/lifecycle.ts +0 -79
  80. package/src/mcp/manager-tool.ts +0 -104
  81. package/src/mcp/mcpc.ts +0 -113
  82. package/src/mcp/registry.ts +0 -98
  83. package/src/mcp/triggers.ts +0 -62
  84. package/src/mcp/types.ts +0 -95
@@ -1,104 +0,0 @@
1
- interface ManagerParams {
2
- action: string;
3
- name?: string;
4
- url?: string;
5
- transport?: string;
6
- docsUrl?: string;
7
- activation?: string;
8
- taggable?: boolean;
9
- }
10
-
11
- interface RouteResult {
12
- action: string;
13
- error?: string;
14
- }
15
-
16
- const ACTIONS_REQUIRING_NAME = new Set([
17
- "add", "remove", "enable", "disable", "login", "logout",
18
- "set-activation", "set-taggable", "info",
19
- ]);
20
-
21
- export type { ManagerParams };
22
-
23
- interface ManagerContent {
24
- type: string;
25
- text: string;
26
- }
27
-
28
- interface ManagerResult {
29
- content: ManagerContent[];
30
- error?: boolean;
31
- }
32
-
33
- interface ManagerContext {
34
- hasUI: boolean;
35
- ui: { confirm?: (title: string, message: string) => Promise<boolean> };
36
- cwd: string;
37
- }
38
-
39
- interface ManagerDeps {
40
- addServer: Function;
41
- removeServer: Function;
42
- updateServer: Function;
43
- }
44
-
45
- export async function executeManagerAction(
46
- params: ManagerParams,
47
- ctx: ManagerContext,
48
- _deps: ManagerDeps,
49
- ): Promise<ManagerResult> {
50
- const route = routeManagerAction(params);
51
- if (route.error) {
52
- return { content: [{ type: "text", text: route.error }], error: true };
53
- }
54
-
55
- switch (params.action) {
56
- case "add": {
57
- // Confirmation gate for agent-triggered adds
58
- if (ctx.hasUI && ctx.ui.confirm) {
59
- const confirmed = await ctx.ui.confirm(
60
- "Add MCP Server",
61
- `Add "${params.name}" from ${params.url}?`,
62
- );
63
- if (!confirmed) {
64
- return { content: [{ type: "text", text: "User cancelled the add operation." }] };
65
- }
66
- }
67
- // Delegate to addServer
68
- return { content: [{ type: "text", text: `Server "${params.name}" add initiated.` }] };
69
- }
70
- default:
71
- return { content: [{ type: "text", text: `Action "${params.action}" executed.` }] };
72
- }
73
- }
74
-
75
- export function routeManagerAction(params: ManagerParams): RouteResult {
76
- const { action, name } = params;
77
-
78
- if (ACTIONS_REQUIRING_NAME.has(action) && !name) {
79
- return { action, error: `Action "${action}" requires a server name` };
80
- }
81
-
82
- switch (action) {
83
- case "add":
84
- if (!params.url) return { action, error: "Action \"add\" requires a url" };
85
- return { action };
86
- case "set-activation":
87
- if (!params.activation) return { action, error: "Action \"set-activation\" requires an activation value" };
88
- return { action };
89
- case "set-taggable":
90
- if (params.taggable === undefined) return { action, error: "Action \"set-taggable\" requires a taggable value" };
91
- return { action };
92
- case "remove":
93
- case "enable":
94
- case "disable":
95
- case "login":
96
- case "logout":
97
- case "info":
98
- case "list":
99
- case "refresh":
100
- return { action };
101
- default:
102
- return { action, error: `Unknown action: ${action}` };
103
- }
104
- }
package/src/mcp/mcpc.ts DELETED
@@ -1,113 +0,0 @@
1
- // src/mcp/mcpc.ts
2
- import type { McpTool } from "./types.js";
3
-
4
- type ExecFn = (cmd: string, args: string[], opts?: any) => Promise<{ stdout: string; stderr: string; code: number }>;
5
-
6
- /** Combine stdout + stderr, deduplicating if identical */
7
- function combineOutput(r: { stdout: string; stderr: string }): string {
8
- const out = r.stdout.trim();
9
- const err = r.stderr.trim();
10
- if (!out) return err;
11
- if (!err) return out;
12
- if (out === err) return out;
13
- return [out, err].join("\n");
14
- }
15
-
16
- export class McpcClient {
17
- constructor(private exec: ExecFn) {}
18
-
19
- async checkInstalled(): Promise<{ installed: boolean; version?: string }> {
20
- try {
21
- const r = await this.exec("mcpc", ["--version"]);
22
- if (r.code !== 0) return { installed: false };
23
- const match = r.stdout.match(/mcpc\s+([\d.]+)/);
24
- return { installed: true, version: match?.[1] };
25
- } catch {
26
- return { installed: false };
27
- }
28
- }
29
-
30
- async autoInstall(): Promise<boolean> {
31
- try {
32
- const r = await this.exec("npm", ["install", "-g", "@apify/mcpc"]);
33
- return r.code === 0;
34
- } catch {
35
- return false;
36
- }
37
- }
38
-
39
- async connect(target: string, sessionName: string, authHeader?: string): Promise<{ code: number; output: string }> {
40
- const args = authHeader
41
- ? ["-H", authHeader, target, "connect", `@supi-${sessionName}`]
42
- : [target, "connect", `@supi-${sessionName}`];
43
- const r = await this.exec("mcpc", args);
44
- return { code: r.code, output: combineOutput(r) };
45
- }
46
-
47
- async close(sessionName: string): Promise<{ code: number; output: string }> {
48
- const r = await this.exec("mcpc", [`@supi-${sessionName}`, "close"]);
49
- return { code: r.code, output: combineOutput(r) };
50
- }
51
-
52
- async restart(sessionName: string): Promise<{ code: number; output: string }> {
53
- const r = await this.exec("mcpc", [`@supi-${sessionName}`, "restart"]);
54
- return { code: r.code, output: combineOutput(r) };
55
- }
56
-
57
- async toolsList(sessionName: string): Promise<{ code: number; tools: McpTool[] }> {
58
- const r = await this.exec("mcpc", ["--json", `@supi-${sessionName}`, "tools-list"]);
59
- if (r.code !== 0) return { code: r.code, tools: [] };
60
- try {
61
- const tools = JSON.parse(r.stdout) as McpTool[];
62
- return { code: 0, tools };
63
- } catch {
64
- return { code: r.code, tools: [] };
65
- }
66
- }
67
-
68
- async toolsCall(
69
- sessionName: string,
70
- toolName: string,
71
- args?: Record<string, unknown>,
72
- ): Promise<{ code: number; data?: any; error?: string }> {
73
- const cmdArgs = ["--json", `@supi-${sessionName}`, "tools-call", toolName];
74
- if (args) {
75
- cmdArgs.push(...this.serializeArgs(args));
76
- }
77
- const r = await this.exec("mcpc", cmdArgs);
78
- if (r.code !== 0) return { code: r.code, error: r.stderr || r.stdout };
79
- try {
80
- return { code: 0, data: JSON.parse(r.stdout) };
81
- } catch {
82
- return { code: 0, data: r.stdout };
83
- }
84
- }
85
-
86
- async login(target: string): Promise<{ code: number; output: string }> {
87
- // OAuth flows can take minutes (user approving in browser)
88
- const r = await this.exec("mcpc", [target, "login"], { timeout: 120000 });
89
- return { code: r.code, output: combineOutput(r) };
90
- }
91
-
92
- async logout(target: string): Promise<{ code: number; output: string }> {
93
- const r = await this.exec("mcpc", [target, "logout"]);
94
- return { code: r.code, output: combineOutput(r) };
95
- }
96
-
97
- async listSessions(): Promise<{ code: number; output: string }> {
98
- const r = await this.exec("mcpc", ["--json"]);
99
- return { code: r.code, output: r.stdout };
100
- }
101
-
102
- serializeArgs(args: Record<string, unknown>): string[] {
103
- const result: string[] = [];
104
- for (const [key, value] of Object.entries(args)) {
105
- if (typeof value === "string") {
106
- result.push(`${key}:="${value}"`);
107
- } else {
108
- result.push(`${key}:=${JSON.stringify(value)}`);
109
- }
110
- }
111
- return result;
112
- }
113
- }
@@ -1,98 +0,0 @@
1
- // src/mcp/registry.ts — MCP server registry lookup via registry.modelcontextprotocol.io
2
-
3
- type ExecFn = (cmd: string, args: string[], opts?: any) => Promise<{ stdout: string; stderr: string; code: number }>;
4
-
5
- export interface RegistryServer {
6
- name: string;
7
- title: string;
8
- description: string;
9
- url: string;
10
- transport: "http" | "stdio";
11
- authRequired: boolean;
12
- repoUrl?: string;
13
- docsUrl?: string;
14
- }
15
-
16
- interface RegistryRemote {
17
- type: string;
18
- url: string;
19
- headers?: Array<{ name: string; isRequired?: boolean }>;
20
- }
21
-
22
- interface RegistryEntry {
23
- server: {
24
- name: string;
25
- title?: string;
26
- description?: string;
27
- repository?: { url?: string };
28
- websiteUrl?: string;
29
- remotes?: RegistryRemote[];
30
- };
31
- }
32
-
33
- const REGISTRY_BASE = "https://registry.modelcontextprotocol.io";
34
-
35
- /**
36
- * Look up MCP servers by name from the official MCP registry.
37
- * Uses curl via exec — no web tools or Python needed.
38
- */
39
- export async function lookupMcpServer(
40
- exec: ExecFn,
41
- query: string,
42
- ): Promise<RegistryServer[]> {
43
- const url = `${REGISTRY_BASE}/v0/servers?search=${encodeURIComponent(query)}&version=latest&limit=10`;
44
-
45
- const result = await exec("curl", ["-sf", "--max-time", "10", url]);
46
- if (result.code !== 0) return [];
47
-
48
- try {
49
- const data = JSON.parse(result.stdout);
50
- const entries: RegistryEntry[] = data.servers ?? data ?? [];
51
- return entries
52
- .filter((e) => e.server?.remotes?.length)
53
- .map((e) => parseRegistryEntry(e));
54
- } catch {
55
- return [];
56
- }
57
- }
58
-
59
- function parseRegistryEntry(entry: RegistryEntry): RegistryServer {
60
- const s = entry.server;
61
- const remote = s.remotes![0];
62
- const transport = remote.type === "stdio" ? "stdio" as const : "http" as const;
63
- const authRequired = remote.headers?.some((h) => h.isRequired) ?? false;
64
-
65
- return {
66
- name: s.name,
67
- title: s.title ?? s.name,
68
- description: s.description ?? "",
69
- url: remote.url,
70
- transport,
71
- authRequired,
72
- repoUrl: s.repository?.url,
73
- docsUrl: s.websiteUrl ?? s.repository?.url,
74
- };
75
- }
76
-
77
- /**
78
- * Find the best match for a given name from registry results.
79
- * Prefers exact name matches, then title matches, then substring.
80
- */
81
- export function pickBestMatch(results: RegistryServer[], query: string): RegistryServer | undefined {
82
- const q = query.toLowerCase();
83
-
84
- // Exact name segment match (e.g., "figma" matches "com.figma.mcp/mcp")
85
- const exact = results.find((r) =>
86
- r.name.toLowerCase().split(/[./]/).some((seg) => seg === q)
87
- );
88
- if (exact) return exact;
89
-
90
- // Title contains query
91
- const titleMatch = results.find((r) =>
92
- r.title.toLowerCase().includes(q)
93
- );
94
- if (titleMatch) return titleMatch;
95
-
96
- // Any name contains query
97
- return results.find((r) => r.name.toLowerCase().includes(q));
98
- }
@@ -1,62 +0,0 @@
1
- import type { McpTool } from "./types.js";
2
-
3
- const GENERIC_WORDS = new Set([
4
- "get", "set", "list", "create", "update", "delete", "read", "write",
5
- "find", "search", "fetch", "add", "remove", "the", "a", "an", "of",
6
- "from", "to", "in", "on", "is", "for", "and", "or", "with", "by",
7
- "all", "new", "this", "that", "it", "be", "are", "was", "has", "have",
8
- "will", "can", "do", "does", "not", "no", "if", "use", "using",
9
- ]);
10
-
11
- const MAX_TRIGGERS = 10;
12
-
13
- /** Split camelCase or snake_case into words */
14
- function tokenize(name: string): string[] {
15
- return name
16
- .replace(/([a-z])([A-Z])/g, "$1_$2") // camelCase → snake
17
- .toLowerCase()
18
- .split(/[_\-\s]+/)
19
- .filter((w) => w.length > 2); // skip tiny words
20
- }
21
-
22
- /** Extract keywords from description (simple stop-word filter) */
23
- function extractKeywords(text: string): string[] {
24
- return text
25
- .toLowerCase()
26
- .replace(/[^a-z0-9\s]/g, " ")
27
- .split(/\s+/)
28
- .filter((w) => w.length > 2 && !GENERIC_WORDS.has(w));
29
- }
30
-
31
- export function generateTriggers(serverName: string, tools: McpTool[]): string[] {
32
- const seen = new Set<string>();
33
- const result: string[] = [];
34
-
35
- // Server name always first
36
- result.push(serverName);
37
- seen.add(serverName);
38
-
39
- for (const tool of tools) {
40
- // Tokenize tool name
41
- for (const word of tokenize(tool.name)) {
42
- if (!GENERIC_WORDS.has(word) && !seen.has(word)) {
43
- seen.add(word);
44
- result.push(word);
45
- }
46
- }
47
-
48
- // Extract from description
49
- if (tool.description) {
50
- for (const word of extractKeywords(tool.description)) {
51
- if (!seen.has(word)) {
52
- seen.add(word);
53
- result.push(word);
54
- }
55
- }
56
- }
57
-
58
- if (result.length >= MAX_TRIGGERS) break;
59
- }
60
-
61
- return result.slice(0, MAX_TRIGGERS);
62
- }
package/src/mcp/types.ts DELETED
@@ -1,95 +0,0 @@
1
- // src/mcp/types.ts
2
-
3
- export interface McpAuth {
4
- type: "oauth" | "bearer";
5
- profile?: string;
6
- envVar?: string;
7
- }
8
-
9
- export interface ServerConfig {
10
- url?: string;
11
- command?: string;
12
- args?: string[];
13
- transport: "http" | "stdio";
14
- activation: "always" | "contextual" | "disabled";
15
- taggable: boolean;
16
- triggers: string[];
17
- antiTriggers: string[];
18
- auth?: McpAuth;
19
- docsUrl?: string;
20
- enabled: boolean;
21
- authPending: boolean;
22
- addedAt: string;
23
- }
24
-
25
- export interface McpRegistry {
26
- schemaVersion: number;
27
- servers: Record<string, ServerConfig>;
28
- }
29
-
30
- export interface McpTool {
31
- name: string;
32
- description: string;
33
- inputSchema?: Record<string, unknown>;
34
- }
35
-
36
- export interface ToolCatalog {
37
- serverName: string;
38
- tools: McpTool[];
39
- fetchedAt: string;
40
- }
41
-
42
- export type ServerStatus = "connected" | "disconnected" | "offline" | "auth-pending";
43
-
44
- export interface ServerState {
45
- config: ServerConfig;
46
- status: ServerStatus;
47
- catalog?: ToolCatalog;
48
- }
49
-
50
- /** A server discovered from the OMP host MCP config (not yet in supipowers registry) */
51
- export interface HostMcpServer {
52
- name: string;
53
- scope: "user" | "project" | "claude-code";
54
- transport: "http" | "stdio" | "sse";
55
- url?: string;
56
- command?: string;
57
- args?: string[];
58
- env?: Record<string, string>;
59
- headers?: Record<string, string>;
60
- enabled?: boolean;
61
- hasAuth?: boolean;
62
- }
63
-
64
- /** mcpc exit codes per documentation */
65
- export const MCPC_EXIT = {
66
- SUCCESS: 0,
67
- CLIENT_ERROR: 1,
68
- SERVER_ERROR: 2,
69
- NETWORK_ERROR: 3,
70
- AUTH_ERROR: 4,
71
- } as const;
72
-
73
- export function createEmptyRegistry(): McpRegistry {
74
- return { schemaVersion: 1, servers: {} };
75
- }
76
-
77
- /** Reserved names that cannot be used as server names ($tag safety) */
78
- export const RESERVED_NAMES = new Set([
79
- "path", "home", "user", "shell", "env", "pwd", "term", "lang",
80
- "editor", "display", "host", "port", "http_proxy", "https_proxy", "no_proxy",
81
- ]);
82
-
83
- /** Validate a server name */
84
- export function isValidServerName(name: string): { valid: boolean; reason?: string } {
85
- if (name.length < 1 || name.length > 63) {
86
- return { valid: false, reason: "Name must be 1-63 characters" };
87
- }
88
- if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(name)) {
89
- return { valid: false, reason: "Lowercase alphanumeric and hyphens only, cannot start/end with hyphen" };
90
- }
91
- if (RESERVED_NAMES.has(name)) {
92
- return { valid: false, reason: `"${name}" is reserved (conflicts with shell variables)` };
93
- }
94
- return { valid: true };
95
- }