wyrm-mcp 7.2.1 → 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 (150) hide show
  1. package/LICENSE +26 -667
  2. package/NOTICE +14 -33
  3. package/dist/activation.js +1 -60
  4. package/dist/agent-daemon.js +4 -281
  5. package/dist/agent-loop.js +7 -332
  6. package/dist/analytics.js +13 -236
  7. package/dist/attribution.js +1 -49
  8. package/dist/audit.js +2 -457
  9. package/dist/auto-capture.js +3 -138
  10. package/dist/auto-orchestrator.js +1 -325
  11. package/dist/autoconfig.js +39 -840
  12. package/dist/buddy-runner.js +1 -109
  13. package/dist/buddy.js +14 -564
  14. package/dist/build-flags.js +1 -17
  15. package/dist/capabilities.js +3 -183
  16. package/dist/capture.js +1 -56
  17. package/dist/causality.js +6 -107
  18. package/dist/cli.js +20 -281
  19. package/dist/cloud/cli.js +5 -541
  20. package/dist/cloud/client.js +1 -221
  21. package/dist/cloud/crypto.js +1 -85
  22. package/dist/cloud/machine-id.js +2 -113
  23. package/dist/cloud/recovery.js +1 -60
  24. package/dist/cloud/sync-engine.js +7 -543
  25. package/dist/cloud-backup.js +5 -579
  26. package/dist/cloud-profile.js +1 -138
  27. package/dist/cloud-sync-entrypoint.js +1 -47
  28. package/dist/cloud-sync.js +2 -309
  29. package/dist/constellation.js +12 -168
  30. package/dist/context-build-budgeted.js +4 -144
  31. package/dist/context-ranking.js +1 -69
  32. package/dist/crypto.js +1 -179
  33. package/dist/daemon-write-endpoint.js +1 -290
  34. package/dist/daemon-writer.js +2 -406
  35. package/dist/database.js +43 -1110
  36. package/dist/deprecations.js +2 -162
  37. package/dist/design.js +13 -141
  38. package/dist/event-replication.js +1 -112
  39. package/dist/events-sse.js +7 -43
  40. package/dist/events.js +6 -238
  41. package/dist/failure-patterns.js +42 -659
  42. package/dist/federation.js +12 -236
  43. package/dist/goals.js +13 -101
  44. package/dist/golden.js +3 -355
  45. package/dist/handlers/agent.js +4 -165
  46. package/dist/handlers/alias-adapters.js +1 -129
  47. package/dist/handlers/aliases.js +1 -171
  48. package/dist/handlers/audit.js +1 -87
  49. package/dist/handlers/boundary.js +1 -221
  50. package/dist/handlers/capture.js +73 -1109
  51. package/dist/handlers/causality.js +7 -114
  52. package/dist/handlers/cloud.js +85 -382
  53. package/dist/handlers/companion.js +28 -459
  54. package/dist/handlers/datalake.js +7 -187
  55. package/dist/handlers/dispatch-context.js +0 -22
  56. package/dist/handlers/entity.js +25 -256
  57. package/dist/handlers/events.js +16 -335
  58. package/dist/handlers/failure.js +13 -340
  59. package/dist/handlers/goals.js +4 -296
  60. package/dist/handlers/intelligence.js +126 -674
  61. package/dist/handlers/invoicing.js +1 -70
  62. package/dist/handlers/mcpclient.js +6 -137
  63. package/dist/handlers/orchestration.js +40 -125
  64. package/dist/handlers/output-schemas.js +1 -24
  65. package/dist/handlers/presence.js +3 -99
  66. package/dist/handlers/project.js +28 -182
  67. package/dist/handlers/prompts.js +6 -157
  68. package/dist/handlers/quest.js +4 -224
  69. package/dist/handlers/recall.js +11 -218
  70. package/dist/handlers/registry.js +1 -167
  71. package/dist/handlers/resources.js +1 -288
  72. package/dist/handlers/review.js +11 -74
  73. package/dist/handlers/run.js +17 -487
  74. package/dist/handlers/search.js +15 -326
  75. package/dist/handlers/session.js +28 -615
  76. package/dist/handlers/share.js +8 -184
  77. package/dist/handlers/shims.js +1 -464
  78. package/dist/handlers/skill.js +67 -449
  79. package/dist/handlers/survivors.js +1 -120
  80. package/dist/handlers/symbols.js +8 -109
  81. package/dist/handlers/syncops.js +4 -302
  82. package/dist/handlers/types.js +1 -27
  83. package/dist/harvest.js +5 -191
  84. package/dist/hours.js +7 -156
  85. package/dist/http-auth.js +3 -321
  86. package/dist/http-fast.js +21 -1137
  87. package/dist/icons.js +1 -47
  88. package/dist/index.js +2 -924
  89. package/dist/indexer.js +4 -145
  90. package/dist/intelligence.js +31 -261
  91. package/dist/internal-dispatch.js +3 -212
  92. package/dist/keyset.js +1 -110
  93. package/dist/knowledge-graph.js +12 -176
  94. package/dist/license.js +2 -441
  95. package/dist/logger.js +2 -199
  96. package/dist/maintenance.js +2 -148
  97. package/dist/mcp-client.js +6 -262
  98. package/dist/memory-artifacts.js +30 -449
  99. package/dist/migrate-prompt.js +2 -124
  100. package/dist/migrations.js +40 -655
  101. package/dist/performance.js +1 -228
  102. package/dist/presence.js +11 -140
  103. package/dist/priority-embed.js +5 -164
  104. package/dist/providers/embedding-provider.js +1 -196
  105. package/dist/readonly-gate.js +1 -29
  106. package/dist/rehydration.js +9 -157
  107. package/dist/reindex.js +1 -88
  108. package/dist/render-target.js +21 -514
  109. package/dist/render.js +4 -280
  110. package/dist/repl-guard.js +1 -173
  111. package/dist/replication-daemon-entrypoint.js +1 -31
  112. package/dist/replication-daemon.js +2 -262
  113. package/dist/resilience.js +1 -591
  114. package/dist/reverse-bridge.js +5 -360
  115. package/dist/security.js +1 -244
  116. package/dist/session-seen.js +3 -51
  117. package/dist/setup.js +1 -260
  118. package/dist/skill-author.js +5 -168
  119. package/dist/spec-kit.js +1 -191
  120. package/dist/sqlite-busy.js +1 -154
  121. package/dist/statusline.js +11 -315
  122. package/dist/sub-agent.js +13 -262
  123. package/dist/summarizer.js +13 -139
  124. package/dist/symbols.js +7 -283
  125. package/dist/sync.js +5 -359
  126. package/dist/tasks-dispatch.js +1 -84
  127. package/dist/tasks.js +1 -282
  128. package/dist/token-budget.js +1 -143
  129. package/dist/tool-analytics.js +7 -129
  130. package/dist/tool-annotations.js +1 -365
  131. package/dist/tool-manifest-v2.json +1 -1
  132. package/dist/tool-manifest.json +1 -1
  133. package/dist/tool-profiles.js +1 -75
  134. package/dist/trace-harvest.js +6 -244
  135. package/dist/types.js +1 -30
  136. package/dist/ui-dashboard.js +41 -50
  137. package/dist/ulid.js +1 -81
  138. package/dist/validate.js +1 -129
  139. package/dist/vault.js +1 -534
  140. package/dist/vectors.js +3 -184
  141. package/dist/version-check.js +4 -136
  142. package/dist/visibility.js +19 -155
  143. package/dist/wyrm-cli.js +98 -2464
  144. package/dist/wyrm-guard.js +14 -424
  145. package/dist/wyrm-loop.js +3 -150
  146. package/dist/wyrm-manifest.json +1 -1
  147. package/dist/wyrm-statusline-daemon.js +1 -11
  148. package/dist/wyrm-statusline.js +4 -56
  149. package/dist/wyrm-ui.js +9 -77
  150. 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};