@goodandready/dsh-goal 0.2.20 → 0.2.21
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/lib/addon-store.js +26 -7
- package/lib/engine-milestones.js +10 -1
- package/lib/engine-reports.js +4 -4
- package/lib/engine-retrospective.js +4 -2
- package/lib/engine-sessions.js +1 -0
- package/lib/engine-store.js +4 -3
- package/lib/goal-engine.js +23 -12
- package/lib/index.js +51 -4
- package/lib/milestone-manager.js +10 -6
- package/lib/routes.js +7 -0
- package/lib/tools.js +93 -2
- package/package.json +1 -1
package/lib/addon-store.js
CHANGED
|
@@ -22,6 +22,15 @@ export class AddonStore {
|
|
|
22
22
|
getGoalData(goalId) {
|
|
23
23
|
if (!goalId) return null;
|
|
24
24
|
if (!this.goals.has(goalId)) {
|
|
25
|
+
if (this.goals.size > 100) {
|
|
26
|
+
for (const [k, v] of this.goals.entries()) {
|
|
27
|
+
const isInactive = (!v.milestones || v.milestones.length === 0 || v.milestones.every(m => m.completed));
|
|
28
|
+
if (isInactive) {
|
|
29
|
+
this.goals.delete(k);
|
|
30
|
+
if (this.goals.size <= 80) break;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
25
34
|
this.goals.set(goalId, {
|
|
26
35
|
milestones: [],
|
|
27
36
|
snapshots: [],
|
|
@@ -32,6 +41,13 @@ export class AddonStore {
|
|
|
32
41
|
return this.goals.get(goalId);
|
|
33
42
|
}
|
|
34
43
|
|
|
44
|
+
removeGoalData(goalId) {
|
|
45
|
+
if (!goalId) return false;
|
|
46
|
+
const deleted = this.goals.delete(goalId);
|
|
47
|
+
if (deleted) this.scheduleSave();
|
|
48
|
+
return deleted;
|
|
49
|
+
}
|
|
50
|
+
|
|
35
51
|
getMilestones(goalId) {
|
|
36
52
|
const data = this.getGoalData(goalId);
|
|
37
53
|
return data ? data.milestones : [];
|
|
@@ -102,13 +118,14 @@ export class AddonStore {
|
|
|
102
118
|
return;
|
|
103
119
|
}
|
|
104
120
|
if (!this.saveTimer) {
|
|
105
|
-
|
|
121
|
+
const timer = setTimeout(() => {
|
|
106
122
|
this.saveTimer = null;
|
|
107
123
|
this.writeToDiskSync();
|
|
108
124
|
}, 200);
|
|
109
|
-
if (typeof
|
|
110
|
-
|
|
125
|
+
if (typeof timer?.unref === 'function') {
|
|
126
|
+
timer.unref();
|
|
111
127
|
}
|
|
128
|
+
this.saveTimer = timer;
|
|
112
129
|
}
|
|
113
130
|
}
|
|
114
131
|
|
|
@@ -192,10 +209,12 @@ export class AddonStore {
|
|
|
192
209
|
migratedAny = true;
|
|
193
210
|
}
|
|
194
211
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
212
|
+
if (migratedAny) {
|
|
213
|
+
data._migratedToAddon = true;
|
|
214
|
+
data._migratedAt = Date.now();
|
|
215
|
+
fs.writeFileSync(legacyPath, JSON.stringify(data, null, 2), 'utf8');
|
|
216
|
+
this.scheduleSave(true);
|
|
217
|
+
}
|
|
199
218
|
return migratedAny;
|
|
200
219
|
} catch (err) {
|
|
201
220
|
this.logger.warn?.('[AddonStore] Legacy migration failed:', err.message);
|
package/lib/engine-milestones.js
CHANGED
|
@@ -116,7 +116,16 @@ export function applyMilestoneUpdate(target, status, notes = '', checklist = nul
|
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
if (status
|
|
119
|
+
if (status) {
|
|
120
|
+
if (validStatuses.length > 0 && !validStatuses.includes(status)) {
|
|
121
|
+
return {
|
|
122
|
+
prevStatus,
|
|
123
|
+
updatedStatus: prevStatus,
|
|
124
|
+
blocked: false,
|
|
125
|
+
invalidStatus: true,
|
|
126
|
+
error: `Invalid milestone status: ${status}`,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
120
129
|
target.status = status;
|
|
121
130
|
}
|
|
122
131
|
if (notes !== undefined && notes !== null && notes !== '') {
|
package/lib/engine-reports.js
CHANGED
|
@@ -184,7 +184,7 @@ export function exportReportGitHubPR(state) {
|
|
|
184
184
|
md += `\n</details>\n\n`;
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
md += `*Automated by [@goodandready/dsh-goal](https://github.com/
|
|
187
|
+
md += `*Automated by [@goodandready/dsh-goal](https://github.com/goodandready/dsh-goal)*\n`;
|
|
188
188
|
return md;
|
|
189
189
|
}
|
|
190
190
|
|
|
@@ -261,7 +261,7 @@ export function rollbackToCheckpoint(commitHash, cwd) {
|
|
|
261
261
|
* @param {string} [cwd]
|
|
262
262
|
* @returns {{ relativePath: string, fullPath: string, content: string }|null}
|
|
263
263
|
*/
|
|
264
|
-
export function saveGoalArtifact(state, cwd) {
|
|
264
|
+
export function saveGoalArtifact(state, cwd, addonData = {}) {
|
|
265
265
|
if (!state || (!state.title && !state.objective)) return null;
|
|
266
266
|
try {
|
|
267
267
|
const root = cwd || process.cwd();
|
|
@@ -281,7 +281,7 @@ export function saveGoalArtifact(state, cwd) {
|
|
|
281
281
|
const fullPath = path.join(targetDir, filename);
|
|
282
282
|
const relativePath = path.join('.dsh', 'goals', filename).replace(/\\/g, '/');
|
|
283
283
|
|
|
284
|
-
const content = exportReportMarkdown(state);
|
|
284
|
+
const content = exportReportMarkdown(state, addonData);
|
|
285
285
|
fs.writeFileSync(fullPath, content, 'utf8');
|
|
286
286
|
|
|
287
287
|
return { relativePath, fullPath, content };
|
|
@@ -295,5 +295,5 @@ export function saveGoalArtifact(state, cwd) {
|
|
|
295
295
|
*/
|
|
296
296
|
export function saveGoalReportFile(goal, addonData = {}, baseDir = null) {
|
|
297
297
|
const root = baseDir || process.cwd();
|
|
298
|
-
return saveGoalArtifact(goal, root)?.fullPath || null;
|
|
298
|
+
return saveGoalArtifact(goal, root, addonData)?.fullPath || null;
|
|
299
299
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import { MilestoneStatus, calculateElapsedSeconds } from './goal-engine-constants.js';
|
|
3
|
+
import { isValidGitRef } from './engine-git-branch.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Generate post-goal retrospective analytics card
|
|
@@ -33,8 +34,9 @@ export function generateRetrospectiveData(goal, cwd = process.cwd()) {
|
|
|
33
34
|
let gitDiffStat = '';
|
|
34
35
|
let filesChangedCount = 0;
|
|
35
36
|
try {
|
|
36
|
-
const
|
|
37
|
-
const
|
|
37
|
+
const rawStartRef = goal.gitStartCommit || goal.startCommit || 'HEAD~1';
|
|
38
|
+
const startRef = (rawStartRef && isValidGitRef(rawStartRef)) ? rawStartRef : 'HEAD~1';
|
|
39
|
+
const rawStat = execFileSync('git', ['diff', '--stat', `${startRef}..HEAD`, '--'], {
|
|
38
40
|
cwd,
|
|
39
41
|
encoding: 'utf8',
|
|
40
42
|
stdio: ['ignore', 'pipe', 'ignore'],
|
package/lib/engine-sessions.js
CHANGED
|
@@ -32,6 +32,7 @@ export function addNudgeToGoal(goal, text) {
|
|
|
32
32
|
if (!Array.isArray(goal.nudges)) goal.nudges = [];
|
|
33
33
|
const now = Date.now();
|
|
34
34
|
goal.nudges.push({ text: clean, timestamp: now });
|
|
35
|
+
if (goal.nudges.length > 50) goal.nudges = goal.nudges.slice(-50);
|
|
35
36
|
goal.pendingNudge = clean;
|
|
36
37
|
goal.logs.push({
|
|
37
38
|
timestamp: now,
|
package/lib/engine-store.js
CHANGED
|
@@ -93,13 +93,14 @@ export class EngineStore {
|
|
|
93
93
|
return;
|
|
94
94
|
}
|
|
95
95
|
if (!this.saveTimer) {
|
|
96
|
-
|
|
96
|
+
const timer = setTimeout(() => {
|
|
97
97
|
this.saveTimer = null;
|
|
98
98
|
this.writeStateToDiskSync();
|
|
99
99
|
}, 250);
|
|
100
|
-
if (typeof
|
|
101
|
-
|
|
100
|
+
if (typeof timer?.unref === 'function') {
|
|
101
|
+
timer.unref();
|
|
102
102
|
}
|
|
103
|
+
this.saveTimer = timer;
|
|
103
104
|
}
|
|
104
105
|
}
|
|
105
106
|
|
package/lib/goal-engine.js
CHANGED
|
@@ -18,7 +18,9 @@ export { getGitCurrentCommit, exportReportMarkdown, exportReportGitHubPR, create
|
|
|
18
18
|
*/
|
|
19
19
|
export class GoalEngine {
|
|
20
20
|
constructor(options = {}) {
|
|
21
|
-
this.
|
|
21
|
+
this.logger = options.logger || console;
|
|
22
|
+
const rawDefIters = options.defaultMaxIterations ?? 25;
|
|
23
|
+
this.defaultMaxIterations = typeof rawDefIters === "number" ? rawDefIters : (typeof rawDefIters?.value === "number" ? rawDefIters.value : 25);
|
|
22
24
|
this.autoDrive = options.autoDrive ?? true;
|
|
23
25
|
this.enableSound = options.enableSound ?? true;
|
|
24
26
|
this.showQuickLaunchButton = options.showQuickLaunchButton ?? true;
|
|
@@ -95,11 +97,15 @@ export class GoalEngine {
|
|
|
95
97
|
}
|
|
96
98
|
|
|
97
99
|
updateConfig(config = {}) {
|
|
98
|
-
|
|
100
|
+
const rawMax = config.defaultMaxIterations ?? config.maxIterations;
|
|
101
|
+
const maxVal = typeof rawMax === 'number' ? rawMax : (typeof rawMax?.value === 'number' ? rawMax.value : undefined);
|
|
102
|
+
if (typeof maxVal === 'number' && maxVal >= 1) {
|
|
99
103
|
const prev = this.defaultMaxIterations;
|
|
100
|
-
this.defaultMaxIterations =
|
|
104
|
+
this.defaultMaxIterations = maxVal;
|
|
101
105
|
for (const [_, goal] of this.goals) {
|
|
102
|
-
if (goal && goal.maxIterations === prev
|
|
106
|
+
if (goal && (goal.maxIterations === prev || typeof goal.maxIterations !== 'number' || typeof goal.maxIterations === 'object')) {
|
|
107
|
+
goal.maxIterations = maxVal;
|
|
108
|
+
}
|
|
103
109
|
}
|
|
104
110
|
}
|
|
105
111
|
if (typeof config.autoDrive === 'boolean') this.autoDrive = config.autoDrive;
|
|
@@ -373,13 +379,18 @@ export class GoalEngine {
|
|
|
373
379
|
enforceDependencies: this.enableMilestoneDependencies,
|
|
374
380
|
allMilestones: goal.milestones,
|
|
375
381
|
});
|
|
376
|
-
if (res.blocked) {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
382
|
+
if (res.invalidStatus || res.blocked) {
|
|
383
|
+
if (res.invalidStatus) {
|
|
384
|
+
this.logger?.warn?.(`[dsh-goal] Invalid milestone status "${status}" for milestone ${id}`);
|
|
385
|
+
}
|
|
386
|
+
if (res.blocked) {
|
|
387
|
+
recordActivity(goal, {
|
|
388
|
+
type: 'milestone',
|
|
389
|
+
tool: 'updateMilestone',
|
|
390
|
+
status: 'error',
|
|
391
|
+
details: `Milestone ${id} blocked by ${res.missingDependencies?.join(', ')}`,
|
|
392
|
+
});
|
|
393
|
+
}
|
|
383
394
|
return false;
|
|
384
395
|
}
|
|
385
396
|
|
|
@@ -401,7 +412,7 @@ export class GoalEngine {
|
|
|
401
412
|
recordActivity(goal, { type: 'issue', tool: 'syncIssueChecklist', status: 'success', details: `Issue #${syncRes.issueNumber} updated` });
|
|
402
413
|
this.emit(sid);
|
|
403
414
|
}
|
|
404
|
-
}).catch(() => {});
|
|
415
|
+
}).catch((err) => { this.logger?.warn?.("[dsh-goal] Failed to sync milestone with issue:", err?.message || err); });
|
|
405
416
|
}
|
|
406
417
|
}
|
|
407
418
|
|
package/lib/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
1
2
|
import path from 'node:path';
|
|
2
3
|
import os from 'node:os';
|
|
3
4
|
import z from '@deepseek-ai/schemastery';
|
|
@@ -130,9 +131,12 @@ export function apply(ctx, config = {}) {
|
|
|
130
131
|
|
|
131
132
|
// Attempt migration of legacy state if present
|
|
132
133
|
try {
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
134
|
+
const legacyStatePath = path.resolve(defaultStorageDir, 'dsh-goal-state.json');
|
|
135
|
+
if (fs.existsSync(legacyStatePath)) {
|
|
136
|
+
const agentsSvc = safeService(ctx, "agents");
|
|
137
|
+
const firstAgent = agentsSvc?.get?.(agentsSvc?.keys?.()?.next()?.value);
|
|
138
|
+
addonStore.migrateLegacyState(legacyStatePath, safeService(ctx, "goals"), firstAgent);
|
|
139
|
+
}
|
|
136
140
|
} catch (migErr) {
|
|
137
141
|
logger.warn?.('[dsh-goal] Legacy migration check failed:', migErr.message);
|
|
138
142
|
}
|
|
@@ -162,6 +166,7 @@ export function apply(ctx, config = {}) {
|
|
|
162
166
|
autoScaleBudgetNearCompletion: currentSettings.autoScaleBudgetNearCompletion,
|
|
163
167
|
enableTemplatesDrawer: currentSettings.enableTemplatesDrawer,
|
|
164
168
|
enableMilestoneDependencies: currentSettings.enableMilestoneDependencies,
|
|
169
|
+
logger,
|
|
165
170
|
});
|
|
166
171
|
}
|
|
167
172
|
|
|
@@ -192,14 +197,55 @@ export function apply(ctx, config = {}) {
|
|
|
192
197
|
}
|
|
193
198
|
}
|
|
194
199
|
|
|
200
|
+
const sessionAgents = new Map();
|
|
201
|
+
|
|
202
|
+
const stopRunningAgents = (sessionId = 'default') => {
|
|
203
|
+
try {
|
|
204
|
+
const agentsSvc = safeService(ctx, "agents");
|
|
205
|
+
const agent = sessionAgents.get(sessionId) || agentsSvc?.get?.(sessionId) || agentsSvc?.get?.(agentsSvc?.keys?.()?.next()?.value);
|
|
206
|
+
if (agent && typeof agent.abort === 'function') {
|
|
207
|
+
agent.abort();
|
|
208
|
+
} else if (agent && typeof agent.cancel === 'function') {
|
|
209
|
+
agent.cancel();
|
|
210
|
+
} else if (agent && typeof agent.stop === 'function') {
|
|
211
|
+
agent.stop();
|
|
212
|
+
}
|
|
213
|
+
} catch (err) {
|
|
214
|
+
logger.warn?.('[dsh-goal] Failed to stop agent:', err?.message || err);
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const resumeActiveAgent = (prompt, sessionId = 'default') => {
|
|
219
|
+
try {
|
|
220
|
+
const agentsSvc = safeService(ctx, "agents");
|
|
221
|
+
const agent = sessionAgents.get(sessionId) || agentsSvc?.get?.(sessionId) || agentsSvc?.get?.(agentsSvc?.keys?.()?.next()?.value);
|
|
222
|
+
if (agent) {
|
|
223
|
+
if (typeof agent.resume === 'function') {
|
|
224
|
+
agent.resume(prompt);
|
|
225
|
+
} else if (typeof agent.step === 'function') {
|
|
226
|
+
agent.step(prompt);
|
|
227
|
+
} else if (typeof agent.send === 'function' && prompt) {
|
|
228
|
+
agent.send(prompt);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
} catch (err) {
|
|
232
|
+
logger.warn?.('[dsh-goal] Failed to resume agent:', err?.message || err);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
195
236
|
// 2. WebServer HTTP & SSE routes (registered after updater so it remains primary)
|
|
196
237
|
const { broadcastEvent } = registerRoutes(ctx, {
|
|
197
238
|
engine: fallbackEngine,
|
|
198
239
|
milestoneManager,
|
|
240
|
+
snapshotManager: gitSnapshotManager,
|
|
199
241
|
gitSnapshotManager,
|
|
242
|
+
budgetGuard: tokenBudgetGuard,
|
|
200
243
|
tokenBudgetGuard,
|
|
201
244
|
addonStore,
|
|
202
245
|
goalsService: safeService(ctx, "goals"),
|
|
246
|
+
stopRunningAgents,
|
|
247
|
+
resumeActiveAgent,
|
|
248
|
+
sessionAgents,
|
|
203
249
|
getConfig: () => currentSettings,
|
|
204
250
|
sseClients,
|
|
205
251
|
logger,
|
|
@@ -243,6 +289,7 @@ export function apply(ctx, config = {}) {
|
|
|
243
289
|
|
|
244
290
|
// Inject milestone prompts and check budget during agent turns
|
|
245
291
|
ctx.on('agent/pre-step', async ({ agent, messages, step, signal }, next) => {
|
|
292
|
+
if (agent) { sessionAgents.set(sessionIdOf(agent, "default"), agent); }
|
|
246
293
|
const goalsSvc = safeService(ctx, "goals");
|
|
247
294
|
if (goalsSvc && typeof goalsSvc.get === "function" && agent) {
|
|
248
295
|
try {
|
|
@@ -284,7 +331,7 @@ export function apply(ctx, config = {}) {
|
|
|
284
331
|
|
|
285
332
|
// Compatibility events for turn/end and approval/asked
|
|
286
333
|
ctx.on('turn/end', (event) => {
|
|
287
|
-
//
|
|
334
|
+
// session isolation turn tracking
|
|
288
335
|
});
|
|
289
336
|
|
|
290
337
|
ctx.on('approval/asked', (event) => {
|
package/lib/milestone-manager.js
CHANGED
|
@@ -95,11 +95,15 @@ export class MilestoneManager {
|
|
|
95
95
|
|
|
96
96
|
// Best-effort Gitea issue checklist sync if issue data exists
|
|
97
97
|
const goalData = this.store.getGoalData(goalId);
|
|
98
|
-
if (goalData?.issue?.number) {
|
|
98
|
+
if (goalData?.issueRef?.issueNumber || goalData?.issue?.number) {
|
|
99
99
|
try {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
const issueRef = goalData.issueRef || { issueNumber: goalData.issue.number };
|
|
101
|
+
const fakeGoal = { issueRef, cwd: workspaceRoot };
|
|
102
|
+
syncMilestoneWithIssue(fakeGoal, m, { cwd: workspaceRoot }).catch((err) => {
|
|
103
|
+
this.logger?.warn?.('[MilestoneManager] Issue sync failed:', err?.message || err);
|
|
104
|
+
});
|
|
105
|
+
} catch (err) {
|
|
106
|
+
this.logger?.warn?.('[MilestoneManager] Issue sync error:', err?.message || err);
|
|
103
107
|
}
|
|
104
108
|
}
|
|
105
109
|
|
|
@@ -132,8 +136,8 @@ export class MilestoneManager {
|
|
|
132
136
|
|
|
133
137
|
const uncompletedChecklist = (active.checklist || []).filter(c => !c.done);
|
|
134
138
|
let clText = '';
|
|
135
|
-
if (
|
|
136
|
-
clText = ` | Next items: ${
|
|
139
|
+
if (uncompletedChecklist.length > 0) {
|
|
140
|
+
clText = ` | Next items: ${uncompletedChecklist.map(c => `[ ] ${c.text}`).join(', ')}`;
|
|
137
141
|
}
|
|
138
142
|
|
|
139
143
|
return `[Goal Milestones]: Current step: "${active.title}"${clText}`;
|
package/lib/routes.js
CHANGED
|
@@ -79,10 +79,14 @@ export function registerRoutes(ctx, {
|
|
|
79
79
|
sseClients = new Map(),
|
|
80
80
|
addonStore,
|
|
81
81
|
budgetGuard,
|
|
82
|
+
tokenBudgetGuard,
|
|
82
83
|
snapshotManager,
|
|
84
|
+
gitSnapshotManager,
|
|
83
85
|
milestoneManager,
|
|
84
86
|
logger = console,
|
|
85
87
|
} = {}) {
|
|
88
|
+
const activeBudgetGuard = budgetGuard || tokenBudgetGuard;
|
|
89
|
+
const activeSnapshotManager = snapshotManager || gitSnapshotManager;
|
|
86
90
|
// Subscribe to engine if provided (backward compatibility)
|
|
87
91
|
if (engine && typeof engine.subscribe === 'function') {
|
|
88
92
|
engine.subscribe((snapshot, sid) => {
|
|
@@ -396,6 +400,9 @@ export function registerRoutes(ctx, {
|
|
|
396
400
|
if (engine) {
|
|
397
401
|
result = engine.clear(sid);
|
|
398
402
|
}
|
|
403
|
+
if (addonStore && typeof addonStore.removeGoalData === 'function') {
|
|
404
|
+
addonStore.removeGoalData(sid);
|
|
405
|
+
}
|
|
399
406
|
if (typeof stopRunningAgents === 'function') {
|
|
400
407
|
stopRunningAgents(sid);
|
|
401
408
|
}
|
package/lib/tools.js
CHANGED
|
@@ -141,7 +141,6 @@ export function registerTools(ctx, { milestoneManager, goalsService, logger = co
|
|
|
141
141
|
},
|
|
142
142
|
output: JSON_OUTPUT,
|
|
143
143
|
execute: handleGoalMilestones,
|
|
144
|
-
handler: handleGoalMilestones,
|
|
145
144
|
});
|
|
146
145
|
|
|
147
146
|
// Tool 2: goal_checklist
|
|
@@ -181,7 +180,99 @@ export function registerTools(ctx, { milestoneManager, goalsService, logger = co
|
|
|
181
180
|
},
|
|
182
181
|
output: JSON_OUTPUT,
|
|
183
182
|
execute: handleGoalChecklist,
|
|
184
|
-
|
|
183
|
+
});
|
|
184
|
+
// Tool 3: goal_set_milestones (Issue #127 alias matching autonomous-contract prompt)
|
|
185
|
+
const handleGoalSetMilestones = async (args = {}, toolCtx = {}) => {
|
|
186
|
+
const goalId = args.goalId || args.goal_id || resolveGoalId(toolCtx);
|
|
187
|
+
let list = args.milestones;
|
|
188
|
+
if (Array.isArray(list)) {
|
|
189
|
+
list = list.map((item, idx) => {
|
|
190
|
+
if (typeof item === 'string') {
|
|
191
|
+
return { id: `m-${idx + 1}`, title: item };
|
|
192
|
+
}
|
|
193
|
+
return item;
|
|
194
|
+
});
|
|
195
|
+
} else {
|
|
196
|
+
list = [];
|
|
197
|
+
}
|
|
198
|
+
const res = mm.setMilestones(goalId, list);
|
|
199
|
+
return { ok: true, goalId, count: res.length, milestones: res };
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
safeRegister({
|
|
203
|
+
name: 'goal_set_milestones',
|
|
204
|
+
description: 'Set initial work plan milestones for the active goal.',
|
|
205
|
+
parameters: {
|
|
206
|
+
type: 'object',
|
|
207
|
+
properties: {
|
|
208
|
+
milestones: {
|
|
209
|
+
type: 'array',
|
|
210
|
+
items: { type: ['string', 'object'] },
|
|
211
|
+
description: 'Array of milestone titles or objects',
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
required: ['milestones'],
|
|
215
|
+
},
|
|
216
|
+
output: JSON_OUTPUT,
|
|
217
|
+
execute: handleGoalSetMilestones,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Tool 4: goal_update_progress (Issue #127 alias matching autonomous-contract prompt)
|
|
221
|
+
const handleGoalUpdateProgress = async (args = {}, toolCtx = {}) => {
|
|
222
|
+
const goalId = args.goalId || args.goal_id || resolveGoalId(toolCtx);
|
|
223
|
+
const milestoneId = args.milestone_id || args.milestoneId;
|
|
224
|
+
const status = args.status || 'in_progress';
|
|
225
|
+
const notes = typeof args.notes === 'string' ? args.notes : '';
|
|
226
|
+
if (!milestoneId) throw new Error('milestone_id is required');
|
|
227
|
+
if (status === 'completed') {
|
|
228
|
+
const rawCwd = toolCtx?.session?.cwd || toolCtx?.cwd || process.cwd();
|
|
229
|
+
const safeCwd = getSafeCwd(rawCwd);
|
|
230
|
+
const completed = await mm.completeMilestone(goalId, milestoneId, notes, safeCwd);
|
|
231
|
+
return { ok: true, goalId, milestoneId, status: 'completed', milestone: completed };
|
|
232
|
+
}
|
|
233
|
+
const updated = mm.updateMilestone(goalId, milestoneId, { status, notes, checklist: args.checklist });
|
|
234
|
+
return { ok: true, goalId, milestoneId, status, milestone: updated };
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
safeRegister({
|
|
238
|
+
name: 'goal_update_progress',
|
|
239
|
+
description: 'Update progress and status of a milestone.',
|
|
240
|
+
parameters: {
|
|
241
|
+
type: 'object',
|
|
242
|
+
properties: {
|
|
243
|
+
milestone_id: { type: 'string', description: 'Target milestone ID' },
|
|
244
|
+
status: { type: 'string', enum: ['pending', 'in_progress', 'completed', 'blocked'], description: 'Milestone status' },
|
|
245
|
+
notes: { type: 'string', description: 'Brief outcome or status notes' },
|
|
246
|
+
checklist: { type: 'array', items: { type: 'string' }, description: 'Subtask items' },
|
|
247
|
+
},
|
|
248
|
+
required: ['milestone_id', 'status'],
|
|
249
|
+
},
|
|
250
|
+
output: JSON_OUTPUT,
|
|
251
|
+
execute: handleGoalUpdateProgress,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Tool 5: goal_finish (Issue #127 alias matching autonomous-contract prompt)
|
|
255
|
+
const handleGoalFinish = async (args = {}, toolCtx = {}) => {
|
|
256
|
+
const goalId = args.goalId || args.goal_id || resolveGoalId(toolCtx);
|
|
257
|
+
const summary = typeof args.summary === 'string' ? args.summary : '';
|
|
258
|
+
if (goalsService && typeof goalsService.complete === 'function') {
|
|
259
|
+
try { goalsService.complete(toolCtx?.agent, summary); } catch (e) { /* ignore */ }
|
|
260
|
+
}
|
|
261
|
+
return { ok: true, goalId, completed: true, summary };
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
safeRegister({
|
|
265
|
+
name: 'goal_finish',
|
|
266
|
+
description: 'Finish active goal with final achieved outcome summary.',
|
|
267
|
+
parameters: {
|
|
268
|
+
type: 'object',
|
|
269
|
+
properties: {
|
|
270
|
+
summary: { type: 'string', description: 'Comprehensive summary of results' },
|
|
271
|
+
},
|
|
272
|
+
required: ['summary'],
|
|
273
|
+
},
|
|
274
|
+
output: JSON_OUTPUT,
|
|
275
|
+
execute: handleGoalFinish,
|
|
185
276
|
});
|
|
186
277
|
});
|
|
187
278
|
}
|
package/package.json
CHANGED