@massu/core 1.10.8 → 1.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1334 -0
- package/dist/adapter.js +6 -4
- package/dist/cli.js +581 -448
- package/dist/detect/adapters/.bundle-shasums.json +4 -7
- package/dist/detect/adapters/rails.js +6 -4
- package/dist/detect/adapters/spring.js +6 -4
- package/dist/detect/adapters/tree-sitter-loader.js +6 -4
- package/dist/hooks/auto-learning-pipeline.js +24 -1
- package/dist/hooks/classify-failure.js +32 -3
- package/dist/hooks/cost-tracker.js +24 -0
- package/dist/hooks/fix-detector.js +97 -1
- package/dist/hooks/incident-pipeline.js +30 -1
- package/dist/hooks/intent-suggester.js +6 -3
- package/dist/hooks/post-edit-context.js +24 -1
- package/dist/hooks/post-tool-use.js +67 -19
- package/dist/hooks/pre-compact.js +28 -3
- package/dist/hooks/pre-delete-check.js +53 -27
- package/dist/hooks/pre-tool-use-gate.js +962 -0
- package/dist/hooks/quality-event.js +24 -0
- package/dist/hooks/rule-enforcement-pipeline.js +24 -1
- package/dist/hooks/security-gate.js +29 -16
- package/dist/hooks/session-end.js +31 -6
- package/dist/hooks/session-start.js +8709 -8459
- package/dist/hooks/user-prompt.js +25 -1
- package/package.json +15 -18
- package/commands/README.md +0 -134
- package/src/adapter.ts +0 -121
- package/src/adr-generator.ts +0 -292
- package/src/analytics.ts +0 -373
- package/src/audit-trail.ts +0 -450
- package/src/backfill-sessions.ts +0 -182
- package/src/changelog-generator.ts +0 -178
- package/src/cli.ts +0 -246
- package/src/cloud-sync.ts +0 -234
- package/src/commands/adapters.ts +0 -824
- package/src/commands/changelog.ts +0 -165
- package/src/commands/config-check-drift.ts +0 -133
- package/src/commands/config-refresh.ts +0 -396
- package/src/commands/config-upgrade.ts +0 -127
- package/src/commands/doctor.ts +0 -485
- package/src/commands/hook-runner.ts +0 -145
- package/src/commands/init.ts +0 -1823
- package/src/commands/install-commands.ts +0 -692
- package/src/commands/install-hooks.ts +0 -27
- package/src/commands/permissions.ts +0 -150
- package/src/commands/refresh-log.ts +0 -37
- package/src/commands/show-template.ts +0 -65
- package/src/commands/template-engine.ts +0 -301
- package/src/commands/watch.ts +0 -430
- package/src/config.ts +0 -765
- package/src/cost-tracker.ts +0 -355
- package/src/db.ts +0 -370
- package/src/dependency-scorer.ts +0 -337
- package/src/detect/adapters/aspnet.ts +0 -4
- package/src/detect/adapters/discover.ts +0 -469
- package/src/detect/adapters/file-sampler.ts +0 -225
- package/src/detect/adapters/go-chi.ts +0 -4
- package/src/detect/adapters/index.ts +0 -51
- package/src/detect/adapters/nextjs-trpc.ts +0 -166
- package/src/detect/adapters/parse-guard.ts +0 -133
- package/src/detect/adapters/phoenix.ts +0 -4
- package/src/detect/adapters/python-django.ts +0 -208
- package/src/detect/adapters/python-fastapi.ts +0 -223
- package/src/detect/adapters/python-flask.ts +0 -235
- package/src/detect/adapters/query-helpers.ts +0 -170
- package/src/detect/adapters/rails.ts +0 -4
- package/src/detect/adapters/runner.ts +0 -284
- package/src/detect/adapters/spring.ts +0 -4
- package/src/detect/adapters/swift-swiftui.ts +0 -171
- package/src/detect/adapters/tree-sitter-loader.ts +0 -517
- package/src/detect/adapters/types.ts +0 -191
- package/src/detect/codebase-introspector.ts +0 -210
- package/src/detect/domain-inferrer.ts +0 -197
- package/src/detect/drift.ts +0 -199
- package/src/detect/framework-detector.ts +0 -307
- package/src/detect/index.ts +0 -209
- package/src/detect/manifest-registry.ts +0 -261
- package/src/detect/migrate.ts +0 -362
- package/src/detect/monorepo-detector.ts +0 -348
- package/src/detect/package-detector.ts +0 -828
- package/src/detect/passthrough.ts +0 -108
- package/src/detect/regex-fallback.ts +0 -449
- package/src/detect/source-dir-detector.ts +0 -297
- package/src/detect/vr-command-map.ts +0 -167
- package/src/docs-map.json +0 -100
- package/src/docs-tools.ts +0 -519
- package/src/domains.ts +0 -182
- package/src/hooks/auto-learning-pipeline.ts +0 -228
- package/src/hooks/classify-failure.ts +0 -259
- package/src/hooks/cost-tracker.ts +0 -66
- package/src/hooks/fix-detector.ts +0 -186
- package/src/hooks/incident-pipeline.ts +0 -190
- package/src/hooks/intent-suggester.ts +0 -131
- package/src/hooks/post-edit-context.ts +0 -91
- package/src/hooks/post-tool-use.ts +0 -413
- package/src/hooks/pre-compact.ts +0 -168
- package/src/hooks/pre-delete-check.ts +0 -243
- package/src/hooks/quality-event.ts +0 -127
- package/src/hooks/rule-enforcement-pipeline.ts +0 -159
- package/src/hooks/security-gate.ts +0 -153
- package/src/hooks/session-end.ts +0 -467
- package/src/hooks/session-start.ts +0 -436
- package/src/hooks/user-prompt.ts +0 -157
- package/src/import-resolver.ts +0 -226
- package/src/knowledge-db.ts +0 -176
- package/src/knowledge-indexer.ts +0 -708
- package/src/knowledge-tools.ts +0 -1414
- package/src/lib/fileLock.ts +0 -203
- package/src/lib/framework-supports.ts +0 -85
- package/src/lib/gitToplevel.ts +0 -22
- package/src/lib/hook-registry.ts +0 -43
- package/src/lib/installLock.ts +0 -66
- package/src/lib/memory-path.ts +0 -49
- package/src/lib/pidLiveness.ts +0 -67
- package/src/lib/settings-local.ts +0 -110
- package/src/lib/sql-table-names.ts +0 -71
- package/src/license.ts +0 -547
- package/src/lsp/auto-detect.ts +0 -98
- package/src/lsp/client.ts +0 -776
- package/src/lsp/enrich.ts +0 -127
- package/src/lsp/types.ts +0 -221
- package/src/memory-db.ts +0 -1614
- package/src/memory-file-ingest.ts +0 -128
- package/src/memory-tools.ts +0 -424
- package/src/middleware-tree.ts +0 -71
- package/src/observability-tools.ts +0 -343
- package/src/observation-extractor.ts +0 -418
- package/src/page-deps.ts +0 -285
- package/src/permissions.ts +0 -422
- package/src/prompt-analyzer.ts +0 -332
- package/src/python/coupling-detector.ts +0 -125
- package/src/python/domain-enforcer.ts +0 -84
- package/src/python/impact-analyzer.ts +0 -96
- package/src/python/import-parser.ts +0 -244
- package/src/python/import-resolver.ts +0 -136
- package/src/python/migration-indexer.ts +0 -116
- package/src/python/migration-parser.ts +0 -332
- package/src/python/model-indexer.ts +0 -71
- package/src/python/model-parser.ts +0 -279
- package/src/python/route-indexer.ts +0 -59
- package/src/python/route-parser.ts +0 -317
- package/src/python-tools.ts +0 -630
- package/src/regression-detector.ts +0 -319
- package/src/rules.ts +0 -57
- package/src/schema-mapper.ts +0 -232
- package/src/security/adapter-origin.ts +0 -130
- package/src/security/adapter-verifier.ts +0 -319
- package/src/security/atomic-write.ts +0 -164
- package/src/security/fetcher.ts +0 -200
- package/src/security/install-tracking.ts +0 -319
- package/src/security/license-pubkey.generated.ts +0 -30
- package/src/security/license-response-verifier.ts +0 -149
- package/src/security/local-fingerprint.ts +0 -225
- package/src/security/manifest-cache.ts +0 -333
- package/src/security/manifest-schema.ts +0 -129
- package/src/security/registry-pubkey.generated.ts +0 -35
- package/src/security/telemetry.ts +0 -320
- package/src/security-scorer.ts +0 -405
- package/src/security-utils.ts +0 -133
- package/src/sentinel-db.ts +0 -580
- package/src/sentinel-scanner.ts +0 -406
- package/src/sentinel-tools.ts +0 -513
- package/src/sentinel-types.ts +0 -140
- package/src/server-dispatch.ts +0 -225
- package/src/server.ts +0 -107
- package/src/session-archiver.ts +0 -111
- package/src/session-state-generator.ts +0 -174
- package/src/team-knowledge.ts +0 -407
- package/src/tool-db-needs.ts +0 -232
- package/src/tools.ts +0 -1206
- package/src/transcript-parser.ts +0 -458
- package/src/trpc-index.ts +0 -215
- package/src/validate-features-runner.ts +0 -107
- package/src/validation-engine.ts +0 -358
- package/src/watch/daemon.ts +0 -385
- package/src/watch/lockfile-detector.ts +0 -65
- package/src/watch/paths.ts +0 -279
- package/src/watch/state.ts +0 -178
|
@@ -1,284 +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 runner.
|
|
6
|
-
*
|
|
7
|
-
* Orchestrates: filter adapters via `matches()`, run them, isolate failures
|
|
8
|
-
* via per-adapter try/catch (audit-iter-5 fix HH test (d)), and merge their
|
|
9
|
-
* results.
|
|
10
|
-
*
|
|
11
|
-
* Confidence merge rule (spec §5):
|
|
12
|
-
* - 'high' / 'medium' / 'low' → field is written, with per-field provenance.
|
|
13
|
-
* - 'none' → field DROPPED (introspect's regex fallback may then emit it).
|
|
14
|
-
*
|
|
15
|
-
* AST-wins rule:
|
|
16
|
-
* - When the same conventions key appears in two adapters that BOTH return
|
|
17
|
-
* non-'none', the FIRST adapter (by source-list order) wins. This is
|
|
18
|
-
* deterministic — adapters are listed in `runner.ts`'s static array,
|
|
19
|
-
* never user-provided.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import { basename, relative } from 'path';
|
|
23
|
-
import type {
|
|
24
|
-
AdapterResolved,
|
|
25
|
-
CodebaseAdapter,
|
|
26
|
-
DetectionSignals,
|
|
27
|
-
MergedAdapterOutput,
|
|
28
|
-
Provenance,
|
|
29
|
-
SourceFile,
|
|
30
|
-
} from './types.ts';
|
|
31
|
-
import { isParsableSource, MAX_AST_FILE_BYTES } from './parse-guard.ts';
|
|
32
|
-
|
|
33
|
-
export interface RunAdaptersOptions {
|
|
34
|
-
/**
|
|
35
|
-
* Optional file sampler — given an adapter and the project root, returns
|
|
36
|
-
* the SourceFile[] the adapter should consume. If omitted, the runner
|
|
37
|
-
* passes an empty file list (useful in unit tests where the caller has
|
|
38
|
-
* already constructed adapters that don't need files).
|
|
39
|
-
*/
|
|
40
|
-
sampleFiles?: (adapter: CodebaseAdapter, rootDir: string) => Promise<SourceFile[]> | SourceFile[];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Run a static list of adapters against a project root.
|
|
45
|
-
*
|
|
46
|
-
* Per-adapter try/catch isolation: a single adapter throwing MUST NOT crash
|
|
47
|
-
* the runner. The error is captured in `errored[]` and the runner continues.
|
|
48
|
-
*
|
|
49
|
-
* @param adapters - Static list of first-party adapters (no user-authored
|
|
50
|
-
* adapters at v1 — Plan 3c will add discovery).
|
|
51
|
-
* @param rootDir - Absolute project root.
|
|
52
|
-
* @param signals - Pre-built `DetectionSignals` (manifest reads, present
|
|
53
|
-
* dirs/files). Adapters consume these read-only.
|
|
54
|
-
* @param options - Hooks for testing.
|
|
55
|
-
*/
|
|
56
|
-
export async function runAdapters(
|
|
57
|
-
adapters: CodebaseAdapter[],
|
|
58
|
-
rootDir: string,
|
|
59
|
-
signals: DetectionSignals,
|
|
60
|
-
options: RunAdaptersOptions = {},
|
|
61
|
-
): Promise<MergedAdapterOutput> {
|
|
62
|
-
const out: MergedAdapterOutput = {
|
|
63
|
-
byAdapter: {},
|
|
64
|
-
skipped: [],
|
|
65
|
-
errored: [],
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
// AST-wins / per-adapter merge:
|
|
69
|
-
// Each adapter writes to its own `detected.<adapter.id>` namespace, so
|
|
70
|
-
// global field collisions across adapters can't happen at the conventions
|
|
71
|
-
// level. The "AST-wins" rule in the spec applies at the introspector tier
|
|
72
|
-
// (regex fallback only fills fields the adapter returned 'none' for).
|
|
73
|
-
// Within a single adapter, if `conventions` repeats a key (shouldn't, but
|
|
74
|
-
// defensively), the first occurrence wins. For multiple adapters with the
|
|
75
|
-
// same id (shouldn't, but defensively), the first wins.
|
|
76
|
-
|
|
77
|
-
for (const adapter of adapters) {
|
|
78
|
-
if (out.byAdapter[adapter.id] || out.skipped.includes(adapter.id)) {
|
|
79
|
-
// Duplicate adapter id → skip the second one to preserve first-wins.
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
let matches: boolean;
|
|
83
|
-
try {
|
|
84
|
-
matches = adapter.matches(signals);
|
|
85
|
-
} catch (e) {
|
|
86
|
-
out.errored.push({
|
|
87
|
-
adapterId: adapter.id,
|
|
88
|
-
error: `matches() threw: ${e instanceof Error ? e.message : String(e)}`,
|
|
89
|
-
});
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
if (!matches) {
|
|
93
|
-
out.skipped.push(adapter.id);
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
let files: SourceFile[];
|
|
98
|
-
try {
|
|
99
|
-
files = options.sampleFiles
|
|
100
|
-
? await options.sampleFiles(adapter, rootDir)
|
|
101
|
-
: [];
|
|
102
|
-
} catch (e) {
|
|
103
|
-
out.errored.push({
|
|
104
|
-
adapterId: adapter.id,
|
|
105
|
-
error: `sampleFiles threw: ${e instanceof Error ? e.message : String(e)}`,
|
|
106
|
-
});
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Phase 3.5 fix: size + depth + control-byte gate. Drop adversarial
|
|
111
|
-
// inputs BEFORE the adapter sees them — adapters trust this layer.
|
|
112
|
-
// Files dropped here are logged once per drop so operators see the
|
|
113
|
-
// signal; the adapter then runs against the surviving subset.
|
|
114
|
-
const safeFiles: SourceFile[] = [];
|
|
115
|
-
for (const f of files) {
|
|
116
|
-
const skip = isParsableSource(f.content, f.size);
|
|
117
|
-
if (skip) {
|
|
118
|
-
process.stderr.write(
|
|
119
|
-
`[massu/ast] WARN: skipping ${f.path} for adapter ${adapter.id}: ${skip.reason} (${skip.detail}). Cap=${MAX_AST_FILE_BYTES} bytes. (Phase 3.5 mitigation)\n`,
|
|
120
|
-
);
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
safeFiles.push(f);
|
|
124
|
-
}
|
|
125
|
-
files = safeFiles;
|
|
126
|
-
|
|
127
|
-
let result;
|
|
128
|
-
try {
|
|
129
|
-
result = await adapter.introspect(files, rootDir);
|
|
130
|
-
} catch (e) {
|
|
131
|
-
out.errored.push({
|
|
132
|
-
adapterId: adapter.id,
|
|
133
|
-
error: `introspect() threw: ${e instanceof Error ? e.message : String(e)}`,
|
|
134
|
-
});
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// 'none' confidence drops the entire adapter result. The runner records
|
|
139
|
-
// that the adapter was attempted (in `byAdapter`) so callers can see it
|
|
140
|
-
// ran, but with empty conventions. introspect()'s regex fallback then
|
|
141
|
-
// takes over for the field.
|
|
142
|
-
if (result.confidence === 'none') {
|
|
143
|
-
out.byAdapter[adapter.id] = {
|
|
144
|
-
conventions: {},
|
|
145
|
-
_provenance: {},
|
|
146
|
-
confidence: 'none',
|
|
147
|
-
};
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Merge: keep first occurrence of each field (defensive against an
|
|
152
|
-
// adapter accidentally writing the same field twice).
|
|
153
|
-
const conventions: Record<string, unknown> = {};
|
|
154
|
-
const provenanceMap: Record<string, string> = {};
|
|
155
|
-
for (const [field, value] of Object.entries(result.conventions)) {
|
|
156
|
-
if (value === null || value === undefined) continue;
|
|
157
|
-
if (field in conventions) continue;
|
|
158
|
-
conventions[field] = value;
|
|
159
|
-
}
|
|
160
|
-
for (const p of result.provenance) {
|
|
161
|
-
if (p.field in provenanceMap) continue;
|
|
162
|
-
provenanceMap[p.field] = formatProvenance(p, rootDir);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const resolved: AdapterResolved = {
|
|
166
|
-
conventions,
|
|
167
|
-
_provenance: provenanceMap,
|
|
168
|
-
confidence: result.confidence,
|
|
169
|
-
};
|
|
170
|
-
out.byAdapter[adapter.id] = resolved;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return out;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function formatProvenance(p: Provenance, rootDir: string): string {
|
|
177
|
-
const rel = p.sourceFile.startsWith(rootDir + '/')
|
|
178
|
-
? relative(rootDir, p.sourceFile)
|
|
179
|
-
: basename(p.sourceFile);
|
|
180
|
-
return `${rel}:${p.line} :: ${p.query}`;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// ============================================================
|
|
184
|
-
// Signal builder — used by codebase-introspector to feed the runner
|
|
185
|
-
// ============================================================
|
|
186
|
-
|
|
187
|
-
import { existsSync, readdirSync, readFileSync, statSync } from 'fs';
|
|
188
|
-
import { join } from 'path';
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Build a `DetectionSignals` bundle by reading manifest files at the project
|
|
192
|
-
* root. Cheap (one-level dir scan + a handful of file reads). Failures
|
|
193
|
-
* degrade gracefully — a missing manifest just means that field is undefined.
|
|
194
|
-
*/
|
|
195
|
-
export function buildDetectionSignals(rootDir: string): DetectionSignals {
|
|
196
|
-
const presentDirs = new Set<string>();
|
|
197
|
-
const presentFiles = new Set<string>();
|
|
198
|
-
try {
|
|
199
|
-
for (const entry of readdirSync(rootDir)) {
|
|
200
|
-
if (entry.startsWith('.')) continue;
|
|
201
|
-
try {
|
|
202
|
-
const st = statSync(join(rootDir, entry));
|
|
203
|
-
if (st.isDirectory()) presentDirs.add(entry);
|
|
204
|
-
else if (st.isFile()) presentFiles.add(entry);
|
|
205
|
-
} catch {
|
|
206
|
-
/* ignore */
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
} catch {
|
|
210
|
-
/* unreadable root → empty signals */
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return {
|
|
214
|
-
packageJson: tryReadJson(join(rootDir, 'package.json')),
|
|
215
|
-
pyprojectToml: tryReadToml(join(rootDir, 'pyproject.toml')),
|
|
216
|
-
gemfile: tryReadString(join(rootDir, 'Gemfile')),
|
|
217
|
-
cargoToml: tryReadToml(join(rootDir, 'Cargo.toml')),
|
|
218
|
-
goMod: tryReadString(join(rootDir, 'go.mod')),
|
|
219
|
-
mixExs: tryReadString(join(rootDir, 'mix.exs')),
|
|
220
|
-
csproj: tryReadFirstCsproj(rootDir, presentFiles),
|
|
221
|
-
pomXml: tryReadString(join(rootDir, 'pom.xml')),
|
|
222
|
-
gradleBuild: tryReadGradleBuild(rootDir, presentFiles),
|
|
223
|
-
presentDirs,
|
|
224
|
-
presentFiles,
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Find the first `.csproj` file at the project root (sorted alphabetically
|
|
230
|
-
* for determinism) and return its raw XML content. Returns undefined if
|
|
231
|
-
* none exist. Multi-project solutions where the root has no top-level
|
|
232
|
-
* .csproj (the `.sln` lives at root and projects are in subdirs) get
|
|
233
|
-
* `csproj=undefined` — adapters then degrade per their own logic.
|
|
234
|
-
*/
|
|
235
|
-
function tryReadFirstCsproj(rootDir: string, presentFiles: Set<string>): string | undefined {
|
|
236
|
-
const csprojNames = [...presentFiles].filter((f) => f.endsWith('.csproj')).sort();
|
|
237
|
-
if (csprojNames.length === 0) return undefined;
|
|
238
|
-
return tryReadString(join(rootDir, csprojNames[0]!));
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Read `build.gradle.kts` (Kotlin DSL — modern default per Gradle 7+) if
|
|
243
|
-
* present; otherwise fall back to `build.gradle` (legacy Groovy DSL).
|
|
244
|
-
* Returns undefined if neither exists.
|
|
245
|
-
*/
|
|
246
|
-
function tryReadGradleBuild(rootDir: string, presentFiles: Set<string>): string | undefined {
|
|
247
|
-
if (presentFiles.has('build.gradle.kts')) {
|
|
248
|
-
return tryReadString(join(rootDir, 'build.gradle.kts'));
|
|
249
|
-
}
|
|
250
|
-
if (presentFiles.has('build.gradle')) {
|
|
251
|
-
return tryReadString(join(rootDir, 'build.gradle'));
|
|
252
|
-
}
|
|
253
|
-
return undefined;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function tryReadString(path: string): string | undefined {
|
|
257
|
-
if (!existsSync(path)) return undefined;
|
|
258
|
-
try {
|
|
259
|
-
return readFileSync(path, 'utf-8');
|
|
260
|
-
} catch {
|
|
261
|
-
return undefined;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function tryReadJson(path: string): Record<string, unknown> | undefined {
|
|
266
|
-
const txt = tryReadString(path);
|
|
267
|
-
if (!txt) return undefined;
|
|
268
|
-
try {
|
|
269
|
-
const parsed = JSON.parse(txt);
|
|
270
|
-
return typeof parsed === 'object' && parsed !== null ? (parsed as Record<string, unknown>) : undefined;
|
|
271
|
-
} catch {
|
|
272
|
-
return undefined;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function tryReadToml(path: string): Record<string, unknown> | undefined {
|
|
277
|
-
const txt = tryReadString(path);
|
|
278
|
-
if (!txt) return undefined;
|
|
279
|
-
// Cheap signal-only parse: we just need top-level table presence + keys.
|
|
280
|
-
// Avoid pulling the full toml parser for this; check `[project]`/`[tool.x]`
|
|
281
|
-
// headers and treat `tool.poetry.dependencies` etc. as opaque text-search.
|
|
282
|
-
// Adapters that need structured data can grep `txt` themselves.
|
|
283
|
-
return { __raw: txt };
|
|
284
|
-
}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
// Plan 3c Phase 9b P-A-005: re-export shim. Source-of-truth lives at
|
|
2
|
-
// `packages/adapter-spring/src/index.ts` (workspace package). This shim
|
|
3
|
-
// preserves the legacy import path used by codebase-introspector + tests.
|
|
4
|
-
export { springAdapter } from '@massu/adapter-spring';
|
|
@@ -1,171 +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: SwiftUI AST adapter.
|
|
6
|
-
*
|
|
7
|
-
* Extracts:
|
|
8
|
-
* - api_client_class: identifier ending in `API` (e.g. `HedgeAPI`)
|
|
9
|
-
* - biometric_policy: `LAPolicy.deviceOwnerAuthenticationWithBiometrics` etc.
|
|
10
|
-
* - navigation_pattern: 'NavigationStack' | 'NavigationView' | null
|
|
11
|
-
*
|
|
12
|
-
* Tree-sitter Swift grammar quirks: the `tree-sitter-swift` grammar names some
|
|
13
|
-
* nodes differently from python/typescript. We use simpler, more permissive
|
|
14
|
-
* S-expressions that fall back to capture-text matching where needed.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { Parser } from 'web-tree-sitter';
|
|
18
|
-
import type { CodebaseAdapter, AdapterResult, DetectionSignals, Provenance, SourceFile } from './types.ts';
|
|
19
|
-
import { runQuery, InvalidQueryError } from './query-helpers.ts';
|
|
20
|
-
import { loadGrammar } from './tree-sitter-loader.ts';
|
|
21
|
-
import { isParsableSource, MAX_AST_FILE_BYTES } from './parse-guard.ts';
|
|
22
|
-
|
|
23
|
-
// ============================================================
|
|
24
|
-
// Queries
|
|
25
|
-
// ============================================================
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Identifier that looks like an API client class. Captures any uppercase-led
|
|
29
|
-
* identifier ending in `API`. Predicate filtering is done in JS — Swift's
|
|
30
|
-
* grammar doesn't surface a clean class-instantiation pattern uniformly.
|
|
31
|
-
*/
|
|
32
|
-
const API_CLASS_QUERY = `
|
|
33
|
-
(simple_identifier) @ident
|
|
34
|
-
`;
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* `.deviceOwnerAuthentication` / `.deviceOwnerAuthenticationWithBiometrics`
|
|
38
|
-
* member access. Captures the property name.
|
|
39
|
-
*/
|
|
40
|
-
const POLICY_QUERY = `
|
|
41
|
-
(navigation_expression
|
|
42
|
-
suffix: (navigation_suffix
|
|
43
|
-
(simple_identifier) @policy_name))
|
|
44
|
-
`;
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* NavigationStack / NavigationView usage. Captures any reference to either
|
|
48
|
-
* symbol.
|
|
49
|
-
*/
|
|
50
|
-
const NAV_QUERY = `
|
|
51
|
-
(simple_identifier) @nav_ident
|
|
52
|
-
`;
|
|
53
|
-
|
|
54
|
-
// ============================================================
|
|
55
|
-
// Adapter
|
|
56
|
-
// ============================================================
|
|
57
|
-
|
|
58
|
-
const POLICY_NAMES = new Set([
|
|
59
|
-
'deviceOwnerAuthentication',
|
|
60
|
-
'deviceOwnerAuthenticationWithBiometrics',
|
|
61
|
-
]);
|
|
62
|
-
|
|
63
|
-
export const swiftSwiftUiAdapter: CodebaseAdapter = {
|
|
64
|
-
id: 'swift-swiftui',
|
|
65
|
-
languages: ['swift'],
|
|
66
|
-
|
|
67
|
-
matches(signals: DetectionSignals): boolean {
|
|
68
|
-
// Swift signal: presence of Package.swift, *.xcodeproj, or Sources/ dir
|
|
69
|
-
if (signals.presentFiles.has('Package.swift')) return true;
|
|
70
|
-
for (const dir of signals.presentDirs) {
|
|
71
|
-
if (dir.endsWith('.xcodeproj') || dir.endsWith('.xcworkspace')) return true;
|
|
72
|
-
if (dir === 'Sources') return true;
|
|
73
|
-
}
|
|
74
|
-
for (const file of signals.presentFiles) {
|
|
75
|
-
if (file.endsWith('.swift')) return true;
|
|
76
|
-
}
|
|
77
|
-
return false;
|
|
78
|
-
},
|
|
79
|
-
|
|
80
|
-
async introspect(files: SourceFile[], _rootDir: string): Promise<AdapterResult> {
|
|
81
|
-
if (files.length === 0) {
|
|
82
|
-
return { conventions: {}, provenance: [], confidence: 'none' };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
let language;
|
|
86
|
-
try {
|
|
87
|
-
language = await loadGrammar('swift');
|
|
88
|
-
} catch {
|
|
89
|
-
return { conventions: {}, provenance: [], confidence: 'none' };
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const parser = new Parser();
|
|
93
|
-
parser.setLanguage(language);
|
|
94
|
-
|
|
95
|
-
const apiClasses = new Map<string, { line: number; file: string }>();
|
|
96
|
-
const policies = new Map<string, { line: number; file: string }>();
|
|
97
|
-
const navs = new Map<string, { line: number; file: string }>();
|
|
98
|
-
|
|
99
|
-
try {
|
|
100
|
-
for (const file of files) {
|
|
101
|
-
const skip = isParsableSource(file.content, file.size);
|
|
102
|
-
if (skip) {
|
|
103
|
-
process.stderr.write(
|
|
104
|
-
`[massu/ast] WARN: swift-swiftui skipping ${file.path}: ${skip.reason} (${skip.detail}). Cap=${MAX_AST_FILE_BYTES}. (Phase 3.5 mitigation)\n`,
|
|
105
|
-
);
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
try {
|
|
109
|
-
// API class names: filter via JS regex on the captured identifier
|
|
110
|
-
for (const hit of runQuery(parser, file.content, API_CLASS_QUERY, 'swift-api-class', file.path)) {
|
|
111
|
-
const ident = hit.captures.ident;
|
|
112
|
-
if (ident && /^[A-Z][A-Za-z0-9_]*API$/.test(ident) && !apiClasses.has(ident)) {
|
|
113
|
-
apiClasses.set(ident, { line: hit.line, file: file.path });
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
// Biometric policy
|
|
117
|
-
for (const hit of runQuery(parser, file.content, POLICY_QUERY, 'swift-biometric-policy', file.path)) {
|
|
118
|
-
const name = hit.captures.policy_name;
|
|
119
|
-
if (name && POLICY_NAMES.has(name) && !policies.has(name)) {
|
|
120
|
-
policies.set(name, { line: hit.line, file: file.path });
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
// Navigation
|
|
124
|
-
for (const hit of runQuery(parser, file.content, NAV_QUERY, 'swift-navigation', file.path)) {
|
|
125
|
-
const ident = hit.captures.nav_ident;
|
|
126
|
-
if ((ident === 'NavigationStack' || ident === 'NavigationView') && !navs.has(ident)) {
|
|
127
|
-
navs.set(ident, { line: hit.line, file: file.path });
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
} catch (e) {
|
|
131
|
-
if (e instanceof InvalidQueryError) throw e;
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
} finally {
|
|
136
|
-
try { parser.delete(); } catch { /* ignore */ }
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const conventions: Record<string, unknown> = {};
|
|
140
|
-
const provenance: Provenance[] = [];
|
|
141
|
-
|
|
142
|
-
if (apiClasses.size > 0) {
|
|
143
|
-
const [name, { line, file }] = apiClasses.entries().next().value as [string, { line: number; file: string }];
|
|
144
|
-
conventions.api_client_class = name;
|
|
145
|
-
provenance.push({ field: 'api_client_class', sourceFile: file, line, query: 'swift-api-class' });
|
|
146
|
-
}
|
|
147
|
-
if (policies.size > 0) {
|
|
148
|
-
const [name, { line, file }] = policies.entries().next().value as [string, { line: number; file: string }];
|
|
149
|
-
conventions.biometric_policy = name;
|
|
150
|
-
provenance.push({ field: 'biometric_policy', sourceFile: file, line, query: 'swift-biometric-policy' });
|
|
151
|
-
}
|
|
152
|
-
if (navs.size > 0) {
|
|
153
|
-
const [name, { line, file }] = navs.entries().next().value as [string, { line: number; file: string }];
|
|
154
|
-
conventions.navigation_pattern = name;
|
|
155
|
-
provenance.push({ field: 'navigation_pattern', sourceFile: file, line, query: 'swift-navigation' });
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
let confidence: AdapterResult['confidence'];
|
|
159
|
-
if (Object.keys(conventions).length === 0) {
|
|
160
|
-
confidence = 'none';
|
|
161
|
-
} else if (apiClasses.size === 1 && policies.size <= 1) {
|
|
162
|
-
confidence = 'high';
|
|
163
|
-
} else if (apiClasses.size > 1) {
|
|
164
|
-
confidence = 'low';
|
|
165
|
-
} else {
|
|
166
|
-
confidence = 'medium';
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return { conventions, provenance, confidence };
|
|
170
|
-
},
|
|
171
|
-
};
|