@planu/cli 4.3.21 → 4.3.23

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 CHANGED
@@ -1,3 +1,15 @@
1
+ ## [4.3.23] - 2026-06-02
2
+
3
+ ### Chores
4
+ - chore(deps): refresh direct dependencies
5
+
6
+
7
+ ## [4.3.22] - 2026-06-02
8
+
9
+ ### Bug Fixes
10
+ - fix: stop fabricating spec and test artifacts
11
+
12
+
1
13
  ## [4.3.21] - 2026-06-02
2
14
 
3
15
  ### Bug Fixes
@@ -3,7 +3,11 @@
3
3
  /** Generate lean ## Technical content: YAML-like metadata + files section only. */
4
4
  export function generateLeanTechnicalContent(input) {
5
5
  const { specId, filesToCreate = [], filesToModify = [], filesToTest = [] } = input;
6
- const lines = ['---', `spec: ${specId}`, '---', '', '## Files', ''];
6
+ const lines = ['---', `spec: ${specId}`, '---'];
7
+ if (filesToCreate.length === 0 && filesToModify.length === 0 && filesToTest.length === 0) {
8
+ return lines.join('\n');
9
+ }
10
+ lines.push('', '## Files', '');
7
11
  if (filesToCreate.length > 0) {
8
12
  lines.push('### Create', '');
9
13
  for (const f of filesToCreate) {
@@ -25,12 +29,6 @@ export function generateLeanTechnicalContent(input) {
25
29
  }
26
30
  lines.push('');
27
31
  }
28
- // If no files specified, add placeholder
29
- if (filesToCreate.length === 0 && filesToModify.length === 0 && filesToTest.length === 0) {
30
- lines.push('### Create', '', '- (to be determined)', '');
31
- lines.push('### Modify', '', '- (to be determined)', '');
32
- lines.push('### Test', '', '- (to be determined)', '');
33
- }
34
32
  return lines.join('\n');
35
33
  }
36
34
  //# sourceMappingURL=lean-technical-generator.js.map
@@ -13,7 +13,7 @@
13
13
  * inject sections the user did not author themselves.
14
14
  */
15
15
  export function buildUnifiedSpecContent(leanSpecBody, leanTechnicalBody) {
16
- const technicalBodyRaw = leanTechnicalBody.replace(/^---\n[\s\S]*?\n---\n/, '').trim();
16
+ const technicalBodyRaw = leanTechnicalBody.replace(/^---\n[\s\S]*?\n---(?:\n|$)/, '').trim();
17
17
  if (technicalBodyRaw.length === 0) {
18
18
  return leanSpecBody.endsWith('\n') ? leanSpecBody : `${leanSpecBody}\n`;
19
19
  }
@@ -5,9 +5,10 @@ import type { ContractFormat, ContractSpec, ContractSpecEndpoint, ProjectKnowled
5
5
  */
6
6
  export declare function detectContractFormat(knowledge: ProjectKnowledge): ContractFormat;
7
7
  /**
8
- * Build a sample set of endpoints from a spec title (for scaffolding purposes).
8
+ * Legacy helper retained for API compatibility. Contract generation must not
9
+ * infer endpoints from a title; callers must pass observed endpoints instead.
9
10
  */
10
- export declare function buildSampleEndpoints(specTitle: string): ContractSpecEndpoint[];
11
+ export declare function buildSampleEndpoints(_specTitle: string): ContractSpecEndpoint[];
11
12
  /**
12
13
  * Generate a contract spec for the given spec title and knowledge.
13
14
  * Format is auto-detected from project stack.
@@ -21,49 +21,11 @@ export function detectContractFormat(knowledge) {
21
21
  }
22
22
  // === Contract generation functions ===
23
23
  /**
24
- * Build a sample set of endpoints from a spec title (for scaffolding purposes).
24
+ * Legacy helper retained for API compatibility. Contract generation must not
25
+ * infer endpoints from a title; callers must pass observed endpoints instead.
25
26
  */
26
- export function buildSampleEndpoints(specTitle) {
27
- const resource = specTitle
28
- .toLowerCase()
29
- .replace(/[^a-z0-9]/g, '-')
30
- .replace(/-+/g, '-');
31
- return [
32
- {
33
- path: `/api/${resource}`,
34
- method: 'GET',
35
- requestSchema: {},
36
- responseSchema: {
37
- type: 'array',
38
- items: { type: 'object', properties: { id: { type: 'string' } } },
39
- },
40
- statusCodes: [200, 401, 500],
41
- },
42
- {
43
- path: `/api/${resource}`,
44
- method: 'POST',
45
- requestSchema: {
46
- type: 'object',
47
- required: ['name'],
48
- properties: { name: { type: 'string' } },
49
- },
50
- responseSchema: {
51
- type: 'object',
52
- properties: { id: { type: 'string' }, name: { type: 'string' } },
53
- },
54
- statusCodes: [201, 400, 401, 422, 500],
55
- },
56
- {
57
- path: `/api/${resource}/{id}`,
58
- method: 'GET',
59
- requestSchema: {},
60
- responseSchema: {
61
- type: 'object',
62
- properties: { id: { type: 'string' }, name: { type: 'string' } },
63
- },
64
- statusCodes: [200, 401, 404, 500],
65
- },
66
- ];
27
+ export function buildSampleEndpoints(_specTitle) {
28
+ return [];
67
29
  }
68
30
  // === OpenAPI contract generation ===
69
31
  function buildOpenApiYaml(specTitle, endpoints) {
@@ -244,7 +206,10 @@ export const ${name}Router = router({
244
206
  */
245
207
  export function generateContractSpec(specTitle, knowledge) {
246
208
  const format = detectContractFormat(knowledge);
247
- const endpoints = buildSampleEndpoints(specTitle);
209
+ const endpoints = [];
210
+ if (endpoints.length === 0) {
211
+ return { format, content: '', endpoints };
212
+ }
248
213
  let content;
249
214
  switch (format) {
250
215
  case 'pact':
@@ -2,7 +2,7 @@ import type { ApiMockDefinition, ContractSpecEndpoint, MockServerTool } from '..
2
2
  /**
3
3
  * Generate mock definitions from a list of endpoints.
4
4
  */
5
- export declare function generateMockDefinitions(endpoints: ContractSpecEndpoint[], specTitle: string): ApiMockDefinition[];
5
+ export declare function generateMockDefinitions(endpoints: ContractSpecEndpoint[], _specTitle: string): ApiMockDefinition[];
6
6
  /**
7
7
  * Generate mock server content for the given tool.
8
8
  */
@@ -1,23 +1,5 @@
1
1
  // engine/test-generators/mock-generator.ts
2
2
  // SPEC-018: Mock generation for API endpoints (realistic data, delays, error responses)
3
- // === Realistic sample data generators ===
4
- function generateSampleId() {
5
- return '550e8400-e29b-41d4-a716-446655440000';
6
- }
7
- function generateSampleData(resource) {
8
- return {
9
- id: generateSampleId(),
10
- name: `Sample ${resource}`,
11
- description: `Auto-generated sample for ${resource} (Planu SPEC-018)`,
12
- status: 'active',
13
- createdAt: '2025-01-15T10:30:00.000Z',
14
- updatedAt: '2025-01-15T10:30:00.000Z',
15
- email: `user@example.com`,
16
- count: 42,
17
- tags: ['sample', 'generated'],
18
- metadata: { source: 'planu', version: '1.0' },
19
- };
20
- }
21
3
  const ERROR_RESPONSES = [
22
4
  {
23
5
  status: 400,
@@ -56,16 +38,11 @@ function getErrorResponsesForEndpoint(statusCodes) {
56
38
  /**
57
39
  * Build an ApiMockDefinition from a ContractSpecEndpoint.
58
40
  */
59
- function buildMockFromEndpoint(endpoint, resource) {
60
- const sampleData = endpoint.method === 'GET' && endpoint.path.endsWith('}')
61
- ? generateSampleData(resource)
62
- : endpoint.method === 'GET'
63
- ? { items: [generateSampleData(resource)], total: 1, page: 1, pageSize: 20 }
64
- : generateSampleData(resource);
41
+ function buildMockFromEndpoint(endpoint) {
65
42
  return {
66
43
  endpoint: endpoint.path,
67
44
  method: endpoint.method,
68
- sampleData,
45
+ sampleData: endpoint.responseSchema ?? {},
69
46
  delayMs: 250,
70
47
  errorResponses: getErrorResponsesForEndpoint(endpoint.statusCodes),
71
48
  };
@@ -73,15 +50,14 @@ function buildMockFromEndpoint(endpoint, resource) {
73
50
  /**
74
51
  * Generate mock definitions from a list of endpoints.
75
52
  */
76
- export function generateMockDefinitions(endpoints, specTitle) {
77
- const resource = specTitle
78
- .toLowerCase()
79
- .replace(/[^a-z0-9]/g, '-')
80
- .replace(/-+/g, '-');
81
- return endpoints.map((ep) => buildMockFromEndpoint(ep, resource));
53
+ export function generateMockDefinitions(endpoints, _specTitle) {
54
+ return endpoints.map((ep) => buildMockFromEndpoint(ep));
82
55
  }
83
56
  // === MSW (Mock Service Worker) content generation ===
84
57
  function buildMswMockContent(mocks, specTitle) {
58
+ if (mocks.length === 0) {
59
+ return '';
60
+ }
85
61
  const handlerLines = [];
86
62
  for (const mock of mocks) {
87
63
  const method = mock.method.toLowerCase();
@@ -109,6 +85,9 @@ ${mocks
109
85
  }
110
86
  // === json-server content generation ===
111
87
  function buildJsonServerContent(mocks, specTitle) {
88
+ if (mocks.length === 0) {
89
+ return '';
90
+ }
112
91
  const db = {};
113
92
  const resource = specTitle
114
93
  .toLowerCase()
@@ -133,6 +112,9 @@ ${JSON.stringify(db, null, 2)}
133
112
  }
134
113
  // === WireMock content generation ===
135
114
  function buildWiremockContent(mocks, _specTitle) {
115
+ if (mocks.length === 0) {
116
+ return '';
117
+ }
136
118
  const stubs = mocks.map((mock) => ({
137
119
  request: {
138
120
  method: mock.method,
@@ -56,10 +56,16 @@ export function buildContractMockSummaryLines(specTitle, knowledge) {
56
56
  '### Mock Server',
57
57
  '',
58
58
  `**Tool:** ${tool} (auto-detected)`,
59
- `**Mocks:** ${endpoints.length} endpoint mocks with realistic sample data`,
60
- '**Features:** Simulated delay (250ms), error scenarios (400/401/403/404/500)',
59
+ `**Mocks:** ${endpoints.length} endpoint mocks generated from explicit contracts`,
60
+ ...(endpoints.length === 0
61
+ ? [
62
+ '> No endpoints were provided; Planu did not fabricate API paths, status codes, or mock payloads.',
63
+ ]
64
+ : []),
61
65
  '',
62
- '> Frontend developers can use these mocks to work independently of the backend.',
66
+ ...(endpoints.length > 0
67
+ ? ['> Frontend developers can use these mocks to work independently of the backend.']
68
+ : []),
63
69
  '',
64
70
  ];
65
71
  return lines;
@@ -2,7 +2,7 @@
2
2
  // SPEC-715: heal_spec_docs is now the SINGLE tool that performs migrations.
3
3
  // list_specs is read-only; migrations were moved here.
4
4
  // SPEC-724: Added dryRun, backup flags and Jaccard goal-scenario drift warning.
5
- import { readFile, readdir } from 'node:fs/promises';
5
+ import { readFile, readdir, rm } from 'node:fs/promises';
6
6
  import { atomicWriteFile } from '../engine/safety/atomic-write-file.js';
7
7
  import { join } from 'node:path';
8
8
  import { hashProjectPath } from '../storage/base-store.js';
@@ -46,33 +46,13 @@ function isPlaceholderTechnical(content) {
46
46
  }
47
47
  return false;
48
48
  }
49
- // ---------------------------------------------------------------------------
50
- // File entry generation
51
- // ---------------------------------------------------------------------------
52
- function buildFileEntries(type, isDone) {
53
- const status = isDone ? '(done)' : '(pending)';
54
- // SPEC-579: never fabricate paths from title slug — produces garbage like
55
- // `src/tools/<spanish-title-slug>.ts` for paths that do not exist.
56
- // Always return placeholders; real paths are populated manually or by
57
- // detection tools (scan_project, link_pr_to_spec, sync_spec_from_code).
58
- if (type === 'refactor') {
59
- return [
60
- `- modify: (to be determined by implementation) ${status}`,
61
- `- test: (to be determined) ${status}`,
62
- ];
63
- }
64
- if (type === 'bugfix' || type === 'fix') {
65
- return [
66
- `- modify: (affected files TBD) ${status}`,
67
- `- test: tests/ (regression test) ${status}`,
68
- ];
69
- }
70
- return [`- modify: (to be determined) ${status}`];
49
+ function hasInlineTechnicalFilesPlaceholder(content) {
50
+ return /^##\s+Technical\b[\s\S]*?^##\s+Files\b[\s\S]*?(?:\(pending\)|\(to be determined\)|\bTBD\b|--[a-z])/im.test(content);
71
51
  }
72
52
  // ---------------------------------------------------------------------------
73
- // Technical section writer (SPEC-1010 PR-C: writes into spec.md ## Technical)
53
+ // File entry generation
74
54
  // ---------------------------------------------------------------------------
75
- async function writeTechnicalMd(specDir, specId, type, isDone, projectPath, specBody) {
55
+ async function writeTechnicalMd(specDir, isDone, projectPath, specBody) {
76
56
  // SPEC-586: Try to extract real file paths from spec body before using placeholders
77
57
  let extractedBody = null;
78
58
  if (projectPath && specBody !== undefined) {
@@ -102,18 +82,13 @@ async function writeTechnicalMd(specDir, specId, type, isDone, projectPath, spec
102
82
  // best-effort — fall through to placeholder
103
83
  }
104
84
  }
105
- const fileEntries = buildFileEntries(type, isDone);
106
- // Build the body for the ## Technical section (no outer heading — replaceSectionInSpec adds it)
107
- const technicalBody = extractedBody ??
108
- [
109
- `## Files — ${specId}`,
110
- ...fileEntries,
111
- '',
112
- '## Notes',
113
- `> Auto-generated by heal_spec_docs (SPEC-493). Review and update paths as needed.`,
114
- '',
115
- ].join('\n');
116
85
  const specMdPath = join(specDir, 'spec.md');
86
+ if (extractedBody === null) {
87
+ const removedInline = await removeTechnicalSectionIfPlaceholder(specMdPath);
88
+ const removedLegacy = await removeLegacyTechnicalPlaceholder(join(specDir, 'technical.md'));
89
+ return removedInline || removedLegacy;
90
+ }
91
+ const technicalBody = extractedBody;
117
92
  // SPEC-579: idempotency check — skip write if section already matches.
118
93
  try {
119
94
  const existing = await readFile(specMdPath, 'utf-8');
@@ -128,13 +103,74 @@ async function writeTechnicalMd(specDir, specId, type, isDone, projectPath, spec
128
103
  await replaceSectionInSpec(specMdPath, 'Technical', technicalBody);
129
104
  return true;
130
105
  }
106
+ async function removeLegacyTechnicalPlaceholder(technicalPath) {
107
+ let legacy;
108
+ try {
109
+ legacy = await readFile(technicalPath, 'utf-8');
110
+ }
111
+ catch {
112
+ return false;
113
+ }
114
+ if (!isPlaceholderTechnical(legacy)) {
115
+ return false;
116
+ }
117
+ await rm(technicalPath, { force: true });
118
+ return true;
119
+ }
120
+ async function removeTechnicalSectionIfPlaceholder(specMdPath) {
121
+ let existing;
122
+ try {
123
+ existing = await readFile(specMdPath, 'utf-8');
124
+ }
125
+ catch {
126
+ return false;
127
+ }
128
+ const currentTechnical = extractTechnicalBodyLocal(existing);
129
+ if (currentTechnical === '' || !isPlaceholderTechnical(currentTechnical)) {
130
+ return false;
131
+ }
132
+ const updated = removeTopLevelSection(existing, 'Technical');
133
+ if (updated === existing) {
134
+ return false;
135
+ }
136
+ await atomicWriteFile(specMdPath, updated);
137
+ return true;
138
+ }
139
+ function removeTopLevelSection(content, sectionName) {
140
+ const normalized = content.replace(/\r\n/g, '\n');
141
+ const masked = normalized.replace(/(```|~~~)[\s\S]*?\1/g, (block) => block.replace(/[^\n]/g, ' '));
142
+ const headingRe = new RegExp(`^##\\s+${escapeRegex(sectionName)}\\b.*$`, 'm');
143
+ const match = headingRe.exec(masked);
144
+ if (!match) {
145
+ return content;
146
+ }
147
+ const afterHeading = match.index + match[0].length;
148
+ const nextRe = /^##\s+\S/gm;
149
+ nextRe.lastIndex = afterHeading;
150
+ const next = nextRe.exec(masked);
151
+ const before = normalized.slice(0, match.index).trimEnd();
152
+ const after = next ? normalized.slice(next.index).trimStart() : '';
153
+ if (before.length === 0) {
154
+ return after.length > 0 ? `${after.trimEnd()}\n` : '';
155
+ }
156
+ if (after.length === 0) {
157
+ return `${before}\n`;
158
+ }
159
+ return `${before}\n\n${after.trimEnd()}\n`;
160
+ }
161
+ function escapeRegex(input) {
162
+ return input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
163
+ }
131
164
  /** Local extraction of ## Technical body to avoid circular imports. */
132
165
  function extractTechnicalBodyLocal(content) {
166
+ return extractSectionBodyLocal(content, 'Technical') ?? '';
167
+ }
168
+ function extractSectionBodyLocal(content, sectionName) {
133
169
  const normalized = content.replace(/\r\n/g, '\n');
134
170
  const masked = normalized.replace(/(```|~~~)[\s\S]*?\1/g, (block) => block.replace(/[^\n]/g, ' '));
135
- const headingMatch = /^##\s+Technical\b.*$/m.exec(masked);
171
+ const headingMatch = new RegExp(`^##\\s+${escapeRegex(sectionName)}\\b.*$`, 'm').exec(masked);
136
172
  if (!headingMatch) {
137
- return '';
173
+ return null;
138
174
  }
139
175
  const afterHeading = headingMatch.index + headingMatch[0].length;
140
176
  const nextRe = /^##\s+\S/gm;
@@ -271,9 +307,11 @@ export async function handleHealSpecDocs(params) {
271
307
  let legacyContent = null;
272
308
  // Determine whether healing is needed
273
309
  let needsHeal;
310
+ const inlinePlaceholder = technicalInSpecMd !== null &&
311
+ (isPlaceholderTechnical(technicalInSpecMd) || hasInlineTechnicalFilesPlaceholder(spec.body));
274
312
  if (technicalInSpecMd !== null) {
275
313
  // Check if the inline section is a placeholder
276
- needsHeal = isPlaceholderTechnical(technicalInSpecMd);
314
+ needsHeal = inlinePlaceholder;
277
315
  }
278
316
  else {
279
317
  // No inline section — try standalone technical.md (legacy)
@@ -326,9 +364,27 @@ export async function handleHealSpecDocs(params) {
326
364
  }
327
365
  }
328
366
  }
367
+ if (inlinePlaceholder) {
368
+ const specContent = await readFile(specMdPath, 'utf-8').catch(() => '');
369
+ let updated = removeTopLevelSection(specContent, 'Technical');
370
+ const filesBody = extractSectionBodyLocal(updated, 'Files');
371
+ if (filesBody !== null && isPlaceholderTechnical(filesBody)) {
372
+ updated = removeTopLevelSection(updated, 'Files');
373
+ }
374
+ if (updated !== specContent) {
375
+ await atomicWriteFile(specMdPath, updated);
376
+ healed += 1;
377
+ }
378
+ continue;
379
+ }
380
+ if (legacyContent !== null && isPlaceholderTechnical(legacyContent)) {
381
+ await rm(technicalPath, { force: true });
382
+ healed += 1;
383
+ continue;
384
+ }
329
385
  const isDone = spec.status === 'done' || spec.status === 'implemented';
330
386
  try {
331
- const wrote = await writeTechnicalMd(spec.dir, spec.specId, spec.type, isDone, projectPath, spec.body);
387
+ const wrote = await writeTechnicalMd(spec.dir, isDone, projectPath, spec.body);
332
388
  if (wrote) {
333
389
  healed += 1;
334
390
  }
@@ -1,41 +1,20 @@
1
1
  // tools/migrate-tech/core-handlers.ts — Handlers: analyze | map | plan | strategy | validate
2
2
  import { t } from '../../i18n/index.js';
3
- import { buildStackAnalysis, scoreMigratableComponents, buildEquivalenceMap, generateMigrationPlan, recommendMigrationStrategy, generateParityValidation, } from '../../engine/migration/index.js';
3
+ import { buildStackAnalysis, buildEquivalenceMap, generateMigrationPlan, recommendMigrationStrategy, generateParityValidation, } from '../../engine/migration/index.js';
4
4
  export function handleAnalyze(args) {
5
- const components = scoreMigratableComponents([
6
- {
7
- id: 'app-core',
8
- name: 'Application Core',
9
- path: args.projectPath ?? './src',
10
- type: 'module',
11
- cyclomaticComplexity: 8,
12
- couplingScore: 5,
13
- dependsOn: [],
14
- dependents: ['api-handler'],
15
- linesOfCode: 400,
16
- },
17
- {
18
- id: 'api-handler',
19
- name: 'API Handler',
20
- path: args.projectPath ? `${args.projectPath}/api` : './src/api',
21
- type: 'handler',
22
- cyclomaticComplexity: 12,
23
- couplingScore: 6,
24
- dependsOn: ['app-core'],
25
- dependents: [],
26
- linesOfCode: 250,
27
- },
28
- ]);
29
5
  const data = buildStackAnalysis({
30
6
  language: args.sourceStack ?? 'unknown',
31
7
  framework: args.sourceStack ?? 'unknown',
32
8
  components: [],
33
- entryPoints: [{ type: 'http', name: 'REST API', description: 'Main HTTP entry point' }],
9
+ entryPoints: [],
34
10
  externalIntegrations: [],
35
- migratableComponents: components,
11
+ migratableComponents: [],
36
12
  hasTests: false,
37
13
  });
38
- const warnings = data.riskFlags;
14
+ const warnings = [
15
+ ...data.riskFlags,
16
+ 'No project-derived migration inventory was provided; analysis does not fabricate components or entry points.',
17
+ ];
39
18
  return {
40
19
  action: 'analyze',
41
20
  data,
@@ -60,48 +39,15 @@ export function handleMap(args) {
60
39
  };
61
40
  }
62
41
  export function handlePlan(args) {
63
- const components = scoreMigratableComponents([
64
- {
65
- id: 'utils',
66
- name: 'Utils',
67
- path: './src/utils',
68
- type: 'util',
69
- cyclomaticComplexity: 3,
70
- couplingScore: 1,
71
- dependsOn: [],
72
- dependents: ['service'],
73
- linesOfCode: 100,
74
- },
75
- {
76
- id: 'service',
77
- name: 'Service',
78
- path: './src/service',
79
- type: 'service',
80
- cyclomaticComplexity: 10,
81
- couplingScore: 4,
82
- dependsOn: ['utils'],
83
- dependents: ['handler'],
84
- linesOfCode: 300,
85
- },
86
- {
87
- id: 'handler',
88
- name: 'Handler',
89
- path: './src/handler',
90
- type: 'handler',
91
- cyclomaticComplexity: 6,
92
- couplingScore: 3,
93
- dependsOn: ['service'],
94
- dependents: [],
95
- linesOfCode: 150,
96
- },
97
- ]);
98
42
  const data = generateMigrationPlan({
99
43
  sourceStack: args.sourceStack ?? 'unknown',
100
44
  targetStack: args.targetStack ?? 'unknown',
101
- components,
45
+ components: [],
102
46
  hasTests: false,
103
47
  });
104
- const warnings = [];
48
+ const warnings = [
49
+ 'No project-derived migration components were provided; plan does not fabricate modules.',
50
+ ];
105
51
  if (data.circularDependencies.length > 0) {
106
52
  warnings.push(`${data.circularDependencies.length} circular dependency cycles detected — resolve before migrating`);
107
53
  }
@@ -114,32 +60,38 @@ export function handlePlan(args) {
114
60
  }
115
61
  export function handleStrategy(_args) {
116
62
  const data = recommendMigrationStrategy({
117
- componentCount: 15,
118
- hasActiveUsers: true,
63
+ componentCount: 0,
64
+ hasActiveUsers: false,
119
65
  hasHighSla: false,
120
66
  hasCriticalData: false,
121
67
  migratableComponents: [],
122
- entryPoints: ['/api/users', '/api/orders', '/api/products'],
68
+ entryPoints: [],
123
69
  });
124
70
  return {
125
71
  action: 'strategy',
126
72
  data,
127
73
  message: t('tools.migrate_tech.strategySuccess'),
128
- warnings: [],
74
+ warnings: [
75
+ 'No project-derived strategy inputs were provided; recommendation uses neutral empty evidence.',
76
+ ],
129
77
  };
130
78
  }
131
79
  export function handleValidate(args) {
132
- const component = args.component ?? 'Unknown Component';
80
+ const component = args.component?.trim() ?? '';
133
81
  const data = generateParityValidation({
134
82
  component,
135
- endpoints: ['GET /api/resource', 'POST /api/resource', 'DELETE /api/resource/:id'],
83
+ endpoints: [],
136
84
  observedDifferences: [],
137
85
  });
138
86
  return {
139
87
  action: 'validate',
140
88
  data,
141
89
  message: t('tools.migrate_tech.validateSuccess'),
142
- warnings: [],
90
+ warnings: args.component === undefined
91
+ ? [
92
+ 'No component was provided; validation checklist is unscoped and no contract tests were generated.',
93
+ ]
94
+ : ['No endpoints were provided; contract tests were not generated.'],
143
95
  };
144
96
  }
145
97
  //# sourceMappingURL=core-handlers.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planu/cli",
3
- "version": "4.3.21",
3
+ "version": "4.3.23",
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.3.21",
38
- "@planu/core-darwin-x64": "4.3.21",
39
- "@planu/core-linux-arm64-gnu": "4.3.21",
40
- "@planu/core-linux-arm64-musl": "4.3.21",
41
- "@planu/core-linux-x64-gnu": "4.3.21",
42
- "@planu/core-linux-x64-musl": "4.3.21",
43
- "@planu/core-win32-arm64-msvc": "4.3.21",
44
- "@planu/core-win32-x64-msvc": "4.3.21"
37
+ "@planu/core-darwin-arm64": "4.3.23",
38
+ "@planu/core-darwin-x64": "4.3.23",
39
+ "@planu/core-linux-arm64-gnu": "4.3.23",
40
+ "@planu/core-linux-arm64-musl": "4.3.23",
41
+ "@planu/core-linux-x64-gnu": "4.3.23",
42
+ "@planu/core-linux-x64-musl": "4.3.23",
43
+ "@planu/core-win32-arm64-msvc": "4.3.23",
44
+ "@planu/core-win32-x64-msvc": "4.3.23"
45
45
  },
46
46
  "engines": {
47
47
  "node": ">=24.0.0"
@@ -129,7 +129,7 @@
129
129
  ],
130
130
  "license": "SEE LICENSE IN LICENSE",
131
131
  "dependencies": {
132
- "@anthropic-ai/sdk": "^0.99.0",
132
+ "@anthropic-ai/sdk": "^0.100.1",
133
133
  "@modelcontextprotocol/sdk": "^1.29.0",
134
134
  "glob": "^13.0.6",
135
135
  "yaml": "^2.9.0",
@@ -167,8 +167,8 @@
167
167
  }
168
168
  },
169
169
  "devDependencies": {
170
- "@commitlint/cli": "^21.0.1",
171
- "@commitlint/config-conventional": "^21.0.1",
170
+ "@commitlint/cli": "^21.0.2",
171
+ "@commitlint/config-conventional": "^21.0.2",
172
172
  "@eslint/js": "^10.0.1",
173
173
  "@napi-rs/cli": "^3.7.0",
174
174
  "@secretlint/secretlint-rule-no-homedir": "^13.0.2",
@@ -181,20 +181,20 @@
181
181
  "@semantic-release/release-notes-generator": "^14.1.1",
182
182
  "@stryker-mutator/core": "^9.6.1",
183
183
  "@stryker-mutator/vitest-runner": "^9.6.1",
184
- "@supabase/supabase-js": "^2.106.2",
184
+ "@supabase/supabase-js": "^2.107.0",
185
185
  "@types/node": "^25.9.1",
186
186
  "@vitejs/plugin-vue": "^6.0.7",
187
- "@vitest/coverage-v8": "^4.1.7",
187
+ "@vitest/coverage-v8": "^4.1.8",
188
188
  "@vue/test-utils": "^2.4.10",
189
- "eslint": "^10.4.0",
189
+ "eslint": "^10.4.1",
190
190
  "eslint-config-prettier": "^10.1.8",
191
- "eslint-import-resolver-typescript": "^4.4.4",
191
+ "eslint-import-resolver-typescript": "^4.4.5",
192
192
  "eslint-plugin-import": "^2.32.0",
193
193
  "happy-dom": "^20.9.0",
194
194
  "husky": "^9.1.7",
195
195
  "javascript-obfuscator": "^5.4.3",
196
- "knip": "^6.14.2",
197
- "lint-staged": "^17.0.5",
196
+ "knip": "^6.15.0",
197
+ "lint-staged": "^17.0.7",
198
198
  "madge": "^8.0.0",
199
199
  "prettier": "^3.8.3",
200
200
  "secretlint": "^13.0.2",
@@ -202,9 +202,9 @@
202
202
  "tsc-alias": "^1.8.17",
203
203
  "type-coverage": "^2.29.7",
204
204
  "typescript": "^6.0.3",
205
- "typescript-eslint": "^8.60.0",
206
- "vite": "^8.0.14",
207
- "vitest": "^4.1.7",
205
+ "typescript-eslint": "^8.60.1",
206
+ "vite": "^8.0.16",
207
+ "vitest": "^4.1.8",
208
208
  "vue": "^3.5.35"
209
209
  }
210
210
  }
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.3.21",
4
+ "version": "4.3.23",
5
5
  "packageName": "@planu/cli",
6
6
  "modes": {
7
7
  "lightweight": {
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": "4.3.21",
5
+ "version": "4.3.23",
6
6
  "icon": "assets/plugin/icon.svg",
7
7
  "command": [
8
8
  "npx",