ccraft 1.0.10 → 1.0.12

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.
@@ -64,6 +64,7 @@ program
64
64
  .option('-y, --yes', 'Accept all defaults (non-interactive)')
65
65
  .option('-n, --name <name>', 'Project name (non-interactive mode)')
66
66
  .option('--description <text>', 'Project description (non-interactive mode)')
67
+ .option('--pro', 'Developer mode — skip persona selection, show all options')
67
68
  .option('-d, --dir <path>', 'Parent directory to create the project in (default: cwd)')
68
69
  .action(runCreate);
69
70
 
@@ -72,6 +73,7 @@ program
72
73
  .description('Generate Claude Code configuration files in the current project')
73
74
  .option('-y, --yes', 'Accept all defaults (non-interactive)')
74
75
  .option(`-p, --preset <preset>`, `Apply a framework preset (${Object.keys(PRESET_ALIASES).join(', ')})`)
76
+ .option('--pro', 'Developer mode — skip persona selection, show all options')
75
77
  .option('-d, --dir <path>', 'Target directory (default: cwd)')
76
78
  .action(runInstall);
77
79
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccraft",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "Intelligent Claude Code project configurator — role-aware agents, skills, rules, MCPs, and workflows",
5
5
  "type": "module",
6
6
  "bin": {
@@ -3,13 +3,12 @@ import { mkdirSync, existsSync, writeFileSync, readdirSync } from 'fs';
3
3
  import { execFileSync } from 'child_process';
4
4
  import chalk from 'chalk';
5
5
  import ora from 'ora';
6
- import { gatherCreateProfile, gatherMcpConfig, confirmInstallation } from '../prompts/gather.js';
6
+ import { gatherCreateProfile } from '../prompts/gather.js';
7
7
  import { themedInput } from '../ui/prompts.js';
8
8
  import { callGenerate, ApiError } from '../utils/api-client.js';
9
9
  import { runPreflight } from '../utils/preflight.js';
10
10
  import { writeApiFiles, buildFileList } from '../utils/api-file-writer.js';
11
11
  import { setupMcps } from '../utils/mcp-setup.js';
12
- import { scoreWithClaude } from '../utils/claude-scorer.js';
13
12
  import { optimizeSettings } from '../utils/claude-optimizer.js';
14
13
  import { rewriteClaudeMd } from '../utils/claude-rewriter.js';
15
14
  import { detectProject } from '../utils/detect-project.js';
@@ -19,7 +18,6 @@ import { platformCmd } from '../utils/run-claude.js';
19
18
  import { runBootstrap } from '../utils/bootstrap-runner.js';
20
19
  import {
21
20
  writeAnalysisCache,
22
- writeUserProfile,
23
21
  updateManifest,
24
22
  readAnalysisCache,
25
23
  promoteCache,
@@ -46,7 +44,7 @@ function renderCreatePhase(number, name, totalPhases) {
46
44
  console.log();
47
45
  }
48
46
 
49
- function countTotalItems(summary, selectedCandidateIds) {
47
+ function countTotalItems(summary) {
50
48
  const countBucket = (bucket) => {
51
49
  if (!bucket) return 0;
52
50
  return Object.values(bucket).reduce(
@@ -54,13 +52,7 @@ function countTotalItems(summary, selectedCandidateIds) {
54
52
  0,
55
53
  );
56
54
  };
57
- let total = countBucket(summary.guaranteed);
58
- if (selectedCandidateIds === null) {
59
- total += countBucket(summary.candidates);
60
- } else {
61
- total += selectedCandidateIds.length;
62
- }
63
- return total;
55
+ return countBucket(summary.guaranteed) + countBucket(summary.candidates);
64
56
  }
65
57
 
66
58
  // ── Main command ─────────────────────────────────────────────────────────────
@@ -170,13 +162,6 @@ export async function runCreate(options = {}) {
170
162
  : `Created ${chalk.bold(name)}/ (git init skipped — git not available).`);
171
163
  }
172
164
 
173
- // Build user profile
174
- const userProfile = {
175
- intents: ['implementing', 'debugging', 'refactoring', 'testing', 'reviewing'],
176
- sourceControl: 'github',
177
- documentTools: [],
178
- };
179
-
180
165
  // ================================================================
181
166
  // STEP 3: Configuration & Install
182
167
  // ================================================================
@@ -237,7 +222,6 @@ export async function runCreate(options = {}) {
237
222
  let apiResponse;
238
223
  try {
239
224
  apiResponse = await callGenerate(
240
- userProfile,
241
225
  {
242
226
  ...syntheticProjectInfo,
243
227
  detectedFiles: [],
@@ -270,33 +254,14 @@ export async function runCreate(options = {}) {
270
254
  console.log();
271
255
  renderComponentBreakdown(summary);
272
256
 
273
- // MCP selection — auto-select core/role/stack for new projects (no stack to score against)
274
- let selectedMcps;
275
- let mcpKeys = {};
257
+ // Auto-install all MCPs returned by the server
258
+ const selectedMcps = mcpConfigs || [];
259
+ const mcpKeys = {};
276
260
  const securityConfig = { addSecurityGitignore: true };
277
261
 
278
- if (options.yes) {
279
- selectedMcps = mcpConfigs.filter(
280
- (m) => m.tier === 'core' || m.tier === 'role' || m.tier === 'stack' || m.tier === 'auto' || m.recommended,
281
- );
282
- } else {
283
- const mcpResult = await gatherMcpConfig(mcpConfigs);
284
- selectedMcps = mcpResult.selectedMcps;
285
- mcpKeys = mcpResult.mcpKeys;
286
-
287
- const finalSummary = { ...summary, mcps: selectedMcps.map((m) => ({ id: m.id, tier: m.tier })) };
288
- const proceed = await confirmInstallation(finalSummary);
289
- if (!proceed) {
290
- console.log();
291
- logger.info('Cancelled.');
292
- process.exit(0);
293
- }
294
- }
295
-
296
- // Cache analysis and profile
262
+ // Cache analysis
297
263
  try {
298
264
  writeAnalysisCache(targetDir, syntheticProjectInfo, syntheticDetected, null);
299
- writeUserProfile(targetDir, userProfile);
300
265
  } catch (cacheErr) {
301
266
  logger.debug(`Analysis cache write failed: ${cacheErr.message}`);
302
267
  }
@@ -304,12 +269,10 @@ export async function runCreate(options = {}) {
304
269
  // Write files
305
270
  let results;
306
271
  let filesToWrite;
307
- let selectedCandidateIds = null;
308
272
 
309
273
  try {
310
274
  const installCtx = await runInstallTasks({
311
275
  apiResponse,
312
- selectedCandidateIds: null,
313
276
  targetDir,
314
277
  selectedMcps,
315
278
  mcpKeys,
@@ -317,16 +280,14 @@ export async function runCreate(options = {}) {
317
280
  detected: syntheticDetected,
318
281
  buildFileList,
319
282
  writeApiFiles,
320
- scoreWithClaude,
321
283
  });
322
284
 
323
285
  results = installCtx.results;
324
286
  filesToWrite = installCtx.filesToWrite;
325
- selectedCandidateIds = installCtx.selectedCandidateIds ?? null;
326
287
  } catch {
327
288
  // Fallback to direct write
328
289
  const spinnerWrite = ora('Writing configuration files...').start();
329
- filesToWrite = buildFileList(apiResponse, null);
290
+ filesToWrite = buildFileList(apiResponse);
330
291
  results = await writeApiFiles(filesToWrite, targetDir, {
331
292
  force: true,
332
293
  selectedMcpIds: selectedMcps.map((m) => m.id),
@@ -436,7 +397,6 @@ export async function runCreate(options = {}) {
436
397
  // Overwrite cache with real data
437
398
  const detected = { ...fsDetected, ...projectInfo };
438
399
  writeAnalysisCache(targetDir, projectInfo, detected, null);
439
- writeUserProfile(targetDir, userProfile);
440
400
  } catch (err) {
441
401
  logger.debug(`Post-bootstrap analysis failed: ${err.message}`);
442
402
  }
@@ -471,7 +431,7 @@ export async function runCreate(options = {}) {
471
431
  }
472
432
 
473
433
  // ── Success ──────────────────────────────────────────────────────
474
- const totalItems = countTotalItems(summary, selectedCandidateIds);
434
+ const totalItems = countTotalItems(summary);
475
435
  const mcpsNeedingKeys = mcpResults.filter((r) => r.status === 'needs-key');
476
436
 
477
437
  renderSuccessCard({
@@ -11,26 +11,21 @@ import {
11
11
  } from '../utils/existing-setup.js';
12
12
  import {
13
13
  gatherProjectPath,
14
- gatherUserProfile,
15
- gatherMcpConfig,
16
- gatherSecurityConfig,
17
14
  confirmInstallation,
18
15
  } from '../prompts/gather.js';
19
16
  import { callGenerate, ApiError } from '../utils/api-client.js';
20
17
  import { writeApiFiles, buildFileList } from '../utils/api-file-writer.js';
21
18
  import { setupMcps } from '../utils/mcp-setup.js';
22
- import { scoreWithClaude } from '../utils/claude-scorer.js';
23
19
  import { optimizeSettings } from '../utils/claude-optimizer.js';
24
20
  import { runPreflight } from '../utils/preflight.js';
25
21
  import {
26
22
  writeAnalysisCache,
27
- writeUserProfile,
28
23
  updateManifest,
29
24
  readAnalysisCache,
30
25
  promoteCache,
31
26
  cleanupAnalysisCache,
32
27
  } from '../utils/analysis-cache.js';
33
- import { PRESET_ALIASES, VERSION } from '../constants.js';
28
+ import { VERSION } from '../constants.js';
34
29
  import * as logger from '../utils/logger.js';
35
30
 
36
31
  // UI modules
@@ -39,16 +34,14 @@ import { renderPhaseHeader } from '../ui/phase-header.js';
39
34
  import { renderProjectCard, renderSuccessCard } from '../ui/cards.js';
40
35
  import { renderComponentBreakdown, renderMcpStatus, renderFileResults } from '../ui/tables.js';
41
36
  import { runExistingSetupTasks, runAnalysisTasks, runInstallTasks, runVerifyTasks, runFinalizeTasks } from '../ui/tasks.js';
42
- import { colors } from '../ui/theme.js';
43
- import { dotPad } from '../ui/format.js';
44
37
 
45
38
  /**
46
39
  * Main install command — 5-phase orchestrator.
47
40
  *
48
- * Phase 1: Welcome & Setup (env check + user profile)
41
+ * Phase 1: Preflight (env check + project path)
49
42
  * Phase 2: Project Discovery (Claude analysis + filesystem scan)
50
- * Phase 3: Configuration (server API + MCP selection + security + confirmation)
51
- * Phase 4: Installation (Claude scoring + file writing + MCP verification)
43
+ * Phase 3: Generate & Install (server API + confirmation + file writing)
44
+ * Phase 4: MCP Verification
52
45
  * Phase 5: Finalization (settings optimization + CLAUDE.md rewrite + success)
53
46
  */
54
47
  export async function runInstall(options = {}) {
@@ -72,17 +65,6 @@ export async function runInstall(options = {}) {
72
65
  targetDir = resolve(options.dir || process.cwd());
73
66
  }
74
67
 
75
- // ── User profile ───────────────────────────────────────────────
76
- renderPhaseHeader(1);
77
-
78
- let userProfile;
79
- if (options.yes) {
80
- userProfile = { intents: ['implementing', 'debugging', 'refactoring', 'testing', 'reviewing'], sourceControl: 'github', documentTools: [] };
81
- logger.info('Non-interactive mode — using default profile (web, all intents, github).');
82
- } else {
83
- userProfile = await gatherUserProfile();
84
- }
85
-
86
68
  // ================================================================
87
69
  // PHASE 2: Project Discovery
88
70
  // ================================================================
@@ -180,10 +162,9 @@ export async function runInstall(options = {}) {
180
162
  detected = { ...fsDetected, ...projectInfo };
181
163
  }
182
164
 
183
- // Cache analysis and user profile for later phases and future update runs
165
+ // Cache analysis for later phases and future update runs
184
166
  try {
185
167
  writeAnalysisCache(targetDir, projectInfo, detected, existingContext);
186
- writeUserProfile(targetDir, userProfile);
187
168
  } catch (cacheErr) {
188
169
  logger.debug(`Analysis cache write failed: ${cacheErr.message}`);
189
170
  }
@@ -192,24 +173,6 @@ export async function runInstall(options = {}) {
192
173
  console.log();
193
174
  renderProjectCard(projectInfo);
194
175
 
195
- // Preset alias injection
196
- if (options.preset) {
197
- const alias = PRESET_ALIASES[options.preset];
198
- if (!alias) {
199
- logger.error(
200
- `Unknown preset ${chalk.bold(options.preset)}. ` +
201
- `Available presets: ${Object.keys(PRESET_ALIASES).map((p) => colors.success(p)).join(', ')}`
202
- );
203
- process.exit(1);
204
- }
205
- for (const fw of alias.frameworks) {
206
- if (!projectInfo.frameworks.includes(fw)) projectInfo.frameworks.push(fw);
207
- }
208
- for (const lang of alias.languages) {
209
- if (!projectInfo.languages.includes(lang)) projectInfo.languages.push(lang);
210
- }
211
- }
212
-
213
176
  // ================================================================
214
177
  // PHASE 3: Configuration
215
178
  // ================================================================
@@ -220,7 +183,6 @@ export async function runInstall(options = {}) {
220
183
  let apiResponse;
221
184
  try {
222
185
  apiResponse = await callGenerate(
223
- userProfile,
224
186
  {
225
187
  name: projectInfo.name,
226
188
  projectType: projectInfo.projectType,
@@ -241,7 +203,7 @@ export async function runInstall(options = {}) {
241
203
  detectedFiles: detected._rootFiles || [],
242
204
  databases: projectInfo.databases || detected.databases || [],
243
205
  },
244
- { preset: options.preset, projectPath: targetDir },
206
+ { projectPath: targetDir },
245
207
  );
246
208
  spinner3.succeed('Server returned configuration.');
247
209
  } catch (err) {
@@ -255,27 +217,17 @@ export async function runInstall(options = {}) {
255
217
 
256
218
  const { summary, mcpConfigs } = apiResponse;
257
219
 
258
- // Display scoring summary
220
+ // Display component summary
259
221
  console.log();
260
222
  renderComponentBreakdown(summary);
261
223
 
262
- // MCP selection + credential setup + security + confirmation
263
- let selectedMcps;
264
- let mcpKeys = {};
265
- let securityConfig;
266
-
267
- if (options.yes) {
268
- selectedMcps = mcpConfigs.filter(
269
- (m) => m.tier === 'core' || m.tier === 'role' || m.tier === 'stack' || m.tier === 'auto' || m.recommended
270
- );
271
- securityConfig = { addSecurityGitignore: true };
272
- } else {
273
- const mcpResult = await gatherMcpConfig(mcpConfigs);
274
- selectedMcps = mcpResult.selectedMcps;
275
- mcpKeys = mcpResult.mcpKeys;
276
-
277
- securityConfig = gatherSecurityConfig(detected);
224
+ // All MCPs are auto-installed; no interactive selection
225
+ const selectedMcps = mcpConfigs || [];
226
+ const mcpKeys = {};
227
+ const securityConfig = { addSecurityGitignore: true };
278
228
 
229
+ // Confirmation gate (skipped in non-interactive mode)
230
+ if (!options.yes) {
279
231
  const finalSummary = { ...summary, mcps: selectedMcps.map((m) => ({ id: m.id, tier: m.tier })) };
280
232
  const proceed = await confirmInstallation(finalSummary);
281
233
  if (!proceed) {
@@ -292,12 +244,10 @@ export async function runInstall(options = {}) {
292
244
 
293
245
  let results;
294
246
  let filesToWrite;
295
- let selectedCandidateIds = null;
296
247
 
297
248
  try {
298
249
  const installCtx = await runInstallTasks({
299
250
  apiResponse,
300
- selectedCandidateIds: null,
301
251
  targetDir,
302
252
  selectedMcps,
303
253
  mcpKeys,
@@ -305,29 +255,14 @@ export async function runInstall(options = {}) {
305
255
  detected,
306
256
  buildFileList,
307
257
  writeApiFiles,
308
- scoreWithClaude,
309
258
  });
310
259
 
311
260
  results = installCtx.results;
312
261
  filesToWrite = installCtx.filesToWrite;
313
- selectedCandidateIds = installCtx.selectedCandidateIds ?? null;
314
262
  } catch (installErr) {
315
263
  // Fallback to sequential if task runner fails
316
- const candidateCount = apiResponse.candidates?.items?.length || 0;
317
-
318
- if (apiResponse.prompts?.scoring && candidateCount > 0) {
319
- const spinner4 = ora(`Evaluating ${candidateCount} optional candidates...`).start();
320
- const scoreResult = await scoreWithClaude(apiResponse.prompts.scoring, targetDir);
321
- if (scoreResult.selected) {
322
- selectedCandidateIds = scoreResult.selected;
323
- spinner4.succeed(`Selected ${selectedCandidateIds.length}/${candidateCount} candidates.`);
324
- } else {
325
- spinner4.warn(`Scoring unavailable — including all ${candidateCount} candidates.`);
326
- }
327
- }
328
-
329
264
  const spinnerWrite = ora('Writing configuration files...').start();
330
- filesToWrite = buildFileList(apiResponse, selectedCandidateIds);
265
+ filesToWrite = buildFileList(apiResponse);
331
266
  results = await writeApiFiles(filesToWrite, targetDir, {
332
267
  force: true,
333
268
  selectedMcpIds: selectedMcps.map((m) => m.id),
@@ -417,7 +352,7 @@ export async function runInstall(options = {}) {
417
352
  }
418
353
 
419
354
  // ── Success ──────────────────────────────────────────────────────
420
- const totalItems = countTotalItems(summary, selectedCandidateIds);
355
+ const totalItems = countTotalItems(summary);
421
356
  const mcpsNeedingKeys = mcpResults.filter((r) => r.status === 'needs-key');
422
357
 
423
358
  renderSuccessCard({
@@ -457,9 +392,9 @@ export async function runInstall(options = {}) {
457
392
  }
458
393
 
459
394
  /**
460
- * Count total installed items from guaranteed + selected candidates.
395
+ * Count total installed items from the summary.
461
396
  */
462
- function countTotalItems(summary, selectedCandidateIds) {
397
+ function countTotalItems(summary) {
463
398
  const countBucket = (bucket) => {
464
399
  if (!bucket) return 0;
465
400
  return Object.values(bucket).reduce(
@@ -468,13 +403,5 @@ function countTotalItems(summary, selectedCandidateIds) {
468
403
  );
469
404
  };
470
405
 
471
- let total = countBucket(summary.guaranteed);
472
-
473
- if (selectedCandidateIds === null) {
474
- total += countBucket(summary.candidates);
475
- } else {
476
- total += selectedCandidateIds.length;
477
- }
478
-
479
- return total;
406
+ return countBucket(summary.guaranteed) + countBucket(summary.candidates);
480
407
  }
@@ -3,7 +3,7 @@
3
3
  * calls /api/update to get delta components, and installs only what's new.
4
4
  *
5
5
  * Flow:
6
- * 1. Load stored user profile + analysis from .claude/.claude-craft/
6
+ * 1. Load stored analysis from .claude/.claude-craft/
7
7
  * 2. Re-run project analysis (detectProject + analyzeWithClaude)
8
8
  * 3. Extract installed relative paths from manifest
9
9
  * 4. Call /api/update — server returns only new/unlocked components
@@ -16,7 +16,6 @@ import ora from 'ora';
16
16
  import { detectProject } from '../utils/detect-project.js';
17
17
  import { analyzeWithClaude } from '../utils/claude-analyzer.js';
18
18
  import {
19
- readUserProfile,
20
19
  readPermanentAnalysis,
21
20
  readInstalledManifest,
22
21
  mergePermanentManifest,
@@ -135,18 +134,9 @@ export async function runUpdate(options = {}) {
135
134
  });
136
135
 
137
136
  // ── Guard: must have a previous install ───────────────────────────
138
- const storedProfile = readUserProfile(targetDir);
139
137
  const previousAnalysis = readPermanentAnalysis(targetDir);
140
138
  const installedManifest = readInstalledManifest(targetDir);
141
139
 
142
- if (!storedProfile) {
143
- logger.error(
144
- 'No stored user profile found. Re-run: ' + chalk.bold('ccraft install') +
145
- ' to rebuild the profile cache.',
146
- );
147
- process.exit(1);
148
- }
149
-
150
140
  if (!previousAnalysis) {
151
141
  logger.error(
152
142
  'No stored project analysis found. Re-run: ' + chalk.bold('ccraft install') +
@@ -231,7 +221,6 @@ export async function runUpdate(options = {}) {
231
221
  let updateResponse;
232
222
  try {
233
223
  updateResponse = await callUpdate(
234
- storedProfile,
235
224
  currentProjectInfo,
236
225
  previousAnalysis,
237
226
  installedRelativePaths,
package/src/constants.js CHANGED
@@ -20,6 +20,29 @@ export const INTENTS = [
20
20
  { name: 'Code review', value: 'reviewing', description: 'Review PRs, suggest improvements, catch issues' },
21
21
  ];
22
22
 
23
+ // ── Personas ────────────────────────────────────────────────────────
24
+
25
+ export const PERSONAS = [
26
+ {
27
+ name: 'I\'m building something new — describe what I want, Claude handles the rest',
28
+ value: 'vibe',
29
+ description: 'Streamlined setup with smart defaults',
30
+ },
31
+ {
32
+ name: 'I\'m a developer — full control over agents, skills, MCPs, and workflows',
33
+ value: 'developer',
34
+ description: 'All configuration options available',
35
+ },
36
+ ];
37
+
38
+ export const VIBE_DEFAULTS = {
39
+ // Intentionally narrowed to core intents for non-technical users.
40
+ // Full intent list is available in the developer path via gatherUserProfile().
41
+ intents: ['implementing', 'debugging'],
42
+ sourceControl: 'github',
43
+ documentTools: [],
44
+ };
45
+
23
46
  // ── Source control platforms ─────────────────────────────────────────
24
47
 
25
48
  export const SOURCE_CONTROLS = [
@@ -7,12 +7,29 @@ import {
7
7
  PROJECT_TYPES,
8
8
  SOURCE_CONTROLS,
9
9
  DOCUMENT_TOOLS,
10
+ PERSONAS,
10
11
  } from '../constants.js';
11
12
  import { validateApiKeyFormat } from '../utils/mcp-setup.js';
12
13
  import { themedSelect, themedCheckbox, themedConfirm, themedPassword, themedInput } from '../ui/prompts.js';
13
14
  import { renderSummaryCard, renderWarningCard } from '../ui/cards.js';
14
15
  import { colors } from '../ui/theme.js';
15
16
 
17
+ // ── Persona selection ───────────────────────────────────────────────────
18
+
19
+ export async function gatherPersona() {
20
+ console.log();
21
+ const persona = await themedSelect({
22
+ message: 'How do you want to use Claude Code?',
23
+ choices: PERSONAS.map((p) => ({
24
+ name: p.name,
25
+ value: p.value,
26
+ description: p.description,
27
+ })),
28
+ });
29
+
30
+ return persona;
31
+ }
32
+
16
33
  // ── Project path ────────────────────────────────────────────────────────
17
34
 
18
35
  export async function gatherProjectPath() {
package/src/ui/cards.js CHANGED
@@ -120,33 +120,55 @@ export function renderSummaryCard(summary) {
120
120
  /**
121
121
  * Render the final success card.
122
122
  */
123
- export function renderSuccessCard({ totalItems, mcpCount, mcpsNeedingKeys }) {
123
+ export function renderSuccessCard({ totalItems, mcpCount, mcpsNeedingKeys, persona }) {
124
+ const isVibe = persona === 'vibe';
124
125
  const lines = [];
125
- lines.push(chalk.bold.green(' Installation complete!'));
126
- lines.push('');
127
- lines.push(` ${chalk.dim('Settings installed'.padEnd(22))}${colors.success(String(totalItems))} ${chalk.dim('(guaranteed + selected)')}`);
128
- lines.push(` ${chalk.dim('MCP servers'.padEnd(22))}${colors.success(String(mcpCount))}`);
129
126
 
130
- if (mcpsNeedingKeys.length > 0) {
127
+ if (isVibe) {
128
+ lines.push(chalk.bold.green(' Claude is ready to build your app!'));
131
129
  lines.push('');
132
- lines.push(chalk.yellow(' MCP servers needing API keys:'));
133
- for (const r of mcpsNeedingKeys) {
134
- lines.push(chalk.yellow(` ${chalk.dim('•')} ${r.id}: set ${r.apiKey.keyName}`));
130
+ lines.push(` ${chalk.dim('Components installed'.padEnd(22))}${colors.success(String(totalItems))}`);
131
+ lines.push(` ${chalk.dim('MCP servers'.padEnd(22))}${colors.success(String(mcpCount))}`);
132
+
133
+ if (mcpsNeedingKeys.length > 0) {
134
+ lines.push('');
135
+ lines.push(chalk.yellow(' Some MCP servers need an API key:'));
136
+ for (const r of mcpsNeedingKeys) {
137
+ lines.push(chalk.yellow(` ${chalk.dim('•')} ${r.id}: set ${r.apiKey.keyName}`));
138
+ }
135
139
  }
136
- }
137
140
 
138
- lines.push('');
139
- lines.push(chalk.dim(' Next steps:'));
140
- lines.push(chalk.dim(` 1. Read ${chalk.underline('USER_GUIDE.md')} for a full feature overview`));
141
+ lines.push('');
142
+ lines.push(chalk.dim(' Next steps:'));
143
+ lines.push(chalk.dim(` 1. Start Claude Code and describe what you want to build`));
144
+ lines.push(chalk.dim(` 2. Claude will handle the rest!`));
145
+ } else {
146
+ lines.push(chalk.bold.green(' Installation complete!'));
147
+ lines.push('');
148
+ lines.push(` ${chalk.dim('Settings installed'.padEnd(22))}${colors.success(String(totalItems))} ${chalk.dim('(guaranteed + selected)')}`);
149
+ lines.push(` ${chalk.dim('MCP servers'.padEnd(22))}${colors.success(String(mcpCount))}`);
150
+
151
+ if (mcpsNeedingKeys.length > 0) {
152
+ lines.push('');
153
+ lines.push(chalk.yellow(' MCP servers needing API keys:'));
154
+ for (const r of mcpsNeedingKeys) {
155
+ lines.push(chalk.yellow(` ${chalk.dim('•')} ${r.id}: set ${r.apiKey.keyName}`));
156
+ }
157
+ }
158
+
159
+ lines.push('');
160
+ lines.push(chalk.dim(' Next steps:'));
161
+ lines.push(chalk.dim(` 1. Read ${chalk.underline('USER_GUIDE.md')} for a full feature overview`));
141
162
 
142
- let step = 2;
143
- if (mcpsNeedingKeys.length > 0) {
144
- lines.push(chalk.dim(` ${step}. Set missing API keys for MCP servers (see above)`));
163
+ let step = 2;
164
+ if (mcpsNeedingKeys.length > 0) {
165
+ lines.push(chalk.dim(` ${step}. Set missing API keys for MCP servers (see above)`));
166
+ step++;
167
+ }
168
+ lines.push(chalk.dim(` ${step}. Start Claude Code and try out some commands!`));
145
169
  step++;
170
+ lines.push(chalk.dim(` ${step}. Customize .claude/ to your needs`));
146
171
  }
147
- lines.push(chalk.dim(` ${step}. Start Claude Code and try out some commands!`));
148
- step++;
149
- lines.push(chalk.dim(` ${step}. Customize .claude/ to your needs`));
150
172
 
151
173
  const content = lines.join('\n');
152
174
 
package/src/ui/tasks.js CHANGED
@@ -104,7 +104,6 @@ export async function runAnalysisTasks(targetDir, { analyzeWithClaude, detectPro
104
104
  */
105
105
  export async function runInstallTasks({
106
106
  apiResponse,
107
- selectedCandidateIds,
108
107
  targetDir,
109
108
  selectedMcps,
110
109
  mcpKeys,
@@ -112,43 +111,24 @@ export async function runInstallTasks({
112
111
  detected,
113
112
  buildFileList,
114
113
  writeApiFiles,
115
- scoreWithClaude,
116
114
  }) {
117
115
  const ctx = {};
118
116
 
119
- const candidateCount = apiResponse.candidates?.items?.length || 0;
120
- const hasScoring = apiResponse.prompts?.scoring && candidateCount > 0;
121
-
122
- const taskList = [];
123
-
124
- if (hasScoring) {
125
- taskList.push({
126
- title: `Evaluating ${candidateCount} optional candidates with Claude`,
117
+ const taskList = [
118
+ {
119
+ title: 'Writing configuration files',
127
120
  task: async (ctx) => {
128
- const scoreResult = await scoreWithClaude(apiResponse.prompts.scoring, targetDir);
129
- if (scoreResult.selected) {
130
- ctx.selectedCandidateIds = scoreResult.selected;
131
- } else {
132
- ctx.selectedCandidateIds = null;
133
- }
121
+ ctx.filesToWrite = buildFileList(apiResponse);
122
+ ctx.results = await writeApiFiles(ctx.filesToWrite, targetDir, {
123
+ force: true,
124
+ selectedMcpIds: selectedMcps.map((m) => m.id),
125
+ mcpKeys,
126
+ securityConfig,
127
+ detected,
128
+ });
134
129
  },
135
- });
136
- }
137
-
138
- taskList.push({
139
- title: 'Writing configuration files',
140
- task: async (ctx) => {
141
- const ids = ctx.selectedCandidateIds ?? selectedCandidateIds;
142
- ctx.filesToWrite = buildFileList(apiResponse, ids);
143
- ctx.results = await writeApiFiles(ctx.filesToWrite, targetDir, {
144
- force: true,
145
- selectedMcpIds: selectedMcps.map((m) => m.id),
146
- mcpKeys,
147
- securityConfig,
148
- detected,
149
- });
150
130
  },
151
- });
131
+ ];
152
132
 
153
133
  const tasks = createTaskRunner(taskList);
154
134
  await tasks.run(ctx);
@@ -16,7 +16,7 @@ const CACHE_DIR = '.claude/.claude-craft-temp';
16
16
  const PERMANENT_DIR = '.claude/.claude-craft';
17
17
 
18
18
  /** Files promoted from temp → permanent after install. */
19
- const PERMANENT_FILES = ['project-analysis.json', 'project-context.md', 'installed-manifest.json', 'user-profile.json'];
19
+ const PERMANENT_FILES = ['project-analysis.json', 'project-context.md', 'installed-manifest.json'];
20
20
 
21
21
  /**
22
22
  * Resolve the temp cache directory path for a given target directory.
@@ -315,37 +315,7 @@ export function formatRichProjectContext(projectInfo, detected) {
315
315
  return lines.join('\n').trimEnd();
316
316
  }
317
317
 
318
- /**
319
- * Write user profile to the temp cache directory.
320
- * Called alongside writeAnalysisCache during install.
321
- *
322
- * @param {string} targetDir - Project root
323
- * @param {object} userProfile - { intents, sourceControl, documentTools }
324
- */
325
- export function writeUserProfile(targetDir, userProfile) {
326
- const dir = cachePath(targetDir);
327
- mkdirSync(dir, { recursive: true });
328
- writeFileSync(join(dir, 'user-profile.json'), JSON.stringify(userProfile, null, 2), 'utf8');
329
- logger.debug('User profile written to analysis cache.');
330
- }
331
-
332
- /**
333
- * Read the stored user profile from the permanent .claude/.claude-craft/ directory.
334
- * Returns null if not found.
335
- *
336
- * @param {string} targetDir - Project root
337
- * @returns {object|null}
338
- */
339
- export function readUserProfile(targetDir) {
340
- const filePath = join(permanentPath(targetDir), 'user-profile.json');
341
- try {
342
- if (!existsSync(filePath)) return null;
343
- return JSON.parse(readFileSync(filePath, 'utf8'));
344
- } catch (err) {
345
- logger.debug(`Failed to read user profile: ${err.message}`);
346
- return null;
347
- }
348
- }
318
+ // writeUserProfile and readUserProfile removed — user profile no longer used.
349
319
 
350
320
  /**
351
321
  * Read the permanent project analysis JSON.
@@ -45,12 +45,11 @@ export function saveConfig(config) {
45
45
  /**
46
46
  * Call POST /api/generate on the server.
47
47
  *
48
- * @param {object} profile - { intents, sourceControl, documentTools }
49
48
  * @param {object} analysis - Project analysis data
50
- * @param {object} [options] - { preset }
49
+ * @param {object} [options] - { projectPath }
51
50
  * @returns {Promise<{ files, summary, mcpConfigs, serverVersion }>}
52
51
  */
53
- export async function callGenerate(profile, analysis, options = {}) {
52
+ export async function callGenerate(analysis, options = {}) {
54
53
  const config = loadConfig();
55
54
  if (!config?.apiKey) {
56
55
  throw new ApiError(
@@ -75,7 +74,7 @@ export async function callGenerate(profile, analysis, options = {}) {
75
74
  'X-Claude-Craft-Version': VERSION,
76
75
  'X-Claude-Craft-Api-Version': '1',
77
76
  },
78
- body: JSON.stringify({ profile, analysis, options }),
77
+ body: JSON.stringify({ analysis, options }),
79
78
  signal: controller.signal,
80
79
  });
81
80
 
@@ -131,13 +130,12 @@ export async function callGenerate(profile, analysis, options = {}) {
131
130
  * Call POST /api/update on the server.
132
131
  * Returns delta components (new files not already installed) + change summary.
133
132
  *
134
- * @param {object} profile - { intents, sourceControl, documentTools }
135
133
  * @param {object} currentAnalysis - Freshly computed project analysis
136
134
  * @param {object} previousAnalysis - Previously stored project analysis
137
135
  * @param {string[]} installedRelativePaths - Relative file paths already on disk
138
- * @returns {Promise<{ changes, guaranteed, candidates, prompts, mcpConfigs, summary, serverVersion }>}
136
+ * @returns {Promise<{ changes, guaranteed, mcpConfigs, summary }>}
139
137
  */
140
- export async function callUpdate(profile, currentAnalysis, previousAnalysis, installedRelativePaths) {
138
+ export async function callUpdate(currentAnalysis, previousAnalysis, installedRelativePaths) {
141
139
  const config = loadConfig();
142
140
  if (!config?.apiKey) {
143
141
  throw new ApiError(
@@ -162,7 +160,7 @@ export async function callUpdate(profile, currentAnalysis, previousAnalysis, ins
162
160
  'X-Claude-Craft-Version': VERSION,
163
161
  'X-Claude-Craft-Api-Version': '1',
164
162
  },
165
- body: JSON.stringify({ profile, currentAnalysis, previousAnalysis, installedRelativePaths }),
163
+ body: JSON.stringify({ currentAnalysis, previousAnalysis, installedPaths: installedRelativePaths }),
166
164
  signal: controller.signal,
167
165
  });
168
166
 
@@ -132,19 +132,15 @@ export async function writeApiFiles(files, targetDir, opts = {}) {
132
132
  /**
133
133
  * Build a flat file list from a V3 API response.
134
134
  *
135
- * @param {object} apiResponse - V3 response with guaranteed.files and candidates.items
136
- * @param {string[]|null} selectedCandidateIds - IDs selected by Claude, or null for all
135
+ * @param {object} apiResponse - Response with guaranteed.files and candidates.items
137
136
  * @returns {Array<{relativePath: string, content: string, type: string}>}
138
137
  */
139
- export function buildFileList(apiResponse, selectedCandidateIds) {
138
+ export function buildFileList(apiResponse) {
140
139
  const files = [...(apiResponse.guaranteed?.files || [])];
141
140
 
142
141
  const candidates = apiResponse.candidates?.items || [];
143
- const selectedSet = selectedCandidateIds ? new Set(selectedCandidateIds) : null;
144
142
 
145
143
  for (const candidate of candidates) {
146
- // Skip unselected candidates (null = include all)
147
- if (selectedSet && !selectedSet.has(candidate.id)) continue;
148
144
 
149
145
  if (Array.isArray(candidate.files) && candidate.files.length > 0) {
150
146
  // Multi-file candidates (skills with references/)
@@ -1,101 +0,0 @@
1
- /**
2
- * Claude-powered scoring for optional candidates.
3
- *
4
- * Uses the local Claude CLI to evaluate which optional candidates
5
- * should be included in the configuration.
6
- */
7
- import { isClaudeAvailable, runClaude } from './run-claude.js';
8
- import { extractJsonObject } from './json-extract.js';
9
- import * as logger from './logger.js';
10
-
11
- /**
12
- * Score optional candidates using Claude.
13
- *
14
- * @param {string} scoringPrompt - Prompt generated by the server
15
- * @param {string} targetDir - Project root (cwd for Claude)
16
- * @returns {Promise<{ selected: string[], reasoning: string }>}
17
- * Falls back to selecting ALL candidates if Claude is unavailable.
18
- */
19
- export async function scoreWithClaude(scoringPrompt, targetDir) {
20
- if (!isClaudeAvailable()) {
21
- logger.warn('Claude CLI not available — including all optional candidates.');
22
- return { selected: null, reasoning: 'Claude unavailable — inclusive fallback' };
23
- }
24
-
25
- try {
26
- const output = await runClaude([
27
- '-p',
28
- '--max-turns', '3',
29
- ], { cwd: targetDir, stdinInput: scoringPrompt });
30
-
31
- // Parse the scoring result directly (no JSON wrapper to extract)
32
- const result = parseScoreResult(output);
33
- if (result) {
34
- return result;
35
- }
36
-
37
- logger.warn('Claude did not return valid scoring JSON — including all candidates.');
38
- logger.debug(`Raw output (first 500 chars): ${(output || '').slice(0, 500)}`);
39
- return { selected: null, reasoning: 'Parse failure — inclusive fallback' };
40
- } catch (err) {
41
- if (err.killed) {
42
- logger.warn('Claude scoring timed out — including all candidates.');
43
- } else {
44
- logger.warn('Claude scoring failed — including all candidates.');
45
- }
46
- return { selected: null, reasoning: 'Error — inclusive fallback' };
47
- }
48
- }
49
-
50
- /**
51
- * Parse Claude's scoring response.
52
- * Handles both raw JSON and JSON embedded in markdown fences.
53
- */
54
- function parseScoreResult(text) {
55
- if (!text) return null;
56
-
57
- const tryParse = (obj) => {
58
- // V3 format: { selections: [{ id, category, include, reason }] }
59
- if (Array.isArray(obj.selections)) {
60
- const selected = obj.selections.filter((s) => s.include).map((s) => s.id);
61
- const reasoning = obj.selections
62
- .filter((s) => s.reason)
63
- .map((s) => `${s.id}: ${s.include ? 'included' : 'excluded'} — ${s.reason}`)
64
- .join('; ');
65
- return { selected, reasoning };
66
- }
67
- // Fallback: { selected: ["id1", "id2"], reasoning: "..." }
68
- if (Array.isArray(obj.selected)) {
69
- return { selected: obj.selected, reasoning: obj.reasoning || '' };
70
- }
71
- return null;
72
- };
73
-
74
- // Try direct JSON parse
75
- try {
76
- const result = tryParse(JSON.parse(text.trim()));
77
- if (result) return result;
78
- } catch {
79
- // Not direct JSON
80
- }
81
-
82
- // Try extracting JSON from markdown code fence
83
- const fenceMatch = text.match(/```(?:json)?\s*\n([\s\S]*?)```/);
84
- if (fenceMatch) {
85
- try {
86
- const result = tryParse(JSON.parse(fenceMatch[1].trim()));
87
- if (result) return result;
88
- } catch {
89
- // Invalid JSON in fence
90
- }
91
- }
92
-
93
- // Use brace-balanced extraction
94
- const obj = extractJsonObject(text, 'selections') || extractJsonObject(text, 'selected');
95
- if (obj) {
96
- const result = tryParse(obj);
97
- if (result) return result;
98
- }
99
-
100
- return null;
101
- }