@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.
- package/CHANGELOG.md +16 -0
- package/dist/config/elicitation-questions.json +5 -17
- package/dist/engine/cascade-hooks/registry.js +0 -2
- package/dist/engine/challenge-scenarios-loader.d.ts +3 -3
- package/dist/engine/compliance-injector.d.ts +3 -3
- package/dist/engine/dashboard/kanban-logic.d.ts +1 -1
- package/dist/engine/doc-generator/portal/risk-matrix-svg.d.ts +1 -1
- package/dist/engine/doc-generator/proposal/proposal-generator.d.ts +1 -1
- package/dist/engine/elicitation/answer-extractor.d.ts +1 -4
- package/dist/engine/elicitation/answer-extractor.js +197 -68
- package/dist/engine/elicitation/decision-gap-detector.js +59 -79
- package/dist/engine/elicitation/index.d.ts +0 -1
- package/dist/engine/elicitation/index.js +0 -1
- package/dist/engine/elicitation/question-generator.js +10 -3
- package/dist/engine/elicitation/question-grounding-gate.js +83 -34
- package/dist/engine/evidence-gates/lifecycle-gate.js +27 -14
- package/dist/engine/evidence-index/index-builder.js +7 -2
- package/dist/engine/frontmatter-sha/frozen-fields.d.ts +1 -1
- package/dist/engine/handoff-artifacts/schemas/red-tests-handoff.d.ts +2 -2
- package/dist/engine/handoff-artifacts/schemas.d.ts +22 -22
- package/dist/engine/human-summary.js +3 -3
- package/dist/engine/implementation-contract/common.d.ts +1 -1
- package/dist/engine/implementation-contract/renderer.js +17 -39
- package/dist/engine/keyword-matcher/stopwords.d.ts +2 -2
- package/dist/engine/lifecycle-hints.d.ts +1 -1
- package/dist/engine/lifecycle-hints.js +13 -29
- package/dist/engine/provider-adapters/adapters/claude.d.ts +1 -1
- package/dist/engine/provider-adapters/adapters/gemini.d.ts +1 -1
- package/dist/engine/provider-adapters/adapters/gpt4.d.ts +1 -1
- package/dist/engine/provider-adapters/adapters/markdown.d.ts +1 -1
- package/dist/engine/safety/atomic-write-file.d.ts +1 -1
- package/dist/engine/safety/cross-process-lock.d.ts +1 -1
- package/dist/engine/safety/path-sanitizer.d.ts +1 -1
- package/dist/engine/security-analyzer/profile-detector.js +48 -16
- package/dist/engine/spec-format/copula-blacklist.d.ts +2 -2
- package/dist/engine/spec-format/lean-spec-generator.d.ts +2 -2
- package/dist/engine/spec-format/lean-spec-generator.js +9 -10
- package/dist/engine/spec-format/metadata-value-policy.d.ts +20 -20
- package/dist/engine/spec-generator/fallback-generator.js +13 -20
- package/dist/engine/spec-grounding/contract.d.ts +5 -0
- package/dist/engine/spec-grounding/contract.js +10 -6
- package/dist/engine/spec-migrator/lean-migration.js +9 -16
- package/dist/engine/text-signal-boundaries.js +45 -12
- package/dist/engine/universal-rules/rules/planu-modes.js +0 -2
- package/dist/engine/web-fetcher/stack-advisor.d.ts +2 -2
- package/dist/engine/web-fetcher/stack-advisor.js +78 -17
- package/dist/engine/well-known/manifest-schema.d.ts +33 -33
- package/dist/engine/workers/schema.d.ts +3 -3
- package/dist/hosts/claude-code/ux/mcp-resources.d.ts +4 -4
- package/dist/hosts/codex/rest-shim.d.ts +1 -1
- package/dist/native/lightweight-command-catalog.d.ts +30 -30
- package/dist/server/openapi-spec.d.ts +132 -132
- package/dist/tools/challenge-spec/challenge-report.d.ts +13 -0
- package/dist/tools/challenge-spec/challenge-report.js +55 -0
- package/dist/tools/challenge-spec/event-challenge-scenarios.js +8 -27
- package/dist/tools/challenge-spec/resilience-challenge-scenarios-a.js +16 -32
- package/dist/tools/challenge-spec/resilience-challenge-scenarios-b.js +15 -20
- package/dist/tools/challenge-spec/scenario-collector.d.ts +13 -0
- package/dist/tools/challenge-spec/scenario-collector.js +86 -0
- package/dist/tools/challenge-spec/scenarios-data.js +10 -9
- package/dist/tools/challenge-spec/scenarios-failure.js +38 -54
- package/dist/tools/challenge-spec/scenarios-scale.js +14 -15
- package/dist/tools/challenge-spec/scenarios-security.d.ts +1 -1
- package/dist/tools/challenge-spec/scenarios-security.js +18 -11
- package/dist/tools/challenge-spec/scenarios-utils.d.ts +31 -5
- package/dist/tools/challenge-spec/scenarios-utils.js +102 -6
- package/dist/tools/challenge-spec/security-challenge-scenarios.d.ts +1 -1
- package/dist/tools/challenge-spec/security-challenge-scenarios.js +20 -25
- package/dist/tools/challenge-spec.js +46 -124
- package/dist/tools/clarify-requirements/multiple-choice.js +1 -1
- package/dist/tools/clarify-requirements/questions-context.js +11 -11
- package/dist/tools/clarify-requirements/questions.js +3 -23
- package/dist/tools/clarify-requirements.js +38 -0
- package/dist/tools/compliance-gate-handler.d.ts +1 -1
- package/dist/tools/create-spec/autopilot-analyzer.d.ts +1 -1
- package/dist/tools/create-spec/autopilot-analyzer.js +29 -54
- package/dist/tools/create-spec/post-creation.js +23 -4
- package/dist/tools/create-spec.js +51 -168
- package/dist/tools/delete-first/suggest-deletions.d.ts +1 -1
- package/dist/tools/elicit-requirements-handler.js +20 -12
- package/dist/tools/generate-batch-script.d.ts +2 -2
- package/dist/tools/init-project/rules-generator.js +0 -1
- package/dist/tools/oauth-handler.d.ts +1 -1
- package/dist/tools/register-sdd-tools.d.ts +1 -1
- package/dist/tools/register-spec-tools/core-spec-tools.js +11 -2
- package/dist/tools/render-spec-for-provider.d.ts +2 -2
- package/dist/tools/scan-orphan-spec-refs.d.ts +6 -6
- package/dist/tools/schemas/agents.d.ts +7 -7
- package/dist/tools/schemas/analysis.d.ts +11 -11
- package/dist/tools/schemas/delete.d.ts +5 -5
- package/dist/tools/schemas/github.d.ts +4 -4
- package/dist/tools/schemas/ide-config.d.ts +12 -12
- package/dist/tools/schemas/infra.d.ts +9 -9
- package/dist/tools/schemas/lifecycle.d.ts +50 -50
- package/dist/tools/schemas/llm-provider-schemas.d.ts +7 -7
- package/dist/tools/schemas/plugins-schemas.d.ts +12 -12
- package/dist/tools/schemas/project-graph.d.ts +6 -6
- package/dist/tools/schemas/rbac.d.ts +2 -2
- package/dist/tools/schemas/registry.d.ts +3 -3
- package/dist/tools/schemas/runtime-security.d.ts +2 -2
- package/dist/tools/schemas/session.d.ts +2 -2
- package/dist/tools/schemas/spec.d.ts +15 -15
- package/dist/tools/schemas/token-intelligence.d.ts +6 -6
- package/dist/tools/schemas/token-optimization.d.ts +2 -2
- package/dist/tools/schemas/workers-schema.d.ts +4 -4
- package/dist/tools/skill-bootstrap-handler.d.ts +7 -7
- package/dist/tools/suggest-stack.js +49 -13
- package/dist/tools/tool-registry/deprecated-stubs.d.ts +1 -1
- package/dist/tools/triage-request.d.ts +3 -3
- package/dist/tools/update-status/dod-gates.d.ts +8 -0
- package/dist/tools/update-status/dod-gates.js +68 -43
- package/dist/tools/update-status/file-sync.d.ts +1 -1
- package/dist/tools/update-status/file-sync.js +10 -45
- package/dist/tools/update-status/index.js +133 -97
- package/dist/tools/update-status/qa-gate.js +26 -0
- package/dist/tools/update-status/response-builder.d.ts +1 -1
- package/dist/tools/update-status/response-builder.js +11 -19
- package/dist/tools/update-status/transition-guard.d.ts +2 -2
- package/dist/tools/update-status/transition-guard.js +11 -8
- package/dist/tools/update-status-actions.d.ts +0 -1
- package/dist/tools/update-status-actions.js +10 -26
- package/dist/tools/update-status-convention-gate.d.ts +1 -6
- package/dist/tools/update-status-convention-gate.js +9 -33
- package/dist/tools/validate-lint.d.ts +1 -1
- package/dist/tools/validate-lint.js +39 -1
- package/dist/types/docs.d.ts +0 -4
- package/dist/types/env.d.ts +0 -4
- package/dist/types/estimation.d.ts +0 -2
- package/dist/types/multi-teammate-review.d.ts +4 -4
- package/dist/types/plugin-install.d.ts +7 -7
- package/dist/types/spec/core.d.ts +20 -0
- package/dist/types/spec/inputs.d.ts +1 -1
- package/dist/types/stack/index.d.ts +1 -1
- package/dist/types/stack/recommend.d.ts +10 -0
- package/package.json +21 -18
- package/planu-native.json +1 -1
- package/planu-plugin.json +1 -1
- package/src/i18n/messages/en.json +0 -72
- package/src/i18n/messages/es.json +0 -72
- package/src/i18n/messages/pt.json +0 -72
- package/dist/config/elicitation-dimensions.json +0 -51
- package/dist/engine/actuals-estimator.d.ts +0 -14
- package/dist/engine/actuals-estimator.js +0 -64
- package/dist/engine/cascade-hooks/hooks/living-specs.hook.d.ts +0 -3
- package/dist/engine/cascade-hooks/hooks/living-specs.hook.js +0 -19
- package/dist/engine/elicitation/non-interactive-defaults.d.ts +0 -9
- package/dist/engine/elicitation/non-interactive-defaults.js +0 -28
- package/dist/engine/elicitation/option-builder.d.ts +0 -9
- package/dist/engine/elicitation/option-builder.js +0 -85
- package/dist/engine/progress-writer.d.ts +0 -10
- package/dist/engine/progress-writer.js +0 -63
- package/dist/tools/clarify-requirements/interview-mode.d.ts +0 -42
- package/dist/tools/clarify-requirements/interview-mode.js +0 -342
- package/dist/tools/update-status/mode-hints.d.ts +0 -4
- 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
|
-
|
|
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
|
-
//
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
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(
|
|
185
|
+
const prioritized = prioritizeScenarios(actionableFailureScenarios, spec);
|
|
240
186
|
const prioritizedSummary = buildPrioritizedSummary(prioritized);
|
|
241
|
-
|
|
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:
|
|
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:
|
|
217
|
+
actionableScenarios: failureScenariosScored.length,
|
|
275
218
|
suppressedLowRelevanceScenarios: suppressedScenarioCount,
|
|
276
219
|
shownByDefault: 3,
|
|
277
220
|
mustAddressBeforeCoding: prioritizedSummary,
|
|
278
|
-
criticalImpact:
|
|
279
|
-
highImpact:
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
302
|
+
scenarioCount: String(actionableFailureScenarios.length),
|
|
366
303
|
}),
|
|
367
304
|
},
|
|
368
305
|
{
|
|
369
306
|
type: 'text',
|
|
370
|
-
text: `Actionable scenarios: ${String(
|
|
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
|
|
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
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
[
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
/\b(
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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 = [`
|
|
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
|
|
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: `
|
|
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 (
|
|
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 (
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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,
|
|
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,
|
|
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
|
|
72
|
-
|
|
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((
|
|
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
|
|
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((
|
|
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(
|
|
151
|
+
function detectPatterns(description) {
|
|
139
152
|
const patterns = [];
|
|
140
|
-
const
|
|
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 (
|
|
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 (
|
|
160
|
+
if (hasAffirmedPattern(description, /\b(?:auth|authentication|login)\b/i)) {
|
|
173
161
|
patterns.push('authentication');
|
|
174
162
|
}
|
|
175
|
-
if (
|
|
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 (
|
|
183
|
-
|
|
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;
|