claudeos-core 1.0.7 → 1.2.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.
- package/CHANGELOG.md +84 -1
- package/CONTRIBUTING.md +15 -4
- package/README.de.md +187 -11
- package/README.es.md +187 -11
- package/README.fr.md +187 -11
- package/README.hi.md +187 -11
- package/README.ja.md +186 -10
- package/README.ko.md +331 -364
- package/README.md +200 -11
- package/README.ru.md +187 -11
- package/README.vi.md +188 -12
- package/README.zh-CN.md +186 -10
- package/bin/cli.js +183 -61
- package/bootstrap.sh +128 -21
- package/content-validator/index.js +131 -60
- package/health-checker/index.js +29 -23
- package/import-linter/index.js +14 -8
- package/manifest-generator/index.js +26 -20
- package/package.json +84 -75
- package/pass-json-validator/index.js +92 -70
- package/pass-prompts/templates/common/header.md +4 -4
- package/pass-prompts/templates/common/lang-instructions.json +27 -0
- package/pass-prompts/templates/common/pass3-footer.md +2 -3
- package/pass-prompts/templates/java-spring/pass1.md +84 -81
- package/pass-prompts/templates/java-spring/pass2.md +66 -66
- package/pass-prompts/templates/java-spring/pass3.md +60 -60
- package/pass-prompts/templates/kotlin-spring/pass1.md +172 -0
- package/pass-prompts/templates/kotlin-spring/pass2.md +109 -0
- package/pass-prompts/templates/kotlin-spring/pass3.md +98 -0
- package/pass-prompts/templates/node-express/pass1.md +73 -73
- package/pass-prompts/templates/node-express/pass2.md +66 -66
- package/pass-prompts/templates/node-express/pass3.md +53 -53
- package/pass-prompts/templates/node-nextjs/pass1.md +68 -68
- package/pass-prompts/templates/node-nextjs/pass2.md +61 -61
- package/pass-prompts/templates/node-nextjs/pass3.md +48 -48
- package/pass-prompts/templates/python-django/pass1.md +78 -78
- package/pass-prompts/templates/python-django/pass2.md +69 -69
- package/pass-prompts/templates/python-django/pass3.md +45 -45
- package/pass-prompts/templates/python-fastapi/pass1.md +76 -76
- package/pass-prompts/templates/python-fastapi/pass2.md +67 -67
- package/pass-prompts/templates/python-fastapi/pass3.md +45 -45
- package/plan-installer/index.js +623 -97
- package/plan-validator/index.js +54 -23
- package/sync-checker/index.js +25 -14
package/bootstrap.sh
CHANGED
|
@@ -2,10 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
# ClaudeOS-Core — Bootstrap (3-Pass Auto)
|
|
4
4
|
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
5
|
+
# One-click full system auto-build
|
|
6
|
+
# Automatically splits Pass 1 into N runs based on project size
|
|
7
7
|
#
|
|
8
|
-
#
|
|
8
|
+
# Usage: bash claudeos-core-tools/bootstrap.sh --lang ko
|
|
9
|
+
# bash claudeos-core-tools/bootstrap.sh (interactive)
|
|
10
|
+
|
|
11
|
+
# Require bash (not sh/dash) — script uses bash arrays and [[ ]]
|
|
12
|
+
if [ -z "$BASH_VERSION" ]; then
|
|
13
|
+
echo "❌ This script requires bash. Run with: bash $0" >&2
|
|
14
|
+
exit 1
|
|
15
|
+
fi
|
|
9
16
|
|
|
10
17
|
set -e
|
|
11
18
|
|
|
@@ -16,9 +23,63 @@ GENERATED_DIR="$PROJECT_ROOT/claudeos-core/generated"
|
|
|
16
23
|
|
|
17
24
|
cd "$PROJECT_ROOT"
|
|
18
25
|
|
|
19
|
-
# ───
|
|
26
|
+
# ─── Language selection (required) ──────────────────────────────
|
|
27
|
+
SUPPORTED_LANGS=("en" "ko" "zh-CN" "ja" "es" "vi" "hi" "ru" "fr" "de")
|
|
28
|
+
LANG_LABELS=("English" "한국어 (Korean)" "简体中文 (Chinese Simplified)" "日本語 (Japanese)" "Español (Spanish)" "Tiếng Việt (Vietnamese)" "हिन्दी (Hindi)" "Русский (Russian)" "Français (French)" "Deutsch (German)")
|
|
29
|
+
|
|
30
|
+
CLAUDEOS_LANG=""
|
|
31
|
+
|
|
32
|
+
# Parse --lang argument
|
|
33
|
+
while [[ $# -gt 0 ]]; do
|
|
34
|
+
case $1 in
|
|
35
|
+
--lang) CLAUDEOS_LANG="$2"; shift 2 ;;
|
|
36
|
+
--lang=*) CLAUDEOS_LANG="${1#*=}"; shift ;;
|
|
37
|
+
*) echo "⚠️ Unknown argument: $1 (ignored)"; shift ;;
|
|
38
|
+
esac
|
|
39
|
+
done
|
|
40
|
+
|
|
41
|
+
# Interactive selection if --lang not provided
|
|
42
|
+
if [ -z "$CLAUDEOS_LANG" ]; then
|
|
43
|
+
echo ""
|
|
44
|
+
echo "╔══════════════════════════════════════════════════╗"
|
|
45
|
+
echo "║ Select output language (required) ║"
|
|
46
|
+
echo "╚══════════════════════════════════════════════════╝"
|
|
47
|
+
echo ""
|
|
48
|
+
for i in "${!SUPPORTED_LANGS[@]}"; do
|
|
49
|
+
printf " %2d. %-6s — %s\n" "$((i+1))" "${SUPPORTED_LANGS[$i]}" "${LANG_LABELS[$i]}"
|
|
50
|
+
done
|
|
51
|
+
echo ""
|
|
52
|
+
read -p " Enter number (1-10) or language code: " LANG_INPUT
|
|
53
|
+
|
|
54
|
+
# Accept number
|
|
55
|
+
if [[ "$LANG_INPUT" =~ ^[0-9]+$ ]] && [ "$LANG_INPUT" -ge 1 ] && [ "$LANG_INPUT" -le 10 ]; then
|
|
56
|
+
CLAUDEOS_LANG="${SUPPORTED_LANGS[$((LANG_INPUT-1))]}"
|
|
57
|
+
else
|
|
58
|
+
# Accept language code
|
|
59
|
+
CLAUDEOS_LANG="$LANG_INPUT"
|
|
60
|
+
fi
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# Validate
|
|
64
|
+
VALID=false
|
|
65
|
+
for l in "${SUPPORTED_LANGS[@]}"; do
|
|
66
|
+
if [ "$l" = "$CLAUDEOS_LANG" ]; then VALID=true; break; fi
|
|
67
|
+
done
|
|
68
|
+
if [ "$VALID" = false ]; then
|
|
69
|
+
echo ""
|
|
70
|
+
printf ' ❌ Unsupported language: "%s"\n' "$CLAUDEOS_LANG"
|
|
71
|
+
echo " Supported: ${SUPPORTED_LANGS[*]}"
|
|
72
|
+
echo ""
|
|
73
|
+
exit 1
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
export CLAUDEOS_LANG
|
|
77
|
+
|
|
78
|
+
# ─── Prompt placeholder substitution helper ────────────────────
|
|
20
79
|
inject_project_root() {
|
|
21
|
-
|
|
80
|
+
# Use perl with $ENV{} to avoid sed metachar issues with &, \, etc. in paths
|
|
81
|
+
export _PROJECT_ROOT="$PROJECT_ROOT"
|
|
82
|
+
perl -pe 's/\{\{PROJECT_ROOT\}\}/$ENV{_PROJECT_ROOT}/g'
|
|
22
83
|
}
|
|
23
84
|
|
|
24
85
|
echo ""
|
|
@@ -26,21 +87,28 @@ echo "╔═══════════════════════
|
|
|
26
87
|
echo "║ ClaudeOS-Core — Bootstrap (3-Pass) ║"
|
|
27
88
|
echo "╚════════════════════════════════════════════════════╝"
|
|
28
89
|
echo " Project root: $PROJECT_ROOT"
|
|
90
|
+
# Find label for selected lang
|
|
91
|
+
for i in "${!SUPPORTED_LANGS[@]}"; do
|
|
92
|
+
if [ "${SUPPORTED_LANGS[$i]}" = "$CLAUDEOS_LANG" ]; then
|
|
93
|
+
echo " Language: ${LANG_LABELS[$i]} ($CLAUDEOS_LANG)"
|
|
94
|
+
break
|
|
95
|
+
fi
|
|
96
|
+
done
|
|
29
97
|
echo ""
|
|
30
98
|
|
|
31
|
-
# ─── [1]
|
|
99
|
+
# ─── [1] Install dependencies ────────────────────────────────────
|
|
32
100
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
33
101
|
echo "[1] Installing dependencies..."
|
|
34
102
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
35
103
|
cd "$TOOLS_DIR"
|
|
36
104
|
if [ ! -d "node_modules" ]; then
|
|
37
|
-
npm install --silent
|
|
105
|
+
npm install --silent
|
|
38
106
|
fi
|
|
39
107
|
cd "$PROJECT_ROOT"
|
|
40
108
|
echo " ✅ Done"
|
|
41
109
|
echo ""
|
|
42
110
|
|
|
43
|
-
# ─── [2]
|
|
111
|
+
# ─── [2] Create directory structure ─────────────────────────────
|
|
44
112
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
45
113
|
echo "[2] Creating directory structure..."
|
|
46
114
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
@@ -55,25 +123,31 @@ mkdir -p claudeos-core/mcp-guide
|
|
|
55
123
|
echo " ✅ Done"
|
|
56
124
|
echo ""
|
|
57
125
|
|
|
58
|
-
# ─── [3] plan-installer
|
|
126
|
+
# ─── [3] Run plan-installer (project analysis) ────────────
|
|
59
127
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
60
128
|
echo "[3] Analyzing project (plan-installer)..."
|
|
61
129
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
62
130
|
node "$TOOLS_DIR/plan-installer/index.js"
|
|
63
131
|
echo ""
|
|
64
132
|
|
|
65
|
-
# ─── [4] Pass 1:
|
|
133
|
+
# ─── [4] Pass 1: Deep analysis per domain group (multi-stack) ──────────
|
|
66
134
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
67
135
|
echo "[4] Pass 1 — Deep analysis per domain group..."
|
|
68
136
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
69
137
|
|
|
70
|
-
# domain-groups.json
|
|
138
|
+
# Read groups from domain-groups.json
|
|
71
139
|
TOTAL_GROUPS=$(node -e "
|
|
72
140
|
const g = require(process.argv[1]);
|
|
73
141
|
console.log(g.totalGroups);
|
|
74
142
|
" "$GENERATED_DIR/domain-groups.json")
|
|
75
143
|
|
|
76
|
-
|
|
144
|
+
# Validate TOTAL_GROUPS is a positive integer
|
|
145
|
+
if ! [[ "$TOTAL_GROUPS" =~ ^[0-9]+$ ]] || [ "$TOTAL_GROUPS" -lt 1 ]; then
|
|
146
|
+
echo " ❌ Invalid TOTAL_GROUPS: '${TOTAL_GROUPS}'. domain-groups.json may be malformed."
|
|
147
|
+
exit 1
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
for i in $(seq 1 "$TOTAL_GROUPS"); do
|
|
77
151
|
DOMAIN_LIST=$(node -e "
|
|
78
152
|
const g = require(process.argv[1]);
|
|
79
153
|
console.log(g.groups[process.argv[2] - 1].domains.join(', '));
|
|
@@ -90,28 +164,37 @@ for i in $(seq 1 $TOTAL_GROUPS); do
|
|
|
90
164
|
echo ""
|
|
91
165
|
echo " [Pass 1-${i}/${TOTAL_GROUPS}] (${GROUP_TYPE}) Analyzing: ${DOMAIN_LIST} (~${EST_FILES} files)"
|
|
92
166
|
|
|
93
|
-
#
|
|
167
|
+
# Skip already completed passes
|
|
94
168
|
if [ -f "$GENERATED_DIR/pass1-${i}.json" ]; then
|
|
95
169
|
echo " ⏭️ pass1-${i}.json already exists, skipping"
|
|
96
170
|
continue
|
|
97
171
|
fi
|
|
98
172
|
|
|
99
|
-
#
|
|
173
|
+
# Select prompt by type
|
|
100
174
|
PROMPT_FILE="$GENERATED_DIR/pass1-${GROUP_TYPE}-prompt.md"
|
|
101
175
|
if [ ! -f "$PROMPT_FILE" ]; then
|
|
102
176
|
PROMPT_FILE="$GENERATED_DIR/pass1-prompt.md"
|
|
103
177
|
fi
|
|
104
178
|
PASS1_TEMPLATE=$(cat "$PROMPT_FILE")
|
|
105
179
|
|
|
106
|
-
#
|
|
107
|
-
|
|
180
|
+
# Substitute placeholders via temp file (avoids sed special char issues with &, \, etc.)
|
|
181
|
+
TMP_PROMPT="$GENERATED_DIR/_tmp_pass1_prompt.md"
|
|
182
|
+
printf '%s' "$PASS1_TEMPLATE" > "$TMP_PROMPT"
|
|
183
|
+
# Use perl with $ENV{} for safe literal replacement (no shell interpolation into Perl code)
|
|
184
|
+
export _DOMAIN_LIST="$DOMAIN_LIST"
|
|
185
|
+
export _PASS_NUM="$i"
|
|
186
|
+
perl -pi -e 's/\{\{DOMAIN_GROUP\}\}/$ENV{_DOMAIN_LIST}/g' "$TMP_PROMPT"
|
|
187
|
+
perl -pi -e 's/\{\{PASS_NUM\}\}/$ENV{_PASS_NUM}/g' "$TMP_PROMPT"
|
|
188
|
+
# inject_project_root uses perl with $ENV{} which is safe for special chars in paths
|
|
189
|
+
PASS1_PROMPT=$(cat "$TMP_PROMPT" | inject_project_root)
|
|
190
|
+
rm -f "$TMP_PROMPT"
|
|
108
191
|
|
|
109
192
|
if ! (cd "$PROJECT_ROOT" && claude -p "$PASS1_PROMPT" --dangerously-skip-permissions); then
|
|
110
193
|
echo " ❌ Pass 1-${i} failed. Aborting."
|
|
111
194
|
exit 1
|
|
112
195
|
fi
|
|
113
196
|
|
|
114
|
-
# JSON
|
|
197
|
+
# Verify JSON was created
|
|
115
198
|
if [ ! -f "$GENERATED_DIR/pass1-${i}.json" ]; then
|
|
116
199
|
echo " ❌ pass1-${i}.json was not created. Aborting."
|
|
117
200
|
exit 1
|
|
@@ -121,7 +204,7 @@ for i in $(seq 1 $TOTAL_GROUPS); do
|
|
|
121
204
|
done
|
|
122
205
|
echo ""
|
|
123
206
|
|
|
124
|
-
# ─── [5] Pass 2:
|
|
207
|
+
# ─── [5] Pass 2: Merge analysis results ─────────────────────────
|
|
125
208
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
126
209
|
echo "[5] Pass 2 — Merging analysis results..."
|
|
127
210
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
@@ -145,7 +228,7 @@ else
|
|
|
145
228
|
fi
|
|
146
229
|
echo ""
|
|
147
230
|
|
|
148
|
-
# ─── [6] Pass 3:
|
|
231
|
+
# ─── [6] Pass 3: Generate + Master Plan build + verification ────────
|
|
149
232
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
150
233
|
echo "[6] Pass 3 — Generating all files..."
|
|
151
234
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
@@ -162,7 +245,31 @@ if [ ! -f "$PROJECT_ROOT/CLAUDE.md" ]; then
|
|
|
162
245
|
fi
|
|
163
246
|
echo ""
|
|
164
247
|
|
|
165
|
-
# ───
|
|
248
|
+
# ─── [7] Run verification tools ───────────────────────────────
|
|
249
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
250
|
+
echo "[7] Running verification tools..."
|
|
251
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
252
|
+
|
|
253
|
+
if [ -f "$TOOLS_DIR/manifest-generator/index.js" ]; then
|
|
254
|
+
echo -n " ⏳ manifest-generator..."
|
|
255
|
+
if node "$TOOLS_DIR/manifest-generator/index.js" > /dev/null 2>&1; then
|
|
256
|
+
echo " ✅"
|
|
257
|
+
else
|
|
258
|
+
echo " ❌ (non-fatal)"
|
|
259
|
+
fi
|
|
260
|
+
fi
|
|
261
|
+
|
|
262
|
+
if [ -f "$TOOLS_DIR/health-checker/index.js" ]; then
|
|
263
|
+
echo -n " ⏳ health-checker..."
|
|
264
|
+
if node "$TOOLS_DIR/health-checker/index.js" > /dev/null 2>&1; then
|
|
265
|
+
echo " ✅"
|
|
266
|
+
else
|
|
267
|
+
echo " ⚠️ issues found (non-fatal)"
|
|
268
|
+
fi
|
|
269
|
+
fi
|
|
270
|
+
echo ""
|
|
271
|
+
|
|
272
|
+
# ─── Complete ───────────────────────────────────────────────
|
|
166
273
|
TOTAL_FILES=$(find .claude claudeos-core -type f 2>/dev/null | grep -v node_modules | wc -l | tr -d ' ')
|
|
167
274
|
TOTAL_GROUPS_DONE=$TOTAL_GROUPS
|
|
168
275
|
PASS1_FILES=$(ls -1 "$GENERATED_DIR"/pass1-*.json 2>/dev/null | wc -l | tr -d ' ')
|
|
@@ -179,6 +286,6 @@ echo "║ Verify anytime: ║"
|
|
|
179
286
|
echo "║ node claudeos-core-tools/health-checker/index.js ║"
|
|
180
287
|
echo "║ ║"
|
|
181
288
|
echo "║ Start using: ║"
|
|
182
|
-
echo "║ \"CRUD
|
|
289
|
+
echo "║ \"Create a CRUD for orders\" ║"
|
|
183
290
|
echo "╚════════════════════════════════════════════════════╝"
|
|
184
291
|
echo ""
|
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* ClaudeOS-Core — Content Validator
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* -
|
|
9
|
-
* - standard
|
|
10
|
-
* - rules
|
|
11
|
-
* - CLAUDE.md
|
|
12
|
-
* -
|
|
13
|
-
* -
|
|
14
|
-
* - database/, mcp-guide/
|
|
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: frontmatter + @import
|
|
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
15
|
*
|
|
16
|
-
*
|
|
16
|
+
* Usage: npx claudeos-core <cmd> or node claudeos-core-tools/content-validator/index.js
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
const fs = require("fs");
|
|
@@ -41,21 +41,51 @@ async function main() {
|
|
|
41
41
|
const warnings = [];
|
|
42
42
|
let checked = 0;
|
|
43
43
|
|
|
44
|
+
// ─── Detect language and stack from project-analysis.json ────────
|
|
45
|
+
let detectedLanguage = null;
|
|
46
|
+
let outputLang = "en";
|
|
47
|
+
const paPath = path.join(GEN_DIR, "project-analysis.json");
|
|
48
|
+
if (fs.existsSync(paPath)) {
|
|
49
|
+
try {
|
|
50
|
+
const pa = JSON.parse(fs.readFileSync(paPath, "utf-8"));
|
|
51
|
+
detectedLanguage = pa.stack?.language || null;
|
|
52
|
+
outputLang = pa.lang || "en";
|
|
53
|
+
} catch { /* ignore */ }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Language-aware section keywords for CLAUDE.md validation
|
|
57
|
+
const SECTION_KEYWORDS = {
|
|
58
|
+
en: ["Role", "Build", "Run", "Standard", "Skills"],
|
|
59
|
+
ko: ["역할", "빌드", "실행", "표준", "스킬"],
|
|
60
|
+
"zh-CN": ["角色", "构建", "运行", "标准", "技能"],
|
|
61
|
+
ja: ["役割", "ビルド", "実行", "標準", "スキル"],
|
|
62
|
+
es: ["Rol", "Compilar", "Ejecutar", "Estándar", "Habilidades"],
|
|
63
|
+
vi: ["Vai trò", "Build", "Chạy", "Tiêu chuẩn", "Kỹ năng"],
|
|
64
|
+
hi: ["भूमिका", "बिल्ड", "रन", "मानक", "कौशल"],
|
|
65
|
+
ru: ["Роль", "Сборка", "Запуск", "Стандарт", "Навыки"],
|
|
66
|
+
fr: ["Rôle", "Build", "Exécuter", "Standard", "Compétences"],
|
|
67
|
+
de: ["Rolle", "Build", "Ausführen", "Standard", "Fähigkeiten"],
|
|
68
|
+
};
|
|
69
|
+
|
|
44
70
|
// ─── 1. CLAUDE.md ──────────────────────────────────────
|
|
45
71
|
console.log(" [1/8] CLAUDE.md...");
|
|
46
72
|
const claudeMd = path.join(ROOT, "CLAUDE.md");
|
|
47
73
|
if (!fs.existsSync(claudeMd)) {
|
|
48
|
-
errors.push({ file: "CLAUDE.md", type: "MISSING", msg: "CLAUDE.md
|
|
74
|
+
errors.push({ file: "CLAUDE.md", type: "MISSING", msg: "CLAUDE.md does not exist" });
|
|
49
75
|
} else {
|
|
50
76
|
checked++;
|
|
51
77
|
const content = fs.readFileSync(claudeMd, "utf-8");
|
|
52
78
|
if (content.trim().length < 100) {
|
|
53
|
-
errors.push({ file: "CLAUDE.md", type: "EMPTY", msg: "CLAUDE.md
|
|
79
|
+
errors.push({ file: "CLAUDE.md", type: "EMPTY", msg: "CLAUDE.md content is too short (<100 chars)" });
|
|
54
80
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
81
|
+
// Check sections in both English (fallback) and output language
|
|
82
|
+
const langKeywords = SECTION_KEYWORDS[outputLang] || SECTION_KEYWORDS.en;
|
|
83
|
+
const enKeywords = SECTION_KEYWORDS.en;
|
|
84
|
+
for (let i = 0; i < enKeywords.length; i++) {
|
|
85
|
+
const candidates = [enKeywords[i], langKeywords[i]].filter(Boolean);
|
|
86
|
+
const found = candidates.some(kw => content.toLowerCase().includes(kw.toLowerCase()));
|
|
87
|
+
if (!found) {
|
|
88
|
+
warnings.push({ file: "CLAUDE.md", type: "MISSING_SECTION", msg: `'${enKeywords[i]}' / '${langKeywords[i]}' section is missing` });
|
|
59
89
|
}
|
|
60
90
|
}
|
|
61
91
|
}
|
|
@@ -69,22 +99,26 @@ async function main() {
|
|
|
69
99
|
const c = fs.readFileSync(f, "utf-8");
|
|
70
100
|
const r = rel(f);
|
|
71
101
|
if (c.trim().length === 0) {
|
|
72
|
-
errors.push({ file: r, type: "EMPTY", msg: "
|
|
102
|
+
errors.push({ file: r, type: "EMPTY", msg: "Empty file" });
|
|
73
103
|
continue;
|
|
74
104
|
}
|
|
75
|
-
// sync
|
|
105
|
+
// sync rules don't need paths
|
|
76
106
|
if (!r.includes("50.sync")) {
|
|
77
|
-
|
|
78
|
-
|
|
107
|
+
const hasFrontmatter = c.startsWith("---");
|
|
108
|
+
const hasPathsKey = c.includes("paths:");
|
|
109
|
+
if (!hasFrontmatter) {
|
|
110
|
+
warnings.push({ file: r, type: "NO_FRONTMATTER", msg: "Missing YAML frontmatter (---)" });
|
|
111
|
+
} else if (!hasPathsKey) {
|
|
112
|
+
warnings.push({ file: r, type: "NO_PATHS", msg: "Frontmatter exists but missing paths: key" });
|
|
79
113
|
}
|
|
80
114
|
if (!/^@[^\s]+/m.test(c)) {
|
|
81
|
-
warnings.push({ file: r, type: "NO_IMPORT", msg: "@import
|
|
115
|
+
warnings.push({ file: r, type: "NO_IMPORT", msg: "No @import reference found" });
|
|
82
116
|
}
|
|
83
117
|
}
|
|
84
118
|
}
|
|
85
|
-
console.log(` ${ruleFiles.length}
|
|
119
|
+
console.log(` ${ruleFiles.length} files checked`);
|
|
86
120
|
} else {
|
|
87
|
-
errors.push({ file: ".claude/rules/", type: "MISSING", msg: "rules
|
|
121
|
+
errors.push({ file: ".claude/rules/", type: "MISSING", msg: "rules directory not found" });
|
|
88
122
|
}
|
|
89
123
|
|
|
90
124
|
// ─── 3. claudeos-core/standard/** ─────────────────────
|
|
@@ -96,25 +130,59 @@ async function main() {
|
|
|
96
130
|
const c = fs.readFileSync(f, "utf-8");
|
|
97
131
|
const r = rel(f);
|
|
98
132
|
if (c.trim().length === 0) {
|
|
99
|
-
errors.push({ file: r, type: "EMPTY", msg: "
|
|
133
|
+
errors.push({ file: r, type: "EMPTY", msg: "Empty file" });
|
|
100
134
|
continue;
|
|
101
135
|
}
|
|
102
136
|
if (c.length < 200) {
|
|
103
|
-
warnings.push({ file: r, type: "TOO_SHORT", msg:
|
|
137
|
+
warnings.push({ file: r, type: "TOO_SHORT", msg: `Content is short (${c.length} chars)` });
|
|
104
138
|
}
|
|
105
|
-
|
|
106
|
-
|
|
139
|
+
// Language-aware ✅/❌ example detection (all 10 supported languages)
|
|
140
|
+
const goodKeywords = [
|
|
141
|
+
"✅", "Correct", "correct", "GOOD",
|
|
142
|
+
"올바른", // ko
|
|
143
|
+
"正确", // zh-CN
|
|
144
|
+
"正しい", // ja
|
|
145
|
+
"Correcto", // es
|
|
146
|
+
"Đúng", // vi
|
|
147
|
+
"सही", // hi
|
|
148
|
+
"Правильн", // ru
|
|
149
|
+
"Correct", // fr (same as en)
|
|
150
|
+
"Richtig", // de
|
|
151
|
+
];
|
|
152
|
+
const badKeywords = [
|
|
153
|
+
"❌", "Incorrect", "incorrect", "BAD",
|
|
154
|
+
"잘못된", // ko
|
|
155
|
+
"错误", // zh-CN
|
|
156
|
+
"誤った", // ja
|
|
157
|
+
"Incorrecto", // es
|
|
158
|
+
"Sai", // vi
|
|
159
|
+
"गलत", // hi
|
|
160
|
+
"Неправильн", // ru
|
|
161
|
+
"Incorrect", // fr (same as en)
|
|
162
|
+
"Falsch", // de
|
|
163
|
+
];
|
|
164
|
+
if (!goodKeywords.some(kw => c.includes(kw))) {
|
|
165
|
+
warnings.push({ file: r, type: "NO_GOOD_EXAMPLE", msg: "No correct example (✅) found" });
|
|
107
166
|
}
|
|
108
|
-
if (!
|
|
109
|
-
warnings.push({ file: r, type: "NO_BAD_EXAMPLE", msg: "
|
|
167
|
+
if (!badKeywords.some(kw => c.includes(kw))) {
|
|
168
|
+
warnings.push({ file: r, type: "NO_BAD_EXAMPLE", msg: "No incorrect example (❌) found" });
|
|
110
169
|
}
|
|
111
|
-
if (!c.includes("|") && !c.includes("
|
|
112
|
-
warnings.push({ file: r, type: "NO_TABLE", msg: "
|
|
170
|
+
if (!c.includes("|") && !c.includes("rule") && !c.includes("Rule") && !c.includes("table")) {
|
|
171
|
+
warnings.push({ file: r, type: "NO_TABLE", msg: "Rules summary table appears to be missing" });
|
|
172
|
+
}
|
|
173
|
+
// Kotlin code block check: core and backend standard files should contain ```kotlin blocks
|
|
174
|
+
if (detectedLanguage === "kotlin") {
|
|
175
|
+
const kotlinRelevantPaths = ["backend-api", "00.core/02.", "00.core/03.", "30.security-db"];
|
|
176
|
+
if (kotlinRelevantPaths.some(p => r.includes(p))) {
|
|
177
|
+
if (!c.includes("```kotlin") && !c.includes("```kt")) {
|
|
178
|
+
warnings.push({ file: r, type: "NO_KOTLIN_BLOCK", msg: "No ```kotlin code block found (expected for Kotlin project)" });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
113
181
|
}
|
|
114
182
|
}
|
|
115
|
-
console.log(` ${stdFiles.length}
|
|
183
|
+
console.log(` ${stdFiles.length} files checked`);
|
|
116
184
|
} else {
|
|
117
|
-
errors.push({ file: "claudeos-core/standard/", type: "MISSING", msg: "standard
|
|
185
|
+
errors.push({ file: "claudeos-core/standard/", type: "MISSING", msg: "standard directory not found" });
|
|
118
186
|
}
|
|
119
187
|
|
|
120
188
|
// ─── 4. claudeos-core/skills/** ────────────────────────
|
|
@@ -125,17 +193,17 @@ async function main() {
|
|
|
125
193
|
for (const f of skillFiles) {
|
|
126
194
|
const c = fs.readFileSync(f, "utf-8");
|
|
127
195
|
if (c.trim().length === 0) {
|
|
128
|
-
errors.push({ file: rel(f), type: "EMPTY", msg: "
|
|
196
|
+
errors.push({ file: rel(f), type: "EMPTY", msg: "Empty file" });
|
|
129
197
|
}
|
|
130
198
|
}
|
|
131
|
-
//
|
|
199
|
+
// Check orchestrator existence
|
|
132
200
|
const orchestrators = skillFiles.filter(f => f.includes("01.scaffold") || f.includes("MANIFEST"));
|
|
133
201
|
if (orchestrators.length === 0) {
|
|
134
|
-
warnings.push({ file: "claudeos-core/skills/", type: "NO_ORCHESTRATOR", msg: "
|
|
202
|
+
warnings.push({ file: "claudeos-core/skills/", type: "NO_ORCHESTRATOR", msg: "No orchestrator or MANIFEST found" });
|
|
135
203
|
}
|
|
136
|
-
console.log(` ${skillFiles.length}
|
|
204
|
+
console.log(` ${skillFiles.length} files checked (${orchestrators.length} orchestrators)`);
|
|
137
205
|
} else {
|
|
138
|
-
errors.push({ file: "claudeos-core/skills/", type: "MISSING", msg: "skills
|
|
206
|
+
errors.push({ file: "claudeos-core/skills/", type: "MISSING", msg: "skills directory not found" });
|
|
139
207
|
}
|
|
140
208
|
|
|
141
209
|
// ─── 5. claudeos-core/guide/** ─────────────────────────
|
|
@@ -156,17 +224,17 @@ async function main() {
|
|
|
156
224
|
const gp = path.join(GUIDE_DIR, g);
|
|
157
225
|
checked++;
|
|
158
226
|
if (!fs.existsSync(gp)) {
|
|
159
|
-
errors.push({ file: `claudeos-core/guide/${g}`, type: "MISSING", msg: "
|
|
227
|
+
errors.push({ file: `claudeos-core/guide/${g}`, type: "MISSING", msg: "Guide file not generated" });
|
|
160
228
|
} else {
|
|
161
229
|
const c = fs.readFileSync(gp, "utf-8");
|
|
162
230
|
if (c.trim().length === 0) {
|
|
163
|
-
errors.push({ file: `claudeos-core/guide/${g}`, type: "EMPTY", msg: "
|
|
231
|
+
errors.push({ file: `claudeos-core/guide/${g}`, type: "EMPTY", msg: "Empty file" });
|
|
164
232
|
}
|
|
165
233
|
}
|
|
166
234
|
}
|
|
167
|
-
console.log(` ${expectedGuides.
|
|
235
|
+
console.log(` ${expectedGuides.filter(g => fs.existsSync(path.join(GUIDE_DIR, g))).length} of ${expectedGuides.length} expected files exist`);
|
|
168
236
|
} else {
|
|
169
|
-
errors.push({ file: "claudeos-core/guide/", type: "MISSING", msg: "guide
|
|
237
|
+
errors.push({ file: "claudeos-core/guide/", type: "MISSING", msg: "guide directory not found" });
|
|
170
238
|
}
|
|
171
239
|
|
|
172
240
|
// ─── 6. claudeos-core/plan/** ──────────────────────────
|
|
@@ -177,61 +245,61 @@ async function main() {
|
|
|
177
245
|
checked++;
|
|
178
246
|
const c = fs.readFileSync(f, "utf-8");
|
|
179
247
|
if (c.trim().length === 0) {
|
|
180
|
-
errors.push({ file: rel(f), type: "EMPTY", msg: "
|
|
248
|
+
errors.push({ file: rel(f), type: "EMPTY", msg: "Empty plan file" });
|
|
181
249
|
continue;
|
|
182
250
|
}
|
|
183
|
-
// <file>
|
|
251
|
+
// Must contain <file> blocks (sync-rules-master uses code block format)
|
|
184
252
|
const bn = path.basename(f);
|
|
185
253
|
if (!bn.includes("sync")) {
|
|
186
254
|
if (!c.includes("<file") && !c.includes("```")) {
|
|
187
|
-
warnings.push({ file: rel(f), type: "NO_FILE_BLOCKS", msg: "<file>
|
|
255
|
+
warnings.push({ file: rel(f), type: "NO_FILE_BLOCKS", msg: "No <file> blocks or code blocks found" });
|
|
188
256
|
}
|
|
189
257
|
}
|
|
190
258
|
}
|
|
191
|
-
console.log(` ${planFiles.length}
|
|
259
|
+
console.log(` ${planFiles.length} files checked`);
|
|
192
260
|
} else {
|
|
193
|
-
errors.push({ file: "claudeos-core/plan/", type: "MISSING", msg: "plan
|
|
261
|
+
errors.push({ file: "claudeos-core/plan/", type: "MISSING", msg: "plan directory not found" });
|
|
194
262
|
}
|
|
195
263
|
|
|
196
264
|
// ─── 7. claudeos-core/database/** ──────────────────────
|
|
197
265
|
console.log(" [7/8] claudeos-core/database/...");
|
|
198
266
|
if (fs.existsSync(DB_DIR)) {
|
|
199
|
-
const dbFiles = await glob("
|
|
267
|
+
const dbFiles = await glob("**/*.md", { cwd: DB_DIR, absolute: true });
|
|
200
268
|
checked += dbFiles.length;
|
|
201
269
|
if (dbFiles.length === 0) {
|
|
202
|
-
warnings.push({ file: "claudeos-core/database/", type: "NO_FILES", msg: "database
|
|
270
|
+
warnings.push({ file: "claudeos-core/database/", type: "NO_FILES", msg: "No database files found" });
|
|
203
271
|
}
|
|
204
272
|
for (const f of dbFiles) {
|
|
205
273
|
const c = fs.readFileSync(f, "utf-8");
|
|
206
274
|
if (c.trim().length === 0) {
|
|
207
|
-
errors.push({ file: rel(f), type: "EMPTY", msg: "
|
|
275
|
+
errors.push({ file: rel(f), type: "EMPTY", msg: "Empty file" });
|
|
208
276
|
}
|
|
209
277
|
}
|
|
210
|
-
console.log(` ${dbFiles.length}
|
|
278
|
+
console.log(` ${dbFiles.length} files`);
|
|
211
279
|
} else {
|
|
212
|
-
warnings.push({ file: "claudeos-core/database/", type: "MISSING", msg: "database
|
|
280
|
+
warnings.push({ file: "claudeos-core/database/", type: "MISSING", msg: "database directory not found" });
|
|
213
281
|
}
|
|
214
282
|
|
|
215
283
|
// ─── 8. claudeos-core/mcp-guide/** ─────────────────────
|
|
216
284
|
console.log(" [8/8] claudeos-core/mcp-guide/...");
|
|
217
285
|
if (fs.existsSync(MCP_DIR)) {
|
|
218
|
-
const mcpFiles = await glob("
|
|
286
|
+
const mcpFiles = await glob("**/*.md", { cwd: MCP_DIR, absolute: true });
|
|
219
287
|
checked += mcpFiles.length;
|
|
220
288
|
if (mcpFiles.length === 0) {
|
|
221
|
-
warnings.push({ file: "claudeos-core/mcp-guide/", type: "NO_FILES", msg: "mcp-guide
|
|
289
|
+
warnings.push({ file: "claudeos-core/mcp-guide/", type: "NO_FILES", msg: "No mcp-guide files found" });
|
|
222
290
|
}
|
|
223
291
|
for (const f of mcpFiles) {
|
|
224
292
|
const c = fs.readFileSync(f, "utf-8");
|
|
225
293
|
if (c.trim().length === 0) {
|
|
226
|
-
errors.push({ file: rel(f), type: "EMPTY", msg: "
|
|
294
|
+
errors.push({ file: rel(f), type: "EMPTY", msg: "Empty file" });
|
|
227
295
|
}
|
|
228
296
|
}
|
|
229
|
-
console.log(` ${mcpFiles.length}
|
|
297
|
+
console.log(` ${mcpFiles.length} files`);
|
|
230
298
|
} else {
|
|
231
|
-
warnings.push({ file: "claudeos-core/mcp-guide/", type: "MISSING", msg: "mcp-guide
|
|
299
|
+
warnings.push({ file: "claudeos-core/mcp-guide/", type: "MISSING", msg: "mcp-guide directory not found" });
|
|
232
300
|
}
|
|
233
301
|
|
|
234
|
-
// ───
|
|
302
|
+
// ─── Output results ─────────────────────────────────────────
|
|
235
303
|
console.log(`\n Checked ${checked} files\n`);
|
|
236
304
|
if (errors.length) {
|
|
237
305
|
console.log(` ❌ ERRORS (${errors.length}):`);
|
|
@@ -247,10 +315,13 @@ async function main() {
|
|
|
247
315
|
console.log(" ✅ All content validation passed\n");
|
|
248
316
|
}
|
|
249
317
|
|
|
250
|
-
// stale-report
|
|
318
|
+
// Record in stale-report
|
|
251
319
|
if (fs.existsSync(GEN_DIR)) {
|
|
252
320
|
const rp = path.join(GEN_DIR, "stale-report.json");
|
|
253
|
-
|
|
321
|
+
let ex = {};
|
|
322
|
+
if (fs.existsSync(rp)) {
|
|
323
|
+
try { ex = JSON.parse(fs.readFileSync(rp, "utf-8")); } catch { ex = {}; }
|
|
324
|
+
}
|
|
254
325
|
ex.contentValidation = { checkedAt: new Date().toISOString(), checked, errors: errors.length, warnings: warnings.length, details: { errors, warnings } };
|
|
255
326
|
ex.summary = { ...ex.summary, contentErrors: errors.length, contentWarnings: warnings.length };
|
|
256
327
|
fs.writeFileSync(rp, JSON.stringify(ex, null, 2));
|