claudex-setup 1.6.0 → 1.8.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.
@@ -0,0 +1,250 @@
1
+ const fs = require('fs');
2
+ const os = require('os');
3
+ const path = require('path');
4
+
5
+ const { version } = require('../package.json');
6
+ const { audit } = require('./audit');
7
+ const { setup } = require('./setup');
8
+ const { analyzeProject } = require('./analyze');
9
+ const { getGovernanceSummary } = require('./governance');
10
+
11
+ function copyProject(sourceDir, targetDir) {
12
+ fs.mkdirSync(targetDir, { recursive: true });
13
+ const entries = fs.readdirSync(sourceDir, { withFileTypes: true });
14
+ for (const entry of entries) {
15
+ if (entry.name === '.git' || entry.name === 'node_modules' || entry.name === '__pycache__') {
16
+ continue;
17
+ }
18
+ const from = path.join(sourceDir, entry.name);
19
+ const to = path.join(targetDir, entry.name);
20
+ if (entry.isDirectory()) {
21
+ copyProject(from, to);
22
+ } else if (entry.isFile()) {
23
+ fs.copyFileSync(from, to);
24
+ }
25
+ }
26
+ }
27
+
28
+ function summarizeAudit(result) {
29
+ return {
30
+ score: result.score,
31
+ organicScore: result.organicScore,
32
+ passed: result.passed,
33
+ failed: result.failed,
34
+ checkCount: result.checkCount,
35
+ quickWins: result.quickWins,
36
+ };
37
+ }
38
+
39
+ function buildWorkflowEvidence(before, after, analysisReport, governanceSummary) {
40
+ const tasks = [
41
+ {
42
+ key: 'discover-without-writes',
43
+ label: 'Discover next actions without writing files',
44
+ passed: before.checkCount > 0 && Array.isArray(before.quickWins),
45
+ evidence: `Baseline audit returned ${before.checkCount} applicable checks and ${before.quickWins.length} quick wins.`,
46
+ },
47
+ {
48
+ key: 'starter-safe-improvement',
49
+ label: 'Apply starter-safe improvements in isolation',
50
+ passed: after.score >= before.score && after.failed <= before.failed,
51
+ evidence: `Score moved ${before.score} -> ${after.score}; failed checks moved ${before.failed} -> ${after.failed}.`,
52
+ },
53
+ {
54
+ key: 'governed-rollout-surface',
55
+ label: 'Expose governed rollout controls',
56
+ passed: governanceSummary.permissionProfiles.length >= 3 && governanceSummary.hookRegistry.length >= 1,
57
+ evidence: `${governanceSummary.permissionProfiles.length} profiles and ${governanceSummary.hookRegistry.length} governed hooks available.`,
58
+ },
59
+ {
60
+ key: 'domain-pack-guidance',
61
+ label: 'Recommend a domain pack for the repo',
62
+ passed: analysisReport.recommendedDomainPacks.length > 0,
63
+ evidence: analysisReport.recommendedDomainPacks.map(pack => pack.label).join(', ') || 'No domain pack recommendation generated.',
64
+ },
65
+ {
66
+ key: 'mcp-pack-guidance',
67
+ label: 'Recommend MCP packs when appropriate',
68
+ passed: analysisReport.recommendedMcpPacks.length > 0,
69
+ evidence: analysisReport.recommendedMcpPacks.map(pack => pack.label).join(', ') || 'No MCP pack recommendation generated.',
70
+ },
71
+ ];
72
+
73
+ const passed = tasks.filter(task => task.passed).length;
74
+ const total = tasks.length;
75
+ return {
76
+ taskPack: 'maintainer-core',
77
+ tasks,
78
+ summary: {
79
+ passed,
80
+ total,
81
+ coverageScore: total > 0 ? Math.round((passed / total) * 100) : 0,
82
+ },
83
+ };
84
+ }
85
+
86
+ function buildExecutiveSummary(before, after, workflowEvidence) {
87
+ const scoreDelta = after.score - before.score;
88
+ const organicDelta = after.organicScore - before.organicScore;
89
+ const workflowCoverage = workflowEvidence.summary.coverageScore;
90
+ let headline = 'Benchmark did not improve the score in this run.';
91
+
92
+ if (scoreDelta > 0) {
93
+ headline = `Benchmark improved readiness by ${scoreDelta} points without touching the original repo.`;
94
+ } else if (before.score >= 85 && after.score >= before.score && workflowCoverage >= 80) {
95
+ headline = 'Benchmark confirmed the repo already meets the starter-safe baseline without regression.';
96
+ }
97
+
98
+ return {
99
+ headline,
100
+ scoreDelta,
101
+ organicDelta,
102
+ decisionGuidance: scoreDelta >= 20
103
+ ? 'Strong pilot candidate'
104
+ : scoreDelta >= 10
105
+ ? 'Promising but needs manual review'
106
+ : (before.score >= 85 && workflowCoverage >= 80
107
+ ? 'Use suggest-only mode, domain packs, or task-level benchmarks next'
108
+ : 'Use suggest-only mode before rollout'),
109
+ };
110
+ }
111
+
112
+ function buildCaseStudy(before, after, applyResult) {
113
+ return {
114
+ initialState: `Baseline score ${before.score}/100, organic ${before.organicScore}/100.`,
115
+ chosenMode: 'benchmark-on-isolated-copy',
116
+ whatChanged: applyResult.writtenFiles,
117
+ whatWasPreserved: applyResult.preservedFiles,
118
+ measuredResults: {
119
+ scoreDelta: after.score - before.score,
120
+ organicDelta: after.organicScore - before.organicScore,
121
+ passedDelta: after.passed - before.passed,
122
+ },
123
+ };
124
+ }
125
+
126
+ function renderBenchmarkMarkdown(report) {
127
+ return [
128
+ '# Claudex Setup Benchmark Report',
129
+ '',
130
+ `- Generated by: ${report.generatedBy}`,
131
+ `- Created at: ${report.createdAt}`,
132
+ `- Source repo: ${report.directory}`,
133
+ '',
134
+ '## Methodology',
135
+ ...report.methodology.map(item => `- ${item}`),
136
+ '',
137
+ '## Before',
138
+ `- Score: ${report.before.score}/100`,
139
+ `- Organic score: ${report.before.organicScore}/100`,
140
+ `- Passing checks: ${report.before.passed}/${report.before.checkCount}`,
141
+ '',
142
+ '## After',
143
+ `- Score: ${report.after.score}/100`,
144
+ `- Organic score: ${report.after.organicScore}/100`,
145
+ `- Passing checks: ${report.after.passed}/${report.after.checkCount}`,
146
+ '',
147
+ '## Delta',
148
+ `- Score delta: ${report.delta.score}`,
149
+ `- Organic score delta: ${report.delta.organicScore}`,
150
+ `- Passed checks delta: ${report.delta.passed}`,
151
+ '',
152
+ '## Executive Summary',
153
+ `- ${report.executiveSummary.headline}`,
154
+ `- Recommendation: ${report.executiveSummary.decisionGuidance}`,
155
+ '',
156
+ '## Workflow Evidence',
157
+ `- Task pack: ${report.workflowEvidence.taskPack}`,
158
+ `- Coverage: ${report.workflowEvidence.summary.passed}/${report.workflowEvidence.summary.total} (${report.workflowEvidence.summary.coverageScore}%)`,
159
+ ...report.workflowEvidence.tasks.map(task => `- ${task.label}: ${task.passed ? 'pass' : 'not yet'} — ${task.evidence}`),
160
+ '',
161
+ '## Case Study',
162
+ `- Initial state: ${report.caseStudy.initialState}`,
163
+ `- Chosen mode: ${report.caseStudy.chosenMode}`,
164
+ `- What changed: ${report.caseStudy.whatChanged.join(', ') || 'none'}`,
165
+ `- What was preserved: ${report.caseStudy.whatWasPreserved.join(', ') || 'none'}`,
166
+ '',
167
+ ].join('\n');
168
+ }
169
+
170
+ async function runBenchmark(options) {
171
+ const before = await audit({ dir: options.dir, silent: true });
172
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'claudex-benchmark-'));
173
+ const sandboxDir = path.join(tempRoot, 'repo');
174
+
175
+ try {
176
+ copyProject(options.dir, sandboxDir);
177
+ const applyResult = await setup({
178
+ dir: sandboxDir,
179
+ auto: true,
180
+ silent: true,
181
+ profile: options.profile,
182
+ mcpPacks: options.mcpPacks || [],
183
+ });
184
+ const after = await audit({ dir: sandboxDir, silent: true });
185
+ const analysisReport = await analyzeProject({ dir: sandboxDir, mode: 'suggest-only' });
186
+ const governanceSummary = getGovernanceSummary();
187
+ const workflowEvidence = buildWorkflowEvidence(before, after, analysisReport, governanceSummary);
188
+
189
+ return {
190
+ schemaVersion: 1,
191
+ generatedBy: `claudex-setup@${version}`,
192
+ createdAt: new Date().toISOString(),
193
+ directory: options.dir,
194
+ methodology: [
195
+ 'Run a baseline audit on the source repo.',
196
+ 'Copy the repo into a temporary isolated workspace.',
197
+ 'Apply starter-safe Claude artifacts only on the isolated copy.',
198
+ 'Re-run the audit and compare the results.',
199
+ ],
200
+ before: summarizeAudit(before),
201
+ after: summarizeAudit(after),
202
+ delta: {
203
+ score: after.score - before.score,
204
+ organicScore: after.organicScore - before.organicScore,
205
+ passed: after.passed - before.passed,
206
+ failed: after.failed - before.failed,
207
+ },
208
+ workflowEvidence,
209
+ executiveSummary: buildExecutiveSummary(before, after, workflowEvidence),
210
+ caseStudy: buildCaseStudy(before, after, applyResult),
211
+ };
212
+ } finally {
213
+ fs.rmSync(tempRoot, { recursive: true, force: true });
214
+ }
215
+ }
216
+
217
+ function printBenchmark(report, options = {}) {
218
+ if (options.json) {
219
+ console.log(JSON.stringify(report, null, 2));
220
+ return;
221
+ }
222
+
223
+ console.log('');
224
+ console.log(' claudex-setup benchmark');
225
+ console.log(' ═══════════════════════════════════════');
226
+ console.log(' Runs in an isolated temp copy. Your current repo is not modified.');
227
+ console.log('');
228
+ console.log(` Before: ${report.before.score}/100 (organic ${report.before.organicScore}/100)`);
229
+ console.log(` After: ${report.after.score}/100 (organic ${report.after.organicScore}/100)`);
230
+ console.log(` Delta: score ${report.delta.score >= 0 ? '+' : ''}${report.delta.score}, organic ${report.delta.organicScore >= 0 ? '+' : ''}${report.delta.organicScore}`);
231
+ console.log('');
232
+ console.log(` ${report.executiveSummary.headline}`);
233
+ console.log(` Recommendation: ${report.executiveSummary.decisionGuidance}`);
234
+ console.log(` Workflow evidence: ${report.workflowEvidence.summary.passed}/${report.workflowEvidence.summary.total} tasks (${report.workflowEvidence.summary.coverageScore}%)`);
235
+ console.log('');
236
+ }
237
+
238
+ function writeBenchmarkReport(report, outFile) {
239
+ fs.mkdirSync(path.dirname(outFile), { recursive: true });
240
+ const content = path.extname(outFile).toLowerCase() === '.md'
241
+ ? renderBenchmarkMarkdown(report)
242
+ : JSON.stringify(report, null, 2);
243
+ fs.writeFileSync(outFile, content, 'utf8');
244
+ }
245
+
246
+ module.exports = {
247
+ runBenchmark,
248
+ printBenchmark,
249
+ writeBenchmarkReport,
250
+ };
package/src/context.js CHANGED
@@ -17,11 +17,12 @@ class ProjectContext {
17
17
  try {
18
18
  const entries = fs.readdirSync(this.dir, { withFileTypes: true });
19
19
  for (const entry of entries) {
20
- if (entry.name.startsWith('.') && entry.name !== '.claude' && entry.name !== '.gitignore') continue;
21
- if (entry.name === 'node_modules' || entry.name === '__pycache__') continue;
22
20
  if (entry.isFile()) {
21
+ if (entry.name === '.DS_Store') continue;
23
22
  this.files.push(entry.name);
24
23
  } else if (entry.isDirectory()) {
24
+ if (entry.name.startsWith('.') && entry.name !== '.claude') continue;
25
+ if (entry.name === 'node_modules' || entry.name === '__pycache__') continue;
25
26
  this.files.push(entry.name + '/');
26
27
  // Scan .claude/ subdirectories
27
28
  if (entry.name === '.claude') {
@@ -0,0 +1,160 @@
1
+ const DOMAIN_PACKS = [
2
+ {
3
+ key: 'baseline-general',
4
+ label: 'Baseline General',
5
+ useWhen: 'General repos that need a pragmatic Claude baseline without domain-specific assumptions.',
6
+ recommendedModules: ['CLAUDE.md baseline', 'verification', 'safe-write profile'],
7
+ recommendedMcpPacks: ['context7-docs'],
8
+ benchmarkFocus: ['discover next actions', 'starter-safe improvement', 'governed rollout'],
9
+ },
10
+ {
11
+ key: 'backend-api',
12
+ label: 'Backend API',
13
+ useWhen: 'Service, API, or backend-heavy repos with routes, services, jobs, or data access.',
14
+ recommendedModules: ['verification', 'security workflow', 'commands', 'rules'],
15
+ recommendedMcpPacks: ['context7-docs'],
16
+ benchmarkFocus: ['test + build verification', 'security review workflow', 'safe apply on existing config'],
17
+ },
18
+ {
19
+ key: 'frontend-ui',
20
+ label: 'Frontend UI',
21
+ useWhen: 'React, Next.js, Vue, Angular, or Svelte repos with components and UI-heavy workflows.',
22
+ recommendedModules: ['frontend rules', 'design guidance', 'commands', 'benchmark'],
23
+ recommendedMcpPacks: ['context7-docs', 'next-devtools'],
24
+ benchmarkFocus: ['build checks', 'component workflow quality', 'framework-aware starter output'],
25
+ },
26
+ {
27
+ key: 'data-pipeline',
28
+ label: 'Data Pipeline',
29
+ useWhen: 'Repos with workers, DAGs, marts, ETL jobs, migrations, or analytics-heavy workflows.',
30
+ recommendedModules: ['verification', 'rules', 'agents', 'benchmark'],
31
+ recommendedMcpPacks: ['context7-docs'],
32
+ benchmarkFocus: ['pipeline safety', 'repeatable task flows', 'state-aware review artifacts'],
33
+ },
34
+ {
35
+ key: 'infra-platform',
36
+ label: 'Infra Platform',
37
+ useWhen: 'Terraform, Docker, Kubernetes, Wrangler, or deployment-oriented repos.',
38
+ recommendedModules: ['ci-devops', 'commands', 'governance', 'benchmark'],
39
+ recommendedMcpPacks: ['context7-docs'],
40
+ benchmarkFocus: ['release safety', 'policy-controlled rollout', 'infra verification loops'],
41
+ },
42
+ {
43
+ key: 'oss-library',
44
+ label: 'OSS Library',
45
+ useWhen: 'Public packages or contributor-heavy repos that need a lighter governance footprint.',
46
+ recommendedModules: ['suggest-only profile', 'light rules', 'commands', 'README-aligned CLAUDE.md'],
47
+ recommendedMcpPacks: ['context7-docs'],
48
+ benchmarkFocus: ['low-footprint adoption', 'manual review friendliness', 'contributor-safe defaults'],
49
+ },
50
+ {
51
+ key: 'enterprise-governed',
52
+ label: 'Enterprise Governed',
53
+ useWhen: 'Repos with CI, permissions, hooks, and a need for auditable change controls.',
54
+ recommendedModules: ['governance', 'activity artifacts', 'rollback manifests', 'benchmark evidence'],
55
+ recommendedMcpPacks: ['context7-docs'],
56
+ benchmarkFocus: ['policy-aware rollout', 'approval flow readiness', 'benchmark export quality'],
57
+ },
58
+ ];
59
+
60
+ function uniqueByKey(items) {
61
+ const seen = new Set();
62
+ const result = [];
63
+ for (const item of items) {
64
+ if (seen.has(item.key)) continue;
65
+ seen.add(item.key);
66
+ result.push(item);
67
+ }
68
+ return result;
69
+ }
70
+
71
+ function detectDomainPacks(ctx, stacks, assets = null) {
72
+ const stackKeys = new Set((stacks || []).map(stack => stack.key));
73
+ const pkg = ctx.jsonFile('package.json') || {};
74
+ const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
75
+ const matches = [];
76
+
77
+ function addMatch(key, reasons) {
78
+ const pack = DOMAIN_PACKS.find(item => item.key === key);
79
+ if (!pack) return;
80
+ matches.push({
81
+ ...pack,
82
+ matchReasons: reasons.filter(Boolean).slice(0, 3),
83
+ });
84
+ }
85
+
86
+ const hasFrontend = stackKeys.has('react') || stackKeys.has('nextjs') || stackKeys.has('vue') ||
87
+ stackKeys.has('angular') || stackKeys.has('svelte') || ctx.hasDir('components') || ctx.hasDir('app') || ctx.hasDir('pages');
88
+ const hasBackend = stackKeys.has('node') || stackKeys.has('python') || stackKeys.has('django') ||
89
+ stackKeys.has('fastapi') || stackKeys.has('go') || stackKeys.has('rust') || stackKeys.has('java') ||
90
+ ctx.hasDir('api') || ctx.hasDir('routes') || ctx.hasDir('services') || ctx.hasDir('controllers');
91
+ const hasData = ctx.hasDir('dags') || ctx.hasDir('jobs') || ctx.hasDir('workers') ||
92
+ ctx.hasDir('models') || ctx.hasDir('migrations') || ctx.hasDir('db') ||
93
+ deps.dbt || deps['apache-airflow'] || deps.pandas || deps.polars || deps.duckdb;
94
+ const hasInfra = stackKeys.has('docker') || stackKeys.has('terraform') || stackKeys.has('kubernetes') ||
95
+ ctx.files.includes('wrangler.toml') || ctx.files.includes('serverless.yml') || ctx.files.includes('serverless.yaml') ||
96
+ ctx.files.includes('cdk.json') || ctx.hasDir('infra') || ctx.hasDir('deploy') || ctx.hasDir('helm');
97
+ const isOss = !!ctx.fileContent('LICENSE') && !!ctx.fileContent('CONTRIBUTING.md') && pkg.private !== true;
98
+ const isEnterpriseGoverned = !!(assets && assets.permissions && assets.permissions.hasDenyRules) &&
99
+ !!(assets && assets.files && assets.files.settings) && ctx.hasDir('.github/workflows');
100
+
101
+ if (hasBackend) {
102
+ addMatch('backend-api', [
103
+ 'Detected backend stack or service directories.',
104
+ ctx.hasDir('api') ? 'API-facing structure detected.' : null,
105
+ ctx.hasDir('services') ? 'Service-layer directories detected.' : null,
106
+ ]);
107
+ }
108
+
109
+ if (hasFrontend) {
110
+ addMatch('frontend-ui', [
111
+ 'Detected frontend stack or UI directories.',
112
+ ctx.hasDir('components') ? 'Component directories detected.' : null,
113
+ stackKeys.has('nextjs') ? 'Next.js stack detected.' : null,
114
+ ]);
115
+ }
116
+
117
+ if (hasData) {
118
+ addMatch('data-pipeline', [
119
+ 'Detected worker, jobs, models, or analytics-style structure.',
120
+ ctx.hasDir('jobs') ? 'Job/pipeline directories detected.' : null,
121
+ ctx.hasDir('migrations') ? 'Migration flow detected.' : null,
122
+ ]);
123
+ }
124
+
125
+ if (hasInfra) {
126
+ addMatch('infra-platform', [
127
+ 'Detected deployment or infrastructure signals.',
128
+ ctx.files.includes('wrangler.toml') ? 'Wrangler deployment config detected.' : null,
129
+ ctx.hasDir('deploy') ? 'Deployment directory detected.' : null,
130
+ ]);
131
+ }
132
+
133
+ if (isOss) {
134
+ addMatch('oss-library', [
135
+ 'License and contribution guidance suggest an open-source repo.',
136
+ pkg.private === false ? 'package.json is not marked private.' : null,
137
+ ]);
138
+ }
139
+
140
+ if (isEnterpriseGoverned) {
141
+ addMatch('enterprise-governed', [
142
+ 'Settings, deny rules, and CI indicate a governed team workflow.',
143
+ 'Repo already has policy-aware Claude assets.',
144
+ ]);
145
+ }
146
+
147
+ const deduped = uniqueByKey(matches);
148
+ if (deduped.length === 0) {
149
+ return [{
150
+ ...DOMAIN_PACKS.find(item => item.key === 'baseline-general'),
151
+ matchReasons: ['No stronger domain signal detected yet.'],
152
+ }];
153
+ }
154
+ return deduped;
155
+ }
156
+
157
+ module.exports = {
158
+ DOMAIN_PACKS,
159
+ detectDomainPacks,
160
+ };