@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.
Files changed (177) hide show
  1. package/CHANGELOG.md +1303 -0
  2. package/dist/adapter.js +6 -4
  3. package/dist/cli.js +559 -400
  4. package/dist/detect/adapters/.bundle-shasums.json +4 -7
  5. package/dist/detect/adapters/rails.js +6 -4
  6. package/dist/detect/adapters/spring.js +6 -4
  7. package/dist/detect/adapters/tree-sitter-loader.js +6 -4
  8. package/dist/hooks/auto-learning-pipeline.js +24 -1
  9. package/dist/hooks/classify-failure.js +32 -3
  10. package/dist/hooks/cost-tracker.js +24 -0
  11. package/dist/hooks/fix-detector.js +97 -1
  12. package/dist/hooks/incident-pipeline.js +30 -1
  13. package/dist/hooks/intent-suggester.js +6 -3
  14. package/dist/hooks/post-edit-context.js +24 -1
  15. package/dist/hooks/post-tool-use.js +66 -18
  16. package/dist/hooks/pre-compact.js +27 -2
  17. package/dist/hooks/pre-delete-check.js +48 -22
  18. package/dist/hooks/pre-tool-use-gate.js +962 -0
  19. package/dist/hooks/quality-event.js +24 -0
  20. package/dist/hooks/rule-enforcement-pipeline.js +24 -1
  21. package/dist/hooks/security-gate.js +29 -16
  22. package/dist/hooks/session-end.js +24 -0
  23. package/dist/hooks/session-start.js +8710 -8460
  24. package/dist/hooks/user-prompt.js +24 -0
  25. package/package.json +15 -18
  26. package/commands/README.md +0 -134
  27. package/src/adapter.ts +0 -121
  28. package/src/adr-generator.ts +0 -292
  29. package/src/analytics.ts +0 -373
  30. package/src/audit-trail.ts +0 -450
  31. package/src/backfill-sessions.ts +0 -182
  32. package/src/changelog-generator.ts +0 -178
  33. package/src/cli.ts +0 -246
  34. package/src/cloud-sync.ts +0 -234
  35. package/src/commands/adapters.ts +0 -824
  36. package/src/commands/changelog.ts +0 -165
  37. package/src/commands/config-check-drift.ts +0 -133
  38. package/src/commands/config-refresh.ts +0 -396
  39. package/src/commands/config-upgrade.ts +0 -127
  40. package/src/commands/doctor.ts +0 -485
  41. package/src/commands/hook-runner.ts +0 -145
  42. package/src/commands/init.ts +0 -1823
  43. package/src/commands/install-commands.ts +0 -692
  44. package/src/commands/install-hooks.ts +0 -27
  45. package/src/commands/permissions.ts +0 -150
  46. package/src/commands/refresh-log.ts +0 -37
  47. package/src/commands/show-template.ts +0 -65
  48. package/src/commands/template-engine.ts +0 -301
  49. package/src/commands/watch.ts +0 -430
  50. package/src/config.ts +0 -765
  51. package/src/cost-tracker.ts +0 -355
  52. package/src/db.ts +0 -370
  53. package/src/dependency-scorer.ts +0 -337
  54. package/src/detect/adapters/aspnet.ts +0 -4
  55. package/src/detect/adapters/discover.ts +0 -469
  56. package/src/detect/adapters/file-sampler.ts +0 -225
  57. package/src/detect/adapters/go-chi.ts +0 -4
  58. package/src/detect/adapters/index.ts +0 -51
  59. package/src/detect/adapters/nextjs-trpc.ts +0 -166
  60. package/src/detect/adapters/parse-guard.ts +0 -133
  61. package/src/detect/adapters/phoenix.ts +0 -4
  62. package/src/detect/adapters/python-django.ts +0 -208
  63. package/src/detect/adapters/python-fastapi.ts +0 -223
  64. package/src/detect/adapters/python-flask.ts +0 -235
  65. package/src/detect/adapters/query-helpers.ts +0 -170
  66. package/src/detect/adapters/rails.ts +0 -4
  67. package/src/detect/adapters/runner.ts +0 -284
  68. package/src/detect/adapters/spring.ts +0 -4
  69. package/src/detect/adapters/swift-swiftui.ts +0 -171
  70. package/src/detect/adapters/tree-sitter-loader.ts +0 -517
  71. package/src/detect/adapters/types.ts +0 -191
  72. package/src/detect/codebase-introspector.ts +0 -210
  73. package/src/detect/domain-inferrer.ts +0 -197
  74. package/src/detect/drift.ts +0 -199
  75. package/src/detect/framework-detector.ts +0 -307
  76. package/src/detect/index.ts +0 -209
  77. package/src/detect/manifest-registry.ts +0 -261
  78. package/src/detect/migrate.ts +0 -362
  79. package/src/detect/monorepo-detector.ts +0 -348
  80. package/src/detect/package-detector.ts +0 -828
  81. package/src/detect/passthrough.ts +0 -108
  82. package/src/detect/regex-fallback.ts +0 -449
  83. package/src/detect/source-dir-detector.ts +0 -297
  84. package/src/detect/vr-command-map.ts +0 -167
  85. package/src/docs-map.json +0 -100
  86. package/src/docs-tools.ts +0 -519
  87. package/src/domains.ts +0 -182
  88. package/src/hooks/auto-learning-pipeline.ts +0 -228
  89. package/src/hooks/classify-failure.ts +0 -259
  90. package/src/hooks/cost-tracker.ts +0 -66
  91. package/src/hooks/fix-detector.ts +0 -186
  92. package/src/hooks/incident-pipeline.ts +0 -190
  93. package/src/hooks/intent-suggester.ts +0 -131
  94. package/src/hooks/post-edit-context.ts +0 -91
  95. package/src/hooks/post-tool-use.ts +0 -413
  96. package/src/hooks/pre-compact.ts +0 -168
  97. package/src/hooks/pre-delete-check.ts +0 -243
  98. package/src/hooks/quality-event.ts +0 -127
  99. package/src/hooks/rule-enforcement-pipeline.ts +0 -159
  100. package/src/hooks/security-gate.ts +0 -153
  101. package/src/hooks/session-end.ts +0 -467
  102. package/src/hooks/session-start.ts +0 -436
  103. package/src/hooks/user-prompt.ts +0 -157
  104. package/src/import-resolver.ts +0 -226
  105. package/src/knowledge-db.ts +0 -176
  106. package/src/knowledge-indexer.ts +0 -708
  107. package/src/knowledge-tools.ts +0 -1414
  108. package/src/lib/fileLock.ts +0 -203
  109. package/src/lib/gitToplevel.ts +0 -22
  110. package/src/lib/hook-registry.ts +0 -43
  111. package/src/lib/installLock.ts +0 -66
  112. package/src/lib/memory-path.ts +0 -49
  113. package/src/lib/pidLiveness.ts +0 -67
  114. package/src/lib/settings-local.ts +0 -110
  115. package/src/lib/sql-table-names.ts +0 -71
  116. package/src/license.ts +0 -547
  117. package/src/lsp/auto-detect.ts +0 -98
  118. package/src/lsp/client.ts +0 -776
  119. package/src/lsp/enrich.ts +0 -127
  120. package/src/lsp/types.ts +0 -221
  121. package/src/memory-db.ts +0 -1614
  122. package/src/memory-file-ingest.ts +0 -128
  123. package/src/memory-tools.ts +0 -424
  124. package/src/middleware-tree.ts +0 -71
  125. package/src/observability-tools.ts +0 -343
  126. package/src/observation-extractor.ts +0 -418
  127. package/src/page-deps.ts +0 -285
  128. package/src/permissions.ts +0 -422
  129. package/src/prompt-analyzer.ts +0 -332
  130. package/src/python/coupling-detector.ts +0 -125
  131. package/src/python/domain-enforcer.ts +0 -84
  132. package/src/python/impact-analyzer.ts +0 -96
  133. package/src/python/import-parser.ts +0 -244
  134. package/src/python/import-resolver.ts +0 -136
  135. package/src/python/migration-indexer.ts +0 -116
  136. package/src/python/migration-parser.ts +0 -332
  137. package/src/python/model-indexer.ts +0 -71
  138. package/src/python/model-parser.ts +0 -279
  139. package/src/python/route-indexer.ts +0 -59
  140. package/src/python/route-parser.ts +0 -317
  141. package/src/python-tools.ts +0 -630
  142. package/src/regression-detector.ts +0 -319
  143. package/src/rules.ts +0 -57
  144. package/src/schema-mapper.ts +0 -232
  145. package/src/security/adapter-origin.ts +0 -130
  146. package/src/security/adapter-verifier.ts +0 -319
  147. package/src/security/atomic-write.ts +0 -164
  148. package/src/security/fetcher.ts +0 -200
  149. package/src/security/install-tracking.ts +0 -319
  150. package/src/security/license-pubkey.generated.ts +0 -30
  151. package/src/security/license-response-verifier.ts +0 -149
  152. package/src/security/local-fingerprint.ts +0 -225
  153. package/src/security/manifest-cache.ts +0 -333
  154. package/src/security/manifest-schema.ts +0 -129
  155. package/src/security/registry-pubkey.generated.ts +0 -35
  156. package/src/security/telemetry.ts +0 -320
  157. package/src/security-scorer.ts +0 -405
  158. package/src/security-utils.ts +0 -133
  159. package/src/sentinel-db.ts +0 -580
  160. package/src/sentinel-scanner.ts +0 -406
  161. package/src/sentinel-tools.ts +0 -513
  162. package/src/sentinel-types.ts +0 -140
  163. package/src/server-dispatch.ts +0 -225
  164. package/src/server.ts +0 -107
  165. package/src/session-archiver.ts +0 -111
  166. package/src/session-state-generator.ts +0 -174
  167. package/src/team-knowledge.ts +0 -407
  168. package/src/tool-db-needs.ts +0 -232
  169. package/src/tools.ts +0 -1205
  170. package/src/transcript-parser.ts +0 -458
  171. package/src/trpc-index.ts +0 -215
  172. package/src/validate-features-runner.ts +0 -107
  173. package/src/validation-engine.ts +0 -358
  174. package/src/watch/daemon.ts +0 -385
  175. package/src/watch/lockfile-detector.ts +0 -65
  176. package/src/watch/paths.ts +0 -279
  177. 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
- }