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 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, '../../schema/ripp-1.0.schema.json');
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 build --packet-id my-feature --title "My Feature"
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 (error) {
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
- console.log(`${colors.blue}Building canonical RIPP artifacts...${colors.reset}\n`);
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
- const confirmedPath = path.join(cwd, '.ripp', 'intent.confirmed.yaml');
15
-
16
- if (!fs.existsSync(confirmedPath)) {
17
- throw new Error('No confirmed intent found. Run "ripp confirm" first.');
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
- const confirmedContent = fs.readFileSync(confirmedPath, 'utf8');
21
- const confirmed = yaml.load(confirmedContent);
33
+ const confirmedContent = fs.readFileSync(confirmedPath, 'utf8');
34
+ confirmed = yaml.load(confirmedContent);
22
35
 
23
- if (!confirmed.confirmed || confirmed.confirmed.length === 0) {
24
- throw new Error('No confirmed intent blocks found');
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
  };