javi-forge 0.1.0 → 1.0.0

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 (159) hide show
  1. package/.releaserc +2 -1
  2. package/README.md +143 -31
  3. package/ai-config/commands/workflows/diagnose.md +70 -0
  4. package/ai-config/commands/workflows/discover.md +86 -0
  5. package/dist/commands/doctor.js +24 -1
  6. package/dist/commands/init.js +48 -1
  7. package/dist/commands/llmstxt.d.ts +9 -0
  8. package/dist/commands/llmstxt.js +93 -0
  9. package/dist/commands/llmstxt.test.d.ts +2 -0
  10. package/dist/commands/plugin.d.ts +24 -0
  11. package/dist/commands/plugin.js +78 -0
  12. package/dist/commands/plugin.test.d.ts +2 -0
  13. package/dist/constants.d.ts +8 -0
  14. package/dist/constants.js +8 -0
  15. package/dist/index.js +33 -4
  16. package/dist/lib/plugin.d.ts +39 -0
  17. package/dist/lib/plugin.js +228 -0
  18. package/dist/lib/plugin.test.d.ts +2 -0
  19. package/dist/types/index.d.ts +42 -0
  20. package/dist/ui/App.d.ts +2 -1
  21. package/dist/ui/App.js +2 -1
  22. package/dist/ui/LlmsTxt.d.ts +8 -0
  23. package/dist/ui/LlmsTxt.js +48 -0
  24. package/dist/ui/Plugin.d.ts +9 -0
  25. package/dist/ui/Plugin.js +96 -0
  26. package/modules/obsidian-brain/README.md +32 -0
  27. package/modules/obsidian-brain/core/templates/braindump.md +15 -0
  28. package/modules/obsidian-brain/core/templates/consolidation.md +42 -0
  29. package/modules/obsidian-brain/core/templates/daily-note.md +18 -0
  30. package/modules/obsidian-brain/core/templates/resource-capture.md +14 -0
  31. package/modules/obsidian-brain/developer/templates/adr.md +40 -0
  32. package/modules/obsidian-brain/developer/templates/coding-session.md +24 -0
  33. package/modules/obsidian-brain/developer/templates/debug-journal.md +22 -0
  34. package/modules/obsidian-brain/developer/templates/sdd-feedback.md +27 -0
  35. package/modules/obsidian-brain/developer/templates/tech-debt.md +20 -0
  36. package/modules/obsidian-brain/pm-lead/templates/daily-brief.md +25 -0
  37. package/modules/obsidian-brain/pm-lead/templates/meeting-notes.md +24 -0
  38. package/modules/obsidian-brain/pm-lead/templates/risk-registry.md +12 -0
  39. package/modules/obsidian-brain/pm-lead/templates/sprint-review.md +27 -0
  40. package/modules/obsidian-brain/pm-lead/templates/stakeholder-update.md +24 -0
  41. package/modules/obsidian-brain/pm-lead/templates/team-intelligence.md +19 -0
  42. package/modules/obsidian-brain/pm-lead/templates/weekly-brief.md +29 -0
  43. package/package.json +1 -1
  44. package/schemas/plugin.schema.json +62 -0
  45. package/ai-config/skills/docs/api-documentation/SKILL.md +0 -293
  46. package/ai-config/skills/docs/docs-spring/SKILL.md +0 -377
  47. package/ai-config/skills/docs/mustache-templates/SKILL.md +0 -190
  48. package/ai-config/skills/docs/technical-docs/SKILL.md +0 -447
  49. package/dist/commands/analyze.d.ts.map +0 -1
  50. package/dist/commands/analyze.js.map +0 -1
  51. package/dist/commands/analyze.test.d.ts.map +0 -1
  52. package/dist/commands/analyze.test.js +0 -145
  53. package/dist/commands/analyze.test.js.map +0 -1
  54. package/dist/commands/doctor.d.ts.map +0 -1
  55. package/dist/commands/doctor.js.map +0 -1
  56. package/dist/commands/doctor.test.d.ts.map +0 -1
  57. package/dist/commands/doctor.test.js +0 -200
  58. package/dist/commands/doctor.test.js.map +0 -1
  59. package/dist/commands/init.d.ts.map +0 -1
  60. package/dist/commands/init.js.map +0 -1
  61. package/dist/commands/init.test.d.ts.map +0 -1
  62. package/dist/commands/init.test.js +0 -271
  63. package/dist/commands/init.test.js.map +0 -1
  64. package/dist/commands/sync.d.ts.map +0 -1
  65. package/dist/commands/sync.js.map +0 -1
  66. package/dist/constants.d.ts.map +0 -1
  67. package/dist/constants.js.map +0 -1
  68. package/dist/e2e/aggressive.e2e.test.d.ts.map +0 -1
  69. package/dist/e2e/aggressive.e2e.test.js +0 -350
  70. package/dist/e2e/aggressive.e2e.test.js.map +0 -1
  71. package/dist/e2e/commands.e2e.test.d.ts.map +0 -1
  72. package/dist/e2e/commands.e2e.test.js +0 -213
  73. package/dist/e2e/commands.e2e.test.js.map +0 -1
  74. package/dist/index.d.ts.map +0 -1
  75. package/dist/index.js.map +0 -1
  76. package/dist/lib/common.d.ts.map +0 -1
  77. package/dist/lib/common.js.map +0 -1
  78. package/dist/lib/common.test.d.ts.map +0 -1
  79. package/dist/lib/common.test.js +0 -316
  80. package/dist/lib/common.test.js.map +0 -1
  81. package/dist/lib/frontmatter.d.ts.map +0 -1
  82. package/dist/lib/frontmatter.js.map +0 -1
  83. package/dist/lib/frontmatter.test.d.ts.map +0 -1
  84. package/dist/lib/frontmatter.test.js +0 -257
  85. package/dist/lib/frontmatter.test.js.map +0 -1
  86. package/dist/lib/template.d.ts.map +0 -1
  87. package/dist/lib/template.js.map +0 -1
  88. package/dist/lib/template.test.d.ts.map +0 -1
  89. package/dist/lib/template.test.js +0 -201
  90. package/dist/lib/template.test.js.map +0 -1
  91. package/dist/types/index.d.ts.map +0 -1
  92. package/dist/types/index.js.map +0 -1
  93. package/dist/ui/AnalyzeUI.d.ts.map +0 -1
  94. package/dist/ui/AnalyzeUI.js.map +0 -1
  95. package/dist/ui/App.d.ts.map +0 -1
  96. package/dist/ui/App.js.map +0 -1
  97. package/dist/ui/CIContext.d.ts.map +0 -1
  98. package/dist/ui/CIContext.js.map +0 -1
  99. package/dist/ui/CISelector.d.ts.map +0 -1
  100. package/dist/ui/CISelector.js.map +0 -1
  101. package/dist/ui/Doctor.d.ts.map +0 -1
  102. package/dist/ui/Doctor.js.map +0 -1
  103. package/dist/ui/Header.d.ts.map +0 -1
  104. package/dist/ui/Header.js.map +0 -1
  105. package/dist/ui/MemorySelector.d.ts.map +0 -1
  106. package/dist/ui/MemorySelector.js.map +0 -1
  107. package/dist/ui/NameInput.d.ts.map +0 -1
  108. package/dist/ui/NameInput.js.map +0 -1
  109. package/dist/ui/OptionSelector.d.ts.map +0 -1
  110. package/dist/ui/OptionSelector.js.map +0 -1
  111. package/dist/ui/Progress.d.ts.map +0 -1
  112. package/dist/ui/Progress.js.map +0 -1
  113. package/dist/ui/StackSelector.d.ts.map +0 -1
  114. package/dist/ui/StackSelector.js.map +0 -1
  115. package/dist/ui/Summary.d.ts.map +0 -1
  116. package/dist/ui/Summary.js.map +0 -1
  117. package/dist/ui/SyncUI.d.ts.map +0 -1
  118. package/dist/ui/SyncUI.js.map +0 -1
  119. package/dist/ui/Welcome.d.ts.map +0 -1
  120. package/dist/ui/Welcome.js.map +0 -1
  121. package/dist/ui/theme.d.ts.map +0 -1
  122. package/dist/ui/theme.js.map +0 -1
  123. package/modules/obsidian-brain/.obsidian/plugins/dataview/data.json +0 -25
  124. package/modules/obsidian-brain/.obsidian/plugins/obsidian-kanban/data.json +0 -29
  125. package/modules/obsidian-brain/.obsidian/plugins/templater-obsidian/data.json +0 -18
  126. package/src/commands/analyze.test.ts +0 -145
  127. package/src/commands/analyze.ts +0 -69
  128. package/src/commands/doctor.test.ts +0 -208
  129. package/src/commands/doctor.ts +0 -163
  130. package/src/commands/init.test.ts +0 -298
  131. package/src/commands/init.ts +0 -285
  132. package/src/constants.ts +0 -69
  133. package/src/e2e/aggressive.e2e.test.ts +0 -557
  134. package/src/e2e/commands.e2e.test.ts +0 -298
  135. package/src/index.tsx +0 -106
  136. package/src/lib/common.test.ts +0 -318
  137. package/src/lib/common.ts +0 -127
  138. package/src/lib/frontmatter.test.ts +0 -291
  139. package/src/lib/frontmatter.ts +0 -77
  140. package/src/lib/template.test.ts +0 -226
  141. package/src/lib/template.ts +0 -99
  142. package/src/types/index.ts +0 -53
  143. package/src/ui/AnalyzeUI.tsx +0 -133
  144. package/src/ui/App.tsx +0 -175
  145. package/src/ui/CIContext.tsx +0 -25
  146. package/src/ui/CISelector.tsx +0 -72
  147. package/src/ui/Doctor.tsx +0 -122
  148. package/src/ui/Header.tsx +0 -48
  149. package/src/ui/MemorySelector.tsx +0 -73
  150. package/src/ui/NameInput.tsx +0 -82
  151. package/src/ui/OptionSelector.tsx +0 -100
  152. package/src/ui/Progress.tsx +0 -88
  153. package/src/ui/StackSelector.tsx +0 -101
  154. package/src/ui/Summary.tsx +0 -134
  155. package/src/ui/Welcome.tsx +0 -54
  156. package/src/ui/theme.ts +0 -10
  157. package/stryker.config.json +0 -19
  158. package/tsconfig.json +0 -19
  159. package/vitest.config.ts +0 -16
@@ -1,298 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest'
2
- import type { InitOptions, InitStep } from '../types/index.js'
3
-
4
- // ── Mock fs-extra ────────────────────────────────────────────────────────────
5
- vi.mock('fs-extra', () => {
6
- const mockFs = {
7
- pathExists: vi.fn(),
8
- readFile: vi.fn(),
9
- readJson: vi.fn(),
10
- writeFile: vi.fn(),
11
- writeJson: vi.fn(),
12
- copy: vi.fn(),
13
- ensureDir: vi.fn(),
14
- }
15
- return { default: mockFs, ...mockFs }
16
- })
17
-
18
- // ── Mock child_process ───────────────────────────────────────────────────────
19
- vi.mock('child_process', () => ({
20
- execFile: vi.fn((_cmd: string, _args: string[], _opts: unknown, cb: Function) => {
21
- cb(null, { stdout: '', stderr: '' })
22
- }),
23
- }))
24
-
25
- // ── Mock template module ─────────────────────────────────────────────────────
26
- vi.mock('../lib/template.js', () => ({
27
- generateDependabotYml: vi.fn().mockResolvedValue('dependabot-content'),
28
- generateCIWorkflow: vi.fn().mockResolvedValue('ci-workflow-content'),
29
- getCIDestination: vi.fn().mockReturnValue('.github/workflows/ci.yml'),
30
- }))
31
-
32
- // ── Mock common module ───────────────────────────────────────────────────────
33
- vi.mock('../lib/common.js', () => ({
34
- backupIfExists: vi.fn().mockResolvedValue(false),
35
- ensureDirExists: vi.fn().mockResolvedValue(undefined),
36
- }))
37
-
38
- import fs from 'fs-extra'
39
- import { execFile } from 'child_process'
40
- import { initProject } from './init.js'
41
- import { generateCIWorkflow, getCIDestination } from '../lib/template.js'
42
-
43
- const mockedFs = vi.mocked(fs)
44
- const mockedExecFile = vi.mocked(execFile)
45
- const mockedGenerateCIWorkflow = vi.mocked(generateCIWorkflow)
46
- const mockedGetCIDestination = vi.mocked(getCIDestination)
47
-
48
- beforeEach(() => {
49
- vi.resetAllMocks()
50
-
51
- // Default: most things exist
52
- mockedFs.pathExists.mockResolvedValue(true as never)
53
- mockedFs.writeFile.mockResolvedValue(undefined as never)
54
- mockedFs.writeJson.mockResolvedValue(undefined as never)
55
- mockedFs.copy.mockResolvedValue(undefined as never)
56
- mockedFs.ensureDir.mockResolvedValue(undefined as never)
57
-
58
- // Default: execFile succeeds (promisified version)
59
- mockedExecFile.mockImplementation((_cmd: unknown, _args: unknown, _opts: unknown, cb: unknown) => {
60
- if (typeof cb === 'function') cb(null, { stdout: '', stderr: '' })
61
- return undefined as any
62
- })
63
-
64
- // Default: CI workflow available
65
- mockedGenerateCIWorkflow.mockResolvedValue('ci-workflow-content')
66
- mockedGetCIDestination.mockReturnValue('.github/workflows/ci.yml')
67
- })
68
-
69
- function makeOptions(overrides: Partial<InitOptions> = {}): InitOptions {
70
- return {
71
- projectName: 'test-project',
72
- projectDir: '/test/project',
73
- stack: 'node',
74
- ciProvider: 'github',
75
- memory: 'engram',
76
- aiSync: true,
77
- sdd: true,
78
- ghagga: true,
79
- dryRun: false,
80
- ...overrides,
81
- }
82
- }
83
-
84
- function collectSteps(options: InitOptions): Promise<InitStep[]> {
85
- const steps: InitStep[] = []
86
- return initProject(options, (step) => steps.push(step)).then(() => steps)
87
- }
88
-
89
- // ═══════════════════════════════════════════════════════════════════════════════
90
- // initProject
91
- // ═══════════════════════════════════════════════════════════════════════════════
92
- describe('initProject', () => {
93
- it('completes full happy path — all steps report done', async () => {
94
- // .git doesn't exist yet so it initializes
95
- mockedFs.pathExists.mockImplementation(async (p: unknown) => {
96
- const s = String(p)
97
- if (s.endsWith('.git')) return false as never
98
- return true as never
99
- })
100
-
101
- const steps = await collectSteps(makeOptions())
102
- const doneSteps = steps.filter(s => s.status === 'done')
103
- // Should have multiple 'done' status steps
104
- expect(doneSteps.length).toBeGreaterThanOrEqual(8)
105
- })
106
-
107
- it('dry-run: no filesystem writes are made', async () => {
108
- mockedFs.pathExists.mockImplementation(async (p: unknown) => {
109
- const s = String(p)
110
- if (s.endsWith('.git')) return false as never
111
- return true as never
112
- })
113
-
114
- const steps = await collectSteps(makeOptions({ dryRun: true }))
115
- // In dry-run, fs.writeFile and fs.writeJson should not be called
116
- expect(mockedFs.writeFile).not.toHaveBeenCalled()
117
- expect(mockedFs.writeJson).not.toHaveBeenCalled()
118
- })
119
-
120
- it('continues other steps when one step errors', async () => {
121
- // Make CI generation throw
122
- mockedGenerateCIWorkflow.mockRejectedValue(new Error('CI template error'))
123
- mockedFs.pathExists.mockImplementation(async (p: unknown) => {
124
- const s = String(p)
125
- if (s.endsWith('.git')) return false as never
126
- return true as never
127
- })
128
-
129
- const steps = await collectSteps(makeOptions())
130
- // Should have both error and done steps
131
- const errorSteps = steps.filter(s => s.status === 'error')
132
- const doneSteps = steps.filter(s => s.status === 'done')
133
- expect(errorSteps.length).toBeGreaterThanOrEqual(1)
134
- expect(doneSteps.length).toBeGreaterThanOrEqual(5)
135
- })
136
-
137
- it('skips memory when memory is none', async () => {
138
- mockedFs.pathExists.mockResolvedValue(true as never)
139
- const steps = await collectSteps(makeOptions({ memory: 'none' }))
140
- const memStep = steps.find(s => s.id === 'memory' && s.status === 'skipped')
141
- expect(memStep).toBeDefined()
142
- })
143
-
144
- it('skips ghagga when ghagga is false', async () => {
145
- mockedFs.pathExists.mockResolvedValue(true as never)
146
- const steps = await collectSteps(makeOptions({ ghagga: false }))
147
- const ghStep = steps.find(s => s.id === 'ghagga' && s.status === 'skipped')
148
- expect(ghStep).toBeDefined()
149
- })
150
-
151
- it('skips SDD when sdd is false', async () => {
152
- mockedFs.pathExists.mockResolvedValue(true as never)
153
- const steps = await collectSteps(makeOptions({ sdd: false }))
154
- const sddStep = steps.find(s => s.id === 'sdd' && s.status === 'skipped')
155
- expect(sddStep).toBeDefined()
156
- })
157
-
158
- it('skips AI sync when aiSync is false', async () => {
159
- mockedFs.pathExists.mockResolvedValue(true as never)
160
- const steps = await collectSteps(makeOptions({ aiSync: false }))
161
- const aiStep = steps.find(s => s.id === 'ai-sync' && s.status === 'skipped')
162
- expect(aiStep).toBeDefined()
163
- })
164
-
165
- it('reports already exists when .git directory is present', async () => {
166
- mockedFs.pathExists.mockResolvedValue(true as never)
167
- const steps = await collectSteps(makeOptions())
168
- const gitStep = steps.find(s => s.id === 'git-init' && s.status === 'done' && s.detail === 'already exists')
169
- expect(gitStep).toBeDefined()
170
- })
171
-
172
- it('skips CI step when no template found', async () => {
173
- mockedGenerateCIWorkflow.mockResolvedValue(null)
174
- mockedFs.pathExists.mockResolvedValue(true as never)
175
-
176
- const steps = await collectSteps(makeOptions())
177
- const ciStep = steps.find(s => s.id === 'ci-template' && s.status === 'skipped')
178
- expect(ciStep).toBeDefined()
179
- })
180
-
181
- it('writes manifest with correct structure', async () => {
182
- mockedFs.pathExists.mockImplementation(async (p: unknown) => {
183
- const s = String(p)
184
- if (s.endsWith('.git')) return false as never
185
- return true as never
186
- })
187
-
188
- await collectSteps(makeOptions({
189
- projectName: 'test-manifest',
190
- stack: 'node',
191
- ciProvider: 'github',
192
- memory: 'engram',
193
- ghagga: true,
194
- sdd: true,
195
- aiSync: true,
196
- }))
197
-
198
- expect(mockedFs.writeJson).toHaveBeenCalled()
199
- const [manifestPath, manifestData] = mockedFs.writeJson.mock.calls[0]
200
- expect(String(manifestPath)).toContain('manifest.json')
201
- expect(manifestData).toMatchObject({
202
- version: '0.1.0',
203
- projectName: 'test-manifest',
204
- stack: 'node',
205
- ciProvider: 'github',
206
- memory: 'engram',
207
- })
208
- expect((manifestData as any).modules).toContain('engram')
209
- expect((manifestData as any).modules).toContain('ghagga')
210
- expect((manifestData as any).modules).toContain('sdd')
211
- expect((manifestData as any).modules).toContain('ai-config')
212
- })
213
-
214
- it('reports error with helpful message when javi-ai not found', async () => {
215
- mockedFs.pathExists.mockImplementation(async (p: unknown) => {
216
- const s = String(p)
217
- if (s.endsWith('.git')) return true as never
218
- return true as never
219
- })
220
-
221
- // Make javi-ai sync fail with ENOENT
222
- mockedExecFile.mockImplementation((_cmd: unknown, _args: unknown, _opts: unknown, cb: unknown) => {
223
- const cmdStr = String(_cmd)
224
- const argsArr = _args as string[]
225
- if (cmdStr === 'npx' && argsArr?.includes('javi-ai')) {
226
- if (typeof cb === 'function') cb(new Error('ENOENT: command not found'), { stdout: '', stderr: '' })
227
- } else {
228
- if (typeof cb === 'function') cb(null, { stdout: '', stderr: '' })
229
- }
230
- return undefined as any
231
- })
232
-
233
- const steps = await collectSteps(makeOptions({ aiSync: true }))
234
- const aiStep = steps.find(s => s.id === 'ai-sync' && s.status === 'error')
235
- expect(aiStep).toBeDefined()
236
- expect(aiStep!.detail).toContain('javi-ai not found')
237
- })
238
-
239
- it('reports steps in order via callback', async () => {
240
- mockedFs.pathExists.mockImplementation(async (p: unknown) => {
241
- const s = String(p)
242
- if (s.endsWith('.git')) return false as never
243
- return true as never
244
- })
245
-
246
- const steps = await collectSteps(makeOptions())
247
- const stepIds = steps.map(s => s.id)
248
-
249
- // First step should be git-init
250
- expect(stepIds[0]).toBe('git-init')
251
-
252
- // Manifest should be among the last
253
- const manifestIdx = stepIds.lastIndexOf('manifest')
254
- expect(manifestIdx).toBeGreaterThan(stepIds.indexOf('git-init'))
255
- })
256
-
257
- it('skips dependabot for non-github providers', async () => {
258
- mockedFs.pathExists.mockResolvedValue(true as never)
259
- const steps = await collectSteps(makeOptions({ ciProvider: 'gitlab' }))
260
- const depStep = steps.find(s => s.id === 'dependabot' && s.status === 'skipped')
261
- expect(depStep).toBeDefined()
262
- })
263
-
264
- it('skips gitignore when .gitignore already exists', async () => {
265
- mockedFs.pathExists.mockResolvedValue(true as never)
266
- const steps = await collectSteps(makeOptions())
267
- const giStep = steps.find(s => s.id === 'gitignore' && s.detail === 'already exists')
268
- expect(giStep).toBeDefined()
269
- })
270
-
271
- it('skips hooks when ci-local dir is missing', async () => {
272
- mockedFs.pathExists.mockImplementation(async (p: unknown) => {
273
- const s = String(p)
274
- if (s.includes('ci-local')) return false as never
275
- if (s.endsWith('.git')) return false as never
276
- return true as never
277
- })
278
-
279
- const steps = await collectSteps(makeOptions())
280
- const hookStep = steps.find(s => s.id === 'git-hooks' && s.status === 'skipped')
281
- expect(hookStep).toBeDefined()
282
- })
283
-
284
- it('reports error when memory module not found', async () => {
285
- mockedFs.pathExists.mockImplementation(async (p: unknown) => {
286
- const s = String(p)
287
- // Module source directory doesn't exist
288
- if (s.includes('modules/engram') && !s.includes('.javi-forge')) return false as never
289
- if (s.endsWith('.git')) return false as never
290
- return true as never
291
- })
292
-
293
- const steps = await collectSteps(makeOptions({ memory: 'engram' }))
294
- const memStep = steps.find(s => s.id === 'memory' && s.status === 'error')
295
- expect(memStep).toBeDefined()
296
- expect(memStep!.detail).toContain('module not found')
297
- })
298
- })
@@ -1,285 +0,0 @@
1
- import fs from 'fs-extra'
2
- import path from 'path'
3
- import { execFile } from 'child_process'
4
- import { promisify } from 'util'
5
- import type { InitOptions, InitStep, ForgeManifest } from '../types/index.js'
6
- import { backupIfExists, ensureDirExists } from '../lib/common.js'
7
- import { generateDependabotYml, generateCIWorkflow, getCIDestination } from '../lib/template.js'
8
- import {
9
- FORGE_ROOT,
10
- TEMPLATES_DIR,
11
- MODULES_DIR,
12
- CI_LOCAL_DIR,
13
- } from '../constants.js'
14
-
15
- const execFileAsync = promisify(execFile)
16
-
17
- type StepCallback = (step: InitStep) => void
18
-
19
- function report(onStep: StepCallback, id: string, label: string, status: InitStep['status'], detail?: string) {
20
- onStep({ id, label, status, detail })
21
- }
22
-
23
- /**
24
- * Main init orchestrator: bootstraps a project with CI, git hooks,
25
- * memory module, AI config sync, SDD, and ghagga.
26
- */
27
- export async function initProject(
28
- options: InitOptions,
29
- onStep: StepCallback
30
- ): Promise<void> {
31
- const { projectDir, projectName, stack, ciProvider, memory, aiSync, sdd, ghagga, dryRun } = options
32
-
33
- // Ensure project directory exists before any steps
34
- if (!dryRun && projectDir) {
35
- await ensureDirExists(projectDir)
36
- }
37
-
38
- // ── Step 1: Initialize git ────────────────────────────────────────────────
39
- const stepGit = 'git-init'
40
- report(onStep, stepGit, 'Initialize git repository', 'running')
41
- try {
42
- const gitDir = path.join(projectDir, '.git')
43
- if (!await fs.pathExists(gitDir)) {
44
- if (!dryRun) {
45
- await execFileAsync('git', ['init'], { cwd: projectDir })
46
- }
47
- report(onStep, stepGit, 'Initialize git repository', 'done', 'initialized')
48
- } else {
49
- report(onStep, stepGit, 'Initialize git repository', 'done', 'already exists')
50
- }
51
- } catch (e) {
52
- report(onStep, stepGit, 'Initialize git repository', 'error', String(e))
53
- }
54
-
55
- // ── Step 2: Configure git hooks path ──────────────────────────────────────
56
- const stepHooks = 'git-hooks'
57
- report(onStep, stepHooks, 'Configure git hooks path', 'running')
58
- try {
59
- const ciLocalSrc = CI_LOCAL_DIR
60
- const ciLocalDest = path.join(projectDir, 'ci-local')
61
- if (await fs.pathExists(ciLocalSrc)) {
62
- if (!dryRun) {
63
- await fs.copy(ciLocalSrc, ciLocalDest, { overwrite: false, errorOnExist: false })
64
- // Set core.hooksPath to ci-local/hooks
65
- const hooksDir = path.join(ciLocalDest, 'hooks')
66
- if (await fs.pathExists(hooksDir)) {
67
- await execFileAsync('git', ['config', 'core.hooksPath', 'ci-local/hooks'], { cwd: projectDir })
68
- }
69
- }
70
- report(onStep, stepHooks, 'Configure git hooks path', 'done', 'ci-local/hooks')
71
- } else {
72
- report(onStep, stepHooks, 'Configure git hooks path', 'skipped', 'no ci-local dir')
73
- }
74
- } catch (e) {
75
- report(onStep, stepHooks, 'Configure git hooks path', 'error', String(e))
76
- }
77
-
78
- // ── Step 3: Copy CI template ──────────────────────────────────────────────
79
- const stepCI = 'ci-template'
80
- report(onStep, stepCI, `Copy ${ciProvider} CI workflow`, 'running')
81
- try {
82
- const ciContent = await generateCIWorkflow(stack, ciProvider)
83
- if (ciContent) {
84
- const dest = path.join(projectDir, getCIDestination(ciProvider))
85
- if (!dryRun) {
86
- await backupIfExists(dest)
87
- await ensureDirExists(path.dirname(dest))
88
- await fs.writeFile(dest, ciContent, 'utf-8')
89
- }
90
- report(onStep, stepCI, `Copy ${ciProvider} CI workflow`, 'done', getCIDestination(ciProvider))
91
- } else {
92
- report(onStep, stepCI, `Copy ${ciProvider} CI workflow`, 'skipped', `no template for ${stack}`)
93
- }
94
- } catch (e) {
95
- report(onStep, stepCI, `Copy ${ciProvider} CI workflow`, 'error', String(e))
96
- }
97
-
98
- // ── Step 4: Generate .gitignore ───────────────────────────────────────────
99
- const stepGitignore = 'gitignore'
100
- report(onStep, stepGitignore, 'Generate .gitignore', 'running')
101
- try {
102
- const templatePath = path.join(FORGE_ROOT, '.gitignore.template')
103
- const dest = path.join(projectDir, '.gitignore')
104
- if (await fs.pathExists(templatePath) && !await fs.pathExists(dest)) {
105
- if (!dryRun) {
106
- await fs.copy(templatePath, dest)
107
- }
108
- report(onStep, stepGitignore, 'Generate .gitignore', 'done', 'from template')
109
- } else if (await fs.pathExists(dest)) {
110
- report(onStep, stepGitignore, 'Generate .gitignore', 'done', 'already exists')
111
- } else {
112
- report(onStep, stepGitignore, 'Generate .gitignore', 'skipped', 'no template')
113
- }
114
- } catch (e) {
115
- report(onStep, stepGitignore, 'Generate .gitignore', 'error', String(e))
116
- }
117
-
118
- // ── Step 5: Generate dependabot.yml ───────────────────────────────────────
119
- const stepDeps = 'dependabot'
120
- report(onStep, stepDeps, 'Generate dependabot.yml', 'running')
121
- try {
122
- if (ciProvider === 'github') {
123
- const content = await generateDependabotYml([stack], true)
124
- const dest = path.join(projectDir, '.github', 'dependabot.yml')
125
- if (!dryRun) {
126
- await backupIfExists(dest)
127
- await ensureDirExists(path.dirname(dest))
128
- await fs.writeFile(dest, content, 'utf-8')
129
- }
130
- report(onStep, stepDeps, 'Generate dependabot.yml', 'done')
131
- } else {
132
- report(onStep, stepDeps, 'Generate dependabot.yml', 'skipped', `not needed for ${ciProvider}`)
133
- }
134
- } catch (e) {
135
- report(onStep, stepDeps, 'Generate dependabot.yml', 'error', String(e))
136
- }
137
-
138
- // ── Step 6: Install memory module ─────────────────────────────────────────
139
- const stepMem = 'memory'
140
- report(onStep, stepMem, `Install memory module: ${memory}`, 'running')
141
- try {
142
- if (memory !== 'none') {
143
- const moduleSrc = path.join(MODULES_DIR, memory)
144
- if (await fs.pathExists(moduleSrc)) {
145
- if (!dryRun) {
146
- // Copy module files to project
147
- const moduleDest = path.join(projectDir, '.javi-forge', 'modules', memory)
148
- await ensureDirExists(moduleDest)
149
- await fs.copy(moduleSrc, moduleDest, { overwrite: false, errorOnExist: false })
150
-
151
- // If engram, copy .mcp-config-snippet.json to project
152
- if (memory === 'engram') {
153
- const snippetSrc = path.join(moduleSrc, '.mcp-config-snippet.json')
154
- if (await fs.pathExists(snippetSrc)) {
155
- const snippetDest = path.join(projectDir, '.mcp-config-snippet.json')
156
- await fs.copy(snippetSrc, snippetDest, { overwrite: false })
157
- }
158
- }
159
- }
160
- report(onStep, stepMem, `Install memory module: ${memory}`, 'done')
161
- } else {
162
- report(onStep, stepMem, `Install memory module: ${memory}`, 'error', 'module not found')
163
- }
164
- } else {
165
- report(onStep, stepMem, `Install memory module: ${memory}`, 'skipped', 'none selected')
166
- }
167
- } catch (e) {
168
- report(onStep, stepMem, `Install memory module: ${memory}`, 'error', String(e))
169
- }
170
-
171
- // ── 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')
174
- try {
175
- if (aiSync) {
176
- if (!dryRun) {
177
- try {
178
- await execFileAsync('npx', ['javi-ai', 'sync', '--project-dir', projectDir, '--target', 'all'], {
179
- cwd: projectDir,
180
- timeout: 120_000,
181
- })
182
- report(onStep, stepAI, 'Sync AI config via javi-ai', 'done', 'javi-ai sync --target all')
183
- } catch (syncErr: unknown) {
184
- const msg = syncErr instanceof Error ? syncErr.message : String(syncErr)
185
- if (msg.includes('ENOENT') || msg.includes('not found') || msg.includes('ERR_MODULE_NOT_FOUND')) {
186
- report(onStep, stepAI, 'Sync AI config via javi-ai', 'error',
187
- 'javi-ai not found. Install with: npm install -g javi-ai (or run npx javi-ai sync manually)')
188
- } else {
189
- report(onStep, stepAI, 'Sync AI config via javi-ai', 'error', msg)
190
- }
191
- }
192
- } else {
193
- report(onStep, stepAI, 'Sync AI config via javi-ai', 'done', 'dry-run: would run javi-ai sync --target all')
194
- }
195
- } else {
196
- report(onStep, stepAI, 'Sync AI config via javi-ai', 'skipped', 'not selected')
197
- }
198
- } catch (e) {
199
- report(onStep, stepAI, 'Sync AI config via javi-ai', 'error', String(e))
200
- }
201
-
202
- // ── Step 8: SDD (Spec-Driven Development) ─────────────────────────────────
203
- const stepSDD = 'sdd'
204
- report(onStep, stepSDD, 'Set up SDD (openspec/)', 'running')
205
- try {
206
- if (sdd) {
207
- if (!dryRun) {
208
- const openspecDir = path.join(projectDir, 'openspec')
209
- await ensureDirExists(openspecDir)
210
- // Create a README if none exists
211
- const readmePath = path.join(openspecDir, 'README.md')
212
- if (!await fs.pathExists(readmePath)) {
213
- 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')
214
- }
215
- }
216
- report(onStep, stepSDD, 'Set up SDD (openspec/)', 'done')
217
- } else {
218
- report(onStep, stepSDD, 'Set up SDD (openspec/)', 'skipped', 'not selected')
219
- }
220
- } catch (e) {
221
- report(onStep, stepSDD, 'Set up SDD (openspec/)', 'error', String(e))
222
- }
223
-
224
- // ── Step 9: GHAGGA ────────────────────────────────────────────────────────
225
- const stepGhagga = 'ghagga'
226
- report(onStep, stepGhagga, 'Install GHAGGA review system', 'running')
227
- try {
228
- if (ghagga) {
229
- const ghaggaSrc = path.join(MODULES_DIR, 'ghagga')
230
- if (await fs.pathExists(ghaggaSrc)) {
231
- if (!dryRun) {
232
- const ghaggaDest = path.join(projectDir, '.javi-forge', 'modules', 'ghagga')
233
- await ensureDirExists(ghaggaDest)
234
- await fs.copy(ghaggaSrc, ghaggaDest, { overwrite: false, errorOnExist: false })
235
-
236
- // Copy ghagga workflow to CI provider location
237
- if (ciProvider === 'github') {
238
- const workflowSrc = path.join(FORGE_ROOT, 'workflows', 'reusable-ghagga-review.yml')
239
- if (await fs.pathExists(workflowSrc)) {
240
- const workflowDest = path.join(projectDir, '.github', 'workflows', 'ghagga-review.yml')
241
- await ensureDirExists(path.dirname(workflowDest))
242
- await fs.copy(workflowSrc, workflowDest, { overwrite: false })
243
- }
244
- }
245
- }
246
- report(onStep, stepGhagga, 'Install GHAGGA review system', 'done')
247
- } else {
248
- report(onStep, stepGhagga, 'Install GHAGGA review system', 'error', 'module not found')
249
- }
250
- } else {
251
- report(onStep, stepGhagga, 'Install GHAGGA review system', 'skipped', 'not selected')
252
- }
253
- } catch (e) {
254
- report(onStep, stepGhagga, 'Install GHAGGA review system', 'error', String(e))
255
- }
256
-
257
- // ── Step 10: Write manifest ───────────────────────────────────────────────
258
- const stepManifest = 'manifest'
259
- report(onStep, stepManifest, 'Write forge manifest', 'running')
260
- try {
261
- if (!dryRun) {
262
- const manifestDir = path.join(projectDir, '.javi-forge')
263
- await ensureDirExists(manifestDir)
264
- const manifest: ForgeManifest = {
265
- version: '0.1.0',
266
- projectName,
267
- stack,
268
- ciProvider,
269
- memory,
270
- createdAt: new Date().toISOString(),
271
- updatedAt: new Date().toISOString(),
272
- modules: [
273
- ...(memory !== 'none' ? [memory] : []),
274
- ...(ghagga ? ['ghagga'] : []),
275
- ...(sdd ? ['sdd'] : []),
276
- ...(aiSync ? ['ai-config'] : []),
277
- ],
278
- }
279
- await fs.writeJson(path.join(manifestDir, 'manifest.json'), manifest, { spaces: 2 })
280
- }
281
- report(onStep, stepManifest, 'Write forge manifest', 'done', '.javi-forge/manifest.json')
282
- } catch (e) {
283
- report(onStep, stepManifest, 'Write forge manifest', 'error', String(e))
284
- }
285
- }
package/src/constants.ts DELETED
@@ -1,69 +0,0 @@
1
- import path from 'path'
2
- import { fileURLToPath } from 'url'
3
-
4
- const __dirname = path.dirname(fileURLToPath(import.meta.url))
5
-
6
- /** Root of the javi-forge package (one level up from dist/) */
7
- export const FORGE_ROOT = path.resolve(__dirname, '..')
8
-
9
- /** Templates directory */
10
- export const TEMPLATES_DIR = path.join(FORGE_ROOT, 'templates')
11
-
12
- /** Modules directory */
13
- export const MODULES_DIR = path.join(FORGE_ROOT, 'modules')
14
-
15
- /** Workflows directory */
16
- export const WORKFLOWS_DIR = path.join(FORGE_ROOT, 'workflows')
17
-
18
- /** AI config directory */
19
- export const AI_CONFIG_DIR = path.join(FORGE_ROOT, 'ai-config')
20
-
21
- /** Schemas directory */
22
- export const SCHEMAS_DIR = path.join(FORGE_ROOT, 'schemas')
23
-
24
- /** CI-local directory */
25
- export const CI_LOCAL_DIR = path.join(FORGE_ROOT, 'ci-local')
26
-
27
- /** Dependabot fragment directory */
28
- export const DEPENDABOT_FRAGMENTS_DIR = path.join(TEMPLATES_DIR, 'common', 'dependabot')
29
-
30
- /** Stack-to-dependabot fragment mapping */
31
- export const STACK_DEPENDABOT_MAP: Record<string, string[]> = {
32
- 'node': ['npm'],
33
- 'python': ['pip'],
34
- 'go': ['gomod'],
35
- 'rust': ['cargo'],
36
- 'java-gradle': ['gradle'],
37
- 'java-maven': ['maven'],
38
- 'elixir': [],
39
- }
40
-
41
- /** Stack-to-CI template filename mapping */
42
- export const STACK_CI_MAP: Record<string, Record<string, string>> = {
43
- github: {
44
- 'node': 'ci-node.yml',
45
- 'python': 'ci-python.yml',
46
- 'go': 'ci-go.yml',
47
- 'rust': 'ci-rust.yml',
48
- 'java-gradle': 'ci-java.yml',
49
- 'java-maven': 'ci-java.yml',
50
- },
51
- gitlab: {
52
- 'node': 'gitlab-ci-node.yml',
53
- 'python': 'gitlab-ci-python.yml',
54
- 'go': 'gitlab-ci-go.yml',
55
- 'rust': 'gitlab-ci-rust.yml',
56
- 'java-gradle': 'gitlab-ci-java.yml',
57
- 'java-maven': 'gitlab-ci-java.yml',
58
- },
59
- woodpecker: {
60
- 'node': 'woodpecker-node.yml',
61
- 'python': 'woodpecker-python.yml',
62
- 'go': 'woodpecker-go.yml',
63
- 'rust': 'woodpecker-rust.yml',
64
- 'java-gradle': 'woodpecker-java.yml',
65
- 'java-maven': 'woodpecker-java.yml',
66
- },
67
- }
68
-
69
-