Skip to content

Commit c838094

Browse files
feat(T-MVI-06): add _next progressive disclosure directives to handlers
All high-volume domain handlers now return _next directives enabling agents to drill deeper with minimal upfront data. New: packages/core/src/mvi-helpers.ts - taskShowNext(id) — full, children, deps - taskListItemNext(id) — show - sessionListItemNext(id) — show, serialize - sessionStartNext() — current, stop - memoryFindHitNext(id) — fetch - NextDirectives type exported from barrel Modified handlers: - tasks/show.ts: TaskDetail includes _next - tasks/list.ts: CompactTask includes _next - tasks/find.ts: FindResult includes _next - sessions/index.ts: start + list attach _next - sessions/find.ts: MinimalSessionRecord includes _next - memory/brain-retrieval.ts: BrainCompactHit includes _next All changes additive — no query logic modified, no breaking changes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 075ed1e commit c838094

8 files changed

Lines changed: 144 additions & 4 deletions

File tree

packages/core/src/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,15 @@ export {
289289
runAllMigrations,
290290
runMigration,
291291
} from './migration/index.js';
292+
// MVI progressive disclosure helpers
293+
export type { NextDirectives } from './mvi-helpers.js';
294+
export {
295+
memoryFindHitNext,
296+
sessionListItemNext,
297+
sessionStartNext,
298+
taskListItemNext,
299+
taskShowNext,
300+
} from './mvi-helpers.js';
292301
// Reconciliation
293302
export { reconcile } from './reconciliation/index.js';
294303
// Sessions

packages/core/src/memory/brain-retrieval.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
*/
1818

1919
import { createHash } from 'node:crypto';
20+
import type { NextDirectives } from '../mvi-helpers.js';
21+
import { memoryFindHitNext } from '../mvi-helpers.js';
2022
import { getBrainAccessor } from '../store/brain-accessor.js';
2123
import type {
2224
BRAIN_OBSERVATION_SOURCE_TYPES,
@@ -43,6 +45,8 @@ export interface BrainCompactHit {
4345
title: string;
4446
date: string;
4547
relevance?: number;
48+
/** Progressive disclosure directives for follow-up operations. */
49+
_next?: NextDirectives;
4650
}
4751

4852
/** Parameters for searchBrainCompact. */
@@ -208,6 +212,11 @@ export async function searchBrainCompact(
208212
results = results.filter((r) => r.date <= dateEnd);
209213
}
210214

215+
// Enrich each hit with _next progressive disclosure directives
216+
for (const hit of results) {
217+
hit._next = memoryFindHitNext(hit.id);
218+
}
219+
211220
return {
212221
results,
213222
total: results.length,

packages/core/src/mvi-helpers.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/**
2+
* MVI _next directive helpers for progressive disclosure.
3+
*
4+
* When CLEO returns results in minimal MVI mode, `_next` provides a map of
5+
* available follow-up operations the agent can take. This enables progressive
6+
* disclosure: agents get lean results and can drill deeper only when needed.
7+
*
8+
* @example
9+
* ```json
10+
* {"id": "T042", "title": "Fix auth", "status": "active",
11+
* "_next": {"full": "cleo show T042", "children": "cleo find --parent T042"}}
12+
* ```
13+
*
14+
* @task T-MVI-06
15+
*/
16+
17+
/** Map of follow-up operation names to CLI command strings. */
18+
export type NextDirectives = Record<string, string>;
19+
20+
/**
21+
* Build `_next` directives for a full task detail result (tasks.show).
22+
*
23+
* @param taskId - The task ID to generate directives for
24+
* @returns A map of available follow-up operations
25+
*/
26+
export function taskShowNext(taskId: string): NextDirectives {
27+
return {
28+
full: `cleo show ${taskId} --mvi full`,
29+
children: `cleo find --parent ${taskId}`,
30+
deps: `cleo deps ${taskId}`,
31+
};
32+
}
33+
34+
/**
35+
* Build `_next` directives for a task in a list or find result.
36+
*
37+
* @param taskId - The task ID to generate directives for
38+
* @returns A map of available follow-up operations
39+
*/
40+
export function taskListItemNext(taskId: string): NextDirectives {
41+
return {
42+
show: `cleo show ${taskId}`,
43+
};
44+
}
45+
46+
/**
47+
* Build `_next` directives for a session in a list or find result.
48+
*
49+
* @param sessionId - The session ID to generate directives for
50+
* @returns A map of available follow-up operations
51+
*/
52+
export function sessionListItemNext(sessionId: string): NextDirectives {
53+
return {
54+
show: `cleo session show ${sessionId}`,
55+
serialize: `cleo session serialize ${sessionId}`,
56+
};
57+
}
58+
59+
/**
60+
* Build `_next` directives for a session start result.
61+
*
62+
* @returns A map of available follow-up operations after starting a session
63+
*/
64+
export function sessionStartNext(): NextDirectives {
65+
return {
66+
current: 'cleo current',
67+
stop: 'cleo session end',
68+
};
69+
}
70+
71+
/**
72+
* Build `_next` directives for a memory search (find) hit.
73+
*
74+
* @param entryId - The brain entry ID to generate directives for
75+
* @returns A map of available follow-up operations
76+
*/
77+
export function memoryFindHitNext(entryId: string): NextDirectives {
78+
return {
79+
fetch: `cleo memory fetch ${entryId}`,
80+
};
81+
}

packages/core/src/sessions/find.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
*/
1010

1111
import type { Session, SessionScope } from '@cleocode/contracts';
12+
import type { NextDirectives } from '../mvi-helpers.js';
13+
import { sessionListItemNext } from '../mvi-helpers.js';
1214
import type { DataAccessor } from '../store/data-accessor.js';
1315

1416
/** Minimal session record returned by findSessions(). */
@@ -18,6 +20,8 @@ export interface MinimalSessionRecord {
1820
status: string;
1921
startedAt: string;
2022
scope: SessionScope;
23+
/** Progressive disclosure directives for follow-up operations. */
24+
_next?: NextDirectives;
2125
}
2226

2327
/** Parameters for findSessions(). */
@@ -82,13 +86,14 @@ export async function findSessions(
8286
return sessions.map(toMinimal);
8387
}
8488

85-
/** Project a full Session to a MinimalSessionRecord. */
89+
/** Project a full Session to a MinimalSessionRecord with _next directives. */
8690
function toMinimal(s: Session): MinimalSessionRecord {
8791
return {
8892
id: s.id,
8993
name: s.name,
9094
status: s.status,
9195
startedAt: s.startedAt,
9296
scope: s.scope,
97+
_next: sessionListItemNext(s.id),
9398
};
9499
}

packages/core/src/sessions/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { randomBytes } from 'node:crypto';
88
import type { Session, SessionScope } from '@cleocode/contracts';
99
import { ExitCode } from '@cleocode/contracts';
1010
import { CleoError } from '../errors.js';
11+
import { sessionListItemNext, sessionStartNext } from '../mvi-helpers.js';
1112
import type { DataAccessor } from '../store/data-accessor.js';
1213
import { getAccessor } from '../store/data-accessor.js';
1314

@@ -185,6 +186,9 @@ export async function startSession(
185186
/* Hooks are best-effort */
186187
});
187188

189+
// Attach _next progressive disclosure directives
190+
Object.assign(session, { _next: sessionStartNext() });
191+
188192
return session;
189193
}
190194

@@ -358,6 +362,11 @@ export async function listSessions(
358362
sessions = sessions.slice(0, options.limit);
359363
}
360364

365+
// Attach _next progressive disclosure directives to each session
366+
for (const s of sessions) {
367+
Object.assign(s, { _next: sessionListItemNext(s.id) });
368+
}
369+
361370
return sessions;
362371
}
363372

packages/core/src/tasks/find.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import type { Task, TaskQueryFilters, TaskStatus } from '@cleocode/contracts';
88
import { ExitCode } from '@cleocode/contracts';
99
import { CleoError } from '../errors.js';
10+
import type { NextDirectives } from '../mvi-helpers.js';
11+
import { taskListItemNext } from '../mvi-helpers.js';
1012
import type { DataAccessor } from '../store/data-accessor.js';
1113
import { getAccessor } from '../store/data-accessor.js';
1214

@@ -19,6 +21,8 @@ export interface FindResult {
1921
type?: string;
2022
parentId?: string | null;
2123
score: number;
24+
/** Progressive disclosure directives for follow-up operations. */
25+
_next?: NextDirectives;
2226
}
2327

2428
/** Options for finding tasks. */
@@ -191,8 +195,14 @@ export async function findTasks(
191195
const offset = options.offset ?? 0;
192196
results = results.slice(offset, offset + limit);
193197

198+
// Enrich each result with _next progressive disclosure directives
199+
const enrichedResults = results.map((r) => ({
200+
...r,
201+
_next: taskListItemNext(r.id),
202+
}));
203+
194204
return {
195-
results,
205+
results: enrichedResults,
196206
total,
197207
query: queryStr,
198208
searchType,

packages/core/src/tasks/list.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
import type { Task, TaskPriority, TaskStatus, TaskType } from '@cleocode/contracts';
88
import type { LAFSPage } from '@cleocode/lafs';
9+
import type { NextDirectives } from '../mvi-helpers.js';
10+
import { taskListItemNext } from '../mvi-helpers.js';
911
import { paginate } from '../pagination.js';
1012
import type { DataAccessor, TaskQueryFilters } from '../store/data-accessor.js';
1113

@@ -19,9 +21,11 @@ export interface CompactTask {
1921
priority: string;
2022
type?: string;
2123
parentId?: string | null;
24+
/** Progressive disclosure directives for follow-up operations. */
25+
_next?: NextDirectives;
2226
}
2327

24-
/** Convert a full Task to compact representation. */
28+
/** Convert a full Task to compact representation with _next directives. */
2529
export function toCompact(task: Task): CompactTask {
2630
return {
2731
id: task.id,
@@ -30,6 +34,7 @@ export function toCompact(task: Task): CompactTask {
3034
priority: task.priority,
3135
type: task.type,
3236
parentId: task.parentId,
37+
_next: taskListItemNext(task.id),
3338
};
3439
}
3540

@@ -107,8 +112,14 @@ export async function listTasks(
107112
}
108113
: undefined;
109114

115+
// Enrich each task with _next progressive disclosure directives
116+
const enrichedTasks = tasks.map((t) => ({
117+
...t,
118+
_next: taskListItemNext(t.id),
119+
}));
120+
110121
return {
111-
tasks,
122+
tasks: enrichedTasks,
112123
total,
113124
filtered: filteredCount,
114125
page,

packages/core/src/tasks/show.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import type { Task, TaskRef } from '@cleocode/contracts';
88
import { ExitCode } from '@cleocode/contracts';
99
import { CleoError } from '../errors.js';
10+
import type { NextDirectives } from '../mvi-helpers.js';
11+
import { taskShowNext } from '../mvi-helpers.js';
1012
import type { DataAccessor } from '../store/data-accessor.js';
1113
import { getAccessor } from '../store/data-accessor.js';
1214

@@ -18,6 +20,8 @@ export interface TaskDetail extends Task {
1820
dependents?: string[];
1921
hierarchyPath?: string[];
2022
isArchived?: boolean;
23+
/** Progressive disclosure directives for follow-up operations. */
24+
_next?: NextDirectives;
2125
}
2226

2327
/**
@@ -104,5 +108,7 @@ export async function showTask(
104108
}
105109
}
106110

111+
detail._next = taskShowNext(taskId);
112+
107113
return detail;
108114
}

0 commit comments

Comments
 (0)