repofence 0.1.7 → 0.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/README.md CHANGED
@@ -14,13 +14,15 @@ npm install -g repofence
14
14
  # 1. Purchase a license and get your token
15
15
  repofence auth --web
16
16
 
17
- # 2. Initialize spec files + install Cursor AI commands
17
+ # 2. Initialize spec files + install assistant assets (Cursor by default)
18
18
  repofence init
19
19
 
20
20
  # 3. Auto-populate specs from your codebase
21
21
  repofence reverse
22
22
  ```
23
23
 
24
+ Brownfield tip: run `repofence reverse` first after `init` so your spec layer reflects the current codebase before any AI-driven changes.
25
+
24
26
  ## Commands
25
27
 
26
28
  ### `repofence auth`
@@ -39,12 +41,14 @@ Get your license at [repofence.com](https://repofence.com).
39
41
 
40
42
  ---
41
43
 
42
- ### `repofence init`
44
+ ### `repofence init [ide]`
43
45
 
44
- Initialize spec files in the current project and install Cursor AI command packs into `~/.cursor/commands/`.
46
+ Initialize spec files in the current project and install assistant assets.
45
47
 
46
48
  ```bash
47
- repofence init
49
+ repofence init # defaults to Cursor
50
+ repofence init cursor
51
+ repofence init claude
48
52
  ```
49
53
 
50
54
  **Requires**: a valid token saved via `repofence auth`.
@@ -57,9 +61,11 @@ Creates in your project:
57
61
  | `spec/CORE_FLOWS.md` | Critical user and system flows |
58
62
  | `spec/BOUNDARIES.md` | Architectural boundaries and constraints |
59
63
  | `spec/AI_RULES.md` | Rules for AI agents working on the codebase |
60
- | `spec/IDE_CURSOR.md` | Cursor IDE integration guide |
64
+ | `spec/IDE_CURSOR.md` or `spec/IDE_CLAUDE.md` | Assistant-specific integration guide |
61
65
 
62
- Also installs Cursor AI commands globally (`~/.cursor/commands/`) that you can use with `/repofence.*` in any Cursor chat.
66
+ Install targets:
67
+ - `cursor`: installs `/repofence.*` command files in `~/.cursor/commands/`
68
+ - `claude`: installs repofence skills in `~/.claude/skills/` and creates project files `CLAUDE.md` + `.claude/rules/sdd-core.md`
63
69
 
64
70
  **Options**:
65
71
 
@@ -89,6 +95,8 @@ Detects:
89
95
 
90
96
  Updates `spec/SYSTEM_OVERVIEW.md`, `spec/CORE_FLOWS.md`, `spec/BOUNDARIES.md`, and `spec/AI_RULES.md` with what it finds, preserving sections you've already customized.
91
97
 
98
+ For brownfield projects this is the most important command to run early.
99
+
92
100
  **Options**:
93
101
 
94
102
  | Flag | Description |
@@ -99,12 +107,14 @@ Updates `spec/SYSTEM_OVERVIEW.md`, `spec/CORE_FLOWS.md`, `spec/BOUNDARIES.md`, a
99
107
 
100
108
  ---
101
109
 
102
- ### `repofence status`
110
+ ### `repofence status [ide]`
103
111
 
104
- Show the status of your local token and installed pack.
112
+ Show the status of your local token and installed pack manifests.
105
113
 
106
114
  ```bash
107
- repofence status
115
+ repofence status # shows cursor + claude
116
+ repofence status cursor
117
+ repofence status claude
108
118
  ```
109
119
 
110
120
  ---
@@ -113,11 +123,12 @@ repofence status
113
123
 
114
124
  ```
115
125
  repofence auth --web # one-time: purchase license + get token
116
- repofence init # per project: create spec files + install AI commands
117
- repofence reverse # per project: auto-fill specs from code
126
+ repofence init claude # or: repofence init cursor
127
+ repofence reverse # critical for brownfield: auto-fill specs from code
118
128
  ```
119
129
 
120
- After running `init`, open any project in Cursor and type `/repofence.help` in the chat to see all available AI commands.
130
+ After running `init cursor`, open Cursor and type `/repofence.help`.
131
+ After running `init claude`, open Claude Code and type `/repofence-reverse`.
121
132
 
122
133
  ## Global Options
123
134
 
@@ -144,6 +155,12 @@ Deploy (e.g. **Railway**): see [`backend/README.md`](backend/README.md) and root
144
155
 
145
156
  Step-by-step: **[docs/RELEASE.md](docs/RELEASE.md)** (`npm version`, `npm publish`). The published package only includes **`dist/`**; the HTTP API in `backend/` is deployed separately.
146
157
 
158
+ ## Claude rollout docs
159
+
160
+ - [docs/CLAUDE-ASSET-CONTRACT.md](docs/CLAUDE-ASSET-CONTRACT.md)
161
+ - [docs/CLAUDE-SKILL-MAPPING.md](docs/CLAUDE-SKILL-MAPPING.md)
162
+ - [docs/CLAUDE-GA-CHECKLIST.md](docs/CLAUDE-GA-CHECKLIST.md)
163
+
147
164
  ## Environment variables
148
165
 
149
166
  | Variable | Description |
@@ -151,6 +168,7 @@ Step-by-step: **[docs/RELEASE.md](docs/RELEASE.md)** (`npm version`, `npm publis
151
168
  | `REPOFENCE_API_BASE_URL` | API host for the CLI (`auth`, `init`). Default normalizes to `…/api`. |
152
169
  | `REPOFENCE_MOCK` | Set to `1` for mock API responses (no network). |
153
170
  | `REPOFENCE_PACK_ID` | Pack id for `repofence init` (default: `core`). |
171
+ | `REPOFENCE_CLAUDE_PACK_ID` | Optional pack id override for `repofence init claude` (falls back to `REPOFENCE_PACK_ID`). |
154
172
  | `REPOFENCE_PUBLIC_KEY` | Optional. PEM or base64 public key to verify signed packs. If unset, the CLI uses its built-in public key. |
155
173
 
156
174
  ## Requirements
package/dist/cli.js CHANGED
@@ -56,6 +56,7 @@ const init_1 = require("./commands/init");
56
56
  const reverse_1 = require("./commands/reverse");
57
57
  const auth_1 = require("./commands/auth");
58
58
  const status_1 = require("./commands/status");
59
+ const targets_1 = require("./core/targets");
59
60
  const program = new commander_1.Command();
60
61
  program
61
62
  .name('repofence')
@@ -69,18 +70,19 @@ program
69
70
  program
70
71
  .command('init')
71
72
  .description('Initialize spec files in the current directory')
72
- .argument('[ide]', 'IDE type (currently only "cursor" is supported)', 'cursor')
73
+ .argument('[ide]', 'IDE target: cursor | claude', 'cursor')
73
74
  .option('-f, --force', 'overwrite existing files')
74
75
  .option('--name <name>', 'override project name detection')
75
76
  .action(async (ide = 'cursor', options) => {
76
- if (ide && ide !== 'cursor') {
77
- console.error(chalk_1.default.red(`❌ Error: Unsupported IDE "${ide}". Only "cursor" is supported.`));
77
+ if (!(0, targets_1.isIdeTarget)(ide)) {
78
+ console.error(chalk_1.default.red(`❌ Error: Unsupported IDE "${ide}". Supported: "cursor", "claude".`));
78
79
  process.exit(1);
79
80
  }
80
81
  const cwd = process.cwd();
81
82
  const globalOpts = program.opts();
82
83
  try {
83
84
  await (0, init_1.initCommand)(cwd, {
85
+ ide,
84
86
  force: options.force || false,
85
87
  dryRun: globalOpts.dryRun || false,
86
88
  verbose: globalOpts.verbose || false,
@@ -134,11 +136,16 @@ program
134
136
  // Status command
135
137
  program
136
138
  .command('status')
137
- .description('Show local token status and installed pack manifest')
138
- .action(async () => {
139
+ .description('Show local token status and installed pack manifest(s)')
140
+ .argument('[ide]', 'Status target: cursor | claude | all', 'all')
141
+ .action(async (ide = 'all') => {
142
+ if (ide !== 'all' && !(0, targets_1.isIdeTarget)(ide)) {
143
+ console.error(chalk_1.default.red(`❌ Error: Unsupported status target "${ide}". Supported: "cursor", "claude", "all".`));
144
+ process.exit(1);
145
+ }
139
146
  const cwd = process.cwd();
140
147
  try {
141
- await (0, status_1.statusCommand)(cwd);
148
+ await (0, status_1.statusCommand)(cwd, { ide });
142
149
  }
143
150
  catch (error) {
144
151
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -39,21 +39,26 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.initCommand = initCommand;
40
40
  const fs = __importStar(require("fs"));
41
41
  const path = __importStar(require("path"));
42
- const os = __importStar(require("os"));
43
42
  const chalk_1 = __importDefault(require("chalk"));
44
43
  const templates_1 = require("../core/templates");
45
44
  const api_client_1 = require("../core/api-client");
46
45
  const auth_1 = require("../core/auth");
47
46
  const pack_manager_1 = require("../core/pack-manager");
48
- const SPEC_FILES = [
47
+ const targets_1 = require("../core/targets");
48
+ const BASE_SPEC_FILES = [
49
49
  'SYSTEM_OVERVIEW.md',
50
50
  'CORE_FLOWS.md',
51
51
  'BOUNDARIES.md',
52
52
  'AI_RULES.md',
53
- 'IDE_CURSOR.md',
53
+ ];
54
+ const CLAUDE_PROJECT_FILES = [
55
+ { relPath: 'CLAUDE.md', template: 'CLAUDE.md' },
56
+ { relPath: path.join('.claude', 'rules', 'sdd-core.md'), template: 'CLAUDE_RULES_SDD.md' },
54
57
  ];
55
58
  async function initCommand(cwd, options) {
56
- const { force = false, dryRun = false, verbose = false, name } = options;
59
+ const { ide, force = false, dryRun = false, verbose = false, name } = options;
60
+ const profile = (0, targets_1.getTargetProfile)(ide);
61
+ const specFiles = [...BASE_SPEC_FILES, profile.specGuideTemplate];
57
62
  // Show banner
58
63
  console.log('\n' + chalk_1.default.bold('='.repeat(60)));
59
64
  console.log(chalk_1.default.bold('Repofence CLI (packs)'));
@@ -68,7 +73,7 @@ async function initCommand(cwd, options) {
68
73
  console.log(`📦 Project name: ${projectName}`);
69
74
  }
70
75
  console.log(`Project: ${cwd}`);
71
- console.log(`Installing for: Cursor\n`);
76
+ console.log(`Installing for: ${profile.displayName}\n`);
72
77
  // Create spec directory
73
78
  const specDir = path.join(cwd, 'spec');
74
79
  if (!dryRun) {
@@ -90,7 +95,7 @@ async function initCommand(cwd, options) {
90
95
  // Process each template file
91
96
  const createdFiles = [];
92
97
  const skippedFiles = [];
93
- for (const fileName of SPEC_FILES) {
98
+ for (const fileName of specFiles) {
94
99
  const filePath = path.join(specDir, fileName);
95
100
  const fileExists = fs.existsSync(filePath);
96
101
  if (fileExists && !force) {
@@ -124,9 +129,33 @@ async function initCommand(cwd, options) {
124
129
  process.exit(1);
125
130
  }
126
131
  }
132
+ // Claude-only project-level files
133
+ if (ide === 'claude') {
134
+ for (const file of CLAUDE_PROJECT_FILES) {
135
+ const filePath = path.join(cwd, file.relPath);
136
+ const fileExists = fs.existsSync(filePath);
137
+ if (fileExists && !force) {
138
+ skippedFiles.push(file.relPath);
139
+ continue;
140
+ }
141
+ const template = (0, templates_1.loadTemplate)(file.template);
142
+ const rendered = (0, templates_1.renderTemplate)(template, context);
143
+ if (dryRun) {
144
+ console.log(`\n[DRY RUN] Would create: ${filePath}`);
145
+ }
146
+ else {
147
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
148
+ fs.writeFileSync(filePath, rendered, 'utf-8');
149
+ createdFiles.push(file.relPath);
150
+ if (verbose) {
151
+ console.log(chalk_1.default.green(`✓ Created: ${file.relPath}`));
152
+ }
153
+ }
154
+ }
155
+ }
127
156
  // Download and install repofence pack
128
157
  let packInstalled = false;
129
- const packId = process.env.REPOFENCE_PACK_ID || 'core';
158
+ const packId = (0, targets_1.getPackIdForTarget)(ide);
130
159
  if (!dryRun) {
131
160
  const token = await (0, auth_1.readToken)();
132
161
  if (!token) {
@@ -163,33 +192,31 @@ async function initCommand(cwd, options) {
163
192
  if (!isValid) {
164
193
  throw new Error('La firma del pack no es válida.');
165
194
  }
166
- await (0, pack_manager_1.installPack)(pack, { force, verbose });
195
+ await (0, pack_manager_1.installPack)(pack, { force, verbose, target: ide }, profile.installBaseDir);
167
196
  packInstalled = true;
168
197
  if (verbose) {
169
198
  console.log(chalk_1.default.green(`✓ Pack "${pack.packId}" instalado`));
170
199
  }
171
200
  }
172
201
  else {
173
- const homeDir = os.homedir();
174
- console.log(`[DRY RUN] Would download pack "${packId}" and install into: ${path.join(homeDir, '.cursor/commands/')}`);
202
+ console.log(`[DRY RUN] Would download pack "${packId}" and install into: ${profile.installBaseDir}`);
175
203
  }
176
204
  // Print summary
177
205
  if (!dryRun) {
178
- printSummary(createdFiles, skippedFiles, force, packInstalled);
206
+ printSummary(createdFiles, skippedFiles, force, packInstalled, ide);
179
207
  }
180
208
  else {
181
209
  console.log(`\n[DRY RUN] Would create ${createdFiles.length} file(s), skip ${skippedFiles.length} file(s) and install pack "${packId}"`);
182
210
  }
183
211
  }
184
- function printSummary(createdFiles, skippedFiles, force, packInstalled) {
185
- const homeDir = os.homedir();
186
- const cursorDir = path.join(homeDir, '.cursor');
212
+ function printSummary(createdFiles, skippedFiles, force, packInstalled, ide) {
213
+ const profile = (0, targets_1.getTargetProfile)(ide);
187
214
  console.log('\n' + chalk_1.default.bold('='.repeat(60)));
188
215
  console.log(chalk_1.default.green.bold('✨ Installation successful!'));
189
216
  console.log(chalk_1.default.bold('='.repeat(60)));
190
217
  if (packInstalled) {
191
218
  console.log('\n📊 Repofence pack:');
192
- console.log(` • Installed at: ${path.join(cursorDir, 'commands')}`);
219
+ console.log(` • Installed at: ${profile.installBaseDir}`);
193
220
  }
194
221
  console.log('\n' + chalk_1.default.dim('─'.repeat(60)));
195
222
  console.log(chalk_1.default.bold('What was installed:'));
@@ -203,21 +230,35 @@ function printSummary(createdFiles, skippedFiles, force, packInstalled) {
203
230
  });
204
231
  }
205
232
  // Pack installation
206
- if (packInstalled) {
233
+ if (packInstalled && ide === 'cursor') {
207
234
  console.log('\n Cursor (GLOBAL at ~/.cursor/):');
208
- console.log(` • ~/.cursor/commands/ repofence commands`);
235
+ console.log(' • ~/.cursor/commands/ repofence commands');
236
+ }
237
+ if (packInstalled && ide === 'claude') {
238
+ console.log('\n Claude Code (GLOBAL at ~/.claude/):');
239
+ console.log(' • ~/.claude/skills/ repofence skills');
209
240
  }
210
241
  console.log('\n' + chalk_1.default.dim('─'.repeat(60)));
211
242
  console.log(chalk_1.default.cyan.bold('Next Steps - Read Carefully!'));
212
243
  console.log(chalk_1.default.dim('─'.repeat(60)));
213
- console.log(chalk_1.default.green('\n✓ Installation complete. The /repofence.* commands are available in Cursor.\n'));
214
- console.log(chalk_1.default.yellow.bold('IMPORTANT:') + ' These commands run ' + chalk_1.default.green.bold('INSIDE CURSOR'));
215
- console.log(' ' + chalk_1.default.dim('(not in this terminal)') + '\n');
216
- console.log(' 1. Open any project in Cursor');
217
- console.log(' 2. Start a chat with the AI assistant');
218
- console.log(' 3. In the chat window, type: ' + chalk_1.default.cyan('/repofence.help'));
219
- console.log(' 4. Use pack commands such as: ' + chalk_1.default.cyan('/repofence.read-specs'));
220
- console.log(chalk_1.default.yellow('\nRemember:') + ' /repofence.* commands are used in the Cursor chat, not in bash.');
244
+ if (ide === 'cursor') {
245
+ console.log(chalk_1.default.green('\n✓ Installation complete. The /repofence.* commands are available in Cursor.\n'));
246
+ console.log(chalk_1.default.yellow.bold('IMPORTANT:') + ' These commands run ' + chalk_1.default.green.bold('INSIDE CURSOR'));
247
+ console.log(' ' + chalk_1.default.dim('(not in this terminal)') + '\n');
248
+ console.log(' 1. Open any project in Cursor');
249
+ console.log(' 2. Start a chat with the AI assistant');
250
+ console.log(' 3. In the chat window, type: ' + chalk_1.default.cyan('/repofence.help'));
251
+ console.log(' 4. Use pack commands such as: ' + chalk_1.default.cyan('/repofence.read-specs'));
252
+ console.log(chalk_1.default.yellow('\nRemember:') + ' /repofence.* commands are used in the Cursor chat, not in bash.');
253
+ }
254
+ else {
255
+ console.log(chalk_1.default.green('\n✓ Installation complete. The repofence skills are available in Claude Code.\n'));
256
+ console.log(chalk_1.default.yellow.bold('IMPORTANT:') + ' These skills run ' + chalk_1.default.green.bold('INSIDE CLAUDE CODE'));
257
+ console.log(' ' + chalk_1.default.dim('(not in this terminal)') + '\n');
258
+ console.log(' 1. Open Claude Code in any project');
259
+ console.log(' 2. Type: ' + chalk_1.default.cyan('/repofence-reverse'));
260
+ console.log(' 3. Continue with: ' + chalk_1.default.cyan('/repofence-plan') + ' and ' + chalk_1.default.cyan('/repofence-build'));
261
+ }
221
262
  console.log(chalk_1.default.dim('─'.repeat(60)) + '\n');
222
263
  }
223
264
  //# sourceMappingURL=init.js.map
@@ -142,13 +142,27 @@ async function reverseCommand(cwd, options) {
142
142
  }
143
143
  // Print summary
144
144
  if (!dryRun) {
145
- printSummary(updatedFiles, skippedFiles, scanResult, force);
145
+ printSummary(updatedFiles, skippedFiles, scanResult, force, specDir);
146
146
  }
147
147
  else {
148
148
  console.log(`\n[DRY RUN] Would update ${updatedFiles.length} file(s), skip ${skippedFiles.length} file(s)`);
149
149
  }
150
150
  }
151
- function printSummary(updatedFiles, skippedFiles, scanResult, force) {
151
+ function getIdeGuideLine(specDir) {
152
+ const cursorGuide = fs.existsSync(path.join(specDir, 'IDE_CURSOR.md'));
153
+ const claudeGuide = fs.existsSync(path.join(specDir, 'IDE_CLAUDE.md'));
154
+ if (claudeGuide && !cursorGuide) {
155
+ return ' 4. Use these specs with Claude Code (see /spec/IDE_CLAUDE.md)';
156
+ }
157
+ if (cursorGuide && !claudeGuide) {
158
+ return ' 4. Use these specs with Cursor AI (see /spec/IDE_CURSOR.md)';
159
+ }
160
+ if (claudeGuide && cursorGuide) {
161
+ return ' 4. Use the guide that matches your target: /spec/IDE_CLAUDE.md or /spec/IDE_CURSOR.md';
162
+ }
163
+ return ' 4. Add an IDE guide in /spec/ (IDE_CLAUDE.md or IDE_CURSOR.md)';
164
+ }
165
+ function printSummary(updatedFiles, skippedFiles, scanResult, force, specDir) {
152
166
  console.log('\n' + chalk_1.default.bold('='.repeat(60)));
153
167
  console.log(chalk_1.default.green.bold('✨ Reverse engineering complete!'));
154
168
  console.log(chalk_1.default.bold('='.repeat(60)));
@@ -177,7 +191,7 @@ function printSummary(updatedFiles, skippedFiles, scanResult, force) {
177
191
  console.log(' 1. Review the updated spec files in /spec/');
178
192
  console.log(' 2. Fill in any UNKNOWN sections with actual details');
179
193
  console.log(' 3. Customize flows and boundaries as needed');
180
- console.log(' 4. Use these specs with Cursor AI (see /spec/IDE_CURSOR.md)');
194
+ console.log(getIdeGuideLine(specDir));
181
195
  console.log('');
182
196
  }
183
197
  //# sourceMappingURL=reverse.js.map
@@ -7,25 +7,38 @@ exports.statusCommand = void 0;
7
7
  const chalk_1 = __importDefault(require("chalk"));
8
8
  const auth_1 = require("../core/auth");
9
9
  const pack_manager_1 = require("../core/pack-manager");
10
- const statusCommand = async (cwd) => {
10
+ const targets_1 = require("../core/targets");
11
+ const printManifest = async (target) => {
12
+ const profile = (0, targets_1.getTargetProfile)(target);
13
+ const manifest = await (0, pack_manager_1.readManifest)(profile.installBaseDir);
14
+ console.log(chalk_1.default.bold(`\n[${profile.displayName}]`));
15
+ if (!manifest) {
16
+ console.log(chalk_1.default.yellow(`⚠️ No se encontró manifest de pack en ${(0, pack_manager_1.manifestFilePath)(profile.installBaseDir)}`));
17
+ return;
18
+ }
19
+ console.log(chalk_1.default.cyan(`Pack: ${manifest.pack_id} v${manifest.pack_version}`));
20
+ console.log(chalk_1.default.cyan(`Instalado: ${manifest.installed_at}`));
21
+ console.log(chalk_1.default.cyan(`Expira: ${manifest.expires_at ?? 'sin expirar'}`));
22
+ console.log(chalk_1.default.cyan(`Artefactos:`));
23
+ manifest.commands.forEach((cmd) => console.log(` - ${cmd}`));
24
+ console.log(chalk_1.default.gray(`Manifest: ${(0, pack_manager_1.manifestFilePath)(profile.installBaseDir)}`));
25
+ };
26
+ const statusCommand = async (cwd, options = {}) => {
27
+ void cwd;
28
+ const target = options.ide || 'all';
11
29
  const token = await (0, auth_1.readToken)();
12
- const manifest = await (0, pack_manager_1.readManifest)();
13
30
  if (token) {
14
31
  console.log(chalk_1.default.green(`✅ Token presente (${auth_1.tokenPath})`));
15
32
  }
16
33
  else {
17
34
  console.log(chalk_1.default.yellow('⚠️ Token no configurado. Ejecuta `repofence auth --token <valor>`'));
18
35
  }
19
- if (!manifest) {
20
- console.log(chalk_1.default.yellow('⚠️ No se encontró manifest de pack en .cursor/commands/.repofence-manifest.json'));
36
+ if (target === 'all') {
37
+ await printManifest('cursor');
38
+ await printManifest('claude');
21
39
  return;
22
40
  }
23
- console.log(chalk_1.default.cyan(`Pack: ${manifest.pack_id} v${manifest.pack_version}`));
24
- console.log(chalk_1.default.cyan(`Instalado: ${manifest.installed_at}`));
25
- console.log(chalk_1.default.cyan(`Expira: ${manifest.expires_at ?? 'sin expirar'}`));
26
- console.log(chalk_1.default.cyan(`Comandos:`));
27
- manifest.commands.forEach((cmd) => console.log(` - ${cmd}`));
28
- console.log(chalk_1.default.gray(`Manifest: ${(0, pack_manager_1.manifestFilePath)()}`));
41
+ await printManifest(target);
29
42
  };
30
43
  exports.statusCommand = statusCommand;
31
44
  //# sourceMappingURL=status.js.map
@@ -54,7 +54,86 @@ const downloadBuffer = async (url) => {
54
54
  const arrayBuffer = await res.arrayBuffer();
55
55
  return Buffer.from(arrayBuffer);
56
56
  };
57
+ const listFilesRecursive = async (root, relative = '.') => {
58
+ const dir = path_1.default.join(root, relative);
59
+ const entries = await promises_1.default.readdir(dir, { withFileTypes: true });
60
+ const files = [];
61
+ for (const entry of entries) {
62
+ const rel = path_1.default.join(relative, entry.name);
63
+ if (entry.isDirectory()) {
64
+ files.push(...(await listFilesRecursive(root, rel)));
65
+ }
66
+ else if (entry.isFile()) {
67
+ files.push(rel);
68
+ }
69
+ }
70
+ return files;
71
+ };
72
+ const toSkillName = (commandPath) => {
73
+ const base = path_1.default.basename(commandPath, '.md');
74
+ if (base.startsWith('repofence.')) {
75
+ return `repofence-${base.slice('repofence.'.length)}`;
76
+ }
77
+ return base.replace(/[._]/g, '-');
78
+ };
79
+ const extractDescription = (markdown) => {
80
+ const match = markdown.match(/\*\*Description\*\*:\s*(.+)/);
81
+ if (match?.[1])
82
+ return match[1].trim();
83
+ const firstSentence = markdown.split('\n').find((line) => line.trim() && !line.startsWith('#'));
84
+ return (firstSentence || 'Repofence workflow skill').replace(/^[-*]\s*/, '').trim();
85
+ };
86
+ const stripCursorCommandHeader = (markdown) => {
87
+ const lines = markdown.split('\n');
88
+ const filtered = [];
89
+ let skipUntilAfterVersion = false;
90
+ for (const line of lines) {
91
+ if (line.startsWith('# Command: /')) {
92
+ skipUntilAfterVersion = true;
93
+ continue;
94
+ }
95
+ if (skipUntilAfterVersion && line.startsWith('**Version**:')) {
96
+ continue;
97
+ }
98
+ if (skipUntilAfterVersion && line.startsWith('**Description**:')) {
99
+ continue;
100
+ }
101
+ if (skipUntilAfterVersion && line.trim() === '---') {
102
+ skipUntilAfterVersion = false;
103
+ continue;
104
+ }
105
+ filtered.push(line);
106
+ }
107
+ return filtered.join('\n').trimStart();
108
+ };
109
+ const adaptBodyForClaude = (markdown) => {
110
+ return markdown
111
+ .replace(/\/repofence\./g, '/repofence-')
112
+ .replace(/\/spec\/IDE_CURSOR\.md/g, '/spec/IDE_CLAUDE.md')
113
+ .replace(/Cursor chat/gi, 'Claude Code chat')
114
+ .replace(/Cursor AI/gi, 'Claude Code');
115
+ };
116
+ const toClaudeSkillMarkdown = (commandPath, markdown) => {
117
+ const skillName = toSkillName(commandPath);
118
+ const description = extractDescription(markdown);
119
+ const hasSideEffects = skillName.includes('build') ||
120
+ skillName.includes('update') ||
121
+ skillName.includes('finish') ||
122
+ skillName.includes('start') ||
123
+ skillName.includes('reverse');
124
+ const frontmatter = [
125
+ '---',
126
+ `name: ${skillName}`,
127
+ `description: ${description}`,
128
+ ...(hasSideEffects ? ['disable-model-invocation: true'] : []),
129
+ '---',
130
+ '',
131
+ ].join('\n');
132
+ const body = adaptBodyForClaude(stripCursorCommandHeader(markdown));
133
+ return { skillName, content: `${frontmatter}${body}\n` };
134
+ };
57
135
  const installFromArchive = async (pack, options, baseDir) => {
136
+ const target = options.target || 'cursor';
58
137
  const dir = commandsDir(baseDir);
59
138
  await promises_1.default.mkdir(dir, { recursive: true });
60
139
  const archiveUrl = pack.archiveUrl;
@@ -77,16 +156,9 @@ const installFromArchive = async (pack, options, baseDir) => {
77
156
  // Extraer lista de archivos y luego extraer contenido
78
157
  const tmpDir = await promises_1.default.mkdtemp(path_1.default.join(os_1.default.tmpdir(), 'repofence-archive-'));
79
158
  const archivePath = path_1.default.join(tmpDir, 'pack.tar.gz');
159
+ const extractedPath = path_1.default.join(tmpDir, 'extracted');
160
+ await promises_1.default.mkdir(extractedPath, { recursive: true });
80
161
  await promises_1.default.writeFile(archivePath, archiveBuf);
81
- const extractedFiles = [];
82
- await tar_1.default.t({
83
- file: archivePath,
84
- onentry: (entry) => {
85
- if (entry.type === 'File') {
86
- extractedFiles.push(entry.path);
87
- }
88
- },
89
- });
90
162
  // Validación de firma (manifestHash ya viene en el pack; archiveHash validada arriba)
91
163
  await (0, exports.validateSignature)({
92
164
  ...pack,
@@ -94,29 +166,99 @@ const installFromArchive = async (pack, options, baseDir) => {
94
166
  });
95
167
  await tar_1.default.x({
96
168
  file: archivePath,
97
- cwd: dir,
169
+ cwd: extractedPath,
98
170
  });
171
+ const manifest = target === 'claude'
172
+ ? await installClaudeFromExtracted(extractedPath, pack, baseDir, options)
173
+ : await installCursorFromExtracted(extractedPath, pack, baseDir);
99
174
  await promises_1.default.rm(tmpDir, { recursive: true, force: true });
175
+ if (options.verbose) {
176
+ console.log(`[repofence] instalado desde archive ${archiveUrl}`);
177
+ }
178
+ return manifest;
179
+ };
180
+ const installCursorFromExtracted = async (extractedPath, pack, baseDir) => {
181
+ const dir = commandsDir(baseDir);
182
+ await promises_1.default.mkdir(dir, { recursive: true });
183
+ const files = await listFilesRecursive(extractedPath);
184
+ for (const rel of files) {
185
+ const src = path_1.default.join(extractedPath, rel);
186
+ const dst = path_1.default.join(dir, rel);
187
+ await promises_1.default.mkdir(path_1.default.dirname(dst), { recursive: true });
188
+ await promises_1.default.copyFile(src, dst);
189
+ }
100
190
  const manifest = {
101
191
  pack_id: pack.packId,
102
192
  pack_version: pack.packVersion,
103
193
  installed_at: new Date().toISOString(),
104
194
  expires_at: pack.expiresAt ?? null,
105
- commands: extractedFiles,
195
+ commands: files.map((f) => f.replace(/^\.\//, '')),
106
196
  };
107
197
  await (0, exports.writeManifest)(baseDir, manifest);
108
- if (options.verbose) {
109
- console.log(`[repofence] instalado desde archive ${archiveUrl}`);
198
+ return manifest;
199
+ };
200
+ const installClaudeFromExtracted = async (extractedPath, pack, baseDir, options) => {
201
+ const dir = commandsDir(baseDir);
202
+ await promises_1.default.mkdir(dir, { recursive: true });
203
+ const files = await listFilesRecursive(extractedPath);
204
+ const markdownFiles = files.filter((f) => f.endsWith('.md'));
205
+ const artifacts = [];
206
+ for (const rel of markdownFiles) {
207
+ const sourcePath = path_1.default.join(extractedPath, rel);
208
+ const sourceMarkdown = await promises_1.default.readFile(sourcePath, 'utf8');
209
+ const { skillName, content } = toClaudeSkillMarkdown(rel, sourceMarkdown);
210
+ const targetPath = path_1.default.join(dir, skillName, 'SKILL.md');
211
+ if (!options.force && (await fileExists(targetPath))) {
212
+ throw new Error(`El skill ya existe: ${targetPath} (usa --force para sobrescribir)`);
213
+ }
214
+ await promises_1.default.mkdir(path_1.default.dirname(targetPath), { recursive: true });
215
+ await promises_1.default.writeFile(targetPath, content, 'utf8');
216
+ artifacts.push(path_1.default.relative(dir, targetPath));
110
217
  }
218
+ const manifest = {
219
+ pack_id: pack.packId,
220
+ pack_version: pack.packVersion,
221
+ installed_at: new Date().toISOString(),
222
+ expires_at: pack.expiresAt ?? null,
223
+ commands: artifacts,
224
+ };
225
+ await (0, exports.writeManifest)(baseDir, manifest);
111
226
  return manifest;
112
227
  };
113
228
  const installPack = async (pack, options = {}, baseDir) => {
229
+ const target = options.target || 'cursor';
114
230
  // Si viene archiveUrl, descargar tar.gz y extraer.
115
231
  if (pack.archiveUrl) {
116
232
  return installFromArchive(pack, options, baseDir);
117
233
  }
118
234
  const dir = commandsDir(baseDir);
119
235
  await promises_1.default.mkdir(dir, { recursive: true });
236
+ if (target === 'claude') {
237
+ const artifacts = [];
238
+ for (const file of pack.files) {
239
+ const source = Buffer.from(file.contentBase64, 'base64').toString('utf8');
240
+ const { skillName, content } = toClaudeSkillMarkdown(file.path, source);
241
+ const targetPath = path_1.default.join(dir, skillName, 'SKILL.md');
242
+ if (!options.force && (await fileExists(targetPath))) {
243
+ throw new Error(`El skill ya existe: ${targetPath} (usa --force para sobrescribir)`);
244
+ }
245
+ await promises_1.default.mkdir(path_1.default.dirname(targetPath), { recursive: true });
246
+ await promises_1.default.writeFile(targetPath, content, 'utf8');
247
+ artifacts.push(path_1.default.relative(dir, targetPath));
248
+ if (options.verbose) {
249
+ console.log(`[repofence] skill escrito ${targetPath}`);
250
+ }
251
+ }
252
+ const manifest = {
253
+ pack_id: pack.packId,
254
+ pack_version: pack.packVersion,
255
+ installed_at: new Date().toISOString(),
256
+ expires_at: pack.expiresAt ?? null,
257
+ commands: artifacts,
258
+ };
259
+ await (0, exports.writeManifest)(baseDir, manifest);
260
+ return manifest;
261
+ }
120
262
  for (const file of pack.files) {
121
263
  const target = path_1.default.join(dir, file.path);
122
264
  if (!options.force && (await fileExists(target))) {
@@ -3,11 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.EMBEDDED_PACK_PUBLIC_KEY_PEM = void 0;
4
4
  /**
5
5
  * Default Ed25519 public key for verifying signed packs from the Repofence API.
6
- * Keep in sync with `public_key.pem` at the repo root (pair to backend REPOFENCE_SIGNING_KEY).
6
+ * Must match `public_key.pem` in the repo (copy PEM lines verbatim from that file).
7
7
  * Override with env REPOFENCE_PUBLIC_KEY for testing or key rotation.
8
8
  */
9
9
  exports.EMBEDDED_PACK_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
10
- LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUNvd0JRWURLMlZ3QXlFQUpUZnhjN2JhVzg1dzdyM3V4YllOdWFaUk1vZTFlMmxjdmEybDdNTzBpYWs9Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo=
10
+ MCowBQYDK2VwAyEAJTfxc7baW85w7r3uxbYNuaZRMoe1e2lcva2l7MO0iak=
11
11
  -----END PUBLIC KEY-----
12
12
  `;
13
13
  //# sourceMappingURL=pack-public-key.js.map
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getPackIdForTarget = exports.isIdeTarget = exports.getTargetProfile = void 0;
7
+ const os_1 = __importDefault(require("os"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const home = os_1.default.homedir();
10
+ const TARGETS = {
11
+ cursor: {
12
+ id: 'cursor',
13
+ displayName: 'Cursor',
14
+ installBaseDir: path_1.default.join(home, '.cursor', 'commands'),
15
+ helpCommand: '/repofence.help',
16
+ packIdEnv: 'REPOFENCE_PACK_ID',
17
+ specGuideTemplate: 'IDE_CURSOR.md',
18
+ },
19
+ claude: {
20
+ id: 'claude',
21
+ displayName: 'Claude Code',
22
+ installBaseDir: path_1.default.join(home, '.claude', 'skills'),
23
+ helpCommand: '/repofence-reverse',
24
+ packIdEnv: 'REPOFENCE_CLAUDE_PACK_ID',
25
+ specGuideTemplate: 'IDE_CLAUDE.md',
26
+ },
27
+ };
28
+ const getTargetProfile = (target) => TARGETS[target];
29
+ exports.getTargetProfile = getTargetProfile;
30
+ const isIdeTarget = (value) => value === 'cursor' || value === 'claude';
31
+ exports.isIdeTarget = isIdeTarget;
32
+ const getPackIdForTarget = (target) => {
33
+ const profile = (0, exports.getTargetProfile)(target);
34
+ if (profile.id === 'claude') {
35
+ return process.env[profile.packIdEnv] || process.env.REPOFENCE_PACK_ID || 'core';
36
+ }
37
+ return process.env[profile.packIdEnv] || 'core';
38
+ };
39
+ exports.getPackIdForTarget = getPackIdForTarget;
40
+ //# sourceMappingURL=targets.js.map
@@ -0,0 +1,27 @@
1
+ # Repofence SDD Instructions
2
+
3
+ Use the spec layer as the primary source of truth in this repository.
4
+
5
+ ## Mandatory workflow
6
+
7
+ 1. Read `/spec/AI_RULES.md` first.
8
+ 2. Read `/spec/SYSTEM_OVERVIEW.md`, `/spec/CORE_FLOWS.md`, and `/spec/BOUNDARIES.md`.
9
+ 3. For brownfield projects, run `/repofence-reverse` before planning implementation work.
10
+ 4. If specs are missing or stale, say so explicitly and propose how to refresh.
11
+
12
+ ## Commanding model
13
+
14
+ - Use Repofence skills for repeatable workflows:
15
+ - `/repofence-reverse`
16
+ - `/repofence-ask`
17
+ - `/repofence-plan`
18
+ - `/repofence-build`
19
+ - `/repofence-review`
20
+ - `/repofence-finish`
21
+
22
+ ## Boundaries
23
+
24
+ - Do not invent architecture details not present in specs.
25
+ - Do not silently violate constraints in `BOUNDARIES.md`.
26
+ - If implementation work conflicts with specs, stop and ask for direction.
27
+
@@ -0,0 +1,20 @@
1
+ # SDD Rules (Repofence)
2
+
3
+ These rules apply when editing this project with Claude Code.
4
+
5
+ ## Spec-first
6
+
7
+ - Always read `spec/AI_RULES.md` before implementation.
8
+ - Treat `spec/SYSTEM_OVERVIEW.md`, `spec/CORE_FLOWS.md`, and `spec/BOUNDARIES.md` as authoritative context.
9
+
10
+ ## Brownfield priority
11
+
12
+ - Use `/repofence-reverse` early to keep specs aligned with code.
13
+ - If spec coverage is incomplete, label unknowns explicitly instead of guessing.
14
+
15
+ ## Quality checks
16
+
17
+ - Validate changes against boundaries before finalizing.
18
+ - Keep specs updated when workflows, boundaries, or invariants change.
19
+ - Prefer small, traceable diffs from task to implementation.
20
+
@@ -0,0 +1,33 @@
1
+ # Claude Code Integration Guide
2
+
3
+ This guide provides ready-to-copy prompts for using Claude Code with `{{PROJECT_NAME}}`.
4
+
5
+ ## First Prompt
6
+
7
+ Use this at the start of every session:
8
+
9
+ ```
10
+ Read /spec/AI_RULES.md first, then /spec/SYSTEM_OVERVIEW.md, /spec/CORE_FLOWS.md, and /spec/BOUNDARIES.md. Summarize constraints before changing code.
11
+ ```
12
+
13
+ ## Brownfield Fast Path (reverse-first)
14
+
15
+ ```
16
+ Run /repofence-reverse first to refresh the spec layer for this codebase. Then use /repofence-ask to answer questions only from specs.
17
+ ```
18
+
19
+ ## Recommended Skill Flow
20
+
21
+ 1. `/repofence-reverse` for brownfield discovery
22
+ 2. `/repofence-ask` for spec-only Q&A
23
+ 3. `/repofence-plan` before code changes
24
+ 4. `/repofence-build` for task execution
25
+ 5. `/repofence-review` and `/repofence-finish` to close work
26
+
27
+ ## Guardrails
28
+
29
+ - Use specs as source of truth.
30
+ - If a spec is stale or missing, call it out explicitly.
31
+ - Never guess implementation details when specs do not contain them.
32
+ - Update specs when behavior or boundaries change.
33
+
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "repofence",
3
- "version": "0.1.7",
3
+ "version": "0.2.0",
4
4
  "description": "Repofence CLI (packs + backend auth)",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {
7
- "repofence": "./dist/cli.js"
7
+ "repofence": "dist/cli.js"
8
8
  },
9
9
  "files": [
10
10
  "dist/**/*.js",
@@ -37,7 +37,6 @@
37
37
  "commander": "^11.1.0",
38
38
  "dotenv": "^16.4.5",
39
39
  "open": "^11.0.0",
40
- "repofence": "^0.1.2",
41
40
  "tar": "^6.2.1"
42
41
  },
43
42
  "devDependencies": {