@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
|
@@ -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((
|
|
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
|
|
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
|
|
207
|
+
// Item 4: Suggest templates only when the user explicitly requests one.
|
|
192
208
|
for (const { pattern, category } of TEMPLATE_PATTERNS) {
|
|
193
|
-
if (pattern
|
|
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 {
|
|
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
|
-
|
|
87
|
-
|
|
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(
|
|
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
|
|
139
|
-
|
|
140
|
-
|
|
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,
|
|
165
|
-
|
|
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
|
-
...(
|
|
174
|
-
|
|
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
|
-
/**
|
|
248
|
-
function resolveOutOfScope(
|
|
249
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
1044
|
-
if (
|
|
1045
|
-
spec.outOfScope =
|
|
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,
|
|
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
|
|
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', `
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
38
|
-
|
|
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
|
-
//
|
|
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
|
|
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:
|
|
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 [
|
|
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('
|
|
222
|
+
.describe('Explicit user-confirmed out-of-scope items for this spec.'),
|
|
214
223
|
specId: z
|
|
215
224
|
.string()
|
|
216
225
|
.optional()
|