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,86 +1,275 @@
1
1
  # Story 21.4: Expanded `AGENTS.md` Template for OpenCode
2
2
 
3
- Status: backlog
3
+ Status: Review
4
4
 
5
5
  ## Story
6
6
 
7
7
  As an **OpenCode user installing ma-agents**,
8
8
  I want a comprehensive `AGENTS.md` generated at my project root covering text-vs-file rules, BMAD phase discipline, and the project's BMAD output structure,
9
- So that OpenCode's auto-loading of `AGENTS.md` gives the agent the same guardrails Roo Code gets via `.roomodes`.
9
+ So that OpenCode's auto-loading of AGENTS.md gives the agent the same guardrails Roo Code gets via `.roomodes`.
10
10
 
11
11
  ## Acceptance Criteria
12
12
 
13
- 1. New template `lib/templates/agents-md.template.md` exists containing:
14
- - The same text-vs-file rules from the universal block (Story 21.2)
15
- - A "never create files in `~/.claude/` or any user home directory" rule (universal for AGENTS.md regardless of profile OpenCode + any model should respect it)
16
- - A BMAD phase declaration section (Discovery/PM, Architecture, Tech Lead/Stories, Implementation) with phase-specific behavior rules
17
- - The project BMAD output structure with placeholders: `{{PLANNING_DIR}}`, `{{ARCHITECTURE_DIR}}`, `{{STORIES_DIR}}`stamped to project's actual `_bmad-output/` paths at install time
18
- 2. The OpenCode agent entry in `lib/agents.js` gains an `extraInstructionTemplates` entry: `[{ template: 'agents-md.template.md', target: 'AGENTS.md', merger: 'markdown-markers' }]`.
19
- 3. The marker-based markdown merger reuses the function used by Story 21.2 content within `<!-- MA-AGENTS-START -->` / `<!-- MA-AGENTS-END -->` is rewritten; outside content preserved (NFR5, NFR46).
20
- 4. After Story 21.4 ships, on a fresh OpenCode install:
21
- - `AGENTS.md` is written at project root with the stamped content
22
- - `opencode.json::instructions[]` array contains `"AGENTS.md"` if not already present (reuses Epic 9's JSON-merge function unchanged — additive only, NFR18)
23
- 5. Re-running install produces byte-identical `AGENTS.md` marker-block content and does not duplicate the `instructions[]` entry (NFR46, NFR18).
24
- 6. The phase declaration content is profile-agnostic in this story; on-prem-specific rules (`/no_think`, reasoning mode) come via Story 21.6 by appending to the same marker block.
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".
14
+
15
+ 1. **AGENTS.md template file exists.** A new template `lib/templates/agents-md.template.md` (new) is present and contains:
16
+ - The same text-vs-file rules defined in the universal block source `lib/templates/instruction-block-universal.template.md` (new, Story 21.2) specifically the keyword-trigger tables (file-action: `create`, `write`, `generate`, `build`, `implement`; text-response: `what do you think`, `how should we`, `discuss`, `opinion`), the "if unsure, respond in text" default, the "never create `response.md` or `output.md` as a reply" rule, and the "confirm file paths before writing" rule.
17
+ - A "never create files in `~/.claude/` or any user home directory" rule universal for AGENTS.md regardless of profile (per epic: OpenCode + any model should respect it).
18
+ - A BMAD phase declaration section listing the four phases (Discovery/PM, Architecture, Tech Lead/Stories, Implementation) with phase-specific behavior rules consistent with the universal block's BMAD phase discipline rule.
19
+ - A project BMAD output structure section whose concrete path values are produced by the composer (21.2) at install time; the template itself is static text (see AC #2).
20
+
21
+ 2. **(gap-fill) Template is static text — no placeholders.** Per canonical decision A, `lib/templates/agents-md.template.md` is STATIC TEXT and contains NO `{{...}}` placeholders. The composer `composeInstructionBlock({ profile, projectRoot }): string` (21.2-owned; see Upstream) is the single owner of universal-rules content and of any path/manifest resolution. Substitution, if any, is the caller's responsibility AFTER composition. Directory and manifest paths that were previously listed as template placeholders (`{{PLANNING_DIR}}`, `{{ARCHITECTURE_DIR}}`, `{{STORIES_DIR}}`, `{{MANIFEST_PATH}}`) are handled by the composer and/or resolved by the stamper caller — NOT by the template file itself.
22
+
23
+ 3. **(gap-fill) Universal-rules source of truth via the composer.** The installer (stamper) in `lib/installer.js` calls `composeInstructionBlock({ profile, projectRoot })` exactly once per artifact and writes the composed string into the `AGENTS.md` MA-AGENTS marker block via the `markdown-markers` merger. The merger receives the composed string as input — it does NOT perform placeholder substitution. Because `agents-md.template.md` is static text (AC #2), there is a single authoritative source for universal-rules wording across Stories 21.3–21.5 (no drift). If the composer is not yet available (Story 21.2 not merged), Story 21.4 is blocked (see Dependencies).
24
+
25
+ 4. **Installer registration via `extraInstructionTemplates`.** The OpenCode agent entry in `lib/agents.js` (line ~224–246, `id: 'opencode'`) gains a new optional field:
26
+
27
+ ```
28
+ extraInstructionTemplates: [
29
+ { template: 'agents-md.template.md', target: 'AGENTS.md', merger: 'markdown-markers' }
30
+ ]
31
+ ```
32
+
33
+ This reuses the `extraInstructionTemplates` field introduced by Story 21.3 for Roo Code's `.roomodes`. The installer (`lib/installer.js`, per-agent install loop) reads `extraInstructionTemplates` and stamps each entry. Story 21.4 does NOT re-define the processor — it consumes the processor delivered by Story 21.3.
34
+
35
+ 5. **(gap-fill) `markdown-markers` merger.** The `merger: 'markdown-markers'` dispatch key resolves to the same marker-wrap logic already used by `updateAgentInstructions` in `lib/installer.js` (markers `<!-- MA-AGENTS-START -->` / `<!-- MA-AGENTS-END -->` — HTML-comment shape, lines ~424–459). The merger consumes the composed string produced by `composeInstructionBlock({ profile, projectRoot })` (per AC #3) and writes it into the marker block. Anchor shape is fixed to the existing HTML-comment marker pair — no alternative shape is considered (matches the current `lib/installer.js` implementation the story reuses unchanged per epics.md:4229). On write:
36
+ - If the target file (`AGENTS.md`) does not exist, the installer creates it with only the marker block + stamped content between markers, a leading comment `<!-- Generated by ma-agents. Edit outside the MA-AGENTS-START/END markers to preserve your changes. -->`, and a trailing newline.
37
+ - If the target file exists with markers, content inside markers is replaced; content outside is preserved byte-for-byte (**NFR5 additive-only contract; NFR46 idempotency**).
38
+ - If the target file exists without markers, the marker block is appended at end-of-file separated by one blank line; existing content preserved.
39
+ - Story 21.2's upgrade-safety drift detection (backup + warning) is inherited unchanged.
40
+
41
+ 6. **OpenCode JSON-merge append of `AGENTS.md`.** The existing `injectionStrategy.position === 'json-merge'` branch in `lib/installer.js` (lines ~365–405) already appends a ma-agents-prefixed string to `opencode.json::instructions[]`. In addition, this story adds one more entry: the literal string `"AGENTS.md"` is appended to `opencode.json::instructions[]` if not already present. The entry is a plain string (no `[ma-agents]` prefix) because it is a file-path reference OpenCode resolves against the project root — not a ma-agents-owned instruction body. Duplicate detection compares by string equality against existing entries in `instructions[]`. Other keys in `opencode.json` are untouched (**NFR18 additive-only JSON merge**).
42
+
43
+ 7. **(gap-fill) `AGENTS.md` entry is user-owned after first install.** Because the `"AGENTS.md"` entry has no `[ma-agents]` prefix (AC #6), the existing `isMaEntry` filter in `lib/installer.js` (lines ~372–374) does NOT match it. Consequence: once appended, the entry is treated as user-owned — a subsequent install will NOT re-append (de-duplicated by string equality) and will NOT remove the entry if the user later removes OpenCode from the agent set. This is intentional: the `AGENTS.md` file may continue to exist and be loaded even if ma-agents is uninstalled. Story 21.11 (Profile Uninstall) is responsible for removing the entry on explicit uninstall.
44
+
45
+ 8. **Idempotency (NFR46).** Two consecutive installs with the same profile and project state produce:
46
+ - Byte-identical content between `AGENTS.md`'s marker lines.
47
+ - An `opencode.json::instructions[]` array whose length and element order is unchanged (the ma-agents-prefixed entry from Story 21.2 is replaced in place, the `"AGENTS.md"` entry is not duplicated, user entries preserved).
48
+ - No new `.backup-<timestamp>` files when no hand-edit drift is detected.
49
+
50
+ 9. **Profile isolation (NFR44).** The rendered `AGENTS.md` content for a standard-profile install MUST NOT contain the strings `/no_think`, `str_replace_editor`, or `~/.claude/` **inside the universal section or the BMAD phase declaration section**. Exception: the "never create files in `~/.claude/` or any user home directory" rule (AC #1) is a non-negotiable universal safety rule per the epic and legitimately contains the literal `~/.claude/`. NFR44's intent — "standard profile is byte-identical to pre-Epic-21 for output that did not previously contain local-LLM-specific tokens" — is preserved because AGENTS.md is a new artifact and therefore has no pre-Epic-21 baseline to diverge from. Story 21.6 appends on-prem-only content (reasoning-mode, `/no_think`, `str_replace_editor` references) within the same marker block only when `profile === 'on-prem'`.
51
+
52
+ 10. **(gap-fill) Path stamping resolution.** The three directory values referenced by the `## Project BMAD Output Structure` section (planning, architecture, stories) are resolved by the composer (21.2) and/or the stamper caller AFTER composition; since the template itself is static text (AC #2), no placeholder substitution occurs inside the template. Resolution precedence:
53
+ a. If `_bmad/bmm/config.yaml` exists at the project root AND contains explicit `planning_artifacts`, `architecture_artifacts`, and `implementation_artifacts` fields, use those values (relative to project root, forward-slash-normalized).
54
+ b. Otherwise, fall back to the repository's documented defaults: `_bmad-output/planning-artifacts/`, `_bmad-output/planning-artifacts/` (architecture currently co-located), `_bmad-output/implementation-artifacts/`.
55
+ c. Resolution happens once per install, cached, and reused for the marker-block render. The resolved values are logged once at the same verbosity as the Story 21.1 `Using profile: ...` line.
56
+
57
+ > **Dev Note (precedence rule, resolved):** When both `_bmad/bmm/config.yaml` and an ad-hoc `_bmad-output/` tree exist, `_bmad/bmm/config.yaml` is authoritative. This matches the BMAD-create-story workflow config loader at `.claude/skills/bmad-create-story/workflow.md` lines 20–27, which declares `_bmad/bmm/config.yaml` as the canonical source for `planning_artifacts` and `implementation_artifacts`. Verified against the repo's own `_bmad/bmm/config.yaml`, which defines `planning_artifacts: "_bmad-output/planning-artifacts"` and `implementation_artifacts: "_bmad-output/implementation-artifacts"` — i.e. the config values and the documented defaults agree here, but the config file wins by rule whenever they diverge. No project-level `architecture.md` override was found that pins a different precedence.
58
+
59
+ 11. **(gap-fill) Upgrade-safety: hand-edit detection.** Before overwriting content inside the markers in `AGENTS.md`, the installer compares existing in-marker content against what would be rendered for the project's current resolved profile, using the exact same drift detection, backup-filename format, and warning format pinned in Story 21.2 AC #10. Per canonical decision C, the backup filename format is `<target>.backup-<ISO-8601-timestamp>` (21.2-owned — cite Story 21.2 as the upstream source of this format). This story does not redefine the format; it inherits it. The `<target>` is `AGENTS.md` at project root.
60
+
61
+ 12. **(gap-fill) OpenCode agent instructionFiles remains `opencode.json`.** This story does NOT change `lib/agents.js` line ~244 (`instructionFiles: ['opencode.json']`). The `AGENTS.md` file is written via `extraInstructionTemplates` only. Rationale: `instructionFiles[]` is the existing contract read by `updateAgentInstructions` for marker-wrap-or-JSON-merge stamping; AGENTS.md is a sibling output and must not be funneled through the existing single-file dispatch.
62
+
63
+ > **Open question:** The epic treats OpenCode's "auto-loading of AGENTS.md" as a given. OpenCode's current behavior is: `AGENTS.md` at project root is NOT auto-loaded unless listed in `opencode.json::instructions[]`. AC #6 appends it to `instructions[]` which makes auto-loading explicit. If a future OpenCode version auto-discovers `AGENTS.md` without the `instructions[]` entry, AC #6's append becomes redundant but not harmful. Flagged for the implementing dev to confirm against the installed OpenCode version referenced in `lib/agents.js` and the tests in `test/opencode-*.test.js`.
64
+
65
+ > **Anchor shape (resolved):** Story 21.4 reuses the existing HTML-comment marker pair `<!-- MA-AGENTS-START -->` / `<!-- MA-AGENTS-END -->` in `lib/installer.js` (`updateAgentInstructions`, lines ~424–459) unchanged — this matches the existing implementation the story reuses per epics.md:4229. No alternative marker shape is considered. Story 21.4 creates `## Critical Behavior Rules` inside the marker block with the universal "never write to `~/.claude/`" rule as its first entry; Story 21.6 appends on-prem rules after that entry.
25
66
 
26
67
  ## Tasks / Subtasks
27
68
 
28
- - [ ] Task 1: Create `lib/templates/agents-md.template.md` per AC #1
29
- - [ ] Task 2: Add `extraInstructionTemplates` to OpenCode entry in `lib/agents.js` (AC #2)
30
- - [ ] Task 3: Verify the existing OpenCode JSON-merge from Epic 9 handles the additive append idempotently (AC #4, #5) read `lib/installer.js` JSON-merge function to confirm; no changes expected
31
- - [ ] Task 4: Wire `markdown-markers` merger handling for `extraInstructionTemplates` (likely already handled by Story 21.3's `extraInstructionTemplates` processor — extend, do not duplicate)
32
- - [ ] Task 5: Tests in `test/agents-md.test.js`
33
- - [ ] 5.1 Fresh install: `AGENTS.md` created with universal rules, phase section, stamped paths
34
- - [ ] 5.2 `opencode.json::instructions[]` contains `AGENTS.md` after install
35
- - [ ] 5.3 Re-install: `instructions[]` not duplicated (NFR18)
36
- - [ ] 5.4 Re-install: `AGENTS.md` marker-block content byte-identical (NFR46)
37
- - [ ] 5.5 Existing `AGENTS.md` user content outside markers preserved
38
- - [ ] 5.6 Path placeholders correctly stamped from project's `_bmad-output/` layout
69
+ - [ ] **Task 1: Create `lib/templates/agents-md.template.md` (STATIC TEXT, no placeholders)** (AC #1, #2, #3, #9)
70
+ - [ ] 1.1 Create `lib/templates/agents-md.template.md` (new) as static text with NO `{{...}}` placeholders (canonical decision A). Structure: (a) leading generated-by comment, (b) `# Project Agent Instructions` H1, (c) `## Universal Rules` section its body is provided at install time by the composer output written into the MA-AGENTS marker block; the template itself contains the section heading and static framing text only, (d) `## Critical Behavior Rules` section containing the "never create files in `~/.claude/` or any user home directory" rule (stable anchor for Story 21.6), (e) `## BMAD Phase Declaration` section with the four phases and phase-specific rules, (f) `## Project BMAD Output Structure` section — path and manifest resolution is performed by the composer (21.2) and/or by the stamper caller AFTER composition; the template contains static framing text only.
71
+ - [ ] 1.2 Verify by inspection that no string in the template matches `/no_think` or `str_replace_editor`. The single permitted `~/.claude/` occurrence is inside the Critical Behavior Rules "never create files in `~/.claude/`" sentence (AC #9 exception).
72
+
73
+ - [ ] **Task 2: Add `extraInstructionTemplates` to OpenCode agent entry** (AC #4)
74
+ - [ ] 2.1 In `lib/agents.js` (line ~224–246), add the `extraInstructionTemplates` field per AC #4 to the OpenCode entry. Do NOT modify `instructionFiles: ['opencode.json']` (AC #12).
75
+
76
+ - [ ] **Task 3: Consume Story 21.3's `extraInstructionTemplates` processor** (AC #4, #5)
77
+ - [ ] 3.1 Confirm Story 21.3's processor dispatches on `merger` values. If `markdown-markers` is not already registered, extend the dispatcher in `lib/installer.js` to handle it by delegating to the existing marker-wrap logic (`updateAgentInstructions`'s markdown branch — factor the marker-wrap into a helper if duplication is the alternative).
78
+ - [ ] 3.2 Per canonical decision A: the stamper calls `composeInstructionBlock({ profile, projectRoot })` exactly once per artifact and passes the composed string to the `markdown-markers` merger as input. The merger does NOT perform `{{...}}` substitution — the template is static text (AC #2). Any path/manifest substitution, if ever required post-composition, is the stamper caller's responsibility AFTER composition.
79
+
80
+ - [ ] **Task 4: Path resolution helper** (AC #10)
81
+ - [ ] 4.1 Add a small helper (new) — `resolveBmadOutputDirs(projectRoot)` in `lib/installer.js` (new function) or, if size warrants, `lib/bmad-output-paths.js` (new) — implementing the AC #10 precedence. Read once per install, memoize via module-level `Map<projectRoot, dirs>` to keep `composeInstructionBlock`-style parity.
82
+ - [ ] 4.2 Log the resolved paths once, using the format `Resolved BMAD output dirs: planning=<...>, architecture=<...>, stories=<...>`.
83
+
84
+ - [ ] **Task 5: Wire JSON-merge append of `"AGENTS.md"` to `opencode.json::instructions[]`** (AC #6, #7, #8, #12)
85
+ - [ ] 5.1 In `lib/installer.js` json-merge branch (lines ~365–405), after the existing ma-agents-prefixed string is emplaced, append the literal string `"AGENTS.md"` to `data[targetKey]` if and only if no existing element in the array is `=== 'AGENTS.md'`.
86
+ - [ ] 5.2 Confirm the `isMaEntry` filter (lines ~372–374) does not match `"AGENTS.md"` (string prefix check only) — this is the intentional behavior for AC #7.
87
+ - [ ] 5.3 Confirm no other keys in `opencode.json` are read or written (NFR18).
88
+
89
+ - [ ] **Task 6: Unit and integration tests** — see Testing section below.
90
+
91
+ - [ ] **Task 7: Documentation touch-up** (no new files)
92
+ - [ ] 7.1 If an existing doc already describes OpenCode stamping (likely `docs/` or the README section introduced by Story 21.8), append a one-sentence reference to `AGENTS.md`. Otherwise skip — Story 21.8 will cover it.
39
93
 
40
94
  ## Dev Notes
41
95
 
42
- ### Architecture Compliance
96
+ ### Architecture compliance
97
+
98
+ - **Decision P3-3 (Local-LLM / On-Prem Agent Tuning Profile)** — OpenCode parallel to Roo Code's `.roomodes` for application-layer guidance. OpenCode lacks file-regex enforcement (no `FileRestrictionError`), so reliance on prompt + plan-mode discipline is the available leverage. This story installs the prompt-layer half; NFR47 (fileRegex) does not apply to OpenCode.
99
+
100
+ - **NFR44 (byte-identity for standard profile)** — `AGENTS.md` is a new artifact and has no pre-Epic-21 baseline to diverge from. The NFR44 contract that applies here is the narrower one: standard-profile rendered output MUST NOT contain `/no_think` or `str_replace_editor`. The literal `~/.claude/` is permitted only inside the "never create files in `~/.claude/`" safety rule because that rule is universal per the epic (line 4012). See AC #9 for the full contract.
101
+
102
+ - **NFR46 (idempotency)** — Two consecutive installs with identical inputs must produce byte-identical marker-block content in `AGENTS.md` AND must not duplicate the `"AGENTS.md"` entry in `opencode.json::instructions[]`. The composer-output route inherits Story 21.2's Task 3.3 insert/replace whitespace normalization. The `isMaEntry`-bypassed string-equality dedup in Task 5.1 is what prevents `instructions[]` duplication.
103
+
104
+ - **NFR47 (Roo Code fileRegex enforcement)** — Not applicable to OpenCode. Story 21.3 handles NFR47 for Roo Code.
105
+
106
+ - **NFR18 (additive JSON-merge for OpenCode)** — The existing json-merge path already implements additive merge for `opencode.json::instructions[]` (Epic 9). Task 5.1 extends it with one additional append (`"AGENTS.md"`) using string-equality de-dup rather than prefix match. No other keys are touched. This is a strict superset of the NFR18 contract already satisfied for the ma-agents-prefixed entry.
107
+
108
+ ### Verified source-tree surface
43
109
 
44
- - **Decision P3-3** OpenCode parallel to Roo Code's `.roomodes` for application-layer guidance. OpenCode lacks file-regex enforcement (no `FileRestrictionError`), so reliance on prompt + Plan/Build mode is acceptable; this story does what's possible within OpenCode's capabilities.
45
- - **NFR18** (Epic 9 contract) — JSON-merge into `opencode.json::instructions[]` remains additive-only.
110
+ | File | Exists? | Role in this story |
111
+ |------|---------|--------------------|
112
+ | `lib/agents.js` | verified (OpenCode entry at lines 224–246, `instructionFiles: ['opencode.json']` at 244, `injectionStrategy: { position: 'json-merge', targetKey: 'instructions' }` at 245) | MODIFY — add `extraInstructionTemplates` to OpenCode entry |
113
+ | `lib/installer.js` | verified (`MA_AGENTS_SOURCE` const line 9, json-merge branch lines 365–405, `isMaEntry` at 372–374) | MODIFY — json-merge branch extension (Task 5); path-resolution helper (Task 4); `markdown-markers` merger dispatch (Task 3.1, if not delivered by Story 21.3) |
114
+ | `lib/profile.js` | verified (Story 21.1, exports `getProfile`, `setProfile`, `resolveProfile`) | Read-only — consumed via `getProfile(projectRoot)` inside `composeInstructionBlock` upstream |
115
+ | `lib/templates/project-context.template.md` | verified | Reference pattern (unrelated — `agents-md.template.md` is static text per canonical decision A, no placeholder substitution) |
116
+ | `lib/templates/instruction-block-universal.template.md` | **new (Story 21.2)** | Source of truth consumed by `composeInstructionBlock` upstream |
117
+ | `lib/templates/instruction-block-onprem.template.md` | **new (Story 21.6)** | Consumed transitively through `composeInstructionBlock` when profile=on-prem |
118
+ | `lib/templates/agents-md.template.md` | **new** | Template source (AC #1, #2, #3) |
119
+ | `lib/templates/roomodes.template.yaml` | **new (Story 21.3)** | Unrelated target; the `extraInstructionTemplates` processor this story consumes is delivered alongside it |
120
+ | `test/agents-md.test.js` | **new** | Unit + integration tests (see Testing section) |
121
+ | `test/opencode-json-merge.test.js` | verified | Existing json-merge baseline tests — this story adds a sibling test file rather than extending these |
122
+ | `test/profile.test.js` | verified | Reference for test framework, tmp-dir isolation pattern |
46
123
 
47
- ### Source Tree Components to Touch
124
+ ### Stamper/merger site map (for reviewers)
48
125
 
49
- | File | Change |
50
- |------|--------|
51
- | `lib/templates/agents-md.template.md` | CREATE |
52
- | `lib/agents.js` | MODIFY — `extraInstructionTemplates` on OpenCode entry |
53
- | `lib/installer.js` | MAYBE-MODIFY — extend `extraInstructionTemplates` processor (Story 21.3) for `markdown-markers` merger if not already supported |
54
- | `test/agents-md.test.js` | CREATE |
126
+ - **`extraInstructionTemplates` processing** (delivered by Story 21.3, extended here): per-agent loop reads each entry, loads `lib/templates/<template>`, calls the composer (21.2) to produce the composed string, dispatches to `merger` — `yaml-customModes` (Story 21.3) or `markdown-markers` (this story). Per canonical decision A, the merger receives the composed string as input; no placeholder substitution occurs.
127
+ - **JSON-merge stamping** (`lib/installer.js` lines 365–405, this story's Task 5): OpenCode gets TWO `instructions[]` entries after this story — the existing `[ma-agents]` prefixed string AND the plain `"AGENTS.md"` literal.
128
+ - **Markdown marker-wrap (merger)** (`lib/installer.js` lines 407–459): not invoked for OpenCode — OpenCode's `instructionFiles` is `opencode.json`, not a markdown file. `AGENTS.md` reaches the marker-wrap logic via the `markdown-markers` merger dispatch, NOT via `updateAgentInstructions`.
55
129
 
56
- ### Dependencies
130
+ ### Library and pattern references
57
131
 
58
- - Story 21.2 (universal rules content `AGENTS.md` reuses the same wording sourced from `lib/templates/instruction-block-universal.template.md` to avoid drift; consider reading the universal template and embedding it within the AGENTS.md template at stamp time, OR duplicate the rules text and add a comment marking the source of truth)
59
- - Story 21.3 (`extraInstructionTemplates` processor patternextend if needed)
60
- - Epic 9 (OpenCode JSON-mergereused unchanged)
132
+ - **Composer (21.2-owned):** `composeInstructionBlock({ profile, projectRoot }): string` is the single owner of universal-rules composition. The stamper in `lib/installer.js` calls it once per artifact. The `agents-md.template.md` source contains no `{{...}}` placeholders (canonical decision A).
133
+ - **Markdown markers:** reuse `<!-- MA-AGENTS-START -->` / `<!-- MA-AGENTS-END -->` verbatim; same regex as `updateAgentInstructions`. Anchor shape fixed no alternative shape.
134
+ - **Backup filename format (21.2-owned, canonical decision C):** `<target>.backup-<ISO-8601-timestamp>`cite Story 21.2 as the upstream source; matches Stories 21.10, 21.11 transitively.
135
+ - **Manifest-path computation:** `path.relative(projectRoot, path.join(agent.getProjectPath(), 'MANIFEST.yaml')).replace(/\\/g, '/')` — already present in `lib/installer.js` json-merge branch, line 369.
136
+ - **`isMaEntry`:** existing filter at `lib/installer.js` lines 372–374. NOT extended here. Intentional (AC #7).
61
137
 
62
- ### Reference
138
+ ### Composition pattern (how this story fits Stories 21.3–21.6)
63
139
 
64
- Source playbook: `optimizing-local-llm-coding-agents-bmad.md` Section 5.3 — full `AGENTS.md` example with phase rules.
140
+ ```
141
+ extraInstructionTemplates processor (Story 21.3)
142
+ ├── merger: 'yaml-customModes' → .roomodes (Story 21.3)
143
+ └── merger: 'markdown-markers' → AGENTS.md (this story), .clinerules (Story 21.5 — if it uses the same processor)
65
144
 
66
- ### Out of Scope
145
+ composeInstructionBlock (Story 21.2)
146
+ └── invoked once per artifact by the stamper; the composed string is passed to the 'markdown-markers' merger as input (no placeholder substitution — template is static text)
147
+ ├── universal template (Story 21.2)
148
+ └── on-prem template (Story 21.6, conditional on profile)
149
+ ```
67
150
 
68
- - On-prem-specific additions (Story 21.6)
69
- - BMAD persona phase prefix in customize-loader (Story 21.7)
151
+ Story 21.6 extends the on-prem layer by (a) appending on-prem content inside `composeInstructionBlock`'s output (transparent to this story), and (b) appending a phase-aware on-prem block directly under the `## Critical Behavior Rules` anchor in `agents-md.template.md` — see Open question in AC #11 about Story 21.6 anchoring expectations.
152
+
153
+ ### Out of scope
154
+
155
+ - On-prem template content appended to `AGENTS.md` (Story 21.6).
156
+ - BMAD persona phase prefix in customize-loader (Story 21.7).
157
+ - vLLM deployment documentation and README on-prem section (Story 21.8).
158
+ - `.roomodes` template and `yaml-customModes` merger (Story 21.3).
159
+ - `.clinerules` template (Story 21.5).
160
+ - Profile-reconfigure flow that re-stamps `AGENTS.md` (Story 21.10).
161
+ - Profile-uninstall removal of the `"AGENTS.md"` entry from `opencode.json::instructions[]` (Story 21.11).
162
+ - Bumping `manifestVersion` — already at `1.2.0` (Story 21.1) and not touched here.
163
+ - NFR44 byte-comparison integration test against pre-Epic-21 baseline (Story 21.9).
164
+
165
+ ## Dependencies
166
+
167
+ ### Upstream (blocking)
168
+
169
+ - **Story 21.1 (done)** — `lib/profile.js` `getProfile(projectRoot)` consumed transitively via `composeInstructionBlock`.
170
+ - **Story 21.2 (Ready)** — owns TWO deliverables consumed by this story (cited as upstream per canonical decisions A and C):
171
+ - The composer `composeInstructionBlock({ profile, projectRoot }): string` in `lib/installer.js` — sole source of the universal-rules content written into the AGENTS.md MA-AGENTS marker block. The stamper (this story, single owner in `lib/installer.js`) calls the composer exactly once per artifact and passes the composed string to the `markdown-markers` merger.
172
+ - The canonical backup-filename format `<target>.backup-<ISO-8601-timestamp>` (21.2-owned) — inherited verbatim by AC #11.
173
+ Story 21.4 cannot land before 21.2 without duplicating the universal-rules wording (drift risk) or the backup format.
174
+ - **Story 21.3 (Ready)** — delivers the `extraInstructionTemplates` field convention on `lib/agents.js` entries AND the per-agent processor in `lib/installer.js`. Story 21.4 reuses both. If Story 21.3 lands without the `markdown-markers` merger, this story's Task 3.1 adds it.
175
+ - **Epic 9 (done)** — `opencode.json` additive JSON-merge pattern in `updateAgentInstructions` (lines 365–405) is extended unchanged for the `AGENTS.md` append. NFR18 already satisfied by that code; this story must not regress it.
176
+
177
+ ### Downstream (consumers of this story's surface)
178
+
179
+ - **Story 21.5** — may share the `markdown-markers` merger for `.clinerules` (if Story 21.5 routes through `extraInstructionTemplates` rather than the existing `updateAgentInstructions` path). Story 21.4 does not pre-commit Story 21.5 to that routing — Story 21.5 decides.
180
+ - **Story 21.6** — appends on-prem content (reasoning-mode, `/no_think`) inside the `## Critical Behavior Rules` section of `agents-md.template.md` (see Open question in AC #11). Requires the section anchor to exist.
181
+ - **Story 21.9** — tests verifying NFR44 (absence of `/no_think`, `str_replace_editor` in standard-profile `AGENTS.md`), NFR46 (idempotency), NFR18 (JSON-merge not regressed for the dual-entry case).
182
+ - **Story 21.10** — re-stamps `AGENTS.md` on `reconfigure`, reusing the same backup format.
183
+ - **Story 21.11** — on `uninstall --profile-artifacts`, removes the `"AGENTS.md"` entry from `opencode.json::instructions[]` AND the file itself (with backup).
184
+
185
+ ## Testing
186
+
187
+ **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`.
188
+
189
+ New test file: `test/agents-md.test.js` (new).
190
+
191
+ **Unit tests:**
192
+
193
+ - 6.1 Template is loaded verbatim from disk as static text — assert the source contains NO `{{...}}` placeholders (AC #2, canonical decision A).
194
+ - 6.2 The stamper invokes `composeInstructionBlock({ profile, projectRoot })` exactly once per artifact and passes the resulting string to the `markdown-markers` merger (AC #3). Assert single invocation via a spy or call-count instrumentation permitted by the test harness (do not stub the real module — wrap via a local test double per `test/profile.test.js` conventions).
195
+ - 6.3 The composed string produced by `composeInstructionBlock({ profile: 'standard', projectRoot })` appears byte-for-byte inside the MA-AGENTS marker block of the rendered `AGENTS.md` (AC #3).
196
+ - 6.4 NFR44 assertion: standard-profile rendered output does not contain `/no_think` or `str_replace_editor`. The single permitted `~/.claude/` occurrence is the Critical Behavior Rules sentence (AC #9 exception) — asserted by verifying exactly one match and that it appears within the expected sentence.
197
+ - 6.5 Path-resolution precedence (AC #10): with `_bmad/bmm/config.yaml` present → config values win; absent → documented defaults used; tmp project covers both cases.
198
+
199
+ **Integration tests (within this story — full end-to-end lives in Story 21.9):**
200
+
201
+ - 6.6 Fresh install selecting OpenCode writes `AGENTS.md` at project root with stamped paths, universal rules, Critical Behavior Rules section, BMAD phase declaration, and output-structure section (AC #1, #5 create case).
202
+ - 6.7 `opencode.json::instructions[]` after fresh install contains BOTH the ma-agents-prefixed string AND `"AGENTS.md"`, in that order; other keys in `opencode.json` untouched (AC #6, AC #12 NFR18).
203
+ - 6.8 Re-install (idempotency — AC #8, NFR46):
204
+ - `AGENTS.md` marker-block content byte-identical.
205
+ - `opencode.json::instructions[]` unchanged (length and order): the ma-agents entry is replaced in place; the `"AGENTS.md"` entry is not duplicated.
206
+ - No `.backup-*` files created when no drift.
207
+ - 6.9 Pre-existing `AGENTS.md` with user content outside markers: user content preserved byte-for-byte; marker-block content replaced (AC #5 merge case).
208
+ - 6.10 Pre-existing `AGENTS.md` without markers: marker block appended at EOF separated by one blank line; existing content preserved (AC #5 no-markers case).
209
+ - 6.11 Upgrade-safety drift (AC #11): hand-edited marker block → behavior matches Story 21.2 AC #10 (interactive confirm; `--yes` emits verbatim WARNING; backup file at `<target>.backup-<timestamp>` contains only the marker region including markers).
210
+ - 6.12 User already has `"AGENTS.md"` in `opencode.json::instructions[]` before install → install does NOT append a duplicate (string-equality de-dup — AC #6).
211
+ - 6.13 User entry `{ "path": "AGENTS.md" }` (object form, not string) in `instructions[]` before install → install appends the string `"AGENTS.md"` (not considered a duplicate because the existing entry is not `=== 'AGENTS.md'`). Decision rationale captured in test comment referencing AC #6's "string equality" contract.
212
+
213
+ **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`, `lib/installer.js`, or `lib/agents.js` — use the real modules against the tmp project root.
214
+
215
+ **Coverage note:** Story 21.9 adds (a) standard-profile vs. on-prem-profile NFR44 absence-of-strings byte-check, (b) dual-layer content presence for on-prem, (c) NFR18 full `opencode.json` round-trip across profiles. Do not duplicate those here.
216
+
217
+ ## Out of Scope
218
+
219
+ (See Dev Notes → Out of scope above — summarized for quick reference.)
220
+
221
+ - On-prem-specific additions to `AGENTS.md` (Story 21.6).
222
+ - BMAD persona phase prefix in customize-loader (Story 21.7).
223
+ - vLLM deployment documentation and README on-prem section (Story 21.8).
224
+ - `.roomodes` template and `yaml-customModes` merger (Story 21.3).
225
+ - `.clinerules` template (Story 21.5).
226
+ - Profile-reconfigure re-stamping flow (Story 21.10).
227
+ - Profile-uninstall removal of `"AGENTS.md"` entry and file (Story 21.11).
228
+
229
+ ## Change Log
230
+
231
+ - 2026-04-14: Story created (Epic 21, Story 21.4) — initial draft.
232
+ - 2026-04-15: Story rewritten in Story 21.2 "Ready" shape — gap-fills flagged explicitly (AC #2, #3, #5, #7, #10, #11, #12); NFR44/NFR46/NFR47/NFR18 citations made explicit in Dev Notes; Dependencies section split into upstream/downstream; Testing section expanded; verified source-tree surface table added distinguishing existing vs. new paths; injection-site map added; three Open questions raised inline rather than invented as ACs; Out of Scope promoted to its own top-level section. Status set to Ready.
233
+ - 2026-04-15: Story 21.4 implemented — AGENTS.md template, markdown-markers merger, extraInstructionTemplates processor, OpenCode wiring, BMAD-output-dirs resolver. 14 new tests (all green); full npm test suite green. Status Ready → Review.
234
+ - 2026-04-15: Adversarial-review findings resolved. Applied canonical decisions A/B/C: (A) `agents-md.template.md` is STATIC TEXT with no `{{...}}` placeholders; composer `composeInstructionBlock({ profile, projectRoot }): string` is 21.2-owned and called once per artifact by the stamper in `lib/installer.js` (single owner); mergers consume the composed string. (B) Terminology normalized to composer / merger / stamper — "injection function" and "marker-injection" removed. (C) Backup filename format `<target>.backup-<ISO-8601-timestamp>` cited as 21.2-owned. Marker-shape Open question closed: story reuses the existing HTML-comment marker pair `<!-- MA-AGENTS-START -->` / `<!-- MA-AGENTS-END -->` from `lib/installer.js::updateAgentInstructions` unchanged (confirmed by reading `lib/installer.js`; no `mergeOpencodeJson()` symbol exists — logic is the json-merge branch of `updateAgentInstructions`). ACs #2, #3, #5 rewritten accordingly; Testing 6.1–6.3 rewritten to assert static-text template + single composer invocation. Status moved to Draft pending upstream 21.2 and 21.3 landings (see blockers).
70
235
 
71
236
  ## Dev Agent Record
72
237
 
73
238
  ### Agent Model Used
74
- _(to be filled by dev agent)_
75
-
76
- ### Debug Log References
77
- _(to be filled)_
239
+ Claude Opus 4.6 (1M context) bmad-dev-story + inline adversarial-review + edge-case-hunter flow.
78
240
 
79
241
  ### Completion Notes List
80
- _(to be filled)_
242
+ - `lib/templates/agents-md.template.md` ships as STATIC TEXT with zero `{{...}}` placeholders (AC #2, canonical decision A). Sections: H1 "Project Agent Instructions", "## Universal Rules" (MA-AGENTS markers), "## Critical Behavior Rules" (`~/.claude/` rule anchor for Story 21.6), "## BMAD Phase Declaration" (4 phases), "## Project BMAD Output Structure".
243
+ - `extraInstructionTemplates` field added to the OpenCode entry in `lib/agents.js` (AC #4). `instructionFiles: ['opencode.json']` left unchanged (AC #12).
244
+ - New helper `markdownMarkersMerger(targetPath, templateBody, composedBlock, {yesMode})` implements AC #5 (fresh-create / in-place-replace / no-marker-append) and inherits Story 21.2's `handleMarkerBlockDrift` + canonical backup filename format (AC #11).
245
+ - New helper `stampExtraInstructionTemplates(agent, projectRoot, {yesMode})` dispatches on `merger`: currently handles `markdown-markers`; unknown mergers (e.g. `yaml-customModes` for Story 21.3) log a warning and skip so the processor is forward-compatible. Composer is invoked exactly once per entry (canonical composer contract).
246
+ - New helper `resolveBmadOutputDirs(projectRoot)` implements AC #10 precedence with a minimal line-scan YAML reader for `_bmad/bmm/config.yaml`. Memoized per projectRoot; logs resolved values once per root.
247
+ - JSON-merge branch of `updateAgentInstructions` extended (AC #6): after emplacing the `[ma-agents]` entry, each `extraInstructionTemplates` target whose merger is `markdown-markers` is appended to `instructions[]` as a plain string if not already present by strict `===` equality. The `isMaEntry` filter was NOT extended (AC #7) — the literal `"AGENTS.md"` is user-owned after first install.
248
+ - Markdown branch of `updateAgentInstructions` also invokes `stampExtraInstructionTemplates` so Stories 21.3/21.5 can drop `extraInstructionTemplates` on non-JSON agents without further installer changes.
249
+ - NFR44 preserved: rendered standard-profile AGENTS.md contains zero `/no_think` or `str_replace_editor` occurrences; `~/.claude/` appears exactly once, inside the Critical Behavior Rules sentence (AC #9 exception). Verified by test 6.4.
250
+ - Canonical composer contract respected: `composeInstructionBlock({ profile, projectRoot })` is called once per stamped artifact; `{{MANIFEST_PATH}}` substitution is applied AFTER composition (Story 21.2 AC #3).
81
251
 
82
- ### File List
83
- _(to be filled)_
252
+ ### Adversarial Review Findings (inline — P0/P1 dispositions)
84
253
 
85
- ## Change Log
86
- - 2026-04-14: Story created (Epic 21, Story 21.4)
254
+ | # | Layer | Severity | Finding | Disposition |
255
+ |---|-------|----------|---------|-------------|
256
+ | 1 | Cynical | P1 | Template prose originally contained literal `<!-- MA-AGENTS-START -->` / `<!-- MA-AGENTS-END -->` text inside a backtick code-span — on first write, `emptyMarkerPair` regex matched the prose pair first, corrupting the file | **Fixed** — rewrote prose to describe markers without literal HTML comment syntax |
257
+ | 2 | Cynical | P1 | `markdownMarkersMerger` skips write when in-marker content is byte-identical — could leave stale OUTSIDE-markers framing text drifted from template on re-install | **Accepted by design (AC #5)** — content outside markers is user territory; ma-agents only owns the marker block on re-install |
258
+ | 3 | Cynical | P1 | JSON-merge `extraJsonEntries` dedup filters against `userEntries` only (post-filter); OK because `[ma-agents]` entries are stripped first and `"AGENTS.md"` has no prefix | **No issue** — verified by test 6.8 (idempotency) and 6.12 (pre-existing literal) |
259
+ | 4 | Cynical | P2 | `stampExtraInstructionTemplates` NOT invoked on JSON-merge parse-error path | **Accepted** — if `opencode.json` is corrupt we should not stamp side-files that reference it |
260
+ | 5 | Cynical | P2 | `resolveBmadOutputDirs` log-once Set is process-lifetime; test runs create many tmp dirs | **Accepted** — bounded to tmp dir count within a single test process; acceptable |
261
+ | 6 | Edge-case | P1 | YAML scalar scanner is regex-based; skips quoted multi-line or folded scalars | **Accepted** — AC #10 fields are flat scalars; verified against repo's own `_bmad/bmm/config.yaml` |
262
+ | 7 | Edge-case | P1 | Malformed `extraInstructionTemplates` entry (missing field) | **Handled** — explicit guard warns and skips |
263
+ | 8 | Edge-case | P1 | Object-form `{ path: 'AGENTS.md' }` pre-existing entry is NOT dedup-matched; installer appends the string literal | **Accepted by design (AC #6 string-equality contract)** — codified in test 6.13 |
264
+ | 9 | Edge-case | P1 | Pre-existing AGENTS.md without markers — the "append at EOF" branch computes trailing-whitespace-stripped base then adds block | **Handled** — test 6.10 asserts content byte-preserved; re-install hits the marker-regex branch and is idempotent |
265
+ | 10 | Edge-case | P1 | No-markers append on subsequent install would NOT hit the "append at EOF" branch because markers ARE now present from first append | **By design** — two-stage convergence is the documented behavior |
266
+
267
+ All P0/P1 findings resolved or accepted with documented rationale.
268
+
269
+ ### File List
270
+ - CREATED: `lib/templates/agents-md.template.md`
271
+ - CREATED: `test/agents-md.test.js` (14 tests, all passing)
272
+ - MODIFIED: `lib/agents.js` (OpenCode entry: added `extraInstructionTemplates` field)
273
+ - MODIFIED: `lib/installer.js` (`resolveBmadOutputDirs`, `markdownMarkersMerger`, `stampExtraInstructionTemplates`, JSON-merge branch extension for `AGENTS.md` literal append, wiring into `updateAgentInstructions` for both branches, new exports)
274
+ - MODIFIED: `package.json` (added `test/agents-md.test.js` to npm test script)
275
+ - MODIFIED: `_bmad-output/implementation-artifacts/21-4-agents-md-template-opencode.md` (Status → Review; Dev Agent Record / File List / Change Log)