@redpanda-data/docs-extensions-and-macros 5.14.0 → 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 (44) hide show
  1. package/bin/doc-tools-mcp.js +88 -0
  2. package/bin/doc-tools.js +252 -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/package.json +2 -1
  10. package/tools/generate-cli-docs.js +28 -67
  11. package/tools/lint-strings/diff.js +137 -0
  12. package/tools/lint-strings/engine.js +163 -0
  13. package/tools/lint-strings/fixtures/connect/lint_connect.go +67 -0
  14. package/tools/lint-strings/fixtures/crd/crd-ref-docs-config.yaml +15 -0
  15. package/tools/lint-strings/fixtures/crd/lint_types.go +57 -0
  16. package/tools/lint-strings/fixtures/helm/lint_values.yaml +47 -0
  17. package/tools/lint-strings/fixtures/metrics/lint_probe.cc +41 -0
  18. package/tools/lint-strings/fixtures/properties/lint_fixture.cc +51 -0
  19. package/tools/lint-strings/fixtures/properties/lint_fixture.h +33 -0
  20. package/tools/lint-strings/fixtures/rpk/lint_cmd.go +69 -0
  21. package/tools/lint-strings/go-source.js +327 -0
  22. package/tools/lint-strings/index.js +229 -0
  23. package/tools/lint-strings/rules/common.js +148 -0
  24. package/tools/lint-strings/rules/verbatim-asciidoc.js +108 -0
  25. package/tools/lint-strings/source-text.js +41 -0
  26. package/tools/lint-strings/surfaces/connect.js +233 -0
  27. package/tools/lint-strings/surfaces/crd.js +239 -0
  28. package/tools/lint-strings/surfaces/helm.js +159 -0
  29. package/tools/lint-strings/surfaces/metrics.js +368 -0
  30. package/tools/lint-strings/surfaces/properties.js +216 -0
  31. package/tools/lint-strings/surfaces/rpk.js +377 -0
  32. package/tools/overrides-audit/README.adoc +152 -0
  33. package/tools/overrides-audit/adapters/connect.js +144 -0
  34. package/tools/overrides-audit/adapters/properties.js +117 -0
  35. package/tools/overrides-audit/adapters/rpk.js +192 -0
  36. package/tools/overrides-audit/classify.js +546 -0
  37. package/tools/overrides-audit/index.js +185 -0
  38. package/tools/preview-string/index.js +245 -0
  39. package/tools/property-extractor/Makefile +9 -4
  40. package/tools/property-extractor/parser.py +26 -0
  41. package/tools/property-extractor/property_extractor.py +41 -0
  42. package/tools/property-extractor/transformers.py +15 -2
  43. package/tools/rpk-docs/detect-platform-commands.js +8 -1
  44. 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' }
@@ -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,146 @@ validation
2465
2475
  }
2466
2476
  })
2467
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
+
2468
2617
  programCli.addCommand(automation)
2469
2618
  programCli.addCommand(validation)
2619
+ programCli.addCommand(overridesGroup)
2470
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,
@@ -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
+ };