Eliminate the Remaining Markdown Config Carriers
ADR-0006: Eliminate the Remaining Markdown Config Carriers
Section titled “ADR-0006: Eliminate the Remaining Markdown Config Carriers”Status
Section titled “Status”Accepted
Context
Section titled “Context”Background and Problem Statement
Section titled “Background and Problem Statement”Epic #139 asks for a single config surface across the plugin suite: after
this ADR, no .claude/<plugin>.local.md config carrier remains anywhere,
and .config/gdlc/config.yml (project + global layers, ADR-0004/0005) is
the only place a contributor or an agent looks for gdlc plugin config.
Two markdown carriers are still live:
github-bug-capture’s pack toggles (.claude/github-bug-capture.local.md, apacks:map ofhooks/triage-skills/mcp-integration/gh-awbooleans). ADR-0004 explicitly and deliberately kept this local-only — its own title is “.claude/<plugin>.local.mdStays Local-Only” — reasoning that a personal, per-developer runtime preference should not share a carrier with team-shared, committed policy.github-sdlc-planning’s legacyboard:key (.claude/github-sdlc-planning.local.md), which ADR-0004 already superseded with.config/gdlc/config.yml’sboard:section, kept working “for one release” as a deprecation-notice fallback (hooks/lib/in-progress.mjs’sreadLegacyBoardConfig).
This ADR revisits point 1 and closes out point 2.
What changed since ADR-0004
Section titled “What changed since ADR-0004”ADR-0004’s reasoning for keeping pack toggles local-only was sound on its own terms: a personal opt-in and committed team policy are genuinely different in kind, and conflating their carriers was a real risk. That reasoning is not being disputed here. What changed is the requirement this ADR is scored against: eliminate every markdown-based config carrier in the suite, full stop, even where the original design for keeping one had a legitimate rationale. This ADR makes that trade-off explicit rather than treating the carrier elimination as a bug fix — it is a deliberate policy change, accepted with its cost named.
Constraints
Section titled “Constraints”- Whatever carrier packs move to must already exist and be already parsed by both an MCP-server consumer (if any) and a dependency-free hook-layer reader — introducing a third file format or a third resolution mechanism defeats the stated goal of “one config surface.”
github-bug-capture’shooks/lib/settings.mjscannot depend onnode_modulesat hook-execution time (documented constraint, matchesgithub-sdlc-planning/hooks/lib/in-progress.mjs); any new reader must stay dependency-free, same as the existingboard:section reader (parseGdlcBoardSection) it will mirror.- The legacy
board:fallback’s removal was already decided by ADR-0004; this ADR only needs to confirm the one-release window is over and record that decision’s execution, not re-litigate the carrier choice.
Decision Drivers
Section titled “Decision Drivers”Primary Decision Drivers
Section titled “Primary Decision Drivers”- One config surface, no exceptions. Epic #139’s explicit requirement:
after this Epic,
.config/gdlc/config.ymlis the only config file any plugin reads, for any purpose. - Reuse the existing per-section cascade, not a new mechanism.
targeting/destination/boardalready have a working project-overrides- global cascade (ADR-0004’smergeConfigs); apacks:section should be a fourth section in the same file, not a new resolution scheme. - Name the trade-off, don’t hide it. Moving pack toggles into the committed project layer changes their nature from personal/uncommitted to team-shared/committed. This ADR records that change explicitly so a future reader does not mistake it for an oversight.
Secondary Decision Drivers
Section titled “Secondary Decision Drivers”- Minimize new dependency-free hook code.
in-progress.mjs’sparseGdlcBoardSection/readBoardConfigpattern is proven; the newpacks:reader ingithub-bug-capture’ssettings.mjsshould mirror it structurally rather than invent a different parsing approach. - Bounded migration cost. Removing the legacy
board:fallback and the pack-toggle markdown carrier in the same Epic, rather than staggering them across releases, keeps the “no markdown remains” state easy to verify (a singlegrep -rn "\.local\.md"sweep).
Considered Options
Section titled “Considered Options”Option 1: packs: section in the project-layer .config/gdlc/config.yml
Section titled “Option 1: packs: section in the project-layer .config/gdlc/config.yml”Description: Add a packs: top-level section to the same file
targeting/destination/board already live in. Project layer only for
now (packs are inherently per-repo); the existing global-layer fallback
applies for free once a caller reads it the same way board: does.
Technical Characteristics: One new top-level YAML section, validated by
the same normalizeConfig/parseGdlcBoardSection-style pattern already
proven for board:. No new file, no new resolution mechanism.
Advantages:
- Reuses the exact cascade, path resolution, and upward-search machinery
already built and tested for
targeting/destination/board. - Keeps “one config surface” literally true — a contributor editing project config edits exactly one file for any of the four concerns.
- The hooks-layer reader in
github-bug-capturemirrors an already-proven pattern (in-progress.mjs’s board-section reader) instead of inventing one.
Disadvantages:
- Pack toggles become committed, team-shared policy. A developer who wants
hooksenabled only for their own local sessions no longer has a purely personal, uncommitted way to do that — the whole team (and every agent session reading the committed file) sees the same toggle state. - Loses ADR-0004’s original per-developer/per-session distinction entirely; this is the accepted cost of this decision, not a hidden one.
Risk Assessment:
- Technical Risk: Low. Directly extends proven code.
- Schedule Risk: Low.
- Ecosystem Risk: Low-Medium. Any existing installation relying on a
personal
.claude/github-bug-capture.local.mdtoggle needs to migrate its settings into the committed project file — a real, if small, migration cost for existing adopters (this repo included).
Option 2: packs: section in the global-layer $XDG_CONFIG_HOME/gdlc/config.yml
Section titled “Option 2: packs: section in the global-layer $XDG_CONFIG_HOME/gdlc/config.yml”Description: Move pack toggles to the per-user, per-machine global layer instead of the project layer, preserving “personal” (it’s not committed to any repo) while still eliminating the markdown carrier.
Technical Characteristics: Same section shape as Option 1, read only from the global layer.
Advantages:
- Preserves the “personal, not committed” property ADR-0004 valued — the global layer is a machine-local file, never checked into a repo.
Disadvantages:
- Loses per-repo granularity, which the pack-toggle system explicitly has
today (a developer working across
gdlc,backstage-idp, and other consuming repos may reasonably want different pack toggles per repo — the global layer cannot express that). - Regresses a real, already-used capability (per-repo toggles) to eliminate a file format, trading a bigger capability loss for a smaller cosmetic one.
Risk Assessment:
- Technical Risk: Low.
- Schedule Risk: Low.
- Ecosystem Risk: Medium. Any consuming repo relying on per-repo pack differentiation loses that capability outright, not just its carrier.
Disqualifying Factor: trades a real capability (per-repo granularity) for no gain beyond file format, when Option 1 achieves the stated goal without that loss (at the cost of committed-vs-personal, which the decision explicitly accepts instead).
Option 3: A new uncommitted per-repo YAML file (.config/gdlc/config.local.yml)
Section titled “Option 3: A new uncommitted per-repo YAML file (.config/gdlc/config.local.yml)”Description: Introduce a third file — still YAML, still per-repo, but
gitignored/.git/info/exclude-excluded — layered on top of the committed
project file, preserving both “personal” and “per-repo” while eliminating
markdown as the format.
Technical Characteristics: A third resolution layer between project and global, YAML-shaped, uncommitted by convention.
Advantages:
- Would have preserved every property of the original design (personal, per-repo, uncommitted) while still satisfying “no markdown.”
Disadvantages:
- Directly contradicts this Epic’s primary decision driver: “one config surface, no exceptions.” A third file is a third surface, even if it happens to be YAML-shaped like the other two.
- Adds a third resolution tier to every reader (
config.tsand each hooks-layer reader), each of which already carries real complexity from two tiers plus the upward-search behavior (ADR-0005). A third tier compounds that complexity for a property (packs-stay-personal) this Epic’s confirmed decision does not require preserving.
Risk Assessment:
- Technical Risk: Medium. New resolution tier in every consumer.
- Schedule Risk: Medium.
- Ecosystem Risk: Low.
Disqualifying Factor: solves a problem (preserve personal/uncommitted packs) that this Epic’s explicit, confirmed decision does not ask to be solved, at the cost of a new resolution tier in every config consumer.
Decision
Section titled “Decision”We adopt Option 1: pack toggles move into a packs: section of the
project-layer .config/gdlc/config.yml, and the legacy board: key
fallback in .claude/github-sdlc-planning.local.md is removed outright.
schema/gdlc-config.schema.jsongains apackssection (object of pack-name → boolean).github-sdlc-planning’sconfig.tsgainspacks?: Record<string, boolean>onGdlcConfig, normalized the same fail-soft way as the other sections.github-bug-capture’shooks/lib/settings.mjsdrops its markdown-frontmatter parser entirely and reads thepacks:section of.config/gdlc/config.yml: the project layer’spacks:section, if present at all, replaces the global layer’s wholly (the same section-wise cascadetargeting/destination/boardalready use, not a key-by-key merge); the global layer is only consulted when the project layer has nopacks:section at all. Implemented via a dependency-free reader mirroringin-progress.mjs’sparseGdlcBoardSection.github-sdlc-planning’shooks/lib/in-progress.mjsdropsreadLegacyBoardConfig/parseBoardConfigand the third fallback tier inreadBoardConfig— only the two.config/gdlc/config.ymllayers remain.KNOWN_PACKS(hooks,triage-skills,mcp-integration,gh-aw) stays the validation source of truth; the schema’spackssection is not hardcoded to exactly these four so a future pack can be added without a schema change.
Consequences
Section titled “Consequences”Positive
Section titled “Positive”- One config surface, verifiably.
grep -rn "\.local\.md"across the whole plugin suite returns only historical references after this Epic lands — a mechanically checkable definition of “done.” - No new resolution mechanism. The
packs:reader in both the MCP-server loader and the hooks-layer reader reuses the exact cascade and parsing pattern already proven forboard:. - ADR-0004’s original reasoning is preserved as a documented rationale, even though its conclusion for pack toggles is reversed here — a future reader sees both the original “why personal” argument and this ADR’s “why we changed it anyway” argument side by side.
Negative
Section titled “Negative”- Pack toggles become committed, team-shared policy. A contributor can
no longer set
hooks: truefor their own sessions without every other contributor and every agent session in that repo seeing the same value. This is the accepted cost of this decision. - Migration cost for any existing
.claude/github-bug-capture.local.mdfile. Anyone with pack toggles already set that way needs to move them into.config/gdlc/config.yml’s newpacks:section by hand; there is no automated one-release fallback for this carrier (unlike theboard:key’s prior migration), since Epic #139’s acceptance criteria call for the markdown carrier’s removal to be complete now, not staged.
Neutral
Section titled “Neutral”- The legacy
board:fallback’s removal was already decided by ADR-0004 — this ADR’s role there is confirming the window is closed and recording the removal, not making a new call.
Decision Outcome
Section titled “Decision Outcome”The decision achieves its objective — zero markdown-based config carriers
in the plugin suite — measured by: schema/gdlc-config.schema.json and
config.ts both define a packs section; github-bug-capture’s
hooks/lib/settings.mjs contains no .claude/*.local.md reference;
github-sdlc-planning’s hooks/lib/in-progress.mjs contains no legacy
board: fallback; and a repo-wide grep -rn "\.local\.md" across
plugins/ and docs/ returns only historical references.
Related Decisions
Section titled “Related Decisions”- ADR-0004: One XDG-Mirrored Path for Global and Project Config —
superseded by this ADR specifically for the pack-toggle carrier decision;
its project/global-layer path design and
targeting/destination/boardschema are unaffected and remain in force. - ADR-0005: Upward Directory Search for Project Config — the
upward-search resolution this ADR’s
packs:reader reuses unchanged.
- Epic #139 — this ADR’s tracking epic.
- Issue #140 — this ADR’s own authoring Story.
- Issue #141 —
schema/config.ts implementation of the
packs:section. - Issue #142 —
github-bug-capturepack-toggle reader migration. - Issue #143 —
legacy
board:fallback removal. - Issue #144 — documentation updates.
More Information
Section titled “More Information”- Date: 2026-07-09
- Source: Epic #139.
- Related ADRs: ADR-0004, ADR-0005.
2026-07-09
Section titled “2026-07-09”Status: Compliant
Findings:
| Finding | Files | Lines | Assessment |
|---|---|---|---|
| Decision confirmed explicitly by the repository owner, trade-off (committed vs. personal pack toggles) named and accepted before implementation began | - | - | compliant |
Summary: Drafted and accepted in the same session the implementing
Stories (#141–#144) were filed and executed; no open objections to Option 1,
the packs: schema location, or the legacy board: fallback’s removal.
Action Required: None for this ADR. Issues #141–#144 implement against this chosen design.