@planu/cli 5.7.6 → 5.7.7
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/CHANGELOG.md +13 -0
- package/dist/.planu-build.json +1 -1
- package/dist/config/hook-templates/planu-review-panel-trigger.sh +2 -3
- package/dist/config/skill-templates/planu-multi-teammate-review.md +7 -11
- package/dist/engine/evidence-gates/evidence-skeletons.js +3 -1
- package/dist/engine/readiness-checker.js +17 -6
- package/dist/engine/reconcile/propagate-mirrors.js +6 -6
- package/dist/engine/spec-format/lean-spec-generator.js +2 -2
- package/dist/engine/spec-grounding/contract.d.ts +3 -3
- package/dist/engine/spec-grounding/contract.js +23 -76
- package/dist/engine/spec-grounding/grounding-entry-parser.d.ts +5 -0
- package/dist/engine/spec-grounding/grounding-entry-parser.js +185 -0
- package/dist/storage/spec-store.js +8 -1
- package/dist/tools/bump-spec-version.js +5 -5
- package/dist/tools/init-project/skills-multi-teammate-review-writer.js +1 -1
- package/dist/tools/multi-teammate-review.js +1 -14
- package/dist/tools/register-platform-tools/design-stack-tools.js +3 -3
- package/dist/tools/register-spec-tools/analysis-tools.js +22 -9
- package/dist/tools/register-spec-tools/core-spec-tools.js +97 -43
- package/dist/tools/tool-registry/core-tools.js +71 -24
- package/dist/tools/tool-registry/group-quality-compliance.js +11 -14
- package/dist/tools/tool-registry-helpers.js +6 -2
- package/dist/types/spec-grounding.d.ts +14 -0
- package/package.json +1 -1
- package/planu-plugin.json +1 -1
- package/dist/tools/tool-registry/deprecated-stubs.d.ts +0 -19
- package/dist/tools/tool-registry/deprecated-stubs.js +0 -65
|
@@ -67,17 +67,17 @@ export function registerDesignStackTools(server) {
|
|
|
67
67
|
description: t('tools.challenge_spec.description'),
|
|
68
68
|
annotations: { readOnlyHint: true },
|
|
69
69
|
inputSchema: {
|
|
70
|
-
specId: SpecIdSchema.describe('Spec ID to challenge'),
|
|
70
|
+
specId: SpecIdSchema.describe('Spec ID to challenge. Must match pattern ^SPEC-\\d+$, max 50 characters.'),
|
|
71
71
|
projectId: z
|
|
72
72
|
.string()
|
|
73
73
|
.max(500)
|
|
74
74
|
.optional()
|
|
75
|
-
.describe('Project ID hash. Prefer projectPath — stays correct after context compaction.'),
|
|
75
|
+
.describe('Project ID hash. Prefer projectPath — stays correct after context compaction. Max 500 characters.'),
|
|
76
76
|
projectPath: z
|
|
77
77
|
.string()
|
|
78
78
|
.max(4096)
|
|
79
79
|
.optional()
|
|
80
|
-
.describe('Absolute path to project root. Derives projectId automatically — always correct even after context compaction.'),
|
|
80
|
+
.describe('Max 4096 characters. Absolute path to project root. Derives projectId automatically — always correct even after context compaction.'),
|
|
81
81
|
focus: z
|
|
82
82
|
.array(ChallengeSpecFocusEnum)
|
|
83
83
|
.max(100)
|
|
@@ -26,7 +26,7 @@ export const DeclaredDriftInputSchema = z.object({
|
|
|
26
26
|
.string()
|
|
27
27
|
.min(100)
|
|
28
28
|
.max(10_000)
|
|
29
|
-
.describe('Why the implementation drifted from the approved architectural premise
|
|
29
|
+
.describe('Min 100, max 10000 characters. Why the implementation drifted from the approved architectural premise — becomes the reconciliation transition reason.'),
|
|
30
30
|
});
|
|
31
31
|
import { handleSsrBackMigration } from '../ssr-back-migration.js';
|
|
32
32
|
import { registerMigrateLegacySpecTool } from '../migrate-legacy-spec.js';
|
|
@@ -115,13 +115,17 @@ export function registerAnalysisTools(server) {
|
|
|
115
115
|
server.registerTool('reconcile_spec', {
|
|
116
116
|
description: t('tools.reconcile_spec.description'),
|
|
117
117
|
inputSchema: {
|
|
118
|
-
specId: SpecIdSchema.describe('Spec ID to reconcile'),
|
|
119
|
-
projectId: z
|
|
118
|
+
specId: SpecIdSchema.describe('Spec ID to reconcile. Must match pattern ^SPEC-\\d+$, max 50 characters.'),
|
|
119
|
+
projectId: z
|
|
120
|
+
.string()
|
|
121
|
+
.max(500)
|
|
122
|
+
.optional()
|
|
123
|
+
.describe('Project ID hash. Prefer projectPath. Max 500 characters.'),
|
|
120
124
|
projectPath: z
|
|
121
125
|
.string()
|
|
122
126
|
.max(4096)
|
|
123
127
|
.optional()
|
|
124
|
-
.describe('Absolute path to project root. Derives projectId automatically.'),
|
|
128
|
+
.describe('Absolute path to project root. Derives projectId automatically. Max 4096 characters.'),
|
|
125
129
|
autoDetect: z
|
|
126
130
|
.boolean()
|
|
127
131
|
.optional()
|
|
@@ -132,11 +136,20 @@ export function registerAnalysisTools(server) {
|
|
|
132
136
|
.describe('Enable living spec mode: compare acceptance criteria against codebase, auto-mark criteria as met/pending/drift, update the ## Progress section of spec.md (default: false)'),
|
|
133
137
|
changes: z
|
|
134
138
|
.array(z.object({
|
|
135
|
-
section: z
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
139
|
+
section: z
|
|
140
|
+
.string()
|
|
141
|
+
.max(500)
|
|
142
|
+
.describe('Max 500 characters. Spec section the change applies to.'),
|
|
143
|
+
originalValue: z
|
|
144
|
+
.string()
|
|
145
|
+
.max(10_000)
|
|
146
|
+
.describe('Max 10000 characters. Current text being replaced.'),
|
|
147
|
+
newValue: z.string().max(10_000).describe('Max 10000 characters. Replacement text.'),
|
|
148
|
+
reason: z
|
|
149
|
+
.string()
|
|
150
|
+
.max(10_000)
|
|
151
|
+
.describe('Max 10000 characters. Why the change is needed.'),
|
|
152
|
+
approved: z.boolean().describe('Whether the user approved this change.'),
|
|
140
153
|
}))
|
|
141
154
|
.max(1000)
|
|
142
155
|
.optional()
|
|
@@ -18,7 +18,10 @@ import { handleTypeSafetyGate } from '../type-safety-gate.js';
|
|
|
18
18
|
import { runWithTrustedLocalMcpContext } from '../../engine/lifecycle-reconciliation.js';
|
|
19
19
|
/** init_project inputSchema — extracted to keep registerCoreSpecTools within line budget. */
|
|
20
20
|
const INIT_PROJECT_INPUT_SCHEMA = {
|
|
21
|
-
projectPath: z
|
|
21
|
+
projectPath: z
|
|
22
|
+
.string()
|
|
23
|
+
.max(4096)
|
|
24
|
+
.describe('Absolute path to the project root. Max 4096 characters.'),
|
|
22
25
|
mode: z
|
|
23
26
|
.enum(['existing_project', 'new_project'])
|
|
24
27
|
.optional()
|
|
@@ -27,18 +30,40 @@ const INIT_PROJECT_INPUT_SCHEMA = {
|
|
|
27
30
|
.string()
|
|
28
31
|
.max(4096)
|
|
29
32
|
.optional()
|
|
30
|
-
.describe('Parent workspace path for explicit new_project onboarding'),
|
|
31
|
-
appName: z
|
|
33
|
+
.describe('Parent workspace path for explicit new_project onboarding. Max 4096 characters.'),
|
|
34
|
+
appName: z
|
|
35
|
+
.string()
|
|
36
|
+
.max(500)
|
|
37
|
+
.optional()
|
|
38
|
+
.describe('Human-readable app name for new_project. Max 500 characters.'),
|
|
32
39
|
appSlug: z
|
|
33
40
|
.string()
|
|
34
41
|
.max(200)
|
|
35
42
|
.optional()
|
|
36
|
-
.describe('Safe folder slug for new_project; must not contain path separators'),
|
|
37
|
-
projectType: z
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
.describe('Safe folder slug for new_project; must not contain path separators. Max 200 characters.'),
|
|
44
|
+
projectType: z
|
|
45
|
+
.string()
|
|
46
|
+
.max(500)
|
|
47
|
+
.optional()
|
|
48
|
+
.describe('Type of project being initialized. Max 500 characters.'),
|
|
49
|
+
platform: z
|
|
50
|
+
.string()
|
|
51
|
+
.max(500)
|
|
52
|
+
.optional()
|
|
53
|
+
.describe('Runtime/deployment platform. Max 500 characters.'),
|
|
54
|
+
language: z.string().max(500).optional().describe('User-confirmed language. Max 500 characters.'),
|
|
55
|
+
framework: z
|
|
56
|
+
.string()
|
|
57
|
+
.max(500)
|
|
58
|
+
.nullable()
|
|
59
|
+
.optional()
|
|
60
|
+
.describe('Max 500 characters. User-confirmed framework.'),
|
|
61
|
+
database: z
|
|
62
|
+
.string()
|
|
63
|
+
.max(500)
|
|
64
|
+
.nullable()
|
|
65
|
+
.optional()
|
|
66
|
+
.describe('Max 500 characters. User-confirmed database.'),
|
|
42
67
|
createDirectory: z
|
|
43
68
|
.boolean()
|
|
44
69
|
.optional()
|
|
@@ -162,7 +187,11 @@ export function registerCoreSpecTools(server) {
|
|
|
162
187
|
server.registerTool('create_spec', {
|
|
163
188
|
description: t('tools.create_spec.description'),
|
|
164
189
|
inputSchema: {
|
|
165
|
-
title: z
|
|
190
|
+
title: z
|
|
191
|
+
.string()
|
|
192
|
+
.min(1)
|
|
193
|
+
.max(500)
|
|
194
|
+
.describe('Title of the spec / user story. Max 500 characters.'),
|
|
166
195
|
description: z
|
|
167
196
|
.string()
|
|
168
197
|
.max(10_000)
|
|
@@ -176,21 +205,25 @@ export function registerCoreSpecTools(server) {
|
|
|
176
205
|
.max(128)
|
|
177
206
|
.regex(/^[A-Za-z0-9._:-]+$/)
|
|
178
207
|
.optional()
|
|
179
|
-
.describe('Stable caller key
|
|
208
|
+
.describe('Must match pattern ^[A-Za-z0-9._:-]+$, max 128 characters. Stable caller key — retries return the exact committed create_spec result.'),
|
|
180
209
|
projectPath: z
|
|
181
210
|
.string()
|
|
182
211
|
.max(4096)
|
|
183
212
|
.optional()
|
|
184
|
-
.describe('Absolute path to the project root. Auto-detected from git root when omitted.'),
|
|
213
|
+
.describe('Max 4096 characters. Absolute path to the project root. Auto-detected from git root when omitted.'),
|
|
185
214
|
type: SpecTypeEnum.optional().describe('Spec type (default: feature)'),
|
|
186
215
|
scope: SpecScopeEnum.optional().describe('Spec scope (default: feature)'),
|
|
187
216
|
target: SpecTargetEnum.optional().describe('Target area: frontend, backend, shared, fullstack, infrastructure, database'),
|
|
188
|
-
tags: z
|
|
217
|
+
tags: z
|
|
218
|
+
.array(z.string().max(500))
|
|
219
|
+
.max(100)
|
|
220
|
+
.optional()
|
|
221
|
+
.describe('Tags for categorization. Each tag max 500 characters.'),
|
|
189
222
|
feature: z
|
|
190
223
|
.string()
|
|
191
224
|
.max(500)
|
|
192
225
|
.optional()
|
|
193
|
-
.describe('Feature group tag (e.g. "Authentication", "Billing"). Added to tags for categorization. Flat structure — no subdirectories created.'),
|
|
226
|
+
.describe('Max 500 characters. Feature group tag (e.g. "Authentication", "Billing"). Added to tags for categorization. Flat structure — no subdirectories created.'),
|
|
194
227
|
acFormat: SpecAcFormatEnum.optional().describe('SPEC-224: Format for acceptance criteria in spec.md. ' +
|
|
195
228
|
'checkbox (default) — markdown checkboxes (- [ ] criterion). ' +
|
|
196
229
|
'bdd — Gherkin Given-When-Then scenarios for BDD workflows.'),
|
|
@@ -242,12 +275,12 @@ export function registerCoreSpecTools(server) {
|
|
|
242
275
|
.string()
|
|
243
276
|
.max(500)
|
|
244
277
|
.optional()
|
|
245
|
-
.describe('Project ID (hash). Prefer projectPath if unknown.'),
|
|
278
|
+
.describe('Max 500 characters. Project ID (hash). Prefer projectPath if unknown.'),
|
|
246
279
|
projectPath: z
|
|
247
280
|
.string()
|
|
248
281
|
.max(4096)
|
|
249
282
|
.optional()
|
|
250
|
-
.describe('Absolute path to the project root. Used to derive projectId automatically.'),
|
|
283
|
+
.describe('Max 4096 characters. Absolute path to the project root. Used to derive projectId automatically.'),
|
|
251
284
|
status: SpecStatusEnum.optional().describe('Filter by spec status'),
|
|
252
285
|
type: SpecTypeEnum.optional().describe('Filter by spec type'),
|
|
253
286
|
detail: z
|
|
@@ -275,28 +308,28 @@ export function registerCoreSpecTools(server) {
|
|
|
275
308
|
server.registerTool('update_status', {
|
|
276
309
|
description: t('tools.update_status.description'),
|
|
277
310
|
inputSchema: {
|
|
278
|
-
specId: SpecIdSchema.describe('Spec ID to update'),
|
|
311
|
+
specId: SpecIdSchema.describe('Spec ID to update. Must match pattern ^SPEC-\\d+$ (e.g. SPEC-042), max 50 characters.'),
|
|
279
312
|
projectId: z
|
|
280
313
|
.string()
|
|
281
314
|
.max(500)
|
|
282
315
|
.optional()
|
|
283
|
-
.describe('Project ID (hash). Prefer projectPath if unknown.'),
|
|
316
|
+
.describe('Project ID (hash). Prefer projectPath if unknown. Max 500 characters.'),
|
|
284
317
|
projectPath: z
|
|
285
318
|
.string()
|
|
286
319
|
.max(4096)
|
|
287
320
|
.optional()
|
|
288
|
-
.describe('Absolute path to the project root. Used to derive projectId automatically.'),
|
|
321
|
+
.describe('Absolute path to the project root. Used to derive projectId automatically. Max 4096 characters.'),
|
|
289
322
|
status: SpecStatusEnum.describe('New status'),
|
|
290
323
|
sessionId: z
|
|
291
324
|
.string()
|
|
292
325
|
.max(500)
|
|
293
326
|
.optional()
|
|
294
|
-
.describe('Agent/session ID performing this lifecycle transition.'),
|
|
327
|
+
.describe('Agent/session ID performing this lifecycle transition. Max 500 characters.'),
|
|
295
328
|
modelId: z
|
|
296
329
|
.string()
|
|
297
330
|
.max(500)
|
|
298
331
|
.optional()
|
|
299
|
-
.describe('Concrete model ID used for this transition. Can satisfy SDD model-routing evidence when it maps to the required tier.'),
|
|
332
|
+
.describe('Max 500 characters. Concrete model ID used for this transition. Can satisfy SDD model-routing evidence when it maps to the required tier.'),
|
|
300
333
|
modelTierUsed: z
|
|
301
334
|
.enum(['max', 'implementation', 'review'])
|
|
302
335
|
.optional()
|
|
@@ -305,27 +338,27 @@ export function registerCoreSpecTools(server) {
|
|
|
305
338
|
.string()
|
|
306
339
|
.max(500)
|
|
307
340
|
.optional()
|
|
308
|
-
.describe('SHA-256 hash of the persisted context package used to prove context continuity across agents.'),
|
|
341
|
+
.describe('Max 500 characters. SHA-256 hash of the persisted context package used to prove context continuity across agents.'),
|
|
309
342
|
handoffPath: z
|
|
310
343
|
.string()
|
|
311
344
|
.max(4096)
|
|
312
345
|
.optional()
|
|
313
|
-
.describe('Path to the persisted handoff package generated by package_handoff. Required for implementing/done unless handoffArtifactId is provided.'),
|
|
346
|
+
.describe('Max 4096 characters. Path to the persisted handoff package generated by package_handoff. Required for implementing/done unless handoffArtifactId is provided.'),
|
|
314
347
|
handoffArtifactId: z
|
|
315
348
|
.string()
|
|
316
349
|
.max(500)
|
|
317
350
|
.optional()
|
|
318
|
-
.describe('External handoff artifact ID when the host stores handoff evidence outside the filesystem.'),
|
|
351
|
+
.describe('Max 500 characters. External handoff artifact ID when the host stores handoff evidence outside the filesystem.'),
|
|
319
352
|
reviewedBy: z
|
|
320
353
|
.string()
|
|
321
354
|
.max(500)
|
|
322
355
|
.optional()
|
|
323
|
-
.describe('Reviewer identity/evidence required before status=done.'),
|
|
356
|
+
.describe('Reviewer identity/evidence required before status=done. Max 500 characters.'),
|
|
324
357
|
arbitratedBy: z
|
|
325
358
|
.string()
|
|
326
359
|
.max(500)
|
|
327
360
|
.optional()
|
|
328
|
-
.describe('Arbiter identity/evidence required before status=done.'),
|
|
361
|
+
.describe('Arbiter identity/evidence required before status=done. Max 500 characters.'),
|
|
329
362
|
reconcileRequired: z
|
|
330
363
|
.boolean()
|
|
331
364
|
.optional()
|
|
@@ -339,8 +372,16 @@ export function registerCoreSpecTools(server) {
|
|
|
339
372
|
apiCostUsd: z.number().min(0).optional(),
|
|
340
373
|
humanCostUsd: z.number().min(0).optional(),
|
|
341
374
|
totalCostUsd: z.number().min(0).optional(),
|
|
342
|
-
completedAt: z
|
|
343
|
-
|
|
375
|
+
completedAt: z
|
|
376
|
+
.string()
|
|
377
|
+
.max(500)
|
|
378
|
+
.optional()
|
|
379
|
+
.describe('ISO timestamp. Max 500 characters.'),
|
|
380
|
+
notes: z
|
|
381
|
+
.string()
|
|
382
|
+
.max(10_000)
|
|
383
|
+
.optional()
|
|
384
|
+
.describe('Free-text notes. Max 10000 characters.'),
|
|
344
385
|
})
|
|
345
386
|
.optional()
|
|
346
387
|
.describe('Actual metrics for status = done. All fields optional (SPEC-1356): missing numeric fields default to 0, completedAt/notes are auto-filled when omitted.'),
|
|
@@ -348,7 +389,7 @@ export function registerCoreSpecTools(server) {
|
|
|
348
389
|
.string()
|
|
349
390
|
.max(10_000)
|
|
350
391
|
.optional()
|
|
351
|
-
.describe('Feedback or observations when sending a spec back (e.g., review→draft). Stored in the spec for the implementer to address.'),
|
|
392
|
+
.describe('Max 10000 characters. Feedback or observations when sending a spec back (e.g., review→draft). Stored in the spec for the implementer to address.'),
|
|
352
393
|
force: z
|
|
353
394
|
.boolean()
|
|
354
395
|
.optional()
|
|
@@ -361,7 +402,7 @@ export function registerCoreSpecTools(server) {
|
|
|
361
402
|
.string()
|
|
362
403
|
.max(2000)
|
|
363
404
|
.optional()
|
|
364
|
-
.describe('SPEC-721: Required when forceStatus=true. ≥100 characters explaining why validate gate is bypassed.'),
|
|
405
|
+
.describe('SPEC-721: Required when forceStatus=true. ≥100 and <=2000 characters explaining why validate gate is bypassed.'),
|
|
365
406
|
dry_run: z
|
|
366
407
|
.boolean()
|
|
367
408
|
.optional()
|
|
@@ -370,18 +411,24 @@ export function registerCoreSpecTools(server) {
|
|
|
370
411
|
.string()
|
|
371
412
|
.max(2000)
|
|
372
413
|
.optional()
|
|
373
|
-
.describe('SPEC-733: Required for reverse transitions (done→implementing, discarded→draft)
|
|
414
|
+
.describe('Must be ≥30 and <=2000 chars. SPEC-733: Required for reverse transitions (done→implementing, discarded→draft), explaining why the spec is being reopened.'),
|
|
374
415
|
reconciliationRequestId: z
|
|
375
416
|
.string()
|
|
376
417
|
.regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u)
|
|
377
418
|
.optional()
|
|
378
|
-
.describe('
|
|
379
|
-
|
|
419
|
+
.describe('Must match pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ (lowercase UUID). ' +
|
|
420
|
+
'Trusted local MCP only: idempotency key for implementing to review reconciliation.'),
|
|
421
|
+
expectedImplementingTransitionId: z
|
|
422
|
+
.string()
|
|
423
|
+
.max(500)
|
|
424
|
+
.optional()
|
|
425
|
+
.describe('Max 500 characters. Idempotency guard: the transitionId returned by the prior implementing transition.'),
|
|
380
426
|
implementationReviewDigest: z
|
|
381
427
|
.string()
|
|
382
428
|
.regex(/^sha256:[a-f0-9]{64}$/u)
|
|
383
429
|
.optional()
|
|
384
|
-
.describe('
|
|
430
|
+
.describe('Must match pattern ^sha256:[a-f0-9]{64}$ — the literal prefix "sha256:" followed by 64 lowercase hex characters. ' +
|
|
431
|
+
'SHA-256 of the exact validation-report.json bytes. Required implementation evidence for status=done; for status=review it is valid only inside the complete trusted local-MCP reconciliation tuple.'),
|
|
385
432
|
forceApprove: z
|
|
386
433
|
.boolean()
|
|
387
434
|
.optional()
|
|
@@ -391,20 +438,27 @@ export function registerCoreSpecTools(server) {
|
|
|
391
438
|
server.registerTool('update_status_batch', {
|
|
392
439
|
description: 'Batch update many specs to the same status in one MCP execution. Uses the same transition rules as update_status and reports updated/skipped/failed per spec.',
|
|
393
440
|
inputSchema: {
|
|
394
|
-
specIds: z
|
|
395
|
-
|
|
441
|
+
specIds: z
|
|
442
|
+
.array(SpecIdSchema)
|
|
443
|
+
.min(1)
|
|
444
|
+
.describe('Spec IDs to update. Each must match pattern ^SPEC-\\d+$, max 50 characters.'),
|
|
445
|
+
projectId: z
|
|
446
|
+
.string()
|
|
447
|
+
.max(500)
|
|
448
|
+
.optional()
|
|
449
|
+
.describe('Project ID, if known. Max 500 characters.'),
|
|
396
450
|
projectPath: z
|
|
397
451
|
.string()
|
|
398
452
|
.max(4096)
|
|
399
453
|
.optional()
|
|
400
|
-
.describe('Absolute project root. Preferred when projectId is unknown.'),
|
|
454
|
+
.describe('Absolute project root. Preferred when projectId is unknown. Max 4096 characters.'),
|
|
401
455
|
status: SpecStatusEnum.exclude(['done']).describe('New status for all specs. Batch supports draft, review, approved, implementing, and discarded; close done specs individually with full evidence.'),
|
|
402
456
|
dryRun: z.boolean().optional().describe('Preview the batch without mutating any spec.'),
|
|
403
457
|
reviewNotes: z
|
|
404
458
|
.string()
|
|
405
459
|
.max(10_000)
|
|
406
460
|
.optional()
|
|
407
|
-
.describe('Optional notes for each transition.'),
|
|
461
|
+
.describe('Optional notes for each transition. Max 10000 characters.'),
|
|
408
462
|
evidence: z
|
|
409
463
|
.record(z.string(), z.object({
|
|
410
464
|
modelTierUsed: z
|
|
@@ -429,8 +483,8 @@ export function registerCoreSpecTools(server) {
|
|
|
429
483
|
server.registerTool('estimate', {
|
|
430
484
|
description: t('tools.estimate.description'),
|
|
431
485
|
inputSchema: {
|
|
432
|
-
specId: SpecIdSchema.describe('Spec ID to estimate'),
|
|
433
|
-
projectId: z.string().max(500).describe('Project ID'),
|
|
486
|
+
specId: SpecIdSchema.describe('Spec ID to estimate. Must match pattern ^SPEC-\\d+$ (e.g. SPEC-042), max 50 characters.'),
|
|
487
|
+
projectId: z.string().max(500).describe('Project ID. Max 500 characters.'),
|
|
434
488
|
},
|
|
435
489
|
outputSchema: EstimateOutputSchema,
|
|
436
490
|
}, safeTracked('estimate', async (args) => handleEstimate(args)));
|
|
@@ -450,7 +504,7 @@ export function registerCoreSpecTools(server) {
|
|
|
450
504
|
server.registerTool('validate', {
|
|
451
505
|
description: t('tools.validate.description'),
|
|
452
506
|
inputSchema: {
|
|
453
|
-
specId: SpecIdSchema.describe('Spec ID to validate'),
|
|
507
|
+
specId: SpecIdSchema.describe('Spec ID to validate. Must match pattern ^SPEC-\\d+$ (e.g. SPEC-042), max 50 characters.'),
|
|
454
508
|
mode: z
|
|
455
509
|
.literal('submit')
|
|
456
510
|
.default('submit')
|
|
@@ -459,12 +513,12 @@ export function registerCoreSpecTools(server) {
|
|
|
459
513
|
.string()
|
|
460
514
|
.max(500)
|
|
461
515
|
.optional()
|
|
462
|
-
.describe('Project ID hash. Prefer projectPath — stays correct after context compaction.'),
|
|
516
|
+
.describe('Project ID hash. Prefer projectPath — stays correct after context compaction. Max 500 characters.'),
|
|
463
517
|
projectPath: z
|
|
464
518
|
.string()
|
|
465
519
|
.max(4096)
|
|
466
520
|
.optional()
|
|
467
|
-
.describe('Absolute path to project root. Derives projectId automatically — always correct even after context compaction.'),
|
|
521
|
+
.describe('Max 4096 characters. Absolute path to project root. Derives projectId automatically — always correct even after context compaction.'),
|
|
468
522
|
},
|
|
469
523
|
outputSchema: ValidateOutputSchema,
|
|
470
524
|
annotations: {
|
|
@@ -51,7 +51,12 @@ const coreToolsRegistry = [
|
|
|
51
51
|
name: 'session_checkpoint',
|
|
52
52
|
description: 'Persist a compact, bounded session checkpoint to planu/session-context.md for the next session.',
|
|
53
53
|
schema: {
|
|
54
|
-
projectPath: z
|
|
54
|
+
projectPath: z
|
|
55
|
+
.string()
|
|
56
|
+
.trim()
|
|
57
|
+
.min(1)
|
|
58
|
+
.max(4096)
|
|
59
|
+
.describe('Absolute path to the project root. Max 4096 characters.'),
|
|
55
60
|
},
|
|
56
61
|
handler: handleSessionCheckpoint,
|
|
57
62
|
annotations: {
|
|
@@ -66,9 +71,20 @@ const coreToolsRegistry = [
|
|
|
66
71
|
name: 'get_job',
|
|
67
72
|
description: 'Read the durable state and latest checkpoint or result for a local Planu job.',
|
|
68
73
|
schema: {
|
|
69
|
-
operationId: z
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
operationId: z
|
|
75
|
+
.string()
|
|
76
|
+
.trim()
|
|
77
|
+
.min(1)
|
|
78
|
+
.max(128)
|
|
79
|
+
.describe('Job operation ID. Max 128 characters.'),
|
|
80
|
+
projectId: z.string().trim().min(1).max(128).describe('Project ID hash. Max 128 characters.'),
|
|
81
|
+
workspaceId: z
|
|
82
|
+
.string()
|
|
83
|
+
.trim()
|
|
84
|
+
.min(1)
|
|
85
|
+
.max(128)
|
|
86
|
+
.optional()
|
|
87
|
+
.describe('Workspace ID. Max 128 characters.'),
|
|
72
88
|
},
|
|
73
89
|
handler: handleGetJob,
|
|
74
90
|
annotations: {
|
|
@@ -84,9 +100,20 @@ const coreToolsRegistry = [
|
|
|
84
100
|
name: 'restart_job',
|
|
85
101
|
description: 'Restart a cancelled or failed durable job and schedule its typed executor.',
|
|
86
102
|
schema: {
|
|
87
|
-
operationId: z
|
|
88
|
-
|
|
89
|
-
|
|
103
|
+
operationId: z
|
|
104
|
+
.string()
|
|
105
|
+
.trim()
|
|
106
|
+
.min(1)
|
|
107
|
+
.max(128)
|
|
108
|
+
.describe('Job operation ID. Max 128 characters.'),
|
|
109
|
+
projectId: z.string().trim().min(1).max(128).describe('Project ID hash. Max 128 characters.'),
|
|
110
|
+
workspaceId: z
|
|
111
|
+
.string()
|
|
112
|
+
.trim()
|
|
113
|
+
.min(1)
|
|
114
|
+
.max(128)
|
|
115
|
+
.optional()
|
|
116
|
+
.describe('Workspace ID. Max 128 characters.'),
|
|
90
117
|
},
|
|
91
118
|
handler: handleRestartJob,
|
|
92
119
|
annotations: {
|
|
@@ -257,12 +284,16 @@ const coreToolsRegistry = [
|
|
|
257
284
|
name: 'planu_status',
|
|
258
285
|
description: 'Get a compact world-snapshot of the current Planu project state in one call: active spec (in-progress), next recommended spec (highest-priority approved), queue count, git state (staged/modified files), and an actionable suggestion. Designed to return the full picture in ≤150 tokens.',
|
|
259
286
|
schema: {
|
|
260
|
-
projectPath: z
|
|
287
|
+
projectPath: z
|
|
288
|
+
.string()
|
|
289
|
+
.min(1)
|
|
290
|
+
.max(4096)
|
|
291
|
+
.describe('Max 4096 characters. Absolute path to project root.'),
|
|
261
292
|
projectId: z
|
|
262
293
|
.string()
|
|
263
294
|
.max(500)
|
|
264
295
|
.optional()
|
|
265
|
-
.describe('Project ID (optional — derived from projectPath if omitted)'),
|
|
296
|
+
.describe('Max 500 characters. Project ID (optional — derived from projectPath if omitted).'),
|
|
266
297
|
},
|
|
267
298
|
handler: async (args) => handlePlanStatus(args),
|
|
268
299
|
annotations: { readOnlyHint: true },
|
|
@@ -544,17 +575,17 @@ const coreToolsRegistry = [
|
|
|
544
575
|
description: z
|
|
545
576
|
.string()
|
|
546
577
|
.max(10_000)
|
|
547
|
-
.describe('Free-form description: what you want to build, your problem, or your request'),
|
|
578
|
+
.describe('Max 10000 characters. Free-form description: what you want to build, your problem, or your request.'),
|
|
548
579
|
projectId: z
|
|
549
580
|
.string()
|
|
550
581
|
.max(500)
|
|
551
582
|
.optional()
|
|
552
|
-
.describe('Project ID or project path (used for context loading and focus persistence)'),
|
|
583
|
+
.describe('Max 500 characters. Project ID or project path (used for context loading and focus persistence).'),
|
|
553
584
|
sessionId: z
|
|
554
585
|
.string()
|
|
555
586
|
.max(500)
|
|
556
587
|
.optional()
|
|
557
|
-
.describe('Session ID (overrides projectId for focus tracking)'),
|
|
588
|
+
.describe('Max 500 characters. Session ID (overrides projectId for focus tracking).'),
|
|
558
589
|
scenario: z
|
|
559
590
|
.enum(['new_project', 'existing_project', 'concrete_problem'])
|
|
560
591
|
.optional()
|
|
@@ -567,28 +598,44 @@ const coreToolsRegistry = [
|
|
|
567
598
|
.string()
|
|
568
599
|
.max(4096)
|
|
569
600
|
.optional()
|
|
570
|
-
.describe('Parent workspace path that should contain a new project folder'),
|
|
571
|
-
appName: z
|
|
601
|
+
.describe('Max 4096 characters. Parent workspace path that should contain a new project folder.'),
|
|
602
|
+
appName: z
|
|
603
|
+
.string()
|
|
604
|
+
.max(500)
|
|
605
|
+
.optional()
|
|
606
|
+
.describe('Max 500 characters. Human-readable new app name.'),
|
|
572
607
|
appSlug: z
|
|
573
608
|
.string()
|
|
574
609
|
.max(200)
|
|
575
610
|
.optional()
|
|
576
|
-
.describe('Safe folder name for the new app; must not contain path separators'),
|
|
577
|
-
projectType: z
|
|
578
|
-
|
|
579
|
-
|
|
611
|
+
.describe('Max 200 characters. Safe folder name for the new app; must not contain path separators.'),
|
|
612
|
+
projectType: z
|
|
613
|
+
.string()
|
|
614
|
+
.max(500)
|
|
615
|
+
.optional()
|
|
616
|
+
.describe('Max 500 characters. Kind of project to create.'),
|
|
617
|
+
platform: z
|
|
618
|
+
.string()
|
|
619
|
+
.max(500)
|
|
620
|
+
.optional()
|
|
621
|
+
.describe('Max 500 characters. Runtime or deployment platform.'),
|
|
622
|
+
language: z
|
|
623
|
+
.string()
|
|
624
|
+
.max(500)
|
|
625
|
+
.optional()
|
|
626
|
+
.describe('Max 500 characters. User-confirmed programming language.'),
|
|
580
627
|
framework: z
|
|
581
628
|
.string()
|
|
582
629
|
.max(500)
|
|
583
630
|
.nullable()
|
|
584
631
|
.optional()
|
|
585
|
-
.describe('User-confirmed framework, null for no framework'),
|
|
632
|
+
.describe('Max 500 characters. User-confirmed framework, null for no framework.'),
|
|
586
633
|
database: z
|
|
587
634
|
.string()
|
|
588
635
|
.max(500)
|
|
589
636
|
.nullable()
|
|
590
637
|
.optional()
|
|
591
|
-
.describe('User-confirmed database, null for no database'),
|
|
638
|
+
.describe('Max 500 characters. User-confirmed database, null for no database.'),
|
|
592
639
|
createDirectory: z
|
|
593
640
|
.boolean()
|
|
594
641
|
.optional()
|
|
@@ -603,12 +650,12 @@ const coreToolsRegistry = [
|
|
|
603
650
|
.min(0)
|
|
604
651
|
.max(10)
|
|
605
652
|
.optional()
|
|
606
|
-
.describe('Current step index for new_project flow (0–2)'),
|
|
653
|
+
.describe('Maximum 10. Current step index for new_project flow (0–2).'),
|
|
607
654
|
targetTool: z
|
|
608
655
|
.string()
|
|
609
656
|
.max(500)
|
|
610
657
|
.optional()
|
|
611
|
-
.describe('Planu tool name to check ambiguity for before calling it'),
|
|
658
|
+
.describe('Max 500 characters. Planu tool name to check ambiguity for before calling it.'),
|
|
612
659
|
checkFocus: z
|
|
613
660
|
.boolean()
|
|
614
661
|
.optional()
|
|
@@ -617,12 +664,12 @@ const coreToolsRegistry = [
|
|
|
617
664
|
.string()
|
|
618
665
|
.max(10_000)
|
|
619
666
|
.optional()
|
|
620
|
-
.describe('Explicitly declare a new session objective'),
|
|
667
|
+
.describe('Max 10000 characters. Explicitly declare a new session objective.'),
|
|
621
668
|
planContent: z
|
|
622
669
|
.string()
|
|
623
670
|
.max(10_000)
|
|
624
671
|
.optional()
|
|
625
|
-
.describe('Plan Mode output to convert into a spec. Paste the plan markdown here to extract ACs and route to create_spec.'),
|
|
672
|
+
.describe('Max 10000 characters. Plan Mode output to convert into a spec. Paste the plan markdown here to extract ACs and route to create_spec.'),
|
|
626
673
|
},
|
|
627
674
|
handler: (args) => handleFacilitate(args),
|
|
628
675
|
annotations: { readOnlyHint: false },
|