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.
- package/.releaserc +2 -1
- package/README.md +143 -31
- package/ai-config/commands/workflows/diagnose.md +70 -0
- package/ai-config/commands/workflows/discover.md +86 -0
- package/dist/commands/doctor.js +24 -1
- package/dist/commands/init.js +48 -1
- package/dist/commands/llmstxt.d.ts +9 -0
- package/dist/commands/llmstxt.js +93 -0
- package/dist/commands/llmstxt.test.d.ts +2 -0
- package/dist/commands/plugin.d.ts +24 -0
- package/dist/commands/plugin.js +78 -0
- package/dist/commands/plugin.test.d.ts +2 -0
- package/dist/constants.d.ts +8 -0
- package/dist/constants.js +8 -0
- package/dist/index.js +33 -4
- package/dist/lib/plugin.d.ts +39 -0
- package/dist/lib/plugin.js +228 -0
- package/dist/lib/plugin.test.d.ts +2 -0
- package/dist/types/index.d.ts +42 -0
- package/dist/ui/App.d.ts +2 -1
- package/dist/ui/App.js +2 -1
- package/dist/ui/LlmsTxt.d.ts +8 -0
- package/dist/ui/LlmsTxt.js +48 -0
- package/dist/ui/Plugin.d.ts +9 -0
- package/dist/ui/Plugin.js +96 -0
- package/modules/obsidian-brain/README.md +32 -0
- package/modules/obsidian-brain/core/templates/braindump.md +15 -0
- package/modules/obsidian-brain/core/templates/consolidation.md +42 -0
- package/modules/obsidian-brain/core/templates/daily-note.md +18 -0
- package/modules/obsidian-brain/core/templates/resource-capture.md +14 -0
- package/modules/obsidian-brain/developer/templates/adr.md +40 -0
- package/modules/obsidian-brain/developer/templates/coding-session.md +24 -0
- package/modules/obsidian-brain/developer/templates/debug-journal.md +22 -0
- package/modules/obsidian-brain/developer/templates/sdd-feedback.md +27 -0
- package/modules/obsidian-brain/developer/templates/tech-debt.md +20 -0
- package/modules/obsidian-brain/pm-lead/templates/daily-brief.md +25 -0
- package/modules/obsidian-brain/pm-lead/templates/meeting-notes.md +24 -0
- package/modules/obsidian-brain/pm-lead/templates/risk-registry.md +12 -0
- package/modules/obsidian-brain/pm-lead/templates/sprint-review.md +27 -0
- package/modules/obsidian-brain/pm-lead/templates/stakeholder-update.md +24 -0
- package/modules/obsidian-brain/pm-lead/templates/team-intelligence.md +19 -0
- package/modules/obsidian-brain/pm-lead/templates/weekly-brief.md +29 -0
- package/package.json +1 -1
- package/schemas/plugin.schema.json +62 -0
- package/ai-config/skills/docs/api-documentation/SKILL.md +0 -293
- package/ai-config/skills/docs/docs-spring/SKILL.md +0 -377
- package/ai-config/skills/docs/mustache-templates/SKILL.md +0 -190
- package/ai-config/skills/docs/technical-docs/SKILL.md +0 -447
- package/dist/commands/analyze.d.ts.map +0 -1
- package/dist/commands/analyze.js.map +0 -1
- package/dist/commands/analyze.test.d.ts.map +0 -1
- package/dist/commands/analyze.test.js +0 -145
- package/dist/commands/analyze.test.js.map +0 -1
- package/dist/commands/doctor.d.ts.map +0 -1
- package/dist/commands/doctor.js.map +0 -1
- package/dist/commands/doctor.test.d.ts.map +0 -1
- package/dist/commands/doctor.test.js +0 -200
- package/dist/commands/doctor.test.js.map +0 -1
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/init.test.d.ts.map +0 -1
- package/dist/commands/init.test.js +0 -271
- package/dist/commands/init.test.js.map +0 -1
- package/dist/commands/sync.d.ts.map +0 -1
- package/dist/commands/sync.js.map +0 -1
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js.map +0 -1
- package/dist/e2e/aggressive.e2e.test.d.ts.map +0 -1
- package/dist/e2e/aggressive.e2e.test.js +0 -350
- package/dist/e2e/aggressive.e2e.test.js.map +0 -1
- package/dist/e2e/commands.e2e.test.d.ts.map +0 -1
- package/dist/e2e/commands.e2e.test.js +0 -213
- package/dist/e2e/commands.e2e.test.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lib/common.d.ts.map +0 -1
- package/dist/lib/common.js.map +0 -1
- package/dist/lib/common.test.d.ts.map +0 -1
- package/dist/lib/common.test.js +0 -316
- package/dist/lib/common.test.js.map +0 -1
- package/dist/lib/frontmatter.d.ts.map +0 -1
- package/dist/lib/frontmatter.js.map +0 -1
- package/dist/lib/frontmatter.test.d.ts.map +0 -1
- package/dist/lib/frontmatter.test.js +0 -257
- package/dist/lib/frontmatter.test.js.map +0 -1
- package/dist/lib/template.d.ts.map +0 -1
- package/dist/lib/template.js.map +0 -1
- package/dist/lib/template.test.d.ts.map +0 -1
- package/dist/lib/template.test.js +0 -201
- package/dist/lib/template.test.js.map +0 -1
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js.map +0 -1
- package/dist/ui/AnalyzeUI.d.ts.map +0 -1
- package/dist/ui/AnalyzeUI.js.map +0 -1
- package/dist/ui/App.d.ts.map +0 -1
- package/dist/ui/App.js.map +0 -1
- package/dist/ui/CIContext.d.ts.map +0 -1
- package/dist/ui/CIContext.js.map +0 -1
- package/dist/ui/CISelector.d.ts.map +0 -1
- package/dist/ui/CISelector.js.map +0 -1
- package/dist/ui/Doctor.d.ts.map +0 -1
- package/dist/ui/Doctor.js.map +0 -1
- package/dist/ui/Header.d.ts.map +0 -1
- package/dist/ui/Header.js.map +0 -1
- package/dist/ui/MemorySelector.d.ts.map +0 -1
- package/dist/ui/MemorySelector.js.map +0 -1
- package/dist/ui/NameInput.d.ts.map +0 -1
- package/dist/ui/NameInput.js.map +0 -1
- package/dist/ui/OptionSelector.d.ts.map +0 -1
- package/dist/ui/OptionSelector.js.map +0 -1
- package/dist/ui/Progress.d.ts.map +0 -1
- package/dist/ui/Progress.js.map +0 -1
- package/dist/ui/StackSelector.d.ts.map +0 -1
- package/dist/ui/StackSelector.js.map +0 -1
- package/dist/ui/Summary.d.ts.map +0 -1
- package/dist/ui/Summary.js.map +0 -1
- package/dist/ui/SyncUI.d.ts.map +0 -1
- package/dist/ui/SyncUI.js.map +0 -1
- package/dist/ui/Welcome.d.ts.map +0 -1
- package/dist/ui/Welcome.js.map +0 -1
- package/dist/ui/theme.d.ts.map +0 -1
- package/dist/ui/theme.js.map +0 -1
- package/modules/obsidian-brain/.obsidian/plugins/dataview/data.json +0 -25
- package/modules/obsidian-brain/.obsidian/plugins/obsidian-kanban/data.json +0 -29
- package/modules/obsidian-brain/.obsidian/plugins/templater-obsidian/data.json +0 -18
- package/src/commands/analyze.test.ts +0 -145
- package/src/commands/analyze.ts +0 -69
- package/src/commands/doctor.test.ts +0 -208
- package/src/commands/doctor.ts +0 -163
- package/src/commands/init.test.ts +0 -298
- package/src/commands/init.ts +0 -285
- package/src/constants.ts +0 -69
- package/src/e2e/aggressive.e2e.test.ts +0 -557
- package/src/e2e/commands.e2e.test.ts +0 -298
- package/src/index.tsx +0 -106
- package/src/lib/common.test.ts +0 -318
- package/src/lib/common.ts +0 -127
- package/src/lib/frontmatter.test.ts +0 -291
- package/src/lib/frontmatter.ts +0 -77
- package/src/lib/template.test.ts +0 -226
- package/src/lib/template.ts +0 -99
- package/src/types/index.ts +0 -53
- package/src/ui/AnalyzeUI.tsx +0 -133
- package/src/ui/App.tsx +0 -175
- package/src/ui/CIContext.tsx +0 -25
- package/src/ui/CISelector.tsx +0 -72
- package/src/ui/Doctor.tsx +0 -122
- package/src/ui/Header.tsx +0 -48
- package/src/ui/MemorySelector.tsx +0 -73
- package/src/ui/NameInput.tsx +0 -82
- package/src/ui/OptionSelector.tsx +0 -100
- package/src/ui/Progress.tsx +0 -88
- package/src/ui/StackSelector.tsx +0 -101
- package/src/ui/Summary.tsx +0 -134
- package/src/ui/Welcome.tsx +0 -54
- package/src/ui/theme.ts +0 -10
- package/stryker.config.json +0 -19
- package/tsconfig.json +0 -19
- package/vitest.config.ts +0 -16
package/src/types/index.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
export type Stack = 'node' | 'python' | 'go' | 'rust' | 'java-gradle' | 'java-maven' | 'elixir'
|
|
2
|
-
export type CIProvider = 'github' | 'gitlab' | 'woodpecker'
|
|
3
|
-
export type MemoryOption = 'engram' | 'obsidian-brain' | 'memory-simple' | 'none'
|
|
4
|
-
export interface InitOptions {
|
|
5
|
-
projectName: string
|
|
6
|
-
projectDir: string
|
|
7
|
-
stack: Stack
|
|
8
|
-
ciProvider: CIProvider
|
|
9
|
-
memory: MemoryOption
|
|
10
|
-
aiSync: boolean
|
|
11
|
-
sdd: boolean
|
|
12
|
-
ghagga: boolean
|
|
13
|
-
dryRun: boolean
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface InitStep {
|
|
17
|
-
id: string
|
|
18
|
-
label: string
|
|
19
|
-
status: 'pending' | 'running' | 'done' | 'error' | 'skipped'
|
|
20
|
-
detail?: string
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface StackDetection {
|
|
24
|
-
stackType: Stack
|
|
25
|
-
buildTool: string
|
|
26
|
-
javaVersion?: string
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface ForgeManifest {
|
|
30
|
-
version: string
|
|
31
|
-
projectName: string
|
|
32
|
-
stack: Stack
|
|
33
|
-
ciProvider: CIProvider
|
|
34
|
-
memory: MemoryOption
|
|
35
|
-
createdAt: string
|
|
36
|
-
updatedAt: string
|
|
37
|
-
modules: string[]
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface DoctorCheck {
|
|
41
|
-
label: string
|
|
42
|
-
status: 'ok' | 'fail' | 'skip'
|
|
43
|
-
detail?: string
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface DoctorSection {
|
|
47
|
-
title: string
|
|
48
|
-
checks: DoctorCheck[]
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export interface DoctorResult {
|
|
52
|
-
sections: DoctorSection[]
|
|
53
|
-
}
|
package/src/ui/AnalyzeUI.tsx
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useState } from 'react'
|
|
2
|
-
import { Box, Text, useApp, useInput } from 'ink'
|
|
3
|
-
import Spinner from 'ink-spinner'
|
|
4
|
-
import { runAnalyze } from '../commands/analyze.js'
|
|
5
|
-
import type { InitStep } from '../types/index.js'
|
|
6
|
-
import Header from './Header.js'
|
|
7
|
-
import { theme } from './theme.js'
|
|
8
|
-
import { useCIMode } from './CIContext.js'
|
|
9
|
-
|
|
10
|
-
interface Props {
|
|
11
|
-
dryRun: boolean
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export default function AnalyzeUI({ dryRun }: Props) {
|
|
15
|
-
const { exit } = useApp()
|
|
16
|
-
const isCI = useCIMode()
|
|
17
|
-
const [steps, setSteps] = useState<InitStep[]>([])
|
|
18
|
-
const [finished, setFinished] = useState(false)
|
|
19
|
-
const [startTime] = useState(Date.now())
|
|
20
|
-
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
runAnalyze(
|
|
23
|
-
process.cwd(),
|
|
24
|
-
dryRun,
|
|
25
|
-
(step) => setSteps(prev => {
|
|
26
|
-
const idx = prev.findIndex(s => s.id === step.id)
|
|
27
|
-
if (idx >= 0) {
|
|
28
|
-
const next = [...prev]
|
|
29
|
-
next[idx] = step
|
|
30
|
-
return next
|
|
31
|
-
}
|
|
32
|
-
return [...prev, step]
|
|
33
|
-
})
|
|
34
|
-
).then(() => setFinished(true))
|
|
35
|
-
}, [dryRun])
|
|
36
|
-
|
|
37
|
-
// Auto-exit in CI mode once finished
|
|
38
|
-
useEffect(() => {
|
|
39
|
-
if (isCI && finished) {
|
|
40
|
-
const t = setTimeout(() => exit(), 100)
|
|
41
|
-
return () => clearTimeout(t)
|
|
42
|
-
}
|
|
43
|
-
return undefined
|
|
44
|
-
}, [isCI, finished, exit])
|
|
45
|
-
|
|
46
|
-
useInput((_, key) => {
|
|
47
|
-
if (finished && (key.return || key.escape)) exit()
|
|
48
|
-
}, { isActive: !isCI })
|
|
49
|
-
|
|
50
|
-
const doneCount = steps.filter(s => s.status === 'done').length
|
|
51
|
-
const errorCount = steps.filter(s => s.status === 'error').length
|
|
52
|
-
const notInstalled = steps.some(
|
|
53
|
-
s => s.status === 'error' && s.detail?.includes('not found')
|
|
54
|
-
)
|
|
55
|
-
const elapsed = finished
|
|
56
|
-
? `${((Date.now() - startTime) / 1000).toFixed(1)}s`
|
|
57
|
-
: null
|
|
58
|
-
|
|
59
|
-
return (
|
|
60
|
-
<Box flexDirection="column" padding={1}>
|
|
61
|
-
<Header subtitle="analyze" dryRun={dryRun} />
|
|
62
|
-
|
|
63
|
-
{/* Scanning context */}
|
|
64
|
-
{!finished && steps.length === 0 && (
|
|
65
|
-
<Box marginLeft={2}>
|
|
66
|
-
<Text color={theme.warning}>
|
|
67
|
-
<Spinner type="dots" />
|
|
68
|
-
{' '}Checking for repoforge...
|
|
69
|
-
</Text>
|
|
70
|
-
</Box>
|
|
71
|
-
)}
|
|
72
|
-
|
|
73
|
-
<Box flexDirection="column">
|
|
74
|
-
{steps.map(step => (
|
|
75
|
-
<Box key={step.id} marginLeft={2}>
|
|
76
|
-
{step.status === 'running' ? (
|
|
77
|
-
<Text color={theme.warning}>
|
|
78
|
-
<Spinner type="dots" />
|
|
79
|
-
{' '}{step.label}
|
|
80
|
-
</Text>
|
|
81
|
-
) : step.status === 'done' ? (
|
|
82
|
-
<Text color={theme.success}>
|
|
83
|
-
{'\u2713'} {step.label}
|
|
84
|
-
{step.detail && <Text color={theme.muted} dimColor> {step.detail}</Text>}
|
|
85
|
-
</Text>
|
|
86
|
-
) : step.status === 'error' ? (
|
|
87
|
-
<Text color={theme.error}>
|
|
88
|
-
{'\u2717'} {step.label}
|
|
89
|
-
{step.detail && <Text color={theme.muted} dimColor> {step.detail}</Text>}
|
|
90
|
-
</Text>
|
|
91
|
-
) : (
|
|
92
|
-
<Text color={theme.muted}>
|
|
93
|
-
{'\u2013'} {step.label}
|
|
94
|
-
{step.detail && <Text dimColor> {step.detail}</Text>}
|
|
95
|
-
</Text>
|
|
96
|
-
)}
|
|
97
|
-
</Box>
|
|
98
|
-
))}
|
|
99
|
-
</Box>
|
|
100
|
-
|
|
101
|
-
{/* Install instructions when repoforge is missing */}
|
|
102
|
-
{finished && notInstalled && (
|
|
103
|
-
<Box marginTop={1} flexDirection="column" marginLeft={2}>
|
|
104
|
-
<Text color={theme.warning} bold>repoforge is not installed</Text>
|
|
105
|
-
<Box marginTop={1} flexDirection="column">
|
|
106
|
-
<Text color={theme.muted}> Install it with:</Text>
|
|
107
|
-
<Text color={theme.primary}> pip install repoforge</Text>
|
|
108
|
-
<Text color={theme.muted}> Then run:</Text>
|
|
109
|
-
<Text color={theme.primary}> javi-forge analyze</Text>
|
|
110
|
-
</Box>
|
|
111
|
-
</Box>
|
|
112
|
-
)}
|
|
113
|
-
|
|
114
|
-
{finished && (
|
|
115
|
-
<Box marginTop={1} flexDirection="column">
|
|
116
|
-
<Text bold color={errorCount > 0 ? theme.warning : theme.success}>
|
|
117
|
-
{dryRun ? '\u25cb Dry run complete' : '\u2713 Analysis complete'}
|
|
118
|
-
{elapsed && <Text color={theme.muted}> Completed in {elapsed}</Text>}
|
|
119
|
-
</Text>
|
|
120
|
-
{doneCount > 0 && (
|
|
121
|
-
<Text color={theme.success}> {'\u2713'} {doneCount} checks passed</Text>
|
|
122
|
-
)}
|
|
123
|
-
{errorCount > 0 && (
|
|
124
|
-
<Text color={theme.error}> {'\u2717'} {errorCount} errors</Text>
|
|
125
|
-
)}
|
|
126
|
-
<Box marginTop={1}>
|
|
127
|
-
<Text color={theme.muted} dimColor>Press Enter to exit</Text>
|
|
128
|
-
</Box>
|
|
129
|
-
</Box>
|
|
130
|
-
)}
|
|
131
|
-
</Box>
|
|
132
|
-
)
|
|
133
|
-
}
|
package/src/ui/App.tsx
DELETED
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react'
|
|
2
|
-
import { Box } from 'ink'
|
|
3
|
-
import path from 'path'
|
|
4
|
-
import Welcome from './Welcome.js'
|
|
5
|
-
import Header from './Header.js'
|
|
6
|
-
import NameInput from './NameInput.js'
|
|
7
|
-
import StackSelector from './StackSelector.js'
|
|
8
|
-
import CISelector from './CISelector.js'
|
|
9
|
-
import MemorySelector from './MemorySelector.js'
|
|
10
|
-
import OptionSelector from './OptionSelector.js'
|
|
11
|
-
import Progress from './Progress.js'
|
|
12
|
-
import Summary from './Summary.js'
|
|
13
|
-
import { initProject } from '../commands/init.js'
|
|
14
|
-
import type { Stack, CIProvider, MemoryOption, InitStep } from '../types/index.js'
|
|
15
|
-
|
|
16
|
-
type Stage =
|
|
17
|
-
| 'welcome'
|
|
18
|
-
| 'name'
|
|
19
|
-
| 'stack'
|
|
20
|
-
| 'ci'
|
|
21
|
-
| 'memory'
|
|
22
|
-
| 'options'
|
|
23
|
-
| 'running'
|
|
24
|
-
| 'done'
|
|
25
|
-
|
|
26
|
-
interface AppProps {
|
|
27
|
-
dryRun?: boolean
|
|
28
|
-
presetStack?: Stack
|
|
29
|
-
presetCI?: CIProvider
|
|
30
|
-
presetMemory?: MemoryOption
|
|
31
|
-
presetName?: string
|
|
32
|
-
presetGhagga?: boolean
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export default function App({
|
|
36
|
-
dryRun = false,
|
|
37
|
-
presetStack,
|
|
38
|
-
presetCI,
|
|
39
|
-
presetMemory,
|
|
40
|
-
presetName,
|
|
41
|
-
presetGhagga = false,
|
|
42
|
-
}: AppProps) {
|
|
43
|
-
const [stage, setStage] = useState<Stage>('welcome')
|
|
44
|
-
const [projectName, setProjectName] = useState(presetName ?? '')
|
|
45
|
-
const [projectDir, setProjectDir] = useState(
|
|
46
|
-
presetName ? path.resolve(process.cwd(), presetName) : ''
|
|
47
|
-
)
|
|
48
|
-
const [stack, setStack] = useState<Stack>(presetStack ?? 'node')
|
|
49
|
-
const [ciProvider, setCIProvider] = useState<CIProvider>(presetCI ?? 'github')
|
|
50
|
-
const [memory, setMemory] = useState<MemoryOption>(presetMemory ?? 'engram')
|
|
51
|
-
const [aiSync, setAiSync] = useState(true)
|
|
52
|
-
const [sdd, setSdd] = useState(true)
|
|
53
|
-
const [ghagga, setGhagga] = useState(presetGhagga)
|
|
54
|
-
const [steps, setSteps] = useState<InitStep[]>([])
|
|
55
|
-
const [startTime] = useState(Date.now())
|
|
56
|
-
|
|
57
|
-
const handleNameConfirm = (name: string, dir: string) => {
|
|
58
|
-
setProjectName(name)
|
|
59
|
-
setProjectDir(dir)
|
|
60
|
-
setStage(presetStack ? 'ci' : 'stack')
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const handleStackConfirm = (s: Stack) => {
|
|
64
|
-
setStack(s)
|
|
65
|
-
setStage(presetCI ? 'memory' : 'ci')
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const handleCIConfirm = (p: CIProvider) => {
|
|
69
|
-
setCIProvider(p)
|
|
70
|
-
setStage(presetMemory ? 'options' : 'memory')
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const handleMemoryConfirm = (m: MemoryOption) => {
|
|
74
|
-
setMemory(m)
|
|
75
|
-
setStage('options')
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const handleOptionsConfirm = async (opts: { aiSync: boolean; sdd: boolean; ghagga: boolean }) => {
|
|
79
|
-
setAiSync(opts.aiSync)
|
|
80
|
-
setSdd(opts.sdd)
|
|
81
|
-
setGhagga(opts.ghagga)
|
|
82
|
-
setStage('running')
|
|
83
|
-
|
|
84
|
-
await initProject(
|
|
85
|
-
{
|
|
86
|
-
projectName,
|
|
87
|
-
projectDir,
|
|
88
|
-
stack,
|
|
89
|
-
ciProvider,
|
|
90
|
-
memory,
|
|
91
|
-
aiSync: opts.aiSync,
|
|
92
|
-
sdd: opts.sdd,
|
|
93
|
-
ghagga: opts.ghagga,
|
|
94
|
-
dryRun,
|
|
95
|
-
},
|
|
96
|
-
(step) => setSteps(prev => {
|
|
97
|
-
const idx = prev.findIndex(s => s.id === step.id)
|
|
98
|
-
if (idx >= 0) {
|
|
99
|
-
const next = [...prev]
|
|
100
|
-
next[idx] = step
|
|
101
|
-
return next
|
|
102
|
-
}
|
|
103
|
-
return [...prev, step]
|
|
104
|
-
})
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
setStage('done')
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const subtitle =
|
|
111
|
-
stage === 'running' ? 'scaffolding...' :
|
|
112
|
-
stage === 'done' ? 'complete' :
|
|
113
|
-
undefined
|
|
114
|
-
|
|
115
|
-
return (
|
|
116
|
-
<Box flexDirection="column" padding={1}>
|
|
117
|
-
{stage !== 'welcome' && <Header subtitle={subtitle} dryRun={dryRun} />}
|
|
118
|
-
|
|
119
|
-
{stage === 'welcome' && (
|
|
120
|
-
<Welcome onDone={() => {
|
|
121
|
-
// Skip stages for which presets are already provided
|
|
122
|
-
if (presetName && presetStack && presetCI && presetMemory) {
|
|
123
|
-
setStage('options')
|
|
124
|
-
} else if (presetName && presetStack && presetCI) {
|
|
125
|
-
setStage('memory')
|
|
126
|
-
} else if (presetName && presetStack) {
|
|
127
|
-
setStage('ci')
|
|
128
|
-
} else if (presetName) {
|
|
129
|
-
setStage('stack')
|
|
130
|
-
} else {
|
|
131
|
-
setStage('name')
|
|
132
|
-
}
|
|
133
|
-
}} />
|
|
134
|
-
)}
|
|
135
|
-
{stage === 'name' && (
|
|
136
|
-
<NameInput
|
|
137
|
-
defaultName={projectName || 'my-project'}
|
|
138
|
-
onConfirm={handleNameConfirm}
|
|
139
|
-
/>
|
|
140
|
-
)}
|
|
141
|
-
{stage === 'stack' && (
|
|
142
|
-
<StackSelector
|
|
143
|
-
projectDir={projectDir || process.cwd()}
|
|
144
|
-
onConfirm={handleStackConfirm}
|
|
145
|
-
/>
|
|
146
|
-
)}
|
|
147
|
-
{stage === 'ci' && (
|
|
148
|
-
<CISelector onConfirm={handleCIConfirm} />
|
|
149
|
-
)}
|
|
150
|
-
{stage === 'memory' && (
|
|
151
|
-
<MemorySelector onConfirm={handleMemoryConfirm} />
|
|
152
|
-
)}
|
|
153
|
-
{stage === 'options' && (
|
|
154
|
-
<OptionSelector onConfirm={handleOptionsConfirm} presetGhagga={presetGhagga} />
|
|
155
|
-
)}
|
|
156
|
-
{stage === 'running' && (
|
|
157
|
-
<Progress
|
|
158
|
-
steps={steps}
|
|
159
|
-
projectName={projectName}
|
|
160
|
-
contextLine={`${projectName} (${stack} + ${ciProvider})`}
|
|
161
|
-
onDone={() => setStage('done')}
|
|
162
|
-
/>
|
|
163
|
-
)}
|
|
164
|
-
{stage === 'done' && (
|
|
165
|
-
<Summary
|
|
166
|
-
steps={steps}
|
|
167
|
-
dryRun={dryRun}
|
|
168
|
-
projectName={projectName}
|
|
169
|
-
stack={stack}
|
|
170
|
-
elapsedMs={Date.now() - startTime}
|
|
171
|
-
/>
|
|
172
|
-
)}
|
|
173
|
-
</Box>
|
|
174
|
-
)
|
|
175
|
-
}
|
package/src/ui/CIContext.tsx
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import React, { createContext, useContext } from 'react'
|
|
2
|
-
|
|
3
|
-
interface CIContextValue {
|
|
4
|
-
/** True when running in non-interactive mode (CI=1 or --no-tui) */
|
|
5
|
-
isCI: boolean
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
const CIContext = createContext<CIContextValue>({ isCI: false })
|
|
9
|
-
|
|
10
|
-
export function useCIMode(): boolean {
|
|
11
|
-
return useContext(CIContext).isCI
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface Props {
|
|
15
|
-
isCI: boolean
|
|
16
|
-
children: React.ReactNode
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function CIProvider({ isCI, children }: Props) {
|
|
20
|
-
return (
|
|
21
|
-
<CIContext.Provider value={{ isCI }}>
|
|
22
|
-
{children}
|
|
23
|
-
</CIContext.Provider>
|
|
24
|
-
)
|
|
25
|
-
}
|
package/src/ui/CISelector.tsx
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react'
|
|
2
|
-
import { Box, Text, useInput } from 'ink'
|
|
3
|
-
import type { CIProvider } from '../types/index.js'
|
|
4
|
-
import { theme } from './theme.js'
|
|
5
|
-
import { useCIMode } from './CIContext.js'
|
|
6
|
-
|
|
7
|
-
const CI_PROVIDERS: { id: CIProvider; label: string; description: string }[] = [
|
|
8
|
-
{ id: 'github', label: 'GitHub Actions', description: 'GitHub CI/CD with reusable workflows' },
|
|
9
|
-
{ id: 'gitlab', label: 'GitLab CI', description: 'GitLab CI/CD pipelines' },
|
|
10
|
-
{ id: 'woodpecker', label: 'Woodpecker CI', description: 'Container-native CI for Gitea/Forgejo' },
|
|
11
|
-
]
|
|
12
|
-
|
|
13
|
-
interface Props {
|
|
14
|
-
onConfirm: (provider: CIProvider) => void
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export default function CISelector({ onConfirm }: Props) {
|
|
18
|
-
const isCI = useCIMode()
|
|
19
|
-
const [cursor, setCursor] = useState(0)
|
|
20
|
-
|
|
21
|
-
// Auto-confirm in CI mode
|
|
22
|
-
useEffect(() => {
|
|
23
|
-
if (isCI) {
|
|
24
|
-
onConfirm(CI_PROVIDERS[cursor].id)
|
|
25
|
-
}
|
|
26
|
-
}, [isCI]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
27
|
-
|
|
28
|
-
useInput((_, key) => {
|
|
29
|
-
if (key.upArrow) setCursor(c => Math.max(0, c - 1))
|
|
30
|
-
if (key.downArrow) setCursor(c => Math.min(CI_PROVIDERS.length - 1, c + 1))
|
|
31
|
-
if (key.return) {
|
|
32
|
-
onConfirm(CI_PROVIDERS[cursor].id)
|
|
33
|
-
}
|
|
34
|
-
}, { isActive: !isCI })
|
|
35
|
-
|
|
36
|
-
return (
|
|
37
|
-
<Box flexDirection="column">
|
|
38
|
-
<Text bold>Select CI provider:</Text>
|
|
39
|
-
|
|
40
|
-
<Box
|
|
41
|
-
marginTop={1}
|
|
42
|
-
flexDirection="column"
|
|
43
|
-
borderStyle="single"
|
|
44
|
-
borderLeft
|
|
45
|
-
borderRight={false}
|
|
46
|
-
borderTop={false}
|
|
47
|
-
borderBottom={false}
|
|
48
|
-
borderColor={theme.muted}
|
|
49
|
-
paddingLeft={1}
|
|
50
|
-
>
|
|
51
|
-
{CI_PROVIDERS.map((p, i) => (
|
|
52
|
-
<Box key={p.id}>
|
|
53
|
-
<Text color={i === cursor ? theme.primary : 'white'}>
|
|
54
|
-
{i === cursor ? '\u25b6 ' : ' '}
|
|
55
|
-
{i === cursor ? '\u25c9' : '\u25cb'} {p.label}
|
|
56
|
-
</Text>
|
|
57
|
-
<Text color={theme.muted} dimColor> {p.description}</Text>
|
|
58
|
-
</Box>
|
|
59
|
-
))}
|
|
60
|
-
</Box>
|
|
61
|
-
|
|
62
|
-
<Box marginTop={1} gap={2}>
|
|
63
|
-
<Text color={theme.primary}>
|
|
64
|
-
{CI_PROVIDERS[cursor].label}
|
|
65
|
-
</Text>
|
|
66
|
-
<Text color={theme.muted} dimColor>
|
|
67
|
-
{'\u2191\u2193'} navigate Enter confirm
|
|
68
|
-
</Text>
|
|
69
|
-
</Box>
|
|
70
|
-
</Box>
|
|
71
|
-
)
|
|
72
|
-
}
|
package/src/ui/Doctor.tsx
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useState, useCallback } from 'react'
|
|
2
|
-
import { Box, Text, useApp, useInput } from 'ink'
|
|
3
|
-
import Spinner from 'ink-spinner'
|
|
4
|
-
import { runDoctor } from '../commands/doctor.js'
|
|
5
|
-
import type { DoctorResult } from '../types/index.js'
|
|
6
|
-
import Header from './Header.js'
|
|
7
|
-
import { theme } from './theme.js'
|
|
8
|
-
import { useCIMode } from './CIContext.js'
|
|
9
|
-
|
|
10
|
-
type CheckStatus = 'ok' | 'fail' | 'skip'
|
|
11
|
-
|
|
12
|
-
const STATUS_ICON: Record<CheckStatus, string> = {
|
|
13
|
-
ok: '\u2713',
|
|
14
|
-
fail: '\u2717',
|
|
15
|
-
skip: '\u2013',
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const STATUS_COLOR: Record<CheckStatus, string> = {
|
|
19
|
-
ok: theme.success,
|
|
20
|
-
fail: theme.error,
|
|
21
|
-
skip: theme.muted,
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export default function Doctor() {
|
|
25
|
-
const { exit } = useApp()
|
|
26
|
-
const isCI = useCIMode()
|
|
27
|
-
const [result, setResult] = useState<DoctorResult | null>(null)
|
|
28
|
-
const [error, setError] = useState<string | null>(null)
|
|
29
|
-
const [loading, setLoading] = useState(true)
|
|
30
|
-
|
|
31
|
-
const runCheck = useCallback(() => {
|
|
32
|
-
setLoading(true)
|
|
33
|
-
setResult(null)
|
|
34
|
-
setError(null)
|
|
35
|
-
runDoctor()
|
|
36
|
-
.then(r => { setResult(r); setLoading(false) })
|
|
37
|
-
.catch(e => { setError(String(e)); setLoading(false) })
|
|
38
|
-
}, [])
|
|
39
|
-
|
|
40
|
-
useEffect(() => { runCheck() }, [runCheck])
|
|
41
|
-
|
|
42
|
-
// Auto-exit in CI mode once loading finishes
|
|
43
|
-
useEffect(() => {
|
|
44
|
-
if (isCI && !loading) {
|
|
45
|
-
const t = setTimeout(() => exit(), 100)
|
|
46
|
-
return () => clearTimeout(t)
|
|
47
|
-
}
|
|
48
|
-
return undefined
|
|
49
|
-
}, [isCI, loading, exit])
|
|
50
|
-
|
|
51
|
-
useInput((input, key) => {
|
|
52
|
-
if (input.toLowerCase() === 'r') runCheck()
|
|
53
|
-
if (input.toLowerCase() === 'q' || key.return || key.escape) exit()
|
|
54
|
-
}, { isActive: !isCI })
|
|
55
|
-
|
|
56
|
-
// Compute health score
|
|
57
|
-
const allChecks = result?.sections.flatMap(s => s.checks) ?? []
|
|
58
|
-
const passed = allChecks.filter(c => c.status === 'ok').length
|
|
59
|
-
const total = allChecks.filter(c => c.status !== 'skip').length
|
|
60
|
-
|
|
61
|
-
return (
|
|
62
|
-
<Box flexDirection="column" padding={1}>
|
|
63
|
-
<Header subtitle="doctor" />
|
|
64
|
-
|
|
65
|
-
{loading && (
|
|
66
|
-
<Text color={theme.warning}>
|
|
67
|
-
<Spinner type="dots" />
|
|
68
|
-
{' Running checks...'}
|
|
69
|
-
</Text>
|
|
70
|
-
)}
|
|
71
|
-
|
|
72
|
-
{error && (
|
|
73
|
-
<Text color={theme.error}>{'\u2717'} Error: {error}</Text>
|
|
74
|
-
)}
|
|
75
|
-
|
|
76
|
-
{result && (
|
|
77
|
-
<Box flexDirection="column">
|
|
78
|
-
{/* Health score */}
|
|
79
|
-
<Box marginBottom={1}>
|
|
80
|
-
<Text bold>Health: </Text>
|
|
81
|
-
<Text bold color={passed === total ? theme.success : theme.warning}>
|
|
82
|
-
{passed}/{total} checks passed
|
|
83
|
-
</Text>
|
|
84
|
-
</Box>
|
|
85
|
-
|
|
86
|
-
{result.sections.map((section, si) => {
|
|
87
|
-
const sectionHasFail = section.checks.some(c => c.status === 'fail')
|
|
88
|
-
return (
|
|
89
|
-
<Box key={`section-${si}`} flexDirection="column" marginBottom={1}>
|
|
90
|
-
<Text bold color={sectionHasFail ? theme.warning : theme.success}>
|
|
91
|
-
{' '}{section.title}
|
|
92
|
-
</Text>
|
|
93
|
-
{section.checks.map((check, ci) => (
|
|
94
|
-
<Box key={`${si}-check-${ci}`}>
|
|
95
|
-
<Text color={STATUS_COLOR[check.status] as any}>
|
|
96
|
-
{' '}
|
|
97
|
-
{STATUS_ICON[check.status]}
|
|
98
|
-
{' '}
|
|
99
|
-
{check.label}
|
|
100
|
-
{check.detail
|
|
101
|
-
? <Text color={theme.muted} dimColor>{' '}{check.detail}</Text>
|
|
102
|
-
: null}
|
|
103
|
-
</Text>
|
|
104
|
-
</Box>
|
|
105
|
-
))}
|
|
106
|
-
</Box>
|
|
107
|
-
)
|
|
108
|
-
})}
|
|
109
|
-
</Box>
|
|
110
|
-
)}
|
|
111
|
-
|
|
112
|
-
{/* Bottom hint */}
|
|
113
|
-
{!loading && (
|
|
114
|
-
<Box marginTop={1}>
|
|
115
|
-
<Text color={theme.muted} dimColor>
|
|
116
|
-
Press r to refresh, q to quit
|
|
117
|
-
</Text>
|
|
118
|
-
</Box>
|
|
119
|
-
)}
|
|
120
|
-
</Box>
|
|
121
|
-
)
|
|
122
|
-
}
|
package/src/ui/Header.tsx
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { Box, Text } from 'ink'
|
|
3
|
-
import { theme } from './theme.js'
|
|
4
|
-
|
|
5
|
-
interface Props {
|
|
6
|
-
subtitle?: string
|
|
7
|
-
dryRun?: boolean
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const TITLE = '\u2726 javi-forge Project scaffolding'
|
|
11
|
-
|
|
12
|
-
// Fixed inner width (characters between the box walls)
|
|
13
|
-
const BOX_WIDTH = 41
|
|
14
|
-
|
|
15
|
-
function pad(content: string): string {
|
|
16
|
-
const len = [...content].length // unicode-safe length
|
|
17
|
-
const spaces = BOX_WIDTH - len
|
|
18
|
-
return content + ' '.repeat(Math.max(0, spaces))
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export default function Header({ subtitle, dryRun }: Props) {
|
|
22
|
-
const top = '\u256d' + '\u2500'.repeat(BOX_WIDTH) + '\u256e'
|
|
23
|
-
const bottom = '\u2570' + '\u2500'.repeat(BOX_WIDTH) + '\u256f'
|
|
24
|
-
const titleLine = pad(' ' + TITLE + ' ')
|
|
25
|
-
const subLine = subtitle ? pad(' ' + subtitle + ' ') : null
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<Box flexDirection="column" marginBottom={1}>
|
|
29
|
-
<Text color={theme.muted}>{top}</Text>
|
|
30
|
-
<Box>
|
|
31
|
-
<Text color={theme.muted}>{'\u2502'}</Text>
|
|
32
|
-
<Text bold color={theme.primary}>{titleLine}</Text>
|
|
33
|
-
<Text color={theme.muted}>{'\u2502'}</Text>
|
|
34
|
-
</Box>
|
|
35
|
-
{subLine && (
|
|
36
|
-
<Box>
|
|
37
|
-
<Text color={theme.muted}>{'\u2502'}</Text>
|
|
38
|
-
<Text color={theme.muted}>{subLine}</Text>
|
|
39
|
-
<Text color={theme.muted}>{'\u2502'}</Text>
|
|
40
|
-
</Box>
|
|
41
|
-
)}
|
|
42
|
-
<Text color={theme.muted}>{bottom}</Text>
|
|
43
|
-
{dryRun && (
|
|
44
|
-
<Text color={theme.warning}> [DRY RUN]</Text>
|
|
45
|
-
)}
|
|
46
|
-
</Box>
|
|
47
|
-
)
|
|
48
|
-
}
|