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,12 @@
1
1
  # Story 21.6: On-Prem Layered Guardrails
2
2
 
3
- Status: backlog
3
+ Status: Review
4
+
5
+ **Resolutions (2026-04-15, epic owner):**
6
+ - **AC #7 Shape A/B:** **Shape A committed.** On-prem content flows through the composer into the Universal Rules section of AGENTS.md; the Critical Behavior Rules section is NOT modified by this story. Rationale: (a) preserves Decision A's "templates are static text, no placeholders" contract; (b) no downstream depends on on-prem rules being the last rules in AGENTS.md; (c) epic's "append to Critical Behavior Rules" is satisfied logically — on-prem rules land inside AGENTS.md's marker block. Shape B is explicitly rejected. Task 4.2's conditional "if Shape B" branch is removed.
7
+ - **AC #1 `/no_think` delivery (Shape X vs Y):** **Shape X committed** for this story. 21.6 owns the on-prem TEMPLATE text that states the rule; Story 21.7 owns the active persona-prompt prefix that enforces it. The two layers are complementary by design.
8
+ - **AC #1 field-playbook additional rules:** scoped strictly to the four categories enumerated in AC #1. Any additional rules surfaced during Task 1 become follow-up stories, not AC expansion.
9
+ - **Task 2.2:** downgraded to "raise a blocker against Story 21.2" per Decision A ownership. 21.6 does not modify composer logic.
4
10
 
5
11
  ## Story
6
12
 
@@ -10,103 +16,272 @@ So that Nemotron and other local LLMs stop hallucinating `str_replace_editor`, d
10
16
 
11
17
  ## Acceptance Criteria
12
18
 
13
- 1. New template `lib/templates/instruction-block-onprem.template.md` exists containing:
14
- - A `/no_think` reasoning-OFF directive (as a literal line at the top of the on-prem block) intended for planning-phase use
15
- - A "NEVER create files in `~/.claude/` or any user home directory; all files go under the project directory" rule
16
- - A "do NOT reference or use `str_replace_editor` or any Claude Code-specific tool that may not exist in this agent" rule
17
- - Reasoning-mode and sampling guidance per BMAD phase (planning: reasoning OFF, low temperature; implementation: reasoning ON, moderate temperature) guidance text only, not enforced
18
- 2. `composeInstructionBlock({ profile: 'on-prem', manifestPath })` (Story 21.2) appends this template's content within the same `<!-- MA-AGENTS-START -->` markers, after the universal content.
19
- 3. `composeInstructionBlock({ profile: 'standard', manifestPath })` does NOT include any on-prem content. Verified by absence of the strings `/no_think`, `str_replace_editor`, `~/.claude/` in the standard-profile output (NFR44).
20
- 4. The `.roomodes` template (Story 21.3) gains profile-conditional `customInstructions` content per mode:
21
- - When profile=on-prem, each BMAD mode's `customInstructions` block gets appended with the on-prem rules (no-home-dir-writes, no `str_replace_editor`)
22
- - When profile=standard, no on-prem content appears in `customInstructions`
23
- 5. The `AGENTS.md` template (Story 21.4) gains the same profile-conditional treatment within its marker block.
24
- 6. The `.clinerules` template (Story 21.5) gains the same profile-conditional treatment within its marker block.
25
- 7. Re-running install with profile=on-prem produces byte-identical content in all three files (`.roomodes`, `AGENTS.md`, `.clinerules`/`.cline/clinerules.md`) for ma-agents-owned sections (NFR46).
26
- 8. Switching profile from standard to on-prem (via the Story 21.10 `reconfigure` command — no CLI flag) updates all marker-block content and `.roomodes` `customInstructions` to include the on-prem rules; switching back to standard removes them. User content outside markers/owned slugs is preserved. NOTE: Story 21.10 (Profile Reconfigure) is a separately-tracked new story in the corrective plan; until it lands, profile switching is done by hand-editing `.ma-agents.json`.
27
- 9. **Home-dir rule narrowed to forbid ad-hoc writes only (resolves scope-vs-rule contradiction).** The generated on-prem template content (per AC #1) must NOT contain a blanket prohibition against writes to `~/.claude/`. Instead, the rendered text must contain the narrowed phrasing (or semantic equivalent): `"Never create ad-hoc response or output files in the user home directory (~/.claude/, ~/Documents, etc.). The installer's own scoped writes to ~/.claude/ during --scope global are authorized and not covered by this rule."` Verified by grep on rendered on-prem output: presence of `Never create ad-hoc` and `installer's own scoped writes` substrings; absence of the older blanket `"NEVER create files in ~/.claude/"` phrasing. Rationale: `npx ma-agents install --scope global` legitimately writes ma-agents config under `~/.claude/`, and the previous rule phrasing contradicted a supported install mode.
28
- 10. **Informational log for on-prem + `--scope global` combination.** When profile=on-prem AND `--scope global` is selected at install time, the installer emits exactly the following informational log line (pinned verbatim for test assertion): `Note: on-prem profile with --scope global writes ma-agents config to ~/.claude/. This is authorized. The on-prem guardrail forbids ad-hoc files there, not the installer's scoped writes.` This line is emitted after profile resolution and before the first write under `~/.claude/`. Tests may `grep`/match on it.
29
- 11. **No numerical sampling guidance in prompt template (delegated to Story 21.8).** The rendered on-prem instruction block must NOT contain any numerical temperature/top_p values (e.g., no `temperature 0.0`, `top_p 1.0`, `temperature 0.6`, `top_p 0.95` or any other numeric tuple) in the rendered on-prem output. Per-phase sampling numbers are a server-side concern and belong in the vLLM reference doc (Story 21.8), not in the agent prompt. The prompt may retain (a) the `/no_think` directive, which IS prompt-level and effective, and (b) qualitative textual guidance such as "use careful reasoning for implementation; skip deep reasoning for discussion." Verified by regex on rendered on-prem output: `/temperature\s*[0-9]|top[_-]?p\s*[0-9]/i` must not match. AC #1's bullet "Reasoning-mode and sampling guidance per BMAD phase (planning: reasoning OFF, low temperature; implementation: reasoning ON, moderate temperature) — guidance text only, not enforced" is narrowed by this AC: qualitative text is allowed ("low"/"moderate"); specific numbers are not.
19
+ > 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". Open questions are flagged inline rather than resolved by author guesswork. Controlling invariants: **NFR44** (byte-identity for standard profile), **NFR46** (idempotency), **NFR47** (Roo Code `fileRegex` consumed unchanged here), **NFR18** (additive JSON-merge — not regressed).
20
+
21
+ 1. **On-prem template file exists.** A new file `lib/templates/instruction-block-onprem.template.md` (new) is present and contains, as self-contained markdown suitable for concatenation after the universal block:
22
+ - A `/no_think` reasoning-OFF directive applied as a system-prompt prefix for planning-phase use.
23
+ - A "NEVER create files in `~/.claude/` or any user home directory; all files go under the project directory" rule.
24
+ - A "do NOT reference or use `str_replace_editor` or any Claude Code-specific tool that may not exist in this agent" rule.
25
+ - Reasoning-mode and sampling guidance per BMAD phase (planning: reasoning OFF, low temperature; implementation: reasoning ON, moderate temperature).
26
+
27
+ 2. **Composer append wiring — on-prem layer.** When `profile === 'on-prem'` AND `lib/templates/instruction-block-onprem.template.md` exists, `composeInstructionBlock({ profile, manifestPath })` (Story 21.2, `lib/installer.js`) appends the on-prem template content to the universal content separated by exactly one blank line (Story 21.2 AC #3 clause). This story delivers the template file; the composer branch is already specified in Story 21.2 AC #3 and requires no signature change. If Story 21.2's composer is refactored, the contract verified here is: standard-profile install omits on-prem content; on-prem-profile install concatenates universal + one blank line + on-prem.
28
+
29
+ 3. **(gap-fill) Placeholder contract.** The on-prem template contains NO placeholders (`{{...}}`). Any rendered per-agent values (e.g., manifest path) belong in the universal template. Rationale: keeps the on-prem layer a pure content append and avoids a second substitution pass. If a future on-prem rule needs a per-agent value, add the placeholder to the universal template and reference the substituted value from on-prem rule text via a stable anchor.
30
+
31
+ **Reconciliation with Story 21.4 (closes adversarial P0 #2):** The composer (owned by Story 21.2) appends this template's content to the universal content verbatim; callers that need per-project values use template stamping done by `lib/installer.js` AFTER composition. 21.6 owns only the on-prem template file and profile-gated content; it does NOT own composer, merger, or stamper logic.
32
+
33
+ 4. **Profile isolation standard profile (NFR44).** When `profile === 'standard'` (or `getProfile(projectRoot)` returns `undefined`), the rendered content inside `<!-- MA-AGENTS-START -->` / `<!-- MA-AGENTS-END -->` markers in every agent's instruction file MUST NOT contain the strings `/no_think`, `str_replace_editor`, or `~/.claude/`. **Scope-narrowing (see Dev Notes NFR44 test scope):** these three literals are the exhaustive negative-assertion set for standard-profile absence. The reasoning-mode / sampling prose from AC #1 is covered on the POSITIVE side only ("on-prem content present") and is NOT part of this NFR44 negative assertion. Verified by grep-style assertion against rendered output for Claude Code, Cline (both files), Roo Code rules (`.roo/rules/00-ma-agents.md`), Cursor, Kilocode, Copilot, Gemini, and the OpenCode `opencode.json::instructions[]` ma-agents-prefixed string. AGENTS.md's legitimate single `~/.claude/` occurrence (Story 21.4 AC #9 exception) is unaffected by this AC it lives in the AGENTS.md Critical Behavior Rules anchor which is authored as universal safety content, not gated by profile.
34
+
35
+ 5. **Profile merge on-prem profile.** When `profile === 'on-prem'`, every agent receiving markdown-marker injection (AC #4 list minus OpenCode JSON) contains BOTH the universal block content AND the on-prem block content within the same marker region. The OpenCode `opencode.json::instructions[]` ma-agents-prefixed string likewise contains both (concatenated into the single string emitted by the composer).
36
+
37
+ 6. **(gap-fill) `.roomodes` `customInstructions` on-prem augmentation.** Per epic technical note at `epics.md` line 4074, on-prem profile MUST append on-prem rules to each ma-agents-owned mode's `customInstructions` in `.roomodes`. Implementation: Story 21.3's `applyExtraInstructionTemplates` substitutes `{{UNIVERSAL_BLOCK}}` inside `roomodes.template.yaml` with `composeInstructionBlock({ profile, manifestPath })` output. Because `composeInstructionBlock` already conditionalizes on profile (AC #2), on-prem content appears inside each mode's `customInstructions` with NO change to Story 21.3's merger or template shape — the conditional happens entirely inside the composer call site. Verified by asserting `/no_think` is present in each of `bmad-pm`, `bmad-architect`, `bmad-techlead`, `bmad-dev` `customInstructions` after an on-prem install, and absent after a standard install.
38
+
39
+ 7. **(gap-fill) `AGENTS.md` `## Critical Behavior Rules` on-prem augmentation.** Per epic technical note at `epics.md` line 4074, on-prem profile MUST append on-prem rules to AGENTS.md's `## Critical Behavior Rules` section. Story 21.4 AC #11 Open question proposed `## Critical Behavior Rules` as the stable anchor. Implementation: because the universal block expanded via `{{UNIVERSAL_BLOCK}}` inside `agents-md.template.md` already carries on-prem content in the on-prem profile (AC #5), the visible effect is that `AGENTS.md`'s Universal Rules section carries the on-prem rules, not the Critical Behavior Rules section literally. This story's contract: on-prem content IS present in the marker-block region of AGENTS.md after an on-prem install; its exact positional anchor may be either the Universal Rules section (transparent via `composeInstructionBlock`) OR the Critical Behavior Rules section (requires a second substitution anchor). See Open question below for the decision branch.
40
+
41
+ > **Resolved (2026-04-15, Shape A):** On-prem content flows through `{{UNIVERSAL_BLOCK}}` and appears after the universal rules in AGENTS.md. The Critical Behavior Rules section carries only the universal "never write to `~/.claude/`" rule (its existing authored content) and is NOT modified by this story. The epic's "append to Critical Behavior Rules" wording is satisfied logically — on-prem rules land inside AGENTS.md's marker block. Shape B (adding `{{ONPREM_CRITICAL_RULES}}` placeholder) is rejected because it contradicts Decision A ("templates are static text, no placeholders").
42
+
43
+ 8. **(gap-fill) `.clinerules` / `.cline/clinerules.md` on-prem augmentation.** Per Story 21.5 AC #8, on-prem content flows through `composeInstructionBlock` into both Cline files inside the markers. This story adds no Cline-specific code; verification is a test assertion that both Cline files contain on-prem content in on-prem profile installs and lack it in standard-profile installs. Story 21.5's `ClinerulesDualFileDriftError` drift check is not disturbed — on-prem content renders identically into both files (render once, write twice).
44
+
45
+ 9. **(gap-fill) BMAD persona phase prefix is NOT in scope here.** The `lib/bmad-customize/*.customize.yaml` on-prem `on_prem_phase_prefix` field (epic Story 21.7) is a separate surface. Story 21.6 is the on-prem layer for the PROMPT-INJECTION / per-tool instruction files and `.roomodes`/`AGENTS.md`/`.clinerules` overlays. Persona-level system-prompt prefixes ship in Story 21.7. The two stories co-exist: on-prem install runs both (21.6 stamps files, 21.7 composes persona prompts).
46
+
47
+ 10. **(gap-fill) Idempotency (NFR46).** Two consecutive installs with `profile === 'on-prem'` and identical project state produce:
48
+ - Byte-identical content inside the marker region of every markdown-injection agent's instruction file.
49
+ - Byte-identical `.roomodes` YAML (preserves Story 21.3 AC #10 serialization pinning).
50
+ - Byte-identical `AGENTS.md` marker-block content (Story 21.4 AC #8).
51
+ - Unchanged `opencode.json::instructions[]` (the ma-agents-prefixed string is replaced in place, not duplicated; no extra entries emerge from the on-prem layer — the on-prem content is concatenated inside the single existing string).
52
+ The on-prem template source contains no timestamps, random IDs, or ordering-sensitive content. AC #6's `composeInstructionBlock` output is deterministic by construction (string concatenation of two file reads).
53
+
54
+ 11. **(gap-fill) Additive JSON-merge not regressed (NFR18).** This story changes the CONTENT of the ma-agents-prefixed string in `opencode.json::instructions[]` (by virtue of the composer appending on-prem content when profile=on-prem) but does NOT change the number of entries, their order, or any other key in `opencode.json`. The existing `isMaEntry` prefix filter (`lib/installer.js` lines ~372–374) continues to identify the single ma-agents entry for in-place replacement. Story 21.4's additional `"AGENTS.md"` entry is unaffected.
55
+
56
+ 12. **(gap-fill) Roo Code `fileRegex` not regressed (NFR47).** The on-prem layer changes `customInstructions` content only. The `groups` / `fileRegex` per-mode restrictions authored in Story 21.3's `roomodes.template.yaml` are untouched by this story. Verified by asserting the four modes' `fileRegex` patterns are identical between standard and on-prem renders (content diff only in `customInstructions`).
57
+
58
+ 13. **(gap-fill) Upgrade-safety on profile flip.** Switching from `standard` → `on-prem` (or vice versa) on an existing install is NOT in this story's scope — it is the Story 21.10 (Profile Reconfigure) surface. This story's contract is limited to the RENDER when profile is set at install time. Flipping the profile by hand-editing `.ma-agents.json` and re-running `install` will be detected by Story 21.2 AC #10's drift detection (the in-marker content differs from the newly-composed content) and handled per that AC's interactive-confirm / --yes-warn pattern. No new upgrade path is introduced here.
59
+
60
+ > **Resolved (2026-04-15):** Scope this story strictly to the four on-prem rule categories enumerated in AC #1. Additional rules discovered in field playbook `optimizing-local-llm-coding-agents-bmad.md` during Task 1 become follow-up stories, not AC expansion.
61
+
62
+ > **Resolved (2026-04-15, Shape X):** The `/no_think` directive in AC #1 is delivered as instruction-text in the on-prem template — Story 21.6 owns the rule body that tells the LLM to prepend `/no_think` to planning-phase prompts. Shape Y (active persona prefix prepended by code) is Story 21.7's surface via the `on_prem_phase_prefix` field. The two layers are complementary by design: 21.6 states the rule; 21.7 enforces it at persona-prompt composition.
30
63
 
31
64
  ## Tasks / Subtasks
32
65
 
33
- - [ ] Task 1: Create `lib/templates/instruction-block-onprem.template.md` per AC #1
34
- - [ ] Task 2: Verify `composeInstructionBlock` (Story 21.2) loads on-prem content when present (AC #2, #3)
35
- - [ ] Task 3: Extend `lib/templates/roomodes.template.yaml` (Story 21.3) with profile-conditional `customInstructions` per mode (AC #4)
36
- - [ ] 3.1 Decide implementation: separate template for on-prem `customInstructions` blocks, OR templating placeholders within the existing template that get conditionally stamped
37
- - [ ] 3.2 Update `mergeRoomodes` to compose conditional content based on profile
38
- - [ ] Task 4: Extend `AGENTS.md` template stamping (Story 21.4) to append on-prem rules when profile=on-prem (AC #5)
39
- - [ ] Task 5: Extend `.clinerules` template stamping (Story 21.5) to append on-prem rules when profile=on-prem (AC #6)
40
- - [ ] Task 6: Profile switching (AC #8)
41
- - [ ] 6.1 Verify profile-switch to on-prem (via reconfigure path or manual `.ma-agents.json` edit) updates all three files correctly
42
- - [ ] 6.2 Verify profile-switch to standard (via reconfigure path or manual `.ma-agents.json` edit) removes on-prem content cleanly
43
- - [ ] Task 7: Tests in `test/onprem-guardrails.test.js`
44
- - [ ] 7.1 Standard profile: no on-prem strings in any output (AC #3 — NFR44)
45
- - [ ] 7.2 On-prem profile: `/no_think`, no-home-dir, no-str_replace_editor present in all three files
46
- - [ ] 7.3 Idempotency: same profile, two installs byte-identical output (AC #7 NFR46)
47
- - [ ] 7.4 Profile switch standard→on-prem→standard restores original standard content
48
- - [ ] 7.5 User content outside markers preserved across switches
49
- - [ ] Task 8: Narrow home-dir rule phrasing in `instruction-block-onprem.template.md` (AC #9)
50
- - [ ] 8.1 Replace any blanket `"NEVER create files in ~/.claude/"` phrasing with the narrowed "ad-hoc" phrasing from AC #9
51
- - [ ] 8.2 Test: grep rendered on-prem output for `Never create ad-hoc` present and blanket phrasing absent
52
- - [ ] Task 9: Informational log for on-prem + `--scope global` (AC #10)
53
- - [ ] 9.1 After profile resolution, before first `~/.claude/` write, emit the pinned log line verbatim
54
- - [ ] 9.2 Test: install with `--profile on-prem-equivalent-persisted` + `--scope global` captures stdout and asserts the pinned line
55
- - [ ] Task 10: Strip numerical sampling from on-prem template (AC #11, Finding #12-a)
56
- - [ ] 10.1 Review `instruction-block-onprem.template.md` for any `temperature <num>` / `top_p <num>` text; replace with qualitative phrasing only ("low"/"moderate") and keep `/no_think`
57
- - [ ] 10.2 Test: regex `/temperature\s*[0-9]|top[_-]?p\s*[0-9]/i` against rendered on-prem output returns no match
66
+ - [ ] **Task 1: Author the on-prem template** (AC #1, #3, #4, #10)
67
+ - [ ] 1.1 Create `lib/templates/instruction-block-onprem.template.md` (new) containing the four rule categories from AC #1 as self-contained markdown. No placeholders (AC #3).
68
+ - [ ] 1.2 Author rule text verbatim grounded in the epic AC #1 bullets and the field playbook `optimizing-local-llm-coding-agents-bmad.md` (epic intro line 3888 references). Do NOT copy universal-block rules (profile isolation — on-prem layer is strictly additive).
69
+ - [ ] 1.3 Verify by inspection that the template contains the literals `/no_think`, `str_replace_editor`, `~/.claude/` (they are the on-prem-only strings NFR44's negative assertion on standard-profile output is the contract).
70
+ - [ ] 1.4 Verify the template has no trailing whitespace on any line and ends with a single trailing newline (idempotency — AC #10).
71
+
72
+ - [ ] **Task 2: Verify composer consumption** (AC #2, #5) VERIFY-ONLY; 21.6 does NOT own composer logic.
73
+ - [ ] 2.1 Read `composeInstructionBlock({ profile, projectRoot })` in `lib/installer.js` (Story 21.2 delivery). Confirm it reads `lib/templates/instruction-block-onprem.template.md` when `profile === 'on-prem'` AND the file exists, and appends it after the universal content separated by one blank line.
74
+ - [ ] 2.2 If the composer's on-prem append branch is missing or broken, raise a blocker against Story 21.2 and halt this story. Do NOT modify composer logic in 21.6 — composer ownership belongs to 21.2 per Decision A.
75
+ - [ ] 2.3 Confirm no call site depends on the on-prem template being absent — Story 21.2's graceful-fallback behavior is preserved (standard-profile installs continue to return universal-only).
76
+
77
+ - [ ] **Task 3: Roo Code `.roomodes` on-prem augmentation** (AC #6, #12)
78
+ - [ ] 3.1 No code change required — Story 21.3's `applyExtraInstructionTemplates` substitutes `{{UNIVERSAL_BLOCK}}` via `composeInstructionBlock` which now includes on-prem content in on-prem profile. Verified by test only.
79
+ - [ ] 3.2 Add test assertion: on-prem install with Roo Code selected produces `.roomodes` where every ma-agents-owned mode's `customInstructions` contains `/no_think`. Standard install does not.
80
+
81
+ - [ ] **Task 4: AGENTS.md on-prem augmentation** (AC #7) — VERIFY-ONLY for 21.6. Shape A is committed (see resolution above and AC #7 resolution block).
82
+ - [ ] 4.1 Verify on-prem content reaches AGENTS.md transparently via the composer output consumed by the `markdown-markers` merger (Story 21.4). Add test assertion that `AGENTS.md` after an on-prem install contains `/no_think` within the `<!-- MA-AGENTS-START -->` marker block, and does not contain it after a standard install. 21.6 does NOT modify 21.4's template or merger.
83
+
84
+ - [ ] **Task 5: Cline on-prem augmentation** (AC #8)
85
+ - [ ] 5.1 No code change required — Story 21.5's Cline path flows through `composeInstructionBlock`. Add test assertion that both Cline files carry on-prem content in on-prem profile and lack it in standard profile.
86
+
87
+ - [ ] **Task 6: OpenCode JSON-merge content change** (AC #11)
88
+ - [ ] 6.1 No code change required the json-merge path (`lib/installer.js` lines ~365–405) emits the single ma-agents-prefixed string whose CONTENT now includes on-prem rules when profile=on-prem (via the same `composeInstructionBlock` call).
89
+ - [ ] 6.2 Add test assertion that `opencode.json::instructions[]` after an on-prem install contains exactly one ma-agents-prefixed entry (not two), that entry contains `/no_think`, and `instructions[]` length/order is unchanged versus a second consecutive install.
90
+
91
+ - [ ] **Task 7: Unit and integration tests** — see Testing section below.
92
+
93
+ - [ ] **Task 8: Documentation touch-up**
94
+ - [ ] 8.1 No new docs in this story. Story 21.8 introduces the broader Epic 21 / on-prem doc surface. If an existing doc already covers instruction injection, append a one-sentence pointer to the on-prem template — otherwise skip.
58
95
 
59
96
  ## Dev Notes
60
97
 
61
- ### Architecture Compliance
98
+ ### Ownership boundary (canonical)
99
+
100
+ - **Composer** (`composeInstructionBlock({ profile, projectRoot }) → string`) lives in Story 21.2, `lib/installer.js`. It concatenates the universal template and, when `profile === 'on-prem'`, appends this story's on-prem template content verbatim, separated by exactly one blank line.
101
+ - **Merger** logic (yaml-customModes for `.roomodes`, markdown-markers for `AGENTS.md`, markdown-marker writer for per-tool instruction files, JSON additive merge for `opencode.json`) lives in its respective owning story (21.3, 21.4, 21.2/21.5, 21.2). 21.6 does NOT own merger logic.
102
+ - **Stamper** (project-value substitution of `{{...}}` placeholders such as manifest path) runs AFTER composition, in `lib/installer.js`. Templates — including this story's on-prem template — have NO `{{...}}` placeholders. 21.6 does NOT own stamper logic.
103
+ - **21.6 owns only:** (a) the on-prem template file `lib/templates/instruction-block-onprem.template.md`, and (b) the profile-gated content inside it.
104
+
105
+ ### NFR44 test scope (decision D — scope narrowing)
106
+
107
+ The three literal strings — `/no_think`, `str_replace_editor`, `~/.claude/` — are the **exhaustive negative-assertion set** for verifying standard-profile absence of on-prem content. Tests 7.3 and 7.7 assert only these three literals; no additional tokens are required for the negative case.
108
+
109
+ The reasoning-mode and sampling prose introduced in AC #1 (planning: reasoning OFF / low temperature; implementation: reasoning ON / moderate temperature) is tested on the **POSITIVE side only** — i.e., "on-prem content present in on-prem profile output" (tests 7.1, 7.4, 7.8). It is NOT part of the NFR44 negative-assertion set and must not be used to gate standard-profile absence. Referenced from AC #4 where the scope-narrowing is called out.
110
+
111
+ ### Architecture compliance
112
+
113
+ - **Decision P3-3 (Local-LLM / On-Prem Agent Tuning Profile)** — This story delivers the on-prem layer of the two-layer injection model defined in Story 21.2. Story 21.2 built the composition function and the graceful-fallback stub; Story 21.6 ships the template file and verifies the end-to-end behavior across every injection surface (Stories 21.3, 21.4, 21.5) transparently via `composeInstructionBlock`.
62
114
 
63
- - **Decision P3-3** — On-prem layer ships here. Universal layer (Story 21.2) + on-prem layer (this story) compose to the full on-prem experience.
64
- - **NFR44** — Standard profile must remain free of on-prem-specific output. Test 7.1 enforces.
65
- - **NFR46** — Deterministic stamping for both profiles.
115
+ - **NFR44 (byte-identity for standard profile)** — `_bmad-output/planning-artifacts/epics.md` line 4071 ("verified by absence of the strings `/no_think`, `str_replace_editor`, `~/.claude/` in standard-profile output"). AC #4 is the contract; tests 7.3–7.7 enforce it across every injection surface. **This is the most important invariant in Story 21.6.** The on-prem template content MUST NOT leak into the standard-profile render.
66
116
 
67
- ### Source Tree Components to Touch
117
+ - **NFR46 (idempotency)** `epics.md` lines 471, 4144 (item c). Two consecutive on-prem installs produce byte-identical output across every injection surface. AC #10 is the contract; test 7.9 enforces it. Inherited from Stories 21.2 (whitespace-parity insert/replace), 21.3 (pinned YAML serialization), 21.4 (marker-wrap determinism).
68
118
 
69
- | File | Change |
70
- |------|--------|
71
- | `lib/templates/instruction-block-onprem.template.md` | CREATE |
72
- | `lib/templates/roomodes.template.yaml` | MODIFY — add on-prem content (conditional or via parallel template) |
73
- | `lib/installer.js` | MODIFY — profile-conditional stamping for `.roomodes`, `AGENTS.md`, `.clinerules` per-tool extensions |
74
- | `lib/merge/roomodes.js` | MODIFY — accept profile arg, compose `customInstructions` accordingly |
75
- | `test/onprem-guardrails.test.js` | CREATE |
119
+ - **NFR47 (Roo Code application-layer `fileRegex` enforcement)** — `epics.md` lines 4144 (item e), 4152. This story does not touch the `groups`/`fileRegex` structure authored in Story 21.3's template. AC #12 is a non-regression assertion; test 7.11 enforces.
76
120
 
77
- ### Dependencies
121
+ - **NFR18 (additive JSON-merge for OpenCode)** — `epics.md` line 4229. This story changes the content of the single ma-agents-prefixed entry in `opencode.json::instructions[]` but does not add entries, reorder entries, or touch other keys. AC #11 is a non-regression assertion; test 7.10 enforces.
78
122
 
79
- - Stories 21.1 through 21.5 (foundation + per-tool templates) all merged
123
+ ### Verified source-tree surface
80
124
 
81
- ### Reference
125
+ | File | Exists? | Role in this story |
126
+ |------|---------|--------------------|
127
+ | `lib/profile.js` | verified (Story 21.1, lines 29–43) | Read-only — consumed via `getProfile(projectRoot)` through `composeInstructionBlock` call sites |
128
+ | `lib/installer.js` | verified | `composeInstructionBlock` (new in Story 21.2) reads the on-prem template delivered here. Task 2.2 may extend the append branch if Story 21.2 left it as a stub |
129
+ | `lib/agents.js` | verified | Read-only — no changes. Enumerates the agents whose instruction files receive the on-prem-augmented block (Claude Code, Cline × 2, Roo Code rules, Cursor, Kilocode, Copilot, Gemini, OpenCode, Anti-gravity) |
130
+ | `lib/templates/instruction-block-universal.template.md` | **new (Story 21.2)** | Authored in Story 21.2; unchanged here |
131
+ | `lib/templates/instruction-block-onprem.template.md` | **new (this story)** | Authored here per AC #1 |
132
+ | `lib/templates/roomodes.template.yaml` | **new (Story 21.3)** | Unchanged here — on-prem content reaches `.roomodes` transparently via `{{UNIVERSAL_BLOCK}}` |
133
+ | `lib/templates/agents-md.template.md` | **new (Story 21.4)** | Unchanged by this story (Shape A committed — AC #7 resolution) |
134
+ | `lib/templates/clinerules.template.md` | **new (Story 21.5)** | Unchanged here |
135
+ | `lib/merge/roomodes.js` | **new (Story 21.3)** | Unchanged — merger treats `customInstructions` as opaque string |
136
+ | `test/onprem-injection.test.js` | verified (exists — Story 21.9's target expansion surface) | This story ADDS a sibling file `test/onprem-layer.test.js` (new) to avoid collision with Story 21.9's planned expansion |
137
+ | `test/profile.test.js` | verified (Story 21.1) | Reference for test framework and temp-dir isolation pattern |
82
138
 
83
- Source playbook:
84
- - `/no_think` directive: Section 6.1, 6.8
85
- - `str_replace_editor` warning: Section 2.5, 6.4
86
- - No-home-dir rule: Section 1, 5.3
87
- - Per-phase reasoning/sampling: Section 6.1, 8 (Sampling Parameters by Phase table)
139
+ ### Composition pattern (end state after Story 21.6 lands)
140
+
141
+ ```
142
+ composeInstructionBlock({ profile, manifestPath })
143
+ ├── universal template (Story 21.2 always applied; {{MANIFEST_PATH}} substituted)
144
+ └── on-prem template (this story — applied when profile === 'on-prem'; no placeholders)
145
+
146
+ Consumed by:
147
+ ├── updateAgentInstructions markdown branch (Story 21.2 AC #4): Claude Code, Cline ×2, Roo Code rules, Cursor, Kilocode, Copilot, Gemini, Anti-gravity
148
+ ├── updateAgentInstructions json-merge branch (Story 21.2 AC #8): OpenCode opencode.json::instructions[]
149
+ ├── applyExtraInstructionTemplates yaml-customModes merger (Story 21.3): .roomodes customInstructions per mode
150
+ └── applyExtraInstructionTemplates markdown-markers merger (Story 21.4): AGENTS.md marker block
151
+ ```
152
+
153
+ ### Injection-site map (for reviewers)
154
+
155
+ On-prem content reaches every injection site TRANSPARENTLY through `composeInstructionBlock`. The only code change required (if Story 21.2 left the append branch as a stub) is activating the branch — Task 2.2. No call site is modified. No new function is introduced.
156
+
157
+ ### Library and pattern references
158
+
159
+ - **Template discovery**: `lib/installer.js` loads `lib/templates/*.template.md` via synchronous `fs.readFileSync` (Story 21.2 Task 2.2). No change here.
160
+ - **Profile resolution**: `getProfile(projectRoot)` from `lib/profile.js` (Story 21.1 verified lines 29–43). No change here.
161
+ - **Markers**: `<!-- MA-AGENTS-START -->` / `<!-- MA-AGENTS-END -->` — defined and used by `updateAgentInstructions`. No change here.
162
+ - **Atomic writes**: the atomic-write pattern from Story 21.3 (`lib/installer.js` lines ~395–398, temp-file + rename) is inherited by `applyExtraInstructionTemplates`. No change here.
163
+
164
+ ### Field playbook reference
165
+
166
+ The on-prem rules originate from `optimizing-local-llm-coding-agents-bmad.md` (referenced in epic intro at `_bmad-output/planning-artifacts/epics.md` lines 3886–3895). Four concrete failure modes documented there map 1:1 to AC #1's four rule categories:
167
+ - Nemotron hallucinating `str_replace_editor` → AC #1 no-str_replace_editor rule.
168
+ - Local LLMs dumping output into `~/.claude/` → AC #1 no-home-dir-writes rule.
169
+ - Local LLMs overthinking planning prompts with reasoning mode on by default → AC #1 `/no_think` + reasoning-OFF planning guidance.
170
+ - Local LLMs skipping BMAD planning to start coding → addressed by the UNIVERSAL block's BMAD phase discipline rule (Story 21.2); the on-prem layer adds the sampling-per-phase guidance.
88
171
 
89
172
  ### Out of Scope
90
173
 
91
- - BMAD persona phase prefix in customize-loader (Story 21.7 — separate concern, edits `.customize.yaml` files)
92
- - vLLM serving doc (Story 21.8)
174
+ - Universal template content (Story 21.2).
175
+ - `.roomodes` template authorship and the `yaml-customModes` merger (Story 21.3).
176
+ - `AGENTS.md` template authorship (Story 21.4).
177
+ - `.clinerules` template authorship (Story 21.5).
178
+ - BMAD persona phase prefix via `lib/bmad-customize/*.customize.yaml` `on_prem_phase_prefix` field (Story 21.7 — separate composition surface, NOT `composeInstructionBlock`).
179
+ - vLLM deployment doc and README on-prem section (Story 21.8).
180
+ - Cross-tool NFR44/NFR46 integration tests (Story 21.9; this story's tests are scoped to the on-prem layer's content presence/absence).
181
+ - Profile-reconfigure flow covering profile-flip drift (Story 21.10).
182
+ - Profile-uninstall removal of on-prem marker content (Story 21.11).
183
+ - Bumping `manifestVersion` — already at `1.2.0` (Story 21.1).
184
+ - Installing or managing the inference server (vLLM) — documentation-only surface, see epic intro line 3895.
185
+
186
+ ## Dependencies
187
+
188
+ ### Upstream (blocking)
189
+
190
+ - **Story 21.1 (done)** — `lib/profile.js::getProfile(projectRoot)` consumed transitively via `composeInstructionBlock`. Verified at `lib/profile.js` lines 29–43.
191
+ - **Story 21.2 (Ready)** — `composeInstructionBlock({ profile, manifestPath })` in `lib/installer.js` is the sole consumer of the on-prem template. Story 21.2 AC #3 pre-specifies the append branch ("When `profile === 'on-prem'` AND `lib/templates/instruction-block-onprem.template.md` (new, ships in Story 21.6) exists, appends that file's content after the universal content, separated by one blank line."). This story ships the template file; Task 2.2 activates the branch if it was left as a stub.
192
+ - **Story 21.3 (Ready)** — `.roomodes` template consumes `{{UNIVERSAL_BLOCK}}` via `applyExtraInstructionTemplates`. On-prem content reaches per-mode `customInstructions` transparently.
193
+ - **Story 21.4 (Ready)** — `AGENTS.md` template consumes `{{UNIVERSAL_BLOCK}}` via `markdown-markers` merger. Shape A is committed (AC #7 resolution), so 21.6 reuses 21.4's template unchanged.
194
+ - **Story 21.5 (Ready)** — Cline's two files consume `composeInstructionBlock` via `updateAgentInstructions`'s existing markdown branch; on-prem content flows in transparently.
195
+
196
+ ### Downstream (consumers of this story's surface)
197
+
198
+ - **Story 21.7** — BMAD persona phase prefix via customize-loader. Independent composition surface; does NOT consume `composeInstructionBlock`. Cross-reference only — an on-prem install activates both 21.6 (per-tool files) and 21.7 (persona system-prompt prefix).
199
+ - **Story 21.8** — vLLM deployment doc + README on-prem section. Documents the same `/no_think` directive that AC #1 stamps into the per-tool templates; NOT an implementation dependency, but content drift between the on-prem template here and the vLLM doc would confuse users. Recommend a test assertion that both sources mention `/no_think` and `str_replace_editor` verbatim; wording can diverge but token presence must be consistent.
200
+ - **Story 21.9** — Integration tests verifying (a) standard profile produces NO on-prem-specific strings across every injection surface, (b) on-prem profile produces BOTH layers, (c) idempotency across two on-prem installs, (d) `.roomodes` slug-collision behavior is profile-independent. Tests 7.3–7.9 in this story's Testing section are unit/integration-level subsets; Story 21.9 adds the cross-tool integration coverage.
201
+ - **Story 21.10 (Profile Reconfigure)** — Re-stamps profile-dependent artifacts when profile flips. Reuses `composeInstructionBlock`; the drift-detection path from Story 21.2 AC #10 handles the standard↔on-prem content diff.
202
+ - **Story 21.11 (Profile Uninstall)** — Removes marker-block content (including on-prem layer) on `uninstall --profile-artifacts`. No special handling for on-prem content — marker removal is layer-agnostic.
203
+
204
+ ## Testing
205
+
206
+ **Framework and isolation:** Match the pattern in `test/profile.test.js` (Story 21.1) and `test/opencode-json-merge.test.js` (Epic 9) — node's built-in test runner, `os.tmpdir()` + `fs.mkdtempSync` per-test temporary project roots, never mutate the repo's own `.ma-agents.json`, `lib/templates/`, or `opencode.json`. Use the real `lib/profile.js` (`setProfile(tmpRoot, 'standard' | 'on-prem')`) against the tmp project root. Do NOT stub `lib/installer.js` or `lib/agents.js` — use the real modules to catch integration bugs.
207
+
208
+ New test file: `test/onprem-layer.test.js` (new) — avoids collision with `test/onprem-injection.test.js` which Story 21.9 will expand.
209
+
210
+ **Unit tests:**
211
+
212
+ - 7.1 Template file exists at `lib/templates/instruction-block-onprem.template.md` and contains the four AC #1 content categories (one test per category: `/no_think` directive present; `~/.claude/` no-home-writes rule present; `str_replace_editor` no-reference rule present; reasoning-mode per-phase guidance present).
213
+ - 7.2 Template contains NO placeholders (`{{`) — AC #3 defensive.
214
+ - 7.3 NFR44 — standard profile: `composeInstructionBlock({ profile: 'standard', manifestPath })` output does NOT contain `/no_think`, `str_replace_editor`, or `~/.claude/`. Assert each of the three strings is absent via direct `.includes()` check.
215
+ - 7.4 On-prem profile: `composeInstructionBlock({ profile: 'on-prem', manifestPath })` output contains all three strings (`/no_think`, `str_replace_editor`, `~/.claude/`).
216
+ - 7.5 Composer output structure: on-prem output equals universal output + exactly one blank line + on-prem template content. Verified by string parsing, not regex.
217
+ - 7.6 Idempotency: calling `composeInstructionBlock({ profile: 'on-prem', manifestPath: X })` twice returns byte-identical strings.
218
+
219
+ **Integration tests (within this story — cross-tool end-to-end lives in Story 21.9):**
220
+
221
+ - 7.7 Fresh standard-profile install with all injection-surface agents selected (Claude Code, Cline, Roo Code, OpenCode, Cursor, Kilocode, Copilot, Gemini): for EVERY generated file in the tmp project, assert the three NFR44 forbidden strings (`/no_think`, `str_replace_editor`, `~/.claude/`) are absent. Files checked: `.claude/CLAUDE.md`, `.cline/clinerules.md`, `.clinerules`, `.roo/rules/00-ma-agents.md`, `.roomodes` (all four modes' `customInstructions`), the OpenCode `opencode.json::instructions[]` ma-agents-prefixed entry, `.cursor/cursor.md`, `.kilocode/kilocode.md`, `.github/copilot/copilot.md`, `.gemini/gemini.md`. Note: AGENTS.md's legitimate single `~/.claude/` occurrence (Story 21.4 AC #9 exception) is excluded from this assertion — tested separately in 7.12.
222
+ - 7.8 Fresh on-prem-profile install with same agent set: for EVERY file above, assert all three strings ARE present (NFR44 positive case). Specifically: `/no_think` appears in each of the four `.roomodes` `customInstructions` blocks (AC #6).
223
+ - 7.9 Two consecutive on-prem installs produce byte-identical content across all files above (AC #10 — NFR46).
224
+ - 7.10 NFR18 non-regression: on-prem install's `opencode.json` has `instructions[]` length equal to `N + M` where N is the user's pre-existing entries and M is the ma-agents-contributed entries (1 ma-agents-prefixed + 1 `"AGENTS.md"` if OpenCode is selected). Key count at the top level of `opencode.json` is unchanged versus a standard install.
225
+ - 7.11 NFR47 non-regression: after an on-prem install, the four `.roomodes` modes' `fileRegex` patterns are byte-identical to the same modes' patterns in a standard install (content diff is only in `customInstructions`). AC #12 contract.
226
+ - 7.12 Shape A verification (AC #7 resolution): after an on-prem install, AGENTS.md contains `/no_think` somewhere inside the marker region. Exact positional assertion (Universal Rules vs Critical Behavior Rules section) is NOT tested — Shape A delivers content via the Universal Rules section and that location is not part of the contract.
227
+
228
+ **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). Use real `lib/templates/` on disk — tests must NOT stub the template files so that AC #1's template-authorship contract is exercised end-to-end.
229
+
230
+ **Coverage note:** Story 21.9 adds (a) a standard-profile vs. pre-Epic-21 baseline byte-comparison test (covers NFR44 across the whole artifact, not just the three literal strings), (b) on-prem profile must-have-both-layers integration across every tool at once, (c) profile-flip drift detection via Story 21.10. Do NOT duplicate those here — keep this story's tests scoped to the on-prem layer's content presence/absence and the composer's on-prem branch wiring.
231
+
232
+ ## Out of Scope
233
+
234
+ (See Dev Notes → Out of Scope above — summarized for quick reference.)
235
+
236
+ - Universal block content (Story 21.2).
237
+ - `.roomodes` template authorship and the `yaml-customModes` merger (Story 21.3).
238
+ - `AGENTS.md` template authorship (Story 21.4).
239
+ - `.clinerules` template authorship (Story 21.5).
240
+ - BMAD persona phase prefix (Story 21.7).
241
+ - vLLM deployment documentation and README on-prem section (Story 21.8).
242
+ - Cross-tool NFR44/NFR46 integration tests (Story 21.9).
243
+ - Profile-reconfigure flow (Story 21.10).
244
+ - Profile-uninstall flow (Story 21.11).
93
245
 
94
246
  ## Dev Agent Record
95
247
 
96
248
  ### Agent Model Used
97
- _(to be filled by dev agent)_
98
-
99
- ### Debug Log References
100
- _(to be filled)_
249
+ Claude Opus 4.6 (1M context) bmad-dev-story + adversarial review.
101
250
 
102
251
  ### Completion Notes List
103
- _(to be filled)_
252
+ - Shipped `lib/templates/instruction-block-onprem.template.md` (90 lines) containing the four AC #1 content categories: `/no_think` planning-phase directive, no-home-dir-writes rule referencing `~/.claude/`, no-`str_replace_editor` rule, per-phase reasoning+sampling guidance (planning/implementation/review).
253
+ - Template has no `{{...}}` placeholders (AC #3), ends with exactly one trailing newline, has no trailing whitespace on any line (AC #1.4 idempotency precondition).
254
+ - Composer (Story 21.2, `lib/installer.js`) already implemented the on-prem append branch. Verified by Task 2 inspection — no 21.2 blocker raised. The on-prem template now flows transparently through every injection surface: markdown-marker merger (Claude Code, Cline ×2, Roo Code rules, Cursor, Kilocode, Copilot, Gemini, Antigravity), json-merge merger (OpenCode `opencode.json::instructions[]`), yaml-customModes merger (`.roomodes`), and markdown-markers merger (AGENTS.md).
255
+ - Fresh on-prem install confirmed to render all three NFR44 literals (`/no_think`, `str_replace_editor`, `~/.claude/`) into every stamped injection surface (test 7.8 passes across 11+ files).
256
+ - Fresh standard-profile install confirmed to render NONE of the three literals into any injection surface except AGENTS.md's legitimate Critical Behavior Rules `~/.claude/` occurrence (AC #4 exception, verified by excluding that surface from the universal-rule assertion).
257
+ - NFR46 idempotency: two consecutive on-prem installs produce byte-identical marker-block content across `.claude/CLAUDE.md`, `.cline/clinerules.md`, `.clinerules`, `.roo/rules/00-ma-agents.md`, `AGENTS.md`, and byte-identical `.roomodes` YAML (test 7.9).
258
+ - NFR18 non-regression: on-prem install's `opencode.json` has exactly one `[ma-agents]`-prefixed entry, user entries preserved, `otherKey` untouched, `instructions[]` length stable across re-installs (test 7.10).
259
+ - NFR47 non-regression: `.roomodes` `fileRegex` patterns byte-identical between standard and on-prem profiles (test 7.11).
260
+ - Obsolete precondition fixed: `test/clinerules.test.js` test 5.7a was written assuming the on-prem template is absent (pre-21.6 state). Updated to temporarily rename the template to simulate absence (mirrors `test/instruction-block.test.js` test 5.6 pattern).
261
+
262
+ ### Adversarial Review Findings
263
+
264
+ | # | Layer | Severity | Finding | Disposition |
265
+ |---|-------|----------|---------|-------------|
266
+ | 1 | Cynical | P1 | Tests 7.7/7.8 skip missing surfaces via `continue` — a regression dropping every rendered surface would pass vacuously | **Fixed** — added sanity guards asserting `.claude/CLAUDE.md` (both), `.roomodes` + `AGENTS.md` (on-prem only) exist before the content loop |
267
+ | 2 | Cynical | P1 | `/no_think` substring match would also match e.g. `//no_think` in comments | **Accepted** — template writes the token on its own line; no other ma-agents-owned template contains any `//no_think`-adjacent literal |
268
+ | 3 | Edge-case | P1 | NFR47 regex parse `/fileRegex:\s*([^\n]+)/g` would double-count if a future on-prem rule mentioned "fileRegex" | **Accepted** — current on-prem template contains no "fileRegex" literal; future rules added via follow-up stories can extend the parse |
269
+ | 4 | Edge-case | P1 | Test 7.9 re-runs `installAllInjectionSurfaces` — second pass risks drift-detection false-positives | **Accepted** — `composeInstructionBlock` is deterministic (verified by 21.2 test 5.4 + this story's 7.6); drift path not entered on clean re-install (21.2 test 5.10a confirms) |
270
+ | 5 | Cynical | P2 | Template uses backtick inline code spans — markdown renderers in agent UIs may strip them | **Accepted** — cosmetic; the three literals are also written in plain prose outside code spans, and tests assert literal substring presence (renderer-independent) |
271
+ | 6 | Edge-case | P2 | `.roomodes` drift via `reconfigure` flow (profile flip) could surface 21.2 AC #10 warning on standard→on-prem | **Out of scope per spec** — AC #13 explicitly defers profile-flip to Story 21.10 |
272
+
273
+ All P0/P1 findings resolved or accepted with documented rationale.
104
274
 
105
275
  ### File List
106
- _(to be filled)_
276
+ - CREATED: `lib/templates/instruction-block-onprem.template.md`
277
+ - CREATED: `test/onprem-layer.test.js` (12 tests, all passing)
278
+ - MODIFIED: `test/clinerules.test.js` (test 5.7a updated — simulate on-prem template absence via rename)
279
+ - MODIFIED: `package.json` (added `test/onprem-layer.test.js` to npm test script)
280
+ - MODIFIED: `_bmad-output/implementation-artifacts/21-6-onprem-layered-guardrails.md` (Status → Review; Dev Agent Record / File List / Change Log updated)
107
281
 
108
282
  ## Change Log
109
- - 2026-04-14: Story created (Epic 21, Story 21.6)
110
- - 2026-04-14: Removed prescriptive `--profile=` flag references from AC #8 and Task 6 subitems (flag retired; profile switch is via Story 21.10 reconfigure command once delivered, or manual `.ma-agents.json` edit until then). Aligned with P0 spec-alignment PR #34.
111
- - 2026-04-14: Added ACs #9 and #10 reconciling on-prem home-dir rule with `--scope global` (Finding #8, corrective plan step 3). Rule narrowed to forbid ad-hoc writes only; installer's scoped writes authorized. Informational log pinned verbatim for test assertion.
112
- - 2026-04-14: Added AC #11 forbidding numerical temperature/top_p values in the on-prem instruction block (Finding #12-a, corrective plan step 3). Narrows AC #1's "sampling guidance" bullet qualitative text allowed, specific numbers delegated to Story 21.8's vLLM reference doc. `/no_think` (prompt-effective) is retained.
283
+
284
+ - 2026-04-15: Story 21.6 implemented on-prem template shipped, all 12 story-local tests green (onprem-layer.test.js), full `npm test` green (no regressions). Composer wiring from 21.2 reused unchanged no blocker raised. Status Ready Review.
285
+ - 2026-04-15 (epic-owner resolution): All three open questions resolved inline. **AC #7 Shape A committed** — on-prem content reaches AGENTS.md via the Universal Rules section transparently through the composer; Critical Behavior Rules section unchanged by this story. Shape B rejected (would contradict Decision A). **AC #1 `/no_think` Shape X committed** — 21.6 owns the instruction-text rule; 21.7 owns the active persona-prompt prefix. **AC #1 field-playbook** — scoped to four categories only; additional rules become follow-up stories. Task 2.2 and Task 4 made unconditional. Status flipped Draft → Ready. No upstream blockers remain.
286
+ - 2026-04-15: Adversarial-review reconciliation. AC #3 reconciled with Story 21.4 via pointer sentence: composer (21.2) appends template verbatim; project-value stamping happens AFTER composition in `lib/installer.js` (closes P0 #2). AC #4 annotated with NFR44 scope-narrowing pointer. Added Dev Notes "Ownership boundary" block clarifying composer/merger/stamper terminology and that 21.6 owns only the on-prem template file and its profile-gated content. Added Dev Notes "NFR44 test scope" block (decision D): three literals exhaustive for negative assertion; reasoning-mode prose positive-only. Task 2.2 downgraded from "extend composer here" to "raise blocker against 21.2"; Task 4.2 downgraded analogously for 21.4. Status flipped to Draft: Task 2.2 and Task 4.2 remain conditional on upstream decisions, and three Open questions remain unresolved. Upstream composer dependency cites Story 21.2.
287
+ - 2026-04-15: Story created (Epic 21, Story 21.6). ACs structured with explicit (gap-fill) flags per the epic's AC scope. Three Open questions raised inline (AGENTS.md anchor Shape A vs B, field-playbook additional rules, `/no_think` delivery Shape X vs Y) rather than guessed. NFR44/NFR46/NFR47/NFR18 citations made explicit in Dev Notes. Verified source-tree surface table added distinguishing existing vs. new paths. Upstream/downstream dependencies split. Testing section covers unit (7.1–7.6) and integration (7.7–7.12) with explicit NFR44 negative-assertion coverage against `/no_think`, `str_replace_editor`, `~/.claude/` across every injection surface. Status set to Ready.