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
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react'
|
|
2
|
-
import { Box, Text, useInput } from 'ink'
|
|
3
|
-
import type { MemoryOption } from '../types/index.js'
|
|
4
|
-
import { theme } from './theme.js'
|
|
5
|
-
import { useCIMode } from './CIContext.js'
|
|
6
|
-
|
|
7
|
-
const MEMORY_OPTIONS: { id: MemoryOption; label: string; description: string }[] = [
|
|
8
|
-
{ id: 'engram', label: 'Engram', description: 'SQLite-backed persistent memory with MCP' },
|
|
9
|
-
{ id: 'obsidian-brain', label: 'Obsidian Brain', description: 'Obsidian vault with Kanban + Dataview' },
|
|
10
|
-
{ id: 'memory-simple', label: 'Simple Memory', description: 'File-based .project/ memory' },
|
|
11
|
-
{ id: 'none', label: 'None', description: 'Skip memory module' },
|
|
12
|
-
]
|
|
13
|
-
|
|
14
|
-
interface Props {
|
|
15
|
-
onConfirm: (memory: MemoryOption) => void
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export default function MemorySelector({ onConfirm }: Props) {
|
|
19
|
-
const isCI = useCIMode()
|
|
20
|
-
const [cursor, setCursor] = useState(0)
|
|
21
|
-
|
|
22
|
-
// Auto-confirm in CI mode
|
|
23
|
-
useEffect(() => {
|
|
24
|
-
if (isCI) {
|
|
25
|
-
onConfirm(MEMORY_OPTIONS[cursor].id)
|
|
26
|
-
}
|
|
27
|
-
}, [isCI]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
28
|
-
|
|
29
|
-
useInput((_, key) => {
|
|
30
|
-
if (key.upArrow) setCursor(c => Math.max(0, c - 1))
|
|
31
|
-
if (key.downArrow) setCursor(c => Math.min(MEMORY_OPTIONS.length - 1, c + 1))
|
|
32
|
-
if (key.return) {
|
|
33
|
-
onConfirm(MEMORY_OPTIONS[cursor].id)
|
|
34
|
-
}
|
|
35
|
-
}, { isActive: !isCI })
|
|
36
|
-
|
|
37
|
-
return (
|
|
38
|
-
<Box flexDirection="column">
|
|
39
|
-
<Text bold>Select memory module:</Text>
|
|
40
|
-
|
|
41
|
-
<Box
|
|
42
|
-
marginTop={1}
|
|
43
|
-
flexDirection="column"
|
|
44
|
-
borderStyle="single"
|
|
45
|
-
borderLeft
|
|
46
|
-
borderRight={false}
|
|
47
|
-
borderTop={false}
|
|
48
|
-
borderBottom={false}
|
|
49
|
-
borderColor={theme.muted}
|
|
50
|
-
paddingLeft={1}
|
|
51
|
-
>
|
|
52
|
-
{MEMORY_OPTIONS.map((m, i) => (
|
|
53
|
-
<Box key={m.id}>
|
|
54
|
-
<Text color={i === cursor ? theme.primary : 'white'}>
|
|
55
|
-
{i === cursor ? '\u25b6 ' : ' '}
|
|
56
|
-
{i === cursor ? '\u25c9' : '\u25cb'} {m.label}
|
|
57
|
-
</Text>
|
|
58
|
-
<Text color={theme.muted} dimColor> {m.description}</Text>
|
|
59
|
-
</Box>
|
|
60
|
-
))}
|
|
61
|
-
</Box>
|
|
62
|
-
|
|
63
|
-
<Box marginTop={1} gap={2}>
|
|
64
|
-
<Text color={theme.primary}>
|
|
65
|
-
{MEMORY_OPTIONS[cursor].label}
|
|
66
|
-
</Text>
|
|
67
|
-
<Text color={theme.muted} dimColor>
|
|
68
|
-
{'\u2191\u2193'} navigate Enter confirm
|
|
69
|
-
</Text>
|
|
70
|
-
</Box>
|
|
71
|
-
</Box>
|
|
72
|
-
)
|
|
73
|
-
}
|
package/src/ui/NameInput.tsx
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react'
|
|
2
|
-
import { Box, Text, useInput } from 'ink'
|
|
3
|
-
import path from 'path'
|
|
4
|
-
import { theme } from './theme.js'
|
|
5
|
-
import { useCIMode } from './CIContext.js'
|
|
6
|
-
|
|
7
|
-
interface Props {
|
|
8
|
-
defaultName: string
|
|
9
|
-
onConfirm: (name: string, dir: string) => void
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const VALID_NAME = /^[a-z0-9][a-z0-9._-]*$/
|
|
13
|
-
|
|
14
|
-
function validateName(name: string): string | null {
|
|
15
|
-
if (name.length === 0) return 'Name cannot be empty'
|
|
16
|
-
if (name !== name.toLowerCase()) return 'Use lowercase only'
|
|
17
|
-
if (name.includes(' ')) return 'No spaces allowed (use - or _)'
|
|
18
|
-
if (!VALID_NAME.test(name)) return 'Use lowercase letters, numbers, hyphens, dots'
|
|
19
|
-
if (name.length > 60) return 'Name too long (max 60 chars)'
|
|
20
|
-
return null
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export default function NameInput({ defaultName, onConfirm }: Props) {
|
|
24
|
-
const isCI = useCIMode()
|
|
25
|
-
const [value, setValue] = useState(defaultName)
|
|
26
|
-
const trimmed = value.trim()
|
|
27
|
-
const error = validateName(trimmed)
|
|
28
|
-
const isValid = error === null && trimmed.length > 0
|
|
29
|
-
const targetDir = path.resolve(process.cwd(), trimmed || '.')
|
|
30
|
-
|
|
31
|
-
// Auto-confirm in CI mode
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
if (isCI && isValid) {
|
|
34
|
-
onConfirm(trimmed, targetDir)
|
|
35
|
-
}
|
|
36
|
-
}, [isCI]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
37
|
-
|
|
38
|
-
useInput((input, key) => {
|
|
39
|
-
if (key.return && isValid) {
|
|
40
|
-
onConfirm(trimmed, targetDir)
|
|
41
|
-
} else if (key.backspace || key.delete) {
|
|
42
|
-
setValue(v => v.slice(0, -1))
|
|
43
|
-
} else if (input && !key.ctrl && !key.meta) {
|
|
44
|
-
setValue(v => v + input)
|
|
45
|
-
}
|
|
46
|
-
}, { isActive: !isCI })
|
|
47
|
-
|
|
48
|
-
return (
|
|
49
|
-
<Box flexDirection="column">
|
|
50
|
-
<Text bold>Project name:</Text>
|
|
51
|
-
<Box marginTop={1}>
|
|
52
|
-
<Text color={isValid ? theme.primary : theme.error}>{'\u25b6'} </Text>
|
|
53
|
-
<Text>{value}</Text>
|
|
54
|
-
<Text color={theme.muted}>{'\u2588'}</Text>
|
|
55
|
-
</Box>
|
|
56
|
-
|
|
57
|
-
{/* Validation feedback */}
|
|
58
|
-
{trimmed.length > 0 && error && (
|
|
59
|
-
<Box marginTop={1}>
|
|
60
|
-
<Text color={theme.error}> {'\u2717'} {error}</Text>
|
|
61
|
-
</Box>
|
|
62
|
-
)}
|
|
63
|
-
{isValid && (
|
|
64
|
-
<Box marginTop={1}>
|
|
65
|
-
<Text color={theme.success}> {'\u2713'} Valid name</Text>
|
|
66
|
-
</Box>
|
|
67
|
-
)}
|
|
68
|
-
|
|
69
|
-
{/* Target directory */}
|
|
70
|
-
<Box marginTop={1}>
|
|
71
|
-
<Text color={theme.muted}> Will create: </Text>
|
|
72
|
-
<Text color={isValid ? theme.primary : theme.muted} dimColor={!isValid}>
|
|
73
|
-
{targetDir}
|
|
74
|
-
</Text>
|
|
75
|
-
</Box>
|
|
76
|
-
|
|
77
|
-
<Box marginTop={1}>
|
|
78
|
-
<Text color={theme.muted} dimColor>Enter to confirm</Text>
|
|
79
|
-
</Box>
|
|
80
|
-
</Box>
|
|
81
|
-
)
|
|
82
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react'
|
|
2
|
-
import { Box, Text, useInput } from 'ink'
|
|
3
|
-
import { theme } from './theme.js'
|
|
4
|
-
import { useCIMode } from './CIContext.js'
|
|
5
|
-
|
|
6
|
-
interface OptionItem {
|
|
7
|
-
id: string
|
|
8
|
-
label: string
|
|
9
|
-
description: string
|
|
10
|
-
default: boolean
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const OPTIONS: OptionItem[] = [
|
|
14
|
-
{ id: 'aiSync', label: 'AI Config Sync', description: 'Copy agents, skills, and configs to .ai-config/', default: true },
|
|
15
|
-
{ id: 'sdd', label: 'SDD (openspec/)', description: 'Spec-Driven Development workflow', default: true },
|
|
16
|
-
{ id: 'ghagga', label: 'GHAGGA Review', description: 'Multi-agent AI code review system', default: false },
|
|
17
|
-
]
|
|
18
|
-
|
|
19
|
-
interface Props {
|
|
20
|
-
onConfirm: (selected: { aiSync: boolean; sdd: boolean; ghagga: boolean }) => void
|
|
21
|
-
presetGhagga?: boolean
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export default function OptionSelector({ onConfirm, presetGhagga = false }: Props) {
|
|
25
|
-
const isCI = useCIMode()
|
|
26
|
-
const [cursor, setCursor] = useState(0)
|
|
27
|
-
const [selected, setSelected] = useState<Set<string>>(() => {
|
|
28
|
-
const defaults = new Set(OPTIONS.filter(o => o.default).map(o => o.id))
|
|
29
|
-
if (presetGhagga) defaults.add('ghagga')
|
|
30
|
-
return defaults
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
// Auto-confirm in CI mode with defaults
|
|
34
|
-
useEffect(() => {
|
|
35
|
-
if (isCI) {
|
|
36
|
-
onConfirm({
|
|
37
|
-
aiSync: selected.has('aiSync'),
|
|
38
|
-
sdd: selected.has('sdd'),
|
|
39
|
-
ghagga: selected.has('ghagga'),
|
|
40
|
-
})
|
|
41
|
-
}
|
|
42
|
-
}, [isCI]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
43
|
-
|
|
44
|
-
useInput((input, key) => {
|
|
45
|
-
if (key.upArrow) setCursor(c => Math.max(0, c - 1))
|
|
46
|
-
if (key.downArrow) setCursor(c => Math.min(OPTIONS.length - 1, c + 1))
|
|
47
|
-
if (input === ' ') {
|
|
48
|
-
const opt = OPTIONS[cursor].id
|
|
49
|
-
setSelected(prev => {
|
|
50
|
-
const next = new Set(prev)
|
|
51
|
-
next.has(opt) ? next.delete(opt) : next.add(opt)
|
|
52
|
-
return next
|
|
53
|
-
})
|
|
54
|
-
}
|
|
55
|
-
if (key.return) {
|
|
56
|
-
onConfirm({
|
|
57
|
-
aiSync: selected.has('aiSync'),
|
|
58
|
-
sdd: selected.has('sdd'),
|
|
59
|
-
ghagga: selected.has('ghagga'),
|
|
60
|
-
})
|
|
61
|
-
}
|
|
62
|
-
}, { isActive: !isCI })
|
|
63
|
-
|
|
64
|
-
return (
|
|
65
|
-
<Box flexDirection="column">
|
|
66
|
-
<Text bold>Select additional options:</Text>
|
|
67
|
-
|
|
68
|
-
<Box
|
|
69
|
-
marginTop={1}
|
|
70
|
-
flexDirection="column"
|
|
71
|
-
borderStyle="single"
|
|
72
|
-
borderLeft
|
|
73
|
-
borderRight={false}
|
|
74
|
-
borderTop={false}
|
|
75
|
-
borderBottom={false}
|
|
76
|
-
borderColor={theme.muted}
|
|
77
|
-
paddingLeft={1}
|
|
78
|
-
>
|
|
79
|
-
{OPTIONS.map((opt, i) => (
|
|
80
|
-
<Box key={opt.id}>
|
|
81
|
-
<Text color={i === cursor ? theme.primary : 'white'}>
|
|
82
|
-
{i === cursor ? '\u25b6 ' : ' '}
|
|
83
|
-
{selected.has(opt.id) ? '\u25c9' : '\u25cb'} {opt.label}
|
|
84
|
-
</Text>
|
|
85
|
-
<Text color={theme.muted} dimColor> {opt.description}</Text>
|
|
86
|
-
</Box>
|
|
87
|
-
))}
|
|
88
|
-
</Box>
|
|
89
|
-
|
|
90
|
-
<Box marginTop={1} gap={2}>
|
|
91
|
-
<Text color={selected.size > 0 ? theme.accent : theme.muted}>
|
|
92
|
-
{selected.size} selected
|
|
93
|
-
</Text>
|
|
94
|
-
<Text color={theme.muted} dimColor>
|
|
95
|
-
{'\u2191\u2193'} navigate Space toggle Enter confirm
|
|
96
|
-
</Text>
|
|
97
|
-
</Box>
|
|
98
|
-
</Box>
|
|
99
|
-
)
|
|
100
|
-
}
|
package/src/ui/Progress.tsx
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useRef } from 'react'
|
|
2
|
-
import { Box, Text } from 'ink'
|
|
3
|
-
import Spinner from 'ink-spinner'
|
|
4
|
-
import type { InitStep } from '../types/index.js'
|
|
5
|
-
import { theme } from './theme.js'
|
|
6
|
-
|
|
7
|
-
interface Props {
|
|
8
|
-
steps: InitStep[]
|
|
9
|
-
projectName?: string
|
|
10
|
-
contextLine?: string
|
|
11
|
-
onDone?: () => void
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const STATUS_ICON: Record<string, string> = {
|
|
15
|
-
pending: '\u25cb',
|
|
16
|
-
done: '\u2713',
|
|
17
|
-
error: '\u2717',
|
|
18
|
-
skipped: '\u2013',
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const STATUS_COLOR: Record<string, string> = {
|
|
22
|
-
pending: theme.muted,
|
|
23
|
-
running: theme.warning,
|
|
24
|
-
done: theme.success,
|
|
25
|
-
error: theme.error,
|
|
26
|
-
skipped: theme.muted,
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export default function Progress({ steps, projectName, contextLine, onDone }: Props) {
|
|
30
|
-
const doneRef = useRef(false)
|
|
31
|
-
|
|
32
|
-
const total = steps.length
|
|
33
|
-
const completed = steps.filter(s => s.status === 'done' || s.status === 'skipped').length
|
|
34
|
-
const hasError = steps.some(s => s.status === 'error')
|
|
35
|
-
const allFinished = total > 0 && steps.every(
|
|
36
|
-
s => s.status === 'done' || s.status === 'error' || s.status === 'skipped'
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
useEffect(() => {
|
|
40
|
-
if (allFinished && !hasError && !doneRef.current && onDone) {
|
|
41
|
-
doneRef.current = true
|
|
42
|
-
const t = setTimeout(onDone, 600)
|
|
43
|
-
return () => clearTimeout(t)
|
|
44
|
-
}
|
|
45
|
-
return undefined
|
|
46
|
-
}, [allFinished, hasError, onDone])
|
|
47
|
-
|
|
48
|
-
return (
|
|
49
|
-
<Box flexDirection="column">
|
|
50
|
-
{/* Context header */}
|
|
51
|
-
<Box marginBottom={1} flexDirection="column">
|
|
52
|
-
{contextLine && (
|
|
53
|
-
<Text color={theme.muted}>
|
|
54
|
-
{'Initializing: '}
|
|
55
|
-
<Text color={theme.primary}>{contextLine}</Text>
|
|
56
|
-
</Text>
|
|
57
|
-
)}
|
|
58
|
-
{total > 0 && (
|
|
59
|
-
<Text color={theme.muted}>
|
|
60
|
-
{'Progress: '}
|
|
61
|
-
<Text color={completed === total ? theme.success : theme.warning}>
|
|
62
|
-
{completed}/{total} steps
|
|
63
|
-
</Text>
|
|
64
|
-
</Text>
|
|
65
|
-
)}
|
|
66
|
-
</Box>
|
|
67
|
-
|
|
68
|
-
<Box flexDirection="column">
|
|
69
|
-
{steps.map(step => (
|
|
70
|
-
<Box key={step.id} marginLeft={2}>
|
|
71
|
-
{step.status === 'running' ? (
|
|
72
|
-
<Text color={theme.warning}>
|
|
73
|
-
<Spinner type="dots" />
|
|
74
|
-
{' '}{step.label}
|
|
75
|
-
{step.detail ? <Text color={theme.muted} dimColor> {step.detail}</Text> : null}
|
|
76
|
-
</Text>
|
|
77
|
-
) : (
|
|
78
|
-
<Text color={STATUS_COLOR[step.status] as any}>
|
|
79
|
-
{STATUS_ICON[step.status]} {step.label}
|
|
80
|
-
{step.detail ? <Text color={theme.muted} dimColor> {step.detail}</Text> : null}
|
|
81
|
-
</Text>
|
|
82
|
-
)}
|
|
83
|
-
</Box>
|
|
84
|
-
))}
|
|
85
|
-
</Box>
|
|
86
|
-
</Box>
|
|
87
|
-
)
|
|
88
|
-
}
|
package/src/ui/StackSelector.tsx
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect, useRef } from 'react'
|
|
2
|
-
import { Box, Text, useInput } from 'ink'
|
|
3
|
-
import { detectStack, STACK_LABELS } from '../lib/common.js'
|
|
4
|
-
import type { Stack } from '../types/index.js'
|
|
5
|
-
import { theme } from './theme.js'
|
|
6
|
-
import { useCIMode } from './CIContext.js'
|
|
7
|
-
|
|
8
|
-
const ALL_STACKS: Stack[] = ['node', 'python', 'go', 'rust', 'java-gradle', 'java-maven', 'elixir']
|
|
9
|
-
|
|
10
|
-
interface Props {
|
|
11
|
-
projectDir: string
|
|
12
|
-
onConfirm: (stack: Stack) => void
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export default function StackSelector({ projectDir, onConfirm }: Props) {
|
|
16
|
-
const isCI = useCIMode()
|
|
17
|
-
const autoConfirmed = useRef(false)
|
|
18
|
-
const [cursor, setCursor] = useState(0)
|
|
19
|
-
const [detectedStack, setDetectedStack] = useState<Stack | null>(null)
|
|
20
|
-
const [detecting, setDetecting] = useState(true)
|
|
21
|
-
|
|
22
|
-
useEffect(() => {
|
|
23
|
-
detectStack(projectDir).then(result => {
|
|
24
|
-
if (result) {
|
|
25
|
-
setDetectedStack(result.stackType)
|
|
26
|
-
const idx = ALL_STACKS.indexOf(result.stackType)
|
|
27
|
-
if (idx >= 0) setCursor(idx)
|
|
28
|
-
}
|
|
29
|
-
setDetecting(false)
|
|
30
|
-
})
|
|
31
|
-
}, [projectDir])
|
|
32
|
-
|
|
33
|
-
// Auto-confirm in CI mode once detection is done
|
|
34
|
-
useEffect(() => {
|
|
35
|
-
if (isCI && !detecting && !autoConfirmed.current) {
|
|
36
|
-
autoConfirmed.current = true
|
|
37
|
-
onConfirm(ALL_STACKS[cursor])
|
|
38
|
-
}
|
|
39
|
-
}, [isCI, detecting]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
40
|
-
|
|
41
|
-
useInput((input, key) => {
|
|
42
|
-
if (detecting) return
|
|
43
|
-
if (key.upArrow) setCursor(c => Math.max(0, c - 1))
|
|
44
|
-
if (key.downArrow) setCursor(c => Math.min(ALL_STACKS.length - 1, c + 1))
|
|
45
|
-
if (key.return) {
|
|
46
|
-
onConfirm(ALL_STACKS[cursor])
|
|
47
|
-
}
|
|
48
|
-
}, { isActive: !isCI })
|
|
49
|
-
|
|
50
|
-
if (detecting) {
|
|
51
|
-
return (
|
|
52
|
-
<Box>
|
|
53
|
-
<Text color={theme.muted}>Detecting project stack...</Text>
|
|
54
|
-
</Box>
|
|
55
|
-
)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return (
|
|
59
|
-
<Box flexDirection="column">
|
|
60
|
-
<Text bold>Select project stack:</Text>
|
|
61
|
-
{detectedStack && (
|
|
62
|
-
<Text color={theme.success}>
|
|
63
|
-
{' '}Auto-detected: {STACK_LABELS[detectedStack]}
|
|
64
|
-
</Text>
|
|
65
|
-
)}
|
|
66
|
-
|
|
67
|
-
<Box
|
|
68
|
-
marginTop={1}
|
|
69
|
-
flexDirection="column"
|
|
70
|
-
borderStyle="single"
|
|
71
|
-
borderLeft
|
|
72
|
-
borderRight={false}
|
|
73
|
-
borderTop={false}
|
|
74
|
-
borderBottom={false}
|
|
75
|
-
borderColor={theme.muted}
|
|
76
|
-
paddingLeft={1}
|
|
77
|
-
>
|
|
78
|
-
{ALL_STACKS.map((stack, i) => (
|
|
79
|
-
<Box key={stack}>
|
|
80
|
-
<Text color={i === cursor ? theme.primary : 'white'}>
|
|
81
|
-
{i === cursor ? '\u25b6 ' : ' '}
|
|
82
|
-
{stack === detectedStack ? '\u25c9' : '\u25cb'} {STACK_LABELS[stack]}
|
|
83
|
-
{stack === detectedStack && (
|
|
84
|
-
<Text color={theme.success} dimColor> (detected)</Text>
|
|
85
|
-
)}
|
|
86
|
-
</Text>
|
|
87
|
-
</Box>
|
|
88
|
-
))}
|
|
89
|
-
</Box>
|
|
90
|
-
|
|
91
|
-
<Box marginTop={1} gap={2}>
|
|
92
|
-
<Text color={theme.primary}>
|
|
93
|
-
{STACK_LABELS[ALL_STACKS[cursor]]}
|
|
94
|
-
</Text>
|
|
95
|
-
<Text color={theme.muted} dimColor>
|
|
96
|
-
{'\u2191\u2193'} navigate Enter confirm
|
|
97
|
-
</Text>
|
|
98
|
-
</Box>
|
|
99
|
-
</Box>
|
|
100
|
-
)
|
|
101
|
-
}
|
package/src/ui/Summary.tsx
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import React, { useEffect } from 'react'
|
|
2
|
-
import { Box, Text, useApp, useInput } from 'ink'
|
|
3
|
-
import type { InitStep } from '../types/index.js'
|
|
4
|
-
import { theme } from './theme.js'
|
|
5
|
-
import { useCIMode } from './CIContext.js'
|
|
6
|
-
|
|
7
|
-
interface Props {
|
|
8
|
-
steps: InitStep[]
|
|
9
|
-
dryRun: boolean
|
|
10
|
-
projectName: string
|
|
11
|
-
stack?: string
|
|
12
|
-
elapsedMs?: number
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** Map stack to the install command hint */
|
|
16
|
-
function getInstallHint(stack?: string): string | null {
|
|
17
|
-
switch (stack) {
|
|
18
|
-
case 'node': return 'pnpm install'
|
|
19
|
-
case 'python': return 'pip install -r requirements.txt'
|
|
20
|
-
case 'go': return 'go mod tidy'
|
|
21
|
-
case 'rust': return 'cargo build'
|
|
22
|
-
case 'java-gradle': return './gradlew build'
|
|
23
|
-
case 'java-maven': return 'mvn install'
|
|
24
|
-
case 'elixir': return 'mix deps.get'
|
|
25
|
-
default: return null
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export default function Summary({ steps, dryRun, projectName, stack, elapsedMs }: Props) {
|
|
30
|
-
const { exit } = useApp()
|
|
31
|
-
const isCI = useCIMode()
|
|
32
|
-
|
|
33
|
-
const done = steps.filter(s => s.status === 'done').length
|
|
34
|
-
const skipped = steps.filter(s => s.status === 'skipped').length
|
|
35
|
-
const errors = steps.filter(s => s.status === 'error')
|
|
36
|
-
const elapsed = elapsedMs != null
|
|
37
|
-
? `${(elapsedMs / 1000).toFixed(1)}s`
|
|
38
|
-
: null
|
|
39
|
-
|
|
40
|
-
// Auto-exit in CI mode
|
|
41
|
-
useEffect(() => {
|
|
42
|
-
if (isCI) {
|
|
43
|
-
const t = setTimeout(() => exit(), 100)
|
|
44
|
-
return () => clearTimeout(t)
|
|
45
|
-
}
|
|
46
|
-
return undefined
|
|
47
|
-
}, [isCI, exit])
|
|
48
|
-
|
|
49
|
-
useInput((_, key) => {
|
|
50
|
-
if (key.return || key.escape) exit()
|
|
51
|
-
}, { isActive: !isCI })
|
|
52
|
-
|
|
53
|
-
const installHint = getInstallHint(stack)
|
|
54
|
-
|
|
55
|
-
return (
|
|
56
|
-
<Box flexDirection="column">
|
|
57
|
-
{/* Title */}
|
|
58
|
-
<Text bold color={errors.length > 0 ? theme.warning : theme.success}>
|
|
59
|
-
{dryRun ? '\u25cb Dry run complete' : '\u2713 Project scaffolded'}
|
|
60
|
-
{elapsed && <Text color={theme.muted}> Completed in {elapsed}</Text>}
|
|
61
|
-
</Text>
|
|
62
|
-
|
|
63
|
-
{/* Project info */}
|
|
64
|
-
<Box marginTop={1}>
|
|
65
|
-
<Text color={theme.muted}> Project: </Text>
|
|
66
|
-
<Text color={theme.primary} bold>{projectName}</Text>
|
|
67
|
-
{stack && <Text color={theme.muted}> ({stack})</Text>}
|
|
68
|
-
</Box>
|
|
69
|
-
|
|
70
|
-
{/* Dry run note */}
|
|
71
|
-
{dryRun && (
|
|
72
|
-
<Box marginTop={1}>
|
|
73
|
-
<Text color={theme.warning} bold> No changes were made (dry run)</Text>
|
|
74
|
-
</Box>
|
|
75
|
-
)}
|
|
76
|
-
|
|
77
|
-
{/* Step details */}
|
|
78
|
-
<Box marginTop={1} flexDirection="column">
|
|
79
|
-
{steps.map(step => (
|
|
80
|
-
<Box key={step.id} marginLeft={2}>
|
|
81
|
-
{step.status === 'done' ? (
|
|
82
|
-
<Text color={theme.success}>
|
|
83
|
-
{'\u2713'} {step.label}
|
|
84
|
-
{step.detail ? <Text color={theme.muted} dimColor> {step.detail}</Text> : null}
|
|
85
|
-
</Text>
|
|
86
|
-
) : step.status === 'skipped' ? (
|
|
87
|
-
<Text color={theme.muted}>
|
|
88
|
-
{'\u2013'} {step.label}
|
|
89
|
-
{step.detail ? <Text dimColor> {step.detail}</Text> : null}
|
|
90
|
-
</Text>
|
|
91
|
-
) : step.status === 'error' ? (
|
|
92
|
-
<Text color={theme.error}>
|
|
93
|
-
{'\u2717'} {step.label}
|
|
94
|
-
{step.detail ? <Text color={theme.muted} dimColor> {step.detail}</Text> : null}
|
|
95
|
-
</Text>
|
|
96
|
-
) : null}
|
|
97
|
-
</Box>
|
|
98
|
-
))}
|
|
99
|
-
</Box>
|
|
100
|
-
|
|
101
|
-
{/* Totals */}
|
|
102
|
-
<Box marginTop={1} flexDirection="column">
|
|
103
|
-
<Text color={theme.success}> {'\u2713'} {done} steps completed</Text>
|
|
104
|
-
{skipped > 0 && (
|
|
105
|
-
<Text color={theme.muted}> {'\u2013'} {skipped} steps skipped</Text>
|
|
106
|
-
)}
|
|
107
|
-
{errors.length > 0 && (
|
|
108
|
-
<Box flexDirection="column">
|
|
109
|
-
<Text color={theme.error}> {'\u2717'} {errors.length} errors:</Text>
|
|
110
|
-
{errors.map(e => (
|
|
111
|
-
<Text key={`err-${e.id}`} color={theme.error}> {'\u2022'} {e.label}: {e.detail}</Text>
|
|
112
|
-
))}
|
|
113
|
-
</Box>
|
|
114
|
-
)}
|
|
115
|
-
</Box>
|
|
116
|
-
|
|
117
|
-
{/* Next steps */}
|
|
118
|
-
{!dryRun && errors.length === 0 && (
|
|
119
|
-
<Box marginTop={1} flexDirection="column">
|
|
120
|
-
<Text color={theme.muted} bold> Next steps:</Text>
|
|
121
|
-
<Text color={theme.primary}> cd {projectName}</Text>
|
|
122
|
-
{installHint && <Text color={theme.primary}> {installHint}</Text>}
|
|
123
|
-
<Text color={theme.primary}> npx javi-ai sync</Text>
|
|
124
|
-
<Text color={theme.primary}> javi-forge doctor</Text>
|
|
125
|
-
</Box>
|
|
126
|
-
)}
|
|
127
|
-
|
|
128
|
-
{/* Exit hint */}
|
|
129
|
-
<Box marginTop={1}>
|
|
130
|
-
<Text color={theme.muted} dimColor>Press Enter to exit</Text>
|
|
131
|
-
</Box>
|
|
132
|
-
</Box>
|
|
133
|
-
)
|
|
134
|
-
}
|
package/src/ui/Welcome.tsx
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import React, { useEffect } from 'react'
|
|
2
|
-
import { Box, Text } from 'ink'
|
|
3
|
-
import Spinner from 'ink-spinner'
|
|
4
|
-
import Header from './Header.js'
|
|
5
|
-
import { theme } from './theme.js'
|
|
6
|
-
import { useCIMode } from './CIContext.js'
|
|
7
|
-
|
|
8
|
-
interface Props {
|
|
9
|
-
onDone: () => void
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export default function Welcome({ onDone }: Props) {
|
|
13
|
-
const isCI = useCIMode()
|
|
14
|
-
|
|
15
|
-
useEffect(() => {
|
|
16
|
-
// Skip welcome delay in CI mode
|
|
17
|
-
const timer = setTimeout(onDone, isCI ? 0 : 1500)
|
|
18
|
-
return () => clearTimeout(timer)
|
|
19
|
-
}, [onDone, isCI])
|
|
20
|
-
|
|
21
|
-
return (
|
|
22
|
-
<Box flexDirection="column" padding={1}>
|
|
23
|
-
<Header />
|
|
24
|
-
|
|
25
|
-
<Box flexDirection="column" marginTop={1} marginLeft={2}>
|
|
26
|
-
<Text>Bootstrap AI-ready projects with:</Text>
|
|
27
|
-
<Box marginTop={1} flexDirection="column">
|
|
28
|
-
<Text>
|
|
29
|
-
<Text color={theme.primary}>{'\u25c6'} Templates </Text>
|
|
30
|
-
<Text color={theme.muted}> Go, Java, Node, Python, Rust CI</Text>
|
|
31
|
-
</Text>
|
|
32
|
-
<Text>
|
|
33
|
-
<Text color={theme.success}>{'\u25c6'} Memory </Text>
|
|
34
|
-
<Text color={theme.muted}> Engram, Obsidian Brain</Text>
|
|
35
|
-
</Text>
|
|
36
|
-
<Text>
|
|
37
|
-
<Text color={theme.accent}>{'\u25c6'} SDD </Text>
|
|
38
|
-
<Text color={theme.muted}> Spec-Driven Development</Text>
|
|
39
|
-
</Text>
|
|
40
|
-
<Text>
|
|
41
|
-
<Text color={theme.warning}>{'\u25c6'} Review </Text>
|
|
42
|
-
<Text color={theme.muted}> GHAGGA code review</Text>
|
|
43
|
-
</Text>
|
|
44
|
-
</Box>
|
|
45
|
-
<Box marginTop={1}>
|
|
46
|
-
<Text color={theme.muted}>
|
|
47
|
-
<Spinner type="dots" />
|
|
48
|
-
{' '}Detecting your environment...
|
|
49
|
-
</Text>
|
|
50
|
-
</Box>
|
|
51
|
-
</Box>
|
|
52
|
-
</Box>
|
|
53
|
-
)
|
|
54
|
-
}
|
package/src/ui/theme.ts
DELETED
package/stryker.config.json
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"testRunner": "vitest",
|
|
3
|
-
"mutate": [
|
|
4
|
-
"src/lib/frontmatter.ts",
|
|
5
|
-
"src/lib/common.ts",
|
|
6
|
-
"src/lib/template.ts",
|
|
7
|
-
"src/commands/init.ts",
|
|
8
|
-
"src/commands/doctor.ts",
|
|
9
|
-
"src/commands/analyze.ts",
|
|
10
|
-
"src/constants.ts"
|
|
11
|
-
],
|
|
12
|
-
"plugins": [
|
|
13
|
-
"@stryker-mutator/vitest-runner",
|
|
14
|
-
"@stryker-mutator/typescript-checker"
|
|
15
|
-
],
|
|
16
|
-
"checkers": ["typescript"],
|
|
17
|
-
"tsconfigFile": "tsconfig.json",
|
|
18
|
-
"thresholds": { "high": 80, "low": 60, "break": 50 }
|
|
19
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ESNext",
|
|
4
|
-
"module": "NodeNext",
|
|
5
|
-
"moduleResolution": "NodeNext",
|
|
6
|
-
"jsx": "react",
|
|
7
|
-
"jsxFactory": "React.createElement",
|
|
8
|
-
"outDir": "dist",
|
|
9
|
-
"rootDir": "src",
|
|
10
|
-
"strict": true,
|
|
11
|
-
"esModuleInterop": true,
|
|
12
|
-
"skipLibCheck": true,
|
|
13
|
-
"declaration": true,
|
|
14
|
-
"declarationMap": true,
|
|
15
|
-
"sourceMap": true
|
|
16
|
-
},
|
|
17
|
-
"include": ["src/**/*"],
|
|
18
|
-
"exclude": ["node_modules", "dist"]
|
|
19
|
-
}
|