ma-agents 3.5.3 → 3.5.5
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/_bmad-output/implementation-artifacts/21-1-install-time-profile-prompt.md +181 -0
- package/_bmad-output/implementation-artifacts/21-10-profile-reconfigure.md +137 -0
- package/_bmad-output/implementation-artifacts/21-11-profile-uninstall.md +149 -0
- package/_bmad-output/implementation-artifacts/21-2-universal-instruction-block-expansion.md +98 -0
- package/_bmad-output/implementation-artifacts/21-3-roomodes-template-bmad-modes.md +106 -0
- package/_bmad-output/implementation-artifacts/21-4-agents-md-template-opencode.md +86 -0
- package/_bmad-output/implementation-artifacts/21-5-clinerules-template-extension.md +82 -0
- package/_bmad-output/implementation-artifacts/21-6-onprem-layered-guardrails.md +112 -0
- package/_bmad-output/implementation-artifacts/21-7-bmad-persona-phase-prefix.md +126 -0
- package/_bmad-output/implementation-artifacts/21-8-vllm-reference-doc-readme.md +100 -0
- package/_bmad-output/implementation-artifacts/21-9-tests-validation.md +97 -0
- package/_bmad-output/implementation-artifacts/bug-experimentalwarning-about-commonjs-loading-es-module-during-install.md +57 -0
- package/_bmad-output/implementation-artifacts/sprint-status.yaml +43 -1
- package/_bmad-output/methodology/BMAD_AI_Development_Training.pptx +0 -0
- package/_bmad-output/methodology/version.json +1 -1
- package/_bmad-output/planning-artifacts/architecture.md +52 -0
- package/_bmad-output/planning-artifacts/epics.md +397 -0
- package/_bmad-output/planning-artifacts/prd.md +46 -1
- package/bin/cli.js +109 -1
- package/docs/BMAD_AI_Development_Training.pptx +0 -0
- package/lib/bmad-cache/bmb/_git_preserved/index +0 -0
- package/lib/bmad-cache/bmb/_git_preserved/logs/HEAD +1 -1
- package/lib/bmad-cache/bmb/_git_preserved/logs/refs/heads/main +1 -1
- package/lib/bmad-cache/bmb/_git_preserved/logs/refs/remotes/origin/HEAD +1 -1
- package/lib/bmad-cache/bmb/_git_preserved/objects/pack/pack-554778ad4e7254827618ebd2497c3f4bce9054a4.idx +0 -0
- package/lib/bmad-cache/bmb/_git_preserved/objects/pack/{pack-4b395d030ca386fc5748f1b670dcf8c0ef41c94c.pack → pack-554778ad4e7254827618ebd2497c3f4bce9054a4.pack} +0 -0
- package/lib/bmad-cache/bmb/_git_preserved/objects/pack/pack-554778ad4e7254827618ebd2497c3f4bce9054a4.rev +0 -0
- package/lib/bmad-cache/bmb/_git_preserved/packed-refs +1 -1
- package/lib/bmad-cache/bmb/_git_preserved/refs/heads/main +1 -1
- package/lib/bmad-cache/bmb/_git_preserved/shallow +1 -1
- package/lib/bmad-cache/bmb/skills/bmad-module-builder/scripts/tests/test-scaffold-setup-skill.py +7 -0
- package/lib/bmad-cache/cache-manifest.json +5 -5
- package/lib/bmad-cache/tea/_git_preserved/index +0 -0
- package/lib/bmad-cache/tea/_git_preserved/objects/pack/pack-e75385cd52b693dbb8a3b2afb50058952543b3a2.idx +0 -0
- package/lib/bmad-cache/tea/_git_preserved/objects/pack/{pack-c79805bb3fee27fa6d8c612a971af7fc86fc80e1.pack → pack-e75385cd52b693dbb8a3b2afb50058952543b3a2.pack} +0 -0
- package/lib/bmad-cache/tea/_git_preserved/objects/pack/pack-e75385cd52b693dbb8a3b2afb50058952543b3a2.rev +0 -0
- package/lib/bmad-cache/tea/_git_preserved/packed-refs +1 -1
- package/lib/bmad-cache/tea/_git_preserved/refs/heads/main +1 -1
- package/lib/bmad-cache/tea/_git_preserved/refs/tags/v1.10.0 +1 -0
- package/lib/bmad-cache/tea/_git_preserved/shallow +1 -1
- package/lib/bmad-cache/tea/docs/explanation/tea-overview.md +2 -2
- package/lib/bmad-cache/tea/docs/how-to/workflows/run-atdd.md +28 -30
- package/lib/bmad-cache/tea/docs/reference/commands.md +4 -4
- package/lib/bmad-cache/tea/docs/reference/configuration.md +1 -1
- package/lib/bmad-cache/tea/package-lock.json +2 -2
- package/lib/bmad-cache/tea/package.json +1 -1
- package/lib/bmad-cache/tea/src/module-help.csv +1 -1
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/SKILL.md +1 -1
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/atdd-checklist-template.md +50 -27
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/checklist.md +18 -17
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/instructions.md +1 -1
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-01-preflight-and-context.md +21 -3
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-01b-resume.md +1 -1
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-02-generation-mode.md +1 -1
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-03-test-strategy.md +1 -1
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-04-generate-tests.md +20 -19
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-04a-subagent-api-failing.md +13 -13
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-04b-subagent-e2e-failing.md +13 -13
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-04c-aggregate.md +42 -18
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/steps-c/step-05-validate-and-complete.md +12 -3
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/workflow-plan.md +2 -2
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/workflow.md +2 -2
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/workflow.yaml +2 -2
- package/lib/bmad.js +25 -4
- package/lib/installer.js +2 -1
- package/lib/methodology/BMAD_AI_Development_Training.pptx +0 -0
- package/lib/methodology/version.json +1 -1
- package/lib/profile.js +107 -0
- package/lib/warning-filter.js +245 -0
- package/package.json +2 -2
- package/test/experimental-warning.test.js +314 -0
- package/test/fixtures/README.md +74 -0
- package/test/fixtures/empty-project/README.md +5 -0
- package/test/fixtures/empty-project/package.json +5 -0
- package/test/fixtures/onprem-profile-baseline/.gitkeep +2 -0
- package/test/fixtures/standard-profile-baseline/.gitkeep +2 -0
- package/test/onprem-injection.test.js +48 -0
- package/test/profile.test.js +301 -0
- package/lib/bmad-cache/bmb/_git_preserved/objects/pack/pack-4b395d030ca386fc5748f1b670dcf8c0ef41c94c.idx +0 -0
- package/lib/bmad-cache/bmb/_git_preserved/objects/pack/pack-4b395d030ca386fc5748f1b670dcf8c0ef41c94c.rev +0 -0
- package/lib/bmad-cache/tea/_git_preserved/objects/pack/pack-c79805bb3fee27fa6d8c612a971af7fc86fc80e1.idx +0 -0
- package/lib/bmad-cache/tea/_git_preserved/objects/pack/pack-c79805bb3fee27fa6d8c612a971af7fc86fc80e1.rev +0 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Story 21.1: Install-Time Profile Prompt and Persistence
|
|
2
|
+
|
|
3
|
+
Status: done
|
|
4
|
+
|
|
5
|
+
## Story
|
|
6
|
+
|
|
7
|
+
As an **engineer running `npx ma-agents install`**,
|
|
8
|
+
I want the installer to ask whether this is an on-prem / air-gapped install once and remember my answer,
|
|
9
|
+
So that on-prem-specific guardrails activate without me having to remember a CLI flag, and I am not re-prompted on subsequent installs.
|
|
10
|
+
|
|
11
|
+
## Acceptance Criteria
|
|
12
|
+
|
|
13
|
+
1. A new module `lib/profile.js` exists exposing exactly three functions: `getProfile(projectRoot)`, `setProfile(projectRoot, value)`, `resolveProfile({ persisted, yesMode })`. No other public API.
|
|
14
|
+
2. `getProfile(projectRoot)` reads `.ma-agents.json` at project root and returns the value of the top-level `"profile"` field, or `undefined` if the file or field is absent. It does not throw on missing file.
|
|
15
|
+
3. `setProfile(projectRoot, value)` writes `"profile": <value>` to the top-level of `.ma-agents.json`, preserving all existing fields (`manifestVersion`, `agent`, `agents`, `scope`, `skills`). When the file does not exist, it is created via the existing `ensureManifest` path so the standard schema is initialized.
|
|
16
|
+
4. `setProfile` accepts only the values `"on-prem"` and `"standard"`. Any other value throws an `Error` naming the bad value.
|
|
17
|
+
5. `resolveProfile({ persisted, yesMode })` returns the effective profile per this precedence: (a) `persisted` if set; (b) `"standard"` if `yesMode === true`; (c) `null` (caller must prompt). It does not perform any I/O.
|
|
18
|
+
6. The interactive wizard in `bin/cli.js` shows a single `prompts({ type: 'select' })` question after agent selection (Step 2) and before repository-layout selection (Step 2.5), with message `Is this an on-prem / air-gapped install using a local LLM (e.g. Nemotron)?` and choices `Yes — apply local-LLM guardrails (recommended for non-Claude models)` (value `on-prem`) and `No — standard install (Claude on web, Anthropic API, etc.)` (value `standard`). Default initial selection is the `No` (standard) row.
|
|
19
|
+
7. The wizard prompt is shown only when `resolveProfile()` returns `null`. When a value is already persisted or `--yes` is passed, no prompt appears and the resolved value is logged once using one of the canonical formats: `Using profile: <value> (from .ma-agents.json)`, `Using profile: standard (from --yes default)`, or `Using profile: <value> (from wizard)`. These formats are pinned in architecture.md P3-3 and referenced by tests verbatim.
|
|
20
|
+
8. After the wizard prompt is answered (or after resolver returns a non-null value), `setProfile` is called with the resolved value before any agent installation runs — guaranteeing the profile is persisted even if a later install step fails.
|
|
21
|
+
9. Direct install command (`npx ma-agents install <skill> --agent <agent>`) respects `--yes`: `--yes` without a persisted value defaults to `standard` and persists; absence of `--yes` and no persisted value triggers the same prompt as the wizard when stdin is a TTY, and defaults silently to `standard` (same as `--yes`) when stdin is not a TTY.
|
|
22
|
+
10. CI/CD path: `npx ma-agents install --yes` with no persisted profile writes `"profile": "standard"` to `.ma-agents.json` and proceeds without prompting (NFR45).
|
|
23
|
+
11. Re-running install with a persisted profile does NOT re-prompt and does NOT change the persisted value (NFR45).
|
|
24
|
+
12. The existing `manifestVersion` value is bumped from `1.1.0` to `1.2.0` in `ensureManifest` to reflect the new `"profile"` field. Manifests at `1.1.0` (no `profile`) are read without error — the `"profile"` field is treated as optional and absent equals `undefined`. Writing to an existing `1.1.0` manifest via `setProfile` MIGRATES `manifestVersion` to `1.2.0` (the `profile` field is only valid under the 1.2.0 schema — carrying it on a 1.1.0 manifest would be schema drift). The migration is logged once per install: `Migrated .ma-agents.json manifestVersion 1.1.0 → 1.2.0`. Fresh manifests bootstrapped via `ensureManifest` start at `1.2.0` directly. NOTE: This migration behavior is a Step 2 follow-up code change (tracked separately); this AC documents the intended end state.
|
|
25
|
+
|
|
26
|
+
## Tasks / Subtasks
|
|
27
|
+
|
|
28
|
+
- [x] Task 1: Create `lib/profile.js` (AC: #1–#5)
|
|
29
|
+
- [x] 1.1 Create `lib/profile.js` with `'use strict'` and the three exports
|
|
30
|
+
- [x] 1.2 Implement `getProfile` reading via existing `readManifest`-style path; return `undefined` if file or field missing
|
|
31
|
+
- [x] 1.3 Implement `setProfile` with value validation; reuse `ensureManifest` to bootstrap the file when absent; preserve all other fields on write
|
|
32
|
+
- [x] 1.4 Implement `resolveProfile` as a pure function (no I/O) per the precedence in AC #5
|
|
33
|
+
|
|
34
|
+
- [x] Task 2: Add wizard prompt (AC: #6, #7, #8)
|
|
35
|
+
- [x] 2.1 In the wizard flow (cli.js:655–900), after Step 2 (agent selection, ~line 829) and before Step 2.5 (repository layout), call `resolveProfile({ persisted: getProfile(projectRoot), yesMode: yesFlag })`
|
|
36
|
+
- [x] 2.2 If the result is `null`, run the `prompts({ type: 'select' })` question per AC #6 (initial index = 1 for the "No" row)
|
|
37
|
+
- [x] 2.3 Log `Using profile: <value> (from <source>)` exactly once
|
|
38
|
+
- [x] 2.4 Call `setProfile(projectRoot, resolvedValue)` BEFORE any agent install step (AC #8)
|
|
39
|
+
|
|
40
|
+
- [x] Task 3: Direct install path parity (AC: #9, #10)
|
|
41
|
+
- [x] 3.1 Apply the same `resolveProfile` + prompt fallback logic in the direct-install path of `handleInstall`
|
|
42
|
+
- [x] 3.2 When stdin is not a TTY and resolver returns `null`, silently default to `standard` (same as `--yes`) and persist — do not abort
|
|
43
|
+
|
|
44
|
+
- [x] Task 4: Manifest version bump + back-compat (AC: #12)
|
|
45
|
+
- [x] 4.1 In `ensureManifest` (installer.js:64-70), change `manifestVersion: '1.1.0'` to `'1.2.0'`
|
|
46
|
+
- [x] 4.2 Existing files at `1.1.0` continue to load — do NOT migrate or rewrite the version on read; only writes through `setProfile` / `ensureManifest` produce `1.2.0`. A `1.1.0` file with no `profile` field is valid; `getProfile` returns `undefined`
|
|
47
|
+
|
|
48
|
+
- [x] Task 5: Unit tests in `test/profile.test.js` (covers all ACs)
|
|
49
|
+
- [x] 5.1 `getProfile` returns `undefined` when `.ma-agents.json` does not exist (AC #2)
|
|
50
|
+
- [x] 5.2 `getProfile` returns `undefined` when file exists but `profile` field is absent (AC #2, #12 back-compat)
|
|
51
|
+
- [x] 5.3 `getProfile` returns the persisted value when present (AC #2)
|
|
52
|
+
- [x] 5.4 `setProfile` creates the file with the standard schema when absent (AC #3)
|
|
53
|
+
- [x] 5.5 `setProfile` preserves existing fields (`manifestVersion`, `agent`, `agents`, `scope`, `skills`) when updating (AC #3)
|
|
54
|
+
- [x] 5.6 `setProfile` throws on invalid value with the bad value in the error message (AC #4)
|
|
55
|
+
- [x] 5.7 `resolveProfile` precedence — persisted wins over yes-default (AC #5)
|
|
56
|
+
- [x] 5.8 `resolveProfile` precedence — yes-default returns `standard` only when no persisted (AC #5)
|
|
57
|
+
- [x] 5.9 `resolveProfile` returns `null` when nothing is set and `yesMode === false` (AC #5)
|
|
58
|
+
- [x] 5.10 `resolveProfile` does not perform I/O (AC #5 — verified by spying on `fs`)
|
|
59
|
+
- [x] 5.11 Round-trip: `setProfile('on-prem')` then `getProfile()` returns `'on-prem'` (AC #2/#3)
|
|
60
|
+
- [x] 5.12 Re-running `setProfile` with same value is idempotent (file content byte-identical except potentially `installedAt` if other fields update — but profile field stable)
|
|
61
|
+
|
|
62
|
+
- [ ] Task 6: Integration test in `test/onprem-injection.test.js` (deferred to Story 21.9 — list as TODO here, do not implement)
|
|
63
|
+
|
|
64
|
+
## Dev Notes
|
|
65
|
+
|
|
66
|
+
### Architecture Compliance
|
|
67
|
+
|
|
68
|
+
- **Decision P3-3** (Local-LLM / On-Prem Agent Tuning Profile) — this story implements the `lib/profile.js` module and the install-time prompt. Stories 21.2–21.7 consume `getProfile()` to gate content. Stories 21.8 (docs) and 21.9 (tests) close the epic.
|
|
69
|
+
- **NFR45** (CI/CD compatibility) — `--yes` defaults to `standard`; non-TTY direct installs silently default to `standard`; persisted answer is not re-prompted.
|
|
70
|
+
- **NFR44** (profile isolation) — `setProfile` is the only mutator; no other code path writes the `profile` field. Standard profile installs must produce no on-prem-specific output (verified in Story 21.9).
|
|
71
|
+
|
|
72
|
+
### Source Tree Components to Touch
|
|
73
|
+
|
|
74
|
+
| File | Change |
|
|
75
|
+
|------|--------|
|
|
76
|
+
| `lib/profile.js` | CREATE — `getProfile`, `setProfile`, `resolveProfile` |
|
|
77
|
+
| `lib/installer.js` | MODIFY — `manifestVersion` bump 1.1.0 → 1.2.0 in `ensureManifest` (line ~64-70) |
|
|
78
|
+
| `bin/cli.js` | MODIFY — wizard prompt insertion after Step 2 (line ~829); direct-install path resolution in `handleInstall` |
|
|
79
|
+
| `test/profile.test.js` | CREATE — unit tests per Task 6 |
|
|
80
|
+
|
|
81
|
+
### Library and Pattern References
|
|
82
|
+
|
|
83
|
+
- Prompts library: **`prompts`** (npm) — same as everywhere else in the wizard. Existing example to mirror: `lib/installer.js:625-631` (the repo-layout `select` prompt).
|
|
84
|
+
- Manifest read/write: reuse `readManifest` / `writeManifest` / `ensureManifest` in `lib/installer.js:23-70`. Do not create a parallel JSON-IO path. `setProfile` may import these or, if circular-import risk exists, replicate the minimal write pattern (`fs.writeFileSync(path, JSON.stringify(obj, null, 2) + '\n', 'utf-8')`).
|
|
85
|
+
- Flag parsing site: `bin/cli.js:170-176`. No new flag is added — profile is resolved from persisted state or `--yes` default only.
|
|
86
|
+
|
|
87
|
+
### Wizard Insertion Point
|
|
88
|
+
|
|
89
|
+
Per the explore report, wizard step order is:
|
|
90
|
+
- Step 0: Existing installation check (~668)
|
|
91
|
+
- Step 1: Skill selection (~737)
|
|
92
|
+
- Step 2: Agent selection (IDE + BMAD multiselects, ~776–804)
|
|
93
|
+
- **Step 2.4 (NEW): Profile prompt** ← insert here
|
|
94
|
+
- Step 2.5: Repository layout (~829)
|
|
95
|
+
- Step 3: Installation scope (~832)
|
|
96
|
+
|
|
97
|
+
Place the call so that the profile is resolved and persisted BEFORE repo-layout because future stories may want to gate layout-related guidance on profile (out of scope for this story, but the order is correct).
|
|
98
|
+
|
|
99
|
+
### `.ma-agents.json` Example After This Story
|
|
100
|
+
|
|
101
|
+
Standard profile, fresh install:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"manifestVersion": "1.2.0",
|
|
106
|
+
"agent": "claude-code",
|
|
107
|
+
"agents": ["claude-code"],
|
|
108
|
+
"scope": "project",
|
|
109
|
+
"profile": "standard",
|
|
110
|
+
"skills": {}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
On-prem profile after install:
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"manifestVersion": "1.2.0",
|
|
119
|
+
"agent": "claude-code",
|
|
120
|
+
"agents": ["claude-code", "roo-code"],
|
|
121
|
+
"scope": "project",
|
|
122
|
+
"profile": "on-prem",
|
|
123
|
+
"skills": { "...": "..." }
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Out of Scope for This Story
|
|
128
|
+
|
|
129
|
+
- Per-tool template content (Stories 21.2–21.5)
|
|
130
|
+
- On-prem layered guardrail content (Story 21.6)
|
|
131
|
+
- BMAD persona phase prefix (Story 21.7)
|
|
132
|
+
- vLLM deployment doc (Story 21.8)
|
|
133
|
+
- Integration test asserting standard vs on-prem produces correct injection content (Story 21.9)
|
|
134
|
+
|
|
135
|
+
This story is foundation only: prompt + persistence + read API. Downstream stories consume `getProfile(projectRoot)` and branch on its value.
|
|
136
|
+
|
|
137
|
+
### Testing Standards
|
|
138
|
+
|
|
139
|
+
Match the existing test layout in `test/` (mocha-style or node:test — verify by reading one existing test file first). Place `test/profile.test.js` next to `test/generate-project-context.test.js`. All tests must run as part of `npm test`. Use a temporary directory per test (e.g., `os.tmpdir()` + `fs.mkdtempSync`) to isolate `.ma-agents.json` reads/writes — never mutate the repo's own `.ma-agents.json`.
|
|
140
|
+
|
|
141
|
+
### Epic 21 Cross-Story Context
|
|
142
|
+
|
|
143
|
+
**Story 21.1 (this):** Profile prompt + `lib/profile.js` API + persistence. No template content yet.
|
|
144
|
+
**Story 21.2:** Universal instruction-block expansion (consumes `getProfile` indirectly via installer composition function).
|
|
145
|
+
**Stories 21.3–21.5:** Per-tool templates (`.roomodes`, `AGENTS.md`, `.clinerules`).
|
|
146
|
+
**Story 21.6:** On-prem layered guardrails — first story that branches behavior on `profile === 'on-prem'`.
|
|
147
|
+
**Story 21.7:** BMAD persona phase prefix in customize-loader.
|
|
148
|
+
**Story 21.8:** vLLM reference doc + README on-prem section.
|
|
149
|
+
**Story 21.9:** Tests covering NFR44, NFR46, NFR47 (integration-level).
|
|
150
|
+
|
|
151
|
+
## Dev Agent Record
|
|
152
|
+
|
|
153
|
+
### Agent Model Used
|
|
154
|
+
Claude Opus 4.6 (1M context) — bmad-dev-story + bmad-code-review flow.
|
|
155
|
+
|
|
156
|
+
### Debug Log References
|
|
157
|
+
- Initial `npm test` run hit a pre-existing `bmad-version-bump` failure in the worktree due to missing `node_modules/bmad-method/package.json`. Resolved by running `npm install` in the worktree; not related to this story's changes.
|
|
158
|
+
- Self-review finding (Cynical Review layer): initial implementation used `installPath` (the custom skills install target) as the project root for `.ma-agents.json` in the wizard path, which is wrong — `.ma-agents.json` lives at `process.cwd()`, not the skills install dir. Corrected before opening the PR.
|
|
159
|
+
|
|
160
|
+
### Completion Notes List
|
|
161
|
+
- `lib/profile.js` exposes exactly the three required functions (`getProfile`, `setProfile`, `resolveProfile`). `resolveProfile` is pure — verified by fs-spy test (6.11).
|
|
162
|
+
- `lib/installer.js`: bumped `MANIFEST_VERSION` from `1.1.0` → `1.2.0`; added `ensureManifest` to `module.exports` so `setProfile` can bootstrap fresh manifests without duplicating JSON-IO logic.
|
|
163
|
+
- `bin/cli.js`: Both wizard and direct-install paths call `setProfile` BEFORE any agent install step (AC #8) and log `Using profile: <value> (from <source>)` exactly once. No CLI flag is introduced — profile is sourced from persisted `.ma-agents.json` or the `--yes` default.
|
|
164
|
+
- Wizard insertion point is "Step 2.4" — after agent selection, before repo-layout (matches story spec). Direct-install path silently defaults to `standard` when stdin is not a TTY (mirrors `--yes`) rather than aborting.
|
|
165
|
+
- Manifest back-compat: reads never migrate the version. A 1.1.0 file without `profile` reads as `undefined`; writing `setProfile` onto an existing 1.1.0 manifest adds the `profile` field while preserving the original `manifestVersion`. Fresh manifests created through `ensureManifest` are initialized at 1.2.0.
|
|
166
|
+
- Task 6 (integration test) intentionally left unchecked — explicitly deferred to Story 21.9 per spec.
|
|
167
|
+
|
|
168
|
+
### File List
|
|
169
|
+
- CREATED: `lib/profile.js`
|
|
170
|
+
- CREATED: `test/profile.test.js` (14 unit tests, all passing)
|
|
171
|
+
- MODIFIED: `lib/installer.js` (manifest version bump; export `ensureManifest`)
|
|
172
|
+
- MODIFIED: `bin/cli.js` (wizard Step 2.4 profile prompt; direct-install profile resolution with non-TTY standard default)
|
|
173
|
+
- MODIFIED: `package.json` (added `test/profile.test.js` to npm test script)
|
|
174
|
+
|
|
175
|
+
## Change Log
|
|
176
|
+
- 2026-04-14: Story created (Epic 21, Story 21.1)
|
|
177
|
+
- 2026-04-14: Story 21.1 implemented — `lib/profile.js` + install-time prompt + manifest 1.2.0 bump; status moved ready-for-dev → review.
|
|
178
|
+
- 2026-04-13: Spec correction — `--profile=` CLI flag removed (never wanted); `resolveProfile` reduced to 2-arg `{ persisted, yesMode }`; direct-install non-TTY path now defaults to `standard` silently instead of aborting. AC count reduced 14 → 11.
|
|
179
|
+
- 2026-04-14: Story delivered via PR #32 (feature/story-21-1-install-time-profile). Status → done. Dev Notes aligned with no-flag correction (removed stale `--profile=` references in Architecture Compliance, Source Tree Components, Library and Pattern References).
|
|
180
|
+
- 2026-04-14: AC #12 clarified — `setProfile` on 1.1.0 manifest migrates to 1.2.0 (no schema drift). Migration implementation tracked as Step 2 follow-up (separate PR).
|
|
181
|
+
- 2026-04-14: AC #7 log-string enum expanded to match canonical formats pinned in architecture.md P3-3 ("from wizard" source added).
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Story 21.10: Profile Reconfigure
|
|
2
|
+
|
|
3
|
+
Status: ready-for-dev
|
|
4
|
+
|
|
5
|
+
## Story
|
|
6
|
+
|
|
7
|
+
As an **engineer who needs to change a previously-persisted profile**,
|
|
8
|
+
I want a `ma-agents reconfigure` subcommand that re-runs the profile prompt and re-stamps all profile-dependent artifacts,
|
|
9
|
+
So that a CI-default `standard` profile or a mistaken interactive answer can be corrected without hand-editing `.ma-agents.json`.
|
|
10
|
+
|
|
11
|
+
## Acceptance Criteria
|
|
12
|
+
|
|
13
|
+
1. A new CLI subcommand `npx ma-agents reconfigure` exists. It accepts no positional arguments and the usual flags where relevant (`--yes`, scope flags if present — see AC #6 for `--yes` handling). Running it in a project without `.ma-agents.json` exits with a named error and a hint to run `install` first — it does NOT silently bootstrap a manifest, because reconfigure presumes a prior install.
|
|
14
|
+
2. `reconfigure` shows the same profile-selection prompt Story 21.1's wizard shows, but with the currently-persisted value as the default highlighted option. The prompt text references the current value: `Current profile: <value>. Change to?`.
|
|
15
|
+
3. After the user picks a value (or confirms the existing one), `reconfigure` writes the resolved value via `setProfile` (reusing `lib/profile.js` unchanged). Logging follows the canonical log-string formats pinned in architecture.md P3-3 — the source of the write is `wizard` for this command (matching the `(from wizard)` suffix used by the install wizard).
|
|
16
|
+
4. If the newly-resolved profile equals the previously-persisted value, `reconfigure` reports `Profile unchanged: <value>. No re-stamp needed.` and exits 0 without touching any files.
|
|
17
|
+
5. If the profile CHANGED, `reconfigure` re-stamps ALL profile-dependent artifacts:
|
|
18
|
+
- The instruction-block-injection files (`CLAUDE.md`, `.clinerules`, `.cline/clinerules.md`, `.roo/rules/00-ma-agents.md`, `AGENTS.md`)
|
|
19
|
+
- The `.roomodes` file (per-mode `customInstructions`)
|
|
20
|
+
- The BMAD customize-loader output (if Story 21.7 has landed — if it has not, this bullet is a no-op and the Task 3 subitems are skipped)
|
|
21
|
+
Implementation strategy: reuse the exact injection functions the installer uses; do NOT fork logic.
|
|
22
|
+
6. `reconfigure` with `--yes` is explicitly rejected: the command exits nonzero with the message `--yes is not valid for reconfigure — this command is interactive by design to prevent accidental CI-triggered profile changes.` Rationale: Finding #5 is that CI quietly persists `standard`; we must not allow CI to silently flip profiles in either direction.
|
|
23
|
+
7. Before re-stamping, `reconfigure` prints the list of files that will be modified and asks for a single yes/no confirmation: `The following files will be updated to match profile=<new>: [list]. Continue?`. This is in addition to the profile-selection prompt in AC #2 — two-step confirmation for destructive scope.
|
|
24
|
+
8. `reconfigure` backs up each to-be-overwritten marker-block region to `<target>.backup-<ISO-timestamp>` before re-stamping. Same backup convention as Story 21.2 AC #8 — reuse if already implemented.
|
|
25
|
+
9. `reconfigure` respects Story 21.3 AC #9 slug-stomp protection: if user-edited ma-agents-owned `.roomodes` slugs are detected, the command aborts with the same `RoomodesSlugDivergenceError` and instructs the user to either rename the slug or pass `--force-roomodes-overwrite`. `--force-roomodes-overwrite` IS honored by `reconfigure` (unlike `--yes`) because it is a deliberate, user-supplied override.
|
|
26
|
+
10. `reconfigure` respects Story 21.5 AC #6 `.clinerules` dual-file drift detection: if the two Cline rule files (`.clinerules` and `.cline/clinerules.md`) have divergent marker-block content, the command aborts with `ClinerulesDualFileDriftError`. The user reconciles manually before retrying. No override flag is provided for this case.
|
|
27
|
+
11. A new log event is appended to `.ma-agents.json::profileHistory` (new field, append-only, capped at 20 entries — oldest entries dropped first when the cap is reached): `{ date: <ISO>, from: <previous>, to: <new>, source: "reconfigure" }`. The field is created on first reconfigure; a missing field on read means no prior reconfigure event. Gives operators a forensic trail for "when did this project's profile last change, and from what?"
|
|
28
|
+
|
|
29
|
+
## Tasks / Subtasks
|
|
30
|
+
|
|
31
|
+
- [ ] Task 1: Register `reconfigure` subcommand in `bin/cli.js` (AC: #1, #6)
|
|
32
|
+
- [ ] 1.1 Add `reconfigure` case to the top-level command dispatcher
|
|
33
|
+
- [ ] 1.2 Reject `--yes` with the pinned error message per AC #6 (exit nonzero)
|
|
34
|
+
- [ ] 1.3 When `.ma-agents.json` is absent, exit with a named error and a hint to run `install` first (AC #1)
|
|
35
|
+
- [ ] 1.4 Route to the new `lib/reconfigure.js` orchestrator
|
|
36
|
+
|
|
37
|
+
- [ ] Task 2: Create `lib/reconfigure.js` orchestrator (AC: #2, #3, #4, #7)
|
|
38
|
+
- [ ] 2.1 Load persisted profile via `getProfile()`; abort if absent (AC #1)
|
|
39
|
+
- [ ] 2.2 Show profile-selection prompt with persisted value as default (AC #2)
|
|
40
|
+
- [ ] 2.3 If resolved value equals persisted value, log `Profile unchanged: <value>. No re-stamp needed.` and exit 0 (AC #4)
|
|
41
|
+
- [ ] 2.4 Otherwise, compute the list of files that will be touched; print the list and ask the `Continue?` confirmation (AC #7)
|
|
42
|
+
- [ ] 2.5 Call `setProfile(projectRoot, newValue)` and log via the canonical `Using profile: <value> (from wizard)` format (AC #3)
|
|
43
|
+
|
|
44
|
+
- [ ] Task 3: Re-stamp orchestration (AC: #5)
|
|
45
|
+
- [ ] 3.1 Reuse the injection helpers from `lib/installer.js` for the 5 instruction-block-injection files
|
|
46
|
+
- [ ] 3.2 Reuse the `.roomodes` merger from `lib/merge/roomodes.js` (Story 21.3) for `customInstructions`
|
|
47
|
+
- [ ] 3.3 If Story 21.7 has landed, invoke the BMAD customize-loader re-stamp; otherwise skip (documented as conditional)
|
|
48
|
+
- [ ] 3.4 Ensure zero duplication with installer stamping logic — do NOT fork
|
|
49
|
+
|
|
50
|
+
- [ ] Task 4: Backup step (AC: #8)
|
|
51
|
+
- [ ] 4.1 Before each overwrite, capture the current marker-block region and write to `<target>.backup-<ISO-timestamp>`
|
|
52
|
+
- [ ] 4.2 Use UTC ISO format for the timestamp (stable across locales)
|
|
53
|
+
- [ ] 4.3 Reuse Story 21.2 backup helper if present; otherwise implement inline and note the refactor opportunity
|
|
54
|
+
|
|
55
|
+
- [ ] Task 5: Slug-stomp protection integration (AC: #9)
|
|
56
|
+
- [ ] 5.1 Before touching `.roomodes`, run the Story 21.3 AC #9 slug-divergence check
|
|
57
|
+
- [ ] 5.2 On divergence, throw `RoomodesSlugDivergenceError` with a message instructing rename or `--force-roomodes-overwrite`
|
|
58
|
+
- [ ] 5.3 Honor `--force-roomodes-overwrite` when supplied — user-supplied override is allowed
|
|
59
|
+
|
|
60
|
+
- [ ] Task 6: Clinerules dual-file drift detection (AC: #10)
|
|
61
|
+
- [ ] 6.1 Before touching Cline rule files, compare the marker-block content of `.clinerules` and `.cline/clinerules.md`
|
|
62
|
+
- [ ] 6.2 On divergence, throw `ClinerulesDualFileDriftError` with a message pointing the user at both files; no override flag
|
|
63
|
+
|
|
64
|
+
- [ ] Task 7: `profileHistory` append (AC: #11)
|
|
65
|
+
- [ ] 7.1 After a successful re-stamp, append `{ date, from, to, source: "reconfigure" }` to `.ma-agents.json::profileHistory`
|
|
66
|
+
- [ ] 7.2 Create the field on first use; cap at 20 entries (drop oldest first)
|
|
67
|
+
- [ ] 7.3 Ensure the write goes through `setProfile` or an equivalent atomic write path (no parallel JSON-IO)
|
|
68
|
+
|
|
69
|
+
- [ ] Task 8: Tests in `test/reconfigure.test.js` (covers all ACs)
|
|
70
|
+
- [ ] 8.1 `--yes` is rejected with pinned message and nonzero exit (AC #6)
|
|
71
|
+
- [ ] 8.2 Missing `.ma-agents.json` exits with named error (AC #1)
|
|
72
|
+
- [ ] 8.3 Same-value confirmation exits with `Profile unchanged:` and no file writes (AC #4)
|
|
73
|
+
- [ ] 8.4 Profile change from `standard` to `on-prem` re-stamps all injection files + `.roomodes` (AC #5)
|
|
74
|
+
- [ ] 8.5 Two-step confirmation — selecting "no" at the `Continue?` prompt aborts with zero file changes (AC #7)
|
|
75
|
+
- [ ] 8.6 Backup files created at `<target>.backup-<ISO-timestamp>` before each overwrite (AC #8)
|
|
76
|
+
- [ ] 8.7 Slug divergence raises `RoomodesSlugDivergenceError`; `--force-roomodes-overwrite` bypasses (AC #9)
|
|
77
|
+
- [ ] 8.8 Dual-file drift raises `ClinerulesDualFileDriftError`; no override flag (AC #10)
|
|
78
|
+
- [ ] 8.9 `profileHistory` append — first reconfigure creates the field; subsequent reconfigures append; cap at 20 drops oldest (AC #11)
|
|
79
|
+
|
|
80
|
+
## Dev Notes
|
|
81
|
+
|
|
82
|
+
### Architecture Compliance
|
|
83
|
+
|
|
84
|
+
- **Decision P3-3** (Local-LLM / On-Prem Agent Tuning Profile) — `reconfigure` extends the profile API established in Story 21.1 without changing `lib/profile.js`'s public contract. The three exports stay exactly as defined in Story 21.1 AC #1: `getProfile`, `setProfile`, `resolveProfile`. `reconfigure` is a new consumer, not a new API.
|
|
85
|
+
- **NFR44** (profile isolation) — standard-profile output must remain free of on-prem strings after a `reconfigure` that lands on `standard`. Reuse of the installer's injection helpers (AC #5) is what guarantees this — forked re-stamp logic would risk drift.
|
|
86
|
+
- **NFR45** (CI/CD compatibility) — `reconfigure` is *deliberately* incompatible with CI (`--yes` blocked per AC #6). This is not a regression of NFR45; NFR45 covers `install`, not `reconfigure`. CI never needs to call `reconfigure`; profile changes are a human, interactive act.
|
|
87
|
+
- **NFR46** (idempotency) — after `reconfigure`, running `install` with the same profile must produce byte-identical output. Verified in Task 8 by running `install` after `reconfigure` and diffing marker-block regions.
|
|
88
|
+
|
|
89
|
+
### Source Tree Components to Touch
|
|
90
|
+
|
|
91
|
+
| File | Change |
|
|
92
|
+
|------|--------|
|
|
93
|
+
| `bin/cli.js` | MODIFY — register `reconfigure` subcommand; route to new handler |
|
|
94
|
+
| `lib/reconfigure.js` | CREATE — orchestrates prompt → confirmation → backup → re-stamp → history append |
|
|
95
|
+
| `lib/installer.js` | MINOR — export injection functions currently internal if `reconfigure` needs to call them directly (avoid duplication) |
|
|
96
|
+
| `lib/profile.js` | UNCHANGED — `setProfile` already does the right thing post-Step 2 of the corrective plan |
|
|
97
|
+
| `test/reconfigure.test.js` | CREATE |
|
|
98
|
+
|
|
99
|
+
### Library and Pattern References
|
|
100
|
+
|
|
101
|
+
- Prompts library: **`prompts`** (npm) — same as the Story 21.1 wizard. Reuse the exact prompt shape; override only the `initial` index (to highlight the currently-persisted value) and the message text (`Current profile: <value>. Change to?`).
|
|
102
|
+
- Injection helpers: reuse from `lib/installer.js` for the 5 instruction-block-injection files; reuse from `lib/merge/roomodes.js` (Story 21.3) for `.roomodes`.
|
|
103
|
+
- `setProfile` from `lib/profile.js` — unchanged public contract.
|
|
104
|
+
- Backup naming convention: `<target>.backup-<ISO-timestamp>` per Story 21.2 AC #8.
|
|
105
|
+
- Error class names must match upstream exactly: `RoomodesSlugDivergenceError` (Story 21.3 AC #9), `ClinerulesDualFileDriftError` (Story 21.5 AC #6).
|
|
106
|
+
|
|
107
|
+
### Out of Scope
|
|
108
|
+
|
|
109
|
+
- Profile migration / data migration — none required. `.ma-agents.json` schema already has the `profile` field (Story 21.1 AC #12). Only the new optional `profileHistory` field is added; its absence is valid.
|
|
110
|
+
- Programmatic re-stamp without a human — explicitly forbidden by AC #6. `--yes` is blocked.
|
|
111
|
+
- Changing `lib/profile.js`'s public API — Story 21.1 AC #1 pinned three exports; `reconfigure` is a consumer, not a modifier.
|
|
112
|
+
- Auto-rollback on failed re-stamp — out of scope for this story; failures surface as errors and leave the backup files available for manual recovery. A future story may add transactional re-stamp.
|
|
113
|
+
|
|
114
|
+
### Testing Standards
|
|
115
|
+
|
|
116
|
+
Match the existing test layout (node:test or mocha-style — verify by reading one existing test file first). Place `test/reconfigure.test.js` next to `test/profile.test.js`. All tests must run as part of `npm test`. Use `fs.mkdtempSync(os.tmpdir() + ...)` per test to isolate `.ma-agents.json` reads/writes — never mutate the repo's own `.ma-agents.json` or instruction files. Mock `prompts` for deterministic test runs (the installer tests already do this — reuse the pattern).
|
|
117
|
+
|
|
118
|
+
### Epic 21 Cross-Story Context
|
|
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.
|
|
121
|
+
|
|
122
|
+
## Dev Agent Record
|
|
123
|
+
|
|
124
|
+
### Agent Model Used
|
|
125
|
+
_(to be filled by dev agent)_
|
|
126
|
+
|
|
127
|
+
### Debug Log References
|
|
128
|
+
_(to be filled by dev agent)_
|
|
129
|
+
|
|
130
|
+
### Completion Notes List
|
|
131
|
+
_(to be filled by dev agent)_
|
|
132
|
+
|
|
133
|
+
### File List
|
|
134
|
+
_(to be filled by dev agent)_
|
|
135
|
+
|
|
136
|
+
## Change Log
|
|
137
|
+
- 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).
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Story 21.11: Profile Uninstall
|
|
2
|
+
|
|
3
|
+
Status: ready-for-dev
|
|
4
|
+
|
|
5
|
+
## Story
|
|
6
|
+
|
|
7
|
+
As an **engineer migrating a project away from ma-agents or switching from on-prem to a different deployment model**,
|
|
8
|
+
I want a `ma-agents uninstall --profile-artifacts` subcommand that removes all ma-agents-owned profile-dependent content while preserving user content,
|
|
9
|
+
So that I can cleanly reverse the install without hand-editing every generated file or leaving dead guardrails that no longer reflect the project's actual deployment.
|
|
10
|
+
|
|
11
|
+
## Acceptance Criteria
|
|
12
|
+
|
|
13
|
+
1. The existing `ma-agents uninstall` subcommand (if already present) gains a new flag `--profile-artifacts`. If no `uninstall` subcommand exists yet, this story creates it with the `--profile-artifacts` flag as the first supported mode. CLI help text documents the flag. The command accepts optional `--yes` to bypass the confirmation prompt in CI (unlike `reconfigure`, uninstall IS a legitimate CI use case — e.g., a migration script that decommissions a project's ma-agents integration).
|
|
14
|
+
2. On invocation, `ma-agents uninstall --profile-artifacts` lists every file and region it will modify and asks a yes/no confirmation: `The following ma-agents-owned content will be removed: [list]. User content outside markers will be preserved. Continue?`. With `--yes`, the prompt is skipped and the operation proceeds (documented clearly — `--yes` HERE is a deliberate CI affordance, different from its rejection in `reconfigure`).
|
|
15
|
+
3. For each instruction-injection file with `<!-- MA-AGENTS-START -->` / `<!-- MA-AGENTS-END -->` markers (`CLAUDE.md`, `.clinerules`, `.cline/clinerules.md`, `.roo/rules/00-ma-agents.md`, `AGENTS.md`), the command removes the marker block AND the markers themselves, leaving only the user content outside. If the file becomes empty (or contains only whitespace) after removal, the file is deleted.
|
|
16
|
+
4. For `.roomodes`, the command removes ONLY the four ma-agents-owned `customModes` slugs (`bmad-pm`, `bmad-architect`, `bmad-techlead`, `bmad-dev`). Any user-defined `customModes` are preserved untouched. If after removal the file contains an empty `customModes` array and no other top-level content, the file is deleted.
|
|
17
|
+
5. For each removed file or region, the command writes a backup to `<target>.backup-<ISO-timestamp>` before removal — same backup convention as Story 21.2 AC #8 and Story 21.10 AC #8. This provides a recovery path if the user changes their mind within the shell session.
|
|
18
|
+
6. The `profile` field in `.ma-agents.json` is cleared (set to undefined / deleted, not written as empty string). `profileHistory` (from Story 21.10) is PRESERVED — it is an audit trail and must survive uninstall. A new history entry is appended: `{ date: <ISO>, from: <previous>, to: null, source: "uninstall" }`.
|
|
19
|
+
7. The `roomodesOverwriteLog` field (from Story 21.3 AC #10) is PRESERVED — also audit trail. No entries removed.
|
|
20
|
+
8. The `manifestVersion` is NOT downgraded (stays at `1.2.0`). Rationale: `manifestVersion` tracks schema capability, not content presence. Other ma-agents-owned fields (e.g., `skills`, `agents`) are untouched by `--profile-artifacts` — this flag scopes to profile-related artifacts only, not the whole ma-agents install. A user wanting complete removal runs `ma-agents uninstall` without `--profile-artifacts` (scope of that mode is defined by existing uninstall behavior, if any, and is out of scope for this story).
|
|
21
|
+
9. If slug-stomp protection (Story 21.3 AC #9) is armed — i.e., user-edited ma-agents-owned `.roomodes` slug bodies have diverged from the template — `uninstall --profile-artifacts` warns the user but PROCEEDS with removal (after the AC #2 confirmation). Rationale: the user is explicitly asking to remove the slugs; divergence is not a blocker here because the user is accepting the loss. The divergent bodies are captured in the backup file per AC #5. A one-line warning is emitted: `WARNING: removing hand-edited ma-agents-owned slugs [list]. Previous bodies backed up to .roomodes.backup-<ISO>.`
|
|
22
|
+
10. Uninstall is idempotent — running `ma-agents uninstall --profile-artifacts` twice in a row on the same project produces no errors and no additional changes the second time. The second run exits 0 with the message `No ma-agents-owned profile artifacts found. Nothing to do.`
|
|
23
|
+
|
|
24
|
+
## Tasks / Subtasks
|
|
25
|
+
|
|
26
|
+
- [ ] Task 1: CLI registration in `bin/cli.js` (AC: #1, #2)
|
|
27
|
+
- [ ] 1.1 If `uninstall` subcommand does not exist yet, register it; otherwise add the `--profile-artifacts` flag to the existing dispatch
|
|
28
|
+
- [ ] 1.2 Accept `--yes` (documented CI affordance, differs from `reconfigure`)
|
|
29
|
+
- [ ] 1.3 Document the flag in CLI help text, including the `--yes` asymmetry vs `reconfigure`
|
|
30
|
+
- [ ] 1.4 Route to the new `lib/uninstall.js::uninstallProfileArtifacts` handler
|
|
31
|
+
|
|
32
|
+
- [ ] Task 2: Orchestrator module `lib/uninstall.js` (AC: #2, #3, #4, #6, #7, #10)
|
|
33
|
+
- [ ] 2.1 Export `uninstallProfileArtifacts(projectRoot, { yes })`
|
|
34
|
+
- [ ] 2.2 Detect every target file/region; if none present, print the idempotent no-op message and exit 0 (AC #10)
|
|
35
|
+
- [ ] 2.3 Print the target list and ask the `Continue?` confirmation unless `--yes` was passed (AC #2)
|
|
36
|
+
- [ ] 2.4 Orchestrate: backup → marker-block removal → `.roomodes` slug removal → `.ma-agents.json` mutation → history append
|
|
37
|
+
|
|
38
|
+
- [ ] Task 3: Marker-block removal loop (AC: #3)
|
|
39
|
+
- [ ] 3.1 Import marker-convention constants from the installer module (do NOT hardcode `<!-- MA-AGENTS-START -->`) — symmetry with install
|
|
40
|
+
- [ ] 3.2 For each of the 5 injection files, strip the marker block (including markers), preserve surrounding user content byte-for-byte
|
|
41
|
+
- [ ] 3.3 If the resulting file is empty or whitespace-only, delete it
|
|
42
|
+
|
|
43
|
+
- [ ] Task 4: `.roomodes` slug removal (AC: #4, #9)
|
|
44
|
+
- [ ] 4.1 Reuse/centralize the slug list constant `['bmad-pm', 'bmad-architect', 'bmad-techlead', 'bmad-dev']`
|
|
45
|
+
- [ ] 4.2 Parse `.roomodes`, drop only ma-agents-owned slugs, preserve user-defined entries untouched
|
|
46
|
+
- [ ] 4.3 Emit the slug-divergence warning per AC #9 when hand-edited bodies are detected
|
|
47
|
+
- [ ] 4.4 If the post-removal `customModes` array is empty and no other top-level content remains, delete the file
|
|
48
|
+
|
|
49
|
+
- [ ] Task 5: Backup step (AC: #5)
|
|
50
|
+
- [ ] 5.1 Reuse the Story 21.2 / 21.10 backup helper if present (same `<target>.backup-<ISO-timestamp>` convention)
|
|
51
|
+
- [ ] 5.2 Use UTC ISO format for the timestamp (stable across locales)
|
|
52
|
+
- [ ] 5.3 Capture content BEFORE any modification for each file/region
|
|
53
|
+
|
|
54
|
+
- [ ] Task 6: `.ma-agents.json` field mutation + audit-trail preservation (AC: #6, #7, #8)
|
|
55
|
+
- [ ] 6.1 Add `clearProfile(projectRoot)` as a 4th export in `lib/profile.js` — see Dev Notes decision below
|
|
56
|
+
- [ ] 6.2 Append `{ date, from, to: null, source: "uninstall" }` to `profileHistory` (create the field if absent; same 20-entry cap as Story 21.10)
|
|
57
|
+
- [ ] 6.3 PRESERVE `profileHistory` and `roomodesOverwriteLog` — never truncate or delete
|
|
58
|
+
- [ ] 6.4 Do NOT downgrade `manifestVersion` — stays at `1.2.0`
|
|
59
|
+
|
|
60
|
+
- [ ] Task 7: Tests in `test/uninstall.test.js` (covers all ACs)
|
|
61
|
+
- [ ] 7.1 Fresh project with full profile stamping → `uninstall --profile-artifacts` removes all marker blocks, all 4 slugs, clears `profile` (AC #3, #4, #6)
|
|
62
|
+
- [ ] 7.2 User content outside markers preserved byte-for-byte — per-file diff (AC #3, supports NFR49)
|
|
63
|
+
- [ ] 7.3 User-defined `.roomodes` slugs preserved untouched (AC #4)
|
|
64
|
+
- [ ] 7.4 Backup files created at `<target>.backup-<ISO-timestamp>` before each removal (AC #5)
|
|
65
|
+
- [ ] 7.5 `profileHistory` and `roomodesOverwriteLog` preserved; new `{ from, to: null, source: "uninstall" }` entry appended (AC #6, #7)
|
|
66
|
+
- [ ] 7.6 `manifestVersion` unchanged at `1.2.0` after uninstall (AC #8)
|
|
67
|
+
- [ ] 7.7 Slug divergence path — warning emitted, removal proceeds, divergent bodies captured in backup (AC #9)
|
|
68
|
+
- [ ] 7.8 Idempotency — second run exits 0 with `Nothing to do.` message and makes zero additional changes (AC #10)
|
|
69
|
+
- [ ] 7.9 `--yes` bypasses the confirmation prompt and proceeds (AC #2, #1)
|
|
70
|
+
|
|
71
|
+
## Dev Notes
|
|
72
|
+
|
|
73
|
+
### Architecture Compliance
|
|
74
|
+
|
|
75
|
+
- **Decision P3-3** (Local-LLM / On-Prem Agent Tuning Profile) — uninstall is the symmetric counterpart to install's stamping. It MUST reuse the same marker-convention constants the installer defines (do not hardcode `<!-- MA-AGENTS-START -->` inline) to guarantee symmetry — drift between install and uninstall marker strings would leak orphaned blocks.
|
|
76
|
+
- **NFR5** (user content preservation) — uninstall must never touch content outside ma-agents-owned markers or outside ma-agents-owned `.roomodes` slugs. Per-file diff test in Task 7.2 enforces.
|
|
77
|
+
- **NFR44** (profile isolation) — after `uninstall --profile-artifacts`, the project's generated content must be free of ma-agents-owned profile artifacts. Verified by grep for `MA-AGENTS-START`, `bmad-pm`, `bmad-architect`, `bmad-techlead`, `bmad-dev` in the post-uninstall tree yielding no matches within files the installer owns.
|
|
78
|
+
- **NFR49** (uninstall preservation contract — new in this story, added alongside FR181) — audit trail and user content survive uninstall; verified by Task 7.2, 7.5, 7.6.
|
|
79
|
+
|
|
80
|
+
### Source Tree Components to Touch
|
|
81
|
+
|
|
82
|
+
| File | Change |
|
|
83
|
+
|------|--------|
|
|
84
|
+
| `bin/cli.js` | MODIFY — register `uninstall --profile-artifacts` flag (or register `uninstall` subcommand if it does not yet exist); route to new handler |
|
|
85
|
+
| `lib/uninstall.js` | CREATE (or MODIFY if already present for another mode) — export `uninstallProfileArtifacts(projectRoot, { yes })` |
|
|
86
|
+
| `lib/profile.js` | MODIFY — add `clearProfile(projectRoot)` as a 4th export. This expands the Story 21.1 AC #1 three-export contract. See "Decision: expanding `lib/profile.js` public API" below — flag this explicitly when picking up the story |
|
|
87
|
+
| `lib/installer.js` | READ-ONLY import of marker-convention constants and the ma-agents-owned slug list; no mutation |
|
|
88
|
+
| `test/uninstall.test.js` | CREATE |
|
|
89
|
+
|
|
90
|
+
### Library and Pattern References
|
|
91
|
+
|
|
92
|
+
- Prompts library: **`prompts`** (npm) — same as everywhere else.
|
|
93
|
+
- Backup naming convention: `<target>.backup-<ISO-timestamp>` per Story 21.2 AC #8 and Story 21.10 AC #8. Reuse the same helper if already extracted.
|
|
94
|
+
- ma-agents-owned `.roomodes` slug list: `['bmad-pm', 'bmad-architect', 'bmad-techlead', 'bmad-dev']` — centralize as a shared constant if not already done (current Story 21.3 references it inline; refactor opportunity).
|
|
95
|
+
- `profileHistory` shape and 20-entry cap: per Story 21.10 AC #11. This story appends with `source: "uninstall"` and `to: null`.
|
|
96
|
+
- Error class names must match upstream exactly where referenced (e.g., `RoomodesSlugDivergenceError` from Story 21.3 AC #9). This story does NOT introduce a new error class — divergence is a warning, not an abort (AC #9 rationale).
|
|
97
|
+
|
|
98
|
+
### `--yes` Asymmetry Between `reconfigure` and `uninstall`
|
|
99
|
+
|
|
100
|
+
Story 21.10 AC #6 REJECTS `--yes` for `reconfigure` (Finding #5: CI must not silently flip profiles). This story ACCEPTS `--yes` for `uninstall --profile-artifacts`. The two commands have different risk profiles:
|
|
101
|
+
|
|
102
|
+
- `reconfigure` under `--yes` would let CI silently rewrite guardrail content to a different profile's rules — an unintended content swap that may go unnoticed by the team. Blocking `--yes` forces the change to be deliberate.
|
|
103
|
+
- `uninstall --profile-artifacts` under `--yes` serves legitimate decommissioning scripts (e.g., migration automation that removes ma-agents before switching to a different toolchain). The intent is visible in the script being run; the operation is REMOVAL, not replacement, so there is no hidden content swap risk.
|
|
104
|
+
|
|
105
|
+
Both AC #1/#2 in this story and the parent `epics.md` Story 21.11 summary must keep this asymmetry explicit so a future reviewer doesn't "fix" the inconsistency.
|
|
106
|
+
|
|
107
|
+
### Out of Scope
|
|
108
|
+
|
|
109
|
+
- Removing `skills/`, `agents`, or other non-profile ma-agents content — that's a separate `uninstall` mode outside this flag's scope.
|
|
110
|
+
- Undoing BMAD module installation — not a profile artifact; separate concern.
|
|
111
|
+
- Cleaning up `docs/deployment/vllm-nemotron.md` — the vLLM doc ships in the repo as reference documentation (FR179); it is not project-generated and is not removed by uninstall.
|
|
112
|
+
- Restoring a previous on-prem/standard install state — the forward path to change profile is `reconfigure` (Story 21.10), not uninstall+reinstall.
|
|
113
|
+
|
|
114
|
+
### Testing Standards
|
|
115
|
+
|
|
116
|
+
Match `test/reconfigure.test.js` pattern once that lands. Until then, mirror `test/profile.test.js`. Use `fs.mkdtempSync(os.tmpdir() + ...)` per test to isolate state — never mutate the repo's own `.ma-agents.json` or instruction files. Mock `prompts` for deterministic runs (reuse the pattern already used in installer/profile tests).
|
|
117
|
+
|
|
118
|
+
### Decision: expanding `lib/profile.js` public API
|
|
119
|
+
|
|
120
|
+
Story 21.1 AC #1 pinned `lib/profile.js` to exactly three exports: `getProfile`, `setProfile`, `resolveProfile`. This story requires clearing the `profile` field symmetrically, and there are two options:
|
|
121
|
+
|
|
122
|
+
1. **Add `clearProfile(projectRoot)` to `lib/profile.js`** (4th export) — cleanest long-term, keeps all profile-field mutations in one place, preserves the "profile field is only ever touched via `lib/profile.js`" invariant that NFR44 leans on.
|
|
123
|
+
2. **Implement profile clearing inline in `lib/uninstall.js`** via direct JSON manipulation — avoids touching Story 21.1's pinned contract but duplicates IO logic and breaks the single-point-of-mutation invariant.
|
|
124
|
+
|
|
125
|
+
**Recommendation: option 1.** When picking up this story, the dev should:
|
|
126
|
+
- Add `clearProfile` to `lib/profile.js`
|
|
127
|
+
- Submit a small follow-up docs edit to Story 21.1 AC #1 (change "exactly three functions" to "exactly four functions") — paired with the code change, not in a retroactive docs-only PR
|
|
128
|
+
- This Dev Notes section exists to make sure option 2 is not picked silently as the path of least resistance
|
|
129
|
+
|
|
130
|
+
### Epic 21 Cross-Story Context
|
|
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.
|
|
133
|
+
|
|
134
|
+
## Dev Agent Record
|
|
135
|
+
|
|
136
|
+
### Agent Model Used
|
|
137
|
+
_(to be filled by dev agent)_
|
|
138
|
+
|
|
139
|
+
### Debug Log References
|
|
140
|
+
_(to be filled by dev agent)_
|
|
141
|
+
|
|
142
|
+
### Completion Notes List
|
|
143
|
+
_(to be filled by dev agent)_
|
|
144
|
+
|
|
145
|
+
### File List
|
|
146
|
+
_(to be filled by dev agent)_
|
|
147
|
+
|
|
148
|
+
## Change Log
|
|
149
|
+
- 2026-04-14: Story created (Epic 21, Story 21.11). Closes adversarial-review Finding #17 (no uninstall / rollback path).
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Story 21.2: Universal Per-Tool Instruction Block Expansion
|
|
2
|
+
|
|
3
|
+
Status: backlog
|
|
4
|
+
|
|
5
|
+
## Story
|
|
6
|
+
|
|
7
|
+
As a **chief architect**,
|
|
8
|
+
I want the existing `<!-- MA-AGENTS-START -->` injection block to enforce text-vs-file discipline and BMAD phase boundaries for every install (regardless of profile),
|
|
9
|
+
So that all coding agents — even Claude on the web — stop dumping random files as responses and stop skipping BMAD planning to start coding.
|
|
10
|
+
|
|
11
|
+
## Acceptance Criteria
|
|
12
|
+
|
|
13
|
+
1. New template file `lib/templates/instruction-block-universal.template.md` exists and contains, in addition to the current MANIFEST loading instruction:
|
|
14
|
+
- A "Respond in TEXT vs. create FILES" rules section with concrete keyword triggers — file-action keywords (`create`, `write`, `generate`, `build`, `implement`) and text-response keywords (`what do you think`, `how should we`, `discuss`, `opinion`)
|
|
15
|
+
- An "if unsure, respond in text" default rule
|
|
16
|
+
- A "never create `response.md` or `output.md` as a reply" rule
|
|
17
|
+
- A BMAD phase discipline rule: respect the declared phase; do not skip ahead to implementation during planning
|
|
18
|
+
- A "confirm file paths before writing" rule
|
|
19
|
+
2. The template contains `{{MANIFEST_PATH}}` exactly once as a placeholder; no agent-specific paths are hardcoded in the template source.
|
|
20
|
+
3. A new function `composeInstructionBlock({ profile, manifestPath })` in `lib/installer.js` reads the universal template, stamps `{{MANIFEST_PATH}}`, and returns the content. When `profile === 'on-prem'`, it appends the on-prem template content (Story 21.6 wires that file; this story stubs the call site with a graceful fallback if the on-prem template is absent).
|
|
21
|
+
4. The injection function in `lib/installer.js` that writes per-tool instruction files (the existing marker-based path) calls `composeInstructionBlock` and emits the content within `<!-- MA-AGENTS-START -->` / `<!-- MA-AGENTS-END -->` markers.
|
|
22
|
+
5. For every existing markdown-injection agent (Claude Code, Cline, Roo Code rules, Cursor, Kilocode, Copilot, Gemini), a fresh install produces an instruction file containing the universal block. Existing user content outside the markers is preserved byte-for-byte (NFR5, NFR46).
|
|
23
|
+
6. Two consecutive installs with the same profile and project state produce byte-identical content within the marker block (NFR46).
|
|
24
|
+
7. The block must NOT mention `/no_think`, `str_replace_editor`, `~/.claude/`, or any local-LLM-specific concept — those belong in the on-prem template (Story 21.6). Verified by grep on the rendered standard-profile output.
|
|
25
|
+
8. **Upgrade-safety (marker-block hand-edit detection).** When `npx ma-agents install` runs against an already-installed project, before overwriting the content inside `<!-- MA-AGENTS-START -->`/`<!-- MA-AGENTS-END -->` markers the installer compares the existing marker-block content against the ma-agents-generated content for the project's previously recorded profile+version. If the existing content differs from what ma-agents would have produced (i.e., the user hand-edited inside the markers):
|
|
26
|
+
- **Interactive mode:** the installer surfaces a diff of the in-marker changes and requires explicit confirmation before proceeding to overwrite.
|
|
27
|
+
- **`--yes` mode:** the prompt is skipped but the installer emits a visible migration warning line that CI will capture, in the exact format `WARNING: ma-agents marker-block content modified since last install — overwriting. Previous content backed up to <path>.backup-<timestamp>`.
|
|
28
|
+
- **Backup:** before overwriting, the installer writes a backup sibling file alongside the target — same directory, same basename, suffix `.backup-<ISO-timestamp>` (example: `CLAUDE.md.backup-2026-04-14T12-34-56Z`). Only the portion between (and including) the markers is written to the backup; the rest of the target file is untouched by the installer and therefore needs no backup. Rationale: hand-edits inside markers represent work the user expects to keep; silent overwrite is a data-loss regression.
|
|
29
|
+
|
|
30
|
+
## Tasks / Subtasks
|
|
31
|
+
|
|
32
|
+
- [ ] Task 1: Create `lib/templates/instruction-block-universal.template.md` (AC #1, #2, #7)
|
|
33
|
+
- [ ] Task 2: Implement `composeInstructionBlock({ profile, manifestPath })` in `lib/installer.js` (AC #3)
|
|
34
|
+
- [ ] 2.1 Read universal template via existing `fs.readFile` pattern
|
|
35
|
+
- [ ] 2.2 Stamp `{{MANIFEST_PATH}}`
|
|
36
|
+
- [ ] 2.3 If `profile === 'on-prem'` and `lib/templates/instruction-block-onprem.template.md` exists, append its content; otherwise return universal only (Story 21.6 ships the on-prem template)
|
|
37
|
+
- [ ] Task 3: Wire `composeInstructionBlock` into the existing per-tool injection function so the marker block is rewritten with composed content (AC #4, #5)
|
|
38
|
+
- [ ] Task 4: Verify additive behavior — content outside markers preserved across runs (AC #5, #6)
|
|
39
|
+
- [ ] Task 5: Tests in `test/instruction-block.test.js`
|
|
40
|
+
- [ ] 5.1 Universal template stamps manifest path correctly
|
|
41
|
+
- [ ] 5.2 `composeInstructionBlock({ profile: 'standard' })` excludes on-prem content even when on-prem template file exists
|
|
42
|
+
- [ ] 5.3 `composeInstructionBlock({ profile: 'on-prem' })` includes on-prem content when present, falls back gracefully when absent (this story may stub absence — Story 21.6 will ship the file)
|
|
43
|
+
- [ ] 5.4 Idempotency: two installs produce byte-identical marker-block content
|
|
44
|
+
- [ ] 5.5 No on-prem-specific strings (`/no_think`, `str_replace_editor`, `~/.claude/`) appear in standard-profile output
|
|
45
|
+
- [ ] 5.6 User content outside markers preserved across runs
|
|
46
|
+
- [ ] Task 6 (upgrade-safety): Marker-block hand-edit detection, backup, and warning (AC #8)
|
|
47
|
+
- [ ] 6.1 Before overwrite, compare existing in-marker content against expected ma-agents-generated content for the persisted profile+version
|
|
48
|
+
- [ ] 6.2 On drift: interactive prompt shows a diff and requires confirmation; `--yes` bypasses the prompt and emits the pinned WARNING line so CI captures it
|
|
49
|
+
- [ ] 6.3 Write `<target>.backup-<ISO-timestamp>` alongside the target, containing only the original marker-block region (including the marker lines themselves)
|
|
50
|
+
- [ ] 6.4 Test: clean marker block → no warning, no backup file; hand-edited marker block + `--yes` → WARNING emitted and backup file exists with original content
|
|
51
|
+
|
|
52
|
+
## Dev Notes
|
|
53
|
+
|
|
54
|
+
### Architecture Compliance
|
|
55
|
+
|
|
56
|
+
- **Decision P3-3**: This story implements the "Universal layer" half of the two-layer model. The on-prem layer ships in Story 21.6.
|
|
57
|
+
- **NFR44**: Standard profile must produce no on-prem-specific output. AC #7 + Test 5.5 enforce this.
|
|
58
|
+
- **NFR46**: Marker-block content is deterministic given profile + project state.
|
|
59
|
+
|
|
60
|
+
### Source Tree Components to Touch
|
|
61
|
+
|
|
62
|
+
| File | Change |
|
|
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 |
|
|
67
|
+
|
|
68
|
+
### Dependencies
|
|
69
|
+
|
|
70
|
+
- Story 21.1 must be merged first — `composeInstructionBlock` reads `profile` from the resolved value passed in; injection call sites obtain it via `getProfile(projectRoot)` from `lib/profile.js`.
|
|
71
|
+
|
|
72
|
+
### Out of Scope
|
|
73
|
+
|
|
74
|
+
- On-prem template content (Story 21.6 ships `lib/templates/instruction-block-onprem.template.md`)
|
|
75
|
+
- `.roomodes`, `AGENTS.md`, `.clinerules` per-tool templates (Stories 21.3–21.5)
|
|
76
|
+
- BMAD persona phase prefix (Story 21.7)
|
|
77
|
+
|
|
78
|
+
### Testing Standards
|
|
79
|
+
|
|
80
|
+
Match `test/profile.test.js` (created in Story 21.1) for framework and isolation patterns.
|
|
81
|
+
|
|
82
|
+
## Dev Agent Record
|
|
83
|
+
|
|
84
|
+
### Agent Model Used
|
|
85
|
+
_(to be filled by dev agent)_
|
|
86
|
+
|
|
87
|
+
### Debug Log References
|
|
88
|
+
_(to be filled)_
|
|
89
|
+
|
|
90
|
+
### Completion Notes List
|
|
91
|
+
_(to be filled)_
|
|
92
|
+
|
|
93
|
+
### File List
|
|
94
|
+
_(to be filled)_
|
|
95
|
+
|
|
96
|
+
## Change Log
|
|
97
|
+
- 2026-04-14: Story created (Epic 21, Story 21.2)
|
|
98
|
+
- 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).
|