javi-forge 1.5.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +191 -3
- package/ci-local/hooks/pre-push +17 -13
- package/dist/commands/analyze.d.ts +1 -1
- package/dist/commands/analyze.js +15 -15
- package/dist/commands/atlassian-mcp.d.ts +42 -0
- package/dist/commands/atlassian-mcp.js +98 -0
- package/dist/commands/ci.d.ts +3 -3
- package/dist/commands/ci.js +185 -147
- package/dist/commands/crash-recovery.d.ts +34 -0
- package/dist/commands/crash-recovery.js +123 -0
- package/dist/commands/doctor.d.ts +2 -2
- package/dist/commands/doctor.js +113 -61
- package/dist/commands/harness-audit.d.ts +35 -0
- package/dist/commands/harness-audit.js +277 -0
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +415 -118
- package/dist/commands/llmstxt.d.ts +1 -1
- package/dist/commands/llmstxt.js +36 -34
- package/dist/commands/parallel-batch.d.ts +42 -0
- package/dist/commands/parallel-batch.js +90 -0
- package/dist/commands/plugin.d.ts +26 -1
- package/dist/commands/plugin.js +138 -24
- package/dist/commands/secret-scanner.d.ts +30 -0
- package/dist/commands/secret-scanner.js +272 -0
- package/dist/commands/security-analysis.d.ts +74 -0
- package/dist/commands/security-analysis.js +487 -0
- package/dist/commands/security.d.ts +31 -0
- package/dist/commands/security.js +445 -0
- package/dist/commands/skill-scanner.d.ts +63 -0
- package/dist/commands/skill-scanner.js +383 -0
- package/dist/commands/skills.d.ts +139 -0
- package/dist/commands/skills.js +895 -0
- package/dist/commands/supply-chain.d.ts +23 -0
- package/dist/commands/supply-chain.js +126 -0
- package/dist/commands/tdd-pipeline.d.ts +17 -0
- package/dist/commands/tdd-pipeline.js +144 -0
- package/dist/commands/tdd.d.ts +21 -0
- package/dist/commands/tdd.js +120 -0
- package/dist/commands/team-presets.d.ts +53 -0
- package/dist/commands/team-presets.js +201 -0
- package/dist/commands/workflow.d.ts +23 -0
- package/dist/commands/workflow.js +114 -0
- package/dist/constants.d.ts +21 -0
- package/dist/constants.js +208 -37
- package/dist/index.js +400 -54
- package/dist/lib/agent-skills.d.ts +73 -0
- package/dist/lib/agent-skills.js +260 -0
- package/dist/lib/auto-skill-install.d.ts +37 -0
- package/dist/lib/auto-skill-install.js +92 -0
- package/dist/lib/auto-wire.d.ts +20 -0
- package/dist/lib/auto-wire.js +240 -0
- package/dist/lib/claudemd.d.ts +20 -0
- package/dist/lib/claudemd.js +222 -0
- package/dist/lib/codex-export.d.ts +16 -0
- package/dist/lib/codex-export.js +109 -0
- package/dist/lib/common.d.ts +1 -1
- package/dist/lib/common.js +52 -44
- package/dist/lib/context.d.ts +27 -0
- package/dist/lib/context.js +204 -0
- package/dist/lib/docker.d.ts +1 -1
- package/dist/lib/docker.js +141 -112
- package/dist/lib/frontmatter.d.ts +1 -1
- package/dist/lib/frontmatter.js +29 -15
- package/dist/lib/plugin.d.ts +19 -1
- package/dist/lib/plugin.js +174 -47
- package/dist/lib/skill-publish.d.ts +40 -0
- package/dist/lib/skill-publish.js +146 -0
- package/dist/lib/stack-detector.d.ts +38 -0
- package/dist/lib/stack-detector.js +207 -0
- package/dist/lib/template.d.ts +16 -1
- package/dist/lib/template.js +46 -17
- package/dist/lib/workflow/discovery.d.ts +19 -0
- package/dist/lib/workflow/discovery.js +68 -0
- package/dist/lib/workflow/index.d.ts +5 -0
- package/dist/lib/workflow/index.js +5 -0
- package/dist/lib/workflow/parser.d.ts +16 -0
- package/dist/lib/workflow/parser.js +198 -0
- package/dist/lib/workflow/renderer.d.ts +9 -0
- package/dist/lib/workflow/renderer.js +152 -0
- package/dist/lib/workflow/validator.d.ts +10 -0
- package/dist/lib/workflow/validator.js +189 -0
- package/dist/tasks/index.d.ts +4 -0
- package/dist/tasks/index.js +4 -0
- package/dist/tasks/scaffold-tasks.d.ts +3 -0
- package/dist/tasks/scaffold-tasks.js +14 -0
- package/dist/tasks/task-id.d.ts +30 -0
- package/dist/tasks/task-id.js +55 -0
- package/dist/tasks/task-tracker.d.ts +15 -0
- package/dist/tasks/task-tracker.js +81 -0
- package/dist/types/index.d.ts +252 -5
- package/dist/types/index.js +11 -1
- package/dist/ui/AnalyzeUI.d.ts +1 -1
- package/dist/ui/AnalyzeUI.js +38 -39
- package/dist/ui/App.d.ts +5 -3
- package/dist/ui/App.js +92 -46
- package/dist/ui/AutoSkills.d.ts +9 -0
- package/dist/ui/AutoSkills.js +124 -0
- package/dist/ui/CI.d.ts +2 -2
- package/dist/ui/CI.js +24 -26
- package/dist/ui/CIContext.d.ts +1 -1
- package/dist/ui/CIContext.js +3 -2
- package/dist/ui/CISelector.d.ts +2 -2
- package/dist/ui/CISelector.js +23 -15
- package/dist/ui/Doctor.d.ts +1 -1
- package/dist/ui/Doctor.js +35 -29
- package/dist/ui/Header.d.ts +1 -1
- package/dist/ui/Header.js +14 -14
- package/dist/ui/HookProfileSelector.d.ts +9 -0
- package/dist/ui/HookProfileSelector.js +54 -0
- package/dist/ui/LlmsTxt.d.ts +1 -1
- package/dist/ui/LlmsTxt.js +31 -22
- package/dist/ui/MemorySelector.d.ts +2 -2
- package/dist/ui/MemorySelector.js +28 -16
- package/dist/ui/NameInput.d.ts +1 -1
- package/dist/ui/NameInput.js +21 -21
- package/dist/ui/OptionSelector.d.ts +8 -2
- package/dist/ui/OptionSelector.js +83 -26
- package/dist/ui/Plugin.d.ts +4 -3
- package/dist/ui/Plugin.js +89 -29
- package/dist/ui/Progress.d.ts +3 -3
- package/dist/ui/Progress.js +23 -22
- package/dist/ui/Skills.d.ts +11 -0
- package/dist/ui/Skills.js +148 -0
- package/dist/ui/StackSelector.d.ts +2 -2
- package/dist/ui/StackSelector.js +26 -16
- package/dist/ui/Summary.d.ts +3 -3
- package/dist/ui/Summary.js +60 -50
- package/dist/ui/Welcome.d.ts +1 -1
- package/dist/ui/Welcome.js +15 -16
- package/dist/ui/theme.d.ts +1 -1
- package/dist/ui/theme.js +6 -6
- package/package.json +9 -6
- package/templates/common/atlassian/mcp-atlassian-snippet.json +16 -0
- package/templates/common/repoforge/mcp-repoforge-snippet.json +11 -0
- package/templates/common/repoforge/repoforge.yaml +34 -0
- package/templates/github/deploy-docker-zero-downtime.yml +140 -0
- package/templates/github/repoforge-graph.yml +45 -0
- package/templates/gitlab/deploy-docker-zero-downtime.yml +57 -0
- package/templates/local-ai/.env.example +17 -0
- package/templates/local-ai/docker-compose.yml +95 -0
- package/templates/security-hooks/claude-settings-security.json +30 -0
- package/templates/security-hooks/commit-msg-signing +29 -0
- package/templates/security-hooks/pre-commit-permissions +74 -0
- package/templates/security-hooks/pre-commit-secrets +74 -0
- package/templates/security-hooks/pre-push-branch-protection +62 -0
- package/templates/security-hooks/pre-push-deps +83 -0
- package/templates/security-hooks/pre-push-signing +67 -0
- package/templates/woodpecker/deploy-docker-zero-downtime.yml +50 -0
- package/templates/workflows/ci-pipeline.dot +15 -0
- package/templates/workflows/feature-flow.dot +21 -0
- package/templates/workflows/release.dot +16 -0
- package/dist/__integration__/helpers.d.ts +0 -20
- package/dist/__integration__/helpers.d.ts.map +0 -1
- package/dist/__integration__/helpers.js +0 -31
- package/dist/__integration__/helpers.js.map +0 -1
- package/dist/commands/analyze.d.ts.map +0 -1
- package/dist/commands/analyze.js.map +0 -1
- package/dist/commands/ci.d.ts.map +0 -1
- package/dist/commands/ci.js.map +0 -1
- package/dist/commands/doctor.d.ts.map +0 -1
- package/dist/commands/doctor.js.map +0 -1
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/llmstxt.d.ts.map +0 -1
- package/dist/commands/llmstxt.js.map +0 -1
- package/dist/commands/plugin.d.ts.map +0 -1
- package/dist/commands/plugin.js.map +0 -1
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lib/common.d.ts.map +0 -1
- package/dist/lib/common.js.map +0 -1
- package/dist/lib/docker.d.ts.map +0 -1
- package/dist/lib/docker.js.map +0 -1
- package/dist/lib/frontmatter.d.ts.map +0 -1
- package/dist/lib/frontmatter.js.map +0 -1
- package/dist/lib/plugin.d.ts.map +0 -1
- package/dist/lib/plugin.js.map +0 -1
- package/dist/lib/template.d.ts.map +0 -1
- package/dist/lib/template.js.map +0 -1
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js.map +0 -1
- package/dist/ui/AnalyzeUI.d.ts.map +0 -1
- package/dist/ui/AnalyzeUI.js.map +0 -1
- package/dist/ui/App.d.ts.map +0 -1
- package/dist/ui/App.js.map +0 -1
- package/dist/ui/CI.d.ts.map +0 -1
- package/dist/ui/CI.js.map +0 -1
- package/dist/ui/CIContext.d.ts.map +0 -1
- package/dist/ui/CIContext.js.map +0 -1
- package/dist/ui/CISelector.d.ts.map +0 -1
- package/dist/ui/CISelector.js.map +0 -1
- package/dist/ui/Doctor.d.ts.map +0 -1
- package/dist/ui/Doctor.js.map +0 -1
- package/dist/ui/Header.d.ts.map +0 -1
- package/dist/ui/Header.js.map +0 -1
- package/dist/ui/LlmsTxt.d.ts.map +0 -1
- package/dist/ui/LlmsTxt.js.map +0 -1
- package/dist/ui/MemorySelector.d.ts.map +0 -1
- package/dist/ui/MemorySelector.js.map +0 -1
- package/dist/ui/NameInput.d.ts.map +0 -1
- package/dist/ui/NameInput.js.map +0 -1
- package/dist/ui/OptionSelector.d.ts.map +0 -1
- package/dist/ui/OptionSelector.js.map +0 -1
- package/dist/ui/Plugin.d.ts.map +0 -1
- package/dist/ui/Plugin.js.map +0 -1
- package/dist/ui/Progress.d.ts.map +0 -1
- package/dist/ui/Progress.js.map +0 -1
- package/dist/ui/StackSelector.d.ts.map +0 -1
- package/dist/ui/StackSelector.js.map +0 -1
- package/dist/ui/Summary.d.ts.map +0 -1
- package/dist/ui/Summary.js.map +0 -1
- package/dist/ui/Welcome.d.ts.map +0 -1
- package/dist/ui/Welcome.js.map +0 -1
- package/dist/ui/theme.d.ts.map +0 -1
- package/dist/ui/theme.js.map +0 -1
package/dist/commands/init.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import { promisify } from
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { promisify } from "util";
|
|
5
|
+
import { AGENT_SKILLS_MANIFEST_FILE, CI_LOCAL_DIR, FORGE_ROOT, LOCAL_AI_TEMPLATE_DIR, MODULES_DIR, SECURITY_HOOKS_DIR, TEMPLATES_DIR, } from "../constants.js";
|
|
6
|
+
import { generateSmartClaudeMd } from "../lib/claudemd.js";
|
|
7
|
+
import { backupIfExists, ensureDirExists } from "../lib/common.js";
|
|
8
|
+
import { generateContextDir } from "../lib/context.js";
|
|
9
|
+
import { detectProjectStack } from "../lib/stack-detector.js";
|
|
10
|
+
import { generateCIWorkflow, generateDependabotYml, generateDeployWorkflow, getCIDestination, getDeployDestination, } from "../lib/template.js";
|
|
8
11
|
const execFileAsync = promisify(execFile);
|
|
9
12
|
function report(onStep, id, label, status, detail) {
|
|
10
13
|
onStep({ id, label, status, detail });
|
|
@@ -14,61 +17,64 @@ function report(onStep, id, label, status, detail) {
|
|
|
14
17
|
* memory module, AI config sync, SDD, and ghagga.
|
|
15
18
|
*/
|
|
16
19
|
export async function initProject(options, onStep) {
|
|
17
|
-
const { projectDir, projectName, stack, ciProvider, memory, aiSync, sdd, ghagga, dryRun } = options;
|
|
20
|
+
const { projectDir, projectName, stack, ciProvider, memory, aiSync, sdd, ghagga, contextDir, claudeMd, securityHooks, hookProfile, dryRun, } = options;
|
|
18
21
|
// Ensure project directory exists before any steps
|
|
19
22
|
if (!dryRun && projectDir) {
|
|
20
23
|
await ensureDirExists(projectDir);
|
|
21
24
|
}
|
|
22
25
|
// ── Step 1: Initialize git ────────────────────────────────────────────────
|
|
23
|
-
const stepGit =
|
|
24
|
-
report(onStep, stepGit,
|
|
26
|
+
const stepGit = "git-init";
|
|
27
|
+
report(onStep, stepGit, "Initialize git repository", "running");
|
|
25
28
|
try {
|
|
26
|
-
const gitDir = path.join(projectDir,
|
|
27
|
-
if (!await fs.pathExists(gitDir)) {
|
|
29
|
+
const gitDir = path.join(projectDir, ".git");
|
|
30
|
+
if (!(await fs.pathExists(gitDir))) {
|
|
28
31
|
if (!dryRun) {
|
|
29
|
-
await execFileAsync(
|
|
32
|
+
await execFileAsync("git", ["init"], { cwd: projectDir });
|
|
30
33
|
}
|
|
31
|
-
report(onStep, stepGit,
|
|
34
|
+
report(onStep, stepGit, "Initialize git repository", "done", "initialized");
|
|
32
35
|
}
|
|
33
36
|
else {
|
|
34
|
-
report(onStep, stepGit,
|
|
37
|
+
report(onStep, stepGit, "Initialize git repository", "done", "already exists");
|
|
35
38
|
}
|
|
36
39
|
}
|
|
37
40
|
catch (e) {
|
|
38
|
-
report(onStep, stepGit,
|
|
41
|
+
report(onStep, stepGit, "Initialize git repository", "error", String(e));
|
|
39
42
|
}
|
|
40
43
|
// ── Step 2: Configure git hooks path ──────────────────────────────────────
|
|
41
|
-
const stepHooks =
|
|
42
|
-
report(onStep, stepHooks,
|
|
44
|
+
const stepHooks = "git-hooks";
|
|
45
|
+
report(onStep, stepHooks, "Configure git hooks path", "running");
|
|
43
46
|
try {
|
|
44
47
|
const ciLocalSrc = CI_LOCAL_DIR;
|
|
45
|
-
const ciLocalDest = path.join(projectDir,
|
|
48
|
+
const ciLocalDest = path.join(projectDir, "ci-local");
|
|
46
49
|
if (await fs.pathExists(ciLocalSrc)) {
|
|
47
50
|
if (!dryRun) {
|
|
48
|
-
await fs.copy(ciLocalSrc, ciLocalDest, {
|
|
51
|
+
await fs.copy(ciLocalSrc, ciLocalDest, {
|
|
52
|
+
overwrite: false,
|
|
53
|
+
errorOnExist: false,
|
|
54
|
+
});
|
|
49
55
|
// Set core.hooksPath to ci-local/hooks
|
|
50
|
-
const hooksDir = path.join(ciLocalDest,
|
|
56
|
+
const hooksDir = path.join(ciLocalDest, "hooks");
|
|
51
57
|
if (await fs.pathExists(hooksDir)) {
|
|
52
58
|
// Ensure hooks are executable
|
|
53
59
|
const hookFiles = await fs.readdir(hooksDir);
|
|
54
60
|
for (const hook of hookFiles) {
|
|
55
61
|
await fs.chmod(path.join(hooksDir, hook), 0o755);
|
|
56
62
|
}
|
|
57
|
-
await execFileAsync(
|
|
63
|
+
await execFileAsync("git", ["config", "core.hooksPath", "ci-local/hooks"], { cwd: projectDir });
|
|
58
64
|
}
|
|
59
65
|
}
|
|
60
|
-
report(onStep, stepHooks,
|
|
66
|
+
report(onStep, stepHooks, "Configure git hooks path", "done", "ci-local/hooks");
|
|
61
67
|
}
|
|
62
68
|
else {
|
|
63
|
-
report(onStep, stepHooks,
|
|
69
|
+
report(onStep, stepHooks, "Configure git hooks path", "skipped", "no ci-local dir");
|
|
64
70
|
}
|
|
65
71
|
}
|
|
66
72
|
catch (e) {
|
|
67
|
-
report(onStep, stepHooks,
|
|
73
|
+
report(onStep, stepHooks, "Configure git hooks path", "error", String(e));
|
|
68
74
|
}
|
|
69
75
|
// ── Step 3: Copy CI template ──────────────────────────────────────────────
|
|
70
|
-
const stepCI =
|
|
71
|
-
report(onStep, stepCI, `Copy ${ciProvider} CI workflow`,
|
|
76
|
+
const stepCI = "ci-template";
|
|
77
|
+
report(onStep, stepCI, `Copy ${ciProvider} CI workflow`, "running");
|
|
72
78
|
try {
|
|
73
79
|
const ciContent = await generateCIWorkflow(stack, ciProvider);
|
|
74
80
|
if (ciContent) {
|
|
@@ -76,197 +82,207 @@ export async function initProject(options, onStep) {
|
|
|
76
82
|
if (!dryRun) {
|
|
77
83
|
await backupIfExists(dest);
|
|
78
84
|
await ensureDirExists(path.dirname(dest));
|
|
79
|
-
await fs.writeFile(dest, ciContent,
|
|
85
|
+
await fs.writeFile(dest, ciContent, "utf-8");
|
|
80
86
|
}
|
|
81
|
-
report(onStep, stepCI, `Copy ${ciProvider} CI workflow`,
|
|
87
|
+
report(onStep, stepCI, `Copy ${ciProvider} CI workflow`, "done", getCIDestination(ciProvider));
|
|
82
88
|
}
|
|
83
89
|
else {
|
|
84
|
-
report(onStep, stepCI, `Copy ${ciProvider} CI workflow`,
|
|
90
|
+
report(onStep, stepCI, `Copy ${ciProvider} CI workflow`, "skipped", `no template for ${stack}`);
|
|
85
91
|
}
|
|
86
92
|
}
|
|
87
93
|
catch (e) {
|
|
88
|
-
report(onStep, stepCI, `Copy ${ciProvider} CI workflow`,
|
|
94
|
+
report(onStep, stepCI, `Copy ${ciProvider} CI workflow`, "error", String(e));
|
|
89
95
|
}
|
|
90
96
|
// ── Step 4: Generate .gitignore ───────────────────────────────────────────
|
|
91
|
-
const stepGitignore =
|
|
92
|
-
report(onStep, stepGitignore,
|
|
97
|
+
const stepGitignore = "gitignore";
|
|
98
|
+
report(onStep, stepGitignore, "Generate .gitignore", "running");
|
|
93
99
|
try {
|
|
94
|
-
const templatePath = path.join(FORGE_ROOT,
|
|
95
|
-
const dest = path.join(projectDir,
|
|
96
|
-
if (await fs.pathExists(templatePath) && !await fs.pathExists(dest)) {
|
|
100
|
+
const templatePath = path.join(FORGE_ROOT, ".gitignore.template");
|
|
101
|
+
const dest = path.join(projectDir, ".gitignore");
|
|
102
|
+
if ((await fs.pathExists(templatePath)) && !(await fs.pathExists(dest))) {
|
|
97
103
|
if (!dryRun) {
|
|
98
104
|
await fs.copy(templatePath, dest);
|
|
99
105
|
}
|
|
100
|
-
report(onStep, stepGitignore,
|
|
106
|
+
report(onStep, stepGitignore, "Generate .gitignore", "done", "from template");
|
|
101
107
|
}
|
|
102
108
|
else if (await fs.pathExists(dest)) {
|
|
103
|
-
report(onStep, stepGitignore,
|
|
109
|
+
report(onStep, stepGitignore, "Generate .gitignore", "done", "already exists");
|
|
104
110
|
}
|
|
105
111
|
else {
|
|
106
|
-
report(onStep, stepGitignore,
|
|
112
|
+
report(onStep, stepGitignore, "Generate .gitignore", "skipped", "no template");
|
|
107
113
|
}
|
|
108
114
|
}
|
|
109
115
|
catch (e) {
|
|
110
|
-
report(onStep, stepGitignore,
|
|
116
|
+
report(onStep, stepGitignore, "Generate .gitignore", "error", String(e));
|
|
111
117
|
}
|
|
112
118
|
// ── Step 5: Generate dependabot.yml ───────────────────────────────────────
|
|
113
|
-
const stepDeps =
|
|
114
|
-
report(onStep, stepDeps,
|
|
119
|
+
const stepDeps = "dependabot";
|
|
120
|
+
report(onStep, stepDeps, "Generate dependabot.yml", "running");
|
|
115
121
|
try {
|
|
116
|
-
if (ciProvider ===
|
|
122
|
+
if (ciProvider === "github") {
|
|
117
123
|
const content = await generateDependabotYml([stack], true);
|
|
118
|
-
const dest = path.join(projectDir,
|
|
124
|
+
const dest = path.join(projectDir, ".github", "dependabot.yml");
|
|
119
125
|
if (!dryRun) {
|
|
120
126
|
await backupIfExists(dest);
|
|
121
127
|
await ensureDirExists(path.dirname(dest));
|
|
122
|
-
await fs.writeFile(dest, content,
|
|
128
|
+
await fs.writeFile(dest, content, "utf-8");
|
|
123
129
|
}
|
|
124
|
-
report(onStep, stepDeps,
|
|
130
|
+
report(onStep, stepDeps, "Generate dependabot.yml", "done");
|
|
125
131
|
}
|
|
126
132
|
else {
|
|
127
|
-
report(onStep, stepDeps,
|
|
133
|
+
report(onStep, stepDeps, "Generate dependabot.yml", "skipped", `not needed for ${ciProvider}`);
|
|
128
134
|
}
|
|
129
135
|
}
|
|
130
136
|
catch (e) {
|
|
131
|
-
report(onStep, stepDeps,
|
|
137
|
+
report(onStep, stepDeps, "Generate dependabot.yml", "error", String(e));
|
|
132
138
|
}
|
|
133
139
|
// ── Step 6: Install memory module ─────────────────────────────────────────
|
|
134
|
-
const stepMem =
|
|
135
|
-
report(onStep, stepMem, `Install memory module: ${memory}`,
|
|
140
|
+
const stepMem = "memory";
|
|
141
|
+
report(onStep, stepMem, `Install memory module: ${memory}`, "running");
|
|
136
142
|
try {
|
|
137
|
-
if (memory !==
|
|
143
|
+
if (memory !== "none") {
|
|
138
144
|
const moduleSrc = path.join(MODULES_DIR, memory);
|
|
139
145
|
if (await fs.pathExists(moduleSrc)) {
|
|
140
146
|
if (!dryRun) {
|
|
141
147
|
// Copy module files to project
|
|
142
|
-
const moduleDest = path.join(projectDir,
|
|
148
|
+
const moduleDest = path.join(projectDir, ".javi-forge", "modules", memory);
|
|
143
149
|
await ensureDirExists(moduleDest);
|
|
144
|
-
await fs.copy(moduleSrc, moduleDest, {
|
|
150
|
+
await fs.copy(moduleSrc, moduleDest, {
|
|
151
|
+
overwrite: false,
|
|
152
|
+
errorOnExist: false,
|
|
153
|
+
});
|
|
145
154
|
// If engram, copy .mcp-config-snippet.json to project with placeholder replacement
|
|
146
|
-
if (memory ===
|
|
147
|
-
const snippetSrc = path.join(moduleSrc,
|
|
155
|
+
if (memory === "engram") {
|
|
156
|
+
const snippetSrc = path.join(moduleSrc, ".mcp-config-snippet.json");
|
|
148
157
|
if (await fs.pathExists(snippetSrc)) {
|
|
149
|
-
const snippetDest = path.join(projectDir,
|
|
150
|
-
let content = await fs.readFile(snippetSrc,
|
|
158
|
+
const snippetDest = path.join(projectDir, ".mcp-config-snippet.json");
|
|
159
|
+
let content = await fs.readFile(snippetSrc, "utf-8");
|
|
151
160
|
content = content.replace(/__PROJECT_NAME__/g, projectName);
|
|
152
|
-
await fs.writeFile(snippetDest, content,
|
|
161
|
+
await fs.writeFile(snippetDest, content, "utf-8");
|
|
153
162
|
}
|
|
154
163
|
}
|
|
155
164
|
}
|
|
156
|
-
report(onStep, stepMem, `Install memory module: ${memory}`,
|
|
165
|
+
report(onStep, stepMem, `Install memory module: ${memory}`, "done");
|
|
157
166
|
}
|
|
158
167
|
else {
|
|
159
|
-
report(onStep, stepMem, `Install memory module: ${memory}`,
|
|
168
|
+
report(onStep, stepMem, `Install memory module: ${memory}`, "error", "module not found");
|
|
160
169
|
}
|
|
161
170
|
}
|
|
162
171
|
else {
|
|
163
|
-
report(onStep, stepMem, `Install memory module: ${memory}`,
|
|
172
|
+
report(onStep, stepMem, `Install memory module: ${memory}`, "skipped", "none selected");
|
|
164
173
|
}
|
|
165
174
|
}
|
|
166
175
|
catch (e) {
|
|
167
|
-
report(onStep, stepMem, `Install memory module: ${memory}`,
|
|
176
|
+
report(onStep, stepMem, `Install memory module: ${memory}`, "error", String(e));
|
|
168
177
|
}
|
|
169
178
|
// ── Step 7: AI config sync (delegated to javi-ai) ──────────────────────────
|
|
170
|
-
const stepAI =
|
|
171
|
-
report(onStep, stepAI,
|
|
179
|
+
const stepAI = "ai-sync";
|
|
180
|
+
report(onStep, stepAI, "Sync AI config via javi-ai", "running");
|
|
172
181
|
try {
|
|
173
182
|
if (aiSync) {
|
|
174
183
|
if (!dryRun) {
|
|
175
184
|
try {
|
|
176
|
-
const { stderr } = await execFileAsync(
|
|
185
|
+
const { stderr } = await execFileAsync("npx", ["javi-ai", "sync", "--project-dir", projectDir, "--target", "all"], {
|
|
177
186
|
cwd: projectDir,
|
|
178
187
|
timeout: 120_000,
|
|
179
188
|
});
|
|
180
189
|
// javi-ai may exit 0 but crash (e.g. Ink raw mode error) — detect via stderr
|
|
181
|
-
if (stderr &&
|
|
182
|
-
|
|
190
|
+
if (stderr &&
|
|
191
|
+
(stderr.includes("Raw mode is not supported") ||
|
|
192
|
+
stderr.includes("ERROR"))) {
|
|
193
|
+
report(onStep, stepAI, "Sync AI config via javi-ai", "error", "javi-ai crashed. Run manually: npx javi-ai sync --project-dir . --target all");
|
|
183
194
|
}
|
|
184
195
|
else {
|
|
185
|
-
report(onStep, stepAI,
|
|
196
|
+
report(onStep, stepAI, "Sync AI config via javi-ai", "done", "javi-ai sync --target all");
|
|
186
197
|
}
|
|
187
198
|
}
|
|
188
199
|
catch (syncErr) {
|
|
189
200
|
const msg = syncErr instanceof Error ? syncErr.message : String(syncErr);
|
|
190
|
-
if (msg.includes(
|
|
191
|
-
|
|
201
|
+
if (msg.includes("ENOENT") ||
|
|
202
|
+
msg.includes("not found") ||
|
|
203
|
+
msg.includes("ERR_MODULE_NOT_FOUND")) {
|
|
204
|
+
report(onStep, stepAI, "Sync AI config via javi-ai", "error", "javi-ai not found. Install with: npm install -g javi-ai (or run npx javi-ai sync manually)");
|
|
192
205
|
}
|
|
193
206
|
else {
|
|
194
|
-
report(onStep, stepAI,
|
|
207
|
+
report(onStep, stepAI, "Sync AI config via javi-ai", "error", msg);
|
|
195
208
|
}
|
|
196
209
|
}
|
|
197
210
|
}
|
|
198
211
|
else {
|
|
199
|
-
report(onStep, stepAI,
|
|
212
|
+
report(onStep, stepAI, "Sync AI config via javi-ai", "done", "dry-run: would run javi-ai sync --target all");
|
|
200
213
|
}
|
|
201
214
|
}
|
|
202
215
|
else {
|
|
203
|
-
report(onStep, stepAI,
|
|
216
|
+
report(onStep, stepAI, "Sync AI config via javi-ai", "skipped", "not selected");
|
|
204
217
|
}
|
|
205
218
|
}
|
|
206
219
|
catch (e) {
|
|
207
|
-
report(onStep, stepAI,
|
|
220
|
+
report(onStep, stepAI, "Sync AI config via javi-ai", "error", String(e));
|
|
208
221
|
}
|
|
209
222
|
// ── Step 8: SDD (Spec-Driven Development) ─────────────────────────────────
|
|
210
|
-
const stepSDD =
|
|
211
|
-
report(onStep, stepSDD,
|
|
223
|
+
const stepSDD = "sdd";
|
|
224
|
+
report(onStep, stepSDD, "Set up SDD (openspec/)", "running");
|
|
212
225
|
try {
|
|
213
226
|
if (sdd) {
|
|
214
227
|
if (!dryRun) {
|
|
215
|
-
const openspecDir = path.join(projectDir,
|
|
228
|
+
const openspecDir = path.join(projectDir, "openspec");
|
|
216
229
|
await ensureDirExists(openspecDir);
|
|
217
230
|
// Create a README if none exists
|
|
218
|
-
const readmePath = path.join(openspecDir,
|
|
219
|
-
if (!await fs.pathExists(readmePath)) {
|
|
220
|
-
await fs.writeFile(readmePath, `# openspec/\n\nSpec-Driven Development artifacts for ${projectName}.\n\nSee: /sdd:new <name> to start a new change.\n`,
|
|
231
|
+
const readmePath = path.join(openspecDir, "README.md");
|
|
232
|
+
if (!(await fs.pathExists(readmePath))) {
|
|
233
|
+
await fs.writeFile(readmePath, `# openspec/\n\nSpec-Driven Development artifacts for ${projectName}.\n\nSee: /sdd:new <name> to start a new change.\n`, "utf-8");
|
|
221
234
|
}
|
|
222
235
|
}
|
|
223
|
-
report(onStep, stepSDD,
|
|
236
|
+
report(onStep, stepSDD, "Set up SDD (openspec/)", "done");
|
|
224
237
|
}
|
|
225
238
|
else {
|
|
226
|
-
report(onStep, stepSDD,
|
|
239
|
+
report(onStep, stepSDD, "Set up SDD (openspec/)", "skipped", "not selected");
|
|
227
240
|
}
|
|
228
241
|
}
|
|
229
242
|
catch (e) {
|
|
230
|
-
report(onStep, stepSDD,
|
|
243
|
+
report(onStep, stepSDD, "Set up SDD (openspec/)", "error", String(e));
|
|
231
244
|
}
|
|
232
245
|
// ── Step 9: GHAGGA ────────────────────────────────────────────────────────
|
|
233
|
-
const stepGhagga =
|
|
234
|
-
report(onStep, stepGhagga,
|
|
246
|
+
const stepGhagga = "ghagga";
|
|
247
|
+
report(onStep, stepGhagga, "Install GHAGGA review system", "running");
|
|
235
248
|
try {
|
|
236
249
|
if (ghagga) {
|
|
237
|
-
const ghaggaSrc = path.join(MODULES_DIR,
|
|
250
|
+
const ghaggaSrc = path.join(MODULES_DIR, "ghagga");
|
|
238
251
|
if (await fs.pathExists(ghaggaSrc)) {
|
|
239
252
|
if (!dryRun) {
|
|
240
|
-
const ghaggaDest = path.join(projectDir,
|
|
253
|
+
const ghaggaDest = path.join(projectDir, ".javi-forge", "modules", "ghagga");
|
|
241
254
|
await ensureDirExists(ghaggaDest);
|
|
242
|
-
await fs.copy(ghaggaSrc, ghaggaDest, {
|
|
255
|
+
await fs.copy(ghaggaSrc, ghaggaDest, {
|
|
256
|
+
overwrite: false,
|
|
257
|
+
errorOnExist: false,
|
|
258
|
+
});
|
|
243
259
|
// Copy ghagga caller workflow to CI provider location
|
|
244
|
-
if (ciProvider ===
|
|
245
|
-
const workflowSrc = path.join(FORGE_ROOT,
|
|
260
|
+
if (ciProvider === "github") {
|
|
261
|
+
const workflowSrc = path.join(FORGE_ROOT, "templates", "github", "ghagga-review.yml");
|
|
246
262
|
if (await fs.pathExists(workflowSrc)) {
|
|
247
|
-
const workflowDest = path.join(projectDir,
|
|
263
|
+
const workflowDest = path.join(projectDir, ".github", "workflows", "ghagga-review.yml");
|
|
248
264
|
await ensureDirExists(path.dirname(workflowDest));
|
|
249
265
|
await fs.copy(workflowSrc, workflowDest, { overwrite: false });
|
|
250
266
|
}
|
|
251
267
|
}
|
|
252
268
|
}
|
|
253
|
-
report(onStep, stepGhagga,
|
|
269
|
+
report(onStep, stepGhagga, "Install GHAGGA review system", "done");
|
|
254
270
|
}
|
|
255
271
|
else {
|
|
256
|
-
report(onStep, stepGhagga,
|
|
272
|
+
report(onStep, stepGhagga, "Install GHAGGA review system", "error", "module not found");
|
|
257
273
|
}
|
|
258
274
|
}
|
|
259
275
|
else {
|
|
260
|
-
report(onStep, stepGhagga,
|
|
276
|
+
report(onStep, stepGhagga, "Install GHAGGA review system", "skipped", "not selected");
|
|
261
277
|
}
|
|
262
278
|
}
|
|
263
279
|
catch (e) {
|
|
264
|
-
report(onStep, stepGhagga,
|
|
280
|
+
report(onStep, stepGhagga, "Install GHAGGA review system", "error", String(e));
|
|
265
281
|
}
|
|
266
282
|
// ── Step 10: Mock-first mode ───────────────────────────────────────────────
|
|
267
|
-
const stepMock =
|
|
283
|
+
const stepMock = "mock";
|
|
268
284
|
if (options.mock) {
|
|
269
|
-
report(onStep, stepMock,
|
|
285
|
+
report(onStep, stepMock, "Configure mock-first mode", "running");
|
|
270
286
|
try {
|
|
271
287
|
if (!dryRun) {
|
|
272
288
|
// Create .env.example with mock values
|
|
@@ -291,34 +307,307 @@ ENABLE_ANALYTICS=false
|
|
|
291
307
|
ENABLE_EMAILS=false
|
|
292
308
|
ENABLE_WEBHOOKS=false
|
|
293
309
|
`;
|
|
294
|
-
const envExamplePath = path.join(projectDir,
|
|
295
|
-
if (!await fs.pathExists(envExamplePath)) {
|
|
296
|
-
await fs.writeFile(envExamplePath, envExample,
|
|
310
|
+
const envExamplePath = path.join(projectDir, ".env.example");
|
|
311
|
+
if (!(await fs.pathExists(envExamplePath))) {
|
|
312
|
+
await fs.writeFile(envExamplePath, envExample, "utf-8");
|
|
297
313
|
}
|
|
298
314
|
// Create .env from example
|
|
299
|
-
const envPath = path.join(projectDir,
|
|
300
|
-
if (!await fs.pathExists(envPath)) {
|
|
301
|
-
await fs.writeFile(envPath, envExample,
|
|
315
|
+
const envPath = path.join(projectDir, ".env");
|
|
316
|
+
if (!(await fs.pathExists(envPath))) {
|
|
317
|
+
await fs.writeFile(envPath, envExample, "utf-8");
|
|
302
318
|
}
|
|
303
319
|
}
|
|
304
|
-
report(onStep, stepMock,
|
|
320
|
+
report(onStep, stepMock, "Configure mock-first mode", "done", ".env.example + .env with mock values");
|
|
305
321
|
}
|
|
306
322
|
catch (e) {
|
|
307
|
-
report(onStep, stepMock,
|
|
323
|
+
report(onStep, stepMock, "Configure mock-first mode", "error", String(e));
|
|
308
324
|
}
|
|
309
325
|
}
|
|
310
326
|
else {
|
|
311
|
-
report(onStep, stepMock,
|
|
327
|
+
report(onStep, stepMock, "Configure mock-first mode", "skipped", "not selected");
|
|
328
|
+
}
|
|
329
|
+
// ── Step 11: Generate .context/ directory ──────────────────────────────────
|
|
330
|
+
const stepContext = "context-dir";
|
|
331
|
+
report(onStep, stepContext, "Generate .context/ directory", "running");
|
|
332
|
+
try {
|
|
333
|
+
if (contextDir) {
|
|
334
|
+
const contextDirPath = path.join(projectDir, ".context");
|
|
335
|
+
if (await fs.pathExists(contextDirPath)) {
|
|
336
|
+
report(onStep, stepContext, "Generate .context/ directory", "done", "already exists");
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
if (!dryRun) {
|
|
340
|
+
const { index, summary } = await generateContextDir(options);
|
|
341
|
+
await ensureDirExists(contextDirPath);
|
|
342
|
+
await fs.writeFile(path.join(contextDirPath, "INDEX.md"), index, "utf-8");
|
|
343
|
+
await fs.writeFile(path.join(contextDirPath, "summary.md"), summary, "utf-8");
|
|
344
|
+
}
|
|
345
|
+
report(onStep, stepContext, "Generate .context/ directory", "done", dryRun
|
|
346
|
+
? "dry-run: would generate .context/"
|
|
347
|
+
: ".context/INDEX.md + summary.md");
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
report(onStep, stepContext, "Generate .context/ directory", "skipped", "not selected");
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
catch (e) {
|
|
355
|
+
report(onStep, stepContext, "Generate .context/ directory", "error", String(e));
|
|
356
|
+
}
|
|
357
|
+
// ── Step 12: Generate CLAUDE.md (smart: project-aware) ─────────────────────
|
|
358
|
+
const stepClaudeMd = "claude-md";
|
|
359
|
+
report(onStep, stepClaudeMd, "Generate CLAUDE.md", "running");
|
|
360
|
+
try {
|
|
361
|
+
if (claudeMd) {
|
|
362
|
+
const claudeMdPath = path.join(projectDir, "CLAUDE.md");
|
|
363
|
+
if (await fs.pathExists(claudeMdPath)) {
|
|
364
|
+
report(onStep, stepClaudeMd, "Generate CLAUDE.md", "done", "already exists");
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
if (!dryRun) {
|
|
368
|
+
// Detect project stack for smart CLAUDE.md generation
|
|
369
|
+
const detection = await detectProjectStack(projectDir).catch(() => null);
|
|
370
|
+
const content = generateSmartClaudeMd(options, detection);
|
|
371
|
+
await fs.writeFile(claudeMdPath, content, "utf-8");
|
|
372
|
+
const skillCount = detection?.recommendedSkills.length ?? 0;
|
|
373
|
+
report(onStep, stepClaudeMd, "Generate CLAUDE.md", "done", skillCount > 0
|
|
374
|
+
? `CLAUDE.md (${skillCount} skills detected)`
|
|
375
|
+
: "CLAUDE.md");
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
report(onStep, stepClaudeMd, "Generate CLAUDE.md", "done", "dry-run: would generate CLAUDE.md");
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
report(onStep, stepClaudeMd, "Generate CLAUDE.md", "skipped", "not selected");
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
catch (e) {
|
|
387
|
+
report(onStep, stepClaudeMd, "Generate CLAUDE.md", "error", String(e));
|
|
388
|
+
}
|
|
389
|
+
// ── Step 13: Docker zero-downtime deploy ───────────────────────────────────
|
|
390
|
+
const stepDeploy = "docker-deploy";
|
|
391
|
+
report(onStep, stepDeploy, "Scaffold Docker zero-downtime deploy", "running");
|
|
392
|
+
try {
|
|
393
|
+
if (options.dockerDeploy) {
|
|
394
|
+
const deployDest = getDeployDestination(ciProvider);
|
|
395
|
+
if (deployDest) {
|
|
396
|
+
const fullDest = path.join(projectDir, deployDest);
|
|
397
|
+
if (await fs.pathExists(fullDest)) {
|
|
398
|
+
report(onStep, stepDeploy, "Scaffold Docker zero-downtime deploy", "done", "already exists");
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
const serviceName = options.dockerServiceName || "app";
|
|
402
|
+
const content = await generateDeployWorkflow(ciProvider, serviceName);
|
|
403
|
+
if (content) {
|
|
404
|
+
if (!dryRun) {
|
|
405
|
+
await backupIfExists(fullDest);
|
|
406
|
+
await ensureDirExists(path.dirname(fullDest));
|
|
407
|
+
await fs.writeFile(fullDest, content, "utf-8");
|
|
408
|
+
}
|
|
409
|
+
report(onStep, stepDeploy, "Scaffold Docker zero-downtime deploy", "done", dryRun ? `dry-run: would create ${deployDest}` : deployDest);
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
report(onStep, stepDeploy, "Scaffold Docker zero-downtime deploy", "error", `no deploy template for ${ciProvider}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
report(onStep, stepDeploy, "Scaffold Docker zero-downtime deploy", "error", `no deploy destination for ${ciProvider}`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
report(onStep, stepDeploy, "Scaffold Docker zero-downtime deploy", "skipped", "not selected");
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
catch (e) {
|
|
425
|
+
report(onStep, stepDeploy, "Scaffold Docker zero-downtime deploy", "error", String(e));
|
|
426
|
+
}
|
|
427
|
+
// ── Step 14: Security hooks scaffold ────────────────────────────────────────
|
|
428
|
+
const stepSecurity = "security-hooks";
|
|
429
|
+
report(onStep, stepSecurity, "Scaffold security hooks", "running");
|
|
430
|
+
try {
|
|
431
|
+
if (securityHooks) {
|
|
432
|
+
if (await fs.pathExists(SECURITY_HOOKS_DIR)) {
|
|
433
|
+
if (!dryRun) {
|
|
434
|
+
// Copy 6-layer git security hooks into ci-local/hooks/security/
|
|
435
|
+
const secHooksDest = path.join(projectDir, "ci-local", "hooks", "security");
|
|
436
|
+
await ensureDirExists(secHooksDest);
|
|
437
|
+
const hookFiles = await fs.readdir(SECURITY_HOOKS_DIR);
|
|
438
|
+
const gitHooks = hookFiles.filter((f) => !f.endsWith(".json"));
|
|
439
|
+
for (const hook of gitHooks) {
|
|
440
|
+
const src = path.join(SECURITY_HOOKS_DIR, hook);
|
|
441
|
+
const dest = path.join(secHooksDest, hook);
|
|
442
|
+
await fs.copy(src, dest, { overwrite: false });
|
|
443
|
+
await fs.chmod(dest, 0o755);
|
|
444
|
+
}
|
|
445
|
+
// Copy runtime security settings (kiteguard-style) to .claude/
|
|
446
|
+
const settingsSrc = path.join(SECURITY_HOOKS_DIR, "claude-settings-security.json");
|
|
447
|
+
if (await fs.pathExists(settingsSrc)) {
|
|
448
|
+
const claudeDir = path.join(projectDir, ".claude");
|
|
449
|
+
await ensureDirExists(claudeDir);
|
|
450
|
+
const settingsDest = path.join(claudeDir, "settings.json");
|
|
451
|
+
if (!(await fs.pathExists(settingsDest))) {
|
|
452
|
+
await fs.copy(settingsSrc, settingsDest);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
report(onStep, stepSecurity, "Scaffold security hooks", "done", dryRun
|
|
457
|
+
? "dry-run: would scaffold security hooks"
|
|
458
|
+
: "6 git layers + runtime hooks");
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
report(onStep, stepSecurity, "Scaffold security hooks", "error", "security-hooks templates not found");
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
report(onStep, stepSecurity, "Scaffold security hooks", "skipped", "not selected");
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
catch (e) {
|
|
469
|
+
report(onStep, stepSecurity, "Scaffold security hooks", "error", String(e));
|
|
470
|
+
}
|
|
471
|
+
// ── Step 14b: Write hook profile ─────────────────────────────────────────────
|
|
472
|
+
const stepHookProfile = "hook-profile";
|
|
473
|
+
report(onStep, stepHookProfile, "Write hook reliability profile", "running");
|
|
474
|
+
try {
|
|
475
|
+
if (securityHooks) {
|
|
476
|
+
if (!dryRun) {
|
|
477
|
+
const hooksDir = path.join(projectDir, "ci-local", "hooks");
|
|
478
|
+
await ensureDirExists(hooksDir);
|
|
479
|
+
const profilePath = path.join(hooksDir, "profile.json");
|
|
480
|
+
const resolvedProfile = hookProfile ?? "standard";
|
|
481
|
+
await fs.writeJson(profilePath, { profile: resolvedProfile }, { spaces: 2 });
|
|
482
|
+
}
|
|
483
|
+
report(onStep, stepHookProfile, "Write hook reliability profile", "done", dryRun
|
|
484
|
+
? `dry-run: would write profile.json (${hookProfile ?? "standard"})`
|
|
485
|
+
: `ci-local/hooks/profile.json (${hookProfile ?? "standard"})`);
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
report(onStep, stepHookProfile, "Write hook reliability profile", "skipped", "security hooks not selected");
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
catch (e) {
|
|
492
|
+
report(onStep, stepHookProfile, "Write hook reliability profile", "error", String(e));
|
|
493
|
+
}
|
|
494
|
+
// ── Step 15: RepoForge code graph scaffolding ───────────────────────────────
|
|
495
|
+
const stepGraph = "code-graph";
|
|
496
|
+
report(onStep, stepGraph, "Scaffold RepoForge code graph", "running");
|
|
497
|
+
try {
|
|
498
|
+
if (options.codeGraph) {
|
|
499
|
+
if (!dryRun) {
|
|
500
|
+
// 1. Copy .repoforge.yaml config
|
|
501
|
+
const repoforgeConfigSrc = path.join(TEMPLATES_DIR, "common", "repoforge", "repoforge.yaml");
|
|
502
|
+
const repoforgeConfigDest = path.join(projectDir, ".repoforge.yaml");
|
|
503
|
+
if (!(await fs.pathExists(repoforgeConfigDest))) {
|
|
504
|
+
await fs.copy(repoforgeConfigSrc, repoforgeConfigDest);
|
|
505
|
+
}
|
|
506
|
+
// 2. Ensure .repoforge/ output dir exists
|
|
507
|
+
await ensureDirExists(path.join(projectDir, ".repoforge"));
|
|
508
|
+
// 3. Copy CI workflow for graph generation (GitHub only)
|
|
509
|
+
if (ciProvider === "github") {
|
|
510
|
+
const graphWorkflowSrc = path.join(TEMPLATES_DIR, "github", "repoforge-graph.yml");
|
|
511
|
+
if (await fs.pathExists(graphWorkflowSrc)) {
|
|
512
|
+
const graphWorkflowDest = path.join(projectDir, ".github", "workflows", "repoforge-graph.yml");
|
|
513
|
+
await ensureDirExists(path.dirname(graphWorkflowDest));
|
|
514
|
+
await backupIfExists(graphWorkflowDest);
|
|
515
|
+
await fs.copy(graphWorkflowSrc, graphWorkflowDest, {
|
|
516
|
+
overwrite: false,
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
// 4. Copy MCP config snippet for repoforge code intelligence
|
|
521
|
+
const mcpSnippetSrc = path.join(TEMPLATES_DIR, "common", "repoforge", "mcp-repoforge-snippet.json");
|
|
522
|
+
if (await fs.pathExists(mcpSnippetSrc)) {
|
|
523
|
+
const mcpSnippetDest = path.join(projectDir, ".repoforge", "mcp-config-snippet.json");
|
|
524
|
+
let content = await fs.readFile(mcpSnippetSrc, "utf-8");
|
|
525
|
+
content = content.replace(/__PROJECT_NAME__/g, projectName);
|
|
526
|
+
await fs.writeFile(mcpSnippetDest, content, "utf-8");
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
report(onStep, stepGraph, "Scaffold RepoForge code graph", "done", dryRun
|
|
530
|
+
? "dry-run: would scaffold .repoforge.yaml + CI + MCP"
|
|
531
|
+
: ".repoforge.yaml + CI + MCP snippet");
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
report(onStep, stepGraph, "Scaffold RepoForge code graph", "skipped", "not selected");
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
catch (e) {
|
|
538
|
+
report(onStep, stepGraph, "Scaffold RepoForge code graph", "error", String(e));
|
|
539
|
+
}
|
|
540
|
+
// ── Step 16: Local AI dev stack ────────────────────────────────────────────
|
|
541
|
+
const stepLocalAi = "local-ai";
|
|
542
|
+
report(onStep, stepLocalAi, "Scaffold local AI dev stack", "running");
|
|
543
|
+
try {
|
|
544
|
+
if (options.localAi) {
|
|
545
|
+
if (await fs.pathExists(LOCAL_AI_TEMPLATE_DIR)) {
|
|
546
|
+
const composeDest = path.join(projectDir, "docker-compose.yml");
|
|
547
|
+
const envDest = path.join(projectDir, ".env.local-ai");
|
|
548
|
+
if (!dryRun) {
|
|
549
|
+
// Copy docker-compose.yml (skip if exists)
|
|
550
|
+
if (!(await fs.pathExists(composeDest))) {
|
|
551
|
+
await fs.copy(path.join(LOCAL_AI_TEMPLATE_DIR, "docker-compose.yml"), composeDest);
|
|
552
|
+
}
|
|
553
|
+
// Copy .env.example as .env.local-ai
|
|
554
|
+
const envSrc = path.join(LOCAL_AI_TEMPLATE_DIR, ".env.example");
|
|
555
|
+
if ((await fs.pathExists(envSrc)) &&
|
|
556
|
+
!(await fs.pathExists(envDest))) {
|
|
557
|
+
await fs.copy(envSrc, envDest);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
report(onStep, stepLocalAi, "Scaffold local AI dev stack", "done", dryRun
|
|
561
|
+
? "dry-run: would create docker-compose.yml + .env.local-ai"
|
|
562
|
+
: "docker-compose.yml + .env.local-ai");
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
report(onStep, stepLocalAi, "Scaffold local AI dev stack", "error", "local-ai template not found");
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
else {
|
|
569
|
+
report(onStep, stepLocalAi, "Scaffold local AI dev stack", "skipped", "not selected");
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
catch (e) {
|
|
573
|
+
report(onStep, stepLocalAi, "Scaffold local AI dev stack", "error", String(e));
|
|
574
|
+
}
|
|
575
|
+
// ── Step 17: Generate Agent Skills manifest (skills.json) ─────────────────
|
|
576
|
+
const stepSkills = "agent-skills";
|
|
577
|
+
report(onStep, stepSkills, "Generate Agent Skills manifest (skills.json)", "running");
|
|
578
|
+
try {
|
|
579
|
+
if (!dryRun) {
|
|
580
|
+
const skillsManifest = {
|
|
581
|
+
name: projectName,
|
|
582
|
+
version: "0.1.0",
|
|
583
|
+
description: `Agent Skills manifest for ${projectName}`,
|
|
584
|
+
skills: [],
|
|
585
|
+
};
|
|
586
|
+
const skillsJsonPath = path.join(projectDir, AGENT_SKILLS_MANIFEST_FILE);
|
|
587
|
+
if (!(await fs.pathExists(skillsJsonPath))) {
|
|
588
|
+
await fs.writeJson(skillsJsonPath, skillsManifest, { spaces: 2 });
|
|
589
|
+
report(onStep, stepSkills, "Generate Agent Skills manifest (skills.json)", "done", AGENT_SKILLS_MANIFEST_FILE);
|
|
590
|
+
}
|
|
591
|
+
else {
|
|
592
|
+
report(onStep, stepSkills, "Generate Agent Skills manifest (skills.json)", "done", "already exists");
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
else {
|
|
596
|
+
report(onStep, stepSkills, "Generate Agent Skills manifest (skills.json)", "done", `dry-run: would generate ${AGENT_SKILLS_MANIFEST_FILE}`);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
catch (e) {
|
|
600
|
+
report(onStep, stepSkills, "Generate Agent Skills manifest (skills.json)", "error", String(e));
|
|
312
601
|
}
|
|
313
|
-
// ── Step
|
|
314
|
-
const stepManifest =
|
|
315
|
-
report(onStep, stepManifest,
|
|
602
|
+
// ── Step 18: Write manifest ───────────────────────────────────────────────
|
|
603
|
+
const stepManifest = "manifest";
|
|
604
|
+
report(onStep, stepManifest, "Write forge manifest", "running");
|
|
316
605
|
try {
|
|
317
606
|
if (!dryRun) {
|
|
318
|
-
const manifestDir = path.join(projectDir,
|
|
607
|
+
const manifestDir = path.join(projectDir, ".javi-forge");
|
|
319
608
|
await ensureDirExists(manifestDir);
|
|
320
609
|
const manifest = {
|
|
321
|
-
version:
|
|
610
|
+
version: "0.1.0",
|
|
322
611
|
projectName,
|
|
323
612
|
stack,
|
|
324
613
|
ciProvider,
|
|
@@ -326,18 +615,26 @@ ENABLE_WEBHOOKS=false
|
|
|
326
615
|
createdAt: new Date().toISOString(),
|
|
327
616
|
updatedAt: new Date().toISOString(),
|
|
328
617
|
modules: [
|
|
329
|
-
...(memory !==
|
|
330
|
-
...(ghagga ? [
|
|
331
|
-
...(sdd ? [
|
|
332
|
-
...(aiSync ? [
|
|
618
|
+
...(memory !== "none" ? [memory] : []),
|
|
619
|
+
...(ghagga ? ["ghagga"] : []),
|
|
620
|
+
...(sdd ? ["sdd"] : []),
|
|
621
|
+
...(aiSync ? ["ai-config"] : []),
|
|
622
|
+
...(contextDir ? ["context"] : []),
|
|
623
|
+
...(claudeMd ? ["claude-md"] : []),
|
|
624
|
+
...(options.dockerDeploy ? ["docker-deploy"] : []),
|
|
625
|
+
...(securityHooks ? ["security-hooks"] : []),
|
|
626
|
+
...(options.codeGraph ? ["code-graph"] : []),
|
|
627
|
+
...(options.localAi ? ["local-ai"] : []),
|
|
333
628
|
],
|
|
334
629
|
};
|
|
335
|
-
await fs.writeJson(path.join(manifestDir,
|
|
630
|
+
await fs.writeJson(path.join(manifestDir, "manifest.json"), manifest, {
|
|
631
|
+
spaces: 2,
|
|
632
|
+
});
|
|
336
633
|
}
|
|
337
|
-
report(onStep, stepManifest,
|
|
634
|
+
report(onStep, stepManifest, "Write forge manifest", "done", ".javi-forge/manifest.json");
|
|
338
635
|
}
|
|
339
636
|
catch (e) {
|
|
340
|
-
report(onStep, stepManifest,
|
|
637
|
+
report(onStep, stepManifest, "Write forge manifest", "error", String(e));
|
|
341
638
|
}
|
|
342
639
|
}
|
|
343
640
|
//# sourceMappingURL=init.js.map
|