claudeos-core 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1649 -907
- package/CONTRIBUTING.md +92 -92
- package/README.de.md +32 -0
- package/README.es.md +32 -0
- package/README.fr.md +32 -0
- package/README.hi.md +32 -0
- package/README.ja.md +32 -0
- package/README.ko.md +1018 -986
- package/README.md +1020 -987
- package/README.ru.md +32 -0
- package/README.vi.md +1019 -987
- package/README.zh-CN.md +32 -0
- package/bin/cli.js +152 -148
- package/bin/commands/init.js +1673 -1554
- package/bin/commands/lint.js +62 -0
- package/bin/commands/memory.js +438 -438
- package/bin/lib/cli-utils.js +206 -206
- package/claude-md-validator/index.js +184 -0
- package/claude-md-validator/reporter.js +66 -0
- package/claude-md-validator/structural-checks.js +528 -0
- package/content-validator/index.js +666 -441
- package/lib/expected-guides.js +23 -23
- package/lib/expected-outputs.js +90 -90
- package/lib/language-config.js +35 -35
- package/lib/memory-scaffold.js +1058 -1054
- package/lib/plan-parser.js +165 -165
- package/lib/staged-rules.js +118 -118
- package/manifest-generator/index.js +174 -174
- package/package.json +90 -87
- package/pass-json-validator/index.js +337 -337
- package/pass-prompts/templates/common/claude-md-scaffold.md +52 -10
- package/pass-prompts/templates/common/pass3-footer.md +402 -224
- package/pass-prompts/templates/common/pass3b-core-header.md +43 -0
- package/pass-prompts/templates/common/pass4.md +375 -305
- package/pass-prompts/templates/common/staging-override.md +26 -26
- package/pass-prompts/templates/node-vite/pass1.md +117 -117
- package/pass-prompts/templates/node-vite/pass2.md +78 -78
- package/pass-prompts/templates/python-flask/pass1.md +119 -119
- package/pass-prompts/templates/python-flask/pass2.md +85 -85
- package/plan-installer/domain-grouper.js +76 -76
- package/plan-installer/index.js +137 -137
- package/plan-installer/prompt-generator.js +188 -145
- package/plan-installer/scanners/scan-frontend.js +505 -473
- package/plan-installer/scanners/scan-java.js +226 -226
- package/plan-installer/scanners/scan-node.js +57 -57
- package/plan-installer/scanners/scan-python.js +85 -85
- package/plan-installer/stack-detector.js +482 -482
- package/plan-installer/structure-scanner.js +65 -65
- package/sync-checker/index.js +177 -177
package/plan-installer/index.js
CHANGED
|
@@ -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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
|
|
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 };
|