Skip to content

feat: add stacklit export command (markdown overview) + CI matrix#36

Open
thegdsks wants to merge 3 commits intomasterfrom
feat/markdown-export
Open

feat: add stacklit export command (markdown overview) + CI matrix#36
thegdsks wants to merge 3 commits intomasterfrom
feat/markdown-export

Conversation

@thegdsks
Copy link
Copy Markdown
Member

@thegdsks thegdsks commented May 6, 2026

What this adds

A new stacklit export command that renders stacklit.json as readable markdown for PR descriptions, GitHub issues, and chat. Closes #28.

stacklit export                # markdown to stdout
stacklit export -o stacklit.md # markdown to a file
stacklit export --source path/to/index.json

The output covers the same data as stacklit.json but in a form a person can skim:

  • A header with project name, language, file count, and total lines
  • A language and framework summary table
  • Entrypoints and key directories
  • A modules table with purpose, size, and direct dependencies
  • Per-module collapsible <details> blocks listing exports and types
  • Dependency edges plus most-depended-on and isolated modules
  • Hot files from the last 90 days when git data is present
  • Hints for adding features, the test command, and env vars

Rendering is deterministic. Modules, dependencies, edges, and hot files are sorted, so the same stacklit.json produces byte-identical markdown across runs and machines. Pipe characters in user-supplied strings get escaped so they don't break markdown table rows.

Sample output (run on this repo)

# stacklit

go · single · 75 files · 8,960 lines

## Overview

| Language | Files | Lines |
|----------|-------|-------|
| go | 64 | 8,721 |
| typescript | 5 | 24 |
...

## Modules

| Module | Purpose | Files | Lines | Depends on |
|--------|--------|-------|-------|------------|
| `cmd/stacklit` | Stacklit | 1 | 15 | `internal/cli` |
| `internal/cli` | Command-line interface | 8 | 499 | `internal/config`, ... |
...

CI improvements

The Windows install bug from #32 went unnoticed because CI only built on Linux. This PR also tightens the workflow:

  • Test matrix across ubuntu-latest, macos-latest, and windows-latest
  • go test -race so data races in the parser/walker/engine fail the build
  • go vet step (cheap, no extra deps)
  • Smoke step replaced with generate --quiet + export so it doesn't try to open a browser, and so the new export command is exercised on every OS

Test plan

  • go vet ./... clean
  • go test ./... -race -count=1 passes
  • go run ./cmd/stacklit export produces a non-empty markdown file with a top-level heading
  • go run ./cmd/stacklit export --format json exits non-zero with a useful error
  • Reviewer to confirm the new CI matrix runs green on all three OSes
  • Reviewer to paste the markdown output into a GitHub PR/issue preview and confirm it renders

Closes #28

thegdsks added 2 commits May 5, 2026 19:33
Closes #28.

Reading stacklit.json directly is fine for AI agents, but it's hard to
skim in a PR description, GitHub issue, or chat message. The new export
command renders the same data as readable markdown:

  stacklit export                # markdown to stdout
  stacklit export -o stacklit.md # markdown to a file

The output includes a project header with file/line totals, a language
and framework summary, entrypoints, a modules table with direct
dependencies, per-module collapsible <details> blocks listing exports
and types, the dependency edge list, hot files from the last 90 days,
and the existing hints (test command, env vars, where to add features).

Rendering is deterministic. Modules, dependencies, and hot files are
sorted, so the same stacklit.json produces byte-identical markdown
across runs and machines, which makes it safe to commit.

Pipe characters in user-supplied strings (module purposes, etc.) are
escaped so they don't break markdown table rows.
Three improvements:

- Run the test suite on ubuntu-latest, macos-latest, and windows-latest.
  The Windows postinstall bug fixed in #35 went unnoticed because CI
  only built on Linux. Adding macOS and Windows runners catches
  platform-specific regressions before users do.
- Run go test with -race so data races in the parser, walker, and
  engine fail the build instead of slipping through.
- Add a go vet step (cheap signal, no extra deps).
- Replace the 'init' smoke test with a 'generate --quiet' + 'export'
  smoke test. Generate exercises the same code path without trying to
  open a browser, and the export step verifies the new markdown command
  produces a non-empty file with a top-level heading on every OS.

Uses go run instead of a pre-built binary so the smoke step works on
Windows without needing to know about the .exe extension.
Copilot AI review requested due to automatic review settings May 6, 2026 00:34
@thegdsks thegdsks added ci CI/CD and build pipeline enhancement New feature or request feature New feature request output JSON, HTML, or Markdown output format labels May 6, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a new stacklit export CLI command that converts an existing stacklit.json index into a human-readable, deterministic Markdown overview suitable for PRs/issues/chat, and updates CI to test across Linux/macOS/Windows while exercising the new command.

Changes:

  • Add Markdown renderer (internal/renderer/markdown.go) plus unit tests for determinism, escaping, and idempotent file writes.
  • Add stacklit export command to the CLI (supports stdout or -o file output; configurable --source JSON path).
  • Expand CI to a 3-OS matrix and replace the prior smoke test with generate --quiet + export.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
USAGE.md Documents the new stacklit export command and what the Markdown contains.
README.md Adds stacklit export to the quick command list.
internal/renderer/markdown.go Implements deterministic Markdown rendering and idempotent file writing.
internal/renderer/markdown_test.go Adds tests covering expected sections, pipe escaping, determinism, and WriteMarkdown idempotency.
internal/cli/root.go Registers the new export subcommand with the root CLI.
internal/cli/export.go Implements the stacklit export command (format/source/output flags).
.github/workflows/ci.yml Adds OS matrix and smoke-tests generate + export on all runners.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +144 to +150
fmt.Fprintf(sb, "| `%s` | %s | %d | %s | %s |\n",
n,
escapePipe(m.Purpose),
m.Files,
formatThousands(m.Lines),
deps,
)
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.

Fixed in cd250ea. Module names and each dependency name in the modules table now go through escapePipe per element. Added TestRenderMarkdownEscapesPipeEverywhere with adversarial inputs (weird|module, other|dep) to lock this in.

Comment on lines +84 to +91
if hasLangs {
sb.WriteString("| Language | Files | Lines |\n")
sb.WriteString("|----------|-------|-------|\n")
langs := sortedKeys(idx.Tech.Languages)
for _, lang := range langs {
s := idx.Tech.Languages[lang]
fmt.Fprintf(sb, "| %s | %d | %s |\n", lang, s.Files, formatThousands(s.Lines))
}
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.

Fixed in cd250ea. Language keys go through escapePipe now too, covered by the same adversarial test (weird|lang).

Comment thread internal/renderer/markdown.go Outdated
sb.WriteString("| File | Commits |\n")
sb.WriteString("|------|---------|\n")
for _, h := range idx.Git.HotFiles {
fmt.Fprintf(sb, "| `%s` | %d |\n", h.Path, h.Commits90d)
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.

Fixed in cd250ea. HotFile.Path is escaped with escapePipe before writing the row, and the new escape test asserts src/odd|name.go becomes src/odd\\|name.go.

Comment thread internal/renderer/markdown.go Outdated
sb.WriteString("---\n")
sb.WriteString("Generated by [stacklit](https://github.com/glincker/stacklit).")
if idx.GeneratedAt != "" {
fmt.Fprintf(sb, " Source: `stacklit.json` (%s).", idx.GeneratedAt)
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.

Good catch. Dropped the hard-coded filename in cd250ea; the footer now reads Generated at <timestamp>. since --source can point at any path. Test asserts Source: \stacklit.json`` no longer appears.

Comment thread internal/cli/export.go Outdated
RunE: func(cmd *cobra.Command, args []string) error {
data, err := os.ReadFile(source)
if err != nil {
return fmt.Errorf("could not read %s: %w (run 'stacklit init' first)", source, err)
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.

Updated in cd250ea to run 'stacklit generate' first, or 'stacklit init' so the non-browser path is the primary suggestion.

Comment thread internal/cli/export.go Outdated
fmt.Fprintf(cmd.OutOrStderr(), "Wrote %s\n", output)
return nil
default:
return fmt.Errorf("unknown format %q (supported: md)", format)
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.

Updated in cd250ea to (supported: md, markdown) so both accepted spellings are listed.

Six issues from Copilot review:

- Escape pipe characters in every table cell that interpolates a
  user-supplied string, not just module purpose. Module names,
  dependency names, language names, hot-file paths, and dependency
  edge endpoints all go through escapePipe now. Backticks don't
  protect pipes in GitHub markdown tables.
- Drop the hard-coded 'Source: stacklit.json' from the footer. The
  --source flag can point at any JSON path, so naming a fixed file
  was misleading. The footer now just prints 'Generated at <ts>'.
- Update the read-error hint to suggest 'stacklit generate' first
  (the non-browser entry point that CI uses) and mention 'stacklit
  init' as an alternative.
- Update the unknown-format error to list 'md, markdown' since both
  spellings are accepted by the switch.

Adds TestRenderMarkdownEscapesPipeEverywhere to cover module name,
dep name, language name, and hot-file path escaping with adversarial
inputs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci CI/CD and build pipeline enhancement New feature or request feature New feature request output JSON, HTML, or Markdown output format

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Export to Markdown format

2 participants