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,193 +1,37 @@
1
- /**
2
- * wyrm_constellation — cross-project memory query (spec 018 phase 3).
3
- *
4
- * Asks one FTS5 question across *every* registered Wyrm project — not just
5
- * the current one. The killer founder-grade query: "have I solved this
6
- * before in any project?" Returns candidates grouped by project; the
7
- * calling AI does semantic ranking on its side (per spec 018 decision #2 —
8
- * no embedding dependency).
9
- *
10
- * Privacy: respects the `cross_project_visibility` flag (added by
11
- * migration 14, default `'within'`). Only artifacts explicitly flagged
12
- * `'org'` or `'public'` leak across the project boundary.
13
- *
14
- * @copyright 2026 Ghost Protocol (Pvt) Ltd.
15
- * @license AGPL-3.0-or-later — dual-licensed; commercial terms: ghosts.lk@proton.me. See LICENSE.
16
- */
17
- const SAFE_KINDS = new Set(['truth', 'artifact', 'quest', 'decision', 'reference']);
18
- const SAFE_VIS = new Set(['within', 'org', 'public']);
19
- function sanitizeFtsQuery(q) {
20
- // FTS5 syntax allows `"`, `*`, `AND`, `OR`, `NOT`. We strip anything that
21
- // could close out into raw SQL — only allow word chars, spaces, quotes,
22
- // asterisk, +/-. This is defense-in-depth; we already use parameterized
23
- // bind, but the query also drives string-concat in some paths.
24
- return q.replace(/[^\w\s"\*\-\+]/g, ' ').trim().slice(0, 200);
25
- }
26
- /**
27
- * Query a single project's FTS5 indexes for matching artifacts.
28
- * Pre-filtered to honour visibility; returns up to `limit` hits.
29
- */
30
- function queryProject(db, project, query, kinds, visibility, limit) {
31
- const hits = [];
32
- const visList = `(${[...visibility].map((v) => `'${v}'`).join(',')})`;
33
- const safeQ = sanitizeFtsQuery(query);
34
- if (!safeQ)
35
- return [];
36
- // truths
37
- if (kinds.has('truth')) {
38
- try {
39
- const rows = db.prepare(`
1
+ const p=new Set(["truth","artifact","quest","decision","reference"]),u=new Set(["within","org","public"]);function f(s){return s.replace(/[^\w\s"\*\-\+]/g," ").trim().slice(0,200)}function _(s,i,l,c,n,a){const r=[],d=`(${[...n].map(o=>`'${o}'`).join(",")})`,e=f(l);if(!e)return[];if(c.has("truth"))try{const o=s.prepare(`
40
2
  SELECT id, key, value, cross_project_visibility AS visibility, created_at
41
3
  FROM ground_truths
42
4
  WHERE project_id = ?
43
- AND cross_project_visibility IN ${visList}
5
+ AND cross_project_visibility IN ${d}
44
6
  AND (key LIKE '%' || ? || '%' OR value LIKE '%' || ? || '%')
45
7
  ORDER BY created_at DESC
46
8
  LIMIT ?
47
- `).all(project.id, safeQ, safeQ, limit);
48
- for (const r of rows) {
49
- hits.push({
50
- projectId: project.id,
51
- projectName: project.name,
52
- kind: 'truth',
53
- id: r.id,
54
- title: r.key,
55
- snippet: r.value.slice(0, 200),
56
- visibility: r.visibility,
57
- capturedAt: r.created_at,
58
- });
59
- }
60
- }
61
- catch { /* table or column may not exist on older schema */ }
62
- }
63
- // memory artifacts
64
- if (kinds.has('artifact')) {
65
- try {
66
- const rows = db.prepare(`
9
+ `).all(i.id,e,e,a);for(const t of o)r.push({projectId:i.id,projectName:i.name,kind:"truth",id:t.id,title:t.key,snippet:t.value.slice(0,200),visibility:t.visibility,capturedAt:t.created_at})}catch{}if(c.has("artifact"))try{const o=s.prepare(`
67
10
  SELECT ma.id, ma.problem AS title, ma.validated_fix AS snippet,
68
11
  ma.cross_project_visibility AS visibility, ma.created_at
69
12
  FROM memory_artifacts ma
70
13
  WHERE ma.project_id = ?
71
- AND ma.cross_project_visibility IN ${visList}
14
+ AND ma.cross_project_visibility IN ${d}
72
15
  AND (ma.problem LIKE '%' || ? || '%' OR ma.validated_fix LIKE '%' || ? || '%')
73
16
  ORDER BY ma.created_at DESC
74
17
  LIMIT ?
75
- `).all(project.id, safeQ, safeQ, limit);
76
- for (const r of rows) {
77
- hits.push({
78
- projectId: project.id,
79
- projectName: project.name,
80
- kind: 'artifact',
81
- id: r.id,
82
- title: r.title.slice(0, 100),
83
- snippet: (r.snippet || '').slice(0, 200),
84
- visibility: r.visibility,
85
- capturedAt: r.created_at,
86
- });
87
- }
88
- }
89
- catch { /* tolerant */ }
90
- }
91
- // quests
92
- if (kinds.has('quest')) {
93
- try {
94
- const rows = db.prepare(`
18
+ `).all(i.id,e,e,a);for(const t of o)r.push({projectId:i.id,projectName:i.name,kind:"artifact",id:t.id,title:t.title.slice(0,100),snippet:(t.snippet||"").slice(0,200),visibility:t.visibility,capturedAt:t.created_at})}catch{}if(c.has("quest"))try{const o=s.prepare(`
95
19
  SELECT id, title, description, cross_project_visibility AS visibility, created_at
96
20
  FROM quests
97
21
  WHERE project_id = ?
98
- AND cross_project_visibility IN ${visList}
22
+ AND cross_project_visibility IN ${d}
99
23
  AND (title LIKE '%' || ? || '%' OR description LIKE '%' || ? || '%')
100
24
  ORDER BY created_at DESC
101
25
  LIMIT ?
102
- `).all(project.id, safeQ, safeQ, limit);
103
- for (const r of rows) {
104
- hits.push({
105
- projectId: project.id,
106
- projectName: project.name,
107
- kind: 'quest',
108
- id: r.id,
109
- title: r.title,
110
- snippet: (r.description || '').slice(0, 200),
111
- visibility: r.visibility,
112
- capturedAt: r.created_at,
113
- });
114
- }
115
- }
116
- catch { /* tolerant */ }
117
- }
118
- // design references (using existing FTS5 index)
119
- if (kinds.has('reference')) {
120
- try {
121
- const rows = db.prepare(`
26
+ `).all(i.id,e,e,a);for(const t of o)r.push({projectId:i.id,projectName:i.name,kind:"quest",id:t.id,title:t.title,snippet:(t.description||"").slice(0,200),visibility:t.visibility,capturedAt:t.created_at})}catch{}if(c.has("reference"))try{const o=s.prepare(`
122
27
  SELECT dr.id, dr.title, dr.notes, dr.tags, dr.cross_project_visibility AS visibility, dr.captured_at
123
28
  FROM design_references dr
124
29
  WHERE dr.project_id = ?
125
- AND dr.cross_project_visibility IN ${visList}
30
+ AND dr.cross_project_visibility IN ${d}
126
31
  AND (dr.title LIKE '%' || ? || '%' OR dr.notes LIKE '%' || ? || '%' OR dr.tags LIKE '%' || ? || '%')
127
32
  ORDER BY dr.captured_at DESC
128
33
  LIMIT ?
129
- `).all(project.id, safeQ, safeQ, safeQ, limit);
130
- for (const r of rows) {
131
- hits.push({
132
- projectId: project.id,
133
- projectName: project.name,
134
- kind: 'reference',
135
- id: r.id,
136
- title: r.title || `(reference #${r.id})`,
137
- snippet: [r.notes, r.tags].filter(Boolean).join(' · ').slice(0, 200),
138
- visibility: r.visibility,
139
- capturedAt: r.captured_at,
140
- });
141
- }
142
- }
143
- catch { /* tolerant */ }
144
- }
145
- return hits;
146
- }
147
- /**
148
- * Run a constellation query across every project.
149
- * Tolerant of older schemas (any table or column missing simply skips).
150
- */
151
- export function constellationQuery(db, opts) {
152
- const kinds = new Set((opts.kinds ?? ['truth', 'artifact', 'quest', 'reference']).filter((k) => SAFE_KINDS.has(k)));
153
- const visibility = new Set((opts.visibility ?? ['org', 'public']).filter((v) => SAFE_VIS.has(v)));
154
- // If the caller wants their OWN project's data too, allow 'within'.
155
- // Otherwise we strictly enforce the cross-project gate.
156
- const includeOwn = visibility.has('within');
157
- const perProject = Math.min(Math.max(1, opts.perProjectLimit ?? 5), 50);
158
- const projects = db.prepare(`SELECT id, name FROM projects ORDER BY id`).all();
159
- const allHits = [];
160
- for (const p of projects) {
161
- if (!includeOwn && opts.callerProjectId && p.id === opts.callerProjectId)
162
- continue;
163
- const hits = queryProject(db, p, opts.query, kinds, visibility, perProject);
164
- allHits.push(...hits);
165
- }
166
- // Sort: most recent first overall.
167
- allHits.sort((a, b) => b.capturedAt.localeCompare(a.capturedAt));
168
- return allHits;
169
- }
170
- export function renderConstellation(hits, query) {
171
- if (hits.length === 0) {
172
- return `󱅝 No constellation matches for *${query}* across registered projects.\n\n_Set \`cross_project_visibility\` to \`'org'\` or \`'public'\` on the items you want surfaced here. Default is \`'within'\` — within-project only._`;
173
- }
174
- // Group by project
175
- const byProject = new Map();
176
- for (const h of hits) {
177
- const arr = byProject.get(h.projectName) ?? [];
178
- arr.push(h);
179
- byProject.set(h.projectName, arr);
180
- }
181
- const lines = [`󱅝 **Constellation** — ${hits.length} match(es) for *${query}* across ${byProject.size} project(s)`];
182
- for (const [name, items] of byProject) {
183
- lines.push('');
184
- lines.push(`### ${name}`);
185
- for (const h of items) {
186
- lines.push(`- \`${h.kind}:${h.id}\` **${h.title}** (${h.visibility})`);
187
- if (h.snippet)
188
- lines.push(` ${h.snippet}`);
189
- }
190
- }
191
- return lines.join('\n');
192
- }
193
- //# sourceMappingURL=constellation.js.map
34
+ `).all(i.id,e,e,e,a);for(const t of o)r.push({projectId:i.id,projectName:i.name,kind:"reference",id:t.id,title:t.title||`(reference #${t.id})`,snippet:[t.notes,t.tags].filter(Boolean).join(" \xB7 ").slice(0,200),visibility:t.visibility,capturedAt:t.captured_at})}catch{}return r}function h(s,i){const l=new Set((i.kinds??["truth","artifact","quest","reference"]).filter(e=>p.has(e))),c=new Set((i.visibility??["org","public"]).filter(e=>u.has(e))),n=c.has("within"),a=Math.min(Math.max(1,i.perProjectLimit??5),50),r=s.prepare("SELECT id, name FROM projects ORDER BY id").all(),d=[];for(const e of r){if(!n&&i.callerProjectId&&e.id===i.callerProjectId)continue;const o=_(s,e,i.query,l,c,a);d.push(...o)}return d.sort((e,o)=>o.capturedAt.localeCompare(e.capturedAt)),d}function y(s,i){if(s.length===0)return`\u{F115D} No constellation matches for *${i}* across registered projects.
35
+
36
+ _Set \`cross_project_visibility\` to \`'org'\` or \`'public'\` on the items you want surfaced here. Default is \`'within'\` \u2014 within-project only._`;const l=new Map;for(const n of s){const a=l.get(n.projectName)??[];a.push(n),l.set(n.projectName,a)}const c=[`\u{F115D} **Constellation** \u2014 ${s.length} match(es) for *${i}* across ${l.size} project(s)`];for(const[n,a]of l){c.push(""),c.push(`### ${n}`);for(const r of a)c.push(`- \`${r.kind}:${r.id}\` **${r.title}** (${r.visibility})`),r.snippet&&c.push(` ${r.snippet}`)}return c.join(`
37
+ `)}export{h as constellationQuery,y as renderConstellation};
@@ -1,144 +1,4 @@
1
- /**
2
- * Budgeted wyrm_context_build path (Spec 014; v7 F4 T036 extracted from the
3
- * index.ts monolith). Renders a stable preamble (ground truths) + a dynamic
4
- * body (best scaffold + recalled memory) under a token budget; items below the
5
- * cutoff elide to one-line stubs the agent can re-`wyrm_recall`. Lifted VERBATIM
6
- * behind a factory that captures the same index.ts singletons + budgeting
7
- * helpers it always closed over (deterministic; no LLM, Article III).
8
- *
9
- * @copyright 2026 Ghost Protocol (Pvt) Ltd.
10
- * @license AGPL-3.0-or-later
11
- */
12
- import { applyBudget, resolveBudget } from "./token-budget.js";
13
- import { score as rankScore } from "./context-ranking.js";
14
- export function makeRunBudgetedContextBuild(deps) {
15
- const { groundTruths, scaffoldLib, memory, toolAnalytics, sessionSeen, tokenEstimator, rankWeights } = deps;
16
- return function runBudgetedContextBuild(opts) {
17
- // Resolve effective budget per spec 014 D1 precedence: call > env > fallback.
18
- const budget = resolveBudget({
19
- callArg: opts.maxTokens,
20
- envValue: process.env.WYRM_CONTEXT_TOKEN_BUDGET,
21
- clientName: null, // clientInfo wiring is a follow-up; env override is the lever today
22
- });
23
- const seen = opts.sessionId ? sessionSeen().getSeen(opts.sessionId) : new Set();
24
- // --- Stable preamble (ground truths) ---
25
- const truthsText = groundTruths.formatForContext(opts.project.id);
26
- const truthsTokens = tokenEstimator.count(truthsText ?? '');
27
- // Strict budget mode (D3) elides truths if even the preamble overflows;
28
- // default behavior is to warn-and-overflow per spec 014.
29
- let preambleText = truthsText ?? '';
30
- let preambleEmitted = truthsTokens;
31
- if (truthsTokens > budget * 0.5 && !opts.strictBudget) {
32
- process.stderr.write(`[wyrm_context_build] ground truths consume ${truthsTokens} of ${budget}-token budget — body will be heavily elided. Pass strict_budget:true to elide truths too.\n`);
33
- }
34
- if (opts.strictBudget && truthsTokens > budget * 0.5) {
35
- // Strict mode: keep truths brief
36
- preambleText = (truthsText ?? '').slice(0, budget * 2); // 2 chars/token rough cap on chars
37
- preambleEmitted = tokenEstimator.count(preambleText);
38
- }
39
- // --- Build candidate body items: best scaffold + memory artifacts ---
40
- const items = [];
41
- // Scaffold (treated as a single candidate)
42
- const scaffoldMatch = scaffoldLib.findBest(opts.task, opts.project.id);
43
- if (scaffoldMatch) {
44
- const sText = scaffoldLib.formatForContext(scaffoldMatch);
45
- const sId = scaffoldMatch.scaffold.id;
46
- items.push({
47
- item: { kind: 'scaffold', id: sId, title: scaffoldMatch.scaffold.problem_type, body: sText },
48
- key: `scaffold:${sId}`,
49
- inlineCost: tokenEstimator.count(sText),
50
- stubCost: 20,
51
- // Scaffold is high-relevance by definition; assign generous score.
52
- score: rankScore({ confidence: 0.9, relevance: 0.9, updatedAt: new Date().toISOString() }, rankWeights),
53
- });
54
- }
55
- // Memory artifacts via recall (which returns id-level results with relevance scores)
56
- const recalled = memory.recall(opts.project.id, opts.task, {
57
- limit: 20,
58
- minConfidence: opts.minConfidence ?? 0.2,
59
- });
60
- for (const r of recalled) {
61
- const a = r.artifact;
62
- // Skip kind filter mismatches if caller specified one
63
- if (opts.kinds && opts.kinds.length > 0 && !opts.kinds.includes(a.kind))
64
- continue;
65
- const body = [
66
- `**${a.kind.toUpperCase()}** · ${a.problem}`,
67
- a.constraints ? `Constraints: ${a.constraints}` : '',
68
- a.validated_fix ? `Fix: ${a.validated_fix}` : '',
69
- a.why_it_worked ? `Why: ${a.why_it_worked}` : '',
70
- ].filter(Boolean).join('\n');
71
- items.push({
72
- item: { kind: 'memory', id: a.id, title: a.problem.slice(0, 60), body },
73
- key: `memory:${a.id}`,
74
- inlineCost: tokenEstimator.count(body),
75
- stubCost: 25,
76
- score: rankScore({
77
- confidence: a.confidence,
78
- relevance: r.relevance_score,
79
- updatedAt: a.updated_at,
80
- }, rankWeights),
81
- });
82
- }
83
- // --- Apply budget ---
84
- const result = applyBudget(items, {
85
- budget,
86
- alreadySeen: seen,
87
- reserved: opts.strictBudget ? 0 : preambleEmitted,
88
- });
89
- // --- Mark inlined items as seen for this session ---
90
- if (opts.sessionId) {
91
- sessionSeen().markBulk(opts.sessionId, result.inline.map((it) => ({ id: it.id, kind: it.kind, mode: 'inline' })));
92
- sessionSeen().markBulk(opts.sessionId, result.elided.map((e) => ({ id: e.item.id, kind: e.item.kind, mode: 'stub' })));
93
- }
94
- // --- Render output ---
95
- const lines = [];
96
- lines.push(`󱅝 **Context Brief** — "${opts.task}"`);
97
- lines.push('');
98
- lines.push('## Ground truths (stable preamble)');
99
- lines.push('');
100
- lines.push(preambleText || '_no ground truths set for this project yet_');
101
- lines.push('');
102
- lines.push('## Task-relevant memory');
103
- lines.push('');
104
- for (const it of result.inline) {
105
- lines.push(`### [${it.kind}:${it.id}] ${it.title}`);
106
- lines.push(it.body);
107
- lines.push('');
108
- }
109
- if (result.elided.length > 0) {
110
- lines.push('## Elided to stubs');
111
- lines.push('');
112
- lines.push('_Below the budget cutoff or already seen this session. Recall with `wyrm_recall(id)`:_');
113
- lines.push('');
114
- for (const e of result.elided) {
115
- const tag = e.reason === 'seen' ? 'shown earlier in session' : `~${e.stubCost} tokens`;
116
- lines.push(`- [${e.item.kind}:${e.item.id}] ${e.item.title} · ${tag}`);
117
- }
118
- lines.push('');
119
- }
120
- lines.push('---');
121
- lines.push(`_Budget: ${result.tokensInline}/${budget} tokens inline · ${result.elided.length} stub${result.elided.length === 1 ? '' : 's'} · estimator: ${tokenEstimator.source}_`);
122
- const text = lines.join('\n');
123
- // Telemetry — fire-and-forget via the existing tool_call_log surface.
124
- try {
125
- toolAnalytics.log({
126
- tool_name: 'wyrm_context_build',
127
- project_id: opts.project.id,
128
- args: {
129
- budget,
130
- items_total: items.length,
131
- items_inline: result.inline.length,
132
- items_elided: result.elided.length,
133
- tokens_inline: result.tokensInline,
134
- tokens_stubs: result.tokensStubs,
135
- },
136
- success: true,
137
- latency_ms: 0,
138
- });
139
- }
140
- catch { /* never fail context_build on telemetry */ }
141
- return { content: [{ type: 'text', text }] };
142
- };
143
- }
144
- //# sourceMappingURL=context-build-budgeted.js.map
1
+ import{applyBudget as w,resolveBudget as v}from"./token-budget.js";import{score as p}from"./context-ranking.js";function S(g){const{groundTruths:b,scaffoldLib:h,memory:y,toolAnalytics:$,sessionSeen:u,tokenEstimator:d,rankWeights:f}=g;return function(t){const o=v({callArg:t.maxTokens,envValue:process.env.WYRM_CONTEXT_TOKEN_BUDGET,clientName:null}),x=t.sessionId?u().getSeen(t.sessionId):new Set,a=b.formatForContext(t.project.id),r=d.count(a??"");let m=a??"",k=r;r>o*.5&&!t.strictBudget&&process.stderr.write(`[wyrm_context_build] ground truths consume ${r} of ${o}-token budget \u2014 body will be heavily elided. Pass strict_budget:true to elide truths too.
2
+ `),t.strictBudget&&r>o*.5&&(m=(a??"").slice(0,o*2),k=d.count(m));const l=[],c=h.findBest(t.task,t.project.id);if(c){const e=h.formatForContext(c),i=c.scaffold.id;l.push({item:{kind:"scaffold",id:i,title:c.scaffold.problem_type,body:e},key:`scaffold:${i}`,inlineCost:d.count(e),stubCost:20,score:p({confidence:.9,relevance:.9,updatedAt:new Date().toISOString()},f)})}const B=y.recall(t.project.id,t.task,{limit:20,minConfidence:t.minConfidence??.2});for(const e of B){const i=e.artifact;if(t.kinds&&t.kinds.length>0&&!t.kinds.includes(i.kind))continue;const _=[`**${i.kind.toUpperCase()}** \xB7 ${i.problem}`,i.constraints?`Constraints: ${i.constraints}`:"",i.validated_fix?`Fix: ${i.validated_fix}`:"",i.why_it_worked?`Why: ${i.why_it_worked}`:""].filter(Boolean).join(`
3
+ `);l.push({item:{kind:"memory",id:i.id,title:i.problem.slice(0,60),body:_},key:`memory:${i.id}`,inlineCost:d.count(_),stubCost:25,score:p({confidence:i.confidence,relevance:e.relevance_score,updatedAt:i.updated_at},f)})}const s=w(l,{budget:o,alreadySeen:x,reserved:t.strictBudget?0:k});t.sessionId&&(u().markBulk(t.sessionId,s.inline.map(e=>({id:e.id,kind:e.kind,mode:"inline"}))),u().markBulk(t.sessionId,s.elided.map(e=>({id:e.item.id,kind:e.item.kind,mode:"stub"}))));const n=[];n.push(`\u{F115D} **Context Brief** \u2014 "${t.task}"`),n.push(""),n.push("## Ground truths (stable preamble)"),n.push(""),n.push(m||"_no ground truths set for this project yet_"),n.push(""),n.push("## Task-relevant memory"),n.push("");for(const e of s.inline)n.push(`### [${e.kind}:${e.id}] ${e.title}`),n.push(e.body),n.push("");if(s.elided.length>0){n.push("## Elided to stubs"),n.push(""),n.push("_Below the budget cutoff or already seen this session. Recall with `wyrm_recall(id)`:_"),n.push("");for(const e of s.elided){const i=e.reason==="seen"?"shown earlier in session":`~${e.stubCost} tokens`;n.push(`- [${e.item.kind}:${e.item.id}] ${e.item.title} \xB7 ${i}`)}n.push("")}n.push("---"),n.push(`_Budget: ${s.tokensInline}/${o} tokens inline \xB7 ${s.elided.length} stub${s.elided.length===1?"":"s"} \xB7 estimator: ${d.source}_`);const C=n.join(`
4
+ `);try{$.log({tool_name:"wyrm_context_build",project_id:t.project.id,args:{budget:o,items_total:l.length,items_inline:s.inline.length,items_elided:s.elided.length,tokens_inline:s.tokensInline,tokens_stubs:s.tokensStubs},success:!0,latency_ms:0})}catch{}return{content:[{type:"text",text:C}]}}}export{S as makeRunBudgetedContextBuild};
@@ -1,69 +1 @@
1
- /**
2
- * Combined-score computation for context-build candidates (spec 014).
3
- *
4
- * Combines four signals into a single score in [0, 1]:
5
- *
6
- * confidence × wC + recency × wR + relevance × wU + usefulness × wU
7
- *
8
- * Weights load from WYRM_RANK_WEIGHTS env (JSON) with sane defaults.
9
- *
10
- * @copyright 2026 Ghost Protocol (Pvt) Ltd.
11
- * @license AGPL-3.0-or-later — dual-licensed; commercial terms: ghosts.lk@proton.me. See LICENSE.
12
- */
13
- import { logger } from './logger.js';
14
- export const DEFAULT_WEIGHTS = {
15
- confidence: 0.4,
16
- recency: 0.2,
17
- relevance: 0.3,
18
- usefulness: 0.1,
19
- };
20
- /**
21
- * Load weights from WYRM_RANK_WEIGHTS env var. Falls back to defaults
22
- * on any parse/validation error. Logs the active weights at startup.
23
- */
24
- export function loadWeightsFromEnv(env = process.env) {
25
- const raw = env.WYRM_RANK_WEIGHTS;
26
- if (!raw)
27
- return { ...DEFAULT_WEIGHTS };
28
- try {
29
- const parsed = JSON.parse(raw);
30
- const w = {
31
- confidence: clamp01(parsed.confidence ?? DEFAULT_WEIGHTS.confidence),
32
- recency: clamp01(parsed.recency ?? DEFAULT_WEIGHTS.recency),
33
- relevance: clamp01(parsed.relevance ?? DEFAULT_WEIGHTS.relevance),
34
- usefulness: clamp01(parsed.usefulness ?? DEFAULT_WEIGHTS.usefulness),
35
- };
36
- return w;
37
- }
38
- catch (e) {
39
- logger.warn(`Invalid WYRM_RANK_WEIGHTS, using defaults: ${e.message}`);
40
- return { ...DEFAULT_WEIGHTS };
41
- }
42
- }
43
- function clamp01(x) {
44
- if (!Number.isFinite(x))
45
- return 0;
46
- return Math.max(0, Math.min(1, x));
47
- }
48
- /**
49
- * Compute the combined score in [0, 1].
50
- *
51
- * Recency uses exponential decay over 30 days from updatedAt to now.
52
- * Missing signals default to neutral (0.5 confidence, 0.5 relevance, 0.5
53
- * usefulness; recency to 0 if no updatedAt).
54
- */
55
- export function score(item, weights, now = new Date()) {
56
- const c = clamp01(item.confidence ?? 0.5);
57
- const u = clamp01(item.usefulness ?? 0.5);
58
- const r = clamp01(item.relevance ?? 0.5);
59
- const ageDays = item.updatedAt
60
- ? Math.max(0, (now.getTime() - new Date(item.updatedAt).getTime()) / (24 * 60 * 60 * 1000))
61
- : Infinity;
62
- const recency = Number.isFinite(ageDays) ? Math.exp(-ageDays / 30) : 0;
63
- const total = c * weights.confidence +
64
- recency * weights.recency +
65
- r * weights.relevance +
66
- u * weights.usefulness;
67
- return clamp01(total);
68
- }
69
- //# sourceMappingURL=context-ranking.js.map
1
+ import{logger as l}from"./logger.js";const s={confidence:.4,recency:.2,relevance:.3,usefulness:.1};function p(e=process.env){const r=e.WYRM_RANK_WEIGHTS;if(!r)return{...s};try{const n=JSON.parse(r);return{confidence:c(n.confidence??s.confidence),recency:c(n.recency??s.recency),relevance:c(n.relevance??s.relevance),usefulness:c(n.usefulness??s.usefulness)}}catch(n){return l.warn(`Invalid WYRM_RANK_WEIGHTS, using defaults: ${n.message}`),{...s}}}function c(e){return Number.isFinite(e)?Math.max(0,Math.min(1,e)):0}function v(e,r,n=new Date){const t=c(e.confidence??.5),a=c(e.usefulness??.5),u=c(e.relevance??.5),o=e.updatedAt?Math.max(0,(n.getTime()-new Date(e.updatedAt).getTime())/(1440*60*1e3)):1/0,f=Number.isFinite(o)?Math.exp(-o/30):0,i=t*r.confidence+f*r.recency+u*r.relevance+a*r.usefulness;return c(i)}export{s as DEFAULT_WEIGHTS,p as loadWeightsFromEnv,v as score};
package/dist/crypto.js CHANGED
@@ -1,179 +1 @@
1
- /**
2
- * Wyrm Encryption Module
3
- * AES-256-GCM encryption for sensitive memory data
4
- *
5
- * @copyright 2026 Ghost Protocol (Pvt) Ltd.
6
- * @license AGPL-3.0-or-later — dual-licensed; commercial terms: ghosts.lk@proton.me. See LICENSE.
7
- * @module crypto
8
- * @version 3.0.0
9
- */
10
- import { createCipheriv, createDecipheriv, randomBytes, scryptSync, createHash } from 'crypto';
11
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
12
- import { join } from 'path';
13
- import { homedir } from 'os';
14
- const WYRM_DIR = join(homedir(), '.wyrm');
15
- const MASTER_SALT_FILE = join(WYRM_DIR, 'crypto.salt');
16
- const ALGORITHM = 'aes-256-gcm';
17
- const IV_LENGTH = 16;
18
- const SALT_LENGTH = 32;
19
- const TAG_LENGTH = 16;
20
- const KEY_LENGTH = 32;
21
- /**
22
- * Wyrm Crypto - Handles encryption/decryption of sensitive data
23
- */
24
- export class WyrmCrypto {
25
- masterKey = null;
26
- config;
27
- constructor(config) {
28
- this.config = {
29
- enabled: config?.enabled ?? false,
30
- keyDerivation: config?.keyDerivation ?? 'scrypt',
31
- iterations: config?.iterations ?? 100000,
32
- };
33
- }
34
- /**
35
- * Initialize crypto with a master password
36
- */
37
- initialize(password) {
38
- if (!password || password.length < 8) {
39
- throw new Error('Password must be at least 8 characters');
40
- }
41
- // Use a per-installation random salt (persisted, never derived from constant)
42
- if (!existsSync(WYRM_DIR)) {
43
- mkdirSync(WYRM_DIR, { recursive: true, mode: 0o700 });
44
- }
45
- let salt;
46
- if (existsSync(MASTER_SALT_FILE)) {
47
- salt = Buffer.from(readFileSync(MASTER_SALT_FILE, 'utf8').trim(), 'hex');
48
- }
49
- else {
50
- salt = randomBytes(32);
51
- writeFileSync(MASTER_SALT_FILE, salt.toString('hex'), { mode: 0o600 });
52
- }
53
- this.masterKey = scryptSync(password, salt, KEY_LENGTH);
54
- this.config.enabled = true;
55
- }
56
- /**
57
- * Check if encryption is enabled and configured
58
- */
59
- isEnabled() {
60
- return this.config.enabled && this.masterKey !== null;
61
- }
62
- /**
63
- * Derive a unique key for each piece of data
64
- */
65
- deriveKey(salt) {
66
- if (!this.masterKey) {
67
- throw new Error('Crypto not initialized. Call initialize() with a password first.');
68
- }
69
- return scryptSync(this.masterKey, salt, KEY_LENGTH);
70
- }
71
- /**
72
- * Encrypt a string value
73
- */
74
- encrypt(plaintext) {
75
- if (!this.isEnabled()) {
76
- throw new Error('Encryption not enabled');
77
- }
78
- const salt = randomBytes(SALT_LENGTH);
79
- const iv = randomBytes(IV_LENGTH);
80
- const key = this.deriveKey(salt);
81
- const cipher = createCipheriv(ALGORITHM, key, iv);
82
- let encrypted = cipher.update(plaintext, 'utf8', 'hex');
83
- encrypted += cipher.final('hex');
84
- const tag = cipher.getAuthTag();
85
- return {
86
- iv: iv.toString('hex'),
87
- salt: salt.toString('hex'),
88
- tag: tag.toString('hex'),
89
- data: encrypted,
90
- version: 1,
91
- };
92
- }
93
- /**
94
- * Decrypt an encrypted value
95
- */
96
- decrypt(encrypted) {
97
- if (!this.isEnabled()) {
98
- throw new Error('Encryption not enabled');
99
- }
100
- const salt = Buffer.from(encrypted.salt, 'hex');
101
- const iv = Buffer.from(encrypted.iv, 'hex');
102
- const tag = Buffer.from(encrypted.tag, 'hex');
103
- const key = this.deriveKey(salt);
104
- const decipher = createDecipheriv(ALGORITHM, key, iv);
105
- decipher.setAuthTag(tag);
106
- let decrypted = decipher.update(encrypted.data, 'hex', 'utf8');
107
- decrypted += decipher.final('utf8');
108
- return decrypted;
109
- }
110
- /**
111
- * Encrypt if enabled, otherwise return plaintext
112
- */
113
- maybeEncrypt(value) {
114
- if (!this.isEnabled()) {
115
- return value;
116
- }
117
- const encrypted = this.encrypt(value);
118
- return `ENC:${JSON.stringify(encrypted)}`;
119
- }
120
- /**
121
- * Decrypt if encrypted, otherwise return as-is
122
- */
123
- maybeDecrypt(value) {
124
- if (!value.startsWith('ENC:')) {
125
- return value;
126
- }
127
- if (!this.isEnabled()) {
128
- // Can't decrypt without key
129
- return '[ENCRYPTED - KEY REQUIRED]';
130
- }
131
- try {
132
- const encrypted = JSON.parse(value.slice(4));
133
- return this.decrypt(encrypted);
134
- }
135
- catch {
136
- return '[DECRYPT FAILED]';
137
- }
138
- }
139
- /**
140
- * Generate a secure random key for API tokens, etc.
141
- */
142
- static generateSecureToken(length = 32) {
143
- return randomBytes(length).toString('hex');
144
- }
145
- /**
146
- * Hash a value for comparison (one-way)
147
- */
148
- static hash(value, algorithm = 'sha256') {
149
- return createHash(algorithm).update(value).digest('hex');
150
- }
151
- /**
152
- * Verify a hash
153
- */
154
- static verifyHash(value, hash, algorithm = 'sha256') {
155
- const computed = WyrmCrypto.hash(value, algorithm);
156
- // Constant-time comparison
157
- if (computed.length !== hash.length)
158
- return false;
159
- let result = 0;
160
- for (let i = 0; i < computed.length; i++) {
161
- result |= computed.charCodeAt(i) ^ hash.charCodeAt(i);
162
- }
163
- return result === 0;
164
- }
165
- }
166
- // Singleton instance
167
- let cryptoInstance = null;
168
- export function getCrypto() {
169
- if (!cryptoInstance) {
170
- cryptoInstance = new WyrmCrypto();
171
- }
172
- return cryptoInstance;
173
- }
174
- export function initializeCrypto(password) {
175
- cryptoInstance = new WyrmCrypto({ enabled: true });
176
- cryptoInstance.initialize(password);
177
- return cryptoInstance;
178
- }
179
- //# sourceMappingURL=crypto.js.map
1
+ import{createCipheriv as g,createDecipheriv as x,randomBytes as c,scryptSync as u,createHash as S}from"crypto";import{existsSync as y,readFileSync as b,writeFileSync as T,mkdirSync as w}from"fs";import{join as d}from"path";import{homedir as v}from"os";const l=d(v(),".wyrm"),f=d(l,"crypto.salt"),p="aes-256-gcm",C=16,A=32,R=16,m=32;class h{masterKey=null;config;constructor(t){this.config={enabled:t?.enabled??!1,keyDerivation:t?.keyDerivation??"scrypt",iterations:t?.iterations??1e5}}initialize(t){if(!t||t.length<8)throw new Error("Password must be at least 8 characters");y(l)||w(l,{recursive:!0,mode:448});let e;y(f)?e=Buffer.from(b(f,"utf8").trim(),"hex"):(e=c(32),T(f,e.toString("hex"),{mode:384})),this.masterKey=u(t,e,m),this.config.enabled=!0}isEnabled(){return this.config.enabled&&this.masterKey!==null}deriveKey(t){if(!this.masterKey)throw new Error("Crypto not initialized. Call initialize() with a password first.");return u(this.masterKey,t,m)}encrypt(t){if(!this.isEnabled())throw new Error("Encryption not enabled");const e=c(A),s=c(C),n=this.deriveKey(e),i=g(p,n,s);let r=i.update(t,"utf8","hex");r+=i.final("hex");const a=i.getAuthTag();return{iv:s.toString("hex"),salt:e.toString("hex"),tag:a.toString("hex"),data:r,version:1}}decrypt(t){if(!this.isEnabled())throw new Error("Encryption not enabled");const e=Buffer.from(t.salt,"hex"),s=Buffer.from(t.iv,"hex"),n=Buffer.from(t.tag,"hex"),i=this.deriveKey(e),r=x(p,i,s);r.setAuthTag(n);let a=r.update(t.data,"hex","utf8");return a+=r.final("utf8"),a}maybeEncrypt(t){if(!this.isEnabled())return t;const e=this.encrypt(t);return`ENC:${JSON.stringify(e)}`}maybeDecrypt(t){if(!t.startsWith("ENC:"))return t;if(!this.isEnabled())return"[ENCRYPTED - KEY REQUIRED]";try{const e=JSON.parse(t.slice(4));return this.decrypt(e)}catch{return"[DECRYPT FAILED]"}}static generateSecureToken(t=32){return c(t).toString("hex")}static hash(t,e="sha256"){return S(e).update(t).digest("hex")}static verifyHash(t,e,s="sha256"){const n=h.hash(t,s);if(n.length!==e.length)return!1;let i=0;for(let r=0;r<n.length;r++)i|=n.charCodeAt(r)^e.charCodeAt(r);return i===0}}let o=null;function H(){return o||(o=new h),o}function I(E){return o=new h({enabled:!0}),o.initialize(E),o}export{h as WyrmCrypto,H as getCrypto,I as initializeCrypto};