@massu/core 1.10.7 → 1.12.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.
- package/CHANGELOG.md +1303 -0
- package/dist/adapter.js +6 -4
- package/dist/cli.js +559 -400
- 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 +66 -18
- package/dist/hooks/pre-compact.js +27 -2
- package/dist/hooks/pre-delete-check.js +48 -22
- 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 +24 -0
- package/dist/hooks/session-start.js +8710 -8460
- package/dist/hooks/user-prompt.js +24 -0
- 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/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 -1205
- 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,191 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2026 Massu. All rights reserved.
|
|
2
|
-
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Plan 3b — Phase 1: AST Adapter contract types.
|
|
6
|
-
*
|
|
7
|
-
* Lives at `packages/core/src/detect/adapters/types.ts`. All types are local —
|
|
8
|
-
* NONE re-exported from `web-tree-sitter`.
|
|
9
|
-
*
|
|
10
|
-
* Adapter authors import from this module only; the runner (`runner.ts`)
|
|
11
|
-
* orchestrates execution and the loader (`tree-sitter-loader.ts`) handles
|
|
12
|
-
* grammar acquisition.
|
|
13
|
-
*
|
|
14
|
-
* Per-field confidence is enforced (NOT per-adapter): a single weak field
|
|
15
|
-
* MUST NOT poison the rest. The runner consumes `confidence` per-adapter for
|
|
16
|
-
* the moment, but the merge rule reads each `conventions[field]` against the
|
|
17
|
-
* provenance trail to decide what survives.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
// ============================================================
|
|
21
|
-
// Languages enumerated for the AST adapter set (Phase 1 + 3c)
|
|
22
|
-
// ============================================================
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Closed-set of Tree-sitter grammars massu ships first-party adapters for.
|
|
26
|
-
*
|
|
27
|
-
* Note: this is a string-literal union, NOT re-exported from `web-tree-sitter`
|
|
28
|
-
* (which exposes `Language` as a class, not a name list). Phase 1 ships
|
|
29
|
-
* adapters for python/typescript/javascript/swift only — the remaining
|
|
30
|
-
* languages are reserved for Plan 3c.
|
|
31
|
-
*/
|
|
32
|
-
export type TreeSitterLanguage =
|
|
33
|
-
| 'python'
|
|
34
|
-
| 'typescript'
|
|
35
|
-
| 'javascript'
|
|
36
|
-
| 'swift'
|
|
37
|
-
| 'rust'
|
|
38
|
-
| 'go'
|
|
39
|
-
| 'ruby'
|
|
40
|
-
| 'php'
|
|
41
|
-
| 'java'
|
|
42
|
-
| 'kotlin'
|
|
43
|
-
| 'elixir'
|
|
44
|
-
| 'erlang'
|
|
45
|
-
| 'csharp'
|
|
46
|
-
| 'cpp'
|
|
47
|
-
| 'haskell'
|
|
48
|
-
| 'ocaml';
|
|
49
|
-
|
|
50
|
-
// ============================================================
|
|
51
|
-
// Inputs to adapter dispatch
|
|
52
|
-
// ============================================================
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Read-only signal bundle the runner builds BEFORE adapter dispatch.
|
|
56
|
-
*
|
|
57
|
-
* Adapters consume signals to answer `matches()` cheaply (no file IO inside
|
|
58
|
-
* `matches()` — that's why the bundle is built up-front).
|
|
59
|
-
*/
|
|
60
|
-
export interface DetectionSignals {
|
|
61
|
-
/** Parsed `package.json` (root or first workspace) — undefined if absent. */
|
|
62
|
-
packageJson?: Record<string, unknown>;
|
|
63
|
-
/** Parsed `pyproject.toml` — undefined if absent. */
|
|
64
|
-
pyprojectToml?: Record<string, unknown>;
|
|
65
|
-
/** Raw `Gemfile` text — undefined if absent. */
|
|
66
|
-
gemfile?: string;
|
|
67
|
-
/** Parsed `Cargo.toml` — undefined if absent. */
|
|
68
|
-
cargoToml?: Record<string, unknown>;
|
|
69
|
-
/** Raw `go.mod` text — undefined if absent. */
|
|
70
|
-
goMod?: string;
|
|
71
|
-
/** Raw `mix.exs` text — undefined if absent. Elixir/Mix manifest. */
|
|
72
|
-
mixExs?: string;
|
|
73
|
-
/**
|
|
74
|
-
* Raw text of the FIRST `.csproj` file found at the project root —
|
|
75
|
-
* undefined if absent. .NET project file (XML). Adapters that need to
|
|
76
|
-
* inspect more than one csproj (multi-project solutions) can re-scan from
|
|
77
|
-
* `presentFiles`.
|
|
78
|
-
*/
|
|
79
|
-
csproj?: string;
|
|
80
|
-
/** Raw `pom.xml` text — undefined if absent. Maven build manifest. */
|
|
81
|
-
pomXml?: string;
|
|
82
|
-
/**
|
|
83
|
-
* Raw text of `build.gradle` OR `build.gradle.kts` — whichever exists at
|
|
84
|
-
* the project root, with `.kts` (Kotlin DSL) preferred when both are
|
|
85
|
-
* present (Kotlin DSL is the modern default per Gradle 7+ docs).
|
|
86
|
-
* Undefined if neither exists.
|
|
87
|
-
*/
|
|
88
|
-
gradleBuild?: string;
|
|
89
|
-
/** Set of present directory names directly under the project root (one level). */
|
|
90
|
-
presentDirs: Set<string>;
|
|
91
|
-
/** Set of present file basenames directly under the project root (one level). */
|
|
92
|
-
presentFiles: Set<string>;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* A sampled source file the runner hands to the adapter.
|
|
97
|
-
*
|
|
98
|
-
* `content` is pre-read; adapters MUST NOT re-read from disk inside
|
|
99
|
-
* `introspect()`. `size` is in bytes (pre-read length).
|
|
100
|
-
*/
|
|
101
|
-
export interface SourceFile {
|
|
102
|
-
path: string;
|
|
103
|
-
content: string;
|
|
104
|
-
language: TreeSitterLanguage;
|
|
105
|
-
size: number;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// ============================================================
|
|
109
|
-
// Adapter contract
|
|
110
|
-
// ============================================================
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Trail entry produced for every captured field — the user can audit
|
|
114
|
-
* `detected.<adapter>._provenance` to see exactly which file/line/query
|
|
115
|
-
* produced a value.
|
|
116
|
-
*/
|
|
117
|
-
export interface Provenance {
|
|
118
|
-
field: string;
|
|
119
|
-
sourceFile: string;
|
|
120
|
-
line: number;
|
|
121
|
-
query: string;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export interface AdapterResult {
|
|
125
|
-
/**
|
|
126
|
-
* Becomes `detected.<adapter.id>` in `massu.config.yaml`. Field names are
|
|
127
|
-
* adapter-defined; values are `unknown` so adapters can return strings,
|
|
128
|
-
* arrays, or nested records as needed.
|
|
129
|
-
*/
|
|
130
|
-
conventions: Record<string, unknown>;
|
|
131
|
-
/**
|
|
132
|
-
* Per-field provenance trail. The runner writes this to
|
|
133
|
-
* `detected.<adapter.id>._provenance` so a downstream auditor can verify
|
|
134
|
-
* any extracted value.
|
|
135
|
-
*/
|
|
136
|
-
provenance: Provenance[];
|
|
137
|
-
/**
|
|
138
|
-
* 'high' : single canonical match, query produced exactly one result
|
|
139
|
-
* 'medium': multiple matches, all agree
|
|
140
|
-
* 'low' : multiple matches with disagreement (still emitted, with warning)
|
|
141
|
-
* 'none' : no matches, timed out, or threw — fields are dropped
|
|
142
|
-
*/
|
|
143
|
-
confidence: 'high' | 'medium' | 'low' | 'none';
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export interface CodebaseAdapter {
|
|
147
|
-
/** Stable adapter id, e.g. "python-fastapi". Becomes `detected.<id>` block. */
|
|
148
|
-
id: string;
|
|
149
|
-
/** Languages this adapter consumes. Used by the runner to skip work. */
|
|
150
|
-
languages: TreeSitterLanguage[];
|
|
151
|
-
/**
|
|
152
|
-
* Cheap signal check — must NOT do file IO. Returns true if any signal
|
|
153
|
-
* suggests this adapter should run.
|
|
154
|
-
*/
|
|
155
|
-
matches(signals: DetectionSignals): boolean;
|
|
156
|
-
/**
|
|
157
|
-
* Sample N files (already read by the runner), run AST queries, return
|
|
158
|
-
* extracted conventions. May throw — the runner isolates failures.
|
|
159
|
-
*/
|
|
160
|
-
introspect(files: SourceFile[], rootDir: string): Promise<AdapterResult>;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// ============================================================
|
|
164
|
-
// Runner output
|
|
165
|
-
// ============================================================
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* The runner's output: per-adapter id → its conventions block (with the
|
|
169
|
-
* `_provenance` map merged in). The introspector then folds this into the
|
|
170
|
-
* `detected.<adapter.id>` namespace alongside the existing
|
|
171
|
-
* `detected.python` / `detected.swift` / `detected.typescript` regex blocks.
|
|
172
|
-
*/
|
|
173
|
-
export interface MergedAdapterOutput {
|
|
174
|
-
/** Per-adapter id → resolved conventions. */
|
|
175
|
-
byAdapter: Record<string, AdapterResolved>;
|
|
176
|
-
/** Adapters that were skipped (didn't match) for diagnostic logging. */
|
|
177
|
-
skipped: string[];
|
|
178
|
-
/** Adapters that threw during introspect — runner isolates these. */
|
|
179
|
-
errored: Array<{ adapterId: string; error: string }>;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Resolved-and-merged form of an `AdapterResult`. Provenance is folded into
|
|
184
|
-
* `_provenance` (key per field, value = `path:line :: query`).
|
|
185
|
-
*/
|
|
186
|
-
export interface AdapterResolved {
|
|
187
|
-
conventions: Record<string, unknown>;
|
|
188
|
-
/** field-name -> "relativePath:line :: queryName". Empty when no fields. */
|
|
189
|
-
_provenance: Record<string, string>;
|
|
190
|
-
confidence: 'high' | 'medium' | 'low' | 'none';
|
|
191
|
-
}
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2026 Massu. All rights reserved.
|
|
2
|
-
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Codebase Introspector — 2-tier dispatcher (Plan 3b Phase 1)
|
|
6
|
-
* ============================================================
|
|
7
|
-
*
|
|
8
|
-
* Public signature: `introspect(detection, projectRoot): DetectedConventions`.
|
|
9
|
-
* Byte-for-byte unchanged from Plan #2 — `detect/index.ts` calls this
|
|
10
|
-
* function as before.
|
|
11
|
-
*
|
|
12
|
-
* Internal change (Plan 3b Phase 1): the function is now a 2-tier dispatcher.
|
|
13
|
-
*
|
|
14
|
-
* Tier 1 — AST adapters (preferred). Run via `runner.ts` against the four
|
|
15
|
-
* first-party adapters: python-fastapi, python-django, nextjs-trpc,
|
|
16
|
-
* swift-swiftui. Each writes to its own `detected.<adapter.id>` block
|
|
17
|
-
* alongside the regex blocks. AST adapters use Tree-sitter S-expression
|
|
18
|
-
* queries — never regex. Confidence is per-field.
|
|
19
|
-
*
|
|
20
|
-
* Tier 2 — Regex fallback. For fields the AST adapters returned 'none'
|
|
21
|
-
* confidence on, the regex helpers in `regex-fallback.ts` (verbatim moved
|
|
22
|
-
* from this file's previous incarnation) take over. AST-wins rule: when
|
|
23
|
-
* both tiers produce a value for the same `detected.<lang>.<field>` slot,
|
|
24
|
-
* AST wins; the runner records both in provenance.
|
|
25
|
-
*
|
|
26
|
-
* Tier 3 — null. The template engine's `| default("...")` then takes over.
|
|
27
|
-
*
|
|
28
|
-
* The AST tier may degrade silently to regex-only when:
|
|
29
|
-
* - The Tree-sitter grammar is unavailable offline AND uncached
|
|
30
|
-
* - The adapter throws (per-adapter try/catch in `runner.ts`)
|
|
31
|
-
* - The grammar SHA-256 doesn't match the manifest (refused by loader)
|
|
32
|
-
*
|
|
33
|
-
* Exception: if a Tree-sitter query is malformed (developer bug), the loader
|
|
34
|
-
* propagates `InvalidQueryError` so we don't silently mask it.
|
|
35
|
-
*/
|
|
36
|
-
|
|
37
|
-
import type { DetectionResult } from './index.ts';
|
|
38
|
-
import {
|
|
39
|
-
introspectPython,
|
|
40
|
-
introspectSwift,
|
|
41
|
-
introspectTypeScript,
|
|
42
|
-
type DetectedPython,
|
|
43
|
-
type DetectedSwift,
|
|
44
|
-
type DetectedTypeScript,
|
|
45
|
-
} from './regex-fallback.ts';
|
|
46
|
-
import { runAdapters, buildDetectionSignals } from './adapters/runner.ts';
|
|
47
|
-
import { pythonFastApiAdapter } from './adapters/python-fastapi.ts';
|
|
48
|
-
import { pythonDjangoAdapter } from './adapters/python-django.ts';
|
|
49
|
-
import { nextjsTrpcAdapter } from './adapters/nextjs-trpc.ts';
|
|
50
|
-
import { swiftSwiftUiAdapter } from './adapters/swift-swiftui.ts';
|
|
51
|
-
// Plan 1.5.4 R-011 discovery: Phase 7 adapters were committed but never
|
|
52
|
-
// added to FIRST_PARTY_ADAPTERS. The omission was masked pre-1.5.4 by
|
|
53
|
-
// sampleFiles=[] (every adapter returned 'none' anyway). Now that the
|
|
54
|
-
// sampler works, these 6 adapters MUST be in the dispatch list or
|
|
55
|
-
// `npx massu init` against a Phase 7 project produces no detected.<id>:
|
|
56
|
-
// block (verified via debug instrumentation 2026-05-08 against the
|
|
57
|
-
// 1.5.4 published bundle).
|
|
58
|
-
import { pythonFlaskAdapter } from './adapters/python-flask.ts';
|
|
59
|
-
import { goChiAdapter } from './adapters/go-chi.ts';
|
|
60
|
-
import { railsAdapter } from './adapters/rails.ts';
|
|
61
|
-
import { phoenixAdapter } from './adapters/phoenix.ts';
|
|
62
|
-
import { aspnetAdapter } from './adapters/aspnet.ts';
|
|
63
|
-
import { springAdapter } from './adapters/spring.ts';
|
|
64
|
-
import type { CodebaseAdapter, AdapterResolved } from './adapters/types.ts';
|
|
65
|
-
|
|
66
|
-
// ============================================================
|
|
67
|
-
// Public types — unchanged from Plan #2 to preserve consumers
|
|
68
|
-
// ============================================================
|
|
69
|
-
|
|
70
|
-
export type { DetectedPython, DetectedSwift, DetectedTypeScript };
|
|
71
|
-
|
|
72
|
-
export interface DetectedConventions {
|
|
73
|
-
python?: DetectedPython;
|
|
74
|
-
swift?: DetectedSwift;
|
|
75
|
-
typescript?: DetectedTypeScript;
|
|
76
|
-
/**
|
|
77
|
-
* AST adapter blocks live here (Plan 3b). Keys are adapter ids
|
|
78
|
-
* (`python-fastapi`, etc.). Values include both extracted conventions and
|
|
79
|
-
* a `_provenance` map.
|
|
80
|
-
*/
|
|
81
|
-
[adapterId: string]: unknown;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// ============================================================
|
|
85
|
-
// Static adapter list (v1 — first-party only, per spec §6)
|
|
86
|
-
// ============================================================
|
|
87
|
-
|
|
88
|
-
const FIRST_PARTY_ADAPTERS: CodebaseAdapter[] = [
|
|
89
|
-
pythonFastApiAdapter,
|
|
90
|
-
pythonDjangoAdapter,
|
|
91
|
-
pythonFlaskAdapter,
|
|
92
|
-
nextjsTrpcAdapter,
|
|
93
|
-
swiftSwiftUiAdapter,
|
|
94
|
-
goChiAdapter,
|
|
95
|
-
railsAdapter,
|
|
96
|
-
phoenixAdapter,
|
|
97
|
-
aspnetAdapter,
|
|
98
|
-
springAdapter,
|
|
99
|
-
];
|
|
100
|
-
|
|
101
|
-
// ============================================================
|
|
102
|
-
// Public entry point
|
|
103
|
-
// ============================================================
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Introspect the project's source files. Returns per-language conventions or
|
|
107
|
-
* an empty object if nothing was extracted.
|
|
108
|
-
*
|
|
109
|
-
* Synchronous signature is preserved — AST adapter execution is intentionally
|
|
110
|
-
* fire-and-forget at this layer. Phase 1 wires the adapter pipeline behind
|
|
111
|
-
* the existing sync function so `detect/index.ts` is byte-for-byte unchanged
|
|
112
|
-
* (plan line 137-142). The async adapter orchestration lives entirely inside
|
|
113
|
-
* `runIntrospect()`, which `introspect()` does NOT await — adapters either
|
|
114
|
-
* have their grammars cached (fast path, no async needed at the JS level by
|
|
115
|
-
* Phase 4 wiring) or degrade to regex.
|
|
116
|
-
*
|
|
117
|
-
* For Phase 1 Tier 1 to actually contribute values, callers must use the
|
|
118
|
-
* async variant `introspectAsync()`. The sync `introspect()` runs the regex
|
|
119
|
-
* tier ONLY for Phase 1; Phase 4 callers (LSP enrichment + adapter wiring)
|
|
120
|
-
* will switch to async.
|
|
121
|
-
*/
|
|
122
|
-
export function introspect(
|
|
123
|
-
detection: DetectionResult,
|
|
124
|
-
projectRoot: string,
|
|
125
|
-
): DetectedConventions {
|
|
126
|
-
const out: DetectedConventions = {};
|
|
127
|
-
const languages = Array.from(
|
|
128
|
-
new Set(detection.manifests.map(m => m.language)),
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
// Tier 2 — regex fallback. Always runs. AST adapters in the async variant
|
|
132
|
-
// (`introspectAsync`) populate `detected.<adapter-id>` alongside; for the
|
|
133
|
-
// sync entry point, only the regex tier participates so Plan #2 callers
|
|
134
|
-
// see no behavior change.
|
|
135
|
-
if (languages.includes('python')) {
|
|
136
|
-
const python = introspectPython(detection, projectRoot);
|
|
137
|
-
if (python !== null) out.python = python;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (languages.includes('swift')) {
|
|
141
|
-
const swift = introspectSwift(detection, projectRoot);
|
|
142
|
-
if (swift !== null) out.swift = swift;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (languages.includes('typescript') || languages.includes('javascript')) {
|
|
146
|
-
const ts = introspectTypeScript(detection, projectRoot);
|
|
147
|
-
if (ts !== null) out.typescript = ts;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return out;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// ============================================================
|
|
154
|
-
// Async variant — used by callers who want AST tier participation
|
|
155
|
-
// ============================================================
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Async introspect: runs AST adapters first (Tier 1), then regex fallback
|
|
159
|
-
* (Tier 2) for fields the adapters returned 'none' on.
|
|
160
|
-
*
|
|
161
|
-
* Returns the same `DetectedConventions` shape as the sync `introspect()`,
|
|
162
|
-
* plus per-adapter blocks under their ids.
|
|
163
|
-
*
|
|
164
|
-
* Callers who can `await` (CLI commands, tests, etc.) should prefer this
|
|
165
|
-
* variant. The session-start hook keeps using sync `introspect()` for its
|
|
166
|
-
* 5s budget reason (P4-006).
|
|
167
|
-
*/
|
|
168
|
-
export async function introspectAsync(
|
|
169
|
-
detection: DetectionResult,
|
|
170
|
-
projectRoot: string,
|
|
171
|
-
): Promise<DetectedConventions> {
|
|
172
|
-
const out: DetectedConventions = introspect(detection, projectRoot);
|
|
173
|
-
|
|
174
|
-
// Build signals + run AST adapters with the real file sampler (Plan
|
|
175
|
-
// 1.5.4 §3). Pre-1.5.4 this was a placeholder returning [], which kept
|
|
176
|
-
// every AST adapter at 'none' confidence in the init flow. The
|
|
177
|
-
// adapters themselves always worked (verified by adapter-grammar-
|
|
178
|
-
// strict.test.ts); the gap was purely the seam between detection +
|
|
179
|
-
// adapter execution. The sampler reuses EXTENSIONS / TEST_FILE_PATTERNS
|
|
180
|
-
// from source-dir-detector.ts so adding a new language doesn't require
|
|
181
|
-
// a parallel map (CR-46 self-attest #3).
|
|
182
|
-
const signals = buildDetectionSignals(projectRoot);
|
|
183
|
-
const { sampleFilesForAdapter } = await import('./adapters/file-sampler.ts');
|
|
184
|
-
let merged;
|
|
185
|
-
try {
|
|
186
|
-
merged = await runAdapters(FIRST_PARTY_ADAPTERS, projectRoot, signals, {
|
|
187
|
-
sampleFiles: async (adapter, root) => {
|
|
188
|
-
return sampleFilesForAdapter(adapter, root, detection);
|
|
189
|
-
},
|
|
190
|
-
});
|
|
191
|
-
} catch {
|
|
192
|
-
return out;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
for (const [adapterId, resolved] of Object.entries(merged.byAdapter)) {
|
|
196
|
-
if (resolved.confidence === 'none') continue;
|
|
197
|
-
out[adapterId] = serializeAdapterBlock(resolved);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return out;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function serializeAdapterBlock(r: AdapterResolved): Record<string, unknown> {
|
|
204
|
-
const block: Record<string, unknown> = { ...r.conventions };
|
|
205
|
-
if (Object.keys(r._provenance).length > 0) {
|
|
206
|
-
block._provenance = r._provenance;
|
|
207
|
-
}
|
|
208
|
-
block._confidence = r.confidence;
|
|
209
|
-
return block;
|
|
210
|
-
}
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2026 Massu. All rights reserved.
|
|
2
|
-
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Domain Inferrer (P1-006)
|
|
6
|
-
* ========================
|
|
7
|
-
*
|
|
8
|
-
* Suggests `DomainConfig[]` entries based on monorepo + source-dir discovery.
|
|
9
|
-
* Each workspace package (apps/*, packages/*, services/*, libs/*, modules/*)
|
|
10
|
-
* becomes one suggested domain. In single-package repos, top-level
|
|
11
|
-
* `src/<subdir>/` candidates are suggested as domains.
|
|
12
|
-
*
|
|
13
|
-
* Output matches the existing `DomainConfig` type from `config.ts` so init
|
|
14
|
-
* and refresh can write it directly into `massu.config.yaml`. Suggested
|
|
15
|
-
* `allowedImportsFrom` is always empty — the user fills relationships.
|
|
16
|
-
*
|
|
17
|
-
* Deterministic ordering: alphabetical by domain name.
|
|
18
|
-
*
|
|
19
|
-
* Usage:
|
|
20
|
-
* ```ts
|
|
21
|
-
* import { inferDomains } from './detect/domain-inferrer.ts';
|
|
22
|
-
* const domains = inferDomains('/repo', monorepo, sourceDirs);
|
|
23
|
-
* ```
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
import { existsSync, readdirSync } from 'fs';
|
|
27
|
-
import { join } from 'path';
|
|
28
|
-
import type { DomainConfig } from '../config.ts';
|
|
29
|
-
import type { MonorepoInfo, WorkspacePackage } from './monorepo-detector.ts';
|
|
30
|
-
import type { SourceDirMap } from './source-dir-detector.ts';
|
|
31
|
-
|
|
32
|
-
const IGNORED_SUBDIRS = new Set([
|
|
33
|
-
'node_modules',
|
|
34
|
-
'__pycache__',
|
|
35
|
-
'dist',
|
|
36
|
-
'build',
|
|
37
|
-
'.build',
|
|
38
|
-
'target',
|
|
39
|
-
'.next',
|
|
40
|
-
'.git',
|
|
41
|
-
'.massu',
|
|
42
|
-
'coverage',
|
|
43
|
-
'tests',
|
|
44
|
-
'test',
|
|
45
|
-
'__tests__',
|
|
46
|
-
]);
|
|
47
|
-
|
|
48
|
-
function titleCase(s: string): string {
|
|
49
|
-
if (!s) return s;
|
|
50
|
-
return s
|
|
51
|
-
.split(/[-_\s]+/)
|
|
52
|
-
.filter(Boolean)
|
|
53
|
-
.map((p) => p.charAt(0).toUpperCase() + p.slice(1))
|
|
54
|
-
.join(' ');
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function domainFromWorkspace(pkg: WorkspacePackage): DomainConfig {
|
|
58
|
-
// Prefer the explicit package name; fall back to the final path segment.
|
|
59
|
-
const pathTail = pkg.path.split('/').pop() ?? pkg.path;
|
|
60
|
-
const name = pkg.name ?? titleCase(pathTail);
|
|
61
|
-
return {
|
|
62
|
-
name,
|
|
63
|
-
routers: [],
|
|
64
|
-
pages: [],
|
|
65
|
-
tables: [],
|
|
66
|
-
allowedImportsFrom: [],
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Enumerate domain candidates under each detected source directory.
|
|
72
|
-
*
|
|
73
|
-
* The `sourceDirs` argument is the flattened, unique list of relative
|
|
74
|
-
* source paths produced upstream by the source-dir detector
|
|
75
|
-
* (`detectSourceDirs` in `source-dir-detector.ts`). For each path that
|
|
76
|
-
* exists under `root`, this function lists immediate subdirectories as
|
|
77
|
-
* candidate domain names. Hardcoded `src/` lookup was removed (plan
|
|
78
|
-
* `plan-1.7.0-cohesive-cleanup` P-B-002) — the function now consumes
|
|
79
|
-
* the detection pipeline's output verbatim, so projects whose source
|
|
80
|
-
* lives at non-`src/` paths (e.g. `lib/`, `apps/<x>/src/`) are no
|
|
81
|
-
* longer silently dropped.
|
|
82
|
-
*
|
|
83
|
-
* Empty `sourceDirs` is treated as a legacy single-repo `src/` lookup
|
|
84
|
-
* to preserve behavior for callers that pre-date the source-dir
|
|
85
|
-
* pipeline (CLI / test harnesses that hand-wire `inferDomains`).
|
|
86
|
-
*
|
|
87
|
-
* Returns deduplicated subdir names sorted alphabetically.
|
|
88
|
-
*/
|
|
89
|
-
function topLevelSrcSubdirs(root: string, sourceDirs: readonly string[]): string[] {
|
|
90
|
-
const effective = sourceDirs.length > 0 ? sourceDirs : ['src'];
|
|
91
|
-
const seen = new Set<string>();
|
|
92
|
-
for (const rel of effective) {
|
|
93
|
-
const abs = join(root, rel);
|
|
94
|
-
if (!existsSync(abs)) continue;
|
|
95
|
-
try {
|
|
96
|
-
for (const e of readdirSync(abs, { withFileTypes: true })) {
|
|
97
|
-
if (!e.isDirectory()) continue;
|
|
98
|
-
if (IGNORED_SUBDIRS.has(e.name)) continue;
|
|
99
|
-
seen.add(e.name);
|
|
100
|
-
}
|
|
101
|
-
} catch {
|
|
102
|
-
// skip directories that cannot be read; do not throw.
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return Array.from(seen).sort();
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Flatten a `SourceDirMap` into a unique, deduplicated list of relative
|
|
110
|
-
* source paths across all detected languages.
|
|
111
|
-
*
|
|
112
|
-
* Drops the root sentinels `.` and `''` — those are emitted by the
|
|
113
|
-
* source-dir-detector when source files live directly at the project
|
|
114
|
-
* root (e.g. Django's `manage.py` or Swift's `Package.swift`). Treating
|
|
115
|
-
* them as enumerable source dirs causes spurious top-level directory
|
|
116
|
-
* inclusion (Tests/, Sources/, etc.), which collides with the
|
|
117
|
-
* language-fallback path in `inferDomains`. Root-source repos rely on
|
|
118
|
-
* the language-fallback path to emit `{Python}` / `{Swift}` domains,
|
|
119
|
-
* NOT a fan-out of every root subdirectory.
|
|
120
|
-
*
|
|
121
|
-
* Order is not guaranteed — callers that need determinism must sort.
|
|
122
|
-
*/
|
|
123
|
-
function flattenSourceDirs(sourceDirs: SourceDirMap): string[] {
|
|
124
|
-
const flat = new Set<string>();
|
|
125
|
-
for (const entry of Object.values(sourceDirs)) {
|
|
126
|
-
if (!entry) continue;
|
|
127
|
-
for (const dir of entry.source_dirs) {
|
|
128
|
-
if (dir === '.' || dir === '') continue;
|
|
129
|
-
flat.add(dir);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
return Array.from(flat);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Produce a suggested `DomainConfig[]`.
|
|
137
|
-
*
|
|
138
|
-
* @param projectRoot absolute path
|
|
139
|
-
* @param monorepo output of P1-004 detectMonorepo
|
|
140
|
-
* @param sourceDirs output of P1-003 detectSourceDirs
|
|
141
|
-
*/
|
|
142
|
-
export function inferDomains(
|
|
143
|
-
projectRoot: string,
|
|
144
|
-
monorepo: MonorepoInfo,
|
|
145
|
-
sourceDirs: SourceDirMap
|
|
146
|
-
): DomainConfig[] {
|
|
147
|
-
const domains: DomainConfig[] = [];
|
|
148
|
-
|
|
149
|
-
if (monorepo.type !== 'single' && monorepo.packages.length > 0) {
|
|
150
|
-
// Monorepo: one domain per workspace package.
|
|
151
|
-
for (const pkg of monorepo.packages) {
|
|
152
|
-
domains.push(domainFromWorkspace(pkg));
|
|
153
|
-
}
|
|
154
|
-
} else {
|
|
155
|
-
// Single repo: suggest one domain per top-level <sourceDir>/<subdir>/ for
|
|
156
|
-
// every detected source dir (formerly hardcoded to `src/` only — see
|
|
157
|
-
// P-B-002 in plan-1.7.0-cohesive-cleanup).
|
|
158
|
-
const flat = flattenSourceDirs(sourceDirs);
|
|
159
|
-
const subdirs = topLevelSrcSubdirs(projectRoot, flat);
|
|
160
|
-
for (const s of subdirs) {
|
|
161
|
-
domains.push({
|
|
162
|
-
name: titleCase(s),
|
|
163
|
-
routers: [],
|
|
164
|
-
pages: [],
|
|
165
|
-
tables: [],
|
|
166
|
-
allowedImportsFrom: [],
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
// If no src/ subdirs, emit a single-language-based domain when sourceDirs has entries.
|
|
170
|
-
if (domains.length === 0) {
|
|
171
|
-
const langs = Object.keys(sourceDirs);
|
|
172
|
-
for (const lang of langs.sort()) {
|
|
173
|
-
domains.push({
|
|
174
|
-
name: titleCase(lang),
|
|
175
|
-
routers: [],
|
|
176
|
-
pages: [],
|
|
177
|
-
tables: [],
|
|
178
|
-
allowedImportsFrom: [],
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Deterministic alphabetical order by name.
|
|
185
|
-
domains.sort((a, b) => a.name.localeCompare(b.name));
|
|
186
|
-
|
|
187
|
-
// Dedup by name (monorepo workspaces may coincidentally share a name).
|
|
188
|
-
const seen = new Set<string>();
|
|
189
|
-
const dedup: DomainConfig[] = [];
|
|
190
|
-
for (const d of domains) {
|
|
191
|
-
if (seen.has(d.name)) continue;
|
|
192
|
-
seen.add(d.name);
|
|
193
|
-
dedup.push(d);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return dedup;
|
|
197
|
-
}
|