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/README.md +292 -0
- package/index.js +1350 -0
- package/lib/ai-provider.js +354 -0
- package/lib/analyzer.js +394 -0
- package/lib/build.js +338 -0
- package/lib/config.js +277 -0
- package/lib/confirmation.js +183 -0
- package/lib/discovery.js +119 -0
- package/lib/evidence.js +368 -0
- package/lib/init.js +488 -0
- package/lib/linter.js +309 -0
- package/lib/migrate.js +203 -0
- package/lib/packager.js +374 -0
- package/package.json +40 -0
package/lib/packager.js
ADDED
|
@@ -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
|
+
}
|