claudeos-core 2.3.0 → 2.3.2
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 +740 -0
- package/README.de.md +1 -5
- package/README.es.md +1 -5
- package/README.fr.md +1 -5
- package/README.hi.md +1 -5
- package/README.ja.md +1 -5
- package/README.ko.md +1 -5
- package/README.md +2 -6
- package/README.ru.md +1 -5
- package/README.vi.md +1 -5
- package/README.zh-CN.md +1 -5
- package/bin/commands/init.js +378 -227
- package/content-validator/index.js +89 -9
- package/package.json +3 -3
- package/pass-prompts/templates/common/claude-md-scaffold.md +122 -20
- package/pass-prompts/templates/common/pass3-footer.md +193 -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 +1 -1
- package/pass-prompts/templates/kotlin-spring/pass1.md +45 -0
- package/pass-prompts/templates/kotlin-spring/pass3.md +1 -1
- package/plan-installer/index.js +23 -0
- package/plan-installer/pass3-context-builder.js +14 -0
- package/plan-installer/scanners/scan-frontend.js +2 -1
- package/plan-installer/source-paths.js +242 -0
- package/plan-installer/stack-detector.js +478 -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");
|
|
@@ -701,15 +712,22 @@ async function runPass3Split(ctx) {
|
|
|
701
712
|
log(` 🎉 Pass 3 split complete: ${completedGroups.length}/${totalStages} stages successful`);
|
|
702
713
|
}
|
|
703
714
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
715
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
716
|
+
// cmdInit stage helpers
|
|
717
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
718
|
+
// The original cmdInit was 970 lines with 77 if-statements and 17 try-blocks
|
|
719
|
+
// in a single function. Below it is decomposed into one helper per pipeline
|
|
720
|
+
// phase. Each helper owns a self-contained step with clear inputs/outputs.
|
|
721
|
+
//
|
|
722
|
+
// Shared state passed across helpers:
|
|
723
|
+
// - { lang, stepTimes, completedSteps (via ref), progressBar, wasFreshClean }
|
|
724
|
+
// Helpers that advance the outer progress bar return a `stepsDelta` value
|
|
725
|
+
// that cmdInit adds to `completedSteps` locally. This preserves the literal
|
|
726
|
+
// `completedSteps++` text at the top-level orchestrator, which several
|
|
727
|
+
// source-parity tests rely on for stale-check region detection.
|
|
728
|
+
|
|
729
|
+
// ─── Stage 1: Prerequisites check ──────────────────────────────────
|
|
730
|
+
function checkPrerequisites() {
|
|
713
731
|
const hasProjectMarker = [".git", "package.json", "build.gradle", "build.gradle.kts", "pom.xml", "pyproject.toml", "requirements.txt"].some(
|
|
714
732
|
m => fs.existsSync(path.join(PROJECT_ROOT, m))
|
|
715
733
|
);
|
|
@@ -734,8 +752,10 @@ async function cmdInit(parsedArgs) {
|
|
|
734
752
|
if (!claudeAuth) {
|
|
735
753
|
throw new InitError("Claude Code may not be authenticated.\n Run: claude (and complete authentication)\n Then retry: npx claudeos-core init");
|
|
736
754
|
}
|
|
755
|
+
}
|
|
737
756
|
|
|
738
|
-
|
|
757
|
+
// ─── Stage 2: Resolve output language ─────────────────────────────
|
|
758
|
+
async function resolveLanguage(parsedArgs) {
|
|
739
759
|
let lang = parsedArgs.lang;
|
|
740
760
|
if (!lang) {
|
|
741
761
|
lang = await selectLangInteractive();
|
|
@@ -765,114 +785,109 @@ async function cmdInit(parsedArgs) {
|
|
|
765
785
|
}
|
|
766
786
|
|
|
767
787
|
process.env.CLAUDEOS_LANG = lang;
|
|
788
|
+
return lang;
|
|
789
|
+
}
|
|
768
790
|
|
|
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
|
-
}
|
|
791
|
+
// ─── Stage 3: Resume/Fresh selection ──────────────────────────────
|
|
792
|
+
// Returns { wasFreshClean: boolean } — the caller uses this to gate the
|
|
793
|
+
// v1.7.x migration backfill in dispatchPass3.
|
|
794
|
+
async function applyResumeMode(parsedArgs, lang) {
|
|
795
|
+
let wasFreshClean = false;
|
|
829
796
|
|
|
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
|
-
}
|
|
797
|
+
if (!fs.existsSync(GENERATED_DIR)) return { wasFreshClean };
|
|
798
|
+
|
|
799
|
+
const existingPass1 = fs.readdirSync(GENERATED_DIR).filter(f => f.startsWith("pass1-") && f.endsWith(".json"));
|
|
800
|
+
const pass2Exists = fileExists(path.join(GENERATED_DIR, "pass2-merged.json"));
|
|
801
|
+
if (existingPass1.length === 0 && !pass2Exists) return { wasFreshClean };
|
|
802
|
+
|
|
803
|
+
if (parsedArgs.force) {
|
|
804
|
+
// --force: clean all generated files for truly fresh start
|
|
805
|
+
const genFiles = fs.readdirSync(GENERATED_DIR).filter(f => f.endsWith(".json") || f.endsWith(".md"));
|
|
806
|
+
for (const f of genFiles) fs.unlinkSync(path.join(GENERATED_DIR, f));
|
|
807
|
+
// Also clean any leftover .staged-rules/ from a prior crashed run
|
|
808
|
+
// (only .json/.md are unlinked above; directories aren't touched).
|
|
809
|
+
const stagedDir = path.join(GENERATED_DIR, ".staged-rules");
|
|
810
|
+
if (fileExists(stagedDir)) fs.rmSync(stagedDir, { recursive: true, force: true });
|
|
811
|
+
// Also wipe .claude/rules/ so Guard 2 (zero-rules detection) can't
|
|
812
|
+
// false-negative on stale rules from a previous run when the fresh
|
|
813
|
+
// Pass 3 run fails silently (e.g. Claude ignores staging-override).
|
|
814
|
+
// Step [2] recreates the subdirs from scratch. Any manual edits the
|
|
815
|
+
// user made to rule files are lost — acceptable under --force
|
|
816
|
+
// ("truly fresh start").
|
|
817
|
+
const rulesDir = path.join(PROJECT_ROOT, ".claude/rules");
|
|
818
|
+
if (fileExists(rulesDir)) fs.rmSync(rulesDir, { recursive: true, force: true });
|
|
819
|
+
wasFreshClean = true;
|
|
820
|
+
log(" 🔄 Previous results deleted (--force)\n");
|
|
821
|
+
return { wasFreshClean };
|
|
857
822
|
}
|
|
858
823
|
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
824
|
+
// v2.2.0 upgrade detection: if project was generated with older claudeos-core
|
|
825
|
+
// (pre-2.2.0), default "resume" mode will skip regeneration of existing files
|
|
826
|
+
// per Rule B idempotency, meaning v2.2.0 structural improvements will NOT be
|
|
827
|
+
// picked up. Detect this case by checking CLAUDE.md for v2.2.0 markers.
|
|
828
|
+
const claudeMd = path.join(PROJECT_ROOT, "CLAUDE.md");
|
|
829
|
+
if (fileExists(claudeMd)) {
|
|
830
|
+
try {
|
|
831
|
+
const content = fs.readFileSync(claudeMd, "utf-8");
|
|
832
|
+
// v2.2.0 scaffold enforces EXACTLY 8 top-level `##` sections.
|
|
833
|
+
// Pre-v2.2.0 CLAUDE.md files typically carry 9+ sections (extra
|
|
834
|
+
// "Rules Summary" / "Common Rules" / "Required to Observe"
|
|
835
|
+
// blocks that v2.2.0 forbids). Counting `^## ` headings is a
|
|
836
|
+
// language-independent heuristic that works across all 10
|
|
837
|
+
// supported output languages. False positive (an existing
|
|
838
|
+
// 8-section pre-v2.2.0 CLAUDE.md) is acceptable — the user
|
|
839
|
+
// simply won't see the upgrade warning and can still run
|
|
840
|
+
// `--force` manually.
|
|
841
|
+
const sectionCount = (content.match(/^## /gm) || []).length;
|
|
842
|
+
const hasV220Section8 = sectionCount === 8;
|
|
843
|
+
if (!hasV220Section8) {
|
|
844
|
+
log("\n ⚠️ v2.2.0 upgrade detected");
|
|
845
|
+
log(" ─────────────────────────");
|
|
846
|
+
log(" Your existing CLAUDE.md was generated with an older claudeos-core version.");
|
|
847
|
+
log(" v2.2.0 introduces structural changes that the default 'resume' mode");
|
|
848
|
+
log(" CANNOT apply because existing files are preserved under Rule B (idempotency).");
|
|
849
|
+
log("");
|
|
850
|
+
log(" To fully adopt v2.2.0, choose one of:");
|
|
851
|
+
log(" 1. Rerun with --force: npx claudeos-core init --force");
|
|
852
|
+
log(" (overwrites generated files; your memory/ content is preserved)");
|
|
853
|
+
log(" 2. Choose 'fresh' below (equivalent to --force)");
|
|
854
|
+
log("");
|
|
855
|
+
log(" See CHANGELOG.md Migration section for full details.\n");
|
|
856
|
+
}
|
|
857
|
+
} catch (_) { /* Read error is non-fatal; proceed to resume prompt */ }
|
|
858
|
+
}
|
|
866
859
|
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
if (!
|
|
870
|
-
|
|
860
|
+
const status = { pass1Done: existingPass1.length, pass2Done: pass2Exists };
|
|
861
|
+
const mode = await selectResumeMode(lang, status);
|
|
862
|
+
if (!mode) throw new InitError("Cancelled.");
|
|
863
|
+
if (mode === "fresh") {
|
|
864
|
+
for (const f of existingPass1) fs.unlinkSync(path.join(GENERATED_DIR, f));
|
|
865
|
+
if (pass2Exists) fs.unlinkSync(path.join(GENERATED_DIR, "pass2-merged.json"));
|
|
866
|
+
// Also reset pass 3 & pass 4 markers so they re-run
|
|
867
|
+
const pass3M = path.join(GENERATED_DIR, "pass3-complete.json");
|
|
868
|
+
const pass4M = path.join(GENERATED_DIR, "pass4-memory.json");
|
|
869
|
+
if (fileExists(pass3M)) fs.unlinkSync(pass3M);
|
|
870
|
+
if (fileExists(pass4M)) fs.unlinkSync(pass4M);
|
|
871
|
+
// Clean .staged-rules/ leftover from a prior crashed run (same reason as --force branch).
|
|
872
|
+
const stagedDir = path.join(GENERATED_DIR, ".staged-rules");
|
|
873
|
+
if (fileExists(stagedDir)) fs.rmSync(stagedDir, { recursive: true, force: true });
|
|
874
|
+
// Wipe .claude/rules/ for the same Guard 2 false-negative reason as
|
|
875
|
+
// the --force branch. Step [2] recreates the subdirs; any manual
|
|
876
|
+
// edits are lost — acceptable under an explicit "fresh" choice.
|
|
877
|
+
const rulesDir = path.join(PROJECT_ROOT, ".claude/rules");
|
|
878
|
+
if (fileExists(rulesDir)) fs.rmSync(rulesDir, { recursive: true, force: true });
|
|
879
|
+
wasFreshClean = true;
|
|
880
|
+
} else if (mode === "continue" && existingPass1.length === 0 && pass2Exists) {
|
|
881
|
+
// pass2 exists but no pass1 → pass2 is stale, force re-run
|
|
882
|
+
fs.unlinkSync(path.join(GENERATED_DIR, "pass2-merged.json"));
|
|
883
|
+
log(" ⚠️ pass2-merged.json deleted (no pass1 files to continue from)");
|
|
871
884
|
}
|
|
872
|
-
log(" ✅ Done\n");
|
|
873
885
|
|
|
874
|
-
|
|
875
|
-
|
|
886
|
+
return { wasFreshClean };
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// ─── Stage 4: Create directory structure ──────────────────────────
|
|
890
|
+
function ensureDirectories() {
|
|
876
891
|
const dirs = [
|
|
877
892
|
".claude/rules/00.core",
|
|
878
893
|
".claude/rules/10.backend",
|
|
@@ -905,16 +920,10 @@ async function cmdInit(parsedArgs) {
|
|
|
905
920
|
for (const d of dirs) {
|
|
906
921
|
ensureDir(path.join(PROJECT_ROOT, d));
|
|
907
922
|
}
|
|
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...");
|
|
923
|
+
}
|
|
917
924
|
|
|
925
|
+
// ─── Stage 5: Load & validate domain-groups.json ──────────────────
|
|
926
|
+
function loadDomainGroups() {
|
|
918
927
|
let domainGroups;
|
|
919
928
|
try {
|
|
920
929
|
domainGroups = JSON.parse(
|
|
@@ -927,8 +936,15 @@ async function cmdInit(parsedArgs) {
|
|
|
927
936
|
if (!totalGroups || typeof totalGroups !== "number" || totalGroups < 1) {
|
|
928
937
|
throw new InitError(`domain-groups.json has invalid totalGroups: ${totalGroups}\n Re-run plan-installer or check claudeos-core/generated/`);
|
|
929
938
|
}
|
|
939
|
+
if (!domainGroups.groups || totalGroups !== domainGroups.groups.length) {
|
|
940
|
+
throw new InitError(`domain-groups.json is malformed: expected ${totalGroups} groups, found ${domainGroups.groups ? domainGroups.groups.length : 0}`);
|
|
941
|
+
}
|
|
942
|
+
return { domainGroups, totalGroups };
|
|
943
|
+
}
|
|
930
944
|
|
|
931
|
-
|
|
945
|
+
// Loads the per-type pass1 prompt templates. Falls back to the single-stack
|
|
946
|
+
// pass1-prompt.md for backward compatibility with older plan-installer output.
|
|
947
|
+
function loadPass1Prompts() {
|
|
932
948
|
const pass1Prompts = {};
|
|
933
949
|
for (const type of ["backend", "frontend"]) {
|
|
934
950
|
const promptFile = path.join(GENERATED_DIR, `pass1-${type}-prompt.md`);
|
|
@@ -936,23 +952,17 @@ async function cmdInit(parsedArgs) {
|
|
|
936
952
|
pass1Prompts[type] = readFile(promptFile);
|
|
937
953
|
}
|
|
938
954
|
}
|
|
939
|
-
// Single-stack backward compatibility
|
|
940
955
|
if (Object.keys(pass1Prompts).length === 0) {
|
|
941
956
|
const fallback = path.join(GENERATED_DIR, "pass1-prompt.md");
|
|
942
957
|
if (fileExists(fallback)) pass1Prompts["backend"] = readFile(fallback);
|
|
943
958
|
}
|
|
959
|
+
return pass1Prompts;
|
|
960
|
+
}
|
|
944
961
|
|
|
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) {
|
|
962
|
+
// Creates the progressBar closure. Extracted from cmdInit so the bar
|
|
963
|
+
// formatting is testable in isolation if needed.
|
|
964
|
+
function makeProgressBar(totalSteps, passStart, stepTimes) {
|
|
965
|
+
return function progressBar(step, label) {
|
|
956
966
|
const pct = Math.round((step / totalSteps) * 100);
|
|
957
967
|
const elapsed = Date.now() - passStart;
|
|
958
968
|
let eta = "";
|
|
@@ -964,7 +974,14 @@ async function cmdInit(parsedArgs) {
|
|
|
964
974
|
const filled = Math.round(pct / 5);
|
|
965
975
|
const bar = "█".repeat(filled) + "░".repeat(20 - filled);
|
|
966
976
|
log(` [${bar}] ${pct}% (${step}/${totalSteps}) ${formatElapsed(elapsed)}${eta} — ${label}`);
|
|
967
|
-
}
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// ─── Stage 6: Pass 1 — Deep analysis per domain group ─────────────
|
|
981
|
+
// Returns the number of steps to add to the outer completedSteps counter.
|
|
982
|
+
async function runPass1Loop(opts) {
|
|
983
|
+
const { domainGroups, totalGroups, pass1Prompts, progressBar, stepTimes, startingStep } = opts;
|
|
984
|
+
let step = startingStep;
|
|
968
985
|
|
|
969
986
|
for (let i = 1; i <= totalGroups; i++) {
|
|
970
987
|
const group = domainGroups.groups[i - 1];
|
|
@@ -984,7 +1001,7 @@ async function cmdInit(parsedArgs) {
|
|
|
984
1001
|
const existing = JSON.parse(readFile(pass1Json));
|
|
985
1002
|
if (existing && existing.analysisPerDomain) {
|
|
986
1003
|
log(` ⏭️ pass1-${i}.json already exists, skipping`);
|
|
987
|
-
|
|
1004
|
+
step++;
|
|
988
1005
|
continue;
|
|
989
1006
|
}
|
|
990
1007
|
} catch (_e) { /* malformed — re-run */ }
|
|
@@ -1024,13 +1041,17 @@ async function cmdInit(parsedArgs) {
|
|
|
1024
1041
|
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
1042
|
}
|
|
1026
1043
|
|
|
1027
|
-
|
|
1028
|
-
progressBar(
|
|
1044
|
+
step++;
|
|
1045
|
+
progressBar(step, `pass1-${i}.json created (${formatElapsed(elapsed1)})`);
|
|
1029
1046
|
}
|
|
1030
1047
|
log("");
|
|
1048
|
+
return step - startingStep;
|
|
1049
|
+
}
|
|
1031
1050
|
|
|
1032
|
-
|
|
1033
|
-
|
|
1051
|
+
// ─── Stage 7: Pass 2 — Merge analysis results ─────────────────────
|
|
1052
|
+
// Returns the number of steps to add to the outer completedSteps counter (0 or 1).
|
|
1053
|
+
async function runPass2(opts) {
|
|
1054
|
+
const { progressBar, stepTimes, nextStep } = opts;
|
|
1034
1055
|
|
|
1035
1056
|
const pass2Json = path.join(GENERATED_DIR, "pass2-merged.json");
|
|
1036
1057
|
|
|
@@ -1057,46 +1078,47 @@ async function cmdInit(parsedArgs) {
|
|
|
1057
1078
|
|
|
1058
1079
|
if (pass2IsValid) {
|
|
1059
1080
|
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));
|
|
1081
|
+
return 1;
|
|
1082
|
+
}
|
|
1067
1083
|
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
});
|
|
1074
|
-
ticker2.clearLine();
|
|
1075
|
-
const elapsed2 = Date.now() - t2;
|
|
1076
|
-
stepTimes.push(elapsed2);
|
|
1084
|
+
const pass2PromptFile = path.join(GENERATED_DIR, "pass2-prompt.md");
|
|
1085
|
+
if (!fileExists(pass2PromptFile)) {
|
|
1086
|
+
throw new InitError("pass2-prompt.md not found. Re-run plan-installer.");
|
|
1087
|
+
}
|
|
1088
|
+
let prompt = injectProjectRoot(readFile(pass2PromptFile));
|
|
1077
1089
|
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1090
|
+
const t2 = Date.now();
|
|
1091
|
+
const ticker2 = makePassTicker("Pass 2", t2);
|
|
1092
|
+
const ok = await runClaudePromptAsync(prompt, {
|
|
1093
|
+
onTick: ticker2.onTick,
|
|
1094
|
+
tickMs: ticker2.tickMs,
|
|
1095
|
+
});
|
|
1096
|
+
ticker2.clearLine();
|
|
1097
|
+
const elapsed2 = Date.now() - t2;
|
|
1098
|
+
stepTimes.push(elapsed2);
|
|
1081
1099
|
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1100
|
+
if (!ok) {
|
|
1101
|
+
throw new InitError("Pass 2 failed. Check the claude error output above.\n If this persists, try: npx claudeos-core init --force");
|
|
1102
|
+
}
|
|
1085
1103
|
|
|
1086
|
-
|
|
1087
|
-
|
|
1104
|
+
if (!fileExists(pass2Json)) {
|
|
1105
|
+
throw new InitError("pass2-merged.json was not created. Claude may have run but not produced expected output.");
|
|
1088
1106
|
}
|
|
1089
|
-
log("");
|
|
1090
1107
|
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1108
|
+
progressBar(nextStep, `pass2-merged.json created (${formatElapsed(elapsed2)})`);
|
|
1109
|
+
return 1;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
// ─── Stage 8: Build pass3-context.json (v2.1) ─────────────────────
|
|
1113
|
+
// Writes a small (<5 KB) structured summary derived from project-analysis.json
|
|
1114
|
+
// plus pass2-merged.json signals (size, top-level keys). Pass 3 prompts
|
|
1115
|
+
// reference this INSTEAD OF re-reading pass2-merged.json repeatedly, which
|
|
1116
|
+
// was the primary cause of `Prompt is too long` failures on large projects.
|
|
1117
|
+
//
|
|
1118
|
+
// Silent-on-failure: if pass3-context-builder returns null (e.g.
|
|
1119
|
+
// project-analysis.json missing), we skip writing and let Pass 3 fall back
|
|
1120
|
+
// to the pre-v2.1 behavior of reading pass2-merged.json directly.
|
|
1121
|
+
function buildPass3ContextJson() {
|
|
1100
1122
|
try {
|
|
1101
1123
|
const { buildPass3Context } = require("../../plan-installer/pass3-context-builder");
|
|
1102
1124
|
const pass3Ctx = buildPass3Context(GENERATED_DIR);
|
|
@@ -1116,11 +1138,15 @@ async function cmdInit(parsedArgs) {
|
|
|
1116
1138
|
} catch (e) {
|
|
1117
1139
|
log(` ⚠️ pass3-context.json build skipped: ${e.message} (Pass 3 will fall back to pass2-merged.json)`);
|
|
1118
1140
|
}
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
// ─── [6] Pass 3: Generate + verify ─────────────────────────
|
|
1122
|
-
header("[6] Pass 3 — Generating all files...");
|
|
1141
|
+
}
|
|
1123
1142
|
|
|
1143
|
+
// ─── Stage 9a: Pass 3 marker pre-processing ───────────────────────
|
|
1144
|
+
// Handles v1.7.x migration backfill + stale-marker detection (guide/outputs).
|
|
1145
|
+
// The stale region below MUST include dropStalePass3Marker, EXPECTED_GUIDE_FILES,
|
|
1146
|
+
// and findMissingOutputs — tested for by tests/pass3-marker.test.js source
|
|
1147
|
+
// parity. The `completedSteps++` sentinel used by that region's regex lives
|
|
1148
|
+
// in cmdInit directly, after dispatchPass3 returns.
|
|
1149
|
+
function handlePass3StaleMarker(wasFreshClean) {
|
|
1124
1150
|
const pass3Marker = path.join(GENERATED_DIR, "pass3-complete.json");
|
|
1125
1151
|
const claudeMdPath = path.join(PROJECT_ROOT, "CLAUDE.md");
|
|
1126
1152
|
|
|
@@ -1224,6 +1250,17 @@ async function cmdInit(parsedArgs) {
|
|
|
1224
1250
|
}
|
|
1225
1251
|
}
|
|
1226
1252
|
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
// ─── Stage 9b: Pass 3 dispatch (decide + run) ─────────────────────
|
|
1256
|
+
// Returns { ran: boolean } so the caller can increment completedSteps
|
|
1257
|
+
// with the literal "completedSteps++" token that the stale-region
|
|
1258
|
+
// source-parity test regex requires.
|
|
1259
|
+
async function dispatchPass3(opts) {
|
|
1260
|
+
const { wasFreshClean, lang, stepTimes, progressBar, nextStep } = opts;
|
|
1261
|
+
|
|
1262
|
+
const pass3Marker = path.join(GENERATED_DIR, "pass3-complete.json");
|
|
1263
|
+
const claudeMdPath = path.join(PROJECT_ROOT, "CLAUDE.md");
|
|
1227
1264
|
|
|
1228
1265
|
// Pass 3 split mode resolution.
|
|
1229
1266
|
//
|
|
@@ -1243,8 +1280,8 @@ async function cmdInit(parsedArgs) {
|
|
|
1243
1280
|
try {
|
|
1244
1281
|
const ctxPath = path.join(GENERATED_DIR, "pass3-context.json");
|
|
1245
1282
|
if (fileExists(ctxPath)) {
|
|
1246
|
-
const
|
|
1247
|
-
const rec =
|
|
1283
|
+
const pctx = JSON.parse(readFile(ctxPath));
|
|
1284
|
+
const rec = pctx && pctx.splitRecommendation;
|
|
1248
1285
|
if (rec) {
|
|
1249
1286
|
log(` • estimated ${rec.estimatedFileCount} files from ${rec.totalDomains} domains`);
|
|
1250
1287
|
}
|
|
@@ -1298,17 +1335,20 @@ async function cmdInit(parsedArgs) {
|
|
|
1298
1335
|
EXPECTED_GUIDE_FILES, findMissingOutputs,
|
|
1299
1336
|
lang, stepTimes,
|
|
1300
1337
|
});
|
|
1301
|
-
|
|
1302
|
-
progressBar(completedSteps, `Pass 3 complete (split mode)`);
|
|
1338
|
+
progressBar(nextStep, `Pass 3 complete (split mode)`);
|
|
1303
1339
|
log("");
|
|
1304
|
-
|
|
1305
|
-
log(" ⏭️ pass3-complete.json already complete, skipping");
|
|
1306
|
-
completedSteps++;
|
|
1340
|
+
return { ran: true };
|
|
1307
1341
|
}
|
|
1308
|
-
log("");
|
|
1309
1342
|
|
|
1310
|
-
|
|
1311
|
-
|
|
1343
|
+
log(" ⏭️ pass3-complete.json already complete, skipping");
|
|
1344
|
+
return { ran: false };
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
// ─── Stage 10: Pass 4 — L4 memory scaffolding ─────────────────────
|
|
1348
|
+
// Returns 1 unconditionally (Pass 4 always counts as a completed step,
|
|
1349
|
+
// whether skip / static fallback / Claude-driven).
|
|
1350
|
+
async function runPass4(opts) {
|
|
1351
|
+
const { lang, stepTimes, progressBar, nextStep } = opts;
|
|
1312
1352
|
|
|
1313
1353
|
const pass4Marker = path.join(GENERATED_DIR, "pass4-memory.json");
|
|
1314
1354
|
const pass4PromptFile = path.join(GENERATED_DIR, "pass4-prompt.md");
|
|
@@ -1542,13 +1582,12 @@ async function cmdInit(parsedArgs) {
|
|
|
1542
1582
|
// when we actually did real work, so ETA for future steps stays meaningful.
|
|
1543
1583
|
const pass4Elapsed = Date.now() - pass4Start;
|
|
1544
1584
|
if (pass4Elapsed > 500) stepTimes.push(pass4Elapsed);
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
// ─── [8] Run verification tools ───────────────────────────────
|
|
1550
|
-
header("[8] Running verification tools...");
|
|
1585
|
+
progressBar(nextStep, pass4Label);
|
|
1586
|
+
return 1;
|
|
1587
|
+
}
|
|
1551
1588
|
|
|
1589
|
+
// ─── Stage 11: Run external verification tools ────────────────────
|
|
1590
|
+
function runVerificationTools() {
|
|
1552
1591
|
const verifyTools = [
|
|
1553
1592
|
{ name: "manifest-generator", script: path.join(TOOLS_DIR, "manifest-generator/index.js") },
|
|
1554
1593
|
{ name: "health-checker", script: path.join(TOOLS_DIR, "health-checker/index.js") },
|
|
@@ -1564,21 +1603,12 @@ async function cmdInit(parsedArgs) {
|
|
|
1564
1603
|
log(` ⚠️ ${t.name} reported issues (non-fatal)`);
|
|
1565
1604
|
}
|
|
1566
1605
|
}
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
// ─── Complete ─────────────────────────────────────────────
|
|
1570
|
-
const totalFiles = countFiles();
|
|
1571
|
-
const pass1Files = countPass1Files();
|
|
1606
|
+
}
|
|
1572
1607
|
|
|
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.
|
|
1608
|
+
// ─── Stage 12: Structural lint (v2.3.0+) ──────────────────────────
|
|
1609
|
+
// Run the language-invariant CLAUDE.md validator after all passes complete.
|
|
1610
|
+
// Failures do NOT abort the run — informational only.
|
|
1611
|
+
function runLint() {
|
|
1582
1612
|
try {
|
|
1583
1613
|
const { validate } = require("../../claude-md-validator");
|
|
1584
1614
|
const { formatSummaryLine } = require("../../claude-md-validator/reporter");
|
|
@@ -1602,25 +1632,31 @@ async function cmdInit(parsedArgs) {
|
|
|
1602
1632
|
log(` ⚠️ Lint step skipped: ${e.message || e}`);
|
|
1603
1633
|
log("");
|
|
1604
1634
|
}
|
|
1635
|
+
}
|
|
1605
1636
|
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1637
|
+
// ─── Stage 13: Content integrity (Guard 4 — v2.3.0+) ──────────────
|
|
1638
|
+
// Runs content-validator's path-claim + MANIFEST drift checks as a
|
|
1639
|
+
// non-blocking final step after all passes. These are *advisories*, not
|
|
1640
|
+
// generation failures — the documents are usable as-is, the advisories
|
|
1641
|
+
// just flag spots where an LLM may have guessed at a filename or a
|
|
1642
|
+
// skill registration may have drifted. We deliberately do NOT throw or
|
|
1643
|
+
// unset pass3-complete.json here:
|
|
1644
|
+
// - Re-running Pass 3 is not guaranteed to fix LLM hallucinations
|
|
1645
|
+
// (the same fact JSON may trigger the same mis-inference again),
|
|
1646
|
+
// so a throw could deadlock the user in an `init --force` loop.
|
|
1647
|
+
// - content-validator's non-zero exit is preserved so that
|
|
1648
|
+
// `npx claudeos-core health` (and any CI wired to it) still treats
|
|
1649
|
+
// advisories as a real gate. `init` just presents them with softer
|
|
1650
|
+
// UX because by the time `init` finishes, the user's docs are
|
|
1651
|
+
// already on disk and fully usable.
|
|
1652
|
+
function runContentValidator() {
|
|
1618
1653
|
try {
|
|
1619
1654
|
const cvPath = path.join(__dirname, "..", "..", "content-validator", "index.js");
|
|
1620
1655
|
if (fileExists(cvPath)) {
|
|
1621
1656
|
log(" [Content] Checking path-claims and MANIFEST consistency...");
|
|
1622
|
-
// Run in a child process so its process.exit(1) on
|
|
1623
|
-
// not terminate init.
|
|
1657
|
+
// Run in a child process so its process.exit(1) on advisories does
|
|
1658
|
+
// not terminate init. The exit code is informational for us — we
|
|
1659
|
+
// still surface the content as advisories regardless.
|
|
1624
1660
|
const { spawnSync } = require("child_process");
|
|
1625
1661
|
const result = spawnSync(process.execPath, [cvPath], {
|
|
1626
1662
|
cwd: PROJECT_ROOT,
|
|
@@ -1634,11 +1670,11 @@ async function cmdInit(parsedArgs) {
|
|
|
1634
1670
|
log(summary.split("\n").map((l) => " " + l).join("\n"));
|
|
1635
1671
|
if (result.status !== 0) {
|
|
1636
1672
|
log("");
|
|
1637
|
-
log(" ℹ️ Content
|
|
1638
|
-
log("
|
|
1639
|
-
log("
|
|
1640
|
-
log(" - stale-report.json (full
|
|
1641
|
-
log(" -
|
|
1673
|
+
log(" ℹ️ Content advisories detected — these are quality notes,");
|
|
1674
|
+
log(" NOT generation failures. Your generated docs are ready");
|
|
1675
|
+
log(" to use as-is. Review when convenient:");
|
|
1676
|
+
log(" - stale-report.json (full advisory list)");
|
|
1677
|
+
log(" - npx claudeos-core health (standalone gate with exit code)");
|
|
1642
1678
|
}
|
|
1643
1679
|
log("");
|
|
1644
1680
|
}
|
|
@@ -1646,11 +1682,17 @@ async function cmdInit(parsedArgs) {
|
|
|
1646
1682
|
log(` ⚠️ Content check skipped: ${e.message || e}`);
|
|
1647
1683
|
log("");
|
|
1648
1684
|
}
|
|
1685
|
+
}
|
|
1649
1686
|
|
|
1650
|
-
|
|
1687
|
+
// ─── Stage 14: Print completion banner ────────────────────────────
|
|
1688
|
+
function printCompletionBanner(opts) {
|
|
1689
|
+
const { lang, totalGroups, totalStart } = opts;
|
|
1690
|
+
const totalFiles = countFiles();
|
|
1691
|
+
const pass1Files = countPass1Files();
|
|
1651
1692
|
const memoryReady = fileExists(path.join(PROJECT_ROOT, "claudeos-core/memory/decision-log.md"));
|
|
1652
1693
|
const rulesReady = fileExists(path.join(PROJECT_ROOT, ".claude/rules/60.memory/01.decision-log.md"));
|
|
1653
1694
|
const l4Status = (memoryReady && rulesReady) ? "memory + rules" : "partial";
|
|
1695
|
+
log("");
|
|
1654
1696
|
log("╔════════════════════════════════════════════════════╗");
|
|
1655
1697
|
log("║ ✅ ClaudeOS-Core — Complete ║");
|
|
1656
1698
|
log("║ ║");
|
|
@@ -1670,4 +1712,113 @@ async function cmdInit(parsedArgs) {
|
|
|
1670
1712
|
log("");
|
|
1671
1713
|
}
|
|
1672
1714
|
|
|
1715
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
1716
|
+
// Main orchestrator
|
|
1717
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
1718
|
+
async function cmdInit(parsedArgs) {
|
|
1719
|
+
const totalStart = Date.now();
|
|
1720
|
+
|
|
1721
|
+
// ─── Prerequisites check ───────────────────────────────────
|
|
1722
|
+
checkPrerequisites();
|
|
1723
|
+
|
|
1724
|
+
// ─── Language selection ────────────────────────────────────
|
|
1725
|
+
const lang = await resolveLanguage(parsedArgs);
|
|
1726
|
+
|
|
1727
|
+
// ─── Resume / Fresh selection ──────────────────────────────
|
|
1728
|
+
// wasFreshClean: tracks whether we just wiped generated state via --force
|
|
1729
|
+
// or "fresh" resume mode. Used by the Pass 3 backfill guard below:
|
|
1730
|
+
// fresh/force explicitly means "regenerate from scratch", so a leftover
|
|
1731
|
+
// CLAUDE.md from a prior run must NOT cause Pass 3 to be skipped via the
|
|
1732
|
+
// v1.7.x migration backfill.
|
|
1733
|
+
const { wasFreshClean } = await applyResumeMode(parsedArgs, lang);
|
|
1734
|
+
|
|
1735
|
+
log("");
|
|
1736
|
+
log("╔════════════════════════════════════════════════════╗");
|
|
1737
|
+
log("║ ClaudeOS-Core — Bootstrap (4-Pass) ║");
|
|
1738
|
+
log("╚════════════════════════════════════════════════════╝");
|
|
1739
|
+
log(` Project root: ${PROJECT_ROOT}`);
|
|
1740
|
+
log(` Language: ${SUPPORTED_LANGS[lang]} (${lang})`);
|
|
1741
|
+
log("");
|
|
1742
|
+
|
|
1743
|
+
// ─── [1] Install dependencies ──────────────────────────────
|
|
1744
|
+
header("[1] Installing dependencies...");
|
|
1745
|
+
if (!fileExists(path.join(TOOLS_DIR, "node_modules"))) {
|
|
1746
|
+
run("npm install --silent", { cwd: TOOLS_DIR });
|
|
1747
|
+
}
|
|
1748
|
+
log(" ✅ Done\n");
|
|
1749
|
+
|
|
1750
|
+
// ─── [2] Create directory structure ────────────────────────
|
|
1751
|
+
header("[2] Creating directory structure...");
|
|
1752
|
+
ensureDirectories();
|
|
1753
|
+
log(" ✅ Done\n");
|
|
1754
|
+
|
|
1755
|
+
// ─── [3] Run plan-installer ────────────────────────────────
|
|
1756
|
+
header("[3] Analyzing project (plan-installer)...");
|
|
1757
|
+
run(`node "${path.join(TOOLS_DIR, "plan-installer/index.js")}"`);
|
|
1758
|
+
log("");
|
|
1759
|
+
|
|
1760
|
+
// ─── [4] Pass 1: Deep analysis per domain group ────────────
|
|
1761
|
+
header("[4] Pass 1 — Deep analysis per domain group...");
|
|
1762
|
+
const { domainGroups, totalGroups } = loadDomainGroups();
|
|
1763
|
+
const pass1Prompts = loadPass1Prompts();
|
|
1764
|
+
|
|
1765
|
+
// Progress tracking: Pass 1 (N groups) + Pass 2 + Pass 3 + Pass 4 = totalSteps
|
|
1766
|
+
const totalSteps = totalGroups + 3;
|
|
1767
|
+
let completedSteps = 0;
|
|
1768
|
+
const stepTimes = [];
|
|
1769
|
+
const passStart = Date.now();
|
|
1770
|
+
const progressBar = makeProgressBar(totalSteps, passStart, stepTimes);
|
|
1771
|
+
|
|
1772
|
+
const p1Delta = await runPass1Loop({
|
|
1773
|
+
domainGroups, totalGroups, pass1Prompts,
|
|
1774
|
+
progressBar, stepTimes,
|
|
1775
|
+
startingStep: completedSteps,
|
|
1776
|
+
});
|
|
1777
|
+
completedSteps += p1Delta;
|
|
1778
|
+
|
|
1779
|
+
// ─── [5] Pass 2: Merge analysis results ────────────────────
|
|
1780
|
+
header("[5] Pass 2 — Merging analysis results...");
|
|
1781
|
+
const p2Delta = await runPass2({
|
|
1782
|
+
progressBar, stepTimes, nextStep: completedSteps + 1,
|
|
1783
|
+
});
|
|
1784
|
+
completedSteps += p2Delta;
|
|
1785
|
+
log("");
|
|
1786
|
+
|
|
1787
|
+
// ─── [5.5] Build pass3-context.json (v2.1) ─────────────────
|
|
1788
|
+
buildPass3ContextJson();
|
|
1789
|
+
log("");
|
|
1790
|
+
|
|
1791
|
+
// ─── [6] Pass 3: Generate + verify ─────────────────────────
|
|
1792
|
+
header("[6] Pass 3 — Generating all files...");
|
|
1793
|
+
handlePass3StaleMarker(wasFreshClean);
|
|
1794
|
+
const { ran: p3Ran } = await dispatchPass3({
|
|
1795
|
+
wasFreshClean, lang, stepTimes,
|
|
1796
|
+
progressBar, nextStep: completedSteps + 1,
|
|
1797
|
+
});
|
|
1798
|
+
if (p3Ran) completedSteps++;
|
|
1799
|
+
log("");
|
|
1800
|
+
|
|
1801
|
+
// ─── [7] Pass 4: L4 memory scaffolding ─────────────────────
|
|
1802
|
+
header("[7] Pass 4 — Memory scaffolding...");
|
|
1803
|
+
const p4Delta = await runPass4({
|
|
1804
|
+
lang, stepTimes, progressBar, nextStep: completedSteps + 1,
|
|
1805
|
+
});
|
|
1806
|
+
completedSteps += p4Delta;
|
|
1807
|
+
log("");
|
|
1808
|
+
|
|
1809
|
+
// ─── [8] Run verification tools ────────────────────────────
|
|
1810
|
+
header("[8] Running verification tools...");
|
|
1811
|
+
runVerificationTools();
|
|
1812
|
+
log("");
|
|
1813
|
+
|
|
1814
|
+
// ─── Structural lint (v2.3.0+) ─────────────────────────────
|
|
1815
|
+
runLint();
|
|
1816
|
+
|
|
1817
|
+
// ─── Content integrity (Guard 4 — v2.3.0+) ─────────────────
|
|
1818
|
+
runContentValidator();
|
|
1819
|
+
|
|
1820
|
+
// ─── Complete ──────────────────────────────────────────────
|
|
1821
|
+
printCompletionBanner({ lang, totalGroups, totalStart });
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1673
1824
|
module.exports = { cmdInit, InitError };
|