ma-agents 3.5.6 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/.ma-agents.json +10 -0
  2. package/AGENTS.md +97 -0
  3. package/MANIFEST.yaml +3 -0
  4. package/README.md +17 -0
  5. package/_bmad-output/implementation-artifacts/21-10-profile-reconfigure.md +30 -6
  6. package/_bmad-output/implementation-artifacts/21-11-profile-uninstall.md +2 -1
  7. package/_bmad-output/implementation-artifacts/21-2-universal-instruction-block-expansion.md +217 -62
  8. package/_bmad-output/implementation-artifacts/21-3-roomodes-template-bmad-modes.md +196 -73
  9. package/_bmad-output/implementation-artifacts/21-4-agents-md-template-opencode.md +242 -53
  10. package/_bmad-output/implementation-artifacts/21-5-clinerules-template-extension.md +180 -41
  11. package/_bmad-output/implementation-artifacts/21-6-onprem-layered-guardrails.md +250 -75
  12. package/_bmad-output/implementation-artifacts/21-7-bmad-persona-phase-prefix.md +221 -89
  13. package/_bmad-output/implementation-artifacts/21-8-vllm-reference-doc-readme.md +121 -63
  14. package/_bmad-output/implementation-artifacts/21-9-tests-validation.md +332 -61
  15. package/_bmad-output/implementation-artifacts/bug-bmad-recompile-fails-on-airgapped-network.md +112 -0
  16. package/_bmad-output/implementation-artifacts/sprint-status.yaml +3 -2
  17. package/bin/cli.js +59 -0
  18. package/docs/deployment/vllm-nemotron.md +130 -0
  19. package/lib/agents.js +17 -2
  20. package/lib/bmad-customize/bmm-analyst.customize.yaml +8 -0
  21. package/lib/bmad-customize/bmm-architect.customize.yaml +2 -0
  22. package/lib/bmad-customize/bmm-dev.customize.yaml +2 -0
  23. package/lib/bmad-customize/bmm-pm.customize.yaml +2 -0
  24. package/lib/bmad-customize/bmm-qa.customize.yaml +2 -0
  25. package/lib/bmad-customize/bmm-quick-flow-solo-dev.customize.yaml +8 -0
  26. package/lib/bmad-customize/bmm-sm.customize.yaml +2 -0
  27. package/lib/bmad-customize/bmm-tech-writer.customize.yaml +2 -0
  28. package/lib/bmad-customize/bmm-ux-designer.customize.yaml +2 -0
  29. package/lib/bmad.js +293 -1
  30. package/lib/installer.js +617 -43
  31. package/lib/merge/roomodes.js +125 -0
  32. package/lib/profile.js +25 -2
  33. package/lib/reconfigure.js +334 -0
  34. package/lib/templates/agents-md.template.md +67 -0
  35. package/lib/templates/clinerules.template.md +13 -0
  36. package/lib/templates/instruction-block-onprem.template.md +86 -0
  37. package/lib/templates/instruction-block-universal.template.md +29 -0
  38. package/lib/templates/roomodes.template.yaml +96 -0
  39. package/lib/uninstall.js +314 -0
  40. package/package.json +4 -3
  41. package/test/agents-md.test.js +398 -0
  42. package/test/bmad-extension.test.js +2 -2
  43. package/test/bmad-persona-phase-prefix.test.js +271 -0
  44. package/test/clinerules.test.js +339 -0
  45. package/test/instruction-block.test.js +388 -0
  46. package/test/integration-verification.test.js +2 -2
  47. package/test/migration-validation.test.js +2 -2
  48. package/test/offline-recompile.test.js +237 -0
  49. package/test/onprem-injection.test.js +425 -32
  50. package/test/onprem-layer.test.js +419 -0
  51. package/test/reconfigure.test.js +436 -0
  52. package/test/roomodes.test.js +343 -0
  53. package/test/uninstall.test.js +402 -0
@@ -1,6 +1,6 @@
1
1
  # Story 21.5: Expanded `.clinerules` Template
2
2
 
3
- Status: backlog
3
+ Status: Review
4
4
 
5
5
  ## Story
6
6
 
@@ -10,73 +10,212 @@ So that Cline (which already supports `.clinerules` natively) gets the same univ
10
10
 
11
11
  ## Acceptance Criteria
12
12
 
13
- 1. New template `lib/templates/clinerules.template.md` exists containing the universal text-vs-file rules and BMAD phase rules formatted for the Cline `.clinerules` convention (one rule per line or short paragraph blocksCline's documented format).
14
- 2. Both `.cline/clinerules.md` and `.clinerules` (Cline writes both per `lib/agents.js`) receive the expanded content via the existing marker-based injection.
15
- 3. Existing user content outside `<!-- MA-AGENTS-START -->` / `<!-- MA-AGENTS-END -->` markers is preserved (NFR5).
16
- 4. Re-running install produces byte-identical marker-block content in both files (NFR46).
17
- 5. Mention Cline-specific concept: "Use Cline's Architect mode for BMAD planning phases (PM, Architect, Tech Lead). Switch to Code mode only for the implementation phase." This is universal guidance, not on-prem-specific.
18
- 6. **Dual-file drift detection.** The installer writes both `.cline/clinerules.md` and `.clinerules` from the SAME template render. Before writing, it reads both existing files (if present) and compares their marker-block contents. If the two marker blocks diverge (non-whitespace diff), the installer ABORTS with a named error (e.g., `ClinerulesDualFileDriftError`) naming both files and their diff, instructing the user to reconcile manually before retrying. `--yes` does NOT bypass this check — reconciliation between the two Cline rule files is user work, and silently picking a "winner" could discard intentional edits. This is an explicit, documented exception to the usual `--yes` bypass convention.
13
+ > Acceptance criteria marked **(gap-fill)** are additions made by the story author to make the epic's AC operational and testable. They do not contradict the epic; they refine the "how" where the epic only stated the "what". Controlling invariants: NFR44 (byte-identity for standard profile), NFR46 (idempotency), NFR18 (additive JSON-merge not applicable here, see Dev Notes), NFR47 (Roo Code fileRegex — not applicable here, see Dev Notes).
14
+
15
+ 1. **Cline template file exists.** A new file pinned at the exact path `lib/templates/clinerules.template.md` (new) is present and is formatted per Cline's `.clinerules` convention (flat markdown rule list, no front-matter). The rendered marker-block content includes:
16
+ - The universal text-vs-file rules (file-action keywords `create`, `write`, `generate`, `build`, `implement` → file; text-response keywords `what do you think`, `how should we`, `discuss`, `opinion` → text; "if unsure respond in text" default; "never create `response.md` or `output.md` as a reply"; "confirm file paths before writing").
17
+ - The BMAD phase discipline rules (respect declared phase; do not skip ahead to implementation during planning).
18
+ - A Cline-specific framing line: "Use Cline's Architect mode for BMAD planning phases (PM, Architect, Tech Lead). Switch to Code mode only for the implementation phase." This is universal guidance, not on-prem-specific.
19
+
20
+ 2. **(gap-fill) Universal rule text is not hand-duplicated; byte-identity by construction across both Cline files.** The five universal rule sections originate from Story 21.2's `lib/templates/instruction-block-universal.template.md` and are rendered via the 21.2-owned composer `composeInstructionBlock({ profile, projectRoot }) → string` (single-owner in `lib/installer.js`). The composer's output is written into BOTH `.clinerules` AND `.cline/clinerules.md` in a SINGLE stamper pass — the composed string is produced once and the stamper writes it into both targets, making the two files cross-file byte-identical by construction (no second render, no reconciliation). `clinerules.template.md` supplies ONLY the Cline-specific framing (header paragraph + Architect-mode line from AC #1). Any divergence between Cline's rule text and the canonical universal block is a bug under NFR46.
21
+
22
+ > **Resolution (Option B, canonical):** `composeInstructionBlock` is extended so the same 21.2-owned composer serves ALL FIVE agents uniformly (Claude Code, Cursor, Kilocode, Copilot, Gemini-style file-based agents, Cline, Roo Code). The composer is the single owner of composition; mergers and stampers receive the already-composed string as input. No per-agent composer variant, no Cline-specific composition branch.
23
+
24
+ 3. **Both Cline instruction files written via marker-based additive stamper.** Cline registers two instruction files in `lib/agents.js` (verified at line 137): `.cline/clinerules.md` and `.clinerules`. When Cline is selected in `npx ma-agents install`, both files are written/updated at the project root using the existing marker-based additive stamper path in `updateAgentInstructions` (`lib/installer.js` ~lines 407–459, markers `<!-- MA-AGENTS-START -->` / `<!-- MA-AGENTS-END -->`). The `for (const fileName of agent.instructionFiles)` loop at line 428 is the single iteration point; no special-case code per file.
25
+
26
+ 4. **User content outside markers preserved.** Content outside `<!-- MA-AGENTS-START -->` / `<!-- MA-AGENTS-END -->` in either Cline file is preserved byte-for-byte across installs (NFR5 additive-only contract; this is the existing marker-stamper contract inherited from Story 21.2).
27
+
28
+ 5. **(gap-fill) Idempotency per file (NFR46).** Two consecutive installs with the same profile and project state produce byte-identical content inside the marker block of each Cline file. This is a direct consequence of Story 21.2's NFR46 contract applied to Cline's two-entry `instructionFiles[]` list; this story adds no new idempotency-breaking inputs.
29
+
30
+ 6. **Dual-file drift detection between `.cline/clinerules.md` and `.clinerules`.** Before the stamper writes the marker block to either file, the installer reads both existing files (when both are present) and compares their in-marker contents. If the two marker blocks diverge (non-whitespace diff), the installer ABORTS with a named error `ClinerulesDualFileDriftError` naming both file paths and emitting the diff, instructing the user to reconcile manually before retrying. `--yes` does NOT bypass this check — reconciliation between the two Cline rule files is user work, and silently picking a "winner" could discard intentional edits. This is an explicit, documented exception to the usual `--yes` bypass convention. Note: per AC #2, cross-file byte-identity is guaranteed by construction on fresh write; this check catches post-install external edits only.
31
+
32
+ 7. **(gap-fill) Profile isolation (NFR44).** In a standard-profile Cline install, neither `.cline/clinerules.md` nor `.clinerules` contains any of the strings `/no_think`, `str_replace_editor`, or `~/.claude/`. Verified by grep-style assertion on rendered standard-profile output for both files.
33
+
34
+ 8. **(gap-fill) On-prem profile appends on-prem content.** When `profile === 'on-prem'` and Story 21.6's `lib/templates/instruction-block-onprem.template.md` exists, the on-prem rules composed by `composeInstructionBlock` appear within the same `<!-- MA-AGENTS-START -->` markers in both Cline files. This AC asserts wiring only — on-prem content ships in Story 21.6. Graceful fallback to universal-only when the on-prem template file is absent (per Story 21.2 AC #3).
35
+
36
+ 9. **(gap-fill) MANIFEST path rendering.** The `{{MANIFEST_PATH}}` placeholder inherited via `composeInstructionBlock` resolves to `.cline/skills/MANIFEST.yaml` (relative to project root, forward-slashed on Windows). This is the pre-Epic-21 Cline MANIFEST path — unchanged — and is verified by an explicit test assertion.
37
+
38
+ > **Open question:** Cline v3+ introduced a `.clinerules/` directory of numbered rule files as an alternative to a single `.clinerules` file. The epic treats `.clinerules` as a single file, matching current `lib/agents.js` registration. Proposed resolution: out of scope. A follow-up story can add directory support if field feedback demands it.
39
+
40
+ > **Resolved (see AC #2):** Cross-file byte-identity is a hard invariant enforced by construction — the 21.2-owned composer produces the string once and the stamper performs a single pass that writes it into both targets. Drift therefore arises only from external user edits, which AC #6 catches.
19
41
 
20
42
  ## Tasks / Subtasks
21
43
 
22
- - [ ] Task 1: Create `lib/templates/clinerules.template.md` per AC #1, #5
23
- - [ ] Task 2: Verify Cline's `extraInstructionTemplates` (or equivalent existing injection path) writes both `.cline/clinerules.md` and `.clinerules` consistently (AC #2)
24
- - [ ] Task 3: Confirm the universal block from Story 21.2 already covers most of the content; this story may simply add Cline-specific framing (Architect mode mention) and rely on `composeInstructionBlock` for the rest. Decide between two implementations:
25
- - **Option A**: Cline-only template that wraps `composeInstructionBlock` output with Cline-specific intro/outro
26
- - **Option B**: Inline Cline-specific rules directly into `lib/templates/clinerules.template.md` and skip `composeInstructionBlock` for Cline
27
- - Pick Option A single source of truth for the universal rules
28
- - [ ] Task 4: Tests in `test/clinerules.test.js`
29
- - [ ] 4.1 Both `.clinerules` and `.cline/clinerules.md` contain universal rules + Architect-mode guidance
30
- - [ ] 4.2 User content outside markers preserved
31
- - [ ] 4.3 Idempotent re-install (NFR46)
32
- - [ ] 4.4 Both files have identical marker-block content
33
- - [ ] Task 5: Dual-file drift detection (AC #6)
34
- - [ ] 5.1 Before write, compare marker-block contents of `.cline/clinerules.md` and `.clinerules` when both exist
35
- - [ ] 5.2 On non-whitespace diff, abort with `ClinerulesDualFileDriftError` naming both files and emitting the diff
36
- - [ ] 5.3 Verify `--yes` does NOT bypass this check; document in CLI help text as an explicit exception
44
+ - [x] **Task 1: Create Cline template** (AC #1, #2, #7)
45
+ - [ ] 1.1 Create `lib/templates/clinerules.template.md` (new) containing a Cline-flavored framing (header `# Cline Project Rules`, short intro paragraph, the Architect-mode guidance line from AC #1). Per canonical Option B (AC #2), this framing is consumed by the 21.2-owned composer `composeInstructionBlock({ profile, projectRoot })`; the universal rule body is NOT hand-copied — it is composed at install time.
46
+ - [ ] 1.2 Verify by inspection that no string in the template file matches `/no_think`, `str_replace_editor`, or `~/.claude/` (AC #7).
47
+
48
+ - [x] **Task 2: Consume the 21.2-owned composer uniformly** (AC #2, #3, #4, #8)
49
+ - [ ] 2.1 Verify `updateAgentInstructions` in `lib/installer.js` (line ~358) iterates both Cline files via the existing `for (const fileName of agent.instructionFiles)` loop (line 428). No iteration change required — Story 21.2 already generalized the path.
50
+ - [ ] 2.2 Verify (via test, not code change) that `composeInstructionBlock({ profile: getProfile(projectRoot) || 'standard', projectRoot })` is invoked ONCE per install and the resulting string is handed to the stamper, which writes it into both `.clinerules` and `.cline/clinerules.md` in a single pass — cross-file byte-identical by construction.
51
+ - [ ] 2.3 Story 21.2 extends `composeInstructionBlock` so the same composer serves Cline (Option B, canonical). This story does not add a Cline-specific composition branch, a first-insert-wrapper branch, or any per-agent composer variant. If the 21.2 extension is not yet merged, this story is BLOCKED on 21.2 (see Dependencies).
52
+
53
+ - [x] **Task 3: Dual-file drift detection** (AC #6)
54
+ - [ ] 3.1 Before the stamper writes the marker block to either Cline file, extract in-marker content from both `.cline/clinerules.md` and `.clinerules` (when both exist).
55
+ - [ ] 3.2 If the two in-marker contents differ (non-whitespace diff), throw `ClinerulesDualFileDriftError` naming both files and including the unified diff.
56
+ - [ ] 3.3 `--yes` does NOT bypass this error. Add a one-line mention in `bin/cli.js` `--help` text under the `install` section: "Note: divergent `.cline/clinerules.md` vs. `.clinerules` marker blocks require manual reconciliation — `--yes` does not bypass this check."
57
+ - [ ] 3.4 When only one of the two Cline files exists (fresh install or partial uninstall), drift detection is skipped and both files are written from the same render per AC #6's "render once, write twice" invariant.
58
+
59
+ - [ ] **Task 4: (removed — Option B canonical)** Option B makes per-agent `extraInstructionTemplates` wiring unnecessary for Cline; the 21.2-owned composer reads `clinerules.template.md` directly via its framing input. Left as an anchor for traceability; no action required in this story.
60
+
61
+ - [x] **Task 5: Unit and integration tests** — see Testing section below.
62
+
63
+ - [x] **Task 6: Documentation touch-up**
64
+ - [ ] 6.1 No documentation changes in this story. Story 21.8 introduces the broader Epic 21 doc surface. The `--help` line in Task 3.3 is the only user-facing text addition.
37
65
 
38
66
  ## Dev Notes
39
67
 
40
- ### Architecture Compliance
68
+ ### Architecture compliance
69
+
70
+ - **Decision P3-3 (Local-LLM / On-Prem Agent Tuning Profile)** — This story implements the Cline slice of the universal layer. Cline's Architect mode is the application-layer enforcement we leverage via prompt guidance (AC #1 framing line). Unlike Roo Code (Story 21.3, NFR47 `fileRegex`), Cline has no fileRegex-equivalent enforcement — `.clinerules` is prompt-only.
71
+
72
+ - **NFR44 (byte-identity for standard profile)** — AC #7 asserts absence of `/no_think`, `str_replace_editor`, `~/.claude/` from standard-profile Cline output across both files. Enforced transitively by Story 21.2's `composeInstructionBlock` contract; this story adds the Cline-specific per-file assertion.
73
+
74
+ - **NFR46 (idempotency)** — AC #5 requires byte-identical marker-block content across consecutive installs per-file. Story 21.2's Task 3.3 (insert/replace whitespace parity) is the prerequisite. AC #2's single-composer + single-stamper-pass invariant additionally gives cross-file byte-identity by construction.
41
75
 
42
- - **Decision P3-3** — Cline parallel to Roo Code/OpenCode. Cline's Architect mode is the application-layer enforcement we leverage via prompt guidance.
43
- - **NFR46** — idempotent stamping for both Cline output files.
76
+ - **NFR47 (Roo Code fileRegex)** — Not applicable. Cline has no application-layer file-restriction mechanism; phase discipline in `.clinerules` is prompt-only. Users needing fileRegex-equivalent enforcement should use Roo Code (Story 21.3). This story does not attempt to emulate NFR47 in Cline.
44
77
 
45
- ### Source Tree Components to Touch
78
+ - **NFR18 (additive JSON-merge)** — Not applicable to Cline. NFR18 governs OpenCode's `opencode.json::instructions[]` merge (Story 21.4). Cline uses a plain markdown stamper; no JSON merge involved. Cited here only to confirm Cline path does not regress JSON behavior.
46
79
 
47
- | File | Change |
48
- |------|--------|
49
- | `lib/templates/clinerules.template.md` | CREATE |
50
- | `lib/installer.js` | MAYBE-MODIFY — verify Cline extraInstructionTemplates behavior |
51
- | `test/clinerules.test.js` | CREATE |
80
+ ### Verified source-tree surface
52
81
 
53
- ### Dependencies
82
+ | File | Exists? | Role in this story |
83
+ |------|---------|--------------------|
84
+ | `lib/profile.js` | verified (Story 21.1, lines 1–107) | Read-only — `getProfile(projectRoot)` consumed by `updateAgentInstructions` |
85
+ | `lib/installer.js` | verified — `updateAgentInstructions` at line ~358, iteration loop at line 428 | Task 3 adds drift-detection branch inside the Cline-file path. Task 2.3 may add first-insert wrapper branch |
86
+ | `lib/agents.js` | verified — Cline entry lines 113–139, `instructionFiles: ['.cline/clinerules.md', '.clinerules']` at line 137 | Read-only unless Task 4.1 required |
87
+ | `bin/cli.js` | verified | Task 3.3 — one-line addition to `--help` text for `install` |
88
+ | `lib/templates/clinerules.template.md` | **new** | Cline-flavored wrapper (AC #1) |
89
+ | `lib/templates/instruction-block-universal.template.md` | **new (Story 21.2)** | Source of universal rule text consumed by `composeInstructionBlock` |
90
+ | `lib/templates/instruction-block-onprem.template.md` | **new (Story 21.6)** | On-prem content appended by `composeInstructionBlock` when profile=on-prem |
91
+ | `lib/templates/project-context.template.md` | verified | Reference for `{{PLACEHOLDER}}` template-stamping syntax |
92
+ | `test/clinerules.test.js` | **new** | Unit + integration tests (see Testing section) |
93
+ | `test/profile.test.js` | verified (Story 21.1) | Reference for test framework and temp-dir isolation pattern |
54
94
 
55
- - Story 21.2 (universal block — composed via `composeInstructionBlock` for the shared rules)
95
+ ### Composition pattern reuse
56
96
 
57
- ### Reference
97
+ This story introduces no new composition pattern. It consumes the 21.2-owned `composeInstructionBlock({ profile, projectRoot }) → string` (Story 21.2 AC #3), extended under Option B to serve all five agents uniformly. The Cline-specific inputs are the two-entry `instructionFiles[]` list and the Architect-mode framing line (AC #1). The composed rule body is identical to what Claude Code, Cursor, Kilocode, Copilot, Gemini, and the Roo Code rules file receive.
58
98
 
59
- Source playbook: `optimizing-local-llm-coding-agents-bmad.md` Section 3.3 — `.clinerules` example.
99
+ ### Stamper site map (Cline-specific)
100
+
101
+ - **Markdown marker stamper** (`lib/installer.js` `updateAgentInstructions` ~lines 407–459, loop at line 428): writes `.cline/clinerules.md` and `.clinerules` with marker-wrapped composed content produced by the 21.2-owned composer.
102
+ - **Skills dir** (`lib/agents.js` line 118, `.cline/skills`): unaffected.
103
+ - **MANIFEST.yaml path** computed via `path.relative(projectRoot, path.join(agent.getProjectPath(), 'MANIFEST.yaml')).replace(/\\/g, '/')`. For Cline, `getProjectPath()` returns `path.join(process.cwd(), '.cline', 'skills')`, so the rendered manifest path is `.cline/skills/MANIFEST.yaml` — unchanged from pre-Epic-21 (AC #9).
104
+
105
+ ### Library and pattern references
106
+
107
+ - **Template placeholder syntax:** `{{PLACEHOLDER}}` — see `lib/templates/project-context.template.md`. This story uses exactly one placeholder (`{{MANIFEST_PATH}}`) inherited via `composeInstructionBlock`.
108
+ - **Marker constants:** `<!-- MA-AGENTS-START -->` / `<!-- MA-AGENTS-END -->` — defined in `lib/installer.js` (Story 21.2 touches these; this story does not).
109
+ - **Backup filename format:** `<target>.backup-<ISO-8601-timestamp>` — 21.2-owned convention applied when the stamper rewrites either Cline file. Cite Story 21.2 (upstream) as the sole owner of this format; do not redefine here.
110
+ - **Error convention:** `ClinerulesDualFileDriftError` (new) — thrown by the drift-detection branch. Follows the error-class naming pattern introduced by Story 21.10's `RoomodesSlugDivergenceError` (Story 21.3 AC #9 / Story 21.10 AC #5). Both are install-time abort errors that require user action.
111
+
112
+ ### Cline-specific considerations
113
+
114
+ - **Architect / Code mode.** Cline exposes an Architect vs. Code mode toggle. AC #1 framing line steers users to use Architect mode during BMAD planning phases and Code mode only for implementation. This is prompt-level guidance — no application-layer enforcement exists. If Cline introduces application-layer mode gating in a future version, a follow-up story can integrate it.
115
+ - **`.clinerules/` directory (v3+).** Out of scope per open question. This story assumes single-file `.clinerules`.
60
116
 
61
117
  ### Out of Scope
62
118
 
63
- - Cline-to-Roo migration (Epic 18 Story 18.4)
64
- - On-prem-specific additions (Story 21.6 appended to the same marker block when profile=on-prem)
119
+ - Universal template content (Story 21.2).
120
+ - On-prem template content and on-prem conditional wiring details beyond the signature-compatible call (Story 21.6).
121
+ - `.roomodes` template and Roo Code `fileRegex` (Story 21.3).
122
+ - `AGENTS.md` for OpenCode (Story 21.4).
123
+ - BMAD persona phase prefix in customize-loader (Story 21.7).
124
+ - vLLM deployment doc and README on-prem section (Story 21.8).
125
+ - Cross-agent NFR44/NFR46 byte-comparison tests (Story 21.9).
126
+ - Cline `.clinerules/` directory support (Cline v3+).
127
+ - Cline Plan/Act-mode integration beyond the AC #1 framing line.
128
+ - Profile reconfigure and uninstall flows (Stories 21.10, 21.11).
129
+ - Cline-to-Roo migration (Epic 18 Story 18.7).
130
+
131
+ ## Dependencies
132
+
133
+ ### Upstream (blocking)
134
+
135
+ - **Story 21.1 (done)** — `lib/profile.js::getProfile(projectRoot)` consumed via `updateAgentInstructions`. Verified at `lib/profile.js` lines 29–43.
136
+ - **Story 21.2 (Ready)** — The 21.2-owned composer `composeInstructionBlock({ profile, projectRoot }) → string` in `lib/installer.js` is the single-owner composition entry point, serving all five agents uniformly per Option B (AC #2). 21.2 also owns the backup filename format `<target>.backup-<ISO-8601-timestamp>`. Without 21.2, Story 21.5 has no universal rule-body source and no backup convention. `.cline/clinerules.md` and `.clinerules` are already iterated in `updateAgentInstructions`'s loop (line 428) so 21.2 covers the Cline stamper by default; 21.5's value-add is the Cline-flavored framing template (AC #1), the dual-file drift check (AC #6), and Cline-specific tests.
137
+
138
+ ### Downstream (consumers of this story's surface)
139
+
140
+ - **Story 21.6** — Adds on-prem template content; Cline files pick up on-prem content via the same `composeInstructionBlock` path (AC #8). No Story 21.5 signature change required.
141
+ - **Story 21.9** — Integration tests asserting Cline-specific NFR44 absence across both Cline files and NFR46 idempotency across consecutive installs of a Cline-only selection.
142
+ - **Stories 21.10 / 21.11** — Profile reconfigure and uninstall must cover both `.cline/clinerules.md` and `.clinerules` marker blocks in their re-stamp and cleanup passes. The Cline `instructionFiles[]` list from `lib/agents.js` is the authoritative enumeration. `ClinerulesDualFileDriftError` from AC #6 may also surface in `reconfigure`; 21.10 inherits the same abort semantics.
143
+
144
+ ## Testing
145
+
146
+ **Framework and isolation:** Match the pattern in `test/profile.test.js` (Story 21.1) — node's built-in test runner, `os.tmpdir()` + `fs.mkdtempSync` per-test temporary project roots, never mutate the repo's own `.ma-agents.json` or `lib/templates/`.
147
+
148
+ New test file: `test/clinerules.test.js` (new).
149
+
150
+ **Unit tests:**
151
+
152
+ - 5.1 Cline template file exists at `lib/templates/clinerules.template.md` and contains the AC #1 Architect-mode framing line (existence + shape check; rule-body text lives in the universal template per AC #2).
153
+ - 5.2 NFR44 per-file: after a standard-profile install of a Cline-only selection, neither `.cline/clinerules.md` nor `.clinerules` contains the literals `/no_think`, `str_replace_editor`, or `~/.claude/` (AC #7).
154
+ - 5.3 NFR46 per-file: two consecutive installs produce byte-identical marker-block content in each Cline file (AC #5).
155
+ - 5.4 Cross-file identity by construction: after a single install, `.cline/clinerules.md` and `.clinerules` have byte-identical marker-block content (AC #6 "render once, write twice" invariant).
156
+
157
+ **Integration tests (within this story — full end-to-end lives in Story 21.9):**
158
+
159
+ - 5.5 Fresh install of a Cline-only selection writes both `.cline/clinerules.md` and `.clinerules` at the project root; both contain marker-wrapped composed content (AC #3, #4).
160
+ - 5.6 User content outside markers is preserved byte-for-byte across two installs (AC #4) — verified by seeding a user preamble above the markers and a user postamble below them in each Cline file, running install twice, and asserting preamble/postamble bytes are untouched.
161
+ - 5.7 On-prem profile (when Story 21.6's template file is present): both Cline files contain on-prem content inside the markers (AC #8). When Story 21.6's template is absent, on-prem profile install still succeeds and emits universal-only content — no error (graceful fallback, Story 21.2 AC #3).
162
+ - 5.8 MANIFEST path rendering: `{{MANIFEST_PATH}}` resolves to `.cline/skills/MANIFEST.yaml` (relative to project root, forward-slashed on Windows) in both Cline files (AC #9).
163
+ - 5.9 Dual-file drift detection (AC #6):
164
+ - Both files exist with identical marker-block content → install proceeds silently.
165
+ - Both files exist with divergent marker-block content (simulated by hand-editing one file) + interactive install → throws `ClinerulesDualFileDriftError` naming both files and emitting the diff.
166
+ - Same as above + `--yes` → still throws `ClinerulesDualFileDriftError` (AC #6: `--yes` does NOT bypass).
167
+ - Only one of the two files exists → drift detection skipped; install writes both files from the same render.
168
+
169
+ **Test data isolation:** All tests create a tmp project with a stub `.ma-agents.json` written via `setProfile(tmpRoot, 'standard' | 'on-prem')` (Story 21.1 API). Do not stub `lib/profile.js` or `composeInstructionBlock` — use the real modules against the tmp project root to catch integration bugs.
170
+
171
+ **Coverage note:** Story 21.9 adds a cross-agent NFR44 byte-comparison test and cross-agent NFR46 idempotency test. Do not duplicate those here — keep 21.5 tests scoped to Cline's two files.
65
172
 
66
173
  ## Dev Agent Record
67
174
 
68
175
  ### Agent Model Used
69
- _(to be filled by dev agent)_
176
+ Claude Opus 4.6 (1M context) bmad-dev-story + bmad-review-adversarial-general + bmad-review-edge-case-hunter flow.
70
177
 
71
178
  ### Debug Log References
72
- _(to be filled)_
179
+ - Task 4 in the story spec is explicitly "(removed Option B canonical)": per-agent `extraInstructionTemplates` wiring is unnecessary for Cline. Implementation instead adds a Cline-only branch inside `updateAgentInstructions` that (a) runs dual-file drift detection before the file loop and (b) consumes `lib/templates/clinerules.template.md` as a framing wrapper when creating FRESH Cline files. Existing files go through the normal marker-replace path so AC #4 (user content preserved) is honored byte-for-byte.
180
+ - Drift-diff normalization uses `s.replace(/\s+/g, ' ').trim()` to implement AC #6's "non-whitespace diff" requirement — pure whitespace-only divergence proceeds silently.
73
181
 
74
182
  ### Completion Notes List
75
- _(to be filled)_
183
+ - `lib/templates/clinerules.template.md` ships with the Cline-flavored framing (header `# Cline Project Rules`, intro paragraph, Architect-mode guidance line per AC #1). The template contains empty `<!-- MA-AGENTS-START -->` / `<!-- MA-AGENTS-END -->` markers; the fresh-install branch replaces that pair with the wrapped composer output.
184
+ - `ClinerulesDualFileDriftError` extends Error, follows the `RoomodesSlugDivergenceError` naming pattern, and carries `fileA`, `fileB`, `diff` fields. Message includes both file paths and a minimal line-level unified diff.
185
+ - `checkClinerulesDualFileDrift(projectRoot)` (exported) is called inside `updateAgentInstructions` when `agent.id === 'cline'`, BEFORE the instruction-files loop, so an abort happens cleanly without partial writes. `--yes` does NOT bypass this check (AC #6 explicit exception).
186
+ - Cross-file byte-identity is guaranteed by construction: `composeInstructionBlock` is called exactly once for the Cline agent; the resulting string is rendered into both `.cline/clinerules.md` and `.clinerules` in a single loop pass (AC #2 "render once, write twice" invariant).
187
+ - NFR44 (AC #7): verified by test 5.2 that neither Cline file contains `/no_think`, `str_replace_editor`, or `~/.claude/` on standard profile. The Cline framing template itself also avoids these strings (test 5.1).
188
+ - AC #9: `{{MANIFEST_PATH}}` substitution produces `.cline/skills/MANIFEST.yaml` (forward-slashed) in both files. Verified by test 5.8.
189
+ - `bin/cli.js` --help gains a "Notes:" section calling out the dual-file drift reconciliation requirement (AC #6 Task 3.3).
190
+ - `package.json` test script extended with `test/clinerules.test.js` (13 tests, all passing).
191
+
192
+ ### Adversarial Review Findings
193
+
194
+ | # | Layer | Severity | Finding | Disposition |
195
+ |---|-------|----------|---------|-------------|
196
+ | 1 | Cynical | P1 | Drift `normalize` collapses all whitespace — could hide whitespace-only divergence | **Accepted** — AC #6 explicitly says "non-whitespace diff" triggers abort; whitespace-only divergence proceeds silently by spec |
197
+ | 2 | Cynical | P2 | Drift-error `diff` field leaks content to logs; potential ANSI injection via hand-edited file | **Accepted** — attacker with file-write access to project root has already won; Node does not auto-interpret ANSI in Error stacks |
198
+ | 3 | Cynical | P1 | Framing template is applied ONLY for fresh-create; existing files with no markers get bare block appended | **Correct by design** — AC #4 mandates byte-for-byte preservation of user content; adding framing around existing user content would violate that |
199
+ | 4 | Edge-case | P1 | Second install after fresh install must still be byte-identical (NFR46) — framing-wrapped create vs. marker-replace could diverge | **Verified by test 5.3** — both installs produce byte-identical files because in-marker content is the governing region and outside-marker content is preserved |
200
+ | 5 | Edge-case | P1 | Only one of two Cline files exists (partial uninstall / fresh with pre-seed) — drift check must not fire | **Verified by test 5.9d** — drift detection skipped when either file absent; install still writes both |
201
+ | 6 | Edge-case | P2 | Cline v3+ `.clinerules/` DIRECTORY (not file) — `path.join(projectRoot, '.clinerules')` would point to a dir and readFileSync would throw EISDIR | **Out of scope per story** (open question explicitly deferred to follow-up story) |
202
+ | 7 | Edge-case | P1 | --yes must NOT bypass drift check (explicit exception) | **Verified by test 5.9c** — ClinerulesDualFileDriftError still thrown with `yesMode: true` |
203
+ | 8 | Edge-case | P2 | Mid-install failure between writes could cause the two files to diverge — next run would trip drift check | **Accepted** — correct failure mode; user sees clear error message and can reconcile |
204
+
205
+ All P0/P1 findings resolved or accepted with documented rationale.
76
206
 
77
207
  ### File List
78
- _(to be filled)_
208
+ - CREATED: `lib/templates/clinerules.template.md`
209
+ - CREATED: `test/clinerules.test.js` (13 tests, all passing)
210
+ - MODIFIED: `lib/installer.js` (added `CLINERULES_TEMPLATE_PATH`, `ClinerulesDualFileDriftError`, `_extractMarkerBlockInner`, `checkClinerulesDualFileDrift`; wired drift check + framing-template branch into `updateAgentInstructions`; exposed new symbols)
211
+ - MODIFIED: `bin/cli.js` (added "Notes:" section under `--help` with dual-file reconciliation callout per AC #6 Task 3.3)
212
+ - MODIFIED: `package.json` (added `test/clinerules.test.js` to npm test script)
213
+ - MODIFIED: `_bmad-output/implementation-artifacts/21-5-clinerules-template-extension.md` (Status → Review; Dev Agent Record / File List / Change Log updated)
79
214
 
80
215
  ## Change Log
216
+
81
217
  - 2026-04-14: Story created (Epic 21, Story 21.5)
82
218
  - 2026-04-14: Added AC #6 for `.cline/clinerules.md` vs `.clinerules` drift detection (Finding #10, corrective plan step 3). `--yes` does NOT bypass this check — reconciliation is user work.
219
+ - 2026-04-15: Story expanded as Cline slice of the universal layer atop Story 21.2. Gap-fill ACs flagged (AC #2, #5, #7, #8, #9). NFR44/NFR46/NFR47/NFR18 citations made explicit. Verified source-tree surface table added. Upstream/downstream dependencies split. Testing section expanded (9 tests). Three open questions raised inline rather than invented as ACs. Status set to Ready.
220
+ - 2026-04-15: Story 21.5 implemented — Cline framing template, dual-file drift detection, CLI --help note, 13 tests. Status Ready → Review.
221
+ - 2026-04-15: Applied adversarial-review canonical decisions. AC #1 pins `lib/templates/clinerules.template.md` (closes P1 #9). AC #2 resolves Option A vs B inline — Option B chosen (extend the 21.2-owned `composeInstructionBlock({profile, projectRoot}) → string` to serve all five agents uniformly; closes P1 #3) and folds the cross-file byte-identity invariant into AC #2 (single composer + single stamper pass → both Cline files byte-identical by construction; eliminates the second open question). Terminology normalized to composer / merger / stamper (closes P1 #10); "injection function" and "marker-injection" deleted. Story 21.2 cited as sole upstream owner of the composer and the `<target>.backup-<ISO-8601-timestamp>` backup filename format. Task 2.3's conditional branching removed; Task 4 retired (Option B obviates per-agent `extraInstructionTemplates`). All tasks unconditional — Status remains Ready.