@planu/cli 5.3.58 → 5.3.60
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 +26 -0
- package/README.md +2 -2
- package/dist/.planu-build.json +1 -1
- package/dist/engine/validator/spec-compliance-runner.d.ts +1 -0
- package/dist/engine/validator/spec-compliance-runner.js +32 -2
- package/dist/tools/schemas/validate-output-schema.d.ts +8 -0
- package/dist/tools/schemas/validate-output-schema.js +2 -0
- package/dist/tools/validate-graph-coverage.js +19 -7
- package/dist/tools/validate.js +4 -3
- package/dist/types/project-knowledge-graph.d.ts +1 -0
- package/package.json +4 -3
- package/planu-plugin.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
## [5.3.60] - 2026-08-26
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
- fix(SPEC-1634): normalize scenario test-link descriptions across colon spacing
|
|
5
|
+
- fix(SPEC-1634): accept path-shaped colon forms in scenario tests entries
|
|
6
|
+
- fix(SPEC-1633): point the documented human validation gate at validate:full
|
|
7
|
+
|
|
8
|
+
### Refactoring
|
|
9
|
+
- refactor(SPEC-1633): dedupe validate against check:strict in the release plan
|
|
10
|
+
|
|
11
|
+
### Chores
|
|
12
|
+
- chore(planu): close SPEC-1633 and SPEC-1634
|
|
13
|
+
- chore(SPEC-1633,SPEC-1634): normalize file ownership and risk sections for the handoff gate
|
|
14
|
+
- chore(SPEC-1633,SPEC-1634): approve both specs with challenge resolution
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## [5.3.59] - 2026-08-26
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
- fix(SPEC-1631): report graph coverage as unavailable instead of zero gaps
|
|
21
|
+
|
|
22
|
+
### Chores
|
|
23
|
+
- chore(planu): close SPEC-1631
|
|
24
|
+
- chore(planu): record SPEC-1631 implementing transition
|
|
25
|
+
|
|
26
|
+
|
|
1
27
|
## [5.3.58] - 2026-08-26
|
|
2
28
|
|
|
3
29
|
### Bug Fixes
|
package/README.md
CHANGED
|
@@ -114,9 +114,9 @@ planu/ # Portable, version-controlled project contract
|
|
|
114
114
|
|
|
115
115
|
```bash
|
|
116
116
|
pnpm install # Install dependencies
|
|
117
|
-
pnpm build # Compile
|
|
117
|
+
pnpm build # Compile TypeScript
|
|
118
118
|
pnpm dev # Watch mode
|
|
119
|
-
pnpm validate
|
|
119
|
+
pnpm validate:full # Build plus every static gate plus the full suite
|
|
120
120
|
pnpm check:strict # Extended local quality gates
|
|
121
121
|
pnpm test # Run the full test suite
|
|
122
122
|
pnpm release:local # Local-first release flow when shipping
|
package/dist/.planu-build.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"schemaVersion":1,"commit":"
|
|
1
|
+
{"schemaVersion":1,"commit":"178163df95059021ac364f72b65da84d4a145c66"}
|
|
@@ -17,6 +17,35 @@ export class ComplianceCommandTerminalError extends Error {
|
|
|
17
17
|
this.name = 'ComplianceCommandTerminalError';
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
+
const WINDOWS_DRIVE_PATH = /^[A-Za-z]:[\\/]/;
|
|
21
|
+
const PATH_SHAPED = /[/\\]/;
|
|
22
|
+
const FILE_EXTENSION = /\.[A-Za-z0-9]+$/;
|
|
23
|
+
function isPathShapedSegment(segment) {
|
|
24
|
+
return segment.length > 0 && (PATH_SHAPED.test(segment) || FILE_EXTENSION.test(segment));
|
|
25
|
+
}
|
|
26
|
+
function parseTestLinkString(raw) {
|
|
27
|
+
if (WINDOWS_DRIVE_PATH.test(raw)) {
|
|
28
|
+
return { path: raw };
|
|
29
|
+
}
|
|
30
|
+
const colonIndex = raw.indexOf(':');
|
|
31
|
+
if (colonIndex === -1) {
|
|
32
|
+
return { path: raw };
|
|
33
|
+
}
|
|
34
|
+
const path = raw.slice(0, colonIndex);
|
|
35
|
+
if (!isPathShapedSegment(path)) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const remainder = raw.slice(colonIndex + 1);
|
|
39
|
+
const remainderColonIndex = remainder.indexOf(':');
|
|
40
|
+
const linePart = remainderColonIndex === -1 ? remainder : remainder.slice(0, remainderColonIndex);
|
|
41
|
+
if (/^\d+$/.test(linePart)) {
|
|
42
|
+
const description = remainderColonIndex === -1 ? undefined : remainder.slice(remainderColonIndex + 1).trim();
|
|
43
|
+
const line = Number.parseInt(linePart, 10);
|
|
44
|
+
return description ? { path, line, description } : { path, line };
|
|
45
|
+
}
|
|
46
|
+
const description = remainder.trim();
|
|
47
|
+
return description ? { path, description } : { path };
|
|
48
|
+
}
|
|
20
49
|
// eslint-disable-next-line complexity, max-lines-per-function -- parses the supported scenario YAML subset without executing it
|
|
21
50
|
export function parseFrontmatterScenarios(raw) {
|
|
22
51
|
const match = /^---\n([\s\S]*?)\n---\n/.exec(raw);
|
|
@@ -105,9 +134,10 @@ export function parseFrontmatterScenarios(raw) {
|
|
|
105
134
|
continue;
|
|
106
135
|
}
|
|
107
136
|
const stringMatch = /^\s+-\s+["']?([^'":\s][^'"]*?)["']?\s*$/.exec(trimmed);
|
|
108
|
-
|
|
137
|
+
const testLink = stringMatch?.[1] ? parseTestLinkString(stringMatch[1]) : null;
|
|
138
|
+
if (testLink) {
|
|
109
139
|
currentScenario.tests ??= [];
|
|
110
|
-
currentScenario.tests.push(
|
|
140
|
+
currentScenario.tests.push(testLink);
|
|
111
141
|
continue;
|
|
112
142
|
}
|
|
113
143
|
const metadataMatch = /^\s+(line|runner|config):\s*["']?(.+?)["']?\s*$/.exec(trimmed);
|
|
@@ -19,6 +19,10 @@ export declare const ValidateCompletionOutputSchema: {
|
|
|
19
19
|
failing: "failing";
|
|
20
20
|
passing: "passing";
|
|
21
21
|
}>;
|
|
22
|
+
graphAvailability: z.ZodEnum<{
|
|
23
|
+
available: "available";
|
|
24
|
+
unavailable: "unavailable";
|
|
25
|
+
}>;
|
|
22
26
|
}, z.core.$strip>;
|
|
23
27
|
counts: z.ZodObject<{
|
|
24
28
|
matches: z.ZodNumber;
|
|
@@ -198,6 +202,10 @@ export declare const ValidateCompletionOutputSchema: {
|
|
|
198
202
|
}, z.core.$strip>>;
|
|
199
203
|
}, z.core.$strip>>;
|
|
200
204
|
graphCoverage: z.ZodObject<{
|
|
205
|
+
availability: z.ZodEnum<{
|
|
206
|
+
available: "available";
|
|
207
|
+
unavailable: "unavailable";
|
|
208
|
+
}>;
|
|
201
209
|
gapsCount: z.ZodNumber;
|
|
202
210
|
gapsTop5: z.ZodArray<z.ZodString>;
|
|
203
211
|
freshness: z.ZodOptional<z.ZodObject<{
|
|
@@ -40,6 +40,7 @@ export const ValidateCompletionOutputSchema = {
|
|
|
40
40
|
failed: z.number(),
|
|
41
41
|
total: z.number(),
|
|
42
42
|
status: z.enum(['passing', 'failing']),
|
|
43
|
+
graphAvailability: z.enum(['available', 'unavailable']),
|
|
43
44
|
}),
|
|
44
45
|
counts: z.object({
|
|
45
46
|
matches: z.number(),
|
|
@@ -162,6 +163,7 @@ export const ValidateCompletionOutputSchema = {
|
|
|
162
163
|
})
|
|
163
164
|
.optional(),
|
|
164
165
|
graphCoverage: z.object({
|
|
166
|
+
availability: z.enum(['available', 'unavailable']),
|
|
165
167
|
gapsCount: z.number(),
|
|
166
168
|
gapsTop5: z.array(z.string()),
|
|
167
169
|
freshness: z
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
// tools/validate-graph-coverage.ts — Graph coverage helpers for validate.
|
|
2
|
+
import { reportClassifiedDegradation } from '../errors/classified-degradation.js';
|
|
2
3
|
import { queryProjectGraphSlice } from '../engine/project-graph/index.js';
|
|
3
4
|
function graphCoverageGaps(slice) {
|
|
4
|
-
if (slice === null) {
|
|
5
|
-
return [];
|
|
6
|
-
}
|
|
7
5
|
const criteria = slice.nodes.filter((node) => node.type === 'criterion');
|
|
8
6
|
return criteria
|
|
9
7
|
.filter((criterion) => {
|
|
@@ -14,15 +12,29 @@ function graphCoverageGaps(slice) {
|
|
|
14
12
|
.slice(0, 10);
|
|
15
13
|
}
|
|
16
14
|
export async function buildGraphCoverageReport(args) {
|
|
17
|
-
|
|
15
|
+
let graphSlice;
|
|
16
|
+
try {
|
|
17
|
+
graphSlice = await queryProjectGraphSlice(args);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
reportClassifiedDegradation('GRAPH_COVERAGE_UNAVAILABLE', error);
|
|
21
|
+
graphSlice = null;
|
|
22
|
+
}
|
|
23
|
+
if (graphSlice === null) {
|
|
24
|
+
return { availability: 'unavailable', gaps: [], compactNodes: 0, compactEdges: 0 };
|
|
25
|
+
}
|
|
18
26
|
return {
|
|
19
|
-
|
|
27
|
+
availability: 'available',
|
|
28
|
+
freshness: graphSlice.freshness,
|
|
20
29
|
gaps: graphCoverageGaps(graphSlice),
|
|
21
|
-
compactNodes: graphSlice
|
|
22
|
-
compactEdges: graphSlice
|
|
30
|
+
compactNodes: graphSlice.nodes.length,
|
|
31
|
+
compactEdges: graphSlice.edges.length,
|
|
23
32
|
};
|
|
24
33
|
}
|
|
25
34
|
export function formatGraphCoverageText(report) {
|
|
35
|
+
if (report.availability === 'unavailable') {
|
|
36
|
+
return '\nGRAPH unavailable (project graph unreadable)';
|
|
37
|
+
}
|
|
26
38
|
return report.gaps.length > 0
|
|
27
39
|
? `\nGRAPH ${String(report.gaps.length)} graph-backed coverage gap(s)`
|
|
28
40
|
: '';
|
package/dist/tools/validate.js
CHANGED
|
@@ -583,7 +583,7 @@ export async function executeValidate(args, server, onProgress) {
|
|
|
583
583
|
});
|
|
584
584
|
const graphText = formatGraphCoverageText(graphCoverage);
|
|
585
585
|
// SPEC-512: Compact structuredContent — essential fields only at top level
|
|
586
|
-
const compactSummary = buildCompactSummary(specId, effectiveResult.score === null ? null : effectiveScore, passedCount, failedCount + (qualityGateSummary.passed ? 0 : 1), totalCriteria);
|
|
586
|
+
const compactSummary = buildCompactSummary(specId, effectiveResult.score === null ? null : effectiveScore, passedCount, failedCount + (qualityGateSummary.passed ? 0 : 1), totalCriteria, graphCoverage.availability);
|
|
587
587
|
const structuredBase = buildValidateStructuredContent({
|
|
588
588
|
specId,
|
|
589
589
|
title: spec.title,
|
|
@@ -718,7 +718,7 @@ function buildEffectiveValidationResult(result, specCompliance) {
|
|
|
718
718
|
indeterminate: executableCoverage.indeterminate,
|
|
719
719
|
};
|
|
720
720
|
}
|
|
721
|
-
function buildCompactSummary(specId, score, passedCount, failedCount, totalCriteria) {
|
|
721
|
+
function buildCompactSummary(specId, score, passedCount, failedCount, totalCriteria, graphAvailability) {
|
|
722
722
|
return {
|
|
723
723
|
specId,
|
|
724
724
|
score,
|
|
@@ -726,6 +726,7 @@ function buildCompactSummary(specId, score, passedCount, failedCount, totalCrite
|
|
|
726
726
|
failed: failedCount,
|
|
727
727
|
total: totalCriteria,
|
|
728
728
|
status: failedCount === 0 ? 'passing' : 'failing',
|
|
729
|
+
graphAvailability,
|
|
729
730
|
};
|
|
730
731
|
}
|
|
731
732
|
function buildValidateStructuredContent(args) {
|
|
@@ -859,10 +860,10 @@ function buildValidateStructuredContent(args) {
|
|
|
859
860
|
}
|
|
860
861
|
: undefined,
|
|
861
862
|
graphCoverage: {
|
|
863
|
+
availability: args.graphCoverage.availability,
|
|
862
864
|
gapsCount: args.graphCoverage.gaps.length,
|
|
863
865
|
gapsTop5: args.graphCoverage.gaps.slice(0, 5),
|
|
864
866
|
freshness: args.graphCoverage.freshness,
|
|
865
|
-
summary: args.graphCoverage.summary,
|
|
866
867
|
},
|
|
867
868
|
suggestionsTop5: args.suggestions.slice(0, 5),
|
|
868
869
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planu/cli",
|
|
3
|
-
"version": "5.3.
|
|
3
|
+
"version": "5.3.60",
|
|
4
4
|
"description": "Planu — MCP Server for Spec Driven Development. Cross-platform (Linux/macOS/Windows, x64/arm64).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"generate:host-tool-registry": "node scripts/generate-host-tool-registry.mjs",
|
|
30
30
|
"release:local": "bash scripts/release-local.sh",
|
|
31
31
|
"release:publish": "bash scripts/release-local.sh --publish",
|
|
32
|
-
"build": "pnpm
|
|
32
|
+
"build": "pnpm build:ts",
|
|
33
33
|
"build:ts": "pnpm clean && tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && node scripts/copy-runtime-assets.mjs",
|
|
34
34
|
"build:obfuscated": "pnpm build && node scripts/obfuscate.mjs",
|
|
35
35
|
"dev": "tsc --watch",
|
|
@@ -86,7 +86,8 @@
|
|
|
86
86
|
"prepublishOnly": "bash scripts/prepublish-guard.sh",
|
|
87
87
|
"prepack": "pnpm build:ts && pnpm package:size",
|
|
88
88
|
"test:debug": "vitest run --inspect-brk --single-thread",
|
|
89
|
-
"validate": "pnpm build && pnpm
|
|
89
|
+
"validate": "pnpm build && pnpm verify:typescript-migration",
|
|
90
|
+
"validate:full": "pnpm build && pnpm typecheck && pnpm verify:typescript-migration && pnpm lint:gate && pnpm format:check && pnpm test:coverage",
|
|
90
91
|
"docker:build": "docker build -t planu .",
|
|
91
92
|
"docker:run": "docker compose up",
|
|
92
93
|
"publish:blog": "node scripts/publish-blog.mjs",
|
package/planu-plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dev.planu.cli",
|
|
3
3
|
"displayName": "Planu — Spec Driven Development",
|
|
4
4
|
"description": "Manage software specs, estimations, and autonomous SDD workflows. Language-agnostic MCP server for Claude Code.",
|
|
5
|
-
"version": "5.3.
|
|
5
|
+
"version": "5.3.60",
|
|
6
6
|
"icon": "assets/plugin/icon.svg",
|
|
7
7
|
"command": ["npx", "@planu/cli@latest"],
|
|
8
8
|
"packageName": "@planu/cli",
|