@planu/cli 4.11.1 → 4.11.3

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 (155) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/config/elicitation-questions.json +5 -17
  3. package/dist/engine/cascade-hooks/registry.js +0 -2
  4. package/dist/engine/challenge-scenarios-loader.d.ts +3 -3
  5. package/dist/engine/compliance-injector.d.ts +3 -3
  6. package/dist/engine/dashboard/kanban-logic.d.ts +1 -1
  7. package/dist/engine/doc-generator/portal/risk-matrix-svg.d.ts +1 -1
  8. package/dist/engine/doc-generator/proposal/proposal-generator.d.ts +1 -1
  9. package/dist/engine/elicitation/answer-extractor.d.ts +1 -4
  10. package/dist/engine/elicitation/answer-extractor.js +197 -68
  11. package/dist/engine/elicitation/decision-gap-detector.js +59 -79
  12. package/dist/engine/elicitation/index.d.ts +0 -1
  13. package/dist/engine/elicitation/index.js +0 -1
  14. package/dist/engine/elicitation/question-generator.js +10 -3
  15. package/dist/engine/elicitation/question-grounding-gate.js +83 -34
  16. package/dist/engine/evidence-gates/lifecycle-gate.js +27 -14
  17. package/dist/engine/evidence-index/index-builder.js +7 -2
  18. package/dist/engine/frontmatter-sha/frozen-fields.d.ts +1 -1
  19. package/dist/engine/handoff-artifacts/schemas/red-tests-handoff.d.ts +2 -2
  20. package/dist/engine/handoff-artifacts/schemas.d.ts +22 -22
  21. package/dist/engine/human-summary.js +3 -3
  22. package/dist/engine/implementation-contract/common.d.ts +1 -1
  23. package/dist/engine/implementation-contract/renderer.js +17 -39
  24. package/dist/engine/keyword-matcher/stopwords.d.ts +2 -2
  25. package/dist/engine/lifecycle-hints.d.ts +1 -1
  26. package/dist/engine/lifecycle-hints.js +13 -29
  27. package/dist/engine/provider-adapters/adapters/claude.d.ts +1 -1
  28. package/dist/engine/provider-adapters/adapters/gemini.d.ts +1 -1
  29. package/dist/engine/provider-adapters/adapters/gpt4.d.ts +1 -1
  30. package/dist/engine/provider-adapters/adapters/markdown.d.ts +1 -1
  31. package/dist/engine/safety/atomic-write-file.d.ts +1 -1
  32. package/dist/engine/safety/cross-process-lock.d.ts +1 -1
  33. package/dist/engine/safety/path-sanitizer.d.ts +1 -1
  34. package/dist/engine/security-analyzer/profile-detector.js +48 -16
  35. package/dist/engine/spec-format/copula-blacklist.d.ts +2 -2
  36. package/dist/engine/spec-format/lean-spec-generator.d.ts +2 -2
  37. package/dist/engine/spec-format/lean-spec-generator.js +9 -10
  38. package/dist/engine/spec-format/metadata-value-policy.d.ts +20 -20
  39. package/dist/engine/spec-generator/fallback-generator.js +13 -20
  40. package/dist/engine/spec-grounding/contract.d.ts +5 -0
  41. package/dist/engine/spec-grounding/contract.js +10 -6
  42. package/dist/engine/spec-migrator/lean-migration.js +9 -16
  43. package/dist/engine/text-signal-boundaries.js +45 -12
  44. package/dist/engine/universal-rules/rules/planu-modes.js +0 -2
  45. package/dist/engine/web-fetcher/stack-advisor.d.ts +2 -2
  46. package/dist/engine/web-fetcher/stack-advisor.js +78 -17
  47. package/dist/engine/well-known/manifest-schema.d.ts +33 -33
  48. package/dist/engine/workers/schema.d.ts +3 -3
  49. package/dist/hosts/claude-code/ux/mcp-resources.d.ts +4 -4
  50. package/dist/hosts/codex/rest-shim.d.ts +1 -1
  51. package/dist/native/lightweight-command-catalog.d.ts +30 -30
  52. package/dist/server/openapi-spec.d.ts +132 -132
  53. package/dist/tools/challenge-spec/challenge-report.d.ts +13 -0
  54. package/dist/tools/challenge-spec/challenge-report.js +55 -0
  55. package/dist/tools/challenge-spec/event-challenge-scenarios.js +8 -27
  56. package/dist/tools/challenge-spec/resilience-challenge-scenarios-a.js +16 -32
  57. package/dist/tools/challenge-spec/resilience-challenge-scenarios-b.js +15 -20
  58. package/dist/tools/challenge-spec/scenario-collector.d.ts +13 -0
  59. package/dist/tools/challenge-spec/scenario-collector.js +86 -0
  60. package/dist/tools/challenge-spec/scenarios-data.js +10 -9
  61. package/dist/tools/challenge-spec/scenarios-failure.js +38 -54
  62. package/dist/tools/challenge-spec/scenarios-scale.js +14 -15
  63. package/dist/tools/challenge-spec/scenarios-security.d.ts +1 -1
  64. package/dist/tools/challenge-spec/scenarios-security.js +18 -11
  65. package/dist/tools/challenge-spec/scenarios-utils.d.ts +31 -5
  66. package/dist/tools/challenge-spec/scenarios-utils.js +102 -6
  67. package/dist/tools/challenge-spec/security-challenge-scenarios.d.ts +1 -1
  68. package/dist/tools/challenge-spec/security-challenge-scenarios.js +20 -25
  69. package/dist/tools/challenge-spec.js +46 -124
  70. package/dist/tools/clarify-requirements/multiple-choice.js +1 -1
  71. package/dist/tools/clarify-requirements/questions-context.js +11 -11
  72. package/dist/tools/clarify-requirements/questions.js +3 -23
  73. package/dist/tools/clarify-requirements.js +38 -0
  74. package/dist/tools/compliance-gate-handler.d.ts +1 -1
  75. package/dist/tools/create-spec/autopilot-analyzer.d.ts +1 -1
  76. package/dist/tools/create-spec/autopilot-analyzer.js +29 -54
  77. package/dist/tools/create-spec/post-creation.js +23 -4
  78. package/dist/tools/create-spec.js +51 -168
  79. package/dist/tools/delete-first/suggest-deletions.d.ts +1 -1
  80. package/dist/tools/elicit-requirements-handler.js +20 -12
  81. package/dist/tools/generate-batch-script.d.ts +2 -2
  82. package/dist/tools/init-project/rules-generator.js +0 -1
  83. package/dist/tools/oauth-handler.d.ts +1 -1
  84. package/dist/tools/register-sdd-tools.d.ts +1 -1
  85. package/dist/tools/register-spec-tools/core-spec-tools.js +11 -2
  86. package/dist/tools/render-spec-for-provider.d.ts +2 -2
  87. package/dist/tools/scan-orphan-spec-refs.d.ts +6 -6
  88. package/dist/tools/schemas/agents.d.ts +7 -7
  89. package/dist/tools/schemas/analysis.d.ts +11 -11
  90. package/dist/tools/schemas/delete.d.ts +5 -5
  91. package/dist/tools/schemas/github.d.ts +4 -4
  92. package/dist/tools/schemas/ide-config.d.ts +12 -12
  93. package/dist/tools/schemas/infra.d.ts +9 -9
  94. package/dist/tools/schemas/lifecycle.d.ts +50 -50
  95. package/dist/tools/schemas/llm-provider-schemas.d.ts +7 -7
  96. package/dist/tools/schemas/plugins-schemas.d.ts +12 -12
  97. package/dist/tools/schemas/project-graph.d.ts +6 -6
  98. package/dist/tools/schemas/rbac.d.ts +2 -2
  99. package/dist/tools/schemas/registry.d.ts +3 -3
  100. package/dist/tools/schemas/runtime-security.d.ts +2 -2
  101. package/dist/tools/schemas/session.d.ts +2 -2
  102. package/dist/tools/schemas/spec.d.ts +15 -15
  103. package/dist/tools/schemas/token-intelligence.d.ts +6 -6
  104. package/dist/tools/schemas/token-optimization.d.ts +2 -2
  105. package/dist/tools/schemas/workers-schema.d.ts +4 -4
  106. package/dist/tools/skill-bootstrap-handler.d.ts +7 -7
  107. package/dist/tools/suggest-stack.js +49 -13
  108. package/dist/tools/tool-registry/deprecated-stubs.d.ts +1 -1
  109. package/dist/tools/triage-request.d.ts +3 -3
  110. package/dist/tools/update-status/dod-gates.d.ts +8 -0
  111. package/dist/tools/update-status/dod-gates.js +68 -43
  112. package/dist/tools/update-status/file-sync.d.ts +1 -1
  113. package/dist/tools/update-status/file-sync.js +10 -45
  114. package/dist/tools/update-status/index.js +133 -97
  115. package/dist/tools/update-status/qa-gate.js +26 -0
  116. package/dist/tools/update-status/response-builder.d.ts +1 -1
  117. package/dist/tools/update-status/response-builder.js +11 -19
  118. package/dist/tools/update-status/transition-guard.d.ts +2 -2
  119. package/dist/tools/update-status/transition-guard.js +11 -8
  120. package/dist/tools/update-status-actions.d.ts +0 -1
  121. package/dist/tools/update-status-actions.js +10 -26
  122. package/dist/tools/update-status-convention-gate.d.ts +1 -6
  123. package/dist/tools/update-status-convention-gate.js +9 -33
  124. package/dist/tools/validate-lint.d.ts +1 -1
  125. package/dist/tools/validate-lint.js +39 -1
  126. package/dist/types/docs.d.ts +0 -4
  127. package/dist/types/env.d.ts +0 -4
  128. package/dist/types/estimation.d.ts +0 -2
  129. package/dist/types/multi-teammate-review.d.ts +4 -4
  130. package/dist/types/plugin-install.d.ts +7 -7
  131. package/dist/types/spec/core.d.ts +20 -0
  132. package/dist/types/spec/inputs.d.ts +1 -1
  133. package/dist/types/stack/index.d.ts +1 -1
  134. package/dist/types/stack/recommend.d.ts +10 -0
  135. package/package.json +21 -18
  136. package/planu-native.json +1 -1
  137. package/planu-plugin.json +1 -1
  138. package/src/i18n/messages/en.json +0 -72
  139. package/src/i18n/messages/es.json +0 -72
  140. package/src/i18n/messages/pt.json +0 -72
  141. package/dist/config/elicitation-dimensions.json +0 -51
  142. package/dist/engine/actuals-estimator.d.ts +0 -14
  143. package/dist/engine/actuals-estimator.js +0 -64
  144. package/dist/engine/cascade-hooks/hooks/living-specs.hook.d.ts +0 -3
  145. package/dist/engine/cascade-hooks/hooks/living-specs.hook.js +0 -19
  146. package/dist/engine/elicitation/non-interactive-defaults.d.ts +0 -9
  147. package/dist/engine/elicitation/non-interactive-defaults.js +0 -28
  148. package/dist/engine/elicitation/option-builder.d.ts +0 -9
  149. package/dist/engine/elicitation/option-builder.js +0 -85
  150. package/dist/engine/progress-writer.d.ts +0 -10
  151. package/dist/engine/progress-writer.js +0 -63
  152. package/dist/tools/clarify-requirements/interview-mode.d.ts +0 -42
  153. package/dist/tools/clarify-requirements/interview-mode.js +0 -342
  154. package/dist/tools/update-status/mode-hints.d.ts +0 -4
  155. package/dist/tools/update-status/mode-hints.js +0 -24
@@ -137,7 +137,23 @@ const TECH_STACK_PATTERNS = [
137
137
  ];
138
138
  /** Returns true if description references any known technology or version. */
139
139
  function hasTechStackReferences(description) {
140
- return TECH_STACK_PATTERNS.some((p) => p.test(description));
140
+ return TECH_STACK_PATTERNS.some((pattern) => hasAffirmedPattern(description, pattern));
141
+ }
142
+ const NEGATIVE_CONTEXT_RE = /\b(?:no|not|never|without|exclude|excluding|forbid|forbidden|remove|avoid|must not|should not|do not|does not|don't|doesn't|out[- ]of[- ]scope|unsupported|unrelated|false positive)\b/i;
143
+ const INCIDENTAL_CONTEXT_RE = /\b(?:example|for example|e\.g\.|quoted|regression|bug caused by|incorrectly inferred|must not infer)\b/i;
144
+ function hasAffirmedPattern(description, pattern) {
145
+ const withoutQuotedText = description
146
+ .replace(/```[\s\S]*?```/g, ' ')
147
+ .replace(/`[^`]+`/g, ' ')
148
+ .replace(/"[^"]+"|'[^']+'/g, ' ');
149
+ return withoutQuotedText.split(/(?:\r?\n|[.;!?]+)/).some((segment) => {
150
+ const match = new RegExp(pattern.source, pattern.flags.replace('g', '')).exec(segment);
151
+ if (!match) {
152
+ return false;
153
+ }
154
+ const prefix = segment.slice(0, match.index);
155
+ return !NEGATIVE_CONTEXT_RE.test(prefix) && !INCIDENTAL_CONTEXT_RE.test(prefix);
156
+ });
141
157
  }
142
158
  // === Serverless platform detection ===
143
159
  const SERVERLESS_PLATFORMS = ['vercel', 'netlify', 'lambda', 'cloudflare-workers', 'deno-deploy'];
@@ -185,12 +201,12 @@ export async function generatePostCreationSuggestions(projectPath, description,
185
201
  if (isServerlessKnowledge(knowledge)) {
186
202
  suggestions.push({
187
203
  tool: 'scan_project',
188
- reason: '[SERVERLESS WARNING] Module-level mutable state resets on cold starts. Avoid in-memory caches, singletons, or shared mutable variables for cross-request data — use Redis or a database instead.',
204
+ reason: '[SERVERLESS WARNING] Module-level mutable state may reset on cold starts. Confirm the required state lifetime and storage strategy before implementation.',
189
205
  });
190
206
  }
191
- // Item 4: Suggest templates when description matches known patterns
207
+ // Item 4: Suggest templates only when the user explicitly requests one.
192
208
  for (const { pattern, category } of TEMPLATE_PATTERNS) {
193
- if (pattern.test(description)) {
209
+ if (hasAffirmedPattern(description, pattern) && explicitlyRequestsTemplate(description)) {
194
210
  suggestions.push({
195
211
  tool: 'apply_template',
196
212
  reason: `Description matches "${category}" pattern. Consider using an ${category} template.`,
@@ -205,6 +221,9 @@ export async function generatePostCreationSuggestions(projectPath, description,
205
221
  });
206
222
  return suggestions;
207
223
  }
224
+ function explicitlyRequestsTemplate(description) {
225
+ return /\b(?:use|apply|start from|base (?:this|it) on)\s+(?:an?\s+)?(?:[\w-]+\s+)?template\b/i.test(description);
226
+ }
208
227
  /** Formats a structured post-creation suggestion for human-readable next steps. */
209
228
  export function formatPostCreationSuggestion(suggestion) {
210
229
  const templateSuffix = suggestion.templateCategory !== undefined ? ` (${suggestion.templateCategory})` : '';
@@ -7,14 +7,12 @@ import { formatSuccess, addNextSteps, toolResult, interactiveResult } from './re
7
7
  import { writeFile, mkdir, rm, readFile, stat as fsStat, rename, link as hardLink, } from 'node:fs/promises';
8
8
  import { createHash, randomUUID } from 'node:crypto';
9
9
  import { dirname as pathDirname, isAbsolute as pathIsAbsolute, join as pathJoin, relative as pathRelative, sep as pathSeparator, } from 'node:path';
10
- import { estimateSpec } from '../engine/estimator.js';
11
10
  import { checkSpecReadiness } from '../engine/readiness-checker.js';
12
11
  import { buildSpecContext, buildSplitResult } from './create-spec/spec-builder.js';
13
12
  import { validateConstitution } from './create-spec/constitution-validator.js';
14
13
  import { setupGitBranch, checkContradictions, fireSpecCreatedHook, generatePostCreationSuggestions, formatPostCreationSuggestion, runAutopilotAsync, getAsyncAnalysisPath, } from './create-spec/post-creation.js';
15
14
  import { notifyStoreChange } from '../engine/doc-generator/portal/regen-hook.js';
16
15
  import { compactObj } from '../engine/compact-obj.js';
17
- import { buildCreateSpecSummary } from '../engine/human-summary.js';
18
16
  import { runAutoPostCreatePipeline } from './create-spec/auto-pipeline.js';
19
17
  import { extractCriteria, generateLeanSpecContent, } from '../engine/spec-format/lean-spec-generator.js';
20
18
  import { generateLeanTechnicalContent, } from '../engine/spec-format/lean-technical-generator.js';
@@ -22,7 +20,7 @@ import { extractFilesFromSpecBody } from '../engine/spec-format/technical-md-pop
22
20
  import { buildUnifiedSpecContent } from '../engine/spec-format/unified-spec-builder.js';
23
21
  import { appendImplementationContractIfMissing } from '../engine/implementation-contract/index.js';
24
22
  import { validateEnglishOnlySpecText } from '../engine/spec-language/english-only.js';
25
- import { ApiKeyResolver, FallbackGenerator, OpusGenerator, } from '../engine/spec-generator/index.js';
23
+ import { FallbackGenerator } from '../engine/spec-generator/index.js';
26
24
  import { analyzeProjectForSpec, getEmptyAutopilotResult, } from './create-spec/autopilot-analyzer.js';
27
25
  import { AutopilotSummaryCollector } from '../engine/autopilot/summary-collector.js';
28
26
  import { trackCost } from '../engine/cost-tracking/operation-tracker.js';
@@ -33,10 +31,7 @@ import { resolveProjectIdOrAutoDetect } from './resolve-project-id.js';
33
31
  import { hashProjectPath } from '../storage/base-store.js';
34
32
  import { findSimilarSpecs } from '../engine/spec-searcher.js';
35
33
  import { scoreSpecQuality } from '../engine/spec-quality-scorer.js';
36
- import { enrichEstimationWithVelocity } from './create-spec/adapters/velocity-estimation.js';
37
- import { enrichEstimationWithCalibration } from './create-spec/adapters/calibration-estimation.js';
38
34
  import { runPriorDecisionsHint } from './create-spec/adapters/prior-decisions-hint.js';
39
- import { suggestOutOfScope } from '../engine/scope-boundaries/index.js';
40
35
  import { adviseSimilarSpecs } from '../engine/complexity-budget/index.js';
41
36
  import { issuePlannerToken } from '../engine/reviewer-tokens/issuer.js';
42
37
  import { generateInteractiveQuestions } from './create-spec/question-generator.js';
@@ -83,31 +78,11 @@ function handleClarification(_server, description, knowledge, autopilot, params)
83
78
  return null;
84
79
  }
85
80
  const generatedQuestions = generateInteractiveQuestions(description, knowledge);
86
- const questions = generatedQuestions.length > 0
87
- ? generatedQuestions
88
- : [
89
- {
90
- question: 'What specific behavior should this spec deliver?',
91
- header: 'Scope',
92
- options: [
93
- {
94
- label: 'User workflow (Recommended)',
95
- description: 'Focus the spec on the end-to-end user behavior and completion state.',
96
- },
97
- {
98
- label: 'Backend behavior',
99
- description: 'Focus the spec on API, persistence, jobs, or integration behavior.',
100
- },
101
- {
102
- label: 'Cleanup/fix',
103
- description: 'Focus the spec on removing stale behavior or fixing a known bug.',
104
- },
105
- ],
106
- multiSelect: false,
107
- },
108
- ];
81
+ if (generatedQuestions.length === 0) {
82
+ return null;
83
+ }
109
84
  return {
110
- earlyReturn: interactiveResult(questions, 'The description needs more detail before a spec can be created. Answer the questions, then retry create_spec with clarificationAnswers or an enriched description.', 'universal'),
85
+ earlyReturn: interactiveResult(generatedQuestions, 'The description needs more detail before a spec can be created. Answer the questions, then retry create_spec with clarificationAnswers or an enriched description.', 'universal'),
111
86
  };
112
87
  }
113
88
  function hasFileEntries(files) {
@@ -135,10 +110,9 @@ async function resolveTechnicalFiles(input) {
135
110
  .join('\n\n');
136
111
  const extracted = await extractFilesFromSpecBody(generatedSource, input.projectPath);
137
112
  if (extracted !== null && hasFileEntries(extracted)) {
138
- return mergeTechnicalFiles(extracted, input.autopilot.suggestedFiles);
139
- }
140
- if (input.fallbackReason !== undefined && hasFileEntries(input.autopilot.suggestedFiles)) {
141
- return input.autopilot.suggestedFiles;
113
+ return input.fallbackReason === undefined
114
+ ? mergeTechnicalFiles(extracted, input.autopilot.suggestedFiles)
115
+ : extracted;
142
116
  }
143
117
  return { create: [], modify: [], test: [] };
144
118
  }
@@ -161,8 +135,9 @@ async function groundTechnicalFiles(input) {
161
135
  const pathInUserInput = userInput.includes(file.path.toLowerCase());
162
136
  const exists = await pathExists(pathJoin(input.projectPath, file.path));
163
137
  const fromAutopilot = autopilotPaths.has(file.path);
164
- const derivedTest = section === 'test' && isDerivedFromGroundedSource(file.path, input.files.modify);
165
- if (pathInUserInput || exists || fromAutopilot || derivedTest) {
138
+ const derivedTest = section === 'test' && isDerivedFromGroundedSource(file.path, grounded.modify);
139
+ const directProjectEvidence = fromAutopilot && exists;
140
+ if (pathInUserInput || directProjectEvidence || derivedTest) {
166
141
  grounded[section].push(file);
167
142
  records.push({
168
143
  path: file.path,
@@ -170,8 +145,9 @@ async function groundTechnicalFiles(input) {
170
145
  source: pathInUserInput ? 'user_input' : 'project_evidence',
171
146
  evidence: [
172
147
  ...(pathInUserInput ? ['create_spec.description'] : []),
173
- ...(exists ? [`file:${file.path}`] : []),
174
- ...(fromAutopilot ? ['autopilot.suggestedFiles'] : []),
148
+ ...(directProjectEvidence
149
+ ? [`file:${file.path}`, 'autopilot.semantic-file-match']
150
+ : []),
175
151
  ...(derivedTest ? ['derived-test-path'] : []),
176
152
  ],
177
153
  confidence: pathInUserInput || exists ? 'high' : 'medium',
@@ -244,24 +220,9 @@ function buildAdvisoryCompat() {
244
220
  canonicalField: 'advisorySignals',
245
221
  };
246
222
  }
247
- /** SPEC-612: resolve outOfScope items (provided by user, or auto-suggested from description). */
248
- function resolveOutOfScope(description, provided) {
249
- if (provided !== undefined && provided.length > 0) {
250
- return { items: provided, message: null };
251
- }
252
- try {
253
- const auto = suggestOutOfScope(description).allSuggestions.slice(0, 3);
254
- if (auto.length > 0) {
255
- return {
256
- items: auto,
257
- message: `Auto-suggested outOfScope items based on description keywords: ${auto.join(', ')}`,
258
- };
259
- }
260
- }
261
- catch {
262
- /* best-effort */
263
- }
264
- return { items: [], message: null };
223
+ /** Persist only out-of-scope items supplied explicitly by the caller. */
224
+ function resolveOutOfScope(provided) {
225
+ return provided?.filter((item) => item.trim().length > 0) ?? [];
265
226
  }
266
227
  /** SPEC-614 AC4: suggest complexity category based on similar historical specs (best-effort). */
267
228
  async function runComplexityAdvice(projectId, currentSpecId, tags, targetCriteriaCount) {
@@ -887,8 +848,6 @@ export async function handleCreateSpec(inputParams, server) {
887
848
  }
888
849
  params = clarificationResult.params;
889
850
  }
890
- // Create spec directory and write lean files (SPEC-461)
891
- await measureStep('mkdir-specDir', () => mkdir(specDir, { recursive: true }));
892
851
  const filteredCriteria = filterGroundedCriteria(autopilot.suggestedCriteria);
893
852
  const technologyContract = await readTechnologySelectionContract(params.projectPath ?? '');
894
853
  const contractNote = technologyContract
@@ -909,10 +868,7 @@ export async function handleCreateSpec(inputParams, server) {
909
868
  .filter(Boolean)
910
869
  .join('\n')
911
870
  : '';
912
- const anthropicKey = await new ApiKeyResolver().resolveAnthropicKey(params.projectPath ?? '');
913
- const specGenerator = anthropicKey !== undefined
914
- ? new OpusGenerator({ apiKey: anthropicKey })
915
- : new FallbackGenerator();
871
+ const specGenerator = new FallbackGenerator();
916
872
  const generatedSpec = await measureStep('generateSpecBody', () => specGenerator.generate({
917
873
  title: spec.title,
918
874
  description: `${description}${contractNote}`,
@@ -933,18 +889,32 @@ export async function handleCreateSpec(inputParams, server) {
933
889
  const groundingCriteria = buildCriterionGroundingRecords({
934
890
  criteria: [...baseCriteria, ...filteredCriteria],
935
891
  userInput: description,
936
- projectEvidence: [
937
- ...autopilot.detectedPatterns.map((pattern) => `detected-pattern:${pattern}`),
938
- ...autopilot.suggestedFiles.modify.map((file) => `file:${file.path}`),
939
- ...autopilot.suggestedFiles.create.map((file) => `file:${file.path}`),
940
- ...autopilot.suggestedFiles.test.map((file) => `file:${file.path}`),
941
- ],
942
892
  generatedEvidence: [
943
893
  generatedSpec.generation.modelId ?? generatedSpec.generation.method,
944
894
  ],
945
895
  });
946
896
  const contractCriteria = getContractCriteria(groundingCriteria);
947
897
  const advisoryCriteria = getAdvisoryCriteria(groundingCriteria).map((record) => record.text);
898
+ if (contractCriteria.length === 0) {
899
+ return {
900
+ ok: false,
901
+ earlyReturn: {
902
+ content: [
903
+ {
904
+ type: 'text',
905
+ text: 'create_spec did not persist a spec because no acceptance criterion was grounded in the user request. ' +
906
+ 'Needs decision: provide at least one explicit, verifiable criterion as a checkbox or Given/When/Then outcome.',
907
+ },
908
+ ],
909
+ isError: true,
910
+ structuredContent: {
911
+ error: 'MISSING_GROUNDED_ACCEPTANCE_CRITERIA',
912
+ missingDecision: 'Provide at least one explicit, verifiable acceptance criterion.',
913
+ persisted: false,
914
+ },
915
+ },
916
+ };
917
+ }
948
918
  const actionableMetrics = calculateActionableSpecMetrics({
949
919
  criteria: [...baseCriteria, ...filteredCriteria],
950
920
  groundingRecords: groundingCriteria,
@@ -962,7 +932,7 @@ export async function handleCreateSpec(inputParams, server) {
962
932
  userInput: description,
963
933
  autopilot,
964
934
  }));
965
- const outOfScopeResolved = resolveOutOfScope(description, params.outOfScope);
935
+ const outOfScope = resolveOutOfScope(params.outOfScope);
966
936
  const scenarioTestPaths = groundedTechnical.files.test.map((file) => file.path);
967
937
  const leanSpec = generateLeanSpecContent({
968
938
  spec,
@@ -972,7 +942,7 @@ export async function handleCreateSpec(inputParams, server) {
972
942
  text: record.text,
973
943
  done: false,
974
944
  })),
975
- groundingCriteria,
945
+ groundingCriteria: contractCriteria,
976
946
  groundingTechnicalReferences: groundedTechnical.records,
977
947
  acFormat: params.acFormat,
978
948
  scenarioTestPaths,
@@ -992,15 +962,8 @@ export async function handleCreateSpec(inputParams, server) {
992
962
  description: generatedSpec.specBody,
993
963
  criteria: contractCriteria.map((record) => ({ text: record.text, done: false })),
994
964
  files: groundedTechnical.files,
995
- outOfScope: outOfScopeResolved.items,
996
- verificationCommands: [
997
- ...(scenarioTestPaths.length > 0
998
- ? [`pnpm vitest run ${scenarioTestPaths.join(' ')}`]
999
- : []),
1000
- 'pnpm typecheck',
1001
- 'pnpm lint',
1002
- 'pnpm test',
1003
- ],
965
+ outOfScope,
966
+ verificationCommands: [],
1004
967
  });
1005
968
  const genericOutputGate = checkGenericSpecOutput(unifiedSpec);
1006
969
  if (!genericOutputGate.passed) {
@@ -1028,6 +991,7 @@ export async function handleCreateSpec(inputParams, server) {
1028
991
  };
1029
992
  }
1030
993
  try {
994
+ await measureStep('mkdir-specDir', () => mkdir(specDir, { recursive: true }));
1031
995
  // SPEC-713: measure file write — this is the critical persistence step.
1032
996
  await measureStep('writeFile-specPath', () => writeFile(specPath, unifiedSpec, 'utf-8'));
1033
997
  await measureStep('commit-idempotency-evidence', () => commitIdempotencyEvidence(resolvedPath, idempotencyKey, idempotencyClaim, spec, specPath));
@@ -1040,11 +1004,10 @@ export async function handleCreateSpec(inputParams, server) {
1040
1004
  await rm(specDir, { recursive: true, force: true });
1041
1005
  throw writeErr;
1042
1006
  }
1043
- // SPEC-612: Auto-suggest outOfScope when user did not provide any
1044
- if (outOfScopeResolved.items.length > 0) {
1045
- spec.outOfScope = outOfScopeResolved.items;
1007
+ // Persist only explicit out-of-scope input.
1008
+ if (outOfScope.length > 0) {
1009
+ spec.outOfScope = outOfScope;
1046
1010
  }
1047
- const outOfScopeSuggestionMsg = outOfScopeResolved.message;
1048
1011
  // Persist spec in storage
1049
1012
  // SPEC-713: measure specStore.createSpec — file lock + JSON rewrite
1050
1013
  await measureStep('specStore-createSpec', () => specStore.createSpec(projectId, spec));
@@ -1070,7 +1033,6 @@ export async function handleCreateSpec(inputParams, server) {
1070
1033
  ...groundedTechnical.advisoryFiles.map((file) => file.path),
1071
1034
  ],
1072
1035
  actionableMetrics,
1073
- outOfScopeSuggestionMsg,
1074
1036
  },
1075
1037
  };
1076
1038
  }); // end withTotalBudget critical path
@@ -1101,7 +1063,7 @@ export async function handleCreateSpec(inputParams, server) {
1101
1063
  // Destructure critical path results for use in post-creation enrichment
1102
1064
  const { spec, specDir: _specDir, specPath,
1103
1065
  // SPEC-1010 Bug A: no longer surfaced in the response payload (SSR back-migration).
1104
- technicalPath: _technicalPath, estimation, splitSuggestion, duplicate, projectId, agentTeamPlan, knowledge, clarificationSession, constitutionCheck, autopilot, filteredCriteria, advisoryCriteria, actionableMetrics, outOfScopeSuggestionMsg, } = criticalResult.value.data;
1066
+ technicalPath: _technicalPath, estimation, splitSuggestion, duplicate, projectId, agentTeamPlan, knowledge, clarificationSession, constitutionCheck, autopilot, filteredCriteria, advisoryCriteria, actionableMetrics, } = criticalResult.value.data;
1105
1067
  // -----------------------------------------------------------------------
1106
1068
  // Post-creation enrichment (outside 25s ceiling — best-effort, budgeted)
1107
1069
  // -----------------------------------------------------------------------
@@ -1118,21 +1080,6 @@ export async function handleCreateSpec(inputParams, server) {
1118
1080
  // SPEC-713: withBudget 2s — scans all specs for contradiction keywords
1119
1081
  const contradictionResult = await withBudget('checkContradictions', 2_000, () => measureStep('checkContradictions', () => checkContradictions(projectId, spec.id, description)));
1120
1082
  const contradictionHint = unwrapBudget(contradictionResult, undefined);
1121
- // SPEC-555: Enrich estimation with calendar days (best-effort, non-blocking)
1122
- // SPEC-713: withBudget 2s
1123
- const velocityResult = await withBudget('velocityEnrichment', 2_000, () => measureStep('velocityEnrichment', () => enrichEstimationWithVelocity(params.projectPath ?? '', estimation.devHours)));
1124
- const velocityEnrichment = unwrapBudget(velocityResult, {
1125
- calendarDays: null,
1126
- velocityNote: 'velocity data unavailable',
1127
- });
1128
- // SPEC-506: Apply persisted calibration multiplier (best-effort, non-blocking)
1129
- // SPEC-713: withBudget 2s
1130
- const calibrationResult = await withBudget('calibrationEnrichment', 2_000, () => measureStep('calibrationEnrichment', () => enrichEstimationWithCalibration(params.projectPath ?? '', estimation.devHours, spec.scope, spec.type)));
1131
- const calibrationEnrichment = unwrapBudget(calibrationResult, {
1132
- calibratedHours: estimation.devHours,
1133
- applied: false,
1134
- calibrationNote: null,
1135
- });
1136
1083
  // Build result (SPEC-461: lean — no progress, HTML, diagrams, scope filters)
1137
1084
  const result = {
1138
1085
  // SPEC-781: async analysis running in background (external project data)
@@ -1154,15 +1101,6 @@ export async function handleCreateSpec(inputParams, server) {
1154
1101
  // ## Technical; the file is never written and the field would point to
1155
1102
  // a non-existent path. Internal Spec record still carries it for
1156
1103
  // backwards-compat with stored data — that field is removed in PR-C.
1157
- estimation: {
1158
- devHours: calibrationEnrichment.calibratedHours,
1159
- reviewHours: estimation.reviewHours,
1160
- totalCostUsd: estimation.totalCostUsd,
1161
- recommendedModel: estimation.recommendedModel,
1162
- executionMode: estimation.tokenOptimization.mode,
1163
- calendarDays: velocityEnrichment.calendarDays,
1164
- velocityNote: velocityEnrichment.velocityNote,
1165
- },
1166
1104
  duplicateWarning: duplicate
1167
1105
  ? ti('spec.duplicateTitle', { title: spec.title, slug: spec.slug })
1168
1106
  : undefined,
@@ -1176,7 +1114,7 @@ export async function handleCreateSpec(inputParams, server) {
1176
1114
  clarificationAnswersUsed: Object.keys(clarificationSession.answers).length,
1177
1115
  }
1178
1116
  : {
1179
- clarificationNote: 'Spec created directly from description Planu inferred requirements from project context.',
1117
+ clarificationNote: 'Spec created directly from the supplied description; no clarification session was linked.',
1180
1118
  }),
1181
1119
  message: ti('tools.create_spec.success', { id: spec.id, title: spec.title }),
1182
1120
  ...(contradictionHint ? { contradictionHint } : {}),
@@ -1201,7 +1139,6 @@ export async function handleCreateSpec(inputParams, server) {
1201
1139
  }
1202
1140
  const splitResult = buildSplitResult(splitSuggestion, knowledge?.experienceLevel);
1203
1141
  if (splitResult) {
1204
- result.splitSuggestion = splitResult;
1205
1142
  advisorySignals.push(makeAdvisorySignal({
1206
1143
  key: 'split-suggestion',
1207
1144
  kind: 'complexity',
@@ -1211,7 +1148,6 @@ export async function handleCreateSpec(inputParams, server) {
1211
1148
  confidence: 0.5,
1212
1149
  surface: 'structuredContent',
1213
1150
  value: splitResult,
1214
- deprecatedAlias: 'splitSuggestion',
1215
1151
  }));
1216
1152
  }
1217
1153
  // Dispatch hook event (fire-and-forget)
@@ -1220,21 +1156,6 @@ export async function handleCreateSpec(inputParams, server) {
1220
1156
  runAutopilotAsync(spec.id, params.projectPath ?? '', description);
1221
1157
  // SPEC-713: Track warnings from budgeted post-creation steps
1222
1158
  const budgetWarnings = [];
1223
- // Auto-estimation (best-effort, fire-and-forget)
1224
- // SPEC-713: withBudget 2s — estimation is sync, specStore.updateSpec is a single JSON write
1225
- const autoEstimationResult = await withBudget('auto-estimation', 2_000, async () => {
1226
- const autoEstimation = estimateSpec(spec);
1227
- await measureStep('specStore-updateSpec', () => specStore.updateSpec(projectId, spec.id, { estimation: autoEstimation.estimation }));
1228
- return autoEstimation;
1229
- });
1230
- if (!autoEstimationResult.exceeded) {
1231
- const ae = autoEstimationResult.value;
1232
- result.autoEstimation = {
1233
- confidence: ae.confidence,
1234
- reasoning: ae.reasoning,
1235
- similarSpecs: ae.similarSpecs,
1236
- };
1237
- }
1238
1159
  // SPEC-461: No per-spec HTML reports — lean format
1239
1160
  // SPEC-713: Run post-creation checks in parallel to reduce total wall-clock time.
1240
1161
  // Each has an individual budget; results are merged into `result` after settlement.
@@ -1396,9 +1317,6 @@ export async function handleCreateSpec(inputParams, server) {
1396
1317
  `| Target | ${spec.target} |`,
1397
1318
  `| Status | ${spec.status} |`,
1398
1319
  `| Tags | ${spec.tags.join(', ')} |`,
1399
- `| Dev hours | ${String(estimation.devHours)}h |`,
1400
- `| Review hours | ${String(estimation.reviewHours)}h |`,
1401
- `| Cost | $${String(estimation.totalCostUsd)} |`,
1402
1320
  ];
1403
1321
  if (spec.gitBranch) {
1404
1322
  lines.push(`| Branch | \`${spec.gitBranch}\` |`);
@@ -1426,20 +1344,6 @@ export async function handleCreateSpec(inputParams, server) {
1426
1344
  : formatSuccess(ti('tools.create_spec.success', { id: spec.id, title: spec.title }), lines.join('\n'));
1427
1345
  // SPEC-469: Build autopilot summary from analyzer results
1428
1346
  const collector = new AutopilotSummaryCollector();
1429
- // SPEC-612: Record outOfScope auto-suggestion in autopilot summary
1430
- if (outOfScopeSuggestionMsg !== null) {
1431
- collector.pushOk('scope-boundaries', outOfScopeSuggestionMsg);
1432
- advisorySignals.push(makeAdvisorySignal({
1433
- key: 'out-of-scope-suggestions',
1434
- kind: 'out-of-scope',
1435
- message: outOfScopeSuggestionMsg,
1436
- source: 'heuristic',
1437
- evidence: ['scope-boundaries suggester'],
1438
- confidence: 0.45,
1439
- surface: 'structuredContent',
1440
- value: spec.outOfScope ?? [],
1441
- }));
1442
- }
1443
1347
  if (autopilot.detectedPatterns.length > 0) {
1444
1348
  collector.pushOk('pattern-detection', `Detected patterns: ${autopilot.detectedPatterns.join(', ')}`);
1445
1349
  advisorySignals.push(makeAdvisorySignal({
@@ -1479,17 +1383,13 @@ export async function handleCreateSpec(inputParams, server) {
1479
1383
  }));
1480
1384
  }
1481
1385
  if (filteredCriteria.length > 0) {
1482
- collector.pushOk('criteria-enrichment', `Added ${String(filteredCriteria.length)} acceptance criteria from project context`);
1386
+ collector.pushOk('criteria-enrichment', `Evaluated ${String(filteredCriteria.length)} proposed criteria; only directly grounded items entered the contract`);
1483
1387
  }
1484
1388
  if (pipelineResult.challengeSummary) {
1485
1389
  collector.pushOk('challenge', `Challenge analysis complete: ${pipelineResult.challengeSummary}`);
1486
1390
  }
1487
1391
  if (pipelineResult.readinessScore !== null) {
1488
- // SPEC-629: append Opus hint for difficulty >= 3 specs
1489
- const opusHint = spec.difficulty >= 3
1490
- ? ` — difficulty ${String(spec.difficulty)}: use Opus to add exact file paths + function names`
1491
- : '';
1492
- collector.pushOk('readiness', `Readiness score: ${String(pipelineResult.readinessScore)}/100${opusHint}`);
1392
+ collector.pushOk('readiness', `Readiness score: ${String(pipelineResult.readinessScore)}/100`);
1493
1393
  advisorySignals.push(makeAdvisorySignal({
1494
1394
  key: 'readiness-score',
1495
1395
  kind: 'readiness',
@@ -1501,24 +1401,7 @@ export async function handleCreateSpec(inputParams, server) {
1501
1401
  value: pipelineResult.readinessScore,
1502
1402
  }));
1503
1403
  }
1504
- else if (spec.difficulty >= 3) {
1505
- // SPEC-629: always surface the Opus hint for high-difficulty specs
1506
- collector.pushOk('recommend_model', `Difficulty ${String(spec.difficulty)} spec — use Opus to add exact file paths, function names and anticipated test breaks.`);
1507
- advisorySignals.push(makeAdvisorySignal({
1508
- key: 'model-recommendation',
1509
- kind: 'model',
1510
- message: 'High-difficulty spec may benefit from a stronger model for review.',
1511
- source: 'heuristic',
1512
- evidence: [`difficulty:${String(spec.difficulty)}`],
1513
- confidence: 0.4,
1514
- surface: 'structuredContent',
1515
- value: { recommendedTier: 'max' },
1516
- }));
1517
- }
1518
- if (calibrationEnrichment.calibrationNote) {
1519
- collector.pushOk('calibration', `📐 ${calibrationEnrichment.calibrationNote}`);
1520
- }
1521
- const humanSummary = buildCreateSpecSummary(spec.title, estimation.devHours);
1404
+ const humanSummary = `Plan created for "${spec.title}". Review the grounded criteria before approval.`;
1522
1405
  result.advisorySignals = advisorySignals;
1523
1406
  result.compat = buildAdvisoryCompat();
1524
1407
  const compactResult = compactObj(result);
@@ -5,9 +5,9 @@ export declare const SuggestDeletionsInputSchema: {
5
5
  projectPath: z.ZodString;
6
6
  files: z.ZodOptional<z.ZodArray<z.ZodString>>;
7
7
  threshold: z.ZodOptional<z.ZodEnum<{
8
+ high: "high";
8
9
  low: "low";
9
10
  medium: "medium";
10
- high: "high";
11
11
  }>>;
12
12
  };
13
13
  export declare function handleSuggestDeletions(args: {
@@ -1,6 +1,5 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { generateSpecQuestions, formatQuestionsAsPrompt, parseAnswers, } from '../engine/elicitation/index.js';
3
- import { buildDefaultAnswers } from '../engine/elicitation/non-interactive-defaults.js';
4
3
  /** Build a clarification summary from parsed answers */
5
4
  function buildSummary(session, parsedAnswers) {
6
5
  const lines = [`## Clarification Summary for "${session.title}"`, ''];
@@ -34,10 +33,8 @@ export function handleElicitRequirements(input) {
34
33
  const { title, description, answers, mode } = input;
35
34
  // intakeRef is silently acknowledged — used by downstream agents to chain context
36
35
  const context = {
37
- hasDatabase: description.toLowerCase().includes('data') ||
38
- description.toLowerCase().includes('store') ||
39
- description.toLowerCase().includes('database'),
40
- isApi: description.toLowerCase().includes('api') || description.toLowerCase().includes('endpoint'),
36
+ hasDatabase: hasAffirmedTerm(description, /\b(?:data|store|database)\b/i),
37
+ isApi: hasAffirmedTerm(description, /\b(?:api|endpoint)\b/i),
41
38
  };
42
39
  const questions = generateSpecQuestions(title, description, context);
43
40
  const sessionId = input.sessionId ?? randomUUID();
@@ -47,17 +44,21 @@ export function handleElicitRequirements(input) {
47
44
  questions,
48
45
  answers: {},
49
46
  };
50
- // SPEC-724: Non-interactive mode synthesise defaults without AskUserQuestion round-trip
47
+ // Non-interactive mode cannot complete unanswered decisions on the user's behalf.
51
48
  if (mode === 'non-interactive' && (answers === undefined || answers.trim().length === 0)) {
52
- const defaultAnswers = buildDefaultAnswers(input);
53
- session.answers = defaultAnswers;
54
- session.completedAt = new Date().toISOString();
55
- const summary = buildSummary(session, defaultAnswers);
49
+ const missingDecision = 'Needs decision: provide explicit answers before requirements can be treated as confirmed.';
56
50
  return Promise.resolve({
57
- content: [{ type: 'text', text: summary }],
51
+ content: [{ type: 'text', text: missingDecision }],
58
52
  structuredContent: {
59
- summary,
60
53
  mode: 'non-interactive',
54
+ complete: false,
55
+ missingDecision,
56
+ pendingQuestions: questions.map((question) => ({
57
+ id: question.id,
58
+ question: question.question,
59
+ required: question.required,
60
+ ...(question.choices ? { candidates: question.choices } : {}),
61
+ })),
61
62
  },
62
63
  });
63
64
  }
@@ -81,4 +82,11 @@ export function handleElicitRequirements(input) {
81
82
  const summary = buildSummary(session, parsedAnswers);
82
83
  return Promise.resolve({ content: [{ type: 'text', text: summary }] });
83
84
  }
85
+ const NEGATION_RE = /\b(?:no|not|never|without|exclude|excluding|forbid|forbidden|avoid|must not|should not|do not|does not|don't|doesn't|out[- ]of[- ]scope)\b/i;
86
+ function hasAffirmedTerm(description, pattern) {
87
+ return description.split(/(?:\r?\n|[.;!?]+)/).some((segment) => {
88
+ const match = new RegExp(pattern.source, pattern.flags.replace('g', '')).exec(segment);
89
+ return match !== null && !NEGATION_RE.test(segment.slice(0, match.index));
90
+ });
91
+ }
84
92
  //# sourceMappingURL=elicit-requirements-handler.js.map
@@ -7,13 +7,13 @@ export declare const GenerateBatchScriptInputSchema: z.ZodObject<{
7
7
  specId: z.ZodOptional<z.ZodString>;
8
8
  allowedTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
9
9
  outputFormat: z.ZodOptional<z.ZodEnum<{
10
- text: "text";
11
10
  json: "json";
12
11
  "stream-json": "stream-json";
12
+ text: "text";
13
13
  }>>;
14
14
  permissionMode: z.ZodOptional<z.ZodEnum<{
15
- default: "default";
16
15
  auto: "auto";
16
+ default: "default";
17
17
  dontAsk: "dontAsk";
18
18
  }>>;
19
19
  pilotCount: z.ZodOptional<z.ZodNumber>;
@@ -228,7 +228,6 @@ Never suggest bypassPermissions — it disables all safety checks.
228
228
 
229
229
  ## How to apply
230
230
 
231
- - After \`update_status\` returns \`suggestedClaudeMode\`, relay it to the user: "I recommend switching to \`/mode <mode>\`"
232
231
  - NEVER switch modes automatically — always tell the user and let them decide
233
232
  - Critical bash actions (git push, npm publish, rm -rf) require approval regardless of mode
234
233
  `;
@@ -3,10 +3,10 @@ import { z } from 'zod';
3
3
  export declare const StartOAuthFlowInputSchema: {
4
4
  projectPath: z.ZodString;
5
5
  integration: z.ZodEnum<{
6
- supabase: "supabase";
7
6
  figma: "figma";
8
7
  github: "github";
9
8
  sentry: "sentry";
9
+ supabase: "supabase";
10
10
  }>;
11
11
  };
12
12
  export declare const OAuthStatusInputSchema: {
@@ -1,4 +1,4 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- export declare const OFFICIAL_SDD_TOOL_NAMES: readonly ["planu_status", "facilitate", "init_project", "clarify_requirements", "create_spec", "challenge_spec", "check_readiness", "update_status", "update_status_batch", "package_handoff", "validate", "reconcile_spec", "create_rule", "create_skill", "skill_search", "configure_code_graph", "code_graph_status", "submit_feedback", "triage_feedback", "resolve_feedback", "sync_feedback", "feedback_status"];
2
+ export declare const OFFICIAL_SDD_TOOL_NAMES: readonly ['planu_status', 'facilitate', 'init_project', 'clarify_requirements', 'create_spec', 'challenge_spec', 'check_readiness', 'update_status', 'update_status_batch', 'package_handoff', 'validate', 'reconcile_spec', 'create_rule', 'create_skill', 'skill_search', 'configure_code_graph', 'code_graph_status', 'submit_feedback', 'triage_feedback', 'resolve_feedback', 'sync_feedback', 'feedback_status'];
3
3
  export declare function registerSddTools(server: McpServer): void;
4
4
  //# sourceMappingURL=register-sdd-tools.d.ts.map
@@ -167,8 +167,17 @@ export function registerCoreSpecTools(server) {
167
167
  .number()
168
168
  .optional()
169
169
  .describe('Maximum number of questions to generate (default: 5)'),
170
+ sessionId: z
171
+ .string()
172
+ .max(500)
173
+ .optional()
174
+ .describe('Existing clarification session to resume with user-confirmed answers.'),
175
+ answers: z
176
+ .record(z.string(), z.string())
177
+ .optional()
178
+ .describe('Explicit user-confirmed answers keyed by clarification question ID.'),
170
179
  },
171
- }, safeTracked('clarify_requirements', async (args) => handleClarifyRequirements(args)));
180
+ }, safeTracked('clarify_requirements', async (args) => handleClarifyRequirements(args, server)));
172
181
  // 5. create_spec
173
182
  server.registerTool('create_spec', {
174
183
  description: t('tools.create_spec.description'),
@@ -210,7 +219,7 @@ export function registerCoreSpecTools(server) {
210
219
  outOfScope: z
211
220
  .array(z.string())
212
221
  .optional()
213
- .describe('SPEC-612: Explicit out-of-scope items for this spec. If omitted, Planu auto-suggests based on the description.'),
222
+ .describe('Explicit user-confirmed out-of-scope items for this spec.'),
214
223
  specId: z
215
224
  .string()
216
225
  .optional()