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
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# AI Agent Skills - Planning Instruction
|
|
2
|
+
|
|
3
|
+
You have access to a library of skills in your skills directory. Before starting any task:
|
|
4
|
+
|
|
5
|
+
1. Read the skill manifest at {{MANIFEST_PATH}}
|
|
6
|
+
2. Based on the task description, select which skills are relevant
|
|
7
|
+
3. Read only the selected skill files
|
|
8
|
+
4. Then proceed with the task
|
|
9
|
+
|
|
10
|
+
Always load skills marked with always_load: true.
|
|
11
|
+
Do not load skills that are not relevant to the current task.
|
|
12
|
+
|
|
13
|
+
## Respond in TEXT vs. create FILES
|
|
14
|
+
|
|
15
|
+
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.
|
|
16
|
+
|
|
17
|
+
- **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.
|
|
18
|
+
- **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.
|
|
19
|
+
- **If unsure, respond in TEXT.** A text answer can always be followed by file creation on confirmation; an unwanted file cannot be cleanly undone.
|
|
20
|
+
- **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.
|
|
21
|
+
- **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.
|
|
22
|
+
|
|
23
|
+
## BMAD phase discipline
|
|
24
|
+
|
|
25
|
+
BMAD-METHOD organizes work into declared phases (analysis, planning, architecture, story-creation, implementation, review). Respect the currently declared phase.
|
|
26
|
+
|
|
27
|
+
- **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.
|
|
28
|
+
- **Do not retroactively plan after you have already coded.** If implementation has already started, flag the gap instead of fabricating back-dated planning documents.
|
|
29
|
+
- 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.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Story 21.3 — .roomodes template for Roo Code BMAD mode gating.
|
|
2
|
+
#
|
|
3
|
+
# Schema source of truth: the roo-code entry in lib/agents.js (lines 140-163).
|
|
4
|
+
# Schema fields used per mode: slug, name, roleDefinition, whenToUse,
|
|
5
|
+
# customInstructions, groups (with ["edit", { fileRegex, description }] tuple form).
|
|
6
|
+
#
|
|
7
|
+
# The universal-block sentinel below is substituted by the installer (stamper)
|
|
8
|
+
# AFTER calling composeInstructionBlock({ profile, projectRoot }) from Story 21.2.
|
|
9
|
+
# The YAML merger (lib/merge/roomodes.js) treats this template as opaque, already-
|
|
10
|
+
# composed input — it never calls composeInstructionBlock itself (decision A).
|
|
11
|
+
#
|
|
12
|
+
# NFR47 (application-layer fileRegex enforcement): Roo Code enforces fileRegex
|
|
13
|
+
# at the IDE layer via FileRestrictionError — independent of the LLM's prompt
|
|
14
|
+
# compliance. The four ma-agents-owned slugs are: bmad-pm, bmad-architect,
|
|
15
|
+
# bmad-techlead, bmad-dev.
|
|
16
|
+
customModes:
|
|
17
|
+
- slug: bmad-pm
|
|
18
|
+
name: BMAD PM
|
|
19
|
+
roleDefinition: >-
|
|
20
|
+
Product manager driving discovery, requirements capture, and PRD
|
|
21
|
+
authoring. Operates strictly in BMAD's planning phase — produces
|
|
22
|
+
markdown artifacts only.
|
|
23
|
+
whenToUse: >-
|
|
24
|
+
Use when the user asks for requirements discovery, PRD creation,
|
|
25
|
+
scope framing, or any BMAD planning-phase deliverable.
|
|
26
|
+
groups:
|
|
27
|
+
- read
|
|
28
|
+
- - edit
|
|
29
|
+
- fileRegex: \.md$
|
|
30
|
+
description: Markdown planning artifacts only (PRDs, notes, epics)
|
|
31
|
+
customInstructions: |-
|
|
32
|
+
PM mode — discovery, requirements, and PRD authoring only.
|
|
33
|
+
Do not write code or configuration files; produce markdown only.
|
|
34
|
+
|
|
35
|
+
{{UNIVERSAL_BLOCK}}
|
|
36
|
+
|
|
37
|
+
- slug: bmad-architect
|
|
38
|
+
name: BMAD Architect
|
|
39
|
+
roleDefinition: >-
|
|
40
|
+
System architect responsible for architecture decisions, diagrams,
|
|
41
|
+
and structured architecture documents. Produces markdown, XML, and
|
|
42
|
+
drawio assets only — no source code.
|
|
43
|
+
whenToUse: >-
|
|
44
|
+
Use when the user asks to design, review architecture, produce
|
|
45
|
+
diagrams, or author the architecture document.
|
|
46
|
+
groups:
|
|
47
|
+
- read
|
|
48
|
+
- - edit
|
|
49
|
+
- fileRegex: \.(md|xml|drawio)$
|
|
50
|
+
description: Architecture artifacts (markdown, XML, drawio)
|
|
51
|
+
customInstructions: |-
|
|
52
|
+
Architect mode — architecture documents, diagrams, and design decisions only.
|
|
53
|
+
Do not write source code or package configuration; produce md/xml/drawio only.
|
|
54
|
+
|
|
55
|
+
{{UNIVERSAL_BLOCK}}
|
|
56
|
+
|
|
57
|
+
- slug: bmad-techlead
|
|
58
|
+
name: BMAD Tech Lead
|
|
59
|
+
roleDefinition: >-
|
|
60
|
+
Technical lead responsible for story preparation, task breakdown,
|
|
61
|
+
and structured configuration. Edits markdown stories and
|
|
62
|
+
configuration (json/yaml) — no source code.
|
|
63
|
+
whenToUse: >-
|
|
64
|
+
Use when the user asks to prepare stories, break down tasks, edit
|
|
65
|
+
sprint/status yaml, or tune project configuration.
|
|
66
|
+
groups:
|
|
67
|
+
- read
|
|
68
|
+
- - edit
|
|
69
|
+
- fileRegex: \.(md|json|yaml|yml)$
|
|
70
|
+
description: Stories, tasks, and structured configuration only
|
|
71
|
+
customInstructions: |-
|
|
72
|
+
Tech Lead mode — story preparation and configuration only.
|
|
73
|
+
Do not write source code; edit only markdown stories and json/yaml configuration.
|
|
74
|
+
|
|
75
|
+
{{UNIVERSAL_BLOCK}}
|
|
76
|
+
|
|
77
|
+
- slug: bmad-dev
|
|
78
|
+
name: BMAD Dev
|
|
79
|
+
roleDefinition: >-
|
|
80
|
+
Senior developer responsible for implementing an approved story.
|
|
81
|
+
Full editor and command access — enters ONLY after planning is
|
|
82
|
+
complete and the story is in status ready-for-dev.
|
|
83
|
+
whenToUse: >-
|
|
84
|
+
Use when a story is ready for implementation and the user asks to
|
|
85
|
+
code, run tests, or execute commands.
|
|
86
|
+
groups:
|
|
87
|
+
- read
|
|
88
|
+
- edit
|
|
89
|
+
- command
|
|
90
|
+
customInstructions: |-
|
|
91
|
+
Dev mode — implement approved stories only.
|
|
92
|
+
Do not start coding unless the story status is ready-for-dev. If the
|
|
93
|
+
project is still in a planning phase, switch to the appropriate BMAD
|
|
94
|
+
mode (PM, Architect, or Tech Lead) first.
|
|
95
|
+
|
|
96
|
+
{{UNIVERSAL_BLOCK}}
|
package/lib/uninstall.js
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Story 21.11 — Profile Uninstall orchestrator.
|
|
5
|
+
*
|
|
6
|
+
* Public API:
|
|
7
|
+
* async uninstallProfileArtifacts(projectRoot, { yes = false } = {}) -> void
|
|
8
|
+
*
|
|
9
|
+
* Orchestrates:
|
|
10
|
+
* 1. Detect which of the 5 injection files contain MA-AGENTS-START/END markers.
|
|
11
|
+
* 2. Detect whether .roomodes contains any of the 4 owned slugs.
|
|
12
|
+
* 3. Idempotency check: if nothing is found, print "Nothing to do." and return.
|
|
13
|
+
* 4. Confirmation (unless --yes): list files, ask to continue.
|
|
14
|
+
* 5. For each injection file: backup → strip marker block → if empty, delete.
|
|
15
|
+
* 6. For .roomodes: backup → parse → remove owned slugs → write or delete.
|
|
16
|
+
* 7. Update .ma-agents.json: clearProfile(), append profileHistory entry.
|
|
17
|
+
*
|
|
18
|
+
* Notes:
|
|
19
|
+
* - buildBackupFilename is reused from lib/installer.js (Story 21.2).
|
|
20
|
+
* - MA_AGENTS_OWNED_SLUGS is imported from lib/merge/roomodes.js (Story 21.3).
|
|
21
|
+
* - clearProfile is the 4th export added to lib/profile.js in this story.
|
|
22
|
+
* - --yes is SUPPORTED (legitimate CI decommissioning use-case), unlike reconfigure
|
|
23
|
+
* which REJECTS --yes. Documented asymmetry.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const fs = require('fs');
|
|
27
|
+
const path = require('path');
|
|
28
|
+
const chalk = require('chalk');
|
|
29
|
+
const defaultPrompts = require('prompts');
|
|
30
|
+
|
|
31
|
+
const { clearProfile, getProfile } = require('./profile');
|
|
32
|
+
const { buildBackupFilename, EXTRA_TEMPLATE_DIR } = require('./installer');
|
|
33
|
+
const { MA_AGENTS_OWNED_SLUGS } = require('./merge/roomodes');
|
|
34
|
+
|
|
35
|
+
const MANIFEST_FILE = '.ma-agents.json';
|
|
36
|
+
const PROFILE_HISTORY_CAP = 20;
|
|
37
|
+
|
|
38
|
+
const MARKER_START = '<!-- MA-AGENTS-START -->';
|
|
39
|
+
const MARKER_END = '<!-- MA-AGENTS-END -->';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The 5 canonical injection files that can hold MA-AGENTS-START/END blocks.
|
|
43
|
+
* Relative to projectRoot.
|
|
44
|
+
*/
|
|
45
|
+
const INJECTION_FILES = [
|
|
46
|
+
'CLAUDE.md',
|
|
47
|
+
'.clinerules',
|
|
48
|
+
'.cline/clinerules.md',
|
|
49
|
+
'.roo/rules/00-ma-agents.md',
|
|
50
|
+
'AGENTS.md'
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Strip the MA-AGENTS-START...MA-AGENTS-END block (INCLUDING marker lines)
|
|
55
|
+
* from a file's content string. Returns the content with the block removed.
|
|
56
|
+
* If the marker is not found, returns the original content unchanged.
|
|
57
|
+
*/
|
|
58
|
+
function stripMarkerBlock(content) {
|
|
59
|
+
// Match from the start marker line through the end marker line, inclusive.
|
|
60
|
+
// The regex is non-greedy and dot-matches-newline via [\s\S].
|
|
61
|
+
return content.replace(/[ \t]*<!-- MA-AGENTS-START -->[ \t]*\r?\n[\s\S]*?<!-- MA-AGENTS-END -->[ \t]*\r?\n?/g, '');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Returns true if the string `content` is empty or whitespace-only.
|
|
66
|
+
*/
|
|
67
|
+
function isBlankContent(content) {
|
|
68
|
+
return content.trim().length === 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Check for slug divergence between installed .roomodes and the shipped template.
|
|
73
|
+
* Returns an array of divergent owned slug names (may be empty).
|
|
74
|
+
* If either file is absent or malformed, returns [].
|
|
75
|
+
*/
|
|
76
|
+
function detectDivergentSlugs(projectRoot) {
|
|
77
|
+
const roomodesPath = path.join(projectRoot, '.roomodes');
|
|
78
|
+
if (!fs.existsSync(roomodesPath)) return [];
|
|
79
|
+
const templatePath = path.join(EXTRA_TEMPLATE_DIR, 'roomodes.template.yaml');
|
|
80
|
+
if (!fs.existsSync(templatePath)) return [];
|
|
81
|
+
|
|
82
|
+
let yaml;
|
|
83
|
+
try { yaml = require('js-yaml'); } catch { return []; }
|
|
84
|
+
|
|
85
|
+
let existing, template;
|
|
86
|
+
try {
|
|
87
|
+
existing = yaml.load(fs.readFileSync(roomodesPath, 'utf-8')) || {};
|
|
88
|
+
template = yaml.load(fs.readFileSync(templatePath, 'utf-8')) || {};
|
|
89
|
+
} catch {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const existingModes = Array.isArray(existing.customModes) ? existing.customModes : [];
|
|
94
|
+
const templateModes = Array.isArray(template.customModes) ? template.customModes : [];
|
|
95
|
+
const templateBySlug = new Map();
|
|
96
|
+
for (const m of templateModes) {
|
|
97
|
+
if (m && typeof m === 'object' && typeof m.slug === 'string') {
|
|
98
|
+
templateBySlug.set(m.slug, m);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const divergent = [];
|
|
103
|
+
for (const entry of existingModes) {
|
|
104
|
+
if (!entry || typeof entry !== 'object') continue;
|
|
105
|
+
const slug = entry.slug;
|
|
106
|
+
if (typeof slug !== 'string') continue;
|
|
107
|
+
if (!MA_AGENTS_OWNED_SLUGS.includes(slug)) continue;
|
|
108
|
+
const tpl = templateBySlug.get(slug);
|
|
109
|
+
if (!tpl) {
|
|
110
|
+
// Owned slug present but not in template — treat as user-modified.
|
|
111
|
+
divergent.push(slug);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
// Compare everything except customInstructions (which is per-profile composed).
|
|
115
|
+
const normalize = (mode) => {
|
|
116
|
+
const copy = { ...mode };
|
|
117
|
+
delete copy.customInstructions;
|
|
118
|
+
return yaml.dump(copy, { sortKeys: true });
|
|
119
|
+
};
|
|
120
|
+
if (normalize(entry) !== normalize(tpl)) {
|
|
121
|
+
divergent.push(slug);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return divergent;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Append a profileHistory entry, capped at PROFILE_HISTORY_CAP entries.
|
|
129
|
+
* Reads and writes .ma-agents.json directly (same pattern as reconfigure.js).
|
|
130
|
+
*/
|
|
131
|
+
function appendUninstallHistory(projectRoot, entry) {
|
|
132
|
+
const manifestPath = path.join(projectRoot, MANIFEST_FILE);
|
|
133
|
+
if (!fs.existsSync(manifestPath)) return;
|
|
134
|
+
const raw = fs.readFileSync(manifestPath, 'utf-8');
|
|
135
|
+
const manifest = JSON.parse(raw);
|
|
136
|
+
const history = Array.isArray(manifest.profileHistory) ? manifest.profileHistory.slice() : [];
|
|
137
|
+
history.push(entry);
|
|
138
|
+
while (history.length > PROFILE_HISTORY_CAP) {
|
|
139
|
+
history.shift();
|
|
140
|
+
}
|
|
141
|
+
manifest.profileHistory = history;
|
|
142
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Main uninstall orchestrator.
|
|
147
|
+
*
|
|
148
|
+
* @param {string} projectRoot
|
|
149
|
+
* @param {object} [opts]
|
|
150
|
+
* @param {boolean} [opts.yes=false] Skip confirmation prompt (CI decommissioning).
|
|
151
|
+
* NOTE: Unlike `reconfigure`, --yes IS supported
|
|
152
|
+
* here — this is intentional asymmetry since
|
|
153
|
+
* uninstall is a valid CI teardown operation.
|
|
154
|
+
* @param {object} [opts.promptsLib] Prompts implementation override (for tests).
|
|
155
|
+
* @param {Date} [opts.now] Date injection (for tests).
|
|
156
|
+
*/
|
|
157
|
+
async function uninstallProfileArtifacts(projectRoot, { yes = false, promptsLib, now } = {}) {
|
|
158
|
+
const prompts = promptsLib || defaultPrompts;
|
|
159
|
+
const nowDate = now || new Date();
|
|
160
|
+
|
|
161
|
+
// --- Step 1: Detect targets ---
|
|
162
|
+
|
|
163
|
+
// Which injection files have ma-agents marker blocks?
|
|
164
|
+
const injectionTargets = [];
|
|
165
|
+
for (const rel of INJECTION_FILES) {
|
|
166
|
+
const absPath = path.join(projectRoot, rel);
|
|
167
|
+
if (!fs.existsSync(absPath)) continue;
|
|
168
|
+
const content = fs.readFileSync(absPath, 'utf-8');
|
|
169
|
+
if (content.includes(MARKER_START)) {
|
|
170
|
+
injectionTargets.push({ rel, absPath, content });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Does .roomodes contain any owned slugs?
|
|
175
|
+
const roomodesPath = path.join(projectRoot, '.roomodes');
|
|
176
|
+
let roomodesHasOwnedSlugs = false;
|
|
177
|
+
if (fs.existsSync(roomodesPath)) {
|
|
178
|
+
const roomodesContent = fs.readFileSync(roomodesPath, 'utf-8');
|
|
179
|
+
roomodesHasOwnedSlugs = MA_AGENTS_OWNED_SLUGS.some(slug =>
|
|
180
|
+
// Match slug as a YAML key to avoid substring false-positives
|
|
181
|
+
roomodesContent.includes(`slug: ${slug}`) || roomodesContent.includes(`slug: '${slug}'`) || roomodesContent.includes(`slug: "${slug}"`)
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// --- Step 2: Idempotency check ---
|
|
186
|
+
if (injectionTargets.length === 0 && !roomodesHasOwnedSlugs) {
|
|
187
|
+
console.log('No ma-agents-owned profile artifacts found. Nothing to do.');
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// --- Step 3: Confirmation ---
|
|
192
|
+
const filesToModify = [
|
|
193
|
+
...injectionTargets.map(t => t.rel),
|
|
194
|
+
...(roomodesHasOwnedSlugs ? ['.roomodes'] : [])
|
|
195
|
+
];
|
|
196
|
+
|
|
197
|
+
const list = filesToModify.map(f => ` - ${f}`).join('\n');
|
|
198
|
+
console.log(`The following files will be modified:\n${list}`);
|
|
199
|
+
|
|
200
|
+
if (!yes) {
|
|
201
|
+
const resp = await prompts({
|
|
202
|
+
type: 'confirm',
|
|
203
|
+
name: 'proceed',
|
|
204
|
+
message: 'The above ma-agents-owned content will be removed. User content outside markers will be preserved. Continue? (y/N)',
|
|
205
|
+
initial: false
|
|
206
|
+
});
|
|
207
|
+
if (!resp || resp.proceed !== true) {
|
|
208
|
+
console.log(chalk.gray('Aborted — no files modified.'));
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// --- Step 4: Process injection files ---
|
|
214
|
+
for (const { rel, absPath, content } of injectionTargets) {
|
|
215
|
+
// Write backup first
|
|
216
|
+
const backupPath = buildBackupFilename(absPath);
|
|
217
|
+
fs.writeFileSync(backupPath, content, 'utf-8');
|
|
218
|
+
|
|
219
|
+
// Strip the marker block
|
|
220
|
+
const stripped = stripMarkerBlock(content);
|
|
221
|
+
|
|
222
|
+
if (isBlankContent(stripped)) {
|
|
223
|
+
// Resulting file is empty/whitespace-only — delete it
|
|
224
|
+
fs.unlinkSync(absPath);
|
|
225
|
+
console.log(chalk.gray(` Removed (now empty): ${rel}`));
|
|
226
|
+
} else {
|
|
227
|
+
fs.writeFileSync(absPath, stripped, 'utf-8');
|
|
228
|
+
console.log(chalk.gray(` Stripped marker block: ${rel}`));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// --- Step 5: Process .roomodes ---
|
|
233
|
+
if (roomodesHasOwnedSlugs) {
|
|
234
|
+
const roomodesContent = fs.readFileSync(roomodesPath, 'utf-8');
|
|
235
|
+
|
|
236
|
+
// Check for slug divergence — warn but proceed
|
|
237
|
+
const divergentSlugs = detectDivergentSlugs(projectRoot);
|
|
238
|
+
if (divergentSlugs.length > 0) {
|
|
239
|
+
const slugList = divergentSlugs.map(s => `"${s}"`).join(', ');
|
|
240
|
+
console.warn(chalk.yellow(
|
|
241
|
+
`WARNING: removing hand-edited ma-agents-owned slugs [${slugList}]. Previous bodies backed up.`
|
|
242
|
+
));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Write backup before modifying
|
|
246
|
+
const backupPath = buildBackupFilename(roomodesPath);
|
|
247
|
+
fs.writeFileSync(backupPath, roomodesContent, 'utf-8');
|
|
248
|
+
|
|
249
|
+
// Parse YAML, remove only owned slugs
|
|
250
|
+
const yaml = require('js-yaml');
|
|
251
|
+
let parsed;
|
|
252
|
+
try {
|
|
253
|
+
parsed = yaml.load(roomodesContent) || {};
|
|
254
|
+
} catch {
|
|
255
|
+
console.warn(chalk.yellow('WARNING: .roomodes could not be parsed as YAML — skipping slug removal.'));
|
|
256
|
+
parsed = null;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (parsed !== null) {
|
|
260
|
+
const existingModes = Array.isArray(parsed.customModes) ? parsed.customModes : [];
|
|
261
|
+
const ownedSet = new Set(MA_AGENTS_OWNED_SLUGS);
|
|
262
|
+
const userModes = existingModes.filter(
|
|
263
|
+
m => m && typeof m === 'object' && !ownedSet.has(m.slug)
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
// Check if the file should be deleted (customModes empty and no other meaningful keys)
|
|
267
|
+
const otherKeys = Object.keys(parsed).filter(k => k !== 'customModes');
|
|
268
|
+
if (userModes.length === 0 && otherKeys.length === 0) {
|
|
269
|
+
fs.unlinkSync(roomodesPath);
|
|
270
|
+
console.log(chalk.gray(' Removed (now empty): .roomodes'));
|
|
271
|
+
} else {
|
|
272
|
+
parsed.customModes = userModes;
|
|
273
|
+
const DUMP_OPTIONS = {
|
|
274
|
+
indent: 2,
|
|
275
|
+
lineWidth: -1,
|
|
276
|
+
noRefs: true,
|
|
277
|
+
quotingType: '"',
|
|
278
|
+
sortKeys: false
|
|
279
|
+
};
|
|
280
|
+
fs.writeFileSync(roomodesPath, yaml.dump(parsed, DUMP_OPTIONS), 'utf-8');
|
|
281
|
+
console.log(chalk.gray(' Removed owned slugs from: .roomodes'));
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// --- Step 6: Update .ma-agents.json ---
|
|
287
|
+
const manifestPath = path.join(projectRoot, MANIFEST_FILE);
|
|
288
|
+
if (fs.existsSync(manifestPath)) {
|
|
289
|
+
const previousProfile = getProfile(projectRoot);
|
|
290
|
+
|
|
291
|
+
// clearProfile deletes the 'profile' key (not set to null/empty)
|
|
292
|
+
clearProfile(projectRoot);
|
|
293
|
+
|
|
294
|
+
// Append history entry
|
|
295
|
+
appendUninstallHistory(projectRoot, {
|
|
296
|
+
date: nowDate.toISOString(),
|
|
297
|
+
from: previousProfile,
|
|
298
|
+
to: null,
|
|
299
|
+
source: 'uninstall'
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
console.log(chalk.green(' Updated .ma-agents.json: profile cleared, history appended.'));
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
console.log(chalk.bold.green('\n Uninstall complete. All ma-agents-owned artifacts removed.\n'));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
module.exports = {
|
|
309
|
+
uninstallProfileArtifacts,
|
|
310
|
+
// Exposed for tests
|
|
311
|
+
stripMarkerBlock,
|
|
312
|
+
appendUninstallHistory,
|
|
313
|
+
PROFILE_HISTORY_CAP
|
|
314
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ma-agents",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
4
4
|
"description": "NPX tool to install skills for AI coding agents (Claude Code, Gemini, Copilot, Kilocode, Cline, Cursor, Roo Code)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node bin/cli.js",
|
|
11
|
-
"test": "node test/yes-flag.test.js && node test/skill-authoring.test.js && node test/skill-validation.test.js && node test/skill-mandatory.test.js && node test/skill-customize-agent.test.js && node test/create-agent.test.js && node test/generate-project-context.test.js && node test/build-bmad-args.test.js && node test/bmad-version-bump.test.js && node test/extension-module-restructure.test.js && node test/convert-agents-to-skills.test.js && node test/migration.test.js && node test/migration-validation.test.js && node test/story-15-5-workflow-skills.test.js && node test/repo-layout.test.js && node test/config-storage.test.js && node test/cross-repo-validation.test.js && node test/config-lost-on-update.test.js && node test/portable-paths.test.js && node test/cicd-remote-mode.test.js && node test/config-layout.test.js && node test/roo-code-agent.test.js && node test/roo-code-injection.test.js && node test/profile.test.js && node test/onprem-injection.test.js && node test/experimental-warning.test.js",
|
|
11
|
+
"test": "node test/yes-flag.test.js && node test/skill-authoring.test.js && node test/skill-validation.test.js && node test/skill-mandatory.test.js && node test/skill-customize-agent.test.js && node test/create-agent.test.js && node test/generate-project-context.test.js && node test/build-bmad-args.test.js && node test/bmad-version-bump.test.js && node test/extension-module-restructure.test.js && node test/convert-agents-to-skills.test.js && node test/migration.test.js && node test/migration-validation.test.js && node test/story-15-5-workflow-skills.test.js && node test/repo-layout.test.js && node test/config-storage.test.js && node test/cross-repo-validation.test.js && node test/config-lost-on-update.test.js && node test/portable-paths.test.js && node test/cicd-remote-mode.test.js && node test/config-layout.test.js && node test/roo-code-agent.test.js && node test/roo-code-injection.test.js && node test/profile.test.js && node test/instruction-block.test.js && node test/agents-md.test.js && node test/onprem-injection.test.js && node test/onprem-layer.test.js && node test/roomodes.test.js && node test/clinerules.test.js && node test/reconfigure.test.js && node test/experimental-warning.test.js && node test/bmad-persona-phase-prefix.test.js && node test/uninstall.test.js && node test/offline-recompile.test.js",
|
|
12
12
|
"build:bmad-cache": "node scripts/build-bmad-cache.js"
|
|
13
13
|
},
|
|
14
14
|
"keywords": [
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"bmad-method": "6.2.2",
|
|
31
31
|
"prompts": "^2.4.2",
|
|
32
32
|
"chalk": "^4.1.2",
|
|
33
|
-
"fs-extra": "^11.1.1"
|
|
33
|
+
"fs-extra": "^11.1.1",
|
|
34
|
+
"js-yaml": "^4.1.1"
|
|
34
35
|
},
|
|
35
36
|
"engines": {
|
|
36
37
|
"node": ">=16.0.0"
|