claudeos-core 1.2.3 → 1.3.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.

Potentially problematic release.


This version of claudeos-core might be problematic. Click here for more details.

Files changed (44) hide show
  1. package/CHANGELOG.md +66 -1
  2. package/README.de.md +41 -6
  3. package/README.es.md +42 -6
  4. package/README.fr.md +42 -6
  5. package/README.hi.md +42 -6
  6. package/README.ja.md +43 -6
  7. package/README.ko.md +42 -6
  8. package/README.md +53 -11
  9. package/README.ru.md +42 -6
  10. package/README.vi.md +42 -6
  11. package/README.zh-CN.md +42 -6
  12. package/bin/cli.js +171 -36
  13. package/bootstrap.sh +72 -23
  14. package/content-validator/index.js +9 -4
  15. package/health-checker/index.js +4 -3
  16. package/lib/safe-fs.js +110 -0
  17. package/manifest-generator/index.js +16 -20
  18. package/package.json +4 -2
  19. package/pass-json-validator/index.js +3 -5
  20. package/pass-prompts/templates/java-spring/pass1.md +3 -0
  21. package/pass-prompts/templates/java-spring/pass2.md +3 -3
  22. package/pass-prompts/templates/java-spring/pass3.md +17 -0
  23. package/pass-prompts/templates/kotlin-spring/pass1.md +3 -0
  24. package/pass-prompts/templates/kotlin-spring/pass2.md +5 -5
  25. package/pass-prompts/templates/kotlin-spring/pass3.md +17 -0
  26. package/pass-prompts/templates/node-express/pass1.md +3 -0
  27. package/pass-prompts/templates/node-express/pass2.md +4 -1
  28. package/pass-prompts/templates/node-express/pass3.md +20 -1
  29. package/pass-prompts/templates/node-nextjs/pass1.md +13 -3
  30. package/pass-prompts/templates/node-nextjs/pass2.md +6 -4
  31. package/pass-prompts/templates/node-nextjs/pass3.md +20 -1
  32. package/pass-prompts/templates/python-django/pass1.md +3 -1
  33. package/pass-prompts/templates/python-django/pass2.md +4 -4
  34. package/pass-prompts/templates/python-django/pass3.md +18 -0
  35. package/pass-prompts/templates/python-fastapi/pass1.md +3 -0
  36. package/pass-prompts/templates/python-fastapi/pass2.md +4 -4
  37. package/pass-prompts/templates/python-fastapi/pass3.md +18 -0
  38. package/plan-installer/domain-grouper.js +74 -0
  39. package/plan-installer/index.js +43 -1303
  40. package/plan-installer/prompt-generator.js +99 -0
  41. package/plan-installer/stack-detector.js +326 -0
  42. package/plan-installer/structure-scanner.js +783 -0
  43. package/plan-validator/index.js +84 -20
  44. package/sync-checker/index.js +7 -3
@@ -39,12 +39,48 @@ function extractFileBlocks(content) {
39
39
  }
40
40
 
41
41
  // Extract ## N. `path` \n```markdown ... ``` blocks
42
+ // Uses indexOf-based parsing to correctly handle nested code fences inside markdown content
42
43
  function extractCodeBlocks(content) {
43
44
  const result = [];
44
- let m;
45
- const re = /##\s+\d+\.\s+([^\n]+)\n+```markdown\n([\s\S]*?)```/g;
46
- while ((m = re.exec(content)) !== null) {
47
- result.push({ path: m[1].replace(/`/g, "").trim(), content: m[2].trimEnd() });
45
+ const headingRe = /^##\s+\d+\.\s+([^\n]+)/gm;
46
+ let headingMatch;
47
+ while ((headingMatch = headingRe.exec(content)) !== null) {
48
+ const filePath = headingMatch[1].replace(/`/g, "").trim();
49
+ // Find the opening ```markdown after the heading
50
+ const openFence = content.indexOf("```markdown\n", headingMatch.index);
51
+ if (openFence < 0) continue;
52
+ const contentStart = openFence + "```markdown\n".length;
53
+ // Find the matching closing ``` — track nesting depth to skip inner fenced blocks
54
+ let searchPos = contentStart;
55
+ let closingPos = -1;
56
+ let nestDepth = 0;
57
+ while (searchPos < content.length) {
58
+ const nextFence = content.indexOf("\n```", searchPos);
59
+ if (nextFence < 0) break;
60
+ const fenceLineStart = nextFence + 1; // position of ```
61
+ // Determine end of this ``` line
62
+ const nextNewline = content.indexOf("\n", fenceLineStart + 3);
63
+ const restOfLine = nextNewline >= 0
64
+ ? content.substring(fenceLineStart + 3, nextNewline)
65
+ : content.substring(fenceLineStart + 3);
66
+ // Opening fence: ``` followed by a language tag (non-empty alphanumeric text)
67
+ const isOpening = /^[a-zA-Z]/.test(restOfLine.trim());
68
+ if (isOpening) {
69
+ nestDepth++;
70
+ searchPos = nextNewline >= 0 ? nextNewline : fenceLineStart + 3;
71
+ } else if (nestDepth > 0) {
72
+ nestDepth--;
73
+ searchPos = nextNewline >= 0 ? nextNewline : fenceLineStart + 3;
74
+ } else {
75
+ closingPos = fenceLineStart;
76
+ break;
77
+ }
78
+ }
79
+ if (closingPos < 0) continue;
80
+ const blockContent = content.substring(contentStart, closingPos).trimEnd();
81
+ result.push({ path: filePath, content: blockContent });
82
+ // Advance headingRe past this block to avoid re-matching inside content
83
+ headingRe.lastIndex = closingPos;
48
84
  }
49
85
  return result;
50
86
  }
@@ -70,24 +106,29 @@ function replaceCodeBlock(content, filePath, newContent) {
70
106
  const afterHeading = content.indexOf("```markdown\n", headingMatch.index);
71
107
  if (afterHeading < 0) return content;
72
108
  const contentStart = afterHeading + "```markdown\n".length;
73
- // Find the closing ``` that is on its own line (not nested)
74
- // Track nesting depth to skip inner fenced blocks
109
+ // Find the matching closing ``` track nesting depth to skip inner fenced blocks
75
110
  let searchPos = contentStart;
76
111
  let closingPos = -1;
77
112
  let nestDepth = 0;
78
113
  while (searchPos < content.length) {
79
114
  const nextFence = content.indexOf("\n```", searchPos);
80
115
  if (nextFence < 0) break;
81
- const lineAfterFence = content.substring(nextFence + 4, content.indexOf("\n", nextFence + 4));
82
- const isOpening = lineAfterFence.trim().length > 0 && !lineAfterFence.startsWith("\n");
116
+ const fenceLineStart = nextFence + 1; // position of ```
117
+ // Determine end of this ``` line
118
+ const nextNewline = content.indexOf("\n", fenceLineStart + 3);
119
+ const restOfLine = nextNewline >= 0
120
+ ? content.substring(fenceLineStart + 3, nextNewline)
121
+ : content.substring(fenceLineStart + 3);
122
+ // Opening fence: ``` followed by a language tag (non-empty alphanumeric text)
123
+ const isOpening = /^[a-zA-Z]/.test(restOfLine.trim());
83
124
  if (isOpening) {
84
125
  nestDepth++;
85
- searchPos = nextFence + 4;
126
+ searchPos = nextNewline >= 0 ? nextNewline : fenceLineStart + 3;
86
127
  } else if (nestDepth > 0) {
87
128
  nestDepth--;
88
- searchPos = nextFence + 4;
129
+ searchPos = nextNewline >= 0 ? nextNewline : fenceLineStart + 3;
89
130
  } else {
90
- closingPos = nextFence + 1; // position of the closing ```
131
+ closingPos = fenceLineStart;
91
132
  break;
92
133
  }
93
134
  }
@@ -96,12 +137,18 @@ function replaceCodeBlock(content, filePath, newContent) {
96
137
  }
97
138
 
98
139
  async function main() {
140
+ const validModes = ["--check", "--refresh", "--execute"];
99
141
  const mode = process.argv[2] || "--check";
142
+ if (!validModes.includes(mode)) {
143
+ console.log(` ❌ Unknown mode: ${mode}`);
144
+ console.log(` Valid modes: ${validModes.join(", ")}`);
145
+ process.exit(1);
146
+ }
100
147
 
101
- console.log("\n╔══════════════════════════════════════╗");
148
+ console.log("\n╔═══════════════════════════════════════╗");
102
149
  console.log("║ ClaudeOS-Core — Plan Validator ║");
103
- console.log(`║ Mode: ${mode.padEnd(30)}║`);
104
- console.log("╚══════════════════════════════════════╝\n");
150
+ console.log(`║ Mode: ${mode.padEnd(31)}║`);
151
+ console.log("╚═══════════════════════════════════════╝\n");
105
152
 
106
153
  if (!fs.existsSync(PLAN)) {
107
154
  console.log(" ❌ Plan directory not found");
@@ -127,7 +174,7 @@ async function main() {
127
174
  const abs = path.join(ROOT, b.path);
128
175
 
129
176
  // Block path traversal attempts
130
- if (!path.resolve(abs).startsWith(path.resolve(ROOT))) {
177
+ if (!path.resolve(abs).startsWith(path.resolve(ROOT) + path.sep)) {
131
178
  console.log(` ⚠️ SKIPPED: ${b.path} (path traversal blocked)`);
132
179
  continue;
133
180
  }
@@ -148,9 +195,9 @@ async function main() {
148
195
  continue;
149
196
  }
150
197
 
151
- // File exists — compare content
152
- const diskContent = fs.readFileSync(abs, "utf-8").trimEnd();
153
- const planContent = b.content.trimEnd();
198
+ // File exists — compare content (normalize trailing newlines only)
199
+ const diskContent = fs.readFileSync(abs, "utf-8").replace(/\n+$/, "");
200
+ const planContent = b.content.replace(/\n+$/, "");
154
201
 
155
202
  if (diskContent === planContent) {
156
203
  synced++;
@@ -187,15 +234,32 @@ async function main() {
187
234
  console.log(`\n Total: ${total} | Synced: ${synced} | Drift: ${drift} | Missing: ${missing}`);
188
235
  console.log(drift + missing === 0 ? " ✅ All plans in sync\n" : ` ⚠️ ${drift + missing} issues\n`);
189
236
 
190
- // ─── Update plan-sync-status.json (separate from manifest-generator's plan-manifest.json) ──
237
+ // ─── Update plan-sync-status.json + stale-report.json ──
191
238
  if (fs.existsSync(GEN)) {
192
239
  fs.writeFileSync(
193
240
  path.join(GEN, "plan-sync-status.json"),
194
241
  JSON.stringify({ generatedAt: new Date().toISOString(), lastMode: mode, total, synced, drift, missing, issues: results }, null, 2)
195
242
  );
243
+
244
+ // Also write to stale-report.json for consolidated health reporting
245
+ const rp = path.join(GEN, "stale-report.json");
246
+ let ex = {};
247
+ if (fs.existsSync(rp)) {
248
+ try { ex = JSON.parse(fs.readFileSync(rp, "utf-8")); } catch { ex = {}; }
249
+ }
250
+ ex.planValidation = { checkedAt: new Date().toISOString(), mode, total, synced, drift, missing };
251
+ if (!ex.summary) ex.summary = {};
252
+ ex.summary.planDrift = drift;
253
+ ex.summary.planMissing = missing;
254
+ fs.writeFileSync(rp, JSON.stringify(ex, null, 2));
196
255
  }
197
256
 
198
257
  process.exit(drift + missing > 0 ? 1 : 0);
199
258
  }
200
259
 
201
- main().catch(console.error);
260
+ // Export for testing; run main() only when executed directly
261
+ if (require.main === module) {
262
+ main().catch(console.error);
263
+ }
264
+
265
+ module.exports = { extractFileBlocks, extractCodeBlocks, replaceFileBlock, replaceCodeBlock };
@@ -34,9 +34,9 @@ function rel(p) {
34
34
  }
35
35
 
36
36
  async function main() {
37
- console.log("\n╔══════════════════════════════════════╗");
37
+ console.log("\n╔═══════════════════════════════════════╗");
38
38
  console.log("║ ClaudeOS-Core — Sync Checker ║");
39
- console.log("╚══════════════════════════════════════╝\n");
39
+ console.log("╚═══════════════════════════════════════╝\n");
40
40
 
41
41
  if (!fs.existsSync(SMP)) {
42
42
  console.log(" ❌ sync-map.json not found. Run manifest-generator first.\n");
@@ -50,6 +50,10 @@ async function main() {
50
50
  console.log(` ❌ sync-map.json is malformed: ${e.message}\n`);
51
51
  process.exit(1);
52
52
  }
53
+ if (!Array.isArray(sm.mappings)) {
54
+ console.log(" ❌ sync-map.json has no mappings array.\n");
55
+ process.exit(1);
56
+ }
53
57
  const reg = new Set(sm.mappings.map((m) => m.sourcePath));
54
58
  const issues = { unreg: [], orphan: [] };
55
59
 
@@ -78,7 +82,7 @@ async function main() {
78
82
  for (const m of sm.mappings) {
79
83
  const abs = path.join(ROOT, m.sourcePath);
80
84
  // Skip path traversal attempts
81
- if (!path.resolve(abs).startsWith(path.resolve(ROOT))) continue;
85
+ if (!path.resolve(abs).startsWith(path.resolve(ROOT) + path.sep)) continue;
82
86
  if (!fs.existsSync(abs)) {
83
87
  issues.orphan.push({ path: m.sourcePath, plan: m.planFile });
84
88
  }