@planu/cli 4.10.12 → 4.11.1
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 +16 -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
|
@@ -86,9 +86,10 @@ export async function handleRenderSpecForProvider(params) {
|
|
|
86
86
|
extras: { ...output.extras, warnings: loadedSpec.warnings },
|
|
87
87
|
...(provider === 'claude'
|
|
88
88
|
? {
|
|
89
|
-
model:
|
|
90
|
-
modelHint:
|
|
91
|
-
|
|
89
|
+
...(output.extras?.model !== undefined ? { model: output.extras.model } : {}),
|
|
90
|
+
...(output.extras?.modelHint !== undefined ? { modelHint: output.extras.modelHint } : {}),
|
|
91
|
+
...(output.extras?.modelTier !== undefined ? { modelTier: output.extras.modelTier } : {}),
|
|
92
|
+
...(output.extras?.budget !== undefined ? { budget: output.extras.budget } : {}),
|
|
92
93
|
toolCallouts: output.extras?.toolCallouts ?? [],
|
|
93
94
|
}
|
|
94
95
|
: {}),
|
|
@@ -1,15 +1,70 @@
|
|
|
1
1
|
// tools/reverse-engineer/handler.ts — MCP tool handler for reverse_engineer
|
|
2
|
-
import { readFile, stat,
|
|
2
|
+
import { readFile, stat, rm } from 'node:fs/promises';
|
|
3
3
|
import { join, extname, basename, relative, resolve } from 'node:path';
|
|
4
4
|
import { glob } from 'glob';
|
|
5
5
|
import { specStore, knowledgeStore } from '../../storage/index.js';
|
|
6
6
|
import { ti } from '../../i18n/index.js';
|
|
7
|
-
import { inferDifficulty } from '../../engine/estimator.js';
|
|
7
|
+
import { calculateEstimation, inferDifficulty } from '../../engine/estimator.js';
|
|
8
8
|
import { CODE_EXTENSIONS, detectLanguageFromExtension, getFrameworkTerminology, detectTechDebt, IGNORE_PATTERNS, } from './analyzer.js';
|
|
9
9
|
import { runDeepAnalysis, enrichSpecFromAnalysis, generateAnalysisSummary, } from '../../engine/reverse-engineer/index.js';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
10
|
+
import { atomicWriteFile } from '../../engine/safety/atomic-write-file.js';
|
|
11
|
+
import { serializeValueOnlySpecContent } from '../../engine/spec-format/value-only-spec-serializer.js';
|
|
12
|
+
function buildReverseCriteria(analysis, analyzedFiles) {
|
|
13
|
+
const criteria = [
|
|
14
|
+
{
|
|
15
|
+
text: `The reverse-engineered baseline accounts for ${String(analyzedFiles)} analyzed source files and ${String(analysis.totalLines)} source lines.`,
|
|
16
|
+
done: false,
|
|
17
|
+
},
|
|
18
|
+
];
|
|
19
|
+
if (analysis.layers.length > 0) {
|
|
20
|
+
criteria.push({
|
|
21
|
+
text: `The documented architecture includes the detected layers: ${analysis.layers.join(', ')}.`,
|
|
22
|
+
done: false,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
if (analysis.dependencies.length > 0) {
|
|
26
|
+
criteria.push({
|
|
27
|
+
text: `The documented dependency boundary includes: ${analysis.dependencies.slice(0, 10).join(', ')}.`,
|
|
28
|
+
done: false,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return criteria;
|
|
32
|
+
}
|
|
33
|
+
function buildReverseTechnicalContent(analysis, targetPath, analyzedFiles) {
|
|
34
|
+
const lines = [
|
|
35
|
+
'### Analysis Evidence',
|
|
36
|
+
'',
|
|
37
|
+
`- Target: \`${targetPath}\``,
|
|
38
|
+
`- Dominant language: ${analysis.dominantLanguage}`,
|
|
39
|
+
`- Source lines analyzed: ${String(analysis.totalLines)}`,
|
|
40
|
+
];
|
|
41
|
+
if (analysis.layers.length > 0) {
|
|
42
|
+
lines.push(`- Detected layers: ${analysis.layers.join(', ')}`);
|
|
43
|
+
}
|
|
44
|
+
if (analysis.patterns.length > 0) {
|
|
45
|
+
lines.push(`- Detected patterns: ${analysis.patterns.join(', ')}`);
|
|
46
|
+
}
|
|
47
|
+
if (analyzedFiles.length > 0) {
|
|
48
|
+
lines.push('', '### Analyzed Files', '');
|
|
49
|
+
lines.push(...analyzedFiles.slice(0, 20).map((file) => `- \`${file}\``));
|
|
50
|
+
}
|
|
51
|
+
return lines.join('\n');
|
|
52
|
+
}
|
|
53
|
+
async function persistReverseEngineeredSpec(projectId, spec, specDir, content) {
|
|
54
|
+
await atomicWriteFile(spec.specPath, content);
|
|
55
|
+
try {
|
|
56
|
+
await specStore.createSpec(projectId, spec);
|
|
57
|
+
}
|
|
58
|
+
catch (storeError) {
|
|
59
|
+
try {
|
|
60
|
+
await rm(specDir, { recursive: true, force: true });
|
|
61
|
+
}
|
|
62
|
+
catch (cleanupError) {
|
|
63
|
+
throw new AggregateError([storeError, cleanupError], `Failed to register ${spec.id} and remove its unregistered spec directory`, { cause: cleanupError });
|
|
64
|
+
}
|
|
65
|
+
throw storeError;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
13
68
|
async function analyzeCodeStructure(files, targetPath, projectPath, isDir, depth) {
|
|
14
69
|
const interfaces = [];
|
|
15
70
|
const dependencies = [];
|
|
@@ -149,7 +204,7 @@ export async function handleReverseEngineer(args) {
|
|
|
149
204
|
const difficulty = inferDifficulty(specType, scope);
|
|
150
205
|
// 6. Build spec
|
|
151
206
|
const specId = `SPEC-RE-${Date.now().toString(36).toUpperCase()}`;
|
|
152
|
-
const slug = dirName.toLowerCase().replace(/[^a-z0-9]+/g, '-');
|
|
207
|
+
const slug = dirName.toLowerCase().replace(/[^a-z0-9]+/g, '-') || 'reverse-engineered';
|
|
153
208
|
const now = new Date().toISOString();
|
|
154
209
|
const spec = {
|
|
155
210
|
id: specId,
|
|
@@ -165,18 +220,7 @@ export async function handleReverseEngineer(args) {
|
|
|
165
220
|
updatedAt: now,
|
|
166
221
|
specPath: '',
|
|
167
222
|
technicalPath: '',
|
|
168
|
-
estimation:
|
|
169
|
-
devHours: 0,
|
|
170
|
-
reviewHours: 0,
|
|
171
|
-
recommendedModel: 'sonnet',
|
|
172
|
-
tokensOpus: 0,
|
|
173
|
-
tokensSonnet: 0,
|
|
174
|
-
apiCostUsd: 0,
|
|
175
|
-
hourlyRate: 0,
|
|
176
|
-
humanCostUsd: 0,
|
|
177
|
-
totalCostUsd: 0,
|
|
178
|
-
tokenOptimization: { mode: 'local', reasoning: '', estimatedTokens: 0, savings: '' },
|
|
179
|
-
},
|
|
223
|
+
estimation: calculateEstimation(specType, scope, difficulty),
|
|
180
224
|
actuals: null,
|
|
181
225
|
target: ca.layers.includes('presentation')
|
|
182
226
|
? 'frontend'
|
|
@@ -186,7 +230,11 @@ export async function handleReverseEngineer(args) {
|
|
|
186
230
|
tags: [...ca.patterns],
|
|
187
231
|
dependencies: [],
|
|
188
232
|
blockedBy: [],
|
|
189
|
-
gitBranch: ''
|
|
233
|
+
gitBranch: `${specType === 'refactor' ? 'refactor' : 'feat'}/${specId.toLowerCase()}-${slug}`,
|
|
234
|
+
generation: {
|
|
235
|
+
method: 'deterministic',
|
|
236
|
+
generatedAt: now,
|
|
237
|
+
},
|
|
190
238
|
impactAnalysis: {
|
|
191
239
|
affectedModules: ca.layers,
|
|
192
240
|
affectedFiles: files.slice(0, 50),
|
|
@@ -207,28 +255,6 @@ export async function handleReverseEngineer(args) {
|
|
|
207
255
|
const technicalFilePath = specFilePath;
|
|
208
256
|
spec.specPath = specFilePath;
|
|
209
257
|
spec.technicalPath = technicalFilePath;
|
|
210
|
-
try {
|
|
211
|
-
await mkdir(specDir, { recursive: true });
|
|
212
|
-
const leanSpec = generateLeanSpecContent({
|
|
213
|
-
spec,
|
|
214
|
-
description: `Reverse-engineered from ${targetPath}. ${String(files.length)} files analyzed.`,
|
|
215
|
-
estimation: spec.estimation,
|
|
216
|
-
});
|
|
217
|
-
const filesToModify = files.slice(0, 20).map((f) => ({
|
|
218
|
-
path: relative(projectPath, f),
|
|
219
|
-
status: 'pending',
|
|
220
|
-
}));
|
|
221
|
-
const leanTech = generateLeanTechnicalContent({
|
|
222
|
-
specId,
|
|
223
|
-
filesToModify,
|
|
224
|
-
});
|
|
225
|
-
// SPEC-709: write unified spec.md (technical content embedded as `## Technical` section)
|
|
226
|
-
const unified = buildUnifiedSpecContent(leanSpec, leanTech);
|
|
227
|
-
await writeFile(specFilePath, unified, 'utf-8');
|
|
228
|
-
}
|
|
229
|
-
catch {
|
|
230
|
-
// Best-effort — file write failure shouldn't block the tool
|
|
231
|
-
}
|
|
232
258
|
// 7. Run deep-v2 analysis and enrich spec
|
|
233
259
|
let deepV2Result = null;
|
|
234
260
|
let deepV2Summary = null;
|
|
@@ -242,8 +268,15 @@ export async function handleReverseEngineer(args) {
|
|
|
242
268
|
spec.tags = [...new Set([...spec.tags, ...enrichment.tags])];
|
|
243
269
|
deepV2Summary = generateAnalysisSummary(deepV2Result);
|
|
244
270
|
}
|
|
245
|
-
// 8.
|
|
246
|
-
|
|
271
|
+
// 8. Persist the document and store record as one observable operation.
|
|
272
|
+
const analyzedFiles = files.map((file) => isDir ? relative(projectPath, join(targetPath, file)) : relative(projectPath, targetPath));
|
|
273
|
+
const specContent = serializeValueOnlySpecContent({
|
|
274
|
+
spec,
|
|
275
|
+
description: `Reverse-engineered from \`${targetPath}\` using ${String(ca.limit)} source files and ${String(ca.totalLines)} source lines.`,
|
|
276
|
+
criteria: buildReverseCriteria(ca, ca.limit),
|
|
277
|
+
technical: buildReverseTechnicalContent(ca, targetPath, analyzedFiles),
|
|
278
|
+
});
|
|
279
|
+
await persistReverseEngineeredSpec(projectId, spec, specDir, specContent);
|
|
247
280
|
// 9. Build output
|
|
248
281
|
const analysis = {
|
|
249
282
|
filesAnalyzed: ca.limit,
|
|
@@ -4,7 +4,7 @@ import { atomicWriteFile } from '../engine/safety/atomic-write-file.js';
|
|
|
4
4
|
import { dirname } from 'node:path';
|
|
5
5
|
import { specStore } from '../storage/index.js';
|
|
6
6
|
import { analyzeSpecSize, buildChildSpecs } from '../engine/spec-splitter.js';
|
|
7
|
-
import {
|
|
7
|
+
import { serializeValueOnlySpecContent } from '../engine/spec-format/value-only-spec-serializer.js';
|
|
8
8
|
import { detectOverSpec } from '../engine/complexity-budget/index.js';
|
|
9
9
|
// ---------------------------------------------------------------------------
|
|
10
10
|
// analyze_spec_size
|
|
@@ -111,14 +111,14 @@ export async function handleSplitSpec(input) {
|
|
|
111
111
|
isError: true,
|
|
112
112
|
};
|
|
113
113
|
}
|
|
114
|
+
const sourceSpec = await enrichSpecWithCriteria(spec);
|
|
114
115
|
// Resolve proposals: use provided or auto-generate
|
|
115
116
|
let proposals;
|
|
116
117
|
if (proposedSplits && proposedSplits.length > 0) {
|
|
117
118
|
proposals = proposedSplits;
|
|
118
119
|
}
|
|
119
120
|
else {
|
|
120
|
-
const
|
|
121
|
-
const analysis = analyzeSpecSize(specWithCriteria);
|
|
121
|
+
const analysis = analyzeSpecSize(sourceSpec);
|
|
122
122
|
proposals = analysis.suggestedSplits;
|
|
123
123
|
}
|
|
124
124
|
if (proposals.length === 0) {
|
|
@@ -143,14 +143,23 @@ export async function handleSplitSpec(input) {
|
|
|
143
143
|
return `SPEC-${String(num).padStart(3, '0')}`;
|
|
144
144
|
};
|
|
145
145
|
// Build child specs
|
|
146
|
-
const childSpecs = buildChildSpecs(
|
|
146
|
+
const childSpecs = buildChildSpecs(sourceSpec, proposals, nextIdFn);
|
|
147
147
|
const childIds = childSpecs.map((c) => c.id);
|
|
148
|
-
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
for (const child of childSpecs) {
|
|
148
|
+
const sourceCriteria = sourceSpec.reviewNotes ?? [];
|
|
149
|
+
// Persist child specs as one value-only document; no legacy sidecars are created.
|
|
150
|
+
for (const [index, child] of childSpecs.entries()) {
|
|
152
151
|
await mkdir(dirname(child.specPath), { recursive: true });
|
|
153
|
-
|
|
152
|
+
child.generation = {
|
|
153
|
+
method: 'deterministic',
|
|
154
|
+
generatedAt: child.createdAt,
|
|
155
|
+
};
|
|
156
|
+
const proposal = proposals[index];
|
|
157
|
+
const content = serializeValueOnlySpecContent({
|
|
158
|
+
spec: child,
|
|
159
|
+
description: buildChildDescription(child, spec, proposal),
|
|
160
|
+
criteria: buildChildCriteria(proposal, sourceCriteria),
|
|
161
|
+
technical: buildChildTechnicalContent(child, spec),
|
|
162
|
+
});
|
|
154
163
|
await atomicWriteFile(child.specPath, content);
|
|
155
164
|
// NOTE: child.technicalPath / child.progressPath remain populated in the
|
|
156
165
|
// in-memory Spec object for type compatibility (PR-D will remove fields).
|
|
@@ -218,79 +227,31 @@ function extractAcFromContent(content) {
|
|
|
218
227
|
.map((line) => line.replace(/^[-*]\s*\[[ x]\]\s*/i, '').trim())
|
|
219
228
|
.filter((line) => line.length > 10);
|
|
220
229
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
* ## Progress sections inline (SPEC-1010 PR-C: no separate technical.md /
|
|
224
|
-
* progress.md files are written).
|
|
225
|
-
*/
|
|
226
|
-
function buildChildUnifiedContent(child, original) {
|
|
227
|
-
const specBody = buildChildSpecContent(child, original);
|
|
228
|
-
const technicalBody = buildChildTechnicalContent(child, original);
|
|
229
|
-
const progressBody = generateProgressContent(child);
|
|
230
|
-
// Strip any YAML frontmatter that technical/progress generators might emit
|
|
231
|
-
const stripFm = (s) => s.replace(/^---\n[\s\S]*?\n---\n?/, '').trim();
|
|
232
|
-
return [
|
|
233
|
-
specBody.trimEnd(),
|
|
234
|
-
'',
|
|
235
|
-
'## Technical',
|
|
236
|
-
'',
|
|
237
|
-
stripFm(technicalBody),
|
|
238
|
-
'',
|
|
239
|
-
'## Progress',
|
|
240
|
-
'',
|
|
241
|
-
stripFm(progressBody),
|
|
242
|
-
'',
|
|
243
|
-
].join('\n');
|
|
244
|
-
}
|
|
245
|
-
/** Generate minimal spec.md content for a child spec */
|
|
246
|
-
function buildChildSpecContent(child, original) {
|
|
247
|
-
const now = new Date().toISOString().split('T')[0] ?? '';
|
|
230
|
+
function buildChildDescription(child, original, proposal) {
|
|
231
|
+
const description = proposal?.description.trim();
|
|
248
232
|
return [
|
|
249
|
-
'---',
|
|
250
|
-
`id: ${child.id}`,
|
|
251
|
-
`title: "${child.title}"`,
|
|
252
|
-
`type: ${child.type}`,
|
|
253
|
-
`scope: ${child.scope}`,
|
|
254
|
-
`target: ${child.target}`,
|
|
255
|
-
`status: draft`,
|
|
256
|
-
`difficulty: ${String(child.difficulty)}`,
|
|
257
|
-
`risk: ${child.risk}`,
|
|
258
|
-
`tags: [${child.tags.map((t) => JSON.stringify(t)).join(', ')}]`,
|
|
259
|
-
`created: ${now}`,
|
|
260
|
-
'---',
|
|
261
|
-
'',
|
|
262
233
|
`# ${child.id}: ${child.title}`,
|
|
263
234
|
'',
|
|
264
235
|
'## Description',
|
|
265
236
|
'',
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
'## Acceptance Criteria',
|
|
269
|
-
'',
|
|
270
|
-
'- [ ] Implementation matches spec requirements',
|
|
271
|
-
'- [ ] Tests written and passing',
|
|
272
|
-
'',
|
|
273
|
-
'## Out of Scope',
|
|
274
|
-
'',
|
|
275
|
-
`- Criteria addressed by sibling specs split from ${original.id}`,
|
|
276
|
-
'',
|
|
237
|
+
...(description ? [description, ''] : []),
|
|
238
|
+
`Split from \`${original.id}\`: ${original.title}.`,
|
|
277
239
|
].join('\n');
|
|
278
240
|
}
|
|
279
|
-
|
|
241
|
+
function buildChildCriteria(proposal, sourceCriteria) {
|
|
242
|
+
if (!proposal) {
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
return proposal.criteriaIndices
|
|
246
|
+
.map((index) => sourceCriteria[index])
|
|
247
|
+
.filter((criterion) => criterion !== undefined)
|
|
248
|
+
.map((text) => ({ text, done: false }));
|
|
249
|
+
}
|
|
280
250
|
function buildChildTechnicalContent(child, original) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
'',
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
`Split from \`${original.id}\`: ${original.title}`,
|
|
287
|
-
'',
|
|
288
|
-
'## Estimation',
|
|
289
|
-
'',
|
|
290
|
-
`| Dev Hours | ${String(child.estimation.devHours)}h |`,
|
|
291
|
-
`|-----------|--------|`,
|
|
292
|
-
`| Review Hours | ${String(child.estimation.reviewHours)}h |`,
|
|
293
|
-
'',
|
|
294
|
-
].join('\n');
|
|
251
|
+
const lines = ['### Parent Spec', '', `- \`${original.id}\`: ${original.title}`];
|
|
252
|
+
if (child.dependencies.length > 0) {
|
|
253
|
+
lines.push('', '### Dependencies', '', ...child.dependencies.map((id) => `- \`${id}\``));
|
|
254
|
+
}
|
|
255
|
+
return lines.join('\n');
|
|
295
256
|
}
|
|
296
257
|
//# sourceMappingURL=spec-split-handler.js.map
|
|
@@ -227,8 +227,17 @@ export interface ProviderModelListModels {
|
|
|
227
227
|
name?: string;
|
|
228
228
|
}[];
|
|
229
229
|
}
|
|
230
|
+
/** SPEC-644: Evidence describing which provider catalogs populated the cache. */
|
|
231
|
+
export interface ModelMappingCacheProvenance {
|
|
232
|
+
source: 'provider-api';
|
|
233
|
+
verifiedProviders: string[];
|
|
234
|
+
}
|
|
230
235
|
/** SPEC-644: Cached model tier mapping with TTL metadata. */
|
|
231
236
|
export interface ModelMappingCache {
|
|
237
|
+
/** Writer schema. Optional only so legacy caches remain readable and can be invalidated. */
|
|
238
|
+
version?: 1;
|
|
239
|
+
/** Provider-catalog evidence. Missing provenance identifies a legacy, non-routable cache. */
|
|
240
|
+
provenance?: ModelMappingCacheProvenance;
|
|
232
241
|
/** Map of provider ID → tier mapping (haiku/sonnet/opus → model ID). */
|
|
233
242
|
providers: Record<string, ModelTierMapping>;
|
|
234
243
|
/** ISO timestamp of the last fetch. */
|
|
@@ -75,6 +75,71 @@ export interface VectorNode {
|
|
|
75
75
|
id: string;
|
|
76
76
|
vector: number[];
|
|
77
77
|
}
|
|
78
|
+
export interface ProjectGraphSourceFileRs {
|
|
79
|
+
path: string;
|
|
80
|
+
hash: string;
|
|
81
|
+
size: number;
|
|
82
|
+
mtime: number;
|
|
83
|
+
}
|
|
84
|
+
export interface TsRelationRs {
|
|
85
|
+
from: string;
|
|
86
|
+
to: string;
|
|
87
|
+
relation: string;
|
|
88
|
+
sourceFile: string;
|
|
89
|
+
confidence: string;
|
|
90
|
+
classification: string;
|
|
91
|
+
}
|
|
92
|
+
export interface GraphNodeInputRs {
|
|
93
|
+
id: string;
|
|
94
|
+
}
|
|
95
|
+
export interface GraphEdgeInputRs {
|
|
96
|
+
id: string;
|
|
97
|
+
from: string;
|
|
98
|
+
to: string;
|
|
99
|
+
relation: string;
|
|
100
|
+
}
|
|
101
|
+
export interface CompactGraphEdgesRs {
|
|
102
|
+
ids: string[];
|
|
103
|
+
from: string[];
|
|
104
|
+
to: string[];
|
|
105
|
+
relations: string[];
|
|
106
|
+
}
|
|
107
|
+
export interface ProjectGraphLimitValuesRs {
|
|
108
|
+
maxFiles: number;
|
|
109
|
+
maxFileBytes: number;
|
|
110
|
+
maxNodes: number;
|
|
111
|
+
maxEdges: number;
|
|
112
|
+
maxDepth: number;
|
|
113
|
+
}
|
|
114
|
+
export interface ProjectGraphLimitsPolicyRs {
|
|
115
|
+
operational: ProjectGraphLimitValuesRs;
|
|
116
|
+
hard: ProjectGraphLimitValuesRs;
|
|
117
|
+
}
|
|
118
|
+
export interface ResolvedProjectGraphLimitsRs extends ProjectGraphLimitValuesRs {
|
|
119
|
+
hardMaxFiles: number;
|
|
120
|
+
hardMaxFileBytes: number;
|
|
121
|
+
hardMaxNodes: number;
|
|
122
|
+
hardMaxEdges: number;
|
|
123
|
+
hardMaxDepth: number;
|
|
124
|
+
}
|
|
125
|
+
export interface AffectedNodeRs {
|
|
126
|
+
nodeId: string;
|
|
127
|
+
depth: number;
|
|
128
|
+
viaRelation: string;
|
|
129
|
+
}
|
|
130
|
+
export interface ShortestPathResultRs {
|
|
131
|
+
nodeIds: string[];
|
|
132
|
+
edgeIds: string[];
|
|
133
|
+
}
|
|
134
|
+
export interface CompactGraphSliceRs {
|
|
135
|
+
nodeIds: string[];
|
|
136
|
+
edgeIds: string[];
|
|
137
|
+
}
|
|
138
|
+
export interface ProjectGraphNeighborRs {
|
|
139
|
+
node: string;
|
|
140
|
+
edgeId: string;
|
|
141
|
+
relation: string;
|
|
142
|
+
}
|
|
78
143
|
export interface PlanuCore {
|
|
79
144
|
hashProjectPath(projectPath: string): string;
|
|
80
145
|
isSafeProjectId(projectId: string): boolean;
|
|
@@ -97,5 +162,12 @@ export interface PlanuCore {
|
|
|
97
162
|
fastHmacSign(secret: string, data: string): string;
|
|
98
163
|
fastHmacVerify(secret: string, data: string, signature: string): boolean;
|
|
99
164
|
startProjectWatcher(rootPath: string, callback: (err: Error | null, event: string) => void): void;
|
|
165
|
+
scanProjectGraphSources?(rootPath: string, limits: ResolvedProjectGraphLimitsRs): ProjectGraphSourceFileRs[];
|
|
166
|
+
extractTsRelations?(rootPath: string, paths: string[], limits: ResolvedProjectGraphLimitsRs): TsRelationRs[];
|
|
167
|
+
queryAffectedNodes?(seed: string, edges: GraphEdgeInputRs[], relationFilter: string[], limits: ResolvedProjectGraphLimitsRs): AffectedNodeRs[];
|
|
168
|
+
queryAffectedNodesCompact?(seed: string, edges: CompactGraphEdgesRs, relationFilter: string[], limits: ResolvedProjectGraphLimitsRs): AffectedNodeRs[];
|
|
169
|
+
queryShortestPath?(from: string, to: string, edges: GraphEdgeInputRs[], relationFilter: string[], limits: ResolvedProjectGraphLimitsRs): ShortestPathResultRs;
|
|
170
|
+
selectCompactGraphSlice?(seeds: string[], nodes: GraphNodeInputRs[], edges: GraphEdgeInputRs[], limits: ResolvedProjectGraphLimitsRs): CompactGraphSliceRs;
|
|
171
|
+
selectCompactGraphSliceCompact?(seeds: string[], nodeIds: string[], edges: CompactGraphEdgesRs, limits: ResolvedProjectGraphLimitsRs): CompactGraphSliceRs;
|
|
100
172
|
}
|
|
101
173
|
//# sourceMappingURL=core-bridge.d.ts.map
|
|
@@ -8,7 +8,8 @@ export interface NextSpecResult {
|
|
|
8
8
|
export interface Spec686WaveEntry {
|
|
9
9
|
specId: string;
|
|
10
10
|
title: string;
|
|
11
|
-
|
|
11
|
+
/** Explicit legacy routing constraint; omitted when no provider evidence exists. */
|
|
12
|
+
model?: 'haiku' | 'sonnet' | 'opus';
|
|
12
13
|
parallelSafe: boolean;
|
|
13
14
|
dependsOn: string[];
|
|
14
15
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AffectedNodeRs, CompactGraphSliceRs, ProjectGraphSourceFileRs, ShortestPathResultRs, TsRelationRs } from './core-bridge.js';
|
|
1
2
|
export type ProjectGraphNodeType = string;
|
|
2
3
|
export type ProjectGraphEdgeType = string;
|
|
3
4
|
export type ProjectGraphConfidence = 'high' | 'medium' | 'low';
|
|
@@ -23,6 +24,11 @@ export interface ProjectGraphPolicy {
|
|
|
23
24
|
maxEdges: number;
|
|
24
25
|
maxListItems: number;
|
|
25
26
|
};
|
|
27
|
+
limits: {
|
|
28
|
+
operational: ProjectGraphLimits;
|
|
29
|
+
hard: ProjectGraphLimits;
|
|
30
|
+
evidence: string;
|
|
31
|
+
};
|
|
26
32
|
redaction: {
|
|
27
33
|
maxSnippetChars: number;
|
|
28
34
|
redactPatterns: string[];
|
|
@@ -39,6 +45,13 @@ export interface ProjectGraphPolicy {
|
|
|
39
45
|
};
|
|
40
46
|
toolNamePatterns: string[];
|
|
41
47
|
}
|
|
48
|
+
export interface ProjectGraphLimits {
|
|
49
|
+
maxFiles: number;
|
|
50
|
+
maxNodes: number;
|
|
51
|
+
maxEdges: number;
|
|
52
|
+
maxDepth: number;
|
|
53
|
+
maxFileBytes: number;
|
|
54
|
+
}
|
|
42
55
|
export interface ProjectGraphEvidencePointer {
|
|
43
56
|
path: string;
|
|
44
57
|
selector?: string;
|
|
@@ -67,6 +80,7 @@ export interface ProjectGraphEdge {
|
|
|
67
80
|
export interface ProjectKnowledgeGraph {
|
|
68
81
|
version: 1;
|
|
69
82
|
graphVersion: string;
|
|
83
|
+
generation: string;
|
|
70
84
|
projectId: string;
|
|
71
85
|
projectPath?: string;
|
|
72
86
|
generatedAt: string;
|
|
@@ -88,6 +102,38 @@ export interface ProjectGraphSource {
|
|
|
88
102
|
path: string;
|
|
89
103
|
content: string;
|
|
90
104
|
hash: string;
|
|
105
|
+
size?: number;
|
|
106
|
+
mtimeMs?: number;
|
|
107
|
+
contentLoaded?: boolean;
|
|
108
|
+
}
|
|
109
|
+
export interface ProjectGraphSourceCacheRecord {
|
|
110
|
+
sourceId: string;
|
|
111
|
+
path: string;
|
|
112
|
+
kind: string;
|
|
113
|
+
contentHash: string;
|
|
114
|
+
extractorVersion: string;
|
|
115
|
+
nativeCoreVersion: string;
|
|
116
|
+
size: number;
|
|
117
|
+
mtimeMs: number;
|
|
118
|
+
}
|
|
119
|
+
export interface ProjectGraphSourceFragment extends ProjectGraphExtractionResult {
|
|
120
|
+
version: 1;
|
|
121
|
+
sourceId: string;
|
|
122
|
+
kind: string;
|
|
123
|
+
contentHash: string;
|
|
124
|
+
extractorVersion: string;
|
|
125
|
+
nativeCoreVersion: string;
|
|
126
|
+
}
|
|
127
|
+
export interface ProjectGraphCacheEnvelope {
|
|
128
|
+
version: 2;
|
|
129
|
+
generation: string;
|
|
130
|
+
records: Record<string, ProjectGraphSourceCacheRecord>;
|
|
131
|
+
fragments: Record<string, ProjectGraphSourceFragment>;
|
|
132
|
+
}
|
|
133
|
+
export interface ProjectGraphCacheReadResult {
|
|
134
|
+
cache: ProjectGraphCacheEnvelope | null;
|
|
135
|
+
knownSourceIds: string[];
|
|
136
|
+
legacy: boolean;
|
|
91
137
|
}
|
|
92
138
|
export interface ProjectGraphExtractionResult {
|
|
93
139
|
nodes: ProjectGraphNode[];
|
|
@@ -95,9 +141,11 @@ export interface ProjectGraphExtractionResult {
|
|
|
95
141
|
}
|
|
96
142
|
export interface ProjectGraphBuildResult {
|
|
97
143
|
graph: ProjectKnowledgeGraph;
|
|
144
|
+
generation: string;
|
|
98
145
|
graphPath: string;
|
|
99
146
|
cachePath: string;
|
|
100
147
|
reprocessedSources: string[];
|
|
148
|
+
removedSources: string[];
|
|
101
149
|
skippedSources: string[];
|
|
102
150
|
usedCache: boolean;
|
|
103
151
|
}
|
|
@@ -124,6 +172,8 @@ export interface ProjectGraphSlice {
|
|
|
124
172
|
risks: number;
|
|
125
173
|
tools: number;
|
|
126
174
|
releases: number;
|
|
175
|
+
symbols: number;
|
|
176
|
+
toolHandlers: number;
|
|
127
177
|
};
|
|
128
178
|
tokenSavings: {
|
|
129
179
|
compactNodes: number;
|
|
@@ -146,4 +196,12 @@ export interface GraphCoverageReport {
|
|
|
146
196
|
compactNodes: number;
|
|
147
197
|
compactEdges: number;
|
|
148
198
|
}
|
|
199
|
+
export interface NativeProjectGraphRuntime {
|
|
200
|
+
nativeActive: boolean;
|
|
201
|
+
scanSources(projectPath: string, maxFiles?: number): ProjectGraphSourceFileRs[];
|
|
202
|
+
extractTypeScriptRelations(projectPath: string, paths?: string[], maxFiles?: number): TsRelationRs[];
|
|
203
|
+
affectedNodes(seed: string, edges: ProjectGraphEdge[], depth?: number, relationFilter?: string[]): AffectedNodeRs[];
|
|
204
|
+
shortestPath(from: string, to: string, edges: ProjectGraphEdge[], relationFilter?: string[]): ShortestPathResultRs;
|
|
205
|
+
compactSlice(seeds: string[], nodes: ProjectGraphNode[], edges: ProjectGraphEdge[], maxNodes?: number, maxEdges?: number): CompactGraphSliceRs;
|
|
206
|
+
}
|
|
149
207
|
//# sourceMappingURL=project-knowledge-graph.d.ts.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SpecStatus, Difficulty, RiskLevel, SpecType, SpecScope, SpecTarget, DiagramType, ToolResult } from '../common/index.js';
|
|
2
|
+
import type { SpecGenerationProvenance } from '../spec-generator.js';
|
|
2
3
|
import type { SpecFormatVersion, SpecHistoryEntry } from './versioning.js';
|
|
3
4
|
import type { Estimation, Actuals, ImpactAnalysis } from '../estimation.js';
|
|
4
5
|
import type { ConstitutionViolation } from '../project/core.js';
|
|
@@ -53,13 +54,15 @@ export interface Spec {
|
|
|
53
54
|
outOfScope?: string[];
|
|
54
55
|
/** SPEC-615: prior decisions linked to this spec by keyword relevance. */
|
|
55
56
|
priorDecisions?: string[];
|
|
56
|
-
/**
|
|
57
|
+
/** @deprecated Legacy read-only model hint. New specs resolve routing at execution time. */
|
|
57
58
|
model?: 'haiku' | 'sonnet' | 'opus';
|
|
58
|
-
/**
|
|
59
|
+
/** @deprecated Legacy read-only token budget. New specs resolve budgets at execution time. */
|
|
59
60
|
budget?: 800 | 2000 | 4000;
|
|
60
|
-
/**
|
|
61
|
+
/** @deprecated Legacy read-only generator provenance. */
|
|
61
62
|
generatedWithModel?: string;
|
|
62
|
-
/**
|
|
63
|
+
/** Value-only provenance for the component that generated the current body. */
|
|
64
|
+
generation?: SpecGenerationProvenance;
|
|
65
|
+
/** @deprecated Legacy read-only generation timestamp. Use generation.generatedAt. */
|
|
63
66
|
generatedAt?: string;
|
|
64
67
|
/** SPEC-766: Non-blocking quality warnings emitted by the generator. */
|
|
65
68
|
qualityWarnings?: string[];
|
|
@@ -37,7 +37,8 @@ export interface NormalizedAcceptanceCriterion {
|
|
|
37
37
|
export interface LeanSpecInput {
|
|
38
38
|
spec: Spec;
|
|
39
39
|
description: string;
|
|
40
|
-
estimation
|
|
40
|
+
/** @deprecated Advisory estimation is ignored by the value-only spec serializer. */
|
|
41
|
+
estimation?: Estimation;
|
|
41
42
|
/** SPEC-461 Phase 3: Extra criteria from autopilot pattern detection. */
|
|
42
43
|
extraCriteria?: string[];
|
|
43
44
|
/** SPEC-1074: Explicit criteria after grounding filters are applied. */
|
|
@@ -12,11 +12,17 @@ export interface SpecGenerationRequest {
|
|
|
12
12
|
architecture?: string;
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
|
+
export type SpecGenerationMethod = 'deterministic' | 'internal-model' | 'host';
|
|
16
|
+
export interface SpecGenerationProvenance {
|
|
17
|
+
method: SpecGenerationMethod;
|
|
18
|
+
generatedAt: string;
|
|
19
|
+
host?: string;
|
|
20
|
+
modelId?: string;
|
|
21
|
+
}
|
|
15
22
|
export interface SpecGenerationResult {
|
|
16
23
|
specBody: string;
|
|
17
24
|
technicalSection: string;
|
|
18
|
-
|
|
19
|
-
generatedAt: string;
|
|
25
|
+
generation: SpecGenerationProvenance;
|
|
20
26
|
qualityWarnings: string[];
|
|
21
27
|
fallbackReason?: string;
|
|
22
28
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planu/cli",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.11.1",
|
|
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.
|
|
38
|
-
"@planu/core-darwin-x64": "4.
|
|
39
|
-
"@planu/core-linux-arm64-gnu": "4.
|
|
40
|
-
"@planu/core-linux-arm64-musl": "4.
|
|
41
|
-
"@planu/core-linux-x64-gnu": "4.
|
|
42
|
-
"@planu/core-linux-x64-musl": "4.
|
|
43
|
-
"@planu/core-win32-arm64-msvc": "4.
|
|
44
|
-
"@planu/core-win32-x64-msvc": "4.
|
|
37
|
+
"@planu/core-darwin-arm64": "4.11.1",
|
|
38
|
+
"@planu/core-darwin-x64": "4.11.1",
|
|
39
|
+
"@planu/core-linux-arm64-gnu": "4.11.1",
|
|
40
|
+
"@planu/core-linux-arm64-musl": "4.11.1",
|
|
41
|
+
"@planu/core-linux-x64-gnu": "4.11.1",
|
|
42
|
+
"@planu/core-linux-x64-musl": "4.11.1",
|
|
43
|
+
"@planu/core-win32-arm64-msvc": "4.11.1",
|
|
44
|
+
"@planu/core-win32-x64-msvc": "4.11.1"
|
|
45
45
|
},
|
|
46
46
|
"engines": {
|
|
47
47
|
"node": ">=24.0.0"
|
|
@@ -70,6 +70,8 @@
|
|
|
70
70
|
"test:watch": "vitest",
|
|
71
71
|
"test:coverage": "vitest run --coverage --exclude 'tests/integration/**'",
|
|
72
72
|
"test:integration": "vitest run tests/integration",
|
|
73
|
+
"native:project-graph:e2e": "PLANU_NATIVE_E2E=1 vitest run tests/integration/native-project-graph-e2e.test.ts",
|
|
74
|
+
"native:project-graph:benchmark": "node scripts/benchmark-native-performance.mjs --operation project_graph_query --samples 5 --project-path . --spec-id SPEC-1119 --json",
|
|
73
75
|
"check": "pnpm typecheck && pnpm lint && pnpm format:check",
|
|
74
76
|
"check:strict": "pnpm typecheck && pnpm lint && pnpm format:check && pnpm audit:deadcode && pnpm audit:circular && pnpm audit:types && pnpm audit:security && pnpm audit:licenses && pnpm audit:i18n",
|
|
75
77
|
"check:deps:fresh": "bash scripts/check-dependency-freshness.sh",
|