# Story 16.2: Config Storage and Cross-Reference File

Status: review

<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->

## Story

As a **DevOps engineer**,
I want the configured repo paths stored persistently and a cross-reference file created in the code repo,
So that agents and BMAD workflows can discover the correct paths without re-running installation.

## Acceptance Criteria

1. When knowledgebase is configured at `d:/Code/kb-repo` and sprint management at `d:/Code/sprint-repo`, `_bmad/bmm/config.yaml` contains `knowledgebase_path` and `sprint_management_path` fields with those values (FR117)
2. When multi-repo mode is configured, `_bmad-output/project-layout.yaml` is created in the CODE repository with `generated`, `knowledgebase` (mode, path, gitUrl), and `sprint_management` (mode, path) sections (FR122)
3. When single-repo mode is configured (both set to "current repository"), config.yaml contains `knowledgebase_path: "."` and `sprint_management_path: "."` — and NO `project-layout.yaml` is created (NFR34)
4. Running the installer multiple times with the same multi-repo configuration produces an identical `project-layout.yaml` except for the `generated` date field (NFR36 deterministic)
5. `project-layout.yaml` is generated by ma-agents installer, not by bmad-method — it lives in `_bmad-output/` which is version-controlled (per F3)
6. Path values in both YAML files are always double-quoted and use forward slashes (cross-platform safe)
7. If config.yaml already contains `knowledgebase_path`/`sprint_management_path` from a previous run, the values are updated in place (not duplicated)
8. When switching from multi-repo to single-repo, a stale `project-layout.yaml` is deleted (not left behind with outdated paths)

## Tasks / Subtasks

- [x] Task 1: Write repo layout to `_bmad/bmm/config.yaml` (AC: #1, #3, #6, #7)
  - [x] 1.1 After `collectRepoLayout()` returns (Story 16.1), extract knowledgebase and sprint management paths
  - [x] 1.2 Read existing `_bmad/bmm/config.yaml` content as string. If file does not exist, log error and skip (bmad-method should have created it)
  - [x] 1.3 Normalize paths: `resolvedPath.replace(/\\/g, '/')` before writing
  - [x] 1.4 For each field (`knowledgebase_path`, `sprint_management_path`): regex replace or append
  - [x] 1.5 Always double-quote path values in YAML
  - [x] 1.6 Wrap write in try/catch — non-fatal on permission errors
  - [x] 1.7 For `same` mode, write `"."` as the path value

- [x] Task 2: Generate `project-layout.yaml` for multi-repo mode (AC: #2, #4, #5, #6)
  - [x] 2.1 Check if either concern is NOT `same` mode — if both are `same`, skip
  - [x] 2.2 Ensure `_bmad-output/` directory exists
  - [x] 2.3 Generate `_bmad-output/project-layout.yaml` using template string
  - [x] 2.4 Only include `gitUrl` field when mode is `remote`
  - [x] 2.5 Normalize all paths before writing
  - [x] 2.6 Always double-quote path and gitUrl values

- [x] Task 3: Handle single-repo mode and stale files (AC: #3, #8)
  - [x] 3.1 When both concerns are `same` mode, do NOT create `project-layout.yaml`
  - [x] 3.2 Delete stale `project-layout.yaml` when switching to single-repo
  - [x] 3.3 Config.yaml still gets `"."` paths written

- [x] Task 4: Handle mode changes on reinstall (AC: #7, #8)
  - [x] 4.1 Update existing fields in-place via regex
  - [x] 4.2 Mode changes regenerate project-layout.yaml
  - [x] 4.3 Mode changes to both-same delete project-layout.yaml

- [x] Task 5: Integrate into installation pipeline (AC: #1-#8)
  - [x] 5.1 Place config write AFTER bmad-method installation completes
  - [x] 5.2 Place project-layout.yaml generation after config write
  - [x] 5.3 Log what was written
  - [x] 5.4 Verify idempotency

- [x] Task 6: Write unit tests (AC: #1-#8)
  - [x] 6.1 Test config write: both-same writes `"."`, multi-repo writes absolute paths
  - [x] 6.2 Test config idempotency
  - [x] 6.3 Test config update: existing fields replaced in place, no duplicates
  - [x] 6.4 Test project-layout.yaml: created for multi-repo, skipped for single-repo
  - [x] 6.5 Test stale cleanup: switching to single-repo deletes existing project-layout.yaml
  - [x] 6.6 Test path normalization: backslashes converted to forward slashes
  - [x] 6.7 Test YAML quoting: double-quoted paths
  - [x] 6.8 Test file permissions error: non-fatal, logged gracefully

## Dev Notes

### CRITICAL: Correct Config File Location

**BMAD workflows read config from `_bmad/bmm/config.yaml`**, NOT `_bmad/core/config.yaml`. Every workflow.yaml in the codebase uses:
```yaml
config_source: "{project-root}/_bmad/bmm/config.yaml"
```

The architecture doc references `_bmad/core/config.yaml`, but the actual codebase uses `_bmad/bmm/config.yaml` for operational config that workflows consume. Write path fields to `_bmad/bmm/config.yaml` so workflows can resolve them at runtime.

### Source of Truth Precedence

Three files can contain repo layout information. Their roles and precedence:

| File | Role | Authority |
|------|------|-----------|
| `_bmad/bmm/config.yaml` | **Runtime source of truth** — workflows read this at execution time | PRIMARY |
| `_bmad-output/project-layout.yaml` | **Agent discovery file** — agents in code repo discover cross-repo paths without loading BMAD config | INFORMATIONAL |
| `_bmad-output/project-context.md` | **Agent constitutional doc** — stamps paths into agent operating rules | INSTRUCTIONAL |

If these ever disagree, `_bmad/bmm/config.yaml` wins.

### Idempotent YAML Write Pattern

The codebase avoids `js-yaml` as a dependency. Use regex-based find/replace for idempotent config writes:

```javascript
function writeConfigField(content, fieldName, value) {
  const regex = new RegExp(`^${fieldName}:.*$`, 'm');
  const newLine = `${fieldName}: "${value}"`;
  if (regex.test(content)) {
    return content.replace(regex, newLine); // update in place
  }
  // append (ensure trailing newline)
  return content.trimEnd() + '\n' + newLine + '\n';
}
```

### Stale File Cleanup

When switching from multi-repo to single-repo, the `project-layout.yaml` must be deleted — not left behind. Agents reading a stale file would use outdated paths silently. This is a deliberate departure from the "leave it" approach in the original architecture, based on the adversarial review finding that stale files cause silent path misdirection.

### Key Implementation Constraints

1. **Write to `_bmad/bmm/config.yaml`**: This is where workflows actually read from — verified by checking `config_source` in all workflow.yaml files
2. **No js-yaml dependency**: Use string templates and regex for YAML operations
3. **Always double-quote path values**: Handles Windows drive letters (`C:`), spaces, and YAML-special chars (`#`, `%`, `{`, `}`)
4. **Always normalize separators**: `.replace(/\\/g, '/')` on all paths before writing
5. **Idempotent writes**: Regex replace existing fields, append only if missing — never duplicate
6. **Delete stale files**: Remove `project-layout.yaml` when switching to single-repo
7. **project-layout.yaml in `_bmad-output/`**: version-controlled per Epic 10 policy
8. **Generated date**: `YYYY-MM-DD` format matching sprint-status.yaml convention

### NFR Compliance

| NFR | How This Story Addresses It |
|-----|----------------------------|
| NFR34 | Single-repo: write `"."` paths, delete any stale project-layout.yaml |
| NFR36 | Deterministic: template string, only `generated` date varies |
| NFR9 | Path normalization with forward slashes in YAML output |

### Dependencies

- **Story 16.1** must be complete — provides the `layout` object with mode/path/gitUrl for each concern
- **bmad-method installation** must complete first — creates `_bmad/bmm/config.yaml`

### Project Structure Notes

- Config write target: `_bmad/bmm/config.yaml` (verified: all workflows use this as config_source)
- Project-layout.yaml target: `_bmad-output/project-layout.yaml`
- Integration point: after `bmad.installBmad()` completes in the installation pipeline

### References

- [Source: _bmad-output/planning-artifacts/architecture.md — Decision P2-10, config storage section, lines 844-870]
- [Source: _bmad-output/planning-artifacts/epics.md — Story 16.2, lines 2010-2052]
- [Source: _bmad-output/planning-artifacts/prd.md — FR117, FR122, lines 582-587]
- [Source: _bmad-output/planning-artifacts/prd.md — NFR34, NFR36, lines 657-659]
- [Source: _bmad/bmm/workflows/4-implementation/create-story/workflow.yaml — config_source: _bmad/bmm/config.yaml]

## Dev Agent Record

### Agent Model Used

Claude Opus 4.6 (1M context)

### Debug Log References

None — clean implementation.

### Completion Notes List

- Implemented `normalizePath()` utility for backslash-to-forward-slash conversion
- Implemented `writeConfigField()` for idempotent regex-based YAML field writes (replace or append)
- Implemented `writeRepoLayoutConfig()` to write `knowledgebase_path` and `sprint_management_path` to `_bmad/bmm/config.yaml`
- Implemented `writeProjectLayoutYaml()` to generate `_bmad-output/project-layout.yaml` for multi-repo or delete stale file for single-repo
- Integrated both into `installWizard()` as Step 3.6 after BMAD installation
- 16 unit tests covering normalization, config writes, idempotency, stale cleanup, multi-repo/single-repo modes
- All tests pass, no regressions

### Change Log

- 2026-03-28: Implemented story 16.2 — config storage and project-layout.yaml generation

### File List

- bin/cli.js (modified — added writeRepoLayoutConfig, writeProjectLayoutYaml, writeConfigField, normalizePath + integration)
- test/config-storage.test.js (new — 16 tests)
- package.json (modified — added test to script)
- _bmad-output/implementation-artifacts/sprint-status.yaml (modified)
- _bmad-output/implementation-artifacts/16-2-config-storage-and-cross-reference.md (modified)
