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.
@@ -0,0 +1,374 @@
1
+ /**
2
+ * RIPP Packager
3
+ *
4
+ * Read-only artifact generator that creates normalized handoff artifacts.
5
+ *
6
+ * Guardrails:
7
+ * - Does NOT modify source RIPP packets
8
+ * - Validates input before packaging
9
+ * - Generates normalized, clean output
10
+ * - Supports multiple formats (JSON, YAML, Markdown, HTML)
11
+ */
12
+
13
+ const yaml = require('js-yaml');
14
+ const pkg = require('../package.json');
15
+
16
+ /**
17
+ * Package a RIPP packet into a normalized artifact
18
+ * @param {Object} packet - Validated RIPP packet
19
+ * @param {Object} options - Packaging options
20
+ * @returns {Object} Packaged result with metadata
21
+ */
22
+ function packagePacket(packet, options = {}) {
23
+ const normalized = normalizePacket(packet);
24
+
25
+ const packaged = {
26
+ _meta: {
27
+ packaged_at: new Date().toISOString(),
28
+ packaged_by: 'ripp-cli',
29
+ ripp_cli_version: pkg.version,
30
+ source_level: packet.level,
31
+ ripp_version: packet.ripp_version
32
+ },
33
+ ...normalized
34
+ };
35
+
36
+ // Add package version if provided
37
+ if (options.version) {
38
+ packaged._meta.package_version = options.version;
39
+ }
40
+
41
+ // Add git info if available
42
+ if (options.gitInfo) {
43
+ packaged._meta.git_commit = options.gitInfo.commit;
44
+ packaged._meta.git_branch = options.gitInfo.branch;
45
+ }
46
+
47
+ // Add source file info
48
+ if (options.sourceFile) {
49
+ packaged._meta.source_files = [options.sourceFile];
50
+ }
51
+
52
+ // Add validation status
53
+ if (options.validationStatus) {
54
+ packaged._meta.validation_status = options.validationStatus;
55
+ }
56
+
57
+ // Add validation error count if errors exist
58
+ if (options.validationErrors > 0) {
59
+ packaged._meta.validation_errors = options.validationErrors;
60
+ }
61
+
62
+ return packaged;
63
+ }
64
+
65
+ /**
66
+ * Normalize a RIPP packet by removing empty optional fields
67
+ * and organizing data consistently
68
+ */
69
+ function normalizePacket(packet) {
70
+ const normalized = {};
71
+
72
+ // Copy required metadata
73
+ normalized.ripp_version = packet.ripp_version;
74
+ normalized.packet_id = packet.packet_id;
75
+ normalized.title = packet.title;
76
+ normalized.created = packet.created;
77
+ normalized.updated = packet.updated;
78
+ normalized.status = packet.status;
79
+ normalized.level = packet.level;
80
+
81
+ // Copy optional version if present
82
+ if (packet.version) {
83
+ normalized.version = packet.version;
84
+ }
85
+
86
+ // Copy purpose (always required)
87
+ normalized.purpose = { ...packet.purpose };
88
+
89
+ // Copy ux_flow (always required)
90
+ normalized.ux_flow = packet.ux_flow.map(step => ({ ...step }));
91
+
92
+ // Copy data_contracts (always required)
93
+ normalized.data_contracts = {};
94
+ if (packet.data_contracts.inputs) {
95
+ normalized.data_contracts.inputs = packet.data_contracts.inputs.map(entity => ({ ...entity }));
96
+ }
97
+ if (packet.data_contracts.outputs) {
98
+ normalized.data_contracts.outputs = packet.data_contracts.outputs.map(entity => ({
99
+ ...entity
100
+ }));
101
+ }
102
+
103
+ // Copy Level 2+ fields if present
104
+ if (packet.api_contracts) {
105
+ normalized.api_contracts = packet.api_contracts.map(api => ({ ...api }));
106
+ }
107
+
108
+ if (packet.permissions) {
109
+ normalized.permissions = packet.permissions.map(perm => ({ ...perm }));
110
+ }
111
+
112
+ if (packet.failure_modes) {
113
+ normalized.failure_modes = packet.failure_modes.map(fm => ({ ...fm }));
114
+ }
115
+
116
+ // Copy Level 3 fields if present
117
+ if (packet.audit_events) {
118
+ normalized.audit_events = packet.audit_events.map(event => ({ ...event }));
119
+ }
120
+
121
+ if (packet.nfrs) {
122
+ normalized.nfrs = { ...packet.nfrs };
123
+ }
124
+
125
+ if (packet.acceptance_tests) {
126
+ normalized.acceptance_tests = packet.acceptance_tests.map(test => ({ ...test }));
127
+ }
128
+
129
+ return normalized;
130
+ }
131
+
132
+ /**
133
+ * Format packaged packet as JSON
134
+ */
135
+ function formatAsJson(packaged, options = {}) {
136
+ const indent = options.pretty !== false ? 2 : 0;
137
+ return JSON.stringify(packaged, null, indent);
138
+ }
139
+
140
+ /**
141
+ * Format packaged packet as YAML
142
+ */
143
+ function formatAsYaml(packaged, options = {}) {
144
+ return yaml.dump(packaged, {
145
+ indent: 2,
146
+ lineWidth: 100,
147
+ noRefs: true
148
+ });
149
+ }
150
+
151
+ /**
152
+ * Format packaged packet as Markdown
153
+ */
154
+ function formatAsMarkdown(packaged, options = {}) {
155
+ let md = '';
156
+
157
+ // Header
158
+ md += `# ${packaged.title}\n\n`;
159
+ md += `**Packet ID**: \`${packaged.packet_id}\` \n`;
160
+ md += `**Level**: ${packaged.level} \n`;
161
+ md += `**Status**: ${packaged.status} \n`;
162
+ md += `**Created**: ${packaged.created} \n`;
163
+ md += `**Updated**: ${packaged.updated} \n`;
164
+
165
+ if (packaged.version) {
166
+ md += `**Version**: ${packaged.version} \n`;
167
+ }
168
+
169
+ md += '\n---\n\n';
170
+
171
+ // Packaging metadata
172
+ md += '## Packaging Information\n\n';
173
+ md += `This document was packaged by \`${packaged._meta.packaged_by}\` on ${packaged._meta.packaged_at}.\n\n`;
174
+ md += '---\n\n';
175
+
176
+ // Purpose
177
+ md += '## Purpose\n\n';
178
+ md += `### Problem\n${packaged.purpose.problem}\n\n`;
179
+ md += `### Solution\n${packaged.purpose.solution}\n\n`;
180
+ md += `### Value\n${packaged.purpose.value}\n\n`;
181
+
182
+ if (packaged.purpose.out_of_scope) {
183
+ md += `### Out of Scope\n${packaged.purpose.out_of_scope}\n\n`;
184
+ }
185
+
186
+ if (packaged.purpose.assumptions && packaged.purpose.assumptions.length > 0) {
187
+ md += '### Assumptions\n\n';
188
+ packaged.purpose.assumptions.forEach(assumption => {
189
+ md += `- ${assumption}\n`;
190
+ });
191
+ md += '\n';
192
+ }
193
+
194
+ if (packaged.purpose.references && packaged.purpose.references.length > 0) {
195
+ md += '### References\n\n';
196
+ packaged.purpose.references.forEach(ref => {
197
+ md += `- [${ref.title}](${ref.url})\n`;
198
+ });
199
+ md += '\n';
200
+ }
201
+
202
+ // UX Flow
203
+ md += '## UX Flow\n\n';
204
+ packaged.ux_flow.forEach(step => {
205
+ md += `### Step ${step.step}: ${step.actor}\n\n`;
206
+ md += `**Action**: ${step.action}\n\n`;
207
+ if (step.trigger) md += `**Trigger**: ${step.trigger}\n\n`;
208
+ if (step.result) md += `**Result**: ${step.result}\n\n`;
209
+ if (step.condition) md += `**Condition**: ${step.condition}\n\n`;
210
+ });
211
+
212
+ // Data Contracts
213
+ md += '## Data Contracts\n\n';
214
+
215
+ if (packaged.data_contracts.inputs && packaged.data_contracts.inputs.length > 0) {
216
+ md += '### Inputs\n\n';
217
+ packaged.data_contracts.inputs.forEach(entity => {
218
+ md += `#### ${entity.name}\n\n`;
219
+ if (entity.description) md += `${entity.description}\n\n`;
220
+ md += '| Field | Type | Required | Description |\n';
221
+ md += '|-------|------|----------|-------------|\n';
222
+ entity.fields.forEach(field => {
223
+ md += `| ${field.name} | ${field.type} | ${field.required ? 'Yes' : 'No'} | ${field.description} |\n`;
224
+ });
225
+ md += '\n';
226
+ });
227
+ }
228
+
229
+ if (packaged.data_contracts.outputs && packaged.data_contracts.outputs.length > 0) {
230
+ md += '### Outputs\n\n';
231
+ packaged.data_contracts.outputs.forEach(entity => {
232
+ md += `#### ${entity.name}\n\n`;
233
+ if (entity.description) md += `${entity.description}\n\n`;
234
+ md += '| Field | Type | Required | Description |\n';
235
+ md += '|-------|------|----------|-------------|\n';
236
+ entity.fields.forEach(field => {
237
+ md += `| ${field.name} | ${field.type} | ${field.required ? 'Yes' : 'No'} | ${field.description} |\n`;
238
+ });
239
+ md += '\n';
240
+ });
241
+ }
242
+
243
+ // API Contracts (Level 2+)
244
+ if (packaged.api_contracts) {
245
+ md += '## API Contracts\n\n';
246
+ packaged.api_contracts.forEach(api => {
247
+ md += `### ${api.method} ${api.endpoint}\n\n`;
248
+ md += `**Purpose**: ${api.purpose}\n\n`;
249
+
250
+ if (api.request) {
251
+ md += '**Request**:\n';
252
+ if (api.request.content_type) md += `- Content-Type: ${api.request.content_type}\n`;
253
+ if (api.request.schema_ref) md += `- Schema: ${api.request.schema_ref}\n`;
254
+ md += '\n';
255
+ }
256
+
257
+ md += '**Response**:\n';
258
+ md += `- Success: ${api.response.success.status}\n`;
259
+ if (api.response.success.schema_ref) {
260
+ md += ` - Schema: ${api.response.success.schema_ref}\n`;
261
+ }
262
+ md += '\n**Errors**:\n';
263
+ api.response.errors.forEach(err => {
264
+ md += `- ${err.status}: ${err.description}\n`;
265
+ });
266
+ md += '\n';
267
+ });
268
+ }
269
+
270
+ // Permissions (Level 2+)
271
+ if (packaged.permissions) {
272
+ md += '## Permissions\n\n';
273
+ packaged.permissions.forEach(perm => {
274
+ md += `### ${perm.action}\n\n`;
275
+ md += `**Required Roles**: ${perm.required_roles.join(', ')}\n\n`;
276
+ if (perm.resource_scope) md += `**Resource Scope**: ${perm.resource_scope}\n\n`;
277
+ md += `**Description**: ${perm.description}\n\n`;
278
+ });
279
+ }
280
+
281
+ // Failure Modes (Level 2+)
282
+ if (packaged.failure_modes) {
283
+ md += '## Failure Modes\n\n';
284
+ packaged.failure_modes.forEach(fm => {
285
+ md += `### ${fm.scenario}\n\n`;
286
+ md += `**Impact**: ${fm.impact}\n\n`;
287
+ md += `**Handling**: ${fm.handling}\n\n`;
288
+ md += `**User Message**: "${fm.user_message}"\n\n`;
289
+ });
290
+ }
291
+
292
+ // Audit Events (Level 3)
293
+ if (packaged.audit_events) {
294
+ md += '## Audit Events\n\n';
295
+ packaged.audit_events.forEach(event => {
296
+ md += `### ${event.event}\n\n`;
297
+ md += `**Severity**: ${event.severity}\n\n`;
298
+ md += `**Purpose**: ${event.purpose}\n\n`;
299
+ md += `**Includes**: ${event.includes.join(', ')}\n\n`;
300
+ if (event.retention) md += `**Retention**: ${event.retention}\n\n`;
301
+ });
302
+ }
303
+
304
+ // NFRs (Level 3)
305
+ if (packaged.nfrs) {
306
+ md += '## Non-Functional Requirements\n\n';
307
+
308
+ if (packaged.nfrs.performance) {
309
+ md += '### Performance\n\n';
310
+ Object.keys(packaged.nfrs.performance).forEach(key => {
311
+ md += `- **${key}**: ${packaged.nfrs.performance[key]}\n`;
312
+ });
313
+ md += '\n';
314
+ }
315
+
316
+ if (packaged.nfrs.scalability) {
317
+ md += '### Scalability\n\n';
318
+ Object.keys(packaged.nfrs.scalability).forEach(key => {
319
+ md += `- **${key}**: ${packaged.nfrs.scalability[key]}\n`;
320
+ });
321
+ md += '\n';
322
+ }
323
+
324
+ if (packaged.nfrs.availability) {
325
+ md += '### Availability\n\n';
326
+ Object.keys(packaged.nfrs.availability).forEach(key => {
327
+ md += `- **${key}**: ${packaged.nfrs.availability[key]}\n`;
328
+ });
329
+ md += '\n';
330
+ }
331
+
332
+ if (packaged.nfrs.security) {
333
+ md += '### Security\n\n';
334
+ Object.keys(packaged.nfrs.security).forEach(key => {
335
+ md += `- **${key}**: ${packaged.nfrs.security[key]}\n`;
336
+ });
337
+ md += '\n';
338
+ }
339
+
340
+ if (packaged.nfrs.compliance) {
341
+ md += '### Compliance\n\n';
342
+ Object.keys(packaged.nfrs.compliance).forEach(key => {
343
+ md += `- **${key}**: ${packaged.nfrs.compliance[key]}\n`;
344
+ });
345
+ md += '\n';
346
+ }
347
+ }
348
+
349
+ // Acceptance Tests (Level 3)
350
+ if (packaged.acceptance_tests) {
351
+ md += '## Acceptance Tests\n\n';
352
+ packaged.acceptance_tests.forEach(test => {
353
+ md += `### ${test.test_id}: ${test.title}\n\n`;
354
+ md += `**Given**: ${test.given}\n\n`;
355
+ md += `**When**: ${test.when}\n\n`;
356
+ md += `**Then**: ${test.then}\n\n`;
357
+ md += '**Verification**:\n';
358
+ test.verification.forEach(v => {
359
+ md += `- ${v}\n`;
360
+ });
361
+ md += '\n';
362
+ });
363
+ }
364
+
365
+ return md;
366
+ }
367
+
368
+ module.exports = {
369
+ packagePacket,
370
+ normalizePacket,
371
+ formatAsJson,
372
+ formatAsYaml,
373
+ formatAsMarkdown
374
+ };
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "ripp-cli",
3
+ "version": "1.0.0",
4
+ "description": "Official CLI validator for Regenerative Intent Prompting Protocol (RIPP)",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "ripp": "./index.js"
8
+ },
9
+ "scripts": {
10
+ "test": "echo \"Warning: No tests specified\" && exit 0"
11
+ },
12
+ "keywords": [
13
+ "ripp",
14
+ "validator",
15
+ "specification",
16
+ "schema",
17
+ "yaml",
18
+ "json"
19
+ ],
20
+ "author": "RIPP Protocol Contributors",
21
+ "license": "MIT",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/Dylan-Natter/ripp-protocol.git",
25
+ "directory": "tools/ripp-cli"
26
+ },
27
+ "bugs": {
28
+ "url": "https://github.com/Dylan-Natter/ripp-protocol/issues"
29
+ },
30
+ "homepage": "https://dylan-natter.github.io/ripp-protocol",
31
+ "dependencies": {
32
+ "ajv": "^8.12.0",
33
+ "ajv-formats": "^2.1.1",
34
+ "glob": "^10.3.10",
35
+ "js-yaml": "^4.1.0"
36
+ },
37
+ "engines": {
38
+ "node": ">=18.0.0"
39
+ }
40
+ }