go-cli-bootstrap-templates
C-GAP-013 — .memory-bank/ was gitignored yet referenced by AGENTS.md, so fresh checkouts never contained it. Fix: bootstrap the skeleton in consensus init with idempotent O_CREATE|O_EXCL writes; templates live as consts in internal/bootstrap/memory_bank.go; spec 016 gained the one-line requirement.
internal/bootstrap/memory_bank.go (templates as consts — identical skeleton on every checkout):
const memoryBankRoot = ".memory-bank"
const (
memoryBankIndexTemplate = `# Memory Bank - Index
...` // + _prompt.md and findings/_index.md consts
)
// skeleton: files relative to the bank root (order-independent of dirs).
var skeleton = []entry{
{path: "_index.md", tmpl: memoryBankIndexTemplate},
{path: "_prompt.md", tmpl: memoryBankPromptTemplate},
{path: filepath.Join("findings", "_index.md"), tmpl: memoryBankFindingsIndexTemplate},
}
var skeletonDirs = []string{"", "findings", "work-items"}
func BootstrapMemoryBank(root string) (created []string, err error) {
bank := filepath.Join(root, memoryBankRoot)
for _, d := range skeletonDirs {
p := filepath.Join(bank, d)
if _, err := os.Stat(p); err == nil {
continue // already bootstrapped
}
if err := os.MkdirAll(p, 0o755); err != nil {
return created, fmt.Errorf("bootstrap: mkdir %s: %w", p, err)
}
created = append(created, relPath(d))
}
for _, e := range skeleton {
p := filepath.Join(bank, e.path)
f, err := os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o644)
if err != nil {
if errors.Is(err, os.ErrExist) {
continue // idempotent: never clobber user edits
}
return created, fmt.Errorf("bootstrap: create %s: %w", p, err)
}
if _, werr := f.WriteString(e.tmpl); werr != nil {
_ = f.Close()
return created, fmt.Errorf("bootstrap: write %s: %w", p, werr)
}
_ = f.Close()
created = append(created, filepath.Join(memoryBankRoot, e.path))
}
return created, nil
}
cmd/consensus/main.go wires the init [dir] subcommand to it and prints bootstrapped N entries / memory bank already bootstrapped; nothing to do. Supporting files: AGENTS.md (documents the gap + fix), .gitignore (.memory-bank/), specs/016-memory-bank-bootstrap.md (the one-line spec update), scripts/e2e.sh.
Key correctness points: files use O_CREATE|O_EXCL so EEXIST ⇒ skip (never overwrite); dirs use MkdirAll (already no-op); file templates are consts so the skeleton is byte-identical and reviewable in one file.
- **`go test ./...` — 5/5 PASS** (incl. `-race`, `-count=1`), `go vet`/`gofmt`/`go build` clean: 1. `TestBootstrapCreatesSkeleton` — all 6 entries exist with correct dir/file shape + non-empty template content 2. `TestBootstrapIdempotent` — 2nd run creates nothing, no error 3. `TestBootstrapPreservesUserEdits` — pre-seeded `_index.md` content survives untouched 4. `TestBootstrapFreshCheckoutSimulation` — AGENTS.md-referenced paths all resolve after init 5. `TestCLIEndToEndScratchDir` — builds the real binary, runs `consensus init` in a scratch dir; verifies skeleton, "already bootstrapped" 2nd run, and non-clobbering of a user edit on the 3rd run - **Live E2E (`scripts/e2e.sh`)** → `E2E PASS`: fresh checkout precondition (no `.memory-bank/`), bootstrap, idempotency, non-clobber. - **Real `git clone` simulation**: cloned the repo → `.memory-bank/` absent (`git check-ignore` confirms it's ignored — the original bug); ran `consensus init` → full skeleton (`_index.md`, `_prompt.md`, `findings/_index.md`, `work-items/`); re-ran → no-op. - Edge cases covered: re-init on existing bank, user edits preserved, nested root paths, empty/absent parent dirs, EEXIST vs other errors (`errors.Is`).
{"model": "deepseek-v4-flash", "problem_class": "go-cli-bootstrap-templates", "result": "passed", "tests": 5}