claudeos-core 2.3.1 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +1460 -73
  2. package/CODE_OF_CONDUCT.md +15 -0
  3. package/README.de.md +321 -883
  4. package/README.es.md +322 -883
  5. package/README.fr.md +322 -883
  6. package/README.hi.md +322 -883
  7. package/README.ja.md +322 -883
  8. package/README.ko.md +322 -882
  9. package/README.md +321 -883
  10. package/README.ru.md +322 -885
  11. package/README.vi.md +322 -883
  12. package/README.zh-CN.md +321 -881
  13. package/SECURITY.md +51 -0
  14. package/bin/commands/init.js +570 -264
  15. package/content-validator/index.js +185 -12
  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 +203 -20
  21. package/pass-prompts/templates/common/pass3-footer.md +297 -56
  22. package/pass-prompts/templates/common/pass3a-facts.md +48 -3
  23. package/pass-prompts/templates/common/pass4.md +78 -40
  24. package/pass-prompts/templates/java-spring/pass1.md +54 -0
  25. package/pass-prompts/templates/java-spring/pass3.md +20 -19
  26. package/pass-prompts/templates/kotlin-spring/pass1.md +45 -0
  27. package/pass-prompts/templates/kotlin-spring/pass3.md +24 -23
  28. package/pass-prompts/templates/node-express/pass3.md +18 -17
  29. package/pass-prompts/templates/node-fastify/pass3.md +11 -10
  30. package/pass-prompts/templates/node-nestjs/pass3.md +11 -10
  31. package/pass-prompts/templates/node-nextjs/pass3.md +18 -17
  32. package/pass-prompts/templates/node-vite/pass3.md +11 -10
  33. package/pass-prompts/templates/python-django/pass3.md +18 -17
  34. package/pass-prompts/templates/python-fastapi/pass3.md +18 -17
  35. package/pass-prompts/templates/python-flask/pass3.md +9 -8
  36. package/pass-prompts/templates/vue-nuxt/pass3.md +9 -8
  37. package/plan-installer/domain-grouper.js +45 -5
  38. package/plan-installer/index.js +34 -1
  39. package/plan-installer/pass3-context-builder.js +14 -0
  40. package/plan-installer/scanners/scan-frontend.js +2 -1
  41. package/plan-installer/scanners/scan-java.js +98 -2
  42. package/plan-installer/source-paths.js +242 -0
  43. package/plan-installer/stack-detector.js +522 -42
package/CHANGELOG.md CHANGED
@@ -1,5 +1,1393 @@
1
1
  # Changelog
2
2
 
3
+ ## Releases
4
+
5
+ Quick navigation to recent releases:
6
+
7
+ - [`2.4.0`](#240--2026-04-25) — Session Continuity Protocol (v2.4 series feature 1 of 3)
8
+ - [`2.3.3`](#233--2026-04-24) — Template emoji consistency + optional `totalLines` splitter axis
9
+ - [`2.3.2`](#232--2026-04-23) — `cmdInit` decomposition + UX polish + validator co-evolution
10
+ - [`2.3.1`](#231--2026-04-23) — Patch: Windows CI breakage in `npm test`
11
+ - [`2.3.0`](#230--2026-04-23) — Language-invariant structural validation; path-hallucination defense; single-SPA detection
12
+ - [`2.2.0`](#220--2026-04-21) — Deterministic 8-section CLAUDE.md scaffold
13
+ - [`2.1.2`](#212--2026-04-21) — Patch: master plan removal cleanup regression
14
+ - [`2.1.1`](#211--2026-04-20) — Docs-only maintenance
15
+ - [`2.1.0`](#210--2026-04-20) — Pass 3 split mode (3a/3b/3c/3d-aux); `Prompt is too long` mitigation
16
+ - [`2.0.x`](#202--2026-04-20) — Pass 4 architecture refactor; gap-fill no-op invariant
17
+ - [`1.7.x`](#171--2026-04-11) — Initial monorepo detection; multi-stack expansion
18
+ - [`1.6.x`](#162--2026-04-09) — 6 → 10+ stack templates (NestJS, Vue-Nuxt, Fastify, Angular)
19
+ - [`1.5.x`](#151--2026-04-06) — Initial public preview
20
+
21
+ For older entries scroll past v1.5.0 or use the GitHub blame view.
22
+
23
+ ---
24
+
25
+ ## [2.4.0] — 2026-04-25
26
+
27
+ First feature in the v2.4 series: **Session Continuity Protocol** —
28
+ a recommended prose block inside Section 8's Memory Workflow that
29
+ addresses Claude Code's auto-compact behavior. Auto-compact may
30
+ truncate session context mid-work, dropping previously-loaded
31
+ `failure-patterns.md` references, recently-recorded decisions in
32
+ `decision-log.md`, and the `paths`-glob-conditional rule files
33
+ that had been activated for the working files. Session Continuity
34
+ gives the LLM an explicit re-entry routine for these three context
35
+ elements when a session resumes after compact or restart.
36
+
37
+ This release ships only the protocol itself. Two further v2.4
38
+ features (Self-Check Framework, Common Pattern Taxonomy) are
39
+ planned but not yet implemented; they will land in subsequent
40
+ 2.4.x releases. Test suite 702 / 702 pass (up from 699, +3 new
41
+ tests covering positive prose-form, negative H4-form rejection,
42
+ and explicit backward-compatibility for pre-v2.4 CLAUDE.md files).
43
+
44
+ ### Scaffold — Session Resume block in `claude-md-scaffold.md`
45
+
46
+ - **Problem addressed.** Claude Code sessions that exceed the
47
+ context window trigger auto-compact: portions of older turns
48
+ are summarized or dropped to make room. Restarted sessions
49
+ begin with no in-context state. Both situations leave the LLM
50
+ unaware of three pieces of state that were previously loaded:
51
+ (1) the error patterns it had scanned at session start from
52
+ `failure-patterns.md`, (2) the design decisions it had
53
+ consulted (or appended) in `decision-log.md`, and (3) the
54
+ `paths`-conditional rule files under `.claude/rules/**` that
55
+ had been activated for the files being edited. CLAUDE.md is
56
+ always re-loaded on session start, but the rest of the L4
57
+ layer is not.
58
+
59
+ - **Change.** Add a new prose block to the scaffold's Section 8
60
+ template, immediately following the 6-step `Memory Workflow`
61
+ numbered list. The block opens with the bold label
62
+ `**Session Resume (after auto-compact or restart)**:` and
63
+ carries a 3-bullet list instructing the LLM to: re-scan
64
+ `failure-patterns.md`, re-read the 3 most recent entries of
65
+ `decision-log.md`, and re-match rule files by `paths` glob
66
+ for the current working files. The block lives inside the
67
+ scaffold's existing `# CLAUDE.md template structure` fenced
68
+ example, so generated CLAUDE.md files (Korean, Japanese,
69
+ Chinese, etc.) inherit the block in the target language.
70
+
71
+ - **Why prose, not a third H4.** Section 8's structural
72
+ validator enforces EXACTLY 2 `####` headings (`L4 Memory
73
+ Files` + `Memory Workflow`). Adding `#### Session Resume`
74
+ would break every existing CLAUDE.md and require validator
75
+ surgery. Prose form sits inside the Memory Workflow section,
76
+ visually distinguished by the bold label, with no impact on
77
+ the H4 count. The scaffold's per-section spec explicitly
78
+ states "MUST NOT be a `####` subsection" so future
79
+ contributors do not regress this.
80
+
81
+ - **RECOMMENDED, not required.** Pre-v2.4 CLAUDE.md files were
82
+ generated without Session Resume. The validator does not
83
+ enforce the block's presence — only that, if present, it
84
+ takes the prose form rather than an H4. This preserves
85
+ backward compatibility for every existing project. New
86
+ generations via `npx claudeos-core init` (v2.4+) include
87
+ the block by default.
88
+
89
+ - **Tests.** Three new cases in `claude-md-validator.test.js`
90
+ under a new `Session Resume block (v2.4.0)` describe:
91
+ (1) a valid English fixture with the block in prose form
92
+ passes all 25 structural checks identically to the
93
+ Session-Resume-less `valid-en.md`;
94
+ (2) a fixture with the block placed as a third H4 under
95
+ Section 8 fails with the canonical `[S-H4-8]` error
96
+ ("found 3, expected 2"), proving the validator catches the
97
+ most likely manual-editing mistake;
98
+ (3) the existing `valid-en.md` (no Session Resume block at
99
+ all) continues to pass, asserting backward compatibility
100
+ against accidental future tightening.
101
+
102
+ - **Two new fixtures.**
103
+ `tests/fixtures/claude-md/valid-en-with-session-resume.md`
104
+ is a copy of `valid-en.md` with the block appended in prose
105
+ form. `tests/fixtures/claude-md/bad-session-resume-as-h4.md`
106
+ is a minimal valid CLAUDE.md with the block placed as
107
+ `#### Session Resume`. The 10 existing language fixtures
108
+ (`valid-en.md`, `valid-ko.md` (`observed-ko-fixed.md`),
109
+ `valid-ja.md`, `valid-zh-CN.md`, `valid-es.md`,
110
+ `valid-vi.md`, `valid-hi.md`, `valid-ru.md`, `valid-fr.md`,
111
+ `valid-de.md`) are unchanged.
112
+
113
+ ### Prompt-pipeline integration
114
+
115
+ - **Embed safety.** The scaffold ships to Pass 3 prompts via
116
+ `prompt-generator.js::demoteScaffoldMetaHeaders`, which
117
+ rewrites `## ` to `### ` for scaffold meta-sections while
118
+ preserving headings inside fenced code blocks (the template
119
+ example). Session Resume sits inside the markdown fence at
120
+ scaffold lines 7322–13929, so demote leaves it byte-identical.
121
+ Verified: the demoted scaffold contains exactly 2 `####`
122
+ headings (the template's `L4 Memory Files` and `Memory
123
+ Workflow`) and a Session Resume block of 850 bytes with
124
+ 3 bullets — same as the source.
125
+
126
+ - **i18n unaffected.** The scaffold's Section 8 per-section
127
+ spec (lines 603–680) instructs the LLM to render Section 8
128
+ headings with both the English canonical token and the
129
+ target-language gloss (e.g. `## 8. Common Rules & Memory
130
+ (L4) (<localized gloss>)`). Session Resume's bold
131
+ label is treated as ordinary prose and translates freely
132
+ alongside its bullets — verified against Korean and
133
+ Japanese CLAUDE.md fixtures, both of which pass all 25
134
+ structural checks with the block translated.
135
+
136
+ ### Validator behavior matrix
137
+
138
+ | CLAUDE.md state | Session Resume present? | Form | Result |
139
+ |---|---|---|---|
140
+ | Pre-v2.4 generated | No | — | ✅ Pass (unchanged) |
141
+ | v2.4 generated | Yes | Prose | ✅ Pass |
142
+ | Hand-edited mistake | Yes | `#### Session Resume` (3rd H4) | ❌ `[S-H4-8]` error |
143
+ | Hand-edited mistake | Yes | Prose, but in Section 5 instead of 8 | ✅ Pass (validator does not enforce content position) |
144
+
145
+ The last row is a known limitation: structural validation
146
+ checks heading counts, table positions, and canonical heading
147
+ tokens, but does not enforce that arbitrary prose blocks
148
+ appear in their semantically correct section. This is by
149
+ design — the structural validator is the inner ring of
150
+ defense, and over-policing prose position would constrain
151
+ legitimate translation and project-specific elaboration. If
152
+ future operational evidence shows users repeatedly placing
153
+ Session Resume in the wrong section, a `content-validator`
154
+ advisory (warning, not error) can be added without changing
155
+ the structural rule.
156
+
157
+ ### Combined guarantees
158
+
159
+ - **No existing CLAUDE.md is invalidated.** Backward
160
+ compatibility was the explicit primary constraint; verified
161
+ against all 10 language fixtures, the Korean observed
162
+ fixture, and BOM-prefixed variants.
163
+ - **No validator logic changed.** `structural-checks.js` is
164
+ byte-identical to v2.3.3. The Session Resume rule is
165
+ enforced indirectly: prose form is invisible to existing
166
+ checks; H4 form is caught by the pre-existing
167
+ `checkH4Counts` (S-H4-8) rule.
168
+ - **No scanner, splitter, or Pass 1–4 pipeline changes.**
169
+ Pure scaffold + fixtures + tests addition.
170
+ - **Test suite 702 / 702 pass** (up from 699 in v2.3.3),
171
+ with the +3 coming exclusively from the new Session Resume
172
+ describe block. No existing test was modified or skipped.
173
+
174
+ ### Documentation & repository assets
175
+
176
+ A repository-level addition ships alongside the protocol.
177
+ It does not touch code or tests; it improves discoverability
178
+ for prospective users browsing the repository on GitHub.
179
+
180
+ - **CHANGELOG navigation block.** A "Releases" section was
181
+ added at the top of `CHANGELOG.md`, listing the 13 most
182
+ recent releases (v2.4.0 through v1.5.x) with a one-line
183
+ summary and a GitHub-compatible anchor link to each
184
+ entry. Older entries remain reachable by scroll or by
185
+ GitHub blame. Anchor format follows GFM rules
186
+ (`[2.4.0] — 2026-04-25` becomes `#240--2026-04-25`); all
187
+ 13 anchors are verified to resolve on GitHub.
188
+
189
+ ### Verified backward compatibility
190
+
191
+ For the repository addition:
192
+ - **`CHANGELOG.md` ships in npm as before.** The TOC
193
+ addition is plain markdown; the file remains in the
194
+ package, so the navigation block is also visible on
195
+ npmjs.com's package page.
196
+ - **No test impact.** Test suite remains 702 / 702.
197
+ - **No bin script behavior change.** `npx claudeos-core
198
+ init` and all subcommands are byte-identical to the
199
+ protocol-only state earlier in this entry.
200
+
201
+ ### Pipeline robustness — bug fixes (post-protocol additions)
202
+
203
+ Fifteen bug fixes addressing failure modes surfaced during follow-up
204
+ testing on additional stack/layout combinations. All have unit test
205
+ coverage; no behavior change for projects whose previous output was
206
+ already correct. Test suite grows from 702 to 736 (+34 new cases:
207
+ 3 Session Resume + 5 totalLines axis + 5 SKILL.md/path resolution +
208
+ 3 root-package frequency + 5 Logback fallback + 2 nested port
209
+ + 2 cross-module deep-sweep + 3 monorepo path resolution +
210
+ 2 ellipsis placeholder / doc-writing-rules.md exclusion +
211
+ 2 deeply-nested-port window expansion + 2 deep-sweep extended layers +
212
+ 3 MANIFEST global coverage / domains-folder pattern +
213
+ 2 health-checker soft-fail tier +
214
+ 1 70.domains canonical convention regression guard +
215
+ 3 always-typed `70.domains/{type}/` per-domain layout
216
+ (loadDomainTypeMap helper / pass3-footer ALWAYS-typed convention /
217
+ ensureDirectories pre-creates both backend+frontend sub-folders) +
218
+ 1 02.domains.md orchestrator convention regression guard +
219
+ 1 single-batch per-domain enforcement (buildBatchScopeNote always fires);
220
+ minus overlap = +34 net).
221
+
222
+ - **`content-validator/index.js` — orchestrator/sub-skill MANIFEST
223
+ exception generalized.** The pre-existing v2.3.0 exception was
224
+ scoped to sub-skills under the legacy `{NN}.{name}.md` convention,
225
+ so generators that emit a category-level orchestrator at
226
+ `{category}/SKILL.md` (with sub-skills at
227
+ `{category}/{stem}/SKILL.md` or `{category}/{stem}/{name}.md`,
228
+ no `NN.` prefix) produced one `MANIFEST_DRIFT` advisory per
229
+ registered sub-skill. The fix relaxes the
230
+ sub-skill regex (`(?:\d+\.)?[^/]+\.md$`) to make the numeric
231
+ prefix optional, and adds a category-level rule: when CLAUDE.md
232
+ references `{category}/SKILL.md`, every sub-skill registered
233
+ under that category is treated as covered transitively. Integrity
234
+ checks (`STALE_SKILL_ENTRY` for missing files, `MANIFEST_DRIFT`
235
+ for unrelated parents) continue to fire at full strength — this
236
+ is purely a false-positive elimination.
237
+
238
+ - **`plan-installer/scanners/scan-java.js` — root package
239
+ frequency-based selection.** Pre-fix the rootPackage was set from
240
+ the FIRST matched layer-bearing file's package, which is
241
+ glob-enumeration-order-dependent: a multi-module project where
242
+ the bulk of code lives under one root but a small number of stub
243
+ files sit under a different subtree could non-deterministically
244
+ pick the minority root. The fix counts every (1-, 2-, 3-, 4-)
245
+ segment prefix preceding a layer marker, then picks the LONGEST
246
+ prefix whose count is at least 80% of the maximum. This selects
247
+ the most specific root that still covers the majority of files —
248
+ for a single-package project all prefix lengths tie at 100% and
249
+ the longest wins (intuitive). For a multi-module project the
250
+ threshold filters out minority subtrees while still picking a
251
+ specific common ancestor.
252
+
253
+ - **`plan-installer/scanners/scan-java.js` — Pattern B deep-sweep
254
+ fallback for zero-file domains.** Standard per-domain globs assume
255
+ `{domain}/{layer}/X.java`. Multi-module projects with a
256
+ `front/{domain}/` HTTP layer + `core/{domain}/{layer}/`
257
+ service/dao layer split work via the leading `**`, but
258
+ cross-domain coupling (`core/{otherDomain}/{layer}/{domain}/X.java`
259
+ — services for `{domain}` living under another module's layer
260
+ directory) was missed because the layer dir comes BEFORE the
261
+ domain dir, and `**/{domain}/{layer}/*.java` doesn't match. The
262
+ fix: when standard globs return ZERO files for a Pattern B/D
263
+ domain that's already registered (so it provably exists), fall
264
+ back to `**/${dn}/**/*.java` and classify each file by walking up
265
+ to the nearest layer dir. This catches both
266
+ `${dn}/{layer}/X.java` AND `{layer}/${dn}/X.java` placements.
267
+ Restricted to Pattern B/D zero-file case so projects with healthy
268
+ direct-layout counts behave identically to pre-fix.
269
+
270
+ - **`plan-installer/stack-detector.js` — Gradle/Maven
271
+ `packageManager` populated.** Pre-fix `stack.packageManager` was
272
+ set only for Node.js (npm/yarn/pnpm) and Python (pip/poetry/
273
+ pipenv/pdm). JVM projects using Gradle or Maven left it `null`,
274
+ which surfaced as `PackageMgr: none` in init output and `null`
275
+ in `project-analysis.json`. The fix sets `packageManager =
276
+ "gradle"` when a `build.gradle{.kts}` is detected and
277
+ `"maven"` for `pom.xml`. Node/Python detection runs first and
278
+ sets a value; the JVM fallback only fires when nothing else
279
+ claimed it (so a JVM monorepo with a Node-tooling overlay
280
+ retains the Node package manager).
281
+
282
+ - **`plan-installer/stack-detector.js` — Spring Boot default
283
+ Logback fallback.** Spring Boot ships Logback transitively via
284
+ `spring-boot-starter`; most projects don't declare
285
+ `ch.qos.logback:logback-classic` explicitly, so the
286
+ dependency-only `LOGGING_RULES` regex misses Logback for the
287
+ common case (only finding adapters like log4jdbc when
288
+ declared). The fix adds Logback to `stack.loggingFrameworks`
289
+ when `framework === "spring-boot"` AND the project hasn't
290
+ explicitly opted into log4j2 (which would suppress Logback via
291
+ `spring-boot-starter-log4j2`). Provenance is recorded in
292
+ `stack.detected` as `"logback (spring-boot default)"` so
293
+ consumers can distinguish fallback-derived from explicit
294
+ declaration.
295
+
296
+ - **`plan-installer/stack-detector.js` — nested `port:` matching
297
+ inside `server:` block.** Pre-fix port pattern (1) required
298
+ `port:` to be IMMEDIATELY adjacent to the `server:` line
299
+ (`/server:\s*\n\s*port:\s*(\d+)/`). Real Spring Boot configs
300
+ commonly nest `port:` under `server:` with intermediate keys
301
+ (`ssl:`, `http:`, `error:`, etc.) preceding it. The fix adds
302
+ patterns (5) and (6) — `^server:[\s\S]{0,2000}?\n[ \t]+port:\s*(\d+)$/m`
303
+ with placeholder variant — that lazy-match within a 2000-char
304
+ window after `server:` while requiring leading whitespace on
305
+ the `port:` line (so an outdented sibling `port:` at column 0
306
+ is correctly rejected as outside the server: block).
307
+
308
+ - **`content-validator/index.js` — STALE_PATH monorepo prefix
309
+ resolution.** Pre-fix the path-claim check verified each cited
310
+ `src/...\.(ts|tsx|js|jsx)` path by `path.join(ROOT, claimed)`
311
+ followed by `fs.existsSync()`. Turborepo / pnpm-workspace
312
+ projects keep source files under `apps/<app>/src/...` or
313
+ `packages/<pkg>/src/...`; a rule citing the workspace-relative
314
+ shorthand `src/app/layout.tsx` (the natural single-app form)
315
+ was a false-positive `STALE_PATH` even when the actual file
316
+ existed at `apps/<app>/src/app/layout.tsx`. The fix introduces
317
+ a `resolvePathClaim()` helper with three-step resolution:
318
+ (1) direct `<ROOT>/<claimed>`, (2) `<ROOT>/apps/*/<claimed>`,
319
+ (3) `<ROOT>/packages/*/<claimed>`. The fallback only fires for
320
+ `src/`-prefixed paths and only when the direct match fails;
321
+ genuinely missing files still flag STALE_PATH (verified by a
322
+ defense-against-masking test case).
323
+
324
+ - **`content-validator/index.js` — ellipsis placeholder + meta-doc
325
+ exclusion.** Two coordinated false-positive eliminations
326
+ surfaced after the monorepo fix shipped. (1) `hasPlaceholder()`
327
+ now recognizes the `/.../` ellipsis path segment as a
328
+ placeholder marker, alongside the existing `{...}` curly-brace,
329
+ `Xxx` / `XXX`, and `*` glob forms. LLMs commonly write
330
+ illustrative paths like `src/app/api/.../route.ts` to mean
331
+ "any API route under `app/api/`"; pre-fix the literal `...`
332
+ fragment failed `fs.existsSync()` and got reported as
333
+ `STALE_PATH`. The new pattern `/\/\.\.\.\//` matches a
334
+ three-dot segment between path separators — `...` is not a
335
+ valid directory name on any major filesystem (only `.` and `..`
336
+ are legal dot-only names), so this signal is unambiguous. (2)
337
+ `PATH_CLAIM_EXCLUDE_FILES` now includes
338
+ `00.core/51.doc-writing-rules.md` alongside the existing
339
+ `00.core/52.ai-work-rules.md`. Both are meta-documents
340
+ teaching path discipline to the reader: 51 explicitly states
341
+ "verify file paths before writing them in documents" and
342
+ cites example paths (`src/middleware.ts`,
343
+ `src/app/api/<route>/route.ts`) as illustrations of the rule.
344
+ The content-blind validator would otherwise flag every cited
345
+ example as `STALE_PATH` on every project that doesn't happen
346
+ to contain all the cited illustrative files. The exclusion is
347
+ defense-in-depth alongside the placeholder relaxation: prompt
348
+ guidance encourages placeholders, validator tolerates the
349
+ remaining literal-path examples in this one-file class.
350
+
351
+ - **`plan-installer/stack-detector.js` — `server:` block port window
352
+ expansion (2000 → 20000 chars).** Patterns (5)/(6) added in the
353
+ prior nested-port fix used a 2000-char lazy window between
354
+ `server:` and `port:`. Larger enterprise-style YAMLs commonly nest
355
+ `server:` with `ssl:`/`http:`/`tomcat:`/`compression:`/`error:`
356
+ children spanning 3000+ chars before the `port:` line; the 2000
357
+ limit silently failed to match and the detector defaulted to the
358
+ Spring Boot 8080 fallback. The fix expands the window to 20000
359
+ chars (sufficient for ~600 lines of preceding content) while
360
+ keeping the lazy quantifier — first-match-wins semantics
361
+ guarantee the closest `port:` is captured, so the wider window
362
+ costs nothing in correctness.
363
+
364
+ - **`plan-installer/index.js` — multi-DB array surfaced in console.**
365
+ Pre-fix the `[Phase 1] Detecting stack...` block printed only
366
+ `stack.database` (singular); the `stack.databases` array
367
+ (multi-dialect) populated by `detectDb()` since v2.3.2 was
368
+ invisible to the user and to downstream Pass 1 LLMs that grep
369
+ the console transcript. On dual-datasource projects (e.g. Oracle
370
+ primary + MySQL master/slave) Pass 1 had to re-derive the second
371
+ DB from source code. The fix adds a `Databases:` line listing
372
+ the full array when `databases.length > 1`; single-DB output
373
+ is byte-for-byte identical.
374
+
375
+ - **`plan-installer/scanners/scan-java.js` — extended layer
376
+ recognition in deep-sweep + catch-all service classification.**
377
+ The v2.4.0 deep-sweep (separate prior fix) only recognized the
378
+ canonical layer set
379
+ (`controller`/`service`/`aggregator`/`facade`/`usecase`/
380
+ `orchestrator`/`mapper`/`repository`/`dao`/`dto`/`vo`). Larger
381
+ codebases place implementation code under non-canonical
382
+ layers like `factory/`, `strategy/`, `impl/`, `helper/`,
383
+ `handler/`, `manager/`, `client/`, etc. Pre-fix these files were
384
+ silently dropped (no `break`), causing legitimate domains to
385
+ report 0 totalFiles and surfacing as "Group N: ~0 files" in
386
+ Phase 4 output. The fix expands the recognized layer list to 22
387
+ entries and adds a catch-all: any `.java` file under the domain
388
+ tree that fails layer classification is counted as a `service`
389
+ (the most generic backend role). This catches both
390
+ `core/{dn}/{nonstandard-layer}/X.java` and bare-domain
391
+ `core/{dn}/X.java` (no layer subdir) layouts.
392
+
393
+ - **`content-validator/index.js` — global MANIFEST coverage rule for
394
+ sub-skill paths.** The v2.4.0 orchestrator/sub-skill exception
395
+ expected a sibling orchestrator at `{category}/{stem}.md` paired
396
+ with sub-skills at `{category}/{stem}/{file}.md`. Pass 3c
397
+ occasionally invents new folder structures (e.g.
398
+ `{category}/domains/{domain}.md` for per-domain notes) that lack
399
+ a matching sibling orchestrator, and every such registration
400
+ surfaced as `MANIFEST_DRIFT`. The fix adds a category-independent
401
+ coverage rule: when CLAUDE.md mentions any `MANIFEST.md` (the
402
+ global skill registry), all SUB-SKILL paths (paths matching the
403
+ deep-folder regex) are considered covered transitively because
404
+ the reader navigates from MANIFEST to find them. TOP-LEVEL
405
+ registrations (`{category}/{file}.md` with no folder layer) are
406
+ unaffected — they still require direct mention. This is purely a
407
+ false-positive elimination on layouts the design intended to
408
+ support; the integrity check `STALE_SKILL_ENTRY` (registered
409
+ file missing on disk) continues to fire at full strength.
410
+
411
+ - **`pass-prompts/templates/common/pass3-footer.md` — explicit
412
+ ✅/❌ enforcement block for standard files.** Pass 3b LLMs
413
+ occasionally generated standard files with only ✅ "correct"
414
+ examples and no ❌ "incorrect" example, surfacing as
415
+ `[NO_BAD_EXAMPLE]` advisories from `content-validator`. The
416
+ per-stack template instruction ("Each file MUST include
417
+ Correct/Incorrect examples") was sometimes deprioritized
418
+ during long generation runs. The fix adds a CRITICAL-tier block
419
+ to `pass3-footer.md` (which is appended to every Pass 3 prompt
420
+ regardless of stack) explicitly mandating both ✅ and ❌ blocks
421
+ in every standard file, with a self-check rule
422
+ ("Does this file have at least one ❌ block?") to be applied
423
+ before finalizing each file. The pass3-footer is a project-wide
424
+ reminder that runs after the stack-specific body, giving the
425
+ rule late-stage emphasis.
426
+
427
+ - **`bin/commands/init.js` — Pass 3b/3c batch scope clarification.**
428
+ The `buildBatchScopeNote()` helper produced a per-batch
429
+ instruction telling Pass 3 LLMs to "generate per-domain files
430
+ for the domains in this batch". On multi-batch runs (>15
431
+ domains), one observed failure mode was Pass 3b rationalizing
432
+ Rule B (idempotent skip) to bypass the entire batch when common
433
+ files at OTHER paths existed (created by 3b-core). The fix
434
+ strengthens the scope note: explicit per-domain output paths
435
+ (`60.domains/{domain}.md` and `.claude/rules/60.domains/
436
+ {domain}-rules.md`), explicit "Expected output: N new files"
437
+ count to make zero-output detectable, and a guard clause
438
+ forbidding Rule B as justification for whole-batch SKIP when
439
+ per-domain target files don't yet exist. Single-batch runs
440
+ (≤15 domains) are unaffected — this scope note only fires in
441
+ multi-batch mode.
442
+
443
+ - **`health-checker/index.js` — soft-fail (`advisory`) tier for
444
+ `content-validator`.** Pre-fix `content-validator` exited
445
+ non-zero whenever it found any quality advisory (STALE_PATH,
446
+ MANIFEST_DRIFT, NO_BAD_EXAMPLE, etc.), which the health-checker
447
+ rendered as `❌ content-validator fail` in its summary. Init
448
+ output simultaneously printed "ℹ️ Content advisories detected
449
+ — these are quality notes, NOT generation failures", producing
450
+ a confusing dual signal. The fix introduces a third severity
451
+ tier alongside the existing `pass`/`fail`/`warn`: `advisory`
452
+ (icon `ℹ️`), assigned to tools flagged with `softFail: true`.
453
+ `content-validator` carries this flag; on non-zero exit it
454
+ renders as `ℹ️ content-validator advisory` and does NOT propagate
455
+ to the health-checker's overall exit code. The summary line was
456
+ rewritten to distinguish real failures
457
+ (`⚠️ N failed` — gate-blocking) from soft notes
458
+ (`✅ All systems operational (1 advisory, 1 warning)` — gate
459
+ green). Real structural failures (plan-validator, sync-checker,
460
+ manifest-generator) continue to gate the health command's exit
461
+ code, preserving CI-pipeline gating.
462
+
463
+ ### Combined guarantees (post-bug-fix state)
464
+
465
+ - **Test suite 736 / 736** (up from 702 in the protocol-only
466
+ state earlier in this entry; +29 new cases as enumerated
467
+ above; no existing test was modified or skipped, with one
468
+ exception: the integrated MANIFEST_DRIFT scenario test had its
469
+ expected drift count adjusted from 3 to 0 to reflect the new
470
+ global-MANIFEST coverage rule, with the rationale documented
471
+ inline in the test body).
472
+ - **No CLI surface changes.** `init`, `lint`, `health`, `memory`
473
+ subcommands and their flags are unchanged. `package.json`
474
+ remains at v2.4.0; no new dependencies.
475
+ - **No backward-compatibility breaks.** Every fix above is
476
+ defensive (false-positive elimination, fallback for missing
477
+ cases) — projects whose pre-fix output was already correct
478
+ continue to produce byte-identical output.
479
+ - **Token-leak audit.** All identifiers used in the bug-fix code
480
+ and tests are generic CRUD examples (widget / notification /
481
+ inventory / order / product / payment etc.); no project
482
+ codenames or company-specific identifiers introduced.
483
+
484
+ ### Namespace category unification (root-cause structural fix)
485
+
486
+ Following observation that Pass 3 LLM occasionally cross-contaminated
487
+ namespace category names (creating `.claude/rules/10.backend-api/`
488
+ sibling to `.claude/rules/10.backend/`, etc.), the underlying naming
489
+ asymmetry between rules and standard namespaces is eliminated: all
490
+ shared categories now use IDENTICAL folder names across both
491
+ namespaces, and the prefix collision between `rules/50.sync` and
492
+ `standard/50.verification` is resolved by relocating the standard
493
+ side to `80.verification`.
494
+
495
+ **Before** (asymmetric):
496
+ - standard: `10.backend-api`, `20.frontend-ui`, `50.verification`
497
+ - rules: `10.backend`, `20.frontend`, `50.sync`
498
+
499
+ **After** (unified):
500
+ - standard: `10.backend`, `20.frontend`, **`80.verification`**, `90.optional`
501
+ - rules: `10.backend`, `20.frontend`, `50.sync`, `60.memory`, `70.domains/{type}/`
502
+
503
+ Now `10.*` / `20.*` mean the same conceptual category in both
504
+ namespaces (LLM cannot confuse them). `50.sync` (rules) and
505
+ `80.verification` (standard) no longer share a numeric prefix.
506
+
507
+ **Affected files**: `bin/commands/init.js` ensureDirectories,
508
+ buildBatchScopeNote, buildStageCorePrompt, determineActiveDomains;
509
+ all 12 `pass-prompts/templates/*/pass3.md`; tests
510
+ (`init-command.test.js` EXPECTED_DIRS, `pass3-context-builder.test.js`
511
+ activeDomains key, two `tests/fixtures/claude-md/observed-ko-*.md`
512
+ fixtures); 10-language READMEs (directory tree examples).
513
+
514
+ **Migration**: Existing projects generated with the pre-unification
515
+ convention (`standard/10.backend-api/`, `standard/50.verification/`)
516
+ must re-run `npx claudeos-core init --force` to regenerate under
517
+ the new layout. Validators are namespace-agnostic (use globs) and
518
+ require no changes — old generated content remains readable but
519
+ will not be re-emitted at the legacy paths.
520
+
521
+ **Test coverage**: 736 / 736 pass (+1 from a new
522
+ `buildBatchScopeNote always fires` regression guard added in the
523
+ same release). No existing tests modified except the EXPECTED_DIRS
524
+ list and one `activeDomains` literal — both updated to the new
525
+ canonical category names.
526
+
527
+ ## [2.3.3] — 2026-04-24
528
+
529
+ Template hygiene + splitter infrastructure. Two co-shipped changes,
530
+ both derived from two consecutive regression scenarios (one
531
+ React/Vite frontend, 14 domains; one legacy Java/Spring backend,
532
+ 7 domains) that surfaced (a) a template inconsistency causing
533
+ spurious `NO_GOOD_EXAMPLE` / `NO_BAD_EXAMPLE` advisories on Vite/
534
+ Angular/Fastify/Flask projects while Java/Spring and seven other
535
+ stacks were clean, and (b) a Pass 1 time-outlier where a 29-file
536
+ domain group ran ~70% longer than a 39-file group because the group
537
+ contained a single 2544-line source file (TUI Grid wrapper) whose
538
+ analysis cost was driven by line count, not file count. Zero
539
+ functional regression: all existing scanners continue to produce
540
+ the legacy `{name, totalFiles}` domain shape and split exactly as
541
+ before. Test suite 699 / 699 pass (up from 694, +5 new tests for
542
+ the optional `totalLines` axis).
543
+
544
+ ### Prompt — `pass3.md` code-example emoji consistency
545
+
546
+ - **Problem addressed.** `content-validator` checks standard files
547
+ for the presence of ✅ / ❌ emoji (or per-language equivalents
548
+ for "correct" / "incorrect" across the 10 supported output languages) as a
549
+ structural signal that correct and incorrect code examples are
550
+ both present. Eight of the twelve stack-specific `pass3.md`
551
+ templates (`java-spring`, `kotlin-spring`, `node-express`,
552
+ `node-nestjs`, `node-nextjs`, `python-django`, `python-fastapi`,
553
+ `vue-nuxt`) instruct the LLM with the literal phrase
554
+ `- Correct examples (✅ code blocks)` and
555
+ `- Incorrect examples (❌ code blocks)`, causing the emoji to
556
+ propagate into generated standards verbatim. The remaining four
557
+ (`angular`, `node-fastify`, `node-vite`, `python-flask`) omitted
558
+ the emoji in the instruction phrase, producing standards that
559
+ described correct/incorrect cases in prose without the emoji
560
+ marker the validator expects. Result: clean Java/Spring regression scenario
561
+ (0 advisories, 0 notes) vs. Vite regression scenario (0 advisories, 13 notes)
562
+ — identical pipeline, identical generator, different template
563
+ phrasing.
564
+
565
+ - **Change.** Align the four outlier templates with the eight-stack
566
+ majority. The Angular template retains its TypeScript-specific
567
+ wording (`Correct examples (✅ code blocks in TypeScript)` /
568
+ `Incorrect examples (❌ code blocks showing common Angular
569
+ mistakes)`); the other three adopt the same two-line pattern as
570
+ the majority. No other template content changed.
571
+
572
+ - **Rationale for template change rather than validator exemption.**
573
+ An alternative fix would have been to mark overview-style standard
574
+ files (`00.core/01.project-overview.md`,
575
+ `00.core/02.architecture.md`, etc.) as exempt from the emoji
576
+ check on the grounds that they are descriptive rather than
577
+ example-heavy. That option was rejected because it would hide a
578
+ real template inconsistency rather than fix it, and because the
579
+ Java/Spring stack's zero-notes output proves that overview files
580
+ *can* include ✅/❌ markers without becoming artificial — the
581
+ generator simply needs to be told to include them.
582
+
583
+ - **Scope.** Template prose only. `content-validator` regexes and
584
+ keyword tables are unchanged. Generated standards on
585
+ Angular/Fastify/Vite/Flask projects will now pass the emoji check
586
+ on first generation rather than surfacing advisories that Pass 3c
587
+ / Pass 3d / Pass 4 eventually reduce. Existing Java/Spring and
588
+ the seven other stacks are unaffected (their templates already
589
+ carried the emoji).
590
+
591
+ ### Splitter — optional `totalLines` axis in `splitDomainGroups`
592
+
593
+ - **Problem addressed.** Pass 1 batches domains into groups using
594
+ `MAX_FILES_PER_GROUP = 40` and `MAX_DOMAINS_PER_GROUP = 4`. This
595
+ is sound when per-file size is roughly uniform but produces time
596
+ outliers when a group contains a small number of very large
597
+ files. An observed scenario: a 29-file 4-domain batch took 7 m 0 s,
598
+ while a 39-file single-domain batch and a 34-file 3-domain batch
599
+ on the same project ran in 4 m 9 s and 4 m 22 s respectively.
600
+ Root cause: one of the 4 domains in the slow batch included a
601
+ single 2544-line third-party grid library wrapper file whose
602
+ analysis cost is driven by line count, not file count. Pass 1
603
+ ETA estimation and batch balance both suffer when a single
604
+ large file hides inside an otherwise small-looking group.
605
+
606
+ - **Change.** Introduce `MAX_LINES_PER_GROUP = 8000` as an optional
607
+ third splitting axis alongside the existing file-count and
608
+ domain-count budgets. The new axis is strictly additive: the
609
+ splitter consults `d.totalLines` on each incoming domain and
610
+ flushes the current group early if adding the next domain's
611
+ lines would exceed the budget. When `totalLines` is absent,
612
+ negative, non-number, or `NaN`, the line-budget check is skipped
613
+ entirely and the splitter behaves byte-for-byte as in v2.3.2.
614
+
615
+ - **Backward compatibility.** All five existing scanners
616
+ (`scan-java.js`, `scan-kotlin.js`, `scan-node.js`, `scan-python.js`,
617
+ `scan-frontend.js`) continue to emit `{name, totalFiles, ...}`
618
+ without `totalLines`, so their output passes through the splitter
619
+ with identical grouping to v2.3.2. Scanners that wish to opt into
620
+ line-aware splitting can populate `totalLines` per domain in a
621
+ future release; no scanner change ships in 2.3.3.
622
+
623
+ - **Threshold calibration.** 8000 is a conservative starting point
624
+ equivalent to ~40 files × ~200 lines each — roughly the
625
+ file-count budget expressed in lines. It can be revised once
626
+ scanners begin populating `totalLines` and real distribution data
627
+ accumulates across stacks. The constant is declared at module
628
+ scope (lifted out of the function body, alongside the existing
629
+ `MAX_FILES_PER_GROUP` and `MAX_DOMAINS_PER_GROUP` constants) so
630
+ future tuning is a one-line change with accompanying rationale in
631
+ the adjacent block comment.
632
+
633
+ - **Defensive validation.** `totalLines` is read with a strict
634
+ `typeof d.totalLines === "number" && d.totalLines >= 0` guard.
635
+ Malformed values (strings, `NaN`, negatives) cause the domain to
636
+ be treated as line-count-unknown rather than crashing the
637
+ splitter or producing nonsensical groupings. This protects
638
+ against scanner bugs during the migration period when some
639
+ scanners may emit `totalLines` and others not.
640
+
641
+ - **Tests.** Five new cases in `tests/domain-grouper.test.js`:
642
+ (1) legacy shape produces identical output (backward
643
+ compatibility); (2) line budget flushes when two 5000-line
644
+ domains would combine; (3) line budget does not flush when two
645
+ 3000-line domains stay under the 8000 threshold; (4) mixed shape
646
+ (one domain with `totalLines`, one without) works correctly; and
647
+ (5) malformed `totalLines` values (negative, string, `NaN`) fall
648
+ back to legacy behavior. All 33 pre-existing tests in the same
649
+ suite continue to pass unchanged.
650
+
651
+ ### Combined guarantees
652
+
653
+ - **Output parity for existing projects.** Projects that previously
654
+ ran clean on v2.3.2 continue to run clean on v2.3.3. Projects on
655
+ Angular/Fastify/Vite/Flask that previously accumulated "No
656
+ ✅/❌ example found" advisories will produce fewer or zero such
657
+ advisories on first generation.
658
+ - **No scanner changes, no Pass changes, no pipeline changes.**
659
+ The splitter and template modifications are isolated; all Pass
660
+ 1-4 stages, resume semantics, progress accounting, and marker
661
+ validation are byte-identical to v2.3.2.
662
+ - **Test suite 699 / 699 pass** (up from 694), with the +5 coming
663
+ exclusively from the new optional-axis cases. No existing test
664
+ was modified or skipped.
665
+
666
+ ## [2.3.2] — 2026-04-23
667
+
668
+ Internal refactor + UX polish + prompt/validator co-evolution for
669
+ path-hallucination defense + stack-detector hardening. Five co-shipped
670
+ changes: (1) `bin/commands/init.js` — `cmdInit` decomposed from a
671
+ single 970-line function into 16 focused stage helpers plus a 107-line
672
+ orchestrator; (2) `content-validator` output reframed from the
673
+ vocabulary of generation failures to the vocabulary of quality
674
+ advisories; (3) library-convention hallucination warning in
675
+ `pass3-footer.md` / `pass4.md` rescoped from filename-binding to
676
+ topic-binding, with validator-side placeholder-pattern expansion
677
+ (`Xxx` / `XXX` / glob-star) and a narrow file-level exclusion for
678
+ `00.core/52.ai-work-rules.md`, plus a follow-up hypothetical /
679
+ future-tense framing guard that closes the "if this feature were
680
+ added, it would live at `src/middleware.ts`" class of path
681
+ fabrication; (4) `claude-md-scaffold.md` Section 1
682
+ generation rules hardened with a canonical 10-language translation
683
+ table, and Section heading parenthetical gloss reclassified from
684
+ optional to required (for non-English output) / forbidden (for
685
+ English output), with a 10-language × 8-section gloss table;
686
+ (5) `plan-installer/stack-detector.js` extended to cover Gradle
687
+ variable-reference patterns (`sourceCompatibility = "${var}"`, ext-
688
+ block Spring Boot version), Maven property references, Spring
689
+ property-placeholder ports (`${APP_PORT:8090}`), iBatis detection as
690
+ distinct from MyBatis, multi-dialect database arrays, MariaDB
691
+ detection (previously missing from `DB_KEYWORD_RULES`), and logging-
692
+ framework identification (Logback / Log4j2 / log4jdbc / Log4j 1.x
693
+ with oauth-style false-positive guards). Also `pass-prompts/templates/
694
+ java-spring/pass3.md` and `kotlin-spring/pass3.md` logging-rule glob
695
+ extended to cover `.properties`, `.groovy`, and `log4jdbc*` file
696
+ patterns; Pass 1 Java / Kotlin prompts now include an explicit
697
+ "configuration file verification" block instructing the LLM to read
698
+ `build.gradle` / `pom.xml` / `application*.yml` directly as
699
+ ground-truth sources when stack metadata is incomplete. Zero
700
+ functional regression: identical pipeline behavior, identical exit
701
+ codes for CI consumers. Test suite 694 / 694 pass (up from 662).
702
+
703
+ ### Refactor — `cmdInit` decomposition
704
+
705
+ - **Problem addressed.** The main entry-point function had accumulated
706
+ 970 lines, 77 `if` statements, and 17 `try` blocks as each new
707
+ pipeline stage (Pass 1 batching, Pass 2 structural validation,
708
+ Pass 3 split + resume, Pass 3 stale-marker detection, Pass 4
709
+ gap-fill, lint, content-validator) was spliced into the same linear
710
+ body. The function was readable one stage at a time but not as a
711
+ whole, and every new contribution required paging through the
712
+ entire body to locate the correct insertion point. This release
713
+ extracts each stage into a named helper, leaving `cmdInit` as a
714
+ top-to-bottom pipeline of 16 function calls with progress
715
+ accounting between them (107 lines, 2 `if`, 0 `try`; estimated
716
+ McCabe complexity ≥94 → ≤5).
717
+
718
+ - **Extracted stage helpers.** Each owns exactly one phase of the
719
+ pipeline and nothing else:
720
+ `checkPrerequisites`, `resolveLanguage`, `applyResumeMode`,
721
+ `ensureDirectories`, `loadDomainGroups`, `loadPass1Prompts`,
722
+ `makeProgressBar`, `runPass1Loop`, `runPass2`,
723
+ `buildPass3ContextJson`, `handlePass3StaleMarker`, `dispatchPass3`,
724
+ `runPass4`, `runVerificationTools`, `runLint`,
725
+ `runContentValidator`, `printCompletionBanner`. Stage functions
726
+ that advance the outer progress bar return a step-delta that
727
+ `cmdInit` accumulates into its local `completedSteps` counter,
728
+ preserving the `completedSteps++` token required by the
729
+ `pass3-marker.test.js` stale-region regex.
730
+
731
+ - **`runPass3Split` intentionally NOT extracted.** Eight test files
732
+ (`pass3-marker`, `master-plan-removal`, `pass3-batch-subdivision`,
733
+ `pass4-marker-validation`, `pass3-guards`, `translation-skip-env`,
734
+ `pass2-validation`, `pass4-claude-md-untouched`) read
735
+ `bin/commands/init.js` as source text and grep for internal
736
+ patterns (`runStage("3d-aux"`, `function computeBatches`,
737
+ `DOMAINS_PER_BATCH = 15`, `if (isBatched) { ... runStage("3b-core"`
738
+ proximity, etc.). Moving `runPass3Split` to a separate module
739
+ would require re-designing those eight source-parity checks
740
+ against importable exports. That is a deliberate follow-up; this
741
+ patch keeps the test boundary untouched so the refactor is pure
742
+ mechanical decomposition.
743
+
744
+ - **Semantic preservation.** All user-visible behavior is identical:
745
+ every log line, every `InitError` message, every banner frame,
746
+ every progress-bar tick, every resume/fresh branch, every
747
+ stale-marker code path, the static-fallback marker body, and the
748
+ `applyStaticFallback` gap-fill sequence are byte-identical to
749
+ v2.3.1. The only change is *where* the code lives within the same
750
+ file.
751
+
752
+ ### UX — `content-validator` advisory vocabulary
753
+
754
+ - **Problem addressed.** When `init` finished cleanly and the user
755
+ saw the celebratory `✅ ClaudeOS-Core — Complete` banner, the
756
+ previous step's output already said `❌ ERRORS (6): [STALE_PATH] ...`.
757
+ The ordering produced a "success or failure?" flinch even though
758
+ the two messages were describing different questions: `init` had
759
+ succeeded (files are on disk, structure valid, tests pass);
760
+ `content-validator` had merely observed that some LLM-guessed
761
+ filenames inside the generated rules don't resolve on disk. Those
762
+ are quality advisories — the generated docs are usable — but the
763
+ word "ERRORS" made users reach for `init --force`, which does not
764
+ reliably fix the advisories (re-running Pass 3 with the same fact
765
+ JSON often produces the same mis-inference).
766
+
767
+ - **Fix.** Purely linguistic. No logic changes.
768
+
769
+ - **`content-validator/index.js` — output relabeling.** The banner
770
+ `❌ ERRORS (N)` becomes `ℹ️ ADVISORIES (N)`; `⚠️ WARNINGS (M)`
771
+ becomes `⚠️ NOTES (M)`; the final summary `Total: N errors,
772
+ M warnings` becomes `Total: N advisories, M notes`. The internal
773
+ arrays stay named `errors` and `warnings` because they encode
774
+ severity for programmatic consumers.
775
+
776
+ - **Exit code preserved at source.** `content-validator` still
777
+ returns `process.exit(1)` when advisories exist. This is a
778
+ deliberate asymmetry: the tool reports advisories softly in
779
+ output but still signals a non-zero exit code, because
780
+ `npx claudeos-core health` and any CI pipeline wired to it need
781
+ a real gate. Stripping the exit code would silently pass
782
+ `STALE_PATH` / `MANIFEST_DRIFT` findings through `health-checker`
783
+ (which branches on tool exit code + `warnOnly` flag), destroying
784
+ the detection signal v2.3.0 was built for.
785
+
786
+ - **`bin/commands/init.js` `runContentValidator` — advisory
787
+ framing.** The post-subprocess message is rewritten as
788
+ "Content advisories detected — these are quality notes, NOT
789
+ generation failures. Your generated docs are ready to use as-is."
790
+ The guidance pointer reads "npx claudeos-core health (standalone
791
+ gate with exit code)" so users who want a hard gate know where
792
+ to find one.
793
+
794
+ - **`stale-report.json` schema unchanged.** Fields `contentErrors`
795
+ and `contentWarnings` keep their names — they are part of the
796
+ public schema read by `health-checker` and any external CI
797
+ consumer.
798
+
799
+ - **Why this is not severity down-grading.** A naive fix would move
800
+ `STALE_PATH` and `MANIFEST_DRIFT` from the `errors[]` array into
801
+ the `warnings[]` array and exit 0. That would flatten the signal
802
+ in `health-checker` (which distinguishes pass/fail/warn by exit
803
+ code + `warnOnly` flag), so an advisory-heavy project would report
804
+ "✅ All systems operational" even with 20 stale paths — the exact
805
+ silent-failure class v2.3.0 eliminated. This release instead keeps
806
+ the severity distinction intact inside the tool and stale-report,
807
+ and only changes the words the user reads.
808
+
809
+ ### Prompt + Validator — Library-convention hallucination
810
+
811
+ - **Problem addressed.** The library-convention warning in
812
+ `pass3-footer.md` / `pass4.md` was previously scoped to specific
813
+ filenames (`testing-strategy.md`, `styling-patterns.md`,
814
+ `state-management.md`). When a file's topic matched (testing,
815
+ env typing, styling, state management) but its filename did not,
816
+ the LLM ignored the warning and cited canonical library paths
817
+ from training data (`src/test/setup.ts`, `src/types/env.d.ts`,
818
+ `src/__mocks__/handlers.ts`, etc.) that do not exist in the
819
+ project. `content-validator [10/10]` then flagged these as
820
+ `STALE_PATH` advisories.
821
+
822
+ A second, distinct failure class exists when a prompt enumerates
823
+ convention-trap paths as a denylist: the LLM, when generating a
824
+ file whose purpose is to teach future sessions about hallucination
825
+ traps (notably `52.ai-work-rules.md`), treats the denylist as
826
+ source material and copies the literal paths into the output as
827
+ cautionary illustrations ("AI sessions should not invent paths
828
+ like these"). `content-validator`'s path-claim check is content-
829
+ blind and treats the illustrations as literal claims. This is
830
+ **prompt-to-output educational leakage** — not a hallucination,
831
+ but a teaching example that the validator cannot distinguish from
832
+ a real claim.
833
+
834
+ - **Fix.** Four coordinated changes across prompt and validator:
835
+
836
+ - **Scope expansion to topic-binding.** The warning block in
837
+ `pass3-footer.md` and `pass4.md` was rescoped from filename-
838
+ binding to topic-binding — the trigger is "the topic the file
839
+ is about", not "the filename of the document". A "Scope note
840
+ (v2.3.2+)" paragraph makes this explicit.
841
+
842
+ - **No literal convention paths in prompt templates.** The
843
+ enumerated denylist approach was abandoned. The warning
844
+ describes the class behaviorally ("PROJECT-CHOICE files",
845
+ "library's canonical path may not exist here") and points to
846
+ abstract replacement forms ("a shared setup module under a
847
+ test directory of your choice", "augment `ImportMetaEnv` in a
848
+ type-declaration file of your choosing"). Literal example
849
+ paths have been removed from anti-pattern blocks in both
850
+ templates and rewritten as mechanism labels (e.g.
851
+ `Framework-convention entry-point invention`, `Parent-directory
852
+ or constant-name renormalization`, `Plausibly-named utility
853
+ invention`) with prose explanations but no `src/...` strings.
854
+
855
+ - **Educational-example placeholder guidance.** A new block in
856
+ both `pass3-footer.md` and `pass4.md` explains that rule files
857
+ which need to illustrate bad path habits (notably
858
+ `52.ai-work-rules.md`) should use abstract placeholders —
859
+ `{placeholder}`, `Xxx` / `XXX`, glob stars, or prose — rather
860
+ than literal paths. Literal example paths are interpreted as
861
+ real claims by `content-validator [10/10]` regardless of
862
+ surrounding prose.
863
+
864
+ - **Validator: placeholder detection expanded.** The
865
+ `hasPlaceholder(path)` predicate in `content-validator/index.js`
866
+ now skips three placeholder forms:
867
+ 1. `{...}` — the original v2.3.0 curly-brace form.
868
+ 2. `X{3,}` / `Xxx` — uppercase-XXX / `Xxx` placeholder
869
+ tokens. No word boundaries, so `useXXX_CONFIG` and
870
+ `XXXParser.ts` are both correctly skipped.
871
+ 3. `*` — glob wildcards describing a class of files.
872
+
873
+ - **Validator: file-level exclusion for by-design educational
874
+ files.** A new `PATH_CLAIM_EXCLUDE_FILES` set in
875
+ `content-validator/index.js` skips path-claim verification on
876
+ files whose purpose is to cite convention-trap paths as
877
+ warnings. Currently one file: `00.core/52.ai-work-rules.md`
878
+ (the AI Work Rules file). The exclusion is narrow, explicit,
879
+ and documented in a code comment explaining why the exclusion
880
+ is a design choice rather than a band-aid. The output line
881
+ shows "(N file(s) excluded by design)" so users understand the
882
+ count is reduced intentionally.
883
+
884
+ - **Why a split (prompt + validator) rather than prompt-only.** The
885
+ prompt change alone cannot guarantee the LLM will never produce a
886
+ literal example path when writing an educational rule — the LLM
887
+ may genuinely believe a concrete example is pedagogically clearer.
888
+ The validator change alone (exclusion only) would let a genuine
889
+ hallucination in `52.ai-work-rules.md` go undetected. The
890
+ combination is defense-in-depth: the prompt nudges toward
891
+ placeholder form (reducing false positives at source); the
892
+ validator tolerates educational examples in the one file where
893
+ they are expected (eliminating the remaining false positives);
894
+ and genuine hallucinations in every other file continue to be
895
+ flagged as before.
896
+
897
+ - **Test impact.** `tests/pass4-prompt.test.js`'s `pass4 enforces
898
+ path fact grounding` test was updated: literal-path matchers
899
+ (e.g., ``/❌ `src\/__mocks__\/handlers\.ts`/``) were replaced
900
+ with topic-level and mechanism-label matchers (`/Library-
901
+ convention canonical paths | testing.*env typing.*styling/`,
902
+ `/Framework-convention entry-point invention/`, etc.). The test's
903
+ intent is unchanged: it still verifies that the Pass 4 prompt
904
+ warns about library-convention hallucinations; only the form of
905
+ the warning has evolved.
906
+
907
+ #### Follow-up: hypothetical / future-tense framing guard
908
+
909
+ - **Problem addressed.** The library-convention fix closed the
910
+ "canonical path exists here" failure mode, but a sibling failure
911
+ mode was observed: when describing *future* or *hypothetical*
912
+ feature additions, the LLM would wrap a framework-canonical path
913
+ in conditional framing ("if middleware is added later, place it
914
+ at `src/middleware.ts`", "for a future health endpoint,
915
+ `src/app/api/health/route.ts`") and write the literal path
916
+ verbatim. `content-validator [10/10]` is content-blind: it treats
917
+ every backticked `src/...` path as a path claim regardless of the
918
+ conditional prose around it, so these hypothetical examples are
919
+ flagged as `STALE_PATH` advisories even though the author
920
+ understood they were speculative.
921
+
922
+ The topic-binding library-convention warning did not cover this
923
+ case because the framing shifts the register from "this project
924
+ HAS X" to "this project WOULD HAVE X if …" — a different surface
925
+ form the original warning did not name.
926
+
927
+ - **Fix (prompt-only, two files).** Added a dedicated "Hypothetical
928
+ / future-tense framing is NOT a loophole" block to both
929
+ `pass-prompts/templates/common/pass3-footer.md` and
930
+ `pass-prompts/templates/common/pass4.md`. Key rules:
931
+
932
+ - **Conditional framing does not change the validator's
933
+ decision.** `if we adopted X`, `were this feature introduced,
934
+ it would live at …`, `for a future Y`, `when Z is added later`
935
+ (and translated equivalents in any output language) do NOT
936
+ make a literal `src/...` path safe. The block states this
937
+ invariance explicitly so the LLM does not interpret conditional
938
+ prose as a validator-bypass.
939
+
940
+ - **Role / directory form, not filename.** The correct
941
+ hypothetical is expressed as a ROLE + DIRECTORY description
942
+ without committing to a filename (e.g., "If middleware is
943
+ added later, place it at the path the routing convention
944
+ expects — do not cite a specific filename until the file
945
+ actually exists"). Three worked `✅ RIGHT` examples cover the
946
+ middleware, health-endpoint, and env-typing cases.
947
+
948
+ - **OMIT as last resort.** If the LLM cannot name the role +
949
+ directory without committing to a `src/...` path that does NOT
950
+ appear in `pass3a-facts.md`, the guidance is to omit the
951
+ example entirely. An omitted example is better than a
952
+ fabricated path downstream readers may treat as authoritative.
953
+ The OMIT condition is double-gated: (a) role + directory
954
+ description is not possible, AND (b) the path is not in
955
+ `pass3a-facts.md` — paths that DO appear in the allowlist
956
+ continue to be written verbatim per the existing
957
+ "directory-scoped rule is correct" guidance.
958
+
959
+ - **Language-invariant.** The rule explicitly states that
960
+ translated conditional phrases in any output language (Korean,
961
+ Japanese, Chinese, etc.) are subject to the same constraint,
962
+ because the validator matches on the literal path string, not
963
+ on the surrounding prose.
964
+
965
+ - **Placement in `pass4.md`.** The new block is added as a fifth
966
+ ❌ mechanism (after `Framework-convention entry-point invention`,
967
+ `Parent-directory or constant-name renormalization`,
968
+ `Plausibly-named utility invention`, and the topic-binding
969
+ `Library-convention canonical paths` block), immediately before
970
+ the ✅ guidance that says "If pass3a-facts.md shows a specific
971
+ filename and path for a role, write that exact path verbatim".
972
+ The adjacency makes the interaction between the prohibition
973
+ (hypothetical fabrication) and the permission (existing
974
+ allowlist) visible to the LLM at a glance.
975
+
976
+ - **Why prompt-only, not validator-side.** Distinguishing
977
+ "assertive claim about an existing file" from "conditional
978
+ description of a future file" would require NLP-level prose
979
+ understanding, which is out of scope for the structural
980
+ `content-validator`. The existing placeholder forms
981
+ (`{placeholder}`, `Xxx`/`XXX`, glob `*`) remain as the
982
+ validator-side defense-in-depth: an LLM that cannot phrase the
983
+ hypothetical in role/directory form can still fall back to a
984
+ placeholder.
985
+
986
+ - **Test impact.** Five independent verification surfaces now
987
+ cover this block: (1) template-content checks (header,
988
+ ✅/❌ examples, OMIT fallback, language-invariant clause, CJK
989
+ absence); (2) related unit tests unchanged — `pass4-prompt.test.js`
990
+ (12/12) and `prompt-generator.test.js` (33/33) continue to pass
991
+ because existing mechanism-label matchers are unaffected by the
992
+ new fifth block; (3) end-to-end prompt-generation smoke confirms
993
+ the block survives assembly into `pass3-prompt.md` and
994
+ `pass4-prompt.md`; (4) full suite 694/694 unchanged; (5) the
995
+ 4-mechanism ordering invariant
996
+ (`Framework-convention → Parent-directory → Plausibly-named →
997
+ Hypothetical`) is asserted via regex proximity match in the
998
+ smoke test.
999
+
1000
+ ### Prompt — CLAUDE.md Section 1 language localization
1001
+
1002
+ - **Problem addressed.** For non-English `--lang` targets,
1003
+ `claude-md-scaffold.md` Section 1 generation rules previously
1004
+ instructed "emit in the target output language" but immediately
1005
+ followed with a fixed English template containing `{OUTPUT_LANG}`
1006
+ as the only substitution slot:
1007
+
1008
+ ```
1009
+ As the senior developer for this repository, you are responsible
1010
+ for writing, modifying, and reviewing code. Responses must be
1011
+ written in {OUTPUT_LANG}.
1012
+ ```
1013
+
1014
+ The specific English sentence acted as a stronger signal than the
1015
+ abstract instruction to translate — LLMs copy concrete templates
1016
+ verbatim when the template's only visible variable is a
1017
+ substitution slot. The generated output therefore carried a
1018
+ Section 1 Line 1 in English for non-English targets, producing
1019
+ the ironic effect of "Responses must be written in {LANG}" where
1020
+ {LANG} is correctly substituted yet the containing sentence
1021
+ itself is in English. Other sections escaped this trap because
1022
+ their templates were table-shaped or keyword-shaped (`Language |
1023
+ {value}`, etc.); Section 1 was unique in carrying a complete
1024
+ English sentence as "template".
1025
+
1026
+ - **Fix.** Added canonical translations for all 10 supported
1027
+ languages (`en`, `ko`, `zh-CN`, `ja`, `es`, `vi`, `hi`, `ru`,
1028
+ `fr`, `de`) directly inside `claude-md-scaffold.md` Section 1
1029
+ generation rules. Each translation is paired with its language
1030
+ code; the LLM picks the one matching `{OUTPUT_LANG}` and emits it
1031
+ verbatim. Languages outside the canonical 10 fall back to the
1032
+ semantic structure described by the English reference.
1033
+
1034
+ Supporting changes:
1035
+
1036
+ - **Scaffold body warning comment.** The body template's Line 1
1037
+ (still English, since it serves as the generic slot) now
1038
+ carries an inline `{!-- ... --}` comment instructing the LLM
1039
+ to replace with the canonical translation when
1040
+ `{OUTPUT_LANG} != en`. This defends against LLMs that scan the
1041
+ body template first and overlook the generation rules lower in
1042
+ the same file.
1043
+
1044
+ - **Checklist augmentation.** The scaffold's verification
1045
+ checklist gained a new item: "Section 1 Line 1 is in
1046
+ `{OUTPUT_LANG}` — matches the canonical translation (if
1047
+ `{OUTPUT_LANG}` is one of the 10 canonical codes). If Line 1
1048
+ contains 'As the senior developer' while `{OUTPUT_LANG}` is
1049
+ NOT `en`, the translation was skipped — fix it." This gives
1050
+ the LLM an explicit self-check predicate before finalizing
1051
+ output.
1052
+
1053
+ - **Example block framing.** The "Example: Section 1 for
1054
+ different stacks" block's framing comment was upgraded from
1055
+ "Emit the final output in the target output language; the
1056
+ semantic content should match" (weak) to an explicit
1057
+ `⚠️ Language note:` block stating that the English examples
1058
+ show SEMANTIC structure only and pointing back to the
1059
+ canonical translations for Line 1.
1060
+
1061
+ - **Why scaffold-level and not code-level.** This is not a
1062
+ post-processing concern. The translation must happen at
1063
+ generation time inside the LLM context, not as a sed/replace
1064
+ step afterward — sed would catch only the English reference
1065
+ sentence but would miss subsequent rephrased variants the LLM
1066
+ might produce. Making the scaffold explicit about the canonical
1067
+ text eliminates ambiguity at source.
1068
+
1069
+ - **Test impact — none.** Scaffold files are runtime resources;
1070
+ no test asserts on the text of `claude-md-scaffold.md`.
1071
+
1072
+ #### Follow-up: Section heading gloss now required (not optional)
1073
+
1074
+ - **Problem addressed.** A second localization inconsistency existed
1075
+ in `##` section headings: run-to-run variation in whether headings
1076
+ carried their native-language gloss. Some runs emitted
1077
+ `## 1. Role Definition ({gloss})` (English canonical + target-
1078
+ language gloss); others emitted only `## 1. Role Definition`,
1079
+ omitting the gloss entirely. Both outputs were technically
1080
+ compliant with the v2.3.1 scaffold rules, which stated the gloss
1081
+ was "optional" and "a courtesy, not a requirement". The
1082
+ inconsistency broke the operator's expectation that two runs of
1083
+ the same project would produce the same heading format, and
1084
+ removed a useful intelligibility cue for non-English readers.
1085
+
1086
+ - **Fix.** Reclassified the parenthetical gloss from "optional" to
1087
+ "REQUIRED when `{OUTPUT_LANG}` != `en`" / "OMITTED when
1088
+ `{OUTPUT_LANG}` == `en`". This is now a deterministic rule with
1089
+ no LLM-side discretion.
1090
+
1091
+ - **`claude-md-scaffold.md` "Section heading format" rewrite.**
1092
+ The format rule now reads: primary English canonical REQUIRED;
1093
+ parenthetical native-language gloss REQUIRED when non-English,
1094
+ OMITTED when English. A canonical gloss table covering all 10
1095
+ supported languages × all 8 sections (80 entries) was added
1096
+ below the rule so the LLM picks the exact gloss verbatim. The
1097
+ example blocks (ko, ja, en) were expanded to show both the
1098
+ correct form and two failure modes each: missing gloss on
1099
+ non-English output, and gloss present on English output.
1100
+
1101
+ - **Scaffold body template annotation.** A `{!-- SECTION HEADING
1102
+ RULE --}` comment was added at the top of the scaffold body
1103
+ template pointing to the gloss table above. This defends
1104
+ against LLMs that scan the body template first and copy its
1105
+ English-only headings verbatim without consulting the format
1106
+ rule.
1107
+
1108
+ - **Pass 3-footer STEP 4b rewrite.** The title determinism check
1109
+ (executed as a post-generation self-audit by the LLM) was
1110
+ upgraded from "a native-language translation may follow in
1111
+ parentheses" to explicit `(a)` + `(b)` clauses: (a) English
1112
+ canonical as primary (language-invariant); (b) parenthetical
1113
+ native-language gloss required when non-English, omitted when
1114
+ English. Worked examples for `en`, `ko`, `ja` output
1115
+ illustrate each case.
1116
+
1117
+ - **Checklist augmentation (two new items).** The scaffold's
1118
+ verification checklist gained a "Section heading gloss rule"
1119
+ item requiring all 8 headings to carry the parenthetical gloss
1120
+ when `{OUTPUT_LANG}` != `en`, and a paired "English gloss-
1121
+ absence rule" item requiring gloss to be OMITTED when
1122
+ `{OUTPUT_LANG}` == `en`. Both items name-check the canonical
1123
+ table so the LLM knows where to resolve the exact gloss text.
1124
+
1125
+ - **Why strictly a follow-up, not a separate change.** The
1126
+ underlying problem is the same class as the Section 1 Line 1
1127
+ bug: the scaffold left room for LLM discretion on language-
1128
+ localization decisions, and two runs of the same project
1129
+ produced divergent results. The Line 1 fix addressed one
1130
+ specific slot with a canonical translation; this follow-up
1131
+ applies the same "canonical translations, no discretion"
1132
+ pattern to the heading gloss slot.
1133
+
1134
+ - **Test impact — none.** No test asserts on scaffold text;
1135
+ `claude-md-validator`'s heading check (which predates this
1136
+ release) already tolerates the gloss via a regex that matches
1137
+ "English canonical, optionally followed by parenthetical text",
1138
+ so the stricter scaffold rule does not require validator
1139
+ changes to enforce.
1140
+
1141
+ ### Stack detector — variable-reference patterns, iBatis, multi-dialect DBs, logging frameworks
1142
+
1143
+ - **Problem addressed.** `plan-installer/stack-detector.js` is the
1144
+ static analyzer that produces `project-analysis.json`, the input
1145
+ to every Pass 1 run. A class of hallucinations in generated
1146
+ CLAUDE.md (incorrect Java version, server port, or logging-
1147
+ framework labels) traces to the same root cause: the stack-
1148
+ detector regex returns `null` for a field, and the Pass 1 LLM
1149
+ fills the gap by assuming framework defaults (e.g. "Java 17+"
1150
+ for any Spring Boot 3.x project, "port 8080" for any Spring
1151
+ Boot project). Tracing the regexes surfaced a broader gap:
1152
+ multiple modern Gradle/Maven patterns, legacy iBatis projects,
1153
+ multi-dialect backends, and logging-framework identification
1154
+ were all outside the detector's coverage.
1155
+
1156
+ - **Fix — Gradle Java version (4 patterns, not 1).** The v2.3.1
1157
+ regex `sourceCompatibility\s*=\s*['"]?(\d+)['"]?` only matched
1158
+ the direct-literal form. Extended to four patterns, tried in
1159
+ order:
1160
+ 1. Direct literal: `sourceCompatibility = 21` / `'21'` / `"21"`
1161
+ (also matches `targetCompatibility`).
1162
+ 2. `JavaVersion` enum: `sourceCompatibility = JavaVersion.VERSION_21`
1163
+ (with `VERSION_1_8` → Java 8 legacy form).
1164
+ 3. Toolchain block: `JavaLanguageVersion.of(21)` inside
1165
+ `java { toolchain { ... } }`.
1166
+ 4. Variable-reference fallback: when `sourceCompatibility =
1167
+ "${javaVersion}"`, resolve the variable name inside the same
1168
+ file's `ext` block. The RegExp for the resolution
1169
+ dynamically escapes the variable name with the standard
1170
+ regex-meta-character escape pattern.
1171
+
1172
+ - **Fix — Gradle Spring Boot version variable reference.** Parallel
1173
+ fallback for `ext { springBootVersion = '3.5.5' }` combined with
1174
+ `id 'org.springframework.boot' version "${springBootVersion}"`.
1175
+ The three existing patterns are tried first; only when none
1176
+ captures a numeric value (captures starting with `${` are
1177
+ rejected as variable references) does the fallback resolve the
1178
+ variable inside the same file.
1179
+
1180
+ - **Fix — Maven Java version (3 patterns).** Extended from
1181
+ `<java.version>\d+` literal-only to:
1182
+ 1. Direct `<java.version>` value.
1183
+ 2. `<maven.compiler.source>` / `<maven.compiler.target>`
1184
+ values.
1185
+ 3. Property reference like
1186
+ `<java.version>${project.javaVersion}</java.version>` where
1187
+ the referenced property is defined earlier in `<properties>`.
1188
+ Cross-file resolution (parent POM, BOM) is intentionally out
1189
+ of scope — those cases fall through to LLM-side analysis.
1190
+
1191
+ - **Fix — Yml server port Spring placeholder (4 patterns).** The
1192
+ v2.3.1 regexes `server:\n port: (\d+)` and
1193
+ `server\.port[=:](\d+)` only matched literal port numbers.
1194
+ Spring Boot accepts property-placeholder defaults like
1195
+ `port: ${APP_PORT:8090}` — extended to capture the post-colon
1196
+ default value in both yml-nested and flat-key forms. The default
1197
+ is the correct value because it represents what the application
1198
+ falls back to when the environment variable is unset.
1199
+
1200
+ - **Feature — iBatis detection as a first-class ORM.** Apache
1201
+ iBatis (EOL 2010) and Spring iBatis are distinct from MyBatis;
1202
+ MyBatis evolved out of iBatis but uses a different XML namespace
1203
+ and runtime architecture. Conflating them in Pass 3 output would
1204
+ produce incorrect guidance. `IBATIS_REGEX` matches specific
1205
+ coord patterns (`org.apache.ibatis`, `spring-ibatis`,
1206
+ `ibatis-sqlmap`, `ibatis-core`, `ibatis-common`) and runs BEFORE
1207
+ the generic ORM_RULES table in both Gradle and Maven branches.
1208
+ MyBatis projects (`org.mybatis:mybatis`,
1209
+ `mybatis-spring-boot-starter`) continue to resolve to
1210
+ `orm: "mybatis"` — the detection boundary between the two is
1211
+ precise.
1212
+
1213
+ - **Feature — multi-dialect database arrays (`stack.databases`).**
1214
+ v2.x consumers expected a single primary DB (`stack.database`);
1215
+ backends declaring multiple dialect drivers simultaneously lost
1216
+ all but the first indicator. Added a second field
1217
+ `stack.databases` (plural) that collects every DB keyword
1218
+ across all config sources (Gradle `build.gradle`, Maven
1219
+ `pom.xml`, Gradle version catalogs, yml, `.env`, Node
1220
+ `package.json`, Python `requirements.txt`). Order-preserving and
1221
+ deduped. `stack.database` keeps its v2.x semantics as "the
1222
+ first-match primary" for backward compatibility; Pass 1 prompts
1223
+ and Pass 3 standard generation should prefer `stack.databases`
1224
+ when present and non-empty. Empty array (not null) when no DB
1225
+ is detected, to simplify array comprehensions in prompts.
1226
+
1227
+ - **Fix — MariaDB detection.** The `DB_KEYWORD_RULES` table
1228
+ previously had entries for PostgreSQL, MySQL, Oracle, MongoDB,
1229
+ SQLite, and H2 — but NOT for MariaDB. Projects using
1230
+ `org.mariadb.jdbc:mariadb-java-client` were classified as `null`
1231
+ (or as MySQL, when the MySQL driver was also present). MariaDB
1232
+ is now a distinct entry in the keyword table and in the Maven
1233
+ / yml inline DB scans.
1234
+
1235
+ - **Feature — logging framework detection (`stack.loggingFrameworks`).**
1236
+ New array field enumerating JVM logging frameworks detected
1237
+ from Gradle/Maven dependencies and yml `logging.config:`
1238
+ references. Recognizes four frameworks:
1239
+ (a) Log4j2 via `org.apache.logging.log4j:log4j-core` coord or
1240
+ `log4j2-*.xml` config file;
1241
+ (b) Logback via `ch.qos.logback:logback-classic` coord or
1242
+ `logback-*.xml` / `logback*.groovy` config file;
1243
+ (c) log4jdbc (JDBC logging adapter, reported alongside the
1244
+ primary framework);
1245
+ (d) Log4j 1.x (EOL 2015) via precise coord regex `log4j:log4j`
1246
+ with quote/whitespace boundaries to avoid matching
1247
+ `log4j-to-slf4j` or `log4j-api` (Log4j2 ecosystem
1248
+ libraries that contain `log4j:log4j` as a substring). The
1249
+ Log4j 1.x boundary required a specific regex form
1250
+ (quote/colon/whitespace character class before the coord)
1251
+ because word boundaries alone were insufficient.
1252
+
1253
+ - **Fix — Pass 3 logging rule glob extended.** The Pass 3 prompt
1254
+ for Java and Kotlin Spring stacks specified auto-load paths as
1255
+ `["**/*.java", "**/logback*.xml", "**/log4j*.xml"]`. This
1256
+ missed three file types commonly present in Spring
1257
+ projects: Logback's Groovy DSL configuration (`logback*.groovy`),
1258
+ Log4j / Log4j2 properties files (`log4j*.properties`), and
1259
+ log4jdbc adapter configuration (`log4jdbc*.properties`).
1260
+ Extended the glob to cover all five file patterns.
1261
+
1262
+ - **Fix — Pass 1 prompts include configuration-file verification
1263
+ block.** Both `java-spring/pass1.md` and `kotlin-spring/pass1.md`
1264
+ now begin with a "MANDATORY: Configuration file verification"
1265
+ section instructing the LLM to read `build.gradle` (or
1266
+ `build.gradle.kts` / `pom.xml`), `application*.yml` (and profile
1267
+ variants), and referenced logging configuration files BEFORE
1268
+ analyzing domain source code. The LLM is told that
1269
+ `project-analysis.json`'s stack metadata may be incomplete and
1270
+ that the configuration files are ground-truth sources. Explicit
1271
+ examples show variable-reference resolution (`sourceCompatibility
1272
+ = "${javaVersion}"` → resolve via `ext { ... }`) and Spring
1273
+ placeholder port extraction (`port: ${APP_PORT:8090}` → extract
1274
+ `8090`). When the analyzer output and the configuration files
1275
+ disagree, the LLM is instructed to trust the configuration file
1276
+ and record the discrepancy. This adds a second defensive layer:
1277
+ even if future Gradle/Maven syntax evolves past the detector's
1278
+ regex coverage, the LLM's direct file read catches the
1279
+ discrepancy.
1280
+
1281
+ - **Fix — Config file glob expanded to cover Spring's full naming
1282
+ space.** The yml scan in v2.3.1 globbed only
1283
+ `**/application*.yml`, missing three file classes that Spring
1284
+ Boot loads identically: `application.yaml` (spec-official
1285
+ extension), `application.properties` (Spring Initializr default
1286
+ when no format is specified), and
1287
+ `bootstrap.{yml,yaml,properties}` (Spring Cloud Config /
1288
+ Consul / Eureka — loaded BEFORE `application.*` and commonly
1289
+ declaring service ports and config-server URIs). The new glob
1290
+ `**/{application,bootstrap}*.{yml,yaml,properties}` covers all
1291
+ combinations including profile variants (`application-local.yml`,
1292
+ `application-dev.properties`). The inner regex set was already
1293
+ format-agnostic — yml `server:\n port: N` syntax and
1294
+ `.properties`-style `server.port=N` flat-key syntax were both
1295
+ covered by the same pattern list, so no additional regex work
1296
+ was needed.
1297
+
1298
+ - **Fix — Comment stripping (`stripComments()` shared helper).**
1299
+ Commented-out dependency lines must not match `LOGGING_RULES`
1300
+ or the Maven DB / ORM / framework scans. A shared helper strips
1301
+ three comment styles in a single pass:
1302
+ 1. Line-level `//` (Gradle Kotlin/Groovy DSL).
1303
+ 2. Line-level `#` (yml, properties, shell).
1304
+ 3. Block-level `<!-- ... -->` (Maven `pom.xml`, XML config;
1305
+ non-greedy multi-line, so a commented-out `<dependency>`
1306
+ block spanning many lines is handled in a single regex
1307
+ pass).
1308
+
1309
+ `detectLogging` runs on `stripComments(content)`. The Maven
1310
+ branch of `detectStack` derives `pomClean = stripComments(pom)`
1311
+ after `<properties>` parsing is complete, and uses `pomClean`
1312
+ for ALL dependency-layer scans (framework check, ORM, iBatis,
1313
+ DB keyword array, H2, logging). The raw `pom` is retained for
1314
+ `<properties>` reads because commented-out property definitions
1315
+ are rare in practice and the property-reference resolution
1316
+ already scopes itself to the declared property name.
1317
+
1318
+ - **Feature — Maven XML form for Log4j2 / Logback detection.** The
1319
+ Gradle coord regex `org\.apache\.logging\.log4j[.:]log4j-core`
1320
+ expects a `:` or `.` separator between groupId and artifactId.
1321
+ In Maven XML, the two are in separate tags, so the separator
1322
+ is `</groupId>...<artifactId>`, not a single character. Paired
1323
+ regexes now match the XML form within a 300-character window
1324
+ (large enough to span typical whitespace and `<version>` /
1325
+ `<scope>` siblings, small enough that unrelated `<dependency>`
1326
+ blocks further down the file do not falsely pair):
1327
+ - Log4j2: `<groupId>\s*org\.apache\.logging\.log4j\s*<\/groupId>[\s\S]{0,300}?<artifactId>\s*log4j-core\s*<\/artifactId>`.
1328
+ The `log4j-core` artifactId is required — `log4j-to-slf4j`
1329
+ and `log4j-api` (bridges) must NOT trigger "Log4j2 is the
1330
+ primary framework".
1331
+ - Logback: `<groupId>\s*ch\.qos\.logback\s*<\/groupId>[\s\S]{0,300}?<artifactId>\s*logback-(?:classic|core)\s*<\/artifactId>`.
1332
+ Both `logback-classic` (runtime shipped with Spring Boot)
1333
+ and `logback-core` are recognized.
1334
+
1335
+ - **Fix — Placeholder regex boundary relaxation (`X{3,}` without
1336
+ word boundary).** The v2.3.0 `hasPlaceholder` predicate in
1337
+ `content-validator/index.js` used `/\bX{3,}\b|Xxx/` to
1338
+ recognize uppercase-XXX placeholder tokens. The `\b` boundaries
1339
+ caused two false negatives:
1340
+ - `XXXParser.ts`: the right `\b` expects a non-word character
1341
+ after the X run, but `Parser` is alphanumeric.
1342
+ - `useXXX_CONFIG`: the left `\b` requires a non-word
1343
+ character before the X run, but `useXXX` has `e` directly
1344
+ before.
1345
+ Removed both word boundaries. The predicate is now `/X{3,}/`
1346
+ (with the separate `/Xxx/` branch preserved for the
1347
+ capital-lower-lower convention). Audited against a curated set
1348
+ of typical identifier patterns (`matrix`, `XMLParser`,
1349
+ `indexXY`, `taxi`, `examineX`, `textX`, `XX1`): none contain
1350
+ three or more consecutive uppercase X's, so the relaxation
1351
+ introduces no new false positives.
1352
+
1353
+ - **Tests added.** 32 new unit tests in `stack-detector.test.js`:
1354
+ - 8 for Java-version patterns and port patterns (literal,
1355
+ JavaVersion enum, toolchain, ext-variable reference; yml
1356
+ literal, flat-key, yml placeholder, flat-key placeholder).
1357
+ - 18 for iBatis vs MyBatis distinction (4), Maven Java
1358
+ version patterns (3), Gradle ext Spring Boot version
1359
+ reference (1), multi-dialect databases incl. MariaDB (4),
1360
+ logging framework detection incl. false-positive prevention
1361
+ from `log4j-to-slf4j` and comment-stripping (6).
1362
+ - 6 for config-file glob expansion (`.properties`, `.yaml`,
1363
+ `bootstrap.yml`, profile variants) and comment-stripping.
1364
+
1365
+ ### Combined guarantees
1366
+
1367
+ - **Test suite.** 694 / 694 pass (up from 662 in v2.3.1 — 32 new
1368
+ tests for the stack-detector extensions), with one existing
1369
+ test updated (`pass4-prompt.test.js` assertions migrated from
1370
+ literal-path matchers to topic-level and mechanism-label
1371
+ matchers as part of the library-convention warning rewrite).
1372
+ `tests/content-validator.test.js` line 103
1373
+ (`notStrictEqual(result.code, 0, "should exit non-zero")`)
1374
+ still passes because the exit code is preserved. No stdout
1375
+ assertions reference the strings `ERRORS` or `WARNINGS` — they
1376
+ match on advisory types (`STALE_PATH`, `MANIFEST_DRIFT`,
1377
+ `STALE_SKILL_ENTRY`) which are untouched.
1378
+
1379
+ - **No new dependencies. No CLI surface changes.** Template
1380
+ changes are limited to prompt-layer guidance: the library-
1381
+ convention warning block in `pass3-footer.md` and `pass4.md`
1382
+ gained topic-binding scope; `claude-md-scaffold.md` Section 1
1383
+ gained a 10-language canonical translation table plus
1384
+ verification checklist items — all targeted expansions of
1385
+ existing anti-hallucination / language-localization guidance,
1386
+ not structural changes to Pass 3, Pass 4, or CLAUDE.md format.
1387
+ Same two runtime deps (`glob`, `gray-matter`). Same commands,
1388
+ same flags, same outputs (just different labels for
1389
+ `content-validator`).
1390
+
3
1391
  ## [2.3.1] — 2026-04-23
4
1392
 
5
1393
  Patch release. Fixes Windows CI breakage in `npm test`.
@@ -18,21 +1406,21 @@ No source, template, or test changes. Test count unchanged at 662.
18
1406
  ## [2.3.0] — 2026-04-23
19
1407
 
20
1408
  Adds language-invariant structural validation for generated `CLAUDE.md`.
21
- Dogfooding v2.2.0 on a Korean-output Vite + React project (`frontend-react-A`)
1409
+ Regression testing v2.2.0 on a Korean-output Vite + React project (`a Vite frontend test project`)
22
1410
  surfaced the §9 L4-memory re-declaration anti-pattern *despite* the scaffold,
23
1411
  expanded blocklist, and post-generation self-check all being present in the
24
1412
  embedded Pass 3 prompt. Root cause: forbidden-section enforcement depended
25
1413
  on the LLM matching English canonical labels (`"Memory Layer (L4)"`) against
26
- its own translated output (`"메모리 (L4)"`, `"メモリ (L4)"`, etc.) — a
1414
+ its own translated output (the localized form of `"Memory (L4)"` in Korean, Japanese, etc.) — a
27
1415
  natural-language equivalence judgment the LLM does not perform reliably
28
1416
  across 10 supported languages.
29
1417
 
30
- Dogfooding v2.3.0's initial build on a sibling project (`frontend-react-B`,
31
- same organization, same language, same stack family) then surfaced a second
1418
+ Regression testing v2.3.0's initial build on a second test project (`a Vite frontend test project`,
1419
+ same language, same stack family) then surfaced a second
32
1420
  multi-repo invariant failure: the §9 problem was fixed, but the *wording*
33
1421
  of section headings drifted freely. One project's §7 read
34
- `"DO NOT Read (직접 읽지 말아야 할 파일)"` while the sibling's read
35
- `"읽지 (Files Not to Be Read Directly)"`. Both were "equivalent in
1422
+ `"DO NOT Read (<localized gloss A>)"` while the sibling's read
1423
+ `"<localized gloss B> (Files Not to Be Read Directly)"`. Both were "equivalent in
36
1424
  meaning" per the scaffold, but `grep "## 7. DO NOT Read"` matched the
37
1425
  first and missed the second — multi-repo discoverability broken.
38
1426
 
@@ -41,14 +1429,14 @@ LLM self-check to deterministic code-level validation that does not depend
41
1429
  on natural-language matching, and adds a cross-repo title-determinism
42
1430
  invariant (English canonical primary + optional translation parenthetical).
43
1431
 
44
- Continued dogfooding on `frontend-react-B` then surfaced two more failure
1432
+ Continued regression testing on `a Vite frontend test project` then surfaced two more failure
45
1433
  classes unrelated to CLAUDE.md structure:
46
1434
 
47
1435
  1. **Path hallucination in rules/standard**. Pass 3 generated rule files
48
- referencing `src/feature/routers/featureRoutePath.ts` when the actual
1436
+ referencing `src/feature/routers/<feature>RoutePath.ts` when the actual
49
1437
  file was `src/feature/routers/routePath.ts`. Root cause: the LLM saw
50
1438
  the parent directory `src/feature/` and a TypeScript constant
51
- `FEATURE_ROUTE_PATH` and "renormalized" the filename to match. Pre-v2.3.0
1439
+ `<FEATURE>_ROUTE_PATH` and "renormalized" the filename to match. Pre-v2.3.0
52
1440
  validation did not check whether path claims resolved to real files.
53
1441
 
54
1442
  2. **MANIFEST ↔ CLAUDE.md §6 Skills drift**. Four skills registered in
@@ -61,7 +1449,7 @@ paths, file-system existence, MANIFEST vs CLAUDE.md cross-reference) —
61
1449
  no natural-language matching, so it works identically for all 10 output
62
1450
  languages.
63
1451
 
64
- Running the initial v2.3.0 build against `frontend-react-B` surfaced a
1452
+ Running the initial v2.3.0 build against `a Vite frontend test project` surfaced a
65
1453
  third, upstream issue in the frontend domain scanner. The project has
66
1454
  a single-SPA layout (`src/admin/{api,context,dto,routers,pages/*}/`,
67
1455
  plus a separate `src/guide/` for documentation). The subapp scanner,
@@ -70,13 +1458,13 @@ interpreted `admin` as a platform keyword and emitted the architectural
70
1458
  layers beneath it as pseudo-domains: `admin-api`, `admin-context`,
71
1459
  `admin-dto`, `admin-routers`. That fragmented one SPA into 5+ spurious
72
1460
  domains and, critically, primed Pass 3 to fabricate filenames with the
73
- `admin` prefix — the root cause of the `featureRoutePath.ts` hallucination
1461
+ `admin` prefix — the root cause of the `<feature>RoutePath.ts` hallucination
74
1462
  pattern. v2.3.0 adds a single-SPA detection rule: when only ONE distinct
75
1463
  platform keyword matches across the project tree, subapp emission is
76
1464
  suppressed by default, and feature domains are left to the downstream
77
1465
  page/FSD/components scanners to discover correctly.
78
1466
 
79
- Running the v2.3.0 build against `backend-java-spring` then surfaced a
1467
+ Running the v2.3.0 build against `a Spring backend test project` then surfaced a
80
1468
  long-standing resume bug in the init pipeline. When a prior `init` run
81
1469
  is interrupted mid-Pass-3 — most commonly a stream idle timeout during
82
1470
  the 3d-aux (database + mcp-guide) stage — `pass3-complete.json` is
@@ -93,9 +1481,9 @@ orchestrator to inspect marker contents: when the marker is partial,
93
1481
  logic resumes from the next unstarted stage; only fully-completed
94
1482
  markers are skipped.
95
1483
 
96
- Finally, the full v2.3.0 pipeline run against `frontend-react-B` (14
1484
+ Finally, the full v2.3.0 pipeline run against `a Vite frontend test project` (14
97
1485
  domains, Korean output) surfaced a structural regression the validator
98
- itself caught and flagged: `## 9. 메모리 운영 (L4)` appeared in
1486
+ itself caught and flagged: a `## 9. ` heading translating to "Memory Operations (L4)" appeared in
99
1487
  `CLAUDE.md` as a re-declaration of the memory file table already
100
1488
  present in Section 8. This was the exact anti-pattern v2.3.0 was
101
1489
  designed to prevent, now reappearing despite the scaffold's explicit
@@ -117,10 +1505,10 @@ but never touches `CLAUDE.md`. The `appendClaudeMdL4Memory()` export
117
1505
  is preserved as a no-op for any external caller depending on its
118
1506
  signature.
119
1507
 
120
- Post-retirement dogfooding on `frontend-react-B` surfaced a final class
1508
+ Post-retirement regression testing on `a Vite frontend test project` surfaced a final class
121
1509
  of issue: four `STALE_PATH` errors in Pass 4-generated rule and
122
1510
  standard files (`src/feature/main.tsx` assumed from Vite convention;
123
- `src/feature/routers/featureRoutePath.ts` invented by prepending the
1511
+ `src/feature/routers/<feature>RoutePath.ts` invented by prepending the
124
1512
  parent directory name to the filename; `src/components/utils/classNameMaker.ts`
125
1513
  fabricated as a plausible-sounding utility). The root cause was
126
1514
  parallel to the §9 issue: Pass 3's path grounding rules live in
@@ -134,7 +1522,7 @@ The guidance also teaches the positive pattern: when in doubt,
134
1522
  scope a rule to a directory (`src/admin/api/`) rather than
135
1523
  inventing a specific filename.
136
1524
 
137
- Re-running `init` on `backend-java-spring` with the Fix A build proved
1525
+ Re-running `init` on `a Spring backend test project` with the Fix A build proved
138
1526
  path-grounding works in practice — STALE_PATH dropped from the
139
1527
  expected 4 to 0 across all Pass 4-generated rule and standard
140
1528
  files — but left 8 MANIFEST_DRIFT errors in place. Analysis
@@ -170,16 +1558,16 @@ implements both halves of this split:
170
1558
 
171
1559
  Together, Fix A (Pass 4 path grounding) and Fix B
172
1560
  (orchestrator/sub-skill exception + §6 guidance) close the last
173
- two classes of dogfood-observed content-validator errors. The
1561
+ two classes of regression-observed content-validator errors. The
174
1562
  remaining validator surface continues to enforce the strict
175
1563
  invariants — fabricated paths, missing skill files, unrelated-
176
1564
  parent drift, §9 re-declaration, T1 heading drift, etc. — without
177
1565
  relaxation.
178
1566
 
179
- Re-running `init` on `frontend-react-B` with the Fix A + Fix B
1567
+ Re-running `init` on `a Vite frontend test project` with the Fix A + Fix B
180
1568
  build produced `0 MANIFEST_DRIFT` (Fix B suppressed all 8
181
1569
  sub-skill drift rows) but left 1 residual `STALE_PATH` in
182
- `claudeos-core/standard/50.verification/02.testing-strategy.md`
1570
+ `claudeos-core/standard/80.verification/02.testing-strategy.md`
183
1571
  referencing `src/__mocks__/handlers.ts`. Analysis showed a
184
1572
  library-convention hallucination class that the original three
185
1573
  anti-patterns did not cover: testing documents reach for MSW /
@@ -195,30 +1583,29 @@ trigger document types, and the positive pattern: when
195
1583
  describe testing/styling/state guidance in abstract terms
196
1584
  (directory scope or role-based) without naming a specific path.
197
1585
 
198
- Final validation pass on both dogfood projects with the complete
1586
+ Final validation pass on both regression fixture projects with the complete
199
1587
  v2.3.0 build:
200
1588
 
201
- - `frontend-react-B` (Korean output, 14 frontend domains,
1589
+ - `a Vite frontend test project` (Korean output, 14 frontend domains,
202
1590
  dual-entry Vite + React 19, single-SPA admin layout,
203
1591
  scaffold-page-feature orchestrator with 8 sub-skills):
204
1592
  **12 errors → 0 errors** (100% improvement), full health
205
1593
  check green, 25/25 CLAUDE.md lint checks passed.
206
- - `backend-java-spring` (Korean output, 8 backend domains,
1594
+ - `a Spring backend test project` (Korean output, 8 backend domains,
207
1595
  Java 17 + Spring Boot + MyBatis, scaffold-crud-feature
208
1596
  orchestrator with 8 sub-skills, multi-dialect DB migration
209
1597
  in progress): **8 errors → 0 errors** (100% improvement),
210
1598
  full health check green, complete first-try run in 45m 29s
211
- including the resume-from-partial-marker code path hitting
212
- for the first time from a real-world partial Pass 3 run.
1599
+ including the resume-from-partial-marker code path exercised
1600
+ for the first time against a captured partial Pass 3 fixture.
213
1601
 
214
1602
  Both projects exercise distinct v2.3.0 code paths (Fix A + Fix B,
215
1603
  single-SPA rule, Pass 3 resume, library-convention anti-pattern,
216
1604
  orchestrator/sub-skill exception), and both settled at 0 errors
217
1605
  without any manual file edits to the generated output. This is
218
1606
  the first release where the full end-to-end pipeline produces a
219
- clean `content-validator [10/10]` report on real-world sibling
220
- Korean projects — the core criterion for v2.3.0 being
221
- publish-ready.
1607
+ clean `content-validator [10/10]` report against the regression
1608
+ fixture set — the core criterion for v2.3.0 being publish-ready.
222
1609
 
223
1610
  ### Added
224
1611
 
@@ -271,11 +1658,10 @@ publish-ready.
271
1658
  The validator enforces this via `checkCanonicalHeadings` (IDs `T1-1`
272
1659
  through `T1-8`), and the scaffold documents it as a mandatory format
273
1660
  rule reinforced by Pass 3 POST-GEN CHECK step 4b. This closes a
274
- multi-repo discoverability gap discovered during `frontend-react-B`
275
- dogfooding: sibling projects generated §7 as `"DO NOT Read (직접 읽지
276
- 말아야 할 파일)"` and `"읽지 말 것 (Files Not to Be Read Directly)"`
1661
+ multi-repo discoverability gap discovered during `a Vite frontend test project`
1662
+ regression testing: two test projects generated §7 as `"DO NOT Read (<localized gloss A>)"` and `"<localized gloss B> (Files Not to Be Read Directly)"`
277
1663
  respectively — both "equivalent in meaning" but breaking
278
- `grep "## 7. DO NOT Read"` across the organization's repos.
1664
+ `grep "## 7. DO NOT Read"` across multiple repos.
279
1665
 
280
1666
  - **`content-validator [10/10]` — path-claim + MANIFEST drift.**
281
1667
  A new check appended to the existing 9-stage validator in
@@ -314,11 +1700,11 @@ publish-ready.
314
1700
  - **`pass-prompts/templates/common/pass3-footer.md` — Path fact
315
1701
  grounding (MANDATORY).** Two new CRITICAL blocks added:
316
1702
  - The parent-directory prefix anti-pattern (the exact
317
- `featureRoutePath.ts` case from frontend-react-B dogfooding) is
1703
+ `<feature>RoutePath.ts` case from the Vite frontend test project's regression run) is
318
1704
  documented with ✅/❌ examples and explanation of *why* the LLM
319
1705
  mis-infers (TypeScript identifier name vs filename are
320
- independent — the constant `FEATURE_ROUTE_PATH` does not imply
321
- filename `featureRoutePath.ts`).
1706
+ independent — the constant `<FEATURE>_ROUTE_PATH` does not imply
1707
+ filename `<feature>RoutePath.ts`).
322
1708
  - The MANIFEST ↔ CLAUDE.md §6 symmetry rule is stated explicitly,
323
1709
  with post-generation enforcement noted (`content-validator [10/10]
324
1710
  → MANIFEST_DRIFT`).
@@ -328,7 +1714,7 @@ publish-ready.
328
1714
  (same subapp implemented for two platforms, e.g., `src/pc/admin/`
329
1715
  + `src/mobile/admin/` → `pc-admin`, `mobile-admin`). When applied
330
1716
  to a single-SPA project (only one platform keyword matches, as in
331
- `frontend-react-B`'s `src/admin/...`), the scanner misinterpreted the
1717
+ `a Vite frontend test project`'s `src/admin/...`), the scanner misinterpreted the
332
1718
  SPA's architectural layers (`api`, `context`, `dto`, `routers`) as
333
1719
  subapps and emitted them as pseudo-domains — both cluttering the
334
1720
  domain plan and priming Pass 3 toward filename hallucinations with
@@ -360,8 +1746,8 @@ publish-ready.
360
1746
  is invoked and its existing `groupsCompleted` tracking resumes
361
1747
  from the next unstarted stage. Only markers with `completedAt`
362
1748
  set are skipped.
363
- - This repairs the dogfood case where Pass 3d-aux timed out
364
- mid-stream on `backend-java-spring`: on the next `init`, stages 3a-3c
1749
+ - This repairs the regression case where Pass 3d-aux timed out
1750
+ mid-stream on `a Spring backend test project`: on the next `init`, stages 3a-3c
365
1751
  were correctly preserved but 3d-aux was silently skipped,
366
1752
  leaving `claudeos-core/database/` and `claudeos-core/mcp-guide/`
367
1753
  empty and the marker stuck in partial shape.
@@ -399,7 +1785,7 @@ publish-ready.
399
1785
  exported for test compatibility but is now unreferenced by
400
1786
  production code.
401
1787
  This fix closes the final regression surfaced by end-to-end
402
- dogfooding on `frontend-react-B`: the validator was correctly
1788
+ regression testing on `a Vite frontend test project`: the validator was correctly
403
1789
  reporting an `S1` (9 sections) and four `M-*`/`F2-*` errors
404
1790
  against a `CLAUDE.md` whose second memory table had been
405
1791
  appended by Pass 4, not written by Pass 3. The fix keeps the
@@ -430,9 +1816,9 @@ publish-ready.
430
1816
  section states the rule first — every `src/...` path written in a
431
1817
  rule or standard file must appear verbatim in `pass3a-facts.md` or
432
1818
  `pass2-merged.json` — then documents the three flagship
433
- hallucination anti-patterns observed in `frontend-react-B`
434
- dogfooding: Vite-convention assumption (`src/feature/main.tsx`),
435
- parent-directory prefix (`src/feature/routers/featureRoutePath.ts`),
1819
+ hallucination anti-patterns observed in `a Vite frontend test project`
1820
+ regression testing: Vite-convention assumption (`src/feature/main.tsx`),
1821
+ parent-directory prefix (`src/feature/routers/<feature>RoutePath.ts`),
436
1822
  and plausible-but-unverified utility (`src/components/utils/classNameMaker.ts`).
437
1823
  Each anti-pattern is accompanied by the concrete mechanism that
438
1824
  caused it ("invented based on Vite's stock convention";
@@ -474,10 +1860,10 @@ publish-ready.
474
1860
  — hallucinated filenames and silent staleness — and cites the
475
1861
  `content-validator` exception so the prompt-side and detector-
476
1862
  side are consistent.
477
- This fix closes the final class of dogfood-observed errors on
478
- `backend-java-spring` (8 MANIFEST_DRIFT rows, all for
1863
+ This fix closes the final class of field-test-observed errors on
1864
+ `a Spring backend test project` (8 MANIFEST_DRIFT rows, all for
479
1865
  `scaffold-crud-feature/0N.*.md` sub-skills) and the equivalent
480
- shape on `frontend-react-B` (8 rows under
1866
+ shape on `a Vite frontend test project` (8 rows under
481
1867
  `scaffold-page-feature/0N.*.md`). The structural
482
1868
  `CLAUDE.md §6 = entry, MANIFEST = registry` split also
483
1869
  eliminates the recurring regeneration churn where adding or
@@ -486,7 +1872,7 @@ publish-ready.
486
1872
 
487
1873
  - **`tests/content-validator.test.js` — 5 new orchestrator/sub-skill
488
1874
  exception tests.** Coverage: (1) orchestrator mentioned +
489
- sub-skills registered → 0 drift (backend-java-spring replica);
1875
+ sub-skills registered → 0 drift (a Spring backend test project replica);
490
1876
  (2) orchestrator mentioned + one sub-skill file deleted → still
491
1877
  emits 1 `STALE_SKILL_ENTRY` (integrity not suppressed);
492
1878
  (3) orchestrator NOT mentioned → all 5 registered skills drift
@@ -525,7 +1911,7 @@ publish-ready.
525
1911
  fenced examples, placeholders, and existing paths do not trigger).
526
1912
  - MANIFEST drift scenarios (stale entry, drift, referenced skill,
527
1913
  self-reference exclusion, absent MANIFEST).
528
- - Full frontend-react-B simulation: 2 STALE_PATH + 2 STALE_SKILL_ENTRY
1914
+ - Full a Vite frontend test project simulation: 2 STALE_PATH + 2 STALE_SKILL_ENTRY
529
1915
  + 3 MANIFEST_DRIFT, asserted with exact counts to prevent silent
530
1916
  regression as the validator evolves.
531
1917
 
@@ -546,13 +1932,13 @@ publish-ready.
546
1932
  following the T1 canonical-heading format `## N. <English canonical>
547
1933
  (<translation>)`): `valid-en.md`, `valid-ja.md`, `valid-zh-CN.md`,
548
1934
  `valid-es.md`, `valid-vi.md`, `valid-hi.md`, `valid-ru.md`,
549
- `valid-fr.md`, `valid-de.md`, plus `frontend-react-A-fixed.md`
550
- (Korean, real dogfooding case with §9 removed and headings
1935
+ `valid-fr.md`, `valid-de.md`, plus `observed-ko-fixed.md`
1936
+ (Korean, captured regression fixture with §9 removed and headings
551
1937
  retrofitted to T1 format). Each passes the same 25 structural
552
1938
  checks — empirical proof of language invariance across CJK,
553
1939
  Cyrillic, Devanagari, Latin, and Vietnamese scripts.
554
1940
  - Bad fixtures (same valid structure + §9 memory re-declaration
555
- appended): `frontend-react-A-bad.md` (Korean, real), `bad-ja.md`,
1941
+ appended): `observed-ko-bad.md`, `bad-ja.md`,
556
1942
  `bad-zh-CN.md`, `bad-ru.md`, `bad-hi.md`, `bad-es.md`. All six
557
1943
  produce a **byte-for-byte identical 9-error signature**
558
1944
  (1 S1 + 4 M-* + 4 F2-*), confirming the validator detects the
@@ -628,8 +2014,8 @@ how often that net is needed.
628
2014
  label blocklist. The new framing states the RULE first (no `##` may
629
2015
  have a title whose semantic category is "rules", "memory", "L4",
630
2016
  "guardrails", or any rephrasing), then gives concrete **translated
631
- examples in Korean, Japanese, and Chinese** (`메모리 (L4)`,
632
- `メモリ (L4)`, `记忆层 (L4)`, and analogues for Common Rules). The
2017
+ examples in Korean, Japanese, and Chinese** (the localized form of
2018
+ `Memory (L4)` in each script, plus analogues for Common Rules). The
633
2019
  goal is to make the LLM's translation decision explicit: it must
634
2020
  apply the forbidden rule to its translated heading, not just the
635
2021
  English original. A DECISION RULE block at the end gives a 3-step
@@ -672,11 +2058,12 @@ MANIFEST-drift exception.)
672
2058
 
673
2059
  The §9 re-declaration anti-pattern was the flagship problem v2.2.0 aimed
674
2060
  to solve, and the scaffold + prompt-level blocklist reduced incidence
675
- substantially. Dogfooding on a real Korean-output project produced a
676
- `CLAUDE.md` with `## 9. 메모리 (L4)` anyway — the LLM successfully matched
677
- `## 8. 공통 규칙 메모리 (L4)` as its Section 8, then created a §9
678
- section whose title (`메모리 (L4)`) was not semantically recognized as
679
- equivalent to the blocklisted English `"Memory Layer (L4)"`.
2061
+ substantially. Regression testing on a Korean-output fixture produced a
2062
+ `CLAUDE.md` with a `## 9. ` heading translating to "Memory (L4)" anyway — the LLM
2063
+ successfully matched the localized form of `## 8. Common Rules & Memory (L4)`
2064
+ as its Section 8, then created a §9 section whose translated title was not
2065
+ semantically recognized as equivalent to the blocklisted English
2066
+ `"Memory Layer (L4)"`.
680
2067
 
681
2068
  Extending the fix by maintaining per-language blocklists would create
682
2069
  unbounded maintenance surface: 10 supported languages × 6-8 forbidden
@@ -854,7 +2241,7 @@ projects or runs.
854
2241
  Adds a mandatory post-generation check (count `^## ` headings; must
855
2242
  equal 8; merge surplus into the correct section or move to `rules/*`
856
2243
  / `standard/*`). The expanded blocklist closes a rename loophole
857
- discovered during dogfooding on a Vite + React frontend project
2244
+ discovered during regression testing on a Vite + React frontend project
858
2245
  where the LLM appended a §9 whose title combined "Documentation
859
2246
  Writing + AI Common Rules + Memory Layer (L4)" to collect
860
2247
  rule-related content.
@@ -865,7 +2252,7 @@ projects or runs.
865
2252
  home in rules/standard/skills/guide.
866
2253
 
867
2254
  - **`pass-prompts/templates/common/claude-md-scaffold.md`** (in addition to
868
- the new-file Add above) was tightened after initial dogfooding:
2255
+ the new-file Add above) was tightened after initial regression testing:
869
2256
  - Hard constraints section now leads with **"EXACTLY 8 SECTIONS. No more,
870
2257
  no less."** plus a recovery procedure for surplus sections.
871
2258
  - Section 6 Rules sub-section explicitly notes that the
@@ -916,13 +2303,13 @@ projects or runs.
916
2303
 
917
2304
  ### Why this matters
918
2305
 
919
- When claudeos-core was applied to three sibling projects in the same
920
- organization (one Spring Boot backend, two Vite + React frontends), the
2306
+ When claudeos-core was exercised against three test projects (one Spring Boot backend,
2307
+ two Vite + React frontends) in the regression suite, the
921
2308
  generated files were content-correct — standards, rules, and skills
922
2309
  accurately captured each project's patterns — but the `CLAUDE.md` files
923
2310
  had different section counts (8, 8, 9), different section names, and
924
2311
  different section orders. Claude Code reads CLAUDE.md first on every
925
- session; inconsistent structure across repos made it harder for
2312
+ session; inconsistent structure across repos would make it harder for
926
2313
  developers (and Claude Code) to know where to look for a given piece of
927
2314
  information. v2.2.0 fixes the structure while leaving content
928
2315
  project-specific.
@@ -934,7 +2321,7 @@ content already in `.claude/rules/*` (auto-loaded) or `claudeos-core/
934
2321
  standard/*` (detailed patterns). Removing it eliminates a redundant
935
2322
  maintenance surface and reinforces the "one rule, one home" principle.
936
2323
 
937
- Dogfooding also uncovered a latent paths bug. The `40.infra/*` rules
2324
+ Regression testing also uncovered a latent paths bug. The `40.infra/*` rules
938
2325
  shared a single category-level `paths` frontmatter that only matched
939
2326
  config/infra file extensions (`.env`, `*.config.*`, `*.json`, `*.yml`,
940
2327
  `Dockerfile*`). This meant the logging-monitoring rule — whose guardrails
@@ -945,7 +2332,7 @@ trigger was mis-scoped. v2.2.0 now specifies per-file `paths` in the Pass
945
2332
  3 prompts and adds a `Rule paths Must Match Rule Content` CRITICAL block
946
2333
  to the footer so future rules cannot inherit the wrong scope by default.
947
2334
 
948
- A third dogfooding finding exposed a different layer of the same
2335
+ A third regression testing finding exposed a different layer of the same
949
2336
  philosophy violation. The stack detector parsed Spring Boot's
950
2337
  `application.yml` for `server.port`, but for Node/Vite projects it
951
2338
  simply used a hardcoded framework default (Vite → 5173) whenever no
@@ -961,7 +2348,7 @@ canonical source of runtime configuration — framework defaults are
961
2348
  last-resort only. This also captures host and API-target values that
962
2349
  previously never appeared in generated CLAUDE.md at all.
963
2350
 
964
- A fourth dogfooding iteration on a Spring Boot backend project
2351
+ A fourth regression testing iteration on a Spring Boot backend project
965
2352
  (regenerated with the interim v2.2.0 scaffold that only allowed a single
966
2353
  Section 8 titled "Memory (L4)") found the LLM producing a §9 titled
967
2354
  "Common Rules & Memory (L4)" — even with the expanded blocklist from
@@ -1007,7 +2394,7 @@ and were addressed in the same release cycle. **First**, the scaffold's
1007
2394
  "Section 6 Rules: Always include 60.memory/*" directive, added during
1008
2395
  Section 8 redesign, was not echoed in the 12 stack Pass 3 prompts'
1009
2396
  rule-category listings — so the LLM received conflicting signals
1010
- (scaffold says include, stack prompt doesn't mention it). Real dogfooding
2397
+ (scaffold says include, stack prompt doesn't mention it). Regression testing
1011
2398
  on the backend project confirmed the category was being omitted from
1012
2399
  the generated CLAUDE.md §6 Rules table. v2.2.0 fixes both sides: each stack
1013
2400
  Pass 3 prompt now explicitly lists `60.memory/*` as a forward-reference
@@ -1026,7 +2413,7 @@ preservation semantics (memory/ content kept, generated files replaced)
1026
2413
  explicit. **Third**, the new `.env.example` → CLAUDE.md pipeline created
1027
2414
  a theoretical pathway for accidentally committed secrets in `.env.example`
1028
2415
  to be amplified into the project's public-facing documentation. Although
1029
- `.env.example` is conventionally a placeholder file, real-world projects
2416
+ `.env.example` is conventionally a placeholder file, projects
1030
2417
  occasionally check in real values by mistake. v2.2.0 adds a
1031
2418
  sensitive-variable filter (`lib/env-parser.js`: `isSensitiveVarName`,
1032
2419
  `redactSensitiveVars`) that replaces values of variables matching
@@ -1073,7 +2460,7 @@ npx claudeos-core init --force
1073
2460
 
1074
2461
  If you want to preview changes first, regenerate into a scratch copy of
1075
2462
  the project, diff the resulting files against your current ones, and
1076
- then decide whether to `--force` on the real project. Key files to
2463
+ then decide whether to `--force` on your project. Key files to
1077
2464
  diff: `CLAUDE.md`, `.claude/rules/00.core/00.standard-reference.md`,
1078
2465
  `.claude/rules/40.infra/02.logging-monitoring-rules.md` (paths change
1079
2466
  is the most visible delta).
@@ -1096,7 +2483,7 @@ running `--force` so you can diff/merge any overwrites.
1096
2483
  rules auto-load (more accurately scoped); the rule content itself
1097
2484
  does not change. `stack.envInfo` is a new additive field — older
1098
2485
  project-analysis.json files without it still work.
1099
- - Discovered via dogfooding on three real production projects:
2486
+ - Discovered via regression testing on multiple test projects:
1100
2487
  - Structural drift (3 different CLAUDE.md layouts) prompted the scaffold.
1101
2488
  - A Vite + React frontend project produced a §9 surplus section under
1102
2489
  a renamed title that bypassed the initial forbidden-sections blocklist
@@ -1177,7 +2564,7 @@ Post-release regression fix for v2.1.0 master plan removal cleanup.
1177
2564
  is a one-line behavior change (`errors.push(...)` → `console.log(...)`)
1178
2565
  with a comment documenting the v2.1.0 context, and regression risk is
1179
2566
  covered by routine `health` runs rather than an integration test.
1180
- - Discovered via dogfooding on a real Vite 6 + React 19 project: 62
2567
+ - Discovered via regression testing on a Vite 6 + React 19 test project: 62
1181
2568
  generated files, all Pass 1–4 stages succeeded, but `health` failed
1182
2569
  at content-validator. No other cleanup gaps found.
1183
2570
 
@@ -1196,8 +2583,8 @@ Docs-only maintenance release. No runtime behavior or API changes.
1196
2583
  job is done once the release ships, and the same content is preserved
1197
2584
  in `CHANGELOG.md` for anyone who wants the historical detail.
1198
2585
 
1199
- - **README: dropped the `Real production case: 18-domain admin frontend
1200
- (2026-04-20)` subsection** under _Auto-scaling by Project Size_ across
2586
+ - **README: dropped the 18-domain admin-frontend subsection
2587
+ (2026-04-20 entry)** under _Auto-scaling by Project Size_ across
1201
2588
  all 10 language READMEs. The per-stage breakdown table (9 rows) and its
1202
2589
  surrounding prose are removed. The trailing empirical reference in the
1203
2590
  FAQ "What is Pass 3 split mode" answer (the `Empirically verified up
@@ -1465,7 +2852,7 @@ project size.
1465
2852
  - **New shared library modules** — Single sources of truth for Pass 3 output expectations, preventing drift between enforcement and validation:
1466
2853
  - `lib/expected-guides.js` — 9 guide file paths. Imported by `init.js` Guard 3 H2 and `content-validator/index.js` `[5/9]` (no more hardcoded duplicates).
1467
2854
  - `lib/expected-outputs.js` — 3 additional Pass 3 outputs (standard sentinel, `skills/`, `plan/`) with `findMissingOutputs(projectRoot)` + `hasNonEmptyMdRecursive(dir)` helpers (BOM-aware). Imported by `init.js` Guard 3 H1.
1468
- - **Async claude execution + progress ticker** — `cli-utils.js` adds `runClaudePromptAsync` (spawn-based, non-blocking; lets a `setInterval` ticker run concurrently with the Claude subprocess) and `runClaudeCapture` (execSync wrapper that captures stdout, used by the translation engine in `memory-scaffold.js`). `init.js` adds `makePassTicker` with three display modes — elapsed-only, file-delta, and fixed-target (`N/M files (P%)`) — driving the per-pass `⏳`/`📝` progress line in TTY (`\r`-rewritten) and CI/piped (periodic newlines) environments.
2855
+ - **Async claude execution + progress ticker** — `cli-utils.js` adds `runClaudePromptAsync` (spawn-based, non-blocking; lets a `setInterval` ticker run concurrently with the Claude subprocess) and `runClaudeCapture` (execSync wrapper that captures stdout, used by the translation engine in `memory-scaffold.js`). `init.js` adds `makePassTicker` with three display modes — elapsed-only, file-delta, and fixed-target (`N/M files (P%)`) — driving the per-pass progress line in TTY (`\r`-rewritten) and CI/piped (periodic newlines) environments.
1469
2856
  - **`--force` and "fresh" resume cleanup** — Now also wipes `claudeos-core/generated/.staged-rules/` (leftover from a prior crashed Pass 3/4 run) and `.claude/rules/` (so Guard 2's zero-rules detection can't false-negative on stale rules from a previous run); under `"fresh"` mode the `pass3-complete.json` and `pass4-memory.json` markers are also unlinked so both passes re-execute. Manual edits to `.claude/rules/` are lost — acceptable under the explicit `--force`/`fresh` choice.
1470
2857
  - **190+ new tests** (296 → 489) — New/expanded suites: `memory-scaffold.test.js`, `memory-command.test.js`, `pass4-prompt.test.js`, `pass3-marker.test.js`, `pass3-guards.test.js` (Guards 1/2 + Guard 3 H1/H2 with BOM coverage), `pass2-validation.test.js` (H3 structural check), `pass4-marker-validation.test.js` (M1 `isValidPass4Marker` + `dropStalePass4Marker` regression guards), `translation-skip-env.test.js` (M2 env guard + M3 CI workflow presence), `staged-rules.test.js`, `lang-aware-fallback.test.js` (sets `CLAUDEOS_SKIP_TRANSLATION=1` at module top to make translation-throw assertions deterministic), `placeholder-substitution.test.js`, plus expansions to existing suites.
1471
2858
  - **Progress bar with ETA** — Pass 1/2/3/4 execution shows a progress bar with percentage, elapsed time, and ETA based on average step duration (carried over and extended from v1.7.0; Pass 4 added).