ccraft 1.0.11 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccraft",
3
- "version": "1.0.11",
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, gatherPersona, gatherUserProfile, 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,13 +18,12 @@ 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,
26
24
  cleanupAnalysisCache,
27
25
  } from '../utils/analysis-cache.js';
28
- import { VERSION, VIBE_DEFAULTS } from '../constants.js';
26
+ import { VERSION } from '../constants.js';
29
27
  import * as logger from '../utils/logger.js';
30
28
 
31
29
  // UI modules
@@ -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,35 +162,6 @@ export async function runCreate(options = {}) {
170
162
  : `Created ${chalk.bold(name)}/ (git init skipped — git not available).`);
171
163
  }
172
164
 
173
- // ── Persona selection ────────────────────────────────────────────
174
- let persona;
175
- if (options.yes) {
176
- persona = 'developer'; // non-interactive: always developer defaults
177
- } else if (options.pro) {
178
- persona = 'developer'; // interactive developer mode: skip persona prompt
179
- } else {
180
- persona = await gatherPersona();
181
- }
182
-
183
- const isVibe = persona === 'vibe';
184
-
185
- // Build user profile
186
- let userProfile;
187
- if (options.yes) {
188
- userProfile = {
189
- intents: ['implementing', 'debugging', 'refactoring', 'testing', 'reviewing'],
190
- sourceControl: 'github',
191
- documentTools: [],
192
- };
193
- logger.info('Non-interactive mode — using default profile.');
194
- } else if (isVibe) {
195
- userProfile = { ...VIBE_DEFAULTS };
196
- logger.info('Vibe mode — using streamlined defaults.');
197
- } else {
198
- userProfile = await gatherUserProfile();
199
- }
200
- userProfile.persona = persona;
201
-
202
165
  // ================================================================
203
166
  // STEP 3: Configuration & Install
204
167
  // ================================================================
@@ -259,7 +222,6 @@ export async function runCreate(options = {}) {
259
222
  let apiResponse;
260
223
  try {
261
224
  apiResponse = await callGenerate(
262
- userProfile,
263
225
  {
264
226
  ...syntheticProjectInfo,
265
227
  detectedFiles: [],
@@ -292,33 +254,14 @@ export async function runCreate(options = {}) {
292
254
  console.log();
293
255
  renderComponentBreakdown(summary);
294
256
 
295
- // MCP selection — auto-select core/role/stack for new projects (no stack to score against)
296
- let selectedMcps;
297
- let mcpKeys = {};
257
+ // Auto-install all MCPs returned by the server
258
+ const selectedMcps = mcpConfigs || [];
259
+ const mcpKeys = {};
298
260
  const securityConfig = { addSecurityGitignore: true };
299
261
 
300
- if (options.yes || isVibe) {
301
- selectedMcps = mcpConfigs.filter(
302
- (m) => m.tier === 'core' || m.tier === 'role' || m.tier === 'stack' || m.tier === 'auto' || m.recommended,
303
- );
304
- } else {
305
- const mcpResult = await gatherMcpConfig(mcpConfigs);
306
- selectedMcps = mcpResult.selectedMcps;
307
- mcpKeys = mcpResult.mcpKeys;
308
-
309
- const finalSummary = { ...summary, mcps: selectedMcps.map((m) => ({ id: m.id, tier: m.tier })) };
310
- const proceed = await confirmInstallation(finalSummary);
311
- if (!proceed) {
312
- console.log();
313
- logger.info('Cancelled.');
314
- process.exit(0);
315
- }
316
- }
317
-
318
- // Cache analysis and profile
262
+ // Cache analysis
319
263
  try {
320
264
  writeAnalysisCache(targetDir, syntheticProjectInfo, syntheticDetected, null);
321
- writeUserProfile(targetDir, userProfile);
322
265
  } catch (cacheErr) {
323
266
  logger.debug(`Analysis cache write failed: ${cacheErr.message}`);
324
267
  }
@@ -326,12 +269,10 @@ export async function runCreate(options = {}) {
326
269
  // Write files
327
270
  let results;
328
271
  let filesToWrite;
329
- let selectedCandidateIds = null;
330
272
 
331
273
  try {
332
274
  const installCtx = await runInstallTasks({
333
275
  apiResponse,
334
- selectedCandidateIds: null,
335
276
  targetDir,
336
277
  selectedMcps,
337
278
  mcpKeys,
@@ -339,16 +280,14 @@ export async function runCreate(options = {}) {
339
280
  detected: syntheticDetected,
340
281
  buildFileList,
341
282
  writeApiFiles,
342
- scoreWithClaude,
343
283
  });
344
284
 
345
285
  results = installCtx.results;
346
286
  filesToWrite = installCtx.filesToWrite;
347
- selectedCandidateIds = installCtx.selectedCandidateIds ?? null;
348
287
  } catch {
349
288
  // Fallback to direct write
350
289
  const spinnerWrite = ora('Writing configuration files...').start();
351
- filesToWrite = buildFileList(apiResponse, null);
290
+ filesToWrite = buildFileList(apiResponse);
352
291
  results = await writeApiFiles(filesToWrite, targetDir, {
353
292
  force: true,
354
293
  selectedMcpIds: selectedMcps.map((m) => m.id),
@@ -458,7 +397,6 @@ export async function runCreate(options = {}) {
458
397
  // Overwrite cache with real data
459
398
  const detected = { ...fsDetected, ...projectInfo };
460
399
  writeAnalysisCache(targetDir, projectInfo, detected, null);
461
- writeUserProfile(targetDir, userProfile);
462
400
  } catch (err) {
463
401
  logger.debug(`Post-bootstrap analysis failed: ${err.message}`);
464
402
  }
@@ -493,26 +431,21 @@ export async function runCreate(options = {}) {
493
431
  }
494
432
 
495
433
  // ── Success ──────────────────────────────────────────────────────
496
- const totalItems = countTotalItems(summary, selectedCandidateIds);
434
+ const totalItems = countTotalItems(summary);
497
435
  const mcpsNeedingKeys = mcpResults.filter((r) => r.status === 'needs-key');
498
436
 
499
437
  renderSuccessCard({
500
438
  totalItems,
501
439
  mcpCount: selectedMcps.length,
502
440
  mcpsNeedingKeys,
503
- persona,
504
441
  });
505
442
 
506
443
  console.log();
507
444
  if (bootstrapSucceeded) {
508
- logger.success(isVibe
509
- ? `Project ${chalk.bold(name)} is ready! Start Claude and describe what you want.`
510
- : `Project ${chalk.bold(name)} created and bootstrapped!`);
445
+ logger.success(`Project ${chalk.bold(name)} created and bootstrapped!`);
511
446
  console.log(chalk.dim(` cd ${name} && claude`));
512
447
  } else {
513
- logger.success(isVibe
514
- ? `Project ${chalk.bold(name)} is ready! Run Claude to finish setup.`
515
- : `Project ${chalk.bold(name)} created with Claude configuration.`);
448
+ logger.success(`Project ${chalk.bold(name)} created with Claude configuration.`);
516
449
  console.log(chalk.dim(` cd ${name} && claude -p "/bootstrap:auto ${description}"`));
517
450
  }
518
451
  console.log();
@@ -11,27 +11,21 @@ import {
11
11
  } from '../utils/existing-setup.js';
12
12
  import {
13
13
  gatherProjectPath,
14
- gatherPersona,
15
- gatherUserProfile,
16
- gatherMcpConfig,
17
- gatherSecurityConfig,
18
14
  confirmInstallation,
19
15
  } from '../prompts/gather.js';
20
16
  import { callGenerate, ApiError } from '../utils/api-client.js';
21
17
  import { writeApiFiles, buildFileList } from '../utils/api-file-writer.js';
22
18
  import { setupMcps } from '../utils/mcp-setup.js';
23
- import { scoreWithClaude } from '../utils/claude-scorer.js';
24
19
  import { optimizeSettings } from '../utils/claude-optimizer.js';
25
20
  import { runPreflight } from '../utils/preflight.js';
26
21
  import {
27
22
  writeAnalysisCache,
28
- writeUserProfile,
29
23
  updateManifest,
30
24
  readAnalysisCache,
31
25
  promoteCache,
32
26
  cleanupAnalysisCache,
33
27
  } from '../utils/analysis-cache.js';
34
- import { PRESET_ALIASES, VERSION, VIBE_DEFAULTS } from '../constants.js';
28
+ import { VERSION } from '../constants.js';
35
29
  import * as logger from '../utils/logger.js';
36
30
 
37
31
  // UI modules
@@ -40,16 +34,14 @@ import { renderPhaseHeader } from '../ui/phase-header.js';
40
34
  import { renderProjectCard, renderSuccessCard } from '../ui/cards.js';
41
35
  import { renderComponentBreakdown, renderMcpStatus, renderFileResults } from '../ui/tables.js';
42
36
  import { runExistingSetupTasks, runAnalysisTasks, runInstallTasks, runVerifyTasks, runFinalizeTasks } from '../ui/tasks.js';
43
- import { colors } from '../ui/theme.js';
44
- import { dotPad } from '../ui/format.js';
45
37
 
46
38
  /**
47
39
  * Main install command — 5-phase orchestrator.
48
40
  *
49
- * Phase 1: Welcome & Setup (env check + user profile)
41
+ * Phase 1: Preflight (env check + project path)
50
42
  * Phase 2: Project Discovery (Claude analysis + filesystem scan)
51
- * Phase 3: Configuration (server API + MCP selection + security + confirmation)
52
- * Phase 4: Installation (Claude scoring + file writing + MCP verification)
43
+ * Phase 3: Generate & Install (server API + confirmation + file writing)
44
+ * Phase 4: MCP Verification
53
45
  * Phase 5: Finalization (settings optimization + CLAUDE.md rewrite + success)
54
46
  */
55
47
  export async function runInstall(options = {}) {
@@ -73,33 +65,6 @@ export async function runInstall(options = {}) {
73
65
  targetDir = resolve(options.dir || process.cwd());
74
66
  }
75
67
 
76
- // ── Persona selection ────────────────────────────────────────────
77
- renderPhaseHeader(1);
78
-
79
- let persona;
80
- if (options.yes) {
81
- persona = 'developer'; // non-interactive: always developer defaults
82
- } else if (options.pro) {
83
- persona = 'developer'; // interactive developer mode: skip persona prompt
84
- } else {
85
- persona = await gatherPersona();
86
- }
87
-
88
- const isVibe = persona === 'vibe';
89
-
90
- // ── User profile ───────────────────────────────────────────────
91
- let userProfile;
92
- if (options.yes) {
93
- userProfile = { intents: ['implementing', 'debugging', 'refactoring', 'testing', 'reviewing'], sourceControl: 'github', documentTools: [] };
94
- logger.info('Non-interactive mode — using default profile (web, all intents, github).');
95
- } else if (isVibe) {
96
- userProfile = { ...VIBE_DEFAULTS };
97
- logger.info('Vibe mode — using streamlined defaults.');
98
- } else {
99
- userProfile = await gatherUserProfile();
100
- }
101
- userProfile.persona = persona;
102
-
103
68
  // ================================================================
104
69
  // PHASE 2: Project Discovery
105
70
  // ================================================================
@@ -197,10 +162,9 @@ export async function runInstall(options = {}) {
197
162
  detected = { ...fsDetected, ...projectInfo };
198
163
  }
199
164
 
200
- // Cache analysis and user profile for later phases and future update runs
165
+ // Cache analysis for later phases and future update runs
201
166
  try {
202
167
  writeAnalysisCache(targetDir, projectInfo, detected, existingContext);
203
- writeUserProfile(targetDir, userProfile);
204
168
  } catch (cacheErr) {
205
169
  logger.debug(`Analysis cache write failed: ${cacheErr.message}`);
206
170
  }
@@ -209,24 +173,6 @@ export async function runInstall(options = {}) {
209
173
  console.log();
210
174
  renderProjectCard(projectInfo);
211
175
 
212
- // Preset alias injection
213
- if (options.preset) {
214
- const alias = PRESET_ALIASES[options.preset];
215
- if (!alias) {
216
- logger.error(
217
- `Unknown preset ${chalk.bold(options.preset)}. ` +
218
- `Available presets: ${Object.keys(PRESET_ALIASES).map((p) => colors.success(p)).join(', ')}`
219
- );
220
- process.exit(1);
221
- }
222
- for (const fw of alias.frameworks) {
223
- if (!projectInfo.frameworks.includes(fw)) projectInfo.frameworks.push(fw);
224
- }
225
- for (const lang of alias.languages) {
226
- if (!projectInfo.languages.includes(lang)) projectInfo.languages.push(lang);
227
- }
228
- }
229
-
230
176
  // ================================================================
231
177
  // PHASE 3: Configuration
232
178
  // ================================================================
@@ -237,7 +183,6 @@ export async function runInstall(options = {}) {
237
183
  let apiResponse;
238
184
  try {
239
185
  apiResponse = await callGenerate(
240
- userProfile,
241
186
  {
242
187
  name: projectInfo.name,
243
188
  projectType: projectInfo.projectType,
@@ -258,7 +203,7 @@ export async function runInstall(options = {}) {
258
203
  detectedFiles: detected._rootFiles || [],
259
204
  databases: projectInfo.databases || detected.databases || [],
260
205
  },
261
- { preset: options.preset, projectPath: targetDir },
206
+ { projectPath: targetDir },
262
207
  );
263
208
  spinner3.succeed('Server returned configuration.');
264
209
  } catch (err) {
@@ -272,27 +217,17 @@ export async function runInstall(options = {}) {
272
217
 
273
218
  const { summary, mcpConfigs } = apiResponse;
274
219
 
275
- // Display scoring summary
220
+ // Display component summary
276
221
  console.log();
277
222
  renderComponentBreakdown(summary);
278
223
 
279
- // MCP selection + credential setup + security + confirmation
280
- let selectedMcps;
281
- let mcpKeys = {};
282
- let securityConfig;
283
-
284
- if (options.yes || isVibe) {
285
- selectedMcps = mcpConfigs.filter(
286
- (m) => m.tier === 'core' || m.tier === 'role' || m.tier === 'stack' || m.tier === 'auto' || m.recommended
287
- );
288
- securityConfig = { addSecurityGitignore: true };
289
- } else {
290
- const mcpResult = await gatherMcpConfig(mcpConfigs);
291
- selectedMcps = mcpResult.selectedMcps;
292
- mcpKeys = mcpResult.mcpKeys;
293
-
294
- securityConfig = gatherSecurityConfig(detected);
224
+ // All MCPs are auto-installed; no interactive selection
225
+ const selectedMcps = mcpConfigs || [];
226
+ const mcpKeys = {};
227
+ const securityConfig = { addSecurityGitignore: true };
295
228
 
229
+ // Confirmation gate (skipped in non-interactive mode)
230
+ if (!options.yes) {
296
231
  const finalSummary = { ...summary, mcps: selectedMcps.map((m) => ({ id: m.id, tier: m.tier })) };
297
232
  const proceed = await confirmInstallation(finalSummary);
298
233
  if (!proceed) {
@@ -309,12 +244,10 @@ export async function runInstall(options = {}) {
309
244
 
310
245
  let results;
311
246
  let filesToWrite;
312
- let selectedCandidateIds = null;
313
247
 
314
248
  try {
315
249
  const installCtx = await runInstallTasks({
316
250
  apiResponse,
317
- selectedCandidateIds: null,
318
251
  targetDir,
319
252
  selectedMcps,
320
253
  mcpKeys,
@@ -322,29 +255,14 @@ export async function runInstall(options = {}) {
322
255
  detected,
323
256
  buildFileList,
324
257
  writeApiFiles,
325
- scoreWithClaude,
326
258
  });
327
259
 
328
260
  results = installCtx.results;
329
261
  filesToWrite = installCtx.filesToWrite;
330
- selectedCandidateIds = installCtx.selectedCandidateIds ?? null;
331
262
  } catch (installErr) {
332
263
  // Fallback to sequential if task runner fails
333
- const candidateCount = apiResponse.candidates?.items?.length || 0;
334
-
335
- if (apiResponse.prompts?.scoring && candidateCount > 0) {
336
- const spinner4 = ora(`Evaluating ${candidateCount} optional candidates...`).start();
337
- const scoreResult = await scoreWithClaude(apiResponse.prompts.scoring, targetDir);
338
- if (scoreResult.selected) {
339
- selectedCandidateIds = scoreResult.selected;
340
- spinner4.succeed(`Selected ${selectedCandidateIds.length}/${candidateCount} candidates.`);
341
- } else {
342
- spinner4.warn(`Scoring unavailable — including all ${candidateCount} candidates.`);
343
- }
344
- }
345
-
346
264
  const spinnerWrite = ora('Writing configuration files...').start();
347
- filesToWrite = buildFileList(apiResponse, selectedCandidateIds);
265
+ filesToWrite = buildFileList(apiResponse);
348
266
  results = await writeApiFiles(filesToWrite, targetDir, {
349
267
  force: true,
350
268
  selectedMcpIds: selectedMcps.map((m) => m.id),
@@ -434,18 +352,17 @@ export async function runInstall(options = {}) {
434
352
  }
435
353
 
436
354
  // ── Success ──────────────────────────────────────────────────────
437
- const totalItems = countTotalItems(summary, selectedCandidateIds);
355
+ const totalItems = countTotalItems(summary);
438
356
  const mcpsNeedingKeys = mcpResults.filter((r) => r.status === 'needs-key');
439
357
 
440
358
  renderSuccessCard({
441
359
  totalItems,
442
360
  mcpCount: selectedMcps.length,
443
361
  mcpsNeedingKeys,
444
- persona,
445
362
  });
446
363
 
447
364
  console.log();
448
- logger.success(isVibe ? 'Done! Start Claude Code and describe what you want to build.' : 'Done! Claude Code is ready.');
365
+ logger.success('Done! Claude Code is ready.');
449
366
  console.log();
450
367
  } catch (err) {
451
368
  if (
@@ -475,9 +392,9 @@ export async function runInstall(options = {}) {
475
392
  }
476
393
 
477
394
  /**
478
- * Count total installed items from guaranteed + selected candidates.
395
+ * Count total installed items from the summary.
479
396
  */
480
- function countTotalItems(summary, selectedCandidateIds) {
397
+ function countTotalItems(summary) {
481
398
  const countBucket = (bucket) => {
482
399
  if (!bucket) return 0;
483
400
  return Object.values(bucket).reduce(
@@ -486,13 +403,5 @@ function countTotalItems(summary, selectedCandidateIds) {
486
403
  );
487
404
  };
488
405
 
489
- let total = countBucket(summary.guaranteed);
490
-
491
- if (selectedCandidateIds === null) {
492
- total += countBucket(summary.candidates);
493
- } else {
494
- total += selectedCandidateIds.length;
495
- }
496
-
497
- return total;
406
+ return countBucket(summary.guaranteed) + countBucket(summary.candidates);
498
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/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
- }