ripp-cli 1.0.0 → 1.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/CHANGELOG.md +63 -0
- package/README.md +57 -0
- package/index.js +138 -8
- package/lib/build.js +116 -10
- package/lib/checklist-parser.js +224 -0
- package/lib/config.js +2 -3
- package/lib/confirmation.js +77 -6
- package/lib/doctor.js +370 -0
- package/lib/evidence.js +210 -7
- package/lib/metrics.js +410 -0
- package/lib/packager.js +26 -14
- package/package.json +25 -8
- package/schema/evidence-pack.schema.json +201 -0
- package/schema/intent-candidates.schema.json +109 -0
- package/schema/intent-confirmed.schema.json +85 -0
- package/schema/ripp-1.0.schema.json +543 -0
- package/schema/ripp-config.schema.json +104 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
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.2.1] (2025-12-24)
|
|
9
|
+
|
|
10
|
+
### Bug Fixes
|
|
11
|
+
|
|
12
|
+
* **cli:** bundle schema files in npm package to fix validation when installed globally ([ed94409](https://github.com/Dylan-Natter/ripp-protocol/commit/ed94409))
|
|
13
|
+
- Schema files are now included in the npm package under `schema/` directory
|
|
14
|
+
- Updated schema loading paths to use bundled schemas instead of parent directory
|
|
15
|
+
- Fixes post-publish smoke test failures from version 1.2.0
|
|
16
|
+
* **cli:** fix checklist generation and parsing bugs in `ripp confirm` command
|
|
17
|
+
- Fixed extraction of content fields (purpose, ux_flow, data_contracts, etc.) from candidates
|
|
18
|
+
- Use 'purpose' or 'full-packet' as section name instead of 'unknown'
|
|
19
|
+
- Add 'full-packet' to valid section types in checklist parser
|
|
20
|
+
- Fixes empty YAML blocks in generated checklists
|
|
21
|
+
- Fixes 'Unknown section type' error when building from checklist
|
|
22
|
+
|
|
23
|
+
## [1.2.0](https://github.com/Dylan-Natter/ripp-protocol/compare/ripp-cli-v1.1.0...ripp-cli-v1.2.0) (2025-12-23)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Features
|
|
27
|
+
|
|
28
|
+
* **cli:** enhance CLI description to include tooling capabilities ([8f97965](https://github.com/Dylan-Natter/ripp-protocol/commit/8f97965379bbb24287b8d69bb9d4e5af16bca1df))
|
|
29
|
+
|
|
30
|
+
## [1.1.0](https://github.com/Dylan-Natter/ripp-protocol/compare/ripp-cli-v1.0.1...ripp-cli-v1.1.0) (2025-12-23)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
### Features
|
|
34
|
+
|
|
35
|
+
* **cli:** add metrics, doctor, and enhanced workflow commands ([b5c413d](https://github.com/Dylan-Natter/ripp-protocol/commit/b5c413d335088350527e6e9aa8cd6fa1f0debf9f))
|
|
36
|
+
* **vscode:** add metrics command and enhanced workflow integration ([24a3cd0](https://github.com/Dylan-Natter/ripp-protocol/commit/24a3cd02f3657958321cc8f04ca55f487853205c))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
### Documentation
|
|
40
|
+
|
|
41
|
+
* upgrade reference implementation to Level 2 ([a4b18e3](https://github.com/Dylan-Natter/ripp-protocol/commit/a4b18e320d95fbb2754d4eb07dafc8da00eef673))
|
|
42
|
+
|
|
43
|
+
## [1.0.1] - 2025-12-22
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
- Version bump to enable npm publishing of updates
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
|
|
51
|
+
- Synchronized package-lock.json Node.js engine requirement (>=20.0.0) with package.json
|
|
52
|
+
|
|
53
|
+
## [1.0.0] - Initial Release
|
|
54
|
+
|
|
55
|
+
### Features
|
|
56
|
+
|
|
57
|
+
- RIPP packet validation against JSON schema
|
|
58
|
+
- Support for YAML and JSON input formats
|
|
59
|
+
- Multi-file validation with glob patterns
|
|
60
|
+
- Linting with severity levels (error, warning, info)
|
|
61
|
+
- Strict mode for enhanced validation
|
|
62
|
+
- Evidence package generation for handoffs
|
|
63
|
+
- 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/index.js
CHANGED
|
@@ -17,6 +17,14 @@ const { discoverIntent } = require('./lib/discovery');
|
|
|
17
17
|
const { confirmIntent } = require('./lib/confirmation');
|
|
18
18
|
const { buildCanonicalArtifacts } = require('./lib/build');
|
|
19
19
|
const { migrateDirectoryStructure } = require('./lib/migrate');
|
|
20
|
+
const {
|
|
21
|
+
gatherMetrics,
|
|
22
|
+
formatMetricsText,
|
|
23
|
+
loadMetricsHistory,
|
|
24
|
+
saveMetricsHistory,
|
|
25
|
+
formatMetricsHistory
|
|
26
|
+
} = require('./lib/metrics');
|
|
27
|
+
const { runHealthChecks, formatHealthCheckText } = require('./lib/doctor');
|
|
20
28
|
|
|
21
29
|
// ANSI color codes
|
|
22
30
|
const colors = {
|
|
@@ -38,7 +46,7 @@ function log(color, symbol, message) {
|
|
|
38
46
|
}
|
|
39
47
|
|
|
40
48
|
function loadSchema() {
|
|
41
|
-
const schemaPath = path.join(__dirname, '
|
|
49
|
+
const schemaPath = path.join(__dirname, 'schema/ripp-1.0.schema.json');
|
|
42
50
|
try {
|
|
43
51
|
return JSON.parse(fs.readFileSync(schemaPath, 'utf8'));
|
|
44
52
|
} catch (error) {
|
|
@@ -277,6 +285,8 @@ ${colors.blue}vNext - Intent Discovery Mode:${colors.reset}
|
|
|
277
285
|
ripp discover Infer candidate intent (requires AI enabled)
|
|
278
286
|
ripp confirm Confirm candidate intent (interactive)
|
|
279
287
|
ripp build Build canonical RIPP artifacts from confirmed intent
|
|
288
|
+
ripp metrics Display workflow analytics and health metrics
|
|
289
|
+
ripp doctor Run health checks and diagnostics
|
|
280
290
|
|
|
281
291
|
ripp --help Show this help message
|
|
282
292
|
ripp --version Show version
|
|
@@ -300,9 +310,15 @@ ${colors.green}Confirm Options:${colors.reset}
|
|
|
300
310
|
--user <id> User identifier for confirmation
|
|
301
311
|
|
|
302
312
|
${colors.green}Build Options:${colors.reset}
|
|
313
|
+
--from-checklist Build from intent.checklist.md (after manual review)
|
|
303
314
|
--packet-id <id> Packet ID for generated RIPP (default: discovered-intent)
|
|
304
315
|
--title <title> Title for generated RIPP packet
|
|
305
316
|
--output-name <file> Output file name (default: handoff.ripp.yaml)
|
|
317
|
+
--user <id> User identifier for confirmation tracking
|
|
318
|
+
|
|
319
|
+
${colors.green}Metrics Options:${colors.reset}
|
|
320
|
+
--report Write metrics to .ripp/metrics.json
|
|
321
|
+
--history Show metrics trends from previous runs
|
|
306
322
|
|
|
307
323
|
${colors.green}Validate Options:${colors.reset}
|
|
308
324
|
--min-level <1|2|3> Enforce minimum RIPP level
|
|
@@ -317,6 +333,7 @@ ${colors.green}Package Options:${colors.reset}
|
|
|
317
333
|
--in <file> Input RIPP packet file (required)
|
|
318
334
|
--out <file> Output file path (required)
|
|
319
335
|
--format <json|yaml|md> Output format (auto-detected from extension)
|
|
336
|
+
--single Generate consolidated single-file markdown
|
|
320
337
|
--package-version <version> Version string for the package (e.g., 1.0.0)
|
|
321
338
|
--force Overwrite existing output file without versioning
|
|
322
339
|
--skip-validation Skip validation entirely
|
|
@@ -339,6 +356,7 @@ ${colors.green}Examples:${colors.reset}
|
|
|
339
356
|
ripp lint ripp/intent/
|
|
340
357
|
ripp lint ripp/intent/ --strict
|
|
341
358
|
ripp package --in feature.ripp.yaml --out handoff.md
|
|
359
|
+
ripp package --in feature.ripp.yaml --out handoff.md --single
|
|
342
360
|
ripp package --in feature.ripp.yaml --out handoff.md --package-version 1.0.0
|
|
343
361
|
ripp package --in feature.ripp.yaml --out handoff.md --force
|
|
344
362
|
ripp package --in feature.ripp.yaml --out handoff.md --warn-on-invalid
|
|
@@ -351,7 +369,10 @@ ${colors.blue}Intent Discovery Examples:${colors.reset}
|
|
|
351
369
|
ripp evidence build
|
|
352
370
|
RIPP_AI_ENABLED=true ripp discover --target-level 2
|
|
353
371
|
ripp confirm --interactive
|
|
354
|
-
ripp
|
|
372
|
+
ripp confirm --checklist
|
|
373
|
+
ripp build
|
|
374
|
+
ripp build --from-checklist
|
|
375
|
+
ripp build --from-checklist --packet-id my-feature --title "My Feature"
|
|
355
376
|
|
|
356
377
|
${colors.gray}Note: Legacy paths (features/, handoffs/, packages/) are supported for backward compatibility.${colors.reset}
|
|
357
378
|
|
|
@@ -430,7 +451,7 @@ function getGitInfo() {
|
|
|
430
451
|
commit,
|
|
431
452
|
branch
|
|
432
453
|
};
|
|
433
|
-
} catch
|
|
454
|
+
} catch {
|
|
434
455
|
// Not in a git repo or git not available
|
|
435
456
|
return null;
|
|
436
457
|
}
|
|
@@ -565,6 +586,10 @@ async function main() {
|
|
|
565
586
|
await handleConfirmCommand(args);
|
|
566
587
|
} else if (command === 'build') {
|
|
567
588
|
await handleBuildCommand(args);
|
|
589
|
+
} else if (command === 'metrics') {
|
|
590
|
+
await handleMetricsCommand(args);
|
|
591
|
+
} else if (command === 'doctor') {
|
|
592
|
+
handleDoctorCommand(args);
|
|
568
593
|
} else {
|
|
569
594
|
console.error(`${colors.red}Error: Unknown command '${command}'${colors.reset}`);
|
|
570
595
|
console.error("Run 'ripp --help' for usage information.");
|
|
@@ -860,7 +885,8 @@ async function handlePackageCommand(args) {
|
|
|
860
885
|
version: null,
|
|
861
886
|
force: args.includes('--force'),
|
|
862
887
|
skipValidation: args.includes('--skip-validation'),
|
|
863
|
-
warnOnInvalid: args.includes('--warn-on-invalid')
|
|
888
|
+
warnOnInvalid: args.includes('--warn-on-invalid'),
|
|
889
|
+
single: args.includes('--single')
|
|
864
890
|
};
|
|
865
891
|
|
|
866
892
|
const inIndex = args.indexOf('--in');
|
|
@@ -1011,7 +1037,7 @@ async function handlePackageCommand(args) {
|
|
|
1011
1037
|
} else if (options.format === 'yaml') {
|
|
1012
1038
|
output = formatAsYaml(packaged);
|
|
1013
1039
|
} else if (options.format === 'md') {
|
|
1014
|
-
output = formatAsMarkdown(packaged);
|
|
1040
|
+
output = formatAsMarkdown(packaged, { single: options.single });
|
|
1015
1041
|
}
|
|
1016
1042
|
|
|
1017
1043
|
// Write to output file
|
|
@@ -1272,7 +1298,7 @@ async function handleConfirmCommand(args) {
|
|
|
1272
1298
|
console.log(' 1. Review and edit the checklist');
|
|
1273
1299
|
console.log(' 2. Mark accepted candidates with [x]');
|
|
1274
1300
|
console.log(' 3. Save the file');
|
|
1275
|
-
console.log(' 4. Run "ripp build" to compile confirmed intent');
|
|
1301
|
+
console.log(' 4. Run "ripp build --from-checklist" to compile confirmed intent');
|
|
1276
1302
|
console.log('');
|
|
1277
1303
|
} else {
|
|
1278
1304
|
log(colors.green, '✓', 'Intent confirmation complete');
|
|
@@ -1302,7 +1328,8 @@ async function handleBuildCommand(args) {
|
|
|
1302
1328
|
const options = {
|
|
1303
1329
|
packetId: null,
|
|
1304
1330
|
title: null,
|
|
1305
|
-
outputName: null
|
|
1331
|
+
outputName: null,
|
|
1332
|
+
fromChecklist: args.includes('--from-checklist')
|
|
1306
1333
|
};
|
|
1307
1334
|
|
|
1308
1335
|
const packetIdIndex = args.indexOf('--packet-id');
|
|
@@ -1320,11 +1347,32 @@ async function handleBuildCommand(args) {
|
|
|
1320
1347
|
options.outputName = args[outputNameIndex + 1];
|
|
1321
1348
|
}
|
|
1322
1349
|
|
|
1323
|
-
|
|
1350
|
+
const userIndex = args.indexOf('--user');
|
|
1351
|
+
if (userIndex !== -1 && args[userIndex + 1]) {
|
|
1352
|
+
options.user = args[userIndex + 1];
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
if (options.fromChecklist) {
|
|
1356
|
+
console.log(`${colors.blue}Building from checklist...${colors.reset}\n`);
|
|
1357
|
+
} else {
|
|
1358
|
+
console.log(`${colors.blue}Building canonical RIPP artifacts...${colors.reset}\n`);
|
|
1359
|
+
}
|
|
1324
1360
|
|
|
1325
1361
|
try {
|
|
1326
1362
|
const result = buildCanonicalArtifacts(cwd, options);
|
|
1327
1363
|
|
|
1364
|
+
// Display summary of checklist processing if applicable
|
|
1365
|
+
if (options.fromChecklist && options._validationResults) {
|
|
1366
|
+
const vr = options._validationResults;
|
|
1367
|
+
console.log(`${colors.blue}Checklist Summary:${colors.reset}`);
|
|
1368
|
+
console.log(` ${colors.gray}Total checked: ${vr.totalChecked}${colors.reset}`);
|
|
1369
|
+
console.log(` ${colors.green}✓ Accepted: ${vr.accepted}${colors.reset}`);
|
|
1370
|
+
if (vr.rejected > 0) {
|
|
1371
|
+
console.log(` ${colors.yellow}⚠ Rejected: ${vr.rejected}${colors.reset}`);
|
|
1372
|
+
}
|
|
1373
|
+
console.log('');
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1328
1376
|
log(colors.green, '✓', 'Build complete');
|
|
1329
1377
|
console.log(` ${colors.gray}RIPP Packet: ${result.packetPath}${colors.reset}`);
|
|
1330
1378
|
console.log(` ${colors.gray}Handoff MD: ${result.markdownPath}${colors.reset}`);
|
|
@@ -1340,6 +1388,88 @@ async function handleBuildCommand(args) {
|
|
|
1340
1388
|
process.exit(0);
|
|
1341
1389
|
} catch (error) {
|
|
1342
1390
|
console.error(`${colors.red}Error: ${error.message}${colors.reset}`);
|
|
1391
|
+
if (options.fromChecklist) {
|
|
1392
|
+
console.log('');
|
|
1393
|
+
console.log(`${colors.blue}Troubleshooting:${colors.reset}`);
|
|
1394
|
+
console.log(' 1. Verify checklist file exists: .ripp/intent.checklist.md');
|
|
1395
|
+
console.log(' 2. Ensure at least one candidate is marked with [x]');
|
|
1396
|
+
console.log(' 3. Check YAML blocks for syntax errors');
|
|
1397
|
+
console.log(' 4. Run "ripp confirm --checklist" to regenerate checklist');
|
|
1398
|
+
}
|
|
1399
|
+
console.log('');
|
|
1400
|
+
process.exit(1);
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
async function handleMetricsCommand(args) {
|
|
1405
|
+
const cwd = process.cwd();
|
|
1406
|
+
const rippDir = path.join(cwd, '.ripp');
|
|
1407
|
+
|
|
1408
|
+
// Parse options
|
|
1409
|
+
const options = {
|
|
1410
|
+
report: args.includes('--report'),
|
|
1411
|
+
history: args.includes('--history')
|
|
1412
|
+
};
|
|
1413
|
+
|
|
1414
|
+
// Check if .ripp directory exists
|
|
1415
|
+
if (!fs.existsSync(rippDir)) {
|
|
1416
|
+
console.error(`${colors.red}Error: RIPP directory not found${colors.reset}`);
|
|
1417
|
+
console.error('Run "ripp init" to initialize RIPP in this repository');
|
|
1418
|
+
process.exit(1);
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
try {
|
|
1422
|
+
if (options.history) {
|
|
1423
|
+
// Show metrics history
|
|
1424
|
+
const history = loadMetricsHistory(rippDir);
|
|
1425
|
+
console.log(formatMetricsHistory(history));
|
|
1426
|
+
process.exit(0);
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
// Gather current metrics
|
|
1430
|
+
const metrics = gatherMetrics(rippDir);
|
|
1431
|
+
|
|
1432
|
+
// Display metrics
|
|
1433
|
+
console.log(formatMetricsText(metrics));
|
|
1434
|
+
|
|
1435
|
+
// Write report if requested
|
|
1436
|
+
if (options.report) {
|
|
1437
|
+
const reportPath = path.join(rippDir, 'metrics.json');
|
|
1438
|
+
fs.writeFileSync(reportPath, JSON.stringify(metrics, null, 2), 'utf8');
|
|
1439
|
+
|
|
1440
|
+
// Save to history
|
|
1441
|
+
saveMetricsHistory(rippDir, metrics);
|
|
1442
|
+
|
|
1443
|
+
console.log('');
|
|
1444
|
+
log(colors.green, '✓', `Metrics report saved to ${reportPath}`);
|
|
1445
|
+
console.log('');
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
process.exit(0);
|
|
1449
|
+
} catch (error) {
|
|
1450
|
+
console.error(`${colors.red}Error: ${error.message}${colors.reset}`);
|
|
1451
|
+
process.exit(1);
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
function handleDoctorCommand() {
|
|
1456
|
+
const cwd = process.cwd();
|
|
1457
|
+
|
|
1458
|
+
console.log(`${colors.blue}Running RIPP health checks...${colors.reset}`);
|
|
1459
|
+
console.log('');
|
|
1460
|
+
|
|
1461
|
+
try {
|
|
1462
|
+
const results = runHealthChecks(cwd);
|
|
1463
|
+
console.log(formatHealthCheckText(results));
|
|
1464
|
+
|
|
1465
|
+
// Exit with non-zero if there are critical failures
|
|
1466
|
+
const hasCriticalFailures = Object.values(results.checks).some(
|
|
1467
|
+
check => check.status === 'fail'
|
|
1468
|
+
);
|
|
1469
|
+
|
|
1470
|
+
process.exit(hasCriticalFailures ? 1 : 0);
|
|
1471
|
+
} catch (error) {
|
|
1472
|
+
console.error(`${colors.red}Error running health checks: ${error.message}${colors.reset}`);
|
|
1343
1473
|
process.exit(1);
|
|
1344
1474
|
}
|
|
1345
1475
|
}
|
package/lib/build.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const yaml = require('js-yaml');
|
|
4
|
+
const {
|
|
5
|
+
parseChecklist,
|
|
6
|
+
buildConfirmedIntent,
|
|
7
|
+
validateConfirmedBlocks
|
|
8
|
+
} = require('./checklist-parser');
|
|
4
9
|
|
|
5
10
|
/**
|
|
6
11
|
* RIPP Build - Canonical Artifact Compilation
|
|
@@ -11,17 +16,26 @@ const yaml = require('js-yaml');
|
|
|
11
16
|
* Build canonical RIPP artifacts from confirmed intent
|
|
12
17
|
*/
|
|
13
18
|
function buildCanonicalArtifacts(cwd, options = {}) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
let confirmed;
|
|
20
|
+
|
|
21
|
+
// Check if building from checklist
|
|
22
|
+
if (options.fromChecklist) {
|
|
23
|
+
confirmed = buildFromChecklist(cwd, options);
|
|
24
|
+
} else {
|
|
25
|
+
const confirmedPath = path.join(cwd, '.ripp', 'intent.confirmed.yaml');
|
|
26
|
+
|
|
27
|
+
if (!fs.existsSync(confirmedPath)) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
'No confirmed intent found. Run "ripp confirm" first, or use "ripp build --from-checklist" to build from the checklist.'
|
|
30
|
+
);
|
|
31
|
+
}
|
|
19
32
|
|
|
20
|
-
|
|
21
|
-
|
|
33
|
+
const confirmedContent = fs.readFileSync(confirmedPath, 'utf8');
|
|
34
|
+
confirmed = yaml.load(confirmedContent);
|
|
22
35
|
|
|
23
|
-
|
|
24
|
-
|
|
36
|
+
if (!confirmed.confirmed || confirmed.confirmed.length === 0) {
|
|
37
|
+
throw new Error('No confirmed intent blocks found');
|
|
38
|
+
}
|
|
25
39
|
}
|
|
26
40
|
|
|
27
41
|
// Build RIPP packet from confirmed intent
|
|
@@ -50,6 +64,97 @@ function buildCanonicalArtifacts(cwd, options = {}) {
|
|
|
50
64
|
};
|
|
51
65
|
}
|
|
52
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Build from checklist markdown file
|
|
69
|
+
* Parses checklist, validates checked items, and generates confirmed intent
|
|
70
|
+
*/
|
|
71
|
+
function buildFromChecklist(cwd, options = {}) {
|
|
72
|
+
const checklistPath = path.join(cwd, '.ripp', 'intent.checklist.md');
|
|
73
|
+
|
|
74
|
+
// Check if checklist exists
|
|
75
|
+
if (!fs.existsSync(checklistPath)) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`Checklist not found at ${checklistPath}. Run "ripp confirm --checklist" to generate it.`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Read and parse checklist
|
|
82
|
+
const checklistContent = fs.readFileSync(checklistPath, 'utf8');
|
|
83
|
+
const parseResult = parseChecklist(checklistContent);
|
|
84
|
+
|
|
85
|
+
// Check for parsing errors
|
|
86
|
+
if (parseResult.errors.length > 0) {
|
|
87
|
+
const errorMsg = [
|
|
88
|
+
'Failed to parse checklist:',
|
|
89
|
+
...parseResult.errors.map(e => ` - ${e}`)
|
|
90
|
+
].join('\n');
|
|
91
|
+
throw new Error(errorMsg);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Check if any candidates were checked
|
|
95
|
+
if (parseResult.candidates.length === 0) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
'No candidates selected in checklist. Mark candidates with [x] and save the file, then run this command again.'
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Display warnings if any
|
|
102
|
+
if (parseResult.warnings.length > 0 && options.showWarnings !== false) {
|
|
103
|
+
console.warn('\n⚠️ Warnings:');
|
|
104
|
+
parseResult.warnings.forEach(w => console.warn(` - ${w}`));
|
|
105
|
+
console.warn('');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Build confirmed intent structure
|
|
109
|
+
const confirmed = buildConfirmedIntent(parseResult.candidates, {
|
|
110
|
+
user: options.user || 'checklist',
|
|
111
|
+
timestamp: new Date().toISOString()
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Validate confirmed blocks
|
|
115
|
+
const validation = validateConfirmedBlocks(confirmed.confirmed);
|
|
116
|
+
|
|
117
|
+
// Store validation results for reporting
|
|
118
|
+
const validationResults = {
|
|
119
|
+
totalChecked: parseResult.candidates.length,
|
|
120
|
+
accepted: validation.accepted.length,
|
|
121
|
+
rejected: validation.rejected.length,
|
|
122
|
+
reasons: validation.reasons
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// If there are rejected blocks, report them
|
|
126
|
+
if (validation.rejected.length > 0) {
|
|
127
|
+
console.warn('\n⚠️ Some candidates were rejected:');
|
|
128
|
+
validation.rejected.forEach(block => {
|
|
129
|
+
const reasons = validation.reasons[block.section] || [];
|
|
130
|
+
console.warn(` - ${block.section}: ${reasons.join(', ')}`);
|
|
131
|
+
});
|
|
132
|
+
console.warn('');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Check if we have any accepted blocks
|
|
136
|
+
if (validation.accepted.length === 0) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
'No valid candidates found. All selected candidates failed validation. Please review and fix the checklist.'
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Use only accepted blocks
|
|
143
|
+
const finalConfirmed = {
|
|
144
|
+
version: confirmed.version,
|
|
145
|
+
confirmed: validation.accepted
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// Save confirmed intent for traceability
|
|
149
|
+
const confirmedPath = path.join(cwd, '.ripp', 'intent.confirmed.yaml');
|
|
150
|
+
fs.writeFileSync(confirmedPath, yaml.dump(finalConfirmed, { indent: 2 }), 'utf8');
|
|
151
|
+
|
|
152
|
+
// Store validation results on options for reporting in handleBuildCommand
|
|
153
|
+
options._validationResults = validationResults;
|
|
154
|
+
|
|
155
|
+
return finalConfirmed;
|
|
156
|
+
}
|
|
157
|
+
|
|
53
158
|
/**
|
|
54
159
|
* Build RIPP packet from confirmed intent blocks
|
|
55
160
|
*/
|
|
@@ -334,5 +439,6 @@ function generateHandoffMarkdown(packet, confirmed) {
|
|
|
334
439
|
module.exports = {
|
|
335
440
|
buildCanonicalArtifacts,
|
|
336
441
|
buildRippPacket,
|
|
337
|
-
generateHandoffMarkdown
|
|
442
|
+
generateHandoffMarkdown,
|
|
443
|
+
buildFromChecklist
|
|
338
444
|
};
|