@planu/cli 5.7.4 → 5.7.6

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 (208) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +3 -3
  3. package/dist/.planu-build.json +1 -1
  4. package/dist/cli/commands/package-handoff.js +30 -5
  5. package/dist/config/compliance-profiles.json +36 -3
  6. package/dist/config/environment-schema.json +21 -0
  7. package/dist/config/hook-templates/planu-session-safeguard.sh +22 -15
  8. package/dist/config/official-sdd-tools.d.ts +1 -1
  9. package/dist/config/official-sdd-tools.js +11 -24
  10. package/dist/config/registries/hosts/codex.json +14 -30
  11. package/dist/config/registries/hosts/opencode.json +1 -6
  12. package/dist/config/registries/legacy-guidance.json +26 -0
  13. package/dist/config/server-instructions.js +0 -21
  14. package/dist/config/skill-templates/planu-context-assets.md +3 -6
  15. package/dist/config/skill-templates/planu-implement.md +2 -2
  16. package/dist/config/skill-templates/planu-multi-teammate-review.md +1 -1
  17. package/dist/config/skill-templates/planu-release.md +2 -7
  18. package/dist/config/skill-templates/planu-validate.md +3 -3
  19. package/dist/config/spec-templates/crud-rest-api/template.json +5 -1
  20. package/dist/config/spec-templates/file-upload-s3/template.json +5 -1
  21. package/dist/config/spec-templates/stripe-payments/template.json +5 -1
  22. package/dist/config/spec-templates/webhook-system/template.json +5 -1
  23. package/dist/config/subagent-templates/planu-challenger.md +4 -9
  24. package/dist/config/subagent-templates/planu-spec-implementer.md +2 -4
  25. package/dist/config/subagent-templates/planu-validator.md +5 -8
  26. package/dist/config/tool-groups.json +6 -144
  27. package/dist/engine/ambiguity-scorer.js +40 -1
  28. package/dist/engine/autopilot/bootstrap.js +1 -1
  29. package/dist/engine/code-impact-analyzer.js +2 -6
  30. package/dist/engine/context-orchestrator/index.js +2 -2
  31. package/dist/engine/convention-scanner/codebase-scanner.js +2 -2
  32. package/dist/engine/dogfooding/runtime-gap-detector.js +2 -2
  33. package/dist/engine/evidence-gates/artifact-reader.d.ts +1 -0
  34. package/dist/engine/evidence-gates/artifact-reader.js +1 -1
  35. package/dist/engine/evidence-gates/evidence-autofill.d.ts +13 -5
  36. package/dist/engine/evidence-gates/evidence-autofill.js +106 -43
  37. package/dist/engine/evidence-gates/lifecycle-gate.js +7 -4
  38. package/dist/engine/evidence-index/index-builder.js +21 -4
  39. package/dist/engine/execution/job-runtime.js +1 -1
  40. package/dist/engine/execution/package-handoff-job-executor.d.ts +22 -0
  41. package/dist/engine/execution/package-handoff-job-executor.js +490 -0
  42. package/dist/engine/execution/validate-job-executor.js +20 -8
  43. package/dist/engine/git/exec-git.d.ts +4 -0
  44. package/dist/engine/git/exec-git.js +14 -0
  45. package/dist/engine/handoff-artifacts/implementation-review-reader.js +15 -2
  46. package/dist/engine/handoff-artifacts/schemas.d.ts +2 -0
  47. package/dist/engine/handoff-artifacts/schemas.js +1 -0
  48. package/dist/engine/handoff-format.d.ts +36 -0
  49. package/dist/engine/handoff-format.js +314 -0
  50. package/dist/engine/handoff-packager.d.ts +5 -3
  51. package/dist/engine/handoff-packager.js +89 -32
  52. package/dist/engine/host-tool-filter.js +20 -49
  53. package/dist/engine/human-summary.js +14 -3
  54. package/dist/engine/implementation-contract/common.d.ts +8 -3
  55. package/dist/engine/implementation-contract/common.js +9 -6
  56. package/dist/engine/implementation-contract/evaluator.js +4 -6
  57. package/dist/engine/implementation-contract/renderer.js +18 -23
  58. package/dist/engine/project-health-checker.js +2 -2
  59. package/dist/engine/readiness-checker.js +27 -0
  60. package/dist/engine/reconcile/apply-changes.js +4 -0
  61. package/dist/engine/reconcile/propagate-mirrors.d.ts +3 -0
  62. package/dist/engine/reconcile/propagate-mirrors.js +311 -0
  63. package/dist/engine/self-healing/healer.js +12 -5
  64. package/dist/engine/self-healing/strategies/lint-fix.d.ts +1 -1
  65. package/dist/engine/self-healing/strategies/lint-fix.js +4 -1
  66. package/dist/engine/self-healing/strategies/test-fix.d.ts +4 -3
  67. package/dist/engine/self-healing/strategies/test-fix.js +32 -14
  68. package/dist/engine/self-healing/strategies/typescript-fix.d.ts +1 -1
  69. package/dist/engine/self-healing/strategies/typescript-fix.js +28 -10
  70. package/dist/engine/session/checkpoint-writer.js +8 -4
  71. package/dist/engine/session/session-tracker.d.ts +5 -16
  72. package/dist/engine/session/session-tracker.js +29 -26
  73. package/dist/engine/session-state/writer.js +3 -11
  74. package/dist/engine/skill-generator/workflow-skill-generator.d.ts +4 -2
  75. package/dist/engine/skill-generator/workflow-skill-generator.js +113 -38
  76. package/dist/engine/spec-format/bdd-parser.d.ts +1 -11
  77. package/dist/engine/spec-format/bdd-parser.js +40 -11
  78. package/dist/engine/spec-format/lean-spec-generator.js +30 -10
  79. package/dist/engine/spec-format/retired-scaffold.d.ts +3 -0
  80. package/dist/engine/spec-format/retired-scaffold.js +5 -0
  81. package/dist/engine/spec-format/technical-md-populator.d.ts +1 -0
  82. package/dist/engine/spec-format/technical-md-populator.js +27 -0
  83. package/dist/engine/spec-format/unified-spec-builder.d.ts +1 -0
  84. package/dist/engine/spec-format/unified-spec-builder.js +30 -20
  85. package/dist/engine/spec-grounding/contract.js +115 -1
  86. package/dist/engine/spec-migrator/fold-technical.js +60 -19
  87. package/dist/engine/spec-migrator/placeholder-technical.d.ts +2 -0
  88. package/dist/engine/spec-migrator/placeholder-technical.js +18 -0
  89. package/dist/engine/spec-quality/generic-output-gate.js +37 -3
  90. package/dist/engine/technical-enricher/index.js +4 -1
  91. package/dist/engine/type-safety-gate.js +3 -11
  92. package/dist/engine/universal-rules/catalog.js +0 -2
  93. package/dist/engine/universal-rules/rules/planu-workflow.js +1 -1
  94. package/dist/engine/validation/durable-validation.d.ts +3 -2
  95. package/dist/engine/validation/durable-validation.js +106 -40
  96. package/dist/engine/validation/validation-freshness.d.ts +3 -2
  97. package/dist/engine/validation/validation-freshness.js +22 -16
  98. package/dist/engine/validation/validation-worktree.js +34 -0
  99. package/dist/hosts/claude-code/ux/mcp-prompts.js +8 -8
  100. package/dist/index.js +3 -17
  101. package/dist/resources/process.js +38 -65
  102. package/dist/storage/session-state-store.js +2 -0
  103. package/dist/tools/challenge-spec/scenarios-utils.js +2 -2
  104. package/dist/tools/configure-checkpoint-policy.js +3 -3
  105. package/dist/tools/create-spec-helpers.js +2 -5
  106. package/dist/tools/create-spec.js +21 -4
  107. package/dist/tools/facilitate.js +3 -6
  108. package/dist/tools/git/branch-ops.js +16 -4
  109. package/dist/tools/github-release-handler.js +1 -20
  110. package/dist/tools/heal-spec-docs.js +1 -13
  111. package/dist/tools/init-project/agents-md-writer.js +6 -6
  112. package/dist/tools/init-project/claude-md-generator.d.ts +0 -1
  113. package/dist/tools/init-project/claude-md-generator.js +0 -35
  114. package/dist/tools/init-project/handler.js +24 -49
  115. package/dist/tools/init-project/legacy-guidance-scanner.d.ts +12 -0
  116. package/dist/tools/init-project/legacy-guidance-scanner.js +176 -0
  117. package/dist/tools/init-project/per-client-files-writer.js +5 -5
  118. package/dist/tools/init-project/planu-workflow-generator.js +2 -13
  119. package/dist/tools/init-project/portable-index-reconciler.d.ts +21 -1
  120. package/dist/tools/init-project/portable-index-reconciler.js +66 -0
  121. package/dist/tools/init-project/rules-generator.d.ts +0 -11
  122. package/dist/tools/init-project/rules-generator.js +4 -85
  123. package/dist/tools/init-project/scaffold-writer.d.ts +0 -1
  124. package/dist/tools/init-project/scaffold-writer.js +1 -12
  125. package/dist/tools/jobs/handlers.d.ts +0 -6
  126. package/dist/tools/jobs/handlers.js +13 -40
  127. package/dist/tools/list-specs.js +6 -21
  128. package/dist/tools/package-handoff.d.ts +3 -2
  129. package/dist/tools/package-handoff.js +153 -427
  130. package/dist/tools/reconcile-session-safeguard-hook.js +22 -15
  131. package/dist/tools/register-sdd-tools.js +0 -23
  132. package/dist/tools/register-spec-tools/core-spec-tools.js +18 -27
  133. package/dist/tools/schemas/index.d.ts +1 -1
  134. package/dist/tools/schemas/index.js +1 -1
  135. package/dist/tools/schemas/output-schemas.d.ts +1 -18
  136. package/dist/tools/schemas/output-schemas.js +1 -11
  137. package/dist/tools/schemas/package-handoff-output-schema.d.ts +40 -0
  138. package/dist/tools/schemas/package-handoff-output-schema.js +68 -0
  139. package/dist/tools/schemas/validate-output-schema.d.ts +1 -2
  140. package/dist/tools/schemas/validate-output-schema.js +3 -2
  141. package/dist/tools/session-checkpoint.js +2 -2
  142. package/dist/tools/skill-registry/index.d.ts +0 -1
  143. package/dist/tools/skill-registry/index.js +0 -1
  144. package/dist/tools/sync-spec-state-handler.d.ts +2 -2
  145. package/dist/tools/sync-spec-state-handler.js +23 -3
  146. package/dist/tools/tool-registry/core-tools.js +1 -140
  147. package/dist/tools/tool-registry/group-infra.js +2 -39
  148. package/dist/tools/tool-registry/group-quality-compliance.js +9 -1
  149. package/dist/tools/update-status/batch.js +17 -0
  150. package/dist/tools/update-status/dod-gates.js +14 -1
  151. package/dist/tools/update-status/done-receipt-verifier.d.ts +22 -0
  152. package/dist/tools/update-status/done-receipt-verifier.js +56 -26
  153. package/dist/tools/update-status/evidence-gate.js +10 -21
  154. package/dist/tools/update-status/file-sync.d.ts +1 -0
  155. package/dist/tools/update-status/file-sync.js +6 -0
  156. package/dist/tools/update-status/index.d.ts +6 -0
  157. package/dist/tools/update-status/index.js +39 -44
  158. package/dist/tools/update-status-actions.js +2 -7
  159. package/dist/tools/validate.js +34 -28
  160. package/dist/transports/oauth-validator.js +12 -1
  161. package/dist/transports/transport-factory.d.ts +2 -1
  162. package/dist/transports/transport-factory.js +19 -0
  163. package/dist/types/common/primitives.d.ts +2 -0
  164. package/dist/types/durable-job.d.ts +45 -0
  165. package/dist/types/durable-validation.d.ts +1 -1
  166. package/dist/types/evidence-autofill.d.ts +13 -1
  167. package/dist/types/handoff-artifacts.d.ts +2 -1
  168. package/dist/types/index.d.ts +1 -0
  169. package/dist/types/index.js +1 -0
  170. package/dist/types/legacy-guidance.d.ts +20 -0
  171. package/dist/types/legacy-guidance.js +3 -0
  172. package/dist/types/readiness.d.ts +8 -1
  173. package/dist/types/reconcile.d.ts +6 -0
  174. package/dist/types/skill-registry.d.ts +26 -1
  175. package/dist/types/spec/core.d.ts +5 -0
  176. package/dist/types/spec/inputs.d.ts +2 -2
  177. package/dist/types/spec-format.d.ts +11 -1
  178. package/dist/types/spec-grounding.d.ts +12 -0
  179. package/dist/types/ssr-migration.d.ts +1 -1
  180. package/dist/types/transport.d.ts +1 -0
  181. package/dist/types/validation-receipt.d.ts +27 -0
  182. package/package.json +3 -2
  183. package/planu-plugin.json +13 -26
  184. package/src/i18n/messages/en.json +1 -1
  185. package/src/i18n/messages/es.json +1 -1
  186. package/src/i18n/messages/pt.json +1 -1
  187. package/dist/engine/skill-generator/conventions-hasher.d.ts +0 -7
  188. package/dist/engine/skill-generator/conventions-hasher.js +0 -24
  189. package/dist/engine/universal-rules/rules/agent-teams.d.ts +0 -3
  190. package/dist/engine/universal-rules/rules/agent-teams.js +0 -63
  191. package/dist/tools/clarify-requirements/multiple-choice.d.ts +0 -30
  192. package/dist/tools/clarify-requirements/multiple-choice.js +0 -306
  193. package/dist/tools/clarify-requirements/questions-context.d.ts +0 -9
  194. package/dist/tools/clarify-requirements/questions-context.js +0 -74
  195. package/dist/tools/clarify-requirements/questions.d.ts +0 -16
  196. package/dist/tools/clarify-requirements/questions.js +0 -96
  197. package/dist/tools/clarify-requirements.d.ts +0 -4
  198. package/dist/tools/clarify-requirements.js +0 -314
  199. package/dist/tools/code-graph-handler.d.ts +0 -6
  200. package/dist/tools/code-graph-handler.js +0 -72
  201. package/dist/tools/create-rule.d.ts +0 -4
  202. package/dist/tools/create-rule.js +0 -107
  203. package/dist/tools/feedback-handler.d.ts +0 -7
  204. package/dist/tools/feedback-handler.js +0 -150
  205. package/dist/tools/semantic-search-handler.d.ts +0 -7
  206. package/dist/tools/semantic-search-handler.js +0 -71
  207. package/dist/tools/skill-registry/search.d.ts +0 -11
  208. package/dist/tools/skill-registry/search.js +0 -159
@@ -1,96 +0,0 @@
1
- /** Returns true if the topic has fewer than 3 words (extremely vague — meta-questions needed). */
2
- export function isShortTopic(topic) {
3
- return topic.trim().split(/\s+/).length < 3;
4
- }
5
- /** Generate meta-questions to scope a vague topic. */
6
- export function generateMetaQuestions() {
7
- return [
8
- {
9
- id: 'meta-1',
10
- question: 'Which change type best describes the request?',
11
- category: 'scope',
12
- importance: 'critical',
13
- suggestedAnswers: ['New feature', 'Improvement to existing feature', 'Bug fix', 'Refactor'],
14
- },
15
- {
16
- id: 'meta-2',
17
- question: 'What observable outcome must this work produce?',
18
- category: 'scope',
19
- importance: 'critical',
20
- },
21
- {
22
- id: 'meta-3',
23
- question: 'Which boundaries are explicitly in scope and out of scope?',
24
- category: 'scope',
25
- importance: 'critical',
26
- },
27
- ];
28
- }
29
- /**
30
- * Detect contradictions in a set of answers.
31
- * Returns an array of human-readable contradiction descriptions.
32
- */
33
- export function detectContradictions(answers) {
34
- const contradictions = [];
35
- const values = Object.values(answers).map((v) => v.toLowerCase());
36
- const joined = values.join(' ');
37
- // Heuristic: "fast" / "no cache"
38
- if ((joined.includes('fast') || joined.includes('performant') || joined.includes('low latency')) &&
39
- (joined.includes('no cache') ||
40
- joined.includes('without cache') ||
41
- joined.includes('sin cache'))) {
42
- contradictions.push('Performance requirement ("fast") may conflict with "no cache" — caching is a primary tool for low-latency responses.');
43
- }
44
- // Heuristic: "simple" / many features
45
- const featureKeywords = [
46
- 'real-time',
47
- 'realtime',
48
- 'offline',
49
- 'multi-tenant',
50
- 'i18n',
51
- 'analytics',
52
- 'notifications',
53
- ];
54
- const wantsSimple = joined.includes('simple') || joined.includes('minimal') || joined.includes('lightweight');
55
- const wantsComplex = featureKeywords.some((k) => joined.includes(k));
56
- if (wantsSimple && wantsComplex) {
57
- contradictions.push('Scope may be underestimated: "simple/minimal" combined with advanced features (real-time, offline, multi-tenant, etc.) typically results in a complex implementation.');
58
- }
59
- // Heuristic: deadline conflicts
60
- if ((joined.includes('urgent') || joined.includes('asap') || joined.includes('tomorrow')) &&
61
- (joined.includes('comprehensive') ||
62
- joined.includes('complete') ||
63
- joined.includes('all edge cases'))) {
64
- contradictions.push('Timeline conflict: "urgent/ASAP" deadline with "comprehensive/all edge cases" scope — consider scoping down to an MVP first.');
65
- }
66
- return contradictions;
67
- }
68
- /**
69
- * Build suggested answers for a question using project knowledge.
70
- */
71
- export function buildSuggestedAnswers(question, knowledge) {
72
- const lower = question.question.toLowerCase();
73
- // Database-related: suggest the project's database
74
- if (lower.includes('database') || lower.includes('table') || lower.includes('schema')) {
75
- if (knowledge && knowledge.database !== 'unknown') {
76
- return [
77
- `Yes, using project's ${knowledge.database} database`,
78
- 'No new tables needed',
79
- 'Needs new tables',
80
- 'Modifies existing tables',
81
- ];
82
- }
83
- }
84
- // Framework-related: suggest the project's framework
85
- if (lower.includes('framework') || lower.includes('pattern') || lower.includes('component')) {
86
- if (knowledge?.framework) {
87
- return [
88
- `Yes, reuse ${knowledge.framework} patterns`,
89
- 'New pattern needed',
90
- 'Mix of existing and new',
91
- ];
92
- }
93
- }
94
- return [];
95
- }
96
- //# sourceMappingURL=questions.js.map
@@ -1,4 +0,0 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import type { ClarifyRequirementsInput, ToolResult } from '../types/index.js';
3
- export declare function handleClarifyRequirements(params: ClarifyRequirementsInput, server?: McpServer): Promise<ToolResult>;
4
- //# sourceMappingURL=clarify-requirements.d.ts.map
@@ -1,314 +0,0 @@
1
- import { elicitOrFallback, buildEnumSchema } from '../engine/elicitation/elicit-helper.js';
2
- import { ti } from '../i18n/index.js';
3
- import { knowledgeStore } from '../storage/index.js';
4
- import { randomUUID } from 'node:crypto';
5
- import { isShortTopic, generateMetaQuestions, detectContradictions, buildSuggestedAnswers, } from './clarify-requirements/questions.js';
6
- import { generateQuestions } from './clarify-requirements/questions-context.js';
7
- import { detectStack, shouldSkipQuestion, attachOptions, buildSuggestedDescription, } from './clarify-requirements/multiple-choice.js';
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
- }
15
- /** Resume an existing clarification session with new answers. */
16
- async function handleResumeSession(projectId, sessionId, newAnswers, knowledge) {
17
- const existing = await knowledgeStore.getClarification(projectId, sessionId).catch(() => null);
18
- if (!existing) {
19
- return compactError(`Session '${sessionId}' not found. Start a new session by calling clarify_requirements without sessionId.`);
20
- }
21
- const mergedAnswers = { ...existing.answers, ...newAnswers };
22
- const contradictions = Object.keys(mergedAnswers).length > 0 ? detectContradictions(mergedAnswers) : [];
23
- const criticalIds = existing.questions
24
- .filter((q) => q.importance === 'critical')
25
- .map((q) => q.id);
26
- const specReady = criticalIds.every((id) => {
27
- const ans = mergedAnswers[id];
28
- return ans !== undefined && ans.trim() !== '';
29
- });
30
- const enriched = existing.questions.map((q) => ({
31
- ...q,
32
- suggestedAnswers: q.suggestedAnswers ?? buildSuggestedAnswers(q, knowledge),
33
- }));
34
- const suggestedDescription = specReady
35
- ? buildSuggestedDescription(existing.topic, mergedAnswers, enriched)
36
- : undefined;
37
- const updated = {
38
- ...existing,
39
- questions: enriched,
40
- answers: mergedAnswers,
41
- isComplete: specReady,
42
- contradictions: contradictions.length > 0 ? contradictions : undefined,
43
- specReady,
44
- interviewMode: 'multiple-choice',
45
- ...(suggestedDescription !== undefined ? { suggestedDescription } : {}),
46
- };
47
- await knowledgeStore.addClarification(projectId, updated);
48
- const result = {
49
- sessionId: existing.id,
50
- topic: existing.topic,
51
- interviewMode: 'multiple-choice',
52
- questionsCount: existing.questions.length,
53
- questions: enriched.map((q) => ({
54
- id: q.id,
55
- question: q.question,
56
- category: q.category,
57
- importance: q.importance,
58
- suggestedAnswers: q.suggestedAnswers,
59
- ...(q.suggestedAnswers.length > 0 ? { suggestedAnswersClassification: 'candidate' } : {}),
60
- options: q.options,
61
- ...(q.options ? { optionsClassification: 'candidate' } : {}),
62
- skipped: q.skipped,
63
- answered: mergedAnswers[q.id] !== undefined,
64
- })),
65
- specReady,
66
- confirmedChoices: buildConfirmedChoices(enriched, mergedAnswers),
67
- ...(suggestedDescription !== undefined ? { suggestedDescription } : {}),
68
- answeredCount: Object.keys(mergedAnswers).length,
69
- ...(contradictions.length > 0
70
- ? {
71
- contradictions,
72
- contradictionWarning: 'Potential contradictions detected — review before creating the spec.',
73
- }
74
- : {}),
75
- instructions: specReady
76
- ? 'Requirements gathered. Present the suggestedDescription to the user and ask for explicit confirmation before calling create_spec. Do NOT call create_spec automatically.'
77
- : `${criticalIds.length - criticalIds.filter((id) => mergedAnswers[id]?.trim()).length} critical question(s) remaining.`,
78
- message: `Session resumed. ${Object.keys(mergedAnswers).length}/${existing.questions.length} answered.`,
79
- // SPEC-542: Confirmation required before calling create_spec
80
- ...(specReady
81
- ? {
82
- confirmationRequired: {
83
- needsConfirmation: true,
84
- question: 'Do you want me to create the spec with these gathered requirements?',
85
- header: 'Create Spec?',
86
- options: [
87
- {
88
- label: 'Yes, create the spec',
89
- description: 'Proceed to call create_spec with the clarificationSessionId.',
90
- },
91
- {
92
- label: 'No, review first',
93
- description: 'Review or adjust the requirements before creating the spec.',
94
- },
95
- ],
96
- note: 'This tool does NOT create specs automatically. create_spec must be called separately after explicit user confirmation.',
97
- },
98
- }
99
- : {}),
100
- };
101
- return compactResult(compactJson(result, 200));
102
- }
103
- export async function handleClarifyRequirements(params, server) {
104
- const { projectId, topic, context, maxQuestions = 7, answers, sessionId } = params;
105
- try {
106
- // Load project knowledge — proceed with null if project not yet initialized (SPEC-020: criterion 5.1)
107
- const knowledge = await knowledgeStore.getKnowledge(projectId).catch(() => null);
108
- // Resume existing session if sessionId provided
109
- if (sessionId) {
110
- return await handleResumeSession(projectId, sessionId, answers ?? {}, knowledge);
111
- }
112
- // Detect contradictions in provided answers (if any)
113
- const contradictions = answers && Object.keys(answers).length > 0 ? detectContradictions(answers) : [];
114
- // If topic is vague (< 10 words), prepend meta-questions
115
- const metaQuestions = isShortTopic(topic)
116
- ? generateMetaQuestions()
117
- : [];
118
- // Generate main clarification questions (cap total at maxQuestions)
119
- const mainMaxQ = Math.max(1, maxQuestions - metaQuestions.length);
120
- const mainQuestions = generateQuestions(topic, context, knowledge, mainMaxQ);
121
- const rawQuestions = [...metaQuestions, ...mainQuestions].slice(0, maxQuestions);
122
- // Detect project stack for auto-skip and option filtering (SPEC-337)
123
- const projectPath = knowledge?.projectPath ?? '';
124
- const stack = projectPath !== ''
125
- ? await detectStack(projectPath).catch(() => ({
126
- framework: null,
127
- language: null,
128
- database: null,
129
- detectedLibs: [],
130
- }))
131
- : { framework: null, language: null, database: null, detectedLibs: [] };
132
- // Apply multiple-choice options + mark skipped questions (SPEC-337)
133
- const questions = rawQuestions.map((q) => {
134
- const withOptions = attachOptions(q);
135
- const skipped = shouldSkipQuestion(q, stack);
136
- return skipped ? { ...withOptions, skipped: true } : withOptions;
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
- ];
161
- // Determine if spec is ready (all non-skipped critical questions have non-empty answers)
162
- const criticalIds = questions
163
- .filter((q) => q.importance === 'critical' && q.skipped !== true)
164
- .map((q) => q.id);
165
- const specReady = answers !== undefined &&
166
- criticalIds.every((id) => {
167
- const ans = answers[id];
168
- return ans !== undefined && ans.trim() !== '';
169
- });
170
- // Build suggestedDescription when spec is ready (SPEC-337)
171
- const currentAnswers = answers ?? {};
172
- const suggestedDescription = specReady
173
- ? buildSuggestedDescription(topic, currentAnswers, questions)
174
- : undefined;
175
- // Create a clarification session
176
- const session = {
177
- id: randomUUID().slice(0, 8),
178
- projectId,
179
- topic,
180
- questions,
181
- answers: currentAnswers,
182
- isComplete: specReady,
183
- createdAt: new Date().toISOString(),
184
- contradictions: contradictions.length > 0 ? contradictions : undefined,
185
- specReady,
186
- interviewMode: 'multiple-choice',
187
- ...(suggestedDescription !== undefined ? { suggestedDescription } : {}),
188
- };
189
- // Persist the session
190
- await knowledgeStore.addClarification(projectId, session);
191
- const result = {
192
- sessionId: session.id,
193
- topic,
194
- interviewMode: 'multiple-choice',
195
- questionsCount: questions.length,
196
- hasMetaQuestions: metaQuestions.length > 0,
197
- questions: questions.map((q) => ({
198
- id: q.id,
199
- question: q.question,
200
- category: q.category,
201
- importance: q.importance,
202
- suggestedAnswer: q.suggestedAnswer,
203
- /* v8 ignore next 1 */
204
- suggestedAnswers: q.suggestedAnswers ?? [],
205
- ...(q.suggestedAnswers && q.suggestedAnswers.length > 0
206
- ? { suggestedAnswersClassification: 'candidate' }
207
- : {}),
208
- options: q.options,
209
- ...(q.options ? { optionsClassification: 'candidate' } : {}),
210
- skipped: q.skipped,
211
- })),
212
- detectedEvidence,
213
- confirmedChoices: buildConfirmedChoices(questions, currentAnswers),
214
- specReady,
215
- ...(suggestedDescription !== undefined ? { suggestedDescription } : {}),
216
- instructions: specReady
217
- ? 'Requirements gathered. Present the suggestedDescription to the user and ask for explicit confirmation before calling create_spec. Do NOT call create_spec automatically.'
218
- : 'Answer the questions above, then call create_spec with the gathered context. You can skip nice-to-have questions if the user does not have strong opinions.',
219
- message: ti('tools.clarify_requirements.success', {
220
- count: String(questions.length),
221
- topic,
222
- }),
223
- };
224
- if (contradictions.length > 0) {
225
- result.contradictions = contradictions;
226
- result.contradictionWarning =
227
- 'Potential contradictions detected in your answers — please review before creating the spec.';
228
- }
229
- // SPEC-542: When requirements are ready, add a confirmation question so the LLM
230
- // MUST ask the user before calling create_spec — never create silently.
231
- if (specReady) {
232
- result.confirmationRequired = {
233
- needsConfirmation: true,
234
- question: 'Do you want me to create the spec with these gathered requirements?',
235
- header: 'Create Spec?',
236
- options: [
237
- {
238
- label: 'Yes, create the spec',
239
- description: 'Proceed to call create_spec with the clarificationSessionId.',
240
- },
241
- {
242
- label: 'No, review first',
243
- description: 'Review or adjust the requirements before creating the spec.',
244
- },
245
- ],
246
- note: 'This tool does NOT create specs automatically. create_spec must be called separately after explicit user confirmation.',
247
- };
248
- }
249
- // SPEC-595: Elicit the first unanswered critical question directly when client supports it
250
- if (server !== undefined && !specReady) {
251
- const firstPending = questions.find((q) => q.importance === 'critical' &&
252
- q.skipped !== true &&
253
- (!currentAnswers[q.id] || currentAnswers[q.id] === ''));
254
- if (firstPending !== undefined) {
255
- const options = firstPending.options ?? [];
256
- const enumValues = options.length > 0 && options.length <= 5
257
- ? options.map((o) => typeof o === 'string'
258
- ? o
259
- : (o.value ??
260
- o.label ??
261
- String(o)))
262
- : ['yes', 'no', 'unsure', 'other'];
263
- const enumNames = options.length > 0 && options.length <= 5
264
- ? options.map((o) => typeof o === 'string' ? o : (o.label ?? String(o)))
265
- : ['Yes', 'No', 'Unsure', 'Other'];
266
- const { field, property } = buildEnumSchema(firstPending.id, enumValues, enumNames, firstPending.question);
267
- const schema = { type: 'object', properties: { [field]: property } };
268
- const fallback = {
269
- header: firstPending.category,
270
- question: firstPending.question,
271
- multiSelect: false,
272
- options: enumValues.map((v, i) => ({
273
- label: v,
274
- description: enumNames[i] ?? v,
275
- })),
276
- };
277
- const outcome = await elicitOrFallback(server, firstPending.question, schema, [fallback]);
278
- if (outcome.mode === 'fallback') {
279
- return {
280
- content: [
281
- {
282
- type: 'text',
283
- text: `⚡ INTERACTIVE — call AskUserQuestion now with the interactiveQuestions from structuredContent`,
284
- },
285
- ],
286
- structuredContent: {
287
- ...result,
288
- interactiveQuestions: outcome.interactiveQuestions,
289
- },
290
- };
291
- }
292
- const elicitedAnswer = outcome.action === 'accept'
293
- ? (outcome.content?.[firstPending.id] ?? '')
294
- : '';
295
- // Embed the elicited answer for the next call
296
- if (elicitedAnswer) {
297
- result.elicitedAnswer = { questionId: firstPending.id, answer: elicitedAnswer };
298
- result.instructions =
299
- `Elicited answer for "${firstPending.id}": "${elicitedAnswer}". ` +
300
- `Pass it in the answers map in the next clarify_requirements call.`;
301
- }
302
- }
303
- }
304
- return compactResult(compactJson(result, 200));
305
- }
306
- catch (error) {
307
- const message = error instanceof Error ? error.message : String(error);
308
- return {
309
- content: [{ type: 'text', text: ti('errors.internalError', { message }) }],
310
- isError: true,
311
- };
312
- }
313
- }
314
- //# sourceMappingURL=clarify-requirements.js.map
@@ -1,6 +0,0 @@
1
- import type { CodeGraphSetupInput, ToolResult } from '../types/index.js';
2
- export declare function handleConfigureCodeGraph(input: CodeGraphSetupInput): Promise<ToolResult>;
3
- export declare function handleCodeGraphStatus(input: {
4
- projectPath: string;
5
- }): Promise<ToolResult>;
6
- //# sourceMappingURL=code-graph-handler.d.ts.map
@@ -1,72 +0,0 @@
1
- // tools/code-graph-handler.ts — Handler for Code Graph integration tools (SPEC-376)
2
- import { readFile, writeFile } from 'node:fs/promises';
3
- import { join } from 'node:path';
4
- import { actionToProviderKey, buildProviderEntry, buildStatusResult, mergeProviderIntoConfig, } from '../engine/code-graph-configurator.js';
5
- const CLAUDE_JSON = '.claude.json';
6
- // ---------------------------------------------------------------------------
7
- // Helpers
8
- // ---------------------------------------------------------------------------
9
- async function readClaudeJson(projectPath) {
10
- try {
11
- const raw = await readFile(join(projectPath, CLAUDE_JSON), 'utf8');
12
- return JSON.parse(raw);
13
- }
14
- catch {
15
- return {};
16
- }
17
- }
18
- async function writeClaudeJson(projectPath, config) {
19
- await writeFile(join(projectPath, CLAUDE_JSON), JSON.stringify(config, null, 2) + '\n', 'utf8');
20
- }
21
- // ---------------------------------------------------------------------------
22
- // configure_code_graph
23
- // ---------------------------------------------------------------------------
24
- export async function handleConfigureCodeGraph(input) {
25
- const { projectPath, action } = input;
26
- if (action === 'status') {
27
- return handleCodeGraphStatus({ projectPath });
28
- }
29
- const providerKey = actionToProviderKey(action);
30
- const entry = buildProviderEntry(providerKey);
31
- /* v8 ignore next 3 */
32
- if (!entry) {
33
- return { content: [{ type: 'text', text: `Unknown action: ${action}` }] };
34
- }
35
- const existing = await readClaudeJson(projectPath);
36
- const updated = mergeProviderIntoConfig(existing, providerKey, entry);
37
- await writeClaudeJson(projectPath, updated);
38
- const lines = [
39
- `Code Graph provider configured: ${providerKey}`,
40
- '',
41
- `Added to ${join(projectPath, CLAUDE_JSON)}:`,
42
- '```json',
43
- JSON.stringify({ [providerKey]: entry }, null, 2),
44
- '```',
45
- '',
46
- providerKey === 'codegraph'
47
- ? 'Initialize the project index with `npx -y @colbymchenry/codegraph init -i` from the project root before relying on graph queries.'
48
- : 'Initialize the provider index using the provider documentation before relying on graph queries.',
49
- '',
50
- 'Restart Claude Code (or your MCP client) to activate the new server.',
51
- ];
52
- return { content: [{ type: 'text', text: lines.join('\n') }] };
53
- }
54
- // ---------------------------------------------------------------------------
55
- // code_graph_status
56
- // ---------------------------------------------------------------------------
57
- export async function handleCodeGraphStatus(input) {
58
- const config = await readClaudeJson(input.projectPath);
59
- const result = buildStatusResult(config);
60
- const lines = [
61
- 'Code Graph Integration Status',
62
- '==============================',
63
- '',
64
- '| Provider | Configured |',
65
- '|----------|------------|',
66
- ...result.providers.map((p) => `| ${p.name} | ${p.configured ? 'yes' : 'no'} |`),
67
- '',
68
- `Active provider: ${result.activeProvider ?? 'none'}`,
69
- ];
70
- return { content: [{ type: 'text', text: lines.join('\n') }] };
71
- }
72
- //# sourceMappingURL=code-graph-handler.js.map
@@ -1,4 +0,0 @@
1
- import type { ToolResult } from '../types/index.js';
2
- import type { CreateRuleInput } from '../types/universal-rules/index.js';
3
- export declare function handleCreateRule(input: CreateRuleInput): Promise<ToolResult>;
4
- //# sourceMappingURL=create-rule.d.ts.map
@@ -1,107 +0,0 @@
1
- // tools/create-rule.ts — SPEC-779: create_rule MCP tool
2
- // Creates a host-aware rule file for any Planu-using project.
3
- import { readFile } from 'node:fs/promises';
4
- import { join } from 'node:path';
5
- import { detectHost } from '../engine/host-detection/detect-host.js';
6
- import { writeRuleForHost } from '../engine/universal-rules/host-writer.js';
7
- import { hashContent } from '../engine/universal-rules/user-edit-detector.js';
8
- import { hashProjectPath } from '../storage/base-store.js';
9
- import { appendAutopilotLogEntry } from '../storage/autopilot-log-store.js';
10
- // ---------------------------------------------------------------------------
11
- // Host resolution
12
- // ---------------------------------------------------------------------------
13
- /** Resolve 'auto' host to a concrete HostId. Falls back to 'claude-code'. */
14
- function resolveHost(input) {
15
- if (input.host !== 'auto') {
16
- return input.host;
17
- }
18
- const detected = detectHost();
19
- if (detected === 'codex') {
20
- return 'codex';
21
- }
22
- if (detected === 'gemini') {
23
- return 'gemini';
24
- }
25
- // cursor, universal, claude-code → default to claude-code
26
- return 'claude-code';
27
- }
28
- // ---------------------------------------------------------------------------
29
- // Handler
30
- // ---------------------------------------------------------------------------
31
- export async function handleCreateRule(input) {
32
- const { projectPath, name, content, overwriteExisting } = input;
33
- const host = resolveHost(input);
34
- // Build a minimal UniversalRule-compatible object for writeRuleForHost
35
- const rule = {
36
- id: name,
37
- name,
38
- description: '',
39
- category: 'workflow',
40
- applicableHosts: [host],
41
- defaultEnabled: false,
42
- buildContent: (_h) => content,
43
- };
44
- // Compute the expected target path for claude-code (to check overwrite protection)
45
- let targetPath;
46
- if (host === 'claude-code') {
47
- targetPath = join(projectPath, '.claude', 'rules', `${name}.md`);
48
- }
49
- else if (host === 'codex') {
50
- targetPath = join(projectPath, 'AGENTS.md');
51
- }
52
- else {
53
- targetPath = join(projectPath, '.gemini', 'conventions.md');
54
- }
55
- // For claude-code, enforce overwrite protection on distinct content
56
- if (host === 'claude-code') {
57
- try {
58
- const existing = await readFile(targetPath, 'utf-8');
59
- const existingHash = hashContent(existing);
60
- const newHash = hashContent(content);
61
- if (existingHash !== newHash && overwriteExisting !== true) {
62
- return {
63
- content: [
64
- {
65
- type: 'text',
66
- text: `Rule already exists at ${targetPath}. Pass overwriteExisting: true to replace.`,
67
- },
68
- ],
69
- isError: true,
70
- };
71
- }
72
- }
73
- catch {
74
- // File doesn't exist yet — proceed normally
75
- }
76
- }
77
- let writtenPath;
78
- try {
79
- writtenPath = await writeRuleForHost(rule, projectPath, host);
80
- }
81
- catch (err) {
82
- const msg = err instanceof Error ? err.message : String(err);
83
- return {
84
- content: [{ type: 'text', text: `Failed to write rule: ${msg}` }],
85
- isError: true,
86
- };
87
- }
88
- // Fire-and-forget autopilot log entry
89
- const projectId = hashProjectPath(projectPath);
90
- void appendAutopilotLogEntry(projectId, {
91
- specId: 'create_rule',
92
- hookName: 'rule_created',
93
- result: 'pass',
94
- timestamp: new Date().toISOString(),
95
- durationMs: 0,
96
- });
97
- return {
98
- content: [
99
- {
100
- type: 'text',
101
- text: `Rule \`${name}\` written to \`${writtenPath}\` (host: ${host}).`,
102
- },
103
- ],
104
- isError: false,
105
- };
106
- }
107
- //# sourceMappingURL=create-rule.js.map
@@ -1,7 +0,0 @@
1
- import type { ToolResult, SubmitFeedbackInput, TriageFeedbackInput, ResolveFeedbackInput, SyncFeedbackInput, FeedbackStatusInput } from '../types/index.js';
2
- export declare function handleSubmitFeedback(input: SubmitFeedbackInput): Promise<ToolResult>;
3
- export declare function handleTriageFeedback(input: TriageFeedbackInput): ToolResult;
4
- export declare function handleResolveFeedback(input: ResolveFeedbackInput): Promise<ToolResult>;
5
- export declare function handleSyncFeedback(input: SyncFeedbackInput): Promise<ToolResult>;
6
- export declare function handleFeedbackStatus(input: FeedbackStatusInput): ToolResult;
7
- //# sourceMappingURL=feedback-handler.d.ts.map