claudeos-core 2.3.2 → 2.4.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 +790 -74
- package/CODE_OF_CONDUCT.md +15 -0
- package/README.de.md +374 -876
- package/README.es.md +374 -875
- package/README.fr.md +374 -875
- package/README.hi.md +374 -875
- package/README.ja.md +374 -875
- package/README.ko.md +374 -874
- package/README.md +374 -876
- package/README.ru.md +374 -877
- package/README.vi.md +374 -875
- package/README.zh-CN.md +374 -874
- package/SECURITY.md +51 -0
- package/bin/commands/init.js +192 -37
- package/content-validator/index.js +97 -4
- package/health-checker/index.js +44 -10
- package/package.json +92 -90
- package/pass-json-validator/index.js +58 -7
- package/pass-prompts/templates/angular/pass3.md +15 -14
- package/pass-prompts/templates/common/claude-md-scaffold.md +81 -0
- package/pass-prompts/templates/common/pass3-footer.md +104 -0
- package/pass-prompts/templates/java-spring/pass3.md +19 -18
- package/pass-prompts/templates/kotlin-spring/pass3.md +23 -22
- package/pass-prompts/templates/node-express/pass3.md +18 -17
- package/pass-prompts/templates/node-fastify/pass3.md +11 -10
- package/pass-prompts/templates/node-nestjs/pass3.md +11 -10
- package/pass-prompts/templates/node-nextjs/pass3.md +18 -17
- package/pass-prompts/templates/node-vite/pass3.md +11 -10
- package/pass-prompts/templates/python-django/pass3.md +18 -17
- package/pass-prompts/templates/python-fastapi/pass3.md +18 -17
- package/pass-prompts/templates/python-flask/pass3.md +9 -8
- package/pass-prompts/templates/vue-nuxt/pass3.md +9 -8
- package/plan-installer/domain-grouper.js +45 -5
- package/plan-installer/index.js +11 -1
- package/plan-installer/scanners/scan-java.js +98 -2
- package/plan-installer/stack-detector.js +44 -0
|
@@ -47,18 +47,18 @@ Generation targets:
|
|
|
47
47
|
- 00.core/01.project-overview.md — Stack, routing approach, deployment environment
|
|
48
48
|
- 00.core/02.architecture.md — Nuxt/Vue structure, component hierarchy, data flow, Nitro server
|
|
49
49
|
- 00.core/03.naming-conventions.md — File/component/composable/store naming conventions
|
|
50
|
-
- 20.frontend
|
|
51
|
-
- 20.frontend
|
|
52
|
-
- 20.frontend
|
|
53
|
-
- 20.frontend
|
|
54
|
-
- 20.frontend
|
|
55
|
-
- 10.backend
|
|
50
|
+
- 20.frontend/01.component-patterns.md — SFC, <script setup>, Props/Emits, slots, provide/inject
|
|
51
|
+
- 20.frontend/02.page-routing-patterns.md — Pages/layouts/dynamic routes/middleware/definePageMeta
|
|
52
|
+
- 20.frontend/03.data-fetching.md — useFetch, useAsyncData, $fetch, server routes, caching
|
|
53
|
+
- 20.frontend/04.state-management.md — Pinia stores, composables, useState, form state
|
|
54
|
+
- 20.frontend/05.styling-patterns.md — Scoped styles, CSS Modules, Tailwind/UnoCSS, theming
|
|
55
|
+
- 10.backend/01.server-routes.md — Nitro server routes (server/api/), H3 event handlers
|
|
56
56
|
- 30.security-db/01.security-auth.md — Auth, middleware protection, environment variables
|
|
57
57
|
- 40.infra/01.environment-config.md — Runtime config, nuxt.config.ts, Nitro presets
|
|
58
58
|
- 40.infra/02.logging-monitoring.md — Error tracking, analytics, Web Vitals
|
|
59
59
|
- 40.infra/03.cicd-deployment.md — CI/CD, deployment (Vercel/Netlify/Docker), preview
|
|
60
|
-
-
|
|
61
|
-
-
|
|
60
|
+
- 80.verification/01.development-verification.md — Build, startup, Lighthouse
|
|
61
|
+
- 80.verification/02.testing-strategy.md — Vitest, @vue/test-utils, E2E, @nuxt/test-utils
|
|
62
62
|
|
|
63
63
|
Each file MUST include:
|
|
64
64
|
- Correct examples (✅ code blocks)
|
|
@@ -80,6 +80,7 @@ Generation targets:
|
|
|
80
80
|
- `40.infra/03.cicd-deployment-rules.md` paths: `["**/*.yml", "**/*.yaml", "**/Dockerfile*", "**/*.ts", "**/*.vue"]` — CI config + source
|
|
81
81
|
- `50.sync/*` rules: `paths: ["**/claudeos-core/**", "**/.claude/**"]`
|
|
82
82
|
- `60.memory/*` rules: forward reference — Pass 4 will generate 4 files (01.decision-log, 02.failure-patterns, 03.compaction, 04.auto-rule-update), each with file-specific `paths`. Pass 3 must STILL list ```.claude/rules/60.memory/*``` as a row in CLAUDE.md Section 6 Rules table so developers/Claude see the category exists.
|
|
83
|
+
- `70.domains/*` rules (multi-domain projects only): per-domain rules at `.claude/rules/70.domains/{type}/{domain}-rules.md` (where `{type}` is `backend` or `frontend`, ALWAYS present even in single-stack projects for uniform layout + zero-migration future-proofing), each with a `paths:` glob scoped to that domain's source directories so the rule auto-loads only when editing files within the relevant domain. Folder name is PLURAL (`domains/`) — collection of N per-domain files — and each file inside uses the SINGULAR domain name (`{domain}-rules.md`). DO NOT use `60.domains/` (collides with `60.memory/`) and DO NOT skip the `{type}/` sub-folder. See pass3-footer.md "Per-domain folder convention" for the full rationale.
|
|
83
84
|
- MUST generate `.claude/rules/00.core/00.standard-reference.md` — directory of all standard files
|
|
84
85
|
|
|
85
86
|
4. .claude/rules/50.sync/ (2 sync rules)
|
|
@@ -5,22 +5,62 @@
|
|
|
5
5
|
* and selects appropriate templates based on detected stack.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
// ─── Splitting thresholds ───────────────────────────────────────
|
|
9
|
+
//
|
|
10
|
+
// MAX_FILES_PER_GROUP and MAX_DOMAINS_PER_GROUP are the original budgets that
|
|
11
|
+
// have governed Pass 1 batching since v1.x. They are sound for projects with
|
|
12
|
+
// a roughly uniform file-size distribution but can produce time-outlier
|
|
13
|
+
// batches when a single domain contains a very large file (e.g. a 2500-line
|
|
14
|
+
// TUI Grid wrapper) — observed in field testing where a group of ~29
|
|
15
|
+
// files ran ~70% longer than a group of 39 files.
|
|
16
|
+
//
|
|
17
|
+
// MAX_LINES_PER_GROUP is the optional second axis. It only fires when the
|
|
18
|
+
// caller supplies per-domain `totalLines`; scanners that do not yet record
|
|
19
|
+
// line counts leave it unset and the line-budget check is skipped, so this
|
|
20
|
+
// field is strictly additive (no behavior change for existing scanners).
|
|
21
|
+
//
|
|
22
|
+
// The 8000-line threshold is a pragmatic starting point calibrated against
|
|
23
|
+
// observed field tests: ~40 files × ~200 lines/file average. It can be
|
|
24
|
+
// tuned later once more scanners populate `totalLines` and we have more
|
|
25
|
+
// data on per-stack line-count distributions.
|
|
26
|
+
const MAX_FILES_PER_GROUP = 40;
|
|
27
|
+
const MAX_DOMAINS_PER_GROUP = 4;
|
|
28
|
+
const MAX_LINES_PER_GROUP = 8000;
|
|
29
|
+
|
|
8
30
|
function splitDomainGroups(domains, type, template) {
|
|
9
|
-
const MAX_FILES_PER_GROUP = 40;
|
|
10
|
-
const MAX_DOMAINS_PER_GROUP = 4;
|
|
11
31
|
const groups = [];
|
|
12
32
|
let current = [];
|
|
13
33
|
let fileCount = 0;
|
|
34
|
+
let lineCount = 0;
|
|
14
35
|
|
|
15
36
|
for (const d of domains) {
|
|
16
|
-
//
|
|
17
|
-
|
|
37
|
+
// Read `totalLines` as an optional signal. Domains produced by scanners
|
|
38
|
+
// that have not yet been extended to record line counts leave this
|
|
39
|
+
// undefined, which makes the line-budget check a no-op (0 + undefined
|
|
40
|
+
// stays undefined; the `hasLines` guard below suppresses the comparison
|
|
41
|
+
// entirely in that case). This preserves exact byte-for-byte output for
|
|
42
|
+
// all existing callers.
|
|
43
|
+
const domainLines = (typeof d.totalLines === "number" && d.totalLines >= 0) ? d.totalLines : 0;
|
|
44
|
+
const hasLines = domainLines > 0;
|
|
45
|
+
|
|
46
|
+
// Flush current group before adding if it would exceed any budget.
|
|
47
|
+
// The line-budget condition (`hasLines && ...`) is only evaluated when
|
|
48
|
+
// the incoming domain actually carries line-count data — callers using
|
|
49
|
+
// the legacy `{ name, totalFiles }` shape retain the original 2-axis
|
|
50
|
+
// behavior.
|
|
51
|
+
const wouldExceedFiles = fileCount + d.totalFiles > MAX_FILES_PER_GROUP;
|
|
52
|
+
const wouldExceedDomains = current.length >= MAX_DOMAINS_PER_GROUP;
|
|
53
|
+
const wouldExceedLines = hasLines && (lineCount + domainLines > MAX_LINES_PER_GROUP);
|
|
54
|
+
|
|
55
|
+
if (current.length > 0 && (wouldExceedFiles || wouldExceedDomains || wouldExceedLines)) {
|
|
18
56
|
groups.push({ type, template, domains: [...current], estimatedFiles: fileCount });
|
|
19
57
|
current = [];
|
|
20
58
|
fileCount = 0;
|
|
59
|
+
lineCount = 0;
|
|
21
60
|
}
|
|
22
61
|
current.push(d.name);
|
|
23
62
|
fileCount += d.totalFiles;
|
|
63
|
+
lineCount += domainLines;
|
|
24
64
|
}
|
|
25
65
|
if (current.length > 0) {
|
|
26
66
|
groups.push({ type, template, domains: [...current], estimatedFiles: fileCount });
|
|
@@ -38,7 +78,7 @@ function determineActiveDomains(stack) {
|
|
|
38
78
|
"20.frontend": !!stack.frontend,
|
|
39
79
|
"30.security-db": !!(stack.database || isBackend || stack.frontend),
|
|
40
80
|
"40.infra": true,
|
|
41
|
-
"
|
|
81
|
+
"80.verification": true,
|
|
42
82
|
"90.optional": true,
|
|
43
83
|
};
|
|
44
84
|
}
|
package/plan-installer/index.js
CHANGED
|
@@ -40,7 +40,17 @@ async function main() {
|
|
|
40
40
|
console.warn(" Ensure you have build.gradle, package.json, pyproject.toml, or requirements.txt in the project root.\n");
|
|
41
41
|
}
|
|
42
42
|
console.log(` Frontend: ${stack.frontend || "none"} ${stack.frontendVersion || ""}`);
|
|
43
|
-
|
|
43
|
+
// v2.4.0 — when a project ships more than one DB driver (e.g. Oracle +
|
|
44
|
+
// MySQL master/slave), surface the full list so downstream LLMs (Pass 1)
|
|
45
|
+
// see the dual-dialect setup without having to re-derive it from source
|
|
46
|
+
// code. Singular `Database:` line preserved for byte-for-byte parity in
|
|
47
|
+
// single-DB projects (the dominant case).
|
|
48
|
+
if (Array.isArray(stack.databases) && stack.databases.length > 1) {
|
|
49
|
+
console.log(` Database: ${stack.database} (primary)`);
|
|
50
|
+
console.log(` Databases: ${stack.databases.join(", ")} (multi-dialect)`);
|
|
51
|
+
} else {
|
|
52
|
+
console.log(` Database: ${stack.database || "none"}`);
|
|
53
|
+
}
|
|
44
54
|
console.log(` ORM: ${stack.orm || "none"}`);
|
|
45
55
|
console.log(` PackageMgr: ${stack.packageManager || "none"}\n`);
|
|
46
56
|
|
|
@@ -22,9 +22,42 @@ async function scanJavaDomains(stack, ROOT) {
|
|
|
22
22
|
let rootPackage = null;
|
|
23
23
|
|
|
24
24
|
const javaFiles = (await glob("src/main/java/**/*.java", { cwd: ROOT })).map(norm);
|
|
25
|
+
|
|
26
|
+
// v2.4.0 — Pick the LONGEST package prefix (1-4 segments) that still
|
|
27
|
+
// covers ≥80% of layer-bearing files. Pre-v2.4.0 the first matched file
|
|
28
|
+
// won, which misclassified projects whose actual production code lives
|
|
29
|
+
// under one root (e.g. `<orgA>.<projectA>.*`) but where a small number
|
|
30
|
+
// of stub files happen to sit under another deeper subtree (e.g.
|
|
31
|
+
// `<orgA>.<otherModule>.core.<dir>.*`) — glob enumeration order then
|
|
32
|
+
// determined the rootPackage non-deterministically.
|
|
33
|
+
//
|
|
34
|
+
// Algorithm: count every (1-, 2-, 3-, 4-)segment prefix preceding a
|
|
35
|
+
// known layer marker. Then pick the longest prefix whose count is at
|
|
36
|
+
// least 80% of the maximum (1-segment) count. This gives:
|
|
37
|
+
// • Mono-package project (`com.example.app.*` only): root = `com.example.app`
|
|
38
|
+
// (all 4 prefix lengths tied, longest = most specific root).
|
|
39
|
+
// • Multi-module project (95% under `<root>.api.*`, 5% stubs under
|
|
40
|
+
// `<root>.misc.*`): root = `<root>.api` (the LONGEST prefix that
|
|
41
|
+
// still covers ≥80% of files), not `<root>` (too generic) and
|
|
42
|
+
// not the minority `<root>.misc.*` location (no longer first-match).
|
|
43
|
+
const pkgCounts = new Map();
|
|
25
44
|
for (const f of javaFiles) {
|
|
26
45
|
const m = f.match(/src\/main\/java\/(.+?)\/(controller|aggregator|facade|usecase|orchestrator|service|mapper|dao|dto|entity|repository|adapter)/);
|
|
27
|
-
if (m)
|
|
46
|
+
if (!m) continue;
|
|
47
|
+
const segs = m[1].split("/");
|
|
48
|
+
for (let len = Math.min(4, segs.length); len >= 1; len--) {
|
|
49
|
+
const prefix = segs.slice(0, len).join(".");
|
|
50
|
+
pkgCounts.set(prefix, (pkgCounts.get(prefix) || 0) + 1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (pkgCounts.size > 0) {
|
|
54
|
+
const maxCount = Math.max(...pkgCounts.values());
|
|
55
|
+
const threshold = Math.ceil(maxCount * 0.8);
|
|
56
|
+
const candidates = [...pkgCounts.entries()].filter(([_, c]) => c >= threshold);
|
|
57
|
+
// Among candidates, pick the longest prefix (most specific root that
|
|
58
|
+
// still covers the majority of files). Tie-break on length DESC.
|
|
59
|
+
candidates.sort((a, b) => b[0].length - a[0].length);
|
|
60
|
+
rootPackage = candidates[0][0];
|
|
28
61
|
}
|
|
29
62
|
const domainMap = {};
|
|
30
63
|
let detectedPattern = null;
|
|
@@ -172,7 +205,70 @@ async function scanJavaDomains(stack, ROOT) {
|
|
|
172
205
|
domainMap[d].mappers = mpr.length;
|
|
173
206
|
domainMap[d].dtos = dto.length;
|
|
174
207
|
domainMap[d].xmlMappers = xml.length;
|
|
175
|
-
|
|
208
|
+
|
|
209
|
+
// v2.4.0 — Deep-sweep fallback (Pattern B/D only).
|
|
210
|
+
//
|
|
211
|
+
// Pre-v2.4.0: standard globs assume `{domain}/{layer}/X.java`. This
|
|
212
|
+
// misses two real-world layouts:
|
|
213
|
+
// (a) Multi-module split: `front/{domain}/{layer}/` for HTTP
|
|
214
|
+
// layer + `core/{domain}/{layer}/` for service/dao layer.
|
|
215
|
+
// Standard glob `**/{domain}/{layer}/` actually matches BOTH
|
|
216
|
+
// via the leading `**`, so this case generally works.
|
|
217
|
+
// (b) Cross-domain coupling: `core/{otherDomain}/{layer}/{domain}/`
|
|
218
|
+
// — services for `{domain}` living under a different module's
|
|
219
|
+
// layer directory (the layer dir comes BEFORE the domain dir).
|
|
220
|
+
// Standard glob `**/{domain}/{layer}/*.java` does NOT match
|
|
221
|
+
// this layout.
|
|
222
|
+
//
|
|
223
|
+
// When standard globs return zero files for a Pattern B/D domain
|
|
224
|
+
// that is registered in domainMap (so it does exist), fall back to
|
|
225
|
+
// a deep sweep: `**/${dn}/**/*.java` finds every .java file under
|
|
226
|
+
// ANY directory named ${dn}. We then classify each file by walking
|
|
227
|
+
// up its path to find the nearest layer dir, which catches both
|
|
228
|
+
// `${dn}/{layer}/` AND `{layer}/${dn}/` placements.
|
|
229
|
+
//
|
|
230
|
+
// Restricting to Pattern B/D and to the zero-files case keeps the
|
|
231
|
+
// legacy behavior identical for projects whose standard globs
|
|
232
|
+
// already cover everything, and prevents over-counting for
|
|
233
|
+
// domains with healthy direct-layout file counts.
|
|
234
|
+
const standardCount = svc.length + agg.length + mpr.length + dto.length + xml.length;
|
|
235
|
+
if (standardCount === 0 && (p === "B" || p === "D")) {
|
|
236
|
+
const deepFiles = (await glob(`src/main/java/**/${dn}/**/*.java`, { cwd: ROOT })).map(norm);
|
|
237
|
+
// v2.4.0 — extended layer recognition. Real-world enterprise codebases
|
|
238
|
+
// commonly include implementation/support layers beyond the canonical
|
|
239
|
+
// controller/service/mapper/dto trio. Files in `factory/`, `strategy/`,
|
|
240
|
+
// `impl/`, `helper/`, etc. were previously dropped by deep-sweep
|
|
241
|
+
// (no `break`), causing domains with non-standard layer names to
|
|
242
|
+
// report 0 totalFiles. The recognized list is augmented and a
|
|
243
|
+
// catch-all classifies any remaining `.java` file under the domain
|
|
244
|
+
// tree as a service (the most generic backend layer).
|
|
245
|
+
const SVC_LAYERS = ["aggregator", "facade", "usecase", "orchestrator", "service",
|
|
246
|
+
"factory", "strategy", "impl", "helper", "support",
|
|
247
|
+
"client", "provider", "manager", "handler", "interceptor",
|
|
248
|
+
"filter", "listener", "task", "scheduler", "command", "query",
|
|
249
|
+
"validator", "converter", "translator", "resolver"];
|
|
250
|
+
const DAO_LAYERS = ["mapper", "repository", "dao"];
|
|
251
|
+
const DTO_LAYERS = ["dto", "vo", "entity", "model", "request", "response", "payload"];
|
|
252
|
+
for (const f of deepFiles) {
|
|
253
|
+
const parts = f.split("/");
|
|
254
|
+
let classified = false;
|
|
255
|
+
for (let i = parts.length - 2; i >= 0; i--) {
|
|
256
|
+
const seg = parts[i];
|
|
257
|
+
if (seg === "controller") { domainMap[d].controllers++; classified = true; break; }
|
|
258
|
+
if (SVC_LAYERS.includes(seg)) { domainMap[d].services++; classified = true; break; }
|
|
259
|
+
if (DAO_LAYERS.includes(seg)) { domainMap[d].mappers++; classified = true; break; }
|
|
260
|
+
if (DTO_LAYERS.includes(seg)) { domainMap[d].dtos++; classified = true; break; }
|
|
261
|
+
}
|
|
262
|
+
// Fallback: any unclassified .java file under the domain tree is
|
|
263
|
+
// counted as a service. This catches layouts like
|
|
264
|
+
// `core/${dn}/X.java` (no layer subdir) and prevents legitimate
|
|
265
|
+
// backend domains from reporting 0 totalFiles when their files
|
|
266
|
+
// happen to live under unrecognized parent directories.
|
|
267
|
+
if (!classified) domainMap[d].services++;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const totalFiles = domainMap[d].services + domainMap[d].mappers + domainMap[d].dtos + domainMap[d].xmlMappers + domainMap[d].controllers;
|
|
176
272
|
backendDomains.push({ name: d, type: "backend", ...domainMap[d], totalFiles });
|
|
177
273
|
}
|
|
178
274
|
|
|
@@ -270,6 +270,10 @@ async function detectStack(ROOT) {
|
|
|
270
270
|
const g = readFileSafe(path.join(ROOT, gradleFile));
|
|
271
271
|
if (g) {
|
|
272
272
|
stack.buildTool = "gradle"; stack.detected.push(gradleFile);
|
|
273
|
+
// v2.4.0 — JVM project package manager. Set "gradle" so generated docs
|
|
274
|
+
// and downstream tooling don't show "PackageMgr: none" for a build tool
|
|
275
|
+
// that IS the package manager. Only set if not already detected.
|
|
276
|
+
if (!stack.packageManager) stack.packageManager = "gradle";
|
|
273
277
|
if (g.includes("spring-boot")) { stack.language = "java"; stack.framework = "spring-boot"; stack.detected.push("spring-boot"); }
|
|
274
278
|
const svPatterns = [
|
|
275
279
|
/org\.springframework\.boot.*version\s*['"]([^'"]+)['"]/,
|
|
@@ -483,6 +487,8 @@ async function detectStack(ROOT) {
|
|
|
483
487
|
const pom = readFileSafe(path.join(ROOT, "pom.xml"));
|
|
484
488
|
if (pom) {
|
|
485
489
|
if (!stack.buildTool) { stack.buildTool = "maven"; stack.language = "java"; stack.detected.push("pom.xml"); }
|
|
490
|
+
// v2.4.0 — JVM package manager (parallel to Gradle case above).
|
|
491
|
+
if (!stack.packageManager) stack.packageManager = "maven";
|
|
486
492
|
const sv = pom.match(/<spring-boot[^>]*version>([^<]+)/);
|
|
487
493
|
if (sv) stack.frameworkVersion = sv[1];
|
|
488
494
|
// Java version — Maven commonly uses three patterns:
|
|
@@ -815,6 +821,7 @@ async function detectStack(ROOT) {
|
|
|
815
821
|
// framework default).
|
|
816
822
|
const portPatterns = [
|
|
817
823
|
// (1) direct numeric literal in yml `server:\n port: N`
|
|
824
|
+
// (port immediately after server: with no intermediate keys)
|
|
818
825
|
/server:\s*\n\s*port:\s*(\d+)/,
|
|
819
826
|
// (2) flat-key style `server.port=N` or `server.port: N`
|
|
820
827
|
/server\.port\s*[=:]\s*(\d+)/,
|
|
@@ -824,6 +831,29 @@ async function detectStack(ROOT) {
|
|
|
824
831
|
/server:\s*\n\s*port:\s*\$\{[^}:]+:(\d+)\}/,
|
|
825
832
|
// (4) placeholder-with-default in flat-key form
|
|
826
833
|
/server\.port\s*[=:]\s*\$\{[^}:]+:(\d+)\}/,
|
|
834
|
+
// (5) v2.4.0 — nested-block yml: `server:` block with intermediate
|
|
835
|
+
// keys (e.g. `ssl:`, `http:`, `error:`, `tomcat:`, `compression:`)
|
|
836
|
+
// BEFORE `port:`. Real Spring Boot configs often look like:
|
|
837
|
+
// server:
|
|
838
|
+
// ssl:
|
|
839
|
+
// key-store: ...
|
|
840
|
+
// port: 8443
|
|
841
|
+
// Pre-v2.4.0 pattern (1) requires `port:` immediately after
|
|
842
|
+
// `server:\n`, missing this common form. The lazy-quantified
|
|
843
|
+
// gap allows up to ~20000 chars between `server:` and `port:`,
|
|
844
|
+
// while the leading-whitespace constraint on `port:` ensures
|
|
845
|
+
// we match an INDENTED key (still inside the server: block),
|
|
846
|
+
// not an outdented sibling key at column 0.
|
|
847
|
+
//
|
|
848
|
+
// v2.4.0: window expanded from 2000 to 20000 chars after
|
|
849
|
+
// observing an enterprise YAML where the `server:` block
|
|
850
|
+
// contained ssl/http/tomcat/compression children spanning
|
|
851
|
+
// ~3000 chars before `port: 8443`. The 2000 limit silently
|
|
852
|
+
// missed the port and the detector defaulted to the Spring
|
|
853
|
+
// Boot 8080 fallback.
|
|
854
|
+
/^server:[\s\S]{0,20000}?\n[ \t]+port:\s*(\d+)/m,
|
|
855
|
+
// (6) v2.4.0 — same nested-block form with placeholder-with-default
|
|
856
|
+
/^server:[\s\S]{0,20000}?\n[ \t]+port:\s*\$\{[^}:]+:(\d+)\}/m,
|
|
827
857
|
];
|
|
828
858
|
for (const re of portPatterns) {
|
|
829
859
|
const pm = c.match(re);
|
|
@@ -912,6 +942,20 @@ async function detectStack(ROOT) {
|
|
|
912
942
|
}
|
|
913
943
|
}
|
|
914
944
|
|
|
945
|
+
// v2.4.0 — Spring Boot ships Logback as the default logging implementation
|
|
946
|
+
// via spring-boot-starter (transitively). Most projects do not declare
|
|
947
|
+
// `ch.qos.logback:logback-classic` explicitly because the starter brings
|
|
948
|
+
// it in. The dependency-only LOGGING_RULES regex therefore misses Logback
|
|
949
|
+
// for the common case. Fill in the default unless the project has
|
|
950
|
+
// explicitly opted into log4j2 (in which case spring-boot-starter-log4j2
|
|
951
|
+
// would replace the Logback default).
|
|
952
|
+
if (stack.framework === "spring-boot"
|
|
953
|
+
&& !stack.loggingFrameworks.includes("log4j2")
|
|
954
|
+
&& !stack.loggingFrameworks.includes("logback")) {
|
|
955
|
+
stack.loggingFrameworks.push("logback");
|
|
956
|
+
stack.detected.push("logback (spring-boot default)");
|
|
957
|
+
}
|
|
958
|
+
|
|
915
959
|
return stack;
|
|
916
960
|
}
|
|
917
961
|
|