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,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};