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.
- package/.ma-agents.json +10 -0
- package/AGENTS.md +97 -0
- package/MANIFEST.yaml +3 -0
- package/README.md +17 -0
- package/_bmad-output/implementation-artifacts/21-10-profile-reconfigure.md +30 -6
- package/_bmad-output/implementation-artifacts/21-11-profile-uninstall.md +2 -1
- package/_bmad-output/implementation-artifacts/21-2-universal-instruction-block-expansion.md +217 -62
- package/_bmad-output/implementation-artifacts/21-3-roomodes-template-bmad-modes.md +196 -73
- package/_bmad-output/implementation-artifacts/21-4-agents-md-template-opencode.md +242 -53
- package/_bmad-output/implementation-artifacts/21-5-clinerules-template-extension.md +180 -41
- package/_bmad-output/implementation-artifacts/21-6-onprem-layered-guardrails.md +250 -75
- package/_bmad-output/implementation-artifacts/21-7-bmad-persona-phase-prefix.md +221 -89
- package/_bmad-output/implementation-artifacts/21-8-vllm-reference-doc-readme.md +121 -63
- package/_bmad-output/implementation-artifacts/21-9-tests-validation.md +332 -61
- package/_bmad-output/implementation-artifacts/bug-bmad-recompile-fails-on-airgapped-network.md +112 -0
- package/_bmad-output/implementation-artifacts/sprint-status.yaml +3 -2
- package/bin/cli.js +59 -0
- package/docs/deployment/vllm-nemotron.md +130 -0
- package/lib/agents.js +17 -2
- package/lib/bmad-customize/bmm-analyst.customize.yaml +8 -0
- package/lib/bmad-customize/bmm-architect.customize.yaml +2 -0
- package/lib/bmad-customize/bmm-dev.customize.yaml +2 -0
- package/lib/bmad-customize/bmm-pm.customize.yaml +2 -0
- package/lib/bmad-customize/bmm-qa.customize.yaml +2 -0
- package/lib/bmad-customize/bmm-quick-flow-solo-dev.customize.yaml +8 -0
- package/lib/bmad-customize/bmm-sm.customize.yaml +2 -0
- package/lib/bmad-customize/bmm-tech-writer.customize.yaml +2 -0
- package/lib/bmad-customize/bmm-ux-designer.customize.yaml +2 -0
- package/lib/bmad.js +293 -1
- package/lib/installer.js +617 -43
- package/lib/merge/roomodes.js +125 -0
- package/lib/profile.js +25 -2
- package/lib/reconfigure.js +334 -0
- package/lib/templates/agents-md.template.md +67 -0
- package/lib/templates/clinerules.template.md +13 -0
- package/lib/templates/instruction-block-onprem.template.md +86 -0
- package/lib/templates/instruction-block-universal.template.md +29 -0
- package/lib/templates/roomodes.template.yaml +96 -0
- package/lib/uninstall.js +314 -0
- package/package.json +4 -3
- package/test/agents-md.test.js +398 -0
- package/test/bmad-extension.test.js +2 -2
- package/test/bmad-persona-phase-prefix.test.js +271 -0
- package/test/clinerules.test.js +339 -0
- package/test/instruction-block.test.js +388 -0
- package/test/integration-verification.test.js +2 -2
- package/test/migration-validation.test.js +2 -2
- package/test/offline-recompile.test.js +237 -0
- package/test/onprem-injection.test.js +425 -32
- package/test/onprem-layer.test.js +419 -0
- package/test/reconfigure.test.js +436 -0
- package/test/roomodes.test.js +343 -0
- package/test/uninstall.test.js +402 -0
package/.ma-agents.json
ADDED
package/AGENTS.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<!-- Generated by ma-agents. Edit outside the MA-AGENTS-START/END markers to preserve your changes. -->
|
|
2
|
+
# Project Agent Instructions
|
|
3
|
+
|
|
4
|
+
This file is auto-discovered by OpenCode and any agent that respects `AGENTS.md`.
|
|
5
|
+
It establishes universal safety rules, text-vs-file discipline, and BMAD phase
|
|
6
|
+
discipline for every agent operating in this project.
|
|
7
|
+
|
|
8
|
+
## Universal Rules
|
|
9
|
+
|
|
10
|
+
The universal rules block below is stamped and maintained by ma-agents. Edit
|
|
11
|
+
outside the HTML-comment `MA-AGENTS` start and end markers to preserve your
|
|
12
|
+
own additions — content inside the markers is regenerated on every install.
|
|
13
|
+
|
|
14
|
+
<!-- MA-AGENTS-START -->
|
|
15
|
+
# AI Agent Skills - Planning Instruction
|
|
16
|
+
|
|
17
|
+
You have access to a library of skills in your skills directory. Before starting any task:
|
|
18
|
+
|
|
19
|
+
1. Read the skill manifest at .opencode/skills/MANIFEST.yaml
|
|
20
|
+
2. Based on the task description, select which skills are relevant
|
|
21
|
+
3. Read only the selected skill files
|
|
22
|
+
4. Then proceed with the task
|
|
23
|
+
|
|
24
|
+
Always load skills marked with always_load: true.
|
|
25
|
+
Do not load skills that are not relevant to the current task.
|
|
26
|
+
|
|
27
|
+
## Respond in TEXT vs. create FILES
|
|
28
|
+
|
|
29
|
+
Choose your response medium deliberately. Defaulting to file creation when the user asked a question is a common failure mode — especially for coding agents running in web UIs.
|
|
30
|
+
|
|
31
|
+
- **Create or modify FILES when the user's request contains file-action keywords:** `create`, `write`, `generate`, `build`, `implement` (and obvious synonyms such as `add`, `produce`, `refactor`, `fix`, `update <file>`). These signal a concrete artifact is expected.
|
|
32
|
+
- **Respond in TEXT when the request contains text-response keywords:** `what do you think`, `how should we`, `discuss`, `opinion` (and obvious synonyms such as `explain`, `why`, `should I`, `compare`, `recommend`). These signal that a conversation is expected, not a deliverable.
|
|
33
|
+
- **If unsure, respond in TEXT.** A text answer can always be followed by file creation on confirmation; an unwanted file cannot be cleanly undone.
|
|
34
|
+
- **Never create `response.md`, `output.md`, or any similarly named scratch file as a reply.** A reply belongs in the chat transcript, not on disk.
|
|
35
|
+
- **Confirm file paths before writing.** When you are about to create or modify a file whose path the user has not explicitly named, state the intended path in text and wait for confirmation, unless the path is unambiguous from the task context.
|
|
36
|
+
|
|
37
|
+
## BMAD phase discipline
|
|
38
|
+
|
|
39
|
+
BMAD-METHOD organizes work into declared phases (analysis, planning, architecture, story-creation, implementation, review). Respect the currently declared phase.
|
|
40
|
+
|
|
41
|
+
- **Do not skip ahead to implementation during planning.** If the project is in a planning phase — or the user has asked for requirements, architecture, or a story — produce planning artifacts, not code.
|
|
42
|
+
- **Do not retroactively plan after you have already coded.** If implementation has already started, flag the gap instead of fabricating back-dated planning documents.
|
|
43
|
+
- The declared phase is established by the active skill, the story status, or an explicit statement from the user. When none of these is available, ask before assuming.
|
|
44
|
+
<!-- MA-AGENTS-END -->
|
|
45
|
+
|
|
46
|
+
## Critical Behavior Rules
|
|
47
|
+
|
|
48
|
+
These rules are non-negotiable across every profile and every agent.
|
|
49
|
+
|
|
50
|
+
- **Never create files in `~/.claude/` or any user home directory.** All
|
|
51
|
+
project artifacts must land inside the current working directory. Home-
|
|
52
|
+
directory writes cross-contaminate between projects and are a common source
|
|
53
|
+
of secret/config leakage.
|
|
54
|
+
- **Never write outside the project root without an explicit user request
|
|
55
|
+
naming the absolute path.** "Write to disk" means the project, not the
|
|
56
|
+
operator's machine.
|
|
57
|
+
- **Do not modify files you did not read first.** Read the current content
|
|
58
|
+
before proposing or performing an edit — blind writes silently destroy user
|
|
59
|
+
work.
|
|
60
|
+
|
|
61
|
+
## BMAD Phase Declaration
|
|
62
|
+
|
|
63
|
+
BMAD-METHOD organizes work into four phases. Respect the currently declared
|
|
64
|
+
phase; do not skip ahead to the next phase without a phase transition signal
|
|
65
|
+
from the user, the active skill, or the story status.
|
|
66
|
+
|
|
67
|
+
- **Discovery / PM (analysis, planning).** Deliverables: product briefs,
|
|
68
|
+
PRDs, market and domain research, epics and stories lists. Do NOT produce
|
|
69
|
+
code, architecture diagrams, or implementation artifacts in this phase.
|
|
70
|
+
When asked "what do you think", respond in text.
|
|
71
|
+
- **Architecture.** Deliverables: solution design, component boundaries,
|
|
72
|
+
data-flow, interface contracts. Do NOT write application code or skill
|
|
73
|
+
implementations. Narrate decisions and capture them as documents.
|
|
74
|
+
- **Tech Lead / Stories.** Deliverables: individual story files with full
|
|
75
|
+
acceptance criteria, task breakdowns, and dev notes. Do NOT begin
|
|
76
|
+
implementation — stories are contracts the implementer consumes later.
|
|
77
|
+
- **Implementation.** Deliverables: code, tests, and the Dev Agent Record on
|
|
78
|
+
the story file. At this phase, write files. Do NOT retroactively fabricate
|
|
79
|
+
planning documents for code that already exists — flag the gap instead.
|
|
80
|
+
|
|
81
|
+
When no phase is declared (no active skill, no story in progress, no explicit
|
|
82
|
+
user statement), ask before assuming.
|
|
83
|
+
|
|
84
|
+
## Project BMAD Output Structure
|
|
85
|
+
|
|
86
|
+
BMAD artifacts live under `_bmad-output/` (or the paths configured in
|
|
87
|
+
`_bmad/bmm/config.yaml` when present). The install-time resolver logs the
|
|
88
|
+
resolved paths on each run; consult that log output if in doubt.
|
|
89
|
+
|
|
90
|
+
- **Planning artifacts** — PRDs, product briefs, market and domain research.
|
|
91
|
+
- **Architecture artifacts** — solution design, component boundaries. May be
|
|
92
|
+
co-located with planning artifacts when no separate directory is configured.
|
|
93
|
+
- **Implementation artifacts (stories)** — individual story files and their
|
|
94
|
+
Dev Agent Records.
|
|
95
|
+
|
|
96
|
+
Always consult the `MANIFEST.yaml` referenced inside the universal block above
|
|
97
|
+
for the full list of installed skills and their locations.
|
package/MANIFEST.yaml
ADDED
package/README.md
CHANGED
|
@@ -103,6 +103,23 @@ The file is version-controlled as part of `_bmad-output/` project knowledge. Com
|
|
|
103
103
|
|
|
104
104
|
---
|
|
105
105
|
|
|
106
|
+
## On-Prem / Air-Gapped Deployment
|
|
107
|
+
|
|
108
|
+
When running AI coding agents against a locally-hosted LLM (e.g., Nemotron Super 49B on vLLM), `ma-agents install` asks a one-time profile question at setup:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
? Profile (standard / on-prem):
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Your answer is persisted in `.ma-agents.json` under the `profile` field and is read on every subsequent install, update, or reconfigure. The `standard` profile produces the same output as always; the `on-prem` profile additionally stamps on-prem-specific guardrails into every agent's instruction block — including `/no_think` directives for planning personas, `str_replace_editor` prohibition, and home-directory write restrictions.
|
|
115
|
+
|
|
116
|
+
To change your profile after initial setup: `npx ma-agents reconfigure`
|
|
117
|
+
To remove all on-prem profile artifacts: `npx ma-agents uninstall --profile-artifacts`
|
|
118
|
+
|
|
119
|
+
For vLLM server configuration, quantization tradeoffs, per-phase sampling parameters, and the `str_replace_editor` hallucination mitigation, see [`docs/deployment/vllm-nemotron.md`](docs/deployment/vllm-nemotron.md).
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
106
123
|
## Supported Coding Tools
|
|
107
124
|
|
|
108
125
|
Skills can be installed into any of these AI coding agents:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Story 21.10: Profile Reconfigure
|
|
2
2
|
|
|
3
|
-
Status:
|
|
3
|
+
Status: Review
|
|
4
4
|
|
|
5
5
|
## Story
|
|
6
6
|
|
|
@@ -117,21 +117,45 @@ Match the existing test layout (node:test or mocha-style — verify by reading o
|
|
|
117
117
|
|
|
118
118
|
### Epic 21 Cross-Story Context
|
|
119
119
|
|
|
120
|
-
**Story 21.10 (this):** Reconfigure subcommand — escape hatch for a previously-persisted profile. Depends on Stories 21.1 (profile API), 21.2 (marker injection + backup convention), 21.3 (slug-stomp protection), 21.5 (dual-file drift detection), 21.6 (on-prem layer composition — the actual content being re-stamped). Runs after 21.6 in the execution order; must exist before 21.9's end-to-end tests so the round-trip test (21.9 AC #1 (f)) can exercise the real command instead of manual `.ma-agents.json` edits.
|
|
120
|
+
**Story 21.10 (this):** Reconfigure subcommand — escape hatch for a previously-persisted profile. Depends on Stories 21.1 (profile API), 21.2 (marker injection + backup convention), 21.3 (slug-stomp protection), 21.4 (`AGENTS.md` template + `markdown-markers` merger — re-stamped on profile flip), 21.5 (dual-file drift detection), 21.6 (on-prem layer composition — the actual content being re-stamped), 21.7 (BMAD persona `on_prem_phase_prefix` — re-composed when profile flips, per Task 3.3). Runs after 21.6 in the execution order; must exist before 21.9's end-to-end tests so the round-trip test (21.9 AC #1 (f)) can exercise the real command instead of manual `.ma-agents.json` edits.
|
|
121
121
|
|
|
122
122
|
## Dev Agent Record
|
|
123
123
|
|
|
124
124
|
### Agent Model Used
|
|
125
|
-
|
|
125
|
+
Claude Opus 4.6 (1M context) — bmad-dev-story flow.
|
|
126
126
|
|
|
127
127
|
### Debug Log References
|
|
128
|
-
|
|
128
|
+
- `.roomodes` slug-divergence comparison deliberately strips `customInstructions` because installed files already carry the profile-composed universal block; a profile flip legitimately rewrites that field. `whenToUse`, `roleDefinition`, `groups`, `name` are the user-editable surfaces the check catches. Documented inline in `lib/reconfigure.js::checkRoomodesSlugDivergence`.
|
|
129
|
+
- `updateAgentInstructions` is invoked with `yesMode: true` from the reconfigure loop so per-file drift prompts do not re-ask after the global `Continue?` confirmation (AC #7). The installer's `handleMarkerBlockDrift` still emits the pinned WARNING line and writes the canonical `<target>.backup-<ISO>` sibling (AC #8).
|
|
130
|
+
- `appendProfileHistory` does a fresh read-modify-write of `.ma-agents.json` so it composes with `setProfile`'s write — no parallel JSON-IO path. Cap enforcement is `shift()`-based (oldest-first eviction).
|
|
129
131
|
|
|
130
132
|
### Completion Notes List
|
|
131
|
-
|
|
133
|
+
- New `lib/reconfigure.js` orchestrator exports `reconfigure({ projectRoot, argv, promptsLib, now })` plus three named error classes (`RoomodesSlugDivergenceError`, `ManifestNotFoundError`, `ReconfigureYesRejectedError`) and the `PROFILE_HISTORY_CAP` constant (20).
|
|
134
|
+
- `bin/cli.js` registers the `reconfigure` verb and routes to `handleReconfigure`, which maps each error class to a user-facing exit(1) message. `--yes` on reconfigure exits nonzero with the pinned message verbatim (AC #6).
|
|
135
|
+
- Re-stamp reuses the canonical `updateAgentInstructions` path from `lib/installer.js`; zero forked logic. Backups are produced by the installer's existing drift handler using the canonical `buildBackupFilename` helper owned by Story 21.2.
|
|
136
|
+
- `checkRoomodesSlugDivergence` throws `RoomodesSlugDivergenceError` when any ma-agents-owned slug present in the existing `.roomodes` differs from the shipped template on non-customInstructions fields; `--force-roomodes-overwrite` bypasses the check (AC #9). `.clinerules` dual-file drift is delegated to Story 21.5's `checkClinerulesDualFileDrift` (no override, AC #10).
|
|
137
|
+
- `profileHistory` append uses a fresh read-modify-write that composes with `setProfile`'s write — capped at 20, oldest-first eviction (AC #11). Missing-field start is handled (first reconfigure creates the array).
|
|
138
|
+
- Prompt shape mirrors Story 21.1's wizard prompt (same two choices) but with `initial` set to the persisted value's row and the message `Current profile: <value>. Change to?` per AC #2.
|
|
139
|
+
|
|
140
|
+
### Adversarial Review Findings
|
|
141
|
+
|
|
142
|
+
| # | Layer | Severity | Finding | Disposition |
|
|
143
|
+
|---|-------|----------|---------|-------------|
|
|
144
|
+
| 1 | Cynical | P1 | setProfile runs BEFORE re-stamp loop; mid-flight re-stamp failure leaves profile+artifacts out of sync | Accepted — story explicitly lists "Auto-rollback" as out of scope; `profileHistory` append is gated on full loop success so forensic log does not falsely record a partial reconfigure |
|
|
145
|
+
| 2 | Cynical | P2 | `--yes` detection is string-includes on argv; benign for reconfigure (no positional args) | Accepted — same pattern as installer; reconfigure AC #1 forbids positional args |
|
|
146
|
+
| 3 | Edge-case | P1 | Slug-divergence check drops `customInstructions` from the compare | Correct by design — installer-stamped `customInstructions` carries the composed block which legitimately changes per profile; other fields remain user-edit detectors. Test 8.7 exercises `whenToUse` drift |
|
|
147
|
+
| 4 | Cynical | P2 | `appendProfileHistory` is read-modify-write, not atomic rename | Accepted — matches `setProfile`'s write pattern; reconfigure is interactive-only so concurrency isn't a realistic vector |
|
|
148
|
+
| 5 | Edge-case | P2 | Same-value short-circuit when `persistedProfile === undefined` requires user to pick a value; cannot short-circuit to undefined | Accepted — prompt choices are `on-prem` | `standard` only; chosenProfile is always a concrete value |
|
|
149
|
+
| 6 | Edge-case | P2 | No help-text doc for `--force-roomodes-overwrite` | **Fixed** — added Reconfigure options block in `showHelp()` |
|
|
132
150
|
|
|
133
151
|
### File List
|
|
134
|
-
|
|
152
|
+
- CREATED: `lib/reconfigure.js`
|
|
153
|
+
- CREATED: `test/reconfigure.test.js` (12 tests, all passing)
|
|
154
|
+
- MODIFIED: `bin/cli.js` (new `reconfigure` case + `handleReconfigure` + help text)
|
|
155
|
+
- MODIFIED: `package.json` (added `test/reconfigure.test.js` to npm test script)
|
|
156
|
+
- MODIFIED: `_bmad-output/implementation-artifacts/21-10-profile-reconfigure.md` (Dev Agent Record / File List / Change Log)
|
|
135
157
|
|
|
136
158
|
## Change Log
|
|
159
|
+
- 2026-04-15: Story 21.10 implemented — reconfigure CLI verb + orchestrator + slug/dual-file guards + profileHistory append. Status ready-for-dev → review.
|
|
160
|
+
- 2026-04-15: Upstream dependency list completed per adversarial-review finding — added Stories 21.4 (`AGENTS.md` re-stamping surface), 21.7 (BMAD persona phase prefix re-composition). Restores Dependencies bidirectionality with those stories' downstream lists.
|
|
137
161
|
- 2026-04-14: Story created (Epic 21, Story 21.10). Closes adversarial-review Findings #5 and #7 (no escape hatch for persisted profile; CI-default silent-downgrade trap).
|
|
@@ -129,7 +129,7 @@ Story 21.1 AC #1 pinned `lib/profile.js` to exactly three exports: `getProfile`,
|
|
|
129
129
|
|
|
130
130
|
### Epic 21 Cross-Story Context
|
|
131
131
|
|
|
132
|
-
**Story 21.11 (this):** Uninstall subcommand — rollback path for profile-dependent content. Depends on Stories 21.1 (profile API — extends it), 21.2 (marker injection + backup convention), 21.3 (slug list + audit log), 21.6 (on-prem layer composition — reverses it), 21.10 (`profileHistory` field — preserves + appends). Runs LAST in the Epic 21 execution order (after 21.9) because the 21.9 end-to-end test harness needs uninstall as part of round-trip coverage, and uninstall must not land before all upstream stamping work is committable.
|
|
132
|
+
**Story 21.11 (this):** Uninstall subcommand — rollback path for profile-dependent content. Depends on Stories 21.1 (profile API — extends it), 21.2 (marker injection + backup convention), 21.3 (slug list + audit log), 21.4 (`AGENTS.md` — removes the ma-agents-stamped file / marker block), 21.5 (`.clinerules` + `.cline/clinerules.md` — removes both Cline rule files' ma-agents marker blocks), 21.6 (on-prem layer composition — reverses it), 21.7 (BMAD persona `on_prem_phase_prefix` — strips prefix when uninstalling profile artifacts), 21.10 (`profileHistory` field — preserves + appends). Runs LAST in the Epic 21 execution order (after 21.9) because the 21.9 end-to-end test harness needs uninstall as part of round-trip coverage, and uninstall must not land before all upstream stamping work is committable.
|
|
133
133
|
|
|
134
134
|
## Dev Agent Record
|
|
135
135
|
|
|
@@ -146,4 +146,5 @@ _(to be filled by dev agent)_
|
|
|
146
146
|
_(to be filled by dev agent)_
|
|
147
147
|
|
|
148
148
|
## Change Log
|
|
149
|
+
- 2026-04-15: Upstream dependency list completed per adversarial-review finding — added Stories 21.4 (`AGENTS.md` removal surface), 21.5 (`.clinerules` dual-file removal surface), 21.7 (BMAD persona phase-prefix strip on uninstall). Restores Dependencies bidirectionality with those stories' downstream lists.
|
|
149
150
|
- 2026-04-14: Story created (Epic 21, Story 21.11). Closes adversarial-review Finding #17 (no uninstall / rollback path).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Story 21.2: Universal Per-Tool Instruction Block Expansion
|
|
2
2
|
|
|
3
|
-
Status:
|
|
3
|
+
Status: Review
|
|
4
4
|
|
|
5
5
|
## Story
|
|
6
6
|
|
|
@@ -10,89 +10,244 @@ So that all coding agents — even Claude on the web — stop dumping random fil
|
|
|
10
10
|
|
|
11
11
|
## Acceptance Criteria
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- A "
|
|
17
|
-
-
|
|
18
|
-
- A "
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
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. **Universal template file exists.** A new file `lib/templates/instruction-block-universal.template.md` (new) is present and contains, in addition to the current MANIFEST loading instruction already emitted by `updateAgentInstructions` in `lib/installer.js`:
|
|
16
|
+
- A "Respond in TEXT vs. create FILES" rules section listing concrete keyword triggers: file-action keywords (`create`, `write`, `generate`, `build`, `implement`) and text-response keywords (`what do you think`, `how should we`, `discuss`, `opinion`).
|
|
17
|
+
- An "if unsure, respond in text" default rule.
|
|
18
|
+
- A "never create `response.md` or `output.md` as a reply" rule.
|
|
19
|
+
- A BMAD phase discipline rule: respect the declared phase; do not skip ahead to implementation during planning.
|
|
20
|
+
- A "confirm file paths before writing" rule.
|
|
21
|
+
2. **(gap-fill) Placeholder contract.** The template source contains `{{MANIFEST_PATH}}` exactly once as the only placeholder; no agent-specific paths are hardcoded. The existing per-agent manifest-path computation in `updateAgentInstructions` (`lib/installer.js` line ~369 / ~408 — `path.relative(projectRoot, path.join(agentProjectPath, 'MANIFEST.yaml'))`) is the value substituted in.
|
|
22
|
+
3. **(gap-fill) Composer function.** A new function `composeInstructionBlock({ profile, projectRoot })` (new) in `lib/installer.js`:
|
|
23
|
+
- Reads `lib/templates/instruction-block-universal.template.md` (always).
|
|
24
|
+
- When `profile === 'on-prem'`, reads `lib/templates/instruction-block-onprem.template.md` (new, ships in Story 21.6) and appends it to the universal content separated by a single blank line.
|
|
25
|
+
- When `profile === 'on-prem'` AND the on-prem template file is missing, **throws** `Error('on-prem profile selected but instruction-block-onprem.template.md is missing')`. NO silent fallback.
|
|
26
|
+
- When `profile !== 'on-prem'`, returns the universal content only.
|
|
27
|
+
- Templates contain NO `{{...}}` placeholders; any substitution is the caller's responsibility, done AFTER composition.
|
|
28
|
+
- Single-owner contract: `lib/installer.js` calls `composeInstructionBlock` exactly once per stamped artifact. Mergers receive the already-composed string; mergers do not call `composeInstructionBlock` themselves.
|
|
29
|
+
- Is the **single composer entry point** consumed by stories 21.3 (roomodes mode `customInstructions`), 21.4 (AGENTS.md), 21.5 (.clinerules), and 21.6 (on-prem content). Those stories extend behavior by adding inputs to `composeInstructionBlock` or sibling composers — they do not duplicate the text-vs-file / phase-discipline rules.
|
|
30
|
+
4. **Per-tool merger wired.** The existing marker-based merger in `updateAgentInstructions` (`lib/installer.js`, markers `<!-- MA-AGENTS-START -->` / `<!-- MA-AGENTS-END -->`, see `lib/installer.js` lines ~424–459) calls `composeInstructionBlock` with the project's resolved profile (obtained via `getProfile(projectRoot)` from `lib/profile.js`) and emits the composed content between the markers.
|
|
31
|
+
5. **All markdown-merger agents receive the block.** For every agent in `lib/agents.js` whose `instructionFiles[]` is a markdown file — Claude Code (`.claude/CLAUDE.md`), Cline (`.cline/clinerules.md` and `.clinerules`), Roo Code (`.roo/rules/00-ma-agents.md`), Cursor (`.cursor/cursor.md`), Kilocode (`.kilocode/kilocode.md`), Copilot (`.github/copilot/copilot.md`), Gemini (`.gemini/gemini.md`) — a fresh install produces an instruction file whose marker block contains the universal block content. Content outside the markers is preserved byte-for-byte (**NFR5 additive-only contract; NFR46 idempotency**).
|
|
32
|
+
6. **Idempotency (NFR46).** Two consecutive installs with the same profile and project state produce byte-identical content inside the marker block. The per-install header/footer contains no timestamps, random IDs, or ordering-sensitive content.
|
|
33
|
+
7. **Profile isolation (NFR44).** The universal block content MUST NOT mention `/no_think`, `str_replace_editor`, `~/.claude/`, or any local-LLM-specific concept. Those strings belong in the on-prem template (Story 21.6). Verified by a grep-style assertion on rendered standard-profile output: absence of each of those three strings.
|
|
34
|
+
8. **(gap-fill) OpenCode json-merge coexistence (NFR18).** The OpenCode agent uses `injectionStrategy.position === 'json-merge'` (`lib/agents.js` lines ~244, `lib/installer.js` lines ~365–405) writing into `opencode.json::instructions[]` as a plain string. The json-merge path in `updateAgentInstructions` MUST also emit the universal block content (inline in the string, no file markers because the target is JSON). The ma-agents entry is identified by its existing `[ma-agents]` prefix marker; stale entries are replaced, user entries preserved (**NFR18 additive-only JSON merge**). No other keys in `opencode.json` are touched.
|
|
35
|
+
9. **(gap-fill) BMAD agent instruction files unchanged in scope.** The BMAD-category branch in `updateAgentInstructions` (`lib/installer.js` line ~445) that skips missing BMAD instruction files ("BMAD agent file not yet deployed") remains unchanged. When a BMAD agent file does exist and has markers, the universal block is re-rendered inside its markers on re-install. This story does not modify `applyCustomizations` or BMAD persona prompt composition (those belong to Stories 21.6/21.7).
|
|
36
|
+
10. **(gap-fill) Upgrade-safety: marker-block hand-edit detection.** Before overwriting content inside the markers, the installer compares existing in-marker content against the content `composeInstructionBlock` would produce for the project's current resolved profile. If they differ:
|
|
37
|
+
- **Interactive mode:** the installer prints a diff of the in-marker changes and requires explicit confirmation before proceeding.
|
|
38
|
+
- **`--yes` mode:** the prompt is skipped but the installer prints the warning line verbatim: `WARNING: ma-agents marker-block content modified since last install — overwriting. Previous content backed up to <path>.backup-<timestamp>`.
|
|
39
|
+
- **Backup:** before overwriting, the installer writes a sibling backup file at `<target>.backup-<ISO-timestamp>` (UTC, `YYYY-MM-DDTHH-mm-ssZ`, colons replaced with hyphens for Windows compatibility). The backup contains only the original marker-block region (including the marker lines themselves). The rest of the target file is untouched by the installer and therefore needs no backup.
|
|
40
|
+
|
|
41
|
+
> **Open question:** The epic does not specify what to do when the existing in-marker content matches ma-agents output for the *previous* profile but not the *current* profile (e.g., user ran `reconfigure`). Story 21.10 (Profile Reconfigure) introduces its own backup flow. Proposed resolution: AC #10's drift detection compares against the current profile's expected output only — reconfigure-driven rewrites go through Story 21.10's path, not Story 21.2's. To be confirmed during Story 21.10 dev.
|
|
42
|
+
|
|
43
|
+
> **Open question:** Epic AC requires byte-identical marker-block content across runs (NFR46). The current `updateAgentInstructions` trims the wrapped instruction with `.replace(regex, wrappedInstruction.trim())` on replace but uses the non-trimmed form on first insert (line ~443). This is a pre-existing asymmetry that must be normalized so both paths produce identical trailing whitespace. Flagged for the implementing dev to resolve; not a new AC because the epic simply mandates byte-identity and leaves the mechanism to the implementation.
|
|
29
44
|
|
|
30
45
|
## Tasks / Subtasks
|
|
31
46
|
|
|
32
|
-
- [
|
|
33
|
-
- [
|
|
34
|
-
- [
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
- [
|
|
38
|
-
- [
|
|
39
|
-
- [
|
|
40
|
-
- [
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
- [
|
|
44
|
-
- [
|
|
45
|
-
- [
|
|
46
|
-
- [
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
- [
|
|
50
|
-
- [
|
|
47
|
+
- [x] **Task 1: Create universal template** (AC #1, #2, #7)
|
|
48
|
+
- [x] 1.1 Create `lib/templates/instruction-block-universal.template.md` (new) containing the MANIFEST loading section (with `{{MANIFEST_PATH}}` placeholder) followed by the five rule sections listed in AC #1.
|
|
49
|
+
- [x] 1.2 Verify by inspection that no string in the template matches `/no_think`, `str_replace_editor`, or `~/.claude/` (AC #7).
|
|
50
|
+
|
|
51
|
+
- [x] **Task 2: Implement `composeInstructionBlock`** (AC #3)
|
|
52
|
+
- [x] 2.1 Add `composeInstructionBlock({ profile, projectRoot })` to `lib/installer.js` (new function).
|
|
53
|
+
- [x] 2.2 Load the universal template from disk (synchronous `fs.readFileSync` to keep parity with adjacent helpers; module-load-time caching optional). Templates contain NO `{{...}}` placeholders — do not perform substitution inside the composer.
|
|
54
|
+
- [x] 2.3 If `profile === 'on-prem'`, read `lib/templates/instruction-block-onprem.template.md` and append its content separated by a single blank line. If that file is absent, throw `Error('on-prem profile selected but instruction-block-onprem.template.md is missing')`. NO silent fallback.
|
|
55
|
+
- [x] 2.4 Export from `lib/installer.js`'s `module.exports` so tests and Stories 21.3–21.6 can consume it.
|
|
56
|
+
|
|
57
|
+
- [x] **Task 3: Wire composer into the marker-based merger** (AC #4, #5, #9)
|
|
58
|
+
- [x] 3.1 In `updateAgentInstructions` (`lib/installer.js` line ~358), replace the hardcoded `planningInstruction` string (lines ~410–422) with a call to `composeInstructionBlock({ profile: getProfile(projectRoot) || 'standard', projectRoot })`, performing any `{{MANIFEST_PATH}}` substitution on the returned string AFTER composition (caller-owned).
|
|
59
|
+
- [x] 3.2 Keep the marker-wrap (`markerStart`/`markerEnd`) and the first-insert / in-place-replace split unchanged.
|
|
60
|
+
- [x] 3.3 Normalize trailing whitespace so the first-insert and in-place-replace paths produce byte-identical content inside the markers (resolves the Open question under AC).
|
|
61
|
+
- [x] 3.4 Leave the BMAD-category "skip if file missing" branch (line ~445) untouched (AC #9).
|
|
62
|
+
|
|
63
|
+
- [x] **Task 4: Wire composer into the json-merge (OpenCode) merger** (AC #8)
|
|
64
|
+
- [x] 4.1 In the `injectionStrategy.position === 'json-merge'` branch (`lib/installer.js` lines ~365–405), replace the hardcoded `instructionText` template literal (line ~370) with the composed content, prefixed by the existing `[${MA_AGENTS_SOURCE}]` tag used for ma-agents-entry identification.
|
|
65
|
+
- [x] 4.2 Confirm the filter `isMaEntry` still correctly identifies the new entry (prefix unchanged → filter unchanged).
|
|
66
|
+
- [x] 4.3 Verify no other keys in `opencode.json` are modified (NFR18).
|
|
67
|
+
|
|
68
|
+
- [x] **Task 5: Upgrade-safety — hand-edit detection, backup, warning** (AC #10)
|
|
69
|
+
- [x] 5.1 Before overwriting the in-marker region, extract current in-marker content and compare against `composeInstructionBlock(...)` output for the resolved profile.
|
|
70
|
+
- [x] 5.2 On drift in interactive mode: show unified diff (reuse existing prompts library); require confirmation.
|
|
71
|
+
- [x] 5.3 On drift with `--yes`: emit the pinned WARNING line (verbatim format from AC #10).
|
|
72
|
+
- [x] 5.4 Before overwrite, write `<target>.backup-<ISO-timestamp>` containing only the marker-block region including marker lines. Timestamp format: `YYYY-MM-DDTHH-mm-ssZ` (hyphens not colons — Windows filename safety).
|
|
73
|
+
- [x] 5.5 No backup file created when existing in-marker content matches expected output.
|
|
74
|
+
|
|
75
|
+
- [x] **Task 6: Unit and integration tests** — see Testing section below.
|
|
76
|
+
|
|
77
|
+
- [ ] **Task 7: Documentation touch-up** (no new files)
|
|
78
|
+
- [ ] 7.1 Add a single paragraph to `docs/` only if an existing doc already covers instruction injection; do not create a new doc file. (If no existing doc covers this, skip — Story 21.8 introduces documentation for the broader Epic 21 surface.)
|
|
51
79
|
|
|
52
80
|
## Dev Notes
|
|
53
81
|
|
|
54
|
-
###
|
|
82
|
+
### Canonical Composer Contract (decision A — verbatim)
|
|
83
|
+
|
|
84
|
+
> `composeInstructionBlock({ profile, projectRoot }) → string`
|
|
85
|
+
> - Reads `lib/templates/instruction-block-universal.template.md` (always).
|
|
86
|
+
> - If `profile === 'on-prem'`, reads `lib/templates/instruction-block-onprem.template.md` and appends it to the universal content separated by a single blank line.
|
|
87
|
+
> - If `profile === 'on-prem'` and the on-prem template file is missing, THROWS `Error('on-prem profile selected but instruction-block-onprem.template.md is missing')`. NO silent fallback.
|
|
88
|
+
> - Returns the composed string. Templates contain NO `{{...}}` placeholders; any substitution is the caller's responsibility, done AFTER composition.
|
|
89
|
+
> - Single owner: `lib/installer.js` calls `composeInstructionBlock` exactly once per stamped artifact. Mergers receive the already-composed string; mergers do not call `composeInstructionBlock` themselves.
|
|
90
|
+
|
|
91
|
+
### Canonical Backup Filename Format (decision C — verbatim)
|
|
92
|
+
|
|
93
|
+
> Backup filename format: `<target>.backup-<ISO-8601-timestamp>` e.g. `.roomodes.backup-2026-04-15T12-30-00Z`. Story 21.2 OWNS this format.
|
|
94
|
+
|
|
95
|
+
### Terminology (decision B)
|
|
96
|
+
|
|
97
|
+
- **composer** = `composeInstructionBlock` (owned by 21.2).
|
|
98
|
+
- **merger** = per-artifact splicer (marker-block merger for markdown files; json-merge merger for `opencode.json`).
|
|
99
|
+
- **stamper** = installer orchestration.
|
|
100
|
+
- "injection function" / "marker-injection" wording is retired.
|
|
101
|
+
|
|
102
|
+
### Architecture compliance
|
|
103
|
+
|
|
104
|
+
- **Decision P3-3 (Local-LLM / On-Prem Agent Tuning Profile)** — this story implements the "Universal layer" of the two-layer injection model. The on-prem layer ships in Story 21.6 via `lib/templates/instruction-block-onprem.template.md`. The composition function lives in this story; Story 21.6 only adds the on-prem template file and (if needed) one additional conditional branch.
|
|
105
|
+
|
|
106
|
+
- **NFR44 (byte-identity for standard profile)** — Standard-profile rendered output must not contain `/no_think`, `str_replace_editor`, or `~/.claude/`. AC #7 asserts absence; Test 5.5 in the Testing section enforces it with a direct grep assertion. Story 21.9 will add a standard-profile-vs-pre-Epic-21-baseline byte-comparison integration test; this story stays focused on the composition unit.
|
|
107
|
+
|
|
108
|
+
- **NFR46 (idempotency)** — Two consecutive installs with identical inputs must produce byte-identical marker-block content. AC #6 is the contract; Task 3.3 resolves the pre-existing insert/replace asymmetry that otherwise would violate this.
|
|
109
|
+
|
|
110
|
+
- **NFR47 (Roo Code fileRegex enforcement)** — Not directly implemented here. Story 21.3 ships `.roomodes` with `fileRegex` restrictions per BMAD mode; NFR47 is verified by Story 21.9. Story 21.2 must only ensure that Roo Code's instruction file (`.roo/rules/00-ma-agents.md` — registered in `lib/agents.js` line 161) receives the universal block unchanged in shape, so Story 21.3 can cleanly add `.roomodes` alongside without conflicting with this story's markdown merger.
|
|
111
|
+
|
|
112
|
+
- **NFR18 (additive JSON-merge for OpenCode)** — The existing `injectionStrategy.position === 'json-merge'` path already implements additive merge for `opencode.json::instructions[]` (ma-agents entry identified by `[ma-agents]` prefix, stale entries replaced, user entries preserved). Task 4 reuses that machinery unchanged; only the instruction-text content source switches from the hardcoded literal to `composeInstructionBlock`.
|
|
113
|
+
|
|
114
|
+
### Verified source-tree surface
|
|
115
|
+
|
|
116
|
+
| File | Exists? | Role in this story |
|
|
117
|
+
|------|---------|--------------------|
|
|
118
|
+
| `lib/profile.js` | verified (Story 21.1, lines 1–107) | Consumed via `getProfile(projectRoot)` in `updateAgentInstructions` |
|
|
119
|
+
| `lib/installer.js` | verified | Add `composeInstructionBlock`; modify `updateAgentInstructions` markdown branch (line ~358) and json-merge branch (line ~365) |
|
|
120
|
+
| `lib/agents.js` | verified | Read-only — enumerates the agents whose `instructionFiles[]` receive the universal block (Claude Code, Cline, Roo Code, Cursor, Kilocode, Copilot, Gemini, OpenCode) |
|
|
121
|
+
| `lib/templates/instruction-block-universal.template.md` | **new** | Template source (AC #1, #2) |
|
|
122
|
+
| `lib/templates/instruction-block-onprem.template.md` | **new (Story 21.6)** | Consumed conditionally by `composeInstructionBlock`; graceful absence handling required now |
|
|
123
|
+
| `lib/templates/project-context.template.md` | verified | Reference pattern for template stamping syntax (placeholder conventions) |
|
|
124
|
+
| `test/instruction-block.test.js` | **new** | Unit + snapshot tests (see Testing section) |
|
|
125
|
+
| `test/profile.test.js` | verified (Story 21.1) | Reference for test framework, temp-dir isolation pattern |
|
|
126
|
+
|
|
127
|
+
### Composition pattern (template foundation for Stories 21.3–21.6)
|
|
55
128
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
129
|
+
```
|
|
130
|
+
composeInstructionBlock({ profile, projectRoot })
|
|
131
|
+
├── universal template (this story — always applied)
|
|
132
|
+
└── on-prem template (Story 21.6 — applied when profile === 'on-prem'; THROWS if missing)
|
|
133
|
+
```
|
|
59
134
|
|
|
60
|
-
|
|
135
|
+
Stories 21.3 (`.roomodes`), 21.4 (`AGENTS.md`), and 21.5 (`.clinerules`) consume this function where their templates reference the universal text-vs-file and phase-discipline rules — they do not duplicate the rule text. Story 21.6 adds the on-prem template file and, if its rules need structural interleaving with universal rules (rather than straight append), one additional branch in `composeInstructionBlock`. Story 21.7 (BMAD persona phase prefix) is a separate composition site (BMAD customize-loader) and does not call `composeInstructionBlock`.
|
|
61
136
|
|
|
62
|
-
|
|
63
|
-
|------|--------|
|
|
64
|
-
| `lib/templates/instruction-block-universal.template.md` | CREATE |
|
|
65
|
-
| `lib/installer.js` | MODIFY — add `composeInstructionBlock`; wire into existing marker-based injection |
|
|
66
|
-
| `test/instruction-block.test.js` | CREATE |
|
|
137
|
+
### Merger-site map (for reviewers)
|
|
67
138
|
|
|
68
|
-
|
|
139
|
+
- **Markdown marker merger** (`lib/installer.js` `updateAgentInstructions` lines ~407–459): Claude Code, Cline (both files), Roo Code rules, Cursor, Kilocode, Copilot, Gemini.
|
|
140
|
+
- **JSON merge merger** (`lib/installer.js` `updateAgentInstructions` lines ~365–405): OpenCode (`opencode.json::instructions[]`).
|
|
141
|
+
- **BMAD agent files** (`lib/installer.js` lines ~445–451): Skipped when file absent; receive markers via `applyCustomizations` (outside this story's scope).
|
|
142
|
+
- **Anti-gravity** (`lib/agents.js` line 221): Uses `.antigravity/antigravity.md` — treated as markdown marker merger, same path as Claude Code et al.
|
|
69
143
|
|
|
70
|
-
|
|
144
|
+
### Library and pattern references
|
|
71
145
|
|
|
72
|
-
|
|
146
|
+
- **Prompts library** (`prompts` npm) for the drift-confirmation prompt in Task 5.2 — reuse the existing `prompts({ type: 'confirm' })` style used elsewhere in `bin/cli.js`.
|
|
147
|
+
- **Backup filename format** — see the "Canonical Backup Filename Format" block above. Story 21.2 OWNS this format; Stories 21.10 (reconfigure) and 21.11 (uninstall) consume it so uninstall's backup-cleanup logic works uniformly.
|
|
148
|
+
- **Manifest-path computation** reuse `path.relative(projectRoot, path.join(agent.getProjectPath(), 'MANIFEST.yaml')).replace(/\\/g, '/')` (already present in both branches of `updateAgentInstructions`). Do not duplicate — pass it into `composeInstructionBlock` from the call site.
|
|
73
149
|
|
|
74
|
-
|
|
75
|
-
- `.roomodes`, `AGENTS.md`, `.clinerules` per-tool templates (Stories 21.3–21.5)
|
|
76
|
-
- BMAD persona phase prefix (Story 21.7)
|
|
150
|
+
### Out of scope
|
|
77
151
|
|
|
78
|
-
|
|
152
|
+
- On-prem template content and the conditional append wiring details beyond the graceful-fallback stub (Story 21.6).
|
|
153
|
+
- `.roomodes` template and Roo Code `fileRegex` mode restrictions (Story 21.3).
|
|
154
|
+
- `AGENTS.md` template for OpenCode (Story 21.4) — noting that Story 21.4 *reuses* `composeInstructionBlock` for the text-vs-file section; the `AGENTS.md` filename and layout are Story 21.4's concern.
|
|
155
|
+
- `.clinerules` template for Cline (Story 21.5) — Cline's existing `.clinerules` injection already passes through `updateAgentInstructions`; Story 21.5 ships a richer dedicated template and is out of scope here.
|
|
156
|
+
- BMAD persona phase prefix and customize-loader changes (Story 21.7).
|
|
157
|
+
- vLLM deployment documentation (Story 21.8).
|
|
158
|
+
- Integration test covering NFR44 byte-comparison against pre-Epic-21 baseline (Story 21.9).
|
|
159
|
+
- Profile-reconfigure and profile-uninstall flows (Stories 21.10, 21.11).
|
|
160
|
+
- Bumping `manifestVersion` — already bumped to `1.2.0` in Story 21.1 and not touched here.
|
|
79
161
|
|
|
80
|
-
|
|
162
|
+
## Dependencies
|
|
163
|
+
|
|
164
|
+
### Upstream (blocking)
|
|
165
|
+
|
|
166
|
+
- **Story 21.1 (done)** — `lib/profile.js` exports `getProfile(projectRoot)` which `updateAgentInstructions` calls to pick the profile. Verified at `lib/profile.js` lines 29–43.
|
|
167
|
+
- **Epic 9 (done)** — `opencode.json` additive JSON-merge pattern in `updateAgentInstructions` (lines ~365–405) is reused unchanged. NFR18 already satisfied by that code; this story must not regress it.
|
|
168
|
+
|
|
169
|
+
### Downstream (consumers of this story's surface)
|
|
170
|
+
|
|
171
|
+
- **Story 21.3** — `.roomodes` mode `customInstructions` text references the rules defined in the universal template. Adds `extraInstructionTemplates` field to `lib/agents.js` Roo Code entry.
|
|
172
|
+
- **Story 21.4** — `AGENTS.md` template for OpenCode reuses the universal text-vs-file section; also appends `AGENTS.md` to `opencode.json::instructions[]` via the same NFR18-compliant JSON-merge.
|
|
173
|
+
- **Story 21.5** — `.clinerules` template reuses the universal rules, formatted per Cline's convention. Cline's two instruction files (`.cline/clinerules.md`, `.clinerules`) both receive the universal block via this story; Story 21.5 extends with Cline-specific content.
|
|
174
|
+
- **Story 21.6** — Adds `lib/templates/instruction-block-onprem.template.md` and toggles on-prem content via the same `composeInstructionBlock` call signature from this story. Must not require a signature change.
|
|
175
|
+
- **Story 21.9** — Tests verifying NFR44 (standard-profile absence of on-prem strings), NFR46 (idempotency across profiles), and roomodes-level NFR47.
|
|
176
|
+
- **Story 21.10 (Profile Reconfigure)** — Consumes the canonical backup-filename format (`<target>.backup-<ISO-8601-timestamp>`) owned by this story, plus the drift-detection pattern introduced here.
|
|
177
|
+
- **Story 21.11 (Profile Uninstall)** — Consumes the canonical backup-filename format owned by this story so uninstall's backup-cleanup logic matches install-time backups uniformly.
|
|
178
|
+
|
|
179
|
+
## Testing
|
|
180
|
+
|
|
181
|
+
**Framework and isolation:** Match the pattern in `test/profile.test.js` (Story 21.1) — node's built-in test runner, `os.tmpdir()` + `fs.mkdtempSync` per-test temporary project roots, never mutate the repo's own `.ma-agents.json` or `lib/templates/`.
|
|
182
|
+
|
|
183
|
+
New test file: `test/instruction-block.test.js` (new).
|
|
184
|
+
|
|
185
|
+
**Unit tests:**
|
|
186
|
+
|
|
187
|
+
- 5.1 Universal template reads from disk and substitutes `{{MANIFEST_PATH}}` exactly once with the supplied value.
|
|
188
|
+
- 5.2 `composeInstructionBlock({ profile: 'standard', projectRoot })` returns universal content only, even when the on-prem template file exists (test creates it in the tmp-copy of `lib/templates/` or uses a fs spy).
|
|
189
|
+
- 5.3 `composeInstructionBlock({ profile: 'on-prem', projectRoot })` appends on-prem content when the on-prem template file is present; **throws** `Error('on-prem profile selected but instruction-block-onprem.template.md is missing')` when the on-prem template file is absent (no silent fallback — decision A).
|
|
190
|
+
- 5.4 Idempotency: calling the composer twice with identical inputs returns byte-identical strings (trim/whitespace parity).
|
|
191
|
+
- 5.5 NFR44 assertion: standard-profile output does not contain the literals `/no_think`, `str_replace_editor`, or `~/.claude/`.
|
|
192
|
+
- 5.6 `composeInstructionBlock` throws a descriptive Error when the universal template is missing or has no `{{MANIFEST_PATH}}` placeholder (defensive).
|
|
193
|
+
|
|
194
|
+
**Integration tests (within this story — full end-to-end lives in Story 21.9):**
|
|
195
|
+
|
|
196
|
+
- 5.7 Marker-block replacement preserves content outside markers byte-for-byte across two installs (targets `.claude/CLAUDE.md` in a tmp project with pre-existing user content above and below the markers).
|
|
197
|
+
- 5.8 Fresh install writes the universal block to each markdown-injection agent's instruction file when the agent is selected.
|
|
198
|
+
- 5.9 OpenCode `opencode.json::instructions[]` receives the composed string with `[ma-agents]` prefix; other keys untouched; second install replaces the ma-agents entry (not duplicates it) and preserves user entries (NFR18).
|
|
199
|
+
- 5.10 Upgrade-safety (AC #10):
|
|
200
|
+
- Clean marker block (no hand-edit) → no warning, no backup file, overwrite is silent.
|
|
201
|
+
- Hand-edited marker block + `--yes` → WARNING line emitted verbatim, backup file exists at `<target>.backup-<timestamp>`, backup contains only the marker-block region including marker lines.
|
|
202
|
+
- Hand-edited marker block + interactive (no `--yes`) → prompt appears; simulated decline leaves file unchanged; simulated confirm proceeds with backup.
|
|
203
|
+
|
|
204
|
+
**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` — use the real module against the tmp project root to catch integration bugs.
|
|
205
|
+
|
|
206
|
+
**Coverage note:** Story 21.9 adds (a) a standard-profile vs. pre-Epic-21 baseline byte-comparison test, (b) the on-prem profile must-have-both-layers test, (c) roomodes slug-collision test, and (d) NFR47 fileRegex contract test. Do not duplicate those here.
|
|
207
|
+
|
|
208
|
+
## Change Log
|
|
209
|
+
|
|
210
|
+
- 2026-04-14: Story created (Epic 21, Story 21.2).
|
|
211
|
+
- 2026-04-14: Added AC #8 for upgrade-safety — installer detects hand-edited marker content, backs it up, warns on --yes override (Finding #15, corrective plan step 3).
|
|
212
|
+
- 2026-04-15: Story rewritten as template foundation for Stories 21.3–21.6 — ACs reorganized and gap-fills flagged explicitly (AC #2, #3, #8, #9, #10); 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. Two open questions raised inline rather than invented as ACs. Status set to Ready.
|
|
213
|
+
- 2026-04-15: Adversarial-review findings resolved. AC #3 rewritten to canonical composer contract — signature is `composeInstructionBlock({ profile, projectRoot })`; on-prem template absence THROWS (no silent fallback); templates contain no placeholders (caller-owned substitution); single-owner contract (installer calls composer once; mergers consume string). Added Canonical Composer Contract and Canonical Backup Filename Format blocks to Dev Notes. Terminology normalized to composer/merger/stamper — "injection function" / "marker-injection" wording retired. Downstream deps explicitly list 21.10 and 21.11 as backup-filename-format consumers. Tasks 2–4 and tests 5.2–5.3 updated to match. Status remains Ready.
|
|
214
|
+
- 2026-04-15: Story 21.2 implemented — universal template, `composeInstructionBlock`, both mergers wired, drift detection + canonical backup format. Status Ready → Review.
|
|
81
215
|
|
|
82
216
|
## Dev Agent Record
|
|
83
217
|
|
|
84
218
|
### Agent Model Used
|
|
85
|
-
|
|
219
|
+
Claude Opus 4.6 (1M context) — bmad-dev-story + bmad-review-adversarial-general + bmad-review-edge-case-hunter flow.
|
|
86
220
|
|
|
87
221
|
### Debug Log References
|
|
88
|
-
|
|
222
|
+
- Initial implementation had `agent._yesMode` reading — a field never populated anywhere. Adversarial review (Finding #6) surfaced this: CLI `--yes` callers would still hit the interactive drift prompt. Resolved by extending `updateAgentInstructions` with an `opts = {}` argument carrying `yesMode` and wiring both install-path call sites (`action === 'remove'` and the main install branch) to pass `{ yesMode: yes }` from `installSkill`. Uninstall (`uninstallSkill`) left unchanged since it has no `yes` flag surface today; drift during uninstall is rare and the env-var fallback (`MA_AGENTS_YES=1`) still works for CI.
|
|
223
|
+
- Added test 5.10c to guard against regression of the opts-based yesMode path.
|
|
89
224
|
|
|
90
225
|
### Completion Notes List
|
|
91
|
-
|
|
226
|
+
- `lib/templates/instruction-block-universal.template.md` ships with `{{MANIFEST_PATH}}` as the ONLY placeholder (AC #1, #2). Contains the five rule sections from AC #1 verbatim and does NOT mention `/no_think`, `str_replace_editor`, or `~/.claude/` (AC #7, asserted by test 5.5).
|
|
227
|
+
- `composeInstructionBlock({ profile, projectRoot })` is exported from `lib/installer.js` (AC #3). On-prem template absence THROWS with the pinned error message — no silent fallback. Templates carry no placeholders substituted inside the composer; mergers substitute `{{MANIFEST_PATH}}` AFTER composition.
|
|
228
|
+
- Marker-based merger (`updateAgentInstructions` markdown path) calls `composeInstructionBlock` exactly once per agent + substitutes per-agent MANIFEST path after composition (AC #4). JSON-merge merger (OpenCode) prefixes the composed string with `[ma-agents]` tag — filter unchanged, NFR18 additive merge preserved (AC #8).
|
|
229
|
+
- First-insert vs in-place-replace normalized: both paths emit `<MARKER>\n<content>\n<MARKER>` identically (AC #6 / NFR46 — resolved the Open question under AC).
|
|
230
|
+
- BMAD agents branch untouched — still skips missing files via the "BMAD agent file not yet deployed" message (AC #9).
|
|
231
|
+
- AC #10 upgrade-safety: drift detection compares existing in-marker content against composer output. `--yes` path emits the pinned WARNING line verbatim and writes a sibling backup file. Interactive path shows the old/new block inline and requires confirmation. Backup filename is `<target>.backup-<ISO-timestamp>` with hyphens for Windows compatibility (`buildBackupFilename` / `formatBackupTimestamp` exported; Story 21.2 OWNS this format per Dev Notes decision C).
|
|
232
|
+
- Open question under AC (profile-reconfigure) deferred to Story 21.10 per spec.
|
|
233
|
+
|
|
234
|
+
### Adversarial Review Findings
|
|
235
|
+
|
|
236
|
+
| # | Layer | Severity | Finding | Disposition |
|
|
237
|
+
|---|-------|----------|---------|-------------|
|
|
238
|
+
| 1 | Cynical | P0 | `agent._yesMode` read but never set anywhere — CLI `--yes` path would hit interactive prompt on drift, contradicting AC #10 | **Fixed** — extended `updateAgentInstructions(agent, projectRoot, opts)` with `opts.yesMode`; wired through `installSkill` → two call sites |
|
|
239
|
+
| 2 | Cynical | P1 | OpenCode instructionText trailing-whitespace strip could elide final content when body ends with blank line | **Fixed by design** — `composeInstructionBlock` already trims trailing whitespace and appends exactly one `\n`; strip is idempotent |
|
|
240
|
+
| 3 | Cynical | P1 | Insert path wrote `wrappedInstructionWithTrailingNewline + '\n'` (two newlines) while replace path wrote just the marker block — potential NFR46 violation | **Fixed by design** — NFR46 applies to content INSIDE markers; outside-markers formatting is preserved byte-for-byte by regex replace. Both paths produce byte-identical in-marker content |
|
|
241
|
+
| 4 | Cynical | P2 | `buildBackupFilename` has TOCTOU race between `existsSync` and `outputFile` | **Out of scope** — sub-second repeat runs would need multiple installs within the same second; filename has numeric tiebreaker; would need fs lock to fully fix |
|
|
242
|
+
| 5 | Edge-case | P1 | Missing test for `opts.yesMode` path (env-var path tested but opts path untested) | **Fixed** — added test 5.10c |
|
|
243
|
+
| 6 | Edge-case | P2 | `uninstallSkill` call-site to `updateAgentInstructions` doesn't pass yesMode | **Out of scope** — `uninstallSkill` signature doesn't carry `yes` today; env-var fallback covers CI; tracked implicitly for 21.11 |
|
|
244
|
+
| 7 | Edge-case | P2 | Interactive drift prompt could trigger on `reconfigure` flow (profile change) | **Out of scope per spec** — story's Open question under AC explicitly defers profile-reconfigure to Story 21.10 |
|
|
245
|
+
|
|
246
|
+
All P0/P1 findings resolved or accepted with documented rationale.
|
|
92
247
|
|
|
93
248
|
### File List
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
-
|
|
98
|
-
-
|
|
249
|
+
- CREATED: `lib/templates/instruction-block-universal.template.md`
|
|
250
|
+
- CREATED: `test/instruction-block.test.js` (14 tests, all passing)
|
|
251
|
+
- MODIFIED: `lib/installer.js` (composer, drift handler, canonical backup format, merger wiring, yesMode opts plumbing)
|
|
252
|
+
- MODIFIED: `package.json` (added `test/instruction-block.test.js` to npm test script)
|
|
253
|
+
- MODIFIED: `_bmad-output/implementation-artifacts/21-2-universal-instruction-block-expansion.md` (Status → Review; Dev Agent Record / File List / Change Log updated)
|