@massu/core 0.1.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/commands/_shared-preamble.md +76 -0
  2. package/commands/massu-audit-deps.md +211 -0
  3. package/commands/massu-changelog.md +174 -0
  4. package/commands/massu-cleanup.md +315 -0
  5. package/commands/massu-commit.md +481 -0
  6. package/commands/massu-create-plan.md +752 -0
  7. package/commands/massu-dead-code.md +131 -0
  8. package/commands/massu-debug.md +484 -0
  9. package/commands/massu-deploy.md +91 -0
  10. package/commands/massu-deps.md +374 -0
  11. package/commands/massu-doc-gen.md +279 -0
  12. package/commands/massu-docs.md +364 -0
  13. package/commands/massu-estimate.md +313 -0
  14. package/commands/massu-golden-path.md +973 -0
  15. package/commands/massu-guide.md +167 -0
  16. package/commands/massu-hotfix.md +480 -0
  17. package/commands/massu-loop-playwright.md +837 -0
  18. package/commands/massu-loop.md +775 -0
  19. package/commands/massu-new-feature.md +511 -0
  20. package/commands/massu-parity.md +214 -0
  21. package/commands/massu-plan.md +456 -0
  22. package/commands/massu-push-light.md +207 -0
  23. package/commands/massu-push.md +434 -0
  24. package/commands/massu-refactor.md +410 -0
  25. package/commands/massu-release.md +363 -0
  26. package/commands/massu-review.md +238 -0
  27. package/commands/massu-simplify.md +281 -0
  28. package/commands/massu-status.md +278 -0
  29. package/commands/massu-tdd.md +201 -0
  30. package/commands/massu-test.md +516 -0
  31. package/commands/massu-verify-playwright.md +281 -0
  32. package/commands/massu-verify.md +667 -0
  33. package/dist/cli.js +7772 -3140
  34. package/dist/hooks/cost-tracker.js +103 -40
  35. package/dist/hooks/post-edit-context.js +74 -8
  36. package/dist/hooks/post-tool-use.js +268 -106
  37. package/dist/hooks/pre-compact.js +167 -43
  38. package/dist/hooks/pre-delete-check.js +159 -42
  39. package/dist/hooks/quality-event.js +103 -40
  40. package/dist/hooks/security-gate.js +29 -0
  41. package/dist/hooks/session-end.js +143 -84
  42. package/dist/hooks/session-start.js +186 -49
  43. package/dist/hooks/user-prompt.js +189 -43
  44. package/package.json +10 -15
  45. package/src/adr-generator.ts +9 -2
  46. package/src/analytics.ts +9 -3
  47. package/src/audit-trail.ts +10 -3
  48. package/src/backfill-sessions.ts +5 -4
  49. package/src/cli.ts +6 -0
  50. package/src/cloud-sync.ts +14 -18
  51. package/src/commands/doctor.ts +193 -6
  52. package/src/commands/init.ts +230 -5
  53. package/src/commands/install-commands.ts +137 -0
  54. package/src/config.ts +68 -2
  55. package/src/cost-tracker.ts +11 -6
  56. package/src/db.ts +115 -2
  57. package/src/dependency-scorer.ts +9 -2
  58. package/src/docs-tools.ts +21 -16
  59. package/src/hooks/post-edit-context.ts +4 -4
  60. package/src/hooks/post-tool-use.ts +130 -0
  61. package/src/hooks/pre-compact.ts +23 -1
  62. package/src/hooks/pre-delete-check.ts +92 -4
  63. package/src/hooks/security-gate.ts +32 -0
  64. package/src/hooks/session-end.ts +3 -3
  65. package/src/hooks/session-start.ts +99 -6
  66. package/src/hooks/user-prompt.ts +46 -1
  67. package/src/import-resolver.ts +2 -1
  68. package/src/knowledge-db.ts +169 -0
  69. package/src/knowledge-indexer.ts +704 -0
  70. package/src/knowledge-tools.ts +1413 -0
  71. package/src/license.ts +482 -0
  72. package/src/memory-db.ts +1364 -23
  73. package/src/memory-tools.ts +14 -15
  74. package/src/observability-tools.ts +13 -2
  75. package/src/observation-extractor.ts +11 -4
  76. package/src/page-deps.ts +3 -2
  77. package/src/prompt-analyzer.ts +9 -2
  78. package/src/python/coupling-detector.ts +124 -0
  79. package/src/python/domain-enforcer.ts +83 -0
  80. package/src/python/impact-analyzer.ts +95 -0
  81. package/src/python/import-parser.ts +244 -0
  82. package/src/python/import-resolver.ts +135 -0
  83. package/src/python/migration-indexer.ts +115 -0
  84. package/src/python/migration-parser.ts +332 -0
  85. package/src/python/model-indexer.ts +70 -0
  86. package/src/python/model-parser.ts +279 -0
  87. package/src/python/route-indexer.ts +58 -0
  88. package/src/python/route-parser.ts +317 -0
  89. package/src/python-tools.ts +629 -0
  90. package/src/regression-detector.ts +9 -3
  91. package/src/security-scorer.ts +9 -2
  92. package/src/sentinel-db.ts +45 -89
  93. package/src/sentinel-tools.ts +8 -11
  94. package/src/server.ts +29 -7
  95. package/src/session-archiver.ts +4 -5
  96. package/src/team-knowledge.ts +9 -2
  97. package/src/tools.ts +1032 -44
  98. package/src/validate-features-runner.ts +0 -1
  99. package/src/validation-engine.ts +9 -2
  100. package/README.md +0 -40
  101. package/dist/server.js +0 -7008
  102. package/src/__tests__/adr-generator.test.ts +0 -260
  103. package/src/__tests__/analytics.test.ts +0 -282
  104. package/src/__tests__/audit-trail.test.ts +0 -382
  105. package/src/__tests__/backfill-sessions.test.ts +0 -690
  106. package/src/__tests__/cli.test.ts +0 -290
  107. package/src/__tests__/cloud-sync.test.ts +0 -261
  108. package/src/__tests__/config-sections.test.ts +0 -359
  109. package/src/__tests__/config.test.ts +0 -732
  110. package/src/__tests__/cost-tracker.test.ts +0 -348
  111. package/src/__tests__/db.test.ts +0 -177
  112. package/src/__tests__/dependency-scorer.test.ts +0 -325
  113. package/src/__tests__/docs-integration.test.ts +0 -178
  114. package/src/__tests__/docs-tools.test.ts +0 -199
  115. package/src/__tests__/domains.test.ts +0 -236
  116. package/src/__tests__/hooks.test.ts +0 -221
  117. package/src/__tests__/import-resolver.test.ts +0 -95
  118. package/src/__tests__/integration/path-traversal.test.ts +0 -134
  119. package/src/__tests__/integration/pricing-consistency.test.ts +0 -88
  120. package/src/__tests__/integration/tool-registration.test.ts +0 -146
  121. package/src/__tests__/memory-db.test.ts +0 -404
  122. package/src/__tests__/memory-enhancements.test.ts +0 -316
  123. package/src/__tests__/memory-tools.test.ts +0 -199
  124. package/src/__tests__/middleware-tree.test.ts +0 -177
  125. package/src/__tests__/observability-tools.test.ts +0 -595
  126. package/src/__tests__/observability.test.ts +0 -437
  127. package/src/__tests__/observation-extractor.test.ts +0 -167
  128. package/src/__tests__/page-deps.test.ts +0 -60
  129. package/src/__tests__/prompt-analyzer.test.ts +0 -298
  130. package/src/__tests__/regression-detector.test.ts +0 -295
  131. package/src/__tests__/rules.test.ts +0 -87
  132. package/src/__tests__/schema-mapper.test.ts +0 -29
  133. package/src/__tests__/security-scorer.test.ts +0 -238
  134. package/src/__tests__/security-utils.test.ts +0 -175
  135. package/src/__tests__/sentinel-db.test.ts +0 -491
  136. package/src/__tests__/sentinel-scanner.test.ts +0 -750
  137. package/src/__tests__/sentinel-tools.test.ts +0 -324
  138. package/src/__tests__/sentinel-types.test.ts +0 -750
  139. package/src/__tests__/server.test.ts +0 -452
  140. package/src/__tests__/session-archiver.test.ts +0 -524
  141. package/src/__tests__/session-state-generator.test.ts +0 -900
  142. package/src/__tests__/team-knowledge.test.ts +0 -327
  143. package/src/__tests__/tools.test.ts +0 -340
  144. package/src/__tests__/transcript-parser.test.ts +0 -195
  145. package/src/__tests__/trpc-index.test.ts +0 -25
  146. package/src/__tests__/validate-features-runner.test.ts +0 -517
  147. package/src/__tests__/validation-engine.test.ts +0 -300
  148. package/src/core-tools.ts +0 -685
  149. package/src/memory-queries.ts +0 -804
  150. package/src/memory-schema.ts +0 -546
  151. package/src/tool-helpers.ts +0 -41
package/src/license.ts ADDED
@@ -0,0 +1,482 @@
1
+ // Copyright (c) 2026 Massu. All rights reserved.
2
+ // Licensed under BSL 1.1 - see LICENSE file for details.
3
+
4
+ /**
5
+ * License module — tier enforcement for Massu tools.
6
+ *
7
+ * Exports:
8
+ * - ToolTier type and TOOL_TIER_MAP constant
9
+ * - getCurrentTier() — cached license status for the session
10
+ * - getToolTier(name) — required tier for a tool
11
+ * - isToolAllowed(toolName, userTier) — gate check
12
+ * - annotateToolDefinitions(defs) — add tier labels to descriptions
13
+ * - getLicenseToolDefinitions / isLicenseTool / handleLicenseToolCall — 3-function pattern
14
+ */
15
+
16
+ import { createHash } from 'crypto';
17
+ import type { ToolDefinition, ToolResult } from './tools.ts';
18
+ import { getConfig } from './config.ts';
19
+ import { getMemoryDb } from './memory-db.ts';
20
+
21
+ // ============================================================
22
+ // Types
23
+ // ============================================================
24
+
25
+ export type ToolTier = 'free' | 'pro' | 'team' | 'enterprise';
26
+
27
+ // ============================================================
28
+ // Tier Ordering (for comparison)
29
+ // ============================================================
30
+
31
+ const TIER_LEVELS: Record<ToolTier, number> = {
32
+ free: 0,
33
+ pro: 1,
34
+ team: 2,
35
+ enterprise: 3,
36
+ };
37
+
38
+ /** Return numeric level for tier comparison. Higher = more permissive. */
39
+ export function tierLevel(tier: ToolTier): number {
40
+ return TIER_LEVELS[tier] ?? 0;
41
+ }
42
+
43
+ // ============================================================
44
+ // P3-002: Tool Tier Map
45
+ // ============================================================
46
+
47
+ /**
48
+ * Maps every tool base name (without prefix) to its required tier.
49
+ * Tools not in this map default to 'free'.
50
+ *
51
+ * Free: core navigation + basic memory + regression
52
+ * Pro: knowledge, quality, cost, prompt, validation, ADR, observability, docs
53
+ * Team: sentinel, team knowledge
54
+ * Enterprise: audit, security, dependency
55
+ */
56
+ export const TOOL_TIER_MAP: Record<string, ToolTier> = {
57
+ // --- Free tier (12 tools: core navigation + basic memory + regression + license) ---
58
+ sync: 'free',
59
+ context: 'free',
60
+ impact: 'free',
61
+ domains: 'free',
62
+ schema: 'free',
63
+ trpc_map: 'free',
64
+ coupling_check: 'free',
65
+ memory_search: 'free',
66
+ memory_ingest: 'free',
67
+ regression_risk: 'free',
68
+ feature_health: 'free',
69
+ license_status: 'free',
70
+
71
+ // --- Pro tier (35 tools: knowledge, quality, cost, prompt, validation, ADR, observability, docs, advanced memory) ---
72
+ memory_timeline: 'pro',
73
+ memory_detail: 'pro',
74
+ memory_sessions: 'pro',
75
+ memory_failures: 'pro',
76
+ knowledge_search: 'pro',
77
+ knowledge_rule: 'pro',
78
+ knowledge_incident: 'pro',
79
+ knowledge_schema_check: 'pro',
80
+ knowledge_pattern: 'pro',
81
+ knowledge_verification: 'pro',
82
+ knowledge_graph: 'pro',
83
+ knowledge_command: 'pro',
84
+ knowledge_correct: 'pro',
85
+ knowledge_plan: 'pro',
86
+ knowledge_gaps: 'pro',
87
+ knowledge_effectiveness: 'pro',
88
+ quality_score: 'pro',
89
+ quality_trend: 'pro',
90
+ quality_report: 'pro',
91
+ cost_session: 'pro',
92
+ cost_trend: 'pro',
93
+ cost_feature: 'pro',
94
+ prompt_effectiveness: 'pro',
95
+ prompt_suggestions: 'pro',
96
+ validation_check: 'pro',
97
+ validation_report: 'pro',
98
+ adr_list: 'pro',
99
+ adr_detail: 'pro',
100
+ adr_create: 'pro',
101
+ session_replay: 'pro',
102
+ prompt_analysis: 'pro',
103
+ tool_patterns: 'pro',
104
+ session_stats: 'pro',
105
+ docs_audit: 'pro',
106
+ docs_coverage: 'pro',
107
+
108
+ // Pro tier — Python code intelligence
109
+ py_imports: 'pro',
110
+ py_routes: 'pro',
111
+ py_coupling: 'pro',
112
+ py_models: 'pro',
113
+ py_migrations: 'pro',
114
+ py_domains: 'pro',
115
+ py_impact: 'pro',
116
+ py_context: 'pro',
117
+
118
+ // --- Team tier (9 tools: sentinel feature registry + team knowledge) ---
119
+ sentinel_search: 'team',
120
+ sentinel_detail: 'team',
121
+ sentinel_impact: 'team',
122
+ sentinel_validate: 'team',
123
+ sentinel_register: 'team',
124
+ sentinel_parity: 'team',
125
+ team_search: 'team',
126
+ team_expertise: 'team',
127
+ team_conflicts: 'team',
128
+
129
+ // --- Enterprise tier (8 tools: audit trail + security scoring + dependency analysis) ---
130
+ audit_log: 'enterprise',
131
+ audit_report: 'enterprise',
132
+ audit_chain: 'enterprise',
133
+ security_score: 'enterprise',
134
+ security_heatmap: 'enterprise',
135
+ security_trend: 'enterprise',
136
+ dep_score: 'enterprise',
137
+ dep_alternatives: 'enterprise',
138
+ };
139
+
140
+ // ============================================================
141
+ // P3-002: Plan-to-tier mapping (from organizations.plan values)
142
+ // ============================================================
143
+
144
+ export const PLAN_TO_TIER_MAP: Record<string, ToolTier> = {
145
+ free: 'free',
146
+ cloud_pro: 'pro',
147
+ cloud_team: 'team',
148
+ cloud_enterprise: 'enterprise',
149
+ };
150
+
151
+ // ============================================================
152
+ // P3-003: getToolTier
153
+ // ============================================================
154
+
155
+ /**
156
+ * Get the required tier for a tool by name.
157
+ * Strips the configured prefix, looks up in TOOL_TIER_MAP, defaults to 'free'.
158
+ */
159
+ export function getToolTier(name: string): ToolTier {
160
+ const pfx = getConfig().toolPrefix + '_';
161
+ const baseName = name.startsWith(pfx) ? name.slice(pfx.length) : name;
162
+ return TOOL_TIER_MAP[baseName] ?? 'free';
163
+ }
164
+
165
+ // ============================================================
166
+ // P3-001: isToolAllowed
167
+ // ============================================================
168
+
169
+ /**
170
+ * Check if a tool is accessible at the given user tier.
171
+ * A user can access tools at their tier level or below.
172
+ */
173
+ export function isToolAllowed(toolName: string, userTier: ToolTier): boolean {
174
+ const requiredTier = getToolTier(toolName);
175
+ return tierLevel(userTier) >= tierLevel(requiredTier);
176
+ }
177
+
178
+ // ============================================================
179
+ // P3-004: annotateToolDefinitions
180
+ // ============================================================
181
+
182
+ const TIER_LABELS: Record<ToolTier, string> = {
183
+ free: '',
184
+ pro: '[PRO] ',
185
+ team: '[TEAM] ',
186
+ enterprise: '[ENTERPRISE] ',
187
+ };
188
+
189
+ /**
190
+ * Annotate tool definitions with tier labels in descriptions.
191
+ * Also sets the `tier` field on each definition.
192
+ * Free tools get no label prefix.
193
+ */
194
+ export function annotateToolDefinitions(defs: ToolDefinition[]): ToolDefinition[] {
195
+ return defs.map(def => {
196
+ const tier = getToolTier(def.name);
197
+ const label = TIER_LABELS[tier];
198
+ return {
199
+ ...def,
200
+ tier,
201
+ description: label ? `${label}${def.description}` : def.description,
202
+ };
203
+ });
204
+ }
205
+
206
+ // ============================================================
207
+ // P3-005/P3-006/P3-007/P3-013: License validation & caching
208
+ // ============================================================
209
+
210
+ interface LicenseInfo {
211
+ tier: ToolTier;
212
+ validUntil: string;
213
+ features: string[];
214
+ }
215
+
216
+ /** In-memory cache for the current session. Refreshes every 15 minutes. */
217
+ let cachedTier: LicenseInfo | null = null;
218
+ let cachedTierTimestamp: number = 0;
219
+ const IN_MEMORY_CACHE_TTL_MS = 15 * 60 * 1000; // 15 minutes
220
+
221
+ /**
222
+ * Validate a license key against the cloud endpoint.
223
+ * Uses local cache in memory.db with 1-hour freshness window.
224
+ * Performs async cloud validation via fetch() (Node 18+).
225
+ * Falls back to 7-day grace period on network failure.
226
+ */
227
+ export async function validateLicense(apiKey: string): Promise<LicenseInfo> {
228
+ const keyHash = createHash('sha256').update(apiKey).digest('hex');
229
+
230
+ // 1. Check local cache
231
+ const memDb = getMemoryDb();
232
+ try {
233
+ const cached = memDb.prepare(
234
+ 'SELECT tier, valid_until, last_validated, features FROM license_cache WHERE api_key_hash = ?'
235
+ ).get(keyHash) as { tier: string; valid_until: string; last_validated: string; features: string } | undefined;
236
+
237
+ if (cached) {
238
+ const lastValidated = new Date(cached.last_validated);
239
+ const hourAgo = new Date(Date.now() - 60 * 60 * 1000);
240
+
241
+ // Cache is fresh (< 1 hour old)
242
+ if (lastValidated > hourAgo) {
243
+ return {
244
+ tier: cached.tier as ToolTier,
245
+ validUntil: cached.valid_until,
246
+ features: JSON.parse(cached.features || '[]'),
247
+ };
248
+ }
249
+ }
250
+
251
+ // 2. Try cloud validation via fetch (Node 18+ has native fetch)
252
+ const config = getConfig();
253
+ const endpoint = config.cloud?.endpoint;
254
+
255
+ if (endpoint && /^https?:\/\/.+/.test(endpoint)) {
256
+ try {
257
+ const response = await fetch(`${endpoint}/validate-key`, {
258
+ method: 'POST',
259
+ headers: {
260
+ 'Authorization': `Bearer ${apiKey}`,
261
+ 'Content-Type': 'application/json',
262
+ },
263
+ signal: AbortSignal.timeout(10_000), // 10s timeout
264
+ });
265
+
266
+ if (response.ok) {
267
+ const data = await response.json() as {
268
+ valid: boolean;
269
+ plan?: string;
270
+ tier?: string;
271
+ validUntil?: string;
272
+ features?: string[];
273
+ reason?: string;
274
+ };
275
+
276
+ if (data.valid) {
277
+ // Map plan name to tier using PLAN_TO_TIER_MAP
278
+ const tier: ToolTier = data.plan
279
+ ? (PLAN_TO_TIER_MAP[data.plan] ?? 'free')
280
+ : (data.tier as ToolTier ?? 'free');
281
+ const validUntil = data.validUntil ?? '';
282
+ const features = data.features ?? [];
283
+
284
+ // Update local cache
285
+ updateLicenseCache(apiKey, tier, validUntil, features);
286
+
287
+ return { tier, validUntil, features };
288
+ }
289
+ // Server said key is not valid — return free tier
290
+ return { tier: 'free', validUntil: '', features: [] };
291
+ }
292
+ // Non-OK response — fall through to grace period
293
+ } catch {
294
+ // Network failure — fall through to grace period check
295
+ }
296
+ }
297
+
298
+ // 3. Grace period: cache exists but stale (up to 7 days)
299
+ if (cached) {
300
+ const lastValidated = new Date(cached.last_validated);
301
+ const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
302
+
303
+ // P3-013: 7-day grace period
304
+ if (lastValidated > sevenDaysAgo) {
305
+ return {
306
+ tier: cached.tier as ToolTier,
307
+ validUntil: cached.valid_until,
308
+ features: JSON.parse(cached.features || '[]'),
309
+ };
310
+ }
311
+ }
312
+
313
+ // 4. No valid cache — default to free
314
+ return { tier: 'free', validUntil: '', features: [] };
315
+ } finally {
316
+ memDb.close();
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Update the license cache in memory.db.
322
+ * Called by the session-start hook after async cloud validation.
323
+ */
324
+ export function updateLicenseCache(
325
+ apiKey: string,
326
+ tier: ToolTier,
327
+ validUntil: string,
328
+ features: string[] = []
329
+ ): void {
330
+ const keyHash = createHash('sha256').update(apiKey).digest('hex');
331
+ const memDb = getMemoryDb();
332
+ try {
333
+ memDb.prepare(`
334
+ INSERT OR REPLACE INTO license_cache (api_key_hash, tier, valid_until, last_validated, features)
335
+ VALUES (?, ?, ?, datetime('now'), ?)
336
+ `).run(keyHash, tier, validUntil, JSON.stringify(features));
337
+ } finally {
338
+ memDb.close();
339
+ }
340
+ }
341
+
342
+ // ============================================================
343
+ // P3-007: getCurrentTier
344
+ // ============================================================
345
+
346
+ /**
347
+ * Get the current user's tier. Cached in-memory for the server process lifetime.
348
+ * If no API key configured, returns 'free'.
349
+ */
350
+ export async function getCurrentTier(): Promise<ToolTier> {
351
+ // Check if in-memory cache is still fresh (15-minute TTL)
352
+ if (cachedTier && (Date.now() - cachedTierTimestamp) < IN_MEMORY_CACHE_TTL_MS) {
353
+ return cachedTier.tier;
354
+ }
355
+
356
+ const config = getConfig();
357
+ const apiKey = config.cloud?.apiKey;
358
+
359
+ if (!apiKey) {
360
+ cachedTier = { tier: 'free', validUntil: '', features: [] };
361
+ cachedTierTimestamp = Date.now();
362
+ return 'free';
363
+ }
364
+
365
+ const info = await validateLicense(apiKey);
366
+ cachedTier = info;
367
+ cachedTierTimestamp = Date.now();
368
+ return info.tier;
369
+ }
370
+
371
+ /**
372
+ * Get full license info (tier, validUntil, features).
373
+ * Triggers getCurrentTier() if not already cached.
374
+ */
375
+ export async function getLicenseInfo(): Promise<LicenseInfo> {
376
+ if (!cachedTier || (Date.now() - cachedTierTimestamp) >= IN_MEMORY_CACHE_TTL_MS) {
377
+ await getCurrentTier();
378
+ }
379
+ return cachedTier!;
380
+ }
381
+
382
+ /**
383
+ * Days remaining until license expires. Returns -1 if no expiry set.
384
+ */
385
+ export async function daysUntilExpiry(): Promise<number> {
386
+ const info = await getLicenseInfo();
387
+ if (!info.validUntil) return -1;
388
+ const expiry = new Date(info.validUntil);
389
+ const now = new Date();
390
+ const diffMs = expiry.getTime() - now.getTime();
391
+ return Math.ceil(diffMs / (1000 * 60 * 60 * 24));
392
+ }
393
+
394
+ // ============================================================
395
+ // P3-021: License Status Tool (3-function pattern)
396
+ // ============================================================
397
+
398
+ /**
399
+ * Tool definitions for the license status tool.
400
+ * Always available (free tier).
401
+ */
402
+ export function getLicenseToolDefinitions(): ToolDefinition[] {
403
+ const pfx = getConfig().toolPrefix;
404
+ return [
405
+ {
406
+ name: `${pfx}_license_status`,
407
+ description: 'Show current license status, tier, features, and upgrade options.',
408
+ inputSchema: {
409
+ type: 'object',
410
+ properties: {},
411
+ required: [],
412
+ },
413
+ },
414
+ ];
415
+ }
416
+
417
+ /**
418
+ * Check if a tool name matches a license tool.
419
+ */
420
+ export function isLicenseTool(name: string): boolean {
421
+ return name.endsWith('_license_status');
422
+ }
423
+
424
+ /**
425
+ * Handle license tool calls.
426
+ */
427
+ export async function handleLicenseToolCall(
428
+ name: string,
429
+ _args: Record<string, unknown>,
430
+ _memDb: import('better-sqlite3').Database
431
+ ): Promise<ToolResult> {
432
+ if (name.endsWith('_license_status')) {
433
+ const info = await getLicenseInfo();
434
+ const days = await daysUntilExpiry();
435
+
436
+ const lines: string[] = [];
437
+ lines.push('## License Status');
438
+ lines.push('');
439
+ lines.push(`**Tier**: ${info.tier.toUpperCase()}`);
440
+
441
+ if (info.validUntil) {
442
+ lines.push(`**Valid Until**: ${info.validUntil}`);
443
+ if (days >= 0) {
444
+ lines.push(`**Days Remaining**: ${days}`);
445
+ }
446
+ }
447
+
448
+ if (info.features.length > 0) {
449
+ lines.push('');
450
+ lines.push('**Features**:');
451
+ for (const f of info.features) {
452
+ lines.push(`- ${f}`);
453
+ }
454
+ }
455
+
456
+ lines.push('');
457
+ lines.push('### Tier Capabilities');
458
+ lines.push('- **Free**: Core navigation, memory, regression detection');
459
+ lines.push('- **Pro**: Knowledge search, quality analytics, cost tracking, observability');
460
+ lines.push('- **Team**: Sentinel feature registry, team knowledge sharing');
461
+ lines.push('- **Enterprise**: Audit trail, security scoring, dependency analysis');
462
+
463
+ if (info.tier === 'free') {
464
+ lines.push('');
465
+ lines.push('Upgrade at https://massu.ai/pricing');
466
+ }
467
+
468
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
469
+ }
470
+
471
+ return { content: [{ type: 'text', text: `Unknown license tool: ${name}` }] };
472
+ }
473
+
474
+ // ============================================================
475
+ // Reset (for testing)
476
+ // ============================================================
477
+
478
+ /** Reset cached tier (for testing only). */
479
+ export function _resetCachedTier(): void {
480
+ cachedTier = null;
481
+ cachedTierTimestamp = 0;
482
+ }