Skip to content

feat: configurable design-spec save location with arc-owned frontmatter - #54

Merged
bfirestone merged 8 commits into
mainfrom
feat/configurable-spec-dir
Jun 8, 2026
Merged

feat: configurable design-spec save location with arc-owned frontmatter#54
bfirestone merged 8 commits into
mainfrom
feat/configurable-spec-dir

Conversation

@bfirestone

Copy link
Copy Markdown
Contributor

Summary

Implements epic arc-0d80.06kiqm — a per-project, configurable save location for design specs, with arc-owned YAML frontmatter whose status: stays truthful across arc plan approve.

Zero migration: with plans.dir unset, behavior is exactly as today (docs/plans/). No new command surface — folded into existing arc config / arc plan.

What changed

Foundation (internal/config, internal/plans)

  • PlansConfig{Dir} on Config, defaulting to docs/plans
  • ExpandPlansDir / SanitizeSlug{project} / {prefix} template expansion, ~ and relative-to-cwd resolution
  • New internal/plans package: ReadFrontmatter / EnsureFrontmatter / SetStatus (+ ErrNoFrontmatter), atomic writes; robust against horizontal-rule --- lines in the body, EOF without trailing newline, and CRLF files

Config surface (cmd/arc/config.go, internal/config/validate.go)

  • plans.dir validation: rejects empty, .., and unknown template vars (only project/prefix allowed)
  • arc config get/set plans.dir
  • arc config get plans.dir --resolved → absolute per-project directory; hard-errors when no project resolves, errors on unsupported keys

Frontmatter lifecycle (cmd/arc/plan.go)

  • arc plan create injects frontmatter (title/date/project/status: in_review/tags: [arc, design-spec]/arc_review: {kind: legacy, id}); --title override and --no-frontmatter opt-out; deriveTitle pulls the first # heading, else the filename sans YYYY-MM-DD- prefix
  • arc plan approve syncs status: approved (surgical line replace, best-effort — never fails the approve)

Design constraint

Legacy planner only — no arc share integration (per reviewer guidance, encrypted sharing is being removed). arc_review.kind is always legacy.

Testing

  • go test -race ./... green (16 packages); unit tests for templating, frontmatter parse/write/CRLF/EOF, validation, and deriveTitle edge cases
  • Manual e2e verified: plan create injects frontmatter with H1-derived title and preserves a body --- rule; plan approve flips status to approved; --no-frontmatter skips injection

Out of scope

T5 brainstorm-skill wiring ships as a separate agent-marketplace PR.

Design: docs/plans/2026-06-07-configurable-spec-save-location.md (kept untracked).

Closes arc-0d80.06kiqm.

Add PlansConfig to Config with default "docs/plans", SanitizeSlug and ExpandPlansDir helpers, and the plans package with ReadFrontmatter/EnsureFrontmatter/SetStatus for atomic YAML frontmatter management in design-spec markdown files.
- Replace naive `\n---` byte search with `findClosingDelim`, which requires the
  closing delimiter to be exactly `---` followed by `\n`, `\r`, or EOF — preventing
  `----` and `--- x` lines from being mistaken for the closer.
- Return `[]byte{}` (not nil) when the closing `---` has no trailing newline,
  so no body content is silently dropped on EOF-terminated files.
- Fix `SetStatus` CRLF handling: trim `\r\n` (not just `\n`) when scanning for
  the closing delimiter, and preserve the original line ending (`\r\n` or `\n`)
  when rewriting the `status:` line.
- Harden existing `os.WriteFile` setup calls in tests to check error returns.
Adds plans.dir validation (empty, .., unknown template vars) to Validate(),
wires plans.dir into config get/set, and adds --resolved flag to config get
for expanding {project}/{prefix} vars to an absolute path.
- Replace inline regexp.MustCompile in Validate() with the package-level
  templateVarRe from template.go; remove now-unused "regexp" import.
- Break on first unknown template variable so the first offender is
  reported rather than the last (last-write-wins bug).
- Check os.Getwd() error in --resolved handler and return a wrapped error.
- Return an explicit error when --resolved is used with a key other than
  plans.dir instead of silently ignoring the flag.
On `arc plan create`, injects YAML frontmatter (title/date/project/status/tags/arc_review) into the plan file via plans.EnsureFrontmatter unless --no-frontmatter is passed. Derives title from first H1 heading or filename fallback. On `arc plan approve`, best-effort syncs the frontmatter status field to "approved" via plans.SetStatus.
…eTitle tests

- Add scanner.Err() check in deriveTitle with a stderr warning on mid-read I/O error
- Convert all fmt.Fprintf(os.Stderr) calls to the _, _ = idiom used across the codebase
- Replace inline var block for titleFlag/noFrontmatter with per-var doc-comment style matching paths.go
- Clarify deriveTitle doc comment: matches exactly `# ` (## lines intentionally excluded)
- Add three new edge-case tests: non-existent path, empty file, H2-only heading
Match local linter version. Add tagliatelle yaml:snake (Obsidian-queryable
frontmatter tags) and relax goconst on _test.go files (mirrors the existing
dupl test exclusion).
Behavior-preserving cleanup: extract repeated string literals into named
constants (config keys, command verbs, statuses via types.Status*), discard
unhandled best-effort errors, replace magic numbers, switch over if/else
chains, move whitebox tests to _test packages, errors.Is for sentinels,
0o600 test file perms, gofmt/gofumpt formatting, and doc-comment density.
@bfirestone
bfirestone merged commit a96b01f into main Jun 8, 2026
3 checks passed
@bfirestone
bfirestone deleted the feat/configurable-spec-dir branch June 8, 2026 16:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant