@goodandready/dsh-goal 0.2.30 → 0.2.32

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.
@@ -192,6 +192,12 @@ export function buildSnapshot(goal, sid, engine) {
192
192
  enableMilestoneDependencies: engine.enableMilestoneDependencies ?? true,
193
193
  soundScheme: engine.soundScheme ?? 'default',
194
194
  enableVoiceAnnouncements: engine.enableVoiceAnnouncements ?? false,
195
+ enableMilestoneCompaction: engine.enableMilestoneCompaction ?? true,
196
+ parentGoalId: null,
197
+ parentSessionId: null,
198
+ isSubGoal: false,
199
+ subGoals: [],
200
+ breadcrumbs: [],
195
201
  operatorQuery: null,
196
202
  waitingForOperator: false,
197
203
  };
@@ -253,6 +259,12 @@ export function buildSnapshot(goal, sid, engine) {
253
259
  enableMilestoneDependencies: engine.enableMilestoneDependencies ?? true,
254
260
  soundScheme: engine.soundScheme ?? 'default',
255
261
  enableVoiceAnnouncements: engine.enableVoiceAnnouncements ?? false,
262
+ enableMilestoneCompaction: engine.enableMilestoneCompaction ?? true,
263
+ parentGoalId: goal.parentGoalId || null,
264
+ parentSessionId: goal.parentSessionId || null,
265
+ isSubGoal: Boolean(goal.parentGoalId || goal.isSubGoal),
266
+ subGoals: Array.isArray(goal.subGoals) ? goal.subGoals : [],
267
+ breadcrumbs: typeof engine.getSubGoalBreadcrumbs === "function" ? engine.getSubGoalBreadcrumbs(sid) : [],
256
268
  operatorQuery: goal.operatorQuery || null,
257
269
  waitingForOperator: Boolean(goal.waitingForOperator),
258
270
  };
@@ -1,4 +1,6 @@
1
1
  import { verifyMilestoneAcceptance } from './engine-verification.js';
2
+ import { getEngineGoalDiff } from './engine-diff.js';
3
+ import { syncGoalToKanban, syncMilestoneStatusToKanban, handleKanbanCardMoved } from './engine-kanban-sync.js';
2
4
  import { askOperatorQuery, answerOperatorQuery } from './engine-operator-query.js';
3
5
  import { GoalMode, GoalState, MilestoneStatus, formatElapsed, formatETA, detectLanguage, sessionIdOf, calculateElapsedSeconds, calculateRemainingSeconds, buildSnapshot, getSafeCwd } from './goal-engine-constants.js';
4
6
  import { getGitCurrentCommit, exportReportMarkdown, exportReportGitHubPR, createMilestoneCheckpoint, rollbackToCheckpoint, saveGoalArtifact } from './engine-reports.js';
@@ -11,8 +13,10 @@ import { parseIssueRef, parseIssueChecklist, syncMilestoneWithIssue } from './en
11
13
  import { generateRetrospectiveData } from './engine-retrospective.js';
12
14
  import { generatePreplanDraft } from './engine-templates.js';
13
15
  import { pruneInactiveSessions, addNudgeToGoal, consumeGoalNudge, extendGoalBudget, setGoalMode, pauseGoal, resumeGoal, cancelGoal } from './engine-sessions.js';
16
+ import { compactMilestone, estimateCompactionSavings } from './engine-compaction.js';
17
+ import { spawnSubGoal, completeSubGoal, syncSubGoalToParent, getSubGoalBreadcrumbs, getSubGoalsList } from './engine-subgoals.js';
14
18
 
15
- export { GoalMode, GoalState, MilestoneStatus, formatElapsed, formatETA, detectLanguage, sessionIdOf, matchMilestone, parseIssueChecklist, getGitCurrentCommit, exportReportMarkdown, exportReportGitHubPR, createMilestoneCheckpoint, rollbackToCheckpoint, saveGoalArtifact };
19
+ export { GoalMode, GoalState, MilestoneStatus, formatElapsed, formatETA, detectLanguage, sessionIdOf, matchMilestone, parseIssueChecklist, getGitCurrentCommit, exportReportMarkdown, exportReportGitHubPR, createMilestoneCheckpoint, rollbackToCheckpoint, saveGoalArtifact, compactMilestone, estimateCompactionSavings, spawnSubGoal, completeSubGoal, getSubGoalBreadcrumbs, getSubGoalsList };
16
20
 
17
21
  /**
18
22
  * Isolated Goal Management Engine for DeepSeek Harness
@@ -31,8 +35,8 @@ export class GoalEngine {
31
35
  enableLiveActivityFeed: options.enableLiveActivityFeed ?? true, enablePreplanning: options.enablePreplanning ?? true,
32
36
  autoBranchOnGoalStart: options.autoBranchOnGoalStart ?? true, enablePostGoalRetrospective: options.enablePostGoalRetrospective ?? true,
33
37
  autoScaleBudgetNearCompletion: options.autoScaleBudgetNearCompletion ?? false, enableTemplatesDrawer: options.enableTemplatesDrawer ?? true,
34
- enableMilestoneDependencies: options.enableMilestoneDependencies ?? true, soundScheme: options.soundScheme ?? 'default',
35
- enableVoiceAnnouncements: options.enableVoiceAnnouncements ?? false, approvalGate: options.approvalGate || null,
38
+ enableMilestoneDependencies: options.enableMilestoneDependencies ?? true, soundScheme: options.soundScheme ?? 'default', enableKanbanSync: options.enableKanbanSync ?? true, ctx: options.ctx || null, kanbanService: options.kanbanService || null, kanbanStore: options.kanbanStore || null,
39
+ enableVoiceAnnouncements: options.enableVoiceAnnouncements ?? false, enableMilestoneCompaction: options.enableMilestoneCompaction ?? true, approvalGate: options.approvalGate || null,
36
40
  checkApproval: options.checkApproval || null, maxSessions: options.maxSessions ?? 100,
37
41
  });
38
42
  this.goals = new Map();
@@ -56,10 +60,7 @@ export class GoalEngine {
56
60
  flushSync() { this.store.flushSync(); }
57
61
  saveStateToDisk() { this.flushSync(); }
58
62
 
59
- subscribe(callback) {
60
- this.listeners.add(callback);
61
- return () => this.listeners.delete(callback);
62
- }
63
+ subscribe(callback) { this.listeners.add(callback); return () => this.listeners.delete(callback); }
63
64
 
64
65
  emit(sessionId = 'default', immediate = false) {
65
66
  this.scheduleSave(immediate);
@@ -74,21 +75,9 @@ export class GoalEngine {
74
75
  }
75
76
  }
76
77
 
77
- recordProgress(sessionId = 'default') {
78
- this.stallCounters.set(sessionId || 'default', 0);
79
- }
80
-
81
- incrementStallCount(sessionId = 'default') {
82
- const sid = sessionId || 'default';
83
- const current = this.stallCounters.get(sid) || 0;
84
- const next = current + 1;
85
- this.stallCounters.set(sid, next);
86
- return next;
87
- }
88
-
89
- getStallCount(sessionId = 'default') {
90
- return this.stallCounters.get(sessionId || 'default') || 0;
91
- }
78
+ recordProgress(sessionId = 'default') { this.stallCounters.set(sessionId || 'default', 0); }
79
+ incrementStallCount(sessionId = 'default') { const sid = sessionId || 'default'; const n = (this.stallCounters.get(sid) || 0) + 1; this.stallCounters.set(sid, n); return n; }
80
+ getStallCount(sessionId = 'default') { return this.stallCounters.get(sessionId || 'default') || 0; }
92
81
 
93
82
  updateConfig(config = {}) {
94
83
  const rawMax = config.defaultMaxIterations ?? config.maxIterations;
@@ -102,7 +91,7 @@ export class GoalEngine {
102
91
  }
103
92
  }
104
93
  }
105
- const boolKeys = ['autoDrive', 'enableSound', 'showQuickLaunchButton', 'autoCheckpointOnMilestone', 'enableLiveActivityFeed', 'enablePreplanning', 'autoBranchOnGoalStart', 'enablePostGoalRetrospective', 'autoScaleBudgetNearCompletion', 'enableTemplatesDrawer', 'enableMilestoneDependencies', 'enableVoiceAnnouncements'];
94
+ const boolKeys = ['autoDrive', 'enableSound', 'showQuickLaunchButton', 'autoCheckpointOnMilestone', 'enableLiveActivityFeed', 'enablePreplanning', 'autoBranchOnGoalStart', 'enablePostGoalRetrospective', 'autoScaleBudgetNearCompletion', 'enableTemplatesDrawer', 'enableMilestoneDependencies', 'enableVoiceAnnouncements', 'enableMilestoneCompaction'];
106
95
  for (const k of boolKeys) if (typeof config[k] === 'boolean') this[k] = config[k];
107
96
  if (typeof config.consecutiveToolFailureLimit === 'number') this.consecutiveToolFailureLimit = Math.max(0, config.consecutiveToolFailureLimit);
108
97
  if (typeof config.maxTokenBudget === 'number') this.maxTokenBudget = Math.max(0, config.maxTokenBudget);
@@ -180,7 +169,7 @@ export class GoalEngine {
180
169
  title: cleanTitle,
181
170
  description: options.description?.trim() || '',
182
171
  lang: options.lang || detectLanguage(cleanTitle),
183
- cwd: options.cwd ? getSafeCwd(options.cwd) : null,
172
+ cwd: options.cwd ? getSafeCwd(options.cwd, options.allowedRoots || this.allowedRoots || []) : null,
184
173
  mode: initialMode,
185
174
  state: initialState,
186
175
  startedAt: now,
@@ -193,7 +182,7 @@ export class GoalEngine {
193
182
  budgetWarningTriggered: false,
194
183
  budgetAutoScaled: false,
195
184
  gitStartCommit: gitCommit || null,
196
- gitBranch: null,
185
+ gitBranch: options.gitBranch || options.branchName || null,
197
186
  gitParentBranch: null,
198
187
  pendingNudge: null,
199
188
  nudges: [],
@@ -206,6 +195,10 @@ export class GoalEngine {
206
195
  issueRef: parseIssueRef(cleanTitle),
207
196
  operatorQuery: null,
208
197
  waitingForOperator: false,
198
+ parentGoalId: options.parentGoalId || null,
199
+ parentSessionId: options.parentSessionId || null,
200
+ isSubGoal: Boolean(options.parentGoalId),
201
+ subGoals: [],
209
202
  tokensUsage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
210
203
  logs: [{
211
204
  timestamp: now,
@@ -303,19 +296,18 @@ export class GoalEngine {
303
296
  m.status = MilestoneStatus.COMPLETED;
304
297
  }
305
298
  }
299
+ if (goal.parentSessionId) syncSubGoalToParent(this, sid, summary);
306
300
  this.stallCounters.delete(sid);
307
301
  this.toolFailureCounters.delete(sid);
308
302
  this.emit(sid, true);
309
303
  return this.getSnapshot(sid);
310
304
  }
311
305
 
312
- setMilestones(milestonesList, sessionId = 'default') {
313
- this.addMilestones(milestonesList, true, sessionId);
314
- return this.getSnapshot(sessionId);
315
- }
306
+ setMilestones(milestonesList, sessionId = 'default') { this.addMilestones(milestonesList, true, sessionId); return this.getSnapshot(sessionId); }
316
307
 
317
308
  addMilestones(milestonesList, shouldEmit = true, sessionId = 'default') {
318
- const sid = sessionId || 'default';
309
+ const sid = typeof shouldEmit === 'string' ? shouldEmit : (sessionId || 'default');
310
+ const shouldEmitFlag = typeof shouldEmit === 'boolean' ? shouldEmit : true;
319
311
  const goal = this.goals.get(sid);
320
312
  if (!goal || !Array.isArray(milestonesList)) return [];
321
313
 
@@ -325,7 +317,7 @@ export class GoalEngine {
325
317
  }
326
318
  this.recordProgress(sid);
327
319
  recordActivity(goal, { type: 'milestone', tool: 'addMilestones', status: 'success', details: `Added ${newMilestones.length} milestones` });
328
- if (shouldEmit) this.emit(sid);
320
+ if (shouldEmitFlag) this.emit(sid);
329
321
  return newMilestones;
330
322
  }
331
323
 
@@ -359,8 +351,10 @@ export class GoalEngine {
359
351
 
360
352
  const prevStatus = res.prevStatus;
361
353
  recordActivity(goal, { type: 'milestone', tool: 'updateMilestone', status: 'success', details: `${id} -> ${status}` });
354
+ if (this.enableKanbanSync) syncMilestoneStatusToKanban(target, { goal, ctx: this.ctx, store: this.kanbanStore, kanbanService: this.kanbanService }).catch(() => {});
362
355
 
363
356
  if (target.status === MilestoneStatus.COMPLETED && prevStatus !== MilestoneStatus.COMPLETED) {
357
+ if (this.enableMilestoneCompaction !== false) compactMilestone(target, { goal, cwd: goal.cwd });
364
358
  if (this.autoCheckpointOnMilestone) {
365
359
  const commitHash = createMilestoneCheckpoint(target, sid);
366
360
  if (commitHash) {
@@ -435,9 +429,7 @@ export class GoalEngine {
435
429
  return true;
436
430
  }
437
431
 
438
- getElapsedSeconds(sessionId = 'default') {
439
- return calculateElapsedSeconds(this.goals.get(sessionId || 'default'));
440
- }
432
+ getElapsedSeconds(sessionId = 'default') { return calculateElapsedSeconds(this.goals.get(sessionId || 'default')); }
441
433
 
442
434
  addTokenUsage(usage, sessionId = 'default') {
443
435
  if (!usage || typeof usage !== 'object') return;
@@ -467,19 +459,9 @@ export class GoalEngine {
467
459
  this.emit(sid);
468
460
  }
469
461
 
470
- getEstimatedRemainingSeconds(sessionId = 'default') {
471
- const sid = sessionId || 'default';
472
- return calculateRemainingSeconds(this.goals.get(sid), this.getElapsedSeconds(sid));
473
- }
474
-
475
- getSnapshot(sessionId = 'default') {
476
- const sid = sessionId || 'default';
477
- return buildSnapshot(this.goals.get(sid), sid, this);
478
- }
479
-
480
- getGoalSnapshot(sessionId = 'default') {
481
- return this.getSnapshot(sessionId);
482
- }
462
+ getEstimatedRemainingSeconds(sessionId = 'default') { const sid = sessionId || 'default'; return calculateRemainingSeconds(this.goals.get(sid), this.getElapsedSeconds(sid)); }
463
+ getSnapshot(sessionId = 'default') { const sid = sessionId || 'default'; return buildSnapshot(this.goals.get(sid), sid, this); }
464
+ getGoalSnapshot(sessionId = 'default') { return this.getSnapshot(sessionId); }
483
465
 
484
466
  getStatePromptInjection(sessionId = 'default') {
485
467
  const sid = sessionId || 'default';
@@ -579,8 +561,17 @@ export class GoalEngine {
579
561
  return res;
580
562
  }
581
563
 
564
+ getDiff(sessionId = 'default', options = {}) { return getEngineGoalDiff(this, sessionId, options); }
565
+ syncKanban(sessionId = 'default', options = {}) { return syncGoalToKanban(this, sessionId, options); }
566
+ handleKanbanCardMove(payload = {}, sessionId = 'default') { return handleKanbanCardMoved(this, payload, sessionId); }
567
+
582
568
  isPlanMode(sessionId = 'default') {
583
569
  const goal = this.goals.get(sessionId || 'default');
584
570
  return Boolean(goal && (goal.mode === GoalMode.PLAN || goal.state === GoalState.PLANNING));
585
571
  }
572
+ spawnSubGoal(options = {}, sessionId = 'default') { return spawnSubGoal(this, sessionId, options); }
573
+ completeSubGoal(sessionId, summary = '') { return completeSubGoal(this, sessionId, summary); }
574
+ getSubGoals(sessionId = 'default') { return getSubGoalsList(this, sessionId); }
575
+ getSubGoalBreadcrumbs(sessionId = 'default') { return getSubGoalBreadcrumbs(this, sessionId); }
576
+ getCompactionSavings(sessionId = 'default') { const g = this.goals.get(sessionId || 'default'); return estimateCompactionSavings(g?.milestones || [], g?.lang || 'en'); }
586
577
  }
package/lib/index.js CHANGED
@@ -143,13 +143,6 @@ export function apply(ctx, config = {}) {
143
143
  logger.warn?.('[dsh-goal] Legacy migration check failed:', migErr.message);
144
144
  }
145
145
 
146
- // Register addon tools: goal_milestones and goal_checklist
147
- registerTools(ctx, {
148
- milestoneManager,
149
- goalsService: safeService(ctx, "goals"),
150
- logger,
151
- });
152
-
153
146
  // Support fallback engine for tests or non-core environments
154
147
  let fallbackEngine = null;
155
148
  if (!safeService(ctx, "goals")) {
@@ -168,12 +161,22 @@ export function apply(ctx, config = {}) {
168
161
  autoScaleBudgetNearCompletion: currentSettings.autoScaleBudgetNearCompletion,
169
162
  enableTemplatesDrawer: currentSettings.enableTemplatesDrawer,
170
163
  enableMilestoneDependencies: currentSettings.enableMilestoneDependencies,
164
+ enableMilestoneCompaction: currentSettings.enableMilestoneCompaction,
171
165
  approvalGate: safeService(ctx, "approvalGate"),
172
166
  logger,
173
167
  });
168
+ ctx.goalEngine = fallbackEngine;
174
169
  registerPlanModeGuard(ctx, { engine: fallbackEngine, logger });
175
170
  }
176
171
 
172
+ // Register addon tools: goal_milestones, goal_checklist, goal_spawn_subgoal
173
+ registerTools(ctx, {
174
+ milestoneManager,
175
+ goalsService: safeService(ctx, "goals"),
176
+ engine: fallbackEngine,
177
+ logger,
178
+ });
179
+
177
180
  // 1. One-click auto-updater registration (Issue #99)
178
181
  if (ctx?.webServer?.register) {
179
182
  if (typeof ctx?.effect === 'function') {
package/lib/routes.js CHANGED
@@ -383,6 +383,28 @@ export function registerRoutes(ctx, {
383
383
  }));
384
384
  }
385
385
 
386
+ // GET /dsh-goal/diff
387
+ if (req.method === 'GET' && (pathname === '/dsh-goal/diff' || pathname === '/dsh-goal/diff/')) {
388
+ const rejection = getRequestRejection(req, { ctx });
389
+ if (rejection !== undefined) {
390
+ res.statusCode = rejection;
391
+ return res.end(JSON.stringify({ error: rejection === 401 ? 'Unauthorized: authentication required' : 'Forbidden: untrusted caller origin' }));
392
+ }
393
+ const sid = sessionIdOf(req, 'default');
394
+ const target = url.searchParams.get('target') || undefined;
395
+ const base = url.searchParams.get('base') || undefined;
396
+ const file = url.searchParams.get('file') || undefined;
397
+ const cwd = url.searchParams.get('cwd') || undefined;
398
+
399
+ if (engine) {
400
+ const diffResult = engine.getDiff(sid, { target, base, file, cwd });
401
+ res.statusCode = diffResult.ok ? 200 : 400;
402
+ return res.end(JSON.stringify(diffResult));
403
+ }
404
+ res.statusCode = 404;
405
+ return res.end(JSON.stringify({ ok: false, error: 'Goal engine unavailable' }));
406
+ }
407
+
386
408
  // POST /dsh-goal/action
387
409
  // CSRF & Same-Origin guard
388
410
  if (req.method === 'POST' && (pathname === '/dsh-goal/action' || pathname === '/dsh-goal/action/')) {
@@ -429,7 +451,7 @@ export function registerRoutes(ctx, {
429
451
  body += chunk;
430
452
  };
431
453
 
432
- const onEnd = () => {
454
+ const onEnd = async () => {
433
455
  if (limitExceeded) return;
434
456
  try {
435
457
  const data = JSON.parse(body || '{}');
@@ -829,6 +851,53 @@ export function registerRoutes(ctx, {
829
851
  res.statusCode = 200;
830
852
  return res.end(JSON.stringify({ ok: true, templates: getTemplatesCatalogue() }));
831
853
 
854
+ case 'kanban_move': {
855
+ if (engine) {
856
+ result = engine.handleKanbanCardMove(data, sid);
857
+ if (!result.ok) res.statusCode = 400;
858
+ } else {
859
+ res.statusCode = 404;
860
+ result = { ok: false, error: 'Goal engine unavailable' };
861
+ }
862
+ break;
863
+ }
864
+
865
+ case 'kanban_sync': {
866
+ if (engine) {
867
+ result = await engine.syncKanban(sid, { ctx });
868
+ if (!result.ok && !result.skipped) res.statusCode = 400;
869
+ } else {
870
+ res.statusCode = 404;
871
+ result = { ok: false, error: 'Goal engine unavailable' };
872
+ }
873
+ break;
874
+ }
875
+
876
+ case 'spawn_subgoal': {
877
+ const subTitle = typeof data.title === 'string' ? data.title.trim() : '';
878
+ if (!subTitle) {
879
+ res.statusCode = 400;
880
+ return res.end(JSON.stringify({ error: 'Sub-goal title is required' }));
881
+ }
882
+ if (engine) {
883
+ const spawnRes = engine.spawnSubGoal({
884
+ title: subTitle,
885
+ prompt: data.prompt || data.description,
886
+ maxIterations: data.maxIterations ?? data.max_iterations,
887
+ inheritContext: data.inheritContext ?? data.inherit_context,
888
+ }, sid);
889
+ if (!spawnRes.ok) {
890
+ res.statusCode = 400;
891
+ return res.end(JSON.stringify({ error: spawnRes.error || 'Failed to spawn sub-goal' }));
892
+ }
893
+ result = spawnRes;
894
+ } else {
895
+ res.statusCode = 404;
896
+ result = { ok: false, error: 'Goal engine unavailable' };
897
+ }
898
+ break;
899
+ }
900
+
832
901
  case 'instantiate_template': {
833
902
  const tplId = data.templateId || data.id;
834
903
  if (!tplId) {
@@ -870,6 +939,7 @@ export function registerRoutes(ctx, {
870
939
  '/dsh-goal/events': ['GET'],
871
940
  '/dsh-goal/templates': ['GET'],
872
941
  '/dsh-goal/state': ['GET'],
942
+ '/dsh-goal/diff': ['GET'],
873
943
  '/dsh-goal/action': ['POST'],
874
944
  };
875
945
  const cleanPath = pathname.replace(/\/+$/, '') || '/';
package/lib/tools.js CHANGED
@@ -376,5 +376,74 @@ export function registerTools(ctx, { milestoneManager, goalsService, logger = co
376
376
  output: JSON_OUTPUT,
377
377
  execute: handleGoalAskOperator,
378
378
  });
379
+
380
+ // Tool 7: goal_spawn_subgoal (Issue #170)
381
+ const handleGoalSpawnSubgoal = async (args = {}, toolCtx = {}) => {
382
+ const parentSessionId = args.goalId || args.goal_id || resolveGoalId(toolCtx);
383
+ const title = args.title || args.goal;
384
+ if (!title || typeof title !== 'string' || !title.trim()) {
385
+ throw new Error('Field "title" is required and must be a non-empty string');
386
+ }
387
+
388
+ const engine = ctx.goalEngine || (ctx.get && ctx.get('goalEngine'));
389
+ if (engine && typeof engine.spawnSubGoal === 'function') {
390
+ const res = engine.spawnSubGoal({
391
+ title: title.trim(),
392
+ prompt: args.prompt || args.description,
393
+ maxIterations: args.max_iterations ?? args.maxIterations,
394
+ inheritContext: args.inherit_context ?? args.inheritContext,
395
+ }, parentSessionId);
396
+
397
+ if (!res.ok) {
398
+ throw new Error(`Failed to spawn sub-goal: ${res.error}`);
399
+ }
400
+
401
+ return {
402
+ ok: true,
403
+ parentGoalId: res.parentGoalId,
404
+ parentSessionId: res.parentSessionId,
405
+ subGoalId: res.subGoalId,
406
+ childSessionId: res.childSessionId,
407
+ title: title.trim(),
408
+ message: 'Sub-goal spawned with isolated execution context. Complete sub-milestones and invoke goal_finish to conclude.',
409
+ };
410
+ }
411
+
412
+ return {
413
+ ok: true,
414
+ parentSessionId,
415
+ title: title.trim(),
416
+ message: 'Sub-goal registered (fallback mode).',
417
+ };
418
+ };
419
+
420
+ safeRegister({
421
+ name: 'goal_spawn_subgoal',
422
+ description: 'Spawn a child sub-goal with isolated context and task checklist (#170). When all sub-milestones are completed, invoking goal_finish returns deliverables and control to the parent goal.',
423
+ parameters: {
424
+ type: 'object',
425
+ properties: {
426
+ title: {
427
+ type: 'string',
428
+ description: 'Clear, actionable title of the sub-goal',
429
+ },
430
+ prompt: {
431
+ type: 'string',
432
+ description: 'Optional context, detailed instructions, or constraints for the sub-goal',
433
+ },
434
+ max_iterations: {
435
+ type: 'number',
436
+ description: 'Maximum iteration budget for the sub-goal (default: 15)',
437
+ },
438
+ inherit_context: {
439
+ type: 'boolean',
440
+ description: 'Whether the child sub-goal inherits parent goal context summary (default: true)',
441
+ },
442
+ },
443
+ required: ['title'],
444
+ },
445
+ output: JSON_OUTPUT,
446
+ execute: handleGoalSpawnSubgoal,
447
+ });
379
448
  });
380
449
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-goal",
3
- "version": "0.2.30",
3
+ "version": "0.2.32",
4
4
  "description": "Core Goal Addon (Token Budget, Milestones, Git Snapshots & Retrospective Reports) for DeepSeek Harness",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",