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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tenbo-dashboard",
3
- "version": "0.10.2",
3
+ "version": "0.10.3",
4
4
  "description": "Local-first architecture dashboard and CLI tools for .tenbo/ repos",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -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']);
@@ -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: string | undefined): string | undefined {
49
- if (!notes) return undefined;
50
- const lines = notes.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
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,