repofence 0.1.8 → 0.2.1

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,
@@ -117,9 +119,9 @@ program
117
119
  // Auth command
118
120
  program
119
121
  .command('auth')
120
- .description('Autentica el CLI con token emitido por la web repofence')
121
- .option('--token <token>', 'Token emitido por la web repofence')
122
- .option('--web', 'Abrir la web de login/pago y obtener token manualmente')
122
+ .description('Authenticate the CLI with a token from repofence.com')
123
+ .option('--token <token>', 'Token from repofence.com (after purchase)')
124
+ .option('--web', 'Open login/checkout in the browser to get a token')
123
125
  .action(async (options) => {
124
126
  const cwd = process.cwd();
125
127
  try {
@@ -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);
@@ -36,10 +36,10 @@ const authCommand = async (_cwd, options) => {
36
36
  }
37
37
  const token = options.token ?? (await promptToken());
38
38
  if (!token) {
39
- throw new Error('No se proporcionó token; vuelve a intentarlo con --token <valor>.');
39
+ throw new Error('No token provided; try again with --token <value>.');
40
40
  }
41
41
  await (0, auth_1.saveToken)(token);
42
- console.log(chalk_1.default.green(`✅ Token guardado en ${auth_1.tokenPath}`));
42
+ console.log(chalk_1.default.green(`✅ Token saved to ${auth_1.tokenPath}`));
43
43
  };
44
44
  exports.authCommand = authCommand;
45
45
  //# sourceMappingURL=auth.js.map
@@ -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) {
@@ -156,40 +185,38 @@ async function initCommand(cwd, options) {
156
185
  }
157
186
  catch (error) {
158
187
  const message = error instanceof Error ? error.message : String(error);
159
- console.error(chalk_1.default.red(`❌ Error al descargar pack "${packId}" (${resolvedBase}): ${message}`));
188
+ console.error(chalk_1.default.red(`❌ Failed to download pack "${packId}" (${resolvedBase}): ${message}`));
160
189
  throw error;
161
190
  }
162
191
  const isValid = await (0, pack_manager_1.validateSignature)(pack);
163
192
  if (!isValid) {
164
- throw new Error('La firma del pack no es válida.');
193
+ throw new Error('Invalid pack signature.');
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
- console.log(chalk_1.default.green(`✓ Pack "${pack.packId}" instalado`));
198
+ console.log(chalk_1.default.green(`✓ Pack "${pack.packId}" installed`));
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 pack manifest found at ${(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(`Installed: ${manifest.installed_at}`));
21
+ console.log(chalk_1.default.cyan(`Expires: ${manifest.expires_at ?? 'never'}`));
22
+ console.log(chalk_1.default.cyan(`Artifacts:`));
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
- console.log(chalk_1.default.green(`✅ Token presente (${auth_1.tokenPath})`));
31
+ console.log(chalk_1.default.green(`✅ Token present (${auth_1.tokenPath})`));
15
32
  }
16
33
  else {
17
- console.log(chalk_1.default.yellow('⚠️ Token no configurado. Ejecuta `repofence auth --token <valor>`'));
34
+ console.log(chalk_1.default.yellow('⚠️ No token configured. Run `repofence auth --token <value>`'));
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
@@ -21,16 +21,16 @@ const DEMO_PACK = {
21
21
  files: [
22
22
  {
23
23
  path: 'repofence-spec.md',
24
- contentBase64: Buffer.from('# repofence spec (demo)\n\n_TODO: contenido del pack_\n').toString('base64'),
24
+ contentBase64: Buffer.from('# repofence spec (demo)\n\n_TODO: pack content_\n').toString('base64'),
25
25
  },
26
26
  {
27
27
  path: 'repofence-plan.md',
28
- contentBase64: Buffer.from('# repofence plan (demo)\n\n_TODO: contenido del pack_\n').toString('base64'),
28
+ contentBase64: Buffer.from('# repofence plan (demo)\n\n_TODO: pack content_\n').toString('base64'),
29
29
  },
30
30
  ],
31
31
  };
32
32
  const notImplemented = () => {
33
- throw new Error('API client no implementado. Usa REPOFENCE_MOCK=1 para datos dummy.');
33
+ throw new Error('API client not implemented. Set REPOFENCE_MOCK=1 for dummy data.');
34
34
  };
35
35
  /**
36
36
  * Production hosts expose routes under `/api/v1/...`.
package/dist/core/auth.js CHANGED
@@ -16,7 +16,7 @@ exports.tokenPath = tokenFile;
16
16
  const saveToken = async (token) => {
17
17
  const normalized = token.trim();
18
18
  if (!normalized) {
19
- throw new Error('Token vacío; provee un token válido.');
19
+ throw new Error('Empty token; provide a valid token.');
20
20
  }
21
21
  await ensureDir();
22
22
  await promises_1.default.writeFile(tokenFile, normalized, { mode: 0o600 });
@@ -49,84 +49,226 @@ const sha256Base64 = (buf) => crypto_1.default.createHash('sha256').update(buf).
49
49
  const downloadBuffer = async (url) => {
50
50
  const res = await fetch(url);
51
51
  if (!res.ok) {
52
- throw new Error(`Descarga fallida (${res.status}) ${res.statusText}`);
52
+ throw new Error(`Download failed (${res.status}) ${res.statusText}`);
53
53
  }
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;
61
- // Descargar manifest y recalcular hash (robustez)
140
+ // Download manifest and recalculate hash (robustness)
62
141
  let manifestHashCalculated = undefined;
63
142
  if (pack.manifestUrl) {
64
143
  const manifestBuf = await downloadBuffer(pack.manifestUrl);
65
144
  manifestHashCalculated = sha256Base64(manifestBuf);
66
145
  if (pack.manifestHash && pack.manifestHash !== manifestHashCalculated) {
67
- throw new Error(`Hash de manifest inválido. Esperado ${pack.manifestHash} obtenido ${manifestHashCalculated}`);
146
+ throw new Error(`Invalid manifest hash. Expected ${pack.manifestHash}, got ${manifestHashCalculated}`);
68
147
  }
69
148
  }
70
149
  const archiveBuf = await downloadBuffer(archiveUrl);
71
150
  if (pack.archiveHash) {
72
151
  const hash = sha256Base64(archiveBuf);
73
152
  if (hash !== pack.archiveHash) {
74
- throw new Error(`Hash de archive inválido. Esperado ${pack.archiveHash} obtenido ${hash}`);
153
+ throw new Error(`Invalid archive hash. Expected ${pack.archiveHash}, got ${hash}`);
75
154
  }
76
155
  }
77
- // Extraer lista de archivos y luego extraer contenido
156
+ // List files, then extract archive contents
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
- // Validación de firma (manifestHash ya viene en el pack; archiveHash validada arriba)
162
+ // Signature validation (manifestHash from pack; archiveHash validated above)
91
163
  await (0, exports.validateSignature)({
92
164
  ...pack,
93
165
  manifestHash: pack.manifestHash || manifestHashCalculated,
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] installed from 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(`Skill already exists: ${targetPath} (use --force to overwrite)`);
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) => {
114
- // Si viene archiveUrl, descargar tar.gz y extraer.
229
+ const target = options.target || 'cursor';
230
+ // If archiveUrl is set, download tar.gz and extract.
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(`Skill already exists: ${targetPath} (use --force to overwrite)`);
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] wrote skill ${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))) {
123
- throw new Error(`El archivo ya existe: ${target} (usa --force para sobrescribir)`);
265
+ throw new Error(`File already exists: ${target} (use --force to overwrite)`);
124
266
  }
125
267
  await promises_1.default.mkdir(path_1.default.dirname(target), { recursive: true });
126
268
  const buffer = Buffer.from(file.contentBase64, 'base64');
127
269
  await promises_1.default.writeFile(target, buffer);
128
270
  if (options.verbose) {
129
- console.log(`[repofence] escrito ${target}`);
271
+ console.log(`[repofence] wrote ${target}`);
130
272
  }
131
273
  }
132
274
  const manifest = {
@@ -142,26 +284,13 @@ const installPack = async (pack, options = {}, baseDir) => {
142
284
  exports.installPack = installPack;
143
285
  const manifestFilePath = (baseDir) => manifestPath(baseDir);
144
286
  exports.manifestFilePath = manifestFilePath;
145
- /** Standard base64 or base64url (some APIs / env pastes). */
146
- const decodeBase64Flexible = (input) => {
147
- const trimmed = input.trim().replace(/\s/g, '');
148
- if (!trimmed) {
149
- throw new Error('Cadena vacía.');
150
- }
151
- const b64 = trimmed.includes('-') || trimmed.includes('_')
152
- ? trimmed.replace(/-/g, '+').replace(/_/g, '/')
153
- : trimmed;
154
- const pad = b64.length % 4;
155
- const padded = pad ? b64 + '='.repeat(4 - pad) : b64;
156
- return Buffer.from(padded, 'base64');
157
- };
158
287
  const validateSignature = async (_pack) => {
159
288
  const pack = _pack;
160
- // Si no hay firma ni hashes, no validamos (modo demo/mock).
289
+ // If there is no signature or hashes, skip validation (demo/mock mode).
161
290
  if (!pack.signature || !pack.manifestHash) {
162
291
  return true;
163
292
  }
164
- // Construir payload igual que en backend: manifestHash[:archiveHash]
293
+ // Build payload same as backend: manifestHash[:archiveHash]
165
294
  const payloadToVerify = pack.archiveHash
166
295
  ? `${pack.manifestHash}:${pack.archiveHash}`
167
296
  : pack.manifestHash;
@@ -171,35 +300,14 @@ const validateSignature = async (_pack) => {
171
300
  : Buffer.from(rawKey, 'base64').toString('utf8');
172
301
  try {
173
302
  const keyObject = crypto_1.default.createPublicKey(publicKeyPem);
174
- const sigBuf = decodeBase64Flexible(pack.signature);
175
- const dataBuf = Buffer.from(payloadToVerify, 'utf8');
176
- const keyType = keyObject.asymmetricKeyType;
177
- let isValid;
178
- if (keyType === 'ed25519') {
179
- if (sigBuf.length !== 64) {
180
- throw new Error(`Firma Ed25519: tras decodificar se esperaban 64 bytes, hay ${sigBuf.length}. ` +
181
- 'Suele indicar que el API firmó con otro algoritmo (p. ej. RSA) o la firma está corrupta. ' +
182
- 'El servidor debe usar el par Ed25519 que corresponde a la clave pública del CLI.');
183
- }
184
- isValid = crypto_1.default.verify(null, dataBuf, keyObject, sigBuf);
185
- }
186
- else if (keyType === 'rsa') {
187
- isValid = crypto_1.default.verify('sha256', dataBuf, keyObject, sigBuf);
188
- }
189
- else {
190
- throw new Error(`Tipo de clave pública no soportado: ${String(keyType)}`);
191
- }
303
+ const isValid = crypto_1.default.verify(null, Buffer.from(payloadToVerify), keyObject, Buffer.from(pack.signature, 'base64'));
192
304
  if (!isValid) {
193
- throw new Error('Firma de pack inválida.');
305
+ throw new Error('Invalid pack signature.');
194
306
  }
195
307
  return true;
196
308
  }
197
309
  catch (error) {
198
- const msg = error instanceof Error ? error.message : 'error_validando_firma';
199
- if (msg.includes('asn1') || msg.includes('DECODER') || msg.includes('wrong tag')) {
200
- throw new Error(`${msg}. Comprueba que REPOFENCE_SIGNING_KEY en el API sea el par Ed25519 de public_key.pem ` +
201
- '(o que REPOFENCE_PUBLIC_KEY en el cliente sea la clave pública que corresponde a la privada del servidor).');
202
- }
310
+ const msg = error instanceof Error ? error.message : 'signature_validation_error';
203
311
  throw new Error(msg);
204
312
  }
205
313
  };
@@ -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.8",
3
+ "version": "0.2.1",
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",
@@ -20,9 +20,7 @@
20
20
  "start:cli": "node dist/cli.js",
21
21
  "dev": "ts-node src/cli.ts",
22
22
  "backend": "ts-node backend/server.ts",
23
- "backend:start": "node backend-dist/server.js",
24
- "inspect:signing": "ts-node scripts/inspect-signing-key.ts",
25
- "verify:pack-sig": "ts-node scripts/verify-pack-signature-files.ts"
23
+ "backend:start": "node backend-dist/server.js"
26
24
  },
27
25
  "keywords": [
28
26
  "sdd",
@@ -39,7 +37,6 @@
39
37
  "commander": "^11.1.0",
40
38
  "dotenv": "^16.4.5",
41
39
  "open": "^11.0.0",
42
- "repofence": "^0.1.2",
43
40
  "tar": "^6.2.1"
44
41
  },
45
42
  "devDependencies": {