@planu/cli 4.10.4 ā 4.10.6
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 +17 -1
- package/dist/config/version.js +1 -1
- package/dist/config/version.ts +1 -1
- package/dist/engine/compact/tool-list-compactor.d.ts +1 -1
- package/dist/engine/evidence-gates/lifecycle-gate.js +17 -1
- package/dist/engine/evidence-index/done-drift.js +4 -1
- package/dist/engine/local-first/tool-classification.d.ts +6 -0
- package/dist/engine/local-first/tool-classification.js +119 -0
- package/dist/engine/project-graph/extractors/decision-store-extractor.js +1 -1
- package/dist/engine/project-graph/extractors/git-extractor.js +1 -1
- package/dist/engine/project-graph/extractors/handoff-extractor.js +1 -1
- package/dist/engine/project-graph/extractors/spec-extractor.js +1 -1
- package/dist/engine/project-graph/extractors/validation-extractor.js +1 -1
- package/dist/engine/project-graph/index.d.ts +2 -1
- package/dist/engine/project-graph/index.js +2 -1
- package/dist/engine/project-graph/query.d.ts +1 -2
- package/dist/engine/project-graph/query.js +1 -10
- package/dist/engine/project-graph/redaction.d.ts +4 -0
- package/dist/engine/project-graph/redaction.js +11 -0
- package/dist/engine/qa-gate.js +13 -1
- package/dist/engine/scope-boundaries/scope-validator.js +14 -2
- package/dist/engine/validator/validation-report-writer.js +39 -8
- package/dist/tools/design-schema-sql/migrations.js +8 -3
- package/dist/tools/design-schema-sql/tables.js +19 -3
- package/dist/tools/list-specs.js +15 -59
- package/dist/tools/output-compressor.d.ts +14 -1
- package/dist/tools/output-compressor.js +121 -2
- package/dist/tools/package-handoff.js +136 -12
- package/dist/tools/status-handler.js +6 -1
- package/dist/tools/token-recording.d.ts +2 -1
- package/dist/tools/token-recording.js +21 -2
- package/dist/tools/update-status/dod-gates.d.ts +2 -2
- package/dist/tools/update-status/dod-gates.js +8 -28
- package/dist/tools/update-status/index.js +7 -1
- package/dist/tools/update-status/qa-gate.d.ts +5 -0
- package/dist/tools/update-status/qa-gate.js +50 -0
- package/dist/tools/update-status/response-builder.js +96 -2
- package/dist/tools/update-status-convention-gate.js +22 -6
- package/dist/tools/validate-lint.js +1 -1
- package/dist/tools/validate.js +147 -19
- package/dist/transports/transport-factory.js +13 -0
- package/dist/types/qa-gate.d.ts +3 -0
- package/dist/types/tool-classification.d.ts +8 -0
- package/dist/types/tool-classification.js +2 -0
- package/package.json +11 -18
- package/planu-native.json +8 -29
- package/planu-plugin.json +7 -35
- package/dist/engine/context-artifacts/index.d.ts +0 -2
- package/dist/engine/context-artifacts/index.js +0 -2
|
@@ -116,7 +116,10 @@ export function buildStatusResponse(result, specId, currentStatus, newStatus, re
|
|
|
116
116
|
lines.push(`\nš¤ Tokens: ${tokenStr}${costStr}${r.actuals.estimated ? ' *(auto-estimated)*' : ''}`);
|
|
117
117
|
}
|
|
118
118
|
if (r.validateScore !== null && r.validateScore !== undefined) {
|
|
119
|
-
|
|
119
|
+
const sourceSuffix = r.validateScoreSource === 'forced-best-effort-validateSpec'
|
|
120
|
+
? ' (forced best-effort observation from validateSpec)'
|
|
121
|
+
: '';
|
|
122
|
+
lines.push(`\nā
Validate score: ${String(r.validateScore)}/100${sourceSuffix}`);
|
|
120
123
|
}
|
|
121
124
|
if (r.cascade.affectedSpecs.length > 0) {
|
|
122
125
|
lines.push(`\nš Downstream specs affected: ${r.cascade.affectedSpecs.map((s) => `SPEC-${s}`).join(', ')}`);
|
|
@@ -201,12 +204,103 @@ export function buildStatusResponse(result, specId, currentStatus, newStatus, re
|
|
|
201
204
|
: withNextSteps;
|
|
202
205
|
const displayTitle = specTitle ?? `SPEC-${specId}`;
|
|
203
206
|
const humanSummary = buildUpdateStatusSummary(displayTitle, newStatus);
|
|
207
|
+
const warningItems = [
|
|
208
|
+
r.protectedBranchWarning,
|
|
209
|
+
r.mergeWarning,
|
|
210
|
+
r.validationWarning,
|
|
211
|
+
r.codeRealityWarning,
|
|
212
|
+
r.crashShieldWarning,
|
|
213
|
+
r.forcedBypassWarning,
|
|
214
|
+
...(r.constitutionWarnings ?? []).map((w) => w.description),
|
|
215
|
+
...(r.conventionWarnings ?? []),
|
|
216
|
+
...(r.lintWarnings ?? []),
|
|
217
|
+
...(r.testWarnings ?? []),
|
|
218
|
+
...(r.compileWarnings ?? []),
|
|
219
|
+
...(r.frontmatterSyncWarnings ?? []).map((w) => `frontmatter sync failed for ${w.specId}: ${w.reason}`),
|
|
220
|
+
].filter((item) => typeof item === 'string' && item.length > 0);
|
|
221
|
+
const validationIssues = [
|
|
222
|
+
...(r.validationWarning ? [r.validationWarning] : []),
|
|
223
|
+
...(r.complianceGateResult?.issues ?? []),
|
|
224
|
+
...(r.lintWarnings ?? []),
|
|
225
|
+
...(r.testWarnings ?? []),
|
|
226
|
+
...(r.compileWarnings ?? []),
|
|
227
|
+
];
|
|
228
|
+
const structuredContent = {
|
|
229
|
+
specId,
|
|
230
|
+
previousStatus: currentStatus,
|
|
231
|
+
newStatus,
|
|
232
|
+
autoBranch: r.autoBranch ?? undefined,
|
|
233
|
+
protectedBranchWarning: r.protectedBranchWarning ?? undefined,
|
|
234
|
+
mergeWarning: r.mergeWarning ?? undefined,
|
|
235
|
+
validationWarning: r.validationWarning ?? undefined,
|
|
236
|
+
prSuggestion: r.prSuggestion ?? null,
|
|
237
|
+
metrics: r.metrics ?? undefined,
|
|
238
|
+
actuals: r.actuals ?? undefined,
|
|
239
|
+
cascade: {
|
|
240
|
+
...(result.cascade ?? {}),
|
|
241
|
+
affectedSpecs: r.cascade.affectedSpecs,
|
|
242
|
+
},
|
|
243
|
+
constitutionWarnings: r.constitutionWarnings?.slice(0, 5),
|
|
244
|
+
conventionWarnings: r.conventionWarnings?.slice(0, 5),
|
|
245
|
+
lintWarnings: r.lintWarnings?.slice(0, 5),
|
|
246
|
+
testWarnings: r.testWarnings?.slice(0, 5),
|
|
247
|
+
compileWarnings: r.compileWarnings?.slice(0, 5),
|
|
248
|
+
validateScore: r.validateScore ?? undefined,
|
|
249
|
+
validateScoreSource: r.validateScoreSource ?? undefined,
|
|
250
|
+
complianceGateResult: r.complianceGateResult
|
|
251
|
+
? {
|
|
252
|
+
skipped: r.complianceGateResult.skipped,
|
|
253
|
+
score: r.complianceGateResult.score,
|
|
254
|
+
blocked: r.complianceGateResult.blocked,
|
|
255
|
+
issues: r.complianceGateResult.issues.slice(0, 5),
|
|
256
|
+
}
|
|
257
|
+
: undefined,
|
|
258
|
+
forceApproveAuditId: typeof result.forceApproveAuditId === 'string' ? result.forceApproveAuditId : undefined,
|
|
259
|
+
forceStatusAuditId: typeof result.forceStatusAuditId === 'string' ? result.forceStatusAuditId : undefined,
|
|
260
|
+
qualityWarnings: Array.isArray(result.qualityWarnings)
|
|
261
|
+
? result.qualityWarnings.slice(0, 5)
|
|
262
|
+
: undefined,
|
|
263
|
+
interactiveQuestions: Array.isArray(result.interactiveQuestions)
|
|
264
|
+
? result.interactiveQuestions
|
|
265
|
+
: undefined,
|
|
266
|
+
transitionSummary: {
|
|
267
|
+
title: displayTitle,
|
|
268
|
+
from: currentStatus,
|
|
269
|
+
to: newStatus,
|
|
270
|
+
autoBranch: r.autoBranch ?? undefined,
|
|
271
|
+
versionSnapshotTag: r.versionSnapshotTag ?? undefined,
|
|
272
|
+
},
|
|
273
|
+
validationSummary: {
|
|
274
|
+
validateScore: r.validateScore ?? null,
|
|
275
|
+
validateScoreSource: r.validateScoreSource ?? null,
|
|
276
|
+
complianceScore: r.complianceGateResult?.score ?? null,
|
|
277
|
+
complianceBlocked: r.complianceGateResult?.blocked ?? false,
|
|
278
|
+
validationIssuesCount: validationIssues.length,
|
|
279
|
+
validationIssuesTop5: validationIssues.slice(0, 5),
|
|
280
|
+
},
|
|
281
|
+
blockersCount: validationIssues.length,
|
|
282
|
+
blockersTop3: validationIssues.slice(0, 3),
|
|
283
|
+
warningsCount: warningItems.length,
|
|
284
|
+
warningsTop5: warningItems.slice(0, 5),
|
|
285
|
+
affectedSpecsCount: r.cascade.affectedSpecs.length,
|
|
286
|
+
affectedSpecsTop5: r.cascade.affectedSpecs.slice(0, 5),
|
|
287
|
+
nextAction,
|
|
288
|
+
lifecycleMap,
|
|
289
|
+
humanSummary,
|
|
290
|
+
orchestrationPlan: r.orchestrationPlan
|
|
291
|
+
? {
|
|
292
|
+
waves: r.orchestrationPlan.executionOrder.length,
|
|
293
|
+
parallelSafetyVerdict: r.orchestrationPlan.parallelSafetyVerdict,
|
|
294
|
+
executionOrder: r.orchestrationPlan.executionOrder,
|
|
295
|
+
}
|
|
296
|
+
: undefined,
|
|
297
|
+
};
|
|
204
298
|
return {
|
|
205
299
|
content: [
|
|
206
300
|
{ type: 'text', text: markdownText },
|
|
207
301
|
{ type: 'text', text: humanSummary },
|
|
208
302
|
],
|
|
209
|
-
structuredContent
|
|
303
|
+
structuredContent,
|
|
210
304
|
};
|
|
211
305
|
}
|
|
212
306
|
/** Render a compact lifecycle map line showing current, next, and remaining steps */
|
|
@@ -22,7 +22,7 @@ async function execGate(cmd, cwd, timeoutMs) {
|
|
|
22
22
|
timeout: timeoutMs,
|
|
23
23
|
maxBuffer: 4 * 1024 * 1024,
|
|
24
24
|
});
|
|
25
|
-
return { output: stdout.trim(), ok: true };
|
|
25
|
+
return { output: stdout.trim(), ok: true, timedOut: false };
|
|
26
26
|
}
|
|
27
27
|
catch (err) {
|
|
28
28
|
const e = err;
|
|
@@ -31,7 +31,8 @@ async function execGate(cmd, cwd, timeoutMs) {
|
|
|
31
31
|
: typeof e.stdout === 'string'
|
|
32
32
|
? e.stdout
|
|
33
33
|
: '';
|
|
34
|
-
|
|
34
|
+
const timedOut = e.killed === true || e.signal === 'SIGTERM' || e.code === 'ETIMEDOUT';
|
|
35
|
+
return { output: raw.trim(), ok: false, timedOut, errorMessage: e.message };
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
function countLintIssues(output) {
|
|
@@ -87,23 +88,38 @@ async function checkTestGate(projectPath, testCmd) {
|
|
|
87
88
|
}
|
|
88
89
|
// Run tests with a reasonable timeout ā only a warning if they fail
|
|
89
90
|
const cmd = testCmd ?? 'npx vitest run --reporter=verbose';
|
|
90
|
-
const
|
|
91
|
+
const timeoutMs = 120_000;
|
|
92
|
+
const { output, ok, timedOut, errorMessage } = await execGate(cmd, projectPath, timeoutMs);
|
|
91
93
|
if (ok) {
|
|
92
94
|
return [];
|
|
93
95
|
}
|
|
96
|
+
if (timedOut) {
|
|
97
|
+
return [`Tests: timed out after ${String(timeoutMs)}ms. Command: ${cmd}`];
|
|
98
|
+
}
|
|
94
99
|
// Extract summary line (e.g. "5 failed" or "FAIL src/foo.test.ts")
|
|
95
100
|
const failLine = output
|
|
96
101
|
.split('\n')
|
|
97
102
|
.reverse()
|
|
98
|
-
.find((l) =>
|
|
103
|
+
.find((l) => isFailureSummaryLine(l));
|
|
99
104
|
const summary = failLine ??
|
|
100
105
|
output
|
|
101
106
|
.split('\n')
|
|
102
|
-
.filter((l) => l.trim())
|
|
107
|
+
.filter((l) => l.trim() && !isPassingSummaryLine(l))
|
|
103
108
|
.slice(-1)[0] ??
|
|
104
|
-
|
|
109
|
+
errorMessage ??
|
|
110
|
+
'No failure summary available.';
|
|
105
111
|
return [`Tests: failed. ${summary.trim()}`];
|
|
106
112
|
}
|
|
113
|
+
function isPassingSummaryLine(line) {
|
|
114
|
+
return /^\s*(?:ā|PASS\b|Test Files\s+\d+\s+passed|Tests\s+\d+\s+passed)/i.test(line.trim());
|
|
115
|
+
}
|
|
116
|
+
function isFailureSummaryLine(line) {
|
|
117
|
+
const trimmed = line.trim();
|
|
118
|
+
if (trimmed.length === 0 || isPassingSummaryLine(trimmed)) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
return /\b(?:FAIL|failed|failing|error|errored)\b/i.test(trimmed);
|
|
122
|
+
}
|
|
107
123
|
// ---------------------------------------------------------------------------
|
|
108
124
|
// Convention gate (existing logic, unchanged)
|
|
109
125
|
// ---------------------------------------------------------------------------
|
|
@@ -55,7 +55,7 @@ export function runLintCheck(projectPath, lintCommand) {
|
|
|
55
55
|
const output = `Lint command timed out after ${String(seconds)}s and was skipped so validate can complete within MCP request limits. ` +
|
|
56
56
|
`Run \`${plan.executedCommand}\` manually before marking the spec done.`;
|
|
57
57
|
console.warn(`[Planu] lintCheck timed out: command="${plan.executedCommand}" cwd="${projectPath}" timeoutMs=${String(LINT_CHECK_TIMEOUT_MS)}`);
|
|
58
|
-
return { passed:
|
|
58
|
+
return { passed: false, command, issueCount: 1, output };
|
|
59
59
|
}
|
|
60
60
|
const raw = commandOutput(err);
|
|
61
61
|
const output = formatLintFailureDiagnostics(plan, raw);
|
package/dist/tools/validate.js
CHANGED
|
@@ -22,13 +22,13 @@ import { checkPlanuUncommittedChanges, runAssuranceGates } from './validate-assu
|
|
|
22
22
|
// Re-export for external use (SPEC-018)
|
|
23
23
|
export { validateContractCompliance };
|
|
24
24
|
// SPEC-595: server? param enables elicitation on failures
|
|
25
|
+
// eslint-disable-next-line max-lines-per-function -- validate orchestrates many existing gates in one handler.
|
|
25
26
|
export async function handleValidate(args, server) {
|
|
26
27
|
const { specId } = args;
|
|
27
28
|
const projectId = resolveProjectId(args);
|
|
28
29
|
if (!projectId) {
|
|
29
30
|
return missingProjectIdError;
|
|
30
31
|
}
|
|
31
|
-
// 1. Load spec
|
|
32
32
|
const spec = await specStore.getSpec(projectId, specId);
|
|
33
33
|
if (!spec) {
|
|
34
34
|
return {
|
|
@@ -42,7 +42,6 @@ export async function handleValidate(args, server) {
|
|
|
42
42
|
},
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
|
-
// 2. Load project knowledge for project path
|
|
46
45
|
const knowledge = await knowledgeStore.getKnowledge(projectId);
|
|
47
46
|
if (!knowledge) {
|
|
48
47
|
return {
|
|
@@ -66,18 +65,13 @@ export async function handleValidate(args, server) {
|
|
|
66
65
|
if (strictLayoutError !== null) {
|
|
67
66
|
return strictLayoutError;
|
|
68
67
|
}
|
|
69
|
-
// 3. Run validation engine
|
|
70
68
|
const result = await validateSpec(spec, projectPath);
|
|
71
69
|
const graphCoverage = await buildGraphCoverageReport({ projectId, projectPath, specId });
|
|
72
|
-
// 4. Generate DoR and DoD checklists
|
|
73
70
|
const dor = generateDoR(spec);
|
|
74
71
|
const dod = await generateDoD(spec, result, undefined, projectPath);
|
|
75
|
-
// 5. Compute implementation quality score from validated files (SPEC-010 S47)
|
|
76
72
|
const implementationQualityScore = calcQualityScore(result.qualityIssues);
|
|
77
73
|
const auditedFiles = [...new Set(result.qualityIssues.map((i) => i.file))];
|
|
78
|
-
// 6. SPEC-190: Convention compliance check
|
|
79
74
|
const { conventionViolations, regressionDetected } = await scanProjectConventions(projectId, projectPath);
|
|
80
|
-
// 7. Lint check (best-effort ā non-blocking)
|
|
81
75
|
const lintCheck = runLintCheck(projectPath, knowledge.lintCommand ?? null);
|
|
82
76
|
const assuranceGates = runAssuranceGates(projectPath);
|
|
83
77
|
const minimalityReport = await buildMinimalityReport({
|
|
@@ -108,7 +102,6 @@ export async function handleValidate(args, server) {
|
|
|
108
102
|
minimalityBlocked: minimalityReport?.blocked === true,
|
|
109
103
|
score: result.score,
|
|
110
104
|
});
|
|
111
|
-
// 8. Build output
|
|
112
105
|
const output = {
|
|
113
106
|
specId,
|
|
114
107
|
title: spec.title,
|
|
@@ -239,13 +232,6 @@ export async function handleValidate(args, server) {
|
|
|
239
232
|
if (minimalityReport?.blocked) {
|
|
240
233
|
suggestions.push(`${minimalityReport.findings.filter((finding) => finding.blocksDone).length} blocking minimality finding(s) detected. Remove avoidable complexity or record accepted debt before marking done.`);
|
|
241
234
|
}
|
|
242
|
-
const outputWithSuggestions = compactObj({
|
|
243
|
-
...output,
|
|
244
|
-
suggestions: suggestions.length > 0 ? suggestions : undefined,
|
|
245
|
-
});
|
|
246
|
-
// `lintCheck.output` is part of the MCP output contract. Keep it even when
|
|
247
|
-
// empty because compactObj removes empty strings recursively.
|
|
248
|
-
outputWithSuggestions.lintCheck = lintCheck;
|
|
249
235
|
// SPEC-012: Dispatch feedback event for auto-improvement loop (best-effort)
|
|
250
236
|
void dispatchFeedbackEvent(projectId, {
|
|
251
237
|
type: 'validate',
|
|
@@ -296,6 +282,7 @@ export async function handleValidate(args, server) {
|
|
|
296
282
|
passedCount,
|
|
297
283
|
failedCount,
|
|
298
284
|
totalCriteria,
|
|
285
|
+
qualityFailures: qualityGateSummary.failures,
|
|
299
286
|
missing: result.missing,
|
|
300
287
|
indeterminate,
|
|
301
288
|
commitReminder,
|
|
@@ -304,7 +291,26 @@ export async function handleValidate(args, server) {
|
|
|
304
291
|
const graphText = formatGraphCoverageText(graphCoverage);
|
|
305
292
|
// SPEC-512: Compact structuredContent ā essential fields only at top level
|
|
306
293
|
const compactSummary = buildCompactSummary(specId, result.score === null ? null : effectiveScore, passedCount, failedCount + (qualityGateSummary.passed ? 0 : 1), totalCriteria);
|
|
307
|
-
const structuredBase = {
|
|
294
|
+
const structuredBase = buildValidateStructuredContent({
|
|
295
|
+
specId,
|
|
296
|
+
title: spec.title,
|
|
297
|
+
score: result.score === null ? null : effectiveScore,
|
|
298
|
+
compactSummary,
|
|
299
|
+
result,
|
|
300
|
+
indeterminate,
|
|
301
|
+
dor,
|
|
302
|
+
dod,
|
|
303
|
+
qualityIssues: output.qualityIssues,
|
|
304
|
+
implementationQualityScore: output.implementationQualityScore,
|
|
305
|
+
conventionCompliance: output.conventionCompliance,
|
|
306
|
+
lintCheck,
|
|
307
|
+
assuranceGates,
|
|
308
|
+
qualityGateSummary,
|
|
309
|
+
validationReport,
|
|
310
|
+
minimalityReport,
|
|
311
|
+
graphCoverage,
|
|
312
|
+
suggestions,
|
|
313
|
+
});
|
|
308
314
|
// SPEC-595: Elicit next action when validation fails and a server is available
|
|
309
315
|
if (server !== undefined && failedCount > 0) {
|
|
310
316
|
const { field, property } = buildEnumSchema('action', ['re-implement', 'ignore', 'mark-manual'], ['Fix failing criteria (Recommended)', 'Ignore failures', 'Mark as manually reviewed'], 'Next action', 're-implement');
|
|
@@ -399,14 +405,135 @@ function buildCompactSummary(specId, score, passedCount, failedCount, totalCrite
|
|
|
399
405
|
status: failedCount === 0 ? 'passing' : 'failing',
|
|
400
406
|
};
|
|
401
407
|
}
|
|
408
|
+
function buildValidateStructuredContent(args) {
|
|
409
|
+
const failedDorItems = args.dor.items.filter((item) => item.status === 'failed').slice(0, 5);
|
|
410
|
+
const failedDodItems = args.dod.items
|
|
411
|
+
.filter((item) => item.required === true && item.status !== 'passed')
|
|
412
|
+
.slice(0, 5);
|
|
413
|
+
const failedGates = args.validationReport.gates.filter((gate) => !gate.passed);
|
|
414
|
+
const minimalityBlockingFindings = args.minimalityReport?.findings.filter((finding) => finding.blocksDone) ?? [];
|
|
415
|
+
const blockers = [
|
|
416
|
+
...args.result.missing.map((missing) => `missing: ${missing}`),
|
|
417
|
+
...args.indeterminate.map((item) => `indeterminate: ${item}`),
|
|
418
|
+
...args.qualityGateSummary.failures.map((failure) => `gate: ${failure}`),
|
|
419
|
+
...args.qualityIssues.map((issue) => `${issue.rule}: ${issue.message}`),
|
|
420
|
+
];
|
|
421
|
+
const risks = [
|
|
422
|
+
...args.conventionCompliance.violations.map((violation) => violation.message),
|
|
423
|
+
...minimalityBlockingFindings.map((finding) => `${finding.ruleId}: ${finding.target}`),
|
|
424
|
+
...args.suggestions,
|
|
425
|
+
];
|
|
426
|
+
const structured = compactObj({
|
|
427
|
+
specId: args.specId,
|
|
428
|
+
title: args.title,
|
|
429
|
+
ready: args.compactSummary.status === 'passing' && args.qualityGateSummary.passed,
|
|
430
|
+
status: args.compactSummary.status,
|
|
431
|
+
score: args.score,
|
|
432
|
+
summary: args.compactSummary,
|
|
433
|
+
counts: {
|
|
434
|
+
matches: args.result.matches.length,
|
|
435
|
+
missing: args.result.missing.length,
|
|
436
|
+
extra: args.result.extra.length,
|
|
437
|
+
indeterminate: args.indeterminate.length,
|
|
438
|
+
qualityIssues: args.qualityIssues.length,
|
|
439
|
+
conventionViolations: args.conventionCompliance.totalViolations,
|
|
440
|
+
graphGaps: args.graphCoverage.gaps.length,
|
|
441
|
+
failedGates: failedGates.length,
|
|
442
|
+
},
|
|
443
|
+
missingTop5: args.result.missing.slice(0, 5),
|
|
444
|
+
extraTop5: args.result.extra.slice(0, 5),
|
|
445
|
+
indeterminateTop5: args.indeterminate.slice(0, 5),
|
|
446
|
+
qualityIssuesTop5: args.qualityIssues.slice(0, 5),
|
|
447
|
+
qualityGateSummary: args.qualityGateSummary,
|
|
448
|
+
blockersCount: blockers.length,
|
|
449
|
+
blockersTop3: blockers.slice(0, 3),
|
|
450
|
+
risksTop5: risks.slice(0, 5),
|
|
451
|
+
autofixable: args.suggestions.length > 0 || args.qualityIssues.length > 0,
|
|
452
|
+
nextAction: args.suggestions[0] ??
|
|
453
|
+
(args.compactSummary.status === 'passing'
|
|
454
|
+
? 'Proceed to update_status(done) after required release checks pass.'
|
|
455
|
+
: 'Fix the top blockers and rerun validate.'),
|
|
456
|
+
artifactRefs: {
|
|
457
|
+
validationReport: args.validationReport.path,
|
|
458
|
+
validationReportSha: args.validationReport.sha,
|
|
459
|
+
},
|
|
460
|
+
validationReport: {
|
|
461
|
+
written: args.validationReport.written,
|
|
462
|
+
path: args.validationReport.path,
|
|
463
|
+
sha: args.validationReport.sha,
|
|
464
|
+
passed: args.validationReport.passed,
|
|
465
|
+
reviewer: args.validationReport.reviewer,
|
|
466
|
+
gatesCount: args.validationReport.gates.length,
|
|
467
|
+
failedGatesTop5: failedGates.slice(0, 5),
|
|
468
|
+
},
|
|
469
|
+
implementationQualityScore: args.implementationQualityScore,
|
|
470
|
+
definitionOfReady: {
|
|
471
|
+
isReady: args.dor.isReady,
|
|
472
|
+
passed: args.dor.passedCount,
|
|
473
|
+
required: args.dor.requiredCount,
|
|
474
|
+
items: failedDorItems,
|
|
475
|
+
failedItemsTop5: failedDorItems,
|
|
476
|
+
},
|
|
477
|
+
definitionOfDone: {
|
|
478
|
+
isDone: args.dod.isDone,
|
|
479
|
+
passed: args.dod.passedCount,
|
|
480
|
+
required: args.dod.requiredCount,
|
|
481
|
+
failedItems: failedDodItems,
|
|
482
|
+
failedItemsTop5: failedDodItems,
|
|
483
|
+
},
|
|
484
|
+
conventionCompliance: {
|
|
485
|
+
totalViolations: args.conventionCompliance.totalViolations,
|
|
486
|
+
regressionDetected: args.conventionCompliance.regressionDetected,
|
|
487
|
+
violationsTop5: args.conventionCompliance.violations.slice(0, 5),
|
|
488
|
+
},
|
|
489
|
+
lintCheck: {
|
|
490
|
+
passed: args.lintCheck.passed,
|
|
491
|
+
issueCount: args.lintCheck.issueCount,
|
|
492
|
+
command: args.lintCheck.command,
|
|
493
|
+
output: args.lintCheck.output ?? '',
|
|
494
|
+
outputPreview: typeof args.lintCheck.output === 'string' ? args.lintCheck.output.slice(0, 500) : undefined,
|
|
495
|
+
},
|
|
496
|
+
assuranceGates: {
|
|
497
|
+
newCode: {
|
|
498
|
+
passed: args.assuranceGates.newCode.passed,
|
|
499
|
+
findingsCount: args.assuranceGates.newCode.findings.length,
|
|
500
|
+
findingsTop5: args.assuranceGates.newCode.findings.slice(0, 5),
|
|
501
|
+
},
|
|
502
|
+
},
|
|
503
|
+
minimalityReport: args.minimalityReport !== null
|
|
504
|
+
? {
|
|
505
|
+
enabled: args.minimalityReport.enabled,
|
|
506
|
+
blocked: args.minimalityReport.blocked,
|
|
507
|
+
findingCount: args.minimalityReport.findings.length,
|
|
508
|
+
blockingFindingCount: minimalityBlockingFindings.length,
|
|
509
|
+
blockingFindingsTop5: minimalityBlockingFindings.slice(0, 5),
|
|
510
|
+
}
|
|
511
|
+
: undefined,
|
|
512
|
+
graphCoverage: {
|
|
513
|
+
gapsCount: args.graphCoverage.gaps.length,
|
|
514
|
+
gapsTop5: args.graphCoverage.gaps.slice(0, 5),
|
|
515
|
+
freshness: args.graphCoverage.freshness,
|
|
516
|
+
summary: args.graphCoverage.summary,
|
|
517
|
+
},
|
|
518
|
+
suggestionsTop5: args.suggestions.slice(0, 5),
|
|
519
|
+
});
|
|
520
|
+
const lintCheck = structured.lintCheck;
|
|
521
|
+
lintCheck.output = args.lintCheck.output ?? '';
|
|
522
|
+
return structured;
|
|
523
|
+
}
|
|
402
524
|
function buildCompactValidateText(args) {
|
|
403
|
-
const { score, passedCount, failedCount, totalCriteria, missing, indeterminate, commitReminder, planuReminder, } = args;
|
|
404
|
-
if (failedCount === 0) {
|
|
525
|
+
const { score, passedCount, failedCount, totalCriteria, qualityFailures, missing, indeterminate, commitReminder, planuReminder, } = args;
|
|
526
|
+
if (failedCount === 0 && qualityFailures.length === 0) {
|
|
405
527
|
// All passing ā single line
|
|
406
528
|
return (`ā
Validate: ${String(score)}/100 ā ${String(passedCount)}/${String(totalCriteria)} criteria passing` +
|
|
407
529
|
commitReminder +
|
|
408
530
|
planuReminder);
|
|
409
531
|
}
|
|
532
|
+
if (failedCount === 0) {
|
|
533
|
+
return (`ā Validate: ${String(score)}/100 ā failing; ${String(passedCount)}/${String(totalCriteria)} criteria passing; failing gates: ${qualityFailures.join(', ')}` +
|
|
534
|
+
commitReminder +
|
|
535
|
+
planuReminder);
|
|
536
|
+
}
|
|
410
537
|
// Failures exist ā list only the failing ones (max 5)
|
|
411
538
|
const MAX_FAILURES = 5;
|
|
412
539
|
const labelledFailures = [
|
|
@@ -423,7 +550,8 @@ function buildCompactValidateText(args) {
|
|
|
423
550
|
.slice(0, 3)
|
|
424
551
|
.map((failure) => `${failure.label}: ${failure.criterion}`)
|
|
425
552
|
.join(', ');
|
|
426
|
-
const
|
|
553
|
+
const gateSuffix = qualityFailures.length > 0 ? `; failing gates: ${qualityFailures.join(', ')}` : '';
|
|
554
|
+
const summaryLine = `ā Validate: ${String(score)}/100 ā failing; ${String(failedCount)} failing criteria: [${failNames}${remainingCount + shownFailures.length > 3 ? '...' : ''}]${gateSuffix}`;
|
|
427
555
|
return summaryLine + '\n' + failureList + moreLine + commitReminder + planuReminder;
|
|
428
556
|
}
|
|
429
557
|
function scoreToGrade(score) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// transport-factory.ts ā Parses CLI flags and selects the appropriate MCP transport.
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
3
|
import { createHttpTransport } from './http-transport.js';
|
|
4
|
+
import { compactToolsListMessage, isToolsListResponse, } from '../engine/compact/tool-list-compactor.js';
|
|
4
5
|
const VALID_TRANSPORTS = ['stdio', 'http'];
|
|
5
6
|
const DEFAULT_PORT = 3100;
|
|
6
7
|
const DEFAULT_HOST = '127.0.0.1';
|
|
@@ -40,6 +41,7 @@ export async function selectTransport(server, args, serverFactory) {
|
|
|
40
41
|
const config = parseTransportConfig(args);
|
|
41
42
|
if (config.transport === 'stdio') {
|
|
42
43
|
const transport = new StdioServerTransport();
|
|
44
|
+
installToolsListCompaction(transport);
|
|
43
45
|
await server.connect(transport);
|
|
44
46
|
installStdioShutdownHandlers(server);
|
|
45
47
|
return;
|
|
@@ -48,6 +50,17 @@ export async function selectTransport(server, args, serverFactory) {
|
|
|
48
50
|
const factory = serverFactory ?? (() => server);
|
|
49
51
|
await createHttpTransport(factory, config);
|
|
50
52
|
}
|
|
53
|
+
function installToolsListCompaction(transport) {
|
|
54
|
+
const originalSend = transport.send.bind(transport);
|
|
55
|
+
const compactingSend = (message) => {
|
|
56
|
+
const outgoing = isToolsListResponse(message)
|
|
57
|
+
? compactToolsListMessage(message)
|
|
58
|
+
: message;
|
|
59
|
+
return originalSend(outgoing);
|
|
60
|
+
};
|
|
61
|
+
const wrappedTransport = transport;
|
|
62
|
+
wrappedTransport.send = compactingSend;
|
|
63
|
+
}
|
|
51
64
|
function installStdioShutdownHandlers(server) {
|
|
52
65
|
let shuttingDown = false;
|
|
53
66
|
const shutdown = (reason) => {
|
package/dist/types/qa-gate.d.ts
CHANGED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type ToolExecutionClass = 'local' | 'hybrid' | 'llm-required';
|
|
2
|
+
export interface ToolClassification {
|
|
3
|
+
toolName: string;
|
|
4
|
+
executionClass: ToolExecutionClass;
|
|
5
|
+
tokenPolicy: 'no-llm' | 'summarize-first' | 'llm-product';
|
|
6
|
+
reason: string;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=tool-classification.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planu/cli",
|
|
3
|
-
"version": "4.10.
|
|
3
|
+
"version": "4.10.6",
|
|
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.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"
|
|
45
45
|
},
|
|
46
46
|
"engines": {
|
|
47
47
|
"node": ">=24.0.0"
|
|
@@ -142,18 +142,12 @@
|
|
|
142
142
|
"@napi-rs/cli": "^3.7.2",
|
|
143
143
|
"@secretlint/secretlint-rule-no-homedir": "^13.0.2",
|
|
144
144
|
"@secretlint/secretlint-rule-preset-recommend": "^13.0.2",
|
|
145
|
-
"@semantic-release/changelog": "^6.0.3",
|
|
146
|
-
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
147
|
-
"@semantic-release/git": "^10.0.1",
|
|
148
|
-
"@semantic-release/github": "^12.0.9",
|
|
149
|
-
"@semantic-release/npm": "^13.1.5",
|
|
150
|
-
"@semantic-release/release-notes-generator": "^14.1.1",
|
|
151
145
|
"@stryker-mutator/core": "^9.6.1",
|
|
152
146
|
"@stryker-mutator/vitest-runner": "^9.6.1",
|
|
153
147
|
"@supabase/supabase-js": "^2.110.0",
|
|
154
148
|
"@types/node": "^26.1.0",
|
|
155
149
|
"@vitejs/plugin-vue": "^6.0.7",
|
|
156
|
-
"@vitest/coverage-v8": "^4.1.
|
|
150
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
157
151
|
"@vue/test-utils": "^2.4.11",
|
|
158
152
|
"eslint": "^10.6.0",
|
|
159
153
|
"eslint-config-prettier": "^10.1.8",
|
|
@@ -167,13 +161,12 @@
|
|
|
167
161
|
"madge": "^8.0.0",
|
|
168
162
|
"prettier": "^3.9.4",
|
|
169
163
|
"secretlint": "^13.0.2",
|
|
170
|
-
"semantic-release": "^25.0.5",
|
|
171
164
|
"tsc-alias": "^1.9.0",
|
|
172
165
|
"type-coverage": "^2.29.7",
|
|
173
166
|
"typescript": "^6.0.3",
|
|
174
167
|
"typescript-eslint": "^8.62.1",
|
|
175
168
|
"vite": "^8.1.3",
|
|
176
|
-
"vitest": "^4.1.
|
|
169
|
+
"vitest": "^4.1.10",
|
|
177
170
|
"vue": "^3.5.39"
|
|
178
171
|
}
|
|
179
172
|
}
|
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.10.
|
|
4
|
+
"version": "4.10.6",
|
|
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-first release readiness, branch cleanliness, and optional gitflow drift.",
|
|
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"
|