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,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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }