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 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 {
@@ -96,9 +96,9 @@ async function cmdInit(parsedArgs) {
96
96
 
97
97
  if (existingPass1.length > 0 || pass2Exists) {
98
98
  if (parsedArgs.force) {
99
- // --force: delete without prompt
100
- for (const f of existingPass1) fs.unlinkSync(path.join(GENERATED_DIR, f));
101
- if (pass2Exists) fs.unlinkSync(path.join(GENERATED_DIR, "pass2-merged.json"));
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(/[.*+?^${}()|[\]\\]/g, "\\$&")}`, "im");
88
+ const re = new RegExp(`(^|#|\\s)${kw.replace(/[.*+?^${}()|\\[\]\\\\]/g, "\\$&")}`, "im");
89
89
  return re.test(content);
90
90
  });
91
91
  if (!found) {
@@ -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" }
@@ -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 blocks = extractFileBlocksFromFile(p);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudeos-core",
3
- "version": "1.6.1",
3
+ "version": "1.6.2",
4
4
  "description": "Auto-generate Claude Code documentation from your actual source code — Standards, Rules, Skills, and Guides tailored to your project",
5
5
  "main": "bin/cli.js",
6
6
  "bin": {
@@ -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
- console.log(` 🌐 Language: ${langData.labels[lang]} (Pass 3 output)`);
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) detectedPattern = domainMap[Object.keys(domainMap)[0]].pattern;
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 => f.includes(`/${m}/`) || f.startsWith(`${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.replace(/\/?\*?\*?$/, "/*/package.json");
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.framework === "django" || stack.framework === "fastapi" || stack.framework === "flask") {
44
+ if (stack.language === "python") {
45
45
  const r = await scanPythonDomains(stack, ROOT);
46
46
  backendDomains.push(...r.backendDomains);
47
47
  }
@@ -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");
@@ -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;