tenbo-dashboard 0.10.2 → 0.10.3
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/package.json +1 -1
- package/scripts/items.test.ts +25 -0
- package/scripts/next.test.ts +25 -0
- package/scripts/work-queue.test.ts +26 -0
- package/src/api/lib/itemProjection.ts +5 -3
- package/src/api/lib/notes.ts +12 -0
- package/src/api/lib/roadmapStore.ts +1 -13
package/package.json
CHANGED
package/scripts/items.test.ts
CHANGED
|
@@ -120,6 +120,31 @@ describe('items CLI', () => {
|
|
|
120
120
|
expect(JSON.parse(full.stdout).items[0].item.description).toBe('second item');
|
|
121
121
|
});
|
|
122
122
|
|
|
123
|
+
it('summarizes items whose notes are YAML sequences', () => {
|
|
124
|
+
writeFileSync(path.join(dir, '.tenbo/scopes/editor/roadmap.yaml'), [
|
|
125
|
+
'items:',
|
|
126
|
+
' - id: ed-001',
|
|
127
|
+
' title: First',
|
|
128
|
+
' layer: app',
|
|
129
|
+
' status: next',
|
|
130
|
+
' description: first item',
|
|
131
|
+
' notes:',
|
|
132
|
+
' - First note',
|
|
133
|
+
' - Latest list note',
|
|
134
|
+
'',
|
|
135
|
+
].join('\n'));
|
|
136
|
+
|
|
137
|
+
const result = runItemsCli(dir, ['--status', 'next', '--summary', '--json']);
|
|
138
|
+
|
|
139
|
+
expect(result.exitCode).toBe(0);
|
|
140
|
+
expect(JSON.parse(result.stdout).items).toEqual([
|
|
141
|
+
expect.objectContaining({
|
|
142
|
+
id: 'ed-001',
|
|
143
|
+
latest_note: 'Latest list note',
|
|
144
|
+
}),
|
|
145
|
+
]);
|
|
146
|
+
});
|
|
147
|
+
|
|
123
148
|
it('returns misuse errors for invalid status lists and fields', () => {
|
|
124
149
|
const badStatus = runItemsCli(dir, ['--status', 'next,unknown', '--json']);
|
|
125
150
|
const badField = runItemsCli(dir, ['--fields', 'id,nope', '--json']);
|
package/scripts/next.test.ts
CHANGED
|
@@ -64,4 +64,29 @@ describe('next CLI', () => {
|
|
|
64
64
|
]);
|
|
65
65
|
expect(payload.items[0]).not.toHaveProperty('description');
|
|
66
66
|
});
|
|
67
|
+
|
|
68
|
+
it('returns agent summaries when notes are YAML sequences', () => {
|
|
69
|
+
writeFileSync(path.join(dir, '.tenbo/scopes/editor/roadmap.yaml'), [
|
|
70
|
+
'items:',
|
|
71
|
+
' - id: ed-001',
|
|
72
|
+
' title: First',
|
|
73
|
+
' layer: app',
|
|
74
|
+
' status: next',
|
|
75
|
+
' description: first item',
|
|
76
|
+
' notes:',
|
|
77
|
+
' - First note',
|
|
78
|
+
' - Latest list note',
|
|
79
|
+
'',
|
|
80
|
+
].join('\n'));
|
|
81
|
+
|
|
82
|
+
const result = runNextCli(dir, ['--agent-summary', '--json']);
|
|
83
|
+
|
|
84
|
+
expect(result.exitCode).toBe(0);
|
|
85
|
+
expect(JSON.parse(result.stdout).items).toEqual([
|
|
86
|
+
expect.objectContaining({
|
|
87
|
+
id: 'ed-001',
|
|
88
|
+
latest_note: 'Latest list note',
|
|
89
|
+
}),
|
|
90
|
+
]);
|
|
91
|
+
});
|
|
67
92
|
});
|
|
@@ -62,4 +62,30 @@ describe('work-queue CLI', () => {
|
|
|
62
62
|
]);
|
|
63
63
|
expect(payload.items[0]).not.toHaveProperty('description');
|
|
64
64
|
});
|
|
65
|
+
|
|
66
|
+
it('returns compact work queue items when notes are YAML sequences', () => {
|
|
67
|
+
writeFileSync(path.join(dir, '.tenbo/scopes/editor/roadmap.yaml'), [
|
|
68
|
+
'items:',
|
|
69
|
+
' - id: ed-001',
|
|
70
|
+
' title: Active Refactor',
|
|
71
|
+
' layer: app',
|
|
72
|
+
' status: now',
|
|
73
|
+
' type: refactor',
|
|
74
|
+
' description: active refactor',
|
|
75
|
+
' notes:',
|
|
76
|
+
' - First note',
|
|
77
|
+
' - Latest list note',
|
|
78
|
+
'',
|
|
79
|
+
].join('\n'));
|
|
80
|
+
|
|
81
|
+
const result = runWorkQueueCli(dir, ['--type', 'refactor', '--json']);
|
|
82
|
+
|
|
83
|
+
expect(result.exitCode).toBe(0);
|
|
84
|
+
expect(JSON.parse(result.stdout).items).toEqual([
|
|
85
|
+
expect.objectContaining({
|
|
86
|
+
id: 'ed-001',
|
|
87
|
+
latest_note: 'Latest list note',
|
|
88
|
+
}),
|
|
89
|
+
]);
|
|
90
|
+
});
|
|
65
91
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Item, Priority, Status, VerificationStatus } from '../../types';
|
|
2
2
|
import type { LocatedItem } from './roadmapStore';
|
|
3
|
+
import { normalizeNotes } from './notes';
|
|
3
4
|
|
|
4
5
|
export interface ItemSummaryRecord {
|
|
5
6
|
id: string;
|
|
@@ -45,9 +46,10 @@ export function parseItemFields(value: string): ItemFieldName[] {
|
|
|
45
46
|
return fields as ItemFieldName[];
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
function latestNote(notes:
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
function latestNote(notes: unknown): string | undefined {
|
|
50
|
+
const normalized = normalizeNotes(notes);
|
|
51
|
+
if (!normalized) return undefined;
|
|
52
|
+
const lines = normalized.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
51
53
|
return lines.at(-1)?.replace(/^- /, '');
|
|
52
54
|
}
|
|
53
55
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function normalizeNotes(notes: unknown): string {
|
|
2
|
+
if (!notes) return '';
|
|
3
|
+
if (typeof notes === 'string') return notes.trimEnd();
|
|
4
|
+
if (Array.isArray(notes)) {
|
|
5
|
+
return notes
|
|
6
|
+
.map((entry) => String(entry).trim())
|
|
7
|
+
.filter(Boolean)
|
|
8
|
+
.map((entry) => entry.startsWith('- ') ? entry : `- ${entry}`)
|
|
9
|
+
.join('\n');
|
|
10
|
+
}
|
|
11
|
+
return String(notes).trimEnd();
|
|
12
|
+
}
|
|
@@ -6,6 +6,7 @@ import { readState } from './tenboFs';
|
|
|
6
6
|
import { invalidate as invalidateCache } from './parseCache';
|
|
7
7
|
import { validate } from './validator';
|
|
8
8
|
import { comparePriority } from './priority';
|
|
9
|
+
import { normalizeNotes } from './notes';
|
|
9
10
|
import type { Item, Priority, Status, VerificationStatus, ValidateResult } from '../../types';
|
|
10
11
|
|
|
11
12
|
export type RoadmapErrorCode = 'not_found' | 'conflict' | 'invalid_status' | 'invalid_args';
|
|
@@ -123,19 +124,6 @@ function validateVerificationStatus(status: string): VerificationStatus {
|
|
|
123
124
|
throw new RoadmapStoreError('invalid_status', `invalid verification status: ${status}`);
|
|
124
125
|
}
|
|
125
126
|
|
|
126
|
-
function normalizeNotes(notes: unknown): string {
|
|
127
|
-
if (!notes) return '';
|
|
128
|
-
if (typeof notes === 'string') return notes.trimEnd();
|
|
129
|
-
if (Array.isArray(notes)) {
|
|
130
|
-
return notes
|
|
131
|
-
.map((entry) => String(entry).trim())
|
|
132
|
-
.filter(Boolean)
|
|
133
|
-
.map((entry) => entry.startsWith('- ') ? entry : `- ${entry}`)
|
|
134
|
-
.join('\n');
|
|
135
|
-
}
|
|
136
|
-
return String(notes).trimEnd();
|
|
137
|
-
}
|
|
138
|
-
|
|
139
127
|
function mutateItem(
|
|
140
128
|
repoRoot: string,
|
|
141
129
|
itemId: string,
|