add topics_dir to vault-cli per-vault config - #173
Merged
Merged
Conversation
…ccessor, `config list` JSON surface, docs
bborbe
marked this pull request as ready for review
September 15, 2026 13:54
There was a problem hiding this comment.
Chunk 1/3
Based on my review of all changed files, here's my assessment:
Changes reviewed:
pkg/config/config.go: AddedTopicsDirfield withyaml:"topics_dir,omitempty" json:"topics_dir,omitempty"tags andGetTopicsDir()accessor returning"23 Topics"defaultpkg/config/vault_test.go: AddedGetTopicsDirtests covering both custom value and defaultpkg/config/config_test.go: Added integration test fortopics_dirYAML loadingintegration/cli_test.go: AddedcreateTempVaultWithTopicshelper and two integration tests verifying JSON inclusion when set and omission when absentscenarios/001-config-list.md: Updated to includetopics_dirin full vault and as omitted key for minimal vaultCHANGELOG.md: Unreleased entry accurately describes the featureREADME.md: Configuration example includestopics_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:35—TopicsDirjson 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 haveomitempty✓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)"
}]}
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
topics_dirto vault-cli's per-vault config — the last entity directory that could not be declared per vault.Why
Vaultdeclared 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 hardcode23 Topicsin 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
TopicsDirfield onVault, withyaml/jsonomitemptytags matching its seven siblingsGetTopicsDir()returns the configured value, or23 Topicswhen the key is absent or emptyvault-cli config list --output jsonthrough the struct's own tag; a vault that does not set it emits no key, matching howtasks_dirbehavesREADME.md§ Configuration documents the key;scenarios/001-config-list.mdis extended to pin the new surfaceNo new command, no new flag, no changed output. Existing config files load and resolve byte-identically.
Verification
make precommitexits 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:
topics_dir: "23 Topics"config list --output jsonemits"topics_dir": "23 Topics"topics_diroccurrences in stdoutname<TAB>path, exit 0The unit specs pin both accessor branches:
returns custom topics dir when setuses a non-default folder, so an accessor that always returns the literal fails it;returns default 23 Topics when emptyfails one that returns the raw field. The YAML tag is pinned separately byloads topics_dir from YAML, which asserts the rawTopicsDirfield — aGetTopicsDir()-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.mdNote for the reviewer
This commit also carries
specs/in-progress/035-verify-goal-necessity-checks.md, flippedprompted → verifyingby 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.