claudeos-core 1.6.1 → 1.6.2
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 +23 -0
- package/bin/cli.js +1 -1
- package/bin/commands/init.js +9 -5
- package/content-validator/index.js +1 -1
- package/health-checker/index.js +0 -1
- package/lib/plan-parser.js +1 -1
- package/manifest-generator/index.js +4 -1
- package/package.json +1 -1
- package/plan-installer/prompt-generator.js +3 -2
- package/plan-installer/scanners/scan-java.js +6 -1
- package/plan-installer/scanners/scan-kotlin.js +4 -1
- package/plan-installer/scanners/scan-python.js +5 -5
- package/plan-installer/stack-detector.js +3 -1
- package/plan-installer/structure-scanner.js +1 -1
- package/plan-validator/index.js +4 -0
- package/sync-checker/index.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.6.2] — 2026-04-09
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **Sync command crash bypass** — `cli.js` sync throw from `cmdHealth`/`cmdValidate`/`cmdRestore`/`cmdRefresh` now correctly caught by `.catch()` handler; previously caused unhandled exception
|
|
8
|
+
- **`init.js` group.domains crash** — Null guard added for `group.domains` and `group.estimatedFiles` in domain-groups iteration; prevents TypeError on malformed `domain-groups.json`
|
|
9
|
+
- **Kotlin shared query resolution failure** — `scan-kotlin.js` full key (`__` separator) module names now converted back to path form (`/`) before file matching; `resolveSharedQueryDomains` was silently failing to find any files
|
|
10
|
+
- **Python scanner Windows glob failure** — `scan-python.js` added `dir.replace(/\\/g, "/")` for Django and FastAPI/Flask glob patterns; Windows `path.dirname` returns backslashes that break glob (same fix `scan-node.js` already had)
|
|
11
|
+
- **`prompt-generator.js` langData.labels crash** — Added null guard for `langData.labels` access; prevents TypeError when `lang-instructions.json` has `instructions` but missing `labels` key
|
|
12
|
+
- **Plan parser heading description leakage** — `plan-parser.js` `parseCodeBlocks` now strips trailing ` — description` / ` – description` / ` - description` from heading; previously included in `filePath`
|
|
13
|
+
- **Content validator regex escape** — `content-validator/index.js` regex character class now correctly escapes `[` and `]`; previously `[` was unescaped, causing runtime error when keyword contains `[`
|
|
14
|
+
- **Manifest generator CODE_BLOCK_PLANS count** — `plan-manifest.json` now uses `extractCodeBlockPathsFromFile` for code-block-format plans (e.g., `21.sync-rules-master.md`); `fileBlocks` count was always 0
|
|
15
|
+
- **Resume pass1/pass2 inconsistency** — When "continue" is selected but no pass1 files exist while pass2 does, pass2 is now deleted to force re-run; previously new pass1 + stale pass2 caused data mismatch
|
|
16
|
+
- **`--force` incomplete cleanup** — Now deletes all `.json` and `.md` files in `generated/` directory (not just pass1/pass2); ensures truly fresh start including stale prompts, manifests, and reports
|
|
17
|
+
- **Workspace path without wildcard** — `stack-detector.js` now handles concrete workspace paths (e.g., `packages/backend`) by scanning both direct and child `package.json` files; previously only glob patterns with `*` worked
|
|
18
|
+
- **Framework-less Python projects skipped** — `structure-scanner.js` now triggers Python scanner for all `language === "python"` projects; previously required `framework` to be `django`/`fastapi`/`flask`
|
|
19
|
+
- **Root directory router.py false domain** — `scan-python.js` now skips `name === "."` when `router.py` is in project root; previously created a domain named `.`
|
|
20
|
+
- **Sync checker null sourcePath** — `sync-checker/index.js` now skips mappings with null/undefined `sourcePath`; previously produced `path.join(ROOT, undefined)` = `"ROOT/undefined"`
|
|
21
|
+
- **Java Pattern B/D detection instability** — `scan-java.js` `detectedPattern` now determined by majority vote across all domains; previously depended on first `Object.keys` insertion order
|
|
22
|
+
- **Duplicate pass1 prompt overwrite** — `prompt-generator.js` deduplicates `activeTemplates` via `Set`; when backend and frontend share the same template, pass1 is generated once instead of being overwritten
|
|
23
|
+
- **Health checker stale-report overwrite** — Removed redundant `generatedAt` write that was overwriting `manifest-generator`'s `summaryPatch`; manifest-generator (run as prerequisite) already sets this key
|
|
24
|
+
- **Plan validator empty file creation** — `--execute` mode now skips file creation when plan block has empty/whitespace-only content; previously created blank files
|
|
25
|
+
|
|
3
26
|
## [1.6.1] — 2026-04-09
|
|
4
27
|
|
|
5
28
|
### Fixed
|
package/bin/cli.js
CHANGED
|
@@ -130,7 +130,7 @@ if (!commands[command]) {
|
|
|
130
130
|
process.exit(1);
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
Promise.resolve(commands[command]()).catch((e) => {
|
|
133
|
+
Promise.resolve().then(() => commands[command]()).catch((e) => {
|
|
134
134
|
if (e instanceof InitError) {
|
|
135
135
|
log(`\n ❌ ${e.message}\n`);
|
|
136
136
|
} else {
|
package/bin/commands/init.js
CHANGED
|
@@ -96,9 +96,9 @@ async function cmdInit(parsedArgs) {
|
|
|
96
96
|
|
|
97
97
|
if (existingPass1.length > 0 || pass2Exists) {
|
|
98
98
|
if (parsedArgs.force) {
|
|
99
|
-
// --force:
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
// --force: clean all generated files for truly fresh start
|
|
100
|
+
const genFiles = fs.readdirSync(GENERATED_DIR).filter(f => f.endsWith(".json") || f.endsWith(".md"));
|
|
101
|
+
for (const f of genFiles) fs.unlinkSync(path.join(GENERATED_DIR, f));
|
|
102
102
|
log(" 🔄 Previous results deleted (--force)\n");
|
|
103
103
|
} else {
|
|
104
104
|
const status = { pass1Done: existingPass1.length, pass2Done: pass2Exists };
|
|
@@ -107,6 +107,10 @@ async function cmdInit(parsedArgs) {
|
|
|
107
107
|
if (mode === "fresh") {
|
|
108
108
|
for (const f of existingPass1) fs.unlinkSync(path.join(GENERATED_DIR, f));
|
|
109
109
|
if (pass2Exists) fs.unlinkSync(path.join(GENERATED_DIR, "pass2-merged.json"));
|
|
110
|
+
} else if (mode === "continue" && existingPass1.length === 0 && pass2Exists) {
|
|
111
|
+
// pass2 exists but no pass1 → pass2 is stale, force re-run
|
|
112
|
+
fs.unlinkSync(path.join(GENERATED_DIR, "pass2-merged.json"));
|
|
113
|
+
log(" ⚠️ pass2-merged.json deleted (no pass1 files to continue from)");
|
|
110
114
|
}
|
|
111
115
|
}
|
|
112
116
|
}
|
|
@@ -202,8 +206,8 @@ async function cmdInit(parsedArgs) {
|
|
|
202
206
|
}
|
|
203
207
|
for (let i = 1; i <= totalGroups; i++) {
|
|
204
208
|
const group = domainGroups.groups[i - 1];
|
|
205
|
-
const domainList = group.domains.join(", ");
|
|
206
|
-
const estFiles = group.estimatedFiles;
|
|
209
|
+
const domainList = (group.domains || []).join(", ") || "(unknown)";
|
|
210
|
+
const estFiles = group.estimatedFiles || 0;
|
|
207
211
|
const groupType = group.type || "backend";
|
|
208
212
|
const icon = groupType === "frontend" ? "🎨" : "⚙️";
|
|
209
213
|
|
|
@@ -85,7 +85,7 @@ async function main() {
|
|
|
85
85
|
for (let i = 0; i < enKeywords.length; i++) {
|
|
86
86
|
const candidates = [enKeywords[i], langKeywords[i]].filter(Boolean);
|
|
87
87
|
const found = candidates.some(kw => {
|
|
88
|
-
const re = new RegExp(`(^|#|\\s)${kw.replace(/[.*+?^${}()
|
|
88
|
+
const re = new RegExp(`(^|#|\\s)${kw.replace(/[.*+?^${}()|\\[\]\\\\]/g, "\\$&")}`, "im");
|
|
89
89
|
return re.test(content);
|
|
90
90
|
});
|
|
91
91
|
if (!found) {
|
package/health-checker/index.js
CHANGED
|
@@ -113,7 +113,6 @@ function main() {
|
|
|
113
113
|
console.log(" ══════════════════════════════\n");
|
|
114
114
|
|
|
115
115
|
// ─── Update stale-report.json ────────────────────────────
|
|
116
|
-
updateStaleReport(GEN, "generatedAt", new Date().toISOString());
|
|
117
116
|
updateStaleReport(GEN, "healthCheck",
|
|
118
117
|
{ results, status: hasErr ? "fail" : "pass" },
|
|
119
118
|
{ totalIssues: results.filter((r) => r.status === "fail").length, healthStatus: hasErr ? "fail" : "ok" }
|
package/lib/plan-parser.js
CHANGED
|
@@ -52,7 +52,7 @@ function parseCodeBlocks(content, { includeContent = false } = {}) {
|
|
|
52
52
|
: /^##\s+\d+\.\s+`?([^`\n]+)`?/gm;
|
|
53
53
|
let headingMatch;
|
|
54
54
|
while ((headingMatch = headingRe.exec(content)) !== null) {
|
|
55
|
-
const filePath = headingMatch[1].replace(/`/g, "").trim();
|
|
55
|
+
const filePath = headingMatch[1].replace(/`/g, "").replace(/\s+[—–\-].*$/, "").trim();
|
|
56
56
|
|
|
57
57
|
if (!includeContent) {
|
|
58
58
|
// Path-only mode: just validate and collect
|
|
@@ -148,7 +148,10 @@ async function main() {
|
|
|
148
148
|
for (const p of await glob("*.md", { cwd: DIRS.plan, absolute: true })) {
|
|
149
149
|
const r = rel(p);
|
|
150
150
|
const s = stat(p);
|
|
151
|
-
const
|
|
151
|
+
const bn = path.basename(p);
|
|
152
|
+
const blocks = CODE_BLOCK_PLANS.includes(bn)
|
|
153
|
+
? extractCodeBlockPathsFromFile(p)
|
|
154
|
+
: extractFileBlocksFromFile(p);
|
|
152
155
|
pm.plans.push({ path: r, ...s, fileBlocks: blocks.length, status: "ok" });
|
|
153
156
|
}
|
|
154
157
|
}
|
package/package.json
CHANGED
|
@@ -29,7 +29,8 @@ function generatePrompts(templates, lang, templatesDir, generatedDir) {
|
|
|
29
29
|
const langData = readJsonSafe(langPath);
|
|
30
30
|
if (langData && langData.instructions && langData.instructions[lang]) {
|
|
31
31
|
langInstruction = langData.instructions[lang];
|
|
32
|
-
|
|
32
|
+
const label = (langData.labels && langData.labels[lang]) || lang;
|
|
33
|
+
console.log(` 🌐 Language: ${label} (Pass 3 output)`);
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
36
|
|
|
@@ -39,7 +40,7 @@ function generatePrompts(templates, lang, templatesDir, generatedDir) {
|
|
|
39
40
|
return readFileSafe(src);
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
const activeTemplates = [templates.backend, templates.frontend].filter(Boolean);
|
|
43
|
+
const activeTemplates = [...new Set([templates.backend, templates.frontend].filter(Boolean))];
|
|
43
44
|
const primaryTemplate = templates.backend || templates.frontend;
|
|
44
45
|
|
|
45
46
|
for (let ti = 0; ti < activeTemplates.length; ti++) {
|
|
@@ -69,7 +69,12 @@ async function scanJavaDomains(stack, ROOT) {
|
|
|
69
69
|
domainMap[d].controllers += entries.length;
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
|
-
if (Object.keys(domainMap).length > 0)
|
|
72
|
+
if (Object.keys(domainMap).length > 0) {
|
|
73
|
+
// Determine pattern by majority vote (B vs D)
|
|
74
|
+
const patternCounts = {};
|
|
75
|
+
for (const v of Object.values(domainMap)) patternCounts[v.pattern] = (patternCounts[v.pattern] || 0) + 1;
|
|
76
|
+
detectedPattern = Object.entries(patternCounts).sort((a, b) => b[1] - a[1])[0][0];
|
|
77
|
+
}
|
|
73
78
|
}
|
|
74
79
|
|
|
75
80
|
// Pattern E: DDD/Hexagonal — {domain}/adapter/in/web/*.java or {domain}/adapter/in/rest/*.java
|
|
@@ -262,7 +262,10 @@ function resolveSharedQueryDomains(backendDomains, ktFiles) {
|
|
|
262
262
|
for (const shared of sharedModules) {
|
|
263
263
|
const moduleNames = shared.modules || [];
|
|
264
264
|
const sharedKtFiles = ktFiles.filter(f =>
|
|
265
|
-
moduleNames.some(m =>
|
|
265
|
+
moduleNames.some(m => {
|
|
266
|
+
const p = m.includes("__") ? m.replace(/__/g, "/") : m;
|
|
267
|
+
return f.includes(`/${p}/`) || f.startsWith(`${p}/`);
|
|
268
|
+
})
|
|
266
269
|
);
|
|
267
270
|
if (sharedKtFiles.length === 0) continue;
|
|
268
271
|
|
|
@@ -17,7 +17,7 @@ async function scanPythonDomains(stack, ROOT) {
|
|
|
17
17
|
const dir = path.dirname(f);
|
|
18
18
|
if (dir === "." || dir.includes("venv")) continue;
|
|
19
19
|
const name = path.basename(dir);
|
|
20
|
-
const appFiles = await glob(`${dir}/*.py`, { cwd: ROOT });
|
|
20
|
+
const appFiles = await glob(`${dir.replace(/\\/g, "/")}/*.py`, { cwd: ROOT });
|
|
21
21
|
const views = appFiles.filter(x => x.includes("views")).length;
|
|
22
22
|
const models = appFiles.filter(x => x.includes("models")).length;
|
|
23
23
|
const serializers = appFiles.filter(x => x.includes("serializers")).length;
|
|
@@ -25,16 +25,16 @@ async function scanPythonDomains(stack, ROOT) {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
// ── FastAPI / Flask ──
|
|
29
|
-
if (stack.framework === "fastapi" || stack.framework === "flask") {
|
|
28
|
+
// ── FastAPI / Flask / generic Python ──
|
|
29
|
+
if (stack.framework === "fastapi" || stack.framework === "flask" || (stack.language === "python" && stack.framework !== "django")) {
|
|
30
30
|
const routerFiles = await glob("**/{router,routes,endpoints}*.py", { cwd: ROOT, ignore: ["**/venv/**", "**/.venv/**"] });
|
|
31
31
|
const seen = new Set();
|
|
32
32
|
for (const f of routerFiles) {
|
|
33
33
|
const dir = path.dirname(f);
|
|
34
34
|
const name = path.basename(dir);
|
|
35
|
-
if (seen.has(name) || ["venv", ".venv", "__pycache__"].includes(name)) continue;
|
|
35
|
+
if (name === "." || seen.has(name) || ["venv", ".venv", "__pycache__"].includes(name)) continue;
|
|
36
36
|
seen.add(name);
|
|
37
|
-
const appFiles = await glob(`${dir}/*.py`, { cwd: ROOT });
|
|
37
|
+
const appFiles = await glob(`${dir.replace(/\\/g, "/")}/*.py`, { cwd: ROOT });
|
|
38
38
|
backendDomains.push({ name, type: "backend", totalFiles: appFiles.length });
|
|
39
39
|
}
|
|
40
40
|
if (backendDomains.filter(d => d.type === "backend").length === 0) {
|
|
@@ -267,7 +267,9 @@ async function detectStack(ROOT) {
|
|
|
267
267
|
const subPkgGlobs = ["{apps,packages}/*/package.json"];
|
|
268
268
|
if (stack.workspaces) {
|
|
269
269
|
for (const ws of stack.workspaces) {
|
|
270
|
-
const wsGlob = ws
|
|
270
|
+
const wsGlob = /[*?]/.test(ws)
|
|
271
|
+
? ws.replace(/\/?\*?\*?$/, "/*/package.json")
|
|
272
|
+
: `${ws.replace(/\/?$/, "")}/{,*/}package.json`;
|
|
271
273
|
if (!subPkgGlobs.includes(wsGlob)) subPkgGlobs.push(wsGlob);
|
|
272
274
|
}
|
|
273
275
|
}
|
|
@@ -41,7 +41,7 @@ async function scanStructure(stack, ROOT) {
|
|
|
41
41
|
backendDomains.push(...r.backendDomains);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
if (stack.
|
|
44
|
+
if (stack.language === "python") {
|
|
45
45
|
const r = await scanPythonDomains(stack, ROOT);
|
|
46
46
|
backendDomains.push(...r.backendDomains);
|
|
47
47
|
}
|
package/plan-validator/index.js
CHANGED
|
@@ -88,6 +88,10 @@ async function main() {
|
|
|
88
88
|
// File does not exist
|
|
89
89
|
if (!fs.existsSync(abs)) {
|
|
90
90
|
if (mode === "--execute") {
|
|
91
|
+
if (!b.content || b.content.trim().length === 0) {
|
|
92
|
+
console.log(` ⚠️ SKIPPED: ${b.path} (empty content in plan)`);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
91
95
|
const dir = path.dirname(abs);
|
|
92
96
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
93
97
|
fs.writeFileSync(abs, b.content + "\n");
|
package/sync-checker/index.js
CHANGED
|
@@ -91,6 +91,7 @@ async function main() {
|
|
|
91
91
|
// ─── [2/2] Plan → Disk: detect orphaned files ───────────────
|
|
92
92
|
console.log(" [2/2] Plan → Disk...");
|
|
93
93
|
for (const m of sm.mappings) {
|
|
94
|
+
if (!m.sourcePath) continue;
|
|
94
95
|
const abs = path.join(ROOT, m.sourcePath);
|
|
95
96
|
// Skip path traversal attempts (allow files at ROOT level and below)
|
|
96
97
|
if (!isWithinRoot(abs)) continue;
|