ripp-cli 1.0.0 → 1.0.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/CHANGELOG.md ADDED
@@ -0,0 +1,28 @@
1
+ # Changelog
2
+
3
+ All notable changes to the RIPP CLI will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.0.1] - 2025-12-22
9
+
10
+ ### Changed
11
+
12
+ - Version bump to enable npm publishing of updates
13
+
14
+ ### Fixed
15
+
16
+ - Synchronized package-lock.json Node.js engine requirement (>=20.0.0) with package.json
17
+
18
+ ## [1.0.0] - Initial Release
19
+
20
+ ### Features
21
+
22
+ - RIPP packet validation against JSON schema
23
+ - Support for YAML and JSON input formats
24
+ - Multi-file validation with glob patterns
25
+ - Linting with severity levels (error, warning, info)
26
+ - Strict mode for enhanced validation
27
+ - Evidence package generation for handoffs
28
+ - Portable binary distributions for macOS (ARM64 and x64)
package/README.md CHANGED
@@ -274,6 +274,63 @@ npm link
274
274
  ripp validate ../../examples/
275
275
  ```
276
276
 
277
+ ## Publishing
278
+
279
+ ### Prerequisites
280
+
281
+ To publish `ripp-cli` to npm, you need:
282
+
283
+ 1. **npm Account**: A verified npm account with appropriate permissions
284
+ 2. **NPM_TOKEN Secret**: Configured in GitHub repository secrets
285
+
286
+ ### Setting Up NPM_TOKEN
287
+
288
+ The publishing workflow requires an npm **Granular Access Token** with specific permissions:
289
+
290
+ 1. Log in to [npmjs.com](https://www.npmjs.com)
291
+ 2. Go to **Access Tokens** → **Generate New Token** → **Granular Access Token**
292
+ 3. Configure the token:
293
+ - **Permissions**: Select "Read and write" for packages
294
+ - **Packages and scopes**: Select "All packages" or specific packages
295
+ - **Organizations**: (if applicable) Select relevant organizations
296
+ - **Expiration**: Set appropriate expiration date
297
+ - **Bypass 2FA**: ✅ **MUST be enabled** for CI/CD automation
298
+ 4. Copy the generated token
299
+ 5. Add it to GitHub repository secrets:
300
+ - Go to repository **Settings** → **Secrets and variables** → **Actions**
301
+ - Click **New repository secret**
302
+ - Name: `NPM_TOKEN`
303
+ - Value: (paste your token)
304
+
305
+ **Important**: The token MUST have "Bypass 2FA requirement" enabled. Standard automation tokens may fail with E403 errors if 2FA is enabled on your npm account.
306
+
307
+ ### Publishing Process
308
+
309
+ The package is published via the GitHub Actions workflow:
310
+
311
+ 1. Go to **Actions** → **Publish NPM Package**
312
+ 2. Click **Run workflow**
313
+ 3. Configure options:
314
+ - **dry_run**: `true` (test) or `false` (publish)
315
+ - **tag**: `latest`, `next`, or `beta`
316
+ - **package_path**: (default: `tools/ripp-cli`)
317
+ 4. Click **Run workflow**
318
+
319
+ **Workflow Features**:
320
+
321
+ - ✅ Validates package before publishing
322
+ - ✅ Checks version isn't already published
323
+ - ✅ Verifies npm authentication
324
+ - ✅ Runs tests and linting
325
+ - ✅ Dry-run mode for safe testing
326
+ - ✅ Detailed job summaries
327
+
328
+ **Version Management**:
329
+
330
+ - Bump version in `package.json` before publishing
331
+ - Follow [Semantic Versioning](https://semver.org/)
332
+ - Workflow will reject if version already exists
333
+
277
334
  ## Dependencies
278
335
 
279
336
  - **ajv**: JSON Schema validator
package/lib/evidence.js CHANGED
@@ -74,14 +74,15 @@ async function buildEvidencePack(cwd, config) {
74
74
  */
75
75
  async function scanFiles(cwd, evidenceConfig) {
76
76
  const files = [];
77
+ let excludedCount = 0;
77
78
  const { includeGlobs, excludeGlobs, maxFileSize } = evidenceConfig;
78
79
 
79
- // Build combined pattern
80
- const patterns = includeGlobs.map(pattern => path.join(cwd, pattern));
81
-
82
- for (const pattern of patterns) {
80
+ // Use glob with cwd option instead of joining paths
81
+ // This ensures patterns work correctly on all platforms
82
+ for (const pattern of includeGlobs) {
83
83
  const matches = await glob(pattern, {
84
- ignore: excludeGlobs.map(ex => path.join(cwd, ex)),
84
+ cwd: cwd,
85
+ ignore: excludeGlobs,
85
86
  nodir: true,
86
87
  absolute: true
87
88
  });
@@ -92,6 +93,7 @@ async function scanFiles(cwd, evidenceConfig) {
92
93
 
93
94
  // Skip files that are too large
94
95
  if (stats.size > maxFileSize) {
96
+ excludedCount++;
95
97
  continue;
96
98
  }
97
99
 
@@ -109,12 +111,13 @@ async function scanFiles(cwd, evidenceConfig) {
109
111
  });
110
112
  } catch (error) {
111
113
  // Skip files we can't read
114
+ excludedCount++;
112
115
  continue;
113
116
  }
114
117
  }
115
118
  }
116
119
 
117
- return files;
120
+ return { files, excludedCount };
118
121
  }
119
122
 
120
123
  /**
@@ -151,7 +154,10 @@ async function extractEvidence(files, cwd) {
151
154
  routes: [],
152
155
  schemas: [],
153
156
  auth: [],
154
- workflows: []
157
+ workflows: [],
158
+ projectType: detectProjectType(files),
159
+ keyInsights: extractKeyInsights(files, cwd),
160
+ codeSnippets: extractKeyCodeSnippets(files)
155
161
  };
156
162
 
157
163
  for (const file of files) {
@@ -362,6 +368,203 @@ function redactSecrets(text) {
362
368
  return redacted;
363
369
  }
364
370
 
371
+ /**
372
+ * Detect project type from evidence
373
+ */
374
+ function detectProjectType(files) {
375
+ const indicators = {
376
+ cli: 0,
377
+ webApp: 0,
378
+ api: 0,
379
+ library: 0,
380
+ protocol: 0
381
+ };
382
+
383
+ for (const file of files) {
384
+ const content = file.content ? file.content.toLowerCase() : '';
385
+ const path = file.path.toLowerCase();
386
+
387
+ // CLI tool indicators
388
+ if (path.includes('/bin/') || path.includes('cli') || path.includes('command'))
389
+ indicators.cli += 3;
390
+ if (
391
+ content.includes('commander') ||
392
+ content.includes('yargs') ||
393
+ content.includes('process.argv')
394
+ )
395
+ indicators.cli += 2;
396
+ if (path === 'package.json' && content.includes('"bin"')) indicators.cli += 4;
397
+
398
+ // Web app indicators
399
+ if (path.includes('app/') || path.includes('pages/') || path.includes('components/'))
400
+ indicators.webApp += 3;
401
+ if (content.includes('react') || content.includes('vue') || content.includes('angular'))
402
+ indicators.webApp += 2;
403
+ if (path.includes('index.html') || path.includes('app.tsx')) indicators.webApp += 3;
404
+
405
+ // API indicators
406
+ if (path.includes('api/') || path.includes('routes/') || path.includes('controllers/'))
407
+ indicators.api += 3;
408
+ if (content.includes('express') || content.includes('fastify') || content.includes('koa'))
409
+ indicators.api += 2;
410
+ if (content.includes('@app.route') || content.includes('@route') || content.includes('router.'))
411
+ indicators.api += 2;
412
+
413
+ // Library indicators
414
+ if (path === 'package.json' && !content.includes('"bin"') && !content.includes('"scripts"'))
415
+ indicators.library += 2;
416
+ if (path.includes('lib/') || path.includes('src/index')) indicators.library += 1;
417
+
418
+ // Protocol/spec indicators
419
+ if (path.includes('spec.md') || path.includes('protocol') || path.includes('rfc'))
420
+ indicators.protocol += 4;
421
+ if (path.includes('schema/') && path.includes('.json')) indicators.protocol += 2;
422
+ }
423
+
424
+ // Return type with highest score
425
+ const sorted = Object.entries(indicators).sort((a, b) => b[1] - a[1]);
426
+ return {
427
+ primary: sorted[0][0],
428
+ secondary: sorted[1][1] > 0 ? sorted[1][0] : null,
429
+ confidence: sorted[0][1] > 5 ? 'high' : sorted[0][1] > 2 ? 'medium' : 'low',
430
+ scores: indicators
431
+ };
432
+ }
433
+
434
+ /**
435
+ * Extract key insights from README, package.json, and main files
436
+ */
437
+ function extractKeyInsights(files, cwd) {
438
+ const insights = {
439
+ purpose: null,
440
+ description: null,
441
+ mainFeatures: [],
442
+ architecture: null
443
+ };
444
+
445
+ for (const file of files) {
446
+ const path = file.path.toLowerCase();
447
+ const content = file.content || '';
448
+
449
+ // Extract from README
450
+ if (path.includes('readme.md') || path.includes('readme.txt')) {
451
+ // Extract first paragraph as description
452
+ const lines = content.split('\n');
453
+ let desc = '';
454
+ for (const line of lines) {
455
+ if (line.trim() && !line.startsWith('#') && !line.startsWith('[')) {
456
+ desc += line.trim() + ' ';
457
+ if (desc.length > 200) break;
458
+ }
459
+ }
460
+ if (desc) insights.description = desc.slice(0, 300);
461
+
462
+ // Extract features (look for bullet points or numbered lists)
463
+ const featureMatch = content.match(
464
+ /(?:features|capabilities|includes)[\s\S]{0,50}?\n((?:[-*]\s.+\n)+)/i
465
+ );
466
+ if (featureMatch) {
467
+ insights.mainFeatures = featureMatch[1]
468
+ .split('\n')
469
+ .map(f => f.replace(/^[-*]\s+/, '').trim())
470
+ .filter(f => f.length > 0)
471
+ .slice(0, 5);
472
+ }
473
+ }
474
+
475
+ // Extract from package.json
476
+ if (path === 'package.json') {
477
+ try {
478
+ const pkg = JSON.parse(content);
479
+ if (pkg.description && !insights.description) {
480
+ insights.description = pkg.description;
481
+ }
482
+ if (pkg.name && !insights.purpose) {
483
+ insights.purpose = `${pkg.name}: ${pkg.description || 'No description'}`;
484
+ }
485
+ } catch (e) {
486
+ // Ignore JSON parse errors
487
+ }
488
+ }
489
+
490
+ // Extract from SPEC files
491
+ if (path.includes('spec.md') || path.includes('architecture.md')) {
492
+ const purposeMatch = content.match(/(?:purpose|goal|objective)[\s:]+([^\n]{50,300})/i);
493
+ if (purposeMatch && !insights.purpose) {
494
+ insights.purpose = purposeMatch[1].trim();
495
+ }
496
+ }
497
+ }
498
+
499
+ return insights;
500
+ }
501
+
502
+ /**
503
+ * Extract key code snippets (functions, classes, exports)
504
+ */
505
+ function extractKeyCodeSnippets(files) {
506
+ const snippets = [];
507
+ let count = 0;
508
+ const maxSnippets = 15;
509
+
510
+ for (const file of files) {
511
+ if (count >= maxSnippets) break;
512
+ if (!file.content || file.type !== 'source') continue;
513
+
514
+ const content = file.content;
515
+ const path = file.path;
516
+
517
+ // Extract function definitions (JavaScript/TypeScript)
518
+ const funcMatches = content.matchAll(
519
+ /(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\([^)]*\)\s*{([^}]{0,200})/g
520
+ );
521
+ for (const match of funcMatches) {
522
+ if (count >= maxSnippets) break;
523
+ snippets.push({
524
+ file: path,
525
+ type: 'function',
526
+ name: match[1],
527
+ snippet: match[0].substring(0, 150)
528
+ });
529
+ count++;
530
+ }
531
+
532
+ // Extract class definitions
533
+ const classMatches = content.matchAll(
534
+ /(?:export\s+)?class\s+(\w+)(?:\s+extends\s+\w+)?\s*{([^}]{0,150})/g
535
+ );
536
+ for (const match of classMatches) {
537
+ if (count >= maxSnippets) break;
538
+ snippets.push({
539
+ file: path,
540
+ type: 'class',
541
+ name: match[1],
542
+ snippet: match[0].substring(0, 150)
543
+ });
544
+ count++;
545
+ }
546
+
547
+ // Extract key comments (JSDoc, purpose statements)
548
+ const commentMatches = content.matchAll(/\/\*\*\s*\n\s*\*\s*([^\n]{30,200})/g);
549
+ for (const match of commentMatches) {
550
+ if (count >= maxSnippets) break;
551
+ if (
552
+ match[1].toLowerCase().includes('purpose') ||
553
+ match[1].toLowerCase().includes('description')
554
+ ) {
555
+ snippets.push({
556
+ file: path,
557
+ type: 'comment',
558
+ snippet: match[1].trim()
559
+ });
560
+ count++;
561
+ }
562
+ }
563
+ }
564
+
565
+ return snippets.slice(0, maxSnippets);
566
+ }
567
+
365
568
  module.exports = {
366
569
  buildEvidencePack,
367
570
  redactSecrets
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "ripp-cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Official CLI validator for Regenerative Intent Prompting Protocol (RIPP)",
5
5
  "main": "index.js",
6
6
  "bin": {
7
- "ripp": "./index.js"
7
+ "ripp": "index.js"
8
8
  },
9
9
  "scripts": {
10
10
  "test": "echo \"Warning: No tests specified\" && exit 0"
@@ -21,7 +21,7 @@
21
21
  "license": "MIT",
22
22
  "repository": {
23
23
  "type": "git",
24
- "url": "https://github.com/Dylan-Natter/ripp-protocol.git",
24
+ "url": "git+https://github.com/Dylan-Natter/ripp-protocol.git",
25
25
  "directory": "tools/ripp-cli"
26
26
  },
27
27
  "bugs": {
@@ -30,11 +30,21 @@
30
30
  "homepage": "https://dylan-natter.github.io/ripp-protocol",
31
31
  "dependencies": {
32
32
  "ajv": "^8.12.0",
33
- "ajv-formats": "^2.1.1",
34
- "glob": "^10.3.10",
33
+ "ajv-formats": "^3.0.1",
34
+ "glob": "^13.0.0",
35
35
  "js-yaml": "^4.1.0"
36
36
  },
37
37
  "engines": {
38
- "node": ">=18.0.0"
38
+ "node": ">=20.0.0"
39
+ },
40
+ "pkg": {
41
+ "assets": [
42
+ "../../schema/**/*"
43
+ ],
44
+ "targets": [
45
+ "node20-macos-arm64",
46
+ "node20-macos-x64"
47
+ ],
48
+ "outputPath": "dist"
39
49
  }
40
50
  }