@planu/cli 4.4.0 → 4.4.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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## [4.4.2] - 2026-06-05
2
+
3
+ ### Bug Fixes
4
+ - fix: keep spec folders spec.md-only and store evidence externally
5
+
6
+
7
+ ## [4.4.1] - 2026-06-04
8
+
9
+ ### Bug Fixes
10
+ - fix: make validate spec-scoped and non-mutating
11
+
12
+ ### Chores
13
+ - chore(deps): sync lockfile
14
+ - chore(deps): update patch and minor dependencies
15
+
16
+
1
17
  ## [4.4.0] - 2026-06-03
2
18
 
3
19
  ### Features
@@ -4019,4 +4035,4 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) · Versioning:
4019
4035
  - Mermaid diagram generation (architecture, sequence, state machine, ER, data flow)
4020
4036
  - Multi-language i18n (EN/ES/PT) for generated specs
4021
4037
  - Clean Architecture (hexagonal) — engine, tools, storage, types layers
4022
- - 10,857 tests with ≥95% coverage
4038
+ - 10,857 tests with ≥95% coverage
@@ -1,5 +1,5 @@
1
1
  import { readFile } from 'node:fs/promises';
2
- import { dirname, isAbsolute, join } from 'node:path';
2
+ import { join } from 'node:path';
3
3
  import { z } from 'zod';
4
4
  import { projectDataDir } from '../../storage/base-store.js';
5
5
  const DiscoverySchema = z.object({
@@ -49,15 +49,6 @@ const ContractValidationSchema = z.object({
49
49
  reportPath: z.string().optional(),
50
50
  summary: z.string().optional(),
51
51
  });
52
- function resolveSpecPath(spec, projectPath) {
53
- if (isAbsolute(spec.specPath) || !projectPath) {
54
- return spec.specPath;
55
- }
56
- return join(projectPath, spec.specPath);
57
- }
58
- function specEvidencePath(spec, filename, projectPath) {
59
- return join(dirname(resolveSpecPath(spec, projectPath)), 'evidence', filename);
60
- }
61
52
  function handoffEvidencePath(projectId, specId, filename) {
62
53
  return join(projectDataDir(projectId), 'handoffs', specId, filename);
63
54
  }
@@ -81,7 +72,7 @@ async function readUnknown(paths) {
81
72
  }
82
73
  async function readOptional(args) {
83
74
  try {
84
- const found = await readUnknown(args.paths);
75
+ const found = await readUnknown(args.paths.filter((path) => Boolean(path)));
85
76
  if (!found) {
86
77
  return undefined;
87
78
  }
@@ -103,28 +94,19 @@ export async function readEvidenceArtifacts(args) {
103
94
  label: 'Discovery evidence',
104
95
  invalidArtifacts,
105
96
  schema: DiscoverySchema,
106
- paths: [
107
- specEvidencePath(args.spec, 'discovery.json', args.projectPath),
108
- handoffEvidencePath(args.projectId, args.specId, 'discovery.json'),
109
- ],
97
+ paths: [handoffEvidencePath(args.projectId, args.specId, 'discovery.json')],
110
98
  });
111
99
  const taskPlan = await readOptional({
112
100
  label: 'Task plan evidence',
113
101
  invalidArtifacts,
114
102
  schema: TaskPlanSchema,
115
- paths: [
116
- specEvidencePath(args.spec, 'task-plan.json', args.projectPath),
117
- handoffEvidencePath(args.projectId, args.specId, 'task-plan.json'),
118
- ],
103
+ paths: [handoffEvidencePath(args.projectId, args.specId, 'task-plan.json')],
119
104
  });
120
105
  const traceabilityMatrix = await readOptional({
121
106
  label: 'Traceability matrix evidence',
122
107
  invalidArtifacts,
123
108
  schema: TraceabilityMatrixSchema,
124
- paths: [
125
- specEvidencePath(args.spec, 'traceability-matrix.json', args.projectPath),
126
- handoffEvidencePath(args.projectId, args.specId, 'traceability-matrix.json'),
127
- ],
109
+ paths: [handoffEvidencePath(args.projectId, args.specId, 'traceability-matrix.json')],
128
110
  });
129
111
  const contractValidations = [];
130
112
  for (const filename of [
@@ -138,10 +120,7 @@ export async function readEvidenceArtifacts(args) {
138
120
  label: filename,
139
121
  invalidArtifacts,
140
122
  schema: ContractValidationSchema,
141
- paths: [
142
- specEvidencePath(args.spec, filename, args.projectPath),
143
- handoffEvidencePath(args.projectId, args.specId, filename),
144
- ],
123
+ paths: [handoffEvidencePath(args.projectId, args.specId, filename)],
145
124
  });
146
125
  if (evidence) {
147
126
  contractValidations.push(evidence);
@@ -3,6 +3,7 @@ export declare const PLANU_CANONICAL_POLICY: PlanuCanonicalPathPolicy;
3
3
  export declare function isCanonicalPlanuRootFile(name: string): boolean;
4
4
  export declare function isCanonicalPlanuRootDir(name: string): boolean;
5
5
  export declare function isCanonicalSpecFile(name: string): boolean;
6
+ export declare function isCanonicalSpecDir(name: string): boolean;
6
7
  export declare function mustMergeBeforeDeleteSpecFile(name: string): boolean;
7
8
  export declare function isCanonicalReleaseFile(relativeToPlanu: string): boolean;
8
9
  export declare function canonicalContractText(): string;
@@ -4,6 +4,7 @@ export const PLANU_CANONICAL_POLICY = {
4
4
  canonicalRootFiles: ['conventions.json', 'context.md', 'session-context.md', 'session.json'],
5
5
  canonicalRootDirs: ['releases', 'specs'],
6
6
  canonicalSpecFiles: ['spec.md'],
7
+ canonicalSpecDirs: [],
7
8
  forbiddenHostAssetRootDirs: ['agents', 'skills', 'rules', 'hooks'],
8
9
  generatedRuntimePatterns: [
9
10
  'planu/index.html',
@@ -38,6 +39,7 @@ export const PLANU_CANONICAL_POLICY = {
38
39
  const ROOT_FILE_SET = new Set(PLANU_CANONICAL_POLICY.canonicalRootFiles);
39
40
  const ROOT_DIR_SET = new Set(PLANU_CANONICAL_POLICY.canonicalRootDirs);
40
41
  const SPEC_FILE_SET = new Set(PLANU_CANONICAL_POLICY.canonicalSpecFiles);
42
+ const SPEC_DIR_SET = new Set(PLANU_CANONICAL_POLICY.canonicalSpecDirs);
41
43
  const LEGACY_MERGE_SET = new Set(PLANU_CANONICAL_POLICY.legacyMergeBeforeDeleteFiles);
42
44
  export function isCanonicalPlanuRootFile(name) {
43
45
  return ROOT_FILE_SET.has(name);
@@ -48,6 +50,9 @@ export function isCanonicalPlanuRootDir(name) {
48
50
  export function isCanonicalSpecFile(name) {
49
51
  return SPEC_FILE_SET.has(name);
50
52
  }
53
+ export function isCanonicalSpecDir(name) {
54
+ return SPEC_DIR_SET.has(name);
55
+ }
51
56
  export function mustMergeBeforeDeleteSpecFile(name) {
52
57
  return LEGACY_MERGE_SET.has(name);
53
58
  }
@@ -67,6 +72,8 @@ export function canonicalContractText() {
67
72
  ' SPEC-XXX-slug/',
68
73
  ' spec.md',
69
74
  '',
75
+ 'Evidence artifacts are Planu runtime data and are stored outside planu/specs/.',
76
+ '',
70
77
  'Host adapters are written outside planu/:',
71
78
  ' Claude Code: .claude/agents, .claude/skills, .claude/rules',
72
79
  ' Codex: AGENTS.md, .agents/skills, .codex/agents',
@@ -1,6 +1,6 @@
1
- import type { StrictPlanuCleanupResult, StrictPlanuValidationResult } from '../../types/index.js';
1
+ import type { StrictPlanuCleanupResult, StrictPlanuValidationOptions, StrictPlanuValidationResult } from '../../types/index.js';
2
2
  import { PLANU_CANONICAL_POLICY } from './planu-canonical-policy.js';
3
3
  export declare function runStrictPlanuCleanup(projectPath: string): Promise<StrictPlanuCleanupResult>;
4
- export declare function validateStrictPlanuLayout(projectPath: string): Promise<StrictPlanuValidationResult>;
4
+ export declare function validateStrictPlanuLayout(projectPath: string, options?: StrictPlanuValidationOptions): Promise<StrictPlanuValidationResult>;
5
5
  export { PLANU_CANONICAL_POLICY };
6
6
  //# sourceMappingURL=strict-planu-cleanup.d.ts.map
@@ -4,10 +4,10 @@ import { readdir, readFile, rm, stat } from 'node:fs/promises';
4
4
  import { existsSync } from 'node:fs';
5
5
  import { execFile } from 'node:child_process';
6
6
  import { promisify } from 'node:util';
7
- import { join, relative } from 'node:path';
7
+ import { dirname, isAbsolute, join, relative } from 'node:path';
8
8
  import { atomicWriteFile } from '../safety/atomic-write-file.js';
9
9
  import { safeUnlink } from './git-aware-fs.js';
10
- import { PLANU_CANONICAL_POLICY, canonicalContractText, isCanonicalPlanuRootDir, isCanonicalPlanuRootFile, isCanonicalReleaseFile, isCanonicalSpecFile, mustMergeBeforeDeleteSpecFile, } from './planu-canonical-policy.js';
10
+ import { PLANU_CANONICAL_POLICY, canonicalContractText, isCanonicalPlanuRootDir, isCanonicalPlanuRootFile, isCanonicalReleaseFile, isCanonicalSpecDir, isCanonicalSpecFile, mustMergeBeforeDeleteSpecFile, } from './planu-canonical-policy.js';
11
11
  const execFileAsync = promisify(execFile);
12
12
  async function pathIsDirectory(path) {
13
13
  try {
@@ -125,7 +125,9 @@ async function walkSpecDirectory(projectPath, specDir, result) {
125
125
  }
126
126
  continue;
127
127
  }
128
- if (entry === 'reference' || !isCanonicalSpecFile(entry)) {
128
+ const isDir = await pathIsDirectory(full);
129
+ if (entry === 'reference' ||
130
+ (isDir ? !isCanonicalSpecDir(entry) : !isCanonicalSpecFile(entry))) {
129
131
  await removePath(projectPath, full);
130
132
  result.deleted.push(relative(projectPath, full));
131
133
  }
@@ -173,33 +175,46 @@ export async function runStrictPlanuCleanup(projectPath) {
173
175
  result.gitignoreUpdated = await updateGitignore(projectPath);
174
176
  return result;
175
177
  }
176
- export async function validateStrictPlanuLayout(projectPath) {
178
+ function resolveSpecDirsForValidation(projectPath, specPath) {
179
+ if (!specPath?.trim()) {
180
+ return null;
181
+ }
182
+ const resolved = isAbsolute(specPath) ? specPath : join(projectPath, specPath);
183
+ return [dirname(resolved)];
184
+ }
185
+ export async function validateStrictPlanuLayout(projectPath, options = {}) {
177
186
  const offenders = [];
178
187
  const planuDir = join(projectPath, 'planu');
179
- const entries = await readdir(planuDir).catch(() => []);
180
- for (const entry of entries) {
181
- const full = join(planuDir, entry);
182
- const isDir = await pathIsDirectory(full);
183
- if ((isDir && !isCanonicalPlanuRootDir(entry)) ||
184
- (!isDir && !isCanonicalPlanuRootFile(entry))) {
185
- offenders.push(relative(projectPath, full));
188
+ if (options.includeRoot !== false) {
189
+ const entries = await readdir(planuDir).catch(() => []);
190
+ for (const entry of entries) {
191
+ const full = join(planuDir, entry);
192
+ const isDir = await pathIsDirectory(full);
193
+ if ((isDir && !isCanonicalPlanuRootDir(entry)) ||
194
+ (!isDir && !isCanonicalPlanuRootFile(entry))) {
195
+ offenders.push(relative(projectPath, full));
196
+ }
186
197
  }
187
- }
188
- for (const entry of await readdir(join(planuDir, 'releases')).catch(() => [])) {
189
- const rel = `releases/${entry}`;
190
- if (!isCanonicalReleaseFile(rel)) {
191
- offenders.push(`planu/${rel}`);
198
+ for (const entry of await readdir(join(planuDir, 'releases')).catch(() => [])) {
199
+ const rel = `releases/${entry}`;
200
+ if (!isCanonicalReleaseFile(rel)) {
201
+ offenders.push(`planu/${rel}`);
202
+ }
192
203
  }
193
204
  }
194
- for (const specDir of await readdir(join(planuDir, 'specs')).catch(() => [])) {
195
- const full = join(planuDir, 'specs', specDir);
205
+ const scopedSpecDirs = resolveSpecDirsForValidation(projectPath, options.specPath) ??
206
+ (await readdir(join(planuDir, 'specs')).catch(() => [])).map((specDir) => join(planuDir, 'specs', specDir));
207
+ for (const full of scopedSpecDirs) {
208
+ const specDir = relative(join(planuDir, 'specs'), full);
196
209
  if (!(await pathIsDirectory(full)) || specDir === 'data') {
197
210
  offenders.push(relative(projectPath, full));
198
211
  continue;
199
212
  }
200
213
  for (const entry of await readdir(full).catch(() => [])) {
201
- if (!isCanonicalSpecFile(entry)) {
202
- offenders.push(relative(projectPath, join(full, entry)));
214
+ const entryPath = join(full, entry);
215
+ const isDir = await pathIsDirectory(entryPath);
216
+ if (isDir ? !isCanonicalSpecDir(entry) : !isCanonicalSpecFile(entry)) {
217
+ offenders.push(relative(projectPath, entryPath));
203
218
  }
204
219
  }
205
220
  }
@@ -28,7 +28,7 @@ For non-trivial specs, Planu blocks lifecycle transitions unless evidence is pre
28
28
  | done | Traceability matrix covering every acceptance criterion |
29
29
  | done for API/UI/event/MCP work | Passing contract validation evidence |
30
30
 
31
- Evidence can live under \`planu/specs/<spec>/evidence/\` or the external Planu handoff store. Trivial specs may use lightweight evidence, but \`done\` still needs at least one traceability row.
31
+ Evidence lives in the external Planu handoff store, not under \`planu/specs/<spec>/\`. Spec folders remain \`spec.md\`-only. Trivial specs may use lightweight evidence, but \`done\` still needs at least one traceability row.
32
32
 
33
33
  ## When to use \`facilitate\`
34
34
 
@@ -80,7 +80,7 @@ function evidenceGateError(args) {
80
80
  transition: args.transition,
81
81
  issues: args.issues,
82
82
  requiredContractKinds: args.requiredContractKinds,
83
- fixHint: 'Add the missing evidence artifact under planu/specs/<spec>/evidence/ or external Planu project data handoffs/<specId>/, then retry the transition.',
83
+ fixHint: 'Add the missing evidence artifact to the external Planu project data handoff store, then retry the transition. Do not create JSON files under planu/specs/<spec>/.',
84
84
  }, null, 2),
85
85
  },
86
86
  ],
@@ -95,7 +95,7 @@ function evidenceGateError(args) {
95
95
  issues: args.issues,
96
96
  requiredContractKinds: args.requiredContractKinds,
97
97
  },
98
- fixHint: 'Add discovery.json, task-plan.json, traceability-matrix.json, and contract-validation-*.json as required for this transition.',
98
+ fixHint: 'Add discovery.json, task-plan.json, traceability-matrix.json, and contract-validation-*.json to the external Planu handoff store as required for this transition. Spec folders remain spec.md-only.',
99
99
  },
100
100
  };
101
101
  }
@@ -73,9 +73,11 @@ export async function handleValidate(args, server) {
73
73
  const projectPath = knowledge.projectPath;
74
74
  // SPEC-1017: fail closed when planu/ contains non-canonical artifacts.
75
75
  try {
76
- const { runStrictPlanuCleanup, validateStrictPlanuLayout } = await import('../engine/spec-migrator/index.js');
77
- await runStrictPlanuCleanup(projectPath);
78
- const layout = await validateStrictPlanuLayout(projectPath);
76
+ const { validateStrictPlanuLayout } = await import('../engine/spec-migrator/index.js');
77
+ const layout = await validateStrictPlanuLayout(projectPath, {
78
+ specPath: spec.specPath,
79
+ includeRoot: false,
80
+ });
79
81
  if (!layout.ok) {
80
82
  return {
81
83
  content: [
@@ -54,6 +54,7 @@ export interface PlanuCanonicalPathPolicy {
54
54
  readonly canonicalRootFiles: readonly string[];
55
55
  readonly canonicalRootDirs: readonly string[];
56
56
  readonly canonicalSpecFiles: readonly string[];
57
+ readonly canonicalSpecDirs: readonly string[];
57
58
  readonly forbiddenHostAssetRootDirs: readonly string[];
58
59
  readonly generatedRuntimePatterns: readonly string[];
59
60
  readonly legacyMergeBeforeDeleteFiles: readonly string[];
@@ -68,6 +69,10 @@ export interface StrictPlanuValidationResult {
68
69
  offenders: string[];
69
70
  contract: string;
70
71
  }
72
+ export interface StrictPlanuValidationOptions {
73
+ specPath?: string;
74
+ includeRoot?: boolean;
75
+ }
71
76
  export interface UpdateStatusBatchInput {
72
77
  specIds: string[];
73
78
  status: SpecStatus;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planu/cli",
3
- "version": "4.4.0",
3
+ "version": "4.4.2",
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.4.0",
38
- "@planu/core-darwin-x64": "4.4.0",
39
- "@planu/core-linux-arm64-gnu": "4.4.0",
40
- "@planu/core-linux-arm64-musl": "4.4.0",
41
- "@planu/core-linux-x64-gnu": "4.4.0",
42
- "@planu/core-linux-x64-musl": "4.4.0",
43
- "@planu/core-win32-arm64-msvc": "4.4.0",
44
- "@planu/core-win32-x64-msvc": "4.4.0"
37
+ "@planu/core-darwin-arm64": "4.4.2",
38
+ "@planu/core-darwin-x64": "4.4.2",
39
+ "@planu/core-linux-arm64-gnu": "4.4.2",
40
+ "@planu/core-linux-arm64-musl": "4.4.2",
41
+ "@planu/core-linux-x64-gnu": "4.4.2",
42
+ "@planu/core-linux-x64-musl": "4.4.2",
43
+ "@planu/core-win32-arm64-msvc": "4.4.2",
44
+ "@planu/core-win32-x64-msvc": "4.4.2"
45
45
  },
46
46
  "engines": {
47
47
  "node": ">=24.0.0"
@@ -185,12 +185,12 @@
185
185
  "@types/node": "^25.9.1",
186
186
  "@vitejs/plugin-vue": "^6.0.7",
187
187
  "@vitest/coverage-v8": "^4.1.8",
188
- "@vue/test-utils": "^2.4.10",
188
+ "@vue/test-utils": "^2.4.11",
189
189
  "eslint": "^10.4.1",
190
190
  "eslint-config-prettier": "^10.1.8",
191
191
  "eslint-import-resolver-typescript": "^4.4.5",
192
192
  "eslint-plugin-import": "^2.32.0",
193
- "happy-dom": "^20.9.0",
193
+ "happy-dom": "^20.10.1",
194
194
  "husky": "^9.1.7",
195
195
  "javascript-obfuscator": "^5.4.3",
196
196
  "knip": "^6.15.0",
package/planu-native.json CHANGED
@@ -1,26 +1,20 @@
1
1
  {
2
2
  "name": "dev.planu.native",
3
3
  "displayName": "Planu Native Lightweight Surface",
4
- "version": "4.4.0",
4
+ "version": "4.4.2",
5
5
  "packageName": "@planu/cli",
6
6
  "modes": {
7
7
  "lightweight": {
8
8
  "requiresMcp": false,
9
9
  "requiresDaemon": false,
10
- "hosts": [
11
- "codex",
12
- "claude-code"
13
- ],
10
+ "hosts": ["codex", "claude-code"],
14
11
  "commands": [
15
12
  {
16
13
  "id": "planu.status",
17
14
  "title": "Project status",
18
15
  "description": "Show the compact Planu project snapshot without loading the MCP tool graph.",
19
16
  "invocation": "planu status",
20
- "hosts": [
21
- "codex",
22
- "claude-code"
23
- ],
17
+ "hosts": ["codex", "claude-code"],
24
18
  "requiresMcp": false,
25
19
  "requiresDaemon": false,
26
20
  "mapsTo": "handlePlanStatus"
@@ -30,10 +24,7 @@
30
24
  "title": "Create spec",
31
25
  "description": "Create a new spec through the CLI-backed SDD contract.",
32
26
  "invocation": "planu spec create \"<title>\"",
33
- "hosts": [
34
- "codex",
35
- "claude-code"
36
- ],
27
+ "hosts": ["codex", "claude-code"],
37
28
  "requiresMcp": false,
38
29
  "requiresDaemon": false,
39
30
  "mapsTo": "handleCreateSpec"
@@ -43,10 +34,7 @@
43
34
  "title": "List specs",
44
35
  "description": "List specs in the current project with optional status/type filters.",
45
36
  "invocation": "planu spec list",
46
- "hosts": [
47
- "codex",
48
- "claude-code"
49
- ],
37
+ "hosts": ["codex", "claude-code"],
50
38
  "requiresMcp": false,
51
39
  "requiresDaemon": false,
52
40
  "mapsTo": "handleListSpecs"
@@ -56,10 +44,7 @@
56
44
  "title": "Validate spec",
57
45
  "description": "Validate a spec against the current codebase from the native CLI surface.",
58
46
  "invocation": "planu spec validate SPEC-001",
59
- "hosts": [
60
- "codex",
61
- "claude-code"
62
- ],
47
+ "hosts": ["codex", "claude-code"],
63
48
  "requiresMcp": false,
64
49
  "requiresDaemon": false,
65
50
  "mapsTo": "handleValidate"
@@ -69,10 +54,7 @@
69
54
  "title": "Audit technical debt",
70
55
  "description": "Run the read-only project audit path for lightweight debt checks.",
71
56
  "invocation": "planu audit debt",
72
- "hosts": [
73
- "codex",
74
- "claude-code"
75
- ],
57
+ "hosts": ["codex", "claude-code"],
76
58
  "requiresMcp": false,
77
59
  "requiresDaemon": false,
78
60
  "mapsTo": "handleAudit"
@@ -82,10 +64,7 @@
82
64
  "title": "Check release readiness",
83
65
  "description": "Check local branch cleanliness and main/develop/release sync readiness.",
84
66
  "invocation": "planu release check",
85
- "hosts": [
86
- "codex",
87
- "claude-code"
88
- ],
67
+ "hosts": ["codex", "claude-code"],
89
68
  "requiresMcp": false,
90
69
  "requiresDaemon": false,
91
70
  "mapsTo": "releaseCommand"
package/planu-plugin.json CHANGED
@@ -2,12 +2,9 @@
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.4.0",
5
+ "version": "4.4.2",
6
6
  "icon": "assets/plugin/icon.svg",
7
- "command": [
8
- "npx",
9
- "@planu/cli@latest"
10
- ],
7
+ "command": ["npx", "@planu/cli@latest"],
11
8
  "packageName": "@planu/cli",
12
9
  "capabilities": {
13
10
  "tools": [
@@ -26,42 +23,17 @@
26
23
  "create_skill",
27
24
  "skill_search"
28
25
  ],
29
- "resources": [
30
- "planu://specs/list",
31
- "planu://specs/{id}",
32
- "planu://project/status",
33
- "planu://roadmap"
34
- ],
35
- "prompts": [
36
- "create-spec-from-idea",
37
- "review-spec-readiness",
38
- "generate-implementation-plan"
39
- ],
40
- "subagents": [
41
- "sdd-orchestrator",
42
- "spec-challenger",
43
- "test-generator"
44
- ]
26
+ "resources": ["planu://specs/list", "planu://specs/{id}", "planu://project/status", "planu://roadmap"],
27
+ "prompts": ["create-spec-from-idea", "review-spec-readiness", "generate-implementation-plan"],
28
+ "subagents": ["sdd-orchestrator", "spec-challenger", "test-generator"]
45
29
  },
46
30
  "compatibility": {
47
31
  "minimumHostVersion": "1.0.0",
48
- "requiredFeatures": [
49
- "mcp-tools",
50
- "file-editing"
51
- ]
32
+ "requiredFeatures": ["mcp-tools", "file-editing"]
52
33
  },
53
34
  "repository": "https://github.com/planu-dev/planu",
54
35
  "author": "Planu",
55
36
  "license": "MIT",
56
37
  "homepage": "https://planu.dev",
57
- "keywords": [
58
- "sdd",
59
- "spec-driven-development",
60
- "mcp",
61
- "specs",
62
- "planning",
63
- "ai",
64
- "bdd",
65
- "tdd"
66
- ]
38
+ "keywords": ["sdd", "spec-driven-development", "mcp", "specs", "planning", "ai", "bdd", "tdd"]
67
39
  }