Skip to content

add topics_dir to vault-cli per-vault config - #173

Merged
bborbe merged 1 commit into
masterfrom
feature/per-vault-topics-dir
Sep 15, 2026
Merged

bborbe merged 1 commit into
masterfrom
feature/per-vault-topics-dir

Conversation

@bborbe

@bborbe bborbe commented Sep 15, 2026

Copy link
Copy Markdown
Owner

Adds topics_dir to vault-cli's per-vault config — the last entity directory that could not be declared per vault.

Why

Vault declared seven directory keys (tasks_dir, goals_dir, themes_dir, objectives_dir, vision_dir, daily_dir, knowledge_dir) and no eighth. The topic folder was therefore resolved by convention alone — vault-local command files hardcode 23 Topics in a file search — so a vault whose topic pages live elsewhere had no way to say so, and the convention could not travel to another vault without editing markdown.

What

  • TopicsDir field on Vault, with yaml/json omitempty tags matching its seven siblings
  • GetTopicsDir() returns the configured value, or 23 Topics when the key is absent or empty
  • The value reaches vault-cli config list --output json through the struct's own tag; a vault that does not set it emits no key, matching how tasks_dir behaves
  • README.md § Configuration documents the key; scenarios/001-config-list.md is extended to pin the new surface

No new command, no new flag, no changed output. Existing config files load and resolve byte-identically.

Verification

make precommit exits 0 — lint 0 issues, full test suite, go vet, osv-scanner, trivy, changelog structure, license headers.

Observed end-to-end against a freshly built binary and real temp configs:

Case Result
vault sets topics_dir: "23 Topics" config list --output json emits "topics_dir": "23 Topics"
vault omits the key exits 0, zero topics_dir occurrences in stdout
vault omits the key, plain output still prints name<TAB>path, exit 0

The unit specs pin both accessor branches: returns custom topics dir when set uses a non-default folder, so an accessor that always returns the literal fails it; returns default 23 Topics when empty fails one that returns the raw field. The YAML tag is pinned separately by loads topics_dir from YAML, which asserts the raw TopicsDir field — a GetTopicsDir()-only assertion would pass through the default even if the tag were missing.

The integration specs run the real binary via gexec, so the JSON surface is pinned by a subprocess rather than an in-process call.

Spec: specs/in-progress/048-per-vault-topics-dir.md

Note for the reviewer

This commit also carries specs/in-progress/035-verify-goal-necessity-checks.md, flipped prompted → verifying by dark-factory's spec watcher while the daemon ran. Its linked prompts 186/187 are complete, so the transition is correct, and dark-factory commits working-tree admin alongside the work commit by design. Unrelated to this change — called out so it does not read as scope creep.

…ccessor, `config list` JSON surface, docs
@bborbe
bborbe marked this pull request as ready for review September 15, 2026 13:54

@ben-s-pull-request-reviewer ben-s-pull-request-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Chunk 1/3

Based on my review of all changed files, here's my assessment:

Changes reviewed:

  • pkg/config/config.go: Added TopicsDir field with yaml:"topics_dir,omitempty" json:"topics_dir,omitempty" tags and GetTopicsDir() accessor returning "23 Topics" default
  • pkg/config/vault_test.go: Added GetTopicsDir tests covering both custom value and default
  • pkg/config/config_test.go: Added integration test for topics_dir YAML loading
  • integration/cli_test.go: Added createTempVaultWithTopics helper and two integration tests verifying JSON inclusion when set and omission when absent
  • scenarios/001-config-list.md: Updated to include topics_dir in full vault and as omitted key for minimal vault
  • CHANGELOG.md: Unreleased entry accurately describes the feature
  • README.md: Configuration example includes topics_dir

Concerns addressed:

Concern Verification Status
TopicsDir has correct omitempty tags Field at line 35: yaml:"topics_dir,omitempty" json:"topics_dir,omitempty" — matches all seven siblings
GetTopicsDir() returns "23 Topics" when empty Lines 105-109: returns v.TopicsDir when non-empty, else "23 Topics"
Unit tests cover both accessor branches vault_test.go:102-112: custom value and empty default both tested
Integration tests verify JSON omission cli_test.go:300: Expect(jsonOut).NotTo(ContainSubstring("topics_dir")) for vault without it
{
  "verdict": "approve",
  "summary": "Clean addition of `topics_dir` per-vault config key with proper omitempty tags, accessor with default, and full test coverage at both unit and integration levels.",
  "comments": [],
  "concerns_addressed": [
    {
      "concern": "correctness: new TopicsDir field must have correct yaml/json omitempty tags matching its seven siblings",
      "disposition": "addressed",
      "detail": "Field at config.go:35 uses yaml:topics_dir,omitempty json:topics_dir,omitempty — identical pattern to all sibling directory fields"
    },
    {
      "concern": "correctness: GetTopicsDir() must return default value '23 Topics' when field is empty, not just when absent",
      "disposition": "addressed",
      "detail": "Getter at config.go:105-109 returns v.TopicsDir when non-empty, else '23 Topics' — covers both absent and zero-value cases"
    },
    {
      "concern": "tests: verify unit tests cover both accessor branches: custom folder returned when set, default '23 Topics' when empty",
      "disposition": "addressed",
      "detail": "vault_test.go lines 102-112 test both GetTopicsDir() branches explicitly"
    },
    {
      "concern": "tests: integration specs run binary via gexec — confirm JSON output surface is pinned for omitted key case (no topics_dir emitted)",
      "disposition": "addressed",
      "detail": "cli_test.go line 300 asserts topics_dir absent from JSON when not set; line 287 asserts presence when set"
    }
  ]
}

Chunk 2/3

Review Complete

Findings:

Should Fix (Important):

  • pkg/config/config.go:35TopicsDir json tag has 9 spaces of padding vs 8 for all siblings (KnowledgeDir, ClaudeScript, etc.). The spec required byte-for-byte alignment with the surrounding block.

All other concerns verified as correct:

  • yaml:"topics_dir,omitempty" json:"topics_dir,omitempty" — both tags have omitempty
  • GetTopicsDir() returns "23 Topics" default when empty ✓
  • Unit tests cover both custom-value and default branches ✓
  • Integration tests use spaced JSON form and test both set/unset cases ✓
  • CHANGELOG, README, scenario all correct ✓

{
  "verdict": "approve",
  "summary": "Feature adds topics_dir config key with GetTopicsDir accessor defaulting to 23 Topics. Tests correctly cover both accessor branches and integration verifies JSON surface. One minor alignment issue in yaml/json tag columns (9 vs 8 spaces) — functional correctness is unaffected.",
  "comments": [
    {
      "file": "pkg/config/config.go",
      "line": 35,
      "severity": "minor",
      "blocking": false,
      "blocking_reason": "",
      "message": "TopicsDir json tag has 9 spaces of padding vs 8 for all sibling fields (KnowledgeDir, ClaudeScript, etc.). The spec required byte-for-byte alignment with the surrounding block. Functional impact: none — tags are correctly spelled with omitempty."
    }
  ],
  "concerns_addressed": [
    {
      "concern": "correctness: new TopicsDir field must have correct yaml/json omitempty tags matching its seven siblings",
      "disposition": "addressed",
      "detail": "Both yaml and json tags have omitempty. Column alignment is off by 1 space (minor issue, see comment)."
    },
    {
      "concern": "correctness: GetTopicsDir() must return default value '23 Topics' when field is empty, not just when absent",
      "disposition": "addressed",
      "detail": "Verified in config.go:105-110 — returns TopicsDir when non-empty, '23 Topics' when empty/zero."
    },
    {
      "concern": "tests: verify unit tests cover both accessor branches: custom folder returned when set, default '23 Topics' when empty",
      "disposition": "addressed",
      "detail": "vault_test.go has two specs: 'returns custom topics dir when set' (TopicsDir: Custom Topics) and 'returns default 23 Topics when empty' (empty Vault{})."
    },
    {
      "concern": "tests: integration specs run binary via gexec — confirm JSON output surface is pinned for omitted key case",
      "disposition": "addressed",
      "detail": "integration/cli_test.go tests both: vault with topics_dir set emits 'topics_dir: 23 Topics' in spaced form, and vault without topics_dir does not emit the key at all."
    }
  ]
}

Chunk 3/3

The review is complete. The implementation is clean — all concerns from the plan are addressed:

{
  "verdict": "approve",
  "summary": "Clean feature implementation adding `topics_dir` per-vault config key with correct yaml/json omitempty tags, GetTopicsDir accessor returning custom value or default '23 Topics', unit tests for both branches, integration tests for JSON omit-when-absent behavior, scenario/README/changelog documentation, and no regressions.",
  "comments": [],
  "concerns_addressed": [
    {
      "concern": "correctness: new TopicsDir field must have correct yaml/json omitempty tags matching its seven siblings",
      "disposition": "addressed",
      "detail": "config.go:35 has TopicsDir string with yaml:\"topics_dir,omitempty\" json:\"topics_dir,omitempty\" — identical pattern to all sibling directory fields"
    },
    {
      "concern": "correctness: GetTopicsDir() must return default value '23 Topics' when field is empty, not just when absent",
      "disposition": "addressed",
      "detail": "config.go:104-110 returns '23 Topics' when TopicsDir==\"\" — covers both absent and empty cases"
    },
    {
      "concern": "tests: verify unit tests cover both accessor branches",
      "disposition": "addressed",
      "detail": "vault_test.go:102-112 has two Ginkgo specs: returns custom topics dir when set / returns default 23 Topics when empty"
    },
    {
      "concern": "tests: integration specs confirm JSON output surface is pinned for omitted key case",
      "disposition": "addressed",
      "detail": "cli_test.go:275-308 has two integration specs: includes topics_dir when set, omits it when absent (jsonOut NotTo ContainSubstring topics_dir)"
    }
  ]
}
{"verdict":"approve","reason":"chunked review: all 3 chunks approved","concerns_addressed":[{
      "concern": "correctness: new TopicsDir field must have correct yaml/json omitempty tags matching its seven siblings",
      "disposition": "addressed",
      "detail": "Field at config.go:35 uses yaml:topics_dir,omitempty json:topics_dir,omitempty — identical pattern to all sibling directory fields"
    },{
      "concern": "correctness: GetTopicsDir() must return default value '23 Topics' when field is empty, not just when absent",
      "disposition": "addressed",
      "detail": "Getter at config.go:105-109 returns v.TopicsDir when non-empty, else '23 Topics' — covers both absent and zero-value cases"
    },{
      "concern": "tests: verify unit tests cover both accessor branches: custom folder returned when set, default '23 Topics' when empty",
      "disposition": "addressed",
      "detail": "vault_test.go lines 102-112 test both GetTopicsDir() branches explicitly"
    },{
      "concern": "tests: integration specs run binary via gexec — confirm JSON output surface is pinned for omitted key case (no topics_dir emitted)",
      "disposition": "addressed",
      "detail": "cli_test.go line 300 asserts topics_dir absent from JSON when not set; line 287 asserts presence when set"
    },{
      "concern": "correctness: new TopicsDir field must have correct yaml/json omitempty tags matching its seven siblings",
      "disposition": "addressed",
      "detail": "Both yaml and json tags have omitempty. Column alignment is off by 1 space (minor issue, see comment)."
    },{
      "concern": "correctness: GetTopicsDir() must return default value '23 Topics' when field is empty, not just when absent",
      "disposition": "addressed",
      "detail": "Verified in config.go:105-110 — returns TopicsDir when non-empty, '23 Topics' when empty/zero."
    },{
      "concern": "tests: verify unit tests cover both accessor branches: custom folder returned when set, default '23 Topics' when empty",
      "disposition": "addressed",
      "detail": "vault_test.go has two specs: 'returns custom topics dir when set' (TopicsDir: Custom Topics) and 'returns default 23 Topics when empty' (empty Vault{})."
    },{
      "concern": "tests: integration specs run binary via gexec — confirm JSON output surface is pinned for omitted key case",
      "disposition": "addressed",
      "detail": "integration/cli_test.go tests both: vault with topics_dir set emits 'topics_dir: 23 Topics' in spaced form, and vault without topics_dir does not emit the key at all."
    },{
      "concern": "correctness: new TopicsDir field must have correct yaml/json omitempty tags matching its seven siblings",
      "disposition": "addressed",
      "detail": "config.go:35 has TopicsDir string with yaml:\"topics_dir,omitempty\" json:\"topics_dir,omitempty\" — identical pattern to all sibling directory fields"
    },{
      "concern": "correctness: GetTopicsDir() must return default value '23 Topics' when field is empty, not just when absent",
      "disposition": "addressed",
      "detail": "config.go:104-110 returns '23 Topics' when TopicsDir==\"\" — covers both absent and empty cases"
    },{
      "concern": "tests: verify unit tests cover both accessor branches",
      "disposition": "addressed",
      "detail": "vault_test.go:102-112 has two Ginkgo specs: returns custom topics dir when set / returns default 23 Topics when empty"
    },{
      "concern": "tests: integration specs confirm JSON output surface is pinned for omitted key case",
      "disposition": "addressed",
      "detail": "cli_test.go:275-308 has two integration specs: includes topics_dir when set, omits it when absent (jsonOut NotTo ContainSubstring topics_dir)"
    }]}

@bborbe
bborbe merged commit 72c7002 into master Sep 15, 2026
3 checks passed
@bborbe
bborbe deleted the feature/per-vault-topics-dir branch September 15, 2026 14:02
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