@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
@@ -8,23 +8,17 @@ import { updateSpec } from '../storage/spec-store.js';
8
8
  import { resolveProjectId, missingProjectIdError } from './resolve-project-id.js';
9
9
  import { elicitOrFallback, buildEnumSchema } from '../engine/elicitation/elicit-helper.js';
10
10
  import { t, ti } from '../i18n/index.js';
11
- import { generateFailureScenarios, generateSecurityScenarios, generateScaleScenarios, generateDataConsistencyScenarios, generateGameChallenges, generateDesktopAutoUpdateChallenges, } from './challenge-spec-scenarios.js';
12
11
  import { generateConcurrencyAnalysis, buildScalabilityAssessment, calculateOverallRisk, readSpecContent, } from './challenge-spec-helpers.js';
13
12
  import { prioritizeScenarios, buildPrioritizedSummary } from '../engine/challenge-prioritizer.js';
14
13
  import { checkContradictions as checkScopeContradictions } from '../engine/scope-boundaries/index.js';
15
14
  import { buildChallengeSpecSummary } from '../engine/human-summary.js';
16
- import { generateAgentChallengeScenarios, isAgentSpec, } from './challenge-spec/agent-challenge-scenarios.js';
17
- import { generateImplementationContractChallengeScenarios } from './challenge-spec/implementation-contract-challenge-scenarios.js';
18
- import { getPlatformChallenges } from './challenge-spec/platform-challenge-scenarios.js';
19
- import { generateSecurityChallengeScenarios } from './challenge-spec/security-challenge-scenarios.js';
20
- import { generatePrivacyChallengeScenarios } from './challenge-spec/privacy-challenge-scenarios.js';
21
- import { generateEventChallengeScenarios } from './challenge-spec/event-challenge-scenarios.js';
22
- import { generateDddChallenges, generateXpPairClarityChallenge, } from './challenge-spec/architecture-challenge-scenarios.js';
23
- import { generateResilienceChallengeScenarios } from './challenge-spec/resilience-challenge-scenarios.js';
24
15
  import { detectContradictions as detectDecisionContradictions, searchPriorDecisions, } from '../engine/prior-decisions/index.js';
25
16
  import { getDecisions } from '../storage/decision-store.js';
26
17
  import { calculateTokenBudget, injectBudgetIntoPrompt } from '../engine/token-budget/index.js';
27
18
  import { analyzeMinimalImplementation, loadMinimalImplementationPolicy, } from '../engine/minimality/index.js';
19
+ import { detectChallengeCapabilities, } from './challenge-spec/scenarios-utils.js';
20
+ import { collectCapabilityScenarios } from './challenge-spec/scenario-collector.js';
21
+ import { buildChallengeReport } from './challenge-spec/challenge-report.js';
28
22
  const ALL_FOCUS_AREAS = [
29
23
  'failures',
30
24
  'concurrency',
@@ -32,7 +26,6 @@ const ALL_FOCUS_AREAS = [
32
26
  'security',
33
27
  'data-consistency',
34
28
  ];
35
- const ACTIONABLE_RELEVANCE_MIN = 15;
36
29
  /**
37
30
  * SPEC-615 AC3: detect contradictions between spec criteria and prior decisions.
38
31
  * Mutates failureScenarios in place. Best-effort — never throws.
@@ -125,6 +118,7 @@ export async function handleChallengeSpec(args, server) {
125
118
  const constitution = await knowledgeStore.getConstitution(projectId);
126
119
  // 3. Read spec content
127
120
  const specContent = await readSpecContent(spec);
121
+ const capabilities = detectChallengeCapabilities(spec, specContent);
128
122
  // 4. Determine focus areas
129
123
  const focusAreas = focus && focus.length > 0 ? focus : ALL_FOCUS_AREAS;
130
124
  // 5. Generate failure scenarios
@@ -153,58 +147,7 @@ export async function handleChallengeSpec(args, server) {
153
147
  catch {
154
148
  // Minimality challenge is best-effort and must not hide resilience findings.
155
149
  }
156
- if (focusAreas.includes('failures')) {
157
- failureScenarios.push(...generateFailureScenarios(spec, specContent, knowledge));
158
- }
159
- if (focusAreas.includes('security')) {
160
- failureScenarios.push(...generateSecurityScenarios(spec, specContent, knowledge));
161
- }
162
- if (focusAreas.includes('scale')) {
163
- failureScenarios.push(...generateScaleScenarios(spec, specContent, knowledge));
164
- }
165
- if (focusAreas.includes('data-consistency')) {
166
- failureScenarios.push(...generateDataConsistencyScenarios(spec, specContent, knowledge));
167
- }
168
- // 5e. Game-specific challenges (performance, balance, cheating, physics)
169
- if (knowledge.projectCategory === 'game' ||
170
- (knowledge.framework !== null &&
171
- ['unity', 'godot', 'unreal', 'bevy', 'phaser', 'pygame'].includes(knowledge.framework))) {
172
- failureScenarios.push(...generateGameChallenges(spec, specContent, knowledge));
173
- }
174
- // 5f. SPEC-014b: Desktop auto-update edge cases
175
- failureScenarios.push(...generateDesktopAutoUpdateChallenges(spec, specContent, knowledge));
176
- // 5g. SPEC-016a: AI agent-specific failure modes
177
- if (isAgentSpec(spec, specContent)) {
178
- failureScenarios.push(...generateAgentChallengeScenarios(spec, specContent, knowledge));
179
- }
180
- if (focusAreas.includes('failures')) {
181
- failureScenarios.push(...generateImplementationContractChallengeScenarios(spec, specContent));
182
- }
183
- // 5h. SPEC-015a: Smart contract, bot, and IoT challenges
184
- failureScenarios.push(...getPlatformChallenges(spec, specContent, knowledge));
185
- // 5i. SPEC-029: Security authorization and STRIDE challenges
186
- if (focusAreas.includes('security')) {
187
- failureScenarios.push(...generateSecurityChallengeScenarios(spec, specContent, knowledge));
188
- }
189
- // 5j. SPEC-030: Privacy challenges (mandatory when spec has PII)
190
- failureScenarios.push(...generatePrivacyChallengeScenarios(spec, specContent, knowledge));
191
- // 5k. SPEC-031: Event-driven challenges (contract, idempotence, DLQ)
192
- failureScenarios.push(...generateEventChallengeScenarios(spec, specContent, knowledge));
193
- // 5l. SPEC-033: DDD aggregate integrity challenges
194
- const hasDdd = knowledge.architecture.primary === 'ddd' || knowledge.architecture.secondary.includes('ddd');
195
- if (hasDdd) {
196
- failureScenarios.push(...generateDddChallenges(spec, specContent));
197
- }
198
- // 5n. SPEC-034: Resilience, validation, concurrency, and saga challenges
199
- failureScenarios.push(...generateResilienceChallengeScenarios(spec, specContent, knowledge));
200
- // 5m. SPEC-033: XP pair programming clarity challenge
201
- // `agileConfig` may be present on enriched knowledge objects (AgileProcessDetection mixin)
202
- // but is not declared on the base ProjectKnowledge interface — intentional duck-type access.
203
- // ProjectKnowledge has no index signature, so `as unknown` intermediate is required.
204
- const agileConfig = knowledge.agileConfig;
205
- if (agileConfig?.process === 'xp') {
206
- failureScenarios.push(...generateXpPairClarityChallenge(spec, specContent));
207
- }
150
+ failureScenarios.push(...collectCapabilityScenarios({ spec, specContent, knowledge, focusAreas, capabilities }));
208
151
  // SPEC-612: Check for contradictions between outOfScope and acceptance criteria
209
152
  if (spec.outOfScope !== undefined && spec.outOfScope.length > 0) {
210
153
  const criteriaTexts = specContent
@@ -228,35 +171,35 @@ export async function handleChallengeSpec(args, server) {
228
171
  // SPEC-615 AC3: Check criteria against prior decisions for technology contradictions
229
172
  await runPriorDecisionContradictions(projectId, spec.title, spec.tags, specContent, failureScenarios);
230
173
  // 6. Generate concurrency analysis
231
- const concurrencyAnalysis = focusAreas.includes('concurrency')
174
+ const concurrencyAnalysis = focusAreas.includes('concurrency') && capabilities.concurrency
232
175
  ? generateConcurrencyAnalysis(spec, specContent, knowledge)
233
176
  : { hotPaths: [], raceConditions: [], sharedState: [], recommendations: [] };
234
- // 7. Build scalability assessment
235
- const scalabilityAssessment = buildScalabilityAssessment(spec, knowledge, failureScenarios);
236
- // 8. Calculate overall risk
237
- const overallRisk = calculateOverallRisk(failureScenarios, concurrencyAnalysis);
177
+ // Unsupported domain templates are removed before prioritization and risk calculation.
178
+ const actionableFailureScenarios = failureScenarios.filter((scenario) => isScenarioSupportedByCapabilities(scenario, capabilities));
179
+ const suppressedScenarioCount = failureScenarios.length - actionableFailureScenarios.length;
180
+ // 7. Build scalability assessment from grounded scenarios only.
181
+ const scalabilityAssessment = buildScalabilityAssessment(spec, knowledge, actionableFailureScenarios);
182
+ // 8. Calculate overall risk from grounded scenarios only.
183
+ const overallRisk = calculateOverallRisk(actionableFailureScenarios, concurrencyAnalysis);
238
184
  // 9. Compute relevance scores and select top-3 (SPEC-338)
239
- const prioritized = prioritizeScenarios(failureScenarios, spec);
185
+ const prioritized = prioritizeScenarios(actionableFailureScenarios, spec);
240
186
  const prioritizedSummary = buildPrioritizedSummary(prioritized);
241
- // Attach relevanceScore to each scenario in the full list
242
- const failureScenariosScored = failureScenarios.map((s) => {
187
+ const failureScenariosScored = actionableFailureScenarios.map((s) => {
243
188
  const match = prioritized.find((p) => p.scenario === s.scenario);
244
189
  return match !== undefined ? { ...s, relevanceScore: match.relevanceScore } : s;
245
190
  });
246
- const prioritizedScenarios = new Set(prioritized.map((scenario) => scenario.scenario));
247
- const actionableFailureScenarios = failureScenariosScored.filter((scenario) => prioritizedScenarios.has(scenario.scenario) ||
248
- scenario.impact === 'critical' ||
249
- scenario.impact === 'high' ||
250
- isDomainRelevantScenario(scenario, specContent, knowledge) ||
251
- (scenario.relevanceScore ?? computeScenarioRelevanceFallback(scenario)) >=
252
- ACTIONABLE_RELEVANCE_MIN);
253
- const suppressedScenarioCount = failureScenariosScored.length - actionableFailureScenarios.length;
254
191
  const analysis = {
255
- failureScenarios: actionableFailureScenarios,
192
+ failureScenarios: failureScenariosScored,
256
193
  concurrencyAnalysis,
257
194
  scalabilityAssessment,
258
195
  overallRisk,
259
196
  };
197
+ const challengeReport = buildChallengeReport({
198
+ scenarios: failureScenariosScored,
199
+ focusAreas,
200
+ overallRisk,
201
+ previousReport: spec.challengeReport,
202
+ });
260
203
  const rawHumanSummary = buildChallengeSpecSummary(prioritized, overallRisk);
261
204
  // SPEC-620: Inject token budget tag into the LLM-facing summary prompt
262
205
  const specDevHours = spec.estimation.devHours;
@@ -271,12 +214,12 @@ export async function handleChallengeSpec(args, server) {
271
214
  },
272
215
  summary: {
273
216
  totalScenarios: failureScenarios.length,
274
- actionableScenarios: actionableFailureScenarios.length,
217
+ actionableScenarios: failureScenariosScored.length,
275
218
  suppressedLowRelevanceScenarios: suppressedScenarioCount,
276
219
  shownByDefault: 3,
277
220
  mustAddressBeforeCoding: prioritizedSummary,
278
- criticalImpact: failureScenarios.filter((s) => s.impact === 'critical').length,
279
- highImpact: failureScenarios.filter((s) => s.impact === 'high').length,
221
+ criticalImpact: actionableFailureScenarios.filter((s) => s.impact === 'critical').length,
222
+ highImpact: actionableFailureScenarios.filter((s) => s.impact === 'high').length,
280
223
  raceConditions: concurrencyAnalysis.raceConditions.length,
281
224
  overallRisk,
282
225
  focusAreas: focusAreas,
@@ -296,25 +239,19 @@ export async function handleChallengeSpec(args, server) {
296
239
  checkedAt: new Date().toISOString(),
297
240
  }
298
241
  : null,
242
+ challengeReport,
299
243
  };
300
244
  // SPEC-964: Persist challenge report so update_status can gate on it
301
245
  try {
302
246
  await updateSpec(projectId, specId, {
303
- challengeReport: {
304
- runAt: new Date().toISOString(),
305
- totalScenarios: failureScenarios.length,
306
- addressedCount: failureScenarios.length, // All generated scenarios are considered "addressed" by running challenge_spec
307
- focusAreas: focusAreas,
308
- overallRisk: overallRisk,
309
- passed: true,
310
- },
247
+ challengeReport,
311
248
  });
312
249
  }
313
250
  catch {
314
251
  // Best-effort: don't fail the challenge if persistence fails
315
252
  }
316
253
  // SPEC-595: Elicit how user wants to apply suggestions when critical issues exist
317
- const criticalCount = failureScenarios.filter((s) => s.impact === 'critical').length;
254
+ const criticalCount = actionableFailureScenarios.filter((s) => s.impact === 'critical').length;
318
255
  if (server !== undefined && criticalCount > 0) {
319
256
  const { field, property } = buildEnumSchema('apply', ['all', 'critical', 'none'], ['Apply all suggestions', 'Apply critical only (Recommended)', 'Review manually'], 'Apply suggestions', 'critical');
320
257
  const schema = { type: 'object', properties: { [field]: property } };
@@ -326,7 +263,7 @@ export async function handleChallengeSpec(args, server) {
326
263
  {
327
264
  type: 'text',
328
265
  text: ti('tools.challenge_spec.success', {
329
- scenarioCount: String(failureScenarios.length),
266
+ scenarioCount: String(actionableFailureScenarios.length),
330
267
  }),
331
268
  },
332
269
  { type: 'text', text: humanSummary },
@@ -349,7 +286,7 @@ export async function handleChallengeSpec(args, server) {
349
286
  {
350
287
  type: 'text',
351
288
  text: ti('tools.challenge_spec.success', {
352
- scenarioCount: String(failureScenarios.length),
289
+ scenarioCount: String(actionableFailureScenarios.length),
353
290
  }),
354
291
  },
355
292
  { type: 'text', text: humanSummary },
@@ -362,12 +299,12 @@ export async function handleChallengeSpec(args, server) {
362
299
  {
363
300
  type: 'text',
364
301
  text: ti('tools.challenge_spec.success', {
365
- scenarioCount: String(failureScenarios.length),
302
+ scenarioCount: String(actionableFailureScenarios.length),
366
303
  }),
367
304
  },
368
305
  {
369
306
  type: 'text',
370
- text: `Actionable scenarios: ${String(actionableFailureScenarios.length)}` +
307
+ text: `Actionable scenarios: ${String(failureScenariosScored.length)}` +
371
308
  ` | Suppressed low-relevance: ${String(suppressedScenarioCount)}` +
372
309
  ` | Overall risk: ${overallRisk}`,
373
310
  },
@@ -376,37 +313,22 @@ export async function handleChallengeSpec(args, server) {
376
313
  structuredContent: analysisPayload,
377
314
  };
378
315
  }
379
- function computeScenarioRelevanceFallback(scenario) {
380
- if (scenario.impact === 'critical') {
381
- return 100;
382
- }
383
- if (scenario.impact === 'high') {
384
- return 70;
385
- }
386
- if (scenario.probability === 'high') {
387
- return 35;
388
- }
389
- return 0;
390
- }
391
- function isDomainRelevantScenario(scenario, specContent, knowledge) {
316
+ function isScenarioSupportedByCapabilities(scenario, capabilities) {
392
317
  const haystack = `${scenario.scenario} ${scenario.currentHandling} ${scenario.requiredHandling}`
393
318
  .toLowerCase()
394
319
  .trim();
395
- const specText = specContent.toLowerCase();
396
- const framework = knowledge.framework?.toLowerCase() ?? '';
397
- const isGameProject = knowledge.projectCategory === 'game' ||
398
- ['unity', 'godot', 'unreal', 'bevy', 'phaser', 'pygame'].includes(framework);
399
- if (isGameProject && /\b(game|physics|cheat|balance|save|hud|player)\b/.test(haystack)) {
400
- return true;
401
- }
402
- if (/\b(database|query|index|pagination|transaction|cache)\b/.test(haystack) &&
403
- /\b(database|query|index|pagination|transaction|cache|sql|postgres|sqlite)\b/.test(specText)) {
404
- return true;
405
- }
406
- if (/\b(api|auth|jwt|session|csrf|injection|tenant|bola)\b/.test(haystack) &&
407
- /\b(api|auth|jwt|session|csrf|injection|tenant|bola)\b/.test(specText)) {
408
- return true;
409
- }
410
- return false;
320
+ const rules = [
321
+ [/\b(?:payment|billing|checkout|stripe|paypal)\b/, capabilities.payment],
322
+ [/\b(?:privacy|pii|gdpr|personal data)\b/, capabilities.privacy],
323
+ [
324
+ /\b(?:bola|idor|jwt|authentication|authorization|csrf|privilege)\b/,
325
+ capabilities.authentication || capabilities.authorization,
326
+ ],
327
+ [/\b(?:sql|nosql|database|connection pool)\b/, capabilities.database],
328
+ [/\b(?:event contract|event schema|message broker|kafka|rabbitmq|dlq)\b/, capabilities.events],
329
+ [/\b(?:api endpoint|http route|http status|network timeout)\b/, capabilities.networkApi],
330
+ [/\b(?:traffic spike|load test|auto-scaling|throughput)\b/, capabilities.scale],
331
+ ];
332
+ return rules.every(([pattern, supported]) => !pattern.test(haystack) || supported);
411
333
  }
412
334
  //# sourceMappingURL=challenge-spec.js.map
@@ -277,7 +277,7 @@ export function attachOptions(question) {
277
277
  * The returned string is suitable for passing directly to create_spec.
278
278
  */
279
279
  export function buildSuggestedDescription(topic, answers, questions) {
280
- const parts = [`Feature: ${topic}.`];
280
+ const parts = [`Topic: ${topic}.`];
281
281
  for (const q of questions) {
282
282
  const answer = answers[q.id];
283
283
  if (answer === undefined || answer.trim() === '') {
@@ -6,13 +6,12 @@ import { buildSuggestedAnswers } from './questions.js';
6
6
  */
7
7
  export function generateQuestions(topic, _context, knowledge, maxQuestions) {
8
8
  const questions = [];
9
- const lower = topic.toLowerCase();
10
9
  let idx = 0;
11
10
  const nextId = () => `q-${++idx}`;
12
11
  // === Core question: What do you want? (always relevant) ===
13
12
  questions.push({
14
13
  id: nextId(),
15
- question: `What is the main goal of "${topic}"? What should the user be able to do?`,
14
+ question: `What observable behavior or result should "${topic}" produce?`,
16
15
  category: 'scope',
17
16
  importance: 'critical',
18
17
  });
@@ -26,16 +25,13 @@ export function generateQuestions(topic, _context, knowledge, maxQuestions) {
26
25
  // === Acceptance: What does done look like? ===
27
26
  questions.push({
28
27
  id: nextId(),
29
- question: `What does "done" look like? Describe the ideal user flow.`,
28
+ question: `Which explicit observations or checks will prove that "${topic}" is done?`,
30
29
  category: 'acceptance',
31
30
  importance: 'critical',
32
31
  });
33
32
  // === Context-specific (only if topic mentions specific domains) ===
34
33
  // Auth related — only ask which method, nothing else
35
- if (lower.includes('auth') ||
36
- lower.includes('login') ||
37
- lower.includes('register') ||
38
- lower.includes('password')) {
34
+ if (hasAffirmedTerm(topic, /\b(?:auth|authentication|login|register|password)\b/i)) {
39
35
  questions.push({
40
36
  id: nextId(),
41
37
  question: `What login methods? (email/password, Google, Apple, magic link, etc.)`,
@@ -44,10 +40,7 @@ export function generateQuestions(topic, _context, knowledge, maxQuestions) {
44
40
  });
45
41
  }
46
42
  // UI related — only ask if there are mockups
47
- if (lower.includes('ui') ||
48
- lower.includes('page') ||
49
- lower.includes('screen') ||
50
- lower.includes('dashboard')) {
43
+ if (hasAffirmedTerm(topic, /\b(?:ui|page|screen|dashboard)\b/i)) {
51
44
  questions.push({
52
45
  id: nextId(),
53
46
  question: `Are there mockups or a reference design to follow?`,
@@ -71,4 +64,11 @@ export function generateQuestions(topic, _context, knowledge, maxQuestions) {
71
64
  suggestedAnswers: buildSuggestedAnswers(q, knowledge),
72
65
  }));
73
66
  }
67
+ const NEGATION_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)\b/i;
68
+ function hasAffirmedTerm(text, pattern) {
69
+ return text.split(/(?:\r?\n|[.;!?]+)/).some((segment) => {
70
+ const match = new RegExp(pattern.source, pattern.flags.replace('g', '')).exec(segment);
71
+ return match !== null && !NEGATION_RE.test(segment.slice(0, match.index));
72
+ });
73
+ }
74
74
  //# sourceMappingURL=questions-context.js.map
@@ -7,28 +7,22 @@ export function generateMetaQuestions() {
7
7
  return [
8
8
  {
9
9
  id: 'meta-1',
10
- question: 'Is this a brand new feature or an improvement to something that already exists?',
10
+ question: 'Which change type best describes the request?',
11
11
  category: 'scope',
12
12
  importance: 'critical',
13
13
  suggestedAnswers: ['New feature', 'Improvement to existing feature', 'Bug fix', 'Refactor'],
14
14
  },
15
15
  {
16
16
  id: 'meta-2',
17
- question: 'Who is the primary user that benefits from this? (e.g., end user, admin, developer)',
17
+ question: 'What observable outcome must this work produce?',
18
18
  category: 'scope',
19
19
  importance: 'critical',
20
- suggestedAnswers: ['End user', 'Admin', 'Developer/internal', 'All users'],
21
20
  },
22
21
  {
23
22
  id: 'meta-3',
24
- question: 'What is the minimum viable scope what would make this "done" at a basic level?',
23
+ question: 'Which boundaries are explicitly in scope and out of scope?',
25
24
  category: 'scope',
26
25
  importance: 'critical',
27
- suggestedAnswers: [
28
- 'Just the core happy path',
29
- 'Happy path + error handling',
30
- 'Full feature with edge cases',
31
- ],
32
26
  },
33
27
  ];
34
28
  }
@@ -97,20 +91,6 @@ export function buildSuggestedAnswers(question, knowledge) {
97
91
  ];
98
92
  }
99
93
  }
100
- // Performance questions
101
- if (lower.includes('performance') ||
102
- lower.includes('response time') ||
103
- lower.includes('throughput')) {
104
- return ['< 200ms response', '< 1s response', 'No strict requirement', 'Depends on load'];
105
- }
106
- // Auth questions
107
- if (lower.includes('authentication') || lower.includes('auth')) {
108
- return ['JWT tokens', 'Session-based', 'OAuth 2.0', 'No authentication needed', 'API key'];
109
- }
110
- // Timeline questions
111
- if (lower.includes('deadline') || lower.includes('timeline')) {
112
- return ['No strict deadline', 'Within 1 week', 'Within 1 sprint (2 weeks)', 'Specific date'];
113
- }
114
94
  return [];
115
95
  }
116
96
  //# sourceMappingURL=questions.js.map
@@ -6,6 +6,12 @@ import { isShortTopic, generateMetaQuestions, detectContradictions, buildSuggest
6
6
  import { generateQuestions } from './clarify-requirements/questions-context.js';
7
7
  import { detectStack, shouldSkipQuestion, attachOptions, buildSuggestedDescription, } from './clarify-requirements/multiple-choice.js';
8
8
  import { compactJson, compactResult, compactError } from './output-formatter.js';
9
+ function buildConfirmedChoices(questions, answers) {
10
+ return questions.flatMap((question) => {
11
+ const value = answers[question.id]?.trim();
12
+ return value ? [{ questionId: question.id, value, source: 'user_confirmation' }] : [];
13
+ });
14
+ }
9
15
  /** Resume an existing clarification session with new answers. */
10
16
  async function handleResumeSession(projectId, sessionId, newAnswers, knowledge) {
11
17
  const existing = await knowledgeStore.getClarification(projectId, sessionId).catch(() => null);
@@ -50,11 +56,14 @@ async function handleResumeSession(projectId, sessionId, newAnswers, knowledge)
50
56
  category: q.category,
51
57
  importance: q.importance,
52
58
  suggestedAnswers: q.suggestedAnswers,
59
+ ...(q.suggestedAnswers.length > 0 ? { suggestedAnswersClassification: 'candidate' } : {}),
53
60
  options: q.options,
61
+ ...(q.options ? { optionsClassification: 'candidate' } : {}),
54
62
  skipped: q.skipped,
55
63
  answered: mergedAnswers[q.id] !== undefined,
56
64
  })),
57
65
  specReady,
66
+ confirmedChoices: buildConfirmedChoices(enriched, mergedAnswers),
58
67
  ...(suggestedDescription !== undefined ? { suggestedDescription } : {}),
59
68
  answeredCount: Object.keys(mergedAnswers).length,
60
69
  ...(contradictions.length > 0
@@ -126,6 +135,29 @@ export async function handleClarifyRequirements(params, server) {
126
135
  const skipped = shouldSkipQuestion(q, stack);
127
136
  return skipped ? { ...withOptions, skipped: true } : withOptions;
128
137
  });
138
+ const detectedEvidence = [
139
+ ...(stack.language
140
+ ? [{ field: 'language', value: stack.language, source: 'project_manifest' }]
141
+ : knowledge?.language && knowledge.language !== 'unknown'
142
+ ? [{ field: 'language', value: knowledge.language, source: 'project_knowledge' }]
143
+ : []),
144
+ ...(stack.framework
145
+ ? [{ field: 'framework', value: stack.framework, source: 'project_manifest' }]
146
+ : knowledge?.framework
147
+ ? [
148
+ {
149
+ field: 'framework',
150
+ value: knowledge.framework,
151
+ source: 'project_knowledge',
152
+ },
153
+ ]
154
+ : []),
155
+ ...(stack.database
156
+ ? [{ field: 'database', value: stack.database, source: 'project_manifest' }]
157
+ : knowledge?.database && knowledge.database !== 'unknown'
158
+ ? [{ field: 'database', value: knowledge.database, source: 'project_knowledge' }]
159
+ : []),
160
+ ];
129
161
  // Determine if spec is ready (all non-skipped critical questions have non-empty answers)
130
162
  const criticalIds = questions
131
163
  .filter((q) => q.importance === 'critical' && q.skipped !== true)
@@ -170,9 +202,15 @@ export async function handleClarifyRequirements(params, server) {
170
202
  suggestedAnswer: q.suggestedAnswer,
171
203
  /* v8 ignore next 1 */
172
204
  suggestedAnswers: q.suggestedAnswers ?? [],
205
+ ...(q.suggestedAnswers && q.suggestedAnswers.length > 0
206
+ ? { suggestedAnswersClassification: 'candidate' }
207
+ : {}),
173
208
  options: q.options,
209
+ ...(q.options ? { optionsClassification: 'candidate' } : {}),
174
210
  skipped: q.skipped,
175
211
  })),
212
+ detectedEvidence,
213
+ confirmedChoices: buildConfirmedChoices(questions, currentAnswers),
176
214
  specReady,
177
215
  ...(suggestedDescription !== undefined ? { suggestedDescription } : {}),
178
216
  instructions: specReady
@@ -3,8 +3,8 @@ import type { ToolResult } from '../types/index.js';
3
3
  export declare const ConfigureComplianceGateSchema: z.ZodObject<{
4
4
  projectPath: z.ZodString;
5
5
  mode: z.ZodEnum<{
6
- soft: "soft";
7
6
  hard: "hard";
7
+ soft: "soft";
8
8
  }>;
9
9
  threshold: z.ZodDefault<z.ZodNumber>;
10
10
  }, z.core.$strip>;
@@ -20,7 +20,7 @@ export declare function getEmptyAutopilotResult(): AutopilotAnalysis;
20
20
  * Analyze the project to enrich a spec with real file paths and patterns.
21
21
  * Runs automatically during create_spec — no user interaction needed.
22
22
  */
23
- export declare function analyzeProjectForSpec(projectPath: string, description: string, title: string, knowledge: ProjectKnowledge | null): Promise<AutopilotAnalysis>;
23
+ export declare function analyzeProjectForSpec(projectPath: string, description: string, title: string, _knowledge: ProjectKnowledge | null): Promise<AutopilotAnalysis>;
24
24
  /**
25
25
  * SPEC-619 AC1: Detect broad-scope descriptions that mention 2+ major subsystems.
26
26
  * When true, create_spec should ask clarifying questions before proceeding.
@@ -19,7 +19,7 @@ export function getEmptyAutopilotResult() {
19
19
  * Analyze the project to enrich a spec with real file paths and patterns.
20
20
  * Runs automatically during create_spec — no user interaction needed.
21
21
  */
22
- export async function analyzeProjectForSpec(projectPath, description, title, knowledge) {
22
+ export async function analyzeProjectForSpec(projectPath, description, title, _knowledge) {
23
23
  const result = {
24
24
  suggestedFiles: { create: [], modify: [], test: [] },
25
25
  detectedPatterns: [],
@@ -68,10 +68,8 @@ export async function analyzeProjectForSpec(projectPath, description, title, kno
68
68
  catch {
69
69
  // Best-effort — file scanning failure doesn't block spec creation
70
70
  }
71
- // Detect stack patterns and suggest relevant criteria
72
- if (knowledge) {
73
- result.detectedPatterns = detectPatterns(knowledge, description);
74
- }
71
+ // Detect patterns only from affirmed request wording.
72
+ result.detectedPatterns = detectPatterns(description);
75
73
  return result;
76
74
  }
77
75
  /** Check if description contains technical terms (stack, framework, or code references). */
@@ -89,7 +87,23 @@ function hasTechnicalTerms(description) {
89
87
  /\.\w{2,4}\b/, // file extensions like .ts, .py
90
88
  /src\/|tests\/|lib\/|app\//, // file paths
91
89
  ];
92
- return techTerms.some((re) => re.test(description));
90
+ return techTerms.some((pattern) => hasAffirmedPattern(description, pattern));
91
+ }
92
+ 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;
93
+ const INCIDENTAL_CONTEXT_RE = /\b(?:example|for example|e\.g\.|quoted|regression|incorrectly inferred|must not infer)\b/i;
94
+ function hasAffirmedPattern(description, pattern) {
95
+ const withoutQuotedText = description
96
+ .replace(/```[\s\S]*?```/g, ' ')
97
+ .replace(/`[^`]+`/g, ' ')
98
+ .replace(/"[^"]+"|'[^']+'/g, ' ');
99
+ return withoutQuotedText.split(/(?:\r?\n|[.;!?]+)/).some((segment) => {
100
+ const match = new RegExp(pattern.source, pattern.flags.replace('g', '')).exec(segment);
101
+ if (!match) {
102
+ return false;
103
+ }
104
+ const prefix = segment.slice(0, match.index);
105
+ return !NEGATIVE_CONTEXT_RE.test(prefix) && !INCIDENTAL_CONTEXT_RE.test(prefix);
106
+ });
93
107
  }
94
108
  /**
95
109
  * SPEC-619 AC1: Detect broad-scope descriptions that mention 2+ major subsystems.
@@ -100,8 +114,7 @@ function hasTechnicalTerms(description) {
100
114
  * one feature, not two independent systems).
101
115
  */
102
116
  export function detectBroadScope(description) {
103
- const lower = description.toLowerCase();
104
- const wordCount = lower.trim().split(/\s+/).length;
117
+ const wordCount = description.trim().split(/\s+/).length;
105
118
  const subsystems = [
106
119
  /\b(auth|authentication|login|signup|sign.?up|register|oauth|sso)\b/,
107
120
  /\b(payment|billing|invoice|subscription|stripe|checkout|pricing)\b/,
@@ -114,7 +127,7 @@ export function detectBroadScope(description) {
114
127
  /\b(user.?management|profile|account|role|permission|rbac)\b/,
115
128
  /\b(chat|messaging|real.?time|websocket|feed|timeline)\b/,
116
129
  ];
117
- const matched = subsystems.filter((re) => re.test(lower));
130
+ const matched = subsystems.filter((pattern) => hasAffirmedPattern(description, pattern));
118
131
  // Longer descriptions are specific enough to reference adjacent domain terms
119
132
  // without being genuinely broad (e.g. "Stripe billing + webhook handler" = one feature).
120
133
  const threshold = wordCount >= 20 ? 3 : 2;
@@ -135,62 +148,24 @@ function inferTestPath(srcPath) {
135
148
  return `tests/${withoutExt}.test${ext}`;
136
149
  }
137
150
  /** Detect relevant patterns from project knowledge. */
138
- function detectPatterns(knowledge, description) {
151
+ function detectPatterns(description) {
139
152
  const patterns = [];
140
- const desc = description.toLowerCase();
141
- const hasDatabaseIntent = desc.includes('data') ||
142
- desc.includes('database') ||
143
- desc.includes('tabla') ||
144
- desc.includes('schema') ||
145
- desc.includes('rls') ||
146
- desc.includes('rpc') ||
147
- desc.includes('migration') ||
148
- desc.includes('persistence') ||
149
- desc.includes('data access');
150
- const hasUiIntent = desc.includes('component') ||
151
- desc.includes('ui') ||
152
- desc.includes('page') ||
153
- desc.includes('screen') ||
154
- desc.includes('form') ||
155
- desc.includes('layout') ||
156
- desc.includes('style') ||
157
- desc.includes('button') ||
158
- desc.includes('modal') ||
159
- desc.includes('dialog');
153
+ const hasDatabaseIntent = hasAffirmedPattern(description, /\b(?:data|database|tabla|schema|rls|rpc|migration|persistence|data access)\b/i);
160
154
  if (hasDatabaseIntent) {
161
155
  patterns.push('database');
162
156
  }
163
- if (knowledge.framework?.toLowerCase().includes('next') && desc.includes('page')) {
164
- patterns.push('nextjs-pages');
165
- }
166
- if (knowledge.framework?.toLowerCase().includes('react') && hasUiIntent) {
167
- patterns.push('react-components');
168
- }
169
- if (desc.includes('api') || desc.includes('endpoint')) {
157
+ if (hasAffirmedPattern(description, /\b(?:api|endpoint)\b/i)) {
170
158
  patterns.push('api-endpoint');
171
159
  }
172
- if (desc.includes('auth') || desc.includes('login')) {
160
+ if (hasAffirmedPattern(description, /\b(?:auth|authentication|login)\b/i)) {
173
161
  patterns.push('authentication');
174
162
  }
175
- if (desc.includes('test') || desc.includes('coverage')) {
163
+ if (hasAffirmedPattern(description, /\b(?:test|testing|coverage)\b/i)) {
176
164
  patterns.push('testing');
177
165
  }
178
- if (knowledge.stack.some((s) => s.toLowerCase().includes('supabase')) && hasDatabaseIntent) {
179
- patterns.push('supabase');
180
- }
181
166
  // SPEC-535: Detect LLM/foundation model features for EU AI Act Article 53-55 compliance
182
- if (desc.includes('llm') ||
183
- desc.includes('ai ') ||
184
- desc.includes(' ai') ||
185
- desc.includes('claude') ||
186
- desc.includes('openai') ||
187
- desc.includes('gpt') ||
188
- desc.includes('gemini') ||
189
- desc.includes('anthropic') ||
190
- desc.includes('chatbot') ||
191
- desc.includes('language model') ||
192
- desc.includes('foundation model') ||
193
- (desc.includes('chat') && desc.includes('model'))) {
167
+ if (hasAffirmedPattern(description, /\b(?:llm|ai|claude|openai|gpt|gemini|anthropic|chatbot|language model|foundation model)\b/i) ||
168
+ (hasAffirmedPattern(description, /\bchat\b/i) && hasAffirmedPattern(description, /\bmodel\b/i))) {
194
169
  patterns.push('llm-feature');
195
170
  }
196
171
  return patterns;