ripp-cli 1.0.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/lib/init.js ADDED
@@ -0,0 +1,488 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { createDefaultConfig } = require('./config');
4
+
5
+ /**
6
+ * Initialize RIPP in a repository
7
+ * Creates scaffolding for RIPP artifacts, GitHub Actions, and Intent Packages
8
+ */
9
+ function initRepository(options = {}) {
10
+ const force = options.force || false;
11
+ const results = {
12
+ created: [],
13
+ skipped: [],
14
+ errors: []
15
+ };
16
+
17
+ // 1. Create /ripp directory
18
+ try {
19
+ const rippDir = path.join(process.cwd(), 'ripp');
20
+ if (!fs.existsSync(rippDir)) {
21
+ fs.mkdirSync(rippDir, { recursive: true });
22
+ results.created.push('ripp/');
23
+ } else {
24
+ results.skipped.push('ripp/ (already exists)');
25
+ }
26
+ } catch (error) {
27
+ results.errors.push(`Failed to create ripp/: ${error.message}`);
28
+ }
29
+
30
+ // 2. Create /ripp/README.md
31
+ try {
32
+ const rippDir = path.join(process.cwd(), 'ripp');
33
+ const rippReadmePath = path.join(rippDir, 'README.md');
34
+ if (!fs.existsSync(rippReadmePath) || force) {
35
+ const rippReadme = generateRippReadme();
36
+ fs.writeFileSync(rippReadmePath, rippReadme);
37
+ results.created.push('ripp/README.md');
38
+ } else {
39
+ results.skipped.push('ripp/README.md (already exists, use --force to overwrite)');
40
+ }
41
+ } catch (error) {
42
+ results.errors.push(`Failed to create ripp/README.md: ${error.message}`);
43
+ }
44
+
45
+ // 3. Create /ripp/intent directory for intent RIPP files (formerly features/)
46
+ try {
47
+ const rippDir = path.join(process.cwd(), 'ripp');
48
+ const intentDir = path.join(rippDir, 'intent');
49
+ const legacyFeaturesDir = path.join(rippDir, 'features');
50
+
51
+ // Check for legacy features/ directory
52
+ if (fs.existsSync(legacyFeaturesDir) && !fs.existsSync(intentDir)) {
53
+ results.skipped.push(
54
+ 'ripp/features/ (legacy directory exists - use "ripp migrate" to update)'
55
+ );
56
+ } else if (!fs.existsSync(intentDir)) {
57
+ fs.mkdirSync(intentDir, { recursive: true });
58
+ results.created.push('ripp/intent/');
59
+ } else {
60
+ results.skipped.push('ripp/intent/ (already exists)');
61
+ }
62
+ } catch (error) {
63
+ results.errors.push(`Failed to create ripp/intent/: ${error.message}`);
64
+ }
65
+
66
+ // 4. Create /ripp/intent/.gitkeep
67
+ try {
68
+ const rippDir = path.join(process.cwd(), 'ripp');
69
+ const intentDir = path.join(rippDir, 'intent');
70
+ const gitkeepPath = path.join(intentDir, '.gitkeep');
71
+ if (fs.existsSync(intentDir) && (!fs.existsSync(gitkeepPath) || force)) {
72
+ fs.writeFileSync(gitkeepPath, '');
73
+ results.created.push('ripp/intent/.gitkeep');
74
+ } else if (fs.existsSync(gitkeepPath)) {
75
+ results.skipped.push('ripp/intent/.gitkeep (already exists, use --force to overwrite)');
76
+ }
77
+ } catch (error) {
78
+ results.errors.push(`Failed to create ripp/intent/.gitkeep: ${error.message}`);
79
+ }
80
+
81
+ // 5. Create /ripp/output directory (groups generated artifacts)
82
+ try {
83
+ const rippDir = path.join(process.cwd(), 'ripp');
84
+ const outputDir = path.join(rippDir, 'output');
85
+ if (!fs.existsSync(outputDir)) {
86
+ fs.mkdirSync(outputDir, { recursive: true });
87
+ results.created.push('ripp/output/');
88
+ } else {
89
+ results.skipped.push('ripp/output/ (already exists)');
90
+ }
91
+ } catch (error) {
92
+ results.errors.push(`Failed to create ripp/output/: ${error.message}`);
93
+ }
94
+
95
+ // 6. Create /ripp/output/handoffs directory (validated, ready-to-deliver packets)
96
+ try {
97
+ const rippDir = path.join(process.cwd(), 'ripp');
98
+ const handoffsDir = path.join(rippDir, 'output', 'handoffs');
99
+ const legacyHandoffsDir = path.join(rippDir, 'handoffs');
100
+
101
+ // Check for legacy handoffs/ directory
102
+ if (fs.existsSync(legacyHandoffsDir) && !fs.existsSync(handoffsDir)) {
103
+ results.skipped.push(
104
+ 'ripp/handoffs/ (legacy directory exists - use "ripp migrate" to update)'
105
+ );
106
+ } else if (!fs.existsSync(handoffsDir)) {
107
+ fs.mkdirSync(handoffsDir, { recursive: true });
108
+ results.created.push('ripp/output/handoffs/');
109
+ } else {
110
+ results.skipped.push('ripp/output/handoffs/ (already exists)');
111
+ }
112
+ } catch (error) {
113
+ results.errors.push(`Failed to create ripp/output/handoffs/: ${error.message}`);
114
+ }
115
+
116
+ // 7. Create /ripp/output/handoffs/.gitkeep
117
+ try {
118
+ const rippDir = path.join(process.cwd(), 'ripp');
119
+ const handoffsDir = path.join(rippDir, 'output', 'handoffs');
120
+ const gitkeepPath = path.join(handoffsDir, '.gitkeep');
121
+ if (fs.existsSync(handoffsDir) && (!fs.existsSync(gitkeepPath) || force)) {
122
+ fs.writeFileSync(gitkeepPath, '');
123
+ results.created.push('ripp/output/handoffs/.gitkeep');
124
+ } else if (fs.existsSync(gitkeepPath)) {
125
+ results.skipped.push(
126
+ 'ripp/output/handoffs/.gitkeep (already exists, use --force to overwrite)'
127
+ );
128
+ }
129
+ } catch (error) {
130
+ results.errors.push(`Failed to create ripp/output/handoffs/.gitkeep: ${error.message}`);
131
+ }
132
+
133
+ // 8. Create /ripp/output/packages directory (generated outputs, gitignored)
134
+ try {
135
+ const rippDir = path.join(process.cwd(), 'ripp');
136
+ const packagesDir = path.join(rippDir, 'output', 'packages');
137
+ const legacyPackagesDir = path.join(rippDir, 'packages');
138
+
139
+ // Check for legacy packages/ directory
140
+ if (fs.existsSync(legacyPackagesDir) && !fs.existsSync(packagesDir)) {
141
+ results.skipped.push(
142
+ 'ripp/packages/ (legacy directory exists - use "ripp migrate" to update)'
143
+ );
144
+ } else if (!fs.existsSync(packagesDir)) {
145
+ fs.mkdirSync(packagesDir, { recursive: true });
146
+ results.created.push('ripp/output/packages/');
147
+ } else {
148
+ results.skipped.push('ripp/output/packages/ (already exists)');
149
+ }
150
+ } catch (error) {
151
+ results.errors.push(`Failed to create ripp/output/packages/: ${error.message}`);
152
+ }
153
+
154
+ // 9. Create /ripp/output/packages/.gitkeep
155
+ try {
156
+ const rippDir = path.join(process.cwd(), 'ripp');
157
+ const packagesDir = path.join(rippDir, 'output', 'packages');
158
+ const gitkeepPath = path.join(packagesDir, '.gitkeep');
159
+ if (fs.existsSync(packagesDir) && (!fs.existsSync(gitkeepPath) || force)) {
160
+ fs.writeFileSync(gitkeepPath, '');
161
+ results.created.push('ripp/output/packages/.gitkeep');
162
+ } else if (fs.existsSync(gitkeepPath)) {
163
+ results.skipped.push(
164
+ 'ripp/output/packages/.gitkeep (already exists, use --force to overwrite)'
165
+ );
166
+ }
167
+ } catch (error) {
168
+ results.errors.push(`Failed to create ripp/output/packages/.gitkeep: ${error.message}`);
169
+ }
170
+
171
+ // 10. Create /ripp/.gitignore
172
+ try {
173
+ const rippDir = path.join(process.cwd(), 'ripp');
174
+ const gitignorePath = path.join(rippDir, '.gitignore');
175
+ if (!fs.existsSync(gitignorePath) || force) {
176
+ const gitignoreContent = generateRippGitignore();
177
+ fs.writeFileSync(gitignorePath, gitignoreContent);
178
+ results.created.push('ripp/.gitignore');
179
+ } else {
180
+ results.skipped.push('ripp/.gitignore (already exists, use --force to overwrite)');
181
+ }
182
+ } catch (error) {
183
+ results.errors.push(`Failed to create ripp/.gitignore: ${error.message}`);
184
+ }
185
+
186
+ // 11. Create .github/workflows directory
187
+ try {
188
+ const workflowsDir = path.join(process.cwd(), '.github', 'workflows');
189
+ if (!fs.existsSync(workflowsDir)) {
190
+ fs.mkdirSync(workflowsDir, { recursive: true });
191
+ results.created.push('.github/workflows/');
192
+ } else {
193
+ results.skipped.push('.github/workflows/ (already exists)');
194
+ }
195
+ } catch (error) {
196
+ results.errors.push(`Failed to create .github/workflows/: ${error.message}`);
197
+ }
198
+
199
+ // 12. Create GitHub Action workflow
200
+ try {
201
+ const workflowsDir = path.join(process.cwd(), '.github', 'workflows');
202
+ const workflowPath = path.join(workflowsDir, 'ripp-validate.yml');
203
+ if (!fs.existsSync(workflowPath) || force) {
204
+ const workflow = generateGitHubActionWorkflow();
205
+ fs.writeFileSync(workflowPath, workflow);
206
+ results.created.push('.github/workflows/ripp-validate.yml');
207
+ } else {
208
+ results.skipped.push(
209
+ '.github/workflows/ripp-validate.yml (already exists, use --force to overwrite)'
210
+ );
211
+ }
212
+ } catch (error) {
213
+ results.errors.push(`Failed to create .github/workflows/ripp-validate.yml: ${error.message}`);
214
+ }
215
+
216
+ // 13. Create .ripp/config.yaml (vNext)
217
+ try {
218
+ const configResult = createDefaultConfig(process.cwd(), { force });
219
+ if (configResult.created) {
220
+ results.created.push('.ripp/config.yaml');
221
+ } else {
222
+ results.skipped.push('.ripp/config.yaml (already exists, use --force to overwrite)');
223
+ }
224
+ } catch (error) {
225
+ results.errors.push(`Failed to create .ripp/config.yaml: ${error.message}`);
226
+ }
227
+
228
+ return results;
229
+ }
230
+
231
+ function generateRippReadme() {
232
+ return `# RIPP - Regenerative Intent Prompting Protocol
233
+
234
+ This directory contains RIPP (Regenerative Intent Prompting Protocol) packets for this repository.
235
+
236
+ ## What is RIPP?
237
+
238
+ RIPP is a **structured specification format** for capturing feature requirements, design decisions, and implementation contracts in a machine-readable, human-reviewable format.
239
+
240
+ A RIPP packet is a YAML or JSON document that describes:
241
+ - **What** a feature does and **why** it exists
242
+ - **How** users interact with it
243
+ - **What data** it produces and consumes
244
+ - **Who** can do what (permissions)
245
+ - **What can go wrong** and how to handle it
246
+ - **How to verify** correctness (acceptance tests)
247
+
248
+ **Key Benefits:**
249
+ - Preserve original intent during implementation
250
+ - Create reviewable, versioned contracts
251
+ - Enable automated validation
252
+ - Bridge prototyping and production teams
253
+ - Prevent intent erosion and undocumented assumptions
254
+
255
+ ## What RIPP is NOT
256
+
257
+ **RIPP is not a code migration tool:**
258
+ - Does not transform prototype code into production code
259
+ - Does not provide refactoring or modernization capabilities
260
+ - Does not attempt lift-and-shift operations
261
+
262
+ **RIPP is not a code generator:**
263
+ - RIPP is a specification format, not a code generation framework
264
+ - While tools MAY generate code from RIPP packets, this is optional
265
+ - RIPP does not prescribe implementation details
266
+
267
+ **RIPP is not a production hardening helper:**
268
+ - Does not scan code for vulnerabilities
269
+ - Does not inject security controls into existing code
270
+ - Does not automatically make prototype code production-ready
271
+
272
+ ## Directory Structure
273
+
274
+ \`\`\`
275
+ ripp/
276
+ ├── README.md # This file
277
+ ├── .gitignore # Ignores generated packages
278
+ ├── intent/ # Work-in-progress RIPP packets (human-authored intent)
279
+ │ ├── auth-login.ripp.yaml
280
+ │ ├── user-profile.ripp.yaml
281
+ │ └── ...
282
+ └── output/ # Generated artifacts (machine-generated)
283
+ ├── handoffs/ # Validated, approved RIPP packets ready for delivery
284
+ │ ├── sample.ripp.yaml
285
+ │ └── ...
286
+ └── packages/ # Generated output formats (.zip, .md, .json) [gitignored]
287
+ ├── handoff.zip
288
+ ├── sample.md
289
+ └── ...
290
+ \`\`\`
291
+
292
+ **Directory Naming:**
293
+ - \`intent/\` — Human-authored intent specifications (source of truth)
294
+ - \`output/\` — Machine-generated artifacts (derived from intent)
295
+ - \`handoffs/\` — Finalized packets ready for delivery
296
+ - \`packages/\` — Packaged outputs (markdown, JSON, ZIP)
297
+
298
+ **Legacy directories** (\`features/\`, \`handoffs/\`, \`packages/\` at root level) are supported for backward compatibility. Use \`ripp migrate\` to update to the new structure.
299
+
300
+ ## What is RIPP vs What are Artifacts?
301
+
302
+ **RIPP** is the protocol specification — the format and rules for capturing intent.
303
+
304
+ **Artifacts** in RIPP context means:
305
+ - The conceptual outputs of the specification process
306
+ - NOT a specific directory named "artifacts"
307
+ - Includes: intent packets, handoffs, packages, and derived documentation
308
+
309
+ This repository uses **explicit directory names** (\`intent/\`, \`output/\`) to avoid confusion about what goes where.
310
+
311
+ ## Workflow
312
+
313
+ ### 1. Create new packets in \`intent/\`
314
+
315
+ Place your work-in-progress RIPP packets in \`ripp/intent/\` with the naming convention:
316
+
317
+ \`\`\`
318
+ <feature-name>.ripp.yaml
319
+ \`\`\`
320
+
321
+ Example: \`auth-login.ripp.yaml\`
322
+
323
+ ### 2. Minimum required structure (Level 1)
324
+
325
+ \`\`\`yaml
326
+ ripp_version: "1.0"
327
+ packet_id: auth-login
328
+ title: User Authentication and Login
329
+ created: 2024-01-15
330
+ updated: 2024-01-15
331
+ status: draft
332
+ level: 1
333
+
334
+ purpose:
335
+ problem: "Users need a secure way to authenticate and access their accounts"
336
+ solution: "Implement JWT-based authentication with email/password login"
337
+ value: "Enables secure user access and personalized experiences"
338
+
339
+ ux_flow:
340
+ - step: "User navigates to login page"
341
+ - step: "User enters email and password"
342
+ - step: "System validates credentials"
343
+ - step: "User is redirected to dashboard"
344
+
345
+ data_contracts:
346
+ inputs:
347
+ - name: email
348
+ type: string
349
+ format: email
350
+ required: true
351
+ - name: password
352
+ type: string
353
+ required: true
354
+ outputs:
355
+ - name: auth_token
356
+ type: string
357
+ description: "JWT access token"
358
+ \`\`\`
359
+
360
+ ### 3. Validate your packets
361
+
362
+ \`\`\`bash
363
+ # Validate all packets in intent/
364
+ ripp validate ripp/intent/
365
+
366
+ # Validate a specific file
367
+ ripp validate ripp/intent/auth-login.ripp.yaml
368
+
369
+ # Enforce minimum conformance level
370
+ ripp validate ripp/intent/ --min-level 2
371
+
372
+ # Also supports legacy paths (features/, handoffs/, packages/)
373
+ ripp validate ripp/features/ # backward compatible
374
+ \`\`\`
375
+
376
+ ### 4. Lint for best practices
377
+
378
+ \`\`\`bash
379
+ ripp lint ripp/intent/
380
+ ripp lint ripp/intent/ --strict
381
+ \`\`\`
382
+
383
+ ### 5. Move validated packets to \`output/handoffs/\`
384
+
385
+ When a packet is validated and approved, move it to the handoffs directory:
386
+
387
+ \`\`\`bash
388
+ mv ripp/intent/my-feature.ripp.yaml ripp/output/handoffs/
389
+ \`\`\`
390
+
391
+ ### 6. Package for delivery
392
+
393
+ \`\`\`bash
394
+ # Package a single packet
395
+ ripp package --in ripp/output/handoffs/my-feature.ripp.yaml --out ripp/output/packages/handoff.md
396
+
397
+ # Package to JSON
398
+ ripp package --in ripp/output/handoffs/my-feature.ripp.yaml --out ripp/output/packages/packaged.json
399
+ \`\`\`
400
+
401
+ ### 7. Deliver the package
402
+
403
+ Share the generated files in \`ripp/output/packages/\` with receiving teams or upload to artifact repositories.
404
+
405
+ ## Validation
406
+
407
+ This repository includes automated RIPP validation via GitHub Actions.
408
+
409
+ Every pull request that modifies RIPP files will be automatically validated.
410
+
411
+ ## RIPP Levels
412
+
413
+ RIPP supports three conformance levels:
414
+
415
+ - **Level 1**: Basic (purpose, ux_flow, data_contracts)
416
+ - **Level 2**: Standard (adds api_contracts, permissions, failure_modes)
417
+ - **Level 3**: Complete (adds audit_events, nfrs, acceptance_tests)
418
+
419
+ Choose the level appropriate for your feature's complexity and criticality.
420
+
421
+ ## Resources
422
+
423
+ - **CLI Documentation**: \`ripp --help\`
424
+ - **Protocol Website**: https://dylan-natter.github.io/ripp-protocol
425
+ - **GitHub Repository**: https://github.com/Dylan-Natter/ripp-protocol
426
+ - **Issues & Support**: https://github.com/Dylan-Natter/ripp-protocol/issues
427
+
428
+ ---
429
+
430
+ **Write specs first. Ship with confidence.**
431
+ `;
432
+ }
433
+
434
+ function generateRippGitignore() {
435
+ return `# Generated packages (deliverables)
436
+ # These are built artifacts that should not be committed
437
+ output/packages/
438
+
439
+ # Keep directory structure
440
+ !.gitkeep
441
+ `;
442
+ }
443
+
444
+ function generateGitHubActionWorkflow() {
445
+ return `name: Validate RIPP Packets
446
+
447
+ on:
448
+ pull_request:
449
+ paths:
450
+ - 'ripp/**/*.ripp.yaml'
451
+ - 'ripp/**/*.ripp.json'
452
+ workflow_dispatch:
453
+
454
+ jobs:
455
+ validate:
456
+ name: Validate RIPP Packets
457
+ runs-on: ubuntu-latest
458
+
459
+ permissions:
460
+ contents: read
461
+
462
+ steps:
463
+ - name: Checkout repository
464
+ uses: actions/checkout@v4
465
+
466
+ - name: Setup Node.js
467
+ uses: actions/setup-node@v4
468
+ with:
469
+ node-version: '18'
470
+
471
+ - name: Install dependencies
472
+ run: npm ci
473
+
474
+ - name: Validate RIPP packets
475
+ run: npm run ripp:validate
476
+
477
+ - name: Summary
478
+ if: always()
479
+ run: echo "✅ RIPP validation complete"
480
+ `;
481
+ }
482
+
483
+ module.exports = {
484
+ initRepository,
485
+ generateRippReadme,
486
+ generateRippGitignore,
487
+ generateGitHubActionWorkflow
488
+ };