backend-skeleton 1.0.0-beta.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/LICENSE +21 -0
- package/README.md +284 -0
- package/bin/bskel.mjs +2384 -0
- package/contracts/completeness.mjs +176 -0
- package/contracts/emit.mjs +287 -0
- package/contracts/export.mjs +325 -0
- package/contracts/openapi.mjs +869 -0
- package/contracts/validate.mjs +147 -0
- package/handles/_engine.mjs +281 -0
- package/handles/codec.mjs +119 -0
- package/handles/conformance.mjs +74 -0
- package/handles/providers/java-spring/ast-bridge.mjs +59 -0
- package/handles/providers/java-spring/ast-helper/build.gradle +34 -0
- package/handles/providers/java-spring/ast-helper/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/handles/providers/java-spring/ast-helper/gradle/wrapper/gradle-wrapper.properties +9 -0
- package/handles/providers/java-spring/ast-helper/gradlew +248 -0
- package/handles/providers/java-spring/ast-helper/gradlew.bat +82 -0
- package/handles/providers/java-spring/ast-helper/settings.gradle +1 -0
- package/handles/providers/java-spring/ast-helper/src/main/java/com/backendskeleton/asthelper/Main.java +178 -0
- package/handles/providers/java-spring/emit.mjs +232 -0
- package/handles/providers/java-spring/patch-strategy.mjs +229 -0
- package/handles/providers/java-spring/plan.mjs +377 -0
- package/handles/providers/java-spring/templates/HandleAspect.java.tmpl +125 -0
- package/handles/providers/java-spring/templates/HandleCodec.java.tmpl +150 -0
- package/handles/providers/java-spring/templates/HandleController.java.tmpl +177 -0
- package/handles/providers/java-spring/templates/HandleRegistry.java.tmpl +107 -0
- package/handles/providers/java-spring/templates/HandleRegistryRepository.java.tmpl +8 -0
- package/handles/providers/java-spring/templates/HandleService.java.tmpl +95 -0
- package/handles/providers/java-spring/templates/HandleSnapshot.java.tmpl +75 -0
- package/handles/providers/java-spring/templates/HandleSnapshotRepository.java.tmpl +20 -0
- package/handles/providers/java-spring/templates/RecordHandleSnapshot.java.tmpl +50 -0
- package/handles/providers/java-spring/templates/ResourceResolver.java.tmpl +50 -0
- package/handles/providers/java-spring/templates/ResourceResolverStub.java.tmpl +77 -0
- package/handles/providers/java-spring/templates/migration.sql.tmpl +34 -0
- package/handles/providers/java-spring.mjs +21 -0
- package/handles/providers/python-fastapi/emit.mjs +171 -0
- package/handles/providers/python-fastapi/plan.mjs +186 -0
- package/handles/providers/python-fastapi/templates/__init__.py.tmpl +1 -0
- package/handles/providers/python-fastapi/templates/codec.py.tmpl +122 -0
- package/handles/providers/python-fastapi/templates/handle_service.py.tmpl +96 -0
- package/handles/providers/python-fastapi/templates/migration.sql.tmpl +35 -0
- package/handles/providers/python-fastapi/templates/record_snapshot.py.tmpl +155 -0
- package/handles/providers/python-fastapi/templates/registry.py.tmpl +37 -0
- package/handles/providers/python-fastapi/templates/resolver.py.tmpl +59 -0
- package/handles/providers/python-fastapi/templates/resolvers_init.py.tmpl +13 -0
- package/handles/providers/python-fastapi/templates/router.py.tmpl +140 -0
- package/handles/providers/python-fastapi/templates/tables.py.tmpl +66 -0
- package/handles/providers/python-fastapi.mjs +22 -0
- package/handles/providers/typescript-express/emit.mjs +128 -0
- package/handles/providers/typescript-express/plan.mjs +234 -0
- package/handles/providers/typescript-express/templates/codec.ts.tmpl +116 -0
- package/handles/providers/typescript-express/templates/registry.ts.tmpl +39 -0
- package/handles/providers/typescript-express/templates/resolver.ts.tmpl +55 -0
- package/handles/providers/typescript-express/templates/resolvers_index.ts.tmpl +11 -0
- package/handles/providers/typescript-express/templates/router.ts.tmpl +122 -0
- package/handles/providers/typescript-express.mjs +20 -0
- package/handles/registry.mjs +90 -0
- package/lib/cli.mjs +430 -0
- package/lib/doctor.mjs +200 -0
- package/lib/exit-codes.mjs +67 -0
- package/lib/featureid.mjs +55 -0
- package/lib/featurelifecycle.mjs +205 -0
- package/lib/fsutil.mjs +50 -0
- package/lib/gate-definitions.mjs +293 -0
- package/lib/gates.mjs +263 -0
- package/lib/handles-manifest.mjs +92 -0
- package/lib/lock.mjs +68 -0
- package/lib/patch-approvals.mjs +56 -0
- package/lib/paths.mjs +21 -0
- package/lib/repo.mjs +44 -0
- package/lib/schema-validate.mjs +56 -0
- package/lib/state.mjs +124 -0
- package/lib/template.mjs +35 -0
- package/lib/verify.mjs +206 -0
- package/lib/workflow.mjs +142 -0
- package/new/fastapi.mjs +165 -0
- package/new/index.mjs +62 -0
- package/new/params.mjs +233 -0
- package/new/spring.mjs +198 -0
- package/new/templates/fastapi/README.md +26 -0
- package/new/templates/fastapi/app/__init__.py +0 -0
- package/new/templates/fastapi/app/main.py +8 -0
- package/new/templates/fastapi/gitignore +6 -0
- package/new/templates/fastapi/pyproject.toml +14 -0
- package/package.json +50 -0
- package/scanners/adapters/_express-shared.mjs +238 -0
- package/scanners/adapters/_java-spring-analyzer.mjs +273 -0
- package/scanners/adapters/generic-grep.mjs +128 -0
- package/scanners/adapters/java-spring.mjs +301 -0
- package/scanners/adapters/javascript-express.mjs +422 -0
- package/scanners/adapters/python-fastapi.mjs +348 -0
- package/scanners/adapters/typescript-express.mjs +299 -0
- package/scanners/capabilities.mjs +90 -0
- package/scanners/conformance.mjs +59 -0
- package/scanners/db/introspect.mjs +109 -0
- package/scanners/db/migrations.mjs +126 -0
- package/scanners/index.mjs +281 -0
- package/scanners/registry.mjs +130 -0
- package/scanners/render.mjs +136 -0
- package/scanners/text-util.mjs +8 -0
- package/schemas/adapter.schema.json +23 -0
- package/schemas/agent-envelope.schema.json +21 -0
- package/schemas/contract-resolution.schema.json +28 -0
- package/schemas/feature-contract.schema.json +78 -0
- package/schemas/feature-index.schema.json +25 -0
- package/schemas/feature.schema.json +17 -0
- package/schemas/gate-event.schema.json +19 -0
- package/schemas/handles-plan.schema.json +31 -0
- package/schemas/handles-provider.schema.json +26 -0
- package/schemas/patch-approvals.schema.json +28 -0
- package/schemas/scan-report.schema.json +102 -0
- package/schemas/stack-choice.schema.json +89 -0
- package/schemas/stack-record.schema.json +20 -0
- package/schemas/state.schema.json +43 -0
- package/scripts/preflight-base-ref.sh +226 -0
- package/stack/apply.mjs +159 -0
- package/stack/bootstrap/_lib.sh +73 -0
- package/stack/bootstrap/ngrok.sh +90 -0
- package/stack/catalog/ngrok.yml +63 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
// Ripgrep-for-discovery + a masking/balanced-delimiter structural scan (A2 Phase 1,
|
|
2
|
+
// D-java-analyzer -- see scanners/adapters/_java-spring-analyzer.mjs) for structure. Deliberately
|
|
3
|
+
// still no real Java parser/AST (Phase 2, out of scope) -- good enough to find "does a related
|
|
4
|
+
// module already exist" without a compiler dependency, not a general-purpose Java analyzer.
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { execFileSync } from 'node:child_process';
|
|
8
|
+
import { lineNumberAt } from '../text-util.mjs';
|
|
9
|
+
import { maskNonCode, findClassOrRecordDeclaration, findClassLevelMappingArgs, findMappingAnnotations } from './_java-spring-analyzer.mjs';
|
|
10
|
+
|
|
11
|
+
export function detectJavaSpringRoot(repoRoot) {
|
|
12
|
+
const buildFiles = ['build.gradle', 'build.gradle.kts', 'pom.xml'];
|
|
13
|
+
if (!buildFiles.some((f) => fs.existsSync(path.join(repoRoot, f)))) return null;
|
|
14
|
+
const srcRoot = path.join(repoRoot, 'src', 'main', 'java');
|
|
15
|
+
return fs.existsSync(srcRoot) ? srcRoot : null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// O6: `rg --files` (no `--sort`) is explicitly unordered/parallel by ripgrep's own docs -- two
|
|
19
|
+
// runs against an unchanged repo can return files in a different order, which without this sort
|
|
20
|
+
// would propagate into non-deterministic controller/entity/module array order in every scan
|
|
21
|
+
// report and contract, causing spurious diffs even when nothing real changed.
|
|
22
|
+
function listJavaFiles(srcRoot) {
|
|
23
|
+
const out = execFileSync('rg', ['--files', '-g', '*.java', srcRoot], { encoding: 'utf8' });
|
|
24
|
+
return out.split('\n').filter(Boolean).sort();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function moduleOf(filePath, srcRoot) {
|
|
28
|
+
const parts = path.relative(srcRoot, filePath).split(path.sep);
|
|
29
|
+
const domainIdx = parts.indexOf('domain');
|
|
30
|
+
return domainIdx >= 0 && parts[domainIdx + 1] ? parts[domainIdx + 1] : null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function extractQuotedOrValue(argsText) {
|
|
34
|
+
if (argsText == null) return null;
|
|
35
|
+
const named = argsText.match(/value\s*=\s*"([^"]*)"/);
|
|
36
|
+
if (named) return named[1];
|
|
37
|
+
const bare = argsText.match(/"([^"]*)"/);
|
|
38
|
+
return bare ? bare[1] : null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function joinPath(base, segment) {
|
|
42
|
+
const b = (base || '').replace(/\/$/, '');
|
|
43
|
+
const s = (segment || '').replace(/^\//, '');
|
|
44
|
+
return s ? `${b}/${s}` : (b || '/');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// A2 Phase 1 (D-java-analyzer): finds every `operationId = "..."` occurrence's REAL value within
|
|
48
|
+
// `text[searchStart:searchEnd]`, safe from a comment/string phantom match (D-java-analyzer's
|
|
49
|
+
// headline example: a `//` comment mentioning `operationId = "..."` as prose used to appear in
|
|
50
|
+
// controller.operationIds). The POSITION is found on `masked` text (comments/strings blanked to
|
|
51
|
+
// spaces, so a phantom mention can never be found there at all); the VALUE is always re-read from
|
|
52
|
+
// the ORIGINAL text at that same offset, since a masked string body only ever contains spaces.
|
|
53
|
+
function findOperationIdValue(text, masked, searchStart, searchEnd) {
|
|
54
|
+
const posMatch = masked.slice(searchStart, searchEnd).match(/operationId\s*=\s*"/);
|
|
55
|
+
if (!posMatch) return null;
|
|
56
|
+
const real = text.slice(searchStart + posMatch.index).match(/operationId\s*=\s*"([^"]+)"/);
|
|
57
|
+
return real ? real[1] : null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function findAllOperationIdValues(text, masked) {
|
|
61
|
+
const ids = [];
|
|
62
|
+
const re = /operationId\s*=\s*"/g;
|
|
63
|
+
let m;
|
|
64
|
+
while ((m = re.exec(masked))) {
|
|
65
|
+
const real = text.slice(m.index).match(/operationId\s*=\s*"([^"]+)"/);
|
|
66
|
+
if (real) ids.push(real[1]);
|
|
67
|
+
}
|
|
68
|
+
return ids;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function extractController(text, filePath) {
|
|
72
|
+
if (!/@RestController\b/.test(text)) return null;
|
|
73
|
+
const masked = maskNonCode(text);
|
|
74
|
+
|
|
75
|
+
// D3 (D-scanner-evidence): the class declaration's own line -- classLine is null only when
|
|
76
|
+
// there's no class/record declaration at all to point at.
|
|
77
|
+
const classDecl = findClassOrRecordDeclaration(masked);
|
|
78
|
+
const className = classDecl ? classDecl.name : path.basename(filePath, '.java');
|
|
79
|
+
const classLine = classDecl ? lineNumberAt(text, classDecl.index) : null;
|
|
80
|
+
|
|
81
|
+
// A2 Phase 1: class-level @RequestMapping detection now goes through the shared analyzer
|
|
82
|
+
// (balanced-paren args, masked-text lookahead for `class`/`record`) instead of a lazy-backtrack
|
|
83
|
+
// regex -- see D-java-analyzer for the lazy-backtrack misattribution bug this class of pattern
|
|
84
|
+
// used to cause.
|
|
85
|
+
const classMappingArgs = findClassLevelMappingArgs(text);
|
|
86
|
+
const basePath = classMappingArgs != null ? (extractQuotedOrValue(classMappingArgs) ?? '') : '';
|
|
87
|
+
|
|
88
|
+
// Document-order list of every operationId in the file -- this is what the acceptance
|
|
89
|
+
// oracle (10 operationIds for Team-IZ-Backend's OrganizationController) checks directly.
|
|
90
|
+
const operationIds = findAllOperationIdValues(text, masked);
|
|
91
|
+
const operationStarts = [...masked.matchAll(/@Operation\(/g)].map((m) => m.index);
|
|
92
|
+
|
|
93
|
+
const endpoints = [];
|
|
94
|
+
for (const mapping of findMappingAnnotations(text)) {
|
|
95
|
+
const segment = extractQuotedOrValue(mapping.argsText) ?? '';
|
|
96
|
+
|
|
97
|
+
// Correlate with the nearest PRECEDING @Operation( block, even when an @ApiResponses
|
|
98
|
+
// (or similar) annotation sits between it and this mapping -- heuristic, not a parser.
|
|
99
|
+
let operationId = null;
|
|
100
|
+
const preceding = operationStarts.filter((idx) => idx < mapping.index);
|
|
101
|
+
if (preceding.length > 0) {
|
|
102
|
+
operationId = findOperationIdValue(text, masked, preceding[preceding.length - 1], mapping.index);
|
|
103
|
+
}
|
|
104
|
+
// D3: line of the mapping annotation itself. A2 Phase 1: this is now the SAME position
|
|
105
|
+
// handles/providers/java-spring/plan.mjs derives too -- both consume
|
|
106
|
+
// findMappingAnnotations() from scanners/adapters/_java-spring-analyzer.mjs, the duplicate
|
|
107
|
+
// regex pass this comment used to point at is gone.
|
|
108
|
+
endpoints.push({ verb: mapping.verb, path: joinPath(basePath, segment), operationId, method: mapping.methodName, line: mapping.methodLine });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return { className, basePath, operationIds, endpoints, file: filePath, line: classLine };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function extractEntity(text, filePath) {
|
|
115
|
+
if (!/@Entity\b/.test(text)) return null;
|
|
116
|
+
const masked = maskNonCode(text);
|
|
117
|
+
const classDecl = findClassOrRecordDeclaration(masked);
|
|
118
|
+
const tableMatch = text.match(/@Table\(\s*name\s*=\s*"([^"]+)"/);
|
|
119
|
+
const idFieldMatch = text.match(/@Id\b[\s\S]{0,200}?private\s+\S+\s+(\w+)\s*;/);
|
|
120
|
+
return {
|
|
121
|
+
className: classDecl ? classDecl.name : path.basename(filePath, '.java'),
|
|
122
|
+
table: tableMatch ? tableMatch[1] : null,
|
|
123
|
+
idField: idFieldMatch ? idFieldMatch[1] : null,
|
|
124
|
+
file: filePath,
|
|
125
|
+
line: classDecl ? lineNumberAt(text, classDecl.index) : null,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function extractDomainEnum(text, filePath) {
|
|
130
|
+
const match = text.match(/public\s+enum\s+(\w+)\s*\{([\s\S]*?)\n\}/);
|
|
131
|
+
if (!match) return null;
|
|
132
|
+
const [, name, rawBody] = match;
|
|
133
|
+
const body = maskNonCode(rawBody);
|
|
134
|
+
// Each constant ends at the first `,` `;` (or the start of a constructor-arg `(` if present).
|
|
135
|
+
const constants = body
|
|
136
|
+
.split(/[,;]/)
|
|
137
|
+
.map((s) => s.trim().split('(')[0].trim())
|
|
138
|
+
.filter((s) => /^[A-Z][A-Z0-9_]*$/.test(s));
|
|
139
|
+
return { name, constants, file: filePath, line: lineNumberAt(text, match.index) };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const APPLICATION_CONFIG_GLOB = 'application*.{yml,yaml,properties}';
|
|
143
|
+
|
|
144
|
+
function findApplicationConfigFiles(repoRoot) {
|
|
145
|
+
try {
|
|
146
|
+
return execFileSync('rg', ['--files', '-g', APPLICATION_CONFIG_GLOB, repoRoot], { encoding: 'utf8' })
|
|
147
|
+
.split('\n').filter(Boolean).sort(); // O6: rg --files order isn't guaranteed -- see listJavaFiles.
|
|
148
|
+
} catch {
|
|
149
|
+
return []; // rg exits 1 on "no files matched" -- not an error, just nothing to report
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// A1 §7: three independent Spring config signals for a GLOBAL path prefix the regex endpoint
|
|
154
|
+
// scanner structurally cannot see -- extractController() reads one file at a time and has no
|
|
155
|
+
// idea a WebMvcConfigurer or application.yaml elsewhere in the repo prepends something to every
|
|
156
|
+
// path it found. Team-IZ-Backend's ApiPathConfig.java (configurePathMatch + addPathPrefix) is
|
|
157
|
+
// exactly the real defect D-openapi-reconciliation documents: every contract path this tool
|
|
158
|
+
// emits without --openapi-file was silently wrong until A1 closed it with real-document
|
|
159
|
+
// reconciliation. This function can't fix that (only a real OpenAPI document can) -- it exists
|
|
160
|
+
// so a user who doesn't know --openapi-file exists gets told the defect is likely present, via
|
|
161
|
+
// scanners/index.mjs's `unknowns` note.
|
|
162
|
+
export function detectGlobalPathPrefixSignals(repoRoot) {
|
|
163
|
+
const signals = [];
|
|
164
|
+
|
|
165
|
+
// (1) WebMvcConfigurer.configurePathMatch + addPathPrefix("...", ...). Two-step, same style
|
|
166
|
+
// as listJavaFiles/extractController elsewhere in this file: `rg -l` finds candidate FILES
|
|
167
|
+
// mentioning configurePathMatch, then a content read confirms addPathPrefix( is actually
|
|
168
|
+
// present (a configurePathMatch override that does something else entirely -- a custom
|
|
169
|
+
// PathMatcher, a trailing-slash setting -- must not be reported as a prefix).
|
|
170
|
+
const srcRoot = path.join(repoRoot, 'src', 'main', 'java');
|
|
171
|
+
if (fs.existsSync(srcRoot)) {
|
|
172
|
+
let candidates = [];
|
|
173
|
+
try {
|
|
174
|
+
candidates = execFileSync('rg', ['-l', 'configurePathMatch', '-g', '*.java', srcRoot], { encoding: 'utf8' }).split('\n').filter(Boolean).sort();
|
|
175
|
+
} catch {
|
|
176
|
+
// no matches -- not an error
|
|
177
|
+
}
|
|
178
|
+
for (const file of candidates) {
|
|
179
|
+
const text = fs.readFileSync(file, 'utf8');
|
|
180
|
+
const prefixMatch = text.match(/addPathPrefix\s*\(\s*"([^"]+)"/);
|
|
181
|
+
if (!prefixMatch) continue;
|
|
182
|
+
signals.push({ kind: 'configurePathMatch', file: path.relative(repoRoot, file), prefix: prefixMatch[1] });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// (2)/(3) application.yml/.yaml/.properties -- server.servlet.context-path (Spring Boot's own
|
|
187
|
+
// built-in global-prefix mechanism, same blind spot as (1)) and springdoc.paths-to-match
|
|
188
|
+
// (doesn't itself apply a prefix, but a pattern narrower than "/**" is strong circumstantial
|
|
189
|
+
// evidence one is documented, even when (1)/(2) are what actually implement it). Same
|
|
190
|
+
// good-enough-regex-not-a-real-YAML-parser philosophy as the rest of this file -- matches
|
|
191
|
+
// either YAML (`key:`) or properties (`a.b.c=`) form.
|
|
192
|
+
for (const file of findApplicationConfigFiles(repoRoot)) {
|
|
193
|
+
const text = fs.readFileSync(file, 'utf8');
|
|
194
|
+
const contextPath = text.match(/(?:^|\n)\s*context-path\s*:\s*(\S+)/) ?? text.match(/server\.servlet\.context-path\s*=\s*(\S+)/);
|
|
195
|
+
if (contextPath) {
|
|
196
|
+
signals.push({ kind: 'context-path', file: path.relative(repoRoot, file), prefix: contextPath[1].replace(/^["']|["']$/g, '') });
|
|
197
|
+
}
|
|
198
|
+
const pathsToMatch = text.match(/(?:^|\n)\s*paths-to-match\s*:\s*(\S+)/) ?? text.match(/springdoc\.paths-to-match\s*=\s*(\S+)/);
|
|
199
|
+
if (pathsToMatch) {
|
|
200
|
+
signals.push({ kind: 'paths-to-match', file: path.relative(repoRoot, file), pattern: pathsToMatch[1].replace(/^["']|["']$/g, '') });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return signals;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function scanJavaSpring(repoRoot) {
|
|
208
|
+
const srcRoot = detectJavaSpringRoot(repoRoot);
|
|
209
|
+
if (!srcRoot) return null;
|
|
210
|
+
|
|
211
|
+
const modules = new Map();
|
|
212
|
+
const moduleEntry = (name) => {
|
|
213
|
+
const key = name ?? '_unknown';
|
|
214
|
+
if (!modules.has(key)) modules.set(key, { module: key, controllers: [], entities: [], enums: [], dtos: [] });
|
|
215
|
+
return modules.get(key);
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
for (const file of listJavaFiles(srcRoot)) {
|
|
219
|
+
const text = fs.readFileSync(file, 'utf8');
|
|
220
|
+
const mod = moduleOf(file, srcRoot);
|
|
221
|
+
|
|
222
|
+
if (/@RestController\b/.test(text)) {
|
|
223
|
+
const controller = extractController(text, file);
|
|
224
|
+
if (controller) moduleEntry(mod).controllers.push(controller);
|
|
225
|
+
}
|
|
226
|
+
if (/@Entity\b/.test(text)) {
|
|
227
|
+
const entity = extractEntity(text, file);
|
|
228
|
+
if (entity) moduleEntry(mod).entities.push(entity);
|
|
229
|
+
}
|
|
230
|
+
if (mod && file.includes(`${path.sep}domain${path.sep}`) && /public\s+enum\s+\w+/.test(text)) {
|
|
231
|
+
const en = extractDomainEnum(text, file);
|
|
232
|
+
if (en) moduleEntry(mod).enums.push(en);
|
|
233
|
+
}
|
|
234
|
+
if (mod && file.includes(`${path.sep}presentation${path.sep}dto${path.sep}`)) {
|
|
235
|
+
moduleEntry(mod).dtos.push(path.basename(file, '.java'));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// S2 (D-gate-precision, continued): repo-relative (not srcRoot-relative like moduleOf's own
|
|
240
|
+
// paths) -- this is what lib/gate-definitions.mjs's `scan` gate hashes to detect real content
|
|
241
|
+
// drift, and every other manifest-shaped gate input in this codebase (stack's `applied_file:`)
|
|
242
|
+
// is repo-relative too.
|
|
243
|
+
const filesRead = listJavaFiles(srcRoot).map((f) => path.relative(repoRoot, f));
|
|
244
|
+
return { srcRoot, modules: [...modules.values()], pathPrefixSignals: detectGlobalPathPrefixSignals(repoRoot), filesRead };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// G1: adapter descriptor consumed by scanners/registry.mjs -- see D-adapter-registry in
|
|
248
|
+
// DECISIONS.md. Wraps the functions above without changing any of them; `id` must equal this
|
|
249
|
+
// file's stem ("java-spring"). specificity 100: this adapter only detects when a Spring Boot
|
|
250
|
+
// build file AND src/main/java both exist -- a build-file-plus-source-layout-confirmed match, the
|
|
251
|
+
// strongest signal any adapter in this codebase can give.
|
|
252
|
+
export const adapter = {
|
|
253
|
+
contract: 'sbf.adapter/1',
|
|
254
|
+
id: 'java-spring',
|
|
255
|
+
title: 'Java / Spring Boot',
|
|
256
|
+
specificity: 100,
|
|
257
|
+
confidence: 'high',
|
|
258
|
+
capabilities: {
|
|
259
|
+
'api.operations': true,
|
|
260
|
+
'api.request-shape': true,
|
|
261
|
+
'resource.fetch': true,
|
|
262
|
+
'codegen.handles': true,
|
|
263
|
+
},
|
|
264
|
+
detect: detectJavaSpringRoot,
|
|
265
|
+
scan(repoRoot, _detection) {
|
|
266
|
+
const result = scanJavaSpring(repoRoot);
|
|
267
|
+
return { modules: result.modules, pathPrefixSignals: result.pathPrefixSignals, filesRead: result.filesRead };
|
|
268
|
+
},
|
|
269
|
+
// S2 (D-gate-precision, continued): reuses the EXACT same listJavaFiles() call scan() itself
|
|
270
|
+
// makes -- no separate file-walking logic -- so the `scan` gate's staleness token can re-derive
|
|
271
|
+
// the CURRENT read-set fresh every time it's checked (not just re-hash whatever the last scan
|
|
272
|
+
// run happened to record), which is what lets it notice a brand-new file too, not just an edit
|
|
273
|
+
// to a previously-known one.
|
|
274
|
+
listReadSet(repoRoot) {
|
|
275
|
+
const srcRoot = detectJavaSpringRoot(repoRoot);
|
|
276
|
+
if (!srcRoot) return [];
|
|
277
|
+
return listJavaFiles(srcRoot).map((f) => path.relative(repoRoot, f));
|
|
278
|
+
},
|
|
279
|
+
diagnostics(repoRoot) {
|
|
280
|
+
const messages = [];
|
|
281
|
+
const buildFiles = ['build.gradle', 'build.gradle.kts', 'pom.xml'];
|
|
282
|
+
if (!buildFiles.some((f) => fs.existsSync(path.join(repoRoot, f)))) {
|
|
283
|
+
messages.push({ level: 'info', code: 'no-build-file', message: `none of ${buildFiles.join(', ')} found at repo root` });
|
|
284
|
+
}
|
|
285
|
+
const srcRoot = path.join(repoRoot, 'src', 'main', 'java');
|
|
286
|
+
if (!fs.existsSync(srcRoot)) {
|
|
287
|
+
messages.push({ level: 'info', code: 'no-src-main-java', message: 'src/main/java does not exist' });
|
|
288
|
+
} else {
|
|
289
|
+
let rgOk = true;
|
|
290
|
+
try {
|
|
291
|
+
execFileSync('rg', ['--version'], { stdio: 'pipe' });
|
|
292
|
+
} catch {
|
|
293
|
+
rgOk = false;
|
|
294
|
+
}
|
|
295
|
+
if (!rgOk) {
|
|
296
|
+
messages.push({ level: 'warn', code: 'rg-missing', message: 'ripgrep (rg) is not on PATH -- this adapter shells out to it and will throw, not degrade, if it is missing' });
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return messages;
|
|
300
|
+
},
|
|
301
|
+
};
|