@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
package/src/detect/drift.ts
DELETED
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2026 Massu. All rights reserved.
|
|
2
|
-
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Drift detection logic (P7-004).
|
|
6
|
-
*
|
|
7
|
-
* Phase 5 runtime (drift runner / auto-rerun) is NOT in the MVP cut, but the
|
|
8
|
-
* pure detection LOGIC is tested here so the runtime layer lands without bugs.
|
|
9
|
-
*
|
|
10
|
-
* Two primary entry points:
|
|
11
|
-
*
|
|
12
|
-
* 1. `computeFingerprint(detectionResult)` — deterministic SHA-256 of the
|
|
13
|
-
* normalized JSON form of (languages, frameworks, source_dirs, manifest
|
|
14
|
-
* paths sorted). Any meaningful change in the detected stack changes the
|
|
15
|
-
* fingerprint.
|
|
16
|
-
*
|
|
17
|
-
* 2. `detectDrift(currentConfig, actualDetection)` — returns
|
|
18
|
-
* `{ drifted: boolean, changes: Array<{field, before, after}> }`.
|
|
19
|
-
*
|
|
20
|
-
* No filesystem / network / child_process.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import { createHash } from 'crypto';
|
|
24
|
-
import type { AnyConfig } from './migrate.ts';
|
|
25
|
-
import type { DetectionResult, SupportedLanguage } from './index.ts';
|
|
26
|
-
|
|
27
|
-
export interface DriftChange {
|
|
28
|
-
field: string;
|
|
29
|
-
before: unknown;
|
|
30
|
-
after: unknown;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface DriftReport {
|
|
34
|
-
drifted: boolean;
|
|
35
|
-
changes: DriftChange[];
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** Stable-sorted, normalized summary of a DetectionResult used for hashing. */
|
|
39
|
-
interface DetectionFingerprintData {
|
|
40
|
-
languages: string[];
|
|
41
|
-
frameworks: Record<string, { framework: string | null; test_framework: string | null; orm: string | null }>;
|
|
42
|
-
source_dirs: Record<string, string[]>;
|
|
43
|
-
manifests: string[];
|
|
44
|
-
monorepo: string;
|
|
45
|
-
workspaces: string[];
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function summarizeDetection(det: DetectionResult): DetectionFingerprintData {
|
|
49
|
-
const languages = Array.from(new Set(det.manifests.map((m) => m.language))).sort();
|
|
50
|
-
const frameworks: DetectionFingerprintData['frameworks'] = {};
|
|
51
|
-
for (const lang of languages) {
|
|
52
|
-
const fw = det.frameworks[lang as SupportedLanguage];
|
|
53
|
-
frameworks[lang] = {
|
|
54
|
-
framework: fw?.framework ?? null,
|
|
55
|
-
test_framework: fw?.test_framework ?? null,
|
|
56
|
-
orm: fw?.orm ?? null,
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
const sourceDirs: Record<string, string[]> = {};
|
|
60
|
-
for (const lang of languages) {
|
|
61
|
-
const info = det.sourceDirs[lang as SupportedLanguage];
|
|
62
|
-
sourceDirs[lang] = [...(info?.source_dirs ?? [])].sort();
|
|
63
|
-
}
|
|
64
|
-
const manifests = [...det.manifests.map((m) => m.relativePath)].sort();
|
|
65
|
-
const workspaces = [...det.monorepo.packages.map((p) => p.path)].sort();
|
|
66
|
-
return {
|
|
67
|
-
languages,
|
|
68
|
-
frameworks,
|
|
69
|
-
source_dirs: sourceDirs,
|
|
70
|
-
manifests,
|
|
71
|
-
monorepo: det.monorepo.type,
|
|
72
|
-
workspaces,
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/** Deterministic SHA-256 of a detection result. Stable across runs for the
|
|
77
|
-
* same inputs. */
|
|
78
|
-
export function computeFingerprint(det: DetectionResult): string {
|
|
79
|
-
const data = summarizeDetection(det);
|
|
80
|
-
const stable = JSON.stringify(data, Object.keys(data).sort());
|
|
81
|
-
return createHash('sha256').update(stable).digest('hex');
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function stringOf(v: unknown): string | null {
|
|
85
|
-
if (typeof v === 'string') return v;
|
|
86
|
-
if (v === null || v === undefined) return null;
|
|
87
|
-
return String(v);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Compute drift between a currently-loaded config and a freshly-run detection.
|
|
92
|
-
*
|
|
93
|
-
* A non-empty `changes[]` means fields in the config no longer agree with
|
|
94
|
-
* what the repository actually contains. Callers decide whether to auto-repair
|
|
95
|
-
* (via `migrateV1ToV2`) or surface to the user.
|
|
96
|
-
*/
|
|
97
|
-
export function detectDrift(
|
|
98
|
-
currentConfig: AnyConfig,
|
|
99
|
-
actualDetection: DetectionResult
|
|
100
|
-
): DriftReport {
|
|
101
|
-
const changes: DriftChange[] = [];
|
|
102
|
-
|
|
103
|
-
const configFw = (currentConfig.framework && typeof currentConfig.framework === 'object')
|
|
104
|
-
? (currentConfig.framework as Record<string, unknown>)
|
|
105
|
-
: {};
|
|
106
|
-
const configLanguages = (configFw.languages && typeof configFw.languages === 'object')
|
|
107
|
-
? (configFw.languages as Record<string, Record<string, unknown>>)
|
|
108
|
-
: {};
|
|
109
|
-
|
|
110
|
-
const detectedLanguages = Array.from(
|
|
111
|
-
new Set(actualDetection.manifests.map((m) => m.language))
|
|
112
|
-
) as SupportedLanguage[];
|
|
113
|
-
|
|
114
|
-
const configLangKeys = Object.keys(configLanguages).sort();
|
|
115
|
-
const detectedLangKeys = [...detectedLanguages].sort();
|
|
116
|
-
|
|
117
|
-
// 1. Language set drift.
|
|
118
|
-
if (JSON.stringify(configLangKeys) !== JSON.stringify(detectedLangKeys)) {
|
|
119
|
-
changes.push({
|
|
120
|
-
field: 'framework.languages',
|
|
121
|
-
before: configLangKeys,
|
|
122
|
-
after: detectedLangKeys,
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// 2. Per-language framework/test_framework drift.
|
|
127
|
-
for (const lang of detectedLanguages) {
|
|
128
|
-
const detFw = actualDetection.frameworks[lang];
|
|
129
|
-
const cfgEntry = configLanguages[lang];
|
|
130
|
-
if (!cfgEntry) continue;
|
|
131
|
-
const cfgFramework = stringOf(cfgEntry.framework);
|
|
132
|
-
const detFramework = detFw?.framework ?? null;
|
|
133
|
-
if (cfgFramework !== detFramework && detFramework !== null) {
|
|
134
|
-
changes.push({
|
|
135
|
-
field: `framework.languages.${lang}.framework`,
|
|
136
|
-
before: cfgFramework,
|
|
137
|
-
after: detFramework,
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
const cfgTest = stringOf(cfgEntry.test_framework);
|
|
141
|
-
const detTest = detFw?.test_framework ?? null;
|
|
142
|
-
if (cfgTest !== detTest && detTest !== null) {
|
|
143
|
-
changes.push({
|
|
144
|
-
field: `framework.languages.${lang}.test_framework`,
|
|
145
|
-
before: cfgTest,
|
|
146
|
-
after: detTest,
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// 3. Manifest set drift: new/removed manifest files.
|
|
152
|
-
const detectedManifestPaths = new Set(actualDetection.manifests.map((m) => m.relativePath));
|
|
153
|
-
const declaredManifestPaths = new Set<string>();
|
|
154
|
-
// v2 configs don't record manifest paths directly; we look at
|
|
155
|
-
// `canonical_paths.manifest_paths` (string comma-separated) OR a `manifests`
|
|
156
|
-
// top-level array. When neither is present we skip this check.
|
|
157
|
-
const canonical = currentConfig.canonical_paths as Record<string, unknown> | undefined;
|
|
158
|
-
if (canonical && typeof canonical.manifest_paths === 'string') {
|
|
159
|
-
for (const p of (canonical.manifest_paths as string).split(',').map((s) => s.trim())) {
|
|
160
|
-
if (p) declaredManifestPaths.add(p);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
if (Array.isArray(currentConfig.manifests)) {
|
|
164
|
-
for (const p of currentConfig.manifests as unknown[]) {
|
|
165
|
-
if (typeof p === 'string') declaredManifestPaths.add(p);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
if (declaredManifestPaths.size > 0) {
|
|
169
|
-
const added = [...detectedManifestPaths].filter((p) => !declaredManifestPaths.has(p)).sort();
|
|
170
|
-
const removed = [...declaredManifestPaths].filter((p) => !detectedManifestPaths.has(p)).sort();
|
|
171
|
-
if (added.length > 0) {
|
|
172
|
-
changes.push({ field: 'manifests.added', before: [], after: added });
|
|
173
|
-
}
|
|
174
|
-
if (removed.length > 0) {
|
|
175
|
-
changes.push({ field: 'manifests.removed', before: removed, after: [] });
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// 4. Monorepo workspace set drift.
|
|
180
|
-
const configWorkspaces: string[] = [];
|
|
181
|
-
if (Array.isArray((currentConfig.monorepo as Record<string, unknown> | undefined)?.workspaces)) {
|
|
182
|
-
for (const w of ((currentConfig.monorepo as Record<string, unknown>).workspaces as unknown[])) {
|
|
183
|
-
if (typeof w === 'string') configWorkspaces.push(w);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
const detectedWorkspaces = actualDetection.monorepo.packages.map((p) => p.path).sort();
|
|
187
|
-
if (configWorkspaces.length > 0) {
|
|
188
|
-
const cfgSorted = [...configWorkspaces].sort();
|
|
189
|
-
if (JSON.stringify(cfgSorted) !== JSON.stringify(detectedWorkspaces)) {
|
|
190
|
-
changes.push({
|
|
191
|
-
field: 'monorepo.workspaces',
|
|
192
|
-
before: cfgSorted,
|
|
193
|
-
after: detectedWorkspaces,
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return { drifted: changes.length > 0, changes };
|
|
199
|
-
}
|
|
@@ -1,307 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2026 Massu. All rights reserved.
|
|
2
|
-
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Framework Detector (P1-002)
|
|
6
|
-
* ============================
|
|
7
|
-
*
|
|
8
|
-
* Takes `PackageManifest[]` from P1-001 and infers each language's web
|
|
9
|
-
* framework, test framework, ORM, and UI library by matching declared
|
|
10
|
-
* dependencies against an inline `DETECTION_RULES` table.
|
|
11
|
-
*
|
|
12
|
-
* The `DETECTION_RULES` table ships as built-ins. Users may ADD extra entries
|
|
13
|
-
* via `massu.config.yaml` under `detection.rules[<language>][<framework>]`
|
|
14
|
-
* (see P2-008). With `detection.disable_builtin: true`, user entries replace
|
|
15
|
-
* built-ins entirely.
|
|
16
|
-
*
|
|
17
|
-
* Usage:
|
|
18
|
-
* ```ts
|
|
19
|
-
* import { detectFrameworks } from './detect/framework-detector.ts';
|
|
20
|
-
* const map = detectFrameworks(manifests, config.detection);
|
|
21
|
-
* map.python // => { framework: 'fastapi', test_framework: 'pytest', orm: 'sqlalchemy', ... }
|
|
22
|
-
* ```
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
import type { PackageManifest, SupportedLanguage } from './package-detector.ts';
|
|
26
|
-
|
|
27
|
-
export interface FrameworkInfo {
|
|
28
|
-
/** Inferred framework name (e.g., 'fastapi', 'next', 'actix-web'). */
|
|
29
|
-
framework: string | null;
|
|
30
|
-
/** Framework version if declared in manifest. */
|
|
31
|
-
version: string | null;
|
|
32
|
-
/** Inferred test framework (e.g., 'pytest', 'vitest', 'cargo'). */
|
|
33
|
-
test_framework: string | null;
|
|
34
|
-
/** Inferred ORM. */
|
|
35
|
-
orm: string | null;
|
|
36
|
-
/** Inferred UI library (for TS/JS). */
|
|
37
|
-
ui_library: string | null;
|
|
38
|
-
/** Inferred router (for TS/JS — trpc/graphql/express/fastify). */
|
|
39
|
-
router: string | null;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export type FrameworkMap = Partial<Record<SupportedLanguage, FrameworkInfo>>;
|
|
43
|
-
|
|
44
|
-
type RuleKind = 'framework' | 'test_framework' | 'orm' | 'ui_library' | 'router';
|
|
45
|
-
|
|
46
|
-
interface DetectionRule {
|
|
47
|
-
language: SupportedLanguage;
|
|
48
|
-
kind: RuleKind;
|
|
49
|
-
/** Lowercase dependency keyword to search (exact match on dep name). */
|
|
50
|
-
keyword: string;
|
|
51
|
-
/** Value set in FrameworkInfo when matched. */
|
|
52
|
-
value: string;
|
|
53
|
-
/** Higher wins; defaults to 0. */
|
|
54
|
-
priority?: number;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Built-in detection rules. Exported so users (and tests) can inspect.
|
|
59
|
-
* User overrides/additions come from `config.detection.rules`.
|
|
60
|
-
*/
|
|
61
|
-
export const DETECTION_RULES: DetectionRule[] = [
|
|
62
|
-
// Python frameworks
|
|
63
|
-
{ language: 'python', kind: 'framework', keyword: 'fastapi', value: 'fastapi', priority: 10 },
|
|
64
|
-
{ language: 'python', kind: 'framework', keyword: 'flask', value: 'flask', priority: 9 },
|
|
65
|
-
{ language: 'python', kind: 'framework', keyword: 'django', value: 'django', priority: 9 },
|
|
66
|
-
{ language: 'python', kind: 'framework', keyword: 'aiohttp', value: 'aiohttp', priority: 8 },
|
|
67
|
-
{ language: 'python', kind: 'framework', keyword: 'sanic', value: 'sanic', priority: 8 },
|
|
68
|
-
{ language: 'python', kind: 'framework', keyword: 'starlette', value: 'starlette', priority: 7 },
|
|
69
|
-
// Python test
|
|
70
|
-
{ language: 'python', kind: 'test_framework', keyword: 'pytest', value: 'pytest', priority: 10 },
|
|
71
|
-
{ language: 'python', kind: 'test_framework', keyword: 'pytest-asyncio', value: 'pytest', priority: 9 },
|
|
72
|
-
// Python ORM
|
|
73
|
-
{ language: 'python', kind: 'orm', keyword: 'sqlalchemy', value: 'sqlalchemy', priority: 10 },
|
|
74
|
-
{ language: 'python', kind: 'orm', keyword: 'django-orm', value: 'django-orm', priority: 9 },
|
|
75
|
-
{ language: 'python', kind: 'orm', keyword: 'peewee', value: 'peewee', priority: 8 },
|
|
76
|
-
{ language: 'python', kind: 'orm', keyword: 'tortoise-orm', value: 'tortoise-orm', priority: 8 },
|
|
77
|
-
|
|
78
|
-
// TypeScript / JavaScript frameworks
|
|
79
|
-
{ language: 'typescript', kind: 'framework', keyword: 'next', value: 'next', priority: 10 },
|
|
80
|
-
{ language: 'typescript', kind: 'framework', keyword: '@nestjs/core', value: 'nestjs', priority: 10 },
|
|
81
|
-
{ language: 'typescript', kind: 'framework', keyword: 'fastify', value: 'fastify', priority: 9 },
|
|
82
|
-
{ language: 'typescript', kind: 'framework', keyword: 'express', value: 'express', priority: 9 },
|
|
83
|
-
{ language: 'typescript', kind: 'framework', keyword: 'hono', value: 'hono', priority: 9 },
|
|
84
|
-
{ language: 'typescript', kind: 'framework', keyword: '@sveltejs/kit', value: 'sveltekit', priority: 10 },
|
|
85
|
-
{ language: 'typescript', kind: 'framework', keyword: 'nuxt', value: 'nuxt', priority: 10 },
|
|
86
|
-
{ language: 'typescript', kind: 'framework', keyword: '@angular/core', value: 'angular', priority: 10 },
|
|
87
|
-
{ language: 'typescript', kind: 'framework', keyword: 'react', value: 'react', priority: 5 },
|
|
88
|
-
{ language: 'typescript', kind: 'framework', keyword: 'vue', value: 'vue', priority: 5 },
|
|
89
|
-
// Mirror for javascript
|
|
90
|
-
{ language: 'javascript', kind: 'framework', keyword: 'next', value: 'next', priority: 10 },
|
|
91
|
-
{ language: 'javascript', kind: 'framework', keyword: 'express', value: 'express', priority: 9 },
|
|
92
|
-
{ language: 'javascript', kind: 'framework', keyword: 'fastify', value: 'fastify', priority: 9 },
|
|
93
|
-
{ language: 'javascript', kind: 'framework', keyword: 'react', value: 'react', priority: 5 },
|
|
94
|
-
// TS/JS test
|
|
95
|
-
{ language: 'typescript', kind: 'test_framework', keyword: 'vitest', value: 'vitest', priority: 10 },
|
|
96
|
-
{ language: 'typescript', kind: 'test_framework', keyword: 'jest', value: 'jest', priority: 9 },
|
|
97
|
-
{ language: 'typescript', kind: 'test_framework', keyword: 'mocha', value: 'mocha', priority: 8 },
|
|
98
|
-
{ language: 'typescript', kind: 'test_framework', keyword: '@playwright/test', value: 'playwright', priority: 7 },
|
|
99
|
-
{ language: 'javascript', kind: 'test_framework', keyword: 'vitest', value: 'vitest', priority: 10 },
|
|
100
|
-
{ language: 'javascript', kind: 'test_framework', keyword: 'jest', value: 'jest', priority: 9 },
|
|
101
|
-
{ language: 'javascript', kind: 'test_framework', keyword: 'mocha', value: 'mocha', priority: 8 },
|
|
102
|
-
// TS/JS ORM
|
|
103
|
-
{ language: 'typescript', kind: 'orm', keyword: '@prisma/client', value: 'prisma', priority: 10 },
|
|
104
|
-
{ language: 'typescript', kind: 'orm', keyword: 'prisma', value: 'prisma', priority: 9 },
|
|
105
|
-
{ language: 'typescript', kind: 'orm', keyword: 'drizzle-orm', value: 'drizzle', priority: 10 },
|
|
106
|
-
{ language: 'typescript', kind: 'orm', keyword: 'typeorm', value: 'typeorm', priority: 9 },
|
|
107
|
-
{ language: 'typescript', kind: 'orm', keyword: 'mongoose', value: 'mongoose', priority: 9 },
|
|
108
|
-
{ language: 'typescript', kind: 'orm', keyword: 'sequelize', value: 'sequelize', priority: 8 },
|
|
109
|
-
{ language: 'javascript', kind: 'orm', keyword: '@prisma/client', value: 'prisma', priority: 10 },
|
|
110
|
-
{ language: 'javascript', kind: 'orm', keyword: 'mongoose', value: 'mongoose', priority: 9 },
|
|
111
|
-
// TS/JS UI
|
|
112
|
-
{ language: 'typescript', kind: 'ui_library', keyword: 'next', value: 'next', priority: 9 },
|
|
113
|
-
{ language: 'typescript', kind: 'ui_library', keyword: 'react', value: 'react', priority: 8 },
|
|
114
|
-
{ language: 'typescript', kind: 'ui_library', keyword: 'vue', value: 'vue', priority: 8 },
|
|
115
|
-
{ language: 'typescript', kind: 'ui_library', keyword: '@sveltejs/kit', value: 'svelte', priority: 9 },
|
|
116
|
-
{ language: 'javascript', kind: 'ui_library', keyword: 'react', value: 'react', priority: 8 },
|
|
117
|
-
// TS/JS router
|
|
118
|
-
{ language: 'typescript', kind: 'router', keyword: '@trpc/server', value: 'trpc', priority: 10 },
|
|
119
|
-
{ language: 'typescript', kind: 'router', keyword: '@apollo/server', value: 'graphql', priority: 9 },
|
|
120
|
-
{ language: 'typescript', kind: 'router', keyword: 'graphql', value: 'graphql', priority: 8 },
|
|
121
|
-
{ language: 'typescript', kind: 'router', keyword: 'express', value: 'express', priority: 7 },
|
|
122
|
-
{ language: 'typescript', kind: 'router', keyword: 'fastify', value: 'fastify', priority: 7 },
|
|
123
|
-
{ language: 'typescript', kind: 'router', keyword: 'hono', value: 'hono', priority: 7 },
|
|
124
|
-
|
|
125
|
-
// Rust
|
|
126
|
-
{ language: 'rust', kind: 'framework', keyword: 'actix-web', value: 'actix-web', priority: 10 },
|
|
127
|
-
{ language: 'rust', kind: 'framework', keyword: 'axum', value: 'axum', priority: 10 },
|
|
128
|
-
{ language: 'rust', kind: 'framework', keyword: 'rocket', value: 'rocket', priority: 10 },
|
|
129
|
-
{ language: 'rust', kind: 'framework', keyword: 'warp', value: 'warp', priority: 9 },
|
|
130
|
-
{ language: 'rust', kind: 'framework', keyword: 'tokio', value: 'tokio', priority: 5 },
|
|
131
|
-
{ language: 'rust', kind: 'test_framework', keyword: 'cargo', value: 'cargo', priority: 1 },
|
|
132
|
-
{ language: 'rust', kind: 'orm', keyword: 'diesel', value: 'diesel', priority: 10 },
|
|
133
|
-
{ language: 'rust', kind: 'orm', keyword: 'sqlx', value: 'sqlx', priority: 10 },
|
|
134
|
-
{ language: 'rust', kind: 'orm', keyword: 'sea-orm', value: 'sea-orm', priority: 10 },
|
|
135
|
-
|
|
136
|
-
// Go
|
|
137
|
-
{ language: 'go', kind: 'framework', keyword: 'github.com/gin-gonic/gin', value: 'gin', priority: 10 },
|
|
138
|
-
{ language: 'go', kind: 'framework', keyword: 'github.com/labstack/echo', value: 'echo', priority: 10 },
|
|
139
|
-
{ language: 'go', kind: 'framework', keyword: 'github.com/gofiber/fiber', value: 'fiber', priority: 10 },
|
|
140
|
-
{ language: 'go', kind: 'framework', keyword: 'github.com/go-chi/chi', value: 'chi', priority: 9 },
|
|
141
|
-
// chi versioned import paths (Go convention: github.com/<org>/<name>/v<N>).
|
|
142
|
-
// matchRule does exact case-insensitive set lookup, so the unversioned and
|
|
143
|
-
// each major-version path each need their own rule.
|
|
144
|
-
{ language: 'go', kind: 'framework', keyword: 'github.com/go-chi/chi/v2', value: 'chi', priority: 9 },
|
|
145
|
-
{ language: 'go', kind: 'framework', keyword: 'github.com/go-chi/chi/v3', value: 'chi', priority: 9 },
|
|
146
|
-
{ language: 'go', kind: 'framework', keyword: 'github.com/go-chi/chi/v4', value: 'chi', priority: 9 },
|
|
147
|
-
{ language: 'go', kind: 'framework', keyword: 'github.com/go-chi/chi/v5', value: 'chi', priority: 9 },
|
|
148
|
-
{ language: 'go', kind: 'test_framework', keyword: 'github.com/stretchr/testify', value: 'testify', priority: 8 },
|
|
149
|
-
{ language: 'go', kind: 'orm', keyword: 'gorm.io/gorm', value: 'gorm', priority: 10 },
|
|
150
|
-
|
|
151
|
-
// Swift (SPM dependency names, best-effort)
|
|
152
|
-
{ language: 'swift', kind: 'framework', keyword: 'vapor', value: 'vapor', priority: 10 },
|
|
153
|
-
{ language: 'swift', kind: 'framework', keyword: 'swift-nio', value: 'swift-nio', priority: 7 },
|
|
154
|
-
{ language: 'swift', kind: 'test_framework', keyword: 'xctest', value: 'xctest', priority: 5 },
|
|
155
|
-
|
|
156
|
-
// Java
|
|
157
|
-
{ language: 'java', kind: 'framework', keyword: 'spring-boot-starter', value: 'spring-boot', priority: 10 },
|
|
158
|
-
{ language: 'java', kind: 'framework', keyword: 'spring-boot-starter-web', value: 'spring-boot', priority: 10 },
|
|
159
|
-
{ language: 'java', kind: 'test_framework', keyword: 'junit', value: 'junit', priority: 10 },
|
|
160
|
-
{ language: 'java', kind: 'test_framework', keyword: 'junit-jupiter', value: 'junit', priority: 10 },
|
|
161
|
-
|
|
162
|
-
// Ruby
|
|
163
|
-
{ language: 'ruby', kind: 'framework', keyword: 'rails', value: 'rails', priority: 10 },
|
|
164
|
-
{ language: 'ruby', kind: 'framework', keyword: 'sinatra', value: 'sinatra', priority: 9 },
|
|
165
|
-
{ language: 'ruby', kind: 'test_framework', keyword: 'rspec', value: 'rspec', priority: 10 },
|
|
166
|
-
{ language: 'ruby', kind: 'orm', keyword: 'activerecord', value: 'activerecord', priority: 10 },
|
|
167
|
-
// Plan 1.5.1: elixir + csharp framework rules. Closes the CR-39 gap
|
|
168
|
-
// where Phoenix + ASP.NET projects produced `framework.languages.<lang>`
|
|
169
|
-
// entries WITHOUT a `framework:` value, which prevented variant
|
|
170
|
-
// templates from being looked up.
|
|
171
|
-
{ language: 'elixir', kind: 'framework', keyword: 'phoenix', value: 'phoenix', priority: 10 },
|
|
172
|
-
{ language: 'elixir', kind: 'test_framework', keyword: 'ex_unit', value: 'ex-unit', priority: 10 },
|
|
173
|
-
{ language: 'elixir', kind: 'orm', keyword: 'ecto', value: 'ecto', priority: 10 },
|
|
174
|
-
// ASP.NET Core surfaces via several PackageReference names; the canonical
|
|
175
|
-
// ones in modern .NET projects are .App and .Mvc. matchRule does exact
|
|
176
|
-
// (case-insensitive) lookup against the deps set parseCsproj extracts.
|
|
177
|
-
{ language: 'csharp', kind: 'framework', keyword: 'Microsoft.AspNetCore.App', value: 'aspnet-core', priority: 10 },
|
|
178
|
-
{ language: 'csharp', kind: 'framework', keyword: 'Microsoft.AspNetCore.Mvc', value: 'aspnet-core', priority: 10 },
|
|
179
|
-
{ language: 'csharp', kind: 'framework', keyword: 'Microsoft.AspNetCore', value: 'aspnet-core', priority: 9 },
|
|
180
|
-
// SDK-style projects: `<Project Sdk="Microsoft.NET.Sdk.Web">` is the
|
|
181
|
-
// canonical ASP.NET Core declaration in modern .NET. parseCsproj
|
|
182
|
-
// surfaces the Sdk attribute as a dep so this rule can match.
|
|
183
|
-
{ language: 'csharp', kind: 'framework', keyword: 'Microsoft.NET.Sdk.Web', value: 'aspnet-core', priority: 10 },
|
|
184
|
-
{ language: 'csharp', kind: 'test_framework', keyword: 'xunit', value: 'xunit', priority: 10 },
|
|
185
|
-
{ language: 'csharp', kind: 'orm', keyword: 'EntityFrameworkCore', value: 'ef-core', priority: 10 },
|
|
186
|
-
];
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* User-supplied detection overrides, matching the P2-008 schema shape:
|
|
190
|
-
* detection.rules[language][framework] = { signals: string[], priority?: number }
|
|
191
|
-
*/
|
|
192
|
-
export interface UserDetectionRules {
|
|
193
|
-
rules?: Record<
|
|
194
|
-
string,
|
|
195
|
-
Record<string, { signals: string[]; priority?: number }>
|
|
196
|
-
>;
|
|
197
|
-
disable_builtin?: boolean;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Find the highest-priority rule for a given (language, kind) by scanning
|
|
202
|
-
* dep list case-insensitively.
|
|
203
|
-
*/
|
|
204
|
-
function matchRule(
|
|
205
|
-
rules: DetectionRule[],
|
|
206
|
-
language: SupportedLanguage,
|
|
207
|
-
kind: RuleKind,
|
|
208
|
-
deps: Set<string>
|
|
209
|
-
): { value: string; priority: number } | null {
|
|
210
|
-
let best: { value: string; priority: number } | null = null;
|
|
211
|
-
for (const r of rules) {
|
|
212
|
-
if (r.language !== language) continue;
|
|
213
|
-
if (r.kind !== kind) continue;
|
|
214
|
-
if (!deps.has(r.keyword.toLowerCase())) continue;
|
|
215
|
-
const pr = r.priority ?? 0;
|
|
216
|
-
if (!best || pr > best.priority) {
|
|
217
|
-
best = { value: r.value, priority: pr };
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
return best;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Match user-supplied framework rules (signals) against dep list.
|
|
225
|
-
* Each signal string is treated as a dep keyword (case-insensitive exact match
|
|
226
|
-
* on the dep name). If any signal matches, the framework is a candidate.
|
|
227
|
-
*/
|
|
228
|
-
function matchUserFrameworkRules(
|
|
229
|
-
userRules: UserDetectionRules['rules'] | undefined,
|
|
230
|
-
language: string,
|
|
231
|
-
deps: Set<string>
|
|
232
|
-
): { framework: string; priority: number } | null {
|
|
233
|
-
if (!userRules) return null;
|
|
234
|
-
const byLang = userRules[language];
|
|
235
|
-
if (!byLang) return null;
|
|
236
|
-
let best: { framework: string; priority: number } | null = null;
|
|
237
|
-
for (const [framework, entry] of Object.entries(byLang)) {
|
|
238
|
-
const signals = entry.signals ?? [];
|
|
239
|
-
const priority = entry.priority ?? 100; // user rules outrank built-ins by default
|
|
240
|
-
for (const sig of signals) {
|
|
241
|
-
if (deps.has(sig.toLowerCase())) {
|
|
242
|
-
if (!best || priority > best.priority) {
|
|
243
|
-
best = { framework, priority };
|
|
244
|
-
}
|
|
245
|
-
break;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
return best;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Detect frameworks/test-frameworks/ORMs per language.
|
|
254
|
-
*
|
|
255
|
-
* @param manifests - output of P1-001 detectPackageManifests
|
|
256
|
-
* @param userDetection - optional massu.config.yaml `detection` block
|
|
257
|
-
*/
|
|
258
|
-
export function detectFrameworks(
|
|
259
|
-
manifests: PackageManifest[],
|
|
260
|
-
userDetection?: UserDetectionRules
|
|
261
|
-
): FrameworkMap {
|
|
262
|
-
// Merge deps across all manifests of the same language (monorepo aggregate).
|
|
263
|
-
const byLang = new Map<SupportedLanguage, { deps: Set<string>; versionOf: Map<string, string> }>();
|
|
264
|
-
for (const m of manifests) {
|
|
265
|
-
const entry = byLang.get(m.language) ?? {
|
|
266
|
-
deps: new Set<string>(),
|
|
267
|
-
versionOf: new Map<string, string>(),
|
|
268
|
-
};
|
|
269
|
-
for (const d of m.dependencies) entry.deps.add(d.toLowerCase());
|
|
270
|
-
for (const d of m.devDependencies) entry.deps.add(d.toLowerCase());
|
|
271
|
-
byLang.set(m.language, entry);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const rules: DetectionRule[] = userDetection?.disable_builtin
|
|
275
|
-
? []
|
|
276
|
-
: [...DETECTION_RULES];
|
|
277
|
-
|
|
278
|
-
const out: FrameworkMap = {};
|
|
279
|
-
for (const [language, { deps }] of byLang.entries()) {
|
|
280
|
-
const fw = matchRule(rules, language, 'framework', deps);
|
|
281
|
-
const userFw = matchUserFrameworkRules(
|
|
282
|
-
userDetection?.rules,
|
|
283
|
-
language,
|
|
284
|
-
deps
|
|
285
|
-
);
|
|
286
|
-
// Pick user rule when it has higher effective priority.
|
|
287
|
-
let frameworkValue: string | null = null;
|
|
288
|
-
if (userFw && (!fw || userFw.priority > fw.priority)) {
|
|
289
|
-
frameworkValue = userFw.framework;
|
|
290
|
-
} else if (fw) {
|
|
291
|
-
frameworkValue = fw.value;
|
|
292
|
-
}
|
|
293
|
-
const info: FrameworkInfo = {
|
|
294
|
-
framework: frameworkValue,
|
|
295
|
-
version: null,
|
|
296
|
-
test_framework:
|
|
297
|
-
matchRule(rules, language, 'test_framework', deps)?.value ?? null,
|
|
298
|
-
orm: matchRule(rules, language, 'orm', deps)?.value ?? null,
|
|
299
|
-
ui_library:
|
|
300
|
-
matchRule(rules, language, 'ui_library', deps)?.value ?? null,
|
|
301
|
-
router: matchRule(rules, language, 'router', deps)?.value ?? null,
|
|
302
|
-
};
|
|
303
|
-
out[language] = info;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
return out;
|
|
307
|
-
}
|