claudeos-core 1.6.2 → 1.7.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 +26 -0
- package/README.de.md +655 -653
- package/README.es.md +657 -655
- package/README.fr.md +657 -655
- package/README.hi.md +656 -654
- package/README.ja.md +675 -673
- package/README.ko.md +692 -690
- package/README.md +712 -710
- package/README.ru.md +656 -654
- package/README.vi.md +656 -654
- package/README.zh-CN.md +656 -654
- package/bin/commands/init.js +386 -357
- package/package.json +1 -1
- package/pass-prompts/templates/common/pass3-footer.md +23 -3
- package/pass-prompts/templates/node-vite/pass1.md +117 -0
- package/pass-prompts/templates/node-vite/pass2.md +78 -0
- package/pass-prompts/templates/node-vite/pass3.md +103 -0
- package/plan-installer/domain-grouper.js +75 -73
- package/plan-installer/index.js +129 -126
- package/plan-installer/scanners/scan-frontend.js +264 -254
- package/plan-installer/scanners/scan-node.js +57 -46
- package/plan-installer/scanners/scan-python.js +64 -55
- package/plan-installer/stack-detector.js +466 -454
- package/plan-installer/structure-scanner.js +65 -65
package/bin/commands/init.js
CHANGED
|
@@ -1,357 +1,386 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ClaudeOS-Core — Init Command
|
|
3
|
-
*
|
|
4
|
-
* Runs the full 3-Pass pipeline: analyze → merge → generate.
|
|
5
|
-
* This is the main entry point for project bootstrapping.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const fs = require("fs");
|
|
9
|
-
const path = require("path");
|
|
10
|
-
const {
|
|
11
|
-
TOOLS_DIR, PROJECT_ROOT, GENERATED_DIR,
|
|
12
|
-
SUPPORTED_LANGS, LANG_CODES, isValidLang,
|
|
13
|
-
log, header, run, runClaudePrompt,
|
|
14
|
-
ensureDir, fileExists, readFile, injectProjectRoot,
|
|
15
|
-
pad, countFiles, countPass1Files,
|
|
16
|
-
} = require("../lib/cli-utils");
|
|
17
|
-
const { selectLangInteractive } = require("../lib/lang-selector");
|
|
18
|
-
const { selectResumeMode } = require("../lib/resume-selector");
|
|
19
|
-
|
|
20
|
-
// ─── i18n: claude -p waiting message ───────────────────────────
|
|
21
|
-
const CLAUDE_WAIT_TMPL = {
|
|
22
|
-
en: " ⏳ [{{PASS}}] Running claude -p (no output is normal, please wait)...",
|
|
23
|
-
ko: " ⏳ [{{PASS}}] claude -p 실행 중 (출력이 없어도 정상입니다. 잠시 기다려주세요)...",
|
|
24
|
-
"zh-CN": " ⏳ [{{PASS}}] 正在运行 claude -p(没有输出是正常的,请稍候)...",
|
|
25
|
-
ja: " ⏳ [{{PASS}}] claude -p 実行中(出力がなくても正常です。しばらくお待ちください)...",
|
|
26
|
-
es: " ⏳ [{{PASS}}] Ejecutando claude -p (es normal que no haya salida, por favor espere)...",
|
|
27
|
-
vi: " ⏳ [{{PASS}}] Đang chạy claude -p (không có output là bình thường, vui lòng chờ)...",
|
|
28
|
-
hi: " ⏳ [{{PASS}}] claude -p चल रहा है (कोई आउटपुट न होना सामान्य है, कृपया प्रतीक्षा करें)...",
|
|
29
|
-
ru: " ⏳ [{{PASS}}] Выполняется claude -p (отсутствие вывода — это нормально, подождите)...",
|
|
30
|
-
fr: " ⏳ [{{PASS}}] Exécution de claude -p (l'absence de sortie est normale, veuillez patienter)...",
|
|
31
|
-
de: " ⏳ [{{PASS}}] claude -p wird ausgeführt (keine Ausgabe ist normal, bitte warten)...",
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
function claudeWaitMsg(lang, passLabel) {
|
|
35
|
-
return (CLAUDE_WAIT_TMPL[lang] || CLAUDE_WAIT_TMPL.en).replace("{{PASS}}", passLabel);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
class InitError extends Error {
|
|
39
|
-
constructor(msg) { super(msg); this.name = "InitError"; }
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function formatElapsed(ms) {
|
|
43
|
-
const sec = Math.floor(ms / 1000);
|
|
44
|
-
if (sec < 60) return `${sec}s`;
|
|
45
|
-
const min = Math.floor(sec / 60);
|
|
46
|
-
const rem = sec % 60;
|
|
47
|
-
return rem > 0 ? `${min}m ${rem}s` : `${min}m`;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async function cmdInit(parsedArgs) {
|
|
51
|
-
const totalStart = Date.now();
|
|
52
|
-
|
|
53
|
-
// ─── Prerequisites check ───────────────────────────────────
|
|
54
|
-
const hasProjectMarker = [".git", "package.json", "build.gradle", "build.gradle.kts", "pom.xml", "pyproject.toml", "requirements.txt"].some(
|
|
55
|
-
m => fs.existsSync(path.join(PROJECT_ROOT, m))
|
|
56
|
-
);
|
|
57
|
-
if (!hasProjectMarker) {
|
|
58
|
-
log(`\n ⚠️ Warning: ${PROJECT_ROOT} does not look like a project root.`);
|
|
59
|
-
log(" No .git, package.json, build.gradle, or pom.xml found.");
|
|
60
|
-
log(" Run this command from your project directory.\n");
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const nodeVersion = parseInt(process.versions.node.split(".")[0]);
|
|
64
|
-
if (nodeVersion < 18) {
|
|
65
|
-
throw new InitError(`Node.js v18+ required (current: v${process.versions.node})\n Install: https://nodejs.org/`);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const claudeExists = run("claude --version", { silent: true, ignoreError: true });
|
|
69
|
-
if (!claudeExists) {
|
|
70
|
-
throw new InitError("Claude Code CLI not found.\n Install: https://code.claude.com/docs/en/overview\n Then run: claude (and complete authentication)");
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Verify Claude is authenticated (quick prompt test)
|
|
74
|
-
const claudeAuth = run('claude -p "echo ok"', { silent: true, ignoreError: true });
|
|
75
|
-
if (!claudeAuth) {
|
|
76
|
-
throw new InitError("Claude Code may not be authenticated.\n Run: claude (and complete authentication)\n Then retry: npx claudeos-core init");
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// ─── Language selection (required) ────────────────────────────
|
|
80
|
-
let lang = parsedArgs.lang;
|
|
81
|
-
if (!lang) {
|
|
82
|
-
lang = await selectLangInteractive();
|
|
83
|
-
}
|
|
84
|
-
if (!lang) {
|
|
85
|
-
throw new InitError("Cancelled.");
|
|
86
|
-
}
|
|
87
|
-
if (!isValidLang(lang)) {
|
|
88
|
-
throw new InitError(`Unsupported language: "${lang}"\n Supported: ${LANG_CODES.join(", ")}`);
|
|
89
|
-
}
|
|
90
|
-
process.env.CLAUDEOS_LANG = lang;
|
|
91
|
-
|
|
92
|
-
// ─── Resume / Fresh selection ────────────────────────────
|
|
93
|
-
if (fs.existsSync(GENERATED_DIR)) {
|
|
94
|
-
const existingPass1 = fs.readdirSync(GENERATED_DIR).filter(f => f.startsWith("pass1-") && f.endsWith(".json"));
|
|
95
|
-
const pass2Exists = fileExists(path.join(GENERATED_DIR, "pass2-merged.json"));
|
|
96
|
-
|
|
97
|
-
if (existingPass1.length > 0 || pass2Exists) {
|
|
98
|
-
if (parsedArgs.force) {
|
|
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
|
-
log(" 🔄 Previous results deleted (--force)\n");
|
|
103
|
-
} else {
|
|
104
|
-
const status = { pass1Done: existingPass1.length, pass2Done: pass2Exists };
|
|
105
|
-
const mode = await selectResumeMode(lang, status);
|
|
106
|
-
if (!mode) throw new InitError("Cancelled.");
|
|
107
|
-
if (mode === "fresh") {
|
|
108
|
-
for (const f of existingPass1) fs.unlinkSync(path.join(GENERATED_DIR, f));
|
|
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)");
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
log("");
|
|
120
|
-
log("╔════════════════════════════════════════════════════╗");
|
|
121
|
-
log("║ ClaudeOS-Core — Bootstrap (3-Pass) ║");
|
|
122
|
-
log("╚════════════════════════════════════════════════════╝");
|
|
123
|
-
log(` Project root: ${PROJECT_ROOT}`);
|
|
124
|
-
log(` Language: ${SUPPORTED_LANGS[lang]} (${lang})`);
|
|
125
|
-
log("");
|
|
126
|
-
|
|
127
|
-
// ─── [1] Install dependencies ────────────────────────────────
|
|
128
|
-
header("[1] Installing dependencies...");
|
|
129
|
-
if (!fileExists(path.join(TOOLS_DIR, "node_modules"))) {
|
|
130
|
-
run("npm install --silent", { cwd: TOOLS_DIR });
|
|
131
|
-
}
|
|
132
|
-
log(" ✅ Done\n");
|
|
133
|
-
|
|
134
|
-
// ─── [2] Create directory structure ─────────────────────────
|
|
135
|
-
header("[2] Creating directory structure...");
|
|
136
|
-
const dirs = [
|
|
137
|
-
".claude/rules/00.core",
|
|
138
|
-
".claude/rules/10.backend",
|
|
139
|
-
".claude/rules/20.frontend",
|
|
140
|
-
".claude/rules/30.security-db",
|
|
141
|
-
".claude/rules/40.infra",
|
|
142
|
-
".claude/rules/50.sync",
|
|
143
|
-
"claudeos-core/generated",
|
|
144
|
-
"claudeos-core/standard/00.core",
|
|
145
|
-
"claudeos-core/standard/10.backend-api",
|
|
146
|
-
"claudeos-core/standard/20.frontend-ui",
|
|
147
|
-
"claudeos-core/standard/30.security-db",
|
|
148
|
-
"claudeos-core/standard/40.infra",
|
|
149
|
-
"claudeos-core/standard/50.verification",
|
|
150
|
-
"claudeos-core/standard/90.optional",
|
|
151
|
-
"claudeos-core/skills/00.shared",
|
|
152
|
-
"claudeos-core/skills/10.backend-crud/scaffold-crud-feature",
|
|
153
|
-
"claudeos-core/skills/20.frontend-page/scaffold-page-feature",
|
|
154
|
-
"claudeos-core/skills/50.testing",
|
|
155
|
-
"claudeos-core/skills/90.experimental",
|
|
156
|
-
"claudeos-core/plan",
|
|
157
|
-
"claudeos-core/guide/01.onboarding",
|
|
158
|
-
"claudeos-core/guide/02.usage",
|
|
159
|
-
"claudeos-core/guide/03.troubleshooting",
|
|
160
|
-
"claudeos-core/guide/04.architecture",
|
|
161
|
-
"claudeos-core/database",
|
|
162
|
-
"claudeos-core/mcp-guide",
|
|
163
|
-
];
|
|
164
|
-
for (const d of dirs) {
|
|
165
|
-
ensureDir(path.join(PROJECT_ROOT, d));
|
|
166
|
-
}
|
|
167
|
-
log(" ✅ Done\n");
|
|
168
|
-
|
|
169
|
-
// ─── [3] Run plan-installer ─────────────────────────
|
|
170
|
-
header("[3] Analyzing project (plan-installer)...");
|
|
171
|
-
run(`node "${path.join(TOOLS_DIR, "plan-installer/index.js")}"`);
|
|
172
|
-
log("");
|
|
173
|
-
|
|
174
|
-
// ─── [4] Pass 1: Deep analysis per domain group ──────────────────
|
|
175
|
-
header("[4] Pass 1 — Deep analysis per domain group...");
|
|
176
|
-
|
|
177
|
-
let domainGroups;
|
|
178
|
-
try {
|
|
179
|
-
domainGroups = JSON.parse(
|
|
180
|
-
readFile(path.join(GENERATED_DIR, "domain-groups.json"))
|
|
181
|
-
);
|
|
182
|
-
} catch (e) {
|
|
183
|
-
throw new InitError(`domain-groups.json is missing or malformed: ${e.message}\n Re-run plan-installer or check claudeos-core/generated/`);
|
|
184
|
-
}
|
|
185
|
-
const totalGroups = domainGroups.totalGroups;
|
|
186
|
-
if (!totalGroups || typeof totalGroups !== "number" || totalGroups < 1) {
|
|
187
|
-
throw new InitError(`domain-groups.json has invalid totalGroups: ${totalGroups}\n Re-run plan-installer or check claudeos-core/generated/`);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Load pass1 prompts by type
|
|
191
|
-
const pass1Prompts = {};
|
|
192
|
-
for (const type of ["backend", "frontend"]) {
|
|
193
|
-
const promptFile = path.join(GENERATED_DIR, `pass1-${type}-prompt.md`);
|
|
194
|
-
if (fileExists(promptFile)) {
|
|
195
|
-
pass1Prompts[type] = readFile(promptFile);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
// Single-stack backward compatibility
|
|
199
|
-
if (Object.keys(pass1Prompts).length === 0) {
|
|
200
|
-
const fallback = path.join(GENERATED_DIR, "pass1-prompt.md");
|
|
201
|
-
if (fileExists(fallback)) pass1Prompts["backend"] = readFile(fallback);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (!domainGroups.groups || totalGroups !== domainGroups.groups.length) {
|
|
205
|
-
throw new InitError(`domain-groups.json is malformed: expected ${totalGroups} groups, found ${domainGroups.groups ? domainGroups.groups.length : 0}`);
|
|
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
|
-
const
|
|
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
|
-
log(
|
|
266
|
-
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
log("
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
|
|
1
|
+
/**
|
|
2
|
+
* ClaudeOS-Core — Init Command
|
|
3
|
+
*
|
|
4
|
+
* Runs the full 3-Pass pipeline: analyze → merge → generate.
|
|
5
|
+
* This is the main entry point for project bootstrapping.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require("fs");
|
|
9
|
+
const path = require("path");
|
|
10
|
+
const {
|
|
11
|
+
TOOLS_DIR, PROJECT_ROOT, GENERATED_DIR,
|
|
12
|
+
SUPPORTED_LANGS, LANG_CODES, isValidLang,
|
|
13
|
+
log, header, run, runClaudePrompt,
|
|
14
|
+
ensureDir, fileExists, readFile, injectProjectRoot,
|
|
15
|
+
pad, countFiles, countPass1Files,
|
|
16
|
+
} = require("../lib/cli-utils");
|
|
17
|
+
const { selectLangInteractive } = require("../lib/lang-selector");
|
|
18
|
+
const { selectResumeMode } = require("../lib/resume-selector");
|
|
19
|
+
|
|
20
|
+
// ─── i18n: claude -p waiting message ───────────────────────────
|
|
21
|
+
const CLAUDE_WAIT_TMPL = {
|
|
22
|
+
en: " ⏳ [{{PASS}}] Running claude -p (no output is normal, please wait)...",
|
|
23
|
+
ko: " ⏳ [{{PASS}}] claude -p 실행 중 (출력이 없어도 정상입니다. 잠시 기다려주세요)...",
|
|
24
|
+
"zh-CN": " ⏳ [{{PASS}}] 正在运行 claude -p(没有输出是正常的,请稍候)...",
|
|
25
|
+
ja: " ⏳ [{{PASS}}] claude -p 実行中(出力がなくても正常です。しばらくお待ちください)...",
|
|
26
|
+
es: " ⏳ [{{PASS}}] Ejecutando claude -p (es normal que no haya salida, por favor espere)...",
|
|
27
|
+
vi: " ⏳ [{{PASS}}] Đang chạy claude -p (không có output là bình thường, vui lòng chờ)...",
|
|
28
|
+
hi: " ⏳ [{{PASS}}] claude -p चल रहा है (कोई आउटपुट न होना सामान्य है, कृपया प्रतीक्षा करें)...",
|
|
29
|
+
ru: " ⏳ [{{PASS}}] Выполняется claude -p (отсутствие вывода — это нормально, подождите)...",
|
|
30
|
+
fr: " ⏳ [{{PASS}}] Exécution de claude -p (l'absence de sortie est normale, veuillez patienter)...",
|
|
31
|
+
de: " ⏳ [{{PASS}}] claude -p wird ausgeführt (keine Ausgabe ist normal, bitte warten)...",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function claudeWaitMsg(lang, passLabel) {
|
|
35
|
+
return (CLAUDE_WAIT_TMPL[lang] || CLAUDE_WAIT_TMPL.en).replace("{{PASS}}", passLabel);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
class InitError extends Error {
|
|
39
|
+
constructor(msg) { super(msg); this.name = "InitError"; }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function formatElapsed(ms) {
|
|
43
|
+
const sec = Math.floor(ms / 1000);
|
|
44
|
+
if (sec < 60) return `${sec}s`;
|
|
45
|
+
const min = Math.floor(sec / 60);
|
|
46
|
+
const rem = sec % 60;
|
|
47
|
+
return rem > 0 ? `${min}m ${rem}s` : `${min}m`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function cmdInit(parsedArgs) {
|
|
51
|
+
const totalStart = Date.now();
|
|
52
|
+
|
|
53
|
+
// ─── Prerequisites check ───────────────────────────────────
|
|
54
|
+
const hasProjectMarker = [".git", "package.json", "build.gradle", "build.gradle.kts", "pom.xml", "pyproject.toml", "requirements.txt"].some(
|
|
55
|
+
m => fs.existsSync(path.join(PROJECT_ROOT, m))
|
|
56
|
+
);
|
|
57
|
+
if (!hasProjectMarker) {
|
|
58
|
+
log(`\n ⚠️ Warning: ${PROJECT_ROOT} does not look like a project root.`);
|
|
59
|
+
log(" No .git, package.json, build.gradle, or pom.xml found.");
|
|
60
|
+
log(" Run this command from your project directory.\n");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const nodeVersion = parseInt(process.versions.node.split(".")[0]);
|
|
64
|
+
if (nodeVersion < 18) {
|
|
65
|
+
throw new InitError(`Node.js v18+ required (current: v${process.versions.node})\n Install: https://nodejs.org/`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const claudeExists = run("claude --version", { silent: true, ignoreError: true });
|
|
69
|
+
if (!claudeExists) {
|
|
70
|
+
throw new InitError("Claude Code CLI not found.\n Install: https://code.claude.com/docs/en/overview\n Then run: claude (and complete authentication)");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Verify Claude is authenticated (quick prompt test)
|
|
74
|
+
const claudeAuth = run('claude -p "echo ok"', { silent: true, ignoreError: true });
|
|
75
|
+
if (!claudeAuth) {
|
|
76
|
+
throw new InitError("Claude Code may not be authenticated.\n Run: claude (and complete authentication)\n Then retry: npx claudeos-core init");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ─── Language selection (required) ────────────────────────────
|
|
80
|
+
let lang = parsedArgs.lang;
|
|
81
|
+
if (!lang) {
|
|
82
|
+
lang = await selectLangInteractive();
|
|
83
|
+
}
|
|
84
|
+
if (!lang) {
|
|
85
|
+
throw new InitError("Cancelled.");
|
|
86
|
+
}
|
|
87
|
+
if (!isValidLang(lang)) {
|
|
88
|
+
throw new InitError(`Unsupported language: "${lang}"\n Supported: ${LANG_CODES.join(", ")}`);
|
|
89
|
+
}
|
|
90
|
+
process.env.CLAUDEOS_LANG = lang;
|
|
91
|
+
|
|
92
|
+
// ─── Resume / Fresh selection ────────────────────────────
|
|
93
|
+
if (fs.existsSync(GENERATED_DIR)) {
|
|
94
|
+
const existingPass1 = fs.readdirSync(GENERATED_DIR).filter(f => f.startsWith("pass1-") && f.endsWith(".json"));
|
|
95
|
+
const pass2Exists = fileExists(path.join(GENERATED_DIR, "pass2-merged.json"));
|
|
96
|
+
|
|
97
|
+
if (existingPass1.length > 0 || pass2Exists) {
|
|
98
|
+
if (parsedArgs.force) {
|
|
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
|
+
log(" 🔄 Previous results deleted (--force)\n");
|
|
103
|
+
} else {
|
|
104
|
+
const status = { pass1Done: existingPass1.length, pass2Done: pass2Exists };
|
|
105
|
+
const mode = await selectResumeMode(lang, status);
|
|
106
|
+
if (!mode) throw new InitError("Cancelled.");
|
|
107
|
+
if (mode === "fresh") {
|
|
108
|
+
for (const f of existingPass1) fs.unlinkSync(path.join(GENERATED_DIR, f));
|
|
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)");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
log("");
|
|
120
|
+
log("╔════════════════════════════════════════════════════╗");
|
|
121
|
+
log("║ ClaudeOS-Core — Bootstrap (3-Pass) ║");
|
|
122
|
+
log("╚════════════════════════════════════════════════════╝");
|
|
123
|
+
log(` Project root: ${PROJECT_ROOT}`);
|
|
124
|
+
log(` Language: ${SUPPORTED_LANGS[lang]} (${lang})`);
|
|
125
|
+
log("");
|
|
126
|
+
|
|
127
|
+
// ─── [1] Install dependencies ────────────────────────────────
|
|
128
|
+
header("[1] Installing dependencies...");
|
|
129
|
+
if (!fileExists(path.join(TOOLS_DIR, "node_modules"))) {
|
|
130
|
+
run("npm install --silent", { cwd: TOOLS_DIR });
|
|
131
|
+
}
|
|
132
|
+
log(" ✅ Done\n");
|
|
133
|
+
|
|
134
|
+
// ─── [2] Create directory structure ─────────────────────────
|
|
135
|
+
header("[2] Creating directory structure...");
|
|
136
|
+
const dirs = [
|
|
137
|
+
".claude/rules/00.core",
|
|
138
|
+
".claude/rules/10.backend",
|
|
139
|
+
".claude/rules/20.frontend",
|
|
140
|
+
".claude/rules/30.security-db",
|
|
141
|
+
".claude/rules/40.infra",
|
|
142
|
+
".claude/rules/50.sync",
|
|
143
|
+
"claudeos-core/generated",
|
|
144
|
+
"claudeos-core/standard/00.core",
|
|
145
|
+
"claudeos-core/standard/10.backend-api",
|
|
146
|
+
"claudeos-core/standard/20.frontend-ui",
|
|
147
|
+
"claudeos-core/standard/30.security-db",
|
|
148
|
+
"claudeos-core/standard/40.infra",
|
|
149
|
+
"claudeos-core/standard/50.verification",
|
|
150
|
+
"claudeos-core/standard/90.optional",
|
|
151
|
+
"claudeos-core/skills/00.shared",
|
|
152
|
+
"claudeos-core/skills/10.backend-crud/scaffold-crud-feature",
|
|
153
|
+
"claudeos-core/skills/20.frontend-page/scaffold-page-feature",
|
|
154
|
+
"claudeos-core/skills/50.testing",
|
|
155
|
+
"claudeos-core/skills/90.experimental",
|
|
156
|
+
"claudeos-core/plan",
|
|
157
|
+
"claudeos-core/guide/01.onboarding",
|
|
158
|
+
"claudeos-core/guide/02.usage",
|
|
159
|
+
"claudeos-core/guide/03.troubleshooting",
|
|
160
|
+
"claudeos-core/guide/04.architecture",
|
|
161
|
+
"claudeos-core/database",
|
|
162
|
+
"claudeos-core/mcp-guide",
|
|
163
|
+
];
|
|
164
|
+
for (const d of dirs) {
|
|
165
|
+
ensureDir(path.join(PROJECT_ROOT, d));
|
|
166
|
+
}
|
|
167
|
+
log(" ✅ Done\n");
|
|
168
|
+
|
|
169
|
+
// ─── [3] Run plan-installer ─────────────────────────
|
|
170
|
+
header("[3] Analyzing project (plan-installer)...");
|
|
171
|
+
run(`node "${path.join(TOOLS_DIR, "plan-installer/index.js")}"`);
|
|
172
|
+
log("");
|
|
173
|
+
|
|
174
|
+
// ─── [4] Pass 1: Deep analysis per domain group ──────────────────
|
|
175
|
+
header("[4] Pass 1 — Deep analysis per domain group...");
|
|
176
|
+
|
|
177
|
+
let domainGroups;
|
|
178
|
+
try {
|
|
179
|
+
domainGroups = JSON.parse(
|
|
180
|
+
readFile(path.join(GENERATED_DIR, "domain-groups.json"))
|
|
181
|
+
);
|
|
182
|
+
} catch (e) {
|
|
183
|
+
throw new InitError(`domain-groups.json is missing or malformed: ${e.message}\n Re-run plan-installer or check claudeos-core/generated/`);
|
|
184
|
+
}
|
|
185
|
+
const totalGroups = domainGroups.totalGroups;
|
|
186
|
+
if (!totalGroups || typeof totalGroups !== "number" || totalGroups < 1) {
|
|
187
|
+
throw new InitError(`domain-groups.json has invalid totalGroups: ${totalGroups}\n Re-run plan-installer or check claudeos-core/generated/`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Load pass1 prompts by type
|
|
191
|
+
const pass1Prompts = {};
|
|
192
|
+
for (const type of ["backend", "frontend"]) {
|
|
193
|
+
const promptFile = path.join(GENERATED_DIR, `pass1-${type}-prompt.md`);
|
|
194
|
+
if (fileExists(promptFile)) {
|
|
195
|
+
pass1Prompts[type] = readFile(promptFile);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// Single-stack backward compatibility
|
|
199
|
+
if (Object.keys(pass1Prompts).length === 0) {
|
|
200
|
+
const fallback = path.join(GENERATED_DIR, "pass1-prompt.md");
|
|
201
|
+
if (fileExists(fallback)) pass1Prompts["backend"] = readFile(fallback);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (!domainGroups.groups || totalGroups !== domainGroups.groups.length) {
|
|
205
|
+
throw new InitError(`domain-groups.json is malformed: expected ${totalGroups} groups, found ${domainGroups.groups ? domainGroups.groups.length : 0}`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Progress tracking: Pass 1 (N groups) + Pass 2 + Pass 3 = totalSteps
|
|
209
|
+
const totalSteps = totalGroups + 2;
|
|
210
|
+
let completedSteps = 0;
|
|
211
|
+
const stepTimes = [];
|
|
212
|
+
const passStart = Date.now();
|
|
213
|
+
|
|
214
|
+
function progressBar(step, label) {
|
|
215
|
+
const pct = Math.round((step / totalSteps) * 100);
|
|
216
|
+
const elapsed = Date.now() - passStart;
|
|
217
|
+
let eta = "";
|
|
218
|
+
if (stepTimes.length > 0) {
|
|
219
|
+
const avgMs = stepTimes.reduce((a, b) => a + b, 0) / stepTimes.length;
|
|
220
|
+
const remaining = (totalSteps - step) * avgMs;
|
|
221
|
+
eta = ` | ETA ${formatElapsed(remaining)}`;
|
|
222
|
+
}
|
|
223
|
+
const filled = Math.round(pct / 5);
|
|
224
|
+
const bar = "█".repeat(filled) + "░".repeat(20 - filled);
|
|
225
|
+
log(` [${bar}] ${pct}% (${step}/${totalSteps}) ${formatElapsed(elapsed)}${eta} — ${label}`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
for (let i = 1; i <= totalGroups; i++) {
|
|
229
|
+
const group = domainGroups.groups[i - 1];
|
|
230
|
+
const domainList = (group.domains || []).join(", ") || "(unknown)";
|
|
231
|
+
const estFiles = group.estimatedFiles || 0;
|
|
232
|
+
const groupType = group.type || "backend";
|
|
233
|
+
const icon = groupType === "frontend" ? "🎨" : "⚙️";
|
|
234
|
+
|
|
235
|
+
log("");
|
|
236
|
+
log(
|
|
237
|
+
` ${icon} [Pass 1-${i}/${totalGroups}] ${groupType}: ${domainList} (~${estFiles} files)`
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
const pass1Json = path.join(GENERATED_DIR, `pass1-${i}.json`);
|
|
241
|
+
if (fileExists(pass1Json)) {
|
|
242
|
+
try {
|
|
243
|
+
const existing = JSON.parse(readFile(pass1Json));
|
|
244
|
+
if (existing && existing.analysisPerDomain) {
|
|
245
|
+
log(` ⏭️ pass1-${i}.json already exists, skipping`);
|
|
246
|
+
completedSteps++;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
} catch (_e) { /* malformed — re-run */ }
|
|
250
|
+
log(` ⚠️ pass1-${i}.json exists but is malformed, re-running`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Select prompt for this type
|
|
254
|
+
const template = pass1Prompts[groupType] || pass1Prompts["backend"];
|
|
255
|
+
if (!template) {
|
|
256
|
+
throw new InitError(`No pass1 prompt found for type: ${groupType}`);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Placeholder substitution
|
|
260
|
+
let prompt = template
|
|
261
|
+
.replace(/\{\{DOMAIN_GROUP\}\}/g, domainList)
|
|
262
|
+
.replace(/\{\{PASS_NUM\}\}/g, String(i));
|
|
263
|
+
prompt = injectProjectRoot(prompt);
|
|
264
|
+
|
|
265
|
+
log(claudeWaitMsg(lang, `Pass 1-${i}/${totalGroups}`));
|
|
266
|
+
const t1 = Date.now();
|
|
267
|
+
const ok = runClaudePrompt(prompt, { ignoreError: true });
|
|
268
|
+
const elapsed1 = Date.now() - t1;
|
|
269
|
+
stepTimes.push(elapsed1);
|
|
270
|
+
|
|
271
|
+
if (!ok) {
|
|
272
|
+
throw new InitError(`Pass 1-${i} failed. Check the claude error output above.\n If this persists, try: npx claudeos-core init --force`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (!fileExists(pass1Json)) {
|
|
276
|
+
throw new InitError(`pass1-${i}.json was not created. Claude may have run but not produced expected output.\n Ensure the prompt instructs Claude to write to claudeos-core/generated/pass1-${i}.json`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
completedSteps++;
|
|
280
|
+
progressBar(completedSteps, `pass1-${i}.json created (${formatElapsed(elapsed1)})`);
|
|
281
|
+
}
|
|
282
|
+
log("");
|
|
283
|
+
|
|
284
|
+
// ─── [5] Pass 2: Merge analysis results ──────────────────────
|
|
285
|
+
header("[5] Pass 2 — Merging analysis results...");
|
|
286
|
+
|
|
287
|
+
const pass2Json = path.join(GENERATED_DIR, "pass2-merged.json");
|
|
288
|
+
if (fileExists(pass2Json)) {
|
|
289
|
+
log(" ⏭️ pass2-merged.json already exists, skipping");
|
|
290
|
+
completedSteps++;
|
|
291
|
+
} else {
|
|
292
|
+
const pass2PromptFile = path.join(GENERATED_DIR, "pass2-prompt.md");
|
|
293
|
+
if (!fileExists(pass2PromptFile)) {
|
|
294
|
+
throw new InitError("pass2-prompt.md not found. Re-run plan-installer.");
|
|
295
|
+
}
|
|
296
|
+
let prompt = injectProjectRoot(readFile(pass2PromptFile));
|
|
297
|
+
|
|
298
|
+
log(claudeWaitMsg(lang, "Pass 2"));
|
|
299
|
+
const t2 = Date.now();
|
|
300
|
+
const ok = runClaudePrompt(prompt, { ignoreError: true });
|
|
301
|
+
const elapsed2 = Date.now() - t2;
|
|
302
|
+
stepTimes.push(elapsed2);
|
|
303
|
+
|
|
304
|
+
if (!ok) {
|
|
305
|
+
throw new InitError("Pass 2 failed. Check the claude error output above.\n If this persists, try: npx claudeos-core init --force");
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (!fileExists(pass2Json)) {
|
|
309
|
+
throw new InitError("pass2-merged.json was not created. Claude may have run but not produced expected output.");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
completedSteps++;
|
|
313
|
+
progressBar(completedSteps, `pass2-merged.json created (${formatElapsed(elapsed2)})`);
|
|
314
|
+
}
|
|
315
|
+
log("");
|
|
316
|
+
|
|
317
|
+
// ─── [6] Pass 3: Generate + verify ─────────────────────────
|
|
318
|
+
header("[6] Pass 3 — Generating all files...");
|
|
319
|
+
|
|
320
|
+
const pass3PromptFile = path.join(GENERATED_DIR, "pass3-prompt.md");
|
|
321
|
+
if (!fileExists(pass3PromptFile)) {
|
|
322
|
+
throw new InitError("pass3-prompt.md not found. Re-run plan-installer.");
|
|
323
|
+
}
|
|
324
|
+
let prompt = injectProjectRoot(readFile(pass3PromptFile));
|
|
325
|
+
|
|
326
|
+
log(claudeWaitMsg(lang, "Pass 3"));
|
|
327
|
+
const t3 = Date.now();
|
|
328
|
+
const ok3 = runClaudePrompt(prompt, { ignoreError: true });
|
|
329
|
+
const elapsed3 = Date.now() - t3;
|
|
330
|
+
stepTimes.push(elapsed3);
|
|
331
|
+
|
|
332
|
+
if (!ok3) {
|
|
333
|
+
throw new InitError("Pass 3 failed. Check the claude error output above.\n If this persists, try: npx claudeos-core init --force");
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (!fileExists(path.join(PROJECT_ROOT, "CLAUDE.md"))) {
|
|
337
|
+
throw new InitError("CLAUDE.md was not created. Claude ran but did not produce CLAUDE.md.\n Verify pass3-prompt.md instructs Claude to create CLAUDE.md at project root.");
|
|
338
|
+
}
|
|
339
|
+
completedSteps++;
|
|
340
|
+
progressBar(completedSteps, `Pass 3 complete (${formatElapsed(elapsed3)})`);
|
|
341
|
+
log("");
|
|
342
|
+
|
|
343
|
+
// ─── [7] Run verification tools ───────────────────────────────
|
|
344
|
+
header("[7] Running verification tools...");
|
|
345
|
+
|
|
346
|
+
const verifyTools = [
|
|
347
|
+
{ name: "manifest-generator", script: path.join(TOOLS_DIR, "manifest-generator/index.js") },
|
|
348
|
+
{ name: "health-checker", script: path.join(TOOLS_DIR, "health-checker/index.js") },
|
|
349
|
+
];
|
|
350
|
+
|
|
351
|
+
for (const t of verifyTools) {
|
|
352
|
+
if (!fileExists(t.script)) {
|
|
353
|
+
log(` ⏭️ ${t.name} — not found, skipping`);
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
const ok = run(`node "${t.script}"`, { ignoreError: true });
|
|
357
|
+
if (!ok) {
|
|
358
|
+
log(` ⚠️ ${t.name} reported issues (non-fatal)`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
log("");
|
|
362
|
+
|
|
363
|
+
// ─── Complete ─────────────────────────────────────────────
|
|
364
|
+
const totalFiles = countFiles();
|
|
365
|
+
const pass1Files = countPass1Files();
|
|
366
|
+
|
|
367
|
+
log("");
|
|
368
|
+
log("╔════════════════════════════════════════════════════╗");
|
|
369
|
+
log("║ ✅ ClaudeOS-Core — Complete ║");
|
|
370
|
+
log("║ ║");
|
|
371
|
+
log(`║ Files created: ${pad(String(totalFiles), 29)}║`);
|
|
372
|
+
log(`║ Domains analyzed: ${pad(totalGroups + " groups", 29)}║`);
|
|
373
|
+
log(`║ Analysis passes: ${pad(pass1Files + " pass1 files", 29)}║`);
|
|
374
|
+
log(`║ Output language: ${pad(SUPPORTED_LANGS[lang] || lang, 29)}║`);
|
|
375
|
+
log(`║ Total time: ${pad(formatElapsed(Date.now() - totalStart), 29)}║`);
|
|
376
|
+
log("║ ║");
|
|
377
|
+
log("║ Verify anytime: ║");
|
|
378
|
+
log("║ npx claudeos-core health ║");
|
|
379
|
+
log("║ ║");
|
|
380
|
+
log("║ Start using: ║");
|
|
381
|
+
log('║ "Create a CRUD for orders" ║');
|
|
382
|
+
log("╚════════════════════════════════════════════════════╝");
|
|
383
|
+
log("");
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
module.exports = { cmdInit, InitError };
|