@planu/cli 4.10.9 → 4.10.11

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,16 @@
1
+ ## [4.10.11] - 2026-07-08
2
+
3
+ ### Bug Fixes
4
+ - fix(validate): honor executable spec compliance score
5
+
6
+
7
+ ## [4.10.10] - 2026-07-08
8
+
9
+ ### Bug Fixes
10
+ - fix(create-spec): merge readiness-compliant spec generation
11
+ - fix(create-spec): generate readiness-compliant technical details
12
+
13
+
1
14
  ## [4.10.9] - 2026-07-07
2
15
 
3
16
  ### Bug Fixes
@@ -1,5 +1,5 @@
1
1
  import type { LeanFileEntry, LeanTechnicalInput } from '../../types/index.js';
2
2
  export type { LeanFileEntry, LeanTechnicalInput };
3
- /** Generate lean ## Technical content: YAML-like metadata + files section only. */
3
+ /** Generate lean ## Technical content: YAML-like metadata + files and verification notes. */
4
4
  export declare function generateLeanTechnicalContent(input: LeanTechnicalInput): string;
5
5
  //# sourceMappingURL=lean-technical-generator.d.ts.map
@@ -1,10 +1,13 @@
1
1
  // engine/spec-format/lean-technical-generator.ts — Generates lean ## Technical content (SPEC-461)
2
- // Output: ~15-20 lines. Only files section with create/modify/test and pending/done status.
3
- /** Generate lean ## Technical content: YAML-like metadata + files section only. */
2
+ // Output: lean files section with enough detail for readiness to evaluate concrete scope.
3
+ /** Generate lean ## Technical content: YAML-like metadata + files and verification notes. */
4
4
  export function generateLeanTechnicalContent(input) {
5
- const { specId, filesToCreate = [], filesToModify = [], filesToTest = [] } = input;
5
+ const { specId, filesToCreate = [], filesToModify = [], filesToTest = [], includeDecisionRequiredPlaceholder = false, } = input;
6
6
  const lines = ['---', `spec: ${specId}`, '---'];
7
7
  if (filesToCreate.length === 0 && filesToModify.length === 0 && filesToTest.length === 0) {
8
+ if (includeDecisionRequiredPlaceholder) {
9
+ lines.push('', '## Files', '', '- Decision required: identify expected source and test files before implementation.', '', '## Implementation Notes', '', '- File inference did not find grounded `src/` or `tests/` paths.', '- Do not fabricate authoritative paths; update this section during review with concrete files.');
10
+ }
8
11
  return lines.join('\n');
9
12
  }
10
13
  lines.push('', '## Files', '');
@@ -29,6 +32,14 @@ export function generateLeanTechnicalContent(input) {
29
32
  }
30
33
  lines.push('');
31
34
  }
35
+ lines.push('## Implementation Notes', '');
36
+ lines.push('- Keep implementation scoped to the files listed above unless review updates this spec.');
37
+ if (filesToTest.length > 0) {
38
+ lines.push(`- Verification starts with: \`pnpm vitest run ${filesToTest.map((file) => file.path).join(' ')}\`.`);
39
+ }
40
+ else {
41
+ lines.push('- Decision required: add at least one concrete automated test path before approval.');
42
+ }
32
43
  return lines.join('\n');
33
44
  }
34
45
  //# sourceMappingURL=lean-technical-generator.js.map
@@ -1,7 +1,7 @@
1
1
  // engine/spec-format/technical-md-populator.ts — SPEC-586: Parse spec body to populate ## Technical
2
2
  import { stat } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
- const FILE_PATH_RE = /\b(src|tests)\/[\w/-]+\.[a-z]+\b/g;
4
+ const FILE_PATH_RE = /\b(?:src|tests)\/[a-zA-Z0-9/_.-]+\.[a-z]+\b/g;
5
5
  function extractPathsFromSection(sectionText) {
6
6
  const paths = [];
7
7
  const listRe = /^-\s+`?(src\/[\w./-]+|tests\/[\w./-]+)`?/gm;
@@ -18,10 +18,12 @@ function extractPathsFromSection(sectionText) {
18
18
  return paths;
19
19
  }
20
20
  function parseSectionedFileLists(body) {
21
- const filesSection = /##\s+Files\s+likely\s+to\s+change[\s\S]*?(?=^##\s|$)/m.exec(body);
22
- const createSection = /###\s+Create[\s\S]*?(?=###|^##\s|$)/m.exec(filesSection?.[0] ?? body);
23
- const modifySection = /###\s+Modify[\s\S]*?(?=###|^##\s|$)/m.exec(filesSection?.[0] ?? body);
24
- if (!createSection && !modifySection) {
21
+ const filesSection = /##\s+Files\s+likely\s+to\s+change[\s\S]*?(?=\n##\s|$)/.exec(body);
22
+ const scopedBody = filesSection?.[0] ?? body;
23
+ const createSection = /###\s+Create[\s\S]*?(?=\n###|\n##\s|$)/.exec(scopedBody);
24
+ const modifySection = /###\s+Modify[\s\S]*?(?=\n###|\n##\s|$)/.exec(scopedBody);
25
+ const testSection = /###\s+Test[\s\S]*?(?=\n###|\n##\s|$)/.exec(scopedBody);
26
+ if (!createSection && !modifySection && !testSection) {
25
27
  return null;
26
28
  }
27
29
  return {
@@ -33,13 +35,21 @@ function parseSectionedFileLists(body) {
33
35
  path: p,
34
36
  status: 'pending',
35
37
  })),
36
- test: [],
38
+ test: extractPathsFromSection(testSection?.[0] ?? '').map((p) => ({
39
+ path: p,
40
+ status: 'pending',
41
+ })),
37
42
  };
38
43
  }
39
44
  async function categorizeByExistence(paths, projectPath) {
40
45
  const create = [];
41
46
  const modify = [];
47
+ const test = [];
42
48
  for (const p of paths) {
49
+ if (p.startsWith('tests/')) {
50
+ test.push({ path: p, status: 'pending' });
51
+ continue;
52
+ }
43
53
  const full = join(projectPath, p);
44
54
  try {
45
55
  await stat(full);
@@ -49,7 +59,7 @@ async function categorizeByExistence(paths, projectPath) {
49
59
  create.push({ path: p, status: 'pending' });
50
60
  }
51
61
  }
52
- return { create, modify, test: [] };
62
+ return { create, modify, test };
53
63
  }
54
64
  /**
55
65
  * Attempt to extract file paths from spec body for auto-populating ## Technical.
@@ -27,6 +27,7 @@ export function buildUnifiedSpecContent(leanSpecBody, leanTechnicalBody) {
27
27
  // Split the generated technical body into its constituent sections so we can
28
28
  // inject only the ones the user did not provide.
29
29
  const generatedFiles = extractSection(technicalBodyRaw, 'Files');
30
+ const generatedImplementationNotes = extractSection(technicalBodyRaw, 'Implementation Notes');
30
31
  // Generated technical body always starts with `## Files`. If the user already
31
32
  // authored both, return the spec body untouched.
32
33
  if (userHasTechnical && userHasFiles) {
@@ -37,13 +38,21 @@ export function buildUnifiedSpecContent(leanSpecBody, leanTechnicalBody) {
37
38
  sectionsToAppend.push('## Technical', '');
38
39
  }
39
40
  if (!userHasFiles && generatedFiles !== null) {
40
- sectionsToAppend.push(generatedFiles);
41
+ sectionsToAppend.push(demoteTopLevelHeadings(generatedFiles));
42
+ }
43
+ if (!userHasTechnical && generatedImplementationNotes !== null) {
44
+ sectionsToAppend.push('', demoteTopLevelHeadings(generatedImplementationNotes));
41
45
  }
42
46
  if (sectionsToAppend.length === 0) {
43
47
  return leanSpecBody.endsWith('\n') ? leanSpecBody : `${leanSpecBody}\n`;
44
48
  }
45
49
  return `${leanSpecBody.trimEnd()}\n\n${sectionsToAppend.join('\n').trimEnd()}\n`;
46
50
  }
51
+ function demoteTopLevelHeadings(section) {
52
+ return section.replace(/^(#{2,6})(\s+\S.*)$/gm, (_match, hashes, rest) => {
53
+ return `${hashes}#${rest}`;
54
+ });
55
+ }
47
56
  function stripFencedCodeBlocks(body) {
48
57
  return body.replace(/```[\s\S]*?```/g, '').replace(/~~~[\s\S]*?~~~/g, '');
49
58
  }
@@ -2,8 +2,9 @@ import { appendArtifact, readArtifact } from '../handoff-artifacts/io.js';
2
2
  import { runSpecCompliance, } from './spec-compliance-runner.js';
3
3
  export async function writeImplementationReviewReport(input) {
4
4
  const specCompliance = await runSpecCompliance(input.spec, input.projectPath);
5
+ const reportScore = resolveExecutableValidationScore(input.score, specCompliance);
5
6
  const gates = buildGates({
6
- score: input.score,
7
+ score: reportScore,
7
8
  lintPassed: input.lintPassed,
8
9
  conventionRegression: input.conventionRegression ?? false,
9
10
  specCompliance,
@@ -25,7 +26,7 @@ export async function writeImplementationReviewReport(input) {
25
26
  reviewer,
26
27
  specCompliance: reportCompliance,
27
28
  minimalityReport: input.minimalityReport,
28
- score: input.score ?? undefined,
29
+ score: reportScore ?? undefined,
29
30
  completedAt: new Date().toISOString(),
30
31
  };
31
32
  try {
@@ -56,6 +57,9 @@ export async function writeImplementationReviewReport(input) {
56
57
  };
57
58
  }
58
59
  }
60
+ function resolveExecutableValidationScore(score, specCompliance) {
61
+ return specCompliance.perScenario.length > 0 ? specCompliance.dimensionScore : score;
62
+ }
59
63
  async function preserveUnobservedGates(input, nextGates) {
60
64
  const currentGateNames = new Set(nextGates.map((gate) => gate.name));
61
65
  const explicitlyObserved = new Set([
@@ -113,13 +113,29 @@ function handleClarification(_server, description, knowledge, autopilot, params)
113
113
  function hasFileEntries(files) {
114
114
  return files.create.length + files.modify.length + files.test.length > 0;
115
115
  }
116
+ function mergeTechnicalFiles(primary, fallback) {
117
+ return {
118
+ create: mergeFileEntries(primary.create, fallback.create),
119
+ modify: mergeFileEntries(primary.modify, fallback.modify),
120
+ test: mergeFileEntries(primary.test, fallback.test),
121
+ };
122
+ }
123
+ function mergeFileEntries(primary, fallback) {
124
+ const byPath = new Map();
125
+ for (const file of [...primary, ...fallback]) {
126
+ if (!byPath.has(file.path)) {
127
+ byPath.set(file.path, file);
128
+ }
129
+ }
130
+ return [...byPath.values()];
131
+ }
116
132
  async function resolveTechnicalFiles(input) {
117
133
  const generatedSource = [input.generatedTechnicalSection, input.generatedSpecBody]
118
134
  .filter((part) => part.trim().length > 0)
119
135
  .join('\n\n');
120
136
  const extracted = await extractFilesFromSpecBody(generatedSource, input.projectPath);
121
137
  if (extracted !== null && hasFileEntries(extracted)) {
122
- return extracted;
138
+ return mergeTechnicalFiles(extracted, input.autopilot.suggestedFiles);
123
139
  }
124
140
  if (input.fallbackReason !== undefined && hasFileEntries(input.autopilot.suggestedFiles)) {
125
141
  return input.autopilot.suggestedFiles;
@@ -687,6 +703,7 @@ export async function handleCreateSpec(inputParams, server) {
687
703
  filesToCreate: groundedTechnical.files.create,
688
704
  filesToModify: groundedTechnical.files.modify,
689
705
  filesToTest: groundedTechnical.files.test,
706
+ includeDecisionRequiredPlaceholder: spec.scope !== 'trivial',
690
707
  });
691
708
  // SPEC-709: write unified spec.md from origin — no separate technical.md.
692
709
  // The legacy two-file output is preserved by appending the technical body
@@ -6,7 +6,7 @@ import { dirname, join } from 'node:path';
6
6
  import { formatLintFailureDiagnostics, resolveProjectCommandPlan, runProjectCommandPlan, } from './validate-runtime.js';
7
7
  /** Allowlist: alphanumerics, spaces, and safe shell chars. Rejects ; | $ ` & < > */
8
8
  const SAFE_COMMAND_RE = /^[\w\s./:@~=-]+$/;
9
- const DEFAULT_LINT_CHECK_TIMEOUT_MS = 10_000;
9
+ const DEFAULT_LINT_CHECK_TIMEOUT_MS = 60_000;
10
10
  const LINT_CACHE_MAX_AGE_MS = 24 * 60 * 60 * 1000;
11
11
  function isSafeCommand(cmd) {
12
12
  return SAFE_COMMAND_RE.test(cmd);
@@ -0,0 +1,19 @@
1
+ interface ValidateStructuredCompatInput {
2
+ structured: Record<string, unknown>;
3
+ fieldsImplemented: number;
4
+ fieldsTotal: number;
5
+ matches: string[];
6
+ missing: string[];
7
+ extra: string[];
8
+ qualityIssues: unknown[];
9
+ conventionViolations: {
10
+ category: string;
11
+ file: string;
12
+ line?: number;
13
+ message: string;
14
+ severity: string;
15
+ }[];
16
+ }
17
+ export declare function applyValidateStructuredCompat(input: ValidateStructuredCompatInput): void;
18
+ export {};
19
+ //# sourceMappingURL=validate-structured-compat.d.ts.map
@@ -0,0 +1,14 @@
1
+ export function applyValidateStructuredCompat(input) {
2
+ const structured = input.structured;
3
+ structured.fieldsImplemented = input.fieldsImplemented;
4
+ structured.fieldsTotal = input.fieldsTotal;
5
+ structured.matches = input.matches;
6
+ structured.missing = input.missing;
7
+ structured.extra = input.extra;
8
+ structured.qualityIssues = input.qualityIssues;
9
+ const conventionCompliance = structured.conventionCompliance;
10
+ if (conventionCompliance !== undefined) {
11
+ conventionCompliance.violations = input.conventionViolations;
12
+ }
13
+ }
14
+ //# sourceMappingURL=validate-structured-compat.js.map
@@ -1,3 +1,4 @@
1
+ /* eslint-disable max-lines -- validate coordinates multiple legacy gate payloads in one tool. */
1
2
  // tools/validate.ts — Compare spec vs implementation (SPEC-595: elicitation on failures)
2
3
  // Uses the validator engine to check coverage, missing items, quality issues.
3
4
  import { elicitOrFallback, buildEnumSchema } from '../engine/elicitation/elicit-helper.js';
@@ -19,6 +20,7 @@ import { buildMinimalityReport } from './validate-minimality.js';
19
20
  import { buildValidateFailureFallback, validateStrictLayoutOrError } from './validate-helpers.js';
20
21
  import { runLintCheck } from './validate-lint.js';
21
22
  import { checkPlanuUncommittedChanges, runAssuranceGates } from './validate-assurance.js';
23
+ import { applyValidateStructuredCompat } from './validate-structured-compat.js';
22
24
  // Re-export for external use (SPEC-018)
23
25
  export { validateContractCompliance };
24
26
  // SPEC-595: server? param enables elicitation on failures
@@ -97,23 +99,24 @@ export async function handleValidate(args, server) {
97
99
  conventionRegression: regressionDetected,
98
100
  minimalityReport: minimalityReport ?? undefined,
99
101
  });
102
+ const effectiveResult = buildEffectiveValidationResult(result, validationReport.specCompliance);
100
103
  const qualityGateSummary = buildQualityGateSummary({
101
104
  conventionViolations,
102
105
  regressionDetected,
103
106
  lintPassed: lintCheck.passed,
104
107
  assurancePassed: assuranceGates.newCode.passed,
105
108
  minimalityBlocked: minimalityReport?.blocked === true,
106
- score: result.score,
109
+ score: effectiveResult.score,
107
110
  });
108
111
  const output = {
109
112
  specId,
110
113
  title: spec.title,
111
- score: result.score,
112
- fieldsImplemented: result.fieldsImplemented,
113
- fieldsTotal: result.fieldsTotal,
114
- matches: result.matches,
115
- missing: result.missing,
116
- extra: result.extra,
114
+ score: effectiveResult.score,
115
+ fieldsImplemented: effectiveResult.fieldsImplemented,
116
+ fieldsTotal: effectiveResult.fieldsTotal,
117
+ matches: effectiveResult.matches,
118
+ missing: effectiveResult.missing,
119
+ extra: effectiveResult.extra,
117
120
  qualityIssues: result.qualityIssues.map((issue) => ({
118
121
  file: issue.file,
119
122
  line: issue.line,
@@ -200,7 +203,7 @@ export async function handleValidate(args, server) {
200
203
  }
201
204
  // Auto-suggest corrective actions based on validation results
202
205
  // score is null when no criteria can be extracted — treat as 0 for comparisons.
203
- const scoreValue = result.score ?? 0;
206
+ const scoreValue = effectiveResult.score ?? 0;
204
207
  const suggestions = [];
205
208
  if (scoreValue < 50) {
206
209
  suggestions.push('Critical: Many acceptance criteria not implemented. Review spec requirements.');
@@ -214,8 +217,8 @@ export async function handleValidate(args, server) {
214
217
  if (!dod.isDone && spec.status === 'implementing') {
215
218
  suggestions.push('Implementation incomplete. Review DoD checklist before marking as done.');
216
219
  }
217
- if (result.missing.length > 0) {
218
- suggestions.push(`${result.missing.length} criteria still missing. Run detect_drift to identify gaps.`);
220
+ if (effectiveResult.missing.length > 0) {
221
+ suggestions.push(`${effectiveResult.missing.length} criteria still missing. Run detect_drift to identify gaps.`);
219
222
  }
220
223
  if (graphCoverage.gaps.length > 0) {
221
224
  suggestions.push(`${graphCoverage.gaps.length} graph-backed coverage gap(s) found without broad repository scan.`);
@@ -241,8 +244,8 @@ export async function handleValidate(args, server) {
241
244
  specId,
242
245
  input: { specType: spec.type, scope: spec.scope },
243
246
  outcome: {
244
- score: result.score,
245
- missingCount: result.missing.length,
247
+ score: effectiveResult.score,
248
+ missingCount: effectiveResult.missing.length,
246
249
  qualityIssueCount: result.qualityIssues.length,
247
250
  dorPassed: dor.isReady,
248
251
  dodPassed: dod.isDone,
@@ -259,7 +262,7 @@ export async function handleValidate(args, server) {
259
262
  type: 'validate',
260
263
  specId,
261
264
  input: { trigger: 'auto-quality-learn', rules: topIssueRules },
262
- outcome: { score: result.score, antipatterns: topIssueRules },
265
+ outcome: { score: effectiveResult.score, antipatterns: topIssueRules },
263
266
  delta: { qualityGap: 100 - scoreValue },
264
267
  timestamp: new Date().toISOString(),
265
268
  }).catch((err) => {
@@ -275,10 +278,10 @@ export async function handleValidate(args, server) {
275
278
  ? '\n\n📁 HTML reports updated — commit to save: `git add planu/specs/ planu/conventions.json && git commit -m "chore(planu): update reports"`'
276
279
  : '';
277
280
  // SPEC-512: Compact output — only surface failures, cap to 200 tokens
278
- const totalCriteria = result.fieldsTotal;
279
- const passedCount = result.fieldsImplemented;
281
+ const totalCriteria = effectiveResult.fieldsTotal;
282
+ const passedCount = effectiveResult.fieldsImplemented;
280
283
  const failedCount = totalCriteria - passedCount;
281
- const indeterminate = result.indeterminate ?? [];
284
+ const indeterminate = effectiveResult.indeterminate ?? [];
282
285
  const effectiveScore = qualityGateSummary.effectiveScore ?? scoreValue;
283
286
  const compactText = buildCompactValidateText({
284
287
  score: effectiveScore,
@@ -286,20 +289,20 @@ export async function handleValidate(args, server) {
286
289
  failedCount,
287
290
  totalCriteria,
288
291
  qualityFailures: qualityGateSummary.failures,
289
- missing: result.missing,
292
+ missing: effectiveResult.missing,
290
293
  indeterminate,
291
294
  commitReminder,
292
295
  planuReminder,
293
296
  });
294
297
  const graphText = formatGraphCoverageText(graphCoverage);
295
298
  // SPEC-512: Compact structuredContent — essential fields only at top level
296
- const compactSummary = buildCompactSummary(specId, result.score === null ? null : effectiveScore, passedCount, failedCount + (qualityGateSummary.passed ? 0 : 1), totalCriteria);
299
+ const compactSummary = buildCompactSummary(specId, effectiveResult.score === null ? null : effectiveScore, passedCount, failedCount + (qualityGateSummary.passed ? 0 : 1), totalCriteria);
297
300
  const structuredBase = buildValidateStructuredContent({
298
301
  specId,
299
302
  title: spec.title,
300
- score: result.score === null ? null : effectiveScore,
303
+ score: effectiveResult.score === null ? null : effectiveScore,
301
304
  compactSummary,
302
- result,
305
+ result: effectiveResult,
303
306
  indeterminate,
304
307
  dor,
305
308
  dod,
@@ -382,7 +385,6 @@ function buildQualityGateSummary(input) {
382
385
  const blockingConventionViolations = input.conventionViolations.filter((violation) => violation.severity === 'critical');
383
386
  const failures = [
384
387
  ...(input.regressionDetected ? ['convention-regression'] : []),
385
- ...(blockingConventionViolations.length > 0 ? ['blocking-convention-violations'] : []),
386
388
  ...(!input.lintPassed ? ['lint'] : []),
387
389
  ...(!input.assurancePassed ? ['assurance'] : []),
388
390
  ...(input.minimalityBlocked ? ['minimality'] : []),
@@ -398,6 +400,40 @@ function buildQualityGateSummary(input) {
398
400
  minimalityPassed: !input.minimalityBlocked,
399
401
  };
400
402
  }
403
+ function buildExecutableCoverage(specCompliance) {
404
+ if (specCompliance.scenarios.length === 0) {
405
+ return null;
406
+ }
407
+ const matches = specCompliance.scenarios
408
+ .filter((scenario) => scenario.verdict === 'pass')
409
+ .map((scenario) => scenario.title);
410
+ const missing = specCompliance.scenarios
411
+ .filter((scenario) => scenario.verdict !== 'pass')
412
+ .map((scenario) => scenario.title);
413
+ return {
414
+ score: specCompliance.score,
415
+ fieldsImplemented: matches.length,
416
+ fieldsTotal: specCompliance.scenarios.length,
417
+ matches,
418
+ missing,
419
+ indeterminate: [],
420
+ };
421
+ }
422
+ function buildEffectiveValidationResult(result, specCompliance) {
423
+ const executableCoverage = buildExecutableCoverage(specCompliance);
424
+ if (executableCoverage === null) {
425
+ return result;
426
+ }
427
+ return {
428
+ ...result,
429
+ score: executableCoverage.score,
430
+ fieldsImplemented: executableCoverage.fieldsImplemented,
431
+ fieldsTotal: executableCoverage.fieldsTotal,
432
+ matches: executableCoverage.matches,
433
+ missing: executableCoverage.missing,
434
+ indeterminate: executableCoverage.indeterminate,
435
+ };
436
+ }
401
437
  function buildCompactSummary(specId, score, passedCount, failedCount, totalCriteria) {
402
438
  return {
403
439
  specId,
@@ -487,6 +523,7 @@ function buildValidateStructuredContent(args) {
487
523
  conventionCompliance: {
488
524
  totalViolations: args.conventionCompliance.totalViolations,
489
525
  regressionDetected: args.conventionCompliance.regressionDetected,
526
+ violations: args.conventionCompliance.violations,
490
527
  violationsTop5: args.conventionCompliance.violations.slice(0, 5),
491
528
  },
492
529
  lintCheck: {
@@ -522,6 +559,16 @@ function buildValidateStructuredContent(args) {
522
559
  });
523
560
  const lintCheck = structured.lintCheck;
524
561
  lintCheck.output = args.lintCheck.output ?? '';
562
+ applyValidateStructuredCompat({
563
+ structured,
564
+ fieldsImplemented: args.compactSummary.passed,
565
+ fieldsTotal: args.compactSummary.total,
566
+ matches: args.result.matches,
567
+ missing: args.result.missing,
568
+ extra: args.result.extra,
569
+ qualityIssues: args.qualityIssues,
570
+ conventionViolations: args.conventionCompliance.violations,
571
+ });
525
572
  return structured;
526
573
  }
527
574
  function buildCompactValidateText(args) {
@@ -132,6 +132,7 @@ export interface LeanTechnicalInput {
132
132
  filesToCreate?: LeanFileEntry[];
133
133
  filesToModify?: LeanFileEntry[];
134
134
  filesToTest?: LeanFileEntry[];
135
+ includeDecisionRequiredPlaceholder?: boolean;
135
136
  }
136
137
  export interface ImplementationContractInput {
137
138
  description: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planu/cli",
3
- "version": "4.10.9",
3
+ "version": "4.10.11",
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.9",
38
- "@planu/core-darwin-x64": "4.10.9",
39
- "@planu/core-linux-arm64-gnu": "4.10.9",
40
- "@planu/core-linux-arm64-musl": "4.10.9",
41
- "@planu/core-linux-x64-gnu": "4.10.9",
42
- "@planu/core-linux-x64-musl": "4.10.9",
43
- "@planu/core-win32-arm64-msvc": "4.10.9",
44
- "@planu/core-win32-x64-msvc": "4.10.9"
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"
45
45
  },
46
46
  "engines": {
47
47
  "node": ">=24.0.0"
@@ -144,7 +144,7 @@
144
144
  "@secretlint/secretlint-rule-preset-recommend": "^13.0.2",
145
145
  "@stryker-mutator/core": "^9.6.1",
146
146
  "@stryker-mutator/vitest-runner": "^9.6.1",
147
- "@supabase/supabase-js": "^2.110.0",
147
+ "@supabase/supabase-js": "^2.110.1",
148
148
  "@types/node": "^26.1.0",
149
149
  "@vitejs/plugin-vue": "^6.0.7",
150
150
  "@vitest/coverage-v8": "^4.1.10",
@@ -156,7 +156,7 @@
156
156
  "happy-dom": "^20.10.6",
157
157
  "husky": "^9.1.7",
158
158
  "javascript-obfuscator": "^5.4.3",
159
- "knip": "^6.24.0",
159
+ "knip": "^6.25.0",
160
160
  "lint-staged": "^17.0.8",
161
161
  "madge": "^8.0.0",
162
162
  "prettier": "^3.9.4",
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.9",
4
+ "version": "4.10.11",
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.10.9",
5
+ "version": "4.10.11",
6
6
  "icon": "assets/plugin/icon.svg",
7
7
  "command": [
8
8
  "npx",