@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.
Files changed (47) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/config/project-knowledge-graph.json +42 -5
  3. package/dist/engine/core-bridge-project-graph.d.ts +13 -0
  4. package/dist/engine/core-bridge-project-graph.js +499 -0
  5. package/dist/engine/core-bridge.d.ts +7 -2
  6. package/dist/engine/core-bridge.js +55 -0
  7. package/dist/engine/frontmatter-parser.js +73 -23
  8. package/dist/engine/model-tier-resolver.d.ts +8 -7
  9. package/dist/engine/model-tier-resolver.js +70 -73
  10. package/dist/engine/next-spec-resolver/orchestration-planner.d.ts +5 -0
  11. package/dist/engine/next-spec-resolver/orchestration-planner.js +34 -5
  12. package/dist/engine/project-graph/builder.js +271 -36
  13. package/dist/engine/project-graph/cache.d.ts +22 -4
  14. package/dist/engine/project-graph/cache.js +412 -33
  15. package/dist/engine/project-graph/index.d.ts +1 -0
  16. package/dist/engine/project-graph/index.js +1 -0
  17. package/dist/engine/project-graph/native.d.ts +3 -0
  18. package/dist/engine/project-graph/native.js +36 -0
  19. package/dist/engine/project-graph/query.js +34 -2
  20. package/dist/engine/provider-adapters/adapters/claude.js +38 -14
  21. package/dist/engine/scan-project/index.js +88 -15
  22. package/dist/engine/spec-format/lean-spec-generator.d.ts +2 -2
  23. package/dist/engine/spec-format/lean-spec-generator.js +65 -50
  24. package/dist/engine/spec-format/metadata-value-policy.d.ts +161 -0
  25. package/dist/engine/spec-format/metadata-value-policy.js +87 -0
  26. package/dist/engine/spec-format/value-only-spec-serializer.d.ts +12 -0
  27. package/dist/engine/spec-format/value-only-spec-serializer.js +18 -0
  28. package/dist/engine/spec-generator/fallback-generator.js +4 -2
  29. package/dist/engine/spec-generator/opus-generator.js +5 -2
  30. package/dist/engine/spec-migrator/lean-migration.js +26 -13
  31. package/dist/storage/spec-store.js +6 -6
  32. package/dist/tools/create-spec.js +1027 -739
  33. package/dist/tools/render-spec-for-provider.js +4 -3
  34. package/dist/tools/reverse-engineer/handler.js +76 -43
  35. package/dist/tools/spec-split-handler.js +36 -75
  36. package/dist/types/conventions.d.ts +9 -0
  37. package/dist/types/core-bridge.d.ts +72 -0
  38. package/dist/types/next-spec.d.ts +2 -1
  39. package/dist/types/project-knowledge-graph.d.ts +58 -0
  40. package/dist/types/spec/core.d.ts +7 -4
  41. package/dist/types/spec-format.d.ts +2 -1
  42. package/dist/types/spec-generator.d.ts +8 -2
  43. package/package.json +11 -9
  44. package/planu-native.json +1 -1
  45. package/planu-plugin.json +1 -1
  46. package/dist/engine/spec-format/model-budget-deriver.d.ts +0 -5
  47. 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: loadedSpec.spec.model ?? 'sonnet',
90
- modelHint: loadedSpec.spec.model ?? 'sonnet',
91
- budget: loadedSpec.spec.budget ?? 2000,
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, mkdir, writeFile } from 'node:fs/promises';
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 { generateLeanSpecContent } from '../../engine/spec-format/lean-spec-generator.js';
11
- import { generateLeanTechnicalContent } from '../../engine/spec-format/lean-technical-generator.js';
12
- import { buildUnifiedSpecContent } from '../../engine/spec-format/unified-spec-builder.js';
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. Save spec
246
- await specStore.createSpec(projectId, spec);
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 { generateProgressContent } from '../engine/progress-writer.js';
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 specWithCriteria = await enrichSpecWithCriteria(spec);
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(spec, proposals, nextIdFn);
146
+ const childSpecs = buildChildSpecs(sourceSpec, proposals, nextIdFn);
147
147
  const childIds = childSpecs.map((c) => c.id);
148
- // Persist child specs and create their directories.
149
- // Technical and Progress content are embedded in the unified spec.md
150
- // (SPEC-1010 PR-C: stop writing legacy technical.md / progress.md files).
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
- const content = buildChildUnifiedContent(child, spec);
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
- * Build a unified spec.md for a child spec that includes ## Technical and
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
- child.reviewNotes?.[0] ?? `Split from ${original.id}: ${original.title}`,
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
- /** Generate minimal technical.md content for a child spec */
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
- return [
282
- `# ${child.id}: ${child.title} Technical`,
283
- '',
284
- `## Parent Spec`,
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
- model: 'haiku' | 'sonnet' | 'opus';
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
- /** SPEC-630: model routing hint derived from difficulty. */
57
+ /** @deprecated Legacy read-only model hint. New specs resolve routing at execution time. */
57
58
  model?: 'haiku' | 'sonnet' | 'opus';
58
- /** SPEC-630: token budget derived from devHours. */
59
+ /** @deprecated Legacy read-only token budget. New specs resolve budgets at execution time. */
59
60
  budget?: 800 | 2000 | 4000;
60
- /** SPEC-766: Generator that produced the initial spec body. */
61
+ /** @deprecated Legacy read-only generator provenance. */
61
62
  generatedWithModel?: string;
62
- /** SPEC-766: ISO timestamp for initial spec body generation. */
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: 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
- generatedWithModel: string;
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.10.11",
3
+ "version": "4.11.0",
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.11",
38
- "@planu/core-darwin-x64": "4.10.11",
39
- "@planu/core-linux-arm64-gnu": "4.10.11",
40
- "@planu/core-linux-arm64-musl": "4.10.11",
41
- "@planu/core-linux-x64-gnu": "4.10.11",
42
- "@planu/core-linux-x64-musl": "4.10.11",
43
- "@planu/core-win32-arm64-msvc": "4.10.11",
44
- "@planu/core-win32-x64-msvc": "4.10.11"
37
+ "@planu/core-darwin-arm64": "4.11.0",
38
+ "@planu/core-darwin-x64": "4.11.0",
39
+ "@planu/core-linux-arm64-gnu": "4.11.0",
40
+ "@planu/core-linux-arm64-musl": "4.11.0",
41
+ "@planu/core-linux-x64-gnu": "4.11.0",
42
+ "@planu/core-linux-x64-musl": "4.11.0",
43
+ "@planu/core-win32-arm64-msvc": "4.11.0",
44
+ "@planu/core-win32-x64-msvc": "4.11.0"
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",
package/planu-native.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dev.planu.native",
3
3
  "displayName": "Planu Native Lightweight Surface",
4
- "version": "4.10.11",
4
+ "version": "4.11.0",
5
5
  "packageName": "@planu/cli",
6
6
  "modes": {
7
7
  "lightweight": {