@planu/cli 5.3.34 → 5.3.36
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 +12 -0
- package/dist/engine/spec-format/technical-md-populator.d.ts +5 -0
- package/dist/engine/spec-format/technical-md-populator.js +48 -0
- package/dist/engine/spec-quality/generic-output-gate.js +4 -0
- package/dist/tools/create-spec.js +18 -1
- package/package.json +1 -1
- package/planu-plugin.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [5.3.36] - 2026-08-23
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
- fix(spec-1566): admit bare build phase in release resume phasePattern
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
## [5.3.35] - 2026-08-23
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
- fix(spec-1488): scope marker-declared path extraction to the path-list run after each marker
|
|
11
|
+
|
|
12
|
+
|
|
1
13
|
## [5.3.34] - 2026-08-22
|
|
2
14
|
|
|
3
15
|
### Bug Fixes
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { LeanFileEntry } from '../../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Extract the path list declared immediately after each FILES:/FILE:/TEST:/TESTS: marker.
|
|
4
|
+
* Returns null when no such marker is present (caller falls back to whole-text grounding).
|
|
5
|
+
*/
|
|
6
|
+
export declare function extractMarkerDeclaredPaths(description: string): Set<string> | null;
|
|
2
7
|
/**
|
|
3
8
|
* Attempt to extract file paths from spec body for auto-populating ## Technical.
|
|
4
9
|
* Returns null when no structured file section is found (caller uses placeholder).
|
|
@@ -107,6 +107,54 @@ async function isAdmissibleProseCandidate(candidate, projectPath) {
|
|
|
107
107
|
}
|
|
108
108
|
return hasLetterInitialExtension(candidate) && (await isExistingDirectory(dirname(full)));
|
|
109
109
|
}
|
|
110
|
+
const MARKER_KEYWORD_RE = /\b(FILES|FILE|TESTS|TEST)\s*:/gi;
|
|
111
|
+
const PATH_SEPARATOR_RE = /^(?:[ \t,]+|\band\b)+/i;
|
|
112
|
+
const PATH_TOKEN_RE = /^`?([^\s,`]+)`?/;
|
|
113
|
+
const TRAILING_PUNCTUATION_RE = /[.,;:]+$/;
|
|
114
|
+
function isPathShaped(token) {
|
|
115
|
+
return token.includes('/') && /\.[a-zA-Z0-9]+$/.test(token);
|
|
116
|
+
}
|
|
117
|
+
function consumePathRun(remainder) {
|
|
118
|
+
const paths = new Set();
|
|
119
|
+
let cursor = remainder;
|
|
120
|
+
for (;;) {
|
|
121
|
+
cursor = cursor.replace(PATH_SEPARATOR_RE, '');
|
|
122
|
+
const tokenMatch = PATH_TOKEN_RE.exec(cursor);
|
|
123
|
+
if (tokenMatch?.[1] === undefined) {
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
const raw = tokenMatch[1];
|
|
127
|
+
const endsWithTerminator = /[.;:]+$/.test(raw);
|
|
128
|
+
const trimmed = raw.replace(TRAILING_PUNCTUATION_RE, '');
|
|
129
|
+
if (!isPathShaped(trimmed)) {
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
paths.add(trimmed.toLowerCase());
|
|
133
|
+
if (endsWithTerminator) {
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
cursor = cursor.slice(tokenMatch[0].length);
|
|
137
|
+
}
|
|
138
|
+
return paths;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Extract the path list declared immediately after each FILES:/FILE:/TEST:/TESTS: marker.
|
|
142
|
+
* Returns null when no such marker is present (caller falls back to whole-text grounding).
|
|
143
|
+
*/
|
|
144
|
+
export function extractMarkerDeclaredPaths(description) {
|
|
145
|
+
const markers = [...description.matchAll(MARKER_KEYWORD_RE)];
|
|
146
|
+
if (markers.length === 0) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
const paths = new Set();
|
|
150
|
+
for (const marker of markers) {
|
|
151
|
+
const start = marker.index + marker[0].length;
|
|
152
|
+
for (const path of consumePathRun(description.slice(start))) {
|
|
153
|
+
paths.add(path);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return paths;
|
|
157
|
+
}
|
|
110
158
|
/**
|
|
111
159
|
* Attempt to extract file paths from spec body for auto-populating ## Technical.
|
|
112
160
|
* Returns null when no structured file section is found (caller uses placeholder).
|
|
@@ -87,6 +87,10 @@ const PLACEHOLDER_REFERENCE_RULES = [
|
|
|
87
87
|
pattern: /(^|\/)(example|sample|mock|dummy)[\w.-]*\.[a-z]+$/i,
|
|
88
88
|
reason: 'example file names must not be persisted as technical ownership',
|
|
89
89
|
},
|
|
90
|
+
{
|
|
91
|
+
pattern: /(^|\/)(foo|bar|baz)\d*(?:\.[a-z0-9]+)+$/i,
|
|
92
|
+
reason: 'example file names must not be persisted as technical ownership',
|
|
93
|
+
},
|
|
90
94
|
];
|
|
91
95
|
export function checkGenericSpecOutput(content) {
|
|
92
96
|
const criteria = frontmatterCriteria(content);
|
|
@@ -14,7 +14,7 @@ import { validateConstitution } from './create-spec/constitution-validator.js';
|
|
|
14
14
|
import { getAsyncAnalysisPath } from './create-spec/post-creation.js';
|
|
15
15
|
import { extractCriteria, generateLeanSpecContent, } from '../engine/spec-format/lean-spec-generator.js';
|
|
16
16
|
import { generateLeanTechnicalContent, } from '../engine/spec-format/lean-technical-generator.js';
|
|
17
|
-
import { extractFilesFromSpecBody } from '../engine/spec-format/technical-md-populator.js';
|
|
17
|
+
import { extractFilesFromSpecBody, extractMarkerDeclaredPaths, } from '../engine/spec-format/technical-md-populator.js';
|
|
18
18
|
import { buildCanonicalUnifiedSpecContent, containsUnresolvedContractPlaceholder, validateUnifiedSpecCandidate, } from '../engine/spec-format/unified-spec-builder.js';
|
|
19
19
|
import { buildImplementationContractSection } from '../engine/implementation-contract/index.js';
|
|
20
20
|
import { resolveEnglishOnlySpecGate } from '../engine/spec-language/english-only.js';
|
|
@@ -116,8 +116,25 @@ export async function groundTechnicalFiles(input) {
|
|
|
116
116
|
};
|
|
117
117
|
const records = [];
|
|
118
118
|
const advisoryFiles = [];
|
|
119
|
+
const markerPaths = extractMarkerDeclaredPaths(input.userInput);
|
|
119
120
|
for (const section of ['create', 'modify', 'test']) {
|
|
120
121
|
for (const file of input.files[section]) {
|
|
122
|
+
if (markerPaths !== null) {
|
|
123
|
+
if (markerPaths.has(file.path.toLowerCase())) {
|
|
124
|
+
grounded[section].push(file);
|
|
125
|
+
records.push({
|
|
126
|
+
path: file.path,
|
|
127
|
+
section,
|
|
128
|
+
source: 'user_input',
|
|
129
|
+
evidence: ['create_spec.description'],
|
|
130
|
+
confidence: 'high',
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
advisoryFiles.push(file);
|
|
135
|
+
}
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
121
138
|
const pathInUserInput = pathSurvivesNegativeClauseCheck(userInput, file.path.toLowerCase());
|
|
122
139
|
const exists = await pathExists(pathJoin(input.projectPath, file.path));
|
|
123
140
|
const fromAutopilot = autopilotPaths.has(file.path);
|
package/package.json
CHANGED
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.36",
|
|
6
6
|
"icon": "assets/plugin/icon.svg",
|
|
7
7
|
"command": ["npx", "@planu/cli@latest"],
|
|
8
8
|
"packageName": "@planu/cli",
|