hone-ai 0.2.0 → 0.9.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 +8 -5
- 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
|
@@ -1,91 +1,91 @@
|
|
|
1
|
-
import { describe, test, expect, beforeAll, afterAll } from 'bun:test'
|
|
2
|
-
import { existsSync, rmSync, mkdirSync } from 'fs'
|
|
3
|
-
import { readFile } from 'fs/promises'
|
|
4
|
-
import { join } from 'path'
|
|
5
|
-
import { slugify } from './prd-generator'
|
|
1
|
+
import { describe, test, expect, beforeAll, afterAll } from 'bun:test'
|
|
2
|
+
import { existsSync, rmSync, mkdirSync } from 'fs'
|
|
3
|
+
import { readFile } from 'fs/promises'
|
|
4
|
+
import { join } from 'path'
|
|
5
|
+
import { slugify } from './prd-generator'
|
|
6
6
|
|
|
7
7
|
describe('PRD Generator Integration', () => {
|
|
8
|
-
const testPlansDir = join(process.cwd(), '.plans-test')
|
|
9
|
-
const originalCwd = process.cwd()
|
|
8
|
+
const testPlansDir = join(process.cwd(), '.plans-test')
|
|
9
|
+
const originalCwd = process.cwd()
|
|
10
10
|
|
|
11
11
|
beforeAll(() => {
|
|
12
12
|
// Create test .plans directory
|
|
13
13
|
if (existsSync(testPlansDir)) {
|
|
14
|
-
rmSync(testPlansDir, { recursive: true })
|
|
14
|
+
rmSync(testPlansDir, { recursive: true })
|
|
15
15
|
}
|
|
16
|
-
mkdirSync(testPlansDir, { recursive: true })
|
|
17
|
-
})
|
|
16
|
+
mkdirSync(testPlansDir, { recursive: true })
|
|
17
|
+
})
|
|
18
18
|
|
|
19
19
|
afterAll(() => {
|
|
20
20
|
// Cleanup
|
|
21
21
|
if (existsSync(testPlansDir)) {
|
|
22
|
-
rmSync(testPlansDir, { recursive: true })
|
|
22
|
+
rmSync(testPlansDir, { recursive: true })
|
|
23
23
|
}
|
|
24
|
-
})
|
|
24
|
+
})
|
|
25
25
|
|
|
26
26
|
describe('slugify', () => {
|
|
27
27
|
test('converts to lowercase', () => {
|
|
28
|
-
expect(slugify('Email Validation')).toBe('email-validation')
|
|
29
|
-
})
|
|
28
|
+
expect(slugify('Email Validation')).toBe('email-validation')
|
|
29
|
+
})
|
|
30
30
|
|
|
31
31
|
test('replaces spaces with hyphens', () => {
|
|
32
|
-
expect(slugify('user profile page')).toBe('user-profile-page')
|
|
33
|
-
})
|
|
32
|
+
expect(slugify('user profile page')).toBe('user-profile-page')
|
|
33
|
+
})
|
|
34
34
|
|
|
35
35
|
test('removes special characters', () => {
|
|
36
|
-
expect(slugify('user@email.com validation!')).toBe('useremailcom-validation')
|
|
37
|
-
})
|
|
36
|
+
expect(slugify('user@email.com validation!')).toBe('useremailcom-validation')
|
|
37
|
+
})
|
|
38
38
|
|
|
39
39
|
test('trims leading/trailing spaces', () => {
|
|
40
|
-
expect(slugify(' feature name ')).toBe('feature-name')
|
|
41
|
-
})
|
|
40
|
+
expect(slugify(' feature name ')).toBe('feature-name')
|
|
41
|
+
})
|
|
42
42
|
|
|
43
43
|
test('removes leading/trailing hyphens', () => {
|
|
44
|
-
expect(slugify('---feature-name---')).toBe('feature-name')
|
|
45
|
-
})
|
|
44
|
+
expect(slugify('---feature-name---')).toBe('feature-name')
|
|
45
|
+
})
|
|
46
46
|
|
|
47
47
|
test('collapses multiple hyphens', () => {
|
|
48
|
-
expect(slugify('feature--name')).toBe('feature-name')
|
|
49
|
-
expect(slugify('feature---name')).toBe('feature-name')
|
|
50
|
-
})
|
|
48
|
+
expect(slugify('feature--name')).toBe('feature-name')
|
|
49
|
+
expect(slugify('feature---name')).toBe('feature-name')
|
|
50
|
+
})
|
|
51
51
|
|
|
52
52
|
test('truncates to 50 characters', () => {
|
|
53
|
-
const longName = 'this-is-a-very-long-feature-name-that-exceeds-fifty-characters'
|
|
54
|
-
const result = slugify(longName)
|
|
55
|
-
expect(result.length).toBeLessThanOrEqual(50)
|
|
56
|
-
expect(result.length).toBe(50)
|
|
57
|
-
expect(result).toBe('this-is-a-very-long-feature-name-that-exceeds-fift')
|
|
58
|
-
})
|
|
53
|
+
const longName = 'this-is-a-very-long-feature-name-that-exceeds-fifty-characters'
|
|
54
|
+
const result = slugify(longName)
|
|
55
|
+
expect(result.length).toBeLessThanOrEqual(50)
|
|
56
|
+
expect(result.length).toBe(50)
|
|
57
|
+
expect(result).toBe('this-is-a-very-long-feature-name-that-exceeds-fift')
|
|
58
|
+
})
|
|
59
59
|
|
|
60
60
|
test('handles empty string', () => {
|
|
61
|
-
expect(slugify('')).toBe('')
|
|
62
|
-
})
|
|
61
|
+
expect(slugify('')).toBe('')
|
|
62
|
+
})
|
|
63
63
|
|
|
64
64
|
test('handles string with only special characters', () => {
|
|
65
|
-
expect(slugify('!@#$%^&*()')).toBe('')
|
|
66
|
-
})
|
|
65
|
+
expect(slugify('!@#$%^&*()')).toBe('')
|
|
66
|
+
})
|
|
67
67
|
|
|
68
68
|
test('handles mixed case with numbers', () => {
|
|
69
|
-
expect(slugify('Feature123Test')).toBe('feature123test')
|
|
70
|
-
})
|
|
69
|
+
expect(slugify('Feature123Test')).toBe('feature123test')
|
|
70
|
+
})
|
|
71
71
|
|
|
72
72
|
test('preserves existing hyphens', () => {
|
|
73
|
-
expect(slugify('my-feature-name')).toBe('my-feature-name')
|
|
74
|
-
})
|
|
73
|
+
expect(slugify('my-feature-name')).toBe('my-feature-name')
|
|
74
|
+
})
|
|
75
75
|
|
|
76
76
|
test('handles unicode characters', () => {
|
|
77
|
-
expect(slugify('feature café')).toBe('feature-caf')
|
|
78
|
-
})
|
|
79
|
-
})
|
|
77
|
+
expect(slugify('feature café')).toBe('feature-caf')
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
80
|
|
|
81
81
|
describe('Model configuration', () => {
|
|
82
82
|
test('uses correct model name format', async () => {
|
|
83
|
-
const { loadConfig } = await import('./config')
|
|
84
|
-
const config = await loadConfig()
|
|
85
|
-
|
|
83
|
+
const { loadConfig } = await import('./config')
|
|
84
|
+
const config = await loadConfig()
|
|
85
|
+
|
|
86
86
|
// Model name should match Anthropic API format
|
|
87
|
-
expect(config.models.claude).toMatch(/^claude-sonnet-4-\d{8}$/)
|
|
88
|
-
expect(config.models.opencode).toMatch(/^claude-sonnet-4-\d{8}$/)
|
|
89
|
-
})
|
|
90
|
-
})
|
|
91
|
-
})
|
|
87
|
+
expect(config.models.claude).toMatch(/^claude-sonnet-4-\d{8}$/)
|
|
88
|
+
expect(config.models.opencode).toMatch(/^claude-sonnet-4-\d{8}$/)
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
})
|
|
@@ -1,34 +1,75 @@
|
|
|
1
|
-
import { describe, it, expect } from 'bun:test'
|
|
2
|
-
import { slugify } from './prd-generator'
|
|
1
|
+
import { describe, it, expect } from 'bun:test'
|
|
2
|
+
import { slugify } from './prd-generator'
|
|
3
|
+
import { readFile } from 'fs/promises'
|
|
4
|
+
import { join } from 'path'
|
|
3
5
|
|
|
4
6
|
describe('slugify', () => {
|
|
5
7
|
it('should convert to lowercase', () => {
|
|
6
|
-
expect(slugify('My Feature')).toBe('my-feature')
|
|
7
|
-
})
|
|
8
|
-
|
|
8
|
+
expect(slugify('My Feature')).toBe('my-feature')
|
|
9
|
+
})
|
|
10
|
+
|
|
9
11
|
it('should replace spaces with hyphens', () => {
|
|
10
|
-
expect(slugify('user authentication system')).toBe('user-authentication-system')
|
|
11
|
-
})
|
|
12
|
-
|
|
12
|
+
expect(slugify('user authentication system')).toBe('user-authentication-system')
|
|
13
|
+
})
|
|
14
|
+
|
|
13
15
|
it('should remove special characters', () => {
|
|
14
|
-
expect(slugify('Feature (with) special! chars@')).toBe('feature-with-special-chars')
|
|
15
|
-
})
|
|
16
|
-
|
|
16
|
+
expect(slugify('Feature (with) special! chars@')).toBe('feature-with-special-chars')
|
|
17
|
+
})
|
|
18
|
+
|
|
17
19
|
it('should collapse multiple hyphens', () => {
|
|
18
|
-
expect(slugify('feature with spaces')).toBe('feature-with-spaces')
|
|
19
|
-
})
|
|
20
|
-
|
|
20
|
+
expect(slugify('feature with spaces')).toBe('feature-with-spaces')
|
|
21
|
+
})
|
|
22
|
+
|
|
21
23
|
it('should trim to 50 characters', () => {
|
|
22
|
-
const longString = 'this is a very long feature name that exceeds fifty characters'
|
|
23
|
-
const result = slugify(longString)
|
|
24
|
-
expect(result.length).toBeLessThanOrEqual(50)
|
|
25
|
-
})
|
|
26
|
-
|
|
24
|
+
const longString = 'this is a very long feature name that exceeds fifty characters'
|
|
25
|
+
const result = slugify(longString)
|
|
26
|
+
expect(result.length).toBeLessThanOrEqual(50)
|
|
27
|
+
})
|
|
28
|
+
|
|
27
29
|
it('should handle empty string', () => {
|
|
28
|
-
expect(slugify('')).toBe('')
|
|
29
|
-
})
|
|
30
|
-
|
|
30
|
+
expect(slugify('')).toBe('')
|
|
31
|
+
})
|
|
32
|
+
|
|
31
33
|
it('should trim leading and trailing hyphens', () => {
|
|
32
|
-
expect(slugify(' feature ')).toBe('feature')
|
|
33
|
-
})
|
|
34
|
-
})
|
|
34
|
+
expect(slugify(' feature ')).toBe('feature')
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
describe('PRD system prompts', () => {
|
|
39
|
+
it('should include file and URL processing instructions', async () => {
|
|
40
|
+
const sourceFile = await readFile(join(__dirname, 'prd-generator.ts'), 'utf-8')
|
|
41
|
+
|
|
42
|
+
// Verify clarifying questions prompt includes file/URL instructions
|
|
43
|
+
expect(sourceFile).toContain('FILE AND URL PROCESSING:')
|
|
44
|
+
expect(sourceFile).toContain(
|
|
45
|
+
'automatically read and incorporate their content using the Read tool'
|
|
46
|
+
)
|
|
47
|
+
expect(sourceFile).toContain(
|
|
48
|
+
'automatically fetch and incorporate their content using the WebFetch tool'
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
// Verify both prompts contain the instructions
|
|
52
|
+
const fileUrlInstructions = sourceFile.match(
|
|
53
|
+
/FILE AND URL PROCESSING:(.*?)(?=\n\n|\nRules:|\nGenerate)/gs
|
|
54
|
+
)
|
|
55
|
+
expect(fileUrlInstructions).toBeTruthy()
|
|
56
|
+
expect(fileUrlInstructions?.length).toBe(2) // One in clarifying questions, one in PRD generation
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('should include reference access failure handling instructions in clarifying questions', async () => {
|
|
60
|
+
const sourceFile = await readFile(join(__dirname, 'prd-generator.ts'), 'utf-8')
|
|
61
|
+
|
|
62
|
+
// Verify specific reference failure handling instructions
|
|
63
|
+
expect(sourceFile).toContain('REFERENCE ACCESS FAILURES: When file reads or URL fetches fail:')
|
|
64
|
+
expect(sourceFile).toContain('Continue with question generation using available context')
|
|
65
|
+
expect(sourceFile).toContain(
|
|
66
|
+
"Ask clarifying questions about the inaccessible reference's intended purpose or relevance"
|
|
67
|
+
)
|
|
68
|
+
expect(sourceFile).toContain(
|
|
69
|
+
"I couldn't access [file/URL]. What was its relevance to this feature?"
|
|
70
|
+
)
|
|
71
|
+
expect(sourceFile).toContain(
|
|
72
|
+
'Prioritize these clarification questions early in the Q&A session when references are critical'
|
|
73
|
+
)
|
|
74
|
+
})
|
|
75
|
+
})
|