Skip to content

Commit c96a967

Browse files
committed
✨ feat(examples): bundle command example manifest in binary for runtime access
- Create command-example-manifest.txt indexing all import/follow fixture paths - Embed manifest in compiled binary so operators can discover examples without source checkout - Implement manifest sync test with env-gated refresh for drift detection - Consolidate operator guide fixture references into single catalog pointer - Add list-command-examples CLI output matching checked-in manifest file
1 parent a25dd47 commit c96a967

8 files changed

Lines changed: 365 additions & 10 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ They are not MCP tools and are not the normal end-user interaction path.
8484
Removes selected follow-imports checkpoint, retry-artifact, and stale-health artifacts. `--target-profile` can enable common cleanup target sets before you add path, age, dry-run, or `--summary-only` report filters.
8585
- `codex-mem audit-follow-imports [--target-profile all|artifacts|state|retry|health] [...]`
8686
Reports the same hygiene targets without deleting them. `--target-profile` can enable common audit target sets before you add path, age, fail-if-matched, or `--summary-only` controls.
87+
- `codex-mem list-command-examples`
88+
Prints the embedded import/follow example-manifest catalog shipped with the binary so operators and maintainers can discover the checked-in sample outputs without browsing the source tree. Add `--json` for a machine-readable catalog.
8789
- `codex-mem migrate`
8890
Opens the configured SQLite database and applies embedded migrations.
8991
- `codex-mem serve`

docs/go/maintainer/development-tracker.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,27 @@ Immediate next tasks:
151151

152152
### 2026-03-18 Session Update
153153

154+
- Completed: Extended `list-command-examples` with a `--json` mode that parses the embedded text manifest into a stable structured report. Runtime coverage now verifies both text and JSON output, so the packaged-binary example catalog can serve human lookup and simple automation without introducing a second checked-in source of truth.
155+
- In progress: none.
156+
- Blockers: none.
157+
- Next step: decide whether operators need filtering options such as `--command follow-imports`, or whether keeping the catalog intentionally small and whole is the better interface.
158+
159+
### 2026-03-18 Session Update
160+
161+
- Completed: Added a packaged-binary `list-command-examples` command that prints the embedded import/follow example manifest. The manifest remains checked in under `internal/app/testdata`, runtime code now embeds it for operator discovery, and CLI coverage verifies both the happy path and argument rejection.
162+
- In progress: none.
163+
- Blockers: none.
164+
- Next step: decide whether this manifest should eventually gain a machine-readable JSON form, or whether the current text format is the better stable operator-facing surface.
165+
166+
### 2026-03-18 Session Update
167+
168+
- Completed: Added a checked-in import/follow command example manifest under `internal/app/testdata` together with generator/sync tests and an env-gated refresh helper. The operator import-ingestion guide now points maintainers at one catalog file instead of repeating every sample-output path inline, which should reduce doc drift as the fixture set grows.
169+
- In progress: none.
170+
- Blockers: none.
171+
- Next step: decide whether the example catalog should stay testdata-backed only, or whether operators would benefit from a small packaged-binary command that prints the same manifest on demand.
172+
173+
### 2026-03-18 Session Update
174+
154175
- Completed: Added checked-in `follow-imports` audit-only sample outputs under `internal/app/testdata` together with sync and env-gated refresh tests. The new fixtures cover both single-input text output and multi-input aggregate JSON output, so nested batch counters plus suppression-reason summaries now have checked-in examples beyond ordinary assertions. The operator import-ingestion guide and README now point at those follow-mode examples and document the refresh helper.
155176
- In progress: none.
156177
- Blockers: none.

docs/go/operator/import-ingestion.md

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -316,16 +316,19 @@ When `--summary-only` is set, the aggregate counts stay the same but the detaile
316316

317317
Checked-in sample outputs for import and follow workflows live under [../../../internal/app/testdata](../../../internal/app/testdata/):
318318

319-
- [ingest-imports-audit-only-summary.txt](../../../internal/app/testdata/ingest-imports-audit-only-summary.txt)
320-
- [ingest-imports-audit-only-linked.json](../../../internal/app/testdata/ingest-imports-audit-only-linked.json)
321-
- [follow-imports-audit-only-single.txt](../../../internal/app/testdata/follow-imports-audit-only-single.txt)
322-
- [follow-imports-audit-only-multi.json](../../../internal/app/testdata/follow-imports-audit-only-multi.json)
323-
- [cleanup-follow-imports-daily-dry-run.txt](../../../internal/app/testdata/cleanup-follow-imports-daily-dry-run.txt)
324-
- [cleanup-follow-imports-filtered-cleanup.json](../../../internal/app/testdata/cleanup-follow-imports-filtered-cleanup.json)
325-
- [cleanup-follow-imports-target-profile-all.txt](../../../internal/app/testdata/cleanup-follow-imports-target-profile-all.txt)
326-
- [audit-follow-imports-daily-audit.txt](../../../internal/app/testdata/audit-follow-imports-daily-audit.txt)
327-
- [audit-follow-imports-filtered-audit.json](../../../internal/app/testdata/audit-follow-imports-filtered-audit.json)
328-
- [audit-follow-imports-target-profile-retry.json](../../../internal/app/testdata/audit-follow-imports-target-profile-retry.json)
319+
The full checked-in catalog is indexed in [command-example-manifest.txt](../../../internal/app/testdata/command-example-manifest.txt), which records each example's command, fixture name, output format, and relative path.
320+
321+
Packaged binaries can print that same embedded catalog on demand:
322+
323+
```powershell
324+
codex-mem.exe list-command-examples
325+
```
326+
327+
If automation needs a stable machine-readable catalog, use:
328+
329+
```powershell
330+
codex-mem.exe list-command-examples --json
331+
```
329332

330333
If a deliberate output change makes those fixtures drift, refresh the ingest fixtures from the repository root through the test-only maintainer helper:
331334

@@ -355,6 +358,14 @@ go test ./internal/app -run TestRefreshAuditFollowImportsExampleFixtures
355358
Remove-Item Env:CODEX_MEM_REFRESH_AUDIT_EXAMPLES
356359
```
357360

361+
Refresh the checked-in catalog after adding, removing, or renaming any example fixture:
362+
363+
```powershell
364+
$env:CODEX_MEM_REFRESH_EXAMPLE_MANIFEST = "1"
365+
go test ./internal/app -run TestRefreshCommandExampleManifest
366+
Remove-Item Env:CODEX_MEM_REFRESH_EXAMPLE_MANIFEST
367+
```
368+
358369
If you only need one fixture while iterating on a specific report shape, pass a comma-separated fixture-name subset instead of `all`:
359370

360371
```powershell
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package app
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
_ "embed"
8+
)
9+
10+
// EmbeddedCommandExampleManifest contains the checked-in import/follow example catalog.
11+
//
12+
//go:embed testdata/command-example-manifest.txt
13+
var EmbeddedCommandExampleManifest string
14+
15+
type listCommandExamplesOptions struct {
16+
JSON bool
17+
}
18+
19+
type commandExampleManifestEntry struct {
20+
Command string `json:"command"`
21+
Name string `json:"name"`
22+
RelativePath string `json:"path"`
23+
Format string `json:"format"`
24+
}
25+
26+
type commandExampleManifestReport struct {
27+
Version string `json:"version"`
28+
ExampleCount int `json:"example_count"`
29+
Examples []commandExampleManifestEntry `json:"examples"`
30+
}
31+
32+
func parseListCommandExamplesOptions(args []string) (listCommandExamplesOptions, error) {
33+
options := listCommandExamplesOptions{}
34+
for _, arg := range args {
35+
switch strings.TrimSpace(arg) {
36+
case "":
37+
continue
38+
case "--json":
39+
options.JSON = true
40+
default:
41+
return listCommandExamplesOptions{}, fmt.Errorf("unknown list-command-examples flag %q", arg)
42+
}
43+
}
44+
return options, nil
45+
}
46+
47+
func commandExampleManifestReportFromEmbedded() (commandExampleManifestReport, error) {
48+
return parseCommandExampleManifest(EmbeddedCommandExampleManifest)
49+
}
50+
51+
func parseCommandExampleManifest(raw string) (commandExampleManifestReport, error) {
52+
lines := strings.Split(strings.ReplaceAll(raw, "\r\n", "\n"), "\n")
53+
trimmed := make([]string, 0, len(lines))
54+
for _, line := range lines {
55+
line = strings.TrimSpace(line)
56+
if line == "" {
57+
continue
58+
}
59+
trimmed = append(trimmed, line)
60+
}
61+
if len(trimmed) < 2 {
62+
return commandExampleManifestReport{}, fmt.Errorf("command example manifest is incomplete")
63+
}
64+
const prefix = "command example manifest "
65+
if !strings.HasPrefix(trimmed[0], prefix) {
66+
return commandExampleManifestReport{}, fmt.Errorf("invalid command example manifest header %q", trimmed[0])
67+
}
68+
69+
report := commandExampleManifestReport{
70+
Version: strings.TrimSpace(strings.TrimPrefix(trimmed[0], prefix)),
71+
}
72+
for _, line := range trimmed[1:] {
73+
if strings.HasPrefix(line, "example_count=") {
74+
var count int
75+
if _, err := fmt.Sscanf(line, "example_count=%d", &count); err != nil {
76+
return commandExampleManifestReport{}, fmt.Errorf("parse command example manifest count: %w", err)
77+
}
78+
report.ExampleCount = count
79+
continue
80+
}
81+
entry, err := parseCommandExampleManifestEntry(line)
82+
if err != nil {
83+
return commandExampleManifestReport{}, err
84+
}
85+
report.Examples = append(report.Examples, entry)
86+
}
87+
if report.ExampleCount != len(report.Examples) {
88+
return commandExampleManifestReport{}, fmt.Errorf("command example manifest count mismatch: declared %d actual %d", report.ExampleCount, len(report.Examples))
89+
}
90+
return report, nil
91+
}
92+
93+
func parseCommandExampleManifestEntry(line string) (commandExampleManifestEntry, error) {
94+
fields := strings.Fields(line)
95+
entry := commandExampleManifestEntry{}
96+
for _, field := range fields {
97+
key, value, ok := strings.Cut(field, "=")
98+
if !ok {
99+
return commandExampleManifestEntry{}, fmt.Errorf("invalid command example manifest field %q", field)
100+
}
101+
switch key {
102+
case "command":
103+
entry.Command = value
104+
case "example":
105+
entry.Name = value
106+
case "format":
107+
entry.Format = value
108+
case "path":
109+
entry.RelativePath = value
110+
default:
111+
return commandExampleManifestEntry{}, fmt.Errorf("unknown command example manifest field %q", key)
112+
}
113+
}
114+
if entry.Command == "" || entry.Name == "" || entry.Format == "" || entry.RelativePath == "" {
115+
return commandExampleManifestEntry{}, fmt.Errorf("incomplete command example manifest entry %q", line)
116+
}
117+
return entry, nil
118+
}
119+
120+
func formatCommandExampleManifestJSON(report commandExampleManifestReport) (string, error) {
121+
return marshalIndented(report)
122+
}

internal/app/follow_import_example_fixtures_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"io"
77
"os"
8+
"path"
89
"path/filepath"
910
"slices"
1011
"strings"
@@ -14,6 +15,7 @@ import (
1415
)
1516

1617
const commandExampleDirName = "testdata"
18+
const commandExampleManifestName = "command-example-manifest.txt"
1719

1820
type commandExampleFixture[T any] struct {
1921
Name string
@@ -116,6 +118,64 @@ func listCommandExamples[T any](fixtures []commandExampleFixture[T], w io.Writer
116118
return err
117119
}
118120

121+
func commandExampleManifestEntriesFor[T any](command string, fixtures []commandExampleFixture[T]) []commandExampleManifestEntry {
122+
entries := make([]commandExampleManifestEntry, 0, len(fixtures))
123+
for _, fixture := range fixtures {
124+
format := "text"
125+
if fixture.JSON {
126+
format = "json"
127+
}
128+
entries = append(entries, commandExampleManifestEntry{
129+
Command: command,
130+
Name: fixture.Name,
131+
RelativePath: fixture.RelativePath,
132+
Format: format,
133+
})
134+
}
135+
return entries
136+
}
137+
138+
func commandExampleManifestEntries() []commandExampleManifestEntry {
139+
entries := make([]commandExampleManifestEntry, 0,
140+
len(ingestImportsExampleFixtures())+
141+
len(followImportsCommandExampleFixtures())+
142+
len(cleanupFollowImportsExampleFixtures())+
143+
len(auditFollowImportsExampleFixtures()))
144+
entries = append(entries, commandExampleManifestEntriesFor("ingest-imports", ingestImportsExampleFixtures())...)
145+
entries = append(entries, commandExampleManifestEntriesFor("follow-imports", followImportsCommandExampleFixtures())...)
146+
entries = append(entries, commandExampleManifestEntriesFor("cleanup-follow-imports", cleanupFollowImportsExampleFixtures())...)
147+
entries = append(entries, commandExampleManifestEntriesFor("audit-follow-imports", auditFollowImportsExampleFixtures())...)
148+
return entries
149+
}
150+
151+
func renderCommandExampleManifest(entries []commandExampleManifestEntry) []byte {
152+
lines := make([]string, 0, len(entries)+2)
153+
lines = append(lines, "command example manifest v1")
154+
for _, entry := range entries {
155+
lines = append(lines, fmt.Sprintf(
156+
"command=%s example=%s format=%s path=%s",
157+
entry.Command,
158+
entry.Name,
159+
entry.Format,
160+
path.Join(commandExampleDirName, entry.RelativePath),
161+
))
162+
}
163+
lines = append(lines, fmt.Sprintf("example_count=%d", len(entries)))
164+
return []byte(strings.Join(lines, "\n") + "\n")
165+
}
166+
167+
func writeCommandExampleManifest(baseDir string) (string, error) {
168+
body := renderCommandExampleManifest(commandExampleManifestEntries())
169+
path := filepath.Join(baseDir, commandExampleManifestName)
170+
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
171+
return "", err
172+
}
173+
if err := os.WriteFile(path, body, 0o644); err != nil {
174+
return "", err
175+
}
176+
return path, nil
177+
}
178+
119179
func cleanupFollowImportsExampleFixtures() []cleanupFollowImportsExampleFixture {
120180
return []cleanupFollowImportsExampleFixture{
121181
{

internal/app/run.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,25 @@ func Run(ctx context.Context, cfg config.Config, args []string, stdin io.Reader,
3434
case "version":
3535
_, err := fmt.Fprintf(stdout, "codex-mem %s\ncommit=%s\ndate=%s\n", buildinfo.Summary(), buildinfo.Commit, buildinfo.Date)
3636
return err
37+
case "list-command-examples":
38+
options, err := parseListCommandExamplesOptions(commandArgs)
39+
if err != nil {
40+
return err
41+
}
42+
if !options.JSON {
43+
_, err = io.WriteString(stdout, EmbeddedCommandExampleManifest)
44+
return err
45+
}
46+
report, err := commandExampleManifestReportFromEmbedded()
47+
if err != nil {
48+
return err
49+
}
50+
output, err := formatCommandExampleManifestJSON(report)
51+
if err != nil {
52+
return err
53+
}
54+
_, err = io.WriteString(stdout, output)
55+
return err
3756
case "migrate":
3857
instance, err := New(ctx, cfg)
3958
if err != nil {

0 commit comments

Comments
 (0)