Skip to content

Commit a030a7f

Browse files
authored
feat: add stacklit export command for markdown overview (#36)
Closes #28. Adds 'stacklit export' which renders stacklit.json as readable markdown for PR descriptions, GitHub issues, and chat. Output is deterministic, with pipe-escaping in every table cell so user-supplied strings cannot break rows. Footer reports the index timestamp instead of a hard-coded filename so --source can point anywhere. CI now runs on a Linux/macOS/Windows matrix with go vet and -race enabled. Smoke step exercises 'generate --quiet' + 'export' so the new command is covered on every OS.
1 parent e89c82b commit a030a7f

7 files changed

Lines changed: 730 additions & 4 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,35 @@ on:
77

88
jobs:
99
test:
10-
runs-on: ubuntu-latest
10+
name: test (${{ matrix.os }})
11+
runs-on: ${{ matrix.os }}
12+
strategy:
13+
fail-fast: false
14+
matrix:
15+
os: [ubuntu-latest, macos-latest, windows-latest]
1116
steps:
1217
- uses: actions/checkout@v4
1318
- uses: actions/setup-go@v5
1419
with:
1520
go-version: '1.25'
16-
- run: go test ./... -v -count=1
17-
- run: go build -o stacklit ./cmd/stacklit
18-
- run: ./stacklit init
21+
cache: true
22+
23+
- name: go vet
24+
run: go vet ./...
25+
26+
- name: go test
27+
run: go test ./... -race -count=1
28+
29+
- name: build
30+
run: go build ./cmd/stacklit
31+
32+
- name: smoke test (generate)
33+
run: go run ./cmd/stacklit generate --quiet
34+
shell: bash
35+
36+
- name: smoke test (export markdown)
37+
run: |
38+
go run ./cmd/stacklit export -o stacklit-ci.md
39+
test -s stacklit-ci.md
40+
head -1 stacklit-ci.md | grep -q '^# '
41+
shell: bash

‎README.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,8 @@ stacklit diff # check if index is stale
261261
stacklit serve # start MCP server
262262
stacklit derive # print compact nav map (~250 tokens)
263263
stacklit derive --inject claude # inject map into CLAUDE.md
264+
stacklit export # print readable markdown overview
265+
stacklit export -o stacklit.md # write markdown overview to a file
264266
stacklit setup # auto-configure all detected AI tools
265267
stacklit setup claude # configure Claude Code + MCP
266268
stacklit setup cursor # configure Cursor + MCP

‎USAGE.md‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ Regenerates `stacklit.html` and opens it in your browser.
118118
| `stacklit derive` | Print compact navigation map (~250 tokens) to stdout |
119119
| `stacklit derive --inject claude` | Inject map into CLAUDE.md |
120120
| `stacklit derive --inject cursor` | Inject map into .cursorrules |
121+
| `stacklit export` | Print a readable markdown overview to stdout |
122+
| `stacklit export -o stacklit.md` | Write the markdown overview to a file |
121123
| `stacklit setup` | Auto-detect and configure all AI tools |
122124
| `stacklit setup claude` | Configure Claude Code (CLAUDE.md + MCP) |
123125
| `stacklit setup cursor` | Configure Cursor (.cursorrules + MCP) |
@@ -150,6 +152,30 @@ The server auto-reloads when `stacklit.json` changes on disk.
150152

151153
---
152154

155+
## Exporting to markdown
156+
157+
`stacklit.json` is built for AI agents and tooling. When you want something a person can skim — a PR description, a GitHub issue, a Slack message — use the markdown export:
158+
159+
```bash
160+
stacklit export # print to stdout
161+
stacklit export -o stacklit.md # write to a file
162+
```
163+
164+
The markdown contains:
165+
166+
- A title with the project name, language, file count, and total lines
167+
- A language and framework summary table
168+
- Entrypoints and key directories (when present)
169+
- A modules table with purpose, size, and direct dependencies
170+
- Per-module collapsible `<details>` blocks listing exports and types
171+
- A dependency edge list, plus most-depended-on and isolated modules
172+
- Hot files from the last 90 days (when git data is present)
173+
- Hints: how to add a feature, the test command, env vars
174+
175+
Output is deterministic: the same `stacklit.json` produces byte-identical markdown across runs and machines, so it's safe to commit.
176+
177+
---
178+
153179
## Configuration
154180

155181
Create `.stacklitrc.json` in your project root (optional):

‎internal/cli/export.go‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package cli
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"os"
7+
8+
"github.com/glincker/stacklit/internal/renderer"
9+
"github.com/glincker/stacklit/internal/schema"
10+
"github.com/spf13/cobra"
11+
)
12+
13+
func newExportCmd() *cobra.Command {
14+
var format, source, output string
15+
16+
cmd := &cobra.Command{
17+
Use: "export",
18+
Short: "Export the index in a human-readable format",
19+
Long: `Renders stacklit.json in a different format for sharing.
20+
21+
Currently supports markdown, which produces a readable project overview
22+
suitable for pasting into PR descriptions, GitHub issues, or chat.
23+
24+
Examples:
25+
stacklit export Print markdown to stdout
26+
stacklit export --format md Same as above
27+
stacklit export -o stacklit.md Write markdown to a file
28+
stacklit export --source ./other.json Read from a non-default index`,
29+
RunE: func(cmd *cobra.Command, args []string) error {
30+
data, err := os.ReadFile(source)
31+
if err != nil {
32+
return fmt.Errorf("could not read %s: %w (run 'stacklit generate' first, or 'stacklit init')", source, err)
33+
}
34+
var idx schema.Index
35+
if err := json.Unmarshal(data, &idx); err != nil {
36+
return fmt.Errorf("could not parse %s: %w", source, err)
37+
}
38+
39+
switch format {
40+
case "md", "markdown":
41+
if output == "" {
42+
fmt.Print(renderer.RenderMarkdown(&idx))
43+
return nil
44+
}
45+
if err := renderer.WriteMarkdown(&idx, output); err != nil {
46+
return fmt.Errorf("could not write %s: %w", output, err)
47+
}
48+
fmt.Fprintf(cmd.OutOrStderr(), "Wrote %s\n", output)
49+
return nil
50+
default:
51+
return fmt.Errorf("unknown format %q (supported: md, markdown)", format)
52+
}
53+
},
54+
}
55+
56+
cmd.Flags().StringVar(&format, "format", "md", "Output format (md)")
57+
cmd.Flags().StringVar(&source, "source", "stacklit.json", "Path to the index JSON")
58+
cmd.Flags().StringVarP(&output, "output", "o", "", "Write to file instead of stdout")
59+
return cmd
60+
}

‎internal/cli/root.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,5 @@ func init() {
2626
rootCmd.AddCommand(newServeCmd())
2727
rootCmd.AddCommand(newDeriveCmd())
2828
rootCmd.AddCommand(newSetupCmd())
29+
rootCmd.AddCommand(newExportCmd())
2930
}

0 commit comments

Comments
 (0)