@redpanda-data/docs-extensions-and-macros 5.13.5 → 5.17.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.
Files changed (46) hide show
  1. package/bin/doc-tools-mcp.js +88 -0
  2. package/bin/doc-tools.js +349 -102
  3. package/bin/mcp-tools/index.js +20 -0
  4. package/bin/mcp-tools/lint-doc-strings.js +75 -0
  5. package/bin/mcp-tools/overrides-audit.js +100 -0
  6. package/bin/mcp-tools/preview-doc-string.js +71 -0
  7. package/cli-utils/diff-utils.js +77 -0
  8. package/cli-utils/helm-commented-values.js +172 -23
  9. package/cli-utils/sync-schemas.js +144 -0
  10. package/docs-data/rpk-overrides.schema.json +4 -0
  11. package/package.json +2 -1
  12. package/tools/generate-cli-docs.js +28 -67
  13. package/tools/lint-strings/diff.js +137 -0
  14. package/tools/lint-strings/engine.js +163 -0
  15. package/tools/lint-strings/fixtures/connect/lint_connect.go +67 -0
  16. package/tools/lint-strings/fixtures/crd/crd-ref-docs-config.yaml +15 -0
  17. package/tools/lint-strings/fixtures/crd/lint_types.go +57 -0
  18. package/tools/lint-strings/fixtures/helm/lint_values.yaml +47 -0
  19. package/tools/lint-strings/fixtures/metrics/lint_probe.cc +41 -0
  20. package/tools/lint-strings/fixtures/properties/lint_fixture.cc +51 -0
  21. package/tools/lint-strings/fixtures/properties/lint_fixture.h +33 -0
  22. package/tools/lint-strings/fixtures/rpk/lint_cmd.go +69 -0
  23. package/tools/lint-strings/go-source.js +327 -0
  24. package/tools/lint-strings/index.js +229 -0
  25. package/tools/lint-strings/rules/common.js +148 -0
  26. package/tools/lint-strings/rules/verbatim-asciidoc.js +108 -0
  27. package/tools/lint-strings/source-text.js +41 -0
  28. package/tools/lint-strings/surfaces/connect.js +233 -0
  29. package/tools/lint-strings/surfaces/crd.js +239 -0
  30. package/tools/lint-strings/surfaces/helm.js +159 -0
  31. package/tools/lint-strings/surfaces/metrics.js +368 -0
  32. package/tools/lint-strings/surfaces/properties.js +216 -0
  33. package/tools/lint-strings/surfaces/rpk.js +377 -0
  34. package/tools/overrides-audit/README.adoc +152 -0
  35. package/tools/overrides-audit/adapters/connect.js +144 -0
  36. package/tools/overrides-audit/adapters/properties.js +117 -0
  37. package/tools/overrides-audit/adapters/rpk.js +192 -0
  38. package/tools/overrides-audit/classify.js +546 -0
  39. package/tools/overrides-audit/index.js +185 -0
  40. package/tools/preview-string/index.js +245 -0
  41. package/tools/property-extractor/Makefile +9 -4
  42. package/tools/property-extractor/parser.py +26 -0
  43. package/tools/property-extractor/property_extractor.py +41 -0
  44. package/tools/property-extractor/transformers.py +15 -2
  45. package/tools/rpk-docs/detect-platform-commands.js +8 -1
  46. package/tools/rpk-docs/scan-deprecated-commands.js +15 -18
@@ -434,6 +434,94 @@ const tools = [
434
434
  required: ['doc_type']
435
435
  }
436
436
  },
437
+ {
438
+ name: 'lint_doc_strings',
439
+ description: 'Lint user-facing doc strings embedded in engineering source code (property descriptions, rpk Short/Long/flag usage, metric help, Helm values comments, CRD field comments, connect field descriptions). These strings ship verbatim to docs.redpanda.com. Deterministic, suggest-only, with exact file:line spans. Run it inside an engineering checkout (redpanda, redpanda-operator, connect).',
440
+ inputSchema: {
441
+ type: 'object',
442
+ properties: {
443
+ repo: {
444
+ type: 'string',
445
+ description: 'Absolute path to the engineering checkout to lint (required). Nothing is cloned.'
446
+ },
447
+ surface: {
448
+ type: 'string',
449
+ description: 'Comma-separated surfaces to lint: properties, metrics, rpk, helm, crd, connect (optional, defaults to all)'
450
+ },
451
+ diff: {
452
+ type: 'string',
453
+ description: 'Base git ref for declaration-anchored diff mode: lint only declarations whose span intersects lines changed in <ref>...HEAD (optional)'
454
+ },
455
+ skip_rules: {
456
+ type: 'string',
457
+ description: 'Comma-separated rule ids to skip (optional)'
458
+ },
459
+ only_rules: {
460
+ type: 'string',
461
+ description: 'Comma-separated rule ids to run exclusively (optional)'
462
+ }
463
+ },
464
+ required: ['repo']
465
+ }
466
+ },
467
+ {
468
+ name: 'preview_doc_string',
469
+ description: 'Render ONE embedded doc string from local engineering source to the final snippet published on docs.redpanda.com: properties through the real extractor and Handlebars template (with an optional override pane showing docs-repo masking), rpk through the real formatDescription() transformer, and metrics/helm/crd/connect in their published output shapes.',
470
+ inputSchema: {
471
+ type: 'object',
472
+ properties: {
473
+ repo: {
474
+ type: 'string',
475
+ description: 'Absolute path to the engineering checkout (required)'
476
+ },
477
+ surface: {
478
+ type: 'string',
479
+ description: 'Which surface the declaration belongs to',
480
+ enum: ['properties', 'rpk', 'metrics', 'helm', 'crd', 'connect']
481
+ },
482
+ name: {
483
+ type: 'string',
484
+ description: 'Declaration name: property name, rpk command token or --flag, metric name, Helm key path, CRD json field (or Struct.field), connect component/field name'
485
+ },
486
+ overrides: {
487
+ type: 'string',
488
+ description: 'Path to a docs-repo overrides JSON (properties only): adds an "as shipped" pane and a MASKED-BY-OVERRIDE notice when the override differs (optional)'
489
+ }
490
+ },
491
+ required: ['repo', 'surface', 'name']
492
+ }
493
+ },
494
+ {
495
+ name: 'audit_overrides',
496
+ description: 'Audit docs-side override entries against extracted source strings. Classifies each override field as REDUNDANT (retire), UPSTREAMABLE (send prose upstream), KEEP_UNTIL_UPSTREAMED (markup-laden, stripped upstream candidate emitted), UPSTREAMABLE_SLOT, REDUNDANT_OR_UPSTREAMABLE, KEEP, or REVIEW. Writers use this to retire stale overrides and draft upstream source fixes.',
497
+ inputSchema: {
498
+ type: 'object',
499
+ properties: {
500
+ overrides: {
501
+ type: 'string',
502
+ description: 'Path to the overrides JSON file (for example "docs-data/property-overrides.json")'
503
+ },
504
+ extracted: {
505
+ type: 'string',
506
+ description: 'Path to the extracted source JSON: the property extractor\'s RAW --output file, without overrides applied (required for the properties surface, alternative to "repo")'
507
+ },
508
+ repo: {
509
+ type: 'string',
510
+ description: 'Redpanda checkout to extract raw source strings from (properties surface only, alternative to "extracted")'
511
+ },
512
+ surface: {
513
+ type: 'string',
514
+ description: 'Override surface to audit (optional, defaults to "properties")',
515
+ enum: ['properties', 'rpk', 'connect']
516
+ },
517
+ output: {
518
+ type: 'string',
519
+ description: 'Also write the JSON result to this file, relative to repo root (optional)'
520
+ }
521
+ },
522
+ required: ['overrides']
523
+ }
524
+ },
437
525
  {
438
526
  name: 'run_doc_tools_command',
439
527
  description: 'Advanced: Run a raw doc-tools command. Only use this if none of the specific tools above fit your needs. Requires knowledge of doc-tools CLI syntax.',
package/bin/doc-tools.js CHANGED
@@ -27,6 +27,7 @@ const {
27
27
  generatePropertyComparisonReport,
28
28
  updatePropertyOverridesWithVersion,
29
29
  updatePropertiesJsonWithVersion,
30
+ repairPropertyAnchorsInJson,
30
31
  cleanupOldDiffs,
31
32
  resolveDiffBaseline
32
33
  } = require('../cli-utils/diff-utils')
@@ -501,6 +502,7 @@ programCli
501
502
  { name: 'generate_cloud_regions', description: 'Generate cloud regions documentation' },
502
503
  { name: 'generate_bundle_openapi', description: 'Bundle OpenAPI specifications' },
503
504
  { name: 'review_generated_docs', description: 'Review generated documentation' },
505
+ { name: 'audit_overrides', description: 'Audit docs-side overrides against extracted source strings' },
504
506
  { name: 'run_doc_tools_command', description: 'Run raw doc-tools command' },
505
507
  { name: 'get_job_status', description: 'Get background job status' },
506
508
  { name: 'list_jobs', description: 'List background jobs' }
@@ -529,6 +531,103 @@ programCli
529
531
  }
530
532
  })
531
533
 
534
+ /**
535
+ * @description Copy this package's docs-data/*.schema.json files into a
536
+ * content repo's own docs-data/ (the file that documents docs-data/x.json
537
+ * lives alongside x.json itself, so a writer can change both in one PR
538
+ * without waiting on a package release first). Run this after bumping
539
+ * @redpanda-data/docs-extensions-and-macros so the local copy picks up
540
+ * whatever changed upstream.
541
+ * @why Nothing keeps a content repo's schema copy in sync with this
542
+ * package's copy today, in either direction. A content repo's copy can
543
+ * legitimately be ahead of this package's (confirmed happening in
544
+ * practice: the docs repo's rpk-overrides.schema.json documents a real,
545
+ * working `asPartial` override field that this package's own copy is
546
+ * missing) — overwriting it in that case would delete real, correct
547
+ * documentation, not fix drift. This only overwrites when this package's
548
+ * copy is a strict superset of the destination; otherwise it reports which
549
+ * keys only the destination has and leaves the file alone (status
550
+ * 'diverged') unless --force is passed. --check never writes, for a CI
551
+ * gate.
552
+ * @example
553
+ * # Sync into ./docs-data (writes any missing or out-of-date schema)
554
+ * npx doc-tools sync-schemas
555
+ *
556
+ * # Report drift without writing -- exits 1 if anything is out of sync
557
+ * npx doc-tools sync-schemas --check
558
+ *
559
+ * # Sync into a non-default location
560
+ * npx doc-tools sync-schemas --dest path/to/docs-data
561
+ *
562
+ * # Overwrite even a destination that has diverged (rarely correct --
563
+ * # read the 'diverged' output first; it names what would be lost)
564
+ * npx doc-tools sync-schemas --force
565
+ * @requirements None.
566
+ */
567
+ programCli
568
+ .command('sync-schemas')
569
+ .description("Sync this package's docs-data/*.schema.json into a content repo's docs-data/")
570
+ .option('--dest <path>', 'Destination docs-data directory', 'docs-data')
571
+ .option('--check', 'Report drift without writing; exit 1 if any schema is missing, out of date, or diverged')
572
+ .option('--force', 'Overwrite even a destination that has diverged (has content this package lacks)')
573
+ .action((options) => {
574
+ const { syncSchemas } = require('../cli-utils/sync-schemas')
575
+
576
+ try {
577
+ const { destDir, results, drift } = syncSchemas({
578
+ destDir: options.dest,
579
+ check: Boolean(options.check),
580
+ force: Boolean(options.force),
581
+ })
582
+
583
+ if (results.length === 0) {
584
+ console.log('No *.schema.json files found in this package to sync.')
585
+ process.exit(0)
586
+ }
587
+
588
+ console.log(`${options.check ? 'Checking' : 'Syncing'} against: ${destDir}`)
589
+ console.log('')
590
+ let hasUnresolvedDivergence = false
591
+ for (const { name, status, destOnlyPaths } of results) {
592
+ const label = {
593
+ unchanged: '✓ up to date',
594
+ created: '+ created',
595
+ updated: '↻ updated',
596
+ diverged: options.force ? '↻ updated (forced)' : '⚠ diverged, left alone',
597
+ }[status]
598
+ console.log(` ${label} ${name}`)
599
+ if (status === 'diverged' && !options.force) {
600
+ hasUnresolvedDivergence = true
601
+ for (const p of destOnlyPaths) console.log(` only in the destination: ${p}`)
602
+ }
603
+ }
604
+ console.log('')
605
+
606
+ if (options.check) {
607
+ if (drift) {
608
+ const hint = hasUnresolvedDivergence
609
+ ? 'A diverged file needs a human decision (see paths above), not just a re-run.'
610
+ : 'Run `npx doc-tools sync-schemas` to fix.'
611
+ console.log(`✗ One or more schemas are out of date. ${hint}`)
612
+ process.exit(1)
613
+ }
614
+ console.log('✓ All schemas are in sync.')
615
+ process.exit(0)
616
+ }
617
+
618
+ if (hasUnresolvedDivergence) {
619
+ console.log('⚠ Synced what was safe to sync. One or more files diverged and were left alone -- see paths above. Re-run with --force only after confirming the destination-only content should be lost.')
620
+ process.exit(1)
621
+ }
622
+
623
+ console.log(drift ? '✓ Synced.' : '✓ Already in sync -- nothing to do.')
624
+ process.exit(0)
625
+ } catch (err) {
626
+ console.error(`Error: ${err.message}`)
627
+ process.exit(1)
628
+ }
629
+ })
630
+
532
631
  /**
533
632
  * @description Show MCP server version information including available tools
534
633
  * and optionally usage statistics from previous sessions.
@@ -1106,6 +1205,12 @@ automation
1106
1205
  // properties new in this release instead of one release late.
1107
1206
  const extractedJsonPath = path.resolve(outputDir, 'attachments', `redpanda-properties-${newTag}.json`)
1108
1207
  updatePropertiesJsonWithVersion(extractedJsonPath, diffData, newTag)
1208
+ // Repair anchors HERE, in Phase 2, so the attachment the docs UI
1209
+ // reads and the partials Phase 3 renders come from the same
1210
+ // corrected text. Normalizing only in Phase 3 left the two
1211
+ // disagreeing: the partials linked <<flush-bytes>> while the
1212
+ // published attachment still said <<flushbytes>>.
1213
+ repairPropertyAnchorsInJson(extractedJsonPath)
1109
1214
  }
1110
1215
  }
1111
1216
  } catch (err) {
@@ -1502,108 +1607,6 @@ automation
1502
1607
  }
1503
1608
  })
1504
1609
 
1505
- /**
1506
- * validate rpk-overrides
1507
- *
1508
- * @description
1509
- * Validates the rpk-overrides.json file against the JSON schema and checks for common issues:
1510
- * - Schema compliance (required fields, valid types)
1511
- * - Valid $ref references (no broken or circular refs)
1512
- * - Valid command paths (compared against actual rpk command tree)
1513
- * - Valid admonition locations (after_flags, after_usage, etc.)
1514
- * - Valid platform values (linux, darwin, windows)
1515
- *
1516
- * @why
1517
- * Catching override errors early prevents broken documentation. This command lets writers
1518
- * validate their changes before generation, avoiding cryptic errors during the build process.
1519
- *
1520
- * @example
1521
- * # Validate overrides with default paths
1522
- * npx doc-tools validate rpk-overrides
1523
- *
1524
- * # Validate against a specific rpk tree (for complete command path validation)
1525
- * npx doc-tools validate rpk-overrides --tree docs-data/rpk-v26.2.0.json
1526
- *
1527
- * # Validate a custom overrides file
1528
- * npx doc-tools validate rpk-overrides --overrides my-overrides.json
1529
- *
1530
- * # Strict mode - exit with error code on validation failures
1531
- * npx doc-tools validate rpk-overrides --strict
1532
- *
1533
- * @requirements
1534
- * - rpk-overrides.schema.json in docs-data/
1535
- */
1536
- automation
1537
- .command('rpk-overrides')
1538
- .description('Validate rpk-overrides.json against schema and check for common issues')
1539
- .option('--overrides <path>', 'Path to overrides JSON file', 'docs-data/rpk-overrides.json')
1540
- .option('--tree <path>', 'Path to rpk tree JSON file for command path validation (e.g., docs-data/rpk-v26.2.0.json)')
1541
- .option('--strict', 'Exit with error code on validation failures')
1542
- .action((options) => {
1543
- try {
1544
- const { loadAndValidateOverrides } = require('../tools/rpk-docs/validate-overrides.js')
1545
- const repoRoot = findRepoRoot()
1546
- const overridesPath = path.resolve(repoRoot, options.overrides)
1547
-
1548
- // Load tree if provided for command path validation
1549
- let commandTree = null
1550
- if (options.tree) {
1551
- const treePath = path.resolve(repoRoot, options.tree)
1552
- if (!fs.existsSync(treePath)) {
1553
- console.error(`Error: Tree file not found: ${treePath}`)
1554
- process.exit(1)
1555
- }
1556
- const treeData = JSON.parse(fs.readFileSync(treePath, 'utf8'))
1557
- commandTree = treeData.tree || treeData
1558
- }
1559
-
1560
- console.log(`Validating: ${overridesPath}`)
1561
- if (commandTree) {
1562
- console.log(`Comparing against tree from: ${options.tree}`)
1563
- } else {
1564
- console.log('Note: Skipping command path validation (no --tree provided)')
1565
- }
1566
- console.log('')
1567
-
1568
- const { overrides, validation } = loadAndValidateOverrides(overridesPath, commandTree)
1569
-
1570
- if (!overrides) {
1571
- console.error('Error: Could not load overrides file')
1572
- process.exit(1)
1573
- }
1574
-
1575
- // Print results
1576
- console.log('=' .repeat(60))
1577
- console.log('VALIDATION RESULTS')
1578
- console.log('='.repeat(60))
1579
-
1580
- if (validation.errors.length === 0 && validation.warnings.length === 0) {
1581
- console.log('✓ No issues found')
1582
- } else {
1583
- console.log(validation.format())
1584
- }
1585
-
1586
- console.log('='.repeat(60))
1587
- console.log(`Errors: ${validation.errors.length}`)
1588
- console.log(`Warnings: ${validation.warnings.length}`)
1589
- console.log('='.repeat(60))
1590
-
1591
- // Exit with appropriate code
1592
- if (options.strict && !validation.valid) {
1593
- console.log('\n✗ Validation failed (strict mode)')
1594
- process.exit(1)
1595
- } else if (validation.valid) {
1596
- console.log('\n✓ Validation passed')
1597
- process.exit(0)
1598
- } else {
1599
- console.log('\n⚠ Validation completed with errors (use --strict to fail)')
1600
- process.exit(0)
1601
- }
1602
- } catch (err) {
1603
- console.error(`Error: ${err.message}`)
1604
- process.exit(1)
1605
- }
1606
- })
1607
1610
 
1608
1611
 
1609
1612
  /**
@@ -2276,6 +2279,110 @@ automation
2276
2279
 
2277
2280
  const validation = new Command('validate').description('Validate docs data against internal sources of truth')
2278
2281
 
2282
+ /**
2283
+ * validate rpk-overrides
2284
+ *
2285
+ * @description
2286
+ * Validates the rpk-overrides.json file against the JSON schema and checks for common issues:
2287
+ * - Schema compliance (required fields, valid types)
2288
+ * - Valid $ref references (no broken or circular refs)
2289
+ * - Valid command paths (compared against actual rpk command tree)
2290
+ * - Valid admonition locations (after_flags, after_usage, etc.)
2291
+ * - Valid platform values (linux, darwin, windows)
2292
+ *
2293
+ * @why
2294
+ * Catching override errors early prevents broken documentation. This command lets writers
2295
+ * validate their changes before generation, avoiding cryptic errors during the build process.
2296
+ *
2297
+ * @example
2298
+ * # Validate overrides with default paths
2299
+ * npx doc-tools validate rpk-overrides
2300
+ *
2301
+ * # Validate against a specific rpk tree (for complete command path validation)
2302
+ * npx doc-tools validate rpk-overrides --tree docs-data/rpk-v26.2.0.json
2303
+ *
2304
+ * # Validate a custom overrides file
2305
+ * npx doc-tools validate rpk-overrides --overrides my-overrides.json
2306
+ *
2307
+ * # Strict mode - exit with error code on validation failures
2308
+ * npx doc-tools validate rpk-overrides --strict
2309
+ *
2310
+ * @requirements
2311
+ * - rpk-overrides.schema.json in docs-data/
2312
+ */
2313
+ validation
2314
+ .command('rpk-overrides')
2315
+ .description('Validate rpk-overrides.json against schema and check for common issues')
2316
+ .option('--overrides <path>', 'Path to overrides JSON file', 'docs-data/rpk-overrides.json')
2317
+ .option('--tree <path>', 'Path to rpk tree JSON file for command path validation (e.g., docs-data/rpk-v26.2.0.json)')
2318
+ .option('--strict', 'Exit with error code on validation failures')
2319
+ .action((options) => {
2320
+ try {
2321
+ const { loadAndValidateOverrides } = require('../tools/rpk-docs/validate-overrides.js')
2322
+ const repoRoot = findRepoRoot()
2323
+ const overridesPath = path.resolve(repoRoot, options.overrides)
2324
+
2325
+ // Load tree if provided for command path validation
2326
+ let commandTree = null
2327
+ if (options.tree) {
2328
+ const treePath = path.resolve(repoRoot, options.tree)
2329
+ if (!fs.existsSync(treePath)) {
2330
+ console.error(`Error: Tree file not found: ${treePath}`)
2331
+ process.exit(1)
2332
+ }
2333
+ const treeData = JSON.parse(fs.readFileSync(treePath, 'utf8'))
2334
+ commandTree = treeData.tree || treeData
2335
+ }
2336
+
2337
+ console.log(`Validating: ${overridesPath}`)
2338
+ if (commandTree) {
2339
+ console.log(`Comparing against tree from: ${options.tree}`)
2340
+ } else {
2341
+ console.log('Note: Skipping command path validation (no --tree provided)')
2342
+ }
2343
+ console.log('')
2344
+
2345
+ const { overrides, validation } = loadAndValidateOverrides(overridesPath, commandTree)
2346
+
2347
+ if (!overrides) {
2348
+ console.error('Error: Could not load overrides file')
2349
+ process.exit(1)
2350
+ }
2351
+
2352
+ // Print results
2353
+ console.log('=' .repeat(60))
2354
+ console.log('VALIDATION RESULTS')
2355
+ console.log('='.repeat(60))
2356
+
2357
+ if (validation.errors.length === 0 && validation.warnings.length === 0) {
2358
+ console.log('✓ No issues found')
2359
+ } else {
2360
+ console.log(validation.format())
2361
+ }
2362
+
2363
+ console.log('='.repeat(60))
2364
+ console.log(`Errors: ${validation.errors.length}`)
2365
+ console.log(`Warnings: ${validation.warnings.length}`)
2366
+ console.log('='.repeat(60))
2367
+
2368
+ // Exit with appropriate code
2369
+ if (options.strict && !validation.valid) {
2370
+ console.log('\n✗ Validation failed (strict mode)')
2371
+ process.exit(1)
2372
+ } else if (validation.valid) {
2373
+ console.log('\n✓ Validation passed')
2374
+ process.exit(0)
2375
+ } else {
2376
+ console.log('\n⚠ Validation completed with errors (use --strict to fail)')
2377
+ process.exit(0)
2378
+ }
2379
+ } catch (err) {
2380
+ console.error(`Error: ${err.message}`)
2381
+ process.exit(1)
2382
+ }
2383
+ })
2384
+
2385
+
2279
2386
  /**
2280
2387
  * @description Checks the enterprise features registry (the shared component's
2281
2388
  * enterprise-features.yml in the docs repo) against the internal sources of
@@ -2368,6 +2475,146 @@ validation
2368
2475
  }
2369
2476
  })
2370
2477
 
2478
+ /**
2479
+ * lint-strings
2480
+ *
2481
+ * @description
2482
+ * Deterministic lint for user-facing doc strings embedded in engineering
2483
+ * source code (property descriptions, metric help strings, ...). These
2484
+ * strings ship verbatim to docs.redpanda.com, so this command surfaces
2485
+ * quality problems (empty descriptions, broken markup, name-echo
2486
+ * tautologies, convention drift) at write time, with exact file:line spans
2487
+ * for each declaration.
2488
+ *
2489
+ * @why
2490
+ * The docs team currently patches bad source strings after the fact via
2491
+ * override files. Linting where engineers write the strings - including a
2492
+ * declaration-anchored --diff mode for PR reviews - retires that drift debt.
2493
+ *
2494
+ * @example
2495
+ * # Lint all supported surfaces in a local redpanda checkout
2496
+ * npx doc-tools lint-strings --repo ~/redpanda
2497
+ *
2498
+ * # Lint only properties, machine-readable
2499
+ * npx doc-tools lint-strings --repo ~/redpanda --surface properties --format json
2500
+ *
2501
+ * # PR mode: only declarations whose span intersects the diff
2502
+ * npx doc-tools lint-strings --repo ~/redpanda --diff origin/dev
2503
+ */
2504
+ programCli
2505
+ .command('lint-strings')
2506
+ .description('Lint user-facing doc strings embedded in engineering source code (properties, metrics, ...)')
2507
+ .requiredOption('--repo <path>', 'Path to an existing engineering checkout (for example, a local redpanda clone). Nothing is cloned.')
2508
+ .option('--surface <list>', 'Comma-separated surfaces to lint (default: all registered). Registered: properties, metrics, rpk, helm, crd, connect')
2509
+ .option('--diff <base>', 'Declaration-anchored diff mode: lint only declarations whose full span intersects lines changed in <base>...HEAD')
2510
+ .option('--format <format>', 'Output format: human or json', 'human')
2511
+ .option('--skip-rules <list>', 'Comma-separated rule ids to skip')
2512
+ .option('--only-rules <list>', 'Comma-separated rule ids to run exclusively')
2513
+ .option('--strict', 'Exit 1 when any error-severity finding exists (default: always exit 0 - suggest, never block)')
2514
+ .action((options) => {
2515
+ const { runCli } = require('../tools/lint-strings')
2516
+ runCli(options)
2517
+ })
2518
+
2519
+ /**
2520
+ * preview-string
2521
+ *
2522
+ * @description
2523
+ * Render ONE doc-string declaration from local engineering source to the
2524
+ * final published snippet: properties through the real extractor +
2525
+ * Handlebars template (two panes when --overrides shows a docs-repo
2526
+ * override masking the source string), rpk through the real
2527
+ * formatDescription() transformer, and metrics/helm/crd/connect in their
2528
+ * published output shapes.
2529
+ *
2530
+ * @why
2531
+ * Engineers can see what their embedded string becomes on
2532
+ * docs.redpanda.com BEFORE it ships - including whether an override in the
2533
+ * docs repo would silently mask their fix.
2534
+ *
2535
+ * @example
2536
+ * # What does this property's docs section look like?
2537
+ * npx doc-tools preview-string --repo ~/redpanda --surface properties --name log_segment_size
2538
+ *
2539
+ * # Is my override masking the source string?
2540
+ * npx doc-tools preview-string --repo ~/redpanda --surface properties --name log_segment_size --overrides ~/docs/docs-data/property-overrides.json
2541
+ *
2542
+ * # What does formatDescription do to my rpk Long text?
2543
+ * npx doc-tools preview-string --repo ~/redpanda --surface rpk --name health
2544
+ */
2545
+ programCli
2546
+ .command('preview-string')
2547
+ .description('Render one embedded doc string (property, rpk command/flag, metric, helm key, CRD field, connect field) as it will publish')
2548
+ .requiredOption('--repo <path>', 'Path to an existing engineering checkout. Nothing is cloned.')
2549
+ .requiredOption('--surface <surface>', 'One of: properties, rpk, metrics, helm, crd, connect')
2550
+ .requiredOption('--name <name>', 'Declaration name: property name, rpk command token or --flag, metric name, helm key path, CRD json field (or Struct.field), connect component/field name')
2551
+ .option('--overrides <path>', 'Docs-repo overrides JSON (properties only): adds an "as shipped" pane and a MASKED-BY-OVERRIDE notice when the override differs')
2552
+ .action((options) => {
2553
+ const { runCli } = require('../tools/preview-string')
2554
+ runCli(options)
2555
+ })
2556
+
2557
+ // ====================================================================
2558
+ // OVERRIDES COMMANDS
2559
+ // ====================================================================
2560
+ const overridesGroup = new Command('overrides').description('Audit docs-side override files against extracted source strings')
2561
+
2562
+ /**
2563
+ * @description Field-level classification of override entries against the
2564
+ * strings extracted from engineering source. Each override field classifies
2565
+ * as REDUNDANT (source already matches; retire it), UPSTREAMABLE (send the
2566
+ * prose upstream), KEEP_UNTIL_UPSTREAMED (markup-laden SPLIT case with a
2567
+ * stripped upstream candidate), UPSTREAMABLE_SLOT (migrate to a source
2568
+ * metadata slot), REDUNDANT_OR_UPSTREAMABLE (needs a human ruling), KEEP
2569
+ * (docs enrichment by design), or REVIEW (possible source bug; never
2570
+ * auto-delete). See tools/overrides-audit/README.adoc for the full rules
2571
+ * and the upstream_ref policy.
2572
+ *
2573
+ * @why Description overrides are stopgaps awaiting an upstream source fix;
2574
+ * once the fixed string ships in a release they silently mask all future
2575
+ * source improvements. This audit powers the retirement loop (delete
2576
+ * REDUNDANT fields on each release regeneration) and the upstreaming loop
2577
+ * (draft source PRs from the UPSTREAMABLE/SPLIT candidates).
2578
+ *
2579
+ * @example
2580
+ * # Audit the docs repo property overrides against a raw extraction
2581
+ * npx doc-tools overrides audit \
2582
+ * --overrides docs-data/property-overrides.json \
2583
+ * --extracted tools/property-extractor/gen/properties-output.json \
2584
+ * --format human
2585
+ *
2586
+ * @requirements
2587
+ * The --extracted file must be the property extractor's RAW output (its
2588
+ * --output file, without overrides applied). The enhanced output and the
2589
+ * versioned redpanda-properties-<tag>.json attachments already have
2590
+ * overrides applied, so auditing against them classifies everything
2591
+ * REDUNDANT.
2592
+ */
2593
+ overridesGroup
2594
+ .command('audit')
2595
+ .description('Classify each override field as redundant, upstreamable, or keep against extracted source strings')
2596
+ .requiredOption('--overrides <path>', 'Path to the overrides JSON file (for example docs-data/property-overrides.json)')
2597
+ .option('--extracted <path>', 'Path to the extracted source JSON (property extractor raw output; required for the properties surface)')
2598
+ .addOption(new Option('--surface <surface>', 'Override surface to audit').choices(['properties', 'rpk', 'connect']).default('properties'))
2599
+ .addOption(new Option('--format <format>', 'Output format').choices(['json', 'human']).default('json'))
2600
+ .option('--repo <path>', 'Redpanda checkout to extract raw source strings from (alternative to --extracted)')
2601
+ .option('--output <path>', 'Also write the JSON result to this file')
2602
+ .action((options) => {
2603
+ const { runAudit, formatHumanReport } = require('../tools/overrides-audit')
2604
+ try {
2605
+ const result = runAudit(options)
2606
+ if (options.output) {
2607
+ fs.mkdirSync(path.dirname(path.resolve(options.output)), { recursive: true })
2608
+ fs.writeFileSync(path.resolve(options.output), JSON.stringify(result, null, 2) + '\n')
2609
+ console.error(`JSON result written to ${options.output}`)
2610
+ }
2611
+ console.log(options.format === 'human' ? formatHumanReport(result) : JSON.stringify(result, null, 2))
2612
+ } catch (err) {
2613
+ fail(err.message)
2614
+ }
2615
+ })
2616
+
2371
2617
  programCli.addCommand(automation)
2372
2618
  programCli.addCommand(validation)
2619
+ programCli.addCommand(overridesGroup)
2373
2620
  programCli.parse(process.argv)
@@ -25,6 +25,11 @@ const { generateBundleOpenApi } = require('./openapi');
25
25
 
26
26
  // Review tools
27
27
  const { reviewGeneratedDocs, generateReviewReport } = require('./generated-docs-review');
28
+ const { auditOverrides } = require('./overrides-audit');
29
+
30
+ // Doc-string tools (run in engineering repos)
31
+ const { lintDocStrings } = require('./lint-doc-strings');
32
+ const { previewDocString } = require('./preview-doc-string');
28
33
 
29
34
  // Job queue
30
35
  const { initializeJobQueue, createJob, getJob, listJobs, cleanupOldJobs } = require('./job-queue');
@@ -89,6 +94,18 @@ function executeTool(toolName, args = {}) {
89
94
  result = reviewGeneratedDocs(args);
90
95
  break;
91
96
 
97
+ case 'lint_doc_strings':
98
+ result = lintDocStrings(args);
99
+ break;
100
+
101
+ case 'preview_doc_string':
102
+ result = previewDocString(args);
103
+ break;
104
+
105
+ case 'audit_overrides':
106
+ result = auditOverrides(args);
107
+ break;
108
+
92
109
  case 'run_doc_tools_command': {
93
110
  // Validate and execute raw doc-tools command
94
111
  if (!args || typeof args !== 'object') {
@@ -250,6 +267,9 @@ module.exports = {
250
267
  generateBundleOpenApi,
251
268
  reviewGeneratedDocs,
252
269
  generateReviewReport,
270
+ lintDocStrings,
271
+ previewDocString,
272
+ auditOverrides,
253
273
 
254
274
  // Job queue
255
275
  initializeJobQueue,