claudeos-core 2.4.4 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +125 -0
- package/README.de.md +12 -10
- package/README.es.md +12 -10
- package/README.fr.md +12 -10
- package/README.hi.md +12 -10
- package/README.ja.md +12 -10
- package/README.ko.md +12 -10
- package/README.md +12 -10
- package/README.ru.md +12 -10
- package/README.vi.md +12 -10
- package/README.zh-CN.md +12 -10
- package/bin/commands/init.js +121 -24
- package/bin/commands/lint.js +2 -0
- package/bin/commands/memory.js +10 -3
- package/content-validator/index.js +82 -13
- package/lib/env-parser.js +98 -12
- package/lib/memory-scaffold.js +35 -16
- package/manifest-generator/index.js +15 -4
- package/package.json +92 -92
- package/pass-json-validator/index.js +1 -1
- package/pass-prompts/templates/angular/pass3.md +2 -1
- package/pass-prompts/templates/common/claude-md-scaffold.md +1 -1
- package/pass-prompts/templates/common/pass3a-facts.md +11 -9
- package/pass-prompts/templates/common/pass4.md +3 -3
- package/pass-prompts/templates/java-spring/pass1.md +10 -2
- package/pass-prompts/templates/java-spring/pass3.md +5 -4
- package/pass-prompts/templates/kotlin-spring/pass3.md +2 -2
- package/pass-prompts/templates/node-express/pass3.md +1 -1
- package/pass-prompts/templates/node-fastify/pass3.md +1 -0
- package/pass-prompts/templates/node-nestjs/pass3.md +1 -0
- package/pass-prompts/templates/node-nextjs/pass3.md +1 -1
- package/pass-prompts/templates/node-vite/pass3.md +1 -0
- package/pass-prompts/templates/python-django/pass3.md +1 -1
- package/pass-prompts/templates/python-fastapi/pass3.md +1 -1
- package/pass-prompts/templates/python-flask/pass3.md +1 -0
- package/pass-prompts/templates/vue-nuxt/pass3.md +1 -0
- package/plan-installer/domain-grouper.js +4 -1
- package/plan-installer/index.js +26 -7
- package/plan-installer/jvm-detect.js +562 -0
- package/plan-installer/pass3-context-builder.js +10 -0
- package/plan-installer/prompt-generator.js +18 -2
- package/plan-installer/scanners/scan-frontend.js +67 -6
- package/plan-installer/scanners/scan-java.js +214 -15
- package/plan-installer/scanners/scan-kotlin.js +68 -3
- package/plan-installer/scanners/scan-node.js +115 -0
- package/plan-installer/scanners/scan-python.js +56 -0
- package/plan-installer/source-paths.js +61 -0
- package/plan-installer/stack-detector.js +726 -51
- package/plan-installer/structure-scanner.js +15 -4
package/README.vi.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/claudeos-core)
|
|
4
4
|
[](https://github.com/claudeos-core/claudeos-core/actions/workflows/test.yml)
|
|
5
|
-
[](https://github.com/claudeos-core/claudeos-core/actions/workflows/test.yml)
|
|
6
6
|
[](https://nodejs.org/)
|
|
7
7
|
[](LICENSE)
|
|
8
8
|
[](https://www.npmjs.com/package/claudeos-core)
|
|
@@ -25,7 +25,7 @@ Mỗi khi mở phiên mới, Claude Code lại rơi về mặc định của fra
|
|
|
25
25
|
|
|
26
26
|
**ClaudeOS-Core sinh lại toàn bộ tài liệu này một cách nhất quán, đọc thẳng từ source code thật.** Đầu tiên, một Node.js scanner duyệt dự án để chốt stack, ORM, cấu trúc package và đường dẫn file. Tiếp đó pipeline 4-pass của Claude viết toàn bộ tài liệu: `CLAUDE.md`, `.claude/rules/` auto-load, standards và skills. Mọi pass đều bị khoá trong một allowlist đường dẫn tường minh, LLM không thoát ra được. Cuối cùng, năm validator soi lại kết quả trước khi xuất.
|
|
27
27
|
|
|
28
|
-
Nhờ vậy, cùng input thì luôn ra cùng
|
|
28
|
+
Nhờ vậy, cùng input thì luôn ra cùng một `CLAUDE.md` với cấu trúc 8 section cố định, được kiểm bởi cùng 25 structural check ở cả 10 ngôn ngữ, và mọi đường dẫn source được trích dẫn đều được đối chiếu với đĩa. (Xem chi tiết ở phần [Điểm khác biệt](#điểm-khác-biệt) bên dưới.)
|
|
29
29
|
|
|
30
30
|
Với các dự án chạy lâu dài, công cụ còn seed thêm một [Memory Layer](#memory-layer-tùy-chọn-cho-dự-án-dài-hạn) riêng.
|
|
31
31
|
|
|
@@ -115,7 +115,7 @@ Chạy thử trên [`spring-boot-realworld-example-app`](https://github.com/goth
|
|
|
115
115
|
</details>
|
|
116
116
|
|
|
117
117
|
<details>
|
|
118
|
-
<summary><strong>Phần thực sự đi vào <code>CLAUDE.md</code> của bạn (trích đoạn thật — Section 1 + 2)</strong></summary>
|
|
118
|
+
<summary><strong>Phần thực sự đi vào <code>CLAUDE.md</code> của bạn (trích đoạn thật — Section 1 + 2; heading được hạ xuống <code>####</code> để hiển thị trong README, file thật dùng <code>## N.</code>)</strong></summary>
|
|
119
119
|
|
|
120
120
|
```markdown
|
|
121
121
|
# CLAUDE.md — spring-boot-realworld-example-app
|
|
@@ -148,7 +148,7 @@ an XML-driven MyBatis persistence layer and JWT-based authentication.
|
|
|
148
148
|
| Test Stack | JUnit Jupiter 5, Mockito, AssertJ, rest-assured, spring-mock-mvc |
|
|
149
149
|
```
|
|
150
150
|
|
|
151
|
-
|
|
151
|
+
Các dòng stack (Java 11, Spring Boot 2.6.3, Gradle, MyBatis, SQLite, port 8080) đến từ scanner deterministic. Những chi tiết nhỏ hơn — toạ độ dependency chính xác, tên file `dev.db`, tên migration `V1__create_tables.sql`, ghi chú "no JPA" — do Pass 1 đọc từ `build.gradle`, `application.properties` và cây source, với các sự thật của scanner làm ràng buộc, rồi được validator đối chiếu lại. Không giá trị nào lấy từ default của framework.
|
|
152
152
|
|
|
153
153
|
</details>
|
|
154
154
|
|
|
@@ -309,7 +309,7 @@ Các category cùng số prefix ở `rules/` và `standard/` cùng chỉ về m
|
|
|
309
309
|
| Bạn là... | Vấn đề được giải quyết |
|
|
310
310
|
|---|---|
|
|
311
311
|
| **Solo dev** đang khởi động dự án mới với Claude Code | Khỏi phải dạy lại quy ước cho Claude mỗi phiên. `CLAUDE.md` cùng 8 category trong `.claude/rules/` được sinh ra chỉ trong một lần chạy. |
|
|
312
|
-
| **Team lead** quản lý standards dùng chung giữa nhiều repo | `.claude/rules/` rất hay drift mỗi khi đổi tên package, đổi ORM hay đổi response wrapper. ClaudeOS-Core
|
|
312
|
+
| **Team lead** quản lý standards dùng chung giữa nhiều repo | `.claude/rules/` rất hay drift mỗi khi đổi tên package, đổi ORM hay đổi response wrapper. ClaudeOS-Core sinh lại theo một scaffold 8 section cố định: repo nào cũng cùng cấu trúc, cùng verdict của validator, nên diff chỉ cho thấy thay đổi về convention chứ không phải nhiễu layout. |
|
|
313
313
|
| **Đã quen Claude Code** nhưng chán cảnh phải sửa lại code mỗi lần | Sai response wrapper, sai cấu trúc package, viết JPA trong khi dự án dùng MyBatis, rải `try/catch` trong khi đã có middleware tập trung. Scanner trích quy ước thật của dự án, mỗi pass của Claude đều chạy trong allowlist tường minh. |
|
|
314
314
|
| **Onboard vào repo mới** (dự án có sẵn, vào team mới) | Chạy `init` xong là có ngay tấm bản đồ kiến trúc sống. Bảng stack trong CLAUDE.md, rules theo từng layer kèm ví dụ ✅/❌, decision log seed sẵn lý do đằng sau những lựa chọn lớn (JPA hay MyBatis, REST hay GraphQL...). Đọc 5 file vẫn nhanh hơn lội qua 5.000 file source. |
|
|
315
315
|
| **Làm việc bằng tiếng Hàn, Nhật, Trung và 7 ngôn ngữ khác** | Phần lớn rule generator cho Claude Code chỉ có tiếng Anh. ClaudeOS-Core viết toàn bộ ở **10 ngôn ngữ** (`en/ko/ja/zh-CN/es/vi/hi/ru/fr/de`) và áp **kiểm tra cấu trúc byte-identical**. Verdict của `claude-md-validator` không đổi dù chọn ngôn ngữ output nào. |
|
|
@@ -331,7 +331,7 @@ Cách này: Code đọc stack của bạn → Code đưa fact đã xác nh
|
|
|
331
331
|
|
|
332
332
|
Pipeline gồm **ba giai đoạn**, code có mặt ở cả hai phía của lời gọi LLM.
|
|
333
333
|
|
|
334
|
-
**1. Step A — Scanner (nhất quán, không gọi LLM).** Một Node.js scanner đi qua thư mục gốc, đọc `package.json`, `build.gradle`, `pom.xml`, `pyproject.toml`, parse các file `.env*` (đồng thời redact các biến nhạy cảm như `PASSWORD/SECRET/TOKEN/JWT_SECRET/...`), phân loại pattern kiến trúc (5 pattern A/B/C/D/E của Java; Kotlin CQRS hoặc multi-module; Next.js App Router so với Pages Router; FSD; components-pattern), tìm ra các domain, rồi dựng allowlist tường minh chứa mọi đường dẫn source thật. Output là `project-analysis.json`, đóng vai trò single source of truth cho mọi bước về sau.
|
|
334
|
+
**1. Step A — Scanner (nhất quán, không gọi LLM).** Một Node.js scanner đi qua thư mục gốc, đọc `package.json`, `build.gradle`, `build.gradle.kts`, `pom.xml`, `pyproject.toml`, parse các file `.env*` (đồng thời redact các biến nhạy cảm như `PASSWORD/SECRET/TOKEN/JWT_SECRET/...`), phân loại pattern kiến trúc (5 pattern A/B/C/D/E của Java; Kotlin CQRS hoặc multi-module; Next.js App Router so với Pages Router; FSD; components-pattern), tìm ra các domain, rồi dựng allowlist tường minh chứa mọi đường dẫn source thật. Output là `project-analysis.json`, đóng vai trò single source of truth cho mọi bước về sau.
|
|
335
335
|
|
|
336
336
|
**2. Step B — Pipeline Claude 4-pass (ràng buộc bởi fact của Step A).**
|
|
337
337
|
- **Pass 1** đọc các file đại diện theo từng domain group rồi trích ra khoảng 50–100 quy ước cho mỗi domain: response wrapper, thư viện logging, cách xử lý lỗi, quy ước naming, pattern test. Mỗi domain group chỉ chạy một lần (`max 4 domains, 40 files per group`) nên context không bao giờ tràn.
|
|
@@ -358,12 +358,14 @@ Cốt lõi gắn kết tất cả lại là một bất biến: **Claude chỉ
|
|
|
358
358
|
|
|
359
359
|
12 stack, tự động phát hiện từ chính các file dự án.
|
|
360
360
|
|
|
361
|
-
**Backend:** Java/Spring Boot · Kotlin/Spring Boot · Node/Express · Node/Fastify · Node/NestJS · Python/Django · Python/FastAPI · Python/Flask
|
|
361
|
+
**Backend:** Java/Spring Boot (và Spring Framework không dùng Boot, xem bên dưới) · Kotlin/Spring Boot · Node/Express · Node/Fastify · Node/NestJS · Python/Django · Python/FastAPI · Python/Flask
|
|
362
362
|
|
|
363
363
|
**Frontend:** Node/Next.js · Node/Vite · Angular · Vue/Nuxt
|
|
364
364
|
|
|
365
365
|
Dự án multi-stack (chẳng hạn Spring Boot backend cộng Next.js frontend) chạy được luôn mà không cần điều chỉnh.
|
|
366
366
|
|
|
367
|
+
**Java cũ là mục tiêu hạng nhất (v2.5.1).** Spring 1.x–6.x không dùng Boot được nhận diện: Gradle (thời `apply plugin:`, cách viết `group:/name:/version:` theo thứ tự bất kỳ, phân giải biến qua `gradle.properties` / `apply from:` / buildSrc, version catalog, root chỉ có `settings.gradle`), Maven (POM thời Maven 2, property `${spring.version}`, `spring-framework-bom`, root multi-module, các project anh em không có POM gốc), **Ant + Ivy**, **metadata của Eclipse / IntelliJ / NetBeans** (`.classpath` kể cả tham chiếu tới JAR không commit, mức compliance trong `.settings`, `.idea/misc.xml`, `nbproject`), `WebContent/WEB-INF/lib/**/*.jar`, `WEB-INF/web.xml`, version schema XSD của Spring và **eGovFrame** — kèm framework, version Spring Framework, mức Java, packaging `war`/`ear`, driver JDBC và ORM. Cây source lấy `src/` làm gốc cũng được quét bằng đúng các domain pattern như `src/main/java`. Mọi version báo ra đều đọc từ file build, tên JAR hoặc property định nghĩa trong chính project; không suy ra từ giá trị mặc định của framework. Project JVM hoàn toàn không có Spring (`java-library`, `application`, `war` chỉ dùng servlet) được báo là Java với `framework: null`, không bao giờ là Spring.
|
|
368
|
+
|
|
367
369
|
Quy tắc phát hiện và những gì mỗi scanner trích xuất được mô tả trong [docs/vi/stacks.md](docs/vi/stacks.md).
|
|
368
370
|
|
|
369
371
|
---
|
|
@@ -393,7 +395,7 @@ Phần lớn công cụ tài liệu cho Claude Code sinh ra từ một bản mô
|
|
|
393
395
|
|
|
394
396
|
Cách làm này dẫn tới ba điểm khác biệt cụ thể.
|
|
395
397
|
|
|
396
|
-
1. **Stack detection nhất quán.** Cùng dự án, cùng code thì luôn ra cùng
|
|
398
|
+
1. **Stack detection và cấu trúc nhất quán.** Cùng dự án, cùng code thì luôn ra cùng kết quả scan và cùng layout `CLAUDE.md` 8 section. Câu chữ bên trong từng section vẫn do LLM viết; thứ cố định là các sự thật nó được cung cấp và khuôn nó phải điền vào.
|
|
397
399
|
2. **Không tạo path bịa.** Prompt của Pass 3 liệt kê tường minh mọi source path được phép, vì thế Claude không thể trích ra path không tồn tại.
|
|
398
400
|
3. **Hiểu được multi-stack.** Trong cùng một lần chạy, domain backend và frontend dùng prompt phân tích riêng.
|
|
399
401
|
|
|
@@ -429,7 +431,7 @@ Ngoài pipeline scaffolding kể trên, ClaudeOS-Core còn seed thêm thư mục
|
|
|
429
431
|
|
|
430
432
|
Bốn file, tất cả đều do Pass 4 viết.
|
|
431
433
|
|
|
432
|
-
- `decision-log.md` — sổ append-only ghi "vì sao chọn X thay vì Y", seed từ `pass2-merged.json`
|
|
434
|
+
- `decision-log.md` — sổ append-only ghi "vì sao chọn X thay vì Y", seed từ `pass2-merged.json` (không bao giờ bị compact)
|
|
433
435
|
- `failure-patterns.md` — danh sách lỗi lặp lại kèm điểm frequency và importance
|
|
434
436
|
- `compaction.md` — cách memory được tự động compact theo thời gian
|
|
435
437
|
- `auto-rule-update.md` — các pattern nên được nâng thành rule mới
|
|
@@ -437,7 +439,7 @@ Bốn file, tất cả đều do Pass 4 viết.
|
|
|
437
439
|
Hai lệnh để duy trì layer này lâu dài.
|
|
438
440
|
|
|
439
441
|
```bash
|
|
440
|
-
# Compact log failure-patterns (chạy định
|
|
442
|
+
# Compact log failure-patterns (chạy định kỳ; decision-log.md được giữ nguyên)
|
|
441
443
|
npx claudeos-core memory compact
|
|
442
444
|
|
|
443
445
|
# Đề xuất rule mới từ các failure pattern xuất hiện thường xuyên
|
package/README.zh-CN.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/claudeos-core)
|
|
4
4
|
[](https://github.com/claudeos-core/claudeos-core/actions/workflows/test.yml)
|
|
5
|
-
[](https://github.com/claudeos-core/claudeos-core/actions/workflows/test.yml)
|
|
6
6
|
[](https://nodejs.org/)
|
|
7
7
|
[](LICENSE)
|
|
8
8
|
[](https://www.npmjs.com/package/claudeos-core)
|
|
@@ -25,7 +25,7 @@ npx claudeos-core init
|
|
|
25
25
|
|
|
26
26
|
**ClaudeOS-Core 的做法是直接读源码,稳定地把这些文件重新生成出来。** 第一步,Node.js scanner 扫一遍项目,把技术栈、ORM、包结构、文件路径都摸清楚。第二步,4-pass Claude 流水线产出完整的内容:`CLAUDE.md`、自动加载的 `.claude/rules/`、standards、skills,全部限定在一份明确的路径白名单里,LLM 越不出这个范围。最后,5 个 validator 在交付前再把结果过一遍。
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
这样一来,同样的输入永远得到同样的 8 段式 `CLAUDE.md` 结构。10 种语言里随便选一种,都要通过同样的 25 项结构检查,引用的每一条源码路径都会核对磁盘上是否真实存在。(细节看下面[有什么不同](#有什么不同)。)
|
|
29
29
|
|
|
30
30
|
长期维护的项目,还会顺带生成一份 [Memory Layer](#memory-layer-可选用于长期项目)。
|
|
31
31
|
|
|
@@ -115,7 +115,7 @@ npx claudeos-core init
|
|
|
115
115
|
</details>
|
|
116
116
|
|
|
117
117
|
<details>
|
|
118
|
-
<summary><strong>实际写进 <code>CLAUDE.md</code> 的内容 (真实片段 — Section 1 + 2)</strong></summary>
|
|
118
|
+
<summary><strong>实际写进 <code>CLAUDE.md</code> 的内容 (真实片段 — Section 1 + 2;为了 README 渲染把标题降为 <code>####</code>,真实文件用的是 <code>## N.</code>)</strong></summary>
|
|
119
119
|
|
|
120
120
|
```markdown
|
|
121
121
|
# CLAUDE.md — spring-boot-realworld-example-app
|
|
@@ -148,7 +148,7 @@ an XML-driven MyBatis persistence layer and JWT-based authentication.
|
|
|
148
148
|
| Test Stack | JUnit Jupiter 5, Mockito, AssertJ, rest-assured, spring-mock-mvc |
|
|
149
149
|
```
|
|
150
150
|
|
|
151
|
-
|
|
151
|
+
技术栈那几行 (Java 11、Spring Boot 2.6.3、Gradle、MyBatis、SQLite、端口 8080) 来自确定性的 scanner。更细的信息 —— 精确的依赖坐标、`dev.db` 文件名、`V1__create_tables.sql` 迁移名,乃至 "no JPA" 这种判断 —— 由 Pass 1 以 scanner 确认的事实为约束,从 `build.gradle`、`application.properties` 和源码目录里读出来,再由 validator 交叉核验。没有一处取自框架默认值。
|
|
152
152
|
|
|
153
153
|
</details>
|
|
154
154
|
|
|
@@ -309,7 +309,7 @@ your-project/
|
|
|
309
309
|
| 你是... | 它能帮你解决什么 |
|
|
310
310
|
|---|---|
|
|
311
311
|
| **打算用 Claude Code 启动新项目的独立开发者** | 不用每个会话都把规范从头讲一遍。`CLAUDE.md` 加上 8 大类的 `.claude/rules/`,一次跑完就有了。 |
|
|
312
|
-
| **要在多个仓库之间维护共享标准的技术负责人** | 重命名包、换 ORM、改响应包装器之后,`.claude/rules/` 总是慢半拍跟不上。ClaudeOS-Core
|
|
312
|
+
| **要在多个仓库之间维护共享标准的技术负责人** | 重命名包、换 ORM、改响应包装器之后,`.claude/rules/` 总是慢半拍跟不上。ClaudeOS-Core 按固定的 8 段式 scaffold 重新生成:每个仓库结构相同、validator 判定相同,diff 里看到的是约定的变化,而不是版式噪音。 |
|
|
313
313
|
| **已经在用 Claude Code,却被生成代码搞得头大的人** | 包装器写错、包结构不对、明明用 MyBatis 却生成 JPA、明明有统一中间件却到处 `try/catch`。Scanner 把项目真实的规范抽出来,每一次 Claude pass 都在明确的路径白名单内运行。 |
|
|
314
314
|
| **刚加入一个新仓库的人** (老项目、新团队) | 在仓库里跑一下 `init`,就能拿到一张活的架构地图:CLAUDE.md 里的栈表格、按层划分的规则配 ✅/❌ 示例,加上预先写好"为什么"的 decision log (JPA vs MyBatis、REST vs GraphQL 等)。读 5 份文件,胜过翻 5,000 个源文件。 |
|
|
315
315
|
| **要用中文 / 韩语 / 日语等英语以外的语言工作的人** | 大多数 Claude Code 规则生成器只支持英语。ClaudeOS-Core 把整套产物以 **10 种语言** (`en/ko/ja/zh-CN/es/vi/hi/ru/fr/de`) 输出,而结构校验在所有语言下完全一致 —— `claude-md-validator` 的判定不受输出语言影响。 |
|
|
@@ -331,7 +331,7 @@ ClaudeOS-Core 把常见的 Claude Code 流程倒过来跑:
|
|
|
331
331
|
|
|
332
332
|
整条流水线分**三个阶段**,LLM 调用的两端都有代码把关。
|
|
333
333
|
|
|
334
|
-
**1. Step A — Scanner (确定性,不调用 LLM)。** Node.js scanner 遍历项目根目录,读取 `package.json` / `build.gradle` / `pom.xml` / `pyproject.toml`,解析 `.env*` 文件 (`PASSWORD/SECRET/TOKEN/JWT_SECRET/...` 这类敏感变量自动脱敏)。接着归类架构模式 (Java 的 5 种 A/B/C/D/E、Kotlin 的 CQRS / 多模块、Next.js 的 App vs Pages Router、FSD、components-pattern),识别业务域,再为每一个真实存在的源文件路径生成一份明确的白名单。结果汇总到 `project-analysis.json`,后续所有步骤都以它为唯一事实来源。
|
|
334
|
+
**1. Step A — Scanner (确定性,不调用 LLM)。** Node.js scanner 遍历项目根目录,读取 `package.json` / `build.gradle` / `build.gradle.kts` / `pom.xml` / `pyproject.toml`,解析 `.env*` 文件 (`PASSWORD/SECRET/TOKEN/JWT_SECRET/...` 这类敏感变量自动脱敏)。接着归类架构模式 (Java 的 5 种 A/B/C/D/E、Kotlin 的 CQRS / 多模块、Next.js 的 App vs Pages Router、FSD、components-pattern),识别业务域,再为每一个真实存在的源文件路径生成一份明确的白名单。结果汇总到 `project-analysis.json`,后续所有步骤都以它为唯一事实来源。
|
|
335
335
|
|
|
336
336
|
**2. Step B — 4-Pass Claude 流水线 (受 Step A 的事实约束)。**
|
|
337
337
|
- **Pass 1** 按域分组读取代表性文件,从每个域里提炼大约 50–100 条规范:响应包装器、日志库、错误处理、命名约定、测试模式等。每个域分组只跑一次 (`max 4 domains, 40 files per group`),所以 context 不会爆。
|
|
@@ -358,12 +358,14 @@ severity 分三档 (`fail` / `warn` / `advisory`),这样用户能手动修掉的
|
|
|
358
358
|
|
|
359
359
|
12 种技术栈,从项目文件里自动识别:
|
|
360
360
|
|
|
361
|
-
**Backend:** Java/Spring Boot · Kotlin/Spring Boot · Node/Express · Node/Fastify · Node/NestJS · Python/Django · Python/FastAPI · Python/Flask
|
|
361
|
+
**Backend:** Java/Spring Boot (以及不用 Boot 的 Spring Framework,见下文) · Kotlin/Spring Boot · Node/Express · Node/Fastify · Node/NestJS · Python/Django · Python/FastAPI · Python/Flask
|
|
362
362
|
|
|
363
363
|
**Frontend:** Node/Next.js · Node/Vite · Angular · Vue/Nuxt
|
|
364
364
|
|
|
365
365
|
多栈项目 (例如 Spring Boot 后端 + Next.js 前端) 也能直接跑。
|
|
366
366
|
|
|
367
|
+
**遗留 Java 是一等目标 (v2.5.1)。** 可识别不使用 Boot 的 Spring 1.x–6.x:Gradle (`apply plugin:` 时代、顺序任意的 `group:/name:/version:` 写法、通过 `gradle.properties` / `apply from:` / buildSrc 解析变量、版本目录、只有 `settings.gradle` 的根目录)、Maven (Maven 2 时代的 POM、`${spring.version}` 属性、`spring-framework-bom`、多模块根、没有根 POM 的同级项目)、**Ant + Ivy**、**Eclipse / IntelliJ / NetBeans 元数据** (`.classpath`,包括指向未提交 JAR 的引用、`.settings` 中的 compliance 级别、`.idea/misc.xml`、`nbproject`)、`WebContent/WEB-INF/lib/**/*.jar`、`WEB-INF/web.xml`、Spring XSD 架构版本,以及 **eGovFrame** — 连同框架、Spring Framework 版本、Java 级别、`war`/`ear` 打包方式、JDBC 驱动和 ORM 一并给出。以 `src/` 为根的源码树也会用与 `src/main/java` 相同的 domain 模式扫描。报告的每个版本都来自构建文件、JAR 文件名或项目内定义的属性;不会依据框架默认值推测。完全不用 Spring 的 JVM 项目 (`java-library`、`application`、仅有 servlet 的 `war`) 会被报告为 `framework: null` 的 Java,绝不会当成 Spring。
|
|
368
|
+
|
|
367
369
|
具体的识别规则、每个 scanner 抽取了什么内容,详见 [docs/zh-CN/stacks.md](docs/zh-CN/stacks.md)。
|
|
368
370
|
|
|
369
371
|
---
|
|
@@ -393,7 +395,7 @@ npx claudeos-core health
|
|
|
393
395
|
|
|
394
396
|
具体落到三件事上:
|
|
395
397
|
|
|
396
|
-
1.
|
|
398
|
+
1. **栈识别和结构可复现。** 同样的项目 + 同样的代码 = 同样的扫描结果,同样的 8 段式 `CLAUDE.md` 版式。各段里的措辞仍由 LLM 撰写,固定下来的是交给它的事实和它必须填满的框架。
|
|
397
399
|
2. **不编造路径。** Pass 3 prompt 里写明了所有允许的源码路径,Claude 没法引用不存在的路径。
|
|
398
400
|
3. **多栈感知。** 同一次执行里,后端域和前端域走不同的分析 prompt。
|
|
399
401
|
|
|
@@ -429,7 +431,7 @@ npx claudeos-core health
|
|
|
429
431
|
|
|
430
432
|
一共 4 个文件,全部由 Pass 4 写入:
|
|
431
433
|
|
|
432
|
-
- `decision-log.md` —— append-only 的"为什么选 X 不选 Y",从 `pass2-merged.json` 取种子。
|
|
434
|
+
- `decision-log.md` —— append-only 的"为什么选 X 不选 Y",从 `pass2-merged.json` 取种子。(从不压缩)
|
|
433
435
|
- `failure-patterns.md` —— 反复出现的错误,带 frequency / importance 分数。
|
|
434
436
|
- `compaction.md` —— 记忆随时间自动压缩的方式。
|
|
435
437
|
- `auto-rule-update.md` —— 应当晋升为新规则的模式。
|
|
@@ -437,7 +439,7 @@ npx claudeos-core health
|
|
|
437
439
|
项目持续推进时,这两条命令负责维护这一层:
|
|
438
440
|
|
|
439
441
|
```bash
|
|
440
|
-
# 压缩 failure-patterns 日志 (
|
|
442
|
+
# 压缩 failure-patterns 日志 (定期执行;不会动 decision-log.md)
|
|
441
443
|
npx claudeos-core memory compact
|
|
442
444
|
|
|
443
445
|
# 把高频 failure pattern 提为候选规则
|
package/bin/commands/init.js
CHANGED
|
@@ -615,6 +615,42 @@ async function runPass3Split(ctx) {
|
|
|
615
615
|
},
|
|
616
616
|
});
|
|
617
617
|
|
|
618
|
+
// v2.5.0 — Deterministic allowlist injection ("LLMs guess, code confirms").
|
|
619
|
+
// The `## Allowed Source Paths` section is the single most important
|
|
620
|
+
// anti-hallucination input for 3b/3c/3d, yet until now it reached
|
|
621
|
+
// pass3a-facts.md only if the LLM faithfully hand-copied up to 500 paths
|
|
622
|
+
// out of pass3-context.json — and the 3a validator only checked file
|
|
623
|
+
// length. Node now writes that section itself from project-analysis.json
|
|
624
|
+
// (replacing any LLM-written version), so the allowlist is byte-exact
|
|
625
|
+
// every run, including resumed runs where 3a was skipped.
|
|
626
|
+
{
|
|
627
|
+
const { injectAllowedPathsSection } = require("../../plan-installer/source-paths");
|
|
628
|
+
let collected = null;
|
|
629
|
+
try {
|
|
630
|
+
const pa = JSON.parse(readFile(path.join(GENERATED_DIR, "project-analysis.json")));
|
|
631
|
+
collected = pa && pa.allowedSourcePaths;
|
|
632
|
+
} catch (_e) { /* tolerated — fallback line is written instead */ }
|
|
633
|
+
const before = readFileSafe(factsFile, "");
|
|
634
|
+
if (before.replace(/^\uFEFF/, "").trim().length === 0) {
|
|
635
|
+
// 3a is marked complete in the marker but the facts file is missing/empty
|
|
636
|
+
// (deleted by hand, or a crash between validate and marker write). Do not
|
|
637
|
+
// fabricate a facts file that contains only the allowlist — 3b would run
|
|
638
|
+
// against an empty fact sheet. Surface it and let the user resume/--force.
|
|
639
|
+
throw new InitError(
|
|
640
|
+
"pass3a-facts.md is missing or empty although Pass 3a is marked complete.\n" +
|
|
641
|
+
" Delete claudeos-core/generated/pass3-complete.json to re-run Pass 3a, or use `init --force`."
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
const after = injectAllowedPathsSection(before, collected);
|
|
645
|
+
if (after !== before) {
|
|
646
|
+
if (!writeFileSafe(factsFile, after)) {
|
|
647
|
+
throw new InitError("Failed to write the Allowed Source Paths section into pass3a-facts.md.");
|
|
648
|
+
}
|
|
649
|
+
const n = collected && Array.isArray(collected.paths) ? collected.paths.length : 0;
|
|
650
|
+
log(` 📎 Allowed Source Paths section written by orchestrator (${n} ${collected && collected.mode === "rollup" ? "dirs" : "paths"})`);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
618
654
|
// ═══ Stage 3b: CLAUDE.md + standard/ + .claude/rules/ ═══════════
|
|
619
655
|
//
|
|
620
656
|
// Single batch (domains ≤ 15): keep legacy "3b" marker (backward-compatible).
|
|
@@ -699,8 +735,16 @@ async function runPass3Split(ctx) {
|
|
|
699
735
|
}
|
|
700
736
|
}
|
|
701
737
|
// For every batch, confirm rules/ was generated (at least one staged-rules move must succeed).
|
|
738
|
+
// Count ONLY claudeos-core-managed categories (`NN.` prefix). Since v2.5.0
|
|
739
|
+
// --force/fresh preserve user-authored entries under .claude/rules/, so a
|
|
740
|
+
// plain recursive count could be >0 even when Pass 3 produced nothing.
|
|
702
741
|
const rulesDir = path.join(PROJECT_ROOT, ".claude/rules");
|
|
703
|
-
|
|
742
|
+
let rulesCount = 0;
|
|
743
|
+
try {
|
|
744
|
+
for (const e of fs.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
745
|
+
if (e.isDirectory() && /^\d{2}\./.test(e.name)) rulesCount += countFilesRecursive(path.join(rulesDir, e.name));
|
|
746
|
+
}
|
|
747
|
+
} catch (_e) { rulesCount = 0; }
|
|
704
748
|
if (rulesCount === 0) {
|
|
705
749
|
problems.push(".claude/rules/ has 0 files (staging-override may have been ignored)");
|
|
706
750
|
}
|
|
@@ -855,7 +899,7 @@ function checkPrerequisites() {
|
|
|
855
899
|
);
|
|
856
900
|
if (!hasProjectMarker) {
|
|
857
901
|
log(`\n ⚠️ Warning: ${PROJECT_ROOT} does not look like a project root.`);
|
|
858
|
-
log(" No .git, package.json, build.gradle,
|
|
902
|
+
log(" No .git, package.json, build.gradle(.kts), pom.xml, or pyproject.toml found.");
|
|
859
903
|
log(" Run this command from your project directory.\n");
|
|
860
904
|
}
|
|
861
905
|
|
|
@@ -910,6 +954,28 @@ async function resolveLanguage(parsedArgs) {
|
|
|
910
954
|
return lang;
|
|
911
955
|
}
|
|
912
956
|
|
|
957
|
+
// Remove only the rule categories claudeos-core generates (`NN.` prefixed:
|
|
958
|
+
// 00.core, 10.backend, …, 90.optional). Files/dirs the user authored under
|
|
959
|
+
// .claude/rules/ without that prefix are left untouched. Returns the number
|
|
960
|
+
// of entries removed.
|
|
961
|
+
function wipeManagedRuleCategories(rulesDir) {
|
|
962
|
+
if (!fileExists(rulesDir)) return 0;
|
|
963
|
+
let removed = 0;
|
|
964
|
+
let entries;
|
|
965
|
+
try { entries = fs.readdirSync(rulesDir, { withFileTypes: true }); }
|
|
966
|
+
catch (_e) { return 0; }
|
|
967
|
+
for (const e of entries) {
|
|
968
|
+
// Managed entries are category DIRECTORIES (00.core/, 10.backend/, …).
|
|
969
|
+
// claudeos-core never writes a `NN.`-prefixed FILE at the rules root, so
|
|
970
|
+
// such a file (`.claude/rules/01.team-style.md`) is user-authored — keep it.
|
|
971
|
+
// Guard 2 (zero-rules detection) counts with the same predicate.
|
|
972
|
+
if (!e.isDirectory() || !/^\d{2}\./.test(e.name)) continue;
|
|
973
|
+
fs.rmSync(path.join(rulesDir, e.name), { recursive: true, force: true });
|
|
974
|
+
removed++;
|
|
975
|
+
}
|
|
976
|
+
return removed;
|
|
977
|
+
}
|
|
978
|
+
|
|
913
979
|
// ─── Stage 3: Resume/Fresh selection ──────────────────────────────
|
|
914
980
|
// Returns { wasFreshClean: boolean } — the caller uses this to gate the
|
|
915
981
|
// v1.7.x migration backfill in dispatchPass3.
|
|
@@ -930,14 +996,15 @@ async function applyResumeMode(parsedArgs, lang) {
|
|
|
930
996
|
// (only .json/.md are unlinked above; directories aren't touched).
|
|
931
997
|
const stagedDir = path.join(GENERATED_DIR, ".staged-rules");
|
|
932
998
|
if (fileExists(stagedDir)) fs.rmSync(stagedDir, { recursive: true, force: true });
|
|
933
|
-
// Also wipe .claude/rules/
|
|
934
|
-
//
|
|
935
|
-
// Pass 3 run fails silently (e.g.
|
|
936
|
-
// Step [2] recreates the subdirs
|
|
937
|
-
//
|
|
938
|
-
// ("truly fresh start").
|
|
939
|
-
|
|
940
|
-
|
|
999
|
+
// Also wipe the claudeos-core-managed categories under .claude/rules/
|
|
1000
|
+
// so Guard 2 (zero-rules detection) can't false-negative on stale rules
|
|
1001
|
+
// from a previous run when the fresh Pass 3 run fails silently (e.g.
|
|
1002
|
+
// Claude ignores staging-override). Step [2] recreates the subdirs.
|
|
1003
|
+
// Manual edits to GENERATED rule files are lost — acceptable under
|
|
1004
|
+
// --force ("truly fresh start"). Anything the user placed under
|
|
1005
|
+
// .claude/rules/ that is NOT a claudeos-core category (no `NN.` prefix)
|
|
1006
|
+
// is preserved — it was never ours to delete.
|
|
1007
|
+
wipeManagedRuleCategories(path.join(PROJECT_ROOT, ".claude/rules"));
|
|
941
1008
|
wasFreshClean = true;
|
|
942
1009
|
log(" 🔄 Previous results deleted (--force)\n");
|
|
943
1010
|
return { wasFreshClean };
|
|
@@ -993,11 +1060,9 @@ async function applyResumeMode(parsedArgs, lang) {
|
|
|
993
1060
|
// Clean .staged-rules/ leftover from a prior crashed run (same reason as --force branch).
|
|
994
1061
|
const stagedDir = path.join(GENERATED_DIR, ".staged-rules");
|
|
995
1062
|
if (fileExists(stagedDir)) fs.rmSync(stagedDir, { recursive: true, force: true });
|
|
996
|
-
// Wipe
|
|
997
|
-
// the --force branch
|
|
998
|
-
|
|
999
|
-
const rulesDir = path.join(PROJECT_ROOT, ".claude/rules");
|
|
1000
|
-
if (fileExists(rulesDir)) fs.rmSync(rulesDir, { recursive: true, force: true });
|
|
1063
|
+
// Wipe managed rule categories for the same Guard 2 false-negative
|
|
1064
|
+
// reason as the --force branch (user-owned, non-`NN.` entries survive).
|
|
1065
|
+
wipeManagedRuleCategories(path.join(PROJECT_ROOT, ".claude/rules"));
|
|
1001
1066
|
wasFreshClean = true;
|
|
1002
1067
|
} else if (mode === "continue" && existingPass1.length === 0 && pass2Exists) {
|
|
1003
1068
|
// pass2 exists but no pass1 → pass2 is stale, force re-run
|
|
@@ -1078,7 +1143,7 @@ function ensureDirectories() {
|
|
|
1078
1143
|
}
|
|
1079
1144
|
|
|
1080
1145
|
// ─── Stage 5: Load & validate domain-groups.json ──────────────────
|
|
1081
|
-
function loadDomainGroups() {
|
|
1146
|
+
function loadDomainGroups({ wasFreshClean = false } = {}) {
|
|
1082
1147
|
let domainGroups;
|
|
1083
1148
|
try {
|
|
1084
1149
|
domainGroups = JSON.parse(
|
|
@@ -1089,6 +1154,34 @@ function loadDomainGroups() {
|
|
|
1089
1154
|
}
|
|
1090
1155
|
const totalGroups = domainGroups.totalGroups;
|
|
1091
1156
|
if (!totalGroups || typeof totalGroups !== "number" || totalGroups < 1) {
|
|
1157
|
+
if (totalGroups === 0) {
|
|
1158
|
+
let stackLine = "";
|
|
1159
|
+
try {
|
|
1160
|
+
const pa = JSON.parse(readFile(path.join(GENERATED_DIR, "project-analysis.json")));
|
|
1161
|
+
const st = pa.stack || {};
|
|
1162
|
+
stackLine = ` Detected stack: language=${st.language || "none"} framework=${st.framework || "none"} frontend=${st.frontend || "none"}\n`;
|
|
1163
|
+
} catch (_e) { /* best effort */ }
|
|
1164
|
+
throw new InitError(
|
|
1165
|
+
"No domains were found in this project, so there is nothing for Pass 1-3 to analyze.\n" +
|
|
1166
|
+
stackLine +
|
|
1167
|
+
" The scanner recognizes layouts such as:\n" +
|
|
1168
|
+
" Java/Kotlin src/main/java/<pkg>/<domain>/controller/*.java, <pkg>/controller/<domain>/*.java,\n" +
|
|
1169
|
+
" <pkg>/controller/*Controller.java, <pkg>/<domain>/adapter/in/web/*.java,\n" +
|
|
1170
|
+
" <pkg>/<feature>/*Controller.kt, <module>/src/main/java/... (Gradle/Maven multi-module)\n" +
|
|
1171
|
+
" Node src/<domain>/*.ts, src/modules/<domain>/, src/{controllers,routes,services}/<domain>.*\n" +
|
|
1172
|
+
" Python app/<domain>/, app/{routers,routes}/<domain>.py, Django apps with models.py\n" +
|
|
1173
|
+
" Frontend app/<route>/page.tsx (incl. (group)/), pages/<route>/, src/components/<name>/, src/features/<name>/\n" +
|
|
1174
|
+
" If your layout differs, open an issue with your directory tree, or check claudeos-core/generated/project-analysis.json\n" +
|
|
1175
|
+
" to see what was detected. " +
|
|
1176
|
+
(wasFreshClean
|
|
1177
|
+
// --force / "fresh" already wiped the managed rule categories and
|
|
1178
|
+
// generated/*.json|*.md before the scanner ran — say so, do not
|
|
1179
|
+
// claim nothing was touched.
|
|
1180
|
+
? "CLAUDE.md was not modified, but --force / fresh had already removed the previously generated\n" +
|
|
1181
|
+
" .claude/rules/NN.* categories and claudeos-core/generated/ pass files before this check. Restore them from version control if needed."
|
|
1182
|
+
: "Nothing was written to CLAUDE.md or .claude/rules/.")
|
|
1183
|
+
);
|
|
1184
|
+
}
|
|
1092
1185
|
throw new InitError(`domain-groups.json has invalid totalGroups: ${totalGroups}\n Re-run plan-installer or check claudeos-core/generated/`);
|
|
1093
1186
|
}
|
|
1094
1187
|
if (!domainGroups.groups || totalGroups !== domainGroups.groups.length) {
|
|
@@ -1564,7 +1657,8 @@ async function runPass4(opts) {
|
|
|
1564
1657
|
// Note: master plan files are no longer generated (previously this
|
|
1565
1658
|
// included "claudeos-core/plan/50.memory-master.md"). The marker schema
|
|
1566
1659
|
// still accepts an optional planFiles field for backward compatibility.
|
|
1567
|
-
|
|
1660
|
+
// v2.3.0+: CLAUDE.md is never touched by Pass 4 (Pass 3 §8 is authoritative).
|
|
1661
|
+
claudeMdAppended: false,
|
|
1568
1662
|
}, null, 2);
|
|
1569
1663
|
return writeFileSafe(pass4Marker, markerBody);
|
|
1570
1664
|
}
|
|
@@ -1625,7 +1719,7 @@ async function runPass4(opts) {
|
|
|
1625
1719
|
pass4Label = "Pass 4 already present";
|
|
1626
1720
|
} else if (!fileExists(pass4PromptFile)) {
|
|
1627
1721
|
log(" ⚠️ pass4-prompt.md not found — falling back to static scaffold");
|
|
1628
|
-
if (applyStaticFallback()) { log(" ✅ Memory/Rules/
|
|
1722
|
+
if (applyStaticFallback()) { log(" ✅ Memory/Rules/Standard scaffolded (static fallback)"); pass4Label = "Pass 4 (static fallback)"; }
|
|
1629
1723
|
else { log(" ❌ Static fallback failed to write marker"); pass4Label = "Pass 4 fallback failed"; }
|
|
1630
1724
|
} else {
|
|
1631
1725
|
let prompt4 = injectProjectRoot(readFile(pass4PromptFile));
|
|
@@ -1675,7 +1769,7 @@ async function runPass4(opts) {
|
|
|
1675
1769
|
|
|
1676
1770
|
if (!ok4 || !isValidPass4Marker(pass4Marker)) {
|
|
1677
1771
|
log(" ⚠️ Pass 4 did not produce a valid pass4-memory.json — using static fallback");
|
|
1678
|
-
if (applyStaticFallback()) { log(" ✅ Memory/Rules/
|
|
1772
|
+
if (applyStaticFallback()) { log(" ✅ Memory/Rules/Standard scaffolded (static fallback)"); pass4Label = `Pass 4 (static fallback, ${formatElapsed(elapsed4)})`; }
|
|
1679
1773
|
else { log(" ❌ Static fallback failed to write marker"); pass4Label = "Pass 4 fallback failed"; }
|
|
1680
1774
|
} else {
|
|
1681
1775
|
// Claude-driven Pass 4 succeeded. Ensure memory + rules + plans + standard + CLAUDE.md append exist
|
|
@@ -1744,8 +1838,11 @@ async function runPass4(opts) {
|
|
|
1744
1838
|
// ─── Stage 11: Run external verification tools ────────────────────
|
|
1745
1839
|
function runVerificationTools() {
|
|
1746
1840
|
const verifyTools = [
|
|
1747
|
-
|
|
1748
|
-
|
|
1841
|
+
// --sync-skills: only `init` is allowed to let manifest-generator patch
|
|
1842
|
+
// CLAUDE.md §6 / MANIFEST.md (post-generation reconciliation). `health`
|
|
1843
|
+
// runs the same tool without the flag and stays read-only.
|
|
1844
|
+
{ name: "manifest-generator", script: path.join(TOOLS_DIR, "manifest-generator/index.js"), args: " --sync-skills" },
|
|
1845
|
+
{ name: "health-checker", script: path.join(TOOLS_DIR, "health-checker/index.js"), args: "" },
|
|
1749
1846
|
];
|
|
1750
1847
|
|
|
1751
1848
|
for (const t of verifyTools) {
|
|
@@ -1753,7 +1850,7 @@ function runVerificationTools() {
|
|
|
1753
1850
|
log(` ⏭️ ${t.name} — not found, skipping`);
|
|
1754
1851
|
continue;
|
|
1755
1852
|
}
|
|
1756
|
-
const ok = run(`node "${t.script}"`, { ignoreError: true });
|
|
1853
|
+
const ok = run(`node "${t.script}"${t.args}`, { ignoreError: true });
|
|
1757
1854
|
if (!ok) {
|
|
1758
1855
|
log(` ⚠️ ${t.name} reported issues (non-fatal)`);
|
|
1759
1856
|
}
|
|
@@ -1914,7 +2011,7 @@ async function cmdInit(parsedArgs) {
|
|
|
1914
2011
|
|
|
1915
2012
|
// ─── [4] Pass 1: Deep analysis per domain group ────────────
|
|
1916
2013
|
header("[4] Pass 1 — Deep analysis per domain group...");
|
|
1917
|
-
const { domainGroups, totalGroups } = loadDomainGroups();
|
|
2014
|
+
const { domainGroups, totalGroups } = loadDomainGroups({ wasFreshClean });
|
|
1918
2015
|
const pass1Prompts = loadPass1Prompts();
|
|
1919
2016
|
|
|
1920
2017
|
// Progress tracking: Pass 1 (N groups) + Pass 2 + Pass 3 + Pass 4 = totalSteps
|
|
@@ -1976,4 +2073,4 @@ async function cmdInit(parsedArgs) {
|
|
|
1976
2073
|
printCompletionBanner({ lang, totalGroups, totalStart });
|
|
1977
2074
|
}
|
|
1978
2075
|
|
|
1979
|
-
module.exports = { cmdInit, InitError };
|
|
2076
|
+
module.exports = { cmdInit, InitError, wipeManagedRuleCategories };
|
package/bin/commands/lint.js
CHANGED
|
@@ -32,6 +32,8 @@ Checks:
|
|
|
32
32
|
- Section 6 has exactly 3 ### sub-sections
|
|
33
33
|
- Section 8 has exactly 2 ### sub-sections + 2 #### headings
|
|
34
34
|
- Each L4 memory file appears in exactly 1 table row (inside Section 8)
|
|
35
|
+
- Each \`## N.\` heading contains its English canonical token (T1)
|
|
36
|
+
- Each section has non-trivial body content (S2, warning)
|
|
35
37
|
|
|
36
38
|
Exit codes:
|
|
37
39
|
0 — structure valid
|
package/bin/commands/memory.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* ClaudeOS-Core — Memory command (L4 memory)
|
|
3
3
|
*
|
|
4
4
|
* Subcommands:
|
|
5
|
-
* memory compact — apply 4-stage compaction to decision-log.md
|
|
5
|
+
* memory compact — apply 4-stage compaction to failure-patterns.md (decision-log.md is append-only)
|
|
6
6
|
* memory score — recompute importance of failure-patterns.md entries
|
|
7
7
|
* memory propose-rules — analyze failure patterns, append suggestions to auto-rule-update.md
|
|
8
8
|
*/
|
|
@@ -247,7 +247,13 @@ function cmdCompact() {
|
|
|
247
247
|
ensureDir(MEMORY_DIR);
|
|
248
248
|
const activeRulePaths = loadActiveRulePaths();
|
|
249
249
|
|
|
250
|
-
|
|
250
|
+
// decision-log.md is EXCLUDED from compaction. Its contract (rules/60.memory/
|
|
251
|
+
// 01.decision-log.md, CLAUDE.md §8) is "permanent, append-only". Stage 1
|
|
252
|
+
// summarization dropped the Context/Options/Decision/Consequences body of
|
|
253
|
+
// every entry older than 30 days — i.e. exactly the "why" the file exists
|
|
254
|
+
// to preserve. Only failure-patterns.md carries the frequency/importance
|
|
255
|
+
// metadata the 4-stage policy is designed around.
|
|
256
|
+
const files = ["failure-patterns.md"];
|
|
251
257
|
const summaries = [];
|
|
252
258
|
for (const f of files) {
|
|
253
259
|
const r = compactFile(path.join(MEMORY_DIR, f), activeRulePaths);
|
|
@@ -255,6 +261,7 @@ function cmdCompact() {
|
|
|
255
261
|
if (r.changed) log(` ✅ ${f}: ${r.before} → ${r.after} entries`);
|
|
256
262
|
else log(` ⏭️ ${f}: ${r.reason}`);
|
|
257
263
|
}
|
|
264
|
+
log(" ⏭️ decision-log.md: append-only, never compacted");
|
|
258
265
|
|
|
259
266
|
// Update compaction.md "Last Compaction" section.
|
|
260
267
|
// Replace ONLY the "## Last Compaction" section (up to next `##` heading or EOF).
|
|
@@ -411,7 +418,7 @@ function showHelp() {
|
|
|
411
418
|
Usage: npx claudeos-core memory <subcommand>
|
|
412
419
|
|
|
413
420
|
Subcommands:
|
|
414
|
-
compact Apply 4-stage compaction to decision-log.md
|
|
421
|
+
compact Apply 4-stage compaction to failure-patterns.md (decision-log.md is append-only, never compacted)
|
|
415
422
|
score Recompute importance of failure-patterns.md entries (frequency × recency)
|
|
416
423
|
propose-rules Analyze failure patterns, append rule update suggestions to auto-rule-update.md
|
|
417
424
|
`);
|
|
@@ -423,10 +423,35 @@ async function main() {
|
|
|
423
423
|
// check covers both. No natural-language matching involved.
|
|
424
424
|
console.log(" [10/10] path-claim verification (hallucination + MANIFEST drift)...");
|
|
425
425
|
|
|
426
|
-
// Regex: matches `src/...` paths to
|
|
427
|
-
//
|
|
428
|
-
//
|
|
429
|
-
|
|
426
|
+
// Regex: matches `src/...` paths to source files, not inside inline code
|
|
427
|
+
// already fenced. We still strip fenced blocks first so example blocks
|
|
428
|
+
// inside ```...``` don't produce false positives.
|
|
429
|
+
//
|
|
430
|
+
// v2.5.0 — extension list widened. Pre-v2.5.0 only ts/tsx/js/jsx were
|
|
431
|
+
// checked, so Java/Kotlin/Python projects (and MyBatis mapper XML, the
|
|
432
|
+
// single most-hallucinated path class on Spring projects) got zero
|
|
433
|
+
// path-claim coverage despite "no invented paths" being the headline
|
|
434
|
+
// guarantee. Config extensions (yml/properties/json) are deliberately
|
|
435
|
+
// NOT included: those are cited as illustrative profile names
|
|
436
|
+
// (`application-{profile}.yml`) far more often than as path claims.
|
|
437
|
+
//
|
|
438
|
+
// An optional module prefix (`api/src/…`, `apps/web/src/…`,
|
|
439
|
+
// `servers/query/x/src/…`) is captured as part of the claim. A prefixed
|
|
440
|
+
// claim is checked at that exact location only — citing `core/src/…` for a
|
|
441
|
+
// file that lives under `api/` is a wrong-module claim and must be flagged,
|
|
442
|
+
// which the bare-`src/` module search below would otherwise hide.
|
|
443
|
+
//
|
|
444
|
+
// Two alternatives, each with its own left boundary:
|
|
445
|
+
// (1) `<module>/…/src/…` — the prefix may not start right after a word
|
|
446
|
+
// char, `.`, `/` or `@`, so `@acme/ui/src/Button.tsx` (a package
|
|
447
|
+
// import, not a repo path) is NOT captured as module `acme/ui`;
|
|
448
|
+
// (2) bare `src/…` — may be preceded by `/` (so the `src/Button.tsx`
|
|
449
|
+
// tail of that import is still checked via the module search) but
|
|
450
|
+
// not by a word char, so `libsrc/x.ts` never yields `src/x.ts`.
|
|
451
|
+
// Dependency paths (`node_modules/<pkg>/src/…`) are skipped below.
|
|
452
|
+
// `:` is excluded before a prefix so a dev-server URL (`localhost:5173/src/main.tsx`)
|
|
453
|
+
// does not yield the module `5173`; its `src/main.tsx` tail is still checked.
|
|
454
|
+
const SRC_PATH_RE = /(?:(?<![\w\-./@:])[\w\-][\w\-.]*\/(?:[\w\-][\w\-.]*\/)*src\/|(?<![\w\-.@])src\/)[\w\-./]+\.(?:ts|tsx|js|jsx|mjs|cjs|vue|svelte|java|kt|kts|py|xml|sql)\b/g;
|
|
430
455
|
|
|
431
456
|
// Placeholder paths are scaffold templates / teaching examples, not
|
|
432
457
|
// real path claims. We skip them. Three patterns qualify:
|
|
@@ -541,18 +566,59 @@ async function main() {
|
|
|
541
566
|
// The monorepo fallback only fires for paths starting with `src/`,
|
|
542
567
|
// which is the conventional workspace-relative form. Non-`src/` paths
|
|
543
568
|
// (e.g., `claudeos-core/skills/...`) are checked direct-only.
|
|
569
|
+
// Directories that are never application modules even when they contain a
|
|
570
|
+
// `src/` child: virtualenvs (pip editable installs default to `<venv>/src`),
|
|
571
|
+
// vendored code, docs, tooling, test fixtures.
|
|
572
|
+
//
|
|
573
|
+
// Two tiers: HARD names are never modules at any depth (build output,
|
|
574
|
+
// dependencies, virtualenvs). SOFT names (`docs`, `tools`, `scripts`,
|
|
575
|
+
// `test(s)`, `fixtures`) are skipped only at the project root — inside a
|
|
576
|
+
// workspace container (`apps/docs`, `packages/tools`) they are legitimate
|
|
577
|
+
// packages, and skipping them there produced false STALE_PATH advisories on
|
|
578
|
+
// the default Turborepo layout.
|
|
579
|
+
const MODULE_SCAN_SKIP = new Set(["node_modules", ".git", ".claude", "claudeos-core", "build", "target", "dist", "out", ".gradle", ".idea", "coverage", ".next",
|
|
580
|
+
"venv", ".venv", "env", "virtualenv", "site-packages", "__pycache__", "vendor", "tmp", "temp"]);
|
|
581
|
+
const MODULE_SCAN_SKIP_ROOT_ONLY = new Set(["docs", "doc", "tools", "scripts", "test", "tests", "__tests__", "fixtures"]);
|
|
582
|
+
const WORKSPACE_CONTAINERS = new Set(["apps", "packages", "libs", "services", "servers", "modules"]);
|
|
583
|
+
function listModuleDirs(dir, { root = false, parentName = "" } = {}) {
|
|
584
|
+
let entries;
|
|
585
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); }
|
|
586
|
+
catch (_e) { return []; }
|
|
587
|
+
const inWorkspace = WORKSPACE_CONTAINERS.has(parentName);
|
|
588
|
+
return entries
|
|
589
|
+
.filter(e => e.isDirectory() && !MODULE_SCAN_SKIP.has(e.name) && !e.name.startsWith(".")
|
|
590
|
+
&& !((root || !inWorkspace) && MODULE_SCAN_SKIP_ROOT_ONLY.has(e.name)))
|
|
591
|
+
.map(e => path.join(dir, e.name));
|
|
592
|
+
}
|
|
593
|
+
// Memoized once per run: every directory up to three levels deep that
|
|
594
|
+
// contains a `src/` folder (JS workspaces `apps/x`, Gradle modules `api`,
|
|
595
|
+
// and the nested Kotlin CQRS layout `servers/query/x` the scanner itself
|
|
596
|
+
// supports). Computed lazily on the first `src/...` miss so projects with
|
|
597
|
+
// zero misses pay nothing.
|
|
598
|
+
let moduleSrcRoots = null;
|
|
599
|
+
function getModuleSrcRoots(ROOT) {
|
|
600
|
+
if (moduleSrcRoots) return moduleSrcRoots;
|
|
601
|
+
moduleSrcRoots = [];
|
|
602
|
+
const walk = (dir, depth) => {
|
|
603
|
+
for (const child of listModuleDirs(dir, { root: depth === 1, parentName: path.basename(dir) })) {
|
|
604
|
+
if (fs.existsSync(path.join(child, "src"))) moduleSrcRoots.push(child);
|
|
605
|
+
if (depth < 3) walk(child, depth + 1);
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
walk(ROOT, 1);
|
|
609
|
+
return moduleSrcRoots;
|
|
610
|
+
}
|
|
544
611
|
function resolvePathClaim(ROOT, claimed) {
|
|
545
612
|
if (fs.existsSync(path.join(ROOT, claimed))) return true;
|
|
546
613
|
if (!claimed.startsWith("src/")) return false;
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
}
|
|
614
|
+
// v2.4.0: JS monorepo workspaces (apps/*, packages/*).
|
|
615
|
+
// v2.5.0: any first- or second-level module directory — Gradle/Maven
|
|
616
|
+
// multi-module (`api/src/main/java/...`, `servers/query/x/src/main/kotlin/...`)
|
|
617
|
+
// cite `src/...` relative to the module, exactly like JS workspaces do.
|
|
618
|
+
// Without this, widening SRC_PATH_RE to .java/.kt/.py would have turned
|
|
619
|
+
// every valid multi-module citation into a STALE_PATH false positive.
|
|
620
|
+
for (const moduleRoot of getModuleSrcRoots(ROOT)) {
|
|
621
|
+
if (fs.existsSync(path.join(moduleRoot, claimed))) return true;
|
|
556
622
|
}
|
|
557
623
|
return false;
|
|
558
624
|
}
|
|
@@ -608,6 +674,9 @@ async function main() {
|
|
|
608
674
|
if (seen.has(claimed)) continue;
|
|
609
675
|
seen.add(claimed);
|
|
610
676
|
if (hasPlaceholder(claimed)) continue;
|
|
677
|
+
// A dependency path is a reference to a library, not a claim about
|
|
678
|
+
// this repository's source tree.
|
|
679
|
+
if (/(^|\/)node_modules\//.test(claimed)) continue;
|
|
611
680
|
pathClaimsChecked++;
|
|
612
681
|
if (!resolvePathClaim(ROOT, claimed)) {
|
|
613
682
|
pathClaimErrors++;
|