Skip to content

bugfix: surface --format markdown and --exclude glob filter#238

Merged
stackbilt-admin merged 3 commits into
mainfrom
auto/bugfix/issue-236-format-markdown
Jun 23, 2026
Merged

bugfix: surface --format markdown and --exclude glob filter#238
stackbilt-admin merged 3 commits into
mainfrom
auto/bugfix/issue-236-format-markdown

Conversation

@stackbilt-admin

Copy link
Copy Markdown
Member

Summary

Changes

  • packages/cli/src/index.tsCLIOptions.format extended to 'text' | 'json' | 'markdown'; validation and help text updated
  • packages/cli/src/commands/surface.ts — handles options.format === 'markdown'; collects multiple --exclude values
  • packages/surface/src/index.ts — adds matchGlob() helper, updates walkFiles() to accept root + excludeGlobs, extends SurfaceInputSchema and ExtractOptions with excludeGlobs
  • packages/surface/src/__tests__/surface.test.ts — 5 new regression tests covering schema default, plain path exclusion, and ** wildcard exclusion

Test plan

  • charter surface --format markdown produces markdown output (no error)
  • charter surface --format text and --format json unaffected
  • charter surface --exclude packages/scaffold-core skips that directory
  • charter surface --exclude "**/codegen/**" skips any nested codegen/ directory
  • 760/760 tests pass (pnpm exec vitest run)

🤖 Generated with Claude Code

, #237)

- Add markdown as valid --format value (index.ts + surface.ts); reuses
  existing formatSurfaceMarkdown logic already exported from @stackbilt/surface
- Add --exclude <glob> flag to charter surface; supports plain path
  prefixes and * / ** wildcards to exclude directories from scanning
- Extend SurfaceInputSchema with excludeGlobs field + pass through in analyze()
- Add matchGlob() helper to surface package (no new deps)
- 5 new regression tests: excludeGlobs schema default, plain path prefix,
  ** wildcard, SurfaceInputSchema defaults check

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@stackbilt-admin stackbilt-admin left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CHANGES_REQUESTED

Reviewed against issues #236 (--format markdown rejected) and #237 (scaffold template false-positives). The markdown format fix and the glob filtering mechanism are both structurally correct. Three issues require changes before merge.


1. --exclude flag-value theft from adjacent positional flags (BLOCKING)

packages/cli/src/commands/surface.ts, lines 22–30

The --exclude collection loop and getFlag both operate on the same raw args array with no coordination. When --exclude immediately follows --root or --schema with no value between them, getFlag returns the string '--exclude' as the value for that flag.

Concrete failure:
```
charter surface --root --exclude src/gen
```

  • getFlag(args, '--root') returns '--exclude'rootArg = '--exclude' → resolves to /cwd/--exclude (non-existent)
  • The exclude loop correctly collects 'src/gen'
  • walkFiles catches the missing directory and returns empty; the command throws "No routes or schema tables detected" with no hint that flags were misparsed

Symmetric case: charter surface --schema --exclude src/genschemaFlag = '--exclude'path.resolve('--exclude') passed as schema path → silently reads nothing.

Fix: validate that args[i+1] is not a flag string (does not start with --) before pushing it as a glob value. Alternatively, use getFlag-style multi-value parsing so all flag reads go through one consistent pass.


2. --format markdown falls through to text output on all commands except surface (BLOCKING)

packages/cli/src/index.ts, line 112 — CLIOptions.format is now 'text' | 'json' | 'markdown'

Every other command (score, doctor, validate, drift, audit, bootstrap, etc.) uses the pattern:
```typescript
if (options.format === 'json') { ... } else { /* text output */ }
```
--format markdown lands in the else arm and emits ANSI-styled text output. There is no markdown formatter for those commands (which is fine), but there is also no error. A user who runs:
```
charter validate --format markdown
```
gets plain text with no warning that markdown is not supported for this command.

The fix does not require implementing markdown formatters everywhere. The cleanest fix is to scope 'markdown' as a surface-only flag — either validate it in surfaceCommand (accept it there, reject it globally), or emit a warning in run() when rawFormat === 'markdown' and the command is not surface. At minimum, the help text for the global --format option should document which commands support markdown.


3. matchGlob regex construction for \x00/ (**/) replacement

packages/surface/src/index.ts, lines 139–142

The replacement string for **/ is '(?:[^/]+/)*' — confirmed correct with the * quantifier present. However, a subtle ordering issue exists: the \x00/ substitution replaces **/ (two stars then a slash), but a pattern like a/**/b first converts ** to \x00, yielding a/\x00/b. Then \x00/ (null + slash) matches and produces a/(?:[^/]+/)*b. This is correct.

The edge case worth documenting: ** appearing at the very end of a pattern without a following slash (e.g., packages/**) hits the trailing \x00.* branch, not the \x00/ branch. This produces packages/.* which is correct for excluding all files under packages/. Both tests in the PR exercise this path correctly ('**/codegen/**' and 'packages/codegen'), but there is no test for packages/** (trailing **). Please add a test case for this pattern to the matchGlob or excludeGlobs test block to lock in the behavior.

This is not a blocker but is the pattern most likely to be used in real --exclude invocations.


Checklist for the issues being fixed:

  • --format markdown accepted and routes to formatSurfaceMarkdown()surface.ts line 24 is correct
  • options.format === 'markdown' correctly branches at line 51
  • --exclude flag collected multi-value correctly (when not adjacent to another flag)
  • excludeGlobs passed through SurfaceInputSchemaanalyze()extractSurface() — complete
  • Both walkFiles call sites in extractSurface pass root correctly (lines 511, 533)
  • SurfaceInputSchema.excludeGlobs default []analyze() at line 690 passes it
  • Issue #1 above (flag-value theft)
  • Issue #2 above (--format markdown silently degrades on other commands)
  • Issue #3 above (test coverage for packages/** trailing-** pattern)

Kurt Overmier and others added 2 commits June 23, 2026 06:49
- Scope --format markdown to 'charter surface' only; other subcommands
  now reject it with a clear error instead of silently emitting text
- Guard --exclude value consumption: skip args[i+1] if it starts with '--'
  to prevent adjacent flag names being consumed as glob patterns
- Add test for trailing ** pattern (packages/**)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CHANGELOG, README, surface README, and CLI help text updated for:
  - charter surface --format markdown (issue #236)
  - charter surface --exclude <glob> (issue #237)
  - extractSurface() / SurfaceInputSchema excludeGlobs field

Packages bumped 1.5.0 → 1.6.0 (minor: new additive features).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@stackbilt-admin stackbilt-admin merged commit e87c120 into main Jun 23, 2026
5 checks passed
@stackbilt-admin stackbilt-admin deleted the auto/bugfix/issue-236-format-markdown branch June 23, 2026 12:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant