@planu/cli 4.10.6 → 4.10.8

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.
@@ -7,6 +7,7 @@ import { buildSpecEvidenceIndex } from '../../engine/evidence-index/index-builde
7
7
  import { checkDoneDriftContract } from '../../engine/evidence-index/done-drift.js';
8
8
  import { parseTechnicalReferenceGroundingRecords } from '../../engine/spec-grounding/contract.js';
9
9
  import { extractAcceptanceCriteriaTexts } from '../../engine/spec-format/acceptance-criteria.js';
10
+ import { formatKeyValue } from '../output-formatter.js';
10
11
  /** SPEC-1054: BDD/SDD lifecycle evidence gate. */
11
12
  export async function checkLifecycleEvidenceTransitionGate(args) {
12
13
  const [criteriaResult, artifacts] = await Promise.all([
@@ -69,15 +70,16 @@ function evidenceGateError(args) {
69
70
  content: [
70
71
  {
71
72
  type: 'text',
72
- text: JSON.stringify({
73
+ text: formatKeyValue({
73
74
  error: 'lifecycle_evidence_gate_failed',
74
75
  message: `BDD/SDD evidence gate blocked ${args.transition}.`,
75
76
  specId: args.specId,
76
77
  transition: args.transition,
77
- issues: args.issues,
78
- requiredContractKinds: args.requiredContractKinds,
78
+ issues: args.issues.length,
79
+ firstIssue: args.issues[0]?.message,
80
+ requiredContractKinds: args.requiredContractKinds.length,
79
81
  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>/.',
80
- }, null, 2),
82
+ }),
81
83
  },
82
84
  ],
83
85
  isError: true,
@@ -26,6 +26,7 @@ import { updateFrontmatterField } from '../../engine/frontmatter-parser.js';
26
26
  import { atomicWriteFile } from '../../engine/safety/atomic-write-file.js';
27
27
  import { recordForceUsage } from '../../storage/force-analytics-store.js';
28
28
  import { maybeSafePushOnDone, runCascadeForResponse } from './side-effects.js';
29
+ import { formatKeyValue } from '../output-formatter.js';
29
30
  import { checkCodeReality } from '../../engine/code-scanner/index.js';
30
31
  import { scanCrashRisks } from '../../engine/crash-shield/index.js';
31
32
  import { shouldSkipCrashScan, recordCrashScanRun, } from '../../engine/autopilot/crash-shield-rate-limiter.js';
@@ -341,11 +342,11 @@ export async function handleUpdateStatus(params, server) {
341
342
  content: [
342
343
  {
343
344
  type: 'text',
344
- text: JSON.stringify({
345
+ text: formatKeyValue({
345
346
  error: 'invalid_input',
346
347
  message: reverseValidation.error,
347
348
  fixHint: reverseValidation.fixHint,
348
- }, null, 2),
349
+ }),
349
350
  },
350
351
  ],
351
352
  isError: true,
@@ -8,6 +8,7 @@ import { checkReadinessInternal } from '../../engine/readiness-checker.js';
8
8
  import { validateEnglishOnlySpecText } from '../../engine/spec-language/english-only.js';
9
9
  import { checkGroundedSpecContract } from '../../engine/spec-grounding/contract.js';
10
10
  import { checkGenericSpecOutput } from '../../engine/spec-quality/generic-output-gate.js';
11
+ import { formatKeyValue } from '../output-formatter.js';
11
12
  /**
12
13
  * Valid state transitions for spec lifecycle.
13
14
  * draft -> review -> approved -> implementing -> done
@@ -133,16 +134,29 @@ export function checkDorGate(spec, specId, projectId, newStatus) {
133
134
  content: [
134
135
  {
135
136
  type: 'text',
136
- text: JSON.stringify({
137
- error: 'DoR gate failed',
138
- message: dorResult.humanMessage,
139
- expertSummary: dorResult.expertSummary,
140
- blockingItems: dorResult.blockingItems,
141
- warningItems: dorResult.warningItems,
142
- }, null, 2),
137
+ text: [
138
+ dorResult.humanMessage,
139
+ dorResult.expertSummary,
140
+ ...dorResult.blockingItems.slice(0, 3).map((item) => `Blocker: ${item}`),
141
+ dorResult.blockingItems.length > 3
142
+ ? `More blockers: ${String(dorResult.blockingItems.length - 3)}`
143
+ : undefined,
144
+ dorResult.warningItems.length > 0
145
+ ? `Warnings: ${String(dorResult.warningItems.length)}`
146
+ : undefined,
147
+ ]
148
+ .filter(Boolean)
149
+ .join('\n'),
143
150
  },
144
151
  ],
145
152
  isError: true,
153
+ structuredContent: {
154
+ error: 'DoR gate failed',
155
+ message: dorResult.humanMessage,
156
+ expertSummary: dorResult.expertSummary,
157
+ blockingItems: dorResult.blockingItems,
158
+ warningItems: dorResult.warningItems,
159
+ },
146
160
  };
147
161
  }
148
162
  return null;
@@ -163,16 +177,24 @@ export async function checkAmbiguityGate(spec, newStatus) {
163
177
  content: [
164
178
  {
165
179
  type: 'text',
166
- text: JSON.stringify({
180
+ text: formatKeyValue({
167
181
  error: 'Ambiguity gate failed',
168
182
  message: `Ambiguity score ${String(report.score)}/100 — below 70. Fix vague terms in criteria before approving.`,
169
183
  ambiguityScore: report.score,
170
- violations: report.violations.slice(0, 5),
171
- blockers: report.blockers.slice(0, 5),
172
- }, null, 2),
184
+ violations: report.violations.length,
185
+ blockers: report.blockers.length,
186
+ firstBlocker: report.blockers[0],
187
+ }, 'Ambiguity gate failed'),
173
188
  },
174
189
  ],
175
190
  isError: true,
191
+ structuredContent: {
192
+ error: 'Ambiguity gate failed',
193
+ message: `Ambiguity score ${String(report.score)}/100 — below 70. Fix vague terms in criteria before approving.`,
194
+ ambiguityScore: report.score,
195
+ violations: report.violations.slice(0, 5),
196
+ blockers: report.blockers.slice(0, 5),
197
+ },
176
198
  };
177
199
  }
178
200
  /**
@@ -1,3 +1,8 @@
1
1
  import type { LintCheckResult } from '../types/index.js';
2
- export declare function runLintCheck(projectPath: string, lintCommand: string | null): LintCheckResult;
2
+ interface LintCheckContext {
3
+ projectId?: string;
4
+ specId?: string;
5
+ }
6
+ export declare function runLintCheck(projectPath: string, lintCommand: string | null, _context?: LintCheckContext): LintCheckResult;
7
+ export {};
3
8
  //# sourceMappingURL=validate-lint.d.ts.map
@@ -1,7 +1,13 @@
1
+ // tools/validate-lint.ts — Lint gate for validate.
2
+ import { execFileSync } from 'node:child_process';
3
+ import { createHash } from 'node:crypto';
4
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
5
+ import { dirname, join } from 'node:path';
1
6
  import { formatLintFailureDiagnostics, resolveProjectCommandPlan, runProjectCommandPlan, } from './validate-runtime.js';
2
7
  /** Allowlist: alphanumerics, spaces, and safe shell chars. Rejects ; | $ ` & < > */
3
8
  const SAFE_COMMAND_RE = /^[\w\s./:@~=-]+$/;
4
- const LINT_CHECK_TIMEOUT_MS = 50_000;
9
+ const DEFAULT_LINT_CHECK_TIMEOUT_MS = 10_000;
10
+ const LINT_CACHE_MAX_AGE_MS = 24 * 60 * 60 * 1000;
5
11
  function isSafeCommand(cmd) {
6
12
  return SAFE_COMMAND_RE.test(cmd);
7
13
  }
@@ -33,7 +39,7 @@ function parseLintIssueCount(output) {
33
39
  }
34
40
  return output.split('\n').filter((l) => l.trim().length > 0).length;
35
41
  }
36
- export function runLintCheck(projectPath, lintCommand) {
42
+ export function runLintCheck(projectPath, lintCommand, _context = {}) {
37
43
  if (lintCommand !== null && !isSafeCommand(lintCommand)) {
38
44
  console.warn(`[Planu] validate: lintCommand contains unsafe characters — skipping execution`);
39
45
  return {
@@ -45,16 +51,23 @@ export function runLintCheck(projectPath, lintCommand) {
45
51
  }
46
52
  const command = lintCommand ?? 'pnpm lint';
47
53
  const plan = resolveProjectCommandPlan(projectPath, command);
54
+ const fingerprint = calculateLintFingerprint(projectPath);
55
+ const cached = readPassedLintCache(projectPath, command, fingerprint);
56
+ if (cached !== null) {
57
+ return cached;
58
+ }
59
+ const timeoutMs = lintCheckTimeoutMs();
48
60
  try {
49
- runProjectCommandPlan(plan, projectPath, LINT_CHECK_TIMEOUT_MS);
61
+ runProjectCommandPlan(plan, projectPath, timeoutMs);
62
+ writePassedLintCache(projectPath, command, fingerprint);
50
63
  return { passed: true, command, issueCount: 0, output: '' };
51
64
  }
52
65
  catch (err) {
53
66
  if (isCommandTimeout(err)) {
54
- const seconds = Math.round(LINT_CHECK_TIMEOUT_MS / 1000);
67
+ const seconds = Math.round(timeoutMs / 1000);
55
68
  const output = `Lint command timed out after ${String(seconds)}s and was skipped so validate can complete within MCP request limits. ` +
56
- `Run \`${plan.executedCommand}\` manually before marking the spec done.`;
57
- console.warn(`[Planu] lintCheck timed out: command="${plan.executedCommand}" cwd="${projectPath}" timeoutMs=${String(LINT_CHECK_TIMEOUT_MS)}`);
69
+ `Run \`${plan.executedCommand}\` manually before marking the spec done, or set PLANU_VALIDATE_LINT_TIMEOUT_MS for an explicit full lint pass.`;
70
+ console.warn(`[Planu] lintCheck timed out: command="${plan.executedCommand}" cwd="${projectPath}" timeoutMs=${String(timeoutMs)}`);
58
71
  return { passed: false, command, issueCount: 1, output };
59
72
  }
60
73
  const raw = commandOutput(err);
@@ -64,4 +77,124 @@ export function runLintCheck(projectPath, lintCommand) {
64
77
  return { passed: false, command, issueCount, output };
65
78
  }
66
79
  }
80
+ function lintCheckTimeoutMs() {
81
+ const raw = process.env.PLANU_VALIDATE_LINT_TIMEOUT_MS;
82
+ if (raw === undefined || raw.trim() === '') {
83
+ return DEFAULT_LINT_CHECK_TIMEOUT_MS;
84
+ }
85
+ const parsed = Number.parseInt(raw, 10);
86
+ if (!Number.isFinite(parsed)) {
87
+ return DEFAULT_LINT_CHECK_TIMEOUT_MS;
88
+ }
89
+ return Math.min(300_000, Math.max(1_000, parsed));
90
+ }
91
+ function lintCachePath(projectPath) {
92
+ return join(projectPath, 'planu', 'state', 'lint-evidence.json');
93
+ }
94
+ function readPassedLintCache(projectPath, command, fingerprint) {
95
+ try {
96
+ const path = lintCachePath(projectPath);
97
+ if (!existsSync(path)) {
98
+ return null;
99
+ }
100
+ const cache = JSON.parse(readFileSync(path, 'utf-8'));
101
+ if (cache.schemaVersion !== '1.0.0' ||
102
+ cache.command !== command ||
103
+ cache.fingerprint !== fingerprint ||
104
+ typeof cache.checkedAt !== 'string') {
105
+ return null;
106
+ }
107
+ const ageMs = Date.now() - Date.parse(cache.checkedAt);
108
+ if (!Number.isFinite(ageMs) || ageMs < 0 || ageMs > LINT_CACHE_MAX_AGE_MS) {
109
+ return null;
110
+ }
111
+ return {
112
+ passed: true,
113
+ command,
114
+ issueCount: 0,
115
+ output: `(reused local lint evidence from ${cache.checkedAt})`,
116
+ };
117
+ }
118
+ catch {
119
+ return null;
120
+ }
121
+ }
122
+ function writePassedLintCache(projectPath, command, fingerprint) {
123
+ try {
124
+ const path = lintCachePath(projectPath);
125
+ mkdirSync(dirname(path), { recursive: true });
126
+ const cache = {
127
+ schemaVersion: '1.0.0',
128
+ command,
129
+ fingerprint,
130
+ checkedAt: new Date().toISOString(),
131
+ };
132
+ writeFileSync(path, `${JSON.stringify(cache, null, 2)}\n`, 'utf-8');
133
+ }
134
+ catch {
135
+ // Cache is a performance optimization only; lint result remains authoritative.
136
+ }
137
+ }
138
+ function calculateLintFingerprint(projectPath) {
139
+ const hash = createHash('sha256');
140
+ hash.update(gitOutput(projectPath, ['rev-parse', 'HEAD']));
141
+ hash.update('\0');
142
+ hash.update(gitOutput(projectPath, [
143
+ 'diff',
144
+ '--binary',
145
+ 'HEAD',
146
+ '--',
147
+ 'src',
148
+ 'tests',
149
+ 'package.json',
150
+ 'pnpm-lock.yaml',
151
+ 'eslint.config.js',
152
+ 'eslint.config.mjs',
153
+ 'eslint.config.cjs',
154
+ 'tsconfig.json',
155
+ 'tsconfig.build.json',
156
+ ]));
157
+ hash.update('\0');
158
+ const untracked = gitOutput(projectPath, [
159
+ 'ls-files',
160
+ '--others',
161
+ '--exclude-standard',
162
+ '--',
163
+ 'src',
164
+ 'tests',
165
+ ])
166
+ .split('\n')
167
+ .map((line) => line.trim())
168
+ .filter(Boolean)
169
+ .sort();
170
+ for (const file of untracked) {
171
+ hash.update(file);
172
+ hash.update('\0');
173
+ try {
174
+ hash.update(readFileSync(join(projectPath, file)));
175
+ }
176
+ catch {
177
+ hash.update('unreadable');
178
+ }
179
+ hash.update('\0');
180
+ }
181
+ return hash.digest('hex');
182
+ }
183
+ function gitOutput(projectPath, args) {
184
+ try {
185
+ const output = execFileSync('git', args, {
186
+ cwd: projectPath,
187
+ encoding: 'utf-8',
188
+ timeout: 5_000,
189
+ stdio: ['ignore', 'pipe', 'ignore'],
190
+ });
191
+ if (Buffer.isBuffer(output)) {
192
+ return output.toString('utf-8');
193
+ }
194
+ return typeof output === 'string' ? output : String(output);
195
+ }
196
+ catch {
197
+ return '';
198
+ }
199
+ }
67
200
  //# sourceMappingURL=validate-lint.js.map
@@ -1,11 +1,33 @@
1
1
  import { specStore } from '../storage/index.js';
2
2
  import { validateTeamResults } from '../engine/team-planner/index.js';
3
3
  import { readFile } from 'node:fs/promises';
4
+ import { formatKeyValue } from './output-formatter.js';
4
5
  // ---------------------------------------------------------------------------
5
6
  // Helpers
6
7
  // ---------------------------------------------------------------------------
7
8
  function text(content) {
8
- return { content: [{ type: 'text', text: JSON.stringify(content, null, 2) }] };
9
+ const conflicts = Array.isArray(content.conflicts) ? content.conflicts.length : undefined;
10
+ const mergeOrder = Array.isArray(content.mergeOrder) ? content.mergeOrder.length : undefined;
11
+ const mergeOrderPreview = Array.isArray(content.mergeOrder)
12
+ ? content.mergeOrder.slice(0, 5).join(', ')
13
+ : undefined;
14
+ const warnings = Array.isArray(content.warnings) ? content.warnings.length : undefined;
15
+ return {
16
+ content: [
17
+ {
18
+ type: 'text',
19
+ text: formatKeyValue({
20
+ passed: content.passed,
21
+ conflicts,
22
+ mergeOrder,
23
+ mergeOrderPreview,
24
+ warnings,
25
+ summary: content.summary,
26
+ }) + '\nVerification commands: pnpm typecheck; pnpm lint; pnpm test',
27
+ },
28
+ ],
29
+ structuredContent: content,
30
+ };
9
31
  }
10
32
  function err(message) {
11
33
  return { content: [{ type: 'text', text: message }], isError: true };
@@ -72,7 +72,10 @@ export async function handleValidate(args, server) {
72
72
  const implementationQualityScore = calcQualityScore(result.qualityIssues);
73
73
  const auditedFiles = [...new Set(result.qualityIssues.map((i) => i.file))];
74
74
  const { conventionViolations, regressionDetected } = await scanProjectConventions(projectId, projectPath);
75
- const lintCheck = runLintCheck(projectPath, knowledge.lintCommand ?? null);
75
+ const lintCheck = runLintCheck(projectPath, knowledge.lintCommand ?? null, {
76
+ projectId,
77
+ specId,
78
+ });
76
79
  const assuranceGates = runAssuranceGates(projectPath);
77
80
  const minimalityReport = await buildMinimalityReport({
78
81
  projectPath,
@@ -6,6 +6,7 @@ import { PLANU_VERSION } from '../config/version.js';
6
6
  import { runWithSessionContext } from './session-context.js';
7
7
  import { checkHttpRateLimit } from './http-rate-limiter.js';
8
8
  import { loadOAuthConfig, extractBearerToken, validateToken } from './oauth-validator.js';
9
+ import { compactToolsListMessage, isToolsListResponse, } from '../engine/compact/tool-list-compactor.js';
9
10
  const MAX_BODY_BYTES = 5 * 1024 * 1024; // 5MB
10
11
  const SHUTDOWN_GRACE_MS = 5_000;
11
12
  const sessions = new Map();
@@ -95,6 +96,7 @@ async function handlePost(req, res, sessionId, serverFactory) {
95
96
  const transport = new StreamableHTTPServerTransport({
96
97
  sessionIdGenerator: () => randomUUID(),
97
98
  });
99
+ installToolsListCompaction(transport);
98
100
  transport.onclose = () => {
99
101
  const sid = transport.sessionId;
100
102
  if (sid) {
@@ -177,6 +179,17 @@ async function handleMcpRequest(req, res, serverFactory, corsOrigin) {
177
179
  res.writeHead(405, { 'Content-Type': 'application/json' });
178
180
  res.end(JSON.stringify({ error: 'Method not allowed' }));
179
181
  }
182
+ function installToolsListCompaction(transport) {
183
+ const originalSend = transport.send.bind(transport);
184
+ const compactingSend = (message) => {
185
+ const outgoing = isToolsListResponse(message)
186
+ ? compactToolsListMessage(message)
187
+ : message;
188
+ return originalSend(outgoing);
189
+ };
190
+ const wrappedTransport = transport;
191
+ wrappedTransport.send = compactingSend;
192
+ }
180
193
  export async function createHttpTransport(serverFactory, config) {
181
194
  const httpServer = createServer((req, res) => {
182
195
  void (async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planu/cli",
3
- "version": "4.10.6",
3
+ "version": "4.10.8",
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.10.5",
38
- "@planu/core-darwin-x64": "4.10.5",
39
- "@planu/core-linux-arm64-gnu": "4.10.5",
40
- "@planu/core-linux-arm64-musl": "4.10.5",
41
- "@planu/core-linux-x64-gnu": "4.10.5",
42
- "@planu/core-linux-x64-musl": "4.10.5",
43
- "@planu/core-win32-arm64-msvc": "4.10.5",
44
- "@planu/core-win32-x64-msvc": "4.10.5"
37
+ "@planu/core-darwin-arm64": "4.10.8",
38
+ "@planu/core-darwin-x64": "4.10.8",
39
+ "@planu/core-linux-arm64-gnu": "4.10.8",
40
+ "@planu/core-linux-arm64-musl": "4.10.8",
41
+ "@planu/core-linux-x64-gnu": "4.10.8",
42
+ "@planu/core-linux-x64-musl": "4.10.8",
43
+ "@planu/core-win32-arm64-msvc": "4.10.8",
44
+ "@planu/core-win32-x64-msvc": "4.10.8"
45
45
  },
46
46
  "engines": {
47
47
  "node": ">=24.0.0"
@@ -164,7 +164,7 @@
164
164
  "tsc-alias": "^1.9.0",
165
165
  "type-coverage": "^2.29.7",
166
166
  "typescript": "^6.0.3",
167
- "typescript-eslint": "^8.62.1",
167
+ "typescript-eslint": "^8.63.0",
168
168
  "vite": "^8.1.3",
169
169
  "vitest": "^4.1.10",
170
170
  "vue": "^3.5.39"
package/planu-native.json CHANGED
@@ -1,20 +1,26 @@
1
1
  {
2
2
  "name": "dev.planu.native",
3
3
  "displayName": "Planu Native Lightweight Surface",
4
- "version": "4.10.6",
4
+ "version": "4.10.8",
5
5
  "packageName": "@planu/cli",
6
6
  "modes": {
7
7
  "lightweight": {
8
8
  "requiresMcp": false,
9
9
  "requiresDaemon": false,
10
- "hosts": ["codex", "claude-code"],
10
+ "hosts": [
11
+ "codex",
12
+ "claude-code"
13
+ ],
11
14
  "commands": [
12
15
  {
13
16
  "id": "planu.status",
14
17
  "title": "Project status",
15
18
  "description": "Show the compact Planu project snapshot without loading the MCP tool graph.",
16
19
  "invocation": "planu status",
17
- "hosts": ["codex", "claude-code"],
20
+ "hosts": [
21
+ "codex",
22
+ "claude-code"
23
+ ],
18
24
  "requiresMcp": false,
19
25
  "requiresDaemon": false,
20
26
  "mapsTo": "handlePlanStatus"
@@ -24,7 +30,10 @@
24
30
  "title": "Create spec",
25
31
  "description": "Create a new spec through the CLI-backed SDD contract.",
26
32
  "invocation": "planu spec create \"<title>\"",
27
- "hosts": ["codex", "claude-code"],
33
+ "hosts": [
34
+ "codex",
35
+ "claude-code"
36
+ ],
28
37
  "requiresMcp": false,
29
38
  "requiresDaemon": false,
30
39
  "mapsTo": "handleCreateSpec"
@@ -34,7 +43,10 @@
34
43
  "title": "List specs",
35
44
  "description": "List specs in the current project with optional status/type filters.",
36
45
  "invocation": "planu spec list",
37
- "hosts": ["codex", "claude-code"],
46
+ "hosts": [
47
+ "codex",
48
+ "claude-code"
49
+ ],
38
50
  "requiresMcp": false,
39
51
  "requiresDaemon": false,
40
52
  "mapsTo": "handleListSpecs"
@@ -44,7 +56,10 @@
44
56
  "title": "Validate spec",
45
57
  "description": "Validate a spec against the current codebase from the native CLI surface.",
46
58
  "invocation": "planu spec validate SPEC-001",
47
- "hosts": ["codex", "claude-code"],
59
+ "hosts": [
60
+ "codex",
61
+ "claude-code"
62
+ ],
48
63
  "requiresMcp": false,
49
64
  "requiresDaemon": false,
50
65
  "mapsTo": "handleValidate"
@@ -54,7 +69,10 @@
54
69
  "title": "Audit technical debt",
55
70
  "description": "Run the read-only project audit path for lightweight debt checks.",
56
71
  "invocation": "planu audit debt",
57
- "hosts": ["codex", "claude-code"],
72
+ "hosts": [
73
+ "codex",
74
+ "claude-code"
75
+ ],
58
76
  "requiresMcp": false,
59
77
  "requiresDaemon": false,
60
78
  "mapsTo": "handleAudit"
@@ -64,7 +82,10 @@
64
82
  "title": "Check release readiness",
65
83
  "description": "Check local-first release readiness, branch cleanliness, and optional gitflow drift.",
66
84
  "invocation": "planu release check",
67
- "hosts": ["codex", "claude-code"],
85
+ "hosts": [
86
+ "codex",
87
+ "claude-code"
88
+ ],
68
89
  "requiresMcp": false,
69
90
  "requiresDaemon": false,
70
91
  "mapsTo": "releaseCommand"
package/planu-plugin.json CHANGED
@@ -2,9 +2,12 @@
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.10.6",
5
+ "version": "4.10.8",
6
6
  "icon": "assets/plugin/icon.svg",
7
- "command": ["npx", "@planu/cli@latest"],
7
+ "command": [
8
+ "npx",
9
+ "@planu/cli@latest"
10
+ ],
8
11
  "packageName": "@planu/cli",
9
12
  "capabilities": {
10
13
  "tools": [
@@ -23,17 +26,42 @@
23
26
  "create_skill",
24
27
  "skill_search"
25
28
  ],
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"]
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
+ ]
29
45
  },
30
46
  "compatibility": {
31
47
  "minimumHostVersion": "1.0.0",
32
- "requiredFeatures": ["mcp-tools", "file-editing"]
48
+ "requiredFeatures": [
49
+ "mcp-tools",
50
+ "file-editing"
51
+ ]
33
52
  },
34
53
  "repository": "https://github.com/planu-dev/planu",
35
54
  "author": "Planu",
36
55
  "license": "MIT",
37
56
  "homepage": "https://planu.dev",
38
- "keywords": ["sdd", "spec-driven-development", "mcp", "specs", "planning", "ai", "bdd", "tdd"]
57
+ "keywords": [
58
+ "sdd",
59
+ "spec-driven-development",
60
+ "mcp",
61
+ "specs",
62
+ "planning",
63
+ "ai",
64
+ "bdd",
65
+ "tdd"
66
+ ]
39
67
  }