javi-forge 1.5.0 → 1.6.1

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