claudeos-core 2.2.0 → 2.3.1

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.
Files changed (49) hide show
  1. package/CHANGELOG.md +1664 -907
  2. package/CONTRIBUTING.md +92 -92
  3. package/README.de.md +28 -0
  4. package/README.es.md +28 -0
  5. package/README.fr.md +28 -0
  6. package/README.hi.md +28 -0
  7. package/README.ja.md +28 -0
  8. package/README.ko.md +1014 -986
  9. package/README.md +1016 -987
  10. package/README.ru.md +28 -0
  11. package/README.vi.md +1015 -987
  12. package/README.zh-CN.md +28 -0
  13. package/bin/cli.js +152 -148
  14. package/bin/commands/init.js +1673 -1554
  15. package/bin/commands/lint.js +62 -0
  16. package/bin/commands/memory.js +438 -438
  17. package/bin/lib/cli-utils.js +206 -206
  18. package/claude-md-validator/index.js +184 -0
  19. package/claude-md-validator/reporter.js +66 -0
  20. package/claude-md-validator/structural-checks.js +528 -0
  21. package/content-validator/index.js +666 -441
  22. package/lib/expected-guides.js +23 -23
  23. package/lib/expected-outputs.js +90 -90
  24. package/lib/language-config.js +35 -35
  25. package/lib/memory-scaffold.js +1058 -1054
  26. package/lib/plan-parser.js +165 -165
  27. package/lib/staged-rules.js +118 -118
  28. package/manifest-generator/index.js +174 -174
  29. package/package.json +90 -87
  30. package/pass-json-validator/index.js +337 -337
  31. package/pass-prompts/templates/common/claude-md-scaffold.md +52 -10
  32. package/pass-prompts/templates/common/pass3-footer.md +402 -224
  33. package/pass-prompts/templates/common/pass3b-core-header.md +43 -0
  34. package/pass-prompts/templates/common/pass4.md +375 -305
  35. package/pass-prompts/templates/common/staging-override.md +26 -26
  36. package/pass-prompts/templates/node-vite/pass1.md +117 -117
  37. package/pass-prompts/templates/node-vite/pass2.md +78 -78
  38. package/pass-prompts/templates/python-flask/pass1.md +119 -119
  39. package/pass-prompts/templates/python-flask/pass2.md +85 -85
  40. package/plan-installer/domain-grouper.js +76 -76
  41. package/plan-installer/index.js +137 -137
  42. package/plan-installer/prompt-generator.js +188 -145
  43. package/plan-installer/scanners/scan-frontend.js +505 -473
  44. package/plan-installer/scanners/scan-java.js +226 -226
  45. package/plan-installer/scanners/scan-node.js +57 -57
  46. package/plan-installer/scanners/scan-python.js +85 -85
  47. package/plan-installer/stack-detector.js +482 -482
  48. package/plan-installer/structure-scanner.js +65 -65
  49. package/sync-checker/index.js +177 -177
@@ -1,137 +1,137 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * ClaudeOS-Core — plan-installer (orchestrator)
5
- *
6
- * Modules:
7
- * - stack-detector.js — detectStack()
8
- * - structure-scanner.js — scanStructure(), resolveSharedQueryDomains()
9
- * - domain-grouper.js — splitDomainGroups(), determineActiveDomains(), selectTemplates()
10
- * - prompt-generator.js — generatePrompts()
11
- */
12
-
13
- const path = require("path");
14
- const { ensureDir, writeFileSafe } = require("../lib/safe-fs");
15
- const { detectStack } = require("./stack-detector");
16
- const { scanStructure } = require("./structure-scanner");
17
- const { splitDomainGroups, determineActiveDomains, selectTemplates } = require("./domain-grouper");
18
- const { generatePrompts } = require("./prompt-generator");
19
-
20
- const ROOT = process.env.CLAUDEOS_ROOT || path.resolve(__dirname, "../..");
21
- const GENERATED_DIR = path.join(ROOT, "claudeos-core/generated");
22
- const TEMPLATES_DIR = path.join(__dirname, "../pass-prompts/templates");
23
-
24
- async function main() {
25
- console.log("\n╔═══════════════════════════════════════╗");
26
- console.log("║ ClaudeOS-Core — Plan Installer ║");
27
- console.log("╚═══════════════════════════════════════╝\n");
28
-
29
- ensureDir(GENERATED_DIR);
30
-
31
- // Phase 1: Stack detection
32
- console.log(" [Phase 1] Detecting stack...");
33
- const stack = await detectStack(ROOT);
34
- console.log(` Language: ${stack.language || "unknown"} ${stack.languageVersion || ""}`);
35
- console.log(` Framework: ${stack.framework || "none"} ${stack.frameworkVersion || ""}`);
36
- if (!stack.language && !stack.framework) {
37
- console.warn("\n ⚠️ No language or framework detected.");
38
- console.warn(" Supported: Java, Kotlin, TypeScript, JavaScript, Python");
39
- console.warn(" Ensure you have build.gradle, package.json, pyproject.toml, or requirements.txt in the project root.\n");
40
- }
41
- console.log(` Frontend: ${stack.frontend || "none"} ${stack.frontendVersion || ""}`);
42
- console.log(` Database: ${stack.database || "none"}`);
43
- console.log(` ORM: ${stack.orm || "none"}`);
44
- console.log(` PackageMgr: ${stack.packageManager || "none"}\n`);
45
-
46
- // Phase 2: Structure scan
47
- console.log(" [Phase 2] Scanning structure...");
48
- const { domains, backendDomains, frontendDomains, rootPackage, frontend } = await scanStructure(stack, ROOT);
49
- console.log(` Backend: ${backendDomains.length} domains`);
50
- console.log(` Frontend: ${frontendDomains.length} domains`);
51
- console.log(` Total: ${domains.length} domains`);
52
- if (rootPackage) console.log(` Package: ${rootPackage}`);
53
- if (frontend.exists) console.log(` Components: ${frontend.components} components, ${frontend.pages} pages, ${frontend.hooks} hooks`);
54
- if (backendDomains.length === 0 && frontendDomains.length === 0) {
55
- console.warn("\n ⚠️ No domains detected.");
56
- console.warn(" Pass 1 will be skipped. Generated output may be minimal.\n");
57
- }
58
- console.log();
59
-
60
- // Phase 3: Template selection
61
- console.log(" [Phase 3] Selecting templates...");
62
- const templates = selectTemplates(stack);
63
- const isMultiStack = !!(templates.backend && templates.frontend);
64
- if (templates.backend) console.log(` Backend: ${templates.backend}`);
65
- if (templates.frontend) console.log(` Frontend: ${templates.frontend}`);
66
- console.log(` Mode: ${isMultiStack ? "🔀 Multi-stack" : "Single-stack"}`);
67
- console.log();
68
-
69
- // Phase 4: Domain group splitting
70
- console.log(" [Phase 4] Splitting domain groups...");
71
- const allGroups = [];
72
- if (templates.backend && backendDomains.length > 0) allGroups.push(...splitDomainGroups(backendDomains, "backend", templates.backend));
73
- if (templates.frontend && frontendDomains.length > 0) allGroups.push(...splitDomainGroups(frontendDomains, "frontend", templates.frontend));
74
- allGroups.forEach((g, i) => { g.passNum = i + 1; });
75
- allGroups.forEach((g, i) => {
76
- const icon = g.type === "backend" ? "⚙️" : "🎨";
77
- console.log(` ${icon} Group ${i + 1}: [${g.domains.join(", ")}] (${g.type}, ~${g.estimatedFiles} files)`);
78
- });
79
- console.log();
80
-
81
- // Phase 5: Active domains
82
- console.log(" [Phase 5] Active domains...");
83
- const active = determineActiveDomains(stack);
84
- Object.entries(active).forEach(([k, v]) => console.log(` ${v ? "✅" : "⏭️"} ${k}`));
85
- console.log();
86
-
87
- // Phase 6: Prompt generation
88
- const lang = process.env.CLAUDEOS_LANG || "en";
89
- console.log(` [Phase 6] Generating prompts (lang: ${lang})...`);
90
- generatePrompts(templates, lang, TEMPLATES_DIR, GENERATED_DIR);
91
- console.log();
92
-
93
- // Save outputs
94
- //
95
- // Port resolution precedence (stack.port):
96
- // 1. stack.port already set by stack-detector (Spring application.yml
97
- // server.port, or .env file PORT variable) — highest authority.
98
- // 2. defaultPort fallback below — framework convention, only used when
99
- // the project declares no port of its own. This is a last-resort
100
- // default; prefer that stack-detector extract it from .env.example
101
- // to keep CLAUDE.md truthful to what the project actually runs.
102
- const defaultPort = (stack.framework === "fastapi" || stack.framework === "django") ? 8000
103
- : stack.framework === "flask" ? 5000
104
- : stack.framework === "vite" ? 5173
105
- : stack.frontend === "angular" ? 4200
106
- : stack.frontend === "nextjs" ? 3000
107
- : (stack.framework === "express" || stack.framework === "nestjs" || stack.framework === "fastify") ? 3000 : 8080;
108
- const analysis = {
109
- analyzedAt: new Date().toISOString(), lang,
110
- stack: { ...stack, port: stack.port || defaultPort },
111
- templates, isMultiStack, rootPackage,
112
- domains, backendDomains, frontendDomains, frontend,
113
- activeDomains: active,
114
- summary: {
115
- totalDomains: domains.length, backendDomains: backendDomains.length,
116
- frontendDomains: frontendDomains.length,
117
- totalFiles: domains.reduce((s, d) => s + d.totalFiles, 0),
118
- },
119
- };
120
- writeFileSafe(path.join(GENERATED_DIR, "project-analysis.json"), JSON.stringify(analysis, null, 2));
121
- console.log(" 💾 project-analysis.json saved");
122
-
123
- const domainGroups = {
124
- generatedAt: new Date().toISOString(), isMultiStack, templates,
125
- totalDomains: domains.length, totalGroups: allGroups.length,
126
- maxDomainsPerGroup: 4, maxFilesPerGroup: 40, groups: allGroups,
127
- };
128
- writeFileSafe(path.join(GENERATED_DIR, "domain-groups.json"), JSON.stringify(domainGroups, null, 2));
129
- console.log(" 💾 domain-groups.json saved\n");
130
- console.log(" ✅ Plan Installer complete\n");
131
- }
132
-
133
- main().catch(e => {
134
- console.error(`\n ❌ Plan Installer failed: ${e.message || e}`);
135
- if (e.code === "EACCES" || e.code === "EPERM") console.error(" Check file/directory permissions.");
136
- process.exit(1);
137
- });
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * ClaudeOS-Core — plan-installer (orchestrator)
5
+ *
6
+ * Modules:
7
+ * - stack-detector.js — detectStack()
8
+ * - structure-scanner.js — scanStructure(), resolveSharedQueryDomains()
9
+ * - domain-grouper.js — splitDomainGroups(), determineActiveDomains(), selectTemplates()
10
+ * - prompt-generator.js — generatePrompts()
11
+ */
12
+
13
+ const path = require("path");
14
+ const { ensureDir, writeFileSafe } = require("../lib/safe-fs");
15
+ const { detectStack } = require("./stack-detector");
16
+ const { scanStructure } = require("./structure-scanner");
17
+ const { splitDomainGroups, determineActiveDomains, selectTemplates } = require("./domain-grouper");
18
+ const { generatePrompts } = require("./prompt-generator");
19
+
20
+ const ROOT = process.env.CLAUDEOS_ROOT || path.resolve(__dirname, "../..");
21
+ const GENERATED_DIR = path.join(ROOT, "claudeos-core/generated");
22
+ const TEMPLATES_DIR = path.join(__dirname, "../pass-prompts/templates");
23
+
24
+ async function main() {
25
+ console.log("\n╔═══════════════════════════════════════╗");
26
+ console.log("║ ClaudeOS-Core — Plan Installer ║");
27
+ console.log("╚═══════════════════════════════════════╝\n");
28
+
29
+ ensureDir(GENERATED_DIR);
30
+
31
+ // Phase 1: Stack detection
32
+ console.log(" [Phase 1] Detecting stack...");
33
+ const stack = await detectStack(ROOT);
34
+ console.log(` Language: ${stack.language || "unknown"} ${stack.languageVersion || ""}`);
35
+ console.log(` Framework: ${stack.framework || "none"} ${stack.frameworkVersion || ""}`);
36
+ if (!stack.language && !stack.framework) {
37
+ console.warn("\n ⚠️ No language or framework detected.");
38
+ console.warn(" Supported: Java, Kotlin, TypeScript, JavaScript, Python");
39
+ console.warn(" Ensure you have build.gradle, package.json, pyproject.toml, or requirements.txt in the project root.\n");
40
+ }
41
+ console.log(` Frontend: ${stack.frontend || "none"} ${stack.frontendVersion || ""}`);
42
+ console.log(` Database: ${stack.database || "none"}`);
43
+ console.log(` ORM: ${stack.orm || "none"}`);
44
+ console.log(` PackageMgr: ${stack.packageManager || "none"}\n`);
45
+
46
+ // Phase 2: Structure scan
47
+ console.log(" [Phase 2] Scanning structure...");
48
+ const { domains, backendDomains, frontendDomains, rootPackage, frontend } = await scanStructure(stack, ROOT);
49
+ console.log(` Backend: ${backendDomains.length} domains`);
50
+ console.log(` Frontend: ${frontendDomains.length} domains`);
51
+ console.log(` Total: ${domains.length} domains`);
52
+ if (rootPackage) console.log(` Package: ${rootPackage}`);
53
+ if (frontend.exists) console.log(` Components: ${frontend.components} components, ${frontend.pages} pages, ${frontend.hooks} hooks`);
54
+ if (backendDomains.length === 0 && frontendDomains.length === 0) {
55
+ console.warn("\n ⚠️ No domains detected.");
56
+ console.warn(" Pass 1 will be skipped. Generated output may be minimal.\n");
57
+ }
58
+ console.log();
59
+
60
+ // Phase 3: Template selection
61
+ console.log(" [Phase 3] Selecting templates...");
62
+ const templates = selectTemplates(stack);
63
+ const isMultiStack = !!(templates.backend && templates.frontend);
64
+ if (templates.backend) console.log(` Backend: ${templates.backend}`);
65
+ if (templates.frontend) console.log(` Frontend: ${templates.frontend}`);
66
+ console.log(` Mode: ${isMultiStack ? "🔀 Multi-stack" : "Single-stack"}`);
67
+ console.log();
68
+
69
+ // Phase 4: Domain group splitting
70
+ console.log(" [Phase 4] Splitting domain groups...");
71
+ const allGroups = [];
72
+ if (templates.backend && backendDomains.length > 0) allGroups.push(...splitDomainGroups(backendDomains, "backend", templates.backend));
73
+ if (templates.frontend && frontendDomains.length > 0) allGroups.push(...splitDomainGroups(frontendDomains, "frontend", templates.frontend));
74
+ allGroups.forEach((g, i) => { g.passNum = i + 1; });
75
+ allGroups.forEach((g, i) => {
76
+ const icon = g.type === "backend" ? "⚙️" : "🎨";
77
+ console.log(` ${icon} Group ${i + 1}: [${g.domains.join(", ")}] (${g.type}, ~${g.estimatedFiles} files)`);
78
+ });
79
+ console.log();
80
+
81
+ // Phase 5: Active domains
82
+ console.log(" [Phase 5] Active domains...");
83
+ const active = determineActiveDomains(stack);
84
+ Object.entries(active).forEach(([k, v]) => console.log(` ${v ? "✅" : "⏭️"} ${k}`));
85
+ console.log();
86
+
87
+ // Phase 6: Prompt generation
88
+ const lang = process.env.CLAUDEOS_LANG || "en";
89
+ console.log(` [Phase 6] Generating prompts (lang: ${lang})...`);
90
+ generatePrompts(templates, lang, TEMPLATES_DIR, GENERATED_DIR);
91
+ console.log();
92
+
93
+ // Save outputs
94
+ //
95
+ // Port resolution precedence (stack.port):
96
+ // 1. stack.port already set by stack-detector (Spring application.yml
97
+ // server.port, or .env file PORT variable) — highest authority.
98
+ // 2. defaultPort fallback below — framework convention, only used when
99
+ // the project declares no port of its own. This is a last-resort
100
+ // default; prefer that stack-detector extract it from .env.example
101
+ // to keep CLAUDE.md truthful to what the project actually runs.
102
+ const defaultPort = (stack.framework === "fastapi" || stack.framework === "django") ? 8000
103
+ : stack.framework === "flask" ? 5000
104
+ : stack.framework === "vite" ? 5173
105
+ : stack.frontend === "angular" ? 4200
106
+ : stack.frontend === "nextjs" ? 3000
107
+ : (stack.framework === "express" || stack.framework === "nestjs" || stack.framework === "fastify") ? 3000 : 8080;
108
+ const analysis = {
109
+ analyzedAt: new Date().toISOString(), lang,
110
+ stack: { ...stack, port: stack.port || defaultPort },
111
+ templates, isMultiStack, rootPackage,
112
+ domains, backendDomains, frontendDomains, frontend,
113
+ activeDomains: active,
114
+ summary: {
115
+ totalDomains: domains.length, backendDomains: backendDomains.length,
116
+ frontendDomains: frontendDomains.length,
117
+ totalFiles: domains.reduce((s, d) => s + d.totalFiles, 0),
118
+ },
119
+ };
120
+ writeFileSafe(path.join(GENERATED_DIR, "project-analysis.json"), JSON.stringify(analysis, null, 2));
121
+ console.log(" 💾 project-analysis.json saved");
122
+
123
+ const domainGroups = {
124
+ generatedAt: new Date().toISOString(), isMultiStack, templates,
125
+ totalDomains: domains.length, totalGroups: allGroups.length,
126
+ maxDomainsPerGroup: 4, maxFilesPerGroup: 40, groups: allGroups,
127
+ };
128
+ writeFileSafe(path.join(GENERATED_DIR, "domain-groups.json"), JSON.stringify(domainGroups, null, 2));
129
+ console.log(" 💾 domain-groups.json saved\n");
130
+ console.log(" ✅ Plan Installer complete\n");
131
+ }
132
+
133
+ main().catch(e => {
134
+ console.error(`\n ❌ Plan Installer failed: ${e.message || e}`);
135
+ if (e.code === "EACCES" || e.code === "EPERM") console.error(" Check file/directory permissions.");
136
+ process.exit(1);
137
+ });
@@ -1,145 +1,188 @@
1
- /**
2
- * ClaudeOS-Core — Prompt Generator
3
- *
4
- * Generates dynamic prompts for Pass 1/2/3 based on detected stack and language.
5
- * Supports multi-stack combined prompts (backend + frontend merged in Pass 3).
6
- */
7
-
8
- const path = require("path");
9
- const { readFileSafe, readJsonSafe, existsSafe, writeFileSafe } = require("../lib/safe-fs");
10
-
11
- /**
12
- * Generate pass prompts from templates.
13
- * @param {object} templates - { backend: string|null, frontend: string|null }
14
- * @param {string} lang - output language code (e.g. "ko", "en")
15
- * @param {string} templatesDir - path to pass-prompts/templates/
16
- * @param {string} generatedDir - path to claudeos-core/generated/
17
- */
18
- function generatePrompts(templates, lang, templatesDir, generatedDir) {
19
- const commonDir = path.join(templatesDir, "common");
20
- const headerPath = path.join(commonDir, "header.md");
21
- const footerPath = path.join(commonDir, "pass3-footer.md");
22
- const langPath = path.join(commonDir, "lang-instructions.json");
23
- const stagingOverridePath = path.join(commonDir, "staging-override.md");
24
-
25
- const header = existsSafe(headerPath) ? readFileSafe(headerPath) : "";
26
- const footer = existsSafe(footerPath) ? readFileSafe(footerPath) : "";
27
- // Injected into pass3/pass4 prompts — redirects .claude/rules/* writes to
28
- // claudeos-core/generated/.staged-rules/* to bypass Claude Code's sensitive-
29
- // path block. The Node.js orchestrator moves the staged files after each pass.
30
- const stagingOverride = existsSafe(stagingOverridePath) ? readFileSafe(stagingOverridePath) + "\n" : "";
31
- // v2.1: Phase 1 "Read Once, Extract Facts" block prepended to every Pass 3
32
- // prompt. Teaches Claude to read pass2-merged.json exactly once into a
33
- // compact in-context fact table and reference that table for all subsequent
34
- // file generation — fixes the `Prompt is too long` failure on large projects
35
- // caused by 10-20× re-reads of pass2-merged.json. Also includes idempotent
36
- // skip rules (Rule B) so interrupted Pass 3 runs can resume safely.
37
- const phase1Path = path.join(commonDir, "pass3-phase1.md");
38
- const phase1 = existsSafe(phase1Path) ? readFileSafe(phase1Path) + "\n" : "";
39
-
40
- // v2.2: CLAUDE.md Scaffold — the 8-section deterministic template for CLAUDE.md.
41
- // Embedded inline (not referenced by path) because the prompt runs in the user's
42
- // project directory where the scaffold file does not exist. Stack-specific pass3
43
- // templates and pass3-footer both reference "pass-prompts/templates/common/
44
- // claude-md-scaffold.md" in their instructions, and this embed makes that
45
- // reference resolvable via in-context content. Wrapped in explicit delimiters
46
- // so the LLM can reliably locate the scaffold block.
47
- const scaffoldPath = path.join(commonDir, "claude-md-scaffold.md");
48
- const scaffold = existsSafe(scaffoldPath)
49
- ? "\n---\n\n# === EMBEDDED: claude-md-scaffold.md ===\n\n"
50
- + "The content below is the scaffold referenced by stack-specific sections\n"
51
- + "and the Pass 3 footer. Treat this embedded block as the authoritative\n"
52
- + "source when instructions mention `pass-prompts/templates/common/claude-md-scaffold.md`.\n\n"
53
- + readFileSafe(scaffoldPath)
54
- + "\n\n# === END EMBEDDED: claude-md-scaffold.md ===\n\n---\n\n"
55
- : "";
56
-
57
- let langInstruction = "";
58
- if (lang && lang !== "en" && existsSafe(langPath)) {
59
- const langData = readJsonSafe(langPath);
60
- if (langData && langData.instructions && langData.instructions[lang]) {
61
- langInstruction = langData.instructions[lang];
62
- const label = (langData.labels && langData.labels[lang]) || lang;
63
- console.log(` 🌐 Language: ${label} (Pass 3 output)`);
64
- }
65
- }
66
-
67
- function readTemplate(templateName, passName) {
68
- const src = path.join(templatesDir, templateName, `${passName}.md`);
69
- if (!existsSafe(src)) return null;
70
- return readFileSafe(src);
71
- }
72
-
73
- const activeTemplates = [...new Set([templates.backend, templates.frontend].filter(Boolean))];
74
- const primaryTemplate = templates.backend || templates.frontend;
75
-
76
- for (let ti = 0; ti < activeTemplates.length; ti++) {
77
- const tmpl = activeTemplates[ti];
78
- const type = (tmpl === templates.frontend && tmpl !== templates.backend) ? "frontend"
79
- : (ti === 1 && templates.frontend) ? "frontend" : "backend";
80
- const body = readTemplate(tmpl, "pass1");
81
- if (body) {
82
- writeFileSafe(path.join(generatedDir, `pass1-${type}-prompt.md`), header + body);
83
- console.log(` ✅ pass1-${type}-prompt.md (${tmpl})`);
84
- }
85
- }
86
-
87
- if (primaryTemplate) {
88
- const body = readTemplate(primaryTemplate, "pass2");
89
- if (body) {
90
- writeFileSafe(path.join(generatedDir, "pass2-prompt.md"), header + body);
91
- console.log(` ✅ pass2-prompt.md (${primaryTemplate})`);
92
- }
93
- }
94
-
95
- if (primaryTemplate) {
96
- const primaryBody = readTemplate(primaryTemplate, "pass3");
97
- if (!primaryBody) {
98
- console.log(` ⚠️ pass3 template not found for ${primaryTemplate}, skipping`);
99
- return;
100
- }
101
- let combinedBody = primaryBody;
102
-
103
- if (templates.backend && templates.frontend && templates.backend !== templates.frontend) {
104
- const frontendBody = readTemplate(templates.frontend, "pass3");
105
- if (frontendBody) {
106
- combinedBody += "\n\n---\n\n";
107
- combinedBody += "# Additional: Frontend generation targets (auto-detected)\n\n";
108
- combinedBody += "In addition to the backend standards above, also generate the following frontend standards.\n";
109
- combinedBody += "Reference the frontend analysis results in pass2-merged.json.\n\n";
110
- const frontendSections = frontendBody
111
- .split(/\n(?=\d+\.\s)/)
112
- .filter(s => /frontend|component|page|routing|data[.\-]fetch|state|styling/i.test(s))
113
- .join("\n");
114
- if (frontendSections.trim()) combinedBody += frontendSections;
115
- }
116
- }
117
-
118
- writeFileSafe(
119
- path.join(generatedDir, "pass3-prompt.md"),
120
- header + langInstruction + stagingOverride + phase1 + scaffold + combinedBody.trimEnd() + "\n" + footer
121
- );
122
- console.log(` ✅ pass3-prompt.md${templates.frontend && templates.backend ? " (multi-stack combined)" : ""}`);
123
- }
124
-
125
- // ─── Pass 4 (L4 memory + rules + CLAUDE.md append) ───
126
- const pass4Path = path.join(commonDir, "pass4.md");
127
- if (existsSafe(pass4Path)) {
128
- const langPath2 = path.join(commonDir, "lang-instructions.json");
129
- const langData2 = existsSafe(langPath2) ? readJsonSafe(langPath2) : null;
130
- const langLabel = (langData2 && langData2.labels && langData2.labels[lang]) || "English";
131
- let pass4Body = readFileSafe(pass4Path);
132
- // Replace {{LANG_NAME}} with the resolved language label.
133
- // Use a replacement function to be consistent with other placeholder
134
- // substitutions and to be safe against future labels that might contain
135
- // `$` characters (which would otherwise be interpreted as back-refs).
136
- pass4Body = pass4Body.replace(/\{\{LANG_NAME\}\}/g, () => langLabel);
137
- writeFileSafe(
138
- path.join(generatedDir, "pass4-prompt.md"),
139
- header + langInstruction + stagingOverride + pass4Body
140
- );
141
- console.log(` pass4-prompt.md (memory + rules, lang: ${langLabel})`);
142
- }
143
- }
144
-
145
- module.exports = { generatePrompts };
1
+ /**
2
+ * ClaudeOS-Core — Prompt Generator
3
+ *
4
+ * Generates dynamic prompts for Pass 1/2/3 based on detected stack and language.
5
+ * Supports multi-stack combined prompts (backend + frontend merged in Pass 3).
6
+ */
7
+
8
+ const path = require("path");
9
+ const { readFileSafe, readJsonSafe, existsSafe, writeFileSafe } = require("../lib/safe-fs");
10
+
11
+ /**
12
+ * Generate pass prompts from templates.
13
+ * @param {object} templates - { backend: string|null, frontend: string|null }
14
+ * @param {string} lang - output language code (e.g. "ko", "en")
15
+ * @param {string} templatesDir - path to pass-prompts/templates/
16
+ * @param {string} generatedDir - path to claudeos-core/generated/
17
+ */
18
+ function generatePrompts(templates, lang, templatesDir, generatedDir) {
19
+ const commonDir = path.join(templatesDir, "common");
20
+ const headerPath = path.join(commonDir, "header.md");
21
+ const footerPath = path.join(commonDir, "pass3-footer.md");
22
+ const langPath = path.join(commonDir, "lang-instructions.json");
23
+ const stagingOverridePath = path.join(commonDir, "staging-override.md");
24
+
25
+ const header = existsSafe(headerPath) ? readFileSafe(headerPath) : "";
26
+ const footer = existsSafe(footerPath) ? readFileSafe(footerPath) : "";
27
+ // Injected into pass3/pass4 prompts — redirects .claude/rules/* writes to
28
+ // claudeos-core/generated/.staged-rules/* to bypass Claude Code's sensitive-
29
+ // path block. The Node.js orchestrator moves the staged files after each pass.
30
+ const stagingOverride = existsSafe(stagingOverridePath) ? readFileSafe(stagingOverridePath) + "\n" : "";
31
+ // v2.1: Phase 1 "Read Once, Extract Facts" block prepended to every Pass 3
32
+ // prompt. Teaches Claude to read pass2-merged.json exactly once into a
33
+ // compact in-context fact table and reference that table for all subsequent
34
+ // file generation — fixes the `Prompt is too long` failure on large projects
35
+ // caused by 10-20× re-reads of pass2-merged.json. Also includes idempotent
36
+ // skip rules (Rule B) so interrupted Pass 3 runs can resume safely.
37
+ const phase1Path = path.join(commonDir, "pass3-phase1.md");
38
+ const phase1 = existsSafe(phase1Path) ? readFileSafe(phase1Path) + "\n" : "";
39
+
40
+ // v2.2: CLAUDE.md Scaffold — the 8-section deterministic template for CLAUDE.md.
41
+ // Embedded inline (not referenced by path) because the prompt runs in the user's
42
+ // project directory where the scaffold file does not exist. Stack-specific pass3
43
+ // templates and pass3-footer both reference "pass-prompts/templates/common/
44
+ // claude-md-scaffold.md" in their instructions, and this embed makes that
45
+ // reference resolvable via in-context content. Wrapped in explicit delimiters
46
+ // so the LLM can reliably locate the scaffold block.
47
+ // v2.3.0: Demote scaffold meta-section `##` headers to `###` before
48
+ // embedding. Inside the embedded scaffold, the only `##`-level headings
49
+ // visible to the LLM should be the 8 canonical CLAUDE.md sections inside
50
+ // the Template structure code block (`## 1. Role Definition` ... `## 8.`).
51
+ // Scaffold meta-sections like `## Why this scaffold exists`, `## Hard
52
+ // constraints`, `## Per-section generation rules`, `## Validation checks`,
53
+ // `## Examples`, `## Usage from pass3 prompts` used to share the same
54
+ // `##` level, producing 40+ `##` lines in the prompt and creating an
55
+ // unintended pattern bias ("this prompt has many `##` sections → my
56
+ // output should too"). Demotion is code-block-aware: `##` lines inside
57
+ // ``` or ~~~ fenced blocks are preserved so the Template structure
58
+ // example remains intact.
59
+ function demoteScaffoldMetaHeaders(scaffoldContent) {
60
+ const lines = scaffoldContent.split(/\r?\n/);
61
+ let inFence = false;
62
+ let fenceMarker = null;
63
+ return lines.map(line => {
64
+ const trimmed = line.trimStart();
65
+ const fenceMatch = trimmed.match(/^(```+|~~~+)/);
66
+ if (fenceMatch) {
67
+ if (!inFence) {
68
+ inFence = true;
69
+ fenceMarker = fenceMatch[1][0];
70
+ } else if (trimmed.startsWith(fenceMarker)) {
71
+ inFence = false;
72
+ fenceMarker = null;
73
+ }
74
+ return line;
75
+ }
76
+ // Only demote outside fenced code blocks.
77
+ // Also preserve the top-level `# CLAUDE.md Scaffold Template ...`
78
+ // single `#` it's the scaffold doc title, not a section.
79
+ if (!inFence && /^## (?!#)/.test(line)) {
80
+ return line.replace(/^## /, "### ");
81
+ }
82
+ return line;
83
+ }).join("\n");
84
+ }
85
+
86
+ const scaffoldPath = path.join(commonDir, "claude-md-scaffold.md");
87
+ const scaffold = existsSafe(scaffoldPath)
88
+ ? "\n---\n\n# === EMBEDDED: claude-md-scaffold.md ===\n\n"
89
+ + "The content below is the scaffold referenced by stack-specific sections\n"
90
+ + "and the Pass 3 footer. Treat this embedded block as the authoritative\n"
91
+ + "source when instructions mention `pass-prompts/templates/common/claude-md-scaffold.md`.\n\n"
92
+ + "NOTE: Scaffold meta-section headers have been demoted from `##` to `###`\n"
93
+ + "when embedded here. The ONLY `##` headings visible in this block are the\n"
94
+ + "8 canonical CLAUDE.md sections inside the Template structure example —\n"
95
+ + "those 8 are the count your generated CLAUDE.md must match exactly.\n\n"
96
+ + demoteScaffoldMetaHeaders(readFileSafe(scaffoldPath))
97
+ + "\n\n# === END EMBEDDED: claude-md-scaffold.md ===\n\n---\n\n"
98
+ : "";
99
+
100
+ let langInstruction = "";
101
+ if (lang && lang !== "en" && existsSafe(langPath)) {
102
+ const langData = readJsonSafe(langPath);
103
+ if (langData && langData.instructions && langData.instructions[lang]) {
104
+ langInstruction = langData.instructions[lang];
105
+ const label = (langData.labels && langData.labels[lang]) || lang;
106
+ console.log(` 🌐 Language: ${label} (Pass 3 output)`);
107
+ }
108
+ }
109
+
110
+ function readTemplate(templateName, passName) {
111
+ const src = path.join(templatesDir, templateName, `${passName}.md`);
112
+ if (!existsSafe(src)) return null;
113
+ return readFileSafe(src);
114
+ }
115
+
116
+ const activeTemplates = [...new Set([templates.backend, templates.frontend].filter(Boolean))];
117
+ const primaryTemplate = templates.backend || templates.frontend;
118
+
119
+ for (let ti = 0; ti < activeTemplates.length; ti++) {
120
+ const tmpl = activeTemplates[ti];
121
+ const type = (tmpl === templates.frontend && tmpl !== templates.backend) ? "frontend"
122
+ : (ti === 1 && templates.frontend) ? "frontend" : "backend";
123
+ const body = readTemplate(tmpl, "pass1");
124
+ if (body) {
125
+ writeFileSafe(path.join(generatedDir, `pass1-${type}-prompt.md`), header + body);
126
+ console.log(` ✅ pass1-${type}-prompt.md (${tmpl})`);
127
+ }
128
+ }
129
+
130
+ if (primaryTemplate) {
131
+ const body = readTemplate(primaryTemplate, "pass2");
132
+ if (body) {
133
+ writeFileSafe(path.join(generatedDir, "pass2-prompt.md"), header + body);
134
+ console.log(` pass2-prompt.md (${primaryTemplate})`);
135
+ }
136
+ }
137
+
138
+ if (primaryTemplate) {
139
+ const primaryBody = readTemplate(primaryTemplate, "pass3");
140
+ if (!primaryBody) {
141
+ console.log(` ⚠️ pass3 template not found for ${primaryTemplate}, skipping`);
142
+ return;
143
+ }
144
+ let combinedBody = primaryBody;
145
+
146
+ if (templates.backend && templates.frontend && templates.backend !== templates.frontend) {
147
+ const frontendBody = readTemplate(templates.frontend, "pass3");
148
+ if (frontendBody) {
149
+ combinedBody += "\n\n---\n\n";
150
+ combinedBody += "# Additional: Frontend generation targets (auto-detected)\n\n";
151
+ combinedBody += "In addition to the backend standards above, also generate the following frontend standards.\n";
152
+ combinedBody += "Reference the frontend analysis results in pass2-merged.json.\n\n";
153
+ const frontendSections = frontendBody
154
+ .split(/\n(?=\d+\.\s)/)
155
+ .filter(s => /frontend|component|page|routing|data[.\-]fetch|state|styling/i.test(s))
156
+ .join("\n");
157
+ if (frontendSections.trim()) combinedBody += frontendSections;
158
+ }
159
+ }
160
+
161
+ writeFileSafe(
162
+ path.join(generatedDir, "pass3-prompt.md"),
163
+ header + langInstruction + stagingOverride + phase1 + scaffold + combinedBody.trimEnd() + "\n" + footer
164
+ );
165
+ console.log(` ✅ pass3-prompt.md${templates.frontend && templates.backend ? " (multi-stack combined)" : ""}`);
166
+ }
167
+
168
+ // ─── Pass 4 (L4 memory + rules + CLAUDE.md append) ───
169
+ const pass4Path = path.join(commonDir, "pass4.md");
170
+ if (existsSafe(pass4Path)) {
171
+ const langPath2 = path.join(commonDir, "lang-instructions.json");
172
+ const langData2 = existsSafe(langPath2) ? readJsonSafe(langPath2) : null;
173
+ const langLabel = (langData2 && langData2.labels && langData2.labels[lang]) || "English";
174
+ let pass4Body = readFileSafe(pass4Path);
175
+ // Replace {{LANG_NAME}} with the resolved language label.
176
+ // Use a replacement function to be consistent with other placeholder
177
+ // substitutions and to be safe against future labels that might contain
178
+ // `$` characters (which would otherwise be interpreted as back-refs).
179
+ pass4Body = pass4Body.replace(/\{\{LANG_NAME\}\}/g, () => langLabel);
180
+ writeFileSafe(
181
+ path.join(generatedDir, "pass4-prompt.md"),
182
+ header + langInstruction + stagingOverride + pass4Body
183
+ );
184
+ console.log(` ✅ pass4-prompt.md (memory + rules, lang: ${langLabel})`);
185
+ }
186
+ }
187
+
188
+ module.exports = { generatePrompts };