@planu/cli 4.3.19 → 4.3.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/engine/cascade-hooks/core/append-releases.js +2 -1
- package/dist/engine/cascade-hooks/runner.js +2 -1
- package/dist/engine/session-state/writer.js +4 -0
- package/dist/engine/spec-generator/fallback-generator.js +4 -30
- package/dist/tools/bump-spec-version.js +2 -1
- package/dist/tools/create-spec.js +30 -10
- package/dist/tools/generate-batch-script.js +2 -1
- package/dist/tools/generate-proposal.js +2 -1
- package/dist/tools/register-agent-squad-tools.js +2 -1
- package/dist/tools/register-platform-tools/design-stack-tools.js +10 -10
- package/dist/tools/register-platform-tools/lifecycle-infra-tools.js +8 -8
- package/dist/tools/register-spec-tools/analysis-tools.js +7 -7
- package/dist/tools/register-spec-tools/core-spec-tools.js +6 -6
- package/dist/tools/render-spec-for-provider.js +2 -1
- package/dist/tools/schemas/github.js +4 -3
- package/dist/tools/schemas/index.d.ts +1 -0
- package/dist/tools/schemas/index.js +1 -0
- package/dist/tools/schemas/spec-id.d.ts +7 -0
- package/dist/tools/schemas/spec-id.js +17 -0
- package/dist/tools/spec-prompt-handler.js +2 -1
- package/dist/tools/tool-registry/core-tools.js +3 -2
- package/dist/tools/tool-registry/group-infra.js +3 -3
- package/dist/tools/tool-registry/group-quality-compliance.js +11 -14
- package/dist/tools/update-status-actions.js +0 -36
- package/package.json +9 -9
- package/planu-native.json +1 -1
- package/planu-plugin.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [4.3.21] - 2026-06-02
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
- fix: enforce canonical spec ids and harden fallback specs
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
## [4.3.20] - 2026-05-27
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
- fix(SPEC-1073): make lifecycle state writes idempotent
|
|
11
|
+
|
|
12
|
+
|
|
1
13
|
## [4.3.19] - 2026-05-27
|
|
2
14
|
|
|
3
15
|
### Bug Fixes
|
|
@@ -7,7 +7,7 @@ import { specStore } from '../../../storage/index.js';
|
|
|
7
7
|
const CORE_ACTION_BUDGET_MS = 2000;
|
|
8
8
|
async function actuallyAppendReleasesPending(ctx, opts) {
|
|
9
9
|
const { projectPath, projectId, specId } = ctx;
|
|
10
|
-
if (!projectPath) {
|
|
10
|
+
if (!projectPath || ctx.newStatus !== 'done') {
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
13
|
if (opts.signal.aborted) {
|
|
@@ -34,6 +34,7 @@ async function actuallyAppendReleasesPending(ctx, opts) {
|
|
|
34
34
|
/* file doesn't exist yet — start fresh */
|
|
35
35
|
}
|
|
36
36
|
const completedAt = new Date().toISOString().substring(0, 10);
|
|
37
|
+
pendingList = pendingList.filter((entry) => entry.specId !== specId);
|
|
37
38
|
pendingList.push({ specId, title: spec?.title ?? specId, completedAt });
|
|
38
39
|
await writeFile(pendingPath, JSON.stringify(pendingList, null, 2), 'utf-8');
|
|
39
40
|
// Auto-commit planu/ changes so pending.json is never left unstaged
|
|
@@ -6,9 +6,10 @@ import { appendReleasesPending } from './core/append-releases.js';
|
|
|
6
6
|
const FAST_HOOK_BUDGET_MS = 2000;
|
|
7
7
|
export async function runCascade(ctx, opts) {
|
|
8
8
|
// Core actions: awaited in parallel, each with its own 2-second budget
|
|
9
|
+
const releasePendingPromise = ctx.newStatus === 'done' ? appendReleasesPending(ctx) : Promise.resolve({ ok: true });
|
|
9
10
|
const [sessionResult, releasesResult] = await Promise.all([
|
|
10
11
|
writeSessionJson(ctx),
|
|
11
|
-
|
|
12
|
+
releasePendingPromise,
|
|
12
13
|
]);
|
|
13
14
|
const dispatched = [];
|
|
14
15
|
const skipped = [];
|
|
@@ -135,6 +135,10 @@ export async function updateEpicProgressInSession(projectPath, specs) {
|
|
|
135
135
|
catch {
|
|
136
136
|
/* start fresh */
|
|
137
137
|
}
|
|
138
|
+
const terminalSpecIds = new Set(specs
|
|
139
|
+
.filter((spec) => spec.status === 'done' || spec.status === 'discarded')
|
|
140
|
+
.map((spec) => spec.id));
|
|
141
|
+
index.activeSpecs = index.activeSpecs.filter((specId) => !terminalSpecIds.has(specId));
|
|
138
142
|
index.epicProgress = epicProgress;
|
|
139
143
|
index.updatedAt = new Date().toISOString();
|
|
140
144
|
await writeFile(filePath, JSON.stringify(index, null, 2), 'utf-8');
|
|
@@ -2,17 +2,14 @@ export class FallbackGenerator {
|
|
|
2
2
|
generate(request) {
|
|
3
3
|
const sourceDescription = normalizeSourceDescription(request.description, request.title);
|
|
4
4
|
const contextLine = buildContextLine(request);
|
|
5
|
-
const technicalSection =
|
|
5
|
+
const technicalSection = '';
|
|
6
6
|
const specBody = [
|
|
7
7
|
'## Problem',
|
|
8
8
|
sourceDescription,
|
|
9
9
|
'',
|
|
10
10
|
'## Solution',
|
|
11
|
-
'Use the preserved source description above as the authoritative requirements body.
|
|
12
|
-
|
|
13
|
-
contextLine,
|
|
14
|
-
'',
|
|
15
|
-
technicalSection,
|
|
11
|
+
'Use the preserved source description above as the authoritative requirements body.',
|
|
12
|
+
contextLine,
|
|
16
13
|
].join('\n');
|
|
17
14
|
return Promise.resolve({
|
|
18
15
|
specBody,
|
|
@@ -20,7 +17,7 @@ export class FallbackGenerator {
|
|
|
20
17
|
generatedWithModel: 'deterministic-fallback',
|
|
21
18
|
generatedAt: new Date().toISOString(),
|
|
22
19
|
qualityWarnings: [
|
|
23
|
-
'Fallback generator used. Source description was preserved;
|
|
20
|
+
'Fallback generator used. Source description was preserved; technical ownership must come from explicit source paths or local project analysis.',
|
|
24
21
|
],
|
|
25
22
|
fallbackReason: 'No external model generator is configured for create_spec.',
|
|
26
23
|
});
|
|
@@ -44,27 +41,4 @@ function buildContextLine(request) {
|
|
|
44
41
|
}
|
|
45
42
|
return `Use the existing ${parts.join(' / ')} project conventions.`;
|
|
46
43
|
}
|
|
47
|
-
function buildTechnicalSection(contextLine) {
|
|
48
|
-
return [
|
|
49
|
-
'## Technical',
|
|
50
|
-
'',
|
|
51
|
-
'### Implementation Notes',
|
|
52
|
-
`- ${contextLine}`,
|
|
53
|
-
'- Preserve the source requirements as the contract until a reviewer or agent adds exact implementation ownership.',
|
|
54
|
-
'',
|
|
55
|
-
'## Files',
|
|
56
|
-
'',
|
|
57
|
-
'### Create',
|
|
58
|
-
'- _Not specified in source description._',
|
|
59
|
-
'### Modify',
|
|
60
|
-
'- _Not specified in source description._',
|
|
61
|
-
'### Test',
|
|
62
|
-
'- _Not specified in source description._',
|
|
63
|
-
'',
|
|
64
|
-
'## Verification',
|
|
65
|
-
'- pnpm typecheck',
|
|
66
|
-
'- pnpm lint',
|
|
67
|
-
'- pnpm test',
|
|
68
|
-
].join('\n');
|
|
69
|
-
}
|
|
70
44
|
//# sourceMappingURL=fallback-generator.js.map
|
|
@@ -7,6 +7,7 @@ import { getSpec } from '../storage/spec-store.js';
|
|
|
7
7
|
import { safeTracked } from './safe-handler.js';
|
|
8
8
|
import { bumpSpecVersion } from '../engine/spec-versioning/bump-version.js';
|
|
9
9
|
import { resolveProjectId } from './resolve-project-id.js';
|
|
10
|
+
import { SpecIdSchema } from './schemas/index.js';
|
|
10
11
|
// SPEC-747: Grant unlock token after successful bump
|
|
11
12
|
import { grantUnlock } from '../engine/freeze/unlock-token.js';
|
|
12
13
|
// ---------------------------------------------------------------------------
|
|
@@ -25,7 +26,7 @@ const BumpSpecVersionSchema = {
|
|
|
25
26
|
.max(500)
|
|
26
27
|
.optional()
|
|
27
28
|
.describe('Project ID (hash). Required if projectPath is not provided.'),
|
|
28
|
-
specId:
|
|
29
|
+
specId: SpecIdSchema.describe('Spec ID to bump, e.g. SPEC-042.'),
|
|
29
30
|
kind: z
|
|
30
31
|
.enum(['patch', 'minor', 'major'])
|
|
31
32
|
.describe('Bump kind: patch (typo/format), minor (new criterion/scope expansion), major (breaking change).'),
|
|
@@ -16,7 +16,8 @@ import { compactObj } from '../engine/compact-obj.js';
|
|
|
16
16
|
import { buildCreateSpecSummary } from '../engine/human-summary.js';
|
|
17
17
|
import { runAutoPostCreatePipeline } from './create-spec/auto-pipeline.js';
|
|
18
18
|
import { generateLeanSpecContent } from '../engine/spec-format/lean-spec-generator.js';
|
|
19
|
-
import { generateLeanTechnicalContent } from '../engine/spec-format/lean-technical-generator.js';
|
|
19
|
+
import { generateLeanTechnicalContent, } from '../engine/spec-format/lean-technical-generator.js';
|
|
20
|
+
import { extractFilesFromSpecBody } from '../engine/spec-format/technical-md-populator.js';
|
|
20
21
|
import { buildUnifiedSpecContent } from '../engine/spec-format/unified-spec-builder.js';
|
|
21
22
|
import { validateEnglishOnlySpecText } from '../engine/spec-language/english-only.js';
|
|
22
23
|
import { ApiKeyResolver, FallbackGenerator, OpusGenerator, } from '../engine/spec-generator/index.js';
|
|
@@ -104,6 +105,22 @@ function handleClarification(_server, description, knowledge, autopilot, params)
|
|
|
104
105
|
earlyReturn: interactiveResult(questions, 'The description needs more detail before a spec can be created. Answer the questions, then retry create_spec with clarificationAnswers or an enriched description.', 'universal'),
|
|
105
106
|
};
|
|
106
107
|
}
|
|
108
|
+
function hasFileEntries(files) {
|
|
109
|
+
return files.create.length + files.modify.length + files.test.length > 0;
|
|
110
|
+
}
|
|
111
|
+
async function resolveTechnicalFiles(input) {
|
|
112
|
+
const generatedSource = [input.generatedTechnicalSection, input.generatedSpecBody]
|
|
113
|
+
.filter((part) => part.trim().length > 0)
|
|
114
|
+
.join('\n\n');
|
|
115
|
+
const extracted = await extractFilesFromSpecBody(generatedSource, input.projectPath);
|
|
116
|
+
if (extracted !== null && hasFileEntries(extracted)) {
|
|
117
|
+
return extracted;
|
|
118
|
+
}
|
|
119
|
+
if (input.fallbackReason !== undefined && hasFileEntries(input.autopilot.suggestedFiles)) {
|
|
120
|
+
return input.autopilot.suggestedFiles;
|
|
121
|
+
}
|
|
122
|
+
return { create: [], modify: [], test: [] };
|
|
123
|
+
}
|
|
107
124
|
const HIGH_RISK_WARNING = 'High-risk spec — consider: ' +
|
|
108
125
|
'1) Is the approach technically feasible within the project constraints? ' +
|
|
109
126
|
'2) What edge cases (network failure, concurrent writes, empty state) could break this? ' +
|
|
@@ -558,15 +575,18 @@ export async function handleCreateSpec(inputParams, server) {
|
|
|
558
575
|
extraCriteria: filteredCriteria,
|
|
559
576
|
acFormat: params.acFormat,
|
|
560
577
|
});
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
578
|
+
const technicalFiles = await measureStep('resolveTechnicalFiles', () => resolveTechnicalFiles({
|
|
579
|
+
generatedSpecBody: generatedSpec.specBody,
|
|
580
|
+
generatedTechnicalSection: generatedSpec.technicalSection,
|
|
581
|
+
projectPath: params.projectPath ?? '',
|
|
582
|
+
autopilot,
|
|
583
|
+
fallbackReason: generatedSpec.fallbackReason,
|
|
584
|
+
}));
|
|
565
585
|
const leanTechnical = generateLeanTechnicalContent({
|
|
566
586
|
specId: spec.id,
|
|
567
|
-
filesToCreate:
|
|
568
|
-
filesToModify:
|
|
569
|
-
filesToTest:
|
|
587
|
+
filesToCreate: technicalFiles.create,
|
|
588
|
+
filesToModify: technicalFiles.modify,
|
|
589
|
+
filesToTest: technicalFiles.test,
|
|
570
590
|
});
|
|
571
591
|
// SPEC-709: write unified spec.md from origin — no separate technical.md.
|
|
572
592
|
// The legacy two-file output is preserved by appending the technical body
|
|
@@ -1071,8 +1091,8 @@ export async function handleCreateSpec(inputParams, server) {
|
|
|
1071
1091
|
catch {
|
|
1072
1092
|
// best-effort — token issuance failure must not block spec creation
|
|
1073
1093
|
}
|
|
1074
|
-
// SPEC-1011 Bug E: surface
|
|
1075
|
-
//
|
|
1094
|
+
// SPEC-1011 Bug E / fallback hardening: surface local file analysis in the response payload.
|
|
1095
|
+
// Those paths are also written into ## Files only when used as technical evidence.
|
|
1076
1096
|
const suggestedFilesPayload = autopilot.suggestedFiles.create.length +
|
|
1077
1097
|
autopilot.suggestedFiles.modify.length +
|
|
1078
1098
|
autopilot.suggestedFiles.test.length >
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// tools/generate-batch-script.ts — Handle generate_batch_script tool calls (SPEC-253)
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { generateBatchScript } from '../engine/batch-script-generator/index.js';
|
|
4
|
+
import { SpecIdSchema } from './schemas/index.js';
|
|
4
5
|
export const GenerateBatchScriptInputSchema = z.object({
|
|
5
6
|
projectPath: z.string().describe('Absolute path to the project root'),
|
|
6
7
|
taskDescription: z.string().describe('What claude -p should do to each file'),
|
|
7
8
|
fileGlob: z.string().describe("Glob pattern for files, e.g. 'src/**/*.ts'"),
|
|
8
|
-
specId:
|
|
9
|
+
specId: SpecIdSchema.optional().describe('SPEC ID for naming the output script, e.g. SPEC-253'),
|
|
9
10
|
allowedTools: z
|
|
10
11
|
.array(z.string())
|
|
11
12
|
.optional()
|
|
@@ -6,10 +6,11 @@ import { hashProjectPath, projectDataDir } from '../storage/base-store.js';
|
|
|
6
6
|
import { specStore, knowledgeStore, decisionStore, metricsStore } from '../storage/index.js';
|
|
7
7
|
import { loadAllRiskRegisters } from '../storage/risk-store.js';
|
|
8
8
|
import { generateProposal } from '../engine/doc-generator/proposal/proposal-generator.js';
|
|
9
|
+
import { SpecIdSchema } from './schemas/index.js';
|
|
9
10
|
// ── Zod schema ────────────────────────────────────────────────────────────────
|
|
10
11
|
const PhaseSchema = z.object({
|
|
11
12
|
name: z.string().describe('Phase name (e.g. "Phase 1 — Core", "MVP")'),
|
|
12
|
-
specIds: z.array(
|
|
13
|
+
specIds: z.array(SpecIdSchema).describe('List of spec IDs included in this phase'),
|
|
13
14
|
color: z
|
|
14
15
|
.string()
|
|
15
16
|
.optional()
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { safeLicensed, safeTracked } from './safe-handler.js';
|
|
3
|
+
import { SpecIdSchema } from './schemas/index.js';
|
|
3
4
|
import { onAutopilotEvent } from '../engine/autopilot/event-bus.js';
|
|
4
5
|
import { dispatchSquadForPhase } from '../engine/agent-squad/dispatcher.js';
|
|
5
6
|
import { getSquadConfig, setAgentEnabled, getSpecRunHistory, getSquadRuns, } from '../storage/agent-squad-store.js';
|
|
@@ -143,7 +144,7 @@ export function registerAgentSquadTools(server) {
|
|
|
143
144
|
'which agents ran, their verdict, and when.',
|
|
144
145
|
inputSchema: {
|
|
145
146
|
projectPath: z.string().min(1).max(4096).describe('Absolute path to the project root.'),
|
|
146
|
-
specId:
|
|
147
|
+
specId: SpecIdSchema.describe('Spec ID to query (e.g. SPEC-550).'),
|
|
147
148
|
},
|
|
148
149
|
}, safeTracked('agent_run_history', async (args) => {
|
|
149
150
|
const { projectPath, specId } = args;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { t } from '../../i18n/index.js';
|
|
3
3
|
import { safeTracked, safeLicensed } from '../safe-handler.js';
|
|
4
|
-
import { AgentPlatformEnum, SubAgentFrameworkEnum, ChallengeSpecFocusEnum, } from '../schemas/index.js';
|
|
4
|
+
import { AgentPlatformEnum, SubAgentFrameworkEnum, ChallengeSpecFocusEnum, SpecIdSchema, } from '../schemas/index.js';
|
|
5
5
|
import { handleGenerateADR } from '../generate-adr.js';
|
|
6
6
|
import { handleDesignSchema } from '../design-schema.js';
|
|
7
7
|
import { handleDefineUIContract } from '../define-ui-contract.js';
|
|
@@ -25,7 +25,7 @@ export function registerDesignStackTools(server) {
|
|
|
25
25
|
description: t('tools.generate_adr.description'),
|
|
26
26
|
annotations: { readOnlyHint: true },
|
|
27
27
|
inputSchema: {
|
|
28
|
-
specId:
|
|
28
|
+
specId: SpecIdSchema.describe('Spec ID'),
|
|
29
29
|
projectId: z.string().max(500).describe('Project ID'),
|
|
30
30
|
decisions: z
|
|
31
31
|
.array(z.string().max(10_000))
|
|
@@ -39,7 +39,7 @@ export function registerDesignStackTools(server) {
|
|
|
39
39
|
description: t('tools.design_schema.description'),
|
|
40
40
|
annotations: { readOnlyHint: true },
|
|
41
41
|
inputSchema: {
|
|
42
|
-
specId:
|
|
42
|
+
specId: SpecIdSchema.describe('Spec ID'),
|
|
43
43
|
projectId: z.string().max(500).describe('Project ID'),
|
|
44
44
|
description: z
|
|
45
45
|
.string()
|
|
@@ -53,7 +53,7 @@ export function registerDesignStackTools(server) {
|
|
|
53
53
|
description: t('tools.define_ui_contract.description'),
|
|
54
54
|
annotations: { readOnlyHint: true },
|
|
55
55
|
inputSchema: {
|
|
56
|
-
specId:
|
|
56
|
+
specId: SpecIdSchema.describe('Spec ID'),
|
|
57
57
|
projectId: z.string().max(500).describe('Project ID'),
|
|
58
58
|
screens: z
|
|
59
59
|
.array(z.string().max(500))
|
|
@@ -67,7 +67,7 @@ export function registerDesignStackTools(server) {
|
|
|
67
67
|
description: t('tools.challenge_spec.description'),
|
|
68
68
|
annotations: { readOnlyHint: true },
|
|
69
69
|
inputSchema: {
|
|
70
|
-
specId:
|
|
70
|
+
specId: SpecIdSchema.describe('Spec ID to challenge'),
|
|
71
71
|
projectId: z
|
|
72
72
|
.string()
|
|
73
73
|
.max(500)
|
|
@@ -90,7 +90,7 @@ export function registerDesignStackTools(server) {
|
|
|
90
90
|
description: t('tools.generate_execution_plan.description'),
|
|
91
91
|
annotations: { readOnlyHint: true },
|
|
92
92
|
inputSchema: {
|
|
93
|
-
specId:
|
|
93
|
+
specId: SpecIdSchema.describe('Spec ID'),
|
|
94
94
|
projectId: z.string().max(500).describe('Project ID'),
|
|
95
95
|
},
|
|
96
96
|
}, safeTracked('generate_execution_plan', async (args) => handleGenerateExecutionPlan(args)));
|
|
@@ -145,7 +145,7 @@ export function registerDesignStackTools(server) {
|
|
|
145
145
|
.describe('Skill type: project-rules | domain-skill | workflow | convention'),
|
|
146
146
|
name: z.string().max(500).describe('Skill name'),
|
|
147
147
|
description: z.string().max(10_000).describe('Skill description'),
|
|
148
|
-
specId:
|
|
148
|
+
specId: SpecIdSchema.optional().describe('Spec ID for context'),
|
|
149
149
|
},
|
|
150
150
|
}, safeLicensed('generate_skill', async (args) => handleGenerateSkill(args)));
|
|
151
151
|
// 28. detect_agent
|
|
@@ -208,10 +208,10 @@ export function registerDesignStackTools(server) {
|
|
|
208
208
|
annotations: { readOnlyHint: true },
|
|
209
209
|
inputSchema: {
|
|
210
210
|
specIds: z
|
|
211
|
-
.array(
|
|
211
|
+
.array(SpecIdSchema)
|
|
212
212
|
.min(1)
|
|
213
213
|
.max(100)
|
|
214
|
-
.describe('List of spec IDs to run in parallel (e.g. ["SPEC-013", "SPEC-
|
|
214
|
+
.describe('List of canonical spec IDs to run in parallel (e.g. ["SPEC-013", "SPEC-014"])'),
|
|
215
215
|
projectPath: z.string().max(4096).describe('Absolute path to the project root'),
|
|
216
216
|
mainBranch: z.string().max(500).optional().describe('Main branch name (default: main)'),
|
|
217
217
|
baseBranch: z
|
|
@@ -226,7 +226,7 @@ export function registerDesignStackTools(server) {
|
|
|
226
226
|
description: 'Adversarial review — argues against the spec from first principles before approval. Runs pre-mortem, surfaces hidden assumptions, proposes alternatives.',
|
|
227
227
|
annotations: { readOnlyHint: true },
|
|
228
228
|
inputSchema: {
|
|
229
|
-
specId:
|
|
229
|
+
specId: SpecIdSchema.describe('Spec ID to red-team'),
|
|
230
230
|
projectId: z.string().max(500).describe('Project ID'),
|
|
231
231
|
intensity: z
|
|
232
232
|
.enum(['light', 'full'])
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { t } from '../../i18n/index.js';
|
|
3
3
|
import { safeTracked, safeLicensed } from '../safe-handler.js';
|
|
4
|
-
import { DocumentationTypeEnum, GitActionEnum, DevLifecycleCategoryEnum, PMPlatformEnum, PMActionEnum, OrchestrateActionEnum, ManageContextActionEnum, ContextBudgetActionEnum, } from '../schemas/index.js';
|
|
4
|
+
import { DocumentationTypeEnum, GitActionEnum, DevLifecycleCategoryEnum, PMPlatformEnum, PMActionEnum, OrchestrateActionEnum, ManageContextActionEnum, ContextBudgetActionEnum, SpecIdSchema, } from '../schemas/index.js';
|
|
5
5
|
import { handleSuggestTooling } from '../suggest-tooling.js';
|
|
6
6
|
import { handleGenerateTests } from '../generate-tests.js';
|
|
7
7
|
import { handleGenerateDocs } from '../generate-docs.js';
|
|
@@ -21,7 +21,7 @@ export function registerLifecycleInfraTools(server) {
|
|
|
21
21
|
annotations: { readOnlyHint: true },
|
|
22
22
|
inputSchema: {
|
|
23
23
|
projectId: z.string().max(500).describe('Project ID'),
|
|
24
|
-
specId:
|
|
24
|
+
specId: SpecIdSchema.optional().describe('Spec ID for context'),
|
|
25
25
|
categories: z
|
|
26
26
|
.array(DevLifecycleCategoryEnum)
|
|
27
27
|
.optional()
|
|
@@ -33,7 +33,7 @@ export function registerLifecycleInfraTools(server) {
|
|
|
33
33
|
description: t('tools.generate_tests.description'),
|
|
34
34
|
annotations: { readOnlyHint: true },
|
|
35
35
|
inputSchema: {
|
|
36
|
-
specId:
|
|
36
|
+
specId: SpecIdSchema.describe('Spec ID'),
|
|
37
37
|
projectId: z.string().max(500).describe('Project ID'),
|
|
38
38
|
autoGenerate: z.boolean().optional().describe('Auto-generate test files (default: false)'),
|
|
39
39
|
},
|
|
@@ -49,7 +49,7 @@ export function registerLifecycleInfraTools(server) {
|
|
|
49
49
|
.max(100)
|
|
50
50
|
.describe('Types of documentation to generate'),
|
|
51
51
|
docType: DocumentationTypeEnum.optional().describe('Single documentation type for routing — shorthand for types: [docType]'),
|
|
52
|
-
specId:
|
|
52
|
+
specId: SpecIdSchema.optional().describe('Spec ID for context'),
|
|
53
53
|
audience: z
|
|
54
54
|
.enum(['end-user', 'developer', 'stakeholder', 'ops'])
|
|
55
55
|
.optional()
|
|
@@ -88,7 +88,7 @@ export function registerLifecycleInfraTools(server) {
|
|
|
88
88
|
inputSchema: {
|
|
89
89
|
projectId: z.string().max(500).describe('Project ID'),
|
|
90
90
|
action: GitActionEnum.describe('Git action to perform'),
|
|
91
|
-
specId:
|
|
91
|
+
specId: SpecIdSchema.optional().describe('Spec ID for context'),
|
|
92
92
|
config: z
|
|
93
93
|
.object({
|
|
94
94
|
branchPrefix: z.record(z.string().max(500), z.string().max(500)).optional(),
|
|
@@ -113,10 +113,10 @@ export function registerLifecycleInfraTools(server) {
|
|
|
113
113
|
projectId: z.string().max(500).describe('Project ID'),
|
|
114
114
|
action: OrchestrateActionEnum.describe('Orchestration action'),
|
|
115
115
|
sessionId: z.string().max(500).optional().describe('Agent session ID'),
|
|
116
|
-
specId:
|
|
116
|
+
specId: SpecIdSchema.optional().describe('Spec ID for context'),
|
|
117
117
|
resourcePath: z.string().max(4096).optional().describe('Resource path to lock/unlock'),
|
|
118
118
|
specIds: z
|
|
119
|
-
.array(
|
|
119
|
+
.array(SpecIdSchema)
|
|
120
120
|
.max(100)
|
|
121
121
|
.optional()
|
|
122
122
|
.describe('List of spec IDs for pre-check'),
|
|
@@ -163,7 +163,7 @@ export function registerLifecycleInfraTools(server) {
|
|
|
163
163
|
projectId: z.string().max(500).describe('Project ID'),
|
|
164
164
|
platform: PMPlatformEnum.describe('PM platform'),
|
|
165
165
|
action: PMActionEnum.describe('Integration action'),
|
|
166
|
-
specId:
|
|
166
|
+
specId: SpecIdSchema.optional().describe('Spec ID'),
|
|
167
167
|
config: z
|
|
168
168
|
.record(z.string().max(500), z.string().max(10_000))
|
|
169
169
|
.optional()
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { t } from '../../i18n/index.js';
|
|
3
3
|
import { safeTracked, safeLicensed } from '../safe-handler.js';
|
|
4
|
-
import { ChecklistFocusEnum, AuditCategoryEnum } from '../schemas/index.js';
|
|
4
|
+
import { ChecklistFocusEnum, AuditCategoryEnum, SpecIdSchema } from '../schemas/index.js';
|
|
5
5
|
import { resolveProjectId, missingProjectIdError } from '../resolve-project-id.js';
|
|
6
6
|
import { handleDetectDrift } from '../detect-drift.js';
|
|
7
7
|
import { handleSummarizeSpec } from '../summarize-spec.js';
|
|
@@ -27,7 +27,7 @@ export function registerAnalysisTools(server) {
|
|
|
27
27
|
server.registerTool('detect_drift', {
|
|
28
28
|
description: t('tools.detect_drift.description'),
|
|
29
29
|
inputSchema: {
|
|
30
|
-
specId:
|
|
30
|
+
specId: SpecIdSchema.describe('Spec ID to check'),
|
|
31
31
|
projectId: z.string().max(500).optional().describe('Project ID hash. Prefer projectPath.'),
|
|
32
32
|
projectPath: z
|
|
33
33
|
.string()
|
|
@@ -56,7 +56,7 @@ export function registerAnalysisTools(server) {
|
|
|
56
56
|
description: t('tools.summarize_spec.description'),
|
|
57
57
|
annotations: { readOnlyHint: true },
|
|
58
58
|
inputSchema: {
|
|
59
|
-
specId:
|
|
59
|
+
specId: SpecIdSchema.describe('Spec ID to summarize'),
|
|
60
60
|
projectId: z.string().max(500).optional().describe('Project ID hash. Prefer projectPath.'),
|
|
61
61
|
projectPath: z
|
|
62
62
|
.string()
|
|
@@ -79,7 +79,7 @@ export function registerAnalysisTools(server) {
|
|
|
79
79
|
server.registerTool('generate_checklist', {
|
|
80
80
|
description: t('tools.generate_checklist.description'),
|
|
81
81
|
inputSchema: {
|
|
82
|
-
specId:
|
|
82
|
+
specId: SpecIdSchema.describe('Spec ID'),
|
|
83
83
|
projectId: z.string().max(500).optional().describe('Project ID hash. Prefer projectPath.'),
|
|
84
84
|
projectPath: z
|
|
85
85
|
.string()
|
|
@@ -103,7 +103,7 @@ export function registerAnalysisTools(server) {
|
|
|
103
103
|
server.registerTool('reconcile_spec', {
|
|
104
104
|
description: t('tools.reconcile_spec.description'),
|
|
105
105
|
inputSchema: {
|
|
106
|
-
specId:
|
|
106
|
+
specId: SpecIdSchema.describe('Spec ID to reconcile'),
|
|
107
107
|
projectId: z.string().max(500).optional().describe('Project ID hash. Prefer projectPath.'),
|
|
108
108
|
projectPath: z
|
|
109
109
|
.string()
|
|
@@ -176,7 +176,7 @@ export function registerAnalysisTools(server) {
|
|
|
176
176
|
.max(100)
|
|
177
177
|
.optional()
|
|
178
178
|
.describe('Audit categories to check'),
|
|
179
|
-
specId:
|
|
179
|
+
specId: SpecIdSchema.optional().describe('Spec ID to audit against'),
|
|
180
180
|
},
|
|
181
181
|
}, safeLicensed('audit', async (args) => {
|
|
182
182
|
const pid = resolveProjectId(args);
|
|
@@ -231,7 +231,7 @@ export function registerAnalysisTools(server) {
|
|
|
231
231
|
.max(4096)
|
|
232
232
|
.optional()
|
|
233
233
|
.describe('Absolute path to project root. Derives projectId automatically.'),
|
|
234
|
-
specId:
|
|
234
|
+
specId: SpecIdSchema.optional().describe('Spec ID for context-specific suggestions'),
|
|
235
235
|
},
|
|
236
236
|
}, safeLicensed('suggest_mcps', async (args) => {
|
|
237
237
|
const pid = resolveProjectId(args);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { t } from '../../i18n/index.js';
|
|
3
3
|
import { safeTracked, safeLicensed } from '../safe-handler.js';
|
|
4
|
-
import { SupportedLocaleEnum, SpecStatusEnum, SpecTypeEnum, SpecScopeEnum, SpecTargetEnum, ExperienceLevelEnum, WorkModeEnum, ListSpecsOutputSchema, EstimateOutputSchema, ValidateOutputSchema, } from '../schemas/index.js';
|
|
4
|
+
import { SupportedLocaleEnum, SpecStatusEnum, SpecTypeEnum, SpecScopeEnum, SpecTargetEnum, ExperienceLevelEnum, WorkModeEnum, ListSpecsOutputSchema, EstimateOutputSchema, ValidateOutputSchema, SpecIdSchema, } from '../schemas/index.js';
|
|
5
5
|
import { handleSetLocale } from '../set-locale.js';
|
|
6
6
|
import { handleInitProject } from '../init-project.js';
|
|
7
7
|
import { handleSetWorkMode } from '../set-work-mode-handler.js';
|
|
@@ -274,7 +274,7 @@ export function registerCoreSpecTools(server) {
|
|
|
274
274
|
server.registerTool('update_status', {
|
|
275
275
|
description: t('tools.update_status.description'),
|
|
276
276
|
inputSchema: {
|
|
277
|
-
specId:
|
|
277
|
+
specId: SpecIdSchema.describe('Spec ID to update'),
|
|
278
278
|
projectId: z
|
|
279
279
|
.string()
|
|
280
280
|
.max(500)
|
|
@@ -379,7 +379,7 @@ export function registerCoreSpecTools(server) {
|
|
|
379
379
|
server.registerTool('update_status_batch', {
|
|
380
380
|
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.',
|
|
381
381
|
inputSchema: {
|
|
382
|
-
specIds: z.array(
|
|
382
|
+
specIds: z.array(SpecIdSchema).min(1).describe('Spec IDs to update'),
|
|
383
383
|
projectId: z.string().max(500).optional().describe('Project ID, if known'),
|
|
384
384
|
projectPath: z
|
|
385
385
|
.string()
|
|
@@ -399,7 +399,7 @@ export function registerCoreSpecTools(server) {
|
|
|
399
399
|
server.registerTool('estimate', {
|
|
400
400
|
description: t('tools.estimate.description'),
|
|
401
401
|
inputSchema: {
|
|
402
|
-
specId:
|
|
402
|
+
specId: SpecIdSchema.describe('Spec ID to estimate'),
|
|
403
403
|
projectId: z.string().max(500).describe('Project ID'),
|
|
404
404
|
},
|
|
405
405
|
outputSchema: EstimateOutputSchema,
|
|
@@ -420,7 +420,7 @@ export function registerCoreSpecTools(server) {
|
|
|
420
420
|
server.registerTool('validate', {
|
|
421
421
|
description: t('tools.validate.description'),
|
|
422
422
|
inputSchema: {
|
|
423
|
-
specId:
|
|
423
|
+
specId: SpecIdSchema.describe('Spec ID to validate'),
|
|
424
424
|
projectId: z
|
|
425
425
|
.string()
|
|
426
426
|
.max(500)
|
|
@@ -441,7 +441,7 @@ export function registerCoreSpecTools(server) {
|
|
|
441
441
|
'and missing parameter type annotations. Returns a health score (0-100) and actionable suggestions.',
|
|
442
442
|
inputSchema: {
|
|
443
443
|
projectPath: z.string().max(4096).describe('Absolute path to the project root'),
|
|
444
|
-
specId:
|
|
444
|
+
specId: SpecIdSchema.describe('Spec ID being validated'),
|
|
445
445
|
waiver: z
|
|
446
446
|
.array(z.string().max(4096))
|
|
447
447
|
.max(100)
|
|
@@ -8,9 +8,10 @@ import { detectLLMClient } from '../engine/client-detection.js';
|
|
|
8
8
|
import { getRegisteredProviders, getRenderer, llmClientToProvider, } from '../engine/provider-adapters/registry.js';
|
|
9
9
|
import { loadSpecForRendering } from '../engine/provider-adapters/shared/spec-loader.js';
|
|
10
10
|
import { compactError, compactResult, formatKeyValue } from './output-formatter.js';
|
|
11
|
+
import { SpecIdSchema } from './schemas/index.js';
|
|
11
12
|
const PROVIDER_VALUES = ['claude', 'gpt4', 'gemini', 'markdown'];
|
|
12
13
|
export const RenderSpecForProviderInputSchema = z.object({
|
|
13
|
-
specId:
|
|
14
|
+
specId: SpecIdSchema.describe('Spec identifier, e.g. "SPEC-670"'),
|
|
14
15
|
projectPath: z.string().describe('Absolute path to the project root'),
|
|
15
16
|
provider: z
|
|
16
17
|
.enum(PROVIDER_VALUES)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// schemas/github.ts — Zod schemas for GitHub integration tools (SPEC-086).
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
import { SpecIdSchema } from './spec-id.js';
|
|
3
4
|
export const CreatePRFromSpecSchema = z.object({
|
|
4
|
-
specId:
|
|
5
|
+
specId: SpecIdSchema.describe('ID of the spec (e.g., SPEC-086)'),
|
|
5
6
|
baseBranch: z
|
|
6
7
|
.string()
|
|
7
8
|
.max(500)
|
|
@@ -11,7 +12,7 @@ export const CreatePRFromSpecSchema = z.object({
|
|
|
11
12
|
});
|
|
12
13
|
export const PRStatusSchema = z.object({
|
|
13
14
|
prNumber: z.number().optional().describe('Specific PR number to query'),
|
|
14
|
-
specId:
|
|
15
|
+
specId: SpecIdSchema.optional().describe('Spec ID to find related PRs (e.g., SPEC-086)'),
|
|
15
16
|
view: z.enum(['prs', 'milestones']).default('prs').describe('View mode: prs | milestones'),
|
|
16
17
|
});
|
|
17
18
|
export const ReviewPRSchema = z.object({
|
|
@@ -55,7 +56,7 @@ export const GenerateChangelogSchema = z.object({
|
|
|
55
56
|
.describe('Write the generated changelog to CHANGELOG.md (prepends to existing file). Default: false.'),
|
|
56
57
|
});
|
|
57
58
|
export const CreateIssueFromSpecSchema = z.object({
|
|
58
|
-
specId:
|
|
59
|
+
specId: SpecIdSchema.describe('ID of the spec (e.g., SPEC-086)'),
|
|
59
60
|
criteriaIds: z
|
|
60
61
|
.array(z.string().max(500))
|
|
61
62
|
.max(1000)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { SupportedLocaleEnum, SpecStatusEnum, SpecTypeEnum, SpecScopeEnum, SpecTargetEnum, ExperienceLevelEnum, WorkModeEnum, } from './spec.js';
|
|
2
|
+
export { SPEC_ID_PATTERN, SPEC_DIR_NAME_PATTERN, SpecIdSchema, SpecDirNameSchema, isCanonicalSpecId, } from './spec-id.js';
|
|
2
3
|
export { AuditCategoryEnum, ChecklistFocusEnum, ChallengeSpecFocusEnum } from './analysis.js';
|
|
3
4
|
export { AgentPlatformEnum, SubAgentFrameworkEnum } from './agents.js';
|
|
4
5
|
export { DocumentationTypeEnum, GitActionEnum, DevLifecycleCategoryEnum, PMPlatformEnum, PMActionEnum, } from './lifecycle.js';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// schemas/index.ts — Barrel re-export for all Zod enum schemas.
|
|
2
2
|
export { SupportedLocaleEnum, SpecStatusEnum, SpecTypeEnum, SpecScopeEnum, SpecTargetEnum, ExperienceLevelEnum, WorkModeEnum, } from './spec.js';
|
|
3
|
+
export { SPEC_ID_PATTERN, SPEC_DIR_NAME_PATTERN, SpecIdSchema, SpecDirNameSchema, isCanonicalSpecId, } from './spec-id.js';
|
|
3
4
|
export { AuditCategoryEnum, ChecklistFocusEnum, ChallengeSpecFocusEnum } from './analysis.js';
|
|
4
5
|
export { AgentPlatformEnum, SubAgentFrameworkEnum } from './agents.js';
|
|
5
6
|
export { DocumentationTypeEnum, GitActionEnum, DevLifecycleCategoryEnum, PMPlatformEnum, PMActionEnum, } from './lifecycle.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const SPEC_ID_PATTERN: RegExp;
|
|
3
|
+
export declare const SPEC_DIR_NAME_PATTERN: RegExp;
|
|
4
|
+
export declare const SpecIdSchema: z.ZodString;
|
|
5
|
+
export declare const SpecDirNameSchema: z.ZodString;
|
|
6
|
+
export declare function isCanonicalSpecId(value: string): boolean;
|
|
7
|
+
//# sourceMappingURL=spec-id.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const SPEC_ID_PATTERN = /^SPEC-\d+$/;
|
|
3
|
+
export const SPEC_DIR_NAME_PATTERN = /^SPEC-\d+(?:-[a-z0-9]+(?:-[a-z0-9]+)*)?$/;
|
|
4
|
+
export const SpecIdSchema = z
|
|
5
|
+
.string()
|
|
6
|
+
.min(1)
|
|
7
|
+
.max(50)
|
|
8
|
+
.regex(SPEC_ID_PATTERN, 'Spec ID must use canonical numeric format like SPEC-042.');
|
|
9
|
+
export const SpecDirNameSchema = z
|
|
10
|
+
.string()
|
|
11
|
+
.min(1)
|
|
12
|
+
.max(200)
|
|
13
|
+
.regex(SPEC_DIR_NAME_PATTERN, 'Spec directory must start with a canonical numeric ID like SPEC-042 or SPEC-042-my-feature.');
|
|
14
|
+
export function isCanonicalSpecId(value) {
|
|
15
|
+
return SPEC_ID_PATTERN.test(value);
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=spec-id.js.map
|
|
@@ -4,6 +4,7 @@ import { getSpec } from '../storage/spec-store.js';
|
|
|
4
4
|
import { hashProjectPath } from '../storage/base-store.js';
|
|
5
5
|
import { generatePromptFromSpec } from '../engine/spec-prompt-generator.js';
|
|
6
6
|
import { compactResult } from './output-formatter.js';
|
|
7
|
+
import { SpecIdSchema } from './schemas/index.js';
|
|
7
8
|
// ---------------------------------------------------------------------------
|
|
8
9
|
// In-memory registry of registered prompts for this session
|
|
9
10
|
// ---------------------------------------------------------------------------
|
|
@@ -12,7 +13,7 @@ export const registeredPrompts = new Map();
|
|
|
12
13
|
// Zod schemas
|
|
13
14
|
// ---------------------------------------------------------------------------
|
|
14
15
|
const ExposeSpecAsPromptInputSchema = {
|
|
15
|
-
specId:
|
|
16
|
+
specId: SpecIdSchema.describe('The spec ID to expose as an MCP prompt (e.g. "SPEC-042")'),
|
|
16
17
|
projectPath: z.string().describe('Absolute path to the project root where the spec is stored'),
|
|
17
18
|
promptName: z
|
|
18
19
|
.string()
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
// To add a tool: import its handler + schema, add an entry to the array.
|
|
11
11
|
// To read them: import coreToolsRegistry from this file.
|
|
12
12
|
import { z } from 'zod';
|
|
13
|
+
import { SpecIdSchema } from '../schemas/index.js';
|
|
13
14
|
// ── Handlers ────────────────────────────────────────────────────────────────
|
|
14
15
|
import { GenerateBatchScriptInputSchema, handleGenerateBatchScript, } from '../generate-batch-script.js';
|
|
15
16
|
import { GenerateAutomationGuideInputSchema, handleGenerateAutomationGuide, } from '../generate-automation-guide.js';
|
|
@@ -373,7 +374,7 @@ const coreToolsRegistry = [
|
|
|
373
374
|
'(structured JSON optimized for autonomous coding agents: Devin, Kiro, SWE-agent, generic). ' +
|
|
374
375
|
'Writes files to .specify/specs/{slug}/ by default.',
|
|
375
376
|
schema: {
|
|
376
|
-
specId:
|
|
377
|
+
specId: SpecIdSchema.describe('Spec ID to export (e.g. "SPEC-001")'),
|
|
377
378
|
projectPath: z.string().min(1).max(2000).describe('Absolute path to the project root'),
|
|
378
379
|
format: z
|
|
379
380
|
.enum(['spec-kit', 'agent-ready'])
|
|
@@ -687,7 +688,7 @@ const coreToolsRegistry = [
|
|
|
687
688
|
'detects drift, and updates the ## Progress section of spec.md with real coverage data. ' +
|
|
688
689
|
'Works entirely offline — pure local file analysis.',
|
|
689
690
|
schema: {
|
|
690
|
-
specId:
|
|
691
|
+
specId: SpecIdSchema.describe('Spec ID, e.g. SPEC-042'),
|
|
691
692
|
projectPath: z.string().min(1).max(4096).describe('Absolute path to project root'),
|
|
692
693
|
},
|
|
693
694
|
handler: async (args) => handleReconcileSpecLiving({
|
|
@@ -13,7 +13,7 @@ import { safe, safeLicensed, safeTracked } from '../safe-handler.js';
|
|
|
13
13
|
// ── License tools (register-license-tools.ts) ───────────────────────────────
|
|
14
14
|
import { handleActivateLicense, handleDeactivateLicense } from '../activate-license.js';
|
|
15
15
|
import { handleLicenseStatus } from '../license-status.js';
|
|
16
|
-
import { LicenseStatusOutputSchema } from '../schemas/index.js';
|
|
16
|
+
import { LicenseStatusOutputSchema, SpecIdSchema } from '../schemas/index.js';
|
|
17
17
|
// ── OAuth tools (register-oauth-tools.ts / register-configure-oauth-tool.ts) ─
|
|
18
18
|
import { handleStartOAuthFlow, handleOAuthStatus, StartOAuthFlowInputSchema, OAuthStatusInputSchema, } from '../oauth-handler.js';
|
|
19
19
|
import { handleConfigureOAuth } from '../configure-oauth-handler.js';
|
|
@@ -386,7 +386,7 @@ export function registerInfraGroupTools(server) {
|
|
|
386
386
|
inputSchema: {
|
|
387
387
|
projectPath: z.string().min(1).describe('Absolute path to the project root'),
|
|
388
388
|
provider: deployProviderEnum,
|
|
389
|
-
specId:
|
|
389
|
+
specId: SpecIdSchema.optional().describe('Optional spec ID for traceability'),
|
|
390
390
|
environment: z
|
|
391
391
|
.enum(['production', 'preview'])
|
|
392
392
|
.optional()
|
|
@@ -484,7 +484,7 @@ export function registerInfraGroupTools(server) {
|
|
|
484
484
|
.string()
|
|
485
485
|
.min(1)
|
|
486
486
|
.describe('Absolute path to the project root (e.g. "/home/user/my-app").'),
|
|
487
|
-
specId:
|
|
487
|
+
specId: SpecIdSchema.describe('Spec ID to validate (e.g. "SPEC-042").'),
|
|
488
488
|
baseUrl: z
|
|
489
489
|
.string()
|
|
490
490
|
.min(1)
|
|
@@ -49,7 +49,7 @@ import { handleDefineLintRule, handleListLintRules, handleRunSpecLint, handleDel
|
|
|
49
49
|
// ── Readiness (register-readiness-tools.ts) ───────────────────────────────────
|
|
50
50
|
import { handleCheckReadiness } from '../check-readiness.js';
|
|
51
51
|
import { handlePackageHandoff } from '../package-handoff.js';
|
|
52
|
-
import { CheckReadinessOutputSchema } from '../schemas/index.js';
|
|
52
|
+
import { CheckReadinessOutputSchema, SpecIdSchema } from '../schemas/index.js';
|
|
53
53
|
// ── Approval (register-approval-tools.ts) ────────────────────────────────────
|
|
54
54
|
import { handleConfigureApprovalPolicy, handleApproveSpec, handleRequestChanges, handleApprovalStatus, handleIssueReviewerToken, } from '../approval-handler.js';
|
|
55
55
|
// ── SPEC-961: Architecture lint ─────────────────────────────────────────────
|
|
@@ -250,7 +250,7 @@ export function registerQualityComplianceGroupTools(s) {
|
|
|
250
250
|
description: '[DEPRECATED] Use check_compliance with mode=verify instead. Checks whether implemented code matches an approved spec. Score >=80 = compliant, 60-79 = partial, <60 = non-compliant.',
|
|
251
251
|
annotations: { readOnlyHint: false },
|
|
252
252
|
inputSchema: {
|
|
253
|
-
specId:
|
|
253
|
+
specId: SpecIdSchema.describe('Spec ID to verify (e.g. SPEC-042).'),
|
|
254
254
|
projectPath: z
|
|
255
255
|
.string()
|
|
256
256
|
.max(4096)
|
|
@@ -393,7 +393,7 @@ export function registerQualityComplianceGroupTools(s) {
|
|
|
393
393
|
description: t('tools.security_check.description'),
|
|
394
394
|
annotations: { readOnlyHint: true },
|
|
395
395
|
inputSchema: {
|
|
396
|
-
specId:
|
|
396
|
+
specId: SpecIdSchema.describe('Spec ID to analyze'),
|
|
397
397
|
...projectIdSchema,
|
|
398
398
|
subcommand: z
|
|
399
399
|
.enum(['analyze', 'score', 'drift'])
|
|
@@ -526,7 +526,7 @@ export function registerQualityComplianceGroupTools(s) {
|
|
|
526
526
|
'Use library="all" to generate schemas for all three libraries at once.',
|
|
527
527
|
inputSchema: {
|
|
528
528
|
projectPath: z.string().min(1).max(4096).describe('Absolute path to the project root.'),
|
|
529
|
-
specId:
|
|
529
|
+
specId: SpecIdSchema.describe('Spec ID to analyze (e.g. SPEC-042).'),
|
|
530
530
|
library: z
|
|
531
531
|
.enum(['zod', 'yup', 'valibot', 'all'])
|
|
532
532
|
.describe('Schema library. Values: zod, yup, valibot, all.'),
|
|
@@ -547,7 +547,7 @@ export function registerQualityComplianceGroupTools(s) {
|
|
|
547
547
|
'"tanstack-query" for React Query hooks.',
|
|
548
548
|
inputSchema: {
|
|
549
549
|
projectPath: z.string().min(1).max(4096).describe('Absolute path to the project root.'),
|
|
550
|
-
specId:
|
|
550
|
+
specId: SpecIdSchema.describe('Spec ID to analyze (e.g. SPEC-042).'),
|
|
551
551
|
framework: z
|
|
552
552
|
.enum(['fetch', 'tanstack-query'])
|
|
553
553
|
.describe('Client framework. Values: fetch, tanstack-query.'),
|
|
@@ -583,10 +583,7 @@ export function registerQualityComplianceGroupTools(s) {
|
|
|
583
583
|
annotations: { readOnlyHint: true },
|
|
584
584
|
inputSchema: {
|
|
585
585
|
projectPath: z.string().describe('Absolute path to the project root'),
|
|
586
|
-
specId:
|
|
587
|
-
.string()
|
|
588
|
-
.optional()
|
|
589
|
-
.describe('SPEC-XXX ID to generate acceptance criteria stubs from'),
|
|
586
|
+
specId: SpecIdSchema.optional().describe('Spec ID to generate acceptance criteria stubs from'),
|
|
590
587
|
registerFile: z
|
|
591
588
|
.string()
|
|
592
589
|
.optional()
|
|
@@ -598,7 +595,7 @@ export function registerQualityComplianceGroupTools(s) {
|
|
|
598
595
|
annotations: { readOnlyHint: true },
|
|
599
596
|
inputSchema: {
|
|
600
597
|
projectPath: z.string().describe('Absolute path to the project root'),
|
|
601
|
-
specId:
|
|
598
|
+
specId: SpecIdSchema.optional().describe('Spec ID to check readiness/completion for'),
|
|
602
599
|
},
|
|
603
600
|
}, async (params) => handleTddStatus(params));
|
|
604
601
|
// ── Coverage ─────────────────────────────────────────────────────────────────
|
|
@@ -841,7 +838,7 @@ export function registerQualityComplianceGroupTools(s) {
|
|
|
841
838
|
description: t('tools.check_readiness.description'),
|
|
842
839
|
inputSchema: {
|
|
843
840
|
...projectIdSchema,
|
|
844
|
-
specId:
|
|
841
|
+
specId: SpecIdSchema.describe('Spec ID to evaluate'),
|
|
845
842
|
mode: z
|
|
846
843
|
.enum(['strict', 'lenient'])
|
|
847
844
|
.optional()
|
|
@@ -962,7 +959,7 @@ export function registerQualityComplianceGroupTools(s) {
|
|
|
962
959
|
description: t('tools.package_handoff.description'),
|
|
963
960
|
inputSchema: {
|
|
964
961
|
...projectIdSchema,
|
|
965
|
-
specId:
|
|
962
|
+
specId: SpecIdSchema.describe('Spec ID to generate handoff package for'),
|
|
966
963
|
},
|
|
967
964
|
}, safeLicensed('package_handoff', withProject((args) => handlePackageHandoff(args))));
|
|
968
965
|
// ── Approval ─────────────────────────────────────────────────────────────────
|
|
@@ -1020,7 +1017,7 @@ export function registerQualityComplianceGroupTools(s) {
|
|
|
1020
1017
|
'When the required number of approvals is reached, the approval gate opens.',
|
|
1021
1018
|
inputSchema: {
|
|
1022
1019
|
projectPath: z.string().min(1).max(4096).describe('Absolute path to the project root.'),
|
|
1023
|
-
specId:
|
|
1020
|
+
specId: SpecIdSchema.describe('Spec ID to approve (e.g. SPEC-042).'),
|
|
1024
1021
|
reviewer: z
|
|
1025
1022
|
.string()
|
|
1026
1023
|
.describe('Name, email, or username of the reviewer approving the spec.'),
|
|
@@ -1073,7 +1070,7 @@ export function registerQualityComplianceGroupTools(s) {
|
|
|
1073
1070
|
'time in review, and SLA breach warning if applicable.',
|
|
1074
1071
|
inputSchema: {
|
|
1075
1072
|
projectPath: z.string().min(1).max(4096).describe('Absolute path to the project root.'),
|
|
1076
|
-
specId:
|
|
1073
|
+
specId: SpecIdSchema.describe('Spec ID to check (e.g. SPEC-042).'),
|
|
1077
1074
|
},
|
|
1078
1075
|
annotations: { title: 'Approval Status', readOnlyHint: true },
|
|
1079
1076
|
}, safeTracked('approval_status', (args) => handleApprovalStatus(args)));
|
|
@@ -289,42 +289,6 @@ export async function runDoneActions(projectId, specId, gitBranch) {
|
|
|
289
289
|
clearPending(specId, 'generateSessionContext');
|
|
290
290
|
}
|
|
291
291
|
})(),
|
|
292
|
-
// SPEC-649: Append spec to releases/pending.json — changelog written only at release time
|
|
293
|
-
// SPEC-660: Dedup — pending.json append runs once per spec within 5s window
|
|
294
|
-
(async () => {
|
|
295
|
-
if (hasPending(specId, 'appendPending')) {
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
markPending(specId, 'appendPending');
|
|
299
|
-
try {
|
|
300
|
-
const { join } = await import('node:path');
|
|
301
|
-
const { readFile, writeFile, mkdir } = await import('node:fs/promises');
|
|
302
|
-
const releasesDir = join(projectPath, 'planu', 'releases');
|
|
303
|
-
const pendingPath = join(releasesDir, 'pending.json');
|
|
304
|
-
await mkdir(releasesDir, { recursive: true });
|
|
305
|
-
const spec = await specStore.getSpec(projectId, specId);
|
|
306
|
-
let pendingList = [];
|
|
307
|
-
try {
|
|
308
|
-
const raw = await readFile(pendingPath, 'utf-8');
|
|
309
|
-
const parsed = JSON.parse(raw);
|
|
310
|
-
if (Array.isArray(parsed)) {
|
|
311
|
-
pendingList = parsed;
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
catch {
|
|
315
|
-
/* file doesn't exist yet — start fresh */
|
|
316
|
-
}
|
|
317
|
-
const completedAt = new Date().toISOString().substring(0, 10);
|
|
318
|
-
pendingList.push({ specId, title: spec?.title ?? specId, completedAt });
|
|
319
|
-
await writeFile(pendingPath, JSON.stringify(pendingList, null, 2), 'utf-8');
|
|
320
|
-
}
|
|
321
|
-
catch {
|
|
322
|
-
/* best-effort */
|
|
323
|
-
}
|
|
324
|
-
finally {
|
|
325
|
-
clearPending(specId, 'appendPending');
|
|
326
|
-
}
|
|
327
|
-
})(),
|
|
328
292
|
// SPEC-629: Delete ephemeral prompt.md — file served its purpose once implementing starts
|
|
329
293
|
(async () => {
|
|
330
294
|
/* v8 ignore start */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planu/cli",
|
|
3
|
-
"version": "4.3.
|
|
3
|
+
"version": "4.3.21",
|
|
4
4
|
"description": "Planu — MCP Server for Spec Driven Development with native Rust acceleration for hot paths. Cross-platform (Linux/macOS/Windows, x64/arm64, glibc/musl).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -34,14 +34,14 @@
|
|
|
34
34
|
"packageName": "@planu/core"
|
|
35
35
|
},
|
|
36
36
|
"optionalDependencies": {
|
|
37
|
-
"@planu/core-darwin-arm64": "4.3.
|
|
38
|
-
"@planu/core-darwin-x64": "4.3.
|
|
39
|
-
"@planu/core-linux-arm64-gnu": "4.3.
|
|
40
|
-
"@planu/core-linux-arm64-musl": "4.3.
|
|
41
|
-
"@planu/core-linux-x64-gnu": "4.3.
|
|
42
|
-
"@planu/core-linux-x64-musl": "4.3.
|
|
43
|
-
"@planu/core-win32-arm64-msvc": "4.3.
|
|
44
|
-
"@planu/core-win32-x64-msvc": "4.3.
|
|
37
|
+
"@planu/core-darwin-arm64": "4.3.21",
|
|
38
|
+
"@planu/core-darwin-x64": "4.3.21",
|
|
39
|
+
"@planu/core-linux-arm64-gnu": "4.3.21",
|
|
40
|
+
"@planu/core-linux-arm64-musl": "4.3.21",
|
|
41
|
+
"@planu/core-linux-x64-gnu": "4.3.21",
|
|
42
|
+
"@planu/core-linux-x64-musl": "4.3.21",
|
|
43
|
+
"@planu/core-win32-arm64-msvc": "4.3.21",
|
|
44
|
+
"@planu/core-win32-x64-msvc": "4.3.21"
|
|
45
45
|
},
|
|
46
46
|
"engines": {
|
|
47
47
|
"node": ">=24.0.0"
|
package/planu-native.json
CHANGED
package/planu-plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dev.planu.cli",
|
|
3
3
|
"displayName": "Planu — Spec Driven Development",
|
|
4
4
|
"description": "Manage software specs, estimations, and autonomous SDD workflows. Language-agnostic MCP server for Claude Code.",
|
|
5
|
-
"version": "4.3.
|
|
5
|
+
"version": "4.3.21",
|
|
6
6
|
"icon": "assets/plugin/icon.svg",
|
|
7
7
|
"command": [
|
|
8
8
|
"npx",
|