@planu/cli 4.1.0 → 4.1.2
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 +21 -0
- package/dist/config/license-plans.json +65 -361
- package/dist/engine/hooks/git-hook-generator.js +31 -0
- package/dist/tools/git/hook-ops.js +53 -9
- package/dist/tools/tool-registry/group-infra.js +22 -0
- package/package.json +8 -7
- package/dist/engine/escalator/index.d.ts +0 -5
- package/dist/engine/escalator/index.js +0 -5
- package/dist/engine/freeze/retro-audit.d.ts +0 -6
- package/dist/engine/freeze/retro-audit.js +0 -24
- package/dist/engine/heal/backup.d.ts +0 -9
- package/dist/engine/heal/backup.js +0 -21
- package/dist/engine/idioma-validator/index.d.ts +0 -17
- package/dist/engine/idioma-validator/index.js +0 -89
- package/dist/engine/saga/index.d.ts +0 -4
- package/dist/engine/saga/index.js +0 -4
- package/dist/engine/spec-state-machine/index.d.ts +0 -3
- package/dist/engine/spec-state-machine/index.js +0 -2
- package/dist/engine/spec-summary-html/dashboard-renderer.d.ts +0 -6
- package/dist/engine/spec-summary-html/dashboard-renderer.js +0 -333
- package/dist/engine/triagier/index.d.ts +0 -5
- package/dist/engine/triagier/index.js +0 -5
- package/dist/engine/universal-rules/index.d.ts +0 -5
- package/dist/engine/universal-rules/index.js +0 -6
- package/dist/testing/cassette/index.d.ts +0 -23
- package/dist/testing/cassette/index.js +0 -26
- package/dist/tools/domain-bundle-handler.d.ts +0 -37
- package/dist/tools/domain-bundle-handler.js +0 -71
- package/dist/tools/figma/rules-file.d.ts +0 -5
- package/dist/tools/figma/rules-file.js +0 -45
- package/dist/tools/heal-planu-root.d.ts +0 -8
- package/dist/tools/heal-planu-root.js +0 -144
- package/dist/tools/opencode-host-adapter.d.ts +0 -3
- package/dist/tools/opencode-host-adapter.js +0 -33
- package/dist/tools/plan-team-distribution.d.ts +0 -3
- package/dist/tools/plan-team-distribution.js +0 -71
- package/dist/tools/reconcile-status-json.d.ts +0 -4
- package/dist/tools/reconcile-status-json.js +0 -209
- package/dist/tools/register-all-tools.d.ts +0 -8
- package/dist/tools/register-all-tools.js +0 -239
- package/dist/tools/tool-registry/group-analysis-monitoring.d.ts +0 -3
- package/dist/tools/tool-registry/group-analysis-monitoring.js +0 -942
- package/dist/tools/tool-registry/group-integrations.d.ts +0 -3
- package/dist/tools/tool-registry/group-integrations.js +0 -1046
- package/dist/tools/tool-registry/group-misc.d.ts +0 -3
- package/dist/tools/tool-registry/group-misc.js +0 -1367
- package/dist/tools/tool-registry/group-platform.d.ts +0 -3
- package/dist/tools/tool-registry/group-platform.js +0 -1681
- package/dist/tools/tool-registry/group-session-knowledge.d.ts +0 -3
- package/dist/tools/tool-registry/group-session-knowledge.js +0 -1416
- package/dist/tools/tool-registry/group-spec-ops.d.ts +0 -3
- package/dist/tools/tool-registry/group-spec-ops.js +0 -917
- package/dist/tools/workspace-overview.d.ts +0 -4
- package/dist/tools/workspace-overview.js +0 -316
- package/dist/transports/middleware/index.d.ts +0 -9
- package/dist/transports/middleware/index.js +0 -7
- package/dist/transports/middleware/with-sandbox.d.ts +0 -21
- package/dist/transports/middleware/with-sandbox.js +0 -68
- package/dist/types/heal.d.ts +0 -18
- package/dist/types/heal.js +0 -3
|
@@ -1,1367 +0,0 @@
|
|
|
1
|
-
// SPEC-597-EXEMPT: canonical mapping (npm-package↔framework, keyword catalogue)
|
|
2
|
-
// — must enumerate framework names by design; no detection logic to migrate.
|
|
3
|
-
/* eslint-disable max-lines -- thematic registry group consolidated by SPEC refactor-phase3 (commit aafeea60); each block is a declarative tool registration ~20 lines, split would re-create the ~120 register-*.ts files this phase intentionally collapsed */
|
|
4
|
-
// tools/tool-registry/group-misc.ts — Group G: Misc/Platform tools
|
|
5
|
-
// Consolidates: team, llm-provider, spec-observability, workspace-dashboard, hook-generator,
|
|
6
|
-
// project-dna, steering, skill-bootstrap, release-notes, token-optimizer, tokens,
|
|
7
|
-
// validation-loop, spec-cookbook, doc-compliance, spec-prompt, test-reverse-engineer,
|
|
8
|
-
// governance, reconcile-rules, reconcile-skills, reconcile-hooks, ai-ecosystem-status,
|
|
9
|
-
// sync-ai-configs, sync-spec-state, generate-dashboard, generate-proposal, export-pdf
|
|
10
|
-
// (Phase 3 registry refactor — inline, no delegation)
|
|
11
|
-
import { z } from 'zod';
|
|
12
|
-
import { safeLicensed, safeTracked, safe } from '../safe-handler.js';
|
|
13
|
-
import { projectIdSchema, withProject } from '../tool-registry-helpers.js';
|
|
14
|
-
// ── Team tools (register-team-tools.ts) ──────────────────────────────────────
|
|
15
|
-
import { handleGenerateTeammatePrompt, handleGenerateLayerPrompts, } from '../generate-teammate-prompt.js';
|
|
16
|
-
import { handleValidateTeamResults } from '../validate-team-results.js';
|
|
17
|
-
import { handlePlanTeamDistribution } from '../plan-team-distribution.js';
|
|
18
|
-
// ── LLM Provider tools (register-llm-provider-tools.ts) ──────────────────────
|
|
19
|
-
import { handleConfigureLLMProviders } from '../configure-llm-providers-handler.js';
|
|
20
|
-
import { handleLLMProviderStatus } from '../llm-provider-status-handler.js';
|
|
21
|
-
import { ConfigureLLMProvidersActionEnum, LoadBalancingStrategyEnum, LLMCapabilityEnum, QualityTierEnum, } from '../schemas/llm-provider-schemas.js';
|
|
22
|
-
// ── SPEC-739: flag_spec_gap ───────────────────────────────────────────────────
|
|
23
|
-
import { handleFlagSpecGap } from '../flag-spec-gap.js';
|
|
24
|
-
// ── SPEC-751: housekeeping_sweep ──────────────────────────────────────────────
|
|
25
|
-
import { handleHousekeepingSweep, HousekeepingSweepInputSchema } from '../housekeeping-sweep.js';
|
|
26
|
-
// ── Spec observability (register-spec-observability-tools.ts) ────────────────
|
|
27
|
-
import { handleSpecUsageReport } from '../spec-usage-report.js';
|
|
28
|
-
// ── Workspace dashboard (register-workspace-dashboard-tools.ts) ───────────────
|
|
29
|
-
import { handleWorkspaceOverview, handleWorkspaceHealth, handleBulkProjectOperations, handleWorkspaceReport, handleWorkspaceSearch, handleWorkspaceAlerts, } from '../workspace-dashboard-handler.js';
|
|
30
|
-
// ── Hook generator (register-hook-generator.ts) ───────────────────────────────
|
|
31
|
-
import { handleGenerateHooksForStack, handlePreviewHooks, handleMergeHooks, } from '../hook-generator-handler.js';
|
|
32
|
-
// ── Project DNA (register-project-dna.ts) ────────────────────────────────────
|
|
33
|
-
import { handleDetectProjectDNA, handleBootstrapProjectIntelligence, handleUpdateProjectDNA, DetectProjectDNAInputSchema, BootstrapProjectIntelligenceInputSchema, UpdateDNAInputSchema, } from '../project-dna-handler.js';
|
|
34
|
-
// ── Steering tools (register-steering-tools.ts) ───────────────────────────────
|
|
35
|
-
import { handleGenerateSteeringFile, handleListSteeringFiles } from '../steering-handler.js';
|
|
36
|
-
// ── Skill bootstrap (register-skill-bootstrap-tools.ts) ───────────────────────
|
|
37
|
-
import { handleDiscoverSkillsFromRegistries, handleBootstrapSkills, handleConfigureSkillRegistries, DiscoverSkillsInputSchema, BootstrapSkillsInputSchema, ConfigureSkillRegistriesInputSchema, } from '../skill-bootstrap-handler.js';
|
|
38
|
-
// ── Release notes (register-release-notes-tools.ts) ──────────────────────────
|
|
39
|
-
import { handleGenerateReleaseNotes, handlePreviewReleaseNotes, handleConfigureReleaseNotes, } from '../release-notes-handler.js';
|
|
40
|
-
// ── Tokens (register-tokens-tool.ts) ─────────────────────────────────────────
|
|
41
|
-
import { handleTokens } from '../tokens-handler.js';
|
|
42
|
-
// ── Validation loop (register-validation-loop-tools.ts) ───────────────────────
|
|
43
|
-
import { handleSpecHealthCheck, handleHealthCheckAll, handleRunValidationLoop, } from '../validation-loop-handler.js';
|
|
44
|
-
// ── Spec cookbook (register-spec-cookbook-tools.ts) ───────────────────────────
|
|
45
|
-
import { handleCookbookList, handleCookbookGet, handleCookbookApply, handleCookbookContribute, handleCookbookSearch, } from '../spec-cookbook-handler.js';
|
|
46
|
-
// ── Doc compliance (register-doc-compliance-tools.ts) ────────────────────────
|
|
47
|
-
import { handleDocComplianceReport } from '../doc-compliance-handler.js';
|
|
48
|
-
// ── Spec prompt (register-spec-prompt-tools.ts) ───────────────────────────────
|
|
49
|
-
import { registerSpecPromptHandlers } from '../spec-prompt-handler.js';
|
|
50
|
-
// ── Test reverse engineer (register-test-reverse-engineer-tools.ts) ───────────
|
|
51
|
-
import { ReverseEngineerTestsInputSchema, handleReverseEngineerTests, } from '../test-reverse-engineer-handler.js';
|
|
52
|
-
// ── Reconcile rules (reconcile-rules.ts) ─────────────────────────────────────
|
|
53
|
-
import { reconcileRules } from '../../engine/rules-reconciler.js';
|
|
54
|
-
// ── Reconcile skills (reconcile-skills.ts) ────────────────────────────────────
|
|
55
|
-
import { ReconcileSkillsInputSchema } from '../schemas/reconcile-skills-schemas.js';
|
|
56
|
-
import { reconcileSkills, autoFixSkills } from '../../engine/skills-reconciler.js';
|
|
57
|
-
import { sanitizePath } from '../sanitize-path.js';
|
|
58
|
-
import { knowledgeStore, specStore } from '../../storage/index.js';
|
|
59
|
-
// ── Reconcile hooks (reconcile-hooks.ts) ──────────────────────────────────────
|
|
60
|
-
import { reconcileHooks, patchHooks } from '../../engine/hooks-reconciler.js';
|
|
61
|
-
// ── AI ecosystem status (ai-ecosystem-status.ts) ──────────────────────────────
|
|
62
|
-
import { handleAiEcosystemStatus, AiEcosystemStatusSchema } from '../ai-ecosystem-status.js';
|
|
63
|
-
import { resolveProjectId, missingProjectIdError } from '../resolve-project-id.js';
|
|
64
|
-
// ── Sync AI configs (sync-ai-configs.ts) ──────────────────────────────────────
|
|
65
|
-
import { handleSyncAiConfigs, SyncAiConfigsSchema } from '../sync-ai-configs.js';
|
|
66
|
-
// ── Sync spec state (sync-spec-state-handler.ts) ──────────────────────────────
|
|
67
|
-
import { registerSyncSpecStateTool } from '../sync-spec-state-handler.js';
|
|
68
|
-
import { registerRepairFrontmatterDriftTool } from '../repair-frontmatter-drift.js';
|
|
69
|
-
// ── Generate dashboard (generate-dashboard.ts) ────────────────────────────────
|
|
70
|
-
import { regenerateSpecSummaryHtml } from '../../engine/spec-summary-html.js';
|
|
71
|
-
import { hashProjectPath } from '../../storage/base-store.js';
|
|
72
|
-
// ── Generate proposal (generate-proposal.ts) ──────────────────────────────────
|
|
73
|
-
import { registerGenerateProposalTool } from '../generate-proposal.js';
|
|
74
|
-
// ── Execute SDD flow (SPEC-583) ───────────────────────────────────────────────
|
|
75
|
-
// handleExecuteSddFlow import removed — registration replaced by deprecation stub (SPEC-658)
|
|
76
|
-
import { makeDeprecationStub } from './deprecated-stubs.js';
|
|
77
|
-
import { registerFromEntries } from '../tool-entry.js';
|
|
78
|
-
// ── Reconcile interactive question hooks (SPEC-584) ────────────────────────────
|
|
79
|
-
import { handleReconcileInteractiveQuestionHooks } from '../reconcile-interactive-question-hooks.js';
|
|
80
|
-
// ── Planu session checkpoint (SPEC-585) ───────────────────────────────────────
|
|
81
|
-
import { handlePlanSessionCheckpoint } from '../planu-session-checkpoint.js';
|
|
82
|
-
// ── SPEC-753: workspace_overview (single-call state query) + reconcile_status_json ──
|
|
83
|
-
import { handleProjectOverview } from '../workspace-overview.js';
|
|
84
|
-
import { handleReconcileStatusJson } from '../reconcile-status-json.js';
|
|
85
|
-
// ── SPEC-969: force_status_analytics ─────────────────────────────────────────
|
|
86
|
-
import { handleForceStatusAnalytics } from '../force-status-analytics.js';
|
|
87
|
-
// ---------------------------------------------------------------------------
|
|
88
|
-
// Reconcile rules — inline helpers
|
|
89
|
-
// ---------------------------------------------------------------------------
|
|
90
|
-
function formatRulesReconciliation(result) {
|
|
91
|
-
const { rulesInSync, newRulesNeeded, staleRules, updatedRules, healthScore } = result;
|
|
92
|
-
const lines = [];
|
|
93
|
-
lines.push('# Rules Reconciliation Report');
|
|
94
|
-
lines.push('');
|
|
95
|
-
lines.push(`## Health Score: ${healthScore}/100`);
|
|
96
|
-
lines.push('');
|
|
97
|
-
lines.push(`- Status: **${rulesInSync ? 'IN SYNC' : 'DRIFT DETECTED'}**`);
|
|
98
|
-
lines.push('');
|
|
99
|
-
if (updatedRules.length > 0) {
|
|
100
|
-
lines.push('## Auto-Generated Rules Files');
|
|
101
|
-
lines.push('');
|
|
102
|
-
lines.push('The following files were created in `.claude/rules/` for uncovered convention categories:');
|
|
103
|
-
lines.push('');
|
|
104
|
-
for (const f of updatedRules) {
|
|
105
|
-
lines.push(` - ${f}`);
|
|
106
|
-
}
|
|
107
|
-
lines.push('');
|
|
108
|
-
}
|
|
109
|
-
if (newRulesNeeded.length > 0 && updatedRules.length === 0) {
|
|
110
|
-
lines.push('## Missing Coverage');
|
|
111
|
-
lines.push('');
|
|
112
|
-
lines.push('Convention categories in CLAUDE.md with no rules file coverage:');
|
|
113
|
-
lines.push('');
|
|
114
|
-
for (const cat of newRulesNeeded) {
|
|
115
|
-
lines.push(` - ${cat}`);
|
|
116
|
-
}
|
|
117
|
-
lines.push('');
|
|
118
|
-
}
|
|
119
|
-
if (staleRules.length > 0) {
|
|
120
|
-
lines.push('## Potentially Stale Rules');
|
|
121
|
-
lines.push('');
|
|
122
|
-
lines.push('These files have no categories matching current CLAUDE.md conventions — review manually:');
|
|
123
|
-
lines.push('');
|
|
124
|
-
for (const f of staleRules) {
|
|
125
|
-
lines.push(` - ${f}`);
|
|
126
|
-
}
|
|
127
|
-
lines.push('');
|
|
128
|
-
}
|
|
129
|
-
if (rulesInSync && updatedRules.length === 0) {
|
|
130
|
-
lines.push('All convention categories are covered by .claude/rules/ files. No drift detected.');
|
|
131
|
-
lines.push('');
|
|
132
|
-
}
|
|
133
|
-
return lines.join('\n');
|
|
134
|
-
}
|
|
135
|
-
// ---------------------------------------------------------------------------
|
|
136
|
-
// Reconcile skills — inline helpers
|
|
137
|
-
// ---------------------------------------------------------------------------
|
|
138
|
-
async function handleReconcileSkillsInline(args) {
|
|
139
|
-
const projectPath = sanitizePath(args.projectPath);
|
|
140
|
-
const knowledge = await knowledgeStore.getKnowledge(args.projectId);
|
|
141
|
-
const reconciliation = await reconcileSkills(projectPath, knowledge);
|
|
142
|
-
if (args.autoFix === true) {
|
|
143
|
-
const fixResult = await autoFixSkills(reconciliation, projectPath);
|
|
144
|
-
return buildSkillsAutoFixReport(fixResult);
|
|
145
|
-
}
|
|
146
|
-
return buildSkillsReadOnlyReport(reconciliation);
|
|
147
|
-
}
|
|
148
|
-
function buildSkillsReadOnlyReport(result) {
|
|
149
|
-
const lines = [];
|
|
150
|
-
lines.push(`## Skills Reconciliation Report`);
|
|
151
|
-
lines.push(`**Health score**: ${result.healthScore}/100`);
|
|
152
|
-
lines.push(`**In sync**: ${result.skillsInSync ? 'Yes' : 'No'}`);
|
|
153
|
-
lines.push('');
|
|
154
|
-
if (result.newSkillsDetected.length > 0) {
|
|
155
|
-
lines.push(`### New skills detected (${result.newSkillsDetected.length})`);
|
|
156
|
-
lines.push('These skills are recommended for your current stack but are not installed:');
|
|
157
|
-
for (const name of result.newSkillsDetected) {
|
|
158
|
-
lines.push(`- \`${name}\``);
|
|
159
|
-
}
|
|
160
|
-
lines.push('');
|
|
161
|
-
lines.push('Run `skill_install` for each skill, or use `suggest_tooling` for full details.');
|
|
162
|
-
}
|
|
163
|
-
else {
|
|
164
|
-
lines.push('All recommended skills are installed.');
|
|
165
|
-
}
|
|
166
|
-
lines.push('');
|
|
167
|
-
if (result.staleSkills.length > 0) {
|
|
168
|
-
lines.push(`### Stale skills (${result.staleSkills.length})`);
|
|
169
|
-
lines.push('These skills are installed for technologies no longer detected in the stack:');
|
|
170
|
-
for (const name of result.staleSkills) {
|
|
171
|
-
lines.push(`- \`${name}\``);
|
|
172
|
-
}
|
|
173
|
-
lines.push('');
|
|
174
|
-
lines.push('Consider removing them from `.claude/skills/` if they are no longer needed.');
|
|
175
|
-
}
|
|
176
|
-
else {
|
|
177
|
-
lines.push('No stale skills detected.');
|
|
178
|
-
}
|
|
179
|
-
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
180
|
-
}
|
|
181
|
-
function buildSkillsAutoFixReport(fix) {
|
|
182
|
-
const lines = [];
|
|
183
|
-
lines.push('## Skills Auto-Fix Report');
|
|
184
|
-
lines.push(`**Health score after fix**: ${fix.healthScore}/100`);
|
|
185
|
-
lines.push('');
|
|
186
|
-
if (fix.installed.length > 0) {
|
|
187
|
-
lines.push(`### Installed (${fix.installed.length})`);
|
|
188
|
-
for (const name of fix.installed) {
|
|
189
|
-
lines.push(`- \`${name}\``);
|
|
190
|
-
}
|
|
191
|
-
lines.push('');
|
|
192
|
-
}
|
|
193
|
-
else {
|
|
194
|
-
lines.push('No missing skills to install.');
|
|
195
|
-
lines.push('');
|
|
196
|
-
}
|
|
197
|
-
if (fix.removed.length > 0) {
|
|
198
|
-
lines.push(`### Removed (${fix.removed.length})`);
|
|
199
|
-
for (const name of fix.removed) {
|
|
200
|
-
lines.push(`- \`${name}\``);
|
|
201
|
-
}
|
|
202
|
-
lines.push('');
|
|
203
|
-
}
|
|
204
|
-
else {
|
|
205
|
-
lines.push('No stale skills to remove.');
|
|
206
|
-
lines.push('');
|
|
207
|
-
}
|
|
208
|
-
if (fix.pathsFixed.length > 0) {
|
|
209
|
-
lines.push(`### Paths corrected (${fix.pathsFixed.length})`);
|
|
210
|
-
for (const name of fix.pathsFixed) {
|
|
211
|
-
lines.push(`- \`${name}\``);
|
|
212
|
-
}
|
|
213
|
-
lines.push('');
|
|
214
|
-
}
|
|
215
|
-
else {
|
|
216
|
-
lines.push('No corrupt manifest paths detected.');
|
|
217
|
-
}
|
|
218
|
-
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
219
|
-
}
|
|
220
|
-
// ---------------------------------------------------------------------------
|
|
221
|
-
// Reconcile hooks — inline helpers
|
|
222
|
-
// ---------------------------------------------------------------------------
|
|
223
|
-
function formatHooksReconciliation(result) {
|
|
224
|
-
const { hooksInSync, missingHooks, outdatedHooks, healthScore } = result;
|
|
225
|
-
const lines = [];
|
|
226
|
-
lines.push('# Hooks Reconciliation Report');
|
|
227
|
-
lines.push('');
|
|
228
|
-
lines.push(`## Health Score: ${healthScore}/100`);
|
|
229
|
-
lines.push('');
|
|
230
|
-
lines.push(`- Status: **${hooksInSync ? 'IN SYNC' : 'DRIFT DETECTED'}**`);
|
|
231
|
-
lines.push('');
|
|
232
|
-
if (missingHooks.length > 0) {
|
|
233
|
-
lines.push('## Missing Checks');
|
|
234
|
-
lines.push('');
|
|
235
|
-
lines.push('Expected quality gates not found in current hook scripts:');
|
|
236
|
-
lines.push('');
|
|
237
|
-
for (const item of missingHooks) {
|
|
238
|
-
lines.push(` - ${item}`);
|
|
239
|
-
}
|
|
240
|
-
lines.push('');
|
|
241
|
-
lines.push('Run `configure_hooks` or `manage_git setup-hooks` to add missing steps.');
|
|
242
|
-
lines.push('');
|
|
243
|
-
}
|
|
244
|
-
if (outdatedHooks.length > 0) {
|
|
245
|
-
lines.push('## Enhancement Opportunities');
|
|
246
|
-
lines.push('');
|
|
247
|
-
lines.push('Hooks could be improved based on detected project capabilities:');
|
|
248
|
-
lines.push('');
|
|
249
|
-
for (const item of outdatedHooks) {
|
|
250
|
-
lines.push(` - ${item}`);
|
|
251
|
-
}
|
|
252
|
-
lines.push('');
|
|
253
|
-
}
|
|
254
|
-
if (hooksInSync) {
|
|
255
|
-
lines.push('All detected project capabilities are covered by git hook scripts. No drift found.');
|
|
256
|
-
lines.push('');
|
|
257
|
-
}
|
|
258
|
-
return lines.join('\n');
|
|
259
|
-
}
|
|
260
|
-
function formatHooksPatchResult(reconcileResult, patchResult) {
|
|
261
|
-
const lines = [];
|
|
262
|
-
lines.push('# Hooks Reconciliation Report (Auto-Fix Applied)');
|
|
263
|
-
lines.push('');
|
|
264
|
-
lines.push(`## Health Score: ${patchResult.healthScore}/100`);
|
|
265
|
-
lines.push('');
|
|
266
|
-
lines.push(`- Status: **${reconcileResult.hooksInSync ? 'IN SYNC' : 'DRIFT DETECTED (before fix)'}**`);
|
|
267
|
-
lines.push('');
|
|
268
|
-
if (patchResult.patched.length > 0) {
|
|
269
|
-
lines.push('## Patched Checks');
|
|
270
|
-
lines.push('');
|
|
271
|
-
for (const p of patchResult.patched) {
|
|
272
|
-
lines.push(` - ${p.hook}: \`${p.added}\``);
|
|
273
|
-
}
|
|
274
|
-
lines.push('');
|
|
275
|
-
}
|
|
276
|
-
if (patchResult.skipped.length > 0) {
|
|
277
|
-
lines.push('## Skipped (already present or covered by lint-staged)');
|
|
278
|
-
lines.push('');
|
|
279
|
-
for (const s of patchResult.skipped) {
|
|
280
|
-
lines.push(` - ${s}`);
|
|
281
|
-
}
|
|
282
|
-
lines.push('');
|
|
283
|
-
}
|
|
284
|
-
if (patchResult.patched.length === 0 && patchResult.skipped.length === 0) {
|
|
285
|
-
lines.push('No patches needed — hooks are already up to date.');
|
|
286
|
-
lines.push('');
|
|
287
|
-
}
|
|
288
|
-
if (reconcileResult.outdatedHooks.length > 0) {
|
|
289
|
-
lines.push('## Enhancement Opportunities (not auto-fixed)');
|
|
290
|
-
lines.push('');
|
|
291
|
-
for (const item of reconcileResult.outdatedHooks) {
|
|
292
|
-
lines.push(` - ${item}`);
|
|
293
|
-
}
|
|
294
|
-
lines.push('');
|
|
295
|
-
}
|
|
296
|
-
return lines.join('\n');
|
|
297
|
-
}
|
|
298
|
-
function handleReconcileHooksInline(args) {
|
|
299
|
-
const result = reconcileHooks(args.projectPath);
|
|
300
|
-
if (!args.autoFix) {
|
|
301
|
-
const text = formatHooksReconciliation(result);
|
|
302
|
-
const isError = result.healthScore === 0 && !result.hooksInSync;
|
|
303
|
-
return { content: [{ type: 'text', text }], isError };
|
|
304
|
-
}
|
|
305
|
-
const patch = patchHooks(result.missingHooks, args.projectPath);
|
|
306
|
-
const text = formatHooksPatchResult(result, patch);
|
|
307
|
-
const isError = patch.healthScore === 0 && patch.patched.length === 0;
|
|
308
|
-
return { content: [{ type: 'text', text }], isError };
|
|
309
|
-
}
|
|
310
|
-
// ---------------------------------------------------------------------------
|
|
311
|
-
// Hook section sub-schema (for merge_hooks)
|
|
312
|
-
// ---------------------------------------------------------------------------
|
|
313
|
-
const hookSectionSchema = z.object({
|
|
314
|
-
id: z.string().max(200).describe('Unique section identifier, e.g. typescript-typecheck'),
|
|
315
|
-
phase: z
|
|
316
|
-
.enum(['pre-commit', 'commit-msg', 'pre-push', 'post-merge', 'post-checkout'])
|
|
317
|
-
.describe('Git hook phase. Values: pre-commit | commit-msg | pre-push | post-merge | post-checkout'),
|
|
318
|
-
stack: z.string().max(100).describe('Stack token this section targets'),
|
|
319
|
-
content: z.string().max(5000).describe('Hook commands or config content'),
|
|
320
|
-
description: z.string().max(500).describe('Human-readable description of what this section does'),
|
|
321
|
-
});
|
|
322
|
-
// ---------------------------------------------------------------------------
|
|
323
|
-
// Release notes shared sub-schemas
|
|
324
|
-
// ---------------------------------------------------------------------------
|
|
325
|
-
const audienceSchema = z
|
|
326
|
-
.enum(['developer', 'user', 'stakeholder'])
|
|
327
|
-
.optional()
|
|
328
|
-
.describe('Target audience for the release notes. Valid values: developer, user, stakeholder. ' +
|
|
329
|
-
'developer: includes spec IDs and technical tags. ' +
|
|
330
|
-
'user: simple language without technical IDs. ' +
|
|
331
|
-
'stakeholder: executive summary with business impact. ' +
|
|
332
|
-
'Defaults to user.');
|
|
333
|
-
const formatSchema = z
|
|
334
|
-
.enum(['markdown', 'html', 'plain'])
|
|
335
|
-
.optional()
|
|
336
|
-
.describe('Output format. Valid values: markdown, html, plain. Defaults to markdown.');
|
|
337
|
-
// ---------------------------------------------------------------------------
|
|
338
|
-
// Token deprecation note
|
|
339
|
-
// ---------------------------------------------------------------------------
|
|
340
|
-
const DEPRECATION_NOTE = '[DEPRECATED] This tool will be removed in a future version. Use `tokens` with the appropriate view instead.';
|
|
341
|
-
// ---------------------------------------------------------------------------
|
|
342
|
-
// Main export
|
|
343
|
-
// ---------------------------------------------------------------------------
|
|
344
|
-
// eslint-disable-next-line max-lines-per-function -- registration catalog: one block per tool, each ~20 lines
|
|
345
|
-
export function registerMiscGroupTools(s) {
|
|
346
|
-
// ── Team tools ─────────────────────────────────────────────────────────────
|
|
347
|
-
s.registerTool('plan_team_distribution', {
|
|
348
|
-
description: 'Plan how to distribute approved specs across an Agent Team. Detects file overlaps between specs and produces a phased execution order — non-conflicting specs run in parallel (phase 1), conflicting specs run sequentially (phase 2+). Returns teammate roster, file ownership map, and warnings for any shared files.',
|
|
349
|
-
inputSchema: {
|
|
350
|
-
...projectIdSchema,
|
|
351
|
-
specIds: z
|
|
352
|
-
.array(z.string().max(500))
|
|
353
|
-
.min(1)
|
|
354
|
-
.max(100)
|
|
355
|
-
.describe('Spec IDs to distribute across the team. Typically approved/ready specs.'),
|
|
356
|
-
template: z
|
|
357
|
-
.string()
|
|
358
|
-
.max(200)
|
|
359
|
-
.optional()
|
|
360
|
-
.describe('Optional team template ID (e.g. "parallel-specs"). Defaults to "parallel-specs".'),
|
|
361
|
-
},
|
|
362
|
-
annotations: { readOnlyHint: true },
|
|
363
|
-
}, safeLicensed('plan_team_distribution', withProject((args) => handlePlanTeamDistribution(args))));
|
|
364
|
-
s.registerTool('generate_teammate_prompt', {
|
|
365
|
-
description: 'Generate structured agent prompts for an Agent Team. Two modes: ' +
|
|
366
|
-
'(1) Spec mode — provide specId + teammateName + assignedFiles + role to generate a prompt for a specific spec and teammate. ' +
|
|
367
|
-
'(2) Layer mode — provide only projectId (and optional layer) to generate prompts for each detected architecture layer (frontend, backend, engine, storage, etc.). ' +
|
|
368
|
-
'Layer mode includes file ownership, stack-specific conventions, and quality gates per layer.',
|
|
369
|
-
inputSchema: {
|
|
370
|
-
...projectIdSchema,
|
|
371
|
-
specId: z
|
|
372
|
-
.string()
|
|
373
|
-
.max(500)
|
|
374
|
-
.optional()
|
|
375
|
-
.describe('Spec mode: Spec ID assigned to this teammate. Omit to use layer mode instead.'),
|
|
376
|
-
teammateName: z
|
|
377
|
-
.string()
|
|
378
|
-
.max(500)
|
|
379
|
-
.optional()
|
|
380
|
-
.describe('Spec mode: Unique name for this teammate'),
|
|
381
|
-
assignedFiles: z
|
|
382
|
-
.array(z.string().max(4096))
|
|
383
|
-
.max(1000)
|
|
384
|
-
.optional()
|
|
385
|
-
.describe('Spec mode: Files this teammate has exclusive write access to'),
|
|
386
|
-
role: z
|
|
387
|
-
.enum(['planner', 'implementer', 'reviewer', 'tester', 'researcher'])
|
|
388
|
-
.optional()
|
|
389
|
-
.describe('Spec mode: Role: planner | implementer | reviewer | tester | researcher'),
|
|
390
|
-
layer: z
|
|
391
|
-
.string()
|
|
392
|
-
.max(200)
|
|
393
|
-
.optional()
|
|
394
|
-
.describe('Layer mode: Architecture layer to generate a prompt for (e.g. frontend, backend, engine, storage, types). Omit to generate prompts for all detected layers.'),
|
|
395
|
-
},
|
|
396
|
-
annotations: { readOnlyHint: true },
|
|
397
|
-
}, safeLicensed('generate_teammate_prompt', withProject((args) => {
|
|
398
|
-
const typedArgs = args;
|
|
399
|
-
if (!typedArgs.specId) {
|
|
400
|
-
return handleGenerateLayerPrompts({
|
|
401
|
-
projectId: typedArgs.projectId,
|
|
402
|
-
layer: typedArgs.layer,
|
|
403
|
-
});
|
|
404
|
-
}
|
|
405
|
-
return handleGenerateTeammatePrompt(typedArgs);
|
|
406
|
-
})));
|
|
407
|
-
s.registerTool('validate_team_results', {
|
|
408
|
-
description: 'Validate the combined results from all teammates after parallel execution. ' +
|
|
409
|
-
'Checks for file conflicts, criteria coverage, and recommends merge order.',
|
|
410
|
-
inputSchema: {
|
|
411
|
-
...projectIdSchema,
|
|
412
|
-
specIds: z.array(z.string().max(500)).max(100).describe('Spec IDs that were distributed'),
|
|
413
|
-
teammateNames: z
|
|
414
|
-
.array(z.string().max(500))
|
|
415
|
-
.max(100)
|
|
416
|
-
.describe('Names of teammates whose results to validate'),
|
|
417
|
-
},
|
|
418
|
-
annotations: { readOnlyHint: true },
|
|
419
|
-
}, safeLicensed('validate_team_results', withProject((args) => handleValidateTeamResults(args))));
|
|
420
|
-
// ── LLM Provider tools ─────────────────────────────────────────────────────
|
|
421
|
-
s.registerTool('configure_llm_providers', {
|
|
422
|
-
description: 'Configure LLM providers for multi-provider support. ' +
|
|
423
|
-
'Add/remove providers, set failover priority, and choose load balancing strategy. ' +
|
|
424
|
-
'Supports: Anthropic, OpenAI, Google/Gemini, Mistral, Ollama, DeepSeek, and custom providers.',
|
|
425
|
-
inputSchema: {
|
|
426
|
-
action: ConfigureLLMProvidersActionEnum.describe('Action to perform: add | remove | update | set_priority | set_strategy'),
|
|
427
|
-
providerId: z
|
|
428
|
-
.string()
|
|
429
|
-
.max(500)
|
|
430
|
-
.optional()
|
|
431
|
-
.describe('Provider ID to remove (required for "remove" action).'),
|
|
432
|
-
provider: z
|
|
433
|
-
.object({
|
|
434
|
-
id: z.string().max(500).describe('Unique provider identifier.'),
|
|
435
|
-
name: z.string().max(500).describe('Human-readable provider name.'),
|
|
436
|
-
models: z
|
|
437
|
-
.array(z.object({
|
|
438
|
-
id: z.string().max(500).describe('Model identifier.'),
|
|
439
|
-
name: z.string().max(500).describe('Model display name.'),
|
|
440
|
-
tier: QualityTierEnum,
|
|
441
|
-
pricingPerMToken: z.object({
|
|
442
|
-
input: z.number().describe('Price per 1M input tokens in USD.'),
|
|
443
|
-
output: z.number().describe('Price per 1M output tokens in USD.'),
|
|
444
|
-
}),
|
|
445
|
-
contextWindow: z.number().describe('Maximum context window in tokens.'),
|
|
446
|
-
maxOutputTokens: z.number().describe('Maximum output tokens.'),
|
|
447
|
-
}))
|
|
448
|
-
.max(100)
|
|
449
|
-
.describe('Available models for this provider.'),
|
|
450
|
-
apiKeyEnv: z.string().max(500).describe('Environment variable name for the API key.'),
|
|
451
|
-
baseUrl: z.string().max(4096).describe('Base URL for the provider API.'),
|
|
452
|
-
capabilities: z.array(LLMCapabilityEnum).max(100).describe('Provider capabilities.'),
|
|
453
|
-
keyPattern: z.string().max(500).describe('Regex pattern to validate API key format.'),
|
|
454
|
-
rateLimits: z
|
|
455
|
-
.object({
|
|
456
|
-
requestsPerMinute: z.number().min(0).describe('Max requests per minute.'),
|
|
457
|
-
tokensPerMinute: z.number().min(0).describe('Max tokens per minute.'),
|
|
458
|
-
})
|
|
459
|
-
.describe('Rate limits for this provider.'),
|
|
460
|
-
})
|
|
461
|
-
.optional()
|
|
462
|
-
.describe('Provider definition (required for "add" and "update" actions).'),
|
|
463
|
-
failoverOrder: z
|
|
464
|
-
.array(z.string().max(500))
|
|
465
|
-
.max(100)
|
|
466
|
-
.optional()
|
|
467
|
-
.describe('Ordered list of provider IDs for failover priority.'),
|
|
468
|
-
strategy: LoadBalancingStrategyEnum.optional().describe('Load balancing strategy to use.'),
|
|
469
|
-
},
|
|
470
|
-
annotations: { title: 'Configure LLM Providers' },
|
|
471
|
-
}, safeLicensed('configure_llm_providers', (args) => Promise.resolve(handleConfigureLLMProviders(args))));
|
|
472
|
-
s.registerTool('llm_provider_status', {
|
|
473
|
-
description: 'Show status of all configured LLM providers including health, available models, ' +
|
|
474
|
-
'cost tracking, and recommendations. Displays a summary table with provider health, ' +
|
|
475
|
-
'API key status, model count, and accumulated costs.',
|
|
476
|
-
inputSchema: {
|
|
477
|
-
projectPath: z
|
|
478
|
-
.string()
|
|
479
|
-
.max(4096)
|
|
480
|
-
.optional()
|
|
481
|
-
.describe('Optional project path to show project-specific cost tracking.'),
|
|
482
|
-
detailed: z.boolean().optional().describe('Show detailed model information per provider.'),
|
|
483
|
-
},
|
|
484
|
-
annotations: { title: 'LLM Provider Status', readOnlyHint: true },
|
|
485
|
-
}, safeLicensed('llm_provider_status', (args) => Promise.resolve(handleLLMProviderStatus(args))));
|
|
486
|
-
// ── Spec observability ─────────────────────────────────────────────────────
|
|
487
|
-
s.registerTool('spec_usage_report', {
|
|
488
|
-
description: 'Generate a spec observability report showing which specs agents are actively using. ' +
|
|
489
|
-
'Identifies hot specs (frequently accessed), cold specs (approved but forgotten), ' +
|
|
490
|
-
'AC hotspots, and the tool-to-spec usage matrix. Useful for gap analysis and team focus.',
|
|
491
|
-
inputSchema: {
|
|
492
|
-
...projectIdSchema,
|
|
493
|
-
specId: z
|
|
494
|
-
.string()
|
|
495
|
-
.max(50)
|
|
496
|
-
.optional()
|
|
497
|
-
.describe('Optional: filter report to a single spec (e.g. "SPEC-042"). When omitted, returns full project report.'),
|
|
498
|
-
period: z
|
|
499
|
-
.enum(['day', 'week', 'month', 'all'])
|
|
500
|
-
.optional()
|
|
501
|
-
.describe('Time window for the report. Options: day | week | month | all. Defaults to week.'),
|
|
502
|
-
},
|
|
503
|
-
annotations: { title: 'Spec Usage Report', readOnlyHint: true },
|
|
504
|
-
}, safeLicensed('spec_usage_report', withProject((args) => handleSpecUsageReport(args))));
|
|
505
|
-
// ── Workspace dashboard ────────────────────────────────────────────────────
|
|
506
|
-
s.registerTool('workspace_overview', {
|
|
507
|
-
description: 'Get an overview of all registered projects in the workspace. Shows spec counts, progress, and health scores per project. Prefer resource: planu://workspace/overview (no parameters, zero tool slots).',
|
|
508
|
-
inputSchema: {
|
|
509
|
-
healthThreshold: z
|
|
510
|
-
.number()
|
|
511
|
-
.int()
|
|
512
|
-
.min(0)
|
|
513
|
-
.max(100)
|
|
514
|
-
.optional()
|
|
515
|
-
.default(50)
|
|
516
|
-
.describe('Health score threshold % for highlighting (default: 50)'),
|
|
517
|
-
},
|
|
518
|
-
annotations: { readOnlyHint: true },
|
|
519
|
-
}, safeLicensed('workspace_overview', async (args) => handleWorkspaceOverview(args)));
|
|
520
|
-
s.registerTool('workspace_health', {
|
|
521
|
-
description: 'Get aggregated health metrics across all registered workspace projects. Returns totals for specs, done, in-progress, blocked, and average health score. Prefer resource: planu://workspace/health (no parameters, zero tool slots).',
|
|
522
|
-
inputSchema: {
|
|
523
|
-
healthThreshold: z
|
|
524
|
-
.number()
|
|
525
|
-
.int()
|
|
526
|
-
.min(0)
|
|
527
|
-
.max(100)
|
|
528
|
-
.optional()
|
|
529
|
-
.default(50)
|
|
530
|
-
.describe('Health threshold % to classify projects (default: 50)'),
|
|
531
|
-
},
|
|
532
|
-
annotations: { readOnlyHint: true },
|
|
533
|
-
}, safeLicensed('workspace_health', async (args) => handleWorkspaceHealth(args)));
|
|
534
|
-
s.registerTool('bulk_project_operations', {
|
|
535
|
-
description: 'Run a bulk operation across multiple workspace projects. Supports validate-all (scan specs) and generate-reports. Use dryRun to preview affected projects.',
|
|
536
|
-
inputSchema: {
|
|
537
|
-
operation: z
|
|
538
|
-
.enum(['validate-all', 'generate-reports'])
|
|
539
|
-
.describe('Operation to run: validate-all or generate-reports'),
|
|
540
|
-
projectPaths: z
|
|
541
|
-
.array(z.string().min(1).max(4096))
|
|
542
|
-
.optional()
|
|
543
|
-
.describe('Specific project paths to target (omit for all registered projects)'),
|
|
544
|
-
dryRun: z
|
|
545
|
-
.boolean()
|
|
546
|
-
.optional()
|
|
547
|
-
.default(false)
|
|
548
|
-
.describe('Preview affected projects without running (default: false)'),
|
|
549
|
-
},
|
|
550
|
-
annotations: { readOnlyHint: false },
|
|
551
|
-
}, safeLicensed('bulk_project_operations', async (args) => handleBulkProjectOperations(args)));
|
|
552
|
-
s.registerTool('workspace_report', {
|
|
553
|
-
description: 'Generate an HTML workspace report summarizing all projects, spec counts, health scores, and progress. Suitable for stakeholder sharing.',
|
|
554
|
-
inputSchema: {
|
|
555
|
-
includeCharts: z
|
|
556
|
-
.boolean()
|
|
557
|
-
.optional()
|
|
558
|
-
.default(false)
|
|
559
|
-
.describe('Include visual charts (default: false)'),
|
|
560
|
-
},
|
|
561
|
-
annotations: { readOnlyHint: true },
|
|
562
|
-
}, safeLicensed('workspace_report', async (args) => handleWorkspaceReport(args)));
|
|
563
|
-
s.registerTool('workspace_search', {
|
|
564
|
-
description: 'Search for specs by keyword across all registered workspace projects. Searches spec IDs and titles.',
|
|
565
|
-
inputSchema: {
|
|
566
|
-
query: z.string().min(1).max(200).describe('Search keyword'),
|
|
567
|
-
maxResults: z
|
|
568
|
-
.number()
|
|
569
|
-
.int()
|
|
570
|
-
.min(1)
|
|
571
|
-
.max(200)
|
|
572
|
-
.optional()
|
|
573
|
-
.default(50)
|
|
574
|
-
.describe('Max results (default: 50)'),
|
|
575
|
-
},
|
|
576
|
-
annotations: { readOnlyHint: true },
|
|
577
|
-
}, safeLicensed('workspace_search', async (args) => handleWorkspaceSearch(args)));
|
|
578
|
-
s.registerTool('workspace_alerts', {
|
|
579
|
-
description: 'List stale specs across all workspace projects. Highlights specs in blocked or in-progress status that have not been updated for the configured number of days.',
|
|
580
|
-
inputSchema: {
|
|
581
|
-
staleDaysThreshold: z
|
|
582
|
-
.number()
|
|
583
|
-
.int()
|
|
584
|
-
.min(1)
|
|
585
|
-
.max(365)
|
|
586
|
-
.optional()
|
|
587
|
-
.default(14)
|
|
588
|
-
.describe('Days without update to consider a spec stale (default: 14)'),
|
|
589
|
-
},
|
|
590
|
-
annotations: { readOnlyHint: true },
|
|
591
|
-
}, safeLicensed('workspace_alerts', async (args) => handleWorkspaceAlerts(args)));
|
|
592
|
-
// ── Hook generator ─────────────────────────────────────────────────────────
|
|
593
|
-
s.registerTool('generate_hooks_for_stack', {
|
|
594
|
-
description: 'Generates git hooks and AI-tool-specific hooks based on the detected or specified ' +
|
|
595
|
-
'project stack. Merges intelligently with existing hook files using planu markers ' +
|
|
596
|
-
'(<!-- planu:generated:ID -->) so manual edits outside markers are never overwritten.',
|
|
597
|
-
inputSchema: {
|
|
598
|
-
projectPath: z.string().max(1000).describe('Absolute path to the project root directory'),
|
|
599
|
-
stack: z
|
|
600
|
-
.array(z.string().max(100))
|
|
601
|
-
.optional()
|
|
602
|
-
.describe('Stack tokens to generate hooks for. If omitted, auto-detected from project files. Examples: typescript, eslint, prettier, vitest, jest, python, go, commitlint'),
|
|
603
|
-
aiTool: z
|
|
604
|
-
.string()
|
|
605
|
-
.max(100)
|
|
606
|
-
.optional()
|
|
607
|
-
.describe('AI tool to generate tool-specific hooks for. If omitted, auto-detected. Values: claude | cursor | windsurf | gemini | kiro | cline | copilot | aider'),
|
|
608
|
-
dryRun: z
|
|
609
|
-
.boolean()
|
|
610
|
-
.optional()
|
|
611
|
-
.describe('If true, shows what would be generated without writing files. Default: false'),
|
|
612
|
-
},
|
|
613
|
-
annotations: { title: 'Generate Hooks for Stack', readOnlyHint: false },
|
|
614
|
-
}, safeLicensed('generate_hooks_for_stack', async (args) => handleGenerateHooksForStack(args)));
|
|
615
|
-
s.registerTool('preview_hooks', {
|
|
616
|
-
description: 'Previews what generate_hooks_for_stack would add or update without writing any files. ' +
|
|
617
|
-
'Shows which phases would be modified, which sections are already present, and what new sections would be injected.',
|
|
618
|
-
inputSchema: {
|
|
619
|
-
projectPath: z.string().max(1000).describe('Absolute path to the project root directory'),
|
|
620
|
-
stack: z
|
|
621
|
-
.array(z.string().max(100))
|
|
622
|
-
.optional()
|
|
623
|
-
.describe('Stack tokens to preview. If omitted, auto-detected. Examples: typescript, eslint, vitest, python, go'),
|
|
624
|
-
},
|
|
625
|
-
annotations: { title: 'Preview Hook Changes', readOnlyHint: true },
|
|
626
|
-
}, safeTracked('preview_hooks', async (args) => handlePreviewHooks(args)));
|
|
627
|
-
s.registerTool('merge_hooks', {
|
|
628
|
-
description: 'Merges explicit HookSection entries into the appropriate git hook files. ' +
|
|
629
|
-
'Uses planu markers for idempotent injection — re-running the same sections is safe and produces no duplicate output.',
|
|
630
|
-
inputSchema: {
|
|
631
|
-
projectPath: z.string().max(1000).describe('Absolute path to the project root directory'),
|
|
632
|
-
sections: z
|
|
633
|
-
.array(hookSectionSchema)
|
|
634
|
-
.describe('Hook sections to merge into the project hook files'),
|
|
635
|
-
dryRun: z
|
|
636
|
-
.boolean()
|
|
637
|
-
.optional()
|
|
638
|
-
.describe('If true, computes the merged content but does NOT write files. Default: false'),
|
|
639
|
-
},
|
|
640
|
-
annotations: { title: 'Merge Hook Sections', readOnlyHint: false },
|
|
641
|
-
}, safeLicensed('merge_hooks', async (args) => {
|
|
642
|
-
const typed = args;
|
|
643
|
-
const input = {
|
|
644
|
-
projectPath: typed.projectPath,
|
|
645
|
-
sections: typed.sections,
|
|
646
|
-
dryRun: typed.dryRun,
|
|
647
|
-
};
|
|
648
|
-
return handleMergeHooks(input);
|
|
649
|
-
}));
|
|
650
|
-
// ── Project DNA ────────────────────────────────────────────────────────────
|
|
651
|
-
s.registerTool('detect_project_dna', {
|
|
652
|
-
description: 'Detect the technology stack and active AI tool for a project. ' +
|
|
653
|
-
'Reads package.json, Cargo.toml, go.mod, requirements.txt, tsconfig.json, ' +
|
|
654
|
-
'and AI tool detection files to build a complete Project DNA profile.',
|
|
655
|
-
inputSchema: DetectProjectDNAInputSchema,
|
|
656
|
-
annotations: {
|
|
657
|
-
title: 'Detect Project DNA',
|
|
658
|
-
readOnlyHint: true,
|
|
659
|
-
destructiveHint: false,
|
|
660
|
-
openWorldHint: false,
|
|
661
|
-
},
|
|
662
|
-
}, safeTracked('detect_project_dna', async (args) => handleDetectProjectDNA(args)));
|
|
663
|
-
s.registerTool('bootstrap_project_intelligence', {
|
|
664
|
-
description: 'Generate AI tool rules and suggest skills based on the detected project stack. ' +
|
|
665
|
-
'Creates rules files in the format expected by the active AI tool ' +
|
|
666
|
-
'(markdown for Claude/Kiro/Gemini, .mdc for Cursor, appended sections for Windsurf/Cline/Copilot). ' +
|
|
667
|
-
'Supported stack tokens: supabase, typescript, nextjs, vitest, react, prisma, zod, stripe, jest, ' +
|
|
668
|
-
'fastapi, django, flask, gin, axum, tailwind.',
|
|
669
|
-
inputSchema: BootstrapProjectIntelligenceInputSchema,
|
|
670
|
-
annotations: {
|
|
671
|
-
title: 'Bootstrap Project Intelligence',
|
|
672
|
-
readOnlyHint: false,
|
|
673
|
-
destructiveHint: false,
|
|
674
|
-
openWorldHint: false,
|
|
675
|
-
},
|
|
676
|
-
}, safeLicensed('bootstrap_project_intelligence', async (args) => handleBootstrapProjectIntelligence(args)));
|
|
677
|
-
s.registerTool('update_project_dna', {
|
|
678
|
-
description: 'Re-scan a project to refresh its DNA profile (stack, AI tool, package manager). ' +
|
|
679
|
-
'Use after adding new dependencies or switching AI tools. ' +
|
|
680
|
-
'After updating, run bootstrap_project_intelligence to apply the new configuration.',
|
|
681
|
-
inputSchema: UpdateDNAInputSchema,
|
|
682
|
-
annotations: {
|
|
683
|
-
title: 'Update Project DNA',
|
|
684
|
-
readOnlyHint: true,
|
|
685
|
-
destructiveHint: false,
|
|
686
|
-
openWorldHint: false,
|
|
687
|
-
},
|
|
688
|
-
}, safeLicensed('update_project_dna', async (args) => handleUpdateProjectDNA(args)));
|
|
689
|
-
// ── Steering tools ─────────────────────────────────────────────────────────
|
|
690
|
-
s.registerTool('generate_steering_file', {
|
|
691
|
-
description: 'Generate steering files that provide persistent project context to AI tools. ' +
|
|
692
|
-
'Supports Kiro (.kiro/steering/), Cursor (.cursorrules), Claude Code (CLAUDE.md section), ' +
|
|
693
|
-
'and Cline (.clinerules). Based on project stack and Planu spec data.',
|
|
694
|
-
inputSchema: {
|
|
695
|
-
projectPath: z
|
|
696
|
-
.string()
|
|
697
|
-
.min(1)
|
|
698
|
-
.max(2000)
|
|
699
|
-
.describe('Absolute path to the project root directory.'),
|
|
700
|
-
target: z
|
|
701
|
-
.enum(['kiro', 'cursor', 'claude', 'cline', 'copilot', 'codex', 'all'])
|
|
702
|
-
.describe('AI tool target for steering files. Valid values: kiro, cursor, claude, cline, copilot, codex, all.'),
|
|
703
|
-
stack: z
|
|
704
|
-
.array(z.string().max(200))
|
|
705
|
-
.max(50)
|
|
706
|
-
.optional()
|
|
707
|
-
.describe('Technology stack list (e.g. ["typescript", "react", "nextjs"]). Used to generate relevant architecture guidance.'),
|
|
708
|
-
specCount: z
|
|
709
|
-
.number()
|
|
710
|
-
.int()
|
|
711
|
-
.min(0)
|
|
712
|
-
.optional()
|
|
713
|
-
.describe('Total number of specs in the project. Included in architecture section.'),
|
|
714
|
-
conventions: z
|
|
715
|
-
.array(z.string().max(500))
|
|
716
|
-
.max(50)
|
|
717
|
-
.optional()
|
|
718
|
-
.describe('Key project conventions to include in architecture steering (e.g. ["Use camelCase for variables", "All imports use .js extensions"]).'),
|
|
719
|
-
},
|
|
720
|
-
annotations: { readOnlyHint: false },
|
|
721
|
-
}, safeLicensed('generate_steering_file', async (args) => handleGenerateSteeringFile(args)));
|
|
722
|
-
s.registerTool('list_steering_files', {
|
|
723
|
-
description: 'Show previously generated steering files for a project. Lists the files created by the last generate_steering_file call.',
|
|
724
|
-
inputSchema: {
|
|
725
|
-
projectPath: z
|
|
726
|
-
.string()
|
|
727
|
-
.min(1)
|
|
728
|
-
.max(2000)
|
|
729
|
-
.describe('Absolute path to the project root directory.'),
|
|
730
|
-
},
|
|
731
|
-
annotations: { readOnlyHint: true },
|
|
732
|
-
}, safeLicensed('list_steering_files', async (args) => handleListSteeringFiles(args)));
|
|
733
|
-
// ── Skill bootstrap ────────────────────────────────────────────────────────
|
|
734
|
-
s.registerTool('discover_skills_from_registries', {
|
|
735
|
-
description: 'Detect the project tech stack and discover available skills from external registries ' +
|
|
736
|
-
'(awesome-cursorrules, skills.sh, custom GitHub sources). Shows what would be installed ' +
|
|
737
|
-
'without writing any files. Great for exploring available skills before bootstrapping.',
|
|
738
|
-
inputSchema: DiscoverSkillsInputSchema,
|
|
739
|
-
annotations: { readOnlyHint: true },
|
|
740
|
-
}, safeTracked('discover_skills_from_registries', (input) => handleDiscoverSkillsFromRegistries(input)));
|
|
741
|
-
s.registerTool('bootstrap_skills', {
|
|
742
|
-
description: 'Auto-detect the project stack and install the best matching skills from external ' +
|
|
743
|
-
'registries (awesome-cursorrules, skills.sh, custom GitHub repos) into the appropriate ' +
|
|
744
|
-
'AI tool config files: .cursorrules, .windsurfrules, CLAUDE.md, .kiro/steering/, ' +
|
|
745
|
-
'AGENTS.md, .clinerules. Use dryRun:true to preview without writing.',
|
|
746
|
-
inputSchema: BootstrapSkillsInputSchema,
|
|
747
|
-
annotations: { destructiveHint: true },
|
|
748
|
-
}, safeLicensed('bootstrap_skills', (input) => handleBootstrapSkills(input)));
|
|
749
|
-
s.registerTool('configure_skill_registries', {
|
|
750
|
-
description: 'Configure which skill registries to use, which AI tool files to write, and whether ' +
|
|
751
|
-
'to append or overwrite existing files. Add custom GitHub repos as additional skill sources.',
|
|
752
|
-
inputSchema: ConfigureSkillRegistriesInputSchema,
|
|
753
|
-
}, safeLicensed('configure_skill_registries', (input) => handleConfigureSkillRegistries(input)));
|
|
754
|
-
// ── Release notes ──────────────────────────────────────────────────────────
|
|
755
|
-
s.registerTool('preview_release_notes', {
|
|
756
|
-
description: 'Preview how many specs are eligible for release notes in a given date range. Shows count and first 3 titles without generating the full document. Free tier.',
|
|
757
|
-
annotations: { readOnlyHint: true },
|
|
758
|
-
inputSchema: {
|
|
759
|
-
projectPath: z.string().min(1).max(4096).describe('Absolute path to the project root.'),
|
|
760
|
-
fromDate: z
|
|
761
|
-
.string()
|
|
762
|
-
.optional()
|
|
763
|
-
.describe('ISO date string for the start of the range (default: 30 days ago).'),
|
|
764
|
-
toDate: z
|
|
765
|
-
.string()
|
|
766
|
-
.optional()
|
|
767
|
-
.describe('ISO date string for the end of the range (default: now).'),
|
|
768
|
-
maxEntries: z
|
|
769
|
-
.number()
|
|
770
|
-
.int()
|
|
771
|
-
.min(1)
|
|
772
|
-
.max(200)
|
|
773
|
-
.optional()
|
|
774
|
-
.describe('Maximum number of entries to return (default: 10).'),
|
|
775
|
-
},
|
|
776
|
-
}, safeTracked('preview_release_notes', async (args) => handlePreviewReleaseNotes(args)));
|
|
777
|
-
s.registerTool('generate_release_notes', {
|
|
778
|
-
description: 'Generate human-readable release notes from specs in "done" status filtered by date range. ' +
|
|
779
|
-
'Supports multiple audiences (developer, user, stakeholder) and formats (markdown, html, plain). Pro tier.',
|
|
780
|
-
annotations: { readOnlyHint: true },
|
|
781
|
-
inputSchema: {
|
|
782
|
-
projectPath: z.string().min(1).max(4096).describe('Absolute path to the project root.'),
|
|
783
|
-
fromDate: z
|
|
784
|
-
.string()
|
|
785
|
-
.optional()
|
|
786
|
-
.describe('ISO date string for the start of the range (default: 30 days ago).'),
|
|
787
|
-
toDate: z
|
|
788
|
-
.string()
|
|
789
|
-
.optional()
|
|
790
|
-
.describe('ISO date string for the end of the range (default: now).'),
|
|
791
|
-
version: z
|
|
792
|
-
.string()
|
|
793
|
-
.max(50)
|
|
794
|
-
.optional()
|
|
795
|
-
.describe('Version label to include in the header (e.g. "v1.17.0").'),
|
|
796
|
-
audience: audienceSchema,
|
|
797
|
-
format: formatSchema,
|
|
798
|
-
maxEntries: z
|
|
799
|
-
.number()
|
|
800
|
-
.int()
|
|
801
|
-
.min(1)
|
|
802
|
-
.max(500)
|
|
803
|
-
.optional()
|
|
804
|
-
.describe('Maximum number of entries to include (default: 50).'),
|
|
805
|
-
},
|
|
806
|
-
}, safeLicensed('generate_release_notes', async (args) => handleGenerateReleaseNotes(args)));
|
|
807
|
-
s.registerTool('configure_release_notes', {
|
|
808
|
-
description: 'Save default settings for release notes generation (audience, format, groupByTag). ' +
|
|
809
|
-
'These defaults are applied when generate_release_notes is called without explicit options. Pro tier.',
|
|
810
|
-
annotations: { readOnlyHint: false },
|
|
811
|
-
inputSchema: {
|
|
812
|
-
projectPath: z.string().min(1).max(4096).describe('Absolute path to the project root.'),
|
|
813
|
-
defaultAudience: audienceSchema,
|
|
814
|
-
defaultFormat: formatSchema,
|
|
815
|
-
includeBreakingChanges: z
|
|
816
|
-
.boolean()
|
|
817
|
-
.optional()
|
|
818
|
-
.describe('Whether to include breaking changes section (default: true).'),
|
|
819
|
-
groupByTag: z
|
|
820
|
-
.boolean()
|
|
821
|
-
.optional()
|
|
822
|
-
.describe('Whether to group entries by tag category (default: false).'),
|
|
823
|
-
},
|
|
824
|
-
}, safeLicensed('configure_release_notes', async (args) => handleConfigureReleaseNotes(args)));
|
|
825
|
-
// ── Tokens ─────────────────────────────────────────────────────────────────
|
|
826
|
-
s.registerTool('tokens', {
|
|
827
|
-
description: 'Unified token management dashboard. Choose a view to see different aspects of token usage. ' +
|
|
828
|
-
'Views: usage (session metrics + cache), intelligence (cost analytics + budget), ' +
|
|
829
|
-
'optimizer (RTK/Headroom installation status), savings (spec-guided vs unstructured comparison).',
|
|
830
|
-
inputSchema: {
|
|
831
|
-
view: z
|
|
832
|
-
.enum(['usage', 'intelligence', 'optimizer', 'savings'])
|
|
833
|
-
.describe('View to show. Values: usage, intelligence, optimizer, savings. usage=session token metrics; intelligence=cost analytics dashboard; optimizer=RTK/Headroom status; savings=spec-guided savings report.'),
|
|
834
|
-
projectPath: z
|
|
835
|
-
.string()
|
|
836
|
-
.optional()
|
|
837
|
-
.describe('Absolute path to the project root. Required for optimizer and savings views.'),
|
|
838
|
-
projectId: z.string().optional().describe('Project ID hash. Prefer projectPath.'),
|
|
839
|
-
period: z
|
|
840
|
-
.string()
|
|
841
|
-
.optional()
|
|
842
|
-
.describe('Time period filter. For usage: current_session|last_hour|today|all_time. For intelligence/savings: today|week|month|all_time.'),
|
|
843
|
-
groupBy: z
|
|
844
|
-
.string()
|
|
845
|
-
.optional()
|
|
846
|
-
.describe('Group results by. For usage: tool|session. For intelligence: tool|model|spec|day.'),
|
|
847
|
-
specId: z
|
|
848
|
-
.string()
|
|
849
|
-
.optional()
|
|
850
|
-
.describe('Filter intelligence view by a specific spec ID (e.g. SPEC-180).'),
|
|
851
|
-
intelligenceView: z
|
|
852
|
-
.string()
|
|
853
|
-
.optional()
|
|
854
|
-
.describe('Intelligence sub-view: summary|detailed|budget|reconciliation|trends. Defaults to summary.'),
|
|
855
|
-
},
|
|
856
|
-
annotations: { title: 'Tokens', readOnlyHint: true },
|
|
857
|
-
}, safe(async (args) => handleTokens(args)));
|
|
858
|
-
s.registerTool('token_usage', {
|
|
859
|
-
description: `${DEPRECATION_NOTE} Shows token consumption metrics.`,
|
|
860
|
-
inputSchema: {
|
|
861
|
-
period: z
|
|
862
|
-
.enum(['current_session', 'last_hour', 'today', 'all_time'])
|
|
863
|
-
.optional()
|
|
864
|
-
.describe('Time period: current_session | last_hour | today | all_time.'),
|
|
865
|
-
groupBy: z.enum(['tool', 'session']).optional().describe('Group by: tool | session.'),
|
|
866
|
-
},
|
|
867
|
-
annotations: { title: 'Token Usage (deprecated)', readOnlyHint: true },
|
|
868
|
-
}, safe(async (args) => {
|
|
869
|
-
const shimArgs = args;
|
|
870
|
-
const result = await handleTokens({ view: 'usage', ...shimArgs });
|
|
871
|
-
return {
|
|
872
|
-
...result,
|
|
873
|
-
content: [
|
|
874
|
-
{
|
|
875
|
-
type: 'text',
|
|
876
|
-
text: `> ${DEPRECATION_NOTE}\n\n${result.content[0]?.text ?? ''}`,
|
|
877
|
-
},
|
|
878
|
-
],
|
|
879
|
-
};
|
|
880
|
-
}));
|
|
881
|
-
s.registerTool('token_intelligence', {
|
|
882
|
-
description: `${DEPRECATION_NOTE} Advanced token analytics dashboard.`,
|
|
883
|
-
inputSchema: {
|
|
884
|
-
period: z
|
|
885
|
-
.enum(['today', 'week', 'month', 'all_time'])
|
|
886
|
-
.optional()
|
|
887
|
-
.describe('Time period: today | week | month | all_time.'),
|
|
888
|
-
groupBy: z
|
|
889
|
-
.enum(['tool', 'model', 'spec', 'day'])
|
|
890
|
-
.optional()
|
|
891
|
-
.describe('Group by: tool | model | spec | day.'),
|
|
892
|
-
view: z
|
|
893
|
-
.enum(['summary', 'detailed', 'budget', 'reconciliation', 'trends'])
|
|
894
|
-
.optional()
|
|
895
|
-
.describe('Dashboard view: summary | detailed | budget | reconciliation | trends.'),
|
|
896
|
-
specId: z.string().optional().describe('Filter by a specific spec ID.'),
|
|
897
|
-
projectPath: z.string().optional().describe('Absolute path to the project root.'),
|
|
898
|
-
},
|
|
899
|
-
annotations: { title: 'Token Intelligence (deprecated)', readOnlyHint: true },
|
|
900
|
-
}, safe(async (args) => {
|
|
901
|
-
const shimArgs = args;
|
|
902
|
-
const result = await handleTokens({
|
|
903
|
-
view: 'intelligence',
|
|
904
|
-
period: shimArgs.period,
|
|
905
|
-
groupBy: shimArgs.groupBy,
|
|
906
|
-
intelligenceView: shimArgs.view,
|
|
907
|
-
specId: shimArgs.specId,
|
|
908
|
-
projectPath: shimArgs.projectPath,
|
|
909
|
-
});
|
|
910
|
-
return {
|
|
911
|
-
...result,
|
|
912
|
-
content: [
|
|
913
|
-
{
|
|
914
|
-
type: 'text',
|
|
915
|
-
text: `> ${DEPRECATION_NOTE}\n\n${result.content[0]?.text ?? ''}`,
|
|
916
|
-
},
|
|
917
|
-
],
|
|
918
|
-
};
|
|
919
|
-
}));
|
|
920
|
-
s.registerTool('token_savings_report', {
|
|
921
|
-
description: `${DEPRECATION_NOTE} Compares spec-guided vs unstructured token usage.`,
|
|
922
|
-
inputSchema: {
|
|
923
|
-
period: z
|
|
924
|
-
.enum(['today', 'week', 'month', 'all_time'])
|
|
925
|
-
.optional()
|
|
926
|
-
.describe('Time period: today | week | month | all_time. Defaults to month.'),
|
|
927
|
-
projectId: z.string().max(500).optional().describe('Project ID hash. Prefer projectPath.'),
|
|
928
|
-
projectPath: z.string().max(4096).optional().describe('Absolute path to project root.'),
|
|
929
|
-
},
|
|
930
|
-
annotations: { title: 'Token Savings Report (deprecated)', readOnlyHint: true },
|
|
931
|
-
}, safe(async (args) => {
|
|
932
|
-
const shimArgs = args;
|
|
933
|
-
const result = await handleTokens({ view: 'savings', ...shimArgs });
|
|
934
|
-
return {
|
|
935
|
-
...result,
|
|
936
|
-
content: [
|
|
937
|
-
{
|
|
938
|
-
type: 'text',
|
|
939
|
-
text: `> ${DEPRECATION_NOTE}\n\n${result.content[0]?.text ?? ''}`,
|
|
940
|
-
},
|
|
941
|
-
],
|
|
942
|
-
};
|
|
943
|
-
}));
|
|
944
|
-
// ── Validation loop ────────────────────────────────────────────────────────
|
|
945
|
-
s.registerTool('spec_health_check', {
|
|
946
|
-
description: 'Compute a 0-100 health score for a spec by checking: completeness (title + description + criteria), ' +
|
|
947
|
-
'test file existence, last validation result, spec status (done = coverage ok), and drift status. ' +
|
|
948
|
-
'Each dimension is worth 20 points.',
|
|
949
|
-
inputSchema: {
|
|
950
|
-
projectPath: z
|
|
951
|
-
.string()
|
|
952
|
-
.min(1)
|
|
953
|
-
.max(4096)
|
|
954
|
-
.describe('Absolute path to the project root directory'),
|
|
955
|
-
specId: z.string().min(1).max(200).describe('Spec ID to check (e.g. SPEC-001)'),
|
|
956
|
-
since: z
|
|
957
|
-
.string()
|
|
958
|
-
.optional()
|
|
959
|
-
.describe('ISO 8601 date (e.g. 2026-01-01). When provided, shows the health score delta since that date.'),
|
|
960
|
-
},
|
|
961
|
-
annotations: { title: 'Spec Health Check', readOnlyHint: true },
|
|
962
|
-
}, safeLicensed('spec_health_check', async (args) => handleSpecHealthCheck(args)));
|
|
963
|
-
s.registerTool('health_check_all', {
|
|
964
|
-
description: 'Run spec_health_check on all specs in the project and return them sorted from worst to best score. Use this to identify which specs need the most attention.',
|
|
965
|
-
inputSchema: {
|
|
966
|
-
projectPath: z
|
|
967
|
-
.string()
|
|
968
|
-
.min(1)
|
|
969
|
-
.max(4096)
|
|
970
|
-
.describe('Absolute path to the project root directory'),
|
|
971
|
-
},
|
|
972
|
-
annotations: { title: 'Health Check All Specs', readOnlyHint: true },
|
|
973
|
-
}, safeLicensed('health_check_all', async (args) => handleHealthCheckAll(args)));
|
|
974
|
-
s.registerTool('run_validation_loop', {
|
|
975
|
-
description: 'Generate a structured validation execution plan for a set of specs. ' +
|
|
976
|
-
'Shows the steps to validate each spec (validate → test → fix → re-validate) ' +
|
|
977
|
-
'with the specified number of iterations. ' +
|
|
978
|
-
'NOTE: Planu cannot run shell commands — this returns the plan for you to execute.',
|
|
979
|
-
inputSchema: {
|
|
980
|
-
projectPath: z
|
|
981
|
-
.string()
|
|
982
|
-
.min(1)
|
|
983
|
-
.max(4096)
|
|
984
|
-
.describe('Absolute path to the project root directory'),
|
|
985
|
-
specIds: z
|
|
986
|
-
.array(z.string().min(1).max(200))
|
|
987
|
-
.min(1)
|
|
988
|
-
.max(50)
|
|
989
|
-
.describe('List of spec IDs to validate (e.g. ["SPEC-001", "SPEC-002"])'),
|
|
990
|
-
maxIterations: z
|
|
991
|
-
.number()
|
|
992
|
-
.int()
|
|
993
|
-
.min(1)
|
|
994
|
-
.max(10)
|
|
995
|
-
.optional()
|
|
996
|
-
.describe('Maximum number of fix-and-retry iterations (default: 3)'),
|
|
997
|
-
testCommand: z
|
|
998
|
-
.string()
|
|
999
|
-
.max(500)
|
|
1000
|
-
.optional()
|
|
1001
|
-
.describe('Shell command to run tests (default: "pnpm test")'),
|
|
1002
|
-
autoFix: z
|
|
1003
|
-
.boolean()
|
|
1004
|
-
.optional()
|
|
1005
|
-
.describe('Whether to include auto-fix steps in the plan (default: false)'),
|
|
1006
|
-
},
|
|
1007
|
-
annotations: { title: 'Run Validation Loop (Plan)', readOnlyHint: true },
|
|
1008
|
-
}, safeLicensed('run_validation_loop', (args) => Promise.resolve(handleRunValidationLoop(args))));
|
|
1009
|
-
// ── Spec cookbook ──────────────────────────────────────────────────────────
|
|
1010
|
-
s.registerTool('cookbook_list', {
|
|
1011
|
-
description: 'List spec cookbook entries. Filter by domain or feature type. Returns a table of available spec templates you can apply to your project.',
|
|
1012
|
-
inputSchema: {
|
|
1013
|
-
domain: z
|
|
1014
|
-
.enum(['ecommerce', 'saas', 'mobile', 'api', 'fintech', 'devtools', 'general'])
|
|
1015
|
-
.optional()
|
|
1016
|
-
.describe('Filter by domain: ecommerce, saas, mobile, api, fintech, devtools, general'),
|
|
1017
|
-
featureType: z
|
|
1018
|
-
.string()
|
|
1019
|
-
.max(100)
|
|
1020
|
-
.optional()
|
|
1021
|
-
.describe('Filter by feature type (e.g. auth, payments, notifications)'),
|
|
1022
|
-
limit: z
|
|
1023
|
-
.number()
|
|
1024
|
-
.int()
|
|
1025
|
-
.min(1)
|
|
1026
|
-
.max(100)
|
|
1027
|
-
.optional()
|
|
1028
|
-
.default(50)
|
|
1029
|
-
.describe('Max entries to return (default: 50)'),
|
|
1030
|
-
},
|
|
1031
|
-
annotations: { readOnlyHint: true },
|
|
1032
|
-
}, safeLicensed('cookbook_list', (args) => handleCookbookList(args)));
|
|
1033
|
-
s.registerTool('cookbook_get', {
|
|
1034
|
-
description: 'Get details for a specific cookbook entry by ID. Shows acceptance criteria, tags, and estimated effort.',
|
|
1035
|
-
inputSchema: {
|
|
1036
|
-
id: z.string().min(1).max(200).describe('Cookbook entry ID (e.g. ecommerce-user-auth)'),
|
|
1037
|
-
},
|
|
1038
|
-
annotations: { readOnlyHint: true },
|
|
1039
|
-
}, safeLicensed('cookbook_get', (args) => handleCookbookGet(args)));
|
|
1040
|
-
s.registerTool('cookbook_apply', {
|
|
1041
|
-
description: 'Generate a spec template from a cookbook entry. Returns ready-to-use spec content with [PLACEHOLDER] markers. Copy the output and pass it to create_spec.',
|
|
1042
|
-
inputSchema: {
|
|
1043
|
-
id: z.string().min(1).max(200).describe('Cookbook entry ID to apply'),
|
|
1044
|
-
projectName: z
|
|
1045
|
-
.string()
|
|
1046
|
-
.max(200)
|
|
1047
|
-
.optional()
|
|
1048
|
-
.describe('Project name to personalize the spec template'),
|
|
1049
|
-
},
|
|
1050
|
-
annotations: { readOnlyHint: true },
|
|
1051
|
-
}, safeLicensed('cookbook_apply', (args) => handleCookbookApply(args)));
|
|
1052
|
-
s.registerTool('cookbook_contribute', {
|
|
1053
|
-
description: 'Add a new entry to the cookbook (in-memory for this session). Use to share patterns with your team.',
|
|
1054
|
-
inputSchema: {
|
|
1055
|
-
domain: z
|
|
1056
|
-
.enum(['ecommerce', 'saas', 'mobile', 'api', 'fintech', 'devtools', 'general'])
|
|
1057
|
-
.describe('Domain: ecommerce, saas, mobile, api, fintech, devtools, general'),
|
|
1058
|
-
featureType: z
|
|
1059
|
-
.string()
|
|
1060
|
-
.min(1)
|
|
1061
|
-
.max(100)
|
|
1062
|
-
.describe('Feature type (e.g. auth, payments, search)'),
|
|
1063
|
-
title: z.string().min(1).max(200).describe('Entry title'),
|
|
1064
|
-
description: z.string().min(1).max(1000).describe('Entry description'),
|
|
1065
|
-
acceptanceCriteria: z
|
|
1066
|
-
.array(z.string().min(1).max(500))
|
|
1067
|
-
.min(1)
|
|
1068
|
-
.max(20)
|
|
1069
|
-
.describe('List of acceptance criteria'),
|
|
1070
|
-
tags: z.array(z.string().min(1).max(50)).min(1).max(10).describe('Tags for searchability'),
|
|
1071
|
-
},
|
|
1072
|
-
annotations: { readOnlyHint: false },
|
|
1073
|
-
}, safeLicensed('cookbook_contribute', (args) => handleCookbookContribute(args)));
|
|
1074
|
-
s.registerTool('cookbook_search', {
|
|
1075
|
-
description: 'Search cookbook entries by keyword. Searches ID, title, description, domain, type, and tags.',
|
|
1076
|
-
inputSchema: {
|
|
1077
|
-
query: z.string().min(1).max(200).describe('Search query'),
|
|
1078
|
-
limit: z
|
|
1079
|
-
.number()
|
|
1080
|
-
.int()
|
|
1081
|
-
.min(1)
|
|
1082
|
-
.max(50)
|
|
1083
|
-
.optional()
|
|
1084
|
-
.default(20)
|
|
1085
|
-
.describe('Max results (default: 20)'),
|
|
1086
|
-
},
|
|
1087
|
-
annotations: { readOnlyHint: true },
|
|
1088
|
-
}, safeLicensed('cookbook_search', (args) => handleCookbookSearch(args)));
|
|
1089
|
-
// ── Doc compliance ─────────────────────────────────────────────────────────
|
|
1090
|
-
s.registerTool('doc_compliance_report', {
|
|
1091
|
-
description: 'Check whether a spec aligns with official documentation best practices for detected technologies. ' +
|
|
1092
|
-
'Automatically detects techs from spec content, fetches official docs, and reports anti-patterns ' +
|
|
1093
|
-
'and missing best practices.',
|
|
1094
|
-
inputSchema: {
|
|
1095
|
-
projectPath: z.string().describe('Absolute path to the project root'),
|
|
1096
|
-
specId: z.string().describe('Spec ID to check (e.g. SPEC-042)'),
|
|
1097
|
-
},
|
|
1098
|
-
annotations: { readOnlyHint: true },
|
|
1099
|
-
}, safeLicensed('doc_compliance_report', async (args) => handleDocComplianceReport(args)));
|
|
1100
|
-
// ── Spec prompt (delegates to registerSpecPromptHandlers) ──────────────────
|
|
1101
|
-
registerSpecPromptHandlers(s);
|
|
1102
|
-
// ── Test reverse engineer ──────────────────────────────────────────────────
|
|
1103
|
-
s.registerTool('reverse_engineer_tests', {
|
|
1104
|
-
description: 'Parse an existing E2E or unit test file (Playwright, Cypress, Jest) and extract its test descriptions as Planu acceptance criteria. Optionally create a new spec from the generated ACs. Useful for reverse-engineering undocumented test suites into structured specs.',
|
|
1105
|
-
inputSchema: ReverseEngineerTestsInputSchema.shape,
|
|
1106
|
-
annotations: { readOnlyHint: false },
|
|
1107
|
-
}, safeTracked('reverse_engineer_tests', async (args) => handleReverseEngineerTests(args)));
|
|
1108
|
-
// ── Reconcile rules ────────────────────────────────────────────────────────
|
|
1109
|
-
s.registerTool('reconcile_rules', {
|
|
1110
|
-
description: 'Compare CLAUDE.md conventions against existing .claude/rules/ files and auto-patch drift. Generates new rules files for convention categories not yet covered. Returns a health score (0-100) and list of stale or missing rules.',
|
|
1111
|
-
inputSchema: {
|
|
1112
|
-
projectPath: z
|
|
1113
|
-
.string()
|
|
1114
|
-
.max(4096)
|
|
1115
|
-
.describe('Absolute path to the project root containing CLAUDE.md and .claude/rules/'),
|
|
1116
|
-
},
|
|
1117
|
-
annotations: { readOnlyHint: false, destructiveHint: false, title: 'Reconcile Rules' },
|
|
1118
|
-
}, safeTracked('reconcile_rules', (args) => {
|
|
1119
|
-
const result = reconcileRules(args.projectPath);
|
|
1120
|
-
const text = formatRulesReconciliation(result);
|
|
1121
|
-
const isError = result.healthScore === 0 && !result.rulesInSync;
|
|
1122
|
-
return Promise.resolve({ content: [{ type: 'text', text }], isError });
|
|
1123
|
-
}));
|
|
1124
|
-
// ── Reconcile skills ───────────────────────────────────────────────────────
|
|
1125
|
-
s.registerTool('reconcile_skills', {
|
|
1126
|
-
description: "Analyze the gap between the project's current tech stack and its installed Claude skills. " +
|
|
1127
|
-
'Detects skills that are recommended for the stack but not yet installed (newSkillsDetected) ' +
|
|
1128
|
-
'and skills installed for technologies no longer in use (staleSkills). ' +
|
|
1129
|
-
'Returns a health score (0–100) indicating how well skills match the current stack. ' +
|
|
1130
|
-
'Pass autoFix:true to automatically install missing skills, remove stale skills, ' +
|
|
1131
|
-
'and repair corrupt manifest paths (mutates .claude/skills/).',
|
|
1132
|
-
inputSchema: {
|
|
1133
|
-
projectId: ReconcileSkillsInputSchema.shape.projectId,
|
|
1134
|
-
projectPath: ReconcileSkillsInputSchema.shape.projectPath,
|
|
1135
|
-
autoFix: ReconcileSkillsInputSchema.shape.autoFix,
|
|
1136
|
-
},
|
|
1137
|
-
annotations: { title: 'Reconcile Skills', readOnlyHint: false },
|
|
1138
|
-
}, safeTracked('reconcile_skills', async (args) => handleReconcileSkillsInline(args)));
|
|
1139
|
-
// ── Reconcile hooks ────────────────────────────────────────────────────────
|
|
1140
|
-
s.registerTool('reconcile_hooks', {
|
|
1141
|
-
description: 'Detect drift between the project CI/quality pipeline and its git hooks. Checks whether hooks enforce TypeScript, ESLint, tests, commitlint, and Prettier based on what the project has installed. Returns a health score (0-100) and lists missing or outdated steps. ' +
|
|
1142
|
-
'With autoFix:true, automatically patches husky hook files to add missing quality checks (idempotent).',
|
|
1143
|
-
inputSchema: {
|
|
1144
|
-
projectPath: z
|
|
1145
|
-
.string()
|
|
1146
|
-
.max(4096)
|
|
1147
|
-
.describe('Absolute path to the project root to analyse git hooks against CI capabilities'),
|
|
1148
|
-
autoFix: z
|
|
1149
|
-
.boolean()
|
|
1150
|
-
.optional()
|
|
1151
|
-
.describe('When true, automatically patches husky hook files to add missing quality checks. Idempotent: checks already present are skipped. Default: false.'),
|
|
1152
|
-
},
|
|
1153
|
-
annotations: { readOnlyHint: false, destructiveHint: false, title: 'Reconcile Hooks' },
|
|
1154
|
-
}, safeTracked('reconcile_hooks', (args) => Promise.resolve(handleReconcileHooksInline(args))));
|
|
1155
|
-
// ── AI ecosystem status ────────────────────────────────────────────────────
|
|
1156
|
-
s.registerTool('ecosystem_status', {
|
|
1157
|
-
description: 'Scan the AI tool ecosystem of a project and return a health report showing which AI tools are detected and how well they are configured for Planu SDD workflow.',
|
|
1158
|
-
inputSchema: AiEcosystemStatusSchema,
|
|
1159
|
-
annotations: { readOnlyHint: true, destructiveHint: false, title: 'AI Ecosystem Status' },
|
|
1160
|
-
}, safeTracked('ecosystem_status', async (args) => {
|
|
1161
|
-
const pid = resolveProjectId(args);
|
|
1162
|
-
if (!pid) {
|
|
1163
|
-
return missingProjectIdError;
|
|
1164
|
-
}
|
|
1165
|
-
return handleAiEcosystemStatus(args);
|
|
1166
|
-
}));
|
|
1167
|
-
// ── Sync AI configs ────────────────────────────────────────────────────────
|
|
1168
|
-
s.registerTool('sync_ai_configs', {
|
|
1169
|
-
description: 'Detect installed AI coding tools and synchronize their configuration files with the Planu SDD workflow. Generates .cursorrules, .windsurfrules, .aider.conf.yml, etc. for detected tools.',
|
|
1170
|
-
inputSchema: SyncAiConfigsSchema,
|
|
1171
|
-
annotations: { readOnlyHint: false, destructiveHint: false, title: 'Sync AI Configs' },
|
|
1172
|
-
}, safeTracked('sync_ai_configs', async (args) => {
|
|
1173
|
-
const pid = resolveProjectId(args);
|
|
1174
|
-
if (!pid) {
|
|
1175
|
-
return missingProjectIdError;
|
|
1176
|
-
}
|
|
1177
|
-
return handleSyncAiConfigs(args);
|
|
1178
|
-
}));
|
|
1179
|
-
// ── Sync spec state (delegates — has complex startup helpers) ──────────────
|
|
1180
|
-
registerSyncSpecStateTool(s);
|
|
1181
|
-
// ── Repair frontmatter drift (SPEC-698) ─────────────────────────────────────
|
|
1182
|
-
registerRepairFrontmatterDriftTool(s);
|
|
1183
|
-
// ── Generate dashboard ─────────────────────────────────────────────────────
|
|
1184
|
-
s.registerTool('generate_spec_dashboard', {
|
|
1185
|
-
description: 'Deprecated compatibility tool. Planu no longer writes generated dashboard HTML into planu/.',
|
|
1186
|
-
inputSchema: {
|
|
1187
|
-
projectPath: z.string().describe('Absolute path to the project root directory'),
|
|
1188
|
-
},
|
|
1189
|
-
annotations: { readOnlyHint: true },
|
|
1190
|
-
}, async (args) => {
|
|
1191
|
-
const projectId = hashProjectPath(args.projectPath);
|
|
1192
|
-
const specs = await specStore.listSpecs(projectId);
|
|
1193
|
-
await regenerateSpecSummaryHtml(args.projectPath, specs);
|
|
1194
|
-
const doneCount = specs.filter((sp) => sp.status === 'done').length;
|
|
1195
|
-
return {
|
|
1196
|
-
content: [
|
|
1197
|
-
{
|
|
1198
|
-
type: 'text',
|
|
1199
|
-
text: `Dashboard generation is deprecated; no project files were written. Current specs: ${String(specs.length)} (${String(doneCount)} done).`,
|
|
1200
|
-
},
|
|
1201
|
-
],
|
|
1202
|
-
};
|
|
1203
|
-
});
|
|
1204
|
-
// ── Generate proposal (handler is private — delegate to registerer) ───────────
|
|
1205
|
-
registerGenerateProposalTool(s);
|
|
1206
|
-
// ── Planu session checkpoint (SPEC-585) ───────────────────────────────────
|
|
1207
|
-
s.registerTool('planu_session_checkpoint', {
|
|
1208
|
-
description: 'Run a session continuity checkpoint: regenerate session-context.md if stale, flush ' +
|
|
1209
|
-
'learnings-buffer to MEMORY.md, detect unpushed commits with age warning (>2h), and ' +
|
|
1210
|
-
'optionally push them if autopush is enabled in planu/config.json. ' +
|
|
1211
|
-
'Idempotent — safe to call at any time. Called automatically by the PreCompaction hook.',
|
|
1212
|
-
inputSchema: {
|
|
1213
|
-
projectPath: z
|
|
1214
|
-
.string()
|
|
1215
|
-
.max(4096)
|
|
1216
|
-
.optional()
|
|
1217
|
-
.describe('Absolute path to the project root. Defaults to process.cwd() when omitted.'),
|
|
1218
|
-
dryRun: z
|
|
1219
|
-
.boolean()
|
|
1220
|
-
.optional()
|
|
1221
|
-
.describe('When true, report what would happen without making any changes. Default: false.'),
|
|
1222
|
-
},
|
|
1223
|
-
annotations: {
|
|
1224
|
-
readOnlyHint: false,
|
|
1225
|
-
destructiveHint: false,
|
|
1226
|
-
title: 'Planu Session Checkpoint',
|
|
1227
|
-
},
|
|
1228
|
-
}, safeTracked('planu_session_checkpoint', async (args) => handlePlanSessionCheckpoint(args)));
|
|
1229
|
-
// ── Execute SDD flow (SPEC-583 → migrated to skill, SPEC-658) ───────────────
|
|
1230
|
-
// Tool stays registered for backward compat; callers receive a deprecation notice.
|
|
1231
|
-
registerFromEntries(s, [
|
|
1232
|
-
makeDeprecationStub('execute_sdd_flow', '.claude/skills/new-feature.md', 'new-feature'),
|
|
1233
|
-
]);
|
|
1234
|
-
// ── Reconcile interactive question hooks (SPEC-584) ────────────────────────
|
|
1235
|
-
s.registerTool('reconcile_interactive_question_hooks', {
|
|
1236
|
-
description: 'CRITICAL — Install the PostToolUse hook that forces AskUserQuestion relay when Planu returns interactiveQuestions. ' +
|
|
1237
|
-
'Writes .claude/hooks/planu-force-ask-user-question.sh and registers the mcp__planu__.* PostToolUse matcher in .claude/settings.json. ' +
|
|
1238
|
-
'Idempotent — safe to call multiple times. Called automatically during init_project; use this for existing client projects.',
|
|
1239
|
-
inputSchema: {
|
|
1240
|
-
projectPath: z
|
|
1241
|
-
.string()
|
|
1242
|
-
.max(4096)
|
|
1243
|
-
.describe('Absolute path to the client project root where .claude/ should be written'),
|
|
1244
|
-
},
|
|
1245
|
-
annotations: {
|
|
1246
|
-
readOnlyHint: false,
|
|
1247
|
-
destructiveHint: false,
|
|
1248
|
-
title: 'Reconcile Interactive Question Hooks',
|
|
1249
|
-
},
|
|
1250
|
-
}, safeTracked('reconcile_interactive_question_hooks', async (args) => handleReconcileInteractiveQuestionHooks(args)));
|
|
1251
|
-
// ── SPEC-753: project_overview (single-call state query) ──────────────────
|
|
1252
|
-
s.registerTool('project_overview', {
|
|
1253
|
-
description: 'SPEC-753: Single-call project state query — counts by status, top pending specs, ' +
|
|
1254
|
-
'version sync, self-healing health, stale-implementing detector, drift score. ' +
|
|
1255
|
-
'Use this instead of combining planu_status + list_specs + filesystem reads. ' +
|
|
1256
|
-
'Returns structuredContent for programmatic use.',
|
|
1257
|
-
inputSchema: {
|
|
1258
|
-
projectPath: z
|
|
1259
|
-
.string()
|
|
1260
|
-
.max(4096)
|
|
1261
|
-
.optional()
|
|
1262
|
-
.describe('Project root path (auto-resolved from cwd if omitted)'),
|
|
1263
|
-
includeStaleImplementing: z
|
|
1264
|
-
.boolean()
|
|
1265
|
-
.optional()
|
|
1266
|
-
.default(true)
|
|
1267
|
-
.describe('Include stale-implementing detection (default: true)'),
|
|
1268
|
-
staleDays: z
|
|
1269
|
-
.number()
|
|
1270
|
-
.int()
|
|
1271
|
-
.min(1)
|
|
1272
|
-
.optional()
|
|
1273
|
-
.default(7)
|
|
1274
|
-
.describe('Days threshold for stale-implementing detector (default: 7)'),
|
|
1275
|
-
topN: z
|
|
1276
|
-
.number()
|
|
1277
|
-
.int()
|
|
1278
|
-
.min(1)
|
|
1279
|
-
.max(50)
|
|
1280
|
-
.optional()
|
|
1281
|
-
.default(10)
|
|
1282
|
-
.describe('How many top pending specs to return (default: 10)'),
|
|
1283
|
-
},
|
|
1284
|
-
annotations: { readOnlyHint: true, title: 'Project Overview (SPEC-753)' },
|
|
1285
|
-
}, safeLicensed('project_overview', async (args) => handleProjectOverview(args)));
|
|
1286
|
-
// ── SPEC-753: reconcile_status_json ────────────────────────────────────────
|
|
1287
|
-
s.registerTool('reconcile_status_json', {
|
|
1288
|
-
description: 'SPEC-753: Reconcile status.json with spec.md frontmatter values. ' +
|
|
1289
|
-
'Detects drift between what status.json thinks and what frontmatters say. ' +
|
|
1290
|
-
'Use source=frontmatter-wins to trust frontmatters (safest). ' +
|
|
1291
|
-
'Default is dryRun=true — set dryRun=false to actually apply corrections.',
|
|
1292
|
-
inputSchema: {
|
|
1293
|
-
projectPath: z
|
|
1294
|
-
.string()
|
|
1295
|
-
.max(4096)
|
|
1296
|
-
.optional()
|
|
1297
|
-
.describe('Project root path (auto-resolved from cwd if omitted)'),
|
|
1298
|
-
source: z
|
|
1299
|
-
.enum(['frontmatter-wins', 'status-wins', 'newest-wins'])
|
|
1300
|
-
.optional()
|
|
1301
|
-
.default('frontmatter-wins')
|
|
1302
|
-
.describe("Source of truth: 'frontmatter-wins' (default), 'status-wins', 'newest-wins'"),
|
|
1303
|
-
dryRun: z
|
|
1304
|
-
.boolean()
|
|
1305
|
-
.optional()
|
|
1306
|
-
.default(true)
|
|
1307
|
-
.describe('Preview changes without applying (default: true)'),
|
|
1308
|
-
},
|
|
1309
|
-
annotations: { readOnlyHint: false, title: 'Reconcile status.json (SPEC-753)' },
|
|
1310
|
-
}, safeLicensed('reconcile_status_json', async (args) => handleReconcileStatusJson(args)));
|
|
1311
|
-
// ── SPEC-969: force_status_analytics ────────────────────────────────────────
|
|
1312
|
-
s.registerTool('force_status_analytics', {
|
|
1313
|
-
description: 'SPEC-969: Query force-usage analytics for a project. ' +
|
|
1314
|
-
'Returns a list of specs that bypassed gates via forceStatus or forceApprove, ' +
|
|
1315
|
-
'including reasons, agents, and timestamps. ' +
|
|
1316
|
-
'Set generateReport=true to write a quality-exceptions.md report to planu/reports/.',
|
|
1317
|
-
inputSchema: {
|
|
1318
|
-
projectPath: z
|
|
1319
|
-
.string()
|
|
1320
|
-
.max(4096)
|
|
1321
|
-
.optional()
|
|
1322
|
-
.describe('Project root path (auto-resolved from cwd if omitted)'),
|
|
1323
|
-
generateReport: z
|
|
1324
|
-
.boolean()
|
|
1325
|
-
.optional()
|
|
1326
|
-
.default(false)
|
|
1327
|
-
.describe('Generate a quality-exceptions.md report in planu/reports/ (default: false)'),
|
|
1328
|
-
},
|
|
1329
|
-
annotations: { readOnlyHint: true, title: 'Force Status Analytics (SPEC-969)' },
|
|
1330
|
-
}, safeLicensed('force_status_analytics', async (args) => handleForceStatusAnalytics(args)));
|
|
1331
|
-
// ── SPEC-739: flag_spec_gap ─────────────────────────────────────────────────
|
|
1332
|
-
s.registerTool('flag_spec_gap', {
|
|
1333
|
-
description: 'Record a spec implementation gap discovered mid-implementation. ' +
|
|
1334
|
-
'Persists a hash-chained entry to external Planu project data with severity ' +
|
|
1335
|
-
"classification ('low' | 'medium' | 'high') and affected spec links. " +
|
|
1336
|
-
'Existing callers without severity or affectedSpecs continue to work (defaults applied).',
|
|
1337
|
-
inputSchema: {
|
|
1338
|
-
...projectIdSchema,
|
|
1339
|
-
specId: z.string().min(1).describe('Spec ID where the gap was discovered (e.g. SPEC-732)'),
|
|
1340
|
-
description: z.string().min(1).max(2000).describe('Human-readable description of the gap'),
|
|
1341
|
-
severity: z
|
|
1342
|
-
.enum(['low', 'medium', 'high'])
|
|
1343
|
-
.optional()
|
|
1344
|
-
.describe("Gap severity. Defaults to 'medium' when omitted"),
|
|
1345
|
-
affectedSpecs: z
|
|
1346
|
-
.array(z.string())
|
|
1347
|
-
.optional()
|
|
1348
|
-
.describe('Spec IDs affected by this gap. Defaults to [specId] when omitted'),
|
|
1349
|
-
},
|
|
1350
|
-
annotations: { readOnlyHint: false, destructiveHint: false, title: 'Flag Spec Gap' },
|
|
1351
|
-
}, safe((args) => handleFlagSpecGap(args)));
|
|
1352
|
-
// ── SPEC-751: housekeeping_sweep ─────────────────────────────────────────────
|
|
1353
|
-
s.registerTool('housekeeping_sweep', {
|
|
1354
|
-
description: 'Scan for and optionally delete stale git branches (tmp-*, feat/spec-*, worktree-agent-*), ' +
|
|
1355
|
-
'git worktrees (.claude/worktrees/agent-*), and old stashes. ' +
|
|
1356
|
-
'Defaults to dryRun: true — no changes until you opt in. ' +
|
|
1357
|
-
'Always writes a backup log to planu/.housekeeping-history.jsonl for recovery. ' +
|
|
1358
|
-
'Use after a release or after marking specs done to keep the git workspace clean.',
|
|
1359
|
-
inputSchema: HousekeepingSweepInputSchema.shape,
|
|
1360
|
-
annotations: { readOnlyHint: false, destructiveHint: true, title: 'Housekeeping Sweep' },
|
|
1361
|
-
}, safeLicensed('housekeeping_sweep', async (args) => {
|
|
1362
|
-
const raw = args;
|
|
1363
|
-
const projectPath = raw.projectPath ?? process.cwd();
|
|
1364
|
-
return handleHousekeepingSweep(raw, projectPath);
|
|
1365
|
-
}));
|
|
1366
|
-
}
|
|
1367
|
-
//# sourceMappingURL=group-misc.js.map
|