hone-ai 0.5.0 → 0.10.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/README.md +47 -2
- package/package.json +5 -2
- package/src/agent-client.integration.test.ts +57 -59
- package/src/agent-client.test.ts +27 -27
- package/src/agent-client.ts +109 -77
- package/src/agent.test.ts +16 -16
- package/src/agent.ts +103 -103
- package/src/agents-md-generator.test.ts +360 -0
- package/src/agents-md-generator.ts +900 -0
- package/src/config.test.ts +209 -224
- package/src/config.ts +84 -83
- package/src/errors.test.ts +211 -208
- package/src/errors.ts +107 -101
- package/src/index.integration.test.ts +327 -223
- package/src/index.ts +163 -100
- package/src/integration-test.ts +168 -137
- package/src/logger.test.ts +67 -67
- package/src/logger.ts +8 -8
- package/src/prd-generator.integration.test.ts +50 -50
- package/src/prd-generator.test.ts +66 -25
- package/src/prd-generator.ts +280 -194
- package/src/prds.test.ts +60 -65
- package/src/prds.ts +64 -62
- package/src/prompt.test.ts +154 -155
- package/src/prompt.ts +63 -65
- package/src/run.ts +147 -147
- package/src/status.test.ts +80 -80
- package/src/status.ts +40 -42
- package/src/task-generator.test.ts +93 -66
- package/src/task-generator.ts +125 -112
package/src/config.ts
CHANGED
|
@@ -1,85 +1,86 @@
|
|
|
1
|
-
import { existsSync, mkdirSync } from 'fs'
|
|
2
|
-
import { readFile, writeFile } from 'fs/promises'
|
|
3
|
-
import { join } from 'path'
|
|
4
|
-
import * as yaml from 'js-yaml'
|
|
5
|
-
import { exitWithError, ErrorMessages } from './errors'
|
|
1
|
+
import { existsSync, mkdirSync } from 'fs'
|
|
2
|
+
import { readFile, writeFile } from 'fs/promises'
|
|
3
|
+
import { join } from 'path'
|
|
4
|
+
import * as yaml from 'js-yaml'
|
|
5
|
+
import { exitWithError, ErrorMessages } from './errors'
|
|
6
6
|
|
|
7
7
|
export interface HoneConfig {
|
|
8
|
-
defaultAgent: 'opencode' | 'claude'
|
|
8
|
+
defaultAgent: 'opencode' | 'claude'
|
|
9
9
|
models: {
|
|
10
|
-
opencode: string
|
|
11
|
-
claude: string
|
|
10
|
+
opencode: string
|
|
11
|
+
claude: string
|
|
12
12
|
// Phase-specific model overrides (optional)
|
|
13
|
-
prd?: string
|
|
14
|
-
prdToTasks?: string
|
|
15
|
-
implement?: string
|
|
16
|
-
review?: string
|
|
17
|
-
finalize?: string
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
prd?: string
|
|
14
|
+
prdToTasks?: string
|
|
15
|
+
implement?: string
|
|
16
|
+
review?: string
|
|
17
|
+
finalize?: string
|
|
18
|
+
agentsMd?: string
|
|
19
|
+
}
|
|
20
|
+
feedbackInstructions?: string
|
|
21
|
+
lintCommand?: string
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
const DEFAULT_CONFIG: HoneConfig = {
|
|
24
25
|
defaultAgent: 'claude',
|
|
25
26
|
models: {
|
|
26
27
|
opencode: 'claude-sonnet-4-20250514',
|
|
27
|
-
claude: 'claude-sonnet-4-20250514'
|
|
28
|
+
claude: 'claude-sonnet-4-20250514',
|
|
28
29
|
},
|
|
29
30
|
feedbackInstructions: 'test: bun test',
|
|
30
|
-
lintCommand: undefined
|
|
31
|
-
}
|
|
31
|
+
lintCommand: undefined,
|
|
32
|
+
}
|
|
32
33
|
|
|
33
34
|
export function getPlansDir(): string {
|
|
34
|
-
return join(process.cwd(), '.plans')
|
|
35
|
+
return join(process.cwd(), '.plans')
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
export function ensurePlansDir(): void {
|
|
38
|
-
const plansDir = getPlansDir()
|
|
39
|
+
const plansDir = getPlansDir()
|
|
39
40
|
if (!existsSync(plansDir)) {
|
|
40
|
-
mkdirSync(plansDir, { recursive: true })
|
|
41
|
+
mkdirSync(plansDir, { recursive: true })
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
export function getConfigPath(): string {
|
|
45
|
-
return join(getPlansDir(), 'hone.config.yml')
|
|
46
|
+
return join(getPlansDir(), 'hone.config.yml')
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
export async function loadConfig(): Promise<HoneConfig> {
|
|
49
|
-
ensurePlansDir()
|
|
50
|
-
|
|
51
|
-
const configPath = getConfigPath()
|
|
52
|
-
|
|
50
|
+
ensurePlansDir()
|
|
51
|
+
|
|
52
|
+
const configPath = getConfigPath()
|
|
53
|
+
|
|
53
54
|
if (!existsSync(configPath)) {
|
|
54
|
-
await writeFile(configPath, yaml.dump(DEFAULT_CONFIG))
|
|
55
|
-
return DEFAULT_CONFIG
|
|
55
|
+
await writeFile(configPath, yaml.dump(DEFAULT_CONFIG))
|
|
56
|
+
return DEFAULT_CONFIG
|
|
56
57
|
}
|
|
57
|
-
|
|
58
|
+
|
|
58
59
|
try {
|
|
59
|
-
const content = await readFile(configPath, 'utf-8')
|
|
60
|
-
const config = yaml.load(content) as Partial<HoneConfig
|
|
60
|
+
const content = await readFile(configPath, 'utf-8')
|
|
61
|
+
const config = yaml.load(content) as Partial<HoneConfig>
|
|
61
62
|
// Deep merge models to preserve defaults
|
|
62
|
-
return {
|
|
63
|
-
...DEFAULT_CONFIG,
|
|
63
|
+
return {
|
|
64
|
+
...DEFAULT_CONFIG,
|
|
64
65
|
...config,
|
|
65
|
-
models: { ...DEFAULT_CONFIG.models, ...config.models }
|
|
66
|
-
}
|
|
66
|
+
models: { ...DEFAULT_CONFIG.models, ...config.models },
|
|
67
|
+
}
|
|
67
68
|
} catch (error) {
|
|
68
|
-
console.error('Error reading config, using defaults:', error)
|
|
69
|
-
return DEFAULT_CONFIG
|
|
69
|
+
console.error('Error reading config, using defaults:', error)
|
|
70
|
+
return DEFAULT_CONFIG
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
export async function saveConfig(config: HoneConfig): Promise<void> {
|
|
74
|
-
ensurePlansDir()
|
|
75
|
-
const configPath = getConfigPath()
|
|
76
|
-
await writeFile(configPath, yaml.dump(config))
|
|
75
|
+
ensurePlansDir()
|
|
76
|
+
const configPath = getConfigPath()
|
|
77
|
+
await writeFile(configPath, yaml.dump(config))
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
export type AgentType = 'opencode' | 'claude'
|
|
80
|
+
export type AgentType = 'opencode' | 'claude'
|
|
80
81
|
|
|
81
82
|
export function isValidAgent(agent: string): agent is AgentType {
|
|
82
|
-
return agent === 'opencode' || agent === 'claude'
|
|
83
|
+
return agent === 'opencode' || agent === 'claude'
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
export async function resolveAgent(flagAgent?: string): Promise<AgentType> {
|
|
@@ -89,44 +90,44 @@ export async function resolveAgent(flagAgent?: string): Promise<AgentType> {
|
|
|
89
90
|
exitWithError(
|
|
90
91
|
'Error: Invalid agent',
|
|
91
92
|
`Agent "${flagAgent}" is not valid. Must be "opencode" or "claude".`
|
|
92
|
-
)
|
|
93
|
+
)
|
|
93
94
|
}
|
|
94
|
-
return flagAgent
|
|
95
|
+
return flagAgent
|
|
95
96
|
}
|
|
96
|
-
|
|
97
|
-
const config = await loadConfig()
|
|
98
|
-
return config.defaultAgent
|
|
97
|
+
|
|
98
|
+
const config = await loadConfig()
|
|
99
|
+
return config.defaultAgent
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
export interface InitResult {
|
|
102
|
-
plansCreated: boolean
|
|
103
|
-
configCreated: boolean
|
|
103
|
+
plansCreated: boolean
|
|
104
|
+
configCreated: boolean
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
export async function initProject(): Promise<InitResult> {
|
|
107
|
-
const plansDir = getPlansDir()
|
|
108
|
-
const configPath = getConfigPath()
|
|
109
|
-
|
|
110
|
-
const plansExisted = existsSync(plansDir)
|
|
111
|
-
const configExisted = existsSync(configPath)
|
|
112
|
-
|
|
108
|
+
const plansDir = getPlansDir()
|
|
109
|
+
const configPath = getConfigPath()
|
|
110
|
+
|
|
111
|
+
const plansExisted = existsSync(plansDir)
|
|
112
|
+
const configExisted = existsSync(configPath)
|
|
113
|
+
|
|
113
114
|
// Ensure .plans directory exists
|
|
114
115
|
if (!plansExisted) {
|
|
115
|
-
mkdirSync(plansDir, { recursive: true })
|
|
116
|
+
mkdirSync(plansDir, { recursive: true })
|
|
116
117
|
}
|
|
117
|
-
|
|
118
|
+
|
|
118
119
|
// Create config file if it doesn't exist
|
|
119
120
|
if (!configExisted) {
|
|
120
|
-
await writeFile(configPath, yaml.dump(DEFAULT_CONFIG))
|
|
121
|
+
await writeFile(configPath, yaml.dump(DEFAULT_CONFIG))
|
|
121
122
|
}
|
|
122
|
-
|
|
123
|
+
|
|
123
124
|
return {
|
|
124
125
|
plansCreated: !plansExisted,
|
|
125
|
-
configCreated: !configExisted
|
|
126
|
-
}
|
|
126
|
+
configCreated: !configExisted,
|
|
127
|
+
}
|
|
127
128
|
}
|
|
128
129
|
|
|
129
|
-
export type ModelPhase = 'prd' | 'prdToTasks' | 'implement' | 'review' | 'finalize'
|
|
130
|
+
export type ModelPhase = 'prd' | 'prdToTasks' | 'implement' | 'review' | 'finalize' | 'agentsMd'
|
|
130
131
|
|
|
131
132
|
/**
|
|
132
133
|
* Resolve the model to use for a specific phase.
|
|
@@ -137,20 +138,20 @@ export function resolveModelForPhase(
|
|
|
137
138
|
phase?: ModelPhase,
|
|
138
139
|
agent?: AgentType
|
|
139
140
|
): string {
|
|
140
|
-
const resolvedAgent = agent || config.defaultAgent
|
|
141
|
-
|
|
141
|
+
const resolvedAgent = agent || config.defaultAgent
|
|
142
|
+
|
|
142
143
|
// 1. Check phase-specific model override
|
|
143
144
|
if (phase && config.models[phase]) {
|
|
144
|
-
return config.models[phase]
|
|
145
|
+
return config.models[phase]!
|
|
145
146
|
}
|
|
146
|
-
|
|
147
|
+
|
|
147
148
|
// 2. Fall back to agent-specific model
|
|
148
149
|
if (config.models[resolvedAgent]) {
|
|
149
|
-
return config.models[resolvedAgent]
|
|
150
|
+
return config.models[resolvedAgent]
|
|
150
151
|
}
|
|
151
|
-
|
|
152
|
+
|
|
152
153
|
// 3. Fall back to default model
|
|
153
|
-
return 'claude-sonnet-4-20250514'
|
|
154
|
+
return 'claude-sonnet-4-20250514'
|
|
154
155
|
}
|
|
155
156
|
|
|
156
157
|
/**
|
|
@@ -158,29 +159,29 @@ export function resolveModelForPhase(
|
|
|
158
159
|
* Ensures model names follow the correct format.
|
|
159
160
|
*/
|
|
160
161
|
export function validateConfig(config: HoneConfig): { valid: boolean; errors: string[] } {
|
|
161
|
-
const errors: string[] = []
|
|
162
|
-
const modelRegex = /^claude-(sonnet|opus)-\d+-\d{8}
|
|
163
|
-
|
|
162
|
+
const errors: string[] = []
|
|
163
|
+
const modelRegex = /^claude-(sonnet|opus)-\d+-\d{8}$/
|
|
164
|
+
|
|
164
165
|
// Validate agent-specific models
|
|
165
166
|
if (config.models.opencode && !modelRegex.test(config.models.opencode)) {
|
|
166
|
-
errors.push(`Invalid model format for opencode: ${config.models.opencode}`)
|
|
167
|
+
errors.push(`Invalid model format for opencode: ${config.models.opencode}`)
|
|
167
168
|
}
|
|
168
|
-
|
|
169
|
+
|
|
169
170
|
if (config.models.claude && !modelRegex.test(config.models.claude)) {
|
|
170
|
-
errors.push(`Invalid model format for claude: ${config.models.claude}`)
|
|
171
|
+
errors.push(`Invalid model format for claude: ${config.models.claude}`)
|
|
171
172
|
}
|
|
172
|
-
|
|
173
|
+
|
|
173
174
|
// Validate phase-specific models if present
|
|
174
|
-
const phases: ModelPhase[] = ['prd', 'prdToTasks', 'implement', 'review', 'finalize']
|
|
175
|
+
const phases: ModelPhase[] = ['prd', 'prdToTasks', 'implement', 'review', 'finalize', 'agentsMd']
|
|
175
176
|
for (const phase of phases) {
|
|
176
|
-
const model = config.models[phase]
|
|
177
|
+
const model = config.models[phase]
|
|
177
178
|
if (model && !modelRegex.test(model)) {
|
|
178
|
-
errors.push(`Invalid model format for phase ${phase}: ${model}`)
|
|
179
|
+
errors.push(`Invalid model format for phase ${phase}: ${model}`)
|
|
179
180
|
}
|
|
180
181
|
}
|
|
181
|
-
|
|
182
|
+
|
|
182
183
|
return {
|
|
183
184
|
valid: errors.length === 0,
|
|
184
|
-
errors
|
|
185
|
-
}
|
|
185
|
+
errors,
|
|
186
|
+
}
|
|
186
187
|
}
|