@planu/cli 4.1.0 → 4.1.2

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.
Files changed (60) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/config/license-plans.json +65 -361
  3. package/dist/engine/hooks/git-hook-generator.js +31 -0
  4. package/dist/tools/git/hook-ops.js +53 -9
  5. package/dist/tools/tool-registry/group-infra.js +22 -0
  6. package/package.json +8 -7
  7. package/dist/engine/escalator/index.d.ts +0 -5
  8. package/dist/engine/escalator/index.js +0 -5
  9. package/dist/engine/freeze/retro-audit.d.ts +0 -6
  10. package/dist/engine/freeze/retro-audit.js +0 -24
  11. package/dist/engine/heal/backup.d.ts +0 -9
  12. package/dist/engine/heal/backup.js +0 -21
  13. package/dist/engine/idioma-validator/index.d.ts +0 -17
  14. package/dist/engine/idioma-validator/index.js +0 -89
  15. package/dist/engine/saga/index.d.ts +0 -4
  16. package/dist/engine/saga/index.js +0 -4
  17. package/dist/engine/spec-state-machine/index.d.ts +0 -3
  18. package/dist/engine/spec-state-machine/index.js +0 -2
  19. package/dist/engine/spec-summary-html/dashboard-renderer.d.ts +0 -6
  20. package/dist/engine/spec-summary-html/dashboard-renderer.js +0 -333
  21. package/dist/engine/triagier/index.d.ts +0 -5
  22. package/dist/engine/triagier/index.js +0 -5
  23. package/dist/engine/universal-rules/index.d.ts +0 -5
  24. package/dist/engine/universal-rules/index.js +0 -6
  25. package/dist/testing/cassette/index.d.ts +0 -23
  26. package/dist/testing/cassette/index.js +0 -26
  27. package/dist/tools/domain-bundle-handler.d.ts +0 -37
  28. package/dist/tools/domain-bundle-handler.js +0 -71
  29. package/dist/tools/figma/rules-file.d.ts +0 -5
  30. package/dist/tools/figma/rules-file.js +0 -45
  31. package/dist/tools/heal-planu-root.d.ts +0 -8
  32. package/dist/tools/heal-planu-root.js +0 -144
  33. package/dist/tools/opencode-host-adapter.d.ts +0 -3
  34. package/dist/tools/opencode-host-adapter.js +0 -33
  35. package/dist/tools/plan-team-distribution.d.ts +0 -3
  36. package/dist/tools/plan-team-distribution.js +0 -71
  37. package/dist/tools/reconcile-status-json.d.ts +0 -4
  38. package/dist/tools/reconcile-status-json.js +0 -209
  39. package/dist/tools/register-all-tools.d.ts +0 -8
  40. package/dist/tools/register-all-tools.js +0 -239
  41. package/dist/tools/tool-registry/group-analysis-monitoring.d.ts +0 -3
  42. package/dist/tools/tool-registry/group-analysis-monitoring.js +0 -942
  43. package/dist/tools/tool-registry/group-integrations.d.ts +0 -3
  44. package/dist/tools/tool-registry/group-integrations.js +0 -1046
  45. package/dist/tools/tool-registry/group-misc.d.ts +0 -3
  46. package/dist/tools/tool-registry/group-misc.js +0 -1367
  47. package/dist/tools/tool-registry/group-platform.d.ts +0 -3
  48. package/dist/tools/tool-registry/group-platform.js +0 -1681
  49. package/dist/tools/tool-registry/group-session-knowledge.d.ts +0 -3
  50. package/dist/tools/tool-registry/group-session-knowledge.js +0 -1416
  51. package/dist/tools/tool-registry/group-spec-ops.d.ts +0 -3
  52. package/dist/tools/tool-registry/group-spec-ops.js +0 -917
  53. package/dist/tools/workspace-overview.d.ts +0 -4
  54. package/dist/tools/workspace-overview.js +0 -316
  55. package/dist/transports/middleware/index.d.ts +0 -9
  56. package/dist/transports/middleware/index.js +0 -7
  57. package/dist/transports/middleware/with-sandbox.d.ts +0 -21
  58. package/dist/transports/middleware/with-sandbox.js +0 -68
  59. package/dist/types/heal.d.ts +0 -18
  60. package/dist/types/heal.js +0 -3
@@ -1,1416 +0,0 @@
1
- /* eslint-disable max-lines -- thematic registry group consolidated by SPEC refactor-phase3 (commit aafeea60); each block is a declarative tool registration ~20 lines, split would re-create the ~120 register-*.ts files this phase intentionally collapsed */
2
- // tools/tool-registry/group-session-knowledge.ts — Group E: Session & Knowledge tools
3
- // Consolidates: session-tools, checkpoint-tool, checkpoints, autopilot-tools, autopilot,
4
- // learning, lessons, knowledge-base, backlog, agent, agent-squad, orchestrator,
5
- // delete, delete-first, legacy, elicitation, context, context-manager, scope
6
- // (Phase 3 registry refactor — inline, no delegation)
7
- import { z } from 'zod';
8
- import { safeLicensed, safeTracked } from '../safe-handler.js';
9
- import { projectIdSchema, withProject } from '../tool-registry-helpers.js';
10
- // ── Session tools (register-session-tools.ts) ────────────────────────────────
11
- import { SessionHandoffSchema, RestoreSessionSchema, ListSessionsSchema, ExportSessionSchema, } from '../schemas/session.js';
12
- import { handleSessionHandoff } from '../session-handoff.js';
13
- import { handleRestoreSession } from '../restore-session.js';
14
- import { handleListSessions } from '../list-sessions.js';
15
- import { handleExportSession } from '../export-session.js';
16
- // ── Session checkpoint (register-checkpoint-tool.ts) ─────────────────────────
17
- import { handleSessionCheckpoint } from '../checkpoint-handler.js';
18
- // ── Approval checkpoints (register-checkpoints.ts) ───────────────────────────
19
- import { handleConfigureCheckpointPolicy } from '../checkpoint/configure-policy-handler.js';
20
- import { handleRequireCheckpoint } from '../checkpoint/require-checkpoint-handler.js';
21
- import { handleApproveCheckpoint } from '../checkpoint/approve-checkpoint-handler.js';
22
- import { handleRejectCheckpoint } from '../checkpoint/reject-checkpoint-handler.js';
23
- import { handleListPendingCheckpoints } from '../checkpoint/list-checkpoints-handler.js';
24
- // ── Autopilot audit (register-autopilot-tools.ts) ────────────────────────────
25
- import { handleAutopilotAudit } from '../autopilot-audit.js';
26
- // ── Configure autopilot (register-autopilot.ts) ───────────────────────────────
27
- import { loadAutopilotConfig, saveAutopilotConfig, getAutopilotStatus, } from '../../engine/autopilot/action-executor.js';
28
- // ── Learning tools (register-learning-tools.ts) ───────────────────────────────
29
- import { t } from '../../i18n/index.js';
30
- import { handleCaptureLearning } from '../capture-learning.js';
31
- // ── Lessons tools (register-lessons-tools.ts) ────────────────────────────────
32
- import { handleLogLesson, handleListLessons } from '../lessons-handler.js';
33
- // ── Knowledge base (register-knowledge-base-tools.ts) ────────────────────────
34
- import { handleSemanticDecisionSearch, handleSimilarProblemsFinder, handleExtractLessonsFromText, handleKnowledgeGapDetector, handleKnowledgeSummary, } from '../knowledge-base-handler.js';
35
- // ── Backlog tools (register-backlog-tools.ts) ─────────────────────────────────
36
- import { handleCaptureIdea } from '../capture-idea-handler.js';
37
- import { handleListBacklog } from '../list-backlog-handler.js';
38
- import { handlePromoteIdea } from '../promote-idea-handler.js';
39
- import { handleDiscardIdea } from '../discard-idea-handler.js';
40
- // ── Agent tools (register-agent-tools.ts) ────────────────────────────────────
41
- import { handleOrchestrateAgents } from '../orchestrate-agents.js';
42
- // ── Agent squad tools (register-agent-squad-tools.ts) ────────────────────────
43
- // NOTE: registerAgentSquadHandlers (event bus) is called from index.ts — do NOT inline here.
44
- import { getSquadConfig, setAgentEnabled, getSpecRunHistory, getSquadRuns, } from '../../storage/agent-squad-store.js';
45
- import { hashProjectPath } from '../../storage/base-store.js';
46
- // ── Orchestrator tools (register-orchestrator-tools.ts) ───────────────────────
47
- import { handleOrchestrateRuntime } from '../orchestrate-runtime.js';
48
- import { handleAgentSwarmStatus } from '../agent-swarm-status.js';
49
- // ── Delete tools (register-delete-tools.ts) ───────────────────────────────────
50
- import { TrashActionEnum } from '../schemas/index.js';
51
- import { handleDeleteSpec } from '../delete-spec.js';
52
- import { handleDeleteProject } from '../delete-project.js';
53
- import { handleDeletePattern } from '../delete-pattern.js';
54
- import { handleDeleteDecision } from '../delete-decision.js';
55
- import { handleManageTrash } from '../manage-trash.js';
56
- // ── Delete-first tools (register-delete-first-tools.ts) ───────────────────────
57
- import { handleSuggestDeletions, SuggestDeletionsInputSchema, } from '../delete-first/suggest-deletions.js';
58
- import { handleSustainabilityScore, SustainabilityScoreInputSchema, } from '../delete-first/sustainability-score.js';
59
- import { handleSimplicityMetric, SimplicityMetricInputSchema, } from '../delete-first/simplicity-metric.js';
60
- // ── Legacy tools (register-legacy-tools.ts) ───────────────────────────────────
61
- import { handleCharacterizeLegacyCode, CharacterizeLegacyCodeInputSchema, } from '../legacy/characterize-legacy-code.js';
62
- import { handleDetectHyrumRisks, DetectHyrumRisksInputSchema, } from '../legacy/detect-hyrum-risks.js';
63
- import { handleSeamsDetector, SeamsDetectorInputSchema } from '../legacy/seams-detector.js';
64
- import { handleRefactorWithSafetyNet, RefactorWithSafetyNetInputSchema, } from '../legacy/refactor-with-safety-net.js';
65
- // ── Elicitation tools (register-elicitation-tools.ts) ────────────────────────
66
- import { handleElicitRequirements } from '../elicit-requirements-handler.js';
67
- // ── Context tools (register-context-tools.ts) ────────────────────────────────
68
- import { handleRequestContext, handleContributeContext } from '../contribute-context.js';
69
- // ── Context manager tools (register-context-manager-tools.ts) ────────────────
70
- import { handleContextWindowStatus, handleOptimizeContext, handleContextBudgetConfig, handleArchiveSpecContext, handleCompressSpecHistory, handleRestoreArchivedContext, } from '../context-manager-handler.js';
71
- // ── Scope tools (register-scope-tools.ts) ────────────────────────────────────
72
- import { handleManageScope } from '../manage-scope.js';
73
- import { handleDetectContradictions } from '../detect-contradictions.js';
74
- // ---------------------------------------------------------------------------
75
- // Shared sub-schemas
76
- // ---------------------------------------------------------------------------
77
- const CATEGORY_ENUM = z.enum([
78
- 'estimation',
79
- 'testing',
80
- 'architecture',
81
- 'security',
82
- 'performance',
83
- 'ux',
84
- 'process',
85
- 'other',
86
- ]);
87
- const SEVERITY_ENUM = z.enum(['low', 'medium', 'high']);
88
- const STRATEGY_DESCRIBE = 'Archiving strategy. Valid values: archive-oldest | archive-done | compress-history | manual';
89
- const taskDescriptionSchema = z.object({
90
- id: z.string().max(500).describe('Unique task identifier'),
91
- description: z.string().max(10_000).describe('Natural-language task description'),
92
- requirements: z.array(z.string().max(500)).max(100).describe('Capabilities required'),
93
- files: z.array(z.string().max(4096)).max(1000).describe('File paths this task works with'),
94
- dependencies: z.array(z.string().max(500)).max(100).describe('Task IDs that must complete first'),
95
- priority: z.number().min(0).describe('Priority (higher = more important)'),
96
- critical: z.boolean().describe('If true, orchestration fails if this task fails'),
97
- });
98
- const agentConfigSchema = z.object({
99
- id: z.string().max(500).describe('Unique agent identifier'),
100
- command: z.string().max(4096).describe('CLI command to spawn (e.g. "claude", "python")'),
101
- args: z.array(z.string().max(4096)).max(100).describe('Command arguments'),
102
- capabilities: z.array(z.string().max(500)).max(100).describe('Agent capabilities'),
103
- modelTier: z
104
- .enum(['haiku', 'sonnet', 'opus'])
105
- .optional()
106
- .describe('Model tier: haiku | sonnet | opus'),
107
- workDir: z.string().max(4096).optional().describe('Working directory for the agent'),
108
- });
109
- // ---------------------------------------------------------------------------
110
- // Main export
111
- // ---------------------------------------------------------------------------
112
- // eslint-disable-next-line max-lines-per-function -- registration catalog: one block per tool, each ~20 lines
113
- export function registerSessionKnowledgeGroupTools(s) {
114
- // ── Session tools ──────────────────────────────────────────────────────────
115
- s.registerTool('session_handoff', {
116
- description: 'Generate a compact (≤200-token) session handoff packet to paste at the start of a fresh Claude session. ' +
117
- 'Shows active spec + pending criteria, next queued spec, git branch state, and the next action step. ' +
118
- 'Use before closing a session that has been inactive for ~1 hour to avoid expensive context resumption.',
119
- inputSchema: SessionHandoffSchema,
120
- annotations: { readOnlyHint: true },
121
- }, safeTracked('session_handoff', async (args) => handleSessionHandoff(args)));
122
- s.registerTool('restore_session', {
123
- description: 'Restore a previous session to recover context (active specs, decisions, modified files). ' +
124
- 'If no sessionId is provided, restores the most recent session. ' +
125
- 'Returns context summary, active specs, and recent decisions.',
126
- inputSchema: RestoreSessionSchema,
127
- annotations: { readOnlyHint: true },
128
- }, safeLicensed('restore_session', async (args) => handleRestoreSession(args)));
129
- s.registerTool('list_sessions', {
130
- description: 'List recent sessions for a project with summary info. ' +
131
- 'Returns session IDs, timestamps, context summaries, and tool call counts. ' +
132
- 'Ordered by most recent first.',
133
- inputSchema: ListSessionsSchema,
134
- annotations: { readOnlyHint: true },
135
- }, safeLicensed('list_sessions', async (args) => handleListSessions(args)));
136
- s.registerTool('export_session', {
137
- description: 'Export a session as markdown or JSON for handoff between agents or documentation. ' +
138
- 'Markdown format includes sections for Summary, Specs, Decisions, Files, and Metadata. ' +
139
- 'If no sessionId is provided, exports the most recent session.',
140
- inputSchema: ExportSessionSchema,
141
- annotations: { readOnlyHint: true },
142
- }, safeLicensed('export_session', async (args) => handleExportSession(args)));
143
- // ── Session checkpoint ─────────────────────────────────────────────────────
144
- s.registerTool('session_checkpoint', {
145
- description: 'Save a session checkpoint with current context (active specs, decisions, staged files, next steps). ' +
146
- 'Returns a compact handoff block to paste in the next session for seamless context recovery. ' +
147
- 'State is stored in planu/session-state.json and expires after 24 hours.',
148
- inputSchema: {
149
- projectPath: z.string().min(1).max(4096).describe('Absolute path to the project root'),
150
- projectId: z
151
- .string()
152
- .min(1)
153
- .max(500)
154
- .describe('Project ID (hash). Use the ID returned by init_project.'),
155
- decisions: z
156
- .array(z.string().max(500))
157
- .max(20)
158
- .optional()
159
- .describe('Key decisions made this session (up to 5 stored; provide most important first)'),
160
- nextSteps: z
161
- .array(z.string().max(500))
162
- .max(20)
163
- .optional()
164
- .describe('Pending next steps to resume in the next session'),
165
- },
166
- annotations: { readOnlyHint: false },
167
- }, safeTracked('session_checkpoint', async (args) => handleSessionCheckpoint(args)));
168
- // ── Approval checkpoints ───────────────────────────────────────────────────
169
- s.registerTool('configure_checkpoint_policy', {
170
- description: 'Configure approval checkpoints that block spec status transitions until a designated role approves. ' +
171
- 'Apply a built-in preset (strict/balanced/relaxed) or define custom per-transition policies. ' +
172
- 'This implements the Judge gate in Planner→Worker→Judge governance.',
173
- inputSchema: {
174
- projectPath: z.string().min(1).max(4096).describe('Absolute path to the project root.'),
175
- preset: z
176
- .enum(['strict', 'balanced', 'relaxed'])
177
- .optional()
178
- .describe('Policy preset: strict=all transitions need approval (draft->review, review->approved, approved->implementing, implementing->done), ' +
179
- 'balanced=key transitions only (review->approved, implementing->done), ' +
180
- 'relaxed=only completion needs approval (implementing->done).'),
181
- policies: z
182
- .array(z.object({
183
- transition: z
184
- .string()
185
- .min(1)
186
- .describe('Transition to gate, e.g. "review->approved". Valid statuses: draft, review, approved, implementing, done, discarded.'),
187
- requiredRole: z
188
- .string()
189
- .min(1)
190
- .describe('Role that must approve: developer, tech_lead, qa, product_owner, admin.'),
191
- description: z
192
- .string()
193
- .min(1)
194
- .describe('Human-readable explanation of why this checkpoint exists.'),
195
- autoApproveAfterHours: z
196
- .number()
197
- .positive()
198
- .optional()
199
- .describe('Hours before the checkpoint auto-approves if no action is taken.'),
200
- }))
201
- .optional()
202
- .describe('Custom policies (used when preset is not set). Replaces all existing policies for the project.'),
203
- },
204
- annotations: { title: 'Configure Checkpoint Policy', destructiveHint: false },
205
- }, safeTracked('configure_checkpoint_policy', (args) => handleConfigureCheckpointPolicy(args)));
206
- s.registerTool('require_checkpoint', {
207
- description: 'Create a pending checkpoint that blocks a specific spec status transition. ' +
208
- 'Returns the checkpoint ID and instructions on who needs to approve it. ' +
209
- 'The checkpoint must be approved via approve_checkpoint before the transition can proceed.',
210
- inputSchema: {
211
- projectPath: z.string().min(1).max(4096).describe('Absolute path to the project root.'),
212
- specId: z.string().min(1).max(500).describe('Spec ID to block, e.g. SPEC-042.'),
213
- transition: z
214
- .string()
215
- .min(1)
216
- .describe('Status transition to block, e.g. "review->approved". Must match a configured policy.'),
217
- requestedBy: z
218
- .string()
219
- .optional()
220
- .describe('Identifier of who is requesting the checkpoint (defaults to "system").'),
221
- },
222
- annotations: { title: 'Require Checkpoint', destructiveHint: false },
223
- }, safeTracked('require_checkpoint', (args) => handleRequireCheckpoint(args)));
224
- s.registerTool('approve_checkpoint', {
225
- description: 'Approve a pending checkpoint, unblocking the associated spec status transition. ' +
226
- 'Returns confirmation and next steps for the spec.',
227
- inputSchema: {
228
- projectPath: z.string().min(1).max(4096).describe('Absolute path to the project root.'),
229
- checkpointId: z
230
- .string()
231
- .min(1)
232
- .describe('ID of the checkpoint to approve (returned by require_checkpoint).'),
233
- approvedBy: z
234
- .string()
235
- .optional()
236
- .describe('Identifier of the approver (defaults to "anonymous").'),
237
- },
238
- annotations: { title: 'Approve Checkpoint', destructiveHint: false },
239
- }, safeTracked('approve_checkpoint', (args) => handleApproveCheckpoint(args)));
240
- s.registerTool('reject_checkpoint', {
241
- description: 'Reject a pending checkpoint, blocking the associated spec status transition. ' +
242
- 'A rejection reason is required. The spec stays in its current status.',
243
- inputSchema: {
244
- projectPath: z.string().min(1).max(4096).describe('Absolute path to the project root.'),
245
- checkpointId: z
246
- .string()
247
- .min(1)
248
- .describe('ID of the checkpoint to reject (returned by require_checkpoint).'),
249
- rejectedBy: z
250
- .string()
251
- .optional()
252
- .describe('Identifier of the reviewer rejecting (defaults to "anonymous").'),
253
- reason: z
254
- .string()
255
- .min(1)
256
- .describe('Mandatory explanation of why the checkpoint was rejected.'),
257
- },
258
- annotations: { title: 'Reject Checkpoint', destructiveHint: false },
259
- }, safeTracked('reject_checkpoint', (args) => handleRejectCheckpoint(args)));
260
- s.registerTool('list_pending_checkpoints', {
261
- description: 'List all pending approval checkpoints for a project. ' +
262
- 'Optionally filter by required role to see only checkpoints relevant to you. ' +
263
- 'Returns spec ID, transition being blocked, required role, and requester.',
264
- inputSchema: {
265
- projectPath: z.string().min(1).max(4096).describe('Absolute path to the project root.'),
266
- role: z
267
- .string()
268
- .optional()
269
- .describe('Filter by required role: developer, tech_lead, qa, product_owner, admin. Omit to list all pending checkpoints.'),
270
- },
271
- annotations: { title: 'List Pending Checkpoints', readOnlyHint: true },
272
- }, safeTracked('list_pending_checkpoints', (args) => handleListPendingCheckpoints(args)));
273
- // ── Autopilot audit ────────────────────────────────────────────────────────
274
- s.registerTool('autopilot_audit', {
275
- description: 'Query the persistent audit log of all autopilot cascade actions. Returns timestamped entries for every action automatically triggered by Planu tools (e.g. migrateAllSpecsToLean, cleanPlanuRoot, regenerateSpecSummaryHtml).',
276
- inputSchema: {
277
- projectPath: z.string().min(1).max(4096).describe('Absolute path to the project root.'),
278
- trigger: z
279
- .string()
280
- .max(200)
281
- .optional()
282
- .describe('Filter by the tool that triggered the action. Example: "list_specs", "update_status(done)".'),
283
- action: z
284
- .string()
285
- .max(200)
286
- .optional()
287
- .describe('Filter by action name. Example: "migrateAllSpecsToLean", "cleanPlanuRoot", "regenerateSpecSummaryHtml".'),
288
- status: z
289
- .enum(['ok', 'fail', 'timeout', 'skipped'])
290
- .optional()
291
- .describe('Filter by result status. Values: ok — succeeded, fail — threw an error, timeout — exceeded time limit, skipped — precondition not met.'),
292
- since: z
293
- .string()
294
- .max(100)
295
- .optional()
296
- .describe('ISO 8601 timestamp. Only return entries at or after this time. Example: "2026-04-01T00:00:00.000Z".'),
297
- limit: z
298
- .number()
299
- .int()
300
- .min(1)
301
- .max(500)
302
- .optional()
303
- .describe('Maximum number of entries to return (default: 50, max: 500).'),
304
- },
305
- annotations: { readOnlyHint: true },
306
- }, safeLicensed('autopilot_audit', async (args) => handleAutopilotAudit(args)));
307
- // ── Configure autopilot ────────────────────────────────────────────────────
308
- s.registerTool('configure_autopilot', {
309
- description: 'Enable/disable individual autopilot triggers, set thresholds, toggle complete-loop flags (auto-branch, auto-scaffold-TDD, auto-PR), or view current autopilot status. Autopilot auto-triggers tools like validate_criteria_quality, tdd_scaffold, and resolve_drift_violations at the right moments in the spec lifecycle.',
310
- inputSchema: {
311
- projectPath: z.string().min(1).max(4096).describe('Absolute path to the project root.'),
312
- action: z
313
- .enum(['enable', 'disable', 'status', 'set-threshold'])
314
- .describe('Action to perform. Values: enable — activate a trigger rule or complete-loop flag, disable — deactivate a trigger rule or complete-loop flag, status — show all rules and their state, set-threshold — update numeric threshold for a conditional rule or the auto-merge score gate.'),
315
- triggerId: z
316
- .string()
317
- .max(200)
318
- .optional()
319
- .describe('ID of the trigger rule to enable/disable/configure. Example: "post-create:validate_criteria_quality". For complete-loop flags (SPEC-569) use: "autoCreateBranch", "autoScaffoldTdd", "autoCreatePr", or "autoMergeThreshold". Required for enable/disable/set-threshold.'),
320
- threshold: z
321
- .number()
322
- .optional()
323
- .describe('Threshold value for set-threshold action. For hour-based triggers: minimum estimated hours. For drift triggers: minimum drift score (0-1). For autoMergeThreshold: minimum validate score (0-100) required to auto-create a PR (default 85).'),
324
- autoCreateBranch: z
325
- .boolean()
326
- .optional()
327
- .describe('SPEC-569: When true, update_status(approved) auto-creates a feature branch for the spec. Fire-and-forget.'),
328
- autoScaffoldTdd: z
329
- .boolean()
330
- .optional()
331
- .describe('SPEC-569: When true, update_status(approved) auto-generates TDD test scaffolding. Default false. Fire-and-forget.'),
332
- autoCreatePr: z
333
- .boolean()
334
- .optional()
335
- .describe('SPEC-569: When true, update_status(done) auto-creates a PR once validate score meets autoMergeThreshold. Default false. Fire-and-forget.'),
336
- autoMergeThreshold: z
337
- .number()
338
- .min(0)
339
- .max(100)
340
- .optional()
341
- .describe('SPEC-569: Minimum validate score (0-100) required to auto-create a PR. Default 85. Only used when autoCreatePr is true.'),
342
- },
343
- annotations: { readOnlyHint: false },
344
- }, safeLicensed('configure_autopilot', async (args) => {
345
- // SPEC-569: Allow bulk-setting complete-loop flags regardless of action,
346
- // so callers can toggle them with a single tool invocation.
347
- const completeLoopTouched = args.autoCreateBranch !== undefined ||
348
- args.autoScaffoldTdd !== undefined ||
349
- args.autoCreatePr !== undefined ||
350
- args.autoMergeThreshold !== undefined;
351
- if (completeLoopTouched) {
352
- const config = await loadAutopilotConfig(args.projectPath);
353
- if (args.autoCreateBranch !== undefined) {
354
- config.autoCreateBranch = args.autoCreateBranch;
355
- }
356
- if (args.autoScaffoldTdd !== undefined) {
357
- config.autoScaffoldTdd = args.autoScaffoldTdd;
358
- }
359
- if (args.autoCreatePr !== undefined) {
360
- config.autoCreatePr = args.autoCreatePr;
361
- }
362
- if (args.autoMergeThreshold !== undefined) {
363
- config.autoMergeThreshold = args.autoMergeThreshold;
364
- }
365
- await saveAutopilotConfig(args.projectPath, config);
366
- const summary = [
367
- args.autoCreateBranch !== undefined
368
- ? `autoCreateBranch=${String(args.autoCreateBranch)}`
369
- : null,
370
- args.autoScaffoldTdd !== undefined
371
- ? `autoScaffoldTdd=${String(args.autoScaffoldTdd)}`
372
- : null,
373
- args.autoCreatePr !== undefined ? `autoCreatePr=${String(args.autoCreatePr)}` : null,
374
- args.autoMergeThreshold !== undefined
375
- ? `autoMergeThreshold=${String(args.autoMergeThreshold)}`
376
- : null,
377
- ]
378
- .filter((entry) => entry !== null)
379
- .join(', ');
380
- return {
381
- content: [
382
- {
383
- type: 'text',
384
- text: `✅ Autopilot complete-loop updated: ${summary}.`,
385
- },
386
- ],
387
- };
388
- }
389
- if (args.action === 'status') {
390
- const status = await getAutopilotStatus(args.projectPath);
391
- const lines = status.rules.map((r) => `${r.enabled ? '✅' : '⬜'} \`${r.id}\` — ${r.description}`);
392
- const config = await loadAutopilotConfig(args.projectPath);
393
- const completeLoop = [
394
- `${config.autoCreateBranch === true ? '✅' : '⬜'} autoCreateBranch`,
395
- `${config.autoScaffoldTdd === true ? '✅' : '⬜'} autoScaffoldTdd`,
396
- `${config.autoCreatePr === true ? '✅' : '⬜'} autoCreatePr (threshold: ${String(config.autoMergeThreshold ?? 85)})`,
397
- ].join('\n');
398
- return {
399
- content: [
400
- {
401
- type: 'text',
402
- text: `## Autopilot Status\n\n**${status.enabledRules}/${status.totalRules} rules enabled**\n\n${lines.join('\n')}\n\n### Complete Loop (SPEC-569)\n\n${completeLoop}`,
403
- },
404
- ],
405
- };
406
- }
407
- if (!args.triggerId) {
408
- return {
409
- isError: true,
410
- content: [
411
- {
412
- type: 'text',
413
- text: 'triggerId is required for enable/disable/set-threshold actions.',
414
- },
415
- ],
416
- };
417
- }
418
- const config = await loadAutopilotConfig(args.projectPath);
419
- // SPEC-569: Allow toggling complete-loop flags via triggerId for UX symmetry.
420
- if (args.triggerId === 'autoCreateBranch') {
421
- config.autoCreateBranch = args.action === 'enable';
422
- await saveAutopilotConfig(args.projectPath, config);
423
- return {
424
- content: [
425
- {
426
- type: 'text',
427
- text: `✅ autoCreateBranch ${args.action === 'enable' ? 'enabled' : 'disabled'}.`,
428
- },
429
- ],
430
- };
431
- }
432
- if (args.triggerId === 'autoScaffoldTdd') {
433
- config.autoScaffoldTdd = args.action === 'enable';
434
- await saveAutopilotConfig(args.projectPath, config);
435
- return {
436
- content: [
437
- {
438
- type: 'text',
439
- text: `✅ autoScaffoldTdd ${args.action === 'enable' ? 'enabled' : 'disabled'}.`,
440
- },
441
- ],
442
- };
443
- }
444
- if (args.triggerId === 'autoCreatePr') {
445
- config.autoCreatePr = args.action === 'enable';
446
- await saveAutopilotConfig(args.projectPath, config);
447
- return {
448
- content: [
449
- {
450
- type: 'text',
451
- text: `✅ autoCreatePr ${args.action === 'enable' ? 'enabled' : 'disabled'}.`,
452
- },
453
- ],
454
- };
455
- }
456
- if (args.triggerId === 'autoMergeThreshold' && args.action === 'set-threshold') {
457
- if (args.threshold === undefined) {
458
- return {
459
- isError: true,
460
- content: [
461
- {
462
- type: 'text',
463
- text: 'threshold is required when setting autoMergeThreshold.',
464
- },
465
- ],
466
- };
467
- }
468
- config.autoMergeThreshold = args.threshold;
469
- await saveAutopilotConfig(args.projectPath, config);
470
- return {
471
- content: [
472
- {
473
- type: 'text',
474
- text: `✅ autoMergeThreshold set to ${String(args.threshold)}.`,
475
- },
476
- ],
477
- };
478
- }
479
- if (args.action === 'enable') {
480
- config.rules[args.triggerId] = { ...(config.rules[args.triggerId] ?? {}), enabled: true };
481
- }
482
- else if (args.action === 'disable') {
483
- config.rules[args.triggerId] = {
484
- ...(config.rules[args.triggerId] ?? {}),
485
- enabled: false,
486
- };
487
- }
488
- else if (args.action === 'set-threshold' && args.threshold !== undefined) {
489
- config.rules[args.triggerId] = {
490
- ...(config.rules[args.triggerId] ?? { enabled: true }),
491
- threshold: args.threshold,
492
- };
493
- }
494
- await saveAutopilotConfig(args.projectPath, config);
495
- return {
496
- content: [
497
- {
498
- type: 'text',
499
- text: `✅ Autopilot rule \`${args.triggerId}\` updated (action: ${args.action}).`,
500
- },
501
- ],
502
- };
503
- }));
504
- // ── Learning tools ─────────────────────────────────────────────────────────
505
- s.registerTool('capture_learning', {
506
- description: t('tools.capture_learning.description'),
507
- annotations: { readOnlyHint: false },
508
- inputSchema: {
509
- description: z
510
- .string()
511
- .max(10_000)
512
- .describe('Natural language description of the pattern or improvement discovered'),
513
- specId: z
514
- .string()
515
- .max(500)
516
- .optional()
517
- .describe('Target spec ID override (optional — auto-routed if omitted)'),
518
- subcommand: z
519
- .enum(['lesson'])
520
- .optional()
521
- .describe('lesson: capture a lesson into lessons-learned registry'),
522
- lessonType: z
523
- .enum([
524
- 'bug-pattern',
525
- 'arch-mismatch',
526
- 'estimation-error',
527
- 'tool-limitation',
528
- 'test-complexity',
529
- ])
530
- .optional()
531
- .describe('Lesson type (required when subcommand is lesson)'),
532
- tags: z
533
- .array(z.string().max(500))
534
- .max(100)
535
- .optional()
536
- .describe('Search tags for the lesson (optional)'),
537
- },
538
- }, safeLicensed('capture_learning', async (args) => handleCaptureLearning(args)));
539
- // ── Lessons tools ──────────────────────────────────────────────────────────
540
- s.registerTool('log_lesson', {
541
- description: 'Log a lesson learned from a spec or project experience. ' +
542
- 'Captures what went wrong (or right), the category, severity, and how to prevent it next time. ' +
543
- 'Use after bugs, rollbacks, or effort >50% over estimate.',
544
- inputSchema: {
545
- projectId: z.string().max(500).optional().describe('Project ID hash. Prefer projectPath.'),
546
- projectPath: z
547
- .string()
548
- .max(4096)
549
- .optional()
550
- .describe('Absolute path to project root. Derives projectId automatically.'),
551
- specId: z
552
- .string()
553
- .optional()
554
- .describe('Optional spec ID this lesson relates to (e.g. "SPEC-042").'),
555
- category: CATEGORY_ENUM.describe('Category: estimation | testing | architecture | security | performance | ux | process | other'),
556
- severity: SEVERITY_ENUM.describe('Severity: low | medium | high'),
557
- title: z.string().min(3).max(200).describe('Short title of the lesson.'),
558
- description: z.string().min(5).max(2000).describe('What went wrong or what was learned.'),
559
- prevention: z.string().min(5).max(1000).describe('What to do differently next time.'),
560
- tags: z
561
- .array(z.string().max(50))
562
- .max(10)
563
- .optional()
564
- .describe('Optional tags for searching (e.g. ["async", "zod"]).'),
565
- },
566
- annotations: { readOnlyHint: false },
567
- }, safeLicensed('log_lesson', async (args) => handleLogLesson(args)));
568
- s.registerTool('list_lessons', {
569
- description: 'List lessons learned for a project. ' +
570
- 'Optionally filter by category, severity, or spec ID. ' +
571
- 'Returns matching lessons plus frequency stats by category to surface recurring patterns.',
572
- inputSchema: {
573
- projectId: z.string().max(500).optional().describe('Project ID hash. Prefer projectPath.'),
574
- projectPath: z
575
- .string()
576
- .max(4096)
577
- .optional()
578
- .describe('Absolute path to project root. Derives projectId automatically.'),
579
- category: CATEGORY_ENUM.optional().describe('Filter by category: estimation | testing | architecture | security | performance | ux | process | other'),
580
- severity: SEVERITY_ENUM.optional().describe('Filter by severity: low | medium | high'),
581
- specId: z.string().optional().describe('Filter by spec ID (e.g. "SPEC-042").'),
582
- limit: z
583
- .number()
584
- .int()
585
- .min(1)
586
- .max(100)
587
- .optional()
588
- .default(20)
589
- .describe('Max results to return (default 20, max 100).'),
590
- },
591
- annotations: { readOnlyHint: true },
592
- }, safeLicensed('list_lessons', async (args) => handleListLessons(args)));
593
- // ── Knowledge base ─────────────────────────────────────────────────────────
594
- s.registerTool('semantic_decision_search', {
595
- description: 'Search project decisions using TF-IDF relevance scoring. Returns decisions ranked by relevance to the query.',
596
- annotations: { readOnlyHint: true },
597
- inputSchema: {
598
- projectPath: z.string().min(1).max(4096).describe('Absolute path to the project root.'),
599
- query: z.string().min(1).max(500).describe('Search query to find relevant decisions.'),
600
- limit: z
601
- .number()
602
- .int()
603
- .min(1)
604
- .max(20)
605
- .optional()
606
- .describe('Maximum number of results to return (default: 5).'),
607
- },
608
- }, safeTracked('semantic_decision_search', async (args) => handleSemanticDecisionSearch(args)));
609
- s.registerTool('similar_problems_finder', {
610
- description: 'Find specs with similar titles and descriptions to a given query or problem statement. Uses keyword overlap scoring.',
611
- annotations: { readOnlyHint: true },
612
- inputSchema: {
613
- projectPath: z.string().min(1).max(4096).describe('Absolute path to the project root.'),
614
- query: z
615
- .string()
616
- .min(1)
617
- .max(500)
618
- .describe('Problem description or spec text to match against.'),
619
- limit: z
620
- .number()
621
- .int()
622
- .min(1)
623
- .max(20)
624
- .optional()
625
- .describe('Maximum number of results to return (default: 5).'),
626
- },
627
- }, safeTracked('similar_problems_finder', async (args) => handleSimilarProblemsFinder(args)));
628
- s.registerTool('extract_lessons_from_text', {
629
- description: 'Extract lesson-like phrases from free-form text (retrospectives, post-mortems). Detects patterns like "we learned that", "always", "never", "avoid".',
630
- inputSchema: {
631
- projectPath: z.string().min(1).max(4096).describe('Absolute path to the project root.'),
632
- text: z.string().min(1).max(10000).describe('Free-form text to extract lessons from.'),
633
- },
634
- }, safeTracked('extract_lessons_from_text', (args) => Promise.resolve(handleExtractLessonsFromText(args))));
635
- s.registerTool('knowledge_gap_detector', {
636
- description: 'Detect knowledge gaps in a project: specs missing decisions documentation and lesson categories with no entries.',
637
- annotations: { readOnlyHint: true },
638
- inputSchema: {
639
- projectPath: z.string().min(1).max(4096).describe('Absolute path to the project root.'),
640
- },
641
- }, safeTracked('knowledge_gap_detector', async (args) => handleKnowledgeGapDetector(args)));
642
- s.registerTool('knowledge_summary', {
643
- description: 'Get a snapshot of institutional knowledge: total decisions, lessons, spec coverage, top categories, and detected gaps.',
644
- annotations: { readOnlyHint: true },
645
- inputSchema: {
646
- projectPath: z.string().min(1).max(4096).describe('Absolute path to the project root.'),
647
- },
648
- }, safeTracked('knowledge_summary', async (args) => handleKnowledgeSummary(args)));
649
- // ── Backlog tools ──────────────────────────────────────────────────────────
650
- s.registerTool('capture_idea', {
651
- description: 'Capture a lightweight idea or future possibility without creating a full spec. ' +
652
- 'Use when the user expresses non-urgent ideas (e.g. "sería bueno", "a futuro", "what if"). ' +
653
- 'Ideas expire after 90 days and can later be promoted to a spec with promote_idea.',
654
- inputSchema: {
655
- ...projectIdSchema,
656
- title: z.string().min(3).max(200).describe('Short title for the idea.'),
657
- description: z
658
- .string()
659
- .min(5)
660
- .max(2000)
661
- .describe('Detailed description of the idea or future possibility.'),
662
- tags: z
663
- .array(z.string().max(50))
664
- .max(10)
665
- .optional()
666
- .describe('Optional tags to group related ideas (e.g. ["ux", "performance"]).'),
667
- },
668
- annotations: { readOnlyHint: false },
669
- }, safeLicensed('capture_idea', withProject((args) => handleCaptureIdea(args))));
670
- s.registerTool('list_backlog', {
671
- description: 'List all captured ideas in the project backlog. ' +
672
- 'Optionally filter by status or tag. Groups results by tag. ' +
673
- 'Expired ideas are shown with a ⚠️ indicator.',
674
- inputSchema: {
675
- ...projectIdSchema,
676
- status: z
677
- .enum(['new', 'in-review', 'converted', 'discarded'])
678
- .optional()
679
- .describe('Filter by idea status. Valid values: new | in-review | converted | discarded'),
680
- tag: z.string().max(50).optional().describe('Filter by a specific tag (exact match).'),
681
- },
682
- annotations: { readOnlyHint: true },
683
- }, safeLicensed('list_backlog', withProject((args) => handleListBacklog(args))));
684
- s.registerTool('promote_idea', {
685
- description: 'Promote a backlog idea to a full spec by calling create_spec internally. ' +
686
- 'Only ideas with status "new" or "in-review" can be promoted. ' +
687
- 'After promotion the idea is marked as "converted" with the resulting specId.',
688
- inputSchema: {
689
- ...projectIdSchema,
690
- ideaId: z.string().min(1).describe('ID of the idea to promote (e.g. "idea-20260321-001").'),
691
- },
692
- annotations: { readOnlyHint: false },
693
- }, safeLicensed('promote_idea', withProject((args) => handlePromoteIdea(args))));
694
- s.registerTool('discard_idea', {
695
- description: 'Mark a backlog idea as discarded with a reason. ' +
696
- 'Idempotent — calling this on an already-discarded idea is safe and returns a confirmation.',
697
- inputSchema: {
698
- ...projectIdSchema,
699
- ideaId: z.string().min(1).describe('ID of the idea to discard (e.g. "idea-20260321-001").'),
700
- reason: z
701
- .string()
702
- .min(3)
703
- .max(500)
704
- .describe('Reason for discarding the idea (shown in backlog history).'),
705
- },
706
- annotations: { readOnlyHint: false, destructiveHint: true },
707
- }, safeLicensed('discard_idea', withProject((args) => handleDiscardIdea(args))));
708
- // ── Agent tools ────────────────────────────────────────────────────────────
709
- s.registerTool('orchestrate_agents', {
710
- description: 'Declare agent capabilities, route tasks to the best agent, generate structured handoff contexts, and detect file-ownership conflicts before parallel execution. Supports register-agent | list-agents | route-task | decompose-spec | handoff | conflict-check.',
711
- inputSchema: {
712
- action: z
713
- .enum([
714
- 'register-agent',
715
- 'list-agents',
716
- 'route-task',
717
- 'decompose-spec',
718
- 'handoff',
719
- 'conflict-check',
720
- ])
721
- .describe('Action: register-agent (declare capability) | list-agents (list registered) | route-task (assign best agent) | decompose-spec (triad distribution) | handoff (structured context) | conflict-check (file overlap detection)'),
722
- projectId: z
723
- .string()
724
- .max(500)
725
- .optional()
726
- .describe('Project ID for persistence — defaults to "global"'),
727
- agentId: z
728
- .string()
729
- .max(500)
730
- .optional()
731
- .describe('register-agent: unique identifier for the agent'),
732
- role: z
733
- .enum(['planner', 'implementer', 'reviewer', 'tester', 'researcher'])
734
- .optional()
735
- .describe('register-agent: primary role the agent fulfils'),
736
- ecosystems: z
737
- .array(z.string().max(500))
738
- .max(100)
739
- .optional()
740
- .describe('register-agent: ecosystems the agent handles (e.g. ["typescript","python"])'),
741
- filePatterns: z
742
- .array(z.string().max(4096))
743
- .max(1000)
744
- .optional()
745
- .describe('register-agent: glob patterns for files the agent owns'),
746
- maxConcurrency: z
747
- .number()
748
- .int()
749
- .positive()
750
- .optional()
751
- .describe('register-agent: max concurrent tasks (default: 1)'),
752
- preferredModel: z
753
- .enum(['haiku', 'sonnet', 'opus'])
754
- .optional()
755
- .describe('register-agent: preferred model tier (default: sonnet)'),
756
- taskDescription: z
757
- .string()
758
- .max(10_000)
759
- .optional()
760
- .describe('route-task | decompose-spec: natural-language description of the task'),
761
- specCriteria: z
762
- .array(z.string().max(10_000))
763
- .max(1000)
764
- .optional()
765
- .describe('decompose-spec: acceptance criteria to distribute across agents'),
766
- availableAgents: z
767
- .array(z.string().max(500))
768
- .max(100)
769
- .optional()
770
- .describe('decompose-spec: agent IDs available for assignment'),
771
- sessionContext: z
772
- .object({
773
- fromAgent: z.string().max(500).describe('Agent ID handing off work'),
774
- toAgent: z.string().max(500).describe('Agent ID receiving work'),
775
- completedWork: z
776
- .array(z.string().max(10_000))
777
- .max(1000)
778
- .describe('Short descriptions of completed work'),
779
- decisions: z
780
- .array(z.object({ decision: z.string().max(10_000), rationale: z.string().max(10_000) }))
781
- .max(1000)
782
- .describe('Architectural decisions made during the session'),
783
- fileOwnershipMap: z
784
- .record(z.string().max(4096), z.string().max(500))
785
- .describe('Map of file path → agentId that last modified it'),
786
- pendingWork: z
787
- .array(z.object({ task: z.string().max(10_000), context: z.string().max(10_000) }))
788
- .max(1000)
789
- .describe('Unfinished tasks with context for the receiving agent'),
790
- })
791
- .optional()
792
- .describe('handoff: session context object'),
793
- agentPlans: z
794
- .array(z.object({
795
- agentId: z.string().max(500).describe('Agent ID submitting this plan'),
796
- ownedFiles: z
797
- .array(z.string().max(4096))
798
- .max(1000)
799
- .describe('Files the agent intends to modify'),
800
- }))
801
- .optional()
802
- .describe('conflict-check: array of per-agent plans'),
803
- },
804
- annotations: { readOnlyHint: false },
805
- }, safeLicensed('orchestrate_agents', async (args) => handleOrchestrateAgents(args)));
806
- // ── Agent squad tools ──────────────────────────────────────────────────────
807
- // NOTE: registerAgentSquadHandlers() (event bus) stays in index.ts — NOT inlined here.
808
- s.registerTool('configure_squad', {
809
- description: 'Enable or disable a specific specialist agent for a project. ' +
810
- 'Overrides the default enabled state in agent-registry.json without modifying it. ' +
811
- 'Valid agentIds: figma-agent, developer-agent, code-reviewer-agent, ' +
812
- 'security-reviewer-agent, qa-agent, arbiter-agent, docs-agent.',
813
- inputSchema: {
814
- projectPath: z.string().min(1).max(4096).describe('Absolute path to the project root.'),
815
- agentId: z
816
- .string()
817
- .min(1)
818
- .max(100)
819
- .describe('ID of the specialist agent to configure. Valid values: figma-agent, developer-agent, code-reviewer-agent, security-reviewer-agent, qa-agent, arbiter-agent, docs-agent.'),
820
- enabled: z
821
- .boolean()
822
- .describe('Set to true to enable the agent, false to disable it for this project.'),
823
- },
824
- }, safeLicensed('configure_squad', async (args) => {
825
- const { projectPath, agentId, enabled } = args;
826
- const projectId = hashProjectPath(projectPath);
827
- await setAgentEnabled(projectId, agentId, enabled);
828
- return {
829
- content: [
830
- {
831
- type: 'text',
832
- text: `Agent ${agentId} ${enabled ? 'enabled' : 'disabled'} for this project.`,
833
- },
834
- ],
835
- };
836
- }));
837
- s.registerTool('squad_status', {
838
- description: 'Show all registered specialist agents for a project, ' +
839
- 'whether they are enabled, and their last run result per spec. ' +
840
- 'Reflects any per-project overrides set via configure_squad.',
841
- inputSchema: {
842
- projectPath: z.string().min(1).max(4096).describe('Absolute path to the project root.'),
843
- },
844
- }, safeTracked('squad_status', async (args) => {
845
- const { projectPath } = args;
846
- const projectId = hashProjectPath(projectPath);
847
- const [config, runs] = await Promise.all([
848
- getSquadConfig(projectId),
849
- getSquadRuns(projectId),
850
- ]);
851
- const overrideMap = new Map(config.overrides.map((o) => [o.agentId, o.enabled]));
852
- const recentByAgent = new Map();
853
- for (const history of runs) {
854
- const last = history.runs.at(-1);
855
- if (last) {
856
- const existing = recentByAgent.get(last.agentId);
857
- if (!existing || last.triggeredAt > existing) {
858
- recentByAgent.set(last.agentId, `${last.verdict} (${history.specId})`);
859
- }
860
- }
861
- }
862
- const agents = [
863
- 'figma-agent',
864
- 'developer-agent',
865
- 'code-reviewer-agent',
866
- 'security-reviewer-agent',
867
- 'qa-agent',
868
- 'arbiter-agent',
869
- 'docs-agent',
870
- ];
871
- const lines = agents.map((id) => {
872
- const enabled = overrideMap.get(id) ?? true;
873
- const last = recentByAgent.get(id) ?? 'no runs yet';
874
- return `${enabled ? '✅' : '❌'} ${id} — last: ${last}`;
875
- });
876
- return { content: [{ type: 'text', text: lines.join('\n') }] };
877
- }));
878
- s.registerTool('agent_run_history', {
879
- description: 'Show the history of specialist agent runs for a specific spec: ' +
880
- 'which agents ran, their verdict, and when.',
881
- inputSchema: {
882
- projectPath: z.string().min(1).max(4096).describe('Absolute path to the project root.'),
883
- specId: z.string().min(1).max(100).describe('Spec ID to query (e.g. SPEC-550).'),
884
- },
885
- }, safeTracked('agent_run_history', async (args) => {
886
- const { projectPath, specId } = args;
887
- const projectId = hashProjectPath(projectPath);
888
- const history = await getSpecRunHistory(projectId, specId);
889
- if (!history || history.runs.length === 0) {
890
- return {
891
- content: [{ type: 'text', text: `No agent runs recorded for ${specId}.` }],
892
- };
893
- }
894
- const lines = history.runs.map((r) => `[${r.triggeredAt}] ${r.agentId} (${r.phase}) — ${r.verdict}: ${r.summary}`);
895
- return { content: [{ type: 'text', text: lines.join('\n') }] };
896
- }));
897
- // ── Orchestrator tools ─────────────────────────────────────────────────────
898
- s.registerTool('orchestrate_runtime', {
899
- description: 'Launch and coordinate parallel agents with configurable topologies (mesh, hierarchical, pipeline, star). Distributes tasks, manages file ownership, collects results, and resolves conflicts. Supports dryRun mode for planning without execution.',
900
- inputSchema: {
901
- topology: z
902
- .enum(['mesh', 'hierarchical', 'pipeline', 'star'])
903
- .describe('Topology: mesh (parallel) | hierarchical (coordinator+workers) | pipeline (sequential) | star (hub-spoke)'),
904
- tasks: z.array(taskDescriptionSchema).max(1000).describe('Tasks to distribute'),
905
- agents: z
906
- .array(agentConfigSchema)
907
- .max(100)
908
- .optional()
909
- .describe('Agent configs (auto-generated if omitted)'),
910
- maxRetries: z.number().min(0).optional().describe('Max retries per agent (default: 2)'),
911
- timeoutMs: z.number().optional().describe('Timeout per agent in ms (default: 600000)'),
912
- failFast: z.boolean().optional().describe('Stop on first failure (default: false)'),
913
- spawnMethod: z
914
- .enum(['cli-subprocess', 'mcp-client', 'http-endpoint'])
915
- .optional()
916
- .describe('Spawn method: cli-subprocess | mcp-client | http-endpoint'),
917
- conflictStrategy: z
918
- .enum(['last-wins', 'first-wins', 'merge-auto', 'flag-for-review'])
919
- .optional()
920
- .describe('Conflict strategy: last-wins | first-wins | merge-auto | flag-for-review'),
921
- dryRun: z.boolean().optional().describe('Plan without executing (default: false)'),
922
- templateId: z.string().max(500).optional().describe('Use a pre-defined template'),
923
- },
924
- annotations: { readOnlyHint: false },
925
- }, safeLicensed('orchestrate_runtime', async (args) => handleOrchestrateRuntime(args)));
926
- s.registerTool('agent_swarm_status', {
927
- description: 'Get status of orchestration sessions. Returns agent progress, task status, metrics, and cost. Without sessionId, lists all sessions.',
928
- inputSchema: {
929
- sessionId: z.string().max(500).optional().describe('Specific session ID to query'),
930
- status: z
931
- .enum(['running', 'completed', 'failed'])
932
- .optional()
933
- .describe('Filter by status: running | completed | failed'),
934
- agentId: z.string().max(500).optional().describe('Filter by specific agent ID'),
935
- },
936
- annotations: { readOnlyHint: true },
937
- }, safeLicensed('agent_swarm_status', (args) => Promise.resolve(handleAgentSwarmStatus(args))));
938
- // ── Delete tools ───────────────────────────────────────────────────────────
939
- s.registerTool('delete_spec', {
940
- description: 'Delete one or more specs from a project. Shows a preview by default — set confirm=true to execute. Soft delete (moves to trash) by default — set force=true for permanent deletion. ' +
941
- 'If the response contains interactiveQuestions[], present them to the user using AskUserQuestion (Claude Code) or equivalent interactive UI. DO NOT display the JSON as text.',
942
- inputSchema: {
943
- projectRoot: z.string().max(4096).describe('Absolute path to the project root'),
944
- specId: z
945
- .union([z.string().max(500), z.array(z.string().max(500)).max(100)])
946
- .describe('Spec ID or array of spec IDs to delete'),
947
- confirm: z
948
- .boolean()
949
- .optional()
950
- .describe('Set to true to execute deletion. Without it, only a preview is shown.'),
951
- force: z
952
- .boolean()
953
- .optional()
954
- .describe('Set to true for permanent (hard) deletion. Default is soft delete (trash).'),
955
- },
956
- annotations: { title: 'Delete Spec', destructiveHint: true },
957
- }, safeTracked('delete_spec', async (args) => handleDeleteSpec(args)));
958
- s.registerTool('delete_project', {
959
- description: 'Delete all data for a project. Shows a preview by default — requires confirm=true to execute. Soft delete (moves to trash) by default — set force=true for permanent deletion.',
960
- inputSchema: {
961
- projectRoot: z.string().max(4096).describe('Absolute path to the project root'),
962
- confirm: z
963
- .boolean()
964
- .optional()
965
- .describe('Set to true to execute deletion. Without it, only a preview is shown.'),
966
- force: z
967
- .boolean()
968
- .optional()
969
- .describe('Set to true for permanent (hard) deletion. Default is soft delete (trash).'),
970
- },
971
- annotations: { title: 'Delete Project', destructiveHint: true },
972
- }, safeTracked('delete_project', async (args) => handleDeleteProject(args)));
973
- s.registerTool('delete_pattern', {
974
- description: 'Delete one or more learned patterns from a project. Shows a preview by default — set confirm=true to execute.',
975
- inputSchema: {
976
- projectRoot: z.string().max(4096).describe('Absolute path to the project root'),
977
- patternId: z
978
- .union([z.string().max(500), z.array(z.string().max(500)).max(100)])
979
- .describe('Pattern ID or array of pattern IDs to delete'),
980
- confirm: z
981
- .boolean()
982
- .optional()
983
- .describe('Set to true to execute deletion. Without it, only a preview is shown.'),
984
- force: z
985
- .boolean()
986
- .optional()
987
- .describe('Set to true for permanent (hard) deletion. Default is soft delete (trash).'),
988
- },
989
- annotations: { title: 'Delete Pattern', destructiveHint: true },
990
- }, safeTracked('delete_pattern', async (args) => handleDeletePattern(args)));
991
- s.registerTool('delete_decision', {
992
- description: 'Delete one or more decisions/ADRs from a project. Shows a preview by default — set confirm=true to execute.',
993
- inputSchema: {
994
- projectRoot: z.string().max(4096).describe('Absolute path to the project root'),
995
- decisionId: z
996
- .union([z.string().max(500), z.array(z.string().max(500)).max(100)])
997
- .describe('Decision ID or array of decision IDs to delete'),
998
- confirm: z
999
- .boolean()
1000
- .optional()
1001
- .describe('Set to true to execute deletion. Without it, only a preview is shown.'),
1002
- force: z
1003
- .boolean()
1004
- .optional()
1005
- .describe('Set to true for permanent (hard) deletion. Default is soft delete (trash).'),
1006
- },
1007
- annotations: { title: 'Delete Decision', destructiveHint: true },
1008
- }, safeTracked('delete_decision', async (args) => handleDeleteDecision(args)));
1009
- s.registerTool('manage_trash', {
1010
- description: 'Manage the trash for a project. Actions: list (show trashed items), restore (restore an item from trash), purge (permanently delete trash items).',
1011
- inputSchema: {
1012
- projectRoot: z.string().max(4096).describe('Absolute path to the project root'),
1013
- action: TrashActionEnum.describe('Action to perform: list | restore | purge'),
1014
- itemId: z
1015
- .string()
1016
- .max(500)
1017
- .optional()
1018
- .describe('Trash item ID — required for restore, optional for purge (omit to purge all)'),
1019
- },
1020
- annotations: { title: 'Manage Trash', destructiveHint: true },
1021
- }, safeTracked('manage_trash', async (args) => handleManageTrash(args)));
1022
- // ── Delete-first tools ─────────────────────────────────────────────────────
1023
- s.registerTool('suggest_deletions', {
1024
- description: 'Scan a project for code that should be deleted: dead exports, duplicated blocks, ' +
1025
- 'single-impl interfaces, permanent feature flags, and wrapper functions. ' +
1026
- 'Returns a ranked markdown table of candidates with severity and suggested action.',
1027
- inputSchema: SuggestDeletionsInputSchema,
1028
- annotations: {
1029
- title: 'Suggest Deletions',
1030
- readOnlyHint: true,
1031
- destructiveHint: false,
1032
- openWorldHint: false,
1033
- },
1034
- }, safeTracked('suggest_deletions', async (args) => handleSuggestDeletions(args)));
1035
- s.registerTool('sustainability_score', {
1036
- description: 'Compute a sustainability score (0-100, grade A-F) per module and for the whole project. ' +
1037
- 'Score = simplicity × cohesion / (1 + coupling/10) × 100. ' +
1038
- 'Returns a ranked table of worst modules with per-dimension breakdown.',
1039
- inputSchema: SustainabilityScoreInputSchema,
1040
- annotations: {
1041
- title: 'Sustainability Score',
1042
- readOnlyHint: true,
1043
- destructiveHint: false,
1044
- openWorldHint: false,
1045
- },
1046
- }, safeTracked('sustainability_score', async (args) => handleSustainabilityScore(args)));
1047
- s.registerTool('simplicity_metric', {
1048
- description: 'Measure code simplicity per file: avg function length, max nesting depth, ' +
1049
- 'ternary chains, regex patterns, and avg parameter count. ' +
1050
- 'Returns a grade A-F and ranked table of most complex files.',
1051
- inputSchema: SimplicityMetricInputSchema,
1052
- annotations: {
1053
- title: 'Simplicity Metric',
1054
- readOnlyHint: true,
1055
- destructiveHint: false,
1056
- openWorldHint: false,
1057
- },
1058
- }, safeTracked('simplicity_metric', async (args) => handleSimplicityMetric(args)));
1059
- // ── Legacy tools ───────────────────────────────────────────────────────────
1060
- s.registerTool('characterize_legacy_code', {
1061
- description: 'Generate characterization test stubs for existing functions in legacy code. ' +
1062
- 'Stubs call each function with inferred sample inputs and leave TODO placeholders ' +
1063
- 'for the developer to fill in the observed expected values. ' +
1064
- 'Use before any refactoring to lock in current behavior.',
1065
- inputSchema: CharacterizeLegacyCodeInputSchema,
1066
- annotations: {
1067
- title: 'Characterize Legacy Code',
1068
- readOnlyHint: false,
1069
- destructiveHint: false,
1070
- openWorldHint: false,
1071
- },
1072
- }, safeTracked('characterize_legacy_code', async (args) => handleCharacterizeLegacyCode(args)));
1073
- s.registerTool('detect_hyrum_risks', {
1074
- description: "Scan a project's public API surface for Hyrum's Law risks: observable behaviors " +
1075
- 'that callers may depend on even though they are undocumented. ' +
1076
- 'Detects: exception types thrown, return object shapes, null vs undefined returns, ' +
1077
- 'sort ordering guarantees, and error message text.',
1078
- inputSchema: DetectHyrumRisksInputSchema,
1079
- annotations: {
1080
- title: "Detect Hyrum's Law Risks",
1081
- readOnlyHint: true,
1082
- destructiveHint: false,
1083
- openWorldHint: false,
1084
- },
1085
- }, safeTracked('detect_hyrum_risks', async (args) => handleDetectHyrumRisks(args)));
1086
- s.registerTool('seams_detector', {
1087
- description: 'Scan TypeScript/JavaScript files for dependency break points (seams) that make ' +
1088
- 'code difficult to test and refactor. ' +
1089
- 'Detects: static method calls (ClassName.method), process.env globals, ' +
1090
- 'inline new ExpensiveDep() inside functions, hardcoded file paths, and hardcoded URLs.',
1091
- inputSchema: SeamsDetectorInputSchema,
1092
- annotations: {
1093
- title: 'Detect Seams',
1094
- readOnlyHint: true,
1095
- destructiveHint: false,
1096
- openWorldHint: false,
1097
- },
1098
- }, safeTracked('seams_detector', async (args) => handleSeamsDetector(args)));
1099
- s.registerTool('refactor_with_safety_net', {
1100
- description: 'Orchestrator tool: detects coverage baseline, adds characterization tests, plans ' +
1101
- 'incremental refactoring steps, and validates behavior at each step. ' +
1102
- 'Returns a sequenced safety-net plan — no production code is changed automatically. ' +
1103
- 'Use after characterize_legacy_code and seams_detector to get a full action plan.',
1104
- inputSchema: RefactorWithSafetyNetInputSchema,
1105
- annotations: {
1106
- title: 'Refactor with Safety Net',
1107
- readOnlyHint: false,
1108
- destructiveHint: false,
1109
- openWorldHint: false,
1110
- },
1111
- }, safeTracked('refactor_with_safety_net', async (args) => handleRefactorWithSafetyNet(args)));
1112
- // ── Elicitation tools ──────────────────────────────────────────────────────
1113
- s.registerTool('elicit_requirements', {
1114
- description: 'Interactively clarify requirements before creating a spec. ' +
1115
- 'First call returns structured questions for the user to answer. ' +
1116
- 'Second call (with answers + sessionId) returns a structured summary ready to pass to create_spec.',
1117
- inputSchema: {
1118
- ...projectIdSchema,
1119
- title: z.string().min(3).max(200).describe('Feature title to clarify requirements for.'),
1120
- description: z
1121
- .string()
1122
- .min(10)
1123
- .max(2000)
1124
- .describe('Initial description of the feature or change.'),
1125
- answers: z
1126
- .string()
1127
- .max(3000)
1128
- .optional()
1129
- .describe('User answers to the clarification questions (numbered list). Provide this on the follow-up call to get a spec-ready summary.'),
1130
- sessionId: z
1131
- .string()
1132
- .max(100)
1133
- .optional()
1134
- .describe('Session ID returned from the previous call. Pass this on the follow-up call to continue the same elicitation session.'),
1135
- },
1136
- annotations: { title: 'Elicit Requirements', readOnlyHint: false },
1137
- }, safeLicensed('elicit_requirements', withProject((args) => handleElicitRequirements(args))));
1138
- // ── Context tools ──────────────────────────────────────────────────────────
1139
- s.registerTool('request_context', {
1140
- description: 'Generate a prompt that asks the user for reference documentation (README, PRD, ' +
1141
- 'architecture diagrams, wireframes, ADRs, etc.) so the agent can learn the project. ' +
1142
- 'Sets a flag to avoid re-asking on subsequent calls. Use force=true to re-generate.',
1143
- annotations: { readOnlyHint: true },
1144
- inputSchema: {
1145
- projectId: z
1146
- .string()
1147
- .max(500)
1148
- .describe('Project ID (hash of project path from init_project)'),
1149
- force: z
1150
- .boolean()
1151
- .optional()
1152
- .describe('Force re-generation even if already requested (default: false)'),
1153
- },
1154
- }, safeLicensed('request_context', async (args) => handleRequestContext(args)));
1155
- s.registerTool('contribute_context', {
1156
- description: 'Persist structured knowledge that the agent extracted from user-provided documents. ' +
1157
- 'Merges intelligently with auto-detected knowledge; reports conflicts for agent resolution. ' +
1158
- 'Multiple calls accumulate — they do not overwrite each other.',
1159
- annotations: { readOnlyHint: false },
1160
- inputSchema: {
1161
- projectId: z
1162
- .string()
1163
- .max(500)
1164
- .describe('Project ID (hash of project path from init_project)'),
1165
- context: z
1166
- .object({
1167
- entities: z
1168
- .array(z.object({
1169
- name: z.string().max(500).describe('Entity / model name'),
1170
- fields: z.array(z.string().max(500)).max(1000).describe('Field names'),
1171
- relations: z
1172
- .array(z.string().max(500))
1173
- .max(100)
1174
- .optional()
1175
- .describe('Related entity names'),
1176
- }))
1177
- .max(1000)
1178
- .optional()
1179
- .describe('Data entities / models extracted from the docs'),
1180
- apiContracts: z
1181
- .array(z.object({
1182
- type: z
1183
- .enum(['rest', 'graphql', 'trpc', 'grpc', 'websocket'])
1184
- .describe('Contract type'),
1185
- endpoint: z.string().max(4096).optional().describe('Base endpoint or path'),
1186
- description: z.string().max(10_000).describe('What this API does'),
1187
- }))
1188
- .max(1000)
1189
- .optional()
1190
- .describe('API contract descriptions'),
1191
- architectureNotes: z
1192
- .string()
1193
- .max(10_000)
1194
- .optional()
1195
- .describe('Architecture decisions and patterns from the docs'),
1196
- techDecisions: z
1197
- .array(z.string().max(10_000))
1198
- .max(1000)
1199
- .optional()
1200
- .describe('Key technical decisions recorded in ADRs or equivalent'),
1201
- userFlows: z
1202
- .array(z.string().max(10_000))
1203
- .max(1000)
1204
- .optional()
1205
- .describe('User journey descriptions from wireframes or PRD'),
1206
- nonFunctionalRequirements: z
1207
- .array(z.string().max(10_000))
1208
- .max(1000)
1209
- .optional()
1210
- .describe('Performance, security, and scalability requirements'),
1211
- additionalContext: z
1212
- .string()
1213
- .max(10_000)
1214
- .optional()
1215
- .describe('Free-text additional context (max 10 000 chars)'),
1216
- })
1217
- .describe('Structured knowledge extracted from the user documents'),
1218
- source: z
1219
- .object({
1220
- documentName: z
1221
- .string()
1222
- .max(500)
1223
- .describe('Name or description of the source document (e.g. "README.md", "PRD v2")'),
1224
- contributedAt: z
1225
- .string()
1226
- .max(500)
1227
- .describe('ISO 8601 timestamp of when this contribution was made'),
1228
- contributedBy: z
1229
- .string()
1230
- .max(500)
1231
- .describe('Agent identifier (e.g. "claude-code", "cursor")'),
1232
- })
1233
- .describe('Trace metadata for this contribution'),
1234
- },
1235
- }, safeLicensed('contribute_context', async (args) => handleContributeContext(args)));
1236
- // ── Context manager tools ──────────────────────────────────────────────────
1237
- s.registerTool('context_window_status', {
1238
- description: 'Estimate the number of tokens consumed by the project context. ' +
1239
- 'Uses heuristics: ~1,500 tokens per spec, ~200 per decision, ~150 per lesson. ' +
1240
- 'Shows breakdown by category and compares to configured budget.',
1241
- inputSchema: {
1242
- projectPath: z
1243
- .string()
1244
- .min(1)
1245
- .max(4096)
1246
- .describe('Absolute path to the project root directory'),
1247
- },
1248
- annotations: { title: 'Context Window Status', readOnlyHint: true },
1249
- }, safeLicensed('context_window_status', async (args) => handleContextWindowStatus(args)));
1250
- s.registerTool('optimize_context', {
1251
- description: 'Identify specs that are candidates for archiving to reduce context token usage. ' +
1252
- 'Candidates are done specs older than the configured archiveAfterDays threshold. ' +
1253
- 'Use action="apply" to actually archive them.',
1254
- inputSchema: {
1255
- projectPath: z
1256
- .string()
1257
- .min(1)
1258
- .max(4096)
1259
- .describe('Absolute path to the project root directory'),
1260
- action: z
1261
- .enum(['suggest', 'apply'])
1262
- .optional()
1263
- .describe('Action to take. Valid values: suggest (default, lists candidates) | apply (archives candidates)'),
1264
- },
1265
- annotations: { title: 'Optimize Context', readOnlyHint: false },
1266
- }, safeLicensed('optimize_context', async (args) => handleOptimizeContext(args)));
1267
- s.registerTool('context_budget_config', {
1268
- description: 'Configure the context window budget: maximum tokens, warning threshold, archiving strategy, ' +
1269
- 'and number of days after which done specs are eligible for archiving.',
1270
- inputSchema: {
1271
- projectPath: z
1272
- .string()
1273
- .min(1)
1274
- .max(4096)
1275
- .describe('Absolute path to the project root directory'),
1276
- maxTokens: z
1277
- .number()
1278
- .int()
1279
- .min(10_000)
1280
- .max(2_000_000)
1281
- .optional()
1282
- .describe('Maximum token budget (default: 200,000)'),
1283
- strategy: z
1284
- .enum(['archive-oldest', 'archive-done', 'compress-history', 'manual'])
1285
- .optional()
1286
- .describe(STRATEGY_DESCRIBE),
1287
- warnAt: z
1288
- .number()
1289
- .int()
1290
- .min(1_000)
1291
- .optional()
1292
- .describe('Token count at which to warn (default: 150,000)'),
1293
- archiveAfterDays: z
1294
- .number()
1295
- .int()
1296
- .min(1)
1297
- .optional()
1298
- .describe('Days after done before a spec is eligible for archiving (default: 30)'),
1299
- },
1300
- annotations: { title: 'Configure Context Budget', readOnlyHint: false },
1301
- }, safeLicensed('context_budget_config', async (args) => handleContextBudgetConfig(args)));
1302
- s.registerTool('archive_spec_context', {
1303
- description: 'Archive a specific spec to the archived-context/ directory to free up context tokens. ' +
1304
- 'The spec data is preserved and can be restored with restore_archived_context.',
1305
- inputSchema: {
1306
- projectPath: z
1307
- .string()
1308
- .min(1)
1309
- .max(4096)
1310
- .describe('Absolute path to the project root directory'),
1311
- specId: z.string().min(1).max(200).describe('Spec ID to archive (e.g. SPEC-001)'),
1312
- },
1313
- annotations: { title: 'Archive Spec Context', readOnlyHint: false, destructiveHint: false },
1314
- }, safeLicensed('archive_spec_context', async (args) => handleArchiveSpecContext(args)));
1315
- s.registerTool('compress_spec_history', {
1316
- description: 'Describe what would happen when compressing spec version history, keeping only the most recent N versions. ' +
1317
- 'Reduces token usage from version diffs stored in spec-version-history.',
1318
- inputSchema: {
1319
- projectPath: z
1320
- .string()
1321
- .min(1)
1322
- .max(4096)
1323
- .describe('Absolute path to the project root directory'),
1324
- specId: z.string().min(1).max(200).describe('Spec ID whose version history to compress'),
1325
- keepVersions: z
1326
- .number()
1327
- .int()
1328
- .min(1)
1329
- .max(50)
1330
- .optional()
1331
- .describe('Number of recent versions to keep (default: 3)'),
1332
- },
1333
- annotations: { title: 'Compress Spec History', readOnlyHint: false, destructiveHint: true },
1334
- }, safeLicensed('compress_spec_history', (args) => Promise.resolve(handleCompressSpecHistory(args))));
1335
- s.registerTool('restore_archived_context', {
1336
- description: 'Restore a previously archived spec from archived-context/ back to the active project context.',
1337
- inputSchema: {
1338
- projectPath: z
1339
- .string()
1340
- .min(1)
1341
- .max(4096)
1342
- .describe('Absolute path to the project root directory'),
1343
- specId: z.string().min(1).max(200).describe('Spec ID to restore from archive'),
1344
- },
1345
- annotations: { title: 'Restore Archived Context', readOnlyHint: false },
1346
- }, safeLicensed('restore_archived_context', async (args) => handleRestoreArchivedContext(args)));
1347
- // ── Scope tools ────────────────────────────────────────────────────────────
1348
- s.registerTool('manage_scope', {
1349
- description: 'Manage the project scope definition and backlog. ' +
1350
- 'View current scope, list backlog items, add deferred requirements to backlog, ' +
1351
- 'or propose a formal scope change (requires explicit approval and reason).',
1352
- inputSchema: {
1353
- ...projectIdSchema,
1354
- operation: z
1355
- .enum(['view', 'add_to_backlog', 'propose_change', 'list_backlog'])
1356
- .describe('view: show current scope; list_backlog: list deferred items; add_to_backlog: defer a requirement; propose_change: formally change scope'),
1357
- itemDescription: z
1358
- .string()
1359
- .max(10_000)
1360
- .optional()
1361
- .describe('Description of the item to defer (required for add_to_backlog)'),
1362
- exclusionReason: z
1363
- .string()
1364
- .max(10_000)
1365
- .optional()
1366
- .describe('Why this item is not in the current scope (required for add_to_backlog)'),
1367
- specId: z
1368
- .string()
1369
- .max(500)
1370
- .optional()
1371
- .describe('Associated spec ID (optional for add_to_backlog)'),
1372
- newScope: z
1373
- .object({
1374
- platforms: z
1375
- .array(z.string().max(500))
1376
- .max(100)
1377
- .optional()
1378
- .describe('Target platforms'),
1379
- targetUsers: z
1380
- .array(z.string().max(500))
1381
- .max(100)
1382
- .optional()
1383
- .describe('Target user types'),
1384
- primaryProblem: z
1385
- .string()
1386
- .max(10_000)
1387
- .optional()
1388
- .describe('The core problem being solved'),
1389
- })
1390
- .optional()
1391
- .describe('New scope definition (required for propose_change)'),
1392
- changeReason: z
1393
- .string()
1394
- .max(10_000)
1395
- .optional()
1396
- .describe('Business reason for scope change (required for propose_change)'),
1397
- approvedBy: z
1398
- .string()
1399
- .max(500)
1400
- .optional()
1401
- .describe('Name of the person approving the change (required for propose_change)'),
1402
- },
1403
- annotations: { readOnlyHint: false },
1404
- }, safeLicensed('manage_scope', withProject((args) => handleManageScope(args))));
1405
- s.registerTool('detect_contradictions', {
1406
- description: 'Analyze all specs in a project for semantic contradictions. ' +
1407
- 'Detects patterns like offline vs real-time, sync vs async, simple vs complex, ' +
1408
- 'performance vs security, local vs cloud. ' +
1409
- 'Returns a report with severity (blocker/warning) and recommendations.',
1410
- inputSchema: {
1411
- ...projectIdSchema,
1412
- },
1413
- annotations: { readOnlyHint: true },
1414
- }, safeLicensed('detect_contradictions', withProject((args) => handleDetectContradictions(args))));
1415
- }
1416
- //# sourceMappingURL=group-session-knowledge.js.map