@yeaft/webchat-agent 0.1.615 → 0.1.617

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.
@@ -1,102 +1,111 @@
1
1
  /**
2
- * Task management tools — persistent task tracking for work mode.
2
+ * Feature management tools — persistent feature tracking for Unify work.
3
3
  *
4
- * Tasks are persisted to ~/.yeaft/tasks/ via TaskStore (one folder per task).
5
- * Call initTaskStore(yeaftDir) during session init before tools are used.
4
+ * Features are persisted to ~/.yeaft/features/ via FeatureStore (one folder
5
+ * per feature). Call initFeatureStore(yeaftDir) during session init before
6
+ * tools are used.
7
+ *
8
+ * NOTE (PR-1a refactor): renamed from task-tools.js. Tools renamed
9
+ * Task* → Feature*, exported symbols renamed (taskCreate → featureCreate,
10
+ * etc.), tool param `task_id` → `feature_id`, parent param `parent_id`
11
+ * is unchanged (still refers to a parent feature). The internal
12
+ * feature-store object field names (parentTaskId, taskId, relatedTaskIds,
13
+ * etc.) are still in use here — those rename to feature-equivalents in
14
+ * PR-1b along with the memory schema migration. No backwards-compat
15
+ * aliases are kept (per project policy).
6
16
  */
7
17
 
8
18
  import { defineTool } from './types.js';
9
19
  import { randomUUID } from 'crypto';
10
- import { TaskStore } from '../tasks/store.js';
20
+ import { FeatureStore } from '../features/store.js';
11
21
 
12
- /** @type {TaskStore|null} */
13
- let taskStore = null;
22
+ /** @type {FeatureStore|null} */
23
+ let featureStore = null;
14
24
 
15
25
  /**
16
- * Initialize the task store with the yeaft directory.
17
- * Must be called during session startup before any task tools are used.
26
+ * Initialize the feature store with the yeaft directory.
27
+ * Must be called during session startup before any feature tools are used.
18
28
  * @param {string} yeaftDir — Base ~/.yeaft directory
19
29
  * @param {{ readOnly?: boolean }} [opts]
20
30
  */
21
- export function initTaskStore(yeaftDir, opts) {
22
- taskStore = new TaskStore(yeaftDir, opts);
31
+ export function initFeatureStore(yeaftDir, opts) {
32
+ featureStore = new FeatureStore(yeaftDir, opts);
23
33
  }
24
34
 
25
- /** Get the task store instance (for other tools/tests). */
26
- export function getTaskStore() {
27
- return taskStore;
35
+ /** Get the feature store instance (for other tools/tests). */
36
+ export function getFeatureStore() {
37
+ return featureStore;
28
38
  }
29
39
 
30
40
  /** Get current plan text. */
31
41
  export function getPlan() {
32
- return taskStore ? taskStore.getPlan() : '';
42
+ return featureStore ? featureStore.getPlan() : '';
33
43
  }
34
44
 
35
45
  /** Internal helper — ensure store is initialized. */
36
46
  function requireStore() {
37
- if (!taskStore) {
38
- return '{"error":"Task store not initialized. Session may still be loading."}';
47
+ if (!featureStore) {
48
+ return '{"error":"Feature store not initialized. Session may still be loading."}';
39
49
  }
40
50
  return null;
41
51
  }
42
52
 
43
- // ─── TaskCreate ─────────────────────────────────────────
53
+ // ─── FeatureCreate ──────────────────────────────────────
44
54
 
45
- export const taskCreate = defineTool({
46
- name: 'TaskCreate',
47
- description: `Create a new task for tracking work progress.
55
+ export const featureCreate = defineTool({
56
+ name: 'FeatureCreate',
57
+ description: `Create a new feature for tracking work progress.
48
58
 
49
- Tasks have a title, description, priority, and status.
50
- Each task gets its own folder with task.md, progress.md, and memory.md.
59
+ Features have a title, description, priority, and status.
60
+ Each feature gets its own folder with feature.md, progress.md, and memory.md.
51
61
  Use this to break down complex work into trackable items.
52
62
 
53
- Pass \`parent_id\` to create a subtask under an existing task.
63
+ Pass \`parent_id\` to create a sub-feature under an existing feature.
54
64
 
55
65
  R6 multi-VP groups (Unify): pass \`group_id\` + \`members\` to create a
56
- collaborative task inside a group. The caller's vpId becomes the task
66
+ collaborative feature inside a group. The caller's vpId becomes the feature
57
67
  \`initiator\`. \`members\` MUST be a subset of the group's roster — the
58
68
  tool validates this server-side and returns a \`not_in_roster\` error
59
69
  otherwise. The user owns invitations; the tool will not auto-invite.
60
70
 
61
- Use \`related_task_ids\` to soft-link to other tasks (cross-group OK).`,
71
+ Use \`related_feature_ids\` to soft-link to other features (cross-group OK).`,
62
72
  parameters: {
63
73
  type: 'object',
64
74
  properties: {
65
75
  title: {
66
76
  type: 'string',
67
- description: 'Short task title',
77
+ description: 'Short feature title',
68
78
  },
69
79
  description: {
70
80
  type: 'string',
71
- description: 'Detailed task description',
81
+ description: 'Detailed feature description',
72
82
  },
73
83
  priority: {
74
84
  type: 'string',
75
85
  enum: ['low', 'medium', 'high', 'critical'],
76
- description: 'Task priority (default: "medium")',
86
+ description: 'Feature priority (default: "medium")',
77
87
  },
78
88
  parent_id: {
79
89
  type: 'string',
80
- description: 'Parent task ID for subtasks',
90
+ description: 'Parent feature ID for subtasks',
81
91
  },
82
92
  group_id: {
83
93
  type: 'string',
84
- description: 'R6: group this task belongs to. Required for multi-VP collaboration tasks.',
94
+ description: 'R6: group this feature belongs to. Required for multi-VP collaboration features.',
85
95
  },
86
96
  members: {
87
97
  type: 'array',
88
98
  items: { type: 'string' },
89
- description: 'R6: VP ids participating in this task (≥1). MUST be ⊆ group roster.',
99
+ description: 'R6: VP ids participating in this feature (≥1). MUST be ⊆ group roster.',
90
100
  },
91
- related_task_ids: {
101
+ related_feature_ids: {
92
102
  type: 'array',
93
103
  items: { type: 'string' },
94
- description: 'R6: soft-linked task ids (cross-group OK). See arch §14.',
104
+ description: 'R6: soft-linked feature ids (cross-group OK). See arch §14.',
95
105
  },
96
- // Note (task-333b): `parent_task_id` is accepted by execute() as a
97
- // soft-compat alias for `parent_id` (absorbed from the former
98
- // SpawnTask tool) but intentionally NOT advertised in the schema to
99
- // avoid giving the LLM two live params for one field.
106
+ // PR-1a: legacy `parent_feature_id` (originally `parent_task_id`,
107
+ // an alias absorbed from the former SpawnTask tool) was removed.
108
+ // Use `parent_id` to create a sub-feature.
100
109
  },
101
110
  required: ['title'],
102
111
  },
@@ -111,19 +120,15 @@ Use \`related_task_ids\` to soft-link to other tasks (cross-group OK).`,
111
120
  description,
112
121
  priority = 'medium',
113
122
  parent_id,
114
- parent_task_id,
115
123
  group_id,
116
124
  members,
117
- related_task_ids,
125
+ related_feature_ids,
118
126
  } = input;
119
127
  if (!title) return JSON.stringify({ error: 'title is required' });
120
128
 
121
- // task-333b: accept either `parent_id` (original TaskCreate field) or
122
- // `parent_task_id` (the former SpawnTask field). When both are present,
123
- // parent_id wins.
124
- const parentId = parent_id || parent_task_id || null;
125
- if (parentId && !taskStore.get(parentId)) {
126
- return JSON.stringify({ error: `Parent task not found: ${parentId}` });
129
+ const parentId = parent_id || null;
130
+ if (parentId && !featureStore.get(parentId)) {
131
+ return JSON.stringify({ error: `Parent feature not found: ${parentId}` });
127
132
  }
128
133
 
129
134
  // R6 multi-VP fields — validated only when group_id is present, so
@@ -182,8 +187,8 @@ Use \`related_task_ids\` to soft-link to other tasks (cross-group OK).`,
182
187
  initiator = callerVpId;
183
188
  }
184
189
 
185
- const id = `task-${randomUUID().slice(0, 8)}`;
186
- const task = {
190
+ const id = `feat-${randomUUID().slice(0, 8)}`;
191
+ const feature = {
187
192
  id,
188
193
  title,
189
194
  description: description || '',
@@ -195,19 +200,19 @@ Use \`related_task_ids\` to soft-link to other tasks (cross-group OK).`,
195
200
  updatedAt: Date.now(),
196
201
  };
197
202
  if (groupId) {
198
- task.groupId = groupId;
199
- task.members = normalizedMembers;
200
- if (initiator) task.initiator = initiator;
201
- if (Array.isArray(related_task_ids) && related_task_ids.length) {
202
- task.relatedTaskIds = related_task_ids.map(String);
203
+ feature.groupId = groupId;
204
+ feature.members = normalizedMembers;
205
+ if (initiator) feature.initiator = initiator;
206
+ if (Array.isArray(related_feature_ids) && related_feature_ids.length) {
207
+ feature.relatedTaskIds = related_feature_ids.map(String);
203
208
  }
204
209
  }
205
210
 
206
- taskStore.create(task);
211
+ featureStore.create(feature);
207
212
 
208
213
  return JSON.stringify({
209
214
  success: true,
210
- task: {
215
+ feature: {
211
216
  id,
212
217
  title,
213
218
  priority,
@@ -218,32 +223,32 @@ Use \`related_task_ids\` to soft-link to other tasks (cross-group OK).`,
218
223
  initiator: initiator || undefined,
219
224
  },
220
225
  message: groupId
221
- ? `Task created in group ${groupId}: ${title} (${id}) with members [${(normalizedMembers || []).join(', ')}]`
226
+ ? `Feature created in group ${groupId}: ${title} (${id}) with members [${(normalizedMembers || []).join(', ')}]`
222
227
  : parentId
223
- ? `Subtask created: ${title} (${id}) under ${parentId}`
224
- : `Task created: ${title} (${id})`,
228
+ ? `Sub-feature created: ${title} (${id}) under ${parentId}`
229
+ : `Feature created: ${title} (${id})`,
225
230
  });
226
231
  },
227
232
  });
228
233
 
229
- // ─── TaskUpdate ─────────────────────────────────────────
234
+ // ─── FeatureUpdate ──────────────────────────────────────
230
235
 
231
- export const taskUpdate = defineTool({
232
- name: 'TaskUpdate',
233
- description: `Update a task's status, priority, or details.
236
+ export const featureUpdate = defineTool({
237
+ name: 'FeatureUpdate',
238
+ description: `Update a feature's status, priority, or details.
234
239
 
235
240
  Status values: pending, in_progress, completed, blocked, cancelled`,
236
241
  parameters: {
237
242
  type: 'object',
238
243
  properties: {
239
- task_id: {
244
+ feature_id: {
240
245
  type: 'string',
241
- description: 'Task ID to update',
246
+ description: 'Feature ID to update',
242
247
  },
243
248
  status: {
244
249
  type: 'string',
245
250
  enum: ['pending', 'in_progress', 'completed', 'blocked', 'cancelled'],
246
- description: 'New task status',
251
+ description: 'New feature status',
247
252
  },
248
253
  priority: {
249
254
  type: 'string',
@@ -260,10 +265,10 @@ Status values: pending, in_progress, completed, blocked, cancelled`,
260
265
  },
261
266
  result: {
262
267
  type: 'string',
263
- description: 'Task result or completion notes',
268
+ description: 'Feature result or completion notes',
264
269
  },
265
270
  },
266
- required: ['task_id'],
271
+ required: ['feature_id'],
267
272
  },
268
273
  isConcurrencySafe: () => false,
269
274
  isReadOnly: () => false,
@@ -271,8 +276,8 @@ Status values: pending, in_progress, completed, blocked, cancelled`,
271
276
  const err = requireStore();
272
277
  if (err) return err;
273
278
 
274
- const { task_id, status, priority, title, description, result } = input;
275
- if (!task_id) return JSON.stringify({ error: 'task_id is required' });
279
+ const { feature_id, status, priority, title, description, result } = input;
280
+ if (!feature_id) return JSON.stringify({ error: 'feature_id is required' });
276
281
 
277
282
  const updates = {};
278
283
  if (status) updates.status = status;
@@ -281,24 +286,24 @@ Status values: pending, in_progress, completed, blocked, cancelled`,
281
286
  if (description !== undefined) updates.description = description;
282
287
  if (result) updates.result = result;
283
288
 
284
- const task = taskStore.update(task_id, updates);
285
- if (!task) return JSON.stringify({ error: `Task not found: ${task_id}` });
289
+ const feature = featureStore.update(feature_id, updates);
290
+ if (!feature) return JSON.stringify({ error: `Feature not found: ${feature_id}` });
286
291
 
287
292
  return JSON.stringify({
288
293
  success: true,
289
- task: { id: task.id, title: task.title, status: task.status, priority: task.priority },
290
- message: `Task "${task.title}" updated`,
294
+ feature: { id: feature.id, title: feature.title, status: feature.status, priority: feature.priority },
295
+ message: `Feature "${feature.title}" updated`,
291
296
  });
292
297
  },
293
298
  });
294
299
 
295
- // ─── TaskList ───────────────────────────────────────────
300
+ // ─── FeatureList ────────────────────────────────────────
296
301
 
297
- export const taskList = defineTool({
298
- name: 'TaskList',
299
- description: `List all tracked tasks with their status.
302
+ export const featureList = defineTool({
303
+ name: 'FeatureList',
304
+ description: `List all tracked features with their status.
300
305
 
301
- Shows task IDs, titles, status, and priority. Filter by status if needed.`,
306
+ Shows feature IDs, titles, status, and priority. Filter by status if needed.`,
302
307
  parameters: {
303
308
  type: 'object',
304
309
  properties: {
@@ -309,7 +314,7 @@ Shows task IDs, titles, status, and priority. Filter by status if needed.`,
309
314
  },
310
315
  include_completed: {
311
316
  type: 'boolean',
312
- description: 'Include completed tasks (default: true)',
317
+ description: 'Include completed features (default: true)',
313
318
  },
314
319
  },
315
320
  },
@@ -321,7 +326,7 @@ Shows task IDs, titles, status, and priority. Filter by status if needed.`,
321
326
 
322
327
  const { status, include_completed = true } = input;
323
328
 
324
- let results = taskStore.list(status ? { status } : undefined);
329
+ let results = featureStore.list(status ? { status } : undefined);
325
330
  if (!include_completed) {
326
331
  results = results.filter(t => t.status !== 'completed');
327
332
  }
@@ -340,7 +345,7 @@ Shows task IDs, titles, status, and priority. Filter by status if needed.`,
340
345
  taskItems.sort((a, b) => (ORDER[a.status] ?? 5) - (ORDER[b.status] ?? 5));
341
346
 
342
347
  return JSON.stringify({
343
- tasks: taskItems,
348
+ features: taskItems,
344
349
  totalCount: taskItems.length,
345
350
  summary: {
346
351
  pending: taskItems.filter(t => t.status === 'pending').length,
@@ -352,20 +357,20 @@ Shows task IDs, titles, status, and priority. Filter by status if needed.`,
352
357
  },
353
358
  });
354
359
 
355
- // ─── TaskGet ────────────────────────────────────────────
360
+ // ─── FeatureGet ─────────────────────────────────────────
356
361
 
357
- export const taskGet = defineTool({
358
- name: 'TaskGet',
359
- description: `Get detailed information about a specific task, including its progress log and memory.`,
362
+ export const featureGet = defineTool({
363
+ name: 'FeatureGet',
364
+ description: `Get detailed information about a specific feature, including its progress log and memory.`,
360
365
  parameters: {
361
366
  type: 'object',
362
367
  properties: {
363
- task_id: {
368
+ feature_id: {
364
369
  type: 'string',
365
- description: 'Task ID to retrieve',
370
+ description: 'Feature ID to retrieve',
366
371
  },
367
372
  },
368
- required: ['task_id'],
373
+ required: ['feature_id'],
369
374
  },
370
375
  isConcurrencySafe: () => true,
371
376
  isReadOnly: () => true,
@@ -373,41 +378,41 @@ export const taskGet = defineTool({
373
378
  const err = requireStore();
374
379
  if (err) return err;
375
380
 
376
- const { task_id } = input;
377
- if (!task_id) return JSON.stringify({ error: 'task_id is required' });
381
+ const { feature_id } = input;
382
+ if (!feature_id) return JSON.stringify({ error: 'feature_id is required' });
378
383
 
379
- const task = taskStore.get(task_id);
380
- if (!task) return JSON.stringify({ error: `Task not found: ${task_id}` });
384
+ const feature = featureStore.get(feature_id);
385
+ if (!feature) return JSON.stringify({ error: `Feature not found: ${feature_id}` });
381
386
 
382
387
  // Find subtasks
383
- const allTasks = taskStore.list();
388
+ const allTasks = featureStore.list();
384
389
  const subtasks = allTasks
385
- .filter(t => t.parentId === task_id)
390
+ .filter(t => t.parentId === feature_id)
386
391
  .map(t => ({ id: t.id, title: t.title, status: t.status }));
387
392
 
388
393
  return JSON.stringify({
389
- ...task,
394
+ ...feature,
390
395
  subtasks,
391
- hasProgress: !!taskStore.getProgress(task_id),
392
- hasMemory: !!taskStore.getMemory(task_id),
396
+ hasProgress: !!featureStore.getProgress(feature_id),
397
+ hasMemory: !!featureStore.getMemory(feature_id),
393
398
  }, null, 2);
394
399
  },
395
400
  });
396
401
 
397
- // ─── TaskProgress ───────────────────────────────────────
402
+ // ─── FeatureProgress ────────────────────────────────────
398
403
 
399
- export const taskProgress = defineTool({
400
- name: 'TaskProgress',
401
- description: `View or append to a task's progress log.
404
+ export const featureProgress = defineTool({
405
+ name: 'FeatureProgress',
406
+ description: `View or append to a feature's progress log.
402
407
 
403
- The progress log is an append-only timeline of what happened during task execution.
408
+ The progress log is an append-only timeline of what happened during feature execution.
404
409
  Use "view" to see the full log, or "append" to add a new entry.`,
405
410
  parameters: {
406
411
  type: 'object',
407
412
  properties: {
408
- task_id: {
413
+ feature_id: {
409
414
  type: 'string',
410
- description: 'Task ID',
415
+ description: 'Feature ID',
411
416
  },
412
417
  action: {
413
418
  type: 'string',
@@ -419,7 +424,7 @@ Use "view" to see the full log, or "append" to add a new entry.`,
419
424
  description: 'Progress note to append (required for "append")',
420
425
  },
421
426
  },
422
- required: ['task_id', 'action'],
427
+ required: ['feature_id', 'action'],
423
428
  },
424
429
  isConcurrencySafe: () => false,
425
430
  isReadOnly: (input) => input?.action === 'view',
@@ -427,20 +432,20 @@ Use "view" to see the full log, or "append" to add a new entry.`,
427
432
  const err = requireStore();
428
433
  if (err) return err;
429
434
 
430
- const { task_id, action, note } = input;
431
- if (!task_id) return JSON.stringify({ error: 'task_id is required' });
435
+ const { feature_id, action, note } = input;
436
+ if (!feature_id) return JSON.stringify({ error: 'feature_id is required' });
432
437
 
433
- const task = taskStore.get(task_id);
434
- if (!task) return JSON.stringify({ error: `Task not found: ${task_id}` });
438
+ const feature = featureStore.get(feature_id);
439
+ if (!feature) return JSON.stringify({ error: `Feature not found: ${feature_id}` });
435
440
 
436
441
  switch (action) {
437
442
  case 'view':
438
- return taskStore.getProgress(task_id) || '(No progress entries yet)';
443
+ return featureStore.getProgress(feature_id) || '(No progress entries yet)';
439
444
 
440
445
  case 'append':
441
446
  if (!note) return JSON.stringify({ error: 'note is required for "append"' });
442
- taskStore.appendProgress(task_id, note, { status: task.status });
443
- return JSON.stringify({ success: true, message: `Progress noted for "${task.title}"` });
447
+ featureStore.appendProgress(feature_id, note, { status: feature.status });
448
+ return JSON.stringify({ success: true, message: `Progress noted for "${feature.title}"` });
444
449
 
445
450
  default:
446
451
  return JSON.stringify({ error: `Unknown action: ${action}` });
@@ -448,21 +453,21 @@ Use "view" to see the full log, or "append" to add a new entry.`,
448
453
  },
449
454
  });
450
455
 
451
- // ─── TaskMemory ─────────────────────────────────────────
456
+ // ─── FeatureMemory ──────────────────────────────────────
452
457
 
453
- export const taskMemory = defineTool({
454
- name: 'TaskMemory',
455
- description: `View or update a task's memory (context notes, key decisions, references).
458
+ export const featureMemory = defineTool({
459
+ name: 'FeatureMemory',
460
+ description: `View or update a feature's memory (context notes, key decisions, references).
456
461
 
457
- Task memory stores persistent context relevant to the task — key decisions,
462
+ Feature memory stores persistent context relevant to the feature — key decisions,
458
463
  references to files, architectural notes, etc. Unlike progress (append-only),
459
464
  memory can be rewritten to keep it current.`,
460
465
  parameters: {
461
466
  type: 'object',
462
467
  properties: {
463
- task_id: {
468
+ feature_id: {
464
469
  type: 'string',
465
- description: 'Task ID',
470
+ description: 'Feature ID',
466
471
  },
467
472
  action: {
468
473
  type: 'string',
@@ -474,7 +479,7 @@ memory can be rewritten to keep it current.`,
474
479
  description: 'New memory content (required for "update")',
475
480
  },
476
481
  },
477
- required: ['task_id', 'action'],
482
+ required: ['feature_id', 'action'],
478
483
  },
479
484
  isConcurrencySafe: () => false,
480
485
  isReadOnly: (input) => input?.action === 'view',
@@ -482,20 +487,20 @@ memory can be rewritten to keep it current.`,
482
487
  const err = requireStore();
483
488
  if (err) return err;
484
489
 
485
- const { task_id, action, content } = input;
486
- if (!task_id) return JSON.stringify({ error: 'task_id is required' });
490
+ const { feature_id, action, content } = input;
491
+ if (!feature_id) return JSON.stringify({ error: 'feature_id is required' });
487
492
 
488
- const task = taskStore.get(task_id);
489
- if (!task) return JSON.stringify({ error: `Task not found: ${task_id}` });
493
+ const feature = featureStore.get(feature_id);
494
+ if (!feature) return JSON.stringify({ error: `Feature not found: ${feature_id}` });
490
495
 
491
496
  switch (action) {
492
497
  case 'view':
493
- return taskStore.getMemory(task_id) || '(No memory entries yet)';
498
+ return featureStore.getMemory(feature_id) || '(No memory entries yet)';
494
499
 
495
500
  case 'update':
496
501
  if (!content) return JSON.stringify({ error: 'content is required for "update"' });
497
- taskStore.updateMemory(task_id, content);
498
- return JSON.stringify({ success: true, message: `Memory updated for "${task.title}"` });
502
+ featureStore.updateMemory(feature_id, content);
503
+ return JSON.stringify({ success: true, message: `Memory updated for "${feature.title}"` });
499
504
 
500
505
  default:
501
506
  return JSON.stringify({ error: `Unknown action: ${action}` });
@@ -503,24 +508,24 @@ memory can be rewritten to keep it current.`,
503
508
  },
504
509
  });
505
510
 
506
- // ─── FollowupTask ───────────────────────────────────────
511
+ // ─── FollowupFeature ────────────────────────────────────
507
512
 
508
- export const followupTask = defineTool({
509
- name: 'FollowupTask',
510
- description: `Create a follow-up task linked to an existing task.
513
+ export const followupFeature = defineTool({
514
+ name: 'FollowupFeature',
515
+ description: `Create a follow-up feature linked to an existing feature.
511
516
 
512
- Use when a completed task reveals additional work needed.
513
- The new task is linked as a child of the original.`,
517
+ Use when a completed feature reveals additional work needed.
518
+ The new feature is linked as a child of the original.`,
514
519
  parameters: {
515
520
  type: 'object',
516
521
  properties: {
517
- parent_task_id: {
522
+ parent_feature_id: {
518
523
  type: 'string',
519
- description: 'ID of the original task',
524
+ description: 'ID of the original feature',
520
525
  },
521
526
  title: {
522
527
  type: 'string',
523
- description: 'Follow-up task title',
528
+ description: 'Follow-up feature title',
524
529
  },
525
530
  description: {
526
531
  type: 'string',
@@ -531,7 +536,7 @@ The new task is linked as a child of the original.`,
531
536
  enum: ['low', 'medium', 'high', 'critical'],
532
537
  },
533
538
  },
534
- required: ['parent_task_id', 'title'],
539
+ required: ['parent_feature_id', 'title'],
535
540
  },
536
541
  isConcurrencySafe: () => false,
537
542
  isReadOnly: () => false,
@@ -539,31 +544,31 @@ The new task is linked as a child of the original.`,
539
544
  const err = requireStore();
540
545
  if (err) return err;
541
546
 
542
- const { parent_task_id, title, description, priority = 'medium' } = input;
543
- if (!parent_task_id) return JSON.stringify({ error: 'parent_task_id is required' });
547
+ const { parent_feature_id, title, description, priority = 'medium' } = input;
548
+ if (!parent_feature_id) return JSON.stringify({ error: 'parent_feature_id is required' });
544
549
  if (!title) return JSON.stringify({ error: 'title is required' });
545
550
 
546
- const parent = taskStore.get(parent_task_id);
547
- if (!parent) return JSON.stringify({ error: `Parent task not found: ${parent_task_id}` });
551
+ const parent = featureStore.get(parent_feature_id);
552
+ if (!parent) return JSON.stringify({ error: `Parent feature not found: ${parent_feature_id}` });
548
553
 
549
- const id = `task-${randomUUID().slice(0, 8)}`;
550
- const task = {
554
+ const id = `feat-${randomUUID().slice(0, 8)}`;
555
+ const feature = {
551
556
  id,
552
557
  title,
553
558
  description: description || `Follow-up to: ${parent.title}`,
554
559
  priority,
555
560
  status: 'pending',
556
- parentId: parent_task_id,
561
+ parentId: parent_feature_id,
557
562
  createdAt: Date.now(),
558
563
  updatedAt: Date.now(),
559
564
  };
560
565
 
561
- taskStore.create(task);
566
+ featureStore.create(feature);
562
567
 
563
568
  return JSON.stringify({
564
569
  success: true,
565
- task: { id, title, priority, status: 'pending', parentId: parent_task_id },
566
- message: `Follow-up task created: ${title} (linked to ${parent.title})`,
570
+ feature: { id, title, priority, status: 'pending', parentId: parent_feature_id },
571
+ message: `Follow-up feature created: ${title} (linked to ${parent.title})`,
567
572
  });
568
573
  },
569
574
  });
@@ -601,18 +606,18 @@ approach, steps, and status of the current work.`,
601
606
 
602
607
  switch (action) {
603
608
  case 'view':
604
- return taskStore.getPlan() || '(No plan set yet)';
609
+ return featureStore.getPlan() || '(No plan set yet)';
605
610
 
606
611
  case 'update':
607
612
  if (!content) return JSON.stringify({ error: 'content is required for "update"' });
608
- taskStore.setPlan(content);
613
+ featureStore.setPlan(content);
609
614
  return JSON.stringify({ success: true, message: 'Plan updated', length: content.length });
610
615
 
611
616
  case 'append': {
612
617
  if (!content) return JSON.stringify({ error: 'content is required for "append"' });
613
- const existing = taskStore.getPlan();
618
+ const existing = featureStore.getPlan();
614
619
  const newPlan = existing ? `${existing}\n\n${content}` : content;
615
- taskStore.setPlan(newPlan);
620
+ featureStore.setPlan(newPlan);
616
621
  return JSON.stringify({ success: true, message: 'Plan updated (appended)', length: newPlan.length });
617
622
  }
618
623
 
@@ -622,9 +627,9 @@ approach, steps, and status of the current work.`,
622
627
  },
623
628
  });
624
629
 
625
- // ─── TaskSummaryPost (task-334n) ────────────────────────
630
+ // ─── FeatureSummaryPost (task-334n) ─────────────────────
626
631
 
627
- import { postSummary } from '../tasks/summary.js';
632
+ import { postSummary } from '../features/summary.js';
628
633
  import { openGroup } from '../groups/group-store.js';
629
634
  import { join } from 'path';
630
635
 
@@ -632,48 +637,48 @@ import { join } from 'path';
632
637
  * task-334n §B — initiator posts a progress summary to the group log.
633
638
  * Triggers the summary-extractor automatically (§C).
634
639
  */
635
- export const taskSummaryPost = defineTool({
636
- name: 'task_summary_post',
637
- description: `Post a progress summary for a multi-VP task (task-334n).
640
+ export const featureSummaryPost = defineTool({
641
+ name: 'feature_summary_post',
642
+ description: `Post a progress summary for a multi-VP feature (task-334n).
638
643
 
639
- Only the task initiator should call this. The summary is written to the
640
- group message log as \`type=summary\` and auto-extracts 2-5 task-memory
641
- entries (kind=progress|decision) via the task-memory shard lib.
644
+ Only the feature initiator should call this. The summary is written to the
645
+ group message log as \`type=summary\` and auto-extracts 2-5 feature-memory
646
+ entries (kind=progress|decision) via the feature-memory shard lib.
642
647
 
643
648
  To revise a prior summary, pass its msgId in \`supersedes\` — the old
644
649
  summary is marked \`supersededBy\` while staying on disk for audit.`,
645
650
  parameters: {
646
651
  type: 'object',
647
652
  properties: {
648
- taskId: { type: 'string', description: 'Target task id' },
649
- body: { type: 'string', description: 'Summary body (markdown)' },
650
- progress: { type: 'number', description: '0..100, optional' },
653
+ feature_id: { type: 'string', description: 'Target feature id' },
654
+ body: { type: 'string', description: 'Summary body (markdown)' },
655
+ progress: { type: 'number', description: '0..100, optional' },
651
656
  supersedes: {
652
657
  type: 'array',
653
658
  items: { type: 'string' },
654
659
  description: 'Prior summary msgIds this revision supersedes',
655
660
  },
656
661
  },
657
- required: ['taskId', 'body'],
662
+ required: ['feature_id', 'body'],
658
663
  },
659
664
  isConcurrencySafe: () => false,
660
665
  isReadOnly: () => false,
661
666
  async execute(input, ctx) {
662
667
  const err = requireStore();
663
668
  if (err) return err;
664
- const { taskId, body, progress, supersedes } = input || {};
665
- if (!taskId || !body) {
666
- return JSON.stringify({ error: 'taskId and body are required' });
669
+ const { feature_id, body, progress, supersedes } = input || {};
670
+ if (!feature_id || !body) {
671
+ return JSON.stringify({ error: 'feature_id and body are required' });
667
672
  }
668
- const task = taskStore.get(taskId);
669
- if (!task) return JSON.stringify({ error: `task not found: ${taskId}` });
670
- if (!task.groupId) {
671
- return JSON.stringify({ error: 'task has no groupId; summary requires a group' });
673
+ const feature = featureStore.get(feature_id);
674
+ if (!feature) return JSON.stringify({ error: `feature not found: ${feature_id}` });
675
+ if (!feature.groupId) {
676
+ return JSON.stringify({ error: 'feature has no groupId; summary requires a group' });
672
677
  }
673
678
 
674
679
  const currentVpId = ctx?.currentVpId;
675
- if (currentVpId && task.initiator && currentVpId !== task.initiator) {
676
- return JSON.stringify({ error: 'only the task initiator may post summaries' });
680
+ if (currentVpId && feature.initiator && currentVpId !== feature.initiator) {
681
+ return JSON.stringify({ error: 'only the feature initiator may post summaries' });
677
682
  }
678
683
 
679
684
  const yeaftDir = ctx?.yeaftDir;
@@ -681,14 +686,14 @@ summary is marked \`supersededBy\` while staying on disk for audit.`,
681
686
  return JSON.stringify({ error: 'yeaftDir missing from tool context' });
682
687
  }
683
688
  const groupsRoot = join(yeaftDir, 'groups');
684
- const memoryDir = join(groupsRoot, task.groupId, 'tasks', task.id, 'memory');
689
+ const memoryDir = join(groupsRoot, feature.groupId, 'features', feature.id, 'memory');
685
690
 
686
- const group = openGroup(groupsRoot, task.groupId);
691
+ const group = openGroup(groupsRoot, feature.groupId);
687
692
  try {
688
693
  const res = postSummary({
689
694
  group,
690
- taskId,
691
- fromVpId: currentVpId || task.initiator || 'unknown',
695
+ featureId: feature_id,
696
+ fromVpId: currentVpId || feature.initiator || 'unknown',
692
697
  body,
693
698
  progress,
694
699
  supersedes,