javi-forge 1.6.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/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 +384 -141
- 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 +10 -1
- package/dist/commands/plugin.js +92 -47
- 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 +11 -5
- package/dist/commands/security.js +216 -76
- package/dist/commands/skill-scanner.d.ts +63 -0
- package/dist/commands/skill-scanner.js +383 -0
- package/dist/commands/skills.d.ts +62 -5
- package/dist/commands/skills.js +439 -54
- 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 +1 -1
- package/dist/commands/tdd.js +21 -18
- 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 +15 -1
- package/dist/constants.js +161 -122
- package/dist/index.js +308 -98
- package/dist/lib/agent-skills.d.ts +36 -1
- package/dist/lib/agent-skills.js +168 -19
- 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 +13 -1
- package/dist/lib/claudemd.js +174 -24
- package/dist/lib/codex-export.d.ts +1 -1
- package/dist/lib/codex-export.js +29 -31
- package/dist/lib/common.d.ts +1 -1
- package/dist/lib/common.js +52 -44
- package/dist/lib/context.d.ts +17 -2
- package/dist/lib/context.js +142 -13
- 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 +9 -3
- package/dist/lib/plugin.js +128 -69
- 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 +134 -6
- 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 +86 -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 +6 -2
- package/dist/ui/OptionSelector.js +83 -32
- package/dist/ui/Plugin.d.ts +4 -3
- package/dist/ui/Plugin.js +78 -35
- package/dist/ui/Progress.d.ts +3 -3
- package/dist/ui/Progress.js +23 -22
- package/dist/ui/Skills.d.ts +2 -2
- package/dist/ui/Skills.js +61 -32
- 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/commands/security.d.ts.map +0 -1
- package/dist/commands/security.js.map +0 -1
- package/dist/commands/skills.d.ts.map +0 -1
- package/dist/commands/skills.js.map +0 -1
- package/dist/commands/tdd.d.ts.map +0 -1
- package/dist/commands/tdd.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/agent-skills.d.ts.map +0 -1
- package/dist/lib/agent-skills.js.map +0 -1
- package/dist/lib/claudemd.d.ts.map +0 -1
- package/dist/lib/claudemd.js.map +0 -1
- package/dist/lib/codex-export.d.ts.map +0 -1
- package/dist/lib/codex-export.js.map +0 -1
- package/dist/lib/common.d.ts.map +0 -1
- package/dist/lib/common.js.map +0 -1
- package/dist/lib/context.d.ts.map +0 -1
- package/dist/lib/context.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/Skills.d.ts.map +0 -1
- package/dist/ui/Skills.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,12 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import { promisify } from
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
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";
|
|
10
11
|
const execFileAsync = promisify(execFile);
|
|
11
12
|
function report(onStep, id, label, status, detail) {
|
|
12
13
|
onStep({ id, label, status, detail });
|
|
@@ -16,61 +17,64 @@ function report(onStep, id, label, status, detail) {
|
|
|
16
17
|
* memory module, AI config sync, SDD, and ghagga.
|
|
17
18
|
*/
|
|
18
19
|
export async function initProject(options, onStep) {
|
|
19
|
-
const { projectDir, projectName, stack, ciProvider, memory, aiSync, sdd, ghagga, contextDir, claudeMd, dryRun } = options;
|
|
20
|
+
const { projectDir, projectName, stack, ciProvider, memory, aiSync, sdd, ghagga, contextDir, claudeMd, securityHooks, hookProfile, dryRun, } = options;
|
|
20
21
|
// Ensure project directory exists before any steps
|
|
21
22
|
if (!dryRun && projectDir) {
|
|
22
23
|
await ensureDirExists(projectDir);
|
|
23
24
|
}
|
|
24
25
|
// ── Step 1: Initialize git ────────────────────────────────────────────────
|
|
25
|
-
const stepGit =
|
|
26
|
-
report(onStep, stepGit,
|
|
26
|
+
const stepGit = "git-init";
|
|
27
|
+
report(onStep, stepGit, "Initialize git repository", "running");
|
|
27
28
|
try {
|
|
28
|
-
const gitDir = path.join(projectDir,
|
|
29
|
-
if (!await fs.pathExists(gitDir)) {
|
|
29
|
+
const gitDir = path.join(projectDir, ".git");
|
|
30
|
+
if (!(await fs.pathExists(gitDir))) {
|
|
30
31
|
if (!dryRun) {
|
|
31
|
-
await execFileAsync(
|
|
32
|
+
await execFileAsync("git", ["init"], { cwd: projectDir });
|
|
32
33
|
}
|
|
33
|
-
report(onStep, stepGit,
|
|
34
|
+
report(onStep, stepGit, "Initialize git repository", "done", "initialized");
|
|
34
35
|
}
|
|
35
36
|
else {
|
|
36
|
-
report(onStep, stepGit,
|
|
37
|
+
report(onStep, stepGit, "Initialize git repository", "done", "already exists");
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
catch (e) {
|
|
40
|
-
report(onStep, stepGit,
|
|
41
|
+
report(onStep, stepGit, "Initialize git repository", "error", String(e));
|
|
41
42
|
}
|
|
42
43
|
// ── Step 2: Configure git hooks path ──────────────────────────────────────
|
|
43
|
-
const stepHooks =
|
|
44
|
-
report(onStep, stepHooks,
|
|
44
|
+
const stepHooks = "git-hooks";
|
|
45
|
+
report(onStep, stepHooks, "Configure git hooks path", "running");
|
|
45
46
|
try {
|
|
46
47
|
const ciLocalSrc = CI_LOCAL_DIR;
|
|
47
|
-
const ciLocalDest = path.join(projectDir,
|
|
48
|
+
const ciLocalDest = path.join(projectDir, "ci-local");
|
|
48
49
|
if (await fs.pathExists(ciLocalSrc)) {
|
|
49
50
|
if (!dryRun) {
|
|
50
|
-
await fs.copy(ciLocalSrc, ciLocalDest, {
|
|
51
|
+
await fs.copy(ciLocalSrc, ciLocalDest, {
|
|
52
|
+
overwrite: false,
|
|
53
|
+
errorOnExist: false,
|
|
54
|
+
});
|
|
51
55
|
// Set core.hooksPath to ci-local/hooks
|
|
52
|
-
const hooksDir = path.join(ciLocalDest,
|
|
56
|
+
const hooksDir = path.join(ciLocalDest, "hooks");
|
|
53
57
|
if (await fs.pathExists(hooksDir)) {
|
|
54
58
|
// Ensure hooks are executable
|
|
55
59
|
const hookFiles = await fs.readdir(hooksDir);
|
|
56
60
|
for (const hook of hookFiles) {
|
|
57
61
|
await fs.chmod(path.join(hooksDir, hook), 0o755);
|
|
58
62
|
}
|
|
59
|
-
await execFileAsync(
|
|
63
|
+
await execFileAsync("git", ["config", "core.hooksPath", "ci-local/hooks"], { cwd: projectDir });
|
|
60
64
|
}
|
|
61
65
|
}
|
|
62
|
-
report(onStep, stepHooks,
|
|
66
|
+
report(onStep, stepHooks, "Configure git hooks path", "done", "ci-local/hooks");
|
|
63
67
|
}
|
|
64
68
|
else {
|
|
65
|
-
report(onStep, stepHooks,
|
|
69
|
+
report(onStep, stepHooks, "Configure git hooks path", "skipped", "no ci-local dir");
|
|
66
70
|
}
|
|
67
71
|
}
|
|
68
72
|
catch (e) {
|
|
69
|
-
report(onStep, stepHooks,
|
|
73
|
+
report(onStep, stepHooks, "Configure git hooks path", "error", String(e));
|
|
70
74
|
}
|
|
71
75
|
// ── Step 3: Copy CI template ──────────────────────────────────────────────
|
|
72
|
-
const stepCI =
|
|
73
|
-
report(onStep, stepCI, `Copy ${ciProvider} CI workflow`,
|
|
76
|
+
const stepCI = "ci-template";
|
|
77
|
+
report(onStep, stepCI, `Copy ${ciProvider} CI workflow`, "running");
|
|
74
78
|
try {
|
|
75
79
|
const ciContent = await generateCIWorkflow(stack, ciProvider);
|
|
76
80
|
if (ciContent) {
|
|
@@ -78,197 +82,207 @@ export async function initProject(options, onStep) {
|
|
|
78
82
|
if (!dryRun) {
|
|
79
83
|
await backupIfExists(dest);
|
|
80
84
|
await ensureDirExists(path.dirname(dest));
|
|
81
|
-
await fs.writeFile(dest, ciContent,
|
|
85
|
+
await fs.writeFile(dest, ciContent, "utf-8");
|
|
82
86
|
}
|
|
83
|
-
report(onStep, stepCI, `Copy ${ciProvider} CI workflow`,
|
|
87
|
+
report(onStep, stepCI, `Copy ${ciProvider} CI workflow`, "done", getCIDestination(ciProvider));
|
|
84
88
|
}
|
|
85
89
|
else {
|
|
86
|
-
report(onStep, stepCI, `Copy ${ciProvider} CI workflow`,
|
|
90
|
+
report(onStep, stepCI, `Copy ${ciProvider} CI workflow`, "skipped", `no template for ${stack}`);
|
|
87
91
|
}
|
|
88
92
|
}
|
|
89
93
|
catch (e) {
|
|
90
|
-
report(onStep, stepCI, `Copy ${ciProvider} CI workflow`,
|
|
94
|
+
report(onStep, stepCI, `Copy ${ciProvider} CI workflow`, "error", String(e));
|
|
91
95
|
}
|
|
92
96
|
// ── Step 4: Generate .gitignore ───────────────────────────────────────────
|
|
93
|
-
const stepGitignore =
|
|
94
|
-
report(onStep, stepGitignore,
|
|
97
|
+
const stepGitignore = "gitignore";
|
|
98
|
+
report(onStep, stepGitignore, "Generate .gitignore", "running");
|
|
95
99
|
try {
|
|
96
|
-
const templatePath = path.join(FORGE_ROOT,
|
|
97
|
-
const dest = path.join(projectDir,
|
|
98
|
-
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))) {
|
|
99
103
|
if (!dryRun) {
|
|
100
104
|
await fs.copy(templatePath, dest);
|
|
101
105
|
}
|
|
102
|
-
report(onStep, stepGitignore,
|
|
106
|
+
report(onStep, stepGitignore, "Generate .gitignore", "done", "from template");
|
|
103
107
|
}
|
|
104
108
|
else if (await fs.pathExists(dest)) {
|
|
105
|
-
report(onStep, stepGitignore,
|
|
109
|
+
report(onStep, stepGitignore, "Generate .gitignore", "done", "already exists");
|
|
106
110
|
}
|
|
107
111
|
else {
|
|
108
|
-
report(onStep, stepGitignore,
|
|
112
|
+
report(onStep, stepGitignore, "Generate .gitignore", "skipped", "no template");
|
|
109
113
|
}
|
|
110
114
|
}
|
|
111
115
|
catch (e) {
|
|
112
|
-
report(onStep, stepGitignore,
|
|
116
|
+
report(onStep, stepGitignore, "Generate .gitignore", "error", String(e));
|
|
113
117
|
}
|
|
114
118
|
// ── Step 5: Generate dependabot.yml ───────────────────────────────────────
|
|
115
|
-
const stepDeps =
|
|
116
|
-
report(onStep, stepDeps,
|
|
119
|
+
const stepDeps = "dependabot";
|
|
120
|
+
report(onStep, stepDeps, "Generate dependabot.yml", "running");
|
|
117
121
|
try {
|
|
118
|
-
if (ciProvider ===
|
|
122
|
+
if (ciProvider === "github") {
|
|
119
123
|
const content = await generateDependabotYml([stack], true);
|
|
120
|
-
const dest = path.join(projectDir,
|
|
124
|
+
const dest = path.join(projectDir, ".github", "dependabot.yml");
|
|
121
125
|
if (!dryRun) {
|
|
122
126
|
await backupIfExists(dest);
|
|
123
127
|
await ensureDirExists(path.dirname(dest));
|
|
124
|
-
await fs.writeFile(dest, content,
|
|
128
|
+
await fs.writeFile(dest, content, "utf-8");
|
|
125
129
|
}
|
|
126
|
-
report(onStep, stepDeps,
|
|
130
|
+
report(onStep, stepDeps, "Generate dependabot.yml", "done");
|
|
127
131
|
}
|
|
128
132
|
else {
|
|
129
|
-
report(onStep, stepDeps,
|
|
133
|
+
report(onStep, stepDeps, "Generate dependabot.yml", "skipped", `not needed for ${ciProvider}`);
|
|
130
134
|
}
|
|
131
135
|
}
|
|
132
136
|
catch (e) {
|
|
133
|
-
report(onStep, stepDeps,
|
|
137
|
+
report(onStep, stepDeps, "Generate dependabot.yml", "error", String(e));
|
|
134
138
|
}
|
|
135
139
|
// ── Step 6: Install memory module ─────────────────────────────────────────
|
|
136
|
-
const stepMem =
|
|
137
|
-
report(onStep, stepMem, `Install memory module: ${memory}`,
|
|
140
|
+
const stepMem = "memory";
|
|
141
|
+
report(onStep, stepMem, `Install memory module: ${memory}`, "running");
|
|
138
142
|
try {
|
|
139
|
-
if (memory !==
|
|
143
|
+
if (memory !== "none") {
|
|
140
144
|
const moduleSrc = path.join(MODULES_DIR, memory);
|
|
141
145
|
if (await fs.pathExists(moduleSrc)) {
|
|
142
146
|
if (!dryRun) {
|
|
143
147
|
// Copy module files to project
|
|
144
|
-
const moduleDest = path.join(projectDir,
|
|
148
|
+
const moduleDest = path.join(projectDir, ".javi-forge", "modules", memory);
|
|
145
149
|
await ensureDirExists(moduleDest);
|
|
146
|
-
await fs.copy(moduleSrc, moduleDest, {
|
|
150
|
+
await fs.copy(moduleSrc, moduleDest, {
|
|
151
|
+
overwrite: false,
|
|
152
|
+
errorOnExist: false,
|
|
153
|
+
});
|
|
147
154
|
// If engram, copy .mcp-config-snippet.json to project with placeholder replacement
|
|
148
|
-
if (memory ===
|
|
149
|
-
const snippetSrc = path.join(moduleSrc,
|
|
155
|
+
if (memory === "engram") {
|
|
156
|
+
const snippetSrc = path.join(moduleSrc, ".mcp-config-snippet.json");
|
|
150
157
|
if (await fs.pathExists(snippetSrc)) {
|
|
151
|
-
const snippetDest = path.join(projectDir,
|
|
152
|
-
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");
|
|
153
160
|
content = content.replace(/__PROJECT_NAME__/g, projectName);
|
|
154
|
-
await fs.writeFile(snippetDest, content,
|
|
161
|
+
await fs.writeFile(snippetDest, content, "utf-8");
|
|
155
162
|
}
|
|
156
163
|
}
|
|
157
164
|
}
|
|
158
|
-
report(onStep, stepMem, `Install memory module: ${memory}`,
|
|
165
|
+
report(onStep, stepMem, `Install memory module: ${memory}`, "done");
|
|
159
166
|
}
|
|
160
167
|
else {
|
|
161
|
-
report(onStep, stepMem, `Install memory module: ${memory}`,
|
|
168
|
+
report(onStep, stepMem, `Install memory module: ${memory}`, "error", "module not found");
|
|
162
169
|
}
|
|
163
170
|
}
|
|
164
171
|
else {
|
|
165
|
-
report(onStep, stepMem, `Install memory module: ${memory}`,
|
|
172
|
+
report(onStep, stepMem, `Install memory module: ${memory}`, "skipped", "none selected");
|
|
166
173
|
}
|
|
167
174
|
}
|
|
168
175
|
catch (e) {
|
|
169
|
-
report(onStep, stepMem, `Install memory module: ${memory}`,
|
|
176
|
+
report(onStep, stepMem, `Install memory module: ${memory}`, "error", String(e));
|
|
170
177
|
}
|
|
171
178
|
// ── Step 7: AI config sync (delegated to javi-ai) ──────────────────────────
|
|
172
|
-
const stepAI =
|
|
173
|
-
report(onStep, stepAI,
|
|
179
|
+
const stepAI = "ai-sync";
|
|
180
|
+
report(onStep, stepAI, "Sync AI config via javi-ai", "running");
|
|
174
181
|
try {
|
|
175
182
|
if (aiSync) {
|
|
176
183
|
if (!dryRun) {
|
|
177
184
|
try {
|
|
178
|
-
const { stderr } = await execFileAsync(
|
|
185
|
+
const { stderr } = await execFileAsync("npx", ["javi-ai", "sync", "--project-dir", projectDir, "--target", "all"], {
|
|
179
186
|
cwd: projectDir,
|
|
180
187
|
timeout: 120_000,
|
|
181
188
|
});
|
|
182
189
|
// javi-ai may exit 0 but crash (e.g. Ink raw mode error) — detect via stderr
|
|
183
|
-
if (stderr &&
|
|
184
|
-
|
|
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");
|
|
185
194
|
}
|
|
186
195
|
else {
|
|
187
|
-
report(onStep, stepAI,
|
|
196
|
+
report(onStep, stepAI, "Sync AI config via javi-ai", "done", "javi-ai sync --target all");
|
|
188
197
|
}
|
|
189
198
|
}
|
|
190
199
|
catch (syncErr) {
|
|
191
200
|
const msg = syncErr instanceof Error ? syncErr.message : String(syncErr);
|
|
192
|
-
if (msg.includes(
|
|
193
|
-
|
|
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)");
|
|
194
205
|
}
|
|
195
206
|
else {
|
|
196
|
-
report(onStep, stepAI,
|
|
207
|
+
report(onStep, stepAI, "Sync AI config via javi-ai", "error", msg);
|
|
197
208
|
}
|
|
198
209
|
}
|
|
199
210
|
}
|
|
200
211
|
else {
|
|
201
|
-
report(onStep, stepAI,
|
|
212
|
+
report(onStep, stepAI, "Sync AI config via javi-ai", "done", "dry-run: would run javi-ai sync --target all");
|
|
202
213
|
}
|
|
203
214
|
}
|
|
204
215
|
else {
|
|
205
|
-
report(onStep, stepAI,
|
|
216
|
+
report(onStep, stepAI, "Sync AI config via javi-ai", "skipped", "not selected");
|
|
206
217
|
}
|
|
207
218
|
}
|
|
208
219
|
catch (e) {
|
|
209
|
-
report(onStep, stepAI,
|
|
220
|
+
report(onStep, stepAI, "Sync AI config via javi-ai", "error", String(e));
|
|
210
221
|
}
|
|
211
222
|
// ── Step 8: SDD (Spec-Driven Development) ─────────────────────────────────
|
|
212
|
-
const stepSDD =
|
|
213
|
-
report(onStep, stepSDD,
|
|
223
|
+
const stepSDD = "sdd";
|
|
224
|
+
report(onStep, stepSDD, "Set up SDD (openspec/)", "running");
|
|
214
225
|
try {
|
|
215
226
|
if (sdd) {
|
|
216
227
|
if (!dryRun) {
|
|
217
|
-
const openspecDir = path.join(projectDir,
|
|
228
|
+
const openspecDir = path.join(projectDir, "openspec");
|
|
218
229
|
await ensureDirExists(openspecDir);
|
|
219
230
|
// Create a README if none exists
|
|
220
|
-
const readmePath = path.join(openspecDir,
|
|
221
|
-
if (!await fs.pathExists(readmePath)) {
|
|
222
|
-
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");
|
|
223
234
|
}
|
|
224
235
|
}
|
|
225
|
-
report(onStep, stepSDD,
|
|
236
|
+
report(onStep, stepSDD, "Set up SDD (openspec/)", "done");
|
|
226
237
|
}
|
|
227
238
|
else {
|
|
228
|
-
report(onStep, stepSDD,
|
|
239
|
+
report(onStep, stepSDD, "Set up SDD (openspec/)", "skipped", "not selected");
|
|
229
240
|
}
|
|
230
241
|
}
|
|
231
242
|
catch (e) {
|
|
232
|
-
report(onStep, stepSDD,
|
|
243
|
+
report(onStep, stepSDD, "Set up SDD (openspec/)", "error", String(e));
|
|
233
244
|
}
|
|
234
245
|
// ── Step 9: GHAGGA ────────────────────────────────────────────────────────
|
|
235
|
-
const stepGhagga =
|
|
236
|
-
report(onStep, stepGhagga,
|
|
246
|
+
const stepGhagga = "ghagga";
|
|
247
|
+
report(onStep, stepGhagga, "Install GHAGGA review system", "running");
|
|
237
248
|
try {
|
|
238
249
|
if (ghagga) {
|
|
239
|
-
const ghaggaSrc = path.join(MODULES_DIR,
|
|
250
|
+
const ghaggaSrc = path.join(MODULES_DIR, "ghagga");
|
|
240
251
|
if (await fs.pathExists(ghaggaSrc)) {
|
|
241
252
|
if (!dryRun) {
|
|
242
|
-
const ghaggaDest = path.join(projectDir,
|
|
253
|
+
const ghaggaDest = path.join(projectDir, ".javi-forge", "modules", "ghagga");
|
|
243
254
|
await ensureDirExists(ghaggaDest);
|
|
244
|
-
await fs.copy(ghaggaSrc, ghaggaDest, {
|
|
255
|
+
await fs.copy(ghaggaSrc, ghaggaDest, {
|
|
256
|
+
overwrite: false,
|
|
257
|
+
errorOnExist: false,
|
|
258
|
+
});
|
|
245
259
|
// Copy ghagga caller workflow to CI provider location
|
|
246
|
-
if (ciProvider ===
|
|
247
|
-
const workflowSrc = path.join(FORGE_ROOT,
|
|
260
|
+
if (ciProvider === "github") {
|
|
261
|
+
const workflowSrc = path.join(FORGE_ROOT, "templates", "github", "ghagga-review.yml");
|
|
248
262
|
if (await fs.pathExists(workflowSrc)) {
|
|
249
|
-
const workflowDest = path.join(projectDir,
|
|
263
|
+
const workflowDest = path.join(projectDir, ".github", "workflows", "ghagga-review.yml");
|
|
250
264
|
await ensureDirExists(path.dirname(workflowDest));
|
|
251
265
|
await fs.copy(workflowSrc, workflowDest, { overwrite: false });
|
|
252
266
|
}
|
|
253
267
|
}
|
|
254
268
|
}
|
|
255
|
-
report(onStep, stepGhagga,
|
|
269
|
+
report(onStep, stepGhagga, "Install GHAGGA review system", "done");
|
|
256
270
|
}
|
|
257
271
|
else {
|
|
258
|
-
report(onStep, stepGhagga,
|
|
272
|
+
report(onStep, stepGhagga, "Install GHAGGA review system", "error", "module not found");
|
|
259
273
|
}
|
|
260
274
|
}
|
|
261
275
|
else {
|
|
262
|
-
report(onStep, stepGhagga,
|
|
276
|
+
report(onStep, stepGhagga, "Install GHAGGA review system", "skipped", "not selected");
|
|
263
277
|
}
|
|
264
278
|
}
|
|
265
279
|
catch (e) {
|
|
266
|
-
report(onStep, stepGhagga,
|
|
280
|
+
report(onStep, stepGhagga, "Install GHAGGA review system", "error", String(e));
|
|
267
281
|
}
|
|
268
282
|
// ── Step 10: Mock-first mode ───────────────────────────────────────────────
|
|
269
|
-
const stepMock =
|
|
283
|
+
const stepMock = "mock";
|
|
270
284
|
if (options.mock) {
|
|
271
|
-
report(onStep, stepMock,
|
|
285
|
+
report(onStep, stepMock, "Configure mock-first mode", "running");
|
|
272
286
|
try {
|
|
273
287
|
if (!dryRun) {
|
|
274
288
|
// Create .env.example with mock values
|
|
@@ -293,84 +307,307 @@ ENABLE_ANALYTICS=false
|
|
|
293
307
|
ENABLE_EMAILS=false
|
|
294
308
|
ENABLE_WEBHOOKS=false
|
|
295
309
|
`;
|
|
296
|
-
const envExamplePath = path.join(projectDir,
|
|
297
|
-
if (!await fs.pathExists(envExamplePath)) {
|
|
298
|
-
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");
|
|
299
313
|
}
|
|
300
314
|
// Create .env from example
|
|
301
|
-
const envPath = path.join(projectDir,
|
|
302
|
-
if (!await fs.pathExists(envPath)) {
|
|
303
|
-
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");
|
|
304
318
|
}
|
|
305
319
|
}
|
|
306
|
-
report(onStep, stepMock,
|
|
320
|
+
report(onStep, stepMock, "Configure mock-first mode", "done", ".env.example + .env with mock values");
|
|
307
321
|
}
|
|
308
322
|
catch (e) {
|
|
309
|
-
report(onStep, stepMock,
|
|
323
|
+
report(onStep, stepMock, "Configure mock-first mode", "error", String(e));
|
|
310
324
|
}
|
|
311
325
|
}
|
|
312
326
|
else {
|
|
313
|
-
report(onStep, stepMock,
|
|
327
|
+
report(onStep, stepMock, "Configure mock-first mode", "skipped", "not selected");
|
|
314
328
|
}
|
|
315
329
|
// ── Step 11: Generate .context/ directory ──────────────────────────────────
|
|
316
|
-
const stepContext =
|
|
317
|
-
report(onStep, stepContext,
|
|
330
|
+
const stepContext = "context-dir";
|
|
331
|
+
report(onStep, stepContext, "Generate .context/ directory", "running");
|
|
318
332
|
try {
|
|
319
333
|
if (contextDir) {
|
|
320
|
-
const contextDirPath = path.join(projectDir,
|
|
334
|
+
const contextDirPath = path.join(projectDir, ".context");
|
|
321
335
|
if (await fs.pathExists(contextDirPath)) {
|
|
322
|
-
report(onStep, stepContext,
|
|
336
|
+
report(onStep, stepContext, "Generate .context/ directory", "done", "already exists");
|
|
323
337
|
}
|
|
324
338
|
else {
|
|
325
339
|
if (!dryRun) {
|
|
326
340
|
const { index, summary } = await generateContextDir(options);
|
|
327
341
|
await ensureDirExists(contextDirPath);
|
|
328
|
-
await fs.writeFile(path.join(contextDirPath,
|
|
329
|
-
await fs.writeFile(path.join(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");
|
|
330
344
|
}
|
|
331
|
-
report(onStep, stepContext,
|
|
345
|
+
report(onStep, stepContext, "Generate .context/ directory", "done", dryRun
|
|
346
|
+
? "dry-run: would generate .context/"
|
|
347
|
+
: ".context/INDEX.md + summary.md");
|
|
332
348
|
}
|
|
333
349
|
}
|
|
334
350
|
else {
|
|
335
|
-
report(onStep, stepContext,
|
|
351
|
+
report(onStep, stepContext, "Generate .context/ directory", "skipped", "not selected");
|
|
336
352
|
}
|
|
337
353
|
}
|
|
338
354
|
catch (e) {
|
|
339
|
-
report(onStep, stepContext,
|
|
355
|
+
report(onStep, stepContext, "Generate .context/ directory", "error", String(e));
|
|
340
356
|
}
|
|
341
|
-
// ── Step 12: Generate CLAUDE.md
|
|
342
|
-
const stepClaudeMd =
|
|
343
|
-
report(onStep, stepClaudeMd,
|
|
357
|
+
// ── Step 12: Generate CLAUDE.md (smart: project-aware) ─────────────────────
|
|
358
|
+
const stepClaudeMd = "claude-md";
|
|
359
|
+
report(onStep, stepClaudeMd, "Generate CLAUDE.md", "running");
|
|
344
360
|
try {
|
|
345
361
|
if (claudeMd) {
|
|
346
|
-
const claudeMdPath = path.join(projectDir,
|
|
362
|
+
const claudeMdPath = path.join(projectDir, "CLAUDE.md");
|
|
347
363
|
if (await fs.pathExists(claudeMdPath)) {
|
|
348
|
-
report(onStep, stepClaudeMd,
|
|
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
|
+
}
|
|
349
415
|
}
|
|
350
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");
|
|
351
548
|
if (!dryRun) {
|
|
352
|
-
|
|
353
|
-
await fs.
|
|
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
|
+
}
|
|
354
559
|
}
|
|
355
|
-
report(onStep,
|
|
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");
|
|
356
593
|
}
|
|
357
594
|
}
|
|
358
595
|
else {
|
|
359
|
-
report(onStep,
|
|
596
|
+
report(onStep, stepSkills, "Generate Agent Skills manifest (skills.json)", "done", `dry-run: would generate ${AGENT_SKILLS_MANIFEST_FILE}`);
|
|
360
597
|
}
|
|
361
598
|
}
|
|
362
599
|
catch (e) {
|
|
363
|
-
report(onStep,
|
|
600
|
+
report(onStep, stepSkills, "Generate Agent Skills manifest (skills.json)", "error", String(e));
|
|
364
601
|
}
|
|
365
|
-
// ── Step
|
|
366
|
-
const stepManifest =
|
|
367
|
-
report(onStep, stepManifest,
|
|
602
|
+
// ── Step 18: Write manifest ───────────────────────────────────────────────
|
|
603
|
+
const stepManifest = "manifest";
|
|
604
|
+
report(onStep, stepManifest, "Write forge manifest", "running");
|
|
368
605
|
try {
|
|
369
606
|
if (!dryRun) {
|
|
370
|
-
const manifestDir = path.join(projectDir,
|
|
607
|
+
const manifestDir = path.join(projectDir, ".javi-forge");
|
|
371
608
|
await ensureDirExists(manifestDir);
|
|
372
609
|
const manifest = {
|
|
373
|
-
version:
|
|
610
|
+
version: "0.1.0",
|
|
374
611
|
projectName,
|
|
375
612
|
stack,
|
|
376
613
|
ciProvider,
|
|
@@ -378,20 +615,26 @@ ENABLE_WEBHOOKS=false
|
|
|
378
615
|
createdAt: new Date().toISOString(),
|
|
379
616
|
updatedAt: new Date().toISOString(),
|
|
380
617
|
modules: [
|
|
381
|
-
...(memory !==
|
|
382
|
-
...(ghagga ? [
|
|
383
|
-
...(sdd ? [
|
|
384
|
-
...(aiSync ? [
|
|
385
|
-
...(contextDir ? [
|
|
386
|
-
...(claudeMd ? [
|
|
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"] : []),
|
|
387
628
|
],
|
|
388
629
|
};
|
|
389
|
-
await fs.writeJson(path.join(manifestDir,
|
|
630
|
+
await fs.writeJson(path.join(manifestDir, "manifest.json"), manifest, {
|
|
631
|
+
spaces: 2,
|
|
632
|
+
});
|
|
390
633
|
}
|
|
391
|
-
report(onStep, stepManifest,
|
|
634
|
+
report(onStep, stepManifest, "Write forge manifest", "done", ".javi-forge/manifest.json");
|
|
392
635
|
}
|
|
393
636
|
catch (e) {
|
|
394
|
-
report(onStep, stepManifest,
|
|
637
|
+
report(onStep, stepManifest, "Write forge manifest", "error", String(e));
|
|
395
638
|
}
|
|
396
639
|
}
|
|
397
640
|
//# sourceMappingURL=init.js.map
|