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.
Files changed (231) hide show
  1. package/dist/commands/analyze.d.ts +1 -1
  2. package/dist/commands/analyze.js +15 -15
  3. package/dist/commands/atlassian-mcp.d.ts +42 -0
  4. package/dist/commands/atlassian-mcp.js +98 -0
  5. package/dist/commands/ci.d.ts +3 -3
  6. package/dist/commands/ci.js +185 -147
  7. package/dist/commands/crash-recovery.d.ts +34 -0
  8. package/dist/commands/crash-recovery.js +123 -0
  9. package/dist/commands/doctor.d.ts +2 -2
  10. package/dist/commands/doctor.js +113 -61
  11. package/dist/commands/harness-audit.d.ts +35 -0
  12. package/dist/commands/harness-audit.js +277 -0
  13. package/dist/commands/init.d.ts +1 -1
  14. package/dist/commands/init.js +384 -141
  15. package/dist/commands/llmstxt.d.ts +1 -1
  16. package/dist/commands/llmstxt.js +36 -34
  17. package/dist/commands/parallel-batch.d.ts +42 -0
  18. package/dist/commands/parallel-batch.js +90 -0
  19. package/dist/commands/plugin.d.ts +10 -1
  20. package/dist/commands/plugin.js +92 -47
  21. package/dist/commands/secret-scanner.d.ts +30 -0
  22. package/dist/commands/secret-scanner.js +272 -0
  23. package/dist/commands/security-analysis.d.ts +74 -0
  24. package/dist/commands/security-analysis.js +487 -0
  25. package/dist/commands/security.d.ts +11 -5
  26. package/dist/commands/security.js +216 -76
  27. package/dist/commands/skill-scanner.d.ts +63 -0
  28. package/dist/commands/skill-scanner.js +383 -0
  29. package/dist/commands/skills.d.ts +62 -5
  30. package/dist/commands/skills.js +439 -54
  31. package/dist/commands/supply-chain.d.ts +23 -0
  32. package/dist/commands/supply-chain.js +126 -0
  33. package/dist/commands/tdd-pipeline.d.ts +17 -0
  34. package/dist/commands/tdd-pipeline.js +144 -0
  35. package/dist/commands/tdd.d.ts +1 -1
  36. package/dist/commands/tdd.js +21 -18
  37. package/dist/commands/team-presets.d.ts +53 -0
  38. package/dist/commands/team-presets.js +201 -0
  39. package/dist/commands/workflow.d.ts +23 -0
  40. package/dist/commands/workflow.js +114 -0
  41. package/dist/constants.d.ts +15 -1
  42. package/dist/constants.js +161 -122
  43. package/dist/index.js +308 -98
  44. package/dist/lib/agent-skills.d.ts +36 -1
  45. package/dist/lib/agent-skills.js +168 -19
  46. package/dist/lib/auto-skill-install.d.ts +37 -0
  47. package/dist/lib/auto-skill-install.js +92 -0
  48. package/dist/lib/auto-wire.d.ts +20 -0
  49. package/dist/lib/auto-wire.js +240 -0
  50. package/dist/lib/claudemd.d.ts +13 -1
  51. package/dist/lib/claudemd.js +174 -24
  52. package/dist/lib/codex-export.d.ts +1 -1
  53. package/dist/lib/codex-export.js +29 -31
  54. package/dist/lib/common.d.ts +1 -1
  55. package/dist/lib/common.js +52 -44
  56. package/dist/lib/context.d.ts +17 -2
  57. package/dist/lib/context.js +142 -13
  58. package/dist/lib/docker.d.ts +1 -1
  59. package/dist/lib/docker.js +141 -112
  60. package/dist/lib/frontmatter.d.ts +1 -1
  61. package/dist/lib/frontmatter.js +29 -15
  62. package/dist/lib/plugin.d.ts +9 -3
  63. package/dist/lib/plugin.js +128 -69
  64. package/dist/lib/skill-publish.d.ts +40 -0
  65. package/dist/lib/skill-publish.js +146 -0
  66. package/dist/lib/stack-detector.d.ts +38 -0
  67. package/dist/lib/stack-detector.js +207 -0
  68. package/dist/lib/template.d.ts +16 -1
  69. package/dist/lib/template.js +46 -17
  70. package/dist/lib/workflow/discovery.d.ts +19 -0
  71. package/dist/lib/workflow/discovery.js +68 -0
  72. package/dist/lib/workflow/index.d.ts +5 -0
  73. package/dist/lib/workflow/index.js +5 -0
  74. package/dist/lib/workflow/parser.d.ts +16 -0
  75. package/dist/lib/workflow/parser.js +198 -0
  76. package/dist/lib/workflow/renderer.d.ts +9 -0
  77. package/dist/lib/workflow/renderer.js +152 -0
  78. package/dist/lib/workflow/validator.d.ts +10 -0
  79. package/dist/lib/workflow/validator.js +189 -0
  80. package/dist/tasks/index.d.ts +4 -0
  81. package/dist/tasks/index.js +4 -0
  82. package/dist/tasks/scaffold-tasks.d.ts +3 -0
  83. package/dist/tasks/scaffold-tasks.js +14 -0
  84. package/dist/tasks/task-id.d.ts +30 -0
  85. package/dist/tasks/task-id.js +55 -0
  86. package/dist/tasks/task-tracker.d.ts +15 -0
  87. package/dist/tasks/task-tracker.js +81 -0
  88. package/dist/types/index.d.ts +134 -6
  89. package/dist/types/index.js +11 -1
  90. package/dist/ui/AnalyzeUI.d.ts +1 -1
  91. package/dist/ui/AnalyzeUI.js +38 -39
  92. package/dist/ui/App.d.ts +5 -3
  93. package/dist/ui/App.js +86 -46
  94. package/dist/ui/AutoSkills.d.ts +9 -0
  95. package/dist/ui/AutoSkills.js +124 -0
  96. package/dist/ui/CI.d.ts +2 -2
  97. package/dist/ui/CI.js +24 -26
  98. package/dist/ui/CIContext.d.ts +1 -1
  99. package/dist/ui/CIContext.js +3 -2
  100. package/dist/ui/CISelector.d.ts +2 -2
  101. package/dist/ui/CISelector.js +23 -15
  102. package/dist/ui/Doctor.d.ts +1 -1
  103. package/dist/ui/Doctor.js +35 -29
  104. package/dist/ui/Header.d.ts +1 -1
  105. package/dist/ui/Header.js +14 -14
  106. package/dist/ui/HookProfileSelector.d.ts +9 -0
  107. package/dist/ui/HookProfileSelector.js +54 -0
  108. package/dist/ui/LlmsTxt.d.ts +1 -1
  109. package/dist/ui/LlmsTxt.js +31 -22
  110. package/dist/ui/MemorySelector.d.ts +2 -2
  111. package/dist/ui/MemorySelector.js +28 -16
  112. package/dist/ui/NameInput.d.ts +1 -1
  113. package/dist/ui/NameInput.js +21 -21
  114. package/dist/ui/OptionSelector.d.ts +6 -2
  115. package/dist/ui/OptionSelector.js +83 -32
  116. package/dist/ui/Plugin.d.ts +4 -3
  117. package/dist/ui/Plugin.js +78 -35
  118. package/dist/ui/Progress.d.ts +3 -3
  119. package/dist/ui/Progress.js +23 -22
  120. package/dist/ui/Skills.d.ts +2 -2
  121. package/dist/ui/Skills.js +61 -32
  122. package/dist/ui/StackSelector.d.ts +2 -2
  123. package/dist/ui/StackSelector.js +26 -16
  124. package/dist/ui/Summary.d.ts +3 -3
  125. package/dist/ui/Summary.js +60 -50
  126. package/dist/ui/Welcome.d.ts +1 -1
  127. package/dist/ui/Welcome.js +15 -16
  128. package/dist/ui/theme.d.ts +1 -1
  129. package/dist/ui/theme.js +6 -6
  130. package/package.json +9 -6
  131. package/templates/common/atlassian/mcp-atlassian-snippet.json +16 -0
  132. package/templates/common/repoforge/mcp-repoforge-snippet.json +11 -0
  133. package/templates/common/repoforge/repoforge.yaml +34 -0
  134. package/templates/github/deploy-docker-zero-downtime.yml +140 -0
  135. package/templates/github/repoforge-graph.yml +45 -0
  136. package/templates/gitlab/deploy-docker-zero-downtime.yml +57 -0
  137. package/templates/local-ai/.env.example +17 -0
  138. package/templates/local-ai/docker-compose.yml +95 -0
  139. package/templates/security-hooks/claude-settings-security.json +30 -0
  140. package/templates/security-hooks/commit-msg-signing +29 -0
  141. package/templates/security-hooks/pre-commit-permissions +74 -0
  142. package/templates/security-hooks/pre-commit-secrets +74 -0
  143. package/templates/security-hooks/pre-push-branch-protection +62 -0
  144. package/templates/security-hooks/pre-push-deps +83 -0
  145. package/templates/security-hooks/pre-push-signing +67 -0
  146. package/templates/woodpecker/deploy-docker-zero-downtime.yml +50 -0
  147. package/templates/workflows/ci-pipeline.dot +15 -0
  148. package/templates/workflows/feature-flow.dot +21 -0
  149. package/templates/workflows/release.dot +16 -0
  150. package/dist/__integration__/helpers.d.ts +0 -20
  151. package/dist/__integration__/helpers.d.ts.map +0 -1
  152. package/dist/__integration__/helpers.js +0 -31
  153. package/dist/__integration__/helpers.js.map +0 -1
  154. package/dist/commands/analyze.d.ts.map +0 -1
  155. package/dist/commands/analyze.js.map +0 -1
  156. package/dist/commands/ci.d.ts.map +0 -1
  157. package/dist/commands/ci.js.map +0 -1
  158. package/dist/commands/doctor.d.ts.map +0 -1
  159. package/dist/commands/doctor.js.map +0 -1
  160. package/dist/commands/init.d.ts.map +0 -1
  161. package/dist/commands/init.js.map +0 -1
  162. package/dist/commands/llmstxt.d.ts.map +0 -1
  163. package/dist/commands/llmstxt.js.map +0 -1
  164. package/dist/commands/plugin.d.ts.map +0 -1
  165. package/dist/commands/plugin.js.map +0 -1
  166. package/dist/commands/security.d.ts.map +0 -1
  167. package/dist/commands/security.js.map +0 -1
  168. package/dist/commands/skills.d.ts.map +0 -1
  169. package/dist/commands/skills.js.map +0 -1
  170. package/dist/commands/tdd.d.ts.map +0 -1
  171. package/dist/commands/tdd.js.map +0 -1
  172. package/dist/constants.d.ts.map +0 -1
  173. package/dist/constants.js.map +0 -1
  174. package/dist/index.d.ts.map +0 -1
  175. package/dist/index.js.map +0 -1
  176. package/dist/lib/agent-skills.d.ts.map +0 -1
  177. package/dist/lib/agent-skills.js.map +0 -1
  178. package/dist/lib/claudemd.d.ts.map +0 -1
  179. package/dist/lib/claudemd.js.map +0 -1
  180. package/dist/lib/codex-export.d.ts.map +0 -1
  181. package/dist/lib/codex-export.js.map +0 -1
  182. package/dist/lib/common.d.ts.map +0 -1
  183. package/dist/lib/common.js.map +0 -1
  184. package/dist/lib/context.d.ts.map +0 -1
  185. package/dist/lib/context.js.map +0 -1
  186. package/dist/lib/docker.d.ts.map +0 -1
  187. package/dist/lib/docker.js.map +0 -1
  188. package/dist/lib/frontmatter.d.ts.map +0 -1
  189. package/dist/lib/frontmatter.js.map +0 -1
  190. package/dist/lib/plugin.d.ts.map +0 -1
  191. package/dist/lib/plugin.js.map +0 -1
  192. package/dist/lib/template.d.ts.map +0 -1
  193. package/dist/lib/template.js.map +0 -1
  194. package/dist/types/index.d.ts.map +0 -1
  195. package/dist/types/index.js.map +0 -1
  196. package/dist/ui/AnalyzeUI.d.ts.map +0 -1
  197. package/dist/ui/AnalyzeUI.js.map +0 -1
  198. package/dist/ui/App.d.ts.map +0 -1
  199. package/dist/ui/App.js.map +0 -1
  200. package/dist/ui/CI.d.ts.map +0 -1
  201. package/dist/ui/CI.js.map +0 -1
  202. package/dist/ui/CIContext.d.ts.map +0 -1
  203. package/dist/ui/CIContext.js.map +0 -1
  204. package/dist/ui/CISelector.d.ts.map +0 -1
  205. package/dist/ui/CISelector.js.map +0 -1
  206. package/dist/ui/Doctor.d.ts.map +0 -1
  207. package/dist/ui/Doctor.js.map +0 -1
  208. package/dist/ui/Header.d.ts.map +0 -1
  209. package/dist/ui/Header.js.map +0 -1
  210. package/dist/ui/LlmsTxt.d.ts.map +0 -1
  211. package/dist/ui/LlmsTxt.js.map +0 -1
  212. package/dist/ui/MemorySelector.d.ts.map +0 -1
  213. package/dist/ui/MemorySelector.js.map +0 -1
  214. package/dist/ui/NameInput.d.ts.map +0 -1
  215. package/dist/ui/NameInput.js.map +0 -1
  216. package/dist/ui/OptionSelector.d.ts.map +0 -1
  217. package/dist/ui/OptionSelector.js.map +0 -1
  218. package/dist/ui/Plugin.d.ts.map +0 -1
  219. package/dist/ui/Plugin.js.map +0 -1
  220. package/dist/ui/Progress.d.ts.map +0 -1
  221. package/dist/ui/Progress.js.map +0 -1
  222. package/dist/ui/Skills.d.ts.map +0 -1
  223. package/dist/ui/Skills.js.map +0 -1
  224. package/dist/ui/StackSelector.d.ts.map +0 -1
  225. package/dist/ui/StackSelector.js.map +0 -1
  226. package/dist/ui/Summary.d.ts.map +0 -1
  227. package/dist/ui/Summary.js.map +0 -1
  228. package/dist/ui/Welcome.d.ts.map +0 -1
  229. package/dist/ui/Welcome.js.map +0 -1
  230. package/dist/ui/theme.d.ts.map +0 -1
  231. package/dist/ui/theme.js.map +0 -1
@@ -1,12 +1,13 @@
1
- import fs from 'fs-extra';
2
- import path from 'path';
3
- import { execFile } from 'child_process';
4
- import { promisify } from 'util';
5
- import { backupIfExists, ensureDirExists } from '../lib/common.js';
6
- import { generateDependabotYml, generateCIWorkflow, getCIDestination } from '../lib/template.js';
7
- import { generateContextDir } from '../lib/context.js';
8
- import { generateClaudeMd } from '../lib/claudemd.js';
9
- import { FORGE_ROOT, MODULES_DIR, CI_LOCAL_DIR, } from '../constants.js';
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 = 'git-init';
26
- report(onStep, stepGit, 'Initialize git repository', 'running');
26
+ const stepGit = "git-init";
27
+ report(onStep, stepGit, "Initialize git repository", "running");
27
28
  try {
28
- const gitDir = path.join(projectDir, '.git');
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('git', ['init'], { cwd: projectDir });
32
+ await execFileAsync("git", ["init"], { cwd: projectDir });
32
33
  }
33
- report(onStep, stepGit, 'Initialize git repository', 'done', 'initialized');
34
+ report(onStep, stepGit, "Initialize git repository", "done", "initialized");
34
35
  }
35
36
  else {
36
- report(onStep, stepGit, 'Initialize git repository', 'done', 'already exists');
37
+ report(onStep, stepGit, "Initialize git repository", "done", "already exists");
37
38
  }
38
39
  }
39
40
  catch (e) {
40
- report(onStep, stepGit, 'Initialize git repository', 'error', String(e));
41
+ report(onStep, stepGit, "Initialize git repository", "error", String(e));
41
42
  }
42
43
  // ── Step 2: Configure git hooks path ──────────────────────────────────────
43
- const stepHooks = 'git-hooks';
44
- report(onStep, stepHooks, 'Configure git hooks path', 'running');
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, 'ci-local');
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, { overwrite: false, errorOnExist: false });
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, 'hooks');
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('git', ['config', 'core.hooksPath', 'ci-local/hooks'], { cwd: projectDir });
63
+ await execFileAsync("git", ["config", "core.hooksPath", "ci-local/hooks"], { cwd: projectDir });
60
64
  }
61
65
  }
62
- report(onStep, stepHooks, 'Configure git hooks path', 'done', 'ci-local/hooks');
66
+ report(onStep, stepHooks, "Configure git hooks path", "done", "ci-local/hooks");
63
67
  }
64
68
  else {
65
- report(onStep, stepHooks, 'Configure git hooks path', 'skipped', 'no ci-local dir');
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, 'Configure git hooks path', 'error', String(e));
73
+ report(onStep, stepHooks, "Configure git hooks path", "error", String(e));
70
74
  }
71
75
  // ── Step 3: Copy CI template ──────────────────────────────────────────────
72
- const stepCI = 'ci-template';
73
- report(onStep, stepCI, `Copy ${ciProvider} CI workflow`, 'running');
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, 'utf-8');
85
+ await fs.writeFile(dest, ciContent, "utf-8");
82
86
  }
83
- report(onStep, stepCI, `Copy ${ciProvider} CI workflow`, 'done', getCIDestination(ciProvider));
87
+ report(onStep, stepCI, `Copy ${ciProvider} CI workflow`, "done", getCIDestination(ciProvider));
84
88
  }
85
89
  else {
86
- report(onStep, stepCI, `Copy ${ciProvider} CI workflow`, 'skipped', `no template for ${stack}`);
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`, 'error', String(e));
94
+ report(onStep, stepCI, `Copy ${ciProvider} CI workflow`, "error", String(e));
91
95
  }
92
96
  // ── Step 4: Generate .gitignore ───────────────────────────────────────────
93
- const stepGitignore = 'gitignore';
94
- report(onStep, stepGitignore, 'Generate .gitignore', 'running');
97
+ const stepGitignore = "gitignore";
98
+ report(onStep, stepGitignore, "Generate .gitignore", "running");
95
99
  try {
96
- const templatePath = path.join(FORGE_ROOT, '.gitignore.template');
97
- const dest = path.join(projectDir, '.gitignore');
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, 'Generate .gitignore', 'done', 'from template');
106
+ report(onStep, stepGitignore, "Generate .gitignore", "done", "from template");
103
107
  }
104
108
  else if (await fs.pathExists(dest)) {
105
- report(onStep, stepGitignore, 'Generate .gitignore', 'done', 'already exists');
109
+ report(onStep, stepGitignore, "Generate .gitignore", "done", "already exists");
106
110
  }
107
111
  else {
108
- report(onStep, stepGitignore, 'Generate .gitignore', 'skipped', 'no template');
112
+ report(onStep, stepGitignore, "Generate .gitignore", "skipped", "no template");
109
113
  }
110
114
  }
111
115
  catch (e) {
112
- report(onStep, stepGitignore, 'Generate .gitignore', 'error', String(e));
116
+ report(onStep, stepGitignore, "Generate .gitignore", "error", String(e));
113
117
  }
114
118
  // ── Step 5: Generate dependabot.yml ───────────────────────────────────────
115
- const stepDeps = 'dependabot';
116
- report(onStep, stepDeps, 'Generate dependabot.yml', 'running');
119
+ const stepDeps = "dependabot";
120
+ report(onStep, stepDeps, "Generate dependabot.yml", "running");
117
121
  try {
118
- if (ciProvider === 'github') {
122
+ if (ciProvider === "github") {
119
123
  const content = await generateDependabotYml([stack], true);
120
- const dest = path.join(projectDir, '.github', 'dependabot.yml');
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, 'utf-8');
128
+ await fs.writeFile(dest, content, "utf-8");
125
129
  }
126
- report(onStep, stepDeps, 'Generate dependabot.yml', 'done');
130
+ report(onStep, stepDeps, "Generate dependabot.yml", "done");
127
131
  }
128
132
  else {
129
- report(onStep, stepDeps, 'Generate dependabot.yml', 'skipped', `not needed for ${ciProvider}`);
133
+ report(onStep, stepDeps, "Generate dependabot.yml", "skipped", `not needed for ${ciProvider}`);
130
134
  }
131
135
  }
132
136
  catch (e) {
133
- report(onStep, stepDeps, 'Generate dependabot.yml', 'error', String(e));
137
+ report(onStep, stepDeps, "Generate dependabot.yml", "error", String(e));
134
138
  }
135
139
  // ── Step 6: Install memory module ─────────────────────────────────────────
136
- const stepMem = 'memory';
137
- report(onStep, stepMem, `Install memory module: ${memory}`, 'running');
140
+ const stepMem = "memory";
141
+ report(onStep, stepMem, `Install memory module: ${memory}`, "running");
138
142
  try {
139
- if (memory !== 'none') {
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, '.javi-forge', 'modules', memory);
148
+ const moduleDest = path.join(projectDir, ".javi-forge", "modules", memory);
145
149
  await ensureDirExists(moduleDest);
146
- await fs.copy(moduleSrc, moduleDest, { overwrite: false, errorOnExist: false });
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 === 'engram') {
149
- const snippetSrc = path.join(moduleSrc, '.mcp-config-snippet.json');
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, '.mcp-config-snippet.json');
152
- let content = await fs.readFile(snippetSrc, 'utf-8');
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, 'utf-8');
161
+ await fs.writeFile(snippetDest, content, "utf-8");
155
162
  }
156
163
  }
157
164
  }
158
- report(onStep, stepMem, `Install memory module: ${memory}`, 'done');
165
+ report(onStep, stepMem, `Install memory module: ${memory}`, "done");
159
166
  }
160
167
  else {
161
- report(onStep, stepMem, `Install memory module: ${memory}`, 'error', 'module not found');
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}`, 'skipped', 'none selected');
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}`, 'error', String(e));
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 = 'ai-sync';
173
- report(onStep, stepAI, 'Sync AI config via javi-ai', 'running');
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('npx', ['javi-ai', 'sync', '--project-dir', projectDir, '--target', 'all'], {
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 && (stderr.includes('Raw mode is not supported') || stderr.includes('ERROR'))) {
184
- report(onStep, stepAI, 'Sync AI config via javi-ai', 'error', 'javi-ai crashed. Run manually: npx javi-ai sync --project-dir . --target all');
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, 'Sync AI config via javi-ai', 'done', 'javi-ai sync --target all');
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('ENOENT') || msg.includes('not found') || msg.includes('ERR_MODULE_NOT_FOUND')) {
193
- 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)');
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, 'Sync AI config via javi-ai', 'error', msg);
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, 'Sync AI config via javi-ai', 'done', 'dry-run: would run javi-ai sync --target all');
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, 'Sync AI config via javi-ai', 'skipped', 'not selected');
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, 'Sync AI config via javi-ai', 'error', String(e));
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 = 'sdd';
213
- report(onStep, stepSDD, 'Set up SDD (openspec/)', 'running');
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, 'openspec');
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, 'README.md');
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`, 'utf-8');
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, 'Set up SDD (openspec/)', 'done');
236
+ report(onStep, stepSDD, "Set up SDD (openspec/)", "done");
226
237
  }
227
238
  else {
228
- report(onStep, stepSDD, 'Set up SDD (openspec/)', 'skipped', 'not selected');
239
+ report(onStep, stepSDD, "Set up SDD (openspec/)", "skipped", "not selected");
229
240
  }
230
241
  }
231
242
  catch (e) {
232
- report(onStep, stepSDD, 'Set up SDD (openspec/)', 'error', String(e));
243
+ report(onStep, stepSDD, "Set up SDD (openspec/)", "error", String(e));
233
244
  }
234
245
  // ── Step 9: GHAGGA ────────────────────────────────────────────────────────
235
- const stepGhagga = 'ghagga';
236
- report(onStep, stepGhagga, 'Install GHAGGA review system', 'running');
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, 'ghagga');
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, '.javi-forge', 'modules', 'ghagga');
253
+ const ghaggaDest = path.join(projectDir, ".javi-forge", "modules", "ghagga");
243
254
  await ensureDirExists(ghaggaDest);
244
- await fs.copy(ghaggaSrc, ghaggaDest, { overwrite: false, errorOnExist: false });
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 === 'github') {
247
- const workflowSrc = path.join(FORGE_ROOT, 'templates', 'github', 'ghagga-review.yml');
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, '.github', 'workflows', 'ghagga-review.yml');
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, 'Install GHAGGA review system', 'done');
269
+ report(onStep, stepGhagga, "Install GHAGGA review system", "done");
256
270
  }
257
271
  else {
258
- report(onStep, stepGhagga, 'Install GHAGGA review system', 'error', 'module not found');
272
+ report(onStep, stepGhagga, "Install GHAGGA review system", "error", "module not found");
259
273
  }
260
274
  }
261
275
  else {
262
- report(onStep, stepGhagga, 'Install GHAGGA review system', 'skipped', 'not selected');
276
+ report(onStep, stepGhagga, "Install GHAGGA review system", "skipped", "not selected");
263
277
  }
264
278
  }
265
279
  catch (e) {
266
- report(onStep, stepGhagga, 'Install GHAGGA review system', 'error', String(e));
280
+ report(onStep, stepGhagga, "Install GHAGGA review system", "error", String(e));
267
281
  }
268
282
  // ── Step 10: Mock-first mode ───────────────────────────────────────────────
269
- const stepMock = 'mock';
283
+ const stepMock = "mock";
270
284
  if (options.mock) {
271
- report(onStep, stepMock, 'Configure mock-first mode', 'running');
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, '.env.example');
297
- if (!await fs.pathExists(envExamplePath)) {
298
- await fs.writeFile(envExamplePath, envExample, 'utf-8');
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, '.env');
302
- if (!await fs.pathExists(envPath)) {
303
- await fs.writeFile(envPath, envExample, 'utf-8');
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, 'Configure mock-first mode', 'done', '.env.example + .env with mock values');
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, 'Configure mock-first mode', 'error', String(e));
323
+ report(onStep, stepMock, "Configure mock-first mode", "error", String(e));
310
324
  }
311
325
  }
312
326
  else {
313
- report(onStep, stepMock, 'Configure mock-first mode', 'skipped', 'not selected');
327
+ report(onStep, stepMock, "Configure mock-first mode", "skipped", "not selected");
314
328
  }
315
329
  // ── Step 11: Generate .context/ directory ──────────────────────────────────
316
- const stepContext = 'context-dir';
317
- report(onStep, stepContext, 'Generate .context/ directory', 'running');
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, '.context');
334
+ const contextDirPath = path.join(projectDir, ".context");
321
335
  if (await fs.pathExists(contextDirPath)) {
322
- report(onStep, stepContext, 'Generate .context/ directory', 'done', 'already exists');
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, 'INDEX.md'), index, 'utf-8');
329
- await fs.writeFile(path.join(contextDirPath, 'summary.md'), summary, 'utf-8');
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, 'Generate .context/ directory', 'done', dryRun ? 'dry-run: would generate .context/' : '.context/INDEX.md + summary.md');
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, 'Generate .context/ directory', 'skipped', 'not selected');
351
+ report(onStep, stepContext, "Generate .context/ directory", "skipped", "not selected");
336
352
  }
337
353
  }
338
354
  catch (e) {
339
- report(onStep, stepContext, 'Generate .context/ directory', 'error', String(e));
355
+ report(onStep, stepContext, "Generate .context/ directory", "error", String(e));
340
356
  }
341
- // ── Step 12: Generate CLAUDE.md ────────────────────────────────────────────
342
- const stepClaudeMd = 'claude-md';
343
- report(onStep, stepClaudeMd, 'Generate CLAUDE.md', 'running');
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, 'CLAUDE.md');
362
+ const claudeMdPath = path.join(projectDir, "CLAUDE.md");
347
363
  if (await fs.pathExists(claudeMdPath)) {
348
- report(onStep, stepClaudeMd, 'Generate CLAUDE.md', 'done', 'already exists');
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
- const content = generateClaudeMd(options);
353
- await fs.writeFile(claudeMdPath, content, 'utf-8');
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, stepClaudeMd, 'Generate CLAUDE.md', 'done', dryRun ? 'dry-run: would generate CLAUDE.md' : 'CLAUDE.md');
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, stepClaudeMd, 'Generate CLAUDE.md', 'skipped', 'not selected');
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, stepClaudeMd, 'Generate CLAUDE.md', 'error', String(e));
600
+ report(onStep, stepSkills, "Generate Agent Skills manifest (skills.json)", "error", String(e));
364
601
  }
365
- // ── Step 13: Write manifest ───────────────────────────────────────────────
366
- const stepManifest = 'manifest';
367
- report(onStep, stepManifest, 'Write forge manifest', 'running');
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, '.javi-forge');
607
+ const manifestDir = path.join(projectDir, ".javi-forge");
371
608
  await ensureDirExists(manifestDir);
372
609
  const manifest = {
373
- version: '0.1.0',
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 !== 'none' ? [memory] : []),
382
- ...(ghagga ? ['ghagga'] : []),
383
- ...(sdd ? ['sdd'] : []),
384
- ...(aiSync ? ['ai-config'] : []),
385
- ...(contextDir ? ['context'] : []),
386
- ...(claudeMd ? ['claude-md'] : []),
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, 'manifest.json'), manifest, { spaces: 2 });
630
+ await fs.writeJson(path.join(manifestDir, "manifest.json"), manifest, {
631
+ spaces: 2,
632
+ });
390
633
  }
391
- report(onStep, stepManifest, 'Write forge manifest', 'done', '.javi-forge/manifest.json');
634
+ report(onStep, stepManifest, "Write forge manifest", "done", ".javi-forge/manifest.json");
392
635
  }
393
636
  catch (e) {
394
- report(onStep, stepManifest, 'Write forge manifest', 'error', String(e));
637
+ report(onStep, stepManifest, "Write forge manifest", "error", String(e));
395
638
  }
396
639
  }
397
640
  //# sourceMappingURL=init.js.map