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/src/prds.test.ts CHANGED
@@ -1,26 +1,21 @@
1
- import { describe, test, expect } from 'bun:test';
2
- import {
3
- extractFeatureName,
4
- calculateStatus,
5
- type TaskFile,
6
- type Task
7
- } from './prds';
1
+ import { describe, test, expect } from 'bun:test'
2
+ import { extractFeatureName, calculateStatus, type TaskFile, type Task } from './prds'
8
3
 
9
4
  describe('extractFeatureName', () => {
10
5
  test('extracts feature name from PRD filename', () => {
11
- expect(extractFeatureName('prd-hone.md')).toBe('hone');
12
- expect(extractFeatureName('prd-delete-button.md')).toBe('delete-button');
13
- expect(extractFeatureName('prd-user-auth.md')).toBe('user-auth');
14
- });
15
- });
6
+ expect(extractFeatureName('prd-hone.md')).toBe('hone')
7
+ expect(extractFeatureName('prd-delete-button.md')).toBe('delete-button')
8
+ expect(extractFeatureName('prd-user-auth.md')).toBe('user-auth')
9
+ })
10
+ })
16
11
 
17
12
  describe('calculateStatus', () => {
18
13
  test('returns "not started" when task file is null', () => {
19
- const result = calculateStatus(null);
20
- expect(result.status).toBe('not started');
21
- expect(result.completedCount).toBe(0);
22
- expect(result.totalCount).toBe(0);
23
- });
14
+ const result = calculateStatus(null)
15
+ expect(result.status).toBe('not started')
16
+ expect(result.completedCount).toBe(0)
17
+ expect(result.totalCount).toBe(0)
18
+ })
24
19
 
25
20
  test('returns "not started" when no tasks exist', () => {
26
21
  const taskFile: TaskFile = {
@@ -28,13 +23,13 @@ describe('calculateStatus', () => {
28
23
  prd: 'prd-test.md',
29
24
  created_at: '2026-01-28T12:00:00Z',
30
25
  updated_at: '2026-01-28T12:00:00Z',
31
- tasks: []
32
- };
33
- const result = calculateStatus(taskFile);
34
- expect(result.status).toBe('not started');
35
- expect(result.completedCount).toBe(0);
36
- expect(result.totalCount).toBe(0);
37
- });
26
+ tasks: [],
27
+ }
28
+ const result = calculateStatus(taskFile)
29
+ expect(result.status).toBe('not started')
30
+ expect(result.completedCount).toBe(0)
31
+ expect(result.totalCount).toBe(0)
32
+ })
38
33
 
39
34
  test('returns "not started" when all tasks are pending', () => {
40
35
  const taskFile: TaskFile = {
@@ -44,14 +39,14 @@ describe('calculateStatus', () => {
44
39
  updated_at: '2026-01-28T12:00:00Z',
45
40
  tasks: [
46
41
  { id: 'task-001', title: 'Task 1', description: 'Desc', status: 'pending' },
47
- { id: 'task-002', title: 'Task 2', description: 'Desc', status: 'pending' }
48
- ]
49
- };
50
- const result = calculateStatus(taskFile);
51
- expect(result.status).toBe('not started');
52
- expect(result.completedCount).toBe(0);
53
- expect(result.totalCount).toBe(2);
54
- });
42
+ { id: 'task-002', title: 'Task 2', description: 'Desc', status: 'pending' },
43
+ ],
44
+ }
45
+ const result = calculateStatus(taskFile)
46
+ expect(result.status).toBe('not started')
47
+ expect(result.completedCount).toBe(0)
48
+ expect(result.totalCount).toBe(2)
49
+ })
55
50
 
56
51
  test('returns "in progress" when some tasks are completed', () => {
57
52
  const taskFile: TaskFile = {
@@ -62,14 +57,14 @@ describe('calculateStatus', () => {
62
57
  tasks: [
63
58
  { id: 'task-001', title: 'Task 1', description: 'Desc', status: 'completed' },
64
59
  { id: 'task-002', title: 'Task 2', description: 'Desc', status: 'pending' },
65
- { id: 'task-003', title: 'Task 3', description: 'Desc', status: 'pending' }
66
- ]
67
- };
68
- const result = calculateStatus(taskFile);
69
- expect(result.status).toBe('in progress');
70
- expect(result.completedCount).toBe(1);
71
- expect(result.totalCount).toBe(3);
72
- });
60
+ { id: 'task-003', title: 'Task 3', description: 'Desc', status: 'pending' },
61
+ ],
62
+ }
63
+ const result = calculateStatus(taskFile)
64
+ expect(result.status).toBe('in progress')
65
+ expect(result.completedCount).toBe(1)
66
+ expect(result.totalCount).toBe(3)
67
+ })
73
68
 
74
69
  test('returns "completed" when all tasks are completed', () => {
75
70
  const taskFile: TaskFile = {
@@ -79,14 +74,14 @@ describe('calculateStatus', () => {
79
74
  updated_at: '2026-01-28T12:00:00Z',
80
75
  tasks: [
81
76
  { id: 'task-001', title: 'Task 1', description: 'Desc', status: 'completed' },
82
- { id: 'task-002', title: 'Task 2', description: 'Desc', status: 'completed' }
83
- ]
84
- };
85
- const result = calculateStatus(taskFile);
86
- expect(result.status).toBe('completed');
87
- expect(result.completedCount).toBe(2);
88
- expect(result.totalCount).toBe(2);
89
- });
77
+ { id: 'task-002', title: 'Task 2', description: 'Desc', status: 'completed' },
78
+ ],
79
+ }
80
+ const result = calculateStatus(taskFile)
81
+ expect(result.status).toBe('completed')
82
+ expect(result.completedCount).toBe(2)
83
+ expect(result.totalCount).toBe(2)
84
+ })
90
85
 
91
86
  test('counts cancelled tasks as completed', () => {
92
87
  const taskFile: TaskFile = {
@@ -97,14 +92,14 @@ describe('calculateStatus', () => {
97
92
  tasks: [
98
93
  { id: 'task-001', title: 'Task 1', description: 'Desc', status: 'completed' },
99
94
  { id: 'task-002', title: 'Task 2', description: 'Desc', status: 'cancelled' },
100
- { id: 'task-003', title: 'Task 3', description: 'Desc', status: 'pending' }
101
- ]
102
- };
103
- const result = calculateStatus(taskFile);
104
- expect(result.status).toBe('in progress');
105
- expect(result.completedCount).toBe(2);
106
- expect(result.totalCount).toBe(3);
107
- });
95
+ { id: 'task-003', title: 'Task 3', description: 'Desc', status: 'pending' },
96
+ ],
97
+ }
98
+ const result = calculateStatus(taskFile)
99
+ expect(result.status).toBe('in progress')
100
+ expect(result.completedCount).toBe(2)
101
+ expect(result.totalCount).toBe(3)
102
+ })
108
103
 
109
104
  test('returns "completed" when all tasks are completed or cancelled', () => {
110
105
  const taskFile: TaskFile = {
@@ -114,12 +109,12 @@ describe('calculateStatus', () => {
114
109
  updated_at: '2026-01-28T12:00:00Z',
115
110
  tasks: [
116
111
  { id: 'task-001', title: 'Task 1', description: 'Desc', status: 'completed' },
117
- { id: 'task-002', title: 'Task 2', description: 'Desc', status: 'cancelled' }
118
- ]
119
- };
120
- const result = calculateStatus(taskFile);
121
- expect(result.status).toBe('completed');
122
- expect(result.completedCount).toBe(2);
123
- expect(result.totalCount).toBe(2);
124
- });
125
- });
112
+ { id: 'task-002', title: 'Task 2', description: 'Desc', status: 'cancelled' },
113
+ ],
114
+ }
115
+ const result = calculateStatus(taskFile)
116
+ expect(result.status).toBe('completed')
117
+ expect(result.completedCount).toBe(2)
118
+ expect(result.totalCount).toBe(2)
119
+ })
120
+ })
package/src/prds.ts CHANGED
@@ -1,33 +1,33 @@
1
- import { readdirSync, existsSync } from 'fs';
2
- import { readFile } from 'fs/promises';
3
- import { join } from 'path';
4
- import yaml from 'js-yaml';
5
- import { getPlansDir } from './config';
1
+ import { readdirSync, existsSync } from 'fs'
2
+ import { readFile } from 'fs/promises'
3
+ import { join } from 'path'
4
+ import yaml from 'js-yaml'
5
+ import { getPlansDir } from './config'
6
6
 
7
7
  export interface Task {
8
- id: string;
9
- title: string;
10
- description: string;
11
- status: 'pending' | 'in_progress' | 'completed' | 'failed' | 'cancelled';
12
- dependencies?: string[];
13
- acceptance_criteria?: string[];
14
- completed_at?: string | null;
8
+ id: string
9
+ title: string
10
+ description: string
11
+ status: 'pending' | 'in_progress' | 'completed' | 'failed' | 'cancelled'
12
+ dependencies?: string[]
13
+ acceptance_criteria?: string[]
14
+ completed_at?: string | null
15
15
  }
16
16
 
17
17
  export interface TaskFile {
18
- feature: string;
19
- prd: string;
20
- created_at: string;
21
- updated_at: string;
22
- tasks: Task[];
18
+ feature: string
19
+ prd: string
20
+ created_at: string
21
+ updated_at: string
22
+ tasks: Task[]
23
23
  }
24
24
 
25
25
  export interface PrdInfo {
26
- filename: string;
27
- taskFile?: string;
28
- status: 'not started' | 'in progress' | 'completed';
29
- completedCount?: number;
30
- totalCount?: number;
26
+ filename: string
27
+ taskFile?: string
28
+ status: 'not started' | 'in progress' | 'completed'
29
+ completedCount?: number
30
+ totalCount?: number
31
31
  }
32
32
 
33
33
  /**
@@ -35,40 +35,40 @@ export interface PrdInfo {
35
35
  * e.g., "prd-hone.md" -> "hone"
36
36
  */
37
37
  export function extractFeatureName(prdFilename: string): string {
38
- return prdFilename.replace(/^prd-/, '').replace(/\.md$/, '');
38
+ return prdFilename.replace(/^prd-/, '').replace(/\.md$/, '')
39
39
  }
40
40
 
41
41
  /**
42
42
  * Get all PRD files in .plans/ directory
43
43
  */
44
44
  export function listPrdFiles(): string[] {
45
- const plansDir = getPlansDir();
45
+ const plansDir = getPlansDir()
46
46
  if (!existsSync(plansDir)) {
47
- return [];
47
+ return []
48
48
  }
49
-
50
- const files = readdirSync(plansDir);
51
- return files.filter(file => file.startsWith('prd-') && file.endsWith('.md'));
49
+
50
+ const files = readdirSync(plansDir)
51
+ return files.filter(file => file.startsWith('prd-') && file.endsWith('.md'))
52
52
  }
53
53
 
54
54
  /**
55
55
  * Load and parse a task file
56
56
  */
57
57
  export async function loadTaskFile(taskFilename: string): Promise<TaskFile | null> {
58
- const plansDir = getPlansDir();
59
- const taskPath = join(plansDir, taskFilename);
60
-
58
+ const plansDir = getPlansDir()
59
+ const taskPath = join(plansDir, taskFilename)
60
+
61
61
  if (!existsSync(taskPath)) {
62
- return null;
62
+ return null
63
63
  }
64
-
64
+
65
65
  try {
66
- const content = await readFile(taskPath, 'utf-8');
67
- const parsed = yaml.load(content) as TaskFile;
68
- return parsed;
66
+ const content = await readFile(taskPath, 'utf-8')
67
+ const parsed = yaml.load(content) as TaskFile
68
+ return parsed
69
69
  } catch (error) {
70
- console.error(`Error parsing task file ${taskFilename}:`, error);
71
- return null;
70
+ console.error(`Error parsing task file ${taskFilename}:`, error)
71
+ return null
72
72
  }
73
73
  }
74
74
 
@@ -77,23 +77,25 @@ export async function loadTaskFile(taskFilename: string): Promise<TaskFile | nul
77
77
  * Cancelled tasks are counted towards completion
78
78
  */
79
79
  export function calculateStatus(taskFile: TaskFile | null): {
80
- status: 'not started' | 'in progress' | 'completed';
81
- completedCount: number;
82
- totalCount: number;
80
+ status: 'not started' | 'in progress' | 'completed'
81
+ completedCount: number
82
+ totalCount: number
83
83
  } {
84
84
  if (!taskFile || !taskFile.tasks || taskFile.tasks.length === 0) {
85
- return { status: 'not started', completedCount: 0, totalCount: 0 };
85
+ return { status: 'not started', completedCount: 0, totalCount: 0 }
86
86
  }
87
-
88
- const totalCount = taskFile.tasks.length;
89
- const completedCount = taskFile.tasks.filter(t => t.status === 'completed' || t.status === 'cancelled').length;
90
-
87
+
88
+ const totalCount = taskFile.tasks.length
89
+ const completedCount = taskFile.tasks.filter(
90
+ t => t.status === 'completed' || t.status === 'cancelled'
91
+ ).length
92
+
91
93
  if (completedCount === 0) {
92
- return { status: 'not started', completedCount, totalCount };
94
+ return { status: 'not started', completedCount, totalCount }
93
95
  } else if (completedCount === totalCount) {
94
- return { status: 'completed', completedCount, totalCount };
96
+ return { status: 'completed', completedCount, totalCount }
95
97
  } else {
96
- return { status: 'in progress', completedCount, totalCount };
98
+ return { status: 'in progress', completedCount, totalCount }
97
99
  }
98
100
  }
99
101
 
@@ -101,28 +103,28 @@ export function calculateStatus(taskFile: TaskFile | null): {
101
103
  * Get PRD info including status
102
104
  */
103
105
  export async function getPrdInfo(prdFilename: string): Promise<PrdInfo> {
104
- const featureName = extractFeatureName(prdFilename);
105
- const taskFilename = `tasks-${featureName}.yml`;
106
- const plansDir = getPlansDir();
107
- const taskPath = join(plansDir, taskFilename);
108
-
109
- const taskFile = existsSync(taskPath) ? await loadTaskFile(taskFilename) : null;
110
- const { status, completedCount, totalCount } = calculateStatus(taskFile);
111
-
106
+ const featureName = extractFeatureName(prdFilename)
107
+ const taskFilename = `tasks-${featureName}.yml`
108
+ const plansDir = getPlansDir()
109
+ const taskPath = join(plansDir, taskFilename)
110
+
111
+ const taskFile = existsSync(taskPath) ? await loadTaskFile(taskFilename) : null
112
+ const { status, completedCount, totalCount } = calculateStatus(taskFile)
113
+
112
114
  return {
113
115
  filename: prdFilename,
114
116
  taskFile: taskFile ? taskFilename : undefined,
115
117
  status,
116
118
  completedCount: taskFile ? completedCount : undefined,
117
- totalCount: taskFile ? totalCount : undefined
118
- };
119
+ totalCount: taskFile ? totalCount : undefined,
120
+ }
119
121
  }
120
122
 
121
123
  /**
122
124
  * List all PRDs with their info
123
125
  */
124
126
  export async function listPrds(): Promise<PrdInfo[]> {
125
- const prdFiles = listPrdFiles();
126
- const prds = await Promise.all(prdFiles.map(file => getPrdInfo(file)));
127
- return prds;
127
+ const prdFiles = listPrdFiles()
128
+ const prds = await Promise.all(prdFiles.map(file => getPrdInfo(file)))
129
+ return prds
128
130
  }