@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.
- package/CHANGELOG.md +21 -1
- package/dist/engine/local-first/tool-classification.d.ts +1 -0
- package/dist/engine/local-first/tool-classification.js +294 -100
- package/dist/tools/audit.js +1 -1
- package/dist/tools/challenge-spec.js +52 -2
- package/dist/tools/check-readiness.js +15 -8
- package/dist/tools/data-governance/audit-handler.js +21 -6
- package/dist/tools/data-governance/detect-handler.js +32 -15
- package/dist/tools/define-ui-contract.js +1 -4
- package/dist/tools/design-schema.js +1 -4
- package/dist/tools/detect-drift.js +48 -28
- package/dist/tools/flag-spec-gap.js +47 -12
- package/dist/tools/generate-sub-agent.js +2 -4
- package/dist/tools/orchestrate-locking.js +15 -4
- package/dist/tools/reverse-engineer/handler.js +9 -10
- package/dist/tools/rollback-release.js +20 -2
- package/dist/tools/spec-diff-handler.js +3 -3
- package/dist/tools/suggest-mcp-server.js +2 -4
- package/dist/tools/update-status/dod-gates.js +39 -24
- package/dist/tools/update-status/evidence-gate.js +6 -4
- package/dist/tools/update-status/index.js +3 -2
- package/dist/tools/update-status/transition-guard.js +33 -11
- package/dist/tools/validate-lint.d.ts +6 -1
- package/dist/tools/validate-lint.js +139 -6
- package/dist/tools/validate-team-results.js +23 -1
- package/dist/tools/validate.js +4 -1
- package/dist/transports/http-transport.js +13 -0
- package/package.json +10 -10
- package/planu-native.json +29 -8
- package/planu-plugin.json +35 -7
|
@@ -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:
|
|
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
|
-
|
|
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
|
-
}
|
|
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:
|
|
345
|
+
text: formatKeyValue({
|
|
345
346
|
error: 'invalid_input',
|
|
346
347
|
message: reverseValidation.error,
|
|
347
348
|
fixHint: reverseValidation.fixHint,
|
|
348
|
-
}
|
|
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:
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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:
|
|
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.
|
|
171
|
-
blockers: report.blockers.
|
|
172
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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 };
|
package/dist/tools/validate.js
CHANGED
|
@@ -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.
|
|
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.
|
|
38
|
-
"@planu/core-darwin-x64": "4.10.
|
|
39
|
-
"@planu/core-linux-arm64-gnu": "4.10.
|
|
40
|
-
"@planu/core-linux-arm64-musl": "4.10.
|
|
41
|
-
"@planu/core-linux-x64-gnu": "4.10.
|
|
42
|
-
"@planu/core-linux-x64-musl": "4.10.
|
|
43
|
-
"@planu/core-win32-arm64-msvc": "4.10.
|
|
44
|
-
"@planu/core-win32-x64-msvc": "4.10.
|
|
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.
|
|
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.
|
|
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": [
|
|
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": [
|
|
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": [
|
|
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": [
|
|
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": [
|
|
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": [
|
|
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": [
|
|
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.
|
|
5
|
+
"version": "4.10.8",
|
|
6
6
|
"icon": "assets/plugin/icon.svg",
|
|
7
|
-
"command": [
|
|
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": [
|
|
27
|
-
|
|
28
|
-
|
|
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": [
|
|
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": [
|
|
57
|
+
"keywords": [
|
|
58
|
+
"sdd",
|
|
59
|
+
"spec-driven-development",
|
|
60
|
+
"mcp",
|
|
61
|
+
"specs",
|
|
62
|
+
"planning",
|
|
63
|
+
"ai",
|
|
64
|
+
"bdd",
|
|
65
|
+
"tdd"
|
|
66
|
+
]
|
|
39
67
|
}
|