wyrm-mcp 7.2.0 → 7.2.2

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 (156) hide show
  1. package/LICENSE +26 -667
  2. package/NOTICE +14 -33
  3. package/dist/activation.d.ts.map +1 -1
  4. package/dist/activation.js +1 -44
  5. package/dist/activation.js.map +1 -1
  6. package/dist/agent-daemon.js +4 -281
  7. package/dist/agent-loop.js +7 -332
  8. package/dist/analytics.js +13 -236
  9. package/dist/attribution.js +1 -49
  10. package/dist/audit.js +2 -457
  11. package/dist/auto-capture.js +3 -138
  12. package/dist/auto-orchestrator.js +1 -325
  13. package/dist/autoconfig.js +39 -840
  14. package/dist/buddy-runner.js +1 -109
  15. package/dist/buddy.js +14 -564
  16. package/dist/build-flags.js +1 -17
  17. package/dist/capabilities.js +3 -183
  18. package/dist/capture.js +1 -56
  19. package/dist/causality.js +6 -107
  20. package/dist/cli.js +20 -281
  21. package/dist/cloud/cli.js +5 -541
  22. package/dist/cloud/client.js +1 -221
  23. package/dist/cloud/crypto.js +1 -85
  24. package/dist/cloud/machine-id.js +2 -113
  25. package/dist/cloud/recovery.js +1 -60
  26. package/dist/cloud/sync-engine.js +7 -543
  27. package/dist/cloud-backup.js +5 -579
  28. package/dist/cloud-profile.js +1 -138
  29. package/dist/cloud-sync-entrypoint.js +1 -47
  30. package/dist/cloud-sync.js +2 -309
  31. package/dist/constellation.js +12 -168
  32. package/dist/context-build-budgeted.js +4 -144
  33. package/dist/context-ranking.js +1 -69
  34. package/dist/crypto.js +1 -179
  35. package/dist/daemon-write-endpoint.js +1 -290
  36. package/dist/daemon-writer.js +2 -406
  37. package/dist/database.js +43 -1110
  38. package/dist/deprecations.js +2 -162
  39. package/dist/design.js +13 -141
  40. package/dist/event-replication.js +1 -112
  41. package/dist/events-sse.js +7 -43
  42. package/dist/events.js +6 -238
  43. package/dist/failure-patterns.js +42 -659
  44. package/dist/federation.js +12 -236
  45. package/dist/goals.js +13 -101
  46. package/dist/golden.js +3 -355
  47. package/dist/handlers/agent.js +4 -165
  48. package/dist/handlers/alias-adapters.js +1 -129
  49. package/dist/handlers/aliases.js +1 -171
  50. package/dist/handlers/audit.js +1 -87
  51. package/dist/handlers/boundary.js +1 -221
  52. package/dist/handlers/capture.js +73 -1109
  53. package/dist/handlers/causality.js +7 -114
  54. package/dist/handlers/cloud.js +85 -382
  55. package/dist/handlers/companion.js +28 -459
  56. package/dist/handlers/datalake.js +7 -187
  57. package/dist/handlers/dispatch-context.js +0 -22
  58. package/dist/handlers/entity.js +25 -256
  59. package/dist/handlers/events.js +16 -335
  60. package/dist/handlers/failure.js +13 -340
  61. package/dist/handlers/goals.js +4 -296
  62. package/dist/handlers/intelligence.js +126 -674
  63. package/dist/handlers/invoicing.js +1 -70
  64. package/dist/handlers/mcpclient.js +6 -137
  65. package/dist/handlers/orchestration.js +40 -125
  66. package/dist/handlers/output-schemas.js +1 -24
  67. package/dist/handlers/presence.js +3 -99
  68. package/dist/handlers/project.js +28 -182
  69. package/dist/handlers/prompts.js +6 -157
  70. package/dist/handlers/quest.js +4 -224
  71. package/dist/handlers/recall.js +11 -218
  72. package/dist/handlers/registry.js +1 -167
  73. package/dist/handlers/resources.js +1 -288
  74. package/dist/handlers/review.js +11 -74
  75. package/dist/handlers/run.js +17 -487
  76. package/dist/handlers/search.js +15 -326
  77. package/dist/handlers/session.js +28 -615
  78. package/dist/handlers/share.js +8 -184
  79. package/dist/handlers/shims.js +1 -464
  80. package/dist/handlers/skill.js +67 -449
  81. package/dist/handlers/survivors.js +1 -120
  82. package/dist/handlers/symbols.js +8 -109
  83. package/dist/handlers/syncops.js +4 -302
  84. package/dist/handlers/types.js +1 -27
  85. package/dist/harvest.js +5 -191
  86. package/dist/hours.js +7 -156
  87. package/dist/http-auth.js +3 -321
  88. package/dist/http-fast.js +21 -1137
  89. package/dist/icons.js +1 -47
  90. package/dist/index.js +2 -924
  91. package/dist/indexer.js +4 -145
  92. package/dist/intelligence.js +31 -261
  93. package/dist/internal-dispatch.js +3 -212
  94. package/dist/keyset.js +1 -110
  95. package/dist/knowledge-graph.js +12 -176
  96. package/dist/license.d.ts +11 -0
  97. package/dist/license.d.ts.map +1 -1
  98. package/dist/license.js +2 -414
  99. package/dist/license.js.map +1 -1
  100. package/dist/logger.js +2 -199
  101. package/dist/maintenance.js +2 -148
  102. package/dist/mcp-client.js +6 -262
  103. package/dist/memory-artifacts.js +30 -449
  104. package/dist/migrate-prompt.js +2 -124
  105. package/dist/migrations.js +40 -655
  106. package/dist/performance.js +1 -228
  107. package/dist/presence.js +11 -140
  108. package/dist/priority-embed.js +5 -164
  109. package/dist/providers/embedding-provider.js +1 -196
  110. package/dist/readonly-gate.js +1 -29
  111. package/dist/rehydration.js +9 -157
  112. package/dist/reindex.js +1 -88
  113. package/dist/render-target.js +21 -514
  114. package/dist/render.js +4 -280
  115. package/dist/repl-guard.js +1 -173
  116. package/dist/replication-daemon-entrypoint.js +1 -31
  117. package/dist/replication-daemon.js +2 -262
  118. package/dist/resilience.js +1 -591
  119. package/dist/reverse-bridge.js +5 -360
  120. package/dist/security.js +1 -244
  121. package/dist/session-seen.js +3 -51
  122. package/dist/setup.js +1 -260
  123. package/dist/skill-author.js +5 -168
  124. package/dist/spec-kit.js +1 -191
  125. package/dist/sqlite-busy.js +1 -154
  126. package/dist/statusline.js +11 -315
  127. package/dist/sub-agent.js +13 -262
  128. package/dist/summarizer.js +13 -139
  129. package/dist/symbols.js +7 -283
  130. package/dist/sync.js +5 -359
  131. package/dist/tasks-dispatch.js +1 -84
  132. package/dist/tasks.js +1 -282
  133. package/dist/token-budget.js +1 -143
  134. package/dist/tool-analytics.js +7 -129
  135. package/dist/tool-annotations.js +1 -365
  136. package/dist/tool-manifest-v2.json +1 -1
  137. package/dist/tool-manifest.json +1 -1
  138. package/dist/tool-profiles.js +1 -75
  139. package/dist/trace-harvest.js +6 -244
  140. package/dist/types.js +1 -30
  141. package/dist/ui-dashboard.js +41 -50
  142. package/dist/ulid.js +1 -81
  143. package/dist/validate.js +1 -129
  144. package/dist/vault.js +1 -534
  145. package/dist/vectors.js +3 -184
  146. package/dist/version-check.js +4 -136
  147. package/dist/visibility.js +19 -155
  148. package/dist/wyrm-cli.js +98 -2451
  149. package/dist/wyrm-cli.js.map +1 -1
  150. package/dist/wyrm-guard.js +14 -424
  151. package/dist/wyrm-loop.js +3 -150
  152. package/dist/wyrm-manifest.json +1 -1
  153. package/dist/wyrm-statusline-daemon.js +1 -11
  154. package/dist/wyrm-statusline.js +4 -56
  155. package/dist/wyrm-ui.js +9 -77
  156. package/package.json +4 -2
@@ -1,120 +1 @@
1
- /**
2
- * v7 F3 (T021/T022) — the 7.0 SURVIVOR set (spec FR-4): the frozen advertised
3
- * surface.
4
- *
5
- * The frozen ~31-verb surface at 7.0.0 is: the hot-path singles that keep
6
- * their first-class 6.x names verbatim (§6: "every existing CLAUDE.md
7
- * contract keeps working unmodified"), the action-param nouns whose 6.x name
8
- * already IS the noun, and the T022 noun shims (src/handlers/shims.ts).
9
- *
10
- * The alias spine generator (scripts/gen-alias-spine.mjs) computes the alias
11
- * keyset as
12
- *
13
- * aliases = live ListTools names − SURVIVOR_TOOLS
14
- *
15
- * and CI (tests/alias-spine.test.ts) asserts that equation against the REAL
16
- * booted server — never a prose list (spec FR-4; the panel itself misnamed
17
- * the orchestration trio, which is why generation from the live wire is law).
18
- * The CI test also asserts SURVIVOR_TOOLS ⊆ live names (a ghost survivor
19
- * would silently corrupt the subtraction) — the shims are advertised, so the
20
- * equation needs no special-casing.
21
- *
22
- * T022 reconciliation (recorded deviation): spec FR-4 as approved enumerated
23
- * 15 singles + 19 nouns = 34 advertised names, conflicting with its own §7
24
- * criterion 1 ("default ListTools advertises ≤32 tools", version-pinned).
25
- * The T032 findings pass amended the same reconciliation INTO spec.md FR-4,
26
- * so the spec is again the single source of truth for the frozen surface
27
- * (tests/hygiene-security-floor.test.ts locks the amendment in place).
28
- * Two same-domain folds close the gap without losing any advertised
29
- * capability:
30
- * - wyrm_entity_graph → wyrm_entity action=graph (demoted from the T021
31
- * survivor list to an alias; its 6.x case is untouched);
32
- * - the events noun → wyrm_replication action=publish|since|subscribe|
33
- * replicate (the Live-Memory event mesh IS the replication domain).
34
- * Surface today: 19 + 12 shims + wyrm_run (T027, V7_NEW_TOOL_NAMES) = 32 —
35
- * exactly at the pin.
36
- *
37
- * @copyright 2026 Ghost Protocol (Pvt) Ltd.
38
- * @license AGPL-3.0-or-later
39
- */
40
- import { SHIM_TOOL_NAMES } from './shims.js';
41
- /**
42
- * Survivors whose names exist on the 6.x (pre-shim) surface.
43
- *
44
- * Hot-path singles (spec FR-4 / §6 frozen names):
45
- * buddy (the well-known Buddy Protocol name — wyrm_buddy folds INTO it,
46
- * T021), wyrm_act, wyrm_call_external, wyrm_capabilities (retires to a
47
- * resource in 7.1 once the fallback is proven), wyrm_capture,
48
- * wyrm_context_build, wyrm_decided_because, wyrm_failure_check,
49
- * wyrm_failure_record, wyrm_recall, wyrm_review, wyrm_search,
50
- * wyrm_session_prime, wyrm_truth_get, wyrm_truth_set.
51
- *
52
- * Action-param nouns whose 6.x name IS already the noun:
53
- * wyrm_maintenance, wyrm_replication (absorbing sync_conflicts/sync_resolve
54
- * + the events quartet, T022), wyrm_share, wyrm_stats.
55
- * (wyrm_entity_graph was in this list at T021; T022 folded it into
56
- * wyrm_entity action=graph — see the header deviation.)
57
- */
58
- export const LEGACY_SURVIVOR_TOOLS = new Set([
59
- 'buddy',
60
- 'wyrm_act',
61
- 'wyrm_call_external',
62
- 'wyrm_capabilities',
63
- 'wyrm_capture',
64
- 'wyrm_context_build',
65
- 'wyrm_decided_because',
66
- 'wyrm_failure_check',
67
- 'wyrm_failure_record',
68
- 'wyrm_maintenance',
69
- 'wyrm_recall',
70
- 'wyrm_replication',
71
- 'wyrm_review',
72
- 'wyrm_search',
73
- 'wyrm_session_prime',
74
- 'wyrm_share',
75
- 'wyrm_stats',
76
- 'wyrm_truth_get',
77
- 'wyrm_truth_set',
78
- ]);
79
- /**
80
- * v7 F3 (T027): NEW first-class 7.0 tools — neither 6.x names (so never rows
81
- * in the 137-name disposition table, and excluded from the 6.x-name corpora)
82
- * nor shims (they have real ToolSpec handlers in the registry, not
83
- * resolveShimCall translations). Every count formula that was
84
- * `WYRM_TOOL_COUNT + SHIM_TOOL_NAMES.length` is now `+ V7_NEW_TOOL_NAMES
85
- * .length` too. wyrm_run (spec FR-5, the run loop) is the 32nd survivor —
86
- * the surface lands exactly at the spec §7 criterion-1 pin.
87
- */
88
- export const V7_NEW_TOOL_NAMES = ['wyrm_run'];
89
- /**
90
- * v7 F4 (T040): NEW 7.x tools that are NOT advertised — like V7_NEW_TOOL_NAMES
91
- * they are neither 6.x names (excluded from the 137-name disposition table +
92
- * the 6.x-name corpora) nor shims, BUT they are NOT survivors: they live as
93
- * HIDDEN ALIASES (callable, never on the default ListTools), reached only via a
94
- * survivor's mode/action. wyrm_capture_trace is reached via
95
- * wyrm_capture({ mode: 'trace' }) — the same shape wyrm_auto_capture (mode=
96
- * extract) and wyrm_harvest (mode=artifacts) take, except those were 6.x names
97
- * so they sit in the legacy corpus; this one is brand-new, so it must be
98
- * subtracted from the 6.x corpus the same way V7_NEW is, yet stay OUT of
99
- * SURVIVOR_TOOLS so the §7 criterion-1 ≤32 advertised pin holds.
100
- */
101
- export const V7_HIDDEN_TOOL_NAMES = ['wyrm_capture_trace'];
102
- /** v7 additions over the 6.x set (advertised survivors + hidden aliases) —
103
- * the subtraction that re-derives the frozen 137-name 6.x corpus from the
104
- * live wire (tests/alias-leak.test.ts, scripts/gen-*). */
105
- export const V7_ADDED_TOOL_NAMES = [...V7_NEW_TOOL_NAMES, ...V7_HIDDEN_TOOL_NAMES];
106
- /**
107
- * THE frozen advertised surface (default ListTools): legacy-named survivors +
108
- * the T022 noun shims + the T027 first-class v7 tools. Everything else is a
109
- * hidden alias (callable, never advertised) or a CLI exile.
110
- * NOTE: V7_HIDDEN_TOOL_NAMES are deliberately NOT here — they are hidden
111
- * aliases, reached via a survivor's mode (the ≤32 advertised pin holds).
112
- */
113
- export const SURVIVOR_TOOLS = new Set([
114
- ...LEGACY_SURVIVOR_TOOLS,
115
- ...SHIM_TOOL_NAMES,
116
- ...V7_NEW_TOOL_NAMES,
117
- ]);
118
- /** Sorted array view (codepoint order) for deterministic iteration. */
119
- export const SURVIVOR_NAMES = [...SURVIVOR_TOOLS].sort();
120
- //# sourceMappingURL=survivors.js.map
1
+ import{SHIM_TOOL_NAMES as _}from"./shims.js";const e=new Set(["buddy","wyrm_act","wyrm_call_external","wyrm_capabilities","wyrm_capture","wyrm_context_build","wyrm_decided_because","wyrm_failure_check","wyrm_failure_record","wyrm_maintenance","wyrm_recall","wyrm_replication","wyrm_review","wyrm_search","wyrm_session_prime","wyrm_share","wyrm_stats","wyrm_truth_get","wyrm_truth_set"]),r=["wyrm_run"],t=["wyrm_capture_trace"],c=[...r,...t],m=new Set([...e,..._,...r]),y=[...m].sort();export{e as LEGACY_SURVIVOR_TOOLS,y as SURVIVOR_NAMES,m as SURVIVOR_TOOLS,c as V7_ADDED_TOOL_NAMES,t as V7_HIDDEN_TOOL_NAMES,r as V7_NEW_TOOL_NAMES};
@@ -1,109 +1,8 @@
1
- /**
2
- * @copyright 2026 Ghost Protocol (Pvt) Ltd.
3
- * @license AGPL-3.0-or-later
4
- */
5
- import { TOOL_ANNOTATIONS } from "../tool-annotations.js";
6
- export const symbolsToolSpecs = [
7
- {
8
- name: "wyrm_symbol_index",
9
- description: "Scan a project for function/class/type/interface definitions across TS/JS/Python/Rust/Go/PHP/Ruby and index them. Idempotent — clears the project's symbols before re-indexing.",
10
- inputSchema: {
11
- type: "object",
12
- properties: {
13
- projectPath: { type: "string", description: "Project root to scan" },
14
- },
15
- required: ["projectPath"],
16
- },
17
- annotations: TOOL_ANNOTATIONS["wyrm_symbol_index"],
18
- aliases: [],
19
- handler: async (args, ctx) => {
20
- const { db, symbols } = ctx;
21
- const { projectPath } = args;
22
- const project = db.getProject(projectPath);
23
- if (!project)
24
- return { content: [{ type: "text", text: "Project not found" }], isError: true };
25
- const result = symbols.indexProject(project.id, project.path);
26
- return { content: [{ type: "text", text: `󱅝 Indexed ${result.symbols} symbol(s) across ${result.files} file(s) in ${project.name}.` }] };
27
- },
28
- },
29
- {
30
- name: "wyrm_symbol_search",
31
- description: "Look up a symbol across all (or one) project. Cursor's workspace-symbols stops at the repo boundary; this doesn't.",
32
- inputSchema: {
33
- type: "object",
34
- properties: {
35
- symbol: { type: "string", description: "Name to search for" },
36
- projectPath: { type: "string", description: "Scope to one project (optional)" },
37
- kind: { type: "string", enum: ["function", "class", "type", "interface", "const", "export"], description: "Filter by kind" },
38
- language: { type: "string", description: "Filter by language: ts/tsx/js/jsx/py/rs/go/php/rb" },
39
- exact: { type: "boolean", description: "Exact match (default: substring)" },
40
- limit: { type: "number", description: "Max results (default 50)" },
41
- },
42
- required: ["symbol"],
43
- },
44
- annotations: TOOL_ANNOTATIONS["wyrm_symbol_search"],
45
- aliases: [],
46
- handler: async (args, ctx) => {
47
- const { db, symbols } = ctx;
48
- const { symbol, projectPath, kind, language, exact, limit } = args;
49
- const project = projectPath ? db.getProject(projectPath) : null;
50
- const rows = symbols.search(symbol, {
51
- projectId: project?.id,
52
- kind, language, exact,
53
- limit: Math.min(Math.max(1, limit ?? 50), 500),
54
- });
55
- if (rows.length === 0) {
56
- return { content: [{ type: "text", text: `󱅝 No symbols match "${symbol}".` }] };
57
- }
58
- const text = rows.map(r => `• [${r.language}] ${r.kind} ${r.symbol} — ${r.file_path}:${r.line}\n ${r.signature ?? ''}`).join('\n');
59
- return { content: [{ type: "text", text: `󱅝 ${rows.length} match(es):\n${text}` }] };
60
- },
61
- },
62
- {
63
- name: "wyrm_symbol_callers",
64
- description: "Best-effort caller graph — files in the project (or workspace) whose indexed signature mentions this symbol. Approximate; for a true caller graph use LSP.",
65
- inputSchema: {
66
- type: "object",
67
- properties: {
68
- symbol: { type: "string" },
69
- projectPath: { type: "string", description: "Scope to one project (optional)" },
70
- },
71
- required: ["symbol"],
72
- },
73
- annotations: TOOL_ANNOTATIONS["wyrm_symbol_callers"],
74
- aliases: [],
75
- handler: async (args, ctx) => {
76
- const { db, symbols } = ctx;
77
- const { symbol, projectPath } = args;
78
- const project = projectPath ? db.getProject(projectPath) : null;
79
- const rows = symbols.callers(symbol, project?.id);
80
- if (rows.length === 0) {
81
- return { content: [{ type: "text", text: `󱅝 No indexed callers for "${symbol}".` }] };
82
- }
83
- const text = rows.slice(0, 50).map(r => `• ${r.file_path}:${r.line} — ${r.signature?.slice(0, 100) ?? ''}`).join('\n');
84
- return { content: [{ type: "text", text: `󱅝 ${rows.length} caller candidate(s) (showing first 50):\n${text}` }] };
85
- },
86
- },
87
- {
88
- name: "wyrm_symbol_stats",
89
- description: "Symbol-index stats — total count, per-language and per-kind breakdown. Project-scoped if projectPath given, else workspace-wide.",
90
- inputSchema: {
91
- type: "object",
92
- properties: {
93
- projectPath: { type: "string", description: "Scope to one project (optional)" },
94
- },
95
- },
96
- annotations: TOOL_ANNOTATIONS["wyrm_symbol_stats"],
97
- aliases: [],
98
- handler: async (args, ctx) => {
99
- const { db, symbols } = ctx;
100
- const { projectPath } = args;
101
- const project = projectPath ? db.getProject(projectPath) : null;
102
- const s = symbols.stats(project?.id);
103
- const langs = Object.entries(s.by_language).map(([k, v]) => `${k}: ${v}`).join(', ');
104
- const kinds = Object.entries(s.by_kind).map(([k, v]) => `${k}: ${v}`).join(', ');
105
- return { content: [{ type: "text", text: `󱅝 Symbols indexed: ${s.total}\nLanguages: ${langs || '—'}\nKinds: ${kinds || '—'}` }] };
106
- },
107
- },
108
- ];
109
- //# sourceMappingURL=symbols.js.map
1
+ import{TOOL_ANNOTATIONS as m}from"../tool-annotations.js";const u=[{name:"wyrm_symbol_index",description:"Scan a project for function/class/type/interface definitions across TS/JS/Python/Rust/Go/PHP/Ruby and index them. Idempotent \u2014 clears the project's symbols before re-indexing.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project root to scan"}},required:["projectPath"]},annotations:m.wyrm_symbol_index,aliases:[],handler:async(s,r)=>{const{db:i,symbols:a}=r,{projectPath:e}=s,t=i.getProject(e);if(!t)return{content:[{type:"text",text:"Project not found"}],isError:!0};const o=a.indexProject(t.id,t.path);return{content:[{type:"text",text:`\u{F115D} Indexed ${o.symbols} symbol(s) across ${o.files} file(s) in ${t.name}.`}]}}},{name:"wyrm_symbol_search",description:"Look up a symbol across all (or one) project. Cursor's workspace-symbols stops at the repo boundary; this doesn't.",inputSchema:{type:"object",properties:{symbol:{type:"string",description:"Name to search for"},projectPath:{type:"string",description:"Scope to one project (optional)"},kind:{type:"string",enum:["function","class","type","interface","const","export"],description:"Filter by kind"},language:{type:"string",description:"Filter by language: ts/tsx/js/jsx/py/rs/go/php/rb"},exact:{type:"boolean",description:"Exact match (default: substring)"},limit:{type:"number",description:"Max results (default 50)"}},required:["symbol"]},annotations:m.wyrm_symbol_search,aliases:[],handler:async(s,r)=>{const{db:i,symbols:a}=r,{symbol:e,projectPath:t,kind:o,language:c,exact:l,limit:n}=s,y=t?i.getProject(t):null,d=a.search(e,{projectId:y?.id,kind:o,language:c,exact:l,limit:Math.min(Math.max(1,n??50),500)});if(d.length===0)return{content:[{type:"text",text:`\u{F115D} No symbols match "${e}".`}]};const b=d.map(p=>`\u2022 [${p.language}] ${p.kind} ${p.symbol} \u2014 ${p.file_path}:${p.line}
2
+ ${p.signature??""}`).join(`
3
+ `);return{content:[{type:"text",text:`\u{F115D} ${d.length} match(es):
4
+ ${b}`}]}}},{name:"wyrm_symbol_callers",description:"Best-effort caller graph \u2014 files in the project (or workspace) whose indexed signature mentions this symbol. Approximate; for a true caller graph use LSP.",inputSchema:{type:"object",properties:{symbol:{type:"string"},projectPath:{type:"string",description:"Scope to one project (optional)"}},required:["symbol"]},annotations:m.wyrm_symbol_callers,aliases:[],handler:async(s,r)=>{const{db:i,symbols:a}=r,{symbol:e,projectPath:t}=s,o=t?i.getProject(t):null,c=a.callers(e,o?.id);if(c.length===0)return{content:[{type:"text",text:`\u{F115D} No indexed callers for "${e}".`}]};const l=c.slice(0,50).map(n=>`\u2022 ${n.file_path}:${n.line} \u2014 ${n.signature?.slice(0,100)??""}`).join(`
5
+ `);return{content:[{type:"text",text:`\u{F115D} ${c.length} caller candidate(s) (showing first 50):
6
+ ${l}`}]}}},{name:"wyrm_symbol_stats",description:"Symbol-index stats \u2014 total count, per-language and per-kind breakdown. Project-scoped if projectPath given, else workspace-wide.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Scope to one project (optional)"}}},annotations:m.wyrm_symbol_stats,aliases:[],handler:async(s,r)=>{const{db:i,symbols:a}=r,{projectPath:e}=s,t=e?i.getProject(e):null,o=a.stats(t?.id),c=Object.entries(o.by_language).map(([n,y])=>`${n}: ${y}`).join(", "),l=Object.entries(o.by_kind).map(([n,y])=>`${n}: ${y}`).join(", ");return{content:[{type:"text",text:`\u{F115D} Symbols indexed: ${o.total}
7
+ Languages: ${c||"\u2014"}
8
+ Kinds: ${l||"\u2014"}`}]}}}];export{u as symbolsToolSpecs};
@@ -1,310 +1,12 @@
1
- /**
2
- * @copyright 2026 Ghost Protocol (Pvt) Ltd.
3
- * @license AGPL-3.0-or-later
4
- */
5
- import { TOOL_ANNOTATIONS } from "../tool-annotations.js";
6
- import { logger } from "../logger.js";
7
- import { asInt } from "../validate.js";
8
- import { createCipheriv, createDecipheriv, pbkdf2Sync, randomBytes } from "crypto";
9
- import { chmodSync, copyFileSync, existsSync as fsExistsSync, readFileSync, unlinkSync, writeFileSync } from "fs";
10
- import { homedir } from "os";
11
- import { join as pathJoin } from "path";
12
- export const syncopsToolSpecs = [
13
- {
14
- name: "wyrm_prune",
15
- description: "Prune low-confidence, stale memory artifacts. Dry-run by default — use dry_run:false with confirm_ids to actually delete. Never deletes artifacts in the review queue.",
16
- inputSchema: {
17
- type: "object",
18
- properties: {
19
- project_id: { type: "number", description: "Project ID (optional, all projects if omitted)" },
20
- min_confidence: { type: "number", description: "Prune artifacts below this confidence (default: 0.3)" },
21
- older_than_days: { type: "number", description: "Last accessed more than N days ago (default: 90)" },
22
- types: { type: "array", items: { type: "string" }, description: "Filter by artifact_type (optional)" },
23
- dry_run: { type: "boolean", description: "If true (default), return candidates without deleting. Set false to delete." },
24
- confirm_ids: { type: "array", items: { type: "number" }, description: "Required when dry_run:false — only IDs in this list will be deleted" },
25
- },
26
- },
27
- annotations: TOOL_ANNOTATIONS["wyrm_prune"],
28
- aliases: [],
29
- handler: async (args, ctx) => {
30
- const { db } = ctx;
31
- const a = args || {};
32
- const { project_id: pruneProjectId, min_confidence: pruneMinConf = 0.3, older_than_days: pruneOlderDays = 90, types: pruneTypes, dry_run: pruneDryRun = true, confirm_ids: pruneConfirmIds, } = args;
33
- const rawDbPrune = db.getDatabase();
34
- // SECURITY: project_id must never be string-interpolated into SQL —
35
- // validate at the boundary (throws ValidationError → clean isError), then bind.
36
- const safePruneProjectId = asInt('project_id', pruneProjectId);
37
- const projectClausePrune = safePruneProjectId !== undefined ? 'AND project_id = ?' : '';
38
- const projectParamsPrune = safePruneProjectId !== undefined ? [safePruneProjectId] : [];
39
- const typeClausePrune = pruneTypes && pruneTypes.length > 0
40
- ? `AND kind IN (${pruneTypes.map(() => '?').join(',')})`
41
- : '';
42
- const typeParamsPrune = pruneTypes ?? [];
43
- const candidateRows = rawDbPrune.prepare(`
1
+ import{TOOL_ANNOTATIONS as E}from"../tool-annotations.js";import{logger as R}from"../logger.js";import{asInt as B}from"../validate.js";import{createCipheriv as L,createDecipheriv as F,pbkdf2Sync as O,randomBytes as C}from"crypto";import{chmodSync as U,copyFileSync as j,existsSync as x,readFileSync as M,unlinkSync as c,writeFileSync as I}from"fs";import{homedir as k}from"os";import{join as D}from"path";const K=[{name:"wyrm_prune",description:"Prune low-confidence, stale memory artifacts. Dry-run by default \u2014 use dry_run:false with confirm_ids to actually delete. Never deletes artifacts in the review queue.",inputSchema:{type:"object",properties:{project_id:{type:"number",description:"Project ID (optional, all projects if omitted)"},min_confidence:{type:"number",description:"Prune artifacts below this confidence (default: 0.3)"},older_than_days:{type:"number",description:"Last accessed more than N days ago (default: 90)"},types:{type:"array",items:{type:"string"},description:"Filter by artifact_type (optional)"},dry_run:{type:"boolean",description:"If true (default), return candidates without deleting. Set false to delete."},confirm_ids:{type:"array",items:{type:"number"},description:"Required when dry_run:false \u2014 only IDs in this list will be deleted"}}},annotations:E.wyrm_prune,aliases:[],handler:async(f,b)=>{const{db:i}=b,N=f||{},{project_id:s,min_confidence:g=.3,older_than_days:p=90,types:d,dry_run:e=!0,confirm_ids:t}=f,y=i.getDatabase(),o=B("project_id",s),h=o!==void 0?"AND project_id = ?":"",_=o!==void 0?[o]:[],w=d&&d.length>0?`AND kind IN (${d.map(()=>"?").join(",")})`:"",u=d??[],m=y.prepare(`
44
2
  SELECT id, project_id, kind, problem, confidence, last_accessed_at, access_count
45
3
  FROM memory_artifacts
46
4
  WHERE confidence < ?
47
5
  AND (last_accessed_at IS NULL OR last_accessed_at < datetime('now', ?))
48
6
  AND needs_review = 0
49
7
  AND supersedes_id IS NULL
50
- ${projectClausePrune}
51
- ${typeClausePrune}
8
+ ${h}
9
+ ${w}
52
10
  ORDER BY confidence ASC, last_accessed_at ASC
53
11
  LIMIT 500
54
- `).all(pruneMinConf, `-${pruneOlderDays} days`, ...projectParamsPrune, ...typeParamsPrune);
55
- if (pruneDryRun) {
56
- return {
57
- content: [{
58
- type: "text",
59
- text: JSON.stringify({
60
- dry_run: true,
61
- count: candidateRows.length,
62
- candidates: candidateRows.map(r => ({
63
- id: r.id,
64
- kind: r.kind,
65
- problem: r.problem.slice(0, 100),
66
- confidence: r.confidence,
67
- last_accessed_at: r.last_accessed_at,
68
- })),
69
- }),
70
- }],
71
- };
72
- }
73
- // dry_run: false — require confirm_ids
74
- if (!pruneConfirmIds || pruneConfirmIds.length === 0) {
75
- return {
76
- content: [{ type: "text", text: `󱅝 **Prune**: dry_run:false requires confirm_ids — provide the IDs from a dry-run to confirm deletion.` }],
77
- isError: true,
78
- };
79
- }
80
- const candidateIds = new Set(candidateRows.map(r => r.id));
81
- const toDelete = pruneConfirmIds.filter(id => candidateIds.has(id));
82
- if (toDelete.length === 0) {
83
- return {
84
- content: [{ type: "text", text: `󱅝 **Prune**: No matching IDs to delete (confirm_ids not in candidate set).` }],
85
- };
86
- }
87
- const placeholdersPrune = toDelete.map(() => '?').join(',');
88
- const deleteResult = rawDbPrune.prepare(`DELETE FROM memory_artifacts WHERE id IN (${placeholdersPrune}) AND needs_review = 0`).run(...toDelete);
89
- return {
90
- content: [{
91
- type: "text",
92
- text: JSON.stringify({ deleted: deleteResult.changes, ids: toDelete }),
93
- }],
94
- };
95
- },
96
- },
97
- {
98
- name: "wyrm_sync_export",
99
- description: "Export an AES-256-GCM encrypted snapshot of the Wyrm database for cross-device sync. Passphrase from WYRM_SYNC_PASSPHRASE env var.",
100
- inputSchema: {
101
- type: "object",
102
- properties: {
103
- output_path: { type: "string", description: "File path to write the encrypted snapshot" },
104
- description: { type: "string", description: "Optional description of this export" },
105
- },
106
- required: ["output_path"],
107
- },
108
- annotations: TOOL_ANNOTATIONS["wyrm_sync_export"],
109
- aliases: [],
110
- handler: async (args, ctx) => {
111
- const { db, server } = ctx;
112
- const { output_path: exportPath, description: exportDesc } = args;
113
- const passphrase = process.env.WYRM_SYNC_PASSPHRASE;
114
- if (!passphrase) {
115
- return {
116
- content: [{ type: "text", text: `󱅝 **Sync Export**: Set WYRM_SYNC_PASSPHRASE env var to enable encrypted export.` }],
117
- isError: true,
118
- };
119
- }
120
- const dbPath = db.getDatabasePath();
121
- const wyrmDir = pathJoin(homedir(), '.wyrm');
122
- const tempDbPath = pathJoin(wyrmDir, 'wyrm_sync_export_temp.db');
123
- try {
124
- // WAL-safe snapshot via VACUUM INTO
125
- const rawDbExport = db.getDatabase();
126
- if (fsExistsSync(tempDbPath))
127
- unlinkSync(tempDbPath);
128
- rawDbExport.prepare(`VACUUM INTO ?`).run(tempDbPath);
129
- // Read snapshot
130
- const plaintext = readFileSync(tempDbPath);
131
- // Derive key
132
- const salt = randomBytes(32);
133
- const iv = randomBytes(16);
134
- const key = pbkdf2Sync(passphrase, salt, 600000, 32, 'sha256');
135
- // Encrypt
136
- const cipher = createCipheriv('aes-256-gcm', key, iv);
137
- const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
138
- const authTag = cipher.getAuthTag();
139
- // Binary format: WYRM(4) + version(1) + salt(32) + iv(16) + authTag(16) + data
140
- const magic = Buffer.from('WYRM');
141
- const version = Buffer.alloc(1);
142
- version.writeUInt8(1, 0);
143
- const output = Buffer.concat([magic, version, salt, iv, authTag, encrypted]);
144
- writeFileSync(exportPath, output);
145
- try {
146
- chmodSync(exportPath, 0o600);
147
- }
148
- catch { /* non-fatal */ }
149
- // Clean up temp
150
- try {
151
- unlinkSync(tempDbPath);
152
- }
153
- catch { /* non-fatal */ }
154
- const sizeMb = (output.length / (1024 * 1024)).toFixed(2);
155
- return {
156
- content: [{
157
- type: "text",
158
- text: JSON.stringify({
159
- success: true,
160
- path: exportPath,
161
- size_mb: parseFloat(sizeMb),
162
- exported_at: new Date().toISOString(),
163
- description: exportDesc ?? null,
164
- }),
165
- }],
166
- };
167
- }
168
- catch (err) {
169
- try {
170
- if (fsExistsSync(tempDbPath))
171
- unlinkSync(tempDbPath);
172
- }
173
- catch { /* clean up */ }
174
- logger.error('Sync export failed', { error: err.message });
175
- return {
176
- content: [{ type: "text", text: `󱅝 **Sync Export** failed. Check server logs for details.` }],
177
- isError: true,
178
- };
179
- }
180
- },
181
- },
182
- {
183
- name: "wyrm_sync_import",
184
- description: "Import an encrypted Wyrm snapshot. Use restore_mode:'preview' to inspect without touching the DB; 'restore' to replace current DB (backs up first).",
185
- inputSchema: {
186
- type: "object",
187
- properties: {
188
- input_path: { type: "string", description: "Path to the encrypted snapshot file" },
189
- restore_mode: { type: "string", enum: ["preview", "restore"], description: "preview = show stats only; restore = replace DB (backs up first)" },
190
- },
191
- required: ["input_path", "restore_mode"],
192
- },
193
- annotations: TOOL_ANNOTATIONS["wyrm_sync_import"],
194
- aliases: [],
195
- handler: async (args, ctx) => {
196
- const { db, server } = ctx;
197
- const { input_path: importPath, restore_mode: restoreMode } = args;
198
- const passphrase = process.env.WYRM_SYNC_PASSPHRASE;
199
- if (!passphrase) {
200
- return {
201
- content: [{ type: "text", text: `󱅝 **Sync Import**: Set WYRM_SYNC_PASSPHRASE env var to enable encrypted import.` }],
202
- isError: true,
203
- };
204
- }
205
- const wyrmDirImport = pathJoin(homedir(), '.wyrm');
206
- const tempImportPath = pathJoin(wyrmDirImport, 'wyrm_sync_import_temp.db');
207
- try {
208
- const fileData = readFileSync(importPath);
209
- // Validate magic bytes
210
- const magic = fileData.subarray(0, 4).toString('ascii');
211
- if (magic !== 'WYRM') {
212
- return { content: [{ type: "text", text: `󱅝 Invalid Wyrm snapshot file (bad magic bytes).` }], isError: true };
213
- }
214
- const version = fileData.readUInt8(4);
215
- if (version !== 1) {
216
- return { content: [{ type: "text", text: `󱅝 Unsupported snapshot version: ${version}` }], isError: true };
217
- }
218
- const salt = fileData.subarray(5, 37);
219
- const iv = fileData.subarray(37, 53);
220
- const authTag = fileData.subarray(53, 69);
221
- const encrypted = fileData.subarray(69);
222
- // Derive key & decrypt
223
- const key = pbkdf2Sync(passphrase, salt, 600000, 32, 'sha256');
224
- const decipher = createDecipheriv('aes-256-gcm', key, iv);
225
- decipher.setAuthTag(authTag);
226
- let decrypted;
227
- try {
228
- decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
229
- }
230
- catch {
231
- return { content: [{ type: "text", text: `󱅝 Decryption failed — wrong passphrase or corrupted snapshot.` }], isError: true };
232
- }
233
- if (restoreMode === 'preview') {
234
- // Open temp DB and return stats
235
- if (fsExistsSync(tempImportPath))
236
- unlinkSync(tempImportPath);
237
- writeFileSync(tempImportPath, decrypted);
238
- let previewStats = {};
239
- try {
240
- const BetterSQLite = (await import('better-sqlite3')).default;
241
- const previewDb = new BetterSQLite(tempImportPath, { readonly: true });
242
- const tables = ['projects', 'sessions', 'ground_truths', 'memory_artifacts', 'quests'];
243
- for (const t of tables) {
244
- try {
245
- const row = previewDb.prepare(`SELECT COUNT(*) as n FROM ${t}`).get();
246
- previewStats[t] = row.n;
247
- }
248
- catch {
249
- previewStats[t] = 0;
250
- }
251
- }
252
- previewDb.close();
253
- }
254
- catch { /* stats optional */ }
255
- try {
256
- unlinkSync(tempImportPath);
257
- }
258
- catch { /* clean up */ }
259
- return {
260
- content: [{
261
- type: "text",
262
- text: JSON.stringify({ preview: true, stats: previewStats }),
263
- }],
264
- };
265
- }
266
- // restore mode
267
- const dbPathImport = db.getDatabasePath();
268
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
269
- const backupPath = `${dbPathImport}.backup.${timestamp}`;
270
- // Backup current DB
271
- copyFileSync(dbPathImport, backupPath);
272
- // Write decrypted to temp
273
- if (fsExistsSync(tempImportPath))
274
- unlinkSync(tempImportPath);
275
- writeFileSync(tempImportPath, decrypted);
276
- // Close current DB connection, replace file, reopen
277
- const rawDbImport = db.getDatabase();
278
- rawDbImport.close();
279
- copyFileSync(tempImportPath, dbPathImport);
280
- try {
281
- unlinkSync(tempImportPath);
282
- }
283
- catch { /* clean up */ }
284
- return {
285
- content: [{
286
- type: "text",
287
- text: JSON.stringify({
288
- success: true,
289
- restored_from: importPath,
290
- backup_at: backupPath,
291
- }),
292
- }],
293
- };
294
- }
295
- catch (err) {
296
- try {
297
- if (fsExistsSync(tempImportPath))
298
- unlinkSync(tempImportPath);
299
- }
300
- catch { /* clean up */ }
301
- logger.error('Sync import failed', { error: err.message });
302
- return {
303
- content: [{ type: "text", text: `󱅝 **Sync Import** failed. Check server logs for details.` }],
304
- isError: true,
305
- };
306
- }
307
- },
308
- },
309
- ];
310
- //# sourceMappingURL=syncops.js.map
12
+ `).all(g,`-${p} days`,..._,...u);if(e)return{content:[{type:"text",text:JSON.stringify({dry_run:!0,count:m.length,candidates:m.map(r=>({id:r.id,kind:r.kind,problem:r.problem.slice(0,100),confidence:r.confidence,last_accessed_at:r.last_accessed_at}))})}]};if(!t||t.length===0)return{content:[{type:"text",text:"\u{F115D} **Prune**: dry_run:false requires confirm_ids \u2014 provide the IDs from a dry-run to confirm deletion."}],isError:!0};const l=new Set(m.map(r=>r.id)),n=t.filter(r=>l.has(r));if(n.length===0)return{content:[{type:"text",text:"\u{F115D} **Prune**: No matching IDs to delete (confirm_ids not in candidate set)."}]};const a=n.map(()=>"?").join(","),S=y.prepare(`DELETE FROM memory_artifacts WHERE id IN (${a}) AND needs_review = 0`).run(...n);return{content:[{type:"text",text:JSON.stringify({deleted:S.changes,ids:n})}]}}},{name:"wyrm_sync_export",description:"Export an AES-256-GCM encrypted snapshot of the Wyrm database for cross-device sync. Passphrase from WYRM_SYNC_PASSPHRASE env var.",inputSchema:{type:"object",properties:{output_path:{type:"string",description:"File path to write the encrypted snapshot"},description:{type:"string",description:"Optional description of this export"}},required:["output_path"]},annotations:E.wyrm_sync_export,aliases:[],handler:async(f,b)=>{const{db:i,server:N}=b,{output_path:s,description:g}=f,p=process.env.WYRM_SYNC_PASSPHRASE;if(!p)return{content:[{type:"text",text:"\u{F115D} **Sync Export**: Set WYRM_SYNC_PASSPHRASE env var to enable encrypted export."}],isError:!0};const d=i.getDatabasePath(),e=D(k(),".wyrm"),t=D(e,"wyrm_sync_export_temp.db");try{const y=i.getDatabase();x(t)&&c(t),y.prepare("VACUUM INTO ?").run(t);const o=M(t),h=C(32),_=C(16),w=O(p,h,6e5,32,"sha256"),u=L("aes-256-gcm",w,_),m=Buffer.concat([u.update(o),u.final()]),l=u.getAuthTag(),n=Buffer.from("WYRM"),a=Buffer.alloc(1);a.writeUInt8(1,0);const S=Buffer.concat([n,a,h,_,l,m]);I(s,S);try{U(s,384)}catch{}try{c(t)}catch{}const r=(S.length/(1024*1024)).toFixed(2);return{content:[{type:"text",text:JSON.stringify({success:!0,path:s,size_mb:parseFloat(r),exported_at:new Date().toISOString(),description:g??null})}]}}catch(y){try{x(t)&&c(t)}catch{}return R.error("Sync export failed",{error:y.message}),{content:[{type:"text",text:"\u{F115D} **Sync Export** failed. Check server logs for details."}],isError:!0}}}},{name:"wyrm_sync_import",description:"Import an encrypted Wyrm snapshot. Use restore_mode:'preview' to inspect without touching the DB; 'restore' to replace current DB (backs up first).",inputSchema:{type:"object",properties:{input_path:{type:"string",description:"Path to the encrypted snapshot file"},restore_mode:{type:"string",enum:["preview","restore"],description:"preview = show stats only; restore = replace DB (backs up first)"}},required:["input_path","restore_mode"]},annotations:E.wyrm_sync_import,aliases:[],handler:async(f,b)=>{const{db:i,server:N}=b,{input_path:s,restore_mode:g}=f,p=process.env.WYRM_SYNC_PASSPHRASE;if(!p)return{content:[{type:"text",text:"\u{F115D} **Sync Import**: Set WYRM_SYNC_PASSPHRASE env var to enable encrypted import."}],isError:!0};const d=D(k(),".wyrm"),e=D(d,"wyrm_sync_import_temp.db");try{const t=M(s);if(t.subarray(0,4).toString("ascii")!=="WYRM")return{content:[{type:"text",text:"\u{F115D} Invalid Wyrm snapshot file (bad magic bytes)."}],isError:!0};const o=t.readUInt8(4);if(o!==1)return{content:[{type:"text",text:`\u{F115D} Unsupported snapshot version: ${o}`}],isError:!0};const h=t.subarray(5,37),_=t.subarray(37,53),w=t.subarray(53,69),u=t.subarray(69),m=O(p,h,6e5,32,"sha256"),l=F("aes-256-gcm",m,_);l.setAuthTag(w);let n;try{n=Buffer.concat([l.update(u),l.final()])}catch{return{content:[{type:"text",text:"\u{F115D} Decryption failed \u2014 wrong passphrase or corrupted snapshot."}],isError:!0}}if(g==="preview"){x(e)&&c(e),I(e,n);let P={};try{const T=(await import("better-sqlite3")).default,A=new T(e,{readonly:!0}),Y=["projects","sessions","ground_truths","memory_artifacts","quests"];for(const v of Y)try{const W=A.prepare(`SELECT COUNT(*) as n FROM ${v}`).get();P[v]=W.n}catch{P[v]=0}A.close()}catch{}try{c(e)}catch{}return{content:[{type:"text",text:JSON.stringify({preview:!0,stats:P})}]}}const a=i.getDatabasePath(),S=new Date().toISOString().replace(/[:.]/g,"-"),r=`${a}.backup.${S}`;j(a,r),x(e)&&c(e),I(e,n),i.getDatabase().close(),j(e,a);try{c(e)}catch{}return{content:[{type:"text",text:JSON.stringify({success:!0,restored_from:s,backup_at:r})}]}}catch(t){try{x(e)&&c(e)}catch{}return R.error("Sync import failed",{error:t.message}),{content:[{type:"text",text:"\u{F115D} **Sync Import** failed. Check server logs for details."}],isError:!0}}}}];export{K as syncopsToolSpecs};
@@ -1,27 +1 @@
1
- /**
2
- * Handler-module contract (v7 A2 — decompose the index.ts monolith;
3
- * v7 F3 T018 — ToolSpec contract v2).
4
- *
5
- * Tool handlers are being lifted out of the one giant `switch (name)` in
6
- * index.ts into per-domain modules behind a registry. Each handler is a pure
7
- * function of (args, ctx) — no module-level closure — which makes it unit-
8
- * testable and lets the boundary validator (v7 A1) wrap it uniformly.
9
- *
10
- * v7 F3 (T018) upgrades the contract from bare handlers to full ToolSpec
11
- * modules: a domain exports `ToolSpec[]` carrying the COMPLETE tool identity
12
- * ({name, description, inputSchema, outputSchema, annotations, examples,
13
- * aliases, handler}), and the registry derives BOTH the dispatch map and the
14
- * advertised ListTools entries from the same objects — definition and
15
- * behavior cannot drift because they are one value.
16
- *
17
- * @copyright 2026 Ghost Protocol (Pvt) Ltd.
18
- * @license AGPL-3.0-or-later
19
- */
20
- /** Safely read the text of a content block — returns '' for a resource_link
21
- * (which carries no text). v7 F4 (T034): lets the few call sites that read
22
- * `content[0].text` survive the union widening without a cast (the text block
23
- * is always content[0] by renderResult construction, but TS can't prove it). */
24
- export function blockText(block) {
25
- return block && 'text' in block ? block.text : '';
26
- }
27
- //# sourceMappingURL=types.js.map
1
+ function e(t){return t&&"text"in t?t.text:""}export{e as blockText};