@massu/core 1.10.8 → 1.12.1
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.
- package/CHANGELOG.md +1334 -0
- package/dist/adapter.js +6 -4
- package/dist/cli.js +581 -448
- package/dist/detect/adapters/.bundle-shasums.json +4 -7
- package/dist/detect/adapters/rails.js +6 -4
- package/dist/detect/adapters/spring.js +6 -4
- package/dist/detect/adapters/tree-sitter-loader.js +6 -4
- package/dist/hooks/auto-learning-pipeline.js +24 -1
- package/dist/hooks/classify-failure.js +32 -3
- package/dist/hooks/cost-tracker.js +24 -0
- package/dist/hooks/fix-detector.js +97 -1
- package/dist/hooks/incident-pipeline.js +30 -1
- package/dist/hooks/intent-suggester.js +6 -3
- package/dist/hooks/post-edit-context.js +24 -1
- package/dist/hooks/post-tool-use.js +67 -19
- package/dist/hooks/pre-compact.js +28 -3
- package/dist/hooks/pre-delete-check.js +53 -27
- package/dist/hooks/pre-tool-use-gate.js +962 -0
- package/dist/hooks/quality-event.js +24 -0
- package/dist/hooks/rule-enforcement-pipeline.js +24 -1
- package/dist/hooks/security-gate.js +29 -16
- package/dist/hooks/session-end.js +31 -6
- package/dist/hooks/session-start.js +8709 -8459
- package/dist/hooks/user-prompt.js +25 -1
- package/package.json +15 -18
- package/commands/README.md +0 -134
- package/src/adapter.ts +0 -121
- package/src/adr-generator.ts +0 -292
- package/src/analytics.ts +0 -373
- package/src/audit-trail.ts +0 -450
- package/src/backfill-sessions.ts +0 -182
- package/src/changelog-generator.ts +0 -178
- package/src/cli.ts +0 -246
- package/src/cloud-sync.ts +0 -234
- package/src/commands/adapters.ts +0 -824
- package/src/commands/changelog.ts +0 -165
- package/src/commands/config-check-drift.ts +0 -133
- package/src/commands/config-refresh.ts +0 -396
- package/src/commands/config-upgrade.ts +0 -127
- package/src/commands/doctor.ts +0 -485
- package/src/commands/hook-runner.ts +0 -145
- package/src/commands/init.ts +0 -1823
- package/src/commands/install-commands.ts +0 -692
- package/src/commands/install-hooks.ts +0 -27
- package/src/commands/permissions.ts +0 -150
- package/src/commands/refresh-log.ts +0 -37
- package/src/commands/show-template.ts +0 -65
- package/src/commands/template-engine.ts +0 -301
- package/src/commands/watch.ts +0 -430
- package/src/config.ts +0 -765
- package/src/cost-tracker.ts +0 -355
- package/src/db.ts +0 -370
- package/src/dependency-scorer.ts +0 -337
- package/src/detect/adapters/aspnet.ts +0 -4
- package/src/detect/adapters/discover.ts +0 -469
- package/src/detect/adapters/file-sampler.ts +0 -225
- package/src/detect/adapters/go-chi.ts +0 -4
- package/src/detect/adapters/index.ts +0 -51
- package/src/detect/adapters/nextjs-trpc.ts +0 -166
- package/src/detect/adapters/parse-guard.ts +0 -133
- package/src/detect/adapters/phoenix.ts +0 -4
- package/src/detect/adapters/python-django.ts +0 -208
- package/src/detect/adapters/python-fastapi.ts +0 -223
- package/src/detect/adapters/python-flask.ts +0 -235
- package/src/detect/adapters/query-helpers.ts +0 -170
- package/src/detect/adapters/rails.ts +0 -4
- package/src/detect/adapters/runner.ts +0 -284
- package/src/detect/adapters/spring.ts +0 -4
- package/src/detect/adapters/swift-swiftui.ts +0 -171
- package/src/detect/adapters/tree-sitter-loader.ts +0 -517
- package/src/detect/adapters/types.ts +0 -191
- package/src/detect/codebase-introspector.ts +0 -210
- package/src/detect/domain-inferrer.ts +0 -197
- package/src/detect/drift.ts +0 -199
- package/src/detect/framework-detector.ts +0 -307
- package/src/detect/index.ts +0 -209
- package/src/detect/manifest-registry.ts +0 -261
- package/src/detect/migrate.ts +0 -362
- package/src/detect/monorepo-detector.ts +0 -348
- package/src/detect/package-detector.ts +0 -828
- package/src/detect/passthrough.ts +0 -108
- package/src/detect/regex-fallback.ts +0 -449
- package/src/detect/source-dir-detector.ts +0 -297
- package/src/detect/vr-command-map.ts +0 -167
- package/src/docs-map.json +0 -100
- package/src/docs-tools.ts +0 -519
- package/src/domains.ts +0 -182
- package/src/hooks/auto-learning-pipeline.ts +0 -228
- package/src/hooks/classify-failure.ts +0 -259
- package/src/hooks/cost-tracker.ts +0 -66
- package/src/hooks/fix-detector.ts +0 -186
- package/src/hooks/incident-pipeline.ts +0 -190
- package/src/hooks/intent-suggester.ts +0 -131
- package/src/hooks/post-edit-context.ts +0 -91
- package/src/hooks/post-tool-use.ts +0 -413
- package/src/hooks/pre-compact.ts +0 -168
- package/src/hooks/pre-delete-check.ts +0 -243
- package/src/hooks/quality-event.ts +0 -127
- package/src/hooks/rule-enforcement-pipeline.ts +0 -159
- package/src/hooks/security-gate.ts +0 -153
- package/src/hooks/session-end.ts +0 -467
- package/src/hooks/session-start.ts +0 -436
- package/src/hooks/user-prompt.ts +0 -157
- package/src/import-resolver.ts +0 -226
- package/src/knowledge-db.ts +0 -176
- package/src/knowledge-indexer.ts +0 -708
- package/src/knowledge-tools.ts +0 -1414
- package/src/lib/fileLock.ts +0 -203
- package/src/lib/framework-supports.ts +0 -85
- package/src/lib/gitToplevel.ts +0 -22
- package/src/lib/hook-registry.ts +0 -43
- package/src/lib/installLock.ts +0 -66
- package/src/lib/memory-path.ts +0 -49
- package/src/lib/pidLiveness.ts +0 -67
- package/src/lib/settings-local.ts +0 -110
- package/src/lib/sql-table-names.ts +0 -71
- package/src/license.ts +0 -547
- package/src/lsp/auto-detect.ts +0 -98
- package/src/lsp/client.ts +0 -776
- package/src/lsp/enrich.ts +0 -127
- package/src/lsp/types.ts +0 -221
- package/src/memory-db.ts +0 -1614
- package/src/memory-file-ingest.ts +0 -128
- package/src/memory-tools.ts +0 -424
- package/src/middleware-tree.ts +0 -71
- package/src/observability-tools.ts +0 -343
- package/src/observation-extractor.ts +0 -418
- package/src/page-deps.ts +0 -285
- package/src/permissions.ts +0 -422
- package/src/prompt-analyzer.ts +0 -332
- package/src/python/coupling-detector.ts +0 -125
- package/src/python/domain-enforcer.ts +0 -84
- package/src/python/impact-analyzer.ts +0 -96
- package/src/python/import-parser.ts +0 -244
- package/src/python/import-resolver.ts +0 -136
- package/src/python/migration-indexer.ts +0 -116
- package/src/python/migration-parser.ts +0 -332
- package/src/python/model-indexer.ts +0 -71
- package/src/python/model-parser.ts +0 -279
- package/src/python/route-indexer.ts +0 -59
- package/src/python/route-parser.ts +0 -317
- package/src/python-tools.ts +0 -630
- package/src/regression-detector.ts +0 -319
- package/src/rules.ts +0 -57
- package/src/schema-mapper.ts +0 -232
- package/src/security/adapter-origin.ts +0 -130
- package/src/security/adapter-verifier.ts +0 -319
- package/src/security/atomic-write.ts +0 -164
- package/src/security/fetcher.ts +0 -200
- package/src/security/install-tracking.ts +0 -319
- package/src/security/license-pubkey.generated.ts +0 -30
- package/src/security/license-response-verifier.ts +0 -149
- package/src/security/local-fingerprint.ts +0 -225
- package/src/security/manifest-cache.ts +0 -333
- package/src/security/manifest-schema.ts +0 -129
- package/src/security/registry-pubkey.generated.ts +0 -35
- package/src/security/telemetry.ts +0 -320
- package/src/security-scorer.ts +0 -405
- package/src/security-utils.ts +0 -133
- package/src/sentinel-db.ts +0 -580
- package/src/sentinel-scanner.ts +0 -406
- package/src/sentinel-tools.ts +0 -513
- package/src/sentinel-types.ts +0 -140
- package/src/server-dispatch.ts +0 -225
- package/src/server.ts +0 -107
- package/src/session-archiver.ts +0 -111
- package/src/session-state-generator.ts +0 -174
- package/src/team-knowledge.ts +0 -407
- package/src/tool-db-needs.ts +0 -232
- package/src/tools.ts +0 -1206
- package/src/transcript-parser.ts +0 -458
- package/src/trpc-index.ts +0 -215
- package/src/validate-features-runner.ts +0 -107
- package/src/validation-engine.ts +0 -358
- package/src/watch/daemon.ts +0 -385
- package/src/watch/lockfile-detector.ts +0 -65
- package/src/watch/paths.ts +0 -279
- package/src/watch/state.ts +0 -178
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2026 Massu. All rights reserved.
|
|
2
|
-
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* P-H032 (plan-stage-c-high-batch / 1.10.7): SoT for SQL table names that
|
|
6
|
-
* are prefixed with the customer's configured `toolPrefix`.
|
|
7
|
-
*
|
|
8
|
-
* **Bug class closed**: 20 distinct SQL table names (`massu_imports`,
|
|
9
|
-
* `massu_trpc_procedures`, `massu_sentinel`, etc.) were hardcoded with the
|
|
10
|
-
* `massu_` prefix in ~145 SQL string sites across 21 source files. Tool
|
|
11
|
-
* names were already config-driven via `getConfig().toolPrefix` but
|
|
12
|
-
* persistence wasn't — split-brain where a non-default-prefix customer
|
|
13
|
-
* (e.g. `toolPrefix: 'foo'`) would have tools named `foo_*` but tables
|
|
14
|
-
* still named `massu_*`. Default-prefix customers (100% of current
|
|
15
|
-
* installs) see ZERO behavior change.
|
|
16
|
-
*
|
|
17
|
-
* **Structural fix**: every SQL string that references a prefix-scoped
|
|
18
|
-
* table now goes through `t(suffix)` which returns
|
|
19
|
-
* `\`${getConfig().toolPrefix}_${suffix}\``. The ESLint rule
|
|
20
|
-
* `no-hardcoded-massu-tables` bans bare `'massu_X'` literals so future
|
|
21
|
-
* regressions are impossible. The drift-guard test
|
|
22
|
-
* `sql-table-names-source-grep.test.ts` scans the source tree and asserts
|
|
23
|
-
* zero bare `massu_<table>` literals outside this module + the test files.
|
|
24
|
-
*
|
|
25
|
-
* **20 canonical table suffixes** (registered here as a typed const so
|
|
26
|
-
* typos at callsites are caught at compile time):
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
import { getConfig } from '../config.ts';
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* The complete set of suffixes for prefix-scoped SQL tables. Keep this in
|
|
33
|
-
* sync with `packages/core/src/db.ts` (CodeGraph schema) +
|
|
34
|
-
* `sentinel-db.ts` (sentinel schema) + Python indexer DDL in
|
|
35
|
-
* `packages/core/src/python/*.ts`.
|
|
36
|
-
*/
|
|
37
|
-
export type MassuTableSuffix =
|
|
38
|
-
| 'imports'
|
|
39
|
-
| 'meta'
|
|
40
|
-
| 'middleware_tree'
|
|
41
|
-
| 'page_deps'
|
|
42
|
-
| 'py_fk_edges'
|
|
43
|
-
| 'py_imports'
|
|
44
|
-
| 'py_meta'
|
|
45
|
-
| 'py_migrations'
|
|
46
|
-
| 'py_models'
|
|
47
|
-
| 'py_route_callers'
|
|
48
|
-
| 'py_routes'
|
|
49
|
-
| 'sentinel'
|
|
50
|
-
| 'sentinel_changelog'
|
|
51
|
-
| 'sentinel_components'
|
|
52
|
-
| 'sentinel_deps'
|
|
53
|
-
| 'sentinel_fts'
|
|
54
|
-
| 'sentinel_pages'
|
|
55
|
-
| 'sentinel_procedures'
|
|
56
|
-
| 'trpc_call_sites'
|
|
57
|
-
| 'trpc_procedures';
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Resolve a prefix-scoped table name to its concrete identifier using the
|
|
61
|
-
* current `getConfig().toolPrefix`. For default-prefix installs returns
|
|
62
|
-
* `massu_<suffix>` (current behavior). For custom-prefix installs returns
|
|
63
|
-
* `<customPrefix>_<suffix>`.
|
|
64
|
-
*
|
|
65
|
-
* Use this in EVERY SQL string that references a prefix-scoped table.
|
|
66
|
-
* The ESLint rule bans bare `'massu_X'` literals; the only legitimate
|
|
67
|
-
* exit is this function.
|
|
68
|
-
*/
|
|
69
|
-
export function t(suffix: MassuTableSuffix): string {
|
|
70
|
-
return `${getConfig().toolPrefix}_${suffix}`;
|
|
71
|
-
}
|
package/src/license.ts
DELETED
|
@@ -1,547 +0,0 @@
|
|
|
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
|
-
import { verifyLicenseResponse, isLicenseSignatureRequired } from './security/license-response-verifier.ts';
|
|
21
|
-
|
|
22
|
-
// P-H019 one-shot warning gate. We emit the stderr warning at most once
|
|
23
|
-
// per process lifetime so the customer's terminal isn't spammed every
|
|
24
|
-
// session-start until the operator provisions the Edge Function signing key.
|
|
25
|
-
let _warnedLicenseSig = false;
|
|
26
|
-
function warnLicenseSigOnce(reason: string): void {
|
|
27
|
-
if (_warnedLicenseSig) return;
|
|
28
|
-
_warnedLicenseSig = true;
|
|
29
|
-
process.stderr.write(
|
|
30
|
-
`[massu] WARNING: license-validate response is unsigned or signature invalid (${reason}). ` +
|
|
31
|
-
`Acceptance permitted under transition mode. Operator: provision Supabase Edge Function ` +
|
|
32
|
-
`LICENSE_RESPONSE_SIGNING_PRIVATE_KEY_B64 then set MASSU_REQUIRE_SIGNED_LICENSE=true to enforce strict mode.\n`,
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// ============================================================
|
|
37
|
-
// Types
|
|
38
|
-
// ============================================================
|
|
39
|
-
|
|
40
|
-
export type ToolTier = 'free' | 'pro' | 'team' | 'enterprise';
|
|
41
|
-
|
|
42
|
-
// ============================================================
|
|
43
|
-
// Tier Ordering (for comparison)
|
|
44
|
-
// ============================================================
|
|
45
|
-
|
|
46
|
-
const TIER_LEVELS: Record<ToolTier, number> = {
|
|
47
|
-
free: 0,
|
|
48
|
-
pro: 1,
|
|
49
|
-
team: 2,
|
|
50
|
-
enterprise: 3,
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
/** Return numeric level for tier comparison. Higher = more permissive. */
|
|
54
|
-
export function tierLevel(tier: ToolTier): number {
|
|
55
|
-
return TIER_LEVELS[tier] ?? 0;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// ============================================================
|
|
59
|
-
// P3-002: Tool Tier Map
|
|
60
|
-
// ============================================================
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Maps every tool base name (without prefix) to its required tier.
|
|
64
|
-
* Tools not in this map default to 'free'.
|
|
65
|
-
*
|
|
66
|
-
* Free: core navigation + basic memory + regression
|
|
67
|
-
* Pro: knowledge, quality, cost, prompt, validation, ADR, observability, docs
|
|
68
|
-
* Team: sentinel, team knowledge
|
|
69
|
-
* Enterprise: audit, security, dependency
|
|
70
|
-
*/
|
|
71
|
-
export const TOOL_TIER_MAP: Record<string, ToolTier> = {
|
|
72
|
-
// --- Free tier (12 tools: core navigation + basic memory + regression + license) ---
|
|
73
|
-
sync: 'free',
|
|
74
|
-
context: 'free',
|
|
75
|
-
impact: 'free',
|
|
76
|
-
domains: 'free',
|
|
77
|
-
schema: 'free',
|
|
78
|
-
trpc_map: 'free',
|
|
79
|
-
coupling_check: 'free',
|
|
80
|
-
memory_search: 'free',
|
|
81
|
-
memory_ingest: 'free',
|
|
82
|
-
regression_risk: 'free',
|
|
83
|
-
feature_health: 'free',
|
|
84
|
-
license_status: 'free',
|
|
85
|
-
|
|
86
|
-
// --- Pro tier (35 tools: knowledge, quality, cost, prompt, validation, ADR, observability, docs, advanced memory) ---
|
|
87
|
-
memory_timeline: 'pro',
|
|
88
|
-
memory_detail: 'pro',
|
|
89
|
-
memory_sessions: 'pro',
|
|
90
|
-
memory_failures: 'pro',
|
|
91
|
-
knowledge_search: 'pro',
|
|
92
|
-
knowledge_rule: 'pro',
|
|
93
|
-
knowledge_incident: 'pro',
|
|
94
|
-
knowledge_schema_check: 'pro',
|
|
95
|
-
knowledge_pattern: 'pro',
|
|
96
|
-
knowledge_verification: 'pro',
|
|
97
|
-
knowledge_graph: 'pro',
|
|
98
|
-
knowledge_command: 'pro',
|
|
99
|
-
knowledge_correct: 'pro',
|
|
100
|
-
knowledge_plan: 'pro',
|
|
101
|
-
knowledge_gaps: 'pro',
|
|
102
|
-
knowledge_effectiveness: 'pro',
|
|
103
|
-
quality_score: 'pro',
|
|
104
|
-
quality_trend: 'pro',
|
|
105
|
-
quality_report: 'pro',
|
|
106
|
-
cost_session: 'pro',
|
|
107
|
-
cost_trend: 'pro',
|
|
108
|
-
cost_feature: 'pro',
|
|
109
|
-
prompt_effectiveness: 'pro',
|
|
110
|
-
prompt_suggestions: 'pro',
|
|
111
|
-
validation_check: 'pro',
|
|
112
|
-
validation_report: 'pro',
|
|
113
|
-
adr_list: 'pro',
|
|
114
|
-
adr_detail: 'pro',
|
|
115
|
-
adr_create: 'pro',
|
|
116
|
-
session_replay: 'pro',
|
|
117
|
-
prompt_analysis: 'pro',
|
|
118
|
-
tool_patterns: 'pro',
|
|
119
|
-
session_stats: 'pro',
|
|
120
|
-
docs_audit: 'pro',
|
|
121
|
-
docs_coverage: 'pro',
|
|
122
|
-
|
|
123
|
-
// Pro tier — Python code intelligence
|
|
124
|
-
py_imports: 'pro',
|
|
125
|
-
py_routes: 'pro',
|
|
126
|
-
py_coupling: 'pro',
|
|
127
|
-
py_models: 'pro',
|
|
128
|
-
py_migrations: 'pro',
|
|
129
|
-
py_domains: 'pro',
|
|
130
|
-
py_impact: 'pro',
|
|
131
|
-
py_context: 'pro',
|
|
132
|
-
|
|
133
|
-
// --- Team tier (9 tools: sentinel feature registry + team knowledge) ---
|
|
134
|
-
sentinel_search: 'team',
|
|
135
|
-
sentinel_detail: 'team',
|
|
136
|
-
sentinel_impact: 'team',
|
|
137
|
-
sentinel_validate: 'team',
|
|
138
|
-
sentinel_register: 'team',
|
|
139
|
-
sentinel_parity: 'team',
|
|
140
|
-
team_search: 'team',
|
|
141
|
-
team_expertise: 'team',
|
|
142
|
-
team_conflicts: 'team',
|
|
143
|
-
|
|
144
|
-
// --- Enterprise tier (8 tools: audit trail + security scoring + dependency analysis) ---
|
|
145
|
-
audit_log: 'enterprise',
|
|
146
|
-
audit_report: 'enterprise',
|
|
147
|
-
audit_chain: 'enterprise',
|
|
148
|
-
security_score: 'enterprise',
|
|
149
|
-
security_heatmap: 'enterprise',
|
|
150
|
-
security_trend: 'enterprise',
|
|
151
|
-
dep_score: 'enterprise',
|
|
152
|
-
dep_alternatives: 'enterprise',
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
// ============================================================
|
|
156
|
-
// P3-002: Plan-to-tier mapping (from organizations.plan values)
|
|
157
|
-
// ============================================================
|
|
158
|
-
|
|
159
|
-
export const PLAN_TO_TIER_MAP: Record<string, ToolTier> = {
|
|
160
|
-
free: 'free',
|
|
161
|
-
cloud_pro: 'pro',
|
|
162
|
-
cloud_team: 'team',
|
|
163
|
-
cloud_enterprise: 'enterprise',
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
// ============================================================
|
|
167
|
-
// P3-003: getToolTier
|
|
168
|
-
// ============================================================
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Get the required tier for a tool by name.
|
|
172
|
-
* Strips the configured prefix, looks up in TOOL_TIER_MAP, defaults to 'free'.
|
|
173
|
-
*/
|
|
174
|
-
export function getToolTier(name: string): ToolTier {
|
|
175
|
-
const pfx = getConfig().toolPrefix + '_';
|
|
176
|
-
const baseName = name.startsWith(pfx) ? name.slice(pfx.length) : name;
|
|
177
|
-
return TOOL_TIER_MAP[baseName] ?? 'free';
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// ============================================================
|
|
181
|
-
// P3-001: isToolAllowed
|
|
182
|
-
// ============================================================
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Check if a tool is accessible at the given user tier.
|
|
186
|
-
* A user can access tools at their tier level or below.
|
|
187
|
-
*/
|
|
188
|
-
export function isToolAllowed(toolName: string, userTier: ToolTier): boolean {
|
|
189
|
-
const requiredTier = getToolTier(toolName);
|
|
190
|
-
return tierLevel(userTier) >= tierLevel(requiredTier);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// ============================================================
|
|
194
|
-
// P3-004: annotateToolDefinitions
|
|
195
|
-
// ============================================================
|
|
196
|
-
|
|
197
|
-
const TIER_LABELS: Record<ToolTier, string> = {
|
|
198
|
-
free: '',
|
|
199
|
-
pro: '[PRO] ',
|
|
200
|
-
team: '[TEAM] ',
|
|
201
|
-
enterprise: '[ENTERPRISE] ',
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Annotate tool definitions with tier labels in descriptions.
|
|
206
|
-
* Stores the structured tier under `annotations.tier` (MCP-spec-sanctioned
|
|
207
|
-
* extension point) — never as a top-level field, which violates the
|
|
208
|
-
* canonical Tool schema (schema/2025-11-25/schema.ts line 1251) and is
|
|
209
|
-
* silently rejected by Claude Code 2.1.143+ (massu-ai/massu#4).
|
|
210
|
-
* Free tools get no label prefix.
|
|
211
|
-
*/
|
|
212
|
-
export function annotateToolDefinitions(defs: ToolDefinition[]): ToolDefinition[] {
|
|
213
|
-
return defs.map(def => {
|
|
214
|
-
const tier = getToolTier(def.name);
|
|
215
|
-
const label = TIER_LABELS[tier];
|
|
216
|
-
return {
|
|
217
|
-
...def,
|
|
218
|
-
annotations: { ...(def.annotations ?? {}), tier },
|
|
219
|
-
description: label ? `${label}${def.description}` : def.description,
|
|
220
|
-
};
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// ============================================================
|
|
225
|
-
// plan-1.7.0-cohesive-cleanup P-A-003: Cloud feature availability gate
|
|
226
|
-
// ============================================================
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Whether cloud-gated tool surfaces (team knowledge, etc.) are exposed.
|
|
230
|
-
*
|
|
231
|
-
* Returns true ONLY when `massu.config.yaml` opts the workspace into the
|
|
232
|
-
* cloud feature surface via `cloud.enabled: true`. Defaults to false for
|
|
233
|
-
* fresh installs (the schema's `enabled` default is `false`).
|
|
234
|
-
*
|
|
235
|
-
* This is distinct from {@link isLicenseTool} (which matches tool NAMES);
|
|
236
|
-
* `isCloudFeatureAvailable` is a runtime feature-availability check used
|
|
237
|
-
* by `tools.ts` to gate team-tool registration and routing at the
|
|
238
|
-
* tools-list and dispatch boundaries.
|
|
239
|
-
*/
|
|
240
|
-
export function isCloudFeatureAvailable(): boolean {
|
|
241
|
-
return getConfig().cloud?.enabled === true;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// ============================================================
|
|
245
|
-
// P3-005/P3-006/P3-007/P3-013: License validation & caching
|
|
246
|
-
// ============================================================
|
|
247
|
-
|
|
248
|
-
interface LicenseInfo {
|
|
249
|
-
tier: ToolTier;
|
|
250
|
-
validUntil: string;
|
|
251
|
-
features: string[];
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/** In-memory cache for the current session. Refreshes every 15 minutes. */
|
|
255
|
-
let cachedTier: LicenseInfo | null = null;
|
|
256
|
-
let cachedTierTimestamp: number = 0;
|
|
257
|
-
const IN_MEMORY_CACHE_TTL_MS = 15 * 60 * 1000; // 15 minutes
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Validate a license key against the cloud endpoint.
|
|
261
|
-
* Uses local cache in memory.db with 1-hour freshness window.
|
|
262
|
-
* Performs async cloud validation via fetch() (Node 18+).
|
|
263
|
-
* Falls back to 7-day grace period on network failure.
|
|
264
|
-
*/
|
|
265
|
-
export async function validateLicense(apiKey: string): Promise<LicenseInfo> {
|
|
266
|
-
const keyHash = createHash('sha256').update(apiKey).digest('hex');
|
|
267
|
-
|
|
268
|
-
// 1. Check local cache
|
|
269
|
-
const memDb = getMemoryDb();
|
|
270
|
-
try {
|
|
271
|
-
const cached = memDb.prepare(
|
|
272
|
-
'SELECT tier, valid_until, last_validated, features FROM license_cache WHERE api_key_hash = ?'
|
|
273
|
-
).get(keyHash) as { tier: string; valid_until: string; last_validated: string; features: string } | undefined;
|
|
274
|
-
|
|
275
|
-
if (cached) {
|
|
276
|
-
const lastValidated = new Date(cached.last_validated);
|
|
277
|
-
const hourAgo = new Date(Date.now() - 60 * 60 * 1000);
|
|
278
|
-
|
|
279
|
-
// Cache is fresh (< 1 hour old)
|
|
280
|
-
if (lastValidated > hourAgo) {
|
|
281
|
-
return {
|
|
282
|
-
tier: cached.tier as ToolTier,
|
|
283
|
-
validUntil: cached.valid_until,
|
|
284
|
-
features: JSON.parse(cached.features || '[]'),
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// 2. Try cloud validation via fetch (Node 18+ has native fetch)
|
|
290
|
-
const config = getConfig();
|
|
291
|
-
const endpoint = config.cloud?.endpoint;
|
|
292
|
-
|
|
293
|
-
if (endpoint && /^https?:\/\/.+/.test(endpoint)) {
|
|
294
|
-
try {
|
|
295
|
-
const response = await fetch(`${endpoint}/validate-key`, {
|
|
296
|
-
method: 'POST',
|
|
297
|
-
headers: {
|
|
298
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
299
|
-
'Content-Type': 'application/json',
|
|
300
|
-
},
|
|
301
|
-
signal: AbortSignal.timeout(10_000), // 10s timeout
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
if (response.ok) {
|
|
305
|
-
const data = await response.json() as {
|
|
306
|
-
valid: boolean;
|
|
307
|
-
plan?: string;
|
|
308
|
-
tier?: string;
|
|
309
|
-
validUntil?: string;
|
|
310
|
-
features?: string[];
|
|
311
|
-
reason?: string;
|
|
312
|
-
_signature?: string;
|
|
313
|
-
_signature_alg?: string;
|
|
314
|
-
_signature_payload_keys?: readonly string[];
|
|
315
|
-
_signature_pubkey_fingerprint?: string;
|
|
316
|
-
};
|
|
317
|
-
|
|
318
|
-
// P-H019 (plan-stage-c-high-batch / 1.10.5): verify Ed25519
|
|
319
|
-
// signature on the validate-key response. Closes the bug class
|
|
320
|
-
// where MITM / malicious-cloud-endpoint / local SQLite edit
|
|
321
|
-
// could grant arbitrary tier.
|
|
322
|
-
//
|
|
323
|
-
// Transition mode (default): unsigned/invalid-sig responses are
|
|
324
|
-
// accepted with a one-shot stderr warning. Lets existing customers
|
|
325
|
-
// keep working while operators provision the Edge Function signing
|
|
326
|
-
// key.
|
|
327
|
-
//
|
|
328
|
-
// Strict mode (MASSU_REQUIRE_SIGNED_LICENSE=true, post-cutover):
|
|
329
|
-
// unsigned/invalid-sig responses are rejected; caller falls
|
|
330
|
-
// through to the grace-period cache or free tier.
|
|
331
|
-
const sigResult = verifyLicenseResponse(data);
|
|
332
|
-
if (sigResult.kind !== 'valid') {
|
|
333
|
-
if (isLicenseSignatureRequired()) {
|
|
334
|
-
// Strict mode — reject. Caller falls through to grace period.
|
|
335
|
-
throw new Error(`License response signature invalid: ${sigResult.kind}`);
|
|
336
|
-
}
|
|
337
|
-
// Transition mode — one-shot stderr warning per session.
|
|
338
|
-
warnLicenseSigOnce(sigResult.kind);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
if (data.valid) {
|
|
342
|
-
// Map plan name to tier using PLAN_TO_TIER_MAP
|
|
343
|
-
const tier: ToolTier = data.plan
|
|
344
|
-
? (PLAN_TO_TIER_MAP[data.plan] ?? 'free')
|
|
345
|
-
: (data.tier as ToolTier ?? 'free');
|
|
346
|
-
const validUntil = data.validUntil ?? '';
|
|
347
|
-
const features = data.features ?? [];
|
|
348
|
-
|
|
349
|
-
// Update local cache
|
|
350
|
-
updateLicenseCache(apiKey, tier, validUntil, features);
|
|
351
|
-
|
|
352
|
-
return { tier, validUntil, features };
|
|
353
|
-
}
|
|
354
|
-
// Server said key is not valid — return free tier
|
|
355
|
-
return { tier: 'free', validUntil: '', features: [] };
|
|
356
|
-
}
|
|
357
|
-
// Non-OK response — fall through to grace period
|
|
358
|
-
} catch {
|
|
359
|
-
// Network failure — fall through to grace period check
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// 3. Grace period: cache exists but stale (up to 7 days)
|
|
364
|
-
if (cached) {
|
|
365
|
-
const lastValidated = new Date(cached.last_validated);
|
|
366
|
-
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
|
|
367
|
-
|
|
368
|
-
// P3-013: 7-day grace period
|
|
369
|
-
if (lastValidated > sevenDaysAgo) {
|
|
370
|
-
return {
|
|
371
|
-
tier: cached.tier as ToolTier,
|
|
372
|
-
validUntil: cached.valid_until,
|
|
373
|
-
features: JSON.parse(cached.features || '[]'),
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// 4. No valid cache — default to free
|
|
379
|
-
return { tier: 'free', validUntil: '', features: [] };
|
|
380
|
-
} finally {
|
|
381
|
-
memDb.close();
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
/**
|
|
386
|
-
* Update the license cache in memory.db.
|
|
387
|
-
* Called by the session-start hook after async cloud validation.
|
|
388
|
-
*/
|
|
389
|
-
export function updateLicenseCache(
|
|
390
|
-
apiKey: string,
|
|
391
|
-
tier: ToolTier,
|
|
392
|
-
validUntil: string,
|
|
393
|
-
features: string[] = []
|
|
394
|
-
): void {
|
|
395
|
-
const keyHash = createHash('sha256').update(apiKey).digest('hex');
|
|
396
|
-
const memDb = getMemoryDb();
|
|
397
|
-
try {
|
|
398
|
-
memDb.prepare(`
|
|
399
|
-
INSERT OR REPLACE INTO license_cache (api_key_hash, tier, valid_until, last_validated, features)
|
|
400
|
-
VALUES (?, ?, ?, datetime('now'), ?)
|
|
401
|
-
`).run(keyHash, tier, validUntil, JSON.stringify(features));
|
|
402
|
-
} finally {
|
|
403
|
-
memDb.close();
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// ============================================================
|
|
408
|
-
// P3-007: getCurrentTier
|
|
409
|
-
// ============================================================
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* Get the current user's tier. Cached in-memory for the server process lifetime.
|
|
413
|
-
* If no API key configured, returns 'free'.
|
|
414
|
-
*/
|
|
415
|
-
export async function getCurrentTier(): Promise<ToolTier> {
|
|
416
|
-
// Check if in-memory cache is still fresh (15-minute TTL)
|
|
417
|
-
if (cachedTier && (Date.now() - cachedTierTimestamp) < IN_MEMORY_CACHE_TTL_MS) {
|
|
418
|
-
return cachedTier.tier;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
const config = getConfig();
|
|
422
|
-
const apiKey = config.cloud?.apiKey;
|
|
423
|
-
|
|
424
|
-
if (!apiKey) {
|
|
425
|
-
cachedTier = { tier: 'free', validUntil: '', features: [] };
|
|
426
|
-
cachedTierTimestamp = Date.now();
|
|
427
|
-
return 'free';
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
const info = await validateLicense(apiKey);
|
|
431
|
-
cachedTier = info;
|
|
432
|
-
cachedTierTimestamp = Date.now();
|
|
433
|
-
return info.tier;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
/**
|
|
437
|
-
* Get full license info (tier, validUntil, features).
|
|
438
|
-
* Triggers getCurrentTier() if not already cached.
|
|
439
|
-
*/
|
|
440
|
-
export async function getLicenseInfo(): Promise<LicenseInfo> {
|
|
441
|
-
if (!cachedTier || (Date.now() - cachedTierTimestamp) >= IN_MEMORY_CACHE_TTL_MS) {
|
|
442
|
-
await getCurrentTier();
|
|
443
|
-
}
|
|
444
|
-
return cachedTier!;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
/**
|
|
448
|
-
* Days remaining until license expires. Returns -1 if no expiry set.
|
|
449
|
-
*/
|
|
450
|
-
export async function daysUntilExpiry(): Promise<number> {
|
|
451
|
-
const info = await getLicenseInfo();
|
|
452
|
-
if (!info.validUntil) return -1;
|
|
453
|
-
const expiry = new Date(info.validUntil);
|
|
454
|
-
const now = new Date();
|
|
455
|
-
const diffMs = expiry.getTime() - now.getTime();
|
|
456
|
-
return Math.ceil(diffMs / (1000 * 60 * 60 * 24));
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// ============================================================
|
|
460
|
-
// P3-021: License Status Tool (3-function pattern)
|
|
461
|
-
// ============================================================
|
|
462
|
-
|
|
463
|
-
/**
|
|
464
|
-
* Tool definitions for the license status tool.
|
|
465
|
-
* Always available (free tier).
|
|
466
|
-
*/
|
|
467
|
-
export function getLicenseToolDefinitions(): ToolDefinition[] {
|
|
468
|
-
const pfx = getConfig().toolPrefix;
|
|
469
|
-
return [
|
|
470
|
-
{
|
|
471
|
-
name: `${pfx}_license_status`,
|
|
472
|
-
description: 'Show current license status, tier, features, and upgrade options.',
|
|
473
|
-
inputSchema: {
|
|
474
|
-
type: 'object',
|
|
475
|
-
properties: {},
|
|
476
|
-
required: [],
|
|
477
|
-
},
|
|
478
|
-
},
|
|
479
|
-
];
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
/**
|
|
483
|
-
* Check if a tool name matches a license tool.
|
|
484
|
-
*/
|
|
485
|
-
export function isLicenseTool(name: string): boolean {
|
|
486
|
-
return name.endsWith('_license_status');
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
/**
|
|
490
|
-
* Handle license tool calls.
|
|
491
|
-
*/
|
|
492
|
-
export async function handleLicenseToolCall(
|
|
493
|
-
name: string,
|
|
494
|
-
_args: Record<string, unknown>,
|
|
495
|
-
_memDb: import('better-sqlite3').Database
|
|
496
|
-
): Promise<ToolResult> {
|
|
497
|
-
if (name.endsWith('_license_status')) {
|
|
498
|
-
const info = await getLicenseInfo();
|
|
499
|
-
const days = await daysUntilExpiry();
|
|
500
|
-
|
|
501
|
-
const lines: string[] = [];
|
|
502
|
-
lines.push('## License Status');
|
|
503
|
-
lines.push('');
|
|
504
|
-
lines.push(`**Tier**: ${info.tier.toUpperCase()}`);
|
|
505
|
-
|
|
506
|
-
if (info.validUntil) {
|
|
507
|
-
lines.push(`**Valid Until**: ${info.validUntil}`);
|
|
508
|
-
if (days >= 0) {
|
|
509
|
-
lines.push(`**Days Remaining**: ${days}`);
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
if (info.features.length > 0) {
|
|
514
|
-
lines.push('');
|
|
515
|
-
lines.push('**Features**:');
|
|
516
|
-
for (const f of info.features) {
|
|
517
|
-
lines.push(`- ${f}`);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
lines.push('');
|
|
522
|
-
lines.push('### Tier Capabilities');
|
|
523
|
-
lines.push('- **Free**: Core navigation, memory, regression detection');
|
|
524
|
-
lines.push('- **Pro**: Knowledge search, quality analytics, cost tracking, observability');
|
|
525
|
-
lines.push('- **Team**: Sentinel feature registry, team knowledge sharing');
|
|
526
|
-
lines.push('- **Enterprise**: Audit trail, security scoring, dependency analysis');
|
|
527
|
-
|
|
528
|
-
if (info.tier === 'free') {
|
|
529
|
-
lines.push('');
|
|
530
|
-
lines.push('Upgrade at https://massu.ai/pricing');
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
return { content: [{ type: 'text', text: `Unknown license tool: ${name}` }] };
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
// ============================================================
|
|
540
|
-
// Reset (for testing)
|
|
541
|
-
// ============================================================
|
|
542
|
-
|
|
543
|
-
/** Reset cached tier (for testing only). */
|
|
544
|
-
export function _resetCachedTier(): void {
|
|
545
|
-
cachedTier = null;
|
|
546
|
-
cachedTierTimestamp = 0;
|
|
547
|
-
}
|