claudeos-core 1.7.0 → 2.0.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 +138 -0
- package/CONTRIBUTING.md +92 -59
- package/README.de.md +465 -240
- package/README.es.md +446 -223
- package/README.fr.md +461 -238
- package/README.hi.md +485 -261
- package/README.ja.md +440 -235
- package/README.ko.md +244 -56
- package/README.md +215 -47
- package/README.ru.md +462 -238
- package/README.vi.md +454 -230
- package/README.zh-CN.md +476 -252
- package/bin/cli.js +144 -140
- package/bin/commands/init.js +550 -46
- package/bin/commands/memory.js +426 -0
- package/bin/lib/cli-utils.js +206 -143
- package/bootstrap.sh +81 -390
- package/content-validator/index.js +436 -340
- package/lib/expected-guides.js +23 -0
- package/lib/expected-outputs.js +91 -0
- package/lib/language-config.js +35 -0
- package/lib/memory-scaffold.js +1014 -0
- package/lib/plan-parser.js +153 -149
- package/lib/staged-rules.js +118 -0
- package/manifest-generator/index.js +176 -171
- package/package.json +1 -1
- package/pass-json-validator/index.js +337 -299
- package/pass-prompts/templates/common/pass3-footer.md +16 -0
- package/pass-prompts/templates/common/pass4.md +317 -0
- package/pass-prompts/templates/common/staging-override.md +26 -0
- package/pass-prompts/templates/python-flask/pass1.md +119 -0
- package/pass-prompts/templates/python-flask/pass2.md +85 -0
- package/pass-prompts/templates/python-flask/pass3.md +103 -0
- package/plan-installer/domain-grouper.js +2 -1
- package/plan-installer/prompt-generator.js +120 -96
- package/plan-installer/scanners/scan-frontend.js +219 -10
- package/plan-installer/scanners/scan-java.js +226 -223
- package/plan-installer/scanners/scan-python.js +21 -0
- package/sync-checker/index.js +133 -132
|
@@ -1,340 +1,436 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* ClaudeOS-Core — Content Validator
|
|
5
|
-
*
|
|
6
|
-
* Role: Validate content quality of generated files
|
|
7
|
-
* Validation items:
|
|
8
|
-
* - File is not empty
|
|
9
|
-
* - standard files contain ✅/❌ examples + rules table
|
|
10
|
-
* - rules files contain paths: ["all files"] frontmatter
|
|
11
|
-
* - CLAUDE.md required sections exist
|
|
12
|
-
* - All 9 guide files are generated
|
|
13
|
-
* - Skills orchestrator + sub-skills exist
|
|
14
|
-
* - database/, mcp-guide/ files are generated
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const {
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
"
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
"
|
|
146
|
-
"
|
|
147
|
-
"
|
|
148
|
-
"
|
|
149
|
-
"
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
"
|
|
153
|
-
"
|
|
154
|
-
"
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
"
|
|
158
|
-
"
|
|
159
|
-
"
|
|
160
|
-
"
|
|
161
|
-
"
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (!
|
|
172
|
-
warnings.push({ file: r, type: "
|
|
173
|
-
}
|
|
174
|
-
//
|
|
175
|
-
|
|
176
|
-
if (
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
if (
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ClaudeOS-Core — Content Validator
|
|
5
|
+
*
|
|
6
|
+
* Role: Validate content quality of generated files
|
|
7
|
+
* Validation items:
|
|
8
|
+
* - File is not empty
|
|
9
|
+
* - standard files contain ✅/❌ examples + rules table
|
|
10
|
+
* - rules files contain paths: ["all files"] frontmatter
|
|
11
|
+
* - CLAUDE.md required sections exist
|
|
12
|
+
* - All 9 guide files are generated
|
|
13
|
+
* - Skills orchestrator + sub-skills exist
|
|
14
|
+
* - database/, mcp-guide/ files are generated
|
|
15
|
+
* - memory files follow expected entry structure
|
|
16
|
+
*
|
|
17
|
+
* Usage: npx claudeos-core <cmd> or node claudeos-core-tools/content-validator/index.js
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const fs = require("fs");
|
|
21
|
+
const path = require("path");
|
|
22
|
+
const { glob } = require("glob");
|
|
23
|
+
const { updateStaleReport } = require("../lib/stale-report");
|
|
24
|
+
const { EXPECTED_GUIDE_FILES } = require("../lib/expected-guides");
|
|
25
|
+
|
|
26
|
+
const ROOT = process.env.CLAUDEOS_ROOT || path.resolve(__dirname, "../..");
|
|
27
|
+
const RULES_DIR = path.join(ROOT, ".claude/rules");
|
|
28
|
+
const STANDARD_DIR = path.join(ROOT, "claudeos-core/standard");
|
|
29
|
+
const SKILLS_DIR = path.join(ROOT, "claudeos-core/skills");
|
|
30
|
+
const GUIDE_DIR = path.join(ROOT, "claudeos-core/guide");
|
|
31
|
+
const PLAN_DIR = path.join(ROOT, "claudeos-core/plan");
|
|
32
|
+
const DB_DIR = path.join(ROOT, "claudeos-core/database");
|
|
33
|
+
const MCP_DIR = path.join(ROOT, "claudeos-core/mcp-guide");
|
|
34
|
+
const MEMORY_DIR = path.join(ROOT, "claudeos-core/memory");
|
|
35
|
+
const GEN_DIR = path.join(ROOT, "claudeos-core/generated");
|
|
36
|
+
|
|
37
|
+
const EXPECTED_MEMORY = ["decision-log.md", "failure-patterns.md", "compaction.md", "auto-rule-update.md"];
|
|
38
|
+
|
|
39
|
+
function rel(p) { return path.relative(ROOT, p).replace(/\\/g, "/"); }
|
|
40
|
+
|
|
41
|
+
async function main() {
|
|
42
|
+
console.log("\n╔═══════════════════════════════════════╗");
|
|
43
|
+
console.log("║ ClaudeOS-Core — Content Validator ║");
|
|
44
|
+
console.log("╚═══════════════════════════════════════╝\n");
|
|
45
|
+
|
|
46
|
+
const errors = [];
|
|
47
|
+
const warnings = [];
|
|
48
|
+
let checked = 0;
|
|
49
|
+
|
|
50
|
+
// ─── Detect language and stack from project-analysis.json ────────
|
|
51
|
+
let detectedLanguage = null;
|
|
52
|
+
let outputLang = "en";
|
|
53
|
+
const paPath = path.join(GEN_DIR, "project-analysis.json");
|
|
54
|
+
if (fs.existsSync(paPath)) {
|
|
55
|
+
try {
|
|
56
|
+
const pa = JSON.parse(fs.readFileSync(paPath, "utf-8"));
|
|
57
|
+
detectedLanguage = pa.stack?.language || null;
|
|
58
|
+
outputLang = pa.lang || "en";
|
|
59
|
+
} catch (_e) { /* ignore */ }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Language-aware section keywords for CLAUDE.md validation
|
|
63
|
+
const SECTION_KEYWORDS = {
|
|
64
|
+
en: ["Role", "Build", "Run", "Standard", "Skills"],
|
|
65
|
+
ko: ["역할", "빌드", "실행", "표준", "스킬"],
|
|
66
|
+
"zh-CN": ["角色", "构建", "运行", "标准", "技能"],
|
|
67
|
+
ja: ["役割", "ビルド", "実行", "標準", "スキル"],
|
|
68
|
+
es: ["Rol", "Compilar", "Ejecutar", "Estándar", "Habilidades"],
|
|
69
|
+
vi: ["Vai trò", "Build", "Chạy", "Tiêu chuẩn", "Kỹ năng"],
|
|
70
|
+
hi: ["भूमिका", "बिल्ड", "रन", "मानक", "कौशल"],
|
|
71
|
+
ru: ["Роль", "Сборка", "Запуск", "Стандарт", "Навыки"],
|
|
72
|
+
fr: ["Rôle", "Build", "Exécuter", "Standard", "Compétences"],
|
|
73
|
+
de: ["Rolle", "Build", "Ausführen", "Standard", "Fähigkeiten"],
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// ─── 1. CLAUDE.md ──────────────────────────────────────
|
|
77
|
+
console.log(" [1/9] CLAUDE.md...");
|
|
78
|
+
const claudeMd = path.join(ROOT, "CLAUDE.md");
|
|
79
|
+
if (!fs.existsSync(claudeMd)) {
|
|
80
|
+
errors.push({ file: "CLAUDE.md", type: "MISSING", msg: "CLAUDE.md does not exist" });
|
|
81
|
+
} else {
|
|
82
|
+
checked++;
|
|
83
|
+
const content = fs.readFileSync(claudeMd, "utf-8");
|
|
84
|
+
if (content.trim().length < 100) {
|
|
85
|
+
errors.push({ file: "CLAUDE.md", type: "EMPTY", msg: "CLAUDE.md content is too short (<100 chars)" });
|
|
86
|
+
}
|
|
87
|
+
// Check sections in both English (fallback) and output language
|
|
88
|
+
const langKeywords = SECTION_KEYWORDS[outputLang] || SECTION_KEYWORDS.en;
|
|
89
|
+
const enKeywords = SECTION_KEYWORDS.en;
|
|
90
|
+
for (let i = 0; i < enKeywords.length; i++) {
|
|
91
|
+
const candidates = [enKeywords[i], langKeywords[i]].filter(Boolean);
|
|
92
|
+
const found = candidates.some(kw => {
|
|
93
|
+
const re = new RegExp(`(^|#|\\s)${kw.replace(/[.*+?^${}()|\\[\]\\\\]/g, "\\$&")}`, "im");
|
|
94
|
+
return re.test(content);
|
|
95
|
+
});
|
|
96
|
+
if (!found) {
|
|
97
|
+
warnings.push({ file: "CLAUDE.md", type: "MISSING_SECTION", msg: `'${enKeywords[i]}' / '${langKeywords[i]}' section is missing` });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ─── 2. .claude/rules/** ───────────────────────────────
|
|
103
|
+
console.log(" [2/9] .claude/rules/...");
|
|
104
|
+
if (fs.existsSync(RULES_DIR)) {
|
|
105
|
+
const ruleFiles = await glob("**/*.md", { cwd: RULES_DIR, absolute: true });
|
|
106
|
+
for (const f of ruleFiles) {
|
|
107
|
+
checked++;
|
|
108
|
+
const c = fs.readFileSync(f, "utf-8");
|
|
109
|
+
const r = rel(f);
|
|
110
|
+
if (c.trim().length === 0) {
|
|
111
|
+
errors.push({ file: r, type: "EMPTY", msg: "Empty file" });
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
// All rules must have paths: frontmatter (value varies by category — e.g. ["**/*"] for core/backend, scoped patterns for infra/sync)
|
|
115
|
+
const hasFrontmatter = c.replace(/^\uFEFF/, "").startsWith("---");
|
|
116
|
+
const hasPathsKey = c.includes("paths:");
|
|
117
|
+
if (!hasFrontmatter) {
|
|
118
|
+
warnings.push({ file: r, type: "NO_FRONTMATTER", msg: "Missing YAML frontmatter (---)" });
|
|
119
|
+
} else if (!hasPathsKey) {
|
|
120
|
+
warnings.push({ file: r, type: "NO_PATHS", msg: "Frontmatter exists but missing paths: key" });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
console.log(` ${ruleFiles.length} files checked`);
|
|
124
|
+
} else {
|
|
125
|
+
errors.push({ file: ".claude/rules/", type: "MISSING", msg: "rules directory not found" });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ─── 3. claudeos-core/standard/** ─────────────────────
|
|
129
|
+
console.log(" [3/9] claudeos-core/standard/...");
|
|
130
|
+
if (fs.existsSync(STANDARD_DIR)) {
|
|
131
|
+
const stdFiles = await glob("**/*.md", { cwd: STANDARD_DIR, absolute: true });
|
|
132
|
+
for (const f of stdFiles) {
|
|
133
|
+
checked++;
|
|
134
|
+
const c = fs.readFileSync(f, "utf-8");
|
|
135
|
+
const r = rel(f);
|
|
136
|
+
if (c.trim().length === 0) {
|
|
137
|
+
errors.push({ file: r, type: "EMPTY", msg: "Empty file" });
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (c.length < 200) {
|
|
141
|
+
warnings.push({ file: r, type: "TOO_SHORT", msg: `Content is short (${c.length} chars)` });
|
|
142
|
+
}
|
|
143
|
+
// Language-aware ✅/❌ example detection (all 10 supported languages)
|
|
144
|
+
const goodKeywords = [
|
|
145
|
+
"✅", "Correct", "correct", "GOOD",
|
|
146
|
+
"올바른", // ko
|
|
147
|
+
"正确", // zh-CN
|
|
148
|
+
"正しい", // ja
|
|
149
|
+
"Correcto", // es
|
|
150
|
+
"Đúng", // vi
|
|
151
|
+
"सही", // hi
|
|
152
|
+
"Правильн", // ru
|
|
153
|
+
"Correct", // fr (same as en)
|
|
154
|
+
"Richtig", // de
|
|
155
|
+
];
|
|
156
|
+
const badKeywords = [
|
|
157
|
+
"❌", "Incorrect", "incorrect", "BAD",
|
|
158
|
+
"잘못된", // ko
|
|
159
|
+
"错误", // zh-CN
|
|
160
|
+
"誤った", // ja
|
|
161
|
+
"Incorrecto", // es
|
|
162
|
+
"Sai", // vi
|
|
163
|
+
"गलत", // hi
|
|
164
|
+
"Неправильн", // ru
|
|
165
|
+
"Incorrect", // fr (same as en)
|
|
166
|
+
"Falsch", // de
|
|
167
|
+
];
|
|
168
|
+
if (!goodKeywords.some(kw => c.includes(kw))) {
|
|
169
|
+
warnings.push({ file: r, type: "NO_GOOD_EXAMPLE", msg: "No correct example (✅) found" });
|
|
170
|
+
}
|
|
171
|
+
if (!badKeywords.some(kw => c.includes(kw))) {
|
|
172
|
+
warnings.push({ file: r, type: "NO_BAD_EXAMPLE", msg: "No incorrect example (❌) found" });
|
|
173
|
+
}
|
|
174
|
+
// Check for markdown table: at least one line with | col | col | pattern
|
|
175
|
+
const hasMarkdownTable = /\|.+\|.+\|/.test(c);
|
|
176
|
+
if (!hasMarkdownTable) {
|
|
177
|
+
warnings.push({ file: r, type: "NO_TABLE", msg: "Rules summary table appears to be missing" });
|
|
178
|
+
}
|
|
179
|
+
// Kotlin code block check: backend standard files should contain ```kotlin blocks
|
|
180
|
+
// (core files excluded for multi-stack projects where core may cover frontend too)
|
|
181
|
+
if (detectedLanguage === "kotlin") {
|
|
182
|
+
const kotlinRequiredPaths = ["backend-api", "30.security-db"];
|
|
183
|
+
const kotlinOptionalPaths = ["00.core/02.", "00.core/03."];
|
|
184
|
+
const isRequired = kotlinRequiredPaths.some(p => r.includes(p));
|
|
185
|
+
const isOptional = kotlinOptionalPaths.some(p => r.includes(p));
|
|
186
|
+
if (isRequired || isOptional) {
|
|
187
|
+
if (!c.includes("```kotlin") && !c.includes("```kt")) {
|
|
188
|
+
if (isRequired) {
|
|
189
|
+
warnings.push({ file: r, type: "NO_KOTLIN_BLOCK", msg: "No ```kotlin code block found (expected for Kotlin project)" });
|
|
190
|
+
}
|
|
191
|
+
// optional paths: skip warning (core files may legitimately lack kotlin blocks in multi-stack)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
console.log(` ${stdFiles.length} files checked`);
|
|
197
|
+
} else {
|
|
198
|
+
errors.push({ file: "claudeos-core/standard/", type: "MISSING", msg: "standard directory not found" });
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ─── 4. claudeos-core/skills/** ────────────────────────
|
|
202
|
+
console.log(" [4/9] claudeos-core/skills/...");
|
|
203
|
+
if (fs.existsSync(SKILLS_DIR)) {
|
|
204
|
+
const skillFiles = await glob("**/*.md", { cwd: SKILLS_DIR, absolute: true });
|
|
205
|
+
checked += skillFiles.length;
|
|
206
|
+
for (const f of skillFiles) {
|
|
207
|
+
const c = fs.readFileSync(f, "utf-8");
|
|
208
|
+
if (c.trim().length === 0) {
|
|
209
|
+
errors.push({ file: rel(f), type: "EMPTY", msg: "Empty file" });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// Check orchestrator existence
|
|
213
|
+
const orchestrators = skillFiles.filter(f => f.includes("01.scaffold") || f.includes("MANIFEST"));
|
|
214
|
+
if (orchestrators.length === 0) {
|
|
215
|
+
warnings.push({ file: "claudeos-core/skills/", type: "NO_ORCHESTRATOR", msg: "No orchestrator or MANIFEST found" });
|
|
216
|
+
}
|
|
217
|
+
console.log(` ${skillFiles.length} files checked (${orchestrators.length} orchestrators)`);
|
|
218
|
+
} else {
|
|
219
|
+
errors.push({ file: "claudeos-core/skills/", type: "MISSING", msg: "skills directory not found" });
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ─── 5. claudeos-core/guide/** ─────────────────────────
|
|
223
|
+
console.log(" [5/9] claudeos-core/guide/...");
|
|
224
|
+
const expectedGuides = EXPECTED_GUIDE_FILES;
|
|
225
|
+
if (fs.existsSync(GUIDE_DIR)) {
|
|
226
|
+
for (const g of expectedGuides) {
|
|
227
|
+
const gp = path.join(GUIDE_DIR, g);
|
|
228
|
+
checked++;
|
|
229
|
+
if (!fs.existsSync(gp)) {
|
|
230
|
+
errors.push({ file: `claudeos-core/guide/${g}`, type: "MISSING", msg: "Guide file not generated" });
|
|
231
|
+
} else {
|
|
232
|
+
const c = fs.readFileSync(gp, "utf-8");
|
|
233
|
+
if (c.trim().length === 0) {
|
|
234
|
+
errors.push({ file: `claudeos-core/guide/${g}`, type: "EMPTY", msg: "Empty file" });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
console.log(` ${expectedGuides.filter(g => fs.existsSync(path.join(GUIDE_DIR, g))).length} of ${expectedGuides.length} expected files exist`);
|
|
239
|
+
} else {
|
|
240
|
+
errors.push({ file: "claudeos-core/guide/", type: "MISSING", msg: "guide directory not found" });
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ─── 6. claudeos-core/plan/** ──────────────────────────
|
|
244
|
+
console.log(" [6/9] claudeos-core/plan/...");
|
|
245
|
+
if (fs.existsSync(PLAN_DIR)) {
|
|
246
|
+
const planFiles = await glob("*.md", { cwd: PLAN_DIR, absolute: true });
|
|
247
|
+
for (const f of planFiles) {
|
|
248
|
+
checked++;
|
|
249
|
+
const c = fs.readFileSync(f, "utf-8");
|
|
250
|
+
if (c.trim().length === 0) {
|
|
251
|
+
errors.push({ file: rel(f), type: "EMPTY", msg: "Empty plan file" });
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
// Must contain <file> blocks (sync-rules-master uses code block format)
|
|
255
|
+
const bn = path.basename(f);
|
|
256
|
+
if (!bn.includes("sync")) {
|
|
257
|
+
if (!c.includes("<file") && !c.includes("```")) {
|
|
258
|
+
warnings.push({ file: rel(f), type: "NO_FILE_BLOCKS", msg: "No <file> blocks or code blocks found" });
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
console.log(` ${planFiles.length} files checked`);
|
|
263
|
+
} else {
|
|
264
|
+
errors.push({ file: "claudeos-core/plan/", type: "MISSING", msg: "plan directory not found" });
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ─── 7. claudeos-core/database/** ──────────────────────
|
|
268
|
+
console.log(" [7/9] claudeos-core/database/...");
|
|
269
|
+
if (fs.existsSync(DB_DIR)) {
|
|
270
|
+
const dbFiles = await glob("**/*.md", { cwd: DB_DIR, absolute: true });
|
|
271
|
+
checked += dbFiles.length;
|
|
272
|
+
if (dbFiles.length === 0) {
|
|
273
|
+
warnings.push({ file: "claudeos-core/database/", type: "NO_FILES", msg: "No database files found" });
|
|
274
|
+
}
|
|
275
|
+
for (const f of dbFiles) {
|
|
276
|
+
const c = fs.readFileSync(f, "utf-8");
|
|
277
|
+
if (c.trim().length === 0) {
|
|
278
|
+
errors.push({ file: rel(f), type: "EMPTY", msg: "Empty file" });
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
console.log(` ${dbFiles.length} files`);
|
|
282
|
+
} else {
|
|
283
|
+
warnings.push({ file: "claudeos-core/database/", type: "MISSING", msg: "database directory not found" });
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ─── 8. claudeos-core/mcp-guide/** ─────────────────────
|
|
287
|
+
console.log(" [8/9] claudeos-core/mcp-guide/...");
|
|
288
|
+
if (fs.existsSync(MCP_DIR)) {
|
|
289
|
+
const mcpFiles = await glob("**/*.md", { cwd: MCP_DIR, absolute: true });
|
|
290
|
+
checked += mcpFiles.length;
|
|
291
|
+
if (mcpFiles.length === 0) {
|
|
292
|
+
warnings.push({ file: "claudeos-core/mcp-guide/", type: "NO_FILES", msg: "No mcp-guide files found" });
|
|
293
|
+
}
|
|
294
|
+
for (const f of mcpFiles) {
|
|
295
|
+
const c = fs.readFileSync(f, "utf-8");
|
|
296
|
+
if (c.trim().length === 0) {
|
|
297
|
+
errors.push({ file: rel(f), type: "EMPTY", msg: "Empty file" });
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
console.log(` ${mcpFiles.length} files`);
|
|
301
|
+
} else {
|
|
302
|
+
warnings.push({ file: "claudeos-core/mcp-guide/", type: "MISSING", msg: "mcp-guide directory not found" });
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ─── 9. claudeos-core/memory/ (L4) ─────────────────────
|
|
306
|
+
console.log(" [9/9] claudeos-core/memory/...");
|
|
307
|
+
if (fs.existsSync(MEMORY_DIR)) {
|
|
308
|
+
for (const name of EXPECTED_MEMORY) {
|
|
309
|
+
const fp = path.join(MEMORY_DIR, name);
|
|
310
|
+
checked++;
|
|
311
|
+
if (!fs.existsSync(fp)) {
|
|
312
|
+
errors.push({ file: `claudeos-core/memory/${name}`, type: "MISSING", msg: "Memory file not scaffolded" });
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
const c = fs.readFileSync(fp, "utf-8");
|
|
316
|
+
if (c.trim().length === 0) {
|
|
317
|
+
errors.push({ file: `claudeos-core/memory/${name}`, type: "EMPTY", msg: "Empty memory file" });
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
if (c.trim().length < 50) {
|
|
321
|
+
warnings.push({ file: `claudeos-core/memory/${name}`, type: "TOO_SHORT", msg: `Memory file too short (${c.trim().length} chars)` });
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ─── Structural validation (v2 — prevents silent failures in memory CLI) ───
|
|
325
|
+
if (name === "decision-log.md") {
|
|
326
|
+
// Entries must start with `## YYYY-MM-DD — <title>` format when present.
|
|
327
|
+
// Empty (header-only) seed is allowed.
|
|
328
|
+
// Fence-aware: ignore `## ...` lines inside ```...``` / ~~~...~~~ so
|
|
329
|
+
// example markdown inside a decision's body text isn't flagged.
|
|
330
|
+
const lines = c.split("\n");
|
|
331
|
+
const entryHeadings = [];
|
|
332
|
+
let inFence = false;
|
|
333
|
+
const FENCE_RE = /^(```|~~~)/;
|
|
334
|
+
for (const line of lines) {
|
|
335
|
+
if (FENCE_RE.test(line)) { inFence = !inFence; continue; }
|
|
336
|
+
if (!inFence && /^##\s+.+$/.test(line)) entryHeadings.push(line);
|
|
337
|
+
}
|
|
338
|
+
for (const h of entryHeadings) {
|
|
339
|
+
if (!/^##\s+\d{4}-\d{2}-\d{2}/.test(h)) {
|
|
340
|
+
warnings.push({
|
|
341
|
+
file: `claudeos-core/memory/${name}`,
|
|
342
|
+
type: "MALFORMED_ENTRY",
|
|
343
|
+
msg: `Heading does not start with ISO date: ${h.slice(0, 60)}`,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
} else if (name === "failure-patterns.md") {
|
|
348
|
+
// Each entry should have frequency + last seen + fix/solution fields.
|
|
349
|
+
// Parse entries and flag any that miss required fields (warning, not error).
|
|
350
|
+
// Fence-aware: ignore `## ...` lines inside ```...``` or ~~~...~~~
|
|
351
|
+
// so example markdown inside a Fix body is not treated as an entry.
|
|
352
|
+
const lines = c.split("\n");
|
|
353
|
+
let curId = null;
|
|
354
|
+
let curBody = [];
|
|
355
|
+
const entries = [];
|
|
356
|
+
let inFence = false;
|
|
357
|
+
const FENCE_RE = /^(```|~~~)/;
|
|
358
|
+
for (const line of lines) {
|
|
359
|
+
if (FENCE_RE.test(line)) inFence = !inFence;
|
|
360
|
+
if (!inFence && /^##\s+/.test(line)) {
|
|
361
|
+
if (curId !== null) entries.push({ id: curId, body: curBody.join("\n") });
|
|
362
|
+
curId = line.replace(/^##\s+/, "").trim();
|
|
363
|
+
curBody = [];
|
|
364
|
+
} else if (curId !== null) {
|
|
365
|
+
curBody.push(line);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (curId !== null) entries.push({ id: curId, body: curBody.join("\n") });
|
|
369
|
+
for (const e of entries) {
|
|
370
|
+
// Accept both plain (`- frequency:`) and bold (`- **frequency**:`)
|
|
371
|
+
// markdown — the memory CLI's parseField matches both since v2.0.
|
|
372
|
+
const hasFreq = /\b(?:frequency|count)\*{0,2}\s*[:=]/i.test(e.body);
|
|
373
|
+
const hasLastSeen = /\blast\s*seen\*{0,2}\s*[:=]?/i.test(e.body) || /^\d{4}-\d{2}-\d{2}/.test(e.id);
|
|
374
|
+
// Fix/solution must appear as a field line, not just any word —
|
|
375
|
+
// otherwise a verbose line containing "fix" or "prefix" would
|
|
376
|
+
// falsely satisfy the check.
|
|
377
|
+
const hasFix = /^\s*-\s*\*{0,2}\s*(?:fix|solution)\*{0,2}\s*[:=]/im.test(e.body);
|
|
378
|
+
const missing = [];
|
|
379
|
+
if (!hasFreq) missing.push("frequency");
|
|
380
|
+
if (!hasLastSeen) missing.push("last seen");
|
|
381
|
+
if (!hasFix) missing.push("fix/solution");
|
|
382
|
+
if (missing.length > 0) {
|
|
383
|
+
warnings.push({
|
|
384
|
+
file: `claudeos-core/memory/${name}`,
|
|
385
|
+
type: "MALFORMED_ENTRY",
|
|
386
|
+
msg: `Entry "${e.id.slice(0, 40)}" missing: ${missing.join(", ")} (memory CLI may skip it)`,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
} else if (name === "compaction.md") {
|
|
391
|
+
// CLI-parsed marker `## Last Compaction` must exist (memory compact looks for it).
|
|
392
|
+
if (!c.includes("## Last Compaction")) {
|
|
393
|
+
warnings.push({
|
|
394
|
+
file: `claudeos-core/memory/${name}`,
|
|
395
|
+
type: "MISSING_MARKER",
|
|
396
|
+
msg: "`## Last Compaction` section missing (memory compact will append instead of update)",
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
console.log(` ${EXPECTED_MEMORY.filter(n => fs.existsSync(path.join(MEMORY_DIR, n))).length} of ${EXPECTED_MEMORY.length} expected files exist`);
|
|
402
|
+
} else {
|
|
403
|
+
warnings.push({ file: "claudeos-core/memory/", type: "MISSING", msg: "memory directory not found (run pass 4)" });
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// ─── Output results ─────────────────────────────────────────
|
|
407
|
+
console.log(`\n Checked ${checked} files\n`);
|
|
408
|
+
if (errors.length) {
|
|
409
|
+
console.log(` ❌ ERRORS (${errors.length}):`);
|
|
410
|
+
errors.forEach(e => console.log(` [${e.type}] ${e.file}: ${e.msg}`));
|
|
411
|
+
console.log();
|
|
412
|
+
}
|
|
413
|
+
if (warnings.length) {
|
|
414
|
+
console.log(` ⚠️ WARNINGS (${warnings.length}):`);
|
|
415
|
+
warnings.forEach(w => console.log(` [${w.type}] ${w.file}: ${w.msg}`));
|
|
416
|
+
console.log();
|
|
417
|
+
}
|
|
418
|
+
if (!errors.length && !warnings.length) {
|
|
419
|
+
console.log(" ✅ All content validation passed\n");
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Record in stale-report
|
|
423
|
+
updateStaleReport(GEN_DIR, "contentValidation",
|
|
424
|
+
{ checkedAt: new Date().toISOString(), checked, errors: errors.length, warnings: warnings.length, details: { errors, warnings } },
|
|
425
|
+
{ contentErrors: errors.length, contentWarnings: warnings.length }
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
console.log(` Total: ${errors.length} errors, ${warnings.length} warnings\n`);
|
|
429
|
+
process.exit(errors.length > 0 ? 1 : 0);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (require.main === module) {
|
|
433
|
+
main().catch(e => { console.error(`\n ❌ Unexpected error: ${e.message || e}`); process.exit(1); });
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
module.exports = { main };
|