@planu/cli 4.10.11 → 4.11.0
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 +15 -0
- package/dist/config/project-knowledge-graph.json +42 -5
- package/dist/engine/core-bridge-project-graph.d.ts +13 -0
- package/dist/engine/core-bridge-project-graph.js +499 -0
- package/dist/engine/core-bridge.d.ts +7 -2
- package/dist/engine/core-bridge.js +55 -0
- package/dist/engine/frontmatter-parser.js +73 -23
- package/dist/engine/model-tier-resolver.d.ts +8 -7
- package/dist/engine/model-tier-resolver.js +70 -73
- package/dist/engine/next-spec-resolver/orchestration-planner.d.ts +5 -0
- package/dist/engine/next-spec-resolver/orchestration-planner.js +34 -5
- package/dist/engine/project-graph/builder.js +271 -36
- package/dist/engine/project-graph/cache.d.ts +22 -4
- package/dist/engine/project-graph/cache.js +412 -33
- package/dist/engine/project-graph/index.d.ts +1 -0
- package/dist/engine/project-graph/index.js +1 -0
- package/dist/engine/project-graph/native.d.ts +3 -0
- package/dist/engine/project-graph/native.js +36 -0
- package/dist/engine/project-graph/query.js +34 -2
- package/dist/engine/provider-adapters/adapters/claude.js +38 -14
- package/dist/engine/scan-project/index.js +88 -15
- package/dist/engine/spec-format/lean-spec-generator.d.ts +2 -2
- package/dist/engine/spec-format/lean-spec-generator.js +65 -50
- package/dist/engine/spec-format/metadata-value-policy.d.ts +161 -0
- package/dist/engine/spec-format/metadata-value-policy.js +87 -0
- package/dist/engine/spec-format/value-only-spec-serializer.d.ts +12 -0
- package/dist/engine/spec-format/value-only-spec-serializer.js +18 -0
- package/dist/engine/spec-generator/fallback-generator.js +4 -2
- package/dist/engine/spec-generator/opus-generator.js +5 -2
- package/dist/engine/spec-migrator/lean-migration.js +26 -13
- package/dist/storage/spec-store.js +6 -6
- package/dist/tools/create-spec.js +1027 -739
- package/dist/tools/render-spec-for-provider.js +4 -3
- package/dist/tools/reverse-engineer/handler.js +76 -43
- package/dist/tools/spec-split-handler.js +36 -75
- package/dist/types/conventions.d.ts +9 -0
- package/dist/types/core-bridge.d.ts +72 -0
- package/dist/types/next-spec.d.ts +2 -1
- package/dist/types/project-knowledge-graph.d.ts +58 -0
- package/dist/types/spec/core.d.ts +7 -4
- package/dist/types/spec-format.d.ts +2 -1
- package/dist/types/spec-generator.d.ts +8 -2
- package/package.json +11 -9
- package/planu-native.json +1 -1
- package/planu-plugin.json +1 -1
- package/dist/engine/spec-format/model-budget-deriver.d.ts +0 -5
- package/dist/engine/spec-format/model-budget-deriver.js +0 -7
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { generateLeanSpecContent } from './lean-spec-generator.js';
|
|
2
|
+
/**
|
|
3
|
+
* Authority for secondary new-spec writers. Frontmatter always flows through
|
|
4
|
+
* the value-only serializer; callers may add only source-backed body content.
|
|
5
|
+
*/
|
|
6
|
+
export function serializeValueOnlySpecContent(input) {
|
|
7
|
+
const leanSpec = generateLeanSpecContent({
|
|
8
|
+
spec: input.spec,
|
|
9
|
+
description: input.description,
|
|
10
|
+
criteriaOverride: input.criteria ? [...input.criteria] : [],
|
|
11
|
+
});
|
|
12
|
+
const technical = input.technical?.trim();
|
|
13
|
+
if (!technical || /^##\s+Technical\b/m.test(input.description)) {
|
|
14
|
+
return leanSpec;
|
|
15
|
+
}
|
|
16
|
+
return `${leanSpec.trimEnd()}\n\n## Technical\n\n${technical}\n`;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=value-only-spec-serializer.js.map
|
|
@@ -14,8 +14,10 @@ export class FallbackGenerator {
|
|
|
14
14
|
return Promise.resolve({
|
|
15
15
|
specBody,
|
|
16
16
|
technicalSection,
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
generation: {
|
|
18
|
+
method: 'deterministic',
|
|
19
|
+
generatedAt: new Date().toISOString(),
|
|
20
|
+
},
|
|
19
21
|
qualityWarnings: [
|
|
20
22
|
'Fallback generator used. Source description was preserved; technical ownership must come from explicit source paths or local project analysis.',
|
|
21
23
|
],
|
|
@@ -89,8 +89,11 @@ function parseGeneratedText(text, model) {
|
|
|
89
89
|
return {
|
|
90
90
|
specBody: text,
|
|
91
91
|
technicalSection: technicalMatch?.[1]?.trim() ?? '',
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
generation: {
|
|
93
|
+
method: 'internal-model',
|
|
94
|
+
modelId: model,
|
|
95
|
+
generatedAt: new Date().toISOString(),
|
|
96
|
+
},
|
|
94
97
|
qualityWarnings: [],
|
|
95
98
|
};
|
|
96
99
|
}
|
|
@@ -35,19 +35,31 @@ export async function isOldFormat(specDir) {
|
|
|
35
35
|
return false;
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
-
/** Check if a spec
|
|
38
|
+
/** Check if a spec already uses the lean frontmatter contract. */
|
|
39
39
|
function isLeanFormat(content) {
|
|
40
40
|
const fmMatch = /^---\n([\s\S]*?)\n---/m.exec(content);
|
|
41
41
|
if (!fmMatch?.[1]) {
|
|
42
42
|
return false;
|
|
43
43
|
}
|
|
44
|
-
return
|
|
44
|
+
return (/^spec_format_version:\s*["']?1\.0["']?\s*$/m.test(fmMatch[1]) ||
|
|
45
|
+
/^(?:criteria|scenarios):\s*$/m.test(fmMatch[1]));
|
|
46
|
+
}
|
|
47
|
+
function isTerminalStatus(status) {
|
|
48
|
+
return status === 'done' || status === 'discarded';
|
|
49
|
+
}
|
|
50
|
+
function readTopLevelStatus(content) {
|
|
51
|
+
const frontmatter = /^---\n([\s\S]*?)\n---/m.exec(content)?.[1];
|
|
52
|
+
if (!frontmatter) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
const value = /^status:\s*["']?([^\s"']+)["']?\s*$/m.exec(frontmatter)?.[1];
|
|
56
|
+
return value;
|
|
45
57
|
}
|
|
46
58
|
/** Extract the user description from old spec.md (skip generic sections, keep real content). */
|
|
47
59
|
function extractDescription(content) {
|
|
48
60
|
// Remove frontmatter
|
|
49
61
|
const withoutFm = content.replace(/^---\n[\s\S]*?\n---\n/, '').trim();
|
|
50
|
-
// Remove the heading line
|
|
62
|
+
// Remove the heading line containing a spec ID and title.
|
|
51
63
|
const lines = withoutFm.split('\n');
|
|
52
64
|
const startIdx = lines.findIndex((l) => /^#\s+SPEC-\d+/.test(l));
|
|
53
65
|
const afterHeading = startIdx >= 0 ? lines.slice(startIdx + 1) : lines;
|
|
@@ -320,7 +332,9 @@ function buildSpecFromMetadata(metadata, id, title, specStatus, estimation, spec
|
|
|
320
332
|
tags: Array.isArray(metadata.tags) ? metadata.tags : [],
|
|
321
333
|
dependencies: [],
|
|
322
334
|
blockedBy: [],
|
|
323
|
-
gitBranch: typeof metadata.branch === 'string'
|
|
335
|
+
gitBranch: typeof metadata.branch === 'string' && metadata.branch.trim().length > 0
|
|
336
|
+
? metadata.branch
|
|
337
|
+
: `feat/${id.toLowerCase()}`,
|
|
324
338
|
impactAnalysis: null,
|
|
325
339
|
};
|
|
326
340
|
}
|
|
@@ -369,18 +383,23 @@ export async function migrateSpecToLean(specDir, projectPath) {
|
|
|
369
383
|
catch {
|
|
370
384
|
return 'skipped'; // No spec.md — not a valid spec dir
|
|
371
385
|
}
|
|
386
|
+
// Parse frontmatter
|
|
387
|
+
const { metadata } = parseFrontmatterLocal(specContent);
|
|
388
|
+
const status = readTopLevelStatus(specContent) ?? 'draft';
|
|
389
|
+
// Terminal specs are immutable during automatic migration. This guard runs before
|
|
390
|
+
// backups, writes, or cleanup so init_project cannot alter completed decisions.
|
|
391
|
+
if (isTerminalStatus(status)) {
|
|
392
|
+
return 'skipped';
|
|
393
|
+
}
|
|
372
394
|
// Skip if already lean
|
|
373
395
|
if (isLeanFormat(specContent)) {
|
|
374
396
|
return 'skipped';
|
|
375
397
|
}
|
|
376
|
-
// Parse frontmatter
|
|
377
|
-
const { metadata } = parseFrontmatterLocal(specContent);
|
|
378
398
|
const id = resolveSpecId(metadata, specDir, specContent);
|
|
379
399
|
if (!id) {
|
|
380
400
|
return 'missing valid spec id';
|
|
381
401
|
}
|
|
382
402
|
const title = typeof metadata.title === 'string' ? metadata.title : 'Untitled';
|
|
383
|
-
const status = typeof metadata.status === 'string' ? metadata.status : 'draft';
|
|
384
403
|
// Read old technical.md first (needed for estimation extraction)
|
|
385
404
|
let oldTechContent = '';
|
|
386
405
|
let files = { create: [], modify: [], test: [] };
|
|
@@ -396,12 +415,6 @@ export async function migrateSpecToLean(specDir, projectPath) {
|
|
|
396
415
|
const criteria = extractOldCriteria(specContent);
|
|
397
416
|
const estimation = buildEstimationFromOld(metadata, oldTechContent);
|
|
398
417
|
const spec = buildSpecFromMetadata(metadata, id, title, status, estimation, specPath, techPath);
|
|
399
|
-
// If status is done, mark all criteria as done
|
|
400
|
-
if (status === 'done') {
|
|
401
|
-
for (const c of criteria) {
|
|
402
|
-
c.done = true;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
418
|
// Generate lean spec.md with extracted criteria
|
|
406
419
|
const leanSpecLines = generateLeanSpecContent({ spec, description, estimation });
|
|
407
420
|
// Inject real criteria (replace the default one)
|
|
@@ -118,8 +118,8 @@ export async function createSpec(projectId, spec) {
|
|
|
118
118
|
...spec,
|
|
119
119
|
healthScore: computeHealthScore(spec, criteria.length > 0 ? criteria : undefined).score,
|
|
120
120
|
};
|
|
121
|
-
specs
|
|
122
|
-
await saveAll(projectId,
|
|
121
|
+
const nextSpecs = [...specs, specWithScore];
|
|
122
|
+
await saveAll(projectId, nextSpecs);
|
|
123
123
|
return specWithScore;
|
|
124
124
|
});
|
|
125
125
|
}
|
|
@@ -158,8 +158,8 @@ async function __internalUpdateSpec(projectId, specId, updates) {
|
|
|
158
158
|
...merged,
|
|
159
159
|
healthScore: computeHealthScore(merged, criteria.length > 0 ? criteria : undefined).score,
|
|
160
160
|
};
|
|
161
|
-
specs
|
|
162
|
-
await saveAll(projectId,
|
|
161
|
+
const nextSpecs = specs.map((current, currentIdx) => (currentIdx === idx ? updated : current));
|
|
162
|
+
await saveAll(projectId, nextSpecs);
|
|
163
163
|
return updated;
|
|
164
164
|
});
|
|
165
165
|
}
|
|
@@ -204,8 +204,8 @@ export async function deleteSpec(projectId, specId) {
|
|
|
204
204
|
if (idx === -1) {
|
|
205
205
|
return false;
|
|
206
206
|
}
|
|
207
|
-
specs.
|
|
208
|
-
await saveAll(projectId,
|
|
207
|
+
const nextSpecs = specs.filter((_, currentIdx) => currentIdx !== idx);
|
|
208
|
+
await saveAll(projectId, nextSpecs);
|
|
209
209
|
return true;
|
|
210
210
|
});
|
|
211
211
|
}
|