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.
Files changed (36) hide show
  1. package/CHANGELOG.md +790 -74
  2. package/CODE_OF_CONDUCT.md +15 -0
  3. package/README.de.md +374 -876
  4. package/README.es.md +374 -875
  5. package/README.fr.md +374 -875
  6. package/README.hi.md +374 -875
  7. package/README.ja.md +374 -875
  8. package/README.ko.md +374 -874
  9. package/README.md +374 -876
  10. package/README.ru.md +374 -877
  11. package/README.vi.md +374 -875
  12. package/README.zh-CN.md +374 -874
  13. package/SECURITY.md +51 -0
  14. package/bin/commands/init.js +192 -37
  15. package/content-validator/index.js +97 -4
  16. package/health-checker/index.js +44 -10
  17. package/package.json +92 -90
  18. package/pass-json-validator/index.js +58 -7
  19. package/pass-prompts/templates/angular/pass3.md +15 -14
  20. package/pass-prompts/templates/common/claude-md-scaffold.md +81 -0
  21. package/pass-prompts/templates/common/pass3-footer.md +104 -0
  22. package/pass-prompts/templates/java-spring/pass3.md +19 -18
  23. package/pass-prompts/templates/kotlin-spring/pass3.md +23 -22
  24. package/pass-prompts/templates/node-express/pass3.md +18 -17
  25. package/pass-prompts/templates/node-fastify/pass3.md +11 -10
  26. package/pass-prompts/templates/node-nestjs/pass3.md +11 -10
  27. package/pass-prompts/templates/node-nextjs/pass3.md +18 -17
  28. package/pass-prompts/templates/node-vite/pass3.md +11 -10
  29. package/pass-prompts/templates/python-django/pass3.md +18 -17
  30. package/pass-prompts/templates/python-fastapi/pass3.md +18 -17
  31. package/pass-prompts/templates/python-flask/pass3.md +9 -8
  32. package/pass-prompts/templates/vue-nuxt/pass3.md +9 -8
  33. package/plan-installer/domain-grouper.js +45 -5
  34. package/plan-installer/index.js +11 -1
  35. package/plan-installer/scanners/scan-java.js +98 -2
  36. 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-ui/01.component-patterns.md — SFC, <script setup>, Props/Emits, slots, provide/inject
51
- - 20.frontend-ui/02.page-routing-patterns.md — Pages/layouts/dynamic routes/middleware/definePageMeta
52
- - 20.frontend-ui/03.data-fetching.md — useFetch, useAsyncData, $fetch, server routes, caching
53
- - 20.frontend-ui/04.state-management.md — Pinia stores, composables, useState, form state
54
- - 20.frontend-ui/05.styling-patterns.md — Scoped styles, CSS Modules, Tailwind/UnoCSS, theming
55
- - 10.backend-api/01.server-routes.md — Nitro server routes (server/api/), H3 event handlers
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
- - 50.verification/01.development-verification.md — Build, startup, Lighthouse
61
- - 50.verification/02.testing-strategy.md — Vitest, @vue/test-utils, E2E, @nuxt/test-utils
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
- // Flush current group before adding if it would exceed limits
17
- if (current.length > 0 && (fileCount + d.totalFiles > MAX_FILES_PER_GROUP || current.length >= MAX_DOMAINS_PER_GROUP)) {
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
- "50.verification": true,
81
+ "80.verification": true,
42
82
  "90.optional": true,
43
83
  };
44
84
  }
@@ -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
- console.log(` Database: ${stack.database || "none"}`);
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) { rootPackage = m[1].replace(/\//g, "."); break; }
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
- const totalFiles = svc.length + agg.length + mpr.length + dto.length + xml.length + domainMap[d].controllers;
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