@redpanda-data/docs-extensions-and-macros 5.14.0 → 5.22.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 (56) hide show
  1. package/bin/doc-tools-mcp.js +88 -0
  2. package/bin/doc-tools.js +334 -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/validation-result.js +53 -0
  10. package/macros/REFERENCE.adoc +3 -2
  11. package/macros/prop.js +34 -6
  12. package/package.json +2 -1
  13. package/tools/generate-cli-docs.js +28 -67
  14. package/tools/lint-strings/diff.js +137 -0
  15. package/tools/lint-strings/engine.js +163 -0
  16. package/tools/lint-strings/fixtures/connect/lint_connect.go +67 -0
  17. package/tools/lint-strings/fixtures/crd/crd-ref-docs-config.yaml +15 -0
  18. package/tools/lint-strings/fixtures/crd/lint_types.go +57 -0
  19. package/tools/lint-strings/fixtures/helm/lint_values.yaml +47 -0
  20. package/tools/lint-strings/fixtures/metrics/lint_probe.cc +41 -0
  21. package/tools/lint-strings/fixtures/properties/lint_fixture.cc +51 -0
  22. package/tools/lint-strings/fixtures/properties/lint_fixture.h +33 -0
  23. package/tools/lint-strings/fixtures/rpk/lint_cmd.go +69 -0
  24. package/tools/lint-strings/go-source.js +327 -0
  25. package/tools/lint-strings/index.js +229 -0
  26. package/tools/lint-strings/rules/common.js +148 -0
  27. package/tools/lint-strings/rules/verbatim-asciidoc.js +108 -0
  28. package/tools/lint-strings/source-text.js +41 -0
  29. package/tools/lint-strings/surfaces/connect.js +233 -0
  30. package/tools/lint-strings/surfaces/crd.js +239 -0
  31. package/tools/lint-strings/surfaces/helm.js +159 -0
  32. package/tools/lint-strings/surfaces/metrics.js +368 -0
  33. package/tools/lint-strings/surfaces/properties.js +216 -0
  34. package/tools/lint-strings/surfaces/rpk.js +377 -0
  35. package/tools/overrides-audit/README.adoc +152 -0
  36. package/tools/overrides-audit/adapters/connect.js +144 -0
  37. package/tools/overrides-audit/adapters/properties.js +117 -0
  38. package/tools/overrides-audit/adapters/rpk.js +192 -0
  39. package/tools/overrides-audit/classify.js +546 -0
  40. package/tools/overrides-audit/index.js +185 -0
  41. package/tools/preview-string/index.js +245 -0
  42. package/tools/property-extractor/Makefile +9 -4
  43. package/tools/property-extractor/README.adoc +17 -3
  44. package/tools/property-extractor/helpers/index.js +1 -2
  45. package/tools/property-extractor/helpers/seeAlsoView.js +76 -0
  46. package/tools/property-extractor/parser.py +26 -0
  47. package/tools/property-extractor/property_extractor.py +65 -6
  48. package/tools/property-extractor/templates/property.hbs +25 -37
  49. package/tools/property-extractor/templates/topic-property.hbs +25 -37
  50. package/tools/property-extractor/transformers.py +15 -2
  51. package/tools/property-extractor/validate-overrides.js +120 -0
  52. package/tools/rpk-docs/detect-platform-commands.js +8 -1
  53. package/tools/rpk-docs/scan-deprecated-commands.js +15 -18
  54. package/tools/rpk-docs/validate-overrides.js +1 -45
  55. package/tools/property-extractor/helpers/allTopicsConditional.js +0 -19
  56. package/tools/property-extractor/helpers/parseRelatedTopic.js +0 -13
@@ -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' }
@@ -1203,6 +1205,12 @@ automation
1203
1205
  // properties new in this release instead of one release late.
1204
1206
  const extractedJsonPath = path.resolve(outputDir, 'attachments', `redpanda-properties-${newTag}.json`)
1205
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)
1206
1214
  }
1207
1215
  }
1208
1216
  } catch (err) {
@@ -1599,108 +1607,6 @@ automation
1599
1607
  }
1600
1608
  })
1601
1609
 
1602
- /**
1603
- * validate rpk-overrides
1604
- *
1605
- * @description
1606
- * Validates the rpk-overrides.json file against the JSON schema and checks for common issues:
1607
- * - Schema compliance (required fields, valid types)
1608
- * - Valid $ref references (no broken or circular refs)
1609
- * - Valid command paths (compared against actual rpk command tree)
1610
- * - Valid admonition locations (after_flags, after_usage, etc.)
1611
- * - Valid platform values (linux, darwin, windows)
1612
- *
1613
- * @why
1614
- * Catching override errors early prevents broken documentation. This command lets writers
1615
- * validate their changes before generation, avoiding cryptic errors during the build process.
1616
- *
1617
- * @example
1618
- * # Validate overrides with default paths
1619
- * npx doc-tools validate rpk-overrides
1620
- *
1621
- * # Validate against a specific rpk tree (for complete command path validation)
1622
- * npx doc-tools validate rpk-overrides --tree docs-data/rpk-v26.2.0.json
1623
- *
1624
- * # Validate a custom overrides file
1625
- * npx doc-tools validate rpk-overrides --overrides my-overrides.json
1626
- *
1627
- * # Strict mode - exit with error code on validation failures
1628
- * npx doc-tools validate rpk-overrides --strict
1629
- *
1630
- * @requirements
1631
- * - rpk-overrides.schema.json in docs-data/
1632
- */
1633
- automation
1634
- .command('rpk-overrides')
1635
- .description('Validate rpk-overrides.json against schema and check for common issues')
1636
- .option('--overrides <path>', 'Path to overrides JSON file', 'docs-data/rpk-overrides.json')
1637
- .option('--tree <path>', 'Path to rpk tree JSON file for command path validation (e.g., docs-data/rpk-v26.2.0.json)')
1638
- .option('--strict', 'Exit with error code on validation failures')
1639
- .action((options) => {
1640
- try {
1641
- const { loadAndValidateOverrides } = require('../tools/rpk-docs/validate-overrides.js')
1642
- const repoRoot = findRepoRoot()
1643
- const overridesPath = path.resolve(repoRoot, options.overrides)
1644
-
1645
- // Load tree if provided for command path validation
1646
- let commandTree = null
1647
- if (options.tree) {
1648
- const treePath = path.resolve(repoRoot, options.tree)
1649
- if (!fs.existsSync(treePath)) {
1650
- console.error(`Error: Tree file not found: ${treePath}`)
1651
- process.exit(1)
1652
- }
1653
- const treeData = JSON.parse(fs.readFileSync(treePath, 'utf8'))
1654
- commandTree = treeData.tree || treeData
1655
- }
1656
-
1657
- console.log(`Validating: ${overridesPath}`)
1658
- if (commandTree) {
1659
- console.log(`Comparing against tree from: ${options.tree}`)
1660
- } else {
1661
- console.log('Note: Skipping command path validation (no --tree provided)')
1662
- }
1663
- console.log('')
1664
-
1665
- const { overrides, validation } = loadAndValidateOverrides(overridesPath, commandTree)
1666
-
1667
- if (!overrides) {
1668
- console.error('Error: Could not load overrides file')
1669
- process.exit(1)
1670
- }
1671
-
1672
- // Print results
1673
- console.log('=' .repeat(60))
1674
- console.log('VALIDATION RESULTS')
1675
- console.log('='.repeat(60))
1676
-
1677
- if (validation.errors.length === 0 && validation.warnings.length === 0) {
1678
- console.log('✓ No issues found')
1679
- } else {
1680
- console.log(validation.format())
1681
- }
1682
-
1683
- console.log('='.repeat(60))
1684
- console.log(`Errors: ${validation.errors.length}`)
1685
- console.log(`Warnings: ${validation.warnings.length}`)
1686
- console.log('='.repeat(60))
1687
-
1688
- // Exit with appropriate code
1689
- if (options.strict && !validation.valid) {
1690
- console.log('\n✗ Validation failed (strict mode)')
1691
- process.exit(1)
1692
- } else if (validation.valid) {
1693
- console.log('\n✓ Validation passed')
1694
- process.exit(0)
1695
- } else {
1696
- console.log('\n⚠ Validation completed with errors (use --strict to fail)')
1697
- process.exit(0)
1698
- }
1699
- } catch (err) {
1700
- console.error(`Error: ${err.message}`)
1701
- process.exit(1)
1702
- }
1703
- })
1704
1610
 
1705
1611
 
1706
1612
  /**
@@ -2373,6 +2279,110 @@ automation
2373
2279
 
2374
2280
  const validation = new Command('validate').description('Validate docs data against internal sources of truth')
2375
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
+
2376
2386
  /**
2377
2387
  * @description Checks the enterprise features registry (the shared component's
2378
2388
  * enterprise-features.yml in the docs repo) against the internal sources of
@@ -2465,6 +2475,228 @@ validation
2465
2475
  }
2466
2476
  })
2467
2477
 
2478
+ /**
2479
+ * validate property-overrides
2480
+ *
2481
+ * @description
2482
+ * Validates docs-data/property-overrides.json against its JSON Schema:
2483
+ * unknown keys (a typo that would otherwise be silently dropped by the
2484
+ * extractor), and the see_also shape (a plain string, or an object naming
2485
+ * exactly one of cloud_only/self_hosted_only).
2486
+ *
2487
+ * @why
2488
+ * property-overrides.json has no catch-all pass-through when an override
2489
+ * targets an existing property — an unrecognized key like the typo
2490
+ * `acceptable_values` (the generated-only field is `acceptable_values`, but
2491
+ * the override key the extractor reads is `accepted_values`) is silently
2492
+ * ignored rather than erroring. This check catches that class of mistake
2493
+ * before generation, the same way `validate rpk-overrides` does for rpk.
2494
+ *
2495
+ * @example
2496
+ * # Validate overrides with the default path
2497
+ * npx doc-tools validate property-overrides
2498
+ *
2499
+ * # Validate a custom overrides file
2500
+ * npx doc-tools validate property-overrides --overrides my-overrides.json
2501
+ *
2502
+ * # Strict mode - exit with error code on validation failures
2503
+ * npx doc-tools validate property-overrides --strict
2504
+ *
2505
+ * @requirements
2506
+ * - property-overrides.schema.json in docs-data/
2507
+ */
2508
+ validation
2509
+ .command('property-overrides')
2510
+ .description('Validate property-overrides.json against its schema')
2511
+ .option('--overrides <path>', 'Path to overrides JSON file', 'docs-data/property-overrides.json')
2512
+ .option('--strict', 'Exit with error code on validation failures')
2513
+ .action((options) => {
2514
+ try {
2515
+ const { loadAndValidateOverrides } = require('../tools/property-extractor/validate-overrides.js')
2516
+ const repoRoot = findRepoRoot()
2517
+ const overridesPath = path.resolve(repoRoot, options.overrides)
2518
+
2519
+ console.log(`Validating: ${overridesPath}`)
2520
+ console.log('')
2521
+
2522
+ const { overrides, validation: result } = loadAndValidateOverrides(overridesPath)
2523
+
2524
+ if (!overrides) {
2525
+ console.error('Error: Could not load overrides file')
2526
+ process.exit(1)
2527
+ }
2528
+
2529
+ console.log('='.repeat(60))
2530
+ console.log('VALIDATION RESULTS')
2531
+ console.log('='.repeat(60))
2532
+
2533
+ if (result.errors.length === 0 && result.warnings.length === 0) {
2534
+ console.log('✓ No issues found')
2535
+ } else {
2536
+ console.log(result.format())
2537
+ }
2538
+
2539
+ console.log('='.repeat(60))
2540
+ console.log(`Errors: ${result.errors.length}`)
2541
+ console.log(`Warnings: ${result.warnings.length}`)
2542
+ console.log('='.repeat(60))
2543
+
2544
+ if (options.strict && !result.valid) {
2545
+ console.log('\n✗ Validation failed (strict mode)')
2546
+ process.exit(1)
2547
+ } else if (result.valid) {
2548
+ console.log('\n✓ Validation passed')
2549
+ process.exit(0)
2550
+ } else {
2551
+ console.log('\n⚠ Validation completed with errors (use --strict to fail)')
2552
+ process.exit(0)
2553
+ }
2554
+ } catch (err) {
2555
+ console.error(`Error: ${err.message}`)
2556
+ process.exit(1)
2557
+ }
2558
+ })
2559
+
2560
+ /**
2561
+ * lint-strings
2562
+ *
2563
+ * @description
2564
+ * Deterministic lint for user-facing doc strings embedded in engineering
2565
+ * source code (property descriptions, metric help strings, ...). These
2566
+ * strings ship verbatim to docs.redpanda.com, so this command surfaces
2567
+ * quality problems (empty descriptions, broken markup, name-echo
2568
+ * tautologies, convention drift) at write time, with exact file:line spans
2569
+ * for each declaration.
2570
+ *
2571
+ * @why
2572
+ * The docs team currently patches bad source strings after the fact via
2573
+ * override files. Linting where engineers write the strings - including a
2574
+ * declaration-anchored --diff mode for PR reviews - retires that drift debt.
2575
+ *
2576
+ * @example
2577
+ * # Lint all supported surfaces in a local redpanda checkout
2578
+ * npx doc-tools lint-strings --repo ~/redpanda
2579
+ *
2580
+ * # Lint only properties, machine-readable
2581
+ * npx doc-tools lint-strings --repo ~/redpanda --surface properties --format json
2582
+ *
2583
+ * # PR mode: only declarations whose span intersects the diff
2584
+ * npx doc-tools lint-strings --repo ~/redpanda --diff origin/dev
2585
+ */
2586
+ programCli
2587
+ .command('lint-strings')
2588
+ .description('Lint user-facing doc strings embedded in engineering source code (properties, metrics, ...)')
2589
+ .requiredOption('--repo <path>', 'Path to an existing engineering checkout (for example, a local redpanda clone). Nothing is cloned.')
2590
+ .option('--surface <list>', 'Comma-separated surfaces to lint (default: all registered). Registered: properties, metrics, rpk, helm, crd, connect')
2591
+ .option('--diff <base>', 'Declaration-anchored diff mode: lint only declarations whose full span intersects lines changed in <base>...HEAD')
2592
+ .option('--format <format>', 'Output format: human or json', 'human')
2593
+ .option('--skip-rules <list>', 'Comma-separated rule ids to skip')
2594
+ .option('--only-rules <list>', 'Comma-separated rule ids to run exclusively')
2595
+ .option('--strict', 'Exit 1 when any error-severity finding exists (default: always exit 0 - suggest, never block)')
2596
+ .action((options) => {
2597
+ const { runCli } = require('../tools/lint-strings')
2598
+ runCli(options)
2599
+ })
2600
+
2601
+ /**
2602
+ * preview-string
2603
+ *
2604
+ * @description
2605
+ * Render ONE doc-string declaration from local engineering source to the
2606
+ * final published snippet: properties through the real extractor +
2607
+ * Handlebars template (two panes when --overrides shows a docs-repo
2608
+ * override masking the source string), rpk through the real
2609
+ * formatDescription() transformer, and metrics/helm/crd/connect in their
2610
+ * published output shapes.
2611
+ *
2612
+ * @why
2613
+ * Engineers can see what their embedded string becomes on
2614
+ * docs.redpanda.com BEFORE it ships - including whether an override in the
2615
+ * docs repo would silently mask their fix.
2616
+ *
2617
+ * @example
2618
+ * # What does this property's docs section look like?
2619
+ * npx doc-tools preview-string --repo ~/redpanda --surface properties --name log_segment_size
2620
+ *
2621
+ * # Is my override masking the source string?
2622
+ * npx doc-tools preview-string --repo ~/redpanda --surface properties --name log_segment_size --overrides ~/docs/docs-data/property-overrides.json
2623
+ *
2624
+ * # What does formatDescription do to my rpk Long text?
2625
+ * npx doc-tools preview-string --repo ~/redpanda --surface rpk --name health
2626
+ */
2627
+ programCli
2628
+ .command('preview-string')
2629
+ .description('Render one embedded doc string (property, rpk command/flag, metric, helm key, CRD field, connect field) as it will publish')
2630
+ .requiredOption('--repo <path>', 'Path to an existing engineering checkout. Nothing is cloned.')
2631
+ .requiredOption('--surface <surface>', 'One of: properties, rpk, metrics, helm, crd, connect')
2632
+ .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')
2633
+ .option('--overrides <path>', 'Docs-repo overrides JSON (properties only): adds an "as shipped" pane and a MASKED-BY-OVERRIDE notice when the override differs')
2634
+ .action((options) => {
2635
+ const { runCli } = require('../tools/preview-string')
2636
+ runCli(options)
2637
+ })
2638
+
2639
+ // ====================================================================
2640
+ // OVERRIDES COMMANDS
2641
+ // ====================================================================
2642
+ const overridesGroup = new Command('overrides').description('Audit docs-side override files against extracted source strings')
2643
+
2644
+ /**
2645
+ * @description Field-level classification of override entries against the
2646
+ * strings extracted from engineering source. Each override field classifies
2647
+ * as REDUNDANT (source already matches; retire it), UPSTREAMABLE (send the
2648
+ * prose upstream), KEEP_UNTIL_UPSTREAMED (markup-laden SPLIT case with a
2649
+ * stripped upstream candidate), UPSTREAMABLE_SLOT (migrate to a source
2650
+ * metadata slot), REDUNDANT_OR_UPSTREAMABLE (needs a human ruling), KEEP
2651
+ * (docs enrichment by design), or REVIEW (possible source bug; never
2652
+ * auto-delete). See tools/overrides-audit/README.adoc for the full rules
2653
+ * and the upstream_ref policy.
2654
+ *
2655
+ * @why Description overrides are stopgaps awaiting an upstream source fix;
2656
+ * once the fixed string ships in a release they silently mask all future
2657
+ * source improvements. This audit powers the retirement loop (delete
2658
+ * REDUNDANT fields on each release regeneration) and the upstreaming loop
2659
+ * (draft source PRs from the UPSTREAMABLE/SPLIT candidates).
2660
+ *
2661
+ * @example
2662
+ * # Audit the docs repo property overrides against a raw extraction
2663
+ * npx doc-tools overrides audit \
2664
+ * --overrides docs-data/property-overrides.json \
2665
+ * --extracted tools/property-extractor/gen/properties-output.json \
2666
+ * --format human
2667
+ *
2668
+ * @requirements
2669
+ * The --extracted file must be the property extractor's RAW output (its
2670
+ * --output file, without overrides applied). The enhanced output and the
2671
+ * versioned redpanda-properties-<tag>.json attachments already have
2672
+ * overrides applied, so auditing against them classifies everything
2673
+ * REDUNDANT.
2674
+ */
2675
+ overridesGroup
2676
+ .command('audit')
2677
+ .description('Classify each override field as redundant, upstreamable, or keep against extracted source strings')
2678
+ .requiredOption('--overrides <path>', 'Path to the overrides JSON file (for example docs-data/property-overrides.json)')
2679
+ .option('--extracted <path>', 'Path to the extracted source JSON (property extractor raw output; required for the properties surface)')
2680
+ .addOption(new Option('--surface <surface>', 'Override surface to audit').choices(['properties', 'rpk', 'connect']).default('properties'))
2681
+ .addOption(new Option('--format <format>', 'Output format').choices(['json', 'human']).default('json'))
2682
+ .option('--repo <path>', 'Redpanda checkout to extract raw source strings from (alternative to --extracted)')
2683
+ .option('--output <path>', 'Also write the JSON result to this file')
2684
+ .action((options) => {
2685
+ const { runAudit, formatHumanReport } = require('../tools/overrides-audit')
2686
+ try {
2687
+ const result = runAudit(options)
2688
+ if (options.output) {
2689
+ fs.mkdirSync(path.dirname(path.resolve(options.output)), { recursive: true })
2690
+ fs.writeFileSync(path.resolve(options.output), JSON.stringify(result, null, 2) + '\n')
2691
+ console.error(`JSON result written to ${options.output}`)
2692
+ }
2693
+ console.log(options.format === 'human' ? formatHumanReport(result) : JSON.stringify(result, null, 2))
2694
+ } catch (err) {
2695
+ fail(err.message)
2696
+ }
2697
+ })
2698
+
2468
2699
  programCli.addCommand(automation)
2469
2700
  programCli.addCommand(validation)
2701
+ programCli.addCommand(overridesGroup)
2470
2702
  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,
@@ -0,0 +1,75 @@
1
+ /**
2
+ * MCP Tools - Lint embedded doc strings in engineering source
3
+ */
4
+
5
+ const path = require('path');
6
+ const { spawnSync } = require('child_process');
7
+ const { MAX_EXEC_BUFFER_SIZE, DEFAULT_COMMAND_TIMEOUT } = require('./utils');
8
+
9
+ // The CLI ships in this same package, so invoke it directly: this tool is
10
+ // designed to run inside ENGINEERING repos (redpanda, redpanda-operator,
11
+ // connect), where the docs-repo detection behind getDocToolsCommand does
12
+ // not apply.
13
+ const DOC_TOOLS_BIN = path.resolve(__dirname, '..', 'doc-tools.js');
14
+
15
+ /**
16
+ * Lint user-facing doc strings embedded in engineering source code
17
+ * @param {Object} args - Arguments
18
+ * @param {string} args.repo - Path to the engineering checkout (required)
19
+ * @param {string} [args.surface] - Comma-separated surfaces (properties, metrics, rpk, helm, crd, connect)
20
+ * @param {string} [args.diff] - Base ref for declaration-anchored diff mode
21
+ * @param {string} [args.skip_rules] - Comma-separated rule ids to skip
22
+ * @param {string} [args.only_rules] - Comma-separated rule ids to run exclusively
23
+ * @returns {Object} Lint results (findings + summary, JSON)
24
+ */
25
+ function lintDocStrings(args = {}) {
26
+ if (!args.repo) {
27
+ return {
28
+ success: false,
29
+ error: 'repo is required',
30
+ suggestion: 'Pass the absolute path of the engineering checkout to lint (for example, a local redpanda clone)'
31
+ };
32
+ }
33
+
34
+ const cliArgs = [DOC_TOOLS_BIN, 'lint-strings', '--repo', args.repo, '--format', 'json'];
35
+ if (args.surface) cliArgs.push('--surface', args.surface);
36
+ if (args.diff) cliArgs.push('--diff', args.diff);
37
+ if (args.skip_rules) cliArgs.push('--skip-rules', args.skip_rules);
38
+ if (args.only_rules) cliArgs.push('--only-rules', args.only_rules);
39
+
40
+ try {
41
+ const result = spawnSync(process.execPath, cliArgs, {
42
+ encoding: 'utf8',
43
+ stdio: 'pipe',
44
+ maxBuffer: MAX_EXEC_BUFFER_SIZE,
45
+ timeout: DEFAULT_COMMAND_TIMEOUT
46
+ });
47
+
48
+ if (result.error) {
49
+ throw new Error(`Failed to execute command: ${result.error.message}`);
50
+ }
51
+ if (result.status !== 0) {
52
+ throw new Error(result.stderr || `Command failed with exit code ${result.status}`);
53
+ }
54
+
55
+ const parsed = JSON.parse(result.stdout);
56
+ return {
57
+ success: true,
58
+ summary: parsed.summary,
59
+ findings: parsed.findings,
60
+ unsupported_surfaces: parsed.unsupported_surfaces || []
61
+ };
62
+ } catch (err) {
63
+ return {
64
+ success: false,
65
+ error: err.message,
66
+ stdout: err.stdout || '',
67
+ stderr: err.stderr || '',
68
+ suggestion: 'Check that the repo path exists. The properties surface additionally needs python3 and network access on first run (venv + tree-sitter grammar); lint a specific --surface to skip it.'
69
+ };
70
+ }
71
+ }
72
+
73
+ module.exports = {
74
+ lintDocStrings
75
+ };