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.
- package/CHANGELOG.md +1460 -73
- package/CODE_OF_CONDUCT.md +15 -0
- package/README.de.md +321 -883
- package/README.es.md +322 -883
- package/README.fr.md +322 -883
- package/README.hi.md +322 -883
- package/README.ja.md +322 -883
- package/README.ko.md +322 -882
- package/README.md +321 -883
- package/README.ru.md +322 -885
- package/README.vi.md +322 -883
- package/README.zh-CN.md +321 -881
- package/SECURITY.md +51 -0
- package/bin/commands/init.js +570 -264
- package/content-validator/index.js +185 -12
- package/health-checker/index.js +44 -10
- package/package.json +92 -90
- package/pass-json-validator/index.js +58 -7
- package/pass-prompts/templates/angular/pass3.md +15 -14
- package/pass-prompts/templates/common/claude-md-scaffold.md +203 -20
- package/pass-prompts/templates/common/pass3-footer.md +297 -56
- package/pass-prompts/templates/common/pass3a-facts.md +48 -3
- package/pass-prompts/templates/common/pass4.md +78 -40
- package/pass-prompts/templates/java-spring/pass1.md +54 -0
- package/pass-prompts/templates/java-spring/pass3.md +20 -19
- package/pass-prompts/templates/kotlin-spring/pass1.md +45 -0
- package/pass-prompts/templates/kotlin-spring/pass3.md +24 -23
- package/pass-prompts/templates/node-express/pass3.md +18 -17
- package/pass-prompts/templates/node-fastify/pass3.md +11 -10
- package/pass-prompts/templates/node-nestjs/pass3.md +11 -10
- package/pass-prompts/templates/node-nextjs/pass3.md +18 -17
- package/pass-prompts/templates/node-vite/pass3.md +11 -10
- package/pass-prompts/templates/python-django/pass3.md +18 -17
- package/pass-prompts/templates/python-fastapi/pass3.md +18 -17
- package/pass-prompts/templates/python-flask/pass3.md +9 -8
- package/pass-prompts/templates/vue-nuxt/pass3.md +9 -8
- package/plan-installer/domain-grouper.js +45 -5
- package/plan-installer/index.js +34 -1
- package/plan-installer/pass3-context-builder.js +14 -0
- package/plan-installer/scanners/scan-frontend.js +2 -1
- package/plan-installer/scanners/scan-java.js +98 -2
- package/plan-installer/source-paths.js +242 -0
- 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
|
-
|
|
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 (
|
|
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
|
-
|
|
31
|
-
same
|
|
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 (
|
|
35
|
-
`"
|
|
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
|
|
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
|
|
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
|
-
`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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/
|
|
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
|
|
1586
|
+
Final validation pass on both regression fixture projects with the complete
|
|
199
1587
|
v2.3.0 build:
|
|
200
1588
|
|
|
201
|
-
- `frontend
|
|
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
|
|
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
|
|
212
|
-
for the first time
|
|
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
|
|
220
|
-
|
|
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
|
|
275
|
-
|
|
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
|
|
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
|
-
|
|
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 `
|
|
321
|
-
filename
|
|
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
|
|
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
|
|
364
|
-
mid-stream on `backend
|
|
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
|
-
|
|
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
|
|
434
|
-
|
|
435
|
-
parent-directory prefix (`src/feature/routers
|
|
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
|
|
478
|
-
`backend
|
|
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
|
|
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
|
|
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
|
|
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 `
|
|
550
|
-
(Korean,
|
|
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): `
|
|
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** (
|
|
632
|
-
|
|
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.
|
|
676
|
-
`CLAUDE.md` with `## 9.
|
|
677
|
-
`## 8.
|
|
678
|
-
section whose title
|
|
679
|
-
equivalent to the blocklisted English
|
|
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
|
|
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
|
|
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
|
|
920
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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).
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1200
|
-
(2026-04-20)
|
|
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
|
|
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).
|