claudeos-core 2.3.1 → 2.4.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 +1460 -73
- package/CODE_OF_CONDUCT.md +15 -0
- package/README.de.md +321 -883
- package/README.es.md +322 -883
- package/README.fr.md +322 -883
- package/README.hi.md +322 -883
- package/README.ja.md +322 -883
- package/README.ko.md +322 -882
- package/README.md +321 -883
- package/README.ru.md +322 -885
- package/README.vi.md +322 -883
- package/README.zh-CN.md +321 -881
- package/SECURITY.md +51 -0
- package/bin/commands/init.js +570 -264
- package/content-validator/index.js +185 -12
- package/health-checker/index.js +44 -10
- package/package.json +92 -90
- package/pass-json-validator/index.js +58 -7
- package/pass-prompts/templates/angular/pass3.md +15 -14
- package/pass-prompts/templates/common/claude-md-scaffold.md +203 -20
- package/pass-prompts/templates/common/pass3-footer.md +297 -56
- package/pass-prompts/templates/common/pass3a-facts.md +48 -3
- package/pass-prompts/templates/common/pass4.md +78 -40
- package/pass-prompts/templates/java-spring/pass1.md +54 -0
- package/pass-prompts/templates/java-spring/pass3.md +20 -19
- package/pass-prompts/templates/kotlin-spring/pass1.md +45 -0
- package/pass-prompts/templates/kotlin-spring/pass3.md +24 -23
- package/pass-prompts/templates/node-express/pass3.md +18 -17
- package/pass-prompts/templates/node-fastify/pass3.md +11 -10
- package/pass-prompts/templates/node-nestjs/pass3.md +11 -10
- package/pass-prompts/templates/node-nextjs/pass3.md +18 -17
- package/pass-prompts/templates/node-vite/pass3.md +11 -10
- package/pass-prompts/templates/python-django/pass3.md +18 -17
- package/pass-prompts/templates/python-fastapi/pass3.md +18 -17
- package/pass-prompts/templates/python-flask/pass3.md +9 -8
- package/pass-prompts/templates/vue-nuxt/pass3.md +9 -8
- package/plan-installer/domain-grouper.js +45 -5
- package/plan-installer/index.js +34 -1
- package/plan-installer/pass3-context-builder.js +14 -0
- package/plan-installer/scanners/scan-frontend.js +2 -1
- package/plan-installer/scanners/scan-java.js +98 -2
- package/plan-installer/source-paths.js +242 -0
- package/plan-installer/stack-detector.js +522 -42
package/bin/commands/init.js
CHANGED
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Runs the full 4-Pass pipeline: analyze → merge → generate → memory scaffold.
|
|
5
5
|
* This is the main entry point for project bootstrapping.
|
|
6
|
+
*
|
|
7
|
+
* Refactored internally: cmdInit's 970-line monolith decomposed into ~16 stage
|
|
8
|
+
* helpers (checkPrerequisites, resolveLanguage, applyResumeMode,
|
|
9
|
+
* ensureDirectories, loadDomainGroups, loadPass1Prompts, runPass1Loop,
|
|
10
|
+
* runPass2, buildPass3ContextJson, handlePass3StaleMarker, dispatchPass3,
|
|
11
|
+
* runPass4, runVerificationTools, runLint, runContentValidator,
|
|
12
|
+
* printCompletionBanner). runPass3Split is preserved below unchanged.
|
|
13
|
+
*
|
|
14
|
+
* All string/regex patterns consumed by tests/*.test.js source-parity checks
|
|
15
|
+
* are preserved because the runPass3Split and key stale-marker/pass-4-marker
|
|
16
|
+
* logic are kept in this file verbatim.
|
|
6
17
|
*/
|
|
7
18
|
|
|
8
19
|
const fs = require("fs");
|
|
@@ -194,9 +205,46 @@ async function runPass3Split(ctx) {
|
|
|
194
205
|
return batches;
|
|
195
206
|
}
|
|
196
207
|
|
|
208
|
+
// v2.4.0 — Stack-type classification helper.
|
|
209
|
+
//
|
|
210
|
+
// Returns `{ backend: Set<string>, frontend: Set<string>, isMultiStack: boolean }`
|
|
211
|
+
// built from `project-analysis.json`. The maps are used by
|
|
212
|
+
// `buildBatchScopeNote` to emit ALWAYS-typed per-domain paths
|
|
213
|
+
// (`70.domains/{type}/{domain}.md`) regardless of single/multi-stack.
|
|
214
|
+
// Uniform layout means single-stack projects pay a 1-folder depth
|
|
215
|
+
// cost in exchange for zero-migration when the other stack is later
|
|
216
|
+
// added, and validators recognize a single pattern. The `isMultiStack`
|
|
217
|
+
// flag is retained for tools that may want to surface the distinction
|
|
218
|
+
// (e.g. user-facing console output) but is no longer used to branch
|
|
219
|
+
// path generation.
|
|
220
|
+
function loadDomainTypeMap() {
|
|
221
|
+
const result = { backend: new Set(), frontend: new Set(), isMultiStack: false };
|
|
222
|
+
try {
|
|
223
|
+
const paPath = path.join(GENERATED_DIR, "project-analysis.json");
|
|
224
|
+
if (fileExists(paPath)) {
|
|
225
|
+
const pa = JSON.parse(readFile(paPath));
|
|
226
|
+
if (Array.isArray(pa.backendDomains)) {
|
|
227
|
+
for (const d of pa.backendDomains) {
|
|
228
|
+
const n = d && (d.name || d);
|
|
229
|
+
if (n) result.backend.add(n);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (Array.isArray(pa.frontendDomains)) {
|
|
233
|
+
for (const d of pa.frontendDomains) {
|
|
234
|
+
const n = d && (d.name || d);
|
|
235
|
+
if (n) result.frontend.add(n);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
} catch (_e) { /* tolerate missing/corrupt analysis — fallback to flat paths */ }
|
|
240
|
+
result.isMultiStack = result.backend.size > 0 && result.frontend.size > 0;
|
|
241
|
+
return result;
|
|
242
|
+
}
|
|
243
|
+
|
|
197
244
|
const domainOrder = loadDomainOrder();
|
|
198
245
|
const batches = computeBatches(domainOrder);
|
|
199
246
|
const isBatched = batches.length > 1;
|
|
247
|
+
const domainTypeMap = loadDomainTypeMap();
|
|
200
248
|
|
|
201
249
|
if (isBatched) {
|
|
202
250
|
log(` 📦 Batch sub-division enabled: ${domainOrder.length} domains → ${batches.length} batches per stage (3b, 3c)`);
|
|
@@ -274,28 +322,100 @@ async function runPass3Split(ctx) {
|
|
|
274
322
|
// and which common files to include vs skip.
|
|
275
323
|
function buildBatchScopeNote(stageKind, batchIndex, totalBatches, batchDomains) {
|
|
276
324
|
const isLastBatch = batchIndex === totalBatches - 1;
|
|
325
|
+
const isSingleBatch = totalBatches === 1;
|
|
277
326
|
const domainList = batchDomains.map(d => `\`${d}\``).join(", ");
|
|
278
327
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
328
|
+
// v2.4.0 — Always-typed per-domain layout.
|
|
329
|
+
//
|
|
330
|
+
// Every per-domain file lives under a `{type}/` sub-folder
|
|
331
|
+
// (`70.domains/backend/` or `70.domains/frontend/`) regardless of
|
|
332
|
+
// whether the project is single-stack or multi-stack. This is a
|
|
333
|
+
// deliberate uniform-convention choice:
|
|
334
|
+
//
|
|
335
|
+
// - Single-stack projects pay a 1-folder depth cost; in exchange
|
|
336
|
+
// they migrate to multi-stack with ZERO file moves when frontend
|
|
337
|
+
// (or backend) is later added.
|
|
338
|
+
// - LLM never has to decide which form to use → no probabilistic
|
|
339
|
+
// drift between Pass 3 runs.
|
|
340
|
+
// - Validators (content-validator, claude-md-validator) need
|
|
341
|
+
// to recognize only ONE pattern.
|
|
342
|
+
// - Future stack types (mobile, cli, agent, ...) extend naturally
|
|
343
|
+
// by adding new `{type}/` sub-folders.
|
|
344
|
+
//
|
|
345
|
+
// Per-domain type lookup uses `domainTypeMap` from
|
|
346
|
+
// `project-analysis.json`. Domains not in either set fall back to
|
|
347
|
+
// `backend` (the dominant case in real-world projects) — this
|
|
348
|
+
// happens only when the analysis JSON is malformed or absent.
|
|
349
|
+
const typeOf = (name) => {
|
|
350
|
+
if (domainTypeMap.frontend.has(name)) return "frontend";
|
|
351
|
+
// backend is the default when the domain is unknown to both sets.
|
|
352
|
+
// This happens only on malformed analysis JSON; backend is the
|
|
353
|
+
// safer default since it's the dominant project type.
|
|
354
|
+
return "backend";
|
|
355
|
+
};
|
|
356
|
+
const stdPathFor = (name) => `claudeos-core/standard/70.domains/${typeOf(name)}/${name}.md`;
|
|
357
|
+
const rulePathFor = (name) => `.claude/rules/70.domains/${typeOf(name)}/${name}-rules.md`;
|
|
358
|
+
const stagedRulePathFor = (name) => `claudeos-core/generated/.staged-rules/70.domains/${typeOf(name)}/${name}-rules.md`;
|
|
359
|
+
|
|
360
|
+
let note = isSingleBatch
|
|
361
|
+
? `## Per-domain scope (${stageKind}, single-batch run)\n\n`
|
|
362
|
+
: `## Batch scope (${stageKind}-batch ${batchIndex + 1}/${totalBatches})\n\n`;
|
|
363
|
+
if (isSingleBatch) {
|
|
364
|
+
note += `${batchDomains.length} domain(s) will be processed in this single Pass 3b stage.\n`;
|
|
365
|
+
note += `Per-domain files are REQUIRED regardless of domain count — they enable domain-scoped \`paths\` glob targeting for rules.\n\n`;
|
|
366
|
+
} else {
|
|
367
|
+
note += `This Pass 3 stage has been sub-divided into ${totalBatches} batches to avoid context overflow.\n`;
|
|
368
|
+
note += `**You are processing batch ${batchIndex + 1} of ${totalBatches}.**\n\n`;
|
|
369
|
+
}
|
|
282
370
|
|
|
283
371
|
if (stageKind === "3b") {
|
|
284
|
-
note +=
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
note +=
|
|
290
|
-
|
|
372
|
+
note += isSingleBatch
|
|
373
|
+
? `**Domains in scope (all ${batchDomains.length})**: ${domainList}\n\n`
|
|
374
|
+
: `**Domains in THIS batch**: ${domainList}\n\n`;
|
|
375
|
+
// Always show per-domain target paths so the LLM follows the
|
|
376
|
+
// typed sub-folder convention without inferring from domain name.
|
|
377
|
+
note += `**Per-domain target paths (always under \`{type}/\` sub-folder — \`backend\` or \`frontend\`):**\n`;
|
|
378
|
+
for (const d of batchDomains) {
|
|
379
|
+
note += `- \`${d}\` → \`${stdPathFor(d)}\` and \`${rulePathFor(d)}\`\n`;
|
|
380
|
+
}
|
|
381
|
+
note += `\n`;
|
|
382
|
+
note += `**Rules**:\n`;
|
|
383
|
+
if (isSingleBatch) {
|
|
384
|
+
// Single-batch (≤15 domains): common files AND per-domain files in same stage.
|
|
385
|
+
note += `1. Generate ALL common files (CLAUDE.md + standard/00.core/* + standard/10.backend/* + standard/30.security-db/* + standard/40.infra/* + standard/80.verification/* + .claude/rules/00.core/* + .claude/rules/10.backend/* + .claude/rules/30.security-db/* + .claude/rules/40.infra/* + .claude/rules/50.sync/*) per the stack pass3 template.\n`;
|
|
386
|
+
note += `2. **ALSO** create ONE NEW per-domain standard file PER domain listed above at \`claudeos-core/standard/70.domains/{type}/{domain}.md\` (use the exact \`{type}\` shown in the per-domain target paths above). **Expected output: ${batchDomains.length} new file(s) under \`70.domains/{type}/\`** — one per domain. Do NOT inline these into the common standards (00.core/, 10.backend/, etc.); per-domain files are DOMAIN-scoped while common files are TOPIC-based, and they must live at separate paths so per-domain rules can scope to them via \`paths\` glob.\n`;
|
|
387
|
+
note += `3. **ALSO** create ONE NEW per-domain rule file PER domain listed above at \`.claude/rules/70.domains/{type}/{domain}-rules.md\` (via staging-override path \`claudeos-core/generated/.staged-rules/70.domains/{type}/{domain}-rules.md\`). Each rule file MUST have a \`paths\` frontmatter glob scoped to that domain's source directories so the rule auto-loads only when editing the relevant files.\n`;
|
|
388
|
+
note += `4. Per-domain file generation is MANDATORY even for ${batchDomains.length}-domain projects (any size below the 15-domain batch threshold). The convention is uniform across all project sizes for consistency: same dirtree shape regardless of scale.\n`;
|
|
389
|
+
note += `5. Rule B idempotent skip applies — if a per-domain file already exists with substantive content, print \`[SKIP] <path>\` and continue.\n`;
|
|
390
|
+
} else {
|
|
391
|
+
// Multi-batch (>15 domains): common files in 3b-core, per-domain in batch stages.
|
|
392
|
+
note += `1. CLAUDE.md and all common standard/ files (00.core/, 30.security-db/, 40.infra/, etc.) are ALREADY GENERATED by the 3b-core stage. DO NOT regenerate them.\n`;
|
|
393
|
+
note += `2. Create ONE NEW per-domain standard file PER domain listed above at \`claudeos-core/standard/70.domains/{type}/{domain}.md\` (use the exact \`{type}\` shown in the per-domain target paths above — \`backend\` or \`frontend\`). **Expected output: ${batchDomains.length} new files** — one per domain in this batch. Do NOT inline these into the common files generated by 3b-core; common files are TOPIC-based, per-domain files are DOMAIN-scoped and must live at separate paths so per-domain rules can scope to them via \`paths\` glob.\n`;
|
|
394
|
+
note += `3. Create ONE NEW per-domain rule file PER domain listed above at \`.claude/rules/70.domains/{type}/{domain}-rules.md\` (via staging-override path \`claudeos-core/generated/.staged-rules/70.domains/{type}/{domain}-rules.md\`). Each rule file MUST have a \`paths\` frontmatter glob scoped to that domain's source directories so the rule auto-loads only when editing the relevant files. Common rules are already generated by 3b-core — do NOT regenerate.\n`;
|
|
395
|
+
note += `4. DO NOT generate standard/ or rules/ files for domains NOT in the above list — those are/will be processed in other batches.\n`;
|
|
396
|
+
note += `5. Rule B idempotent skip applies ONLY to files at the per-domain paths shown above for THIS batch's domains — i.e. resume after a crash. **Do NOT use Rule B as justification for skipping the entire batch** because common files at OTHER paths exist (those are out of scope for this batch). If a per-domain target file does NOT exist for a batch domain, you MUST generate it; emitting "0 new files" for the whole batch when domains were assigned to you is a critical Pass 3 failure mode.\n`;
|
|
397
|
+
}
|
|
291
398
|
} else if (stageKind === "3c") {
|
|
292
|
-
note +=
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
note +=
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
399
|
+
note += isSingleBatch
|
|
400
|
+
? `**Domains in scope (all ${batchDomains.length})**: ${domainList}\n\n`
|
|
401
|
+
: `**Domains in THIS batch**: ${domainList}\n\n`;
|
|
402
|
+
note += `**Rules**:\n`;
|
|
403
|
+
if (isSingleBatch) {
|
|
404
|
+
// Single-batch (≤15 domains): ALL of 3c — guides + common skills + per-domain skill notes — in one stage.
|
|
405
|
+
note += `1. Generate ALL guide/ files (01.onboarding, 02.usage, 03.troubleshooting, 04.architecture) per the stack pass3 template.\n`;
|
|
406
|
+
note += `2. Generate common skills: \`claudeos-core/skills/00.shared/MANIFEST.md\` + the category orchestrator(s) at \`claudeos-core/skills/{category}/01.scaffold-*-feature.md\` + their sub-skill files under \`{category}/scaffold-*-feature/\`.\n`;
|
|
407
|
+
note += `3. **ALSO** generate \`claudeos-core/skills/{category}/02.domains.md\` orchestrator (sibling to the \`domains/\` sub-folder) — REQUIRED for ALL projects regardless of domain count, mirrors the canonical \`01.scaffold-*-feature.md\` ↔ \`scaffold-*-feature/\` pattern.\n`;
|
|
408
|
+
note += `4. **ALSO** generate per-domain skill notes at \`claudeos-core/skills/{category}/domains/{domain}.md\` for EACH of the ${batchDomains.length} domain(s). **Expected output: ${batchDomains.length} new file(s) under \`{category}/domains/\`** — content describes domain-specific anti-patterns, dependencies, naming quirks. Per-domain skill generation is MANDATORY even for ${batchDomains.length}-domain projects (the convention is uniform across all project sizes).\n`;
|
|
409
|
+
note += `5. MUST register every newly created skill file in \`00.shared/MANIFEST.md\` (orchestrator + sub-skills + 02.domains.md + per-domain notes).\n`;
|
|
410
|
+
note += `6. Rule B idempotent skip applies — if a skill file already exists, print \`[SKIP] <path>\` and move on.\n`;
|
|
411
|
+
} else {
|
|
412
|
+
// Multi-batch (>15 domains): guides+common skills in 3c-core, per-domain in batch stages.
|
|
413
|
+
note += `1. ALL guide/ files (01.onboarding, 02.usage, 03.troubleshooting, 04.architecture) are ALREADY GENERATED by the 3c-core stage. DO NOT regenerate.\n`;
|
|
414
|
+
note += `2. Common skills (00.shared/, orchestrator SKILL.md) and \`02.domains.md\` orchestrator are ALREADY GENERATED by 3c-core. DO NOT regenerate.\n`;
|
|
415
|
+
note += `3. Generate per-domain skill notes ONLY for the domains listed above at \`claudeos-core/skills/{category}/domains/{domain}.md\` — typically under 10.backend-crud/ or 20.frontend-page/.\n`;
|
|
416
|
+
note += `4. DO NOT generate skills for domains NOT in the above list.\n`;
|
|
417
|
+
note += `5. Rule B idempotent skip applies: if a skill file already exists, print \`[SKIP] <path>\` and move on.\n`;
|
|
418
|
+
}
|
|
299
419
|
}
|
|
300
420
|
|
|
301
421
|
if (!isLastBatch) {
|
|
@@ -326,7 +446,7 @@ async function runPass3Split(ctx) {
|
|
|
326
446
|
coreNote += ` - claudeos-core/standard/00.core/*.md (project overview, architecture, conventions)\n`;
|
|
327
447
|
coreNote += ` - claudeos-core/standard/30.security-db/*.md\n`;
|
|
328
448
|
coreNote += ` - claudeos-core/standard/40.infra/*.md\n`;
|
|
329
|
-
coreNote += ` - claudeos-core/standard/
|
|
449
|
+
coreNote += ` - claudeos-core/standard/80.verification/*.md\n`;
|
|
330
450
|
coreNote += ` - claudeos-core/standard/90.optional/*.md\n`;
|
|
331
451
|
coreNote += ` - (stack-specific common sections as defined in the pass3 template)\n`;
|
|
332
452
|
coreNote += `3. ALL rules files (via staging-override path .claude/rules → generated/.staged-rules):\n`;
|
|
@@ -345,11 +465,13 @@ async function runPass3Split(ctx) {
|
|
|
345
465
|
coreNote += ` - claudeos-core/guide/04.architecture/*.md\n`;
|
|
346
466
|
coreNote += `2. COMMON skills only:\n`;
|
|
347
467
|
coreNote += ` - claudeos-core/skills/00.shared/*\n`;
|
|
348
|
-
coreNote += ` - top-level orchestrator SKILL.md files (e.g. \`10.backend-crud/SKILL.md\` without any subfolder)\n
|
|
468
|
+
coreNote += ` - top-level orchestrator SKILL.md files (e.g. \`10.backend-crud/SKILL.md\` without any subfolder)\n`;
|
|
469
|
+
coreNote += `3. **Per-domain orchestrator** (REQUIRED whenever batches are non-empty — i.e. ${totalDomains} domains will be processed): for EACH active skill category that will receive per-domain notes, generate the sibling orchestrator file at \`claudeos-core/skills/{category}/02.domains.md\`. The basename stem (\`domains\`) MUST match the \`domains/\` sub-folder name so \`content-validator\`'s standard orchestrator-stem matching covers all sub-skills directly. The orchestrator's content describes the per-domain notes pattern, lists the ${totalDomains} domains that will be processed, and links to \`00.shared/MANIFEST.md\`. Generate it BEFORE 3c-N batches run so it's already in place when sub-skills land. Numbered \`02.\` because \`01.scaffold-*-feature.md\` already occupies the \`01.\` slot at the category root.\n\n`;
|
|
349
470
|
coreNote += `**What NOT to generate in ${stageKind}-core**:\n`;
|
|
350
471
|
coreNote += `- Per-domain skill sub-directories (e.g. \`10.backend-crud/scaffold-order-feature/\`) — those belong to 3c-1, 3c-2, ... batch stages.\n`;
|
|
472
|
+
coreNote += `- Per-domain note files (\`10.backend-crud/domains/{domain}.md\`) — those belong to 3c-N batch stages. ${stageKind}-core only generates the parent ORCHESTRATOR (\`02.domains.md\`).\n`;
|
|
351
473
|
coreNote += `- Anything under plan/, database/, mcp-guide/ — those belong to 3d.\n\n`;
|
|
352
|
-
coreNote += `**Per-domain
|
|
474
|
+
coreNote += `**Per-domain skill notes will be generated in subsequent 3c-1, 3c-2, ... batch stages, under each category's \`domains/\` sub-folder.**\n`;
|
|
353
475
|
}
|
|
354
476
|
|
|
355
477
|
coreNote += `\nIf you find yourself about to generate a domain-specific file in this stage: STOP. Emit \`[DEFER] <path> — will be generated in 3b-N / 3c-N batch\` and move on.\n\n`;
|
|
@@ -539,15 +661,23 @@ async function runPass3Split(ctx) {
|
|
|
539
661
|
? `domain batch ${bi + 1}/${batches.length} (${batchDomains.length} domains)`
|
|
540
662
|
: "core files (CLAUDE.md + standard + rules)";
|
|
541
663
|
|
|
542
|
-
// Per-
|
|
543
|
-
//
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
664
|
+
// v2.4.0 — Per-domain scope note ALWAYS injected (single OR multi-batch).
|
|
665
|
+
//
|
|
666
|
+
// Previously single-batch projects (≤15 domains) skipped the scope note,
|
|
667
|
+
// and the stack pass3 template alone didn't mandate per-domain file
|
|
668
|
+
// generation under `70.domains/{type}/`. As a result, small projects
|
|
669
|
+
// got common standards but no per-domain files — inconsistent with
|
|
670
|
+
// multi-batch projects and breaking the uniform-convention contract.
|
|
671
|
+
//
|
|
672
|
+
// The note text itself branches on isSingleBatch internally:
|
|
673
|
+
// - Single batch: "Generate common files AND per-domain files"
|
|
674
|
+
// - Multi batch: "Per-domain only (common files done in 3b-core)"
|
|
675
|
+
const batchScopeNote = buildBatchScopeNote("3b", bi, batches.length, batchDomains);
|
|
547
676
|
const baseprompt = buildStagePrompt("pass3b-core-header.md", true);
|
|
548
|
-
const promptWithScope =
|
|
549
|
-
|
|
550
|
-
|
|
677
|
+
const promptWithScope = baseprompt.replace(
|
|
678
|
+
/\n## Scope of this step/,
|
|
679
|
+
`\n${batchScopeNote}\n## Scope of this step`
|
|
680
|
+
);
|
|
551
681
|
|
|
552
682
|
await runStage(stageId, label, promptWithScope, {
|
|
553
683
|
expectsStagedRules: true,
|
|
@@ -621,13 +751,16 @@ async function runPass3Split(ctx) {
|
|
|
621
751
|
? `domain skills batch ${bi + 1}/${batches.length} (${batchDomains.length} domains)`
|
|
622
752
|
: "skills and guides";
|
|
623
753
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
754
|
+
// v2.4.0 — Per-domain skill scope note ALWAYS injected (single OR multi-batch).
|
|
755
|
+
// Same rationale as Pass 3b: small projects need per-domain skill notes
|
|
756
|
+
// (`{category}/domains/{domain}.md`) and the `02.domains.md` orchestrator
|
|
757
|
+
// for uniform layout across all project sizes.
|
|
758
|
+
const batchScopeNote = buildBatchScopeNote("3c", bi, batches.length, batchDomains);
|
|
627
759
|
const baseprompt = buildStagePrompt("pass3c-skills-guide-header.md", true);
|
|
628
|
-
const promptWithScope =
|
|
629
|
-
|
|
630
|
-
|
|
760
|
+
const promptWithScope = baseprompt.replace(
|
|
761
|
+
/\n## Scope of this step/,
|
|
762
|
+
`\n${batchScopeNote}\n## Scope of this step`
|
|
763
|
+
);
|
|
631
764
|
|
|
632
765
|
await runStage(stageId, label, promptWithScope, {
|
|
633
766
|
expectsStagedRules: true, // skills occasionally include rule files
|
|
@@ -701,15 +834,22 @@ async function runPass3Split(ctx) {
|
|
|
701
834
|
log(` 🎉 Pass 3 split complete: ${completedGroups.length}/${totalStages} stages successful`);
|
|
702
835
|
}
|
|
703
836
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
837
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
838
|
+
// cmdInit stage helpers
|
|
839
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
840
|
+
// The original cmdInit was 970 lines with 77 if-statements and 17 try-blocks
|
|
841
|
+
// in a single function. Below it is decomposed into one helper per pipeline
|
|
842
|
+
// phase. Each helper owns a self-contained step with clear inputs/outputs.
|
|
843
|
+
//
|
|
844
|
+
// Shared state passed across helpers:
|
|
845
|
+
// - { lang, stepTimes, completedSteps (via ref), progressBar, wasFreshClean }
|
|
846
|
+
// Helpers that advance the outer progress bar return a `stepsDelta` value
|
|
847
|
+
// that cmdInit adds to `completedSteps` locally. This preserves the literal
|
|
848
|
+
// `completedSteps++` text at the top-level orchestrator, which several
|
|
849
|
+
// source-parity tests rely on for stale-check region detection.
|
|
850
|
+
|
|
851
|
+
// ─── Stage 1: Prerequisites check ──────────────────────────────────
|
|
852
|
+
function checkPrerequisites() {
|
|
713
853
|
const hasProjectMarker = [".git", "package.json", "build.gradle", "build.gradle.kts", "pom.xml", "pyproject.toml", "requirements.txt"].some(
|
|
714
854
|
m => fs.existsSync(path.join(PROJECT_ROOT, m))
|
|
715
855
|
);
|
|
@@ -734,8 +874,10 @@ async function cmdInit(parsedArgs) {
|
|
|
734
874
|
if (!claudeAuth) {
|
|
735
875
|
throw new InitError("Claude Code may not be authenticated.\n Run: claude (and complete authentication)\n Then retry: npx claudeos-core init");
|
|
736
876
|
}
|
|
877
|
+
}
|
|
737
878
|
|
|
738
|
-
|
|
879
|
+
// ─── Stage 2: Resolve output language ─────────────────────────────
|
|
880
|
+
async function resolveLanguage(parsedArgs) {
|
|
739
881
|
let lang = parsedArgs.lang;
|
|
740
882
|
if (!lang) {
|
|
741
883
|
lang = await selectLangInteractive();
|
|
@@ -765,114 +907,109 @@ async function cmdInit(parsedArgs) {
|
|
|
765
907
|
}
|
|
766
908
|
|
|
767
909
|
process.env.CLAUDEOS_LANG = lang;
|
|
910
|
+
return lang;
|
|
911
|
+
}
|
|
768
912
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
if (existingPass1.length > 0 || pass2Exists) {
|
|
775
|
-
if (parsedArgs.force) {
|
|
776
|
-
// --force: clean all generated files for truly fresh start
|
|
777
|
-
const genFiles = fs.readdirSync(GENERATED_DIR).filter(f => f.endsWith(".json") || f.endsWith(".md"));
|
|
778
|
-
for (const f of genFiles) fs.unlinkSync(path.join(GENERATED_DIR, f));
|
|
779
|
-
// Also clean any leftover .staged-rules/ from a prior crashed run
|
|
780
|
-
// (only .json/.md are unlinked above; directories aren't touched).
|
|
781
|
-
const stagedDir = path.join(GENERATED_DIR, ".staged-rules");
|
|
782
|
-
if (fileExists(stagedDir)) fs.rmSync(stagedDir, { recursive: true, force: true });
|
|
783
|
-
// Also wipe .claude/rules/ so Guard 2 (zero-rules detection) can't
|
|
784
|
-
// false-negative on stale rules from a previous run when the fresh
|
|
785
|
-
// Pass 3 run fails silently (e.g. Claude ignores staging-override).
|
|
786
|
-
// Step [2] recreates the subdirs from scratch. Any manual edits the
|
|
787
|
-
// user made to rule files are lost — acceptable under --force
|
|
788
|
-
// ("truly fresh start").
|
|
789
|
-
const rulesDir = path.join(PROJECT_ROOT, ".claude/rules");
|
|
790
|
-
if (fileExists(rulesDir)) fs.rmSync(rulesDir, { recursive: true, force: true });
|
|
791
|
-
wasFreshClean = true;
|
|
792
|
-
log(" 🔄 Previous results deleted (--force)\n");
|
|
793
|
-
} else {
|
|
794
|
-
// v2.2.0 upgrade detection: if project was generated with older claudeos-core
|
|
795
|
-
// (pre-2.2.0), default "resume" mode will skip regeneration of existing files
|
|
796
|
-
// per Rule B idempotency, meaning v2.2.0 structural improvements will NOT be
|
|
797
|
-
// picked up. Detect this case by checking CLAUDE.md for v2.2.0 markers.
|
|
798
|
-
const claudeMd = path.join(PROJECT_ROOT, "CLAUDE.md");
|
|
799
|
-
if (fileExists(claudeMd)) {
|
|
800
|
-
try {
|
|
801
|
-
const content = fs.readFileSync(claudeMd, "utf-8");
|
|
802
|
-
// v2.2.0 scaffold enforces EXACTLY 8 top-level `##` sections.
|
|
803
|
-
// Pre-v2.2.0 CLAUDE.md files typically carry 9+ sections (extra
|
|
804
|
-
// "Rules Summary" / "Common Rules" / "Required to Observe"
|
|
805
|
-
// blocks that v2.2.0 forbids). Counting `^## ` headings is a
|
|
806
|
-
// language-independent heuristic that works across all 10
|
|
807
|
-
// supported output languages. False positive (an existing
|
|
808
|
-
// 8-section pre-v2.2.0 CLAUDE.md) is acceptable — the user
|
|
809
|
-
// simply won't see the upgrade warning and can still run
|
|
810
|
-
// `--force` manually.
|
|
811
|
-
const sectionCount = (content.match(/^## /gm) || []).length;
|
|
812
|
-
const hasV220Section8 = sectionCount === 8;
|
|
813
|
-
if (!hasV220Section8) {
|
|
814
|
-
log("\n ⚠️ v2.2.0 upgrade detected");
|
|
815
|
-
log(" ─────────────────────────");
|
|
816
|
-
log(" Your existing CLAUDE.md was generated with an older claudeos-core version.");
|
|
817
|
-
log(" v2.2.0 introduces structural changes that the default 'resume' mode");
|
|
818
|
-
log(" CANNOT apply because existing files are preserved under Rule B (idempotency).");
|
|
819
|
-
log("");
|
|
820
|
-
log(" To fully adopt v2.2.0, choose one of:");
|
|
821
|
-
log(" 1. Rerun with --force: npx claudeos-core init --force");
|
|
822
|
-
log(" (overwrites generated files; your memory/ content is preserved)");
|
|
823
|
-
log(" 2. Choose 'fresh' below (equivalent to --force)");
|
|
824
|
-
log("");
|
|
825
|
-
log(" See CHANGELOG.md Migration section for full details.\n");
|
|
826
|
-
}
|
|
827
|
-
} catch (_) { /* Read error is non-fatal; proceed to resume prompt */ }
|
|
828
|
-
}
|
|
913
|
+
// ─── Stage 3: Resume/Fresh selection ──────────────────────────────
|
|
914
|
+
// Returns { wasFreshClean: boolean } — the caller uses this to gate the
|
|
915
|
+
// v1.7.x migration backfill in dispatchPass3.
|
|
916
|
+
async function applyResumeMode(parsedArgs, lang) {
|
|
917
|
+
let wasFreshClean = false;
|
|
829
918
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
}
|
|
856
|
-
}
|
|
919
|
+
if (!fs.existsSync(GENERATED_DIR)) return { wasFreshClean };
|
|
920
|
+
|
|
921
|
+
const existingPass1 = fs.readdirSync(GENERATED_DIR).filter(f => f.startsWith("pass1-") && f.endsWith(".json"));
|
|
922
|
+
const pass2Exists = fileExists(path.join(GENERATED_DIR, "pass2-merged.json"));
|
|
923
|
+
if (existingPass1.length === 0 && !pass2Exists) return { wasFreshClean };
|
|
924
|
+
|
|
925
|
+
if (parsedArgs.force) {
|
|
926
|
+
// --force: clean all generated files for truly fresh start
|
|
927
|
+
const genFiles = fs.readdirSync(GENERATED_DIR).filter(f => f.endsWith(".json") || f.endsWith(".md"));
|
|
928
|
+
for (const f of genFiles) fs.unlinkSync(path.join(GENERATED_DIR, f));
|
|
929
|
+
// Also clean any leftover .staged-rules/ from a prior crashed run
|
|
930
|
+
// (only .json/.md are unlinked above; directories aren't touched).
|
|
931
|
+
const stagedDir = path.join(GENERATED_DIR, ".staged-rules");
|
|
932
|
+
if (fileExists(stagedDir)) fs.rmSync(stagedDir, { recursive: true, force: true });
|
|
933
|
+
// Also wipe .claude/rules/ so Guard 2 (zero-rules detection) can't
|
|
934
|
+
// false-negative on stale rules from a previous run when the fresh
|
|
935
|
+
// Pass 3 run fails silently (e.g. Claude ignores staging-override).
|
|
936
|
+
// Step [2] recreates the subdirs from scratch. Any manual edits the
|
|
937
|
+
// user made to rule files are lost — acceptable under --force
|
|
938
|
+
// ("truly fresh start").
|
|
939
|
+
const rulesDir = path.join(PROJECT_ROOT, ".claude/rules");
|
|
940
|
+
if (fileExists(rulesDir)) fs.rmSync(rulesDir, { recursive: true, force: true });
|
|
941
|
+
wasFreshClean = true;
|
|
942
|
+
log(" 🔄 Previous results deleted (--force)\n");
|
|
943
|
+
return { wasFreshClean };
|
|
857
944
|
}
|
|
858
945
|
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
946
|
+
// v2.2.0 upgrade detection: if project was generated with older claudeos-core
|
|
947
|
+
// (pre-2.2.0), default "resume" mode will skip regeneration of existing files
|
|
948
|
+
// per Rule B idempotency, meaning v2.2.0 structural improvements will NOT be
|
|
949
|
+
// picked up. Detect this case by checking CLAUDE.md for v2.2.0 markers.
|
|
950
|
+
const claudeMd = path.join(PROJECT_ROOT, "CLAUDE.md");
|
|
951
|
+
if (fileExists(claudeMd)) {
|
|
952
|
+
try {
|
|
953
|
+
const content = fs.readFileSync(claudeMd, "utf-8");
|
|
954
|
+
// v2.2.0 scaffold enforces EXACTLY 8 top-level `##` sections.
|
|
955
|
+
// Pre-v2.2.0 CLAUDE.md files typically carry 9+ sections (extra
|
|
956
|
+
// "Rules Summary" / "Common Rules" / "Required to Observe"
|
|
957
|
+
// blocks that v2.2.0 forbids). Counting `^## ` headings is a
|
|
958
|
+
// language-independent heuristic that works across all 10
|
|
959
|
+
// supported output languages. False positive (an existing
|
|
960
|
+
// 8-section pre-v2.2.0 CLAUDE.md) is acceptable — the user
|
|
961
|
+
// simply won't see the upgrade warning and can still run
|
|
962
|
+
// `--force` manually.
|
|
963
|
+
const sectionCount = (content.match(/^## /gm) || []).length;
|
|
964
|
+
const hasV220Section8 = sectionCount === 8;
|
|
965
|
+
if (!hasV220Section8) {
|
|
966
|
+
log("\n ⚠️ v2.2.0 upgrade detected");
|
|
967
|
+
log(" ─────────────────────────");
|
|
968
|
+
log(" Your existing CLAUDE.md was generated with an older claudeos-core version.");
|
|
969
|
+
log(" v2.2.0 introduces structural changes that the default 'resume' mode");
|
|
970
|
+
log(" CANNOT apply because existing files are preserved under Rule B (idempotency).");
|
|
971
|
+
log("");
|
|
972
|
+
log(" To fully adopt v2.2.0, choose one of:");
|
|
973
|
+
log(" 1. Rerun with --force: npx claudeos-core init --force");
|
|
974
|
+
log(" (overwrites generated files; your memory/ content is preserved)");
|
|
975
|
+
log(" 2. Choose 'fresh' below (equivalent to --force)");
|
|
976
|
+
log("");
|
|
977
|
+
log(" See CHANGELOG.md Migration section for full details.\n");
|
|
978
|
+
}
|
|
979
|
+
} catch (_) { /* Read error is non-fatal; proceed to resume prompt */ }
|
|
980
|
+
}
|
|
866
981
|
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
if (!
|
|
870
|
-
|
|
982
|
+
const status = { pass1Done: existingPass1.length, pass2Done: pass2Exists };
|
|
983
|
+
const mode = await selectResumeMode(lang, status);
|
|
984
|
+
if (!mode) throw new InitError("Cancelled.");
|
|
985
|
+
if (mode === "fresh") {
|
|
986
|
+
for (const f of existingPass1) fs.unlinkSync(path.join(GENERATED_DIR, f));
|
|
987
|
+
if (pass2Exists) fs.unlinkSync(path.join(GENERATED_DIR, "pass2-merged.json"));
|
|
988
|
+
// Also reset pass 3 & pass 4 markers so they re-run
|
|
989
|
+
const pass3M = path.join(GENERATED_DIR, "pass3-complete.json");
|
|
990
|
+
const pass4M = path.join(GENERATED_DIR, "pass4-memory.json");
|
|
991
|
+
if (fileExists(pass3M)) fs.unlinkSync(pass3M);
|
|
992
|
+
if (fileExists(pass4M)) fs.unlinkSync(pass4M);
|
|
993
|
+
// Clean .staged-rules/ leftover from a prior crashed run (same reason as --force branch).
|
|
994
|
+
const stagedDir = path.join(GENERATED_DIR, ".staged-rules");
|
|
995
|
+
if (fileExists(stagedDir)) fs.rmSync(stagedDir, { recursive: true, force: true });
|
|
996
|
+
// Wipe .claude/rules/ for the same Guard 2 false-negative reason as
|
|
997
|
+
// the --force branch. Step [2] recreates the subdirs; any manual
|
|
998
|
+
// edits are lost — acceptable under an explicit "fresh" choice.
|
|
999
|
+
const rulesDir = path.join(PROJECT_ROOT, ".claude/rules");
|
|
1000
|
+
if (fileExists(rulesDir)) fs.rmSync(rulesDir, { recursive: true, force: true });
|
|
1001
|
+
wasFreshClean = true;
|
|
1002
|
+
} else if (mode === "continue" && existingPass1.length === 0 && pass2Exists) {
|
|
1003
|
+
// pass2 exists but no pass1 → pass2 is stale, force re-run
|
|
1004
|
+
fs.unlinkSync(path.join(GENERATED_DIR, "pass2-merged.json"));
|
|
1005
|
+
log(" ⚠️ pass2-merged.json deleted (no pass1 files to continue from)");
|
|
871
1006
|
}
|
|
872
|
-
log(" ✅ Done\n");
|
|
873
1007
|
|
|
874
|
-
|
|
875
|
-
|
|
1008
|
+
return { wasFreshClean };
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// ─── Stage 4: Create directory structure ──────────────────────────
|
|
1012
|
+
function ensureDirectories() {
|
|
876
1013
|
const dirs = [
|
|
877
1014
|
".claude/rules/00.core",
|
|
878
1015
|
".claude/rules/10.backend",
|
|
@@ -882,15 +1019,26 @@ async function cmdInit(parsedArgs) {
|
|
|
882
1019
|
".claude/rules/50.sync",
|
|
883
1020
|
"claudeos-core/generated",
|
|
884
1021
|
"claudeos-core/standard/00.core",
|
|
885
|
-
"claudeos-core/standard/10.backend
|
|
886
|
-
"claudeos-core/standard/20.frontend
|
|
1022
|
+
"claudeos-core/standard/10.backend",
|
|
1023
|
+
"claudeos-core/standard/20.frontend",
|
|
887
1024
|
"claudeos-core/standard/30.security-db",
|
|
888
1025
|
"claudeos-core/standard/40.infra",
|
|
889
|
-
"claudeos-core/standard/
|
|
1026
|
+
"claudeos-core/standard/80.verification",
|
|
890
1027
|
"claudeos-core/standard/90.optional",
|
|
891
1028
|
"claudeos-core/skills/00.shared",
|
|
892
1029
|
"claudeos-core/skills/10.backend-crud/scaffold-crud-feature",
|
|
1030
|
+
// v2.4.0 — `domains/` sub-folder under each per-domain skill category.
|
|
1031
|
+
// Pre-created so Pass 3c-N has a stable destination for per-domain
|
|
1032
|
+
// skill notes (`{category}/domains/{domain}.md`) and so the convention
|
|
1033
|
+
// is visible in the post-init dirtree alongside `scaffold-*-feature/`.
|
|
1034
|
+
// The `02.domains.md` orchestrator (sibling at category root) is
|
|
1035
|
+
// generated by Pass 3c-core, not pre-created — the file content is
|
|
1036
|
+
// project-specific. Empty pre-created `domains/` folders are
|
|
1037
|
+
// harmless on filesystems that allow them and self-document the
|
|
1038
|
+
// convention.
|
|
1039
|
+
"claudeos-core/skills/10.backend-crud/domains",
|
|
893
1040
|
"claudeos-core/skills/20.frontend-page/scaffold-page-feature",
|
|
1041
|
+
"claudeos-core/skills/20.frontend-page/domains",
|
|
894
1042
|
"claudeos-core/skills/50.testing",
|
|
895
1043
|
"claudeos-core/skills/90.experimental",
|
|
896
1044
|
"claudeos-core/guide/01.onboarding",
|
|
@@ -901,20 +1049,36 @@ async function cmdInit(parsedArgs) {
|
|
|
901
1049
|
"claudeos-core/mcp-guide",
|
|
902
1050
|
"claudeos-core/memory",
|
|
903
1051
|
".claude/rules/60.memory",
|
|
1052
|
+
// v2.4.0 — 80.verification rules (mirror of standard/80.verification).
|
|
1053
|
+
// Pre-created to give Pass 3 LLM a stable destination for verification
|
|
1054
|
+
// rules (testing strategy, build verification reminders that auto-load
|
|
1055
|
+
// when editing test files / build configs). Without this, prior runs
|
|
1056
|
+
// would create them at the LEGACY 50.verification path (cross-namespace
|
|
1057
|
+
// contamination from standard/50.verification), now corrected to the
|
|
1058
|
+
// unified 80.* numbering.
|
|
1059
|
+
".claude/rules/80.verification",
|
|
1060
|
+
// v2.4.0 — 70.domains/ canonical per-domain folder, ALWAYS typed.
|
|
1061
|
+
// - PLURAL folder (collection of N per-domain files)
|
|
1062
|
+
// - 70 number to avoid 60.* collision with 60.memory
|
|
1063
|
+
// - ALWAYS uses `{type}/` sub-folder (`backend/` or `frontend/`)
|
|
1064
|
+
// regardless of single/multi-stack. Uniform convention prevents
|
|
1065
|
+
// migration when a single-stack project later adds the other
|
|
1066
|
+
// stack, and gives validators a single pattern to recognize.
|
|
1067
|
+
// - Pre-created so Pass 3 LLM has a stable destination for
|
|
1068
|
+
// `claudeos-core/standard/70.domains/{type}/{domain}.md` and
|
|
1069
|
+
// `.claude/rules/70.domains/{type}/{domain}-rules.md` writes.
|
|
1070
|
+
"claudeos-core/standard/70.domains/backend",
|
|
1071
|
+
"claudeos-core/standard/70.domains/frontend",
|
|
1072
|
+
".claude/rules/70.domains/backend",
|
|
1073
|
+
".claude/rules/70.domains/frontend",
|
|
904
1074
|
];
|
|
905
1075
|
for (const d of dirs) {
|
|
906
1076
|
ensureDir(path.join(PROJECT_ROOT, d));
|
|
907
1077
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
// ─── [3] Run plan-installer ─────────────────────────
|
|
911
|
-
header("[3] Analyzing project (plan-installer)...");
|
|
912
|
-
run(`node "${path.join(TOOLS_DIR, "plan-installer/index.js")}"`);
|
|
913
|
-
log("");
|
|
914
|
-
|
|
915
|
-
// ─── [4] Pass 1: Deep analysis per domain group ──────────────────
|
|
916
|
-
header("[4] Pass 1 — Deep analysis per domain group...");
|
|
1078
|
+
}
|
|
917
1079
|
|
|
1080
|
+
// ─── Stage 5: Load & validate domain-groups.json ──────────────────
|
|
1081
|
+
function loadDomainGroups() {
|
|
918
1082
|
let domainGroups;
|
|
919
1083
|
try {
|
|
920
1084
|
domainGroups = JSON.parse(
|
|
@@ -927,8 +1091,15 @@ async function cmdInit(parsedArgs) {
|
|
|
927
1091
|
if (!totalGroups || typeof totalGroups !== "number" || totalGroups < 1) {
|
|
928
1092
|
throw new InitError(`domain-groups.json has invalid totalGroups: ${totalGroups}\n Re-run plan-installer or check claudeos-core/generated/`);
|
|
929
1093
|
}
|
|
1094
|
+
if (!domainGroups.groups || totalGroups !== domainGroups.groups.length) {
|
|
1095
|
+
throw new InitError(`domain-groups.json is malformed: expected ${totalGroups} groups, found ${domainGroups.groups ? domainGroups.groups.length : 0}`);
|
|
1096
|
+
}
|
|
1097
|
+
return { domainGroups, totalGroups };
|
|
1098
|
+
}
|
|
930
1099
|
|
|
931
|
-
|
|
1100
|
+
// Loads the per-type pass1 prompt templates. Falls back to the single-stack
|
|
1101
|
+
// pass1-prompt.md for backward compatibility with older plan-installer output.
|
|
1102
|
+
function loadPass1Prompts() {
|
|
932
1103
|
const pass1Prompts = {};
|
|
933
1104
|
for (const type of ["backend", "frontend"]) {
|
|
934
1105
|
const promptFile = path.join(GENERATED_DIR, `pass1-${type}-prompt.md`);
|
|
@@ -936,23 +1107,17 @@ async function cmdInit(parsedArgs) {
|
|
|
936
1107
|
pass1Prompts[type] = readFile(promptFile);
|
|
937
1108
|
}
|
|
938
1109
|
}
|
|
939
|
-
// Single-stack backward compatibility
|
|
940
1110
|
if (Object.keys(pass1Prompts).length === 0) {
|
|
941
1111
|
const fallback = path.join(GENERATED_DIR, "pass1-prompt.md");
|
|
942
1112
|
if (fileExists(fallback)) pass1Prompts["backend"] = readFile(fallback);
|
|
943
1113
|
}
|
|
1114
|
+
return pass1Prompts;
|
|
1115
|
+
}
|
|
944
1116
|
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
// Progress tracking: Pass 1 (N groups) + Pass 2 + Pass 3 + Pass 4 = totalSteps
|
|
950
|
-
const totalSteps = totalGroups + 3;
|
|
951
|
-
let completedSteps = 0;
|
|
952
|
-
const stepTimes = [];
|
|
953
|
-
const passStart = Date.now();
|
|
954
|
-
|
|
955
|
-
function progressBar(step, label) {
|
|
1117
|
+
// Creates the progressBar closure. Extracted from cmdInit so the bar
|
|
1118
|
+
// formatting is testable in isolation if needed.
|
|
1119
|
+
function makeProgressBar(totalSteps, passStart, stepTimes) {
|
|
1120
|
+
return function progressBar(step, label) {
|
|
956
1121
|
const pct = Math.round((step / totalSteps) * 100);
|
|
957
1122
|
const elapsed = Date.now() - passStart;
|
|
958
1123
|
let eta = "";
|
|
@@ -964,7 +1129,14 @@ async function cmdInit(parsedArgs) {
|
|
|
964
1129
|
const filled = Math.round(pct / 5);
|
|
965
1130
|
const bar = "█".repeat(filled) + "░".repeat(20 - filled);
|
|
966
1131
|
log(` [${bar}] ${pct}% (${step}/${totalSteps}) ${formatElapsed(elapsed)}${eta} — ${label}`);
|
|
967
|
-
}
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// ─── Stage 6: Pass 1 — Deep analysis per domain group ─────────────
|
|
1136
|
+
// Returns the number of steps to add to the outer completedSteps counter.
|
|
1137
|
+
async function runPass1Loop(opts) {
|
|
1138
|
+
const { domainGroups, totalGroups, pass1Prompts, progressBar, stepTimes, startingStep } = opts;
|
|
1139
|
+
let step = startingStep;
|
|
968
1140
|
|
|
969
1141
|
for (let i = 1; i <= totalGroups; i++) {
|
|
970
1142
|
const group = domainGroups.groups[i - 1];
|
|
@@ -984,7 +1156,7 @@ async function cmdInit(parsedArgs) {
|
|
|
984
1156
|
const existing = JSON.parse(readFile(pass1Json));
|
|
985
1157
|
if (existing && existing.analysisPerDomain) {
|
|
986
1158
|
log(` ⏭️ pass1-${i}.json already exists, skipping`);
|
|
987
|
-
|
|
1159
|
+
step++;
|
|
988
1160
|
continue;
|
|
989
1161
|
}
|
|
990
1162
|
} catch (_e) { /* malformed — re-run */ }
|
|
@@ -1024,13 +1196,17 @@ async function cmdInit(parsedArgs) {
|
|
|
1024
1196
|
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`);
|
|
1025
1197
|
}
|
|
1026
1198
|
|
|
1027
|
-
|
|
1028
|
-
progressBar(
|
|
1199
|
+
step++;
|
|
1200
|
+
progressBar(step, `pass1-${i}.json created (${formatElapsed(elapsed1)})`);
|
|
1029
1201
|
}
|
|
1030
1202
|
log("");
|
|
1203
|
+
return step - startingStep;
|
|
1204
|
+
}
|
|
1031
1205
|
|
|
1032
|
-
|
|
1033
|
-
|
|
1206
|
+
// ─── Stage 7: Pass 2 — Merge analysis results ─────────────────────
|
|
1207
|
+
// Returns the number of steps to add to the outer completedSteps counter (0 or 1).
|
|
1208
|
+
async function runPass2(opts) {
|
|
1209
|
+
const { progressBar, stepTimes, nextStep } = opts;
|
|
1034
1210
|
|
|
1035
1211
|
const pass2Json = path.join(GENERATED_DIR, "pass2-merged.json");
|
|
1036
1212
|
|
|
@@ -1057,46 +1233,47 @@ async function cmdInit(parsedArgs) {
|
|
|
1057
1233
|
|
|
1058
1234
|
if (pass2IsValid) {
|
|
1059
1235
|
log(" ⏭️ pass2-merged.json already exists, skipping");
|
|
1060
|
-
|
|
1061
|
-
}
|
|
1062
|
-
const pass2PromptFile = path.join(GENERATED_DIR, "pass2-prompt.md");
|
|
1063
|
-
if (!fileExists(pass2PromptFile)) {
|
|
1064
|
-
throw new InitError("pass2-prompt.md not found. Re-run plan-installer.");
|
|
1065
|
-
}
|
|
1066
|
-
let prompt = injectProjectRoot(readFile(pass2PromptFile));
|
|
1236
|
+
return 1;
|
|
1237
|
+
}
|
|
1067
1238
|
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
});
|
|
1074
|
-
ticker2.clearLine();
|
|
1075
|
-
const elapsed2 = Date.now() - t2;
|
|
1076
|
-
stepTimes.push(elapsed2);
|
|
1239
|
+
const pass2PromptFile = path.join(GENERATED_DIR, "pass2-prompt.md");
|
|
1240
|
+
if (!fileExists(pass2PromptFile)) {
|
|
1241
|
+
throw new InitError("pass2-prompt.md not found. Re-run plan-installer.");
|
|
1242
|
+
}
|
|
1243
|
+
let prompt = injectProjectRoot(readFile(pass2PromptFile));
|
|
1077
1244
|
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1245
|
+
const t2 = Date.now();
|
|
1246
|
+
const ticker2 = makePassTicker("Pass 2", t2);
|
|
1247
|
+
const ok = await runClaudePromptAsync(prompt, {
|
|
1248
|
+
onTick: ticker2.onTick,
|
|
1249
|
+
tickMs: ticker2.tickMs,
|
|
1250
|
+
});
|
|
1251
|
+
ticker2.clearLine();
|
|
1252
|
+
const elapsed2 = Date.now() - t2;
|
|
1253
|
+
stepTimes.push(elapsed2);
|
|
1081
1254
|
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1255
|
+
if (!ok) {
|
|
1256
|
+
throw new InitError("Pass 2 failed. Check the claude error output above.\n If this persists, try: npx claudeos-core init --force");
|
|
1257
|
+
}
|
|
1085
1258
|
|
|
1086
|
-
|
|
1087
|
-
|
|
1259
|
+
if (!fileExists(pass2Json)) {
|
|
1260
|
+
throw new InitError("pass2-merged.json was not created. Claude may have run but not produced expected output.");
|
|
1088
1261
|
}
|
|
1089
|
-
log("");
|
|
1090
1262
|
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1263
|
+
progressBar(nextStep, `pass2-merged.json created (${formatElapsed(elapsed2)})`);
|
|
1264
|
+
return 1;
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
// ─── Stage 8: Build pass3-context.json (v2.1) ─────────────────────
|
|
1268
|
+
// Writes a small (<5 KB) structured summary derived from project-analysis.json
|
|
1269
|
+
// plus pass2-merged.json signals (size, top-level keys). Pass 3 prompts
|
|
1270
|
+
// reference this INSTEAD OF re-reading pass2-merged.json repeatedly, which
|
|
1271
|
+
// was the primary cause of `Prompt is too long` failures on large projects.
|
|
1272
|
+
//
|
|
1273
|
+
// Silent-on-failure: if pass3-context-builder returns null (e.g.
|
|
1274
|
+
// project-analysis.json missing), we skip writing and let Pass 3 fall back
|
|
1275
|
+
// to the pre-v2.1 behavior of reading pass2-merged.json directly.
|
|
1276
|
+
function buildPass3ContextJson() {
|
|
1100
1277
|
try {
|
|
1101
1278
|
const { buildPass3Context } = require("../../plan-installer/pass3-context-builder");
|
|
1102
1279
|
const pass3Ctx = buildPass3Context(GENERATED_DIR);
|
|
@@ -1116,11 +1293,15 @@ async function cmdInit(parsedArgs) {
|
|
|
1116
1293
|
} catch (e) {
|
|
1117
1294
|
log(` ⚠️ pass3-context.json build skipped: ${e.message} (Pass 3 will fall back to pass2-merged.json)`);
|
|
1118
1295
|
}
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
// ─── [6] Pass 3: Generate + verify ─────────────────────────
|
|
1122
|
-
header("[6] Pass 3 — Generating all files...");
|
|
1296
|
+
}
|
|
1123
1297
|
|
|
1298
|
+
// ─── Stage 9a: Pass 3 marker pre-processing ───────────────────────
|
|
1299
|
+
// Handles v1.7.x migration backfill + stale-marker detection (guide/outputs).
|
|
1300
|
+
// The stale region below MUST include dropStalePass3Marker, EXPECTED_GUIDE_FILES,
|
|
1301
|
+
// and findMissingOutputs — tested for by tests/pass3-marker.test.js source
|
|
1302
|
+
// parity. The `completedSteps++` sentinel used by that region's regex lives
|
|
1303
|
+
// in cmdInit directly, after dispatchPass3 returns.
|
|
1304
|
+
function handlePass3StaleMarker(wasFreshClean) {
|
|
1124
1305
|
const pass3Marker = path.join(GENERATED_DIR, "pass3-complete.json");
|
|
1125
1306
|
const claudeMdPath = path.join(PROJECT_ROOT, "CLAUDE.md");
|
|
1126
1307
|
|
|
@@ -1224,6 +1405,17 @@ async function cmdInit(parsedArgs) {
|
|
|
1224
1405
|
}
|
|
1225
1406
|
}
|
|
1226
1407
|
}
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
// ─── Stage 9b: Pass 3 dispatch (decide + run) ─────────────────────
|
|
1411
|
+
// Returns { ran: boolean } so the caller can increment completedSteps
|
|
1412
|
+
// with the literal "completedSteps++" token that the stale-region
|
|
1413
|
+
// source-parity test regex requires.
|
|
1414
|
+
async function dispatchPass3(opts) {
|
|
1415
|
+
const { wasFreshClean, lang, stepTimes, progressBar, nextStep } = opts;
|
|
1416
|
+
|
|
1417
|
+
const pass3Marker = path.join(GENERATED_DIR, "pass3-complete.json");
|
|
1418
|
+
const claudeMdPath = path.join(PROJECT_ROOT, "CLAUDE.md");
|
|
1227
1419
|
|
|
1228
1420
|
// Pass 3 split mode resolution.
|
|
1229
1421
|
//
|
|
@@ -1243,8 +1435,8 @@ async function cmdInit(parsedArgs) {
|
|
|
1243
1435
|
try {
|
|
1244
1436
|
const ctxPath = path.join(GENERATED_DIR, "pass3-context.json");
|
|
1245
1437
|
if (fileExists(ctxPath)) {
|
|
1246
|
-
const
|
|
1247
|
-
const rec =
|
|
1438
|
+
const pctx = JSON.parse(readFile(ctxPath));
|
|
1439
|
+
const rec = pctx && pctx.splitRecommendation;
|
|
1248
1440
|
if (rec) {
|
|
1249
1441
|
log(` • estimated ${rec.estimatedFileCount} files from ${rec.totalDomains} domains`);
|
|
1250
1442
|
}
|
|
@@ -1298,17 +1490,20 @@ async function cmdInit(parsedArgs) {
|
|
|
1298
1490
|
EXPECTED_GUIDE_FILES, findMissingOutputs,
|
|
1299
1491
|
lang, stepTimes,
|
|
1300
1492
|
});
|
|
1301
|
-
|
|
1302
|
-
progressBar(completedSteps, `Pass 3 complete (split mode)`);
|
|
1493
|
+
progressBar(nextStep, `Pass 3 complete (split mode)`);
|
|
1303
1494
|
log("");
|
|
1304
|
-
|
|
1305
|
-
log(" ⏭️ pass3-complete.json already complete, skipping");
|
|
1306
|
-
completedSteps++;
|
|
1495
|
+
return { ran: true };
|
|
1307
1496
|
}
|
|
1308
|
-
log("");
|
|
1309
1497
|
|
|
1310
|
-
|
|
1311
|
-
|
|
1498
|
+
log(" ⏭️ pass3-complete.json already complete, skipping");
|
|
1499
|
+
return { ran: false };
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
// ─── Stage 10: Pass 4 — L4 memory scaffolding ─────────────────────
|
|
1503
|
+
// Returns 1 unconditionally (Pass 4 always counts as a completed step,
|
|
1504
|
+
// whether skip / static fallback / Claude-driven).
|
|
1505
|
+
async function runPass4(opts) {
|
|
1506
|
+
const { lang, stepTimes, progressBar, nextStep } = opts;
|
|
1312
1507
|
|
|
1313
1508
|
const pass4Marker = path.join(GENERATED_DIR, "pass4-memory.json");
|
|
1314
1509
|
const pass4PromptFile = path.join(GENERATED_DIR, "pass4-prompt.md");
|
|
@@ -1542,13 +1737,12 @@ async function cmdInit(parsedArgs) {
|
|
|
1542
1737
|
// when we actually did real work, so ETA for future steps stays meaningful.
|
|
1543
1738
|
const pass4Elapsed = Date.now() - pass4Start;
|
|
1544
1739
|
if (pass4Elapsed > 500) stepTimes.push(pass4Elapsed);
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
// ─── [8] Run verification tools ───────────────────────────────
|
|
1550
|
-
header("[8] Running verification tools...");
|
|
1740
|
+
progressBar(nextStep, pass4Label);
|
|
1741
|
+
return 1;
|
|
1742
|
+
}
|
|
1551
1743
|
|
|
1744
|
+
// ─── Stage 11: Run external verification tools ────────────────────
|
|
1745
|
+
function runVerificationTools() {
|
|
1552
1746
|
const verifyTools = [
|
|
1553
1747
|
{ name: "manifest-generator", script: path.join(TOOLS_DIR, "manifest-generator/index.js") },
|
|
1554
1748
|
{ name: "health-checker", script: path.join(TOOLS_DIR, "health-checker/index.js") },
|
|
@@ -1564,21 +1758,12 @@ async function cmdInit(parsedArgs) {
|
|
|
1564
1758
|
log(` ⚠️ ${t.name} reported issues (non-fatal)`);
|
|
1565
1759
|
}
|
|
1566
1760
|
}
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
// ─── Complete ─────────────────────────────────────────────
|
|
1570
|
-
const totalFiles = countFiles();
|
|
1571
|
-
const pass1Files = countPass1Files();
|
|
1761
|
+
}
|
|
1572
1762
|
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
// alone cannot reliably prevent across 10 output languages.
|
|
1578
|
-
//
|
|
1579
|
-
// Failures do NOT abort the run — the generated content is still
|
|
1580
|
-
// useful and the user can either re-run with --force or hand-edit the
|
|
1581
|
-
// flagged sections. The report is purely informational here.
|
|
1763
|
+
// ─── Stage 12: Structural lint (v2.3.0+) ──────────────────────────
|
|
1764
|
+
// Run the language-invariant CLAUDE.md validator after all passes complete.
|
|
1765
|
+
// Failures do NOT abort the run — informational only.
|
|
1766
|
+
function runLint() {
|
|
1582
1767
|
try {
|
|
1583
1768
|
const { validate } = require("../../claude-md-validator");
|
|
1584
1769
|
const { formatSummaryLine } = require("../../claude-md-validator/reporter");
|
|
@@ -1602,25 +1787,31 @@ async function cmdInit(parsedArgs) {
|
|
|
1602
1787
|
log(` ⚠️ Lint step skipped: ${e.message || e}`);
|
|
1603
1788
|
log("");
|
|
1604
1789
|
}
|
|
1790
|
+
}
|
|
1605
1791
|
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1792
|
+
// ─── Stage 13: Content integrity (Guard 4 — v2.3.0+) ──────────────
|
|
1793
|
+
// Runs content-validator's path-claim + MANIFEST drift checks as a
|
|
1794
|
+
// non-blocking final step after all passes. These are *advisories*, not
|
|
1795
|
+
// generation failures — the documents are usable as-is, the advisories
|
|
1796
|
+
// just flag spots where an LLM may have guessed at a filename or a
|
|
1797
|
+
// skill registration may have drifted. We deliberately do NOT throw or
|
|
1798
|
+
// unset pass3-complete.json here:
|
|
1799
|
+
// - Re-running Pass 3 is not guaranteed to fix LLM hallucinations
|
|
1800
|
+
// (the same fact JSON may trigger the same mis-inference again),
|
|
1801
|
+
// so a throw could deadlock the user in an `init --force` loop.
|
|
1802
|
+
// - content-validator's non-zero exit is preserved so that
|
|
1803
|
+
// `npx claudeos-core health` (and any CI wired to it) still treats
|
|
1804
|
+
// advisories as a real gate. `init` just presents them with softer
|
|
1805
|
+
// UX because by the time `init` finishes, the user's docs are
|
|
1806
|
+
// already on disk and fully usable.
|
|
1807
|
+
function runContentValidator() {
|
|
1618
1808
|
try {
|
|
1619
1809
|
const cvPath = path.join(__dirname, "..", "..", "content-validator", "index.js");
|
|
1620
1810
|
if (fileExists(cvPath)) {
|
|
1621
1811
|
log(" [Content] Checking path-claims and MANIFEST consistency...");
|
|
1622
|
-
// Run in a child process so its process.exit(1) on
|
|
1623
|
-
// not terminate init.
|
|
1812
|
+
// Run in a child process so its process.exit(1) on advisories does
|
|
1813
|
+
// not terminate init. The exit code is informational for us — we
|
|
1814
|
+
// still surface the content as advisories regardless.
|
|
1624
1815
|
const { spawnSync } = require("child_process");
|
|
1625
1816
|
const result = spawnSync(process.execPath, [cvPath], {
|
|
1626
1817
|
cwd: PROJECT_ROOT,
|
|
@@ -1634,11 +1825,11 @@ async function cmdInit(parsedArgs) {
|
|
|
1634
1825
|
log(summary.split("\n").map((l) => " " + l).join("\n"));
|
|
1635
1826
|
if (result.status !== 0) {
|
|
1636
1827
|
log("");
|
|
1637
|
-
log(" ℹ️ Content
|
|
1638
|
-
log("
|
|
1639
|
-
log("
|
|
1640
|
-
log(" - stale-report.json (full
|
|
1641
|
-
log(" -
|
|
1828
|
+
log(" ℹ️ Content advisories detected — these are quality notes,");
|
|
1829
|
+
log(" NOT generation failures. Your generated docs are ready");
|
|
1830
|
+
log(" to use as-is. Review when convenient:");
|
|
1831
|
+
log(" - stale-report.json (full advisory list)");
|
|
1832
|
+
log(" - npx claudeos-core health (standalone gate with exit code)");
|
|
1642
1833
|
}
|
|
1643
1834
|
log("");
|
|
1644
1835
|
}
|
|
@@ -1646,11 +1837,17 @@ async function cmdInit(parsedArgs) {
|
|
|
1646
1837
|
log(` ⚠️ Content check skipped: ${e.message || e}`);
|
|
1647
1838
|
log("");
|
|
1648
1839
|
}
|
|
1840
|
+
}
|
|
1649
1841
|
|
|
1650
|
-
|
|
1842
|
+
// ─── Stage 14: Print completion banner ────────────────────────────
|
|
1843
|
+
function printCompletionBanner(opts) {
|
|
1844
|
+
const { lang, totalGroups, totalStart } = opts;
|
|
1845
|
+
const totalFiles = countFiles();
|
|
1846
|
+
const pass1Files = countPass1Files();
|
|
1651
1847
|
const memoryReady = fileExists(path.join(PROJECT_ROOT, "claudeos-core/memory/decision-log.md"));
|
|
1652
1848
|
const rulesReady = fileExists(path.join(PROJECT_ROOT, ".claude/rules/60.memory/01.decision-log.md"));
|
|
1653
1849
|
const l4Status = (memoryReady && rulesReady) ? "memory + rules" : "partial";
|
|
1850
|
+
log("");
|
|
1654
1851
|
log("╔════════════════════════════════════════════════════╗");
|
|
1655
1852
|
log("║ ✅ ClaudeOS-Core — Complete ║");
|
|
1656
1853
|
log("║ ║");
|
|
@@ -1670,4 +1867,113 @@ async function cmdInit(parsedArgs) {
|
|
|
1670
1867
|
log("");
|
|
1671
1868
|
}
|
|
1672
1869
|
|
|
1870
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
1871
|
+
// Main orchestrator
|
|
1872
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
1873
|
+
async function cmdInit(parsedArgs) {
|
|
1874
|
+
const totalStart = Date.now();
|
|
1875
|
+
|
|
1876
|
+
// ─── Prerequisites check ───────────────────────────────────
|
|
1877
|
+
checkPrerequisites();
|
|
1878
|
+
|
|
1879
|
+
// ─── Language selection ────────────────────────────────────
|
|
1880
|
+
const lang = await resolveLanguage(parsedArgs);
|
|
1881
|
+
|
|
1882
|
+
// ─── Resume / Fresh selection ──────────────────────────────
|
|
1883
|
+
// wasFreshClean: tracks whether we just wiped generated state via --force
|
|
1884
|
+
// or "fresh" resume mode. Used by the Pass 3 backfill guard below:
|
|
1885
|
+
// fresh/force explicitly means "regenerate from scratch", so a leftover
|
|
1886
|
+
// CLAUDE.md from a prior run must NOT cause Pass 3 to be skipped via the
|
|
1887
|
+
// v1.7.x migration backfill.
|
|
1888
|
+
const { wasFreshClean } = await applyResumeMode(parsedArgs, lang);
|
|
1889
|
+
|
|
1890
|
+
log("");
|
|
1891
|
+
log("╔════════════════════════════════════════════════════╗");
|
|
1892
|
+
log("║ ClaudeOS-Core — Bootstrap (4-Pass) ║");
|
|
1893
|
+
log("╚════════════════════════════════════════════════════╝");
|
|
1894
|
+
log(` Project root: ${PROJECT_ROOT}`);
|
|
1895
|
+
log(` Language: ${SUPPORTED_LANGS[lang]} (${lang})`);
|
|
1896
|
+
log("");
|
|
1897
|
+
|
|
1898
|
+
// ─── [1] Install dependencies ──────────────────────────────
|
|
1899
|
+
header("[1] Installing dependencies...");
|
|
1900
|
+
if (!fileExists(path.join(TOOLS_DIR, "node_modules"))) {
|
|
1901
|
+
run("npm install --silent", { cwd: TOOLS_DIR });
|
|
1902
|
+
}
|
|
1903
|
+
log(" ✅ Done\n");
|
|
1904
|
+
|
|
1905
|
+
// ─── [2] Create directory structure ────────────────────────
|
|
1906
|
+
header("[2] Creating directory structure...");
|
|
1907
|
+
ensureDirectories();
|
|
1908
|
+
log(" ✅ Done\n");
|
|
1909
|
+
|
|
1910
|
+
// ─── [3] Run plan-installer ────────────────────────────────
|
|
1911
|
+
header("[3] Analyzing project (plan-installer)...");
|
|
1912
|
+
run(`node "${path.join(TOOLS_DIR, "plan-installer/index.js")}"`);
|
|
1913
|
+
log("");
|
|
1914
|
+
|
|
1915
|
+
// ─── [4] Pass 1: Deep analysis per domain group ────────────
|
|
1916
|
+
header("[4] Pass 1 — Deep analysis per domain group...");
|
|
1917
|
+
const { domainGroups, totalGroups } = loadDomainGroups();
|
|
1918
|
+
const pass1Prompts = loadPass1Prompts();
|
|
1919
|
+
|
|
1920
|
+
// Progress tracking: Pass 1 (N groups) + Pass 2 + Pass 3 + Pass 4 = totalSteps
|
|
1921
|
+
const totalSteps = totalGroups + 3;
|
|
1922
|
+
let completedSteps = 0;
|
|
1923
|
+
const stepTimes = [];
|
|
1924
|
+
const passStart = Date.now();
|
|
1925
|
+
const progressBar = makeProgressBar(totalSteps, passStart, stepTimes);
|
|
1926
|
+
|
|
1927
|
+
const p1Delta = await runPass1Loop({
|
|
1928
|
+
domainGroups, totalGroups, pass1Prompts,
|
|
1929
|
+
progressBar, stepTimes,
|
|
1930
|
+
startingStep: completedSteps,
|
|
1931
|
+
});
|
|
1932
|
+
completedSteps += p1Delta;
|
|
1933
|
+
|
|
1934
|
+
// ─── [5] Pass 2: Merge analysis results ────────────────────
|
|
1935
|
+
header("[5] Pass 2 — Merging analysis results...");
|
|
1936
|
+
const p2Delta = await runPass2({
|
|
1937
|
+
progressBar, stepTimes, nextStep: completedSteps + 1,
|
|
1938
|
+
});
|
|
1939
|
+
completedSteps += p2Delta;
|
|
1940
|
+
log("");
|
|
1941
|
+
|
|
1942
|
+
// ─── [5.5] Build pass3-context.json (v2.1) ─────────────────
|
|
1943
|
+
buildPass3ContextJson();
|
|
1944
|
+
log("");
|
|
1945
|
+
|
|
1946
|
+
// ─── [6] Pass 3: Generate + verify ─────────────────────────
|
|
1947
|
+
header("[6] Pass 3 — Generating all files...");
|
|
1948
|
+
handlePass3StaleMarker(wasFreshClean);
|
|
1949
|
+
const { ran: p3Ran } = await dispatchPass3({
|
|
1950
|
+
wasFreshClean, lang, stepTimes,
|
|
1951
|
+
progressBar, nextStep: completedSteps + 1,
|
|
1952
|
+
});
|
|
1953
|
+
if (p3Ran) completedSteps++;
|
|
1954
|
+
log("");
|
|
1955
|
+
|
|
1956
|
+
// ─── [7] Pass 4: L4 memory scaffolding ─────────────────────
|
|
1957
|
+
header("[7] Pass 4 — Memory scaffolding...");
|
|
1958
|
+
const p4Delta = await runPass4({
|
|
1959
|
+
lang, stepTimes, progressBar, nextStep: completedSteps + 1,
|
|
1960
|
+
});
|
|
1961
|
+
completedSteps += p4Delta;
|
|
1962
|
+
log("");
|
|
1963
|
+
|
|
1964
|
+
// ─── [8] Run verification tools ────────────────────────────
|
|
1965
|
+
header("[8] Running verification tools...");
|
|
1966
|
+
runVerificationTools();
|
|
1967
|
+
log("");
|
|
1968
|
+
|
|
1969
|
+
// ─── Structural lint (v2.3.0+) ─────────────────────────────
|
|
1970
|
+
runLint();
|
|
1971
|
+
|
|
1972
|
+
// ─── Content integrity (Guard 4 — v2.3.0+) ─────────────────
|
|
1973
|
+
runContentValidator();
|
|
1974
|
+
|
|
1975
|
+
// ─── Complete ──────────────────────────────────────────────
|
|
1976
|
+
printCompletionBanner({ lang, totalGroups, totalStart });
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1673
1979
|
module.exports = { cmdInit, InitError };
|