icopilot 2.2.0
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 +250 -0
- package/LICENSE +21 -0
- package/README.md +214 -0
- package/bin/icopilot.js +6 -0
- package/dist/acp/router.js +123 -0
- package/dist/acp/schema.js +53 -0
- package/dist/agents/aggregator.js +187 -0
- package/dist/agents/custom-agents.js +97 -0
- package/dist/agents/goal-driven.js +411 -0
- package/dist/agents/multi-repo.js +350 -0
- package/dist/agents/parallel-runner.js +181 -0
- package/dist/agents/router.js +144 -0
- package/dist/agents/self-heal.js +481 -0
- package/dist/agents/tdd-agent.js +278 -0
- package/dist/api/github-models.js +158 -0
- package/dist/bridge/ide-bridge.js +479 -0
- package/dist/cloud/routine-executor.js +34 -0
- package/dist/cloud/routine-scheduler.js +67 -0
- package/dist/cloud/routine-storage.js +297 -0
- package/dist/commands/acp-cmd.js +143 -0
- package/dist/commands/actions-cmd.js +624 -0
- package/dist/commands/agent-cmd.js +144 -0
- package/dist/commands/alias-cmd.js +132 -0
- package/dist/commands/bookmark-cmd.js +77 -0
- package/dist/commands/changelog-cmd.js +99 -0
- package/dist/commands/changes-cmd.js +120 -0
- package/dist/commands/clipboard-cmd.js +217 -0
- package/dist/commands/cloud-routine-cmd.js +265 -0
- package/dist/commands/codegen-cmd.js +544 -0
- package/dist/commands/compare-cmd.js +116 -0
- package/dist/commands/context-cmd.js +247 -0
- package/dist/commands/context-viz-cmd.js +43 -0
- package/dist/commands/conventions-cmd.js +116 -0
- package/dist/commands/cost-cmd.js +51 -0
- package/dist/commands/deps-cmd.js +294 -0
- package/dist/commands/diagram-cmd.js +658 -0
- package/dist/commands/diff-review-cmd.js +92 -0
- package/dist/commands/doc-cmd.js +412 -0
- package/dist/commands/doctor-cmd.js +152 -0
- package/dist/commands/editor-cmd.js +49 -0
- package/dist/commands/env-cmd.js +86 -0
- package/dist/commands/explain-cmd.js +78 -0
- package/dist/commands/explain-shell-cmd.js +22 -0
- package/dist/commands/explore-cmd.js +231 -0
- package/dist/commands/feedback-cmd.js +98 -0
- package/dist/commands/fix-cmd.js +17 -0
- package/dist/commands/generate-cmd.js +38 -0
- package/dist/commands/git-extra.js +197 -0
- package/dist/commands/git-log-cmd.js +98 -0
- package/dist/commands/git-undo-cmd.js +137 -0
- package/dist/commands/git.js +155 -0
- package/dist/commands/history-cmd.js +122 -0
- package/dist/commands/index-cmd.js +65 -0
- package/dist/commands/init-cmd.js +73 -0
- package/dist/commands/lint-cmd.js +133 -0
- package/dist/commands/memory-cmd.js +98 -0
- package/dist/commands/metrics-cmd.js +97 -0
- package/dist/commands/mode-prefix.js +30 -0
- package/dist/commands/multi-cmd.js +44 -0
- package/dist/commands/notify-cmd.js +204 -0
- package/dist/commands/profile-cmd.js +101 -0
- package/dist/commands/prompts.js +17 -0
- package/dist/commands/rag-cmd.js +60 -0
- package/dist/commands/readme-cmd.js +564 -0
- package/dist/commands/reasoning-cmd.js +34 -0
- package/dist/commands/refactor-cmd.js +96 -0
- package/dist/commands/release-cmd.js +450 -0
- package/dist/commands/repo-cmd.js +195 -0
- package/dist/commands/route-cmd.js +21 -0
- package/dist/commands/schedule-cmd.js +109 -0
- package/dist/commands/search-cmd.js +47 -0
- package/dist/commands/security-cmd.js +156 -0
- package/dist/commands/settings-cmd.js +238 -0
- package/dist/commands/skill-cmd.js +338 -0
- package/dist/commands/slash.js +2721 -0
- package/dist/commands/snippets-cmd.js +83 -0
- package/dist/commands/space-cmd.js +92 -0
- package/dist/commands/stash-cmd.js +156 -0
- package/dist/commands/stats-cmd.js +36 -0
- package/dist/commands/style-cmd.js +85 -0
- package/dist/commands/suggest-cmd.js +40 -0
- package/dist/commands/summary-cmd.js +138 -0
- package/dist/commands/task-cmd.js +58 -0
- package/dist/commands/team-memory-cmd.js +97 -0
- package/dist/commands/template-cmd.js +475 -0
- package/dist/commands/test-cmd.js +146 -0
- package/dist/commands/todo-cmd.js +172 -0
- package/dist/commands/tokens-cmd.js +277 -0
- package/dist/commands/trigger-cmd.js +147 -0
- package/dist/commands/undo-cmd.js +18 -0
- package/dist/commands/voice-cmd.js +89 -0
- package/dist/commands/watch-cmd.js +110 -0
- package/dist/commands/web-cmd.js +183 -0
- package/dist/commands/worktree-cmd.js +119 -0
- package/dist/config-profile.js +66 -0
- package/dist/config.js +288 -0
- package/dist/context/compactor.js +53 -0
- package/dist/context/dep-context.js +329 -0
- package/dist/context/file-refs.js +54 -0
- package/dist/context/git-context.js +229 -0
- package/dist/context/image-input.js +66 -0
- package/dist/context/memory.js +55 -0
- package/dist/context/persistent-memory.js +104 -0
- package/dist/context/pinned.js +96 -0
- package/dist/context/priority.js +150 -0
- package/dist/context/read-only.js +48 -0
- package/dist/context/smart-files.js +286 -0
- package/dist/context/team-memory.js +156 -0
- package/dist/extensions/loader.js +149 -0
- package/dist/extensions/marketplace.js +49 -0
- package/dist/extensions/slack-provider.js +181 -0
- package/dist/extensions/team.js +56 -0
- package/dist/extensions/teams-provider.js +222 -0
- package/dist/extensions/voice.js +18 -0
- package/dist/hooks/lifecycle.js +215 -0
- package/dist/hooks/precommit.js +463 -0
- package/dist/index/embeddings.js +23 -0
- package/dist/index/indexer.js +86 -0
- package/dist/index/retrieve.js +20 -0
- package/dist/index/store.js +95 -0
- package/dist/index.js +286 -0
- package/dist/intelligence/dead-code.js +457 -0
- package/dist/intelligence/error-watch.js +263 -0
- package/dist/intelligence/navigation.js +141 -0
- package/dist/intelligence/stack-trace.js +210 -0
- package/dist/intelligence/symbol-index.js +410 -0
- package/dist/knowledge/auto-memory.js +412 -0
- package/dist/knowledge/conventions.js +475 -0
- package/dist/knowledge/corrections.js +213 -0
- package/dist/knowledge/rag.js +450 -0
- package/dist/knowledge/style-learner.js +324 -0
- package/dist/logger.js +35 -0
- package/dist/mcp/client.js +144 -0
- package/dist/mcp/config.js +24 -0
- package/dist/mcp/index.js +89 -0
- package/dist/modes/auto-compact.js +20 -0
- package/dist/modes/autopilot.js +157 -0
- package/dist/modes/background.js +82 -0
- package/dist/modes/interactive.js +187 -0
- package/dist/modes/oneshot.js +36 -0
- package/dist/modes/tui.js +265 -0
- package/dist/modes/turn.js +342 -0
- package/dist/notifications/manager.js +107 -0
- package/dist/plugins/marketplace.js +244 -0
- package/dist/providers/custom-provider.js +298 -0
- package/dist/providers/local-model.js +121 -0
- package/dist/routing/profiles.js +44 -0
- package/dist/routing/router.js +18 -0
- package/dist/sandbox/container.js +151 -0
- package/dist/security/audit.js +237 -0
- package/dist/security/content-filter.js +449 -0
- package/dist/security/proxy.js +301 -0
- package/dist/security/retention.js +281 -0
- package/dist/security/roles.js +252 -0
- package/dist/server/api-server.js +679 -0
- package/dist/session/bookmarks.js +72 -0
- package/dist/session/cloud-session.js +291 -0
- package/dist/session/handoff.js +405 -0
- package/dist/session/manager.js +35 -0
- package/dist/session/session.js +296 -0
- package/dist/session/share.js +313 -0
- package/dist/session/undo-journal.js +91 -0
- package/dist/snippets/store.js +60 -0
- package/dist/spaces/space-config.js +156 -0
- package/dist/spaces/space.js +220 -0
- package/dist/stats/store.js +101 -0
- package/dist/tools/apply-patch.js +134 -0
- package/dist/tools/auto-check.js +218 -0
- package/dist/tools/diff-edit.js +150 -0
- package/dist/tools/diff-prompt.js +36 -0
- package/dist/tools/edit-file.js +66 -0
- package/dist/tools/file-ops.js +205 -0
- package/dist/tools/glob.js +17 -0
- package/dist/tools/grep.js +56 -0
- package/dist/tools/image.js +194 -0
- package/dist/tools/list-directory.js +228 -0
- package/dist/tools/memory.js +17 -0
- package/dist/tools/multi-edit.js +299 -0
- package/dist/tools/policy.js +95 -0
- package/dist/tools/registry.js +484 -0
- package/dist/tools/retry.js +74 -0
- package/dist/tools/run-in-terminal.js +162 -0
- package/dist/tools/safety.js +64 -0
- package/dist/tools/sandbox.js +15 -0
- package/dist/tools/search-symbols.js +212 -0
- package/dist/tools/shell.js +118 -0
- package/dist/tools/web.js +167 -0
- package/dist/ui/prompt.js +37 -0
- package/dist/ui/render.js +96 -0
- package/dist/ui/screen.js +13 -0
- package/dist/ui/theme.js +56 -0
- package/dist/util/browser.js +34 -0
- package/dist/util/completion.js +350 -0
- package/dist/util/cost.js +28 -0
- package/dist/util/keybindings.js +113 -0
- package/dist/util/lazy.js +26 -0
- package/dist/util/perf.js +25 -0
- package/dist/util/token-worker.js +11 -0
- package/dist/util/tokens.js +50 -0
- package/dist/workflows/builtins.js +128 -0
- package/dist/workflows/engine.js +496 -0
- package/dist/workflows/file-trigger.js +197 -0
- package/package.json +79 -0
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { detectPackageManager } from './deps-cmd.js';
|
|
4
|
+
import { theme } from '../ui/theme.js';
|
|
5
|
+
const SECTION_ORDER = [
|
|
6
|
+
'title',
|
|
7
|
+
'badges',
|
|
8
|
+
'description',
|
|
9
|
+
'install',
|
|
10
|
+
'usage',
|
|
11
|
+
'api',
|
|
12
|
+
'scripts',
|
|
13
|
+
'contributing',
|
|
14
|
+
'license',
|
|
15
|
+
];
|
|
16
|
+
const SECTION_HEADINGS = {
|
|
17
|
+
install: 'Install',
|
|
18
|
+
usage: 'Usage',
|
|
19
|
+
api: 'API',
|
|
20
|
+
scripts: 'Scripts',
|
|
21
|
+
contributing: 'Contributing',
|
|
22
|
+
license: 'License',
|
|
23
|
+
};
|
|
24
|
+
const README_NAME = 'README.md';
|
|
25
|
+
export function generateReadme(rootDir, opts = {}) {
|
|
26
|
+
const ctx = analyzeReadmeContext(rootDir);
|
|
27
|
+
const sections = resolveSections(opts);
|
|
28
|
+
const blocks = renderBlocks(ctx);
|
|
29
|
+
return sections
|
|
30
|
+
.map((section) => blocks[section]?.trim())
|
|
31
|
+
.filter((section) => Boolean(section))
|
|
32
|
+
.join('\n\n')
|
|
33
|
+
.trim()
|
|
34
|
+
.concat('\n');
|
|
35
|
+
}
|
|
36
|
+
export function analyzeProject(rootDir) {
|
|
37
|
+
return analyzeReadmeContext(rootDir).analysis;
|
|
38
|
+
}
|
|
39
|
+
export function readmeCommand(args, cwd) {
|
|
40
|
+
const parsed = parseReadmeArgs(args);
|
|
41
|
+
if (parsed.error) {
|
|
42
|
+
return `${theme.warn(parsed.error)}\n`;
|
|
43
|
+
}
|
|
44
|
+
const readmePath = path.join(cwd, README_NAME);
|
|
45
|
+
if (parsed.mode === 'preview') {
|
|
46
|
+
return `${theme.brand('Generated README preview')}\n\n${generateReadme(cwd, parsed.options)}`;
|
|
47
|
+
}
|
|
48
|
+
if (parsed.mode === 'update') {
|
|
49
|
+
const next = updateExistingReadme(cwd, parsed.options);
|
|
50
|
+
fs.writeFileSync(readmePath, next, 'utf8');
|
|
51
|
+
return `${theme.ok(`Updated ${README_NAME}.`)}\n`;
|
|
52
|
+
}
|
|
53
|
+
if (fs.existsSync(readmePath) && !parsed.options.overwrite) {
|
|
54
|
+
return `${theme.warn(`${README_NAME} already exists.`)}\n${theme.dim('Use /readme update, /readme preview, or /readme --overwrite.')}\n`;
|
|
55
|
+
}
|
|
56
|
+
fs.writeFileSync(readmePath, generateReadme(cwd, parsed.options), 'utf8');
|
|
57
|
+
return `${theme.ok(`Wrote ${README_NAME}.`)}\n`;
|
|
58
|
+
}
|
|
59
|
+
function analyzeReadmeContext(rootDir) {
|
|
60
|
+
const packageJson = readPackageJson(rootDir);
|
|
61
|
+
const scripts = normalizeScripts(packageJson?.scripts);
|
|
62
|
+
const cliNames = collectCliNames(packageJson);
|
|
63
|
+
const entry = detectEntry(rootDir, packageJson);
|
|
64
|
+
const analysis = {
|
|
65
|
+
name: packageJson?.name?.trim() || path.basename(path.resolve(rootDir)),
|
|
66
|
+
description: packageJson?.description?.trim() ||
|
|
67
|
+
`Project scaffold for ${detectLanguage(rootDir, packageJson)} workflows.`,
|
|
68
|
+
language: detectLanguage(rootDir, packageJson),
|
|
69
|
+
packageManager: detectPackageManager(rootDir) ?? (packageJson ? 'npm' : 'unknown'),
|
|
70
|
+
scripts,
|
|
71
|
+
entry,
|
|
72
|
+
license: detectLicense(rootDir, packageJson),
|
|
73
|
+
};
|
|
74
|
+
const entrySource = resolveEntrySource(rootDir, packageJson, entry);
|
|
75
|
+
const entryText = entrySource ? safeRead(path.join(rootDir, entrySource)) : undefined;
|
|
76
|
+
return {
|
|
77
|
+
analysis,
|
|
78
|
+
packageJson,
|
|
79
|
+
cliNames,
|
|
80
|
+
cliCommands: entryText ? scanCliCommands(entryText) : [],
|
|
81
|
+
exportsList: collectExports(packageJson),
|
|
82
|
+
dependencies: collectDependencies(packageJson),
|
|
83
|
+
installSteps: buildInstallSteps(analysis, cliNames),
|
|
84
|
+
usageExamples: buildUsageExamples(analysis, cliNames, entryText),
|
|
85
|
+
contributingFile: detectContributingFile(rootDir),
|
|
86
|
+
rootDir,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function renderBlocks(ctx) {
|
|
90
|
+
return {
|
|
91
|
+
title: `# ${ctx.analysis.name}`,
|
|
92
|
+
badges: renderBadges(ctx),
|
|
93
|
+
description: ctx.analysis.description,
|
|
94
|
+
install: renderTitledSection('install', renderInstall(ctx)),
|
|
95
|
+
usage: renderTitledSection('usage', renderUsage(ctx)),
|
|
96
|
+
api: renderTitledSection('api', renderApi(ctx)),
|
|
97
|
+
scripts: renderTitledSection('scripts', renderScripts(ctx)),
|
|
98
|
+
contributing: renderTitledSection('contributing', renderContributing(ctx)),
|
|
99
|
+
license: renderTitledSection('license', renderLicense(ctx)),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function renderBadges(ctx) {
|
|
103
|
+
const badges = [];
|
|
104
|
+
const engine = ctx.packageJson?.engines?.node;
|
|
105
|
+
if (engine) {
|
|
106
|
+
badges.push(`}-339933?logo=node.js)`);
|
|
107
|
+
}
|
|
108
|
+
if (ctx.analysis.language !== 'Unknown') {
|
|
109
|
+
const langColor = ctx.analysis.language === 'TypeScript'
|
|
110
|
+
? '3178C6'
|
|
111
|
+
: ctx.analysis.language === 'JavaScript'
|
|
112
|
+
? 'F7DF1E'
|
|
113
|
+
: '6E7781';
|
|
114
|
+
badges.push(`}-${langColor})`);
|
|
115
|
+
}
|
|
116
|
+
if (ctx.analysis.license !== 'UNLICENSED') {
|
|
117
|
+
badges.push(`}-blue.svg)`);
|
|
118
|
+
}
|
|
119
|
+
return badges.join('\n');
|
|
120
|
+
}
|
|
121
|
+
function renderInstall(ctx) {
|
|
122
|
+
const lines = ctx.installSteps.length > 0 ? ctx.installSteps : defaultInstallSteps(ctx.analysis);
|
|
123
|
+
return toCodeFence(lines);
|
|
124
|
+
}
|
|
125
|
+
function renderUsage(ctx) {
|
|
126
|
+
const examples = ctx.usageExamples.length > 0 ? ctx.usageExamples : defaultUsageExamples(ctx.analysis);
|
|
127
|
+
return toCodeFence(examples);
|
|
128
|
+
}
|
|
129
|
+
function renderApi(ctx) {
|
|
130
|
+
const lines = [
|
|
131
|
+
`- Language: ${ctx.analysis.language}`,
|
|
132
|
+
`- Package manager: ${ctx.analysis.packageManager}`,
|
|
133
|
+
`- Entry: \`${ctx.analysis.entry}\``,
|
|
134
|
+
];
|
|
135
|
+
if (ctx.cliNames.length > 0) {
|
|
136
|
+
lines.push(`- CLI binaries: ${ctx.cliNames.map((name) => `\`${name}\``).join(', ')}`);
|
|
137
|
+
}
|
|
138
|
+
if (ctx.exportsList.length > 0) {
|
|
139
|
+
lines.push('- Exports:');
|
|
140
|
+
for (const entry of ctx.exportsList)
|
|
141
|
+
lines.push(` - \`${entry}\``);
|
|
142
|
+
}
|
|
143
|
+
if (ctx.cliCommands.length > 0) {
|
|
144
|
+
lines.push('- CLI commands:');
|
|
145
|
+
for (const command of ctx.cliCommands.slice(0, 8))
|
|
146
|
+
lines.push(` - \`${command}\``);
|
|
147
|
+
}
|
|
148
|
+
if (ctx.dependencies.length > 0) {
|
|
149
|
+
lines.push('- Dependencies:');
|
|
150
|
+
for (const dependency of ctx.dependencies.slice(0, 16)) {
|
|
151
|
+
lines.push(` - [${dependency.type}] \`${dependency.name}\` — ${dependency.version}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return lines.join('\n');
|
|
155
|
+
}
|
|
156
|
+
function renderScripts(ctx) {
|
|
157
|
+
const entries = Object.entries(ctx.analysis.scripts);
|
|
158
|
+
if (entries.length === 0) {
|
|
159
|
+
return '- No package scripts were detected.';
|
|
160
|
+
}
|
|
161
|
+
return entries.map(([name, command]) => `- \`${name}\`: \`${command}\``).join('\n');
|
|
162
|
+
}
|
|
163
|
+
function renderContributing(ctx) {
|
|
164
|
+
const lines = [];
|
|
165
|
+
if (ctx.contributingFile) {
|
|
166
|
+
lines.push(`See [${ctx.contributingFile}](./${ctx.contributingFile}) for project-specific guidelines.`);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
lines.push('Contributions are welcome. Start by installing dependencies and running the local quality checks:');
|
|
170
|
+
}
|
|
171
|
+
const checks = buildContributingChecks(ctx.analysis);
|
|
172
|
+
if (checks.length > 0) {
|
|
173
|
+
lines.push('', ...checks.map((command) => `- \`${command}\``));
|
|
174
|
+
}
|
|
175
|
+
return lines.join('\n');
|
|
176
|
+
}
|
|
177
|
+
function renderLicense(ctx) {
|
|
178
|
+
const hasLicenseFile = fs.existsSync(path.join(ctx.rootDir, 'LICENSE'));
|
|
179
|
+
return hasLicenseFile
|
|
180
|
+
? `Licensed under the ${ctx.analysis.license} license. See [LICENSE](./LICENSE).`
|
|
181
|
+
: `Licensed under the ${ctx.analysis.license} license.`;
|
|
182
|
+
}
|
|
183
|
+
function renderTitledSection(section, body) {
|
|
184
|
+
return `## ${SECTION_HEADINGS[section]}\n\n${body.trim()}`;
|
|
185
|
+
}
|
|
186
|
+
function updateExistingReadme(rootDir, opts) {
|
|
187
|
+
const readmePath = path.join(rootDir, README_NAME);
|
|
188
|
+
if (!fs.existsSync(readmePath)) {
|
|
189
|
+
return generateReadme(rootDir, opts);
|
|
190
|
+
}
|
|
191
|
+
const existing = fs.readFileSync(readmePath, 'utf8');
|
|
192
|
+
const sections = resolveSections(opts);
|
|
193
|
+
const blocks = renderBlocks(analyzeReadmeContext(rootDir));
|
|
194
|
+
let next = existing;
|
|
195
|
+
if (sections.some((section) => section === 'title' || section === 'badges' || section === 'description')) {
|
|
196
|
+
const preamble = SECTION_ORDER.filter((section) => (section === 'title' || section === 'badges' || section === 'description') &&
|
|
197
|
+
sections.includes(section))
|
|
198
|
+
.map((section) => blocks[section].trim())
|
|
199
|
+
.filter(Boolean)
|
|
200
|
+
.join('\n\n');
|
|
201
|
+
const firstHeadingMatch = next.match(/^##\s+/m);
|
|
202
|
+
const suffix = firstHeadingMatch ? next.slice(firstHeadingMatch.index ?? 0).trimStart() : '';
|
|
203
|
+
next = [preamble, suffix].filter(Boolean).join('\n\n').trim().concat('\n');
|
|
204
|
+
}
|
|
205
|
+
for (const section of sections) {
|
|
206
|
+
if (section === 'title' || section === 'badges' || section === 'description')
|
|
207
|
+
continue;
|
|
208
|
+
next = replaceSection(next, SECTION_HEADINGS[section], blocks[section]);
|
|
209
|
+
}
|
|
210
|
+
return next.endsWith('\n') ? next : `${next}\n`;
|
|
211
|
+
}
|
|
212
|
+
function replaceSection(readme, heading, block) {
|
|
213
|
+
const escapedHeading = escapeRegExp(heading);
|
|
214
|
+
const sectionPattern = new RegExp(`(^## ${escapedHeading}\\n[\\s\\S]*?)(?=^## \\S|\\Z)`, 'm');
|
|
215
|
+
if (sectionPattern.test(readme)) {
|
|
216
|
+
return readme.replace(sectionPattern, `${block.trim()}\n\n`);
|
|
217
|
+
}
|
|
218
|
+
return `${readme.trim()}\n\n${block.trim()}\n`;
|
|
219
|
+
}
|
|
220
|
+
function parseReadmeArgs(args) {
|
|
221
|
+
const options = {};
|
|
222
|
+
let mode = 'generate';
|
|
223
|
+
let index = 0;
|
|
224
|
+
if (args[0] === 'preview' || args[0] === 'update') {
|
|
225
|
+
mode = args[0];
|
|
226
|
+
index = 1;
|
|
227
|
+
}
|
|
228
|
+
while (index < args.length) {
|
|
229
|
+
const token = args[index];
|
|
230
|
+
if (token === '--overwrite') {
|
|
231
|
+
options.overwrite = true;
|
|
232
|
+
index += 1;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (token === '--template') {
|
|
236
|
+
const value = args[index + 1];
|
|
237
|
+
if (!value)
|
|
238
|
+
return {
|
|
239
|
+
mode,
|
|
240
|
+
options,
|
|
241
|
+
error: 'usage: /readme [preview|update] [--template <name>] [--sections a,b] [--overwrite]',
|
|
242
|
+
};
|
|
243
|
+
options.template = value;
|
|
244
|
+
index += 2;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
if (token.startsWith('--template=')) {
|
|
248
|
+
options.template = token.slice('--template='.length);
|
|
249
|
+
index += 1;
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
if (token === '--sections') {
|
|
253
|
+
const value = args[index + 1];
|
|
254
|
+
if (!value)
|
|
255
|
+
return {
|
|
256
|
+
mode,
|
|
257
|
+
options,
|
|
258
|
+
error: 'usage: /readme [preview|update] [--template <name>] [--sections a,b] [--overwrite]',
|
|
259
|
+
};
|
|
260
|
+
options.sections = splitSections(value);
|
|
261
|
+
index += 2;
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (token.startsWith('--sections=')) {
|
|
265
|
+
options.sections = splitSections(token.slice('--sections='.length));
|
|
266
|
+
index += 1;
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
mode,
|
|
271
|
+
options,
|
|
272
|
+
error: `unknown /readme argument: ${token}`,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
return { mode, options };
|
|
276
|
+
}
|
|
277
|
+
function resolveSections(opts) {
|
|
278
|
+
const requested = (opts.sections ?? defaultSectionsForTemplate(opts.template))
|
|
279
|
+
.map((section) => section.trim().toLowerCase())
|
|
280
|
+
.filter(Boolean);
|
|
281
|
+
const resolved = requested.filter((section) => SECTION_ORDER.includes(section));
|
|
282
|
+
return resolved.length > 0 ? resolved : [...SECTION_ORDER];
|
|
283
|
+
}
|
|
284
|
+
function defaultSectionsForTemplate(template) {
|
|
285
|
+
if (template === 'minimal') {
|
|
286
|
+
return ['title', 'description', 'install', 'usage', 'license'];
|
|
287
|
+
}
|
|
288
|
+
return [...SECTION_ORDER];
|
|
289
|
+
}
|
|
290
|
+
function splitSections(value) {
|
|
291
|
+
return value
|
|
292
|
+
.split(',')
|
|
293
|
+
.map((part) => part.trim())
|
|
294
|
+
.filter(Boolean);
|
|
295
|
+
}
|
|
296
|
+
function readPackageJson(rootDir) {
|
|
297
|
+
const packageJsonPath = path.join(rootDir, 'package.json');
|
|
298
|
+
const text = safeRead(packageJsonPath);
|
|
299
|
+
if (!text)
|
|
300
|
+
return undefined;
|
|
301
|
+
try {
|
|
302
|
+
return JSON.parse(text);
|
|
303
|
+
}
|
|
304
|
+
catch {
|
|
305
|
+
return undefined;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function normalizeScripts(scripts) {
|
|
309
|
+
return Object.fromEntries(Object.entries(scripts ?? {}).sort(([left], [right]) => left.localeCompare(right)));
|
|
310
|
+
}
|
|
311
|
+
function collectCliNames(packageJson) {
|
|
312
|
+
if (!packageJson?.bin)
|
|
313
|
+
return [];
|
|
314
|
+
if (typeof packageJson.bin === 'string') {
|
|
315
|
+
return packageJson.name ? [packageJson.name] : [];
|
|
316
|
+
}
|
|
317
|
+
return Object.keys(packageJson.bin).sort((left, right) => left.localeCompare(right));
|
|
318
|
+
}
|
|
319
|
+
function detectEntry(rootDir, packageJson) {
|
|
320
|
+
const binPath = typeof packageJson?.bin === 'string'
|
|
321
|
+
? packageJson.bin
|
|
322
|
+
: packageJson?.bin
|
|
323
|
+
? Object.values(packageJson.bin)[0]
|
|
324
|
+
: undefined;
|
|
325
|
+
const normalizedBinPath = normalizeRelative(binPath);
|
|
326
|
+
if (normalizedBinPath)
|
|
327
|
+
return normalizedBinPath;
|
|
328
|
+
if (packageJson?.main)
|
|
329
|
+
return normalizeRelative(packageJson.main);
|
|
330
|
+
const fallbacks = ['src/index.ts', 'src/cli.ts', 'index.ts', 'index.js'];
|
|
331
|
+
for (const candidate of fallbacks) {
|
|
332
|
+
if (fs.existsSync(path.join(rootDir, candidate)))
|
|
333
|
+
return candidate;
|
|
334
|
+
}
|
|
335
|
+
return 'src/index.ts';
|
|
336
|
+
}
|
|
337
|
+
function resolveEntrySource(rootDir, packageJson, entry) {
|
|
338
|
+
if (entry.endsWith('.ts') && fs.existsSync(path.join(rootDir, entry))) {
|
|
339
|
+
return entry;
|
|
340
|
+
}
|
|
341
|
+
if (entry.endsWith('.js')) {
|
|
342
|
+
const directTs = entry.replace(/^dist\//u, 'src/').replace(/\.js$/u, '.ts');
|
|
343
|
+
if (fs.existsSync(path.join(rootDir, directTs)))
|
|
344
|
+
return directTs;
|
|
345
|
+
const entryText = safeRead(path.join(rootDir, entry));
|
|
346
|
+
const distImport = entryText?.match(/(?:import\s+['"]\.\.\/dist\/([^'"]+)\.js['"]|require\(['"]\.\.\/dist\/([^'"]+)\.js['"]\))/u);
|
|
347
|
+
const sourceStem = distImport?.[1] ?? distImport?.[2];
|
|
348
|
+
if (sourceStem) {
|
|
349
|
+
const sourcePath = path.join('src', `${sourceStem}.ts`);
|
|
350
|
+
if (fs.existsSync(path.join(rootDir, sourcePath)))
|
|
351
|
+
return sourcePath;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (packageJson?.main) {
|
|
355
|
+
const candidate = packageJson.main.replace(/^dist\//u, 'src/').replace(/\.js$/u, '.ts');
|
|
356
|
+
if (fs.existsSync(path.join(rootDir, candidate)))
|
|
357
|
+
return candidate;
|
|
358
|
+
}
|
|
359
|
+
return undefined;
|
|
360
|
+
}
|
|
361
|
+
function detectLanguage(rootDir, packageJson) {
|
|
362
|
+
if (fs.existsSync(path.join(rootDir, 'tsconfig.json')))
|
|
363
|
+
return 'TypeScript';
|
|
364
|
+
if (hasFileWithExtension(rootDir, '.ts'))
|
|
365
|
+
return 'TypeScript';
|
|
366
|
+
if (packageJson?.type === 'module' || fs.existsSync(path.join(rootDir, 'package.json'))) {
|
|
367
|
+
return 'JavaScript';
|
|
368
|
+
}
|
|
369
|
+
if (fs.existsSync(path.join(rootDir, 'pyproject.toml')) ||
|
|
370
|
+
fs.existsSync(path.join(rootDir, 'requirements.txt'))) {
|
|
371
|
+
return 'Python';
|
|
372
|
+
}
|
|
373
|
+
return 'Unknown';
|
|
374
|
+
}
|
|
375
|
+
function detectLicense(rootDir, packageJson) {
|
|
376
|
+
if (packageJson?.license?.trim())
|
|
377
|
+
return packageJson.license.trim();
|
|
378
|
+
const licenseText = safeRead(path.join(rootDir, 'LICENSE'));
|
|
379
|
+
if (!licenseText)
|
|
380
|
+
return 'UNLICENSED';
|
|
381
|
+
const firstLine = licenseText.split(/\r?\n/u)[0]?.trim();
|
|
382
|
+
return firstLine?.replace(/\s+License$/u, '') || 'UNLICENSED';
|
|
383
|
+
}
|
|
384
|
+
function collectDependencies(packageJson) {
|
|
385
|
+
const prod = Object.entries(packageJson?.dependencies ?? {}).map(([name, version]) => ({
|
|
386
|
+
name,
|
|
387
|
+
version,
|
|
388
|
+
type: 'prod',
|
|
389
|
+
}));
|
|
390
|
+
const dev = Object.entries(packageJson?.devDependencies ?? {}).map(([name, version]) => ({
|
|
391
|
+
name,
|
|
392
|
+
version,
|
|
393
|
+
type: 'dev',
|
|
394
|
+
}));
|
|
395
|
+
return [...prod, ...dev].sort((left, right) => left.name.localeCompare(right.name));
|
|
396
|
+
}
|
|
397
|
+
function buildInstallSteps(analysis, cliNames) {
|
|
398
|
+
switch (analysis.packageManager) {
|
|
399
|
+
case 'npm':
|
|
400
|
+
return cliNames.length > 0
|
|
401
|
+
? [
|
|
402
|
+
'npm install',
|
|
403
|
+
hasScript(analysis.scripts, 'build') ? 'npm run build' : '',
|
|
404
|
+
'npm link',
|
|
405
|
+
].filter(Boolean)
|
|
406
|
+
: ['npm install', hasScript(analysis.scripts, 'build') ? 'npm run build' : ''].filter(Boolean);
|
|
407
|
+
case 'pnpm':
|
|
408
|
+
return cliNames.length > 0
|
|
409
|
+
? [
|
|
410
|
+
'pnpm install',
|
|
411
|
+
hasScript(analysis.scripts, 'build') ? 'pnpm run build' : '',
|
|
412
|
+
'pnpm link --global',
|
|
413
|
+
].filter(Boolean)
|
|
414
|
+
: ['pnpm install', hasScript(analysis.scripts, 'build') ? 'pnpm run build' : ''].filter(Boolean);
|
|
415
|
+
case 'yarn':
|
|
416
|
+
return ['yarn install', hasScript(analysis.scripts, 'build') ? 'yarn build' : ''].filter(Boolean);
|
|
417
|
+
case 'pip':
|
|
418
|
+
return ['pip install -r requirements.txt'];
|
|
419
|
+
default:
|
|
420
|
+
return defaultInstallSteps(analysis);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
function defaultInstallSteps(analysis) {
|
|
424
|
+
if (analysis.language === 'TypeScript' || analysis.language === 'JavaScript') {
|
|
425
|
+
return ['npm install'];
|
|
426
|
+
}
|
|
427
|
+
return ['Follow the project-specific setup workflow for this repository.'];
|
|
428
|
+
}
|
|
429
|
+
function buildUsageExamples(analysis, cliNames, entryText) {
|
|
430
|
+
const examples = [];
|
|
431
|
+
const cli = cliNames[0];
|
|
432
|
+
const commands = entryText ? scanCliCommands(entryText) : [];
|
|
433
|
+
if (cli) {
|
|
434
|
+
examples.push(`${cli} --help`);
|
|
435
|
+
for (const command of commands.slice(0, 3)) {
|
|
436
|
+
examples.push(`${cli} ${command}`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
if (examples.length === 0 && hasScript(analysis.scripts, 'start')) {
|
|
440
|
+
examples.push(runScriptCommand(analysis.packageManager, 'start'));
|
|
441
|
+
}
|
|
442
|
+
if (examples.length === 0 && analysis.entry) {
|
|
443
|
+
examples.push(`node ${analysis.entry}`);
|
|
444
|
+
}
|
|
445
|
+
return unique(examples);
|
|
446
|
+
}
|
|
447
|
+
function defaultUsageExamples(analysis) {
|
|
448
|
+
if (hasScript(analysis.scripts, 'start')) {
|
|
449
|
+
return [runScriptCommand(analysis.packageManager, 'start')];
|
|
450
|
+
}
|
|
451
|
+
return [`node ${analysis.entry}`];
|
|
452
|
+
}
|
|
453
|
+
function scanCliCommands(entryText) {
|
|
454
|
+
const commands = Array.from(entryText.matchAll(/\.command\(\s*['"`]([^'"`]+?)['"`]\s*\)/gu)).map((match) => match[1].trim());
|
|
455
|
+
return unique(commands);
|
|
456
|
+
}
|
|
457
|
+
function collectExports(packageJson) {
|
|
458
|
+
if (!packageJson)
|
|
459
|
+
return [];
|
|
460
|
+
const entries = [];
|
|
461
|
+
flattenExports(packageJson.exports, '.', entries);
|
|
462
|
+
if (entries.length === 0 && packageJson.main)
|
|
463
|
+
entries.push(`main -> ${normalizeRelative(packageJson.main)}`);
|
|
464
|
+
return unique(entries);
|
|
465
|
+
}
|
|
466
|
+
function flattenExports(value, key, acc) {
|
|
467
|
+
if (!value)
|
|
468
|
+
return;
|
|
469
|
+
if (typeof value === 'string') {
|
|
470
|
+
acc.push(`${key} -> ${normalizeRelative(value)}`);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
if (typeof value !== 'object' || Array.isArray(value))
|
|
474
|
+
return;
|
|
475
|
+
for (const [nestedKey, nestedValue] of Object.entries(value)) {
|
|
476
|
+
if (typeof nestedValue === 'string') {
|
|
477
|
+
acc.push(`${key === '.' ? nestedKey : `${key}.${nestedKey}`} -> ${normalizeRelative(nestedValue)}`);
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
if (nestedValue && typeof nestedValue === 'object' && !Array.isArray(nestedValue)) {
|
|
481
|
+
flattenExports(nestedValue, key === '.' ? nestedKey : `${key}.${nestedKey}`, acc);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
function detectContributingFile(rootDir) {
|
|
486
|
+
const candidates = ['CONTRIBUTING.md', 'CONTRIBUTING', 'docs/CONTRIBUTING.md'];
|
|
487
|
+
return candidates.find((candidate) => fs.existsSync(path.join(rootDir, candidate)));
|
|
488
|
+
}
|
|
489
|
+
function buildContributingChecks(analysis) {
|
|
490
|
+
const commands = [];
|
|
491
|
+
if (hasScript(analysis.scripts, 'lint'))
|
|
492
|
+
commands.push(runScriptCommand(analysis.packageManager, 'lint'));
|
|
493
|
+
if (hasScript(analysis.scripts, 'test'))
|
|
494
|
+
commands.push(runScriptCommand(analysis.packageManager, 'test'));
|
|
495
|
+
if (hasScript(analysis.scripts, 'build'))
|
|
496
|
+
commands.push(runScriptCommand(analysis.packageManager, 'build'));
|
|
497
|
+
return unique(commands);
|
|
498
|
+
}
|
|
499
|
+
function runScriptCommand(packageManager, script) {
|
|
500
|
+
switch (packageManager) {
|
|
501
|
+
case 'yarn':
|
|
502
|
+
return `yarn ${script}`;
|
|
503
|
+
case 'pnpm':
|
|
504
|
+
return `pnpm run ${script}`;
|
|
505
|
+
default:
|
|
506
|
+
return `npm run ${script}`;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
function hasScript(scripts, name) {
|
|
510
|
+
return typeof scripts[name] === 'string' && scripts[name].length > 0;
|
|
511
|
+
}
|
|
512
|
+
function safeRead(filePath) {
|
|
513
|
+
try {
|
|
514
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
515
|
+
}
|
|
516
|
+
catch {
|
|
517
|
+
return undefined;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
function hasFileWithExtension(rootDir, extension) {
|
|
521
|
+
const stack = [rootDir];
|
|
522
|
+
while (stack.length > 0) {
|
|
523
|
+
const dirPath = stack.pop();
|
|
524
|
+
let entries = [];
|
|
525
|
+
try {
|
|
526
|
+
entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
527
|
+
}
|
|
528
|
+
catch {
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
for (const entry of entries) {
|
|
532
|
+
if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === 'dist')
|
|
533
|
+
continue;
|
|
534
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
535
|
+
if (entry.isDirectory()) {
|
|
536
|
+
stack.push(fullPath);
|
|
537
|
+
}
|
|
538
|
+
else if (entry.isFile() && entry.name.endsWith(extension)) {
|
|
539
|
+
return true;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return false;
|
|
544
|
+
}
|
|
545
|
+
function normalizeRelative(value) {
|
|
546
|
+
if (!value)
|
|
547
|
+
return '';
|
|
548
|
+
return value
|
|
549
|
+
.replace(/^[.][/\\]/u, '')
|
|
550
|
+
.split(path.sep)
|
|
551
|
+
.join('/');
|
|
552
|
+
}
|
|
553
|
+
function unique(values) {
|
|
554
|
+
return Array.from(new Set(values));
|
|
555
|
+
}
|
|
556
|
+
function toCodeFence(lines) {
|
|
557
|
+
return ['```bash', ...lines, '```'].join('\n');
|
|
558
|
+
}
|
|
559
|
+
function encodeBadgeValue(value) {
|
|
560
|
+
return encodeURIComponent(value).replace(/-/gu, '--');
|
|
561
|
+
}
|
|
562
|
+
function escapeRegExp(value) {
|
|
563
|
+
return value.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
|
564
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { config } from '../config.js';
|
|
2
|
+
export function setReasoningEffort(level) {
|
|
3
|
+
config.reasoningEffort = level;
|
|
4
|
+
}
|
|
5
|
+
export function setThinkTokens(budget) {
|
|
6
|
+
if (budget === null) {
|
|
7
|
+
config.thinkTokens = undefined;
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
if (!Number.isFinite(budget) || budget < 0) {
|
|
11
|
+
throw new Error('think token budget must be a non-negative number');
|
|
12
|
+
}
|
|
13
|
+
config.thinkTokens = Math.floor(budget);
|
|
14
|
+
}
|
|
15
|
+
export function parseTokenBudget(input) {
|
|
16
|
+
const trimmed = input.trim();
|
|
17
|
+
const match = trimmed.match(/^(\d+(?:\.\d+)?)([kKmM]?)$/);
|
|
18
|
+
if (!match) {
|
|
19
|
+
throw new Error(`invalid token budget: ${input}`);
|
|
20
|
+
}
|
|
21
|
+
const amount = Number(match[1]);
|
|
22
|
+
if (!Number.isFinite(amount) || amount < 0) {
|
|
23
|
+
throw new Error(`invalid token budget: ${input}`);
|
|
24
|
+
}
|
|
25
|
+
const suffix = match[2].toLowerCase();
|
|
26
|
+
const multiplier = suffix === 'k' ? 1024 : suffix === 'm' ? 1024 * 1024 : 1;
|
|
27
|
+
return Math.round(amount * multiplier);
|
|
28
|
+
}
|
|
29
|
+
export function getReasoningConfig() {
|
|
30
|
+
return {
|
|
31
|
+
...(config.reasoningEffort ? { effort: config.reasoningEffort } : {}),
|
|
32
|
+
...(typeof config.thinkTokens === 'number' ? { thinkTokens: config.thinkTokens } : {}),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { theme } from '../ui/theme.js';
|
|
4
|
+
function usage() {
|
|
5
|
+
return [
|
|
6
|
+
theme.brand('Refactor command'),
|
|
7
|
+
' /refactor rename <old> <new> [path] build a rename-symbol refactor prompt',
|
|
8
|
+
' /refactor extract <path> <lines> build an extract-function refactor prompt',
|
|
9
|
+
' /refactor inline <path> <symbol> build an inline-symbol refactor prompt',
|
|
10
|
+
'',
|
|
11
|
+
].join('\n');
|
|
12
|
+
}
|
|
13
|
+
function renderPayload(payload) {
|
|
14
|
+
return `${theme.hl(payload.description)}\n${payload.prompt}\n`;
|
|
15
|
+
}
|
|
16
|
+
function renderError(message) {
|
|
17
|
+
return `${theme.err(message)}\n`;
|
|
18
|
+
}
|
|
19
|
+
function resolveExistingPath(cwd, target) {
|
|
20
|
+
const resolvedPath = path.resolve(cwd, target);
|
|
21
|
+
return fs.existsSync(resolvedPath) ? resolvedPath : undefined;
|
|
22
|
+
}
|
|
23
|
+
function buildRenamePayload(args, cwd) {
|
|
24
|
+
const [oldSymbol, newSymbol, targetPath] = args;
|
|
25
|
+
if (!oldSymbol || !newSymbol) {
|
|
26
|
+
return renderError('usage: /refactor rename <old> <new> [path]');
|
|
27
|
+
}
|
|
28
|
+
let scope = `across the workspace rooted at ${cwd}`;
|
|
29
|
+
if (targetPath) {
|
|
30
|
+
const resolvedPath = resolveExistingPath(cwd, targetPath);
|
|
31
|
+
if (!resolvedPath) {
|
|
32
|
+
return renderError(`target file not found: ${path.resolve(cwd, targetPath)}`);
|
|
33
|
+
}
|
|
34
|
+
scope = `starting from ${resolvedPath} and any related files`;
|
|
35
|
+
}
|
|
36
|
+
const payload = {
|
|
37
|
+
kind: 'rename',
|
|
38
|
+
description: `Refactor intent: rename "${oldSymbol}" to "${newSymbol}"`,
|
|
39
|
+
prompt: `Rename the symbol "${oldSymbol}" to "${newSymbol}" ${scope}. ` +
|
|
40
|
+
'Update all references, imports, exports, and type usages consistently. ' +
|
|
41
|
+
'Avoid unrelated edits and preserve behavior.',
|
|
42
|
+
};
|
|
43
|
+
return renderPayload(payload);
|
|
44
|
+
}
|
|
45
|
+
function buildExtractPayload(args, cwd) {
|
|
46
|
+
const [targetPath, lines] = args;
|
|
47
|
+
if (!targetPath || !lines) {
|
|
48
|
+
return renderError('usage: /refactor extract <path> <lines>');
|
|
49
|
+
}
|
|
50
|
+
const resolvedPath = resolveExistingPath(cwd, targetPath);
|
|
51
|
+
if (!resolvedPath) {
|
|
52
|
+
return renderError(`target file not found: ${path.resolve(cwd, targetPath)}`);
|
|
53
|
+
}
|
|
54
|
+
const payload = {
|
|
55
|
+
kind: 'extract',
|
|
56
|
+
description: `Refactor intent: extract a function from ${path.basename(resolvedPath)}:${lines}`,
|
|
57
|
+
prompt: `Extract a well-named function from lines ${lines} in ${resolvedPath}. ` +
|
|
58
|
+
'Preserve the current behavior, keep dependencies explicit, and update the original call site to use the new function. ' +
|
|
59
|
+
'Prefer a minimal, readable refactor.',
|
|
60
|
+
};
|
|
61
|
+
return renderPayload(payload);
|
|
62
|
+
}
|
|
63
|
+
function buildInlinePayload(args, cwd) {
|
|
64
|
+
const [targetPath, symbol] = args;
|
|
65
|
+
if (!targetPath || !symbol) {
|
|
66
|
+
return renderError('usage: /refactor inline <path> <symbol>');
|
|
67
|
+
}
|
|
68
|
+
const resolvedPath = resolveExistingPath(cwd, targetPath);
|
|
69
|
+
if (!resolvedPath) {
|
|
70
|
+
return renderError(`target file not found: ${path.resolve(cwd, targetPath)}`);
|
|
71
|
+
}
|
|
72
|
+
const payload = {
|
|
73
|
+
kind: 'inline',
|
|
74
|
+
description: `Refactor intent: inline "${symbol}" in ${path.basename(resolvedPath)}`,
|
|
75
|
+
prompt: `Inline the symbol "${symbol}" in ${resolvedPath}. ` +
|
|
76
|
+
'Replace its usages with the underlying expression or implementation where appropriate, remove the obsolete declaration if safe, ' +
|
|
77
|
+
'and keep the file behavior unchanged.',
|
|
78
|
+
};
|
|
79
|
+
return renderPayload(payload);
|
|
80
|
+
}
|
|
81
|
+
export function refactorCommand(args, cwd) {
|
|
82
|
+
const [subcommand, ...rest] = args;
|
|
83
|
+
if (!subcommand) {
|
|
84
|
+
return usage();
|
|
85
|
+
}
|
|
86
|
+
switch (subcommand.toLowerCase()) {
|
|
87
|
+
case 'rename':
|
|
88
|
+
return buildRenamePayload(rest, cwd);
|
|
89
|
+
case 'extract':
|
|
90
|
+
return buildExtractPayload(rest, cwd);
|
|
91
|
+
case 'inline':
|
|
92
|
+
return buildInlinePayload(rest, cwd);
|
|
93
|
+
default:
|
|
94
|
+
return `${theme.warn(`unknown refactor subcommand: ${subcommand}`)}\n${usage()}`;
|
|
95
|
+
}
|
|
96
|
+
}
|