@brunosps00/dev-workflow 1.0.4 → 1.0.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/README.md +58 -14
- package/bin/dev-workflow.js +29 -3
- package/lib/agents.js +410 -0
- package/lib/constants.js +12 -0
- package/lib/doctor.js +213 -0
- package/lib/init.js +96 -8
- package/lib/install-state.js +57 -0
- package/lib/list-installed.js +27 -0
- package/lib/migrate-skills.js +26 -2
- package/lib/profiles.js +44 -0
- package/lib/skill-registry.js +109 -0
- package/lib/subtasks.js +264 -0
- package/lib/uninstall.js +21 -12
- package/lib/utils.js +42 -1
- package/lib/wrappers.js +2 -1
- package/package.json +1 -1
- package/scaffold/agent-registry.json +272 -0
- package/scaffold/agents/core/dw-build-fixer.md +21 -0
- package/scaffold/agents/core/dw-code-explorer.md +32 -0
- package/scaffold/agents/core/dw-code-reviewer.md +28 -0
- package/scaffold/agents/core/dw-planner.md +23 -0
- package/scaffold/agents/core/dw-test-author.md +21 -0
- package/scaffold/agents/csharp/dw-csharp-reviewer.md +15 -0
- package/scaffold/agents/csharp/dw-dotnet-build-fixer.md +15 -0
- package/scaffold/agents/frontend/dw-qa-runner.md +21 -0
- package/scaffold/agents/python/dw-python-build-fixer.md +15 -0
- package/scaffold/agents/python/dw-python-reviewer.md +15 -0
- package/scaffold/agents/rust/dw-rust-build-fixer.md +15 -0
- package/scaffold/agents/rust/dw-rust-reviewer.md +15 -0
- package/scaffold/agents/security/dw-security-reviewer.md +24 -0
- package/scaffold/agents/security/dw-silent-failure-hunter.md +22 -0
- package/scaffold/agents/typescript/dw-typescript-build-fixer.md +15 -0
- package/scaffold/agents/typescript/dw-typescript-reviewer.md +15 -0
- package/scaffold/en/agent-instructions.md +44 -1
- package/scaffold/en/commands/dw-bugfix.md +9 -0
- package/scaffold/en/commands/dw-context-budget.md +28 -0
- package/scaffold/en/commands/dw-harness-audit.md +31 -0
- package/scaffold/en/commands/dw-help.md +15 -5
- package/scaffold/en/commands/dw-intel.md +6 -0
- package/scaffold/en/commands/dw-plan.md +11 -0
- package/scaffold/en/commands/dw-review.md +12 -0
- package/scaffold/en/commands/dw-skill-health.md +29 -0
- package/scaffold/en/commands/dw-subtask-complete.md +40 -0
- package/scaffold/en/commands/dw-subtask-resume.md +21 -0
- package/scaffold/en/commands/dw-subtask-start.md +22 -0
- package/scaffold/pt-br/agent-instructions.md +44 -1
- package/scaffold/pt-br/commands/dw-bugfix.md +9 -0
- package/scaffold/pt-br/commands/dw-context-budget.md +28 -0
- package/scaffold/pt-br/commands/dw-harness-audit.md +31 -0
- package/scaffold/pt-br/commands/dw-help.md +15 -5
- package/scaffold/pt-br/commands/dw-intel.md +6 -0
- package/scaffold/pt-br/commands/dw-plan.md +11 -0
- package/scaffold/pt-br/commands/dw-review.md +12 -0
- package/scaffold/pt-br/commands/dw-skill-health.md +29 -0
- package/scaffold/pt-br/commands/dw-subtask-complete.md +40 -0
- package/scaffold/pt-br/commands/dw-subtask-resume.md +21 -0
- package/scaffold/pt-br/commands/dw-subtask-start.md +22 -0
- package/scaffold/skill-registry.json +258 -0
- package/scaffold/skills/dw-codebase-intel/SKILL.md +12 -5
- package/scaffold/skills/dw-context-budget/SKILL.md +45 -0
- package/scaffold/skills/dw-search-first/SKILL.md +31 -0
- package/scaffold/skills/dw-silent-failure/SKILL.md +33 -0
- package/scaffold/skills/dw-verify/SKILL.md +2 -29
- package/scaffold/skills/dw-verify/references/verification-discovery-and-attribution.md +32 -0
- package/scaffold/skills/humanizer/SKILL.md +7 -412
- package/scaffold/skills/humanizer/references/ai-writing-patterns.md +418 -0
package/lib/constants.js
CHANGED
|
@@ -11,6 +11,8 @@ const COMMANDS = {
|
|
|
11
11
|
{ name: 'dw-functional-doc', description: 'Maps screens and user flows into a functional doc, validated end-to-end with Playwright.' },
|
|
12
12
|
{ name: 'dw-generate-pr', description: 'Trigger when commits are ready and the branch needs a PR. Pushes branch and opens PR with summary + test plan + hard verify gate.' },
|
|
13
13
|
{ name: 'dw-help', description: 'Lists primary commands and the flows that connect them. Pass --advanced to see internal/hidden commands.' },
|
|
14
|
+
{ name: 'dw-context-budget', description: 'Audits context overhead from commands, skills, agents, instructions, and MCPs; recommends token savings.' },
|
|
15
|
+
{ name: 'dw-harness-audit', description: 'Runs a deterministic health scorecard for dev-workflow installation, agents, MCPs, gates, and context discipline.' },
|
|
14
16
|
{ name: 'dw-install-aws-skills', description: 'Trigger when user asks to install AWS expertise, setup AWS MCP server, or add AWS agent skills. Opt-in: clones curated skills from aws/agent-toolkit-for-aws into .agents/skills/aws/ and registers the unified AWS MCP Server (stdio via mcp-proxy-for-aws). Requires uv, aws cli, and AWS credentials.' },
|
|
15
17
|
{ name: 'dw-install-azure-skills', description: 'Trigger when user asks to install Azure expertise, setup Microsoft docs MCP, or add Azure agent skills. Opt-in: clones curated skills from MicrosoftDocs/Agent-Skills into .agents/skills/azure/ and registers the Microsoft Learn MCP server.' },
|
|
16
18
|
{ name: 'dw-intel', description: 'Codebase intelligence: query mode (default) answers questions citing .dw/intel/ + .dw/rules/; --build mode (re)builds the index.' },
|
|
@@ -23,6 +25,10 @@ const COMMANDS = {
|
|
|
23
25
|
{ name: 'dw-review', description: 'Trigger when user asks to review code, check quality, or verify PR readiness. Default runs L2 (PRD coverage) + L3 (code quality). Flags --coverage-only / --code-only / --bugfix <slug>.' },
|
|
24
26
|
{ name: 'dw-run', description: 'Trigger when user wants to execute tasks. Default runs all pending in dependency order; \'run <task-id>\' runs one; \'run --resume\' continues an interrupted plan.' },
|
|
25
27
|
{ name: 'dw-secure-audit', description: 'Unified security audit: OWASP + Trivy SCA/secret/IaC + lockfile + supply-chain compromise check. Hidden; auto-invoked by /dw-review and /dw-generate-pr.' },
|
|
28
|
+
{ name: 'dw-skill-health', description: 'Audits installed skills and agents for bloat, duplication, missing references, and stale or unused modules.' },
|
|
29
|
+
{ name: 'dw-subtask-start', description: 'Creates a minimal input packet for a subagent without injecting the parent transcript.' },
|
|
30
|
+
{ name: 'dw-subtask-complete', description: 'Records a structured subagent handoff with result, files, decisions, risks, verification, and blockers.' },
|
|
31
|
+
{ name: 'dw-subtask-resume', description: 'Consumes pending subagent handoffs, summarizes them for the parent, and archives the local packets.' },
|
|
26
32
|
{ name: 'dw-update', description: 'Updates dev-workflow to the latest npm release in-place, with a snapshot you can roll back to.' },
|
|
27
33
|
],
|
|
28
34
|
'pt-br': [
|
|
@@ -37,6 +43,8 @@ const COMMANDS = {
|
|
|
37
43
|
{ name: 'dw-functional-doc', description: 'Mapeia telas e fluxos em um dossie funcional, validado E2E com Playwright.' },
|
|
38
44
|
{ name: 'dw-generate-pr', description: 'Trigger quando commits estao prontos e branch precisa de PR. Push da branch e abre PR com summary + test plan + hard verify gate.' },
|
|
39
45
|
{ name: 'dw-help', description: 'Lista comandos primarios e fluxos que os conectam. Passe --advanced para ver comandos internos/escondidos.' },
|
|
46
|
+
{ name: 'dw-context-budget', description: 'Audita overhead de contexto em comandos, skills, agentes, instrucoes e MCPs; recomenda economia de tokens.' },
|
|
47
|
+
{ name: 'dw-harness-audit', description: 'Roda um scorecard deterministico da instalacao dev-workflow, agentes, MCPs, gates e disciplina de contexto.' },
|
|
40
48
|
{ name: 'dw-install-aws-skills', description: 'Trigger quando usuario pede pra instalar expertise AWS, configurar MCP da AWS, ou adicionar agent skills AWS. Opt-in: clona skills curadas de aws/agent-toolkit-for-aws para .agents/skills/aws/ e registra o AWS MCP Server unificado (stdio via mcp-proxy-for-aws). Requer uv, aws cli e credenciais AWS.' },
|
|
41
49
|
{ name: 'dw-install-azure-skills', description: 'Trigger quando usuario pede pra instalar expertise Azure, configurar MCP do Microsoft docs, ou adicionar agent skills Azure. Opt-in: clona skills curadas de MicrosoftDocs/Agent-Skills para .agents/skills/azure/ e registra o Microsoft Learn MCP server.' },
|
|
42
50
|
{ name: 'dw-intel', description: 'Inteligencia do codebase: modo query (default) responde citando .dw/intel/ + .dw/rules/; modo --build (re)constroi indice.' },
|
|
@@ -49,6 +57,10 @@ const COMMANDS = {
|
|
|
49
57
|
{ name: 'dw-review', description: 'Trigger quando usuario pede pra revisar codigo, checar qualidade ou validar prontidao pra PR. Default roda L2 (cobertura PRD) + L3 (qualidade). Flags --coverage-only / --code-only / --bugfix <slug>.' },
|
|
50
58
|
{ name: 'dw-run', description: 'Trigger quando usuario quer executar tasks. Default roda todas pendentes em ordem de dependencia; \'run <task-id>\' roda uma; \'run --resume\' continua plan interrompido.' },
|
|
51
59
|
{ name: 'dw-secure-audit', description: 'Audit unificado de seguranca: OWASP + Trivy SCA/secret/IaC + lockfile + supply-chain compromise. Hidden; auto-invocado por /dw-review e /dw-generate-pr.' },
|
|
60
|
+
{ name: 'dw-skill-health', description: 'Audita skills e agentes instalados por bloat, duplicacao, referencias quebradas e modulos sem uso.' },
|
|
61
|
+
{ name: 'dw-subtask-start', description: 'Cria um input packet minimo para subagente sem injetar transcript bruto do parent.' },
|
|
62
|
+
{ name: 'dw-subtask-complete', description: 'Registra handoff estruturado do subagente com resultado, arquivos, decisoes, riscos, verificacao e bloqueios.' },
|
|
63
|
+
{ name: 'dw-subtask-resume', description: 'Consome handoffs pendentes de subagentes, resume para o parent e arquiva os pacotes locais.' },
|
|
52
64
|
{ name: 'dw-update', description: 'Atualiza o dev-workflow para o release mais recente no npm, com snapshot para rollback.' },
|
|
53
65
|
],
|
|
54
66
|
};
|
package/lib/doctor.js
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { COMMANDS, PLATFORMS, MCP_SERVERS } = require('./constants');
|
|
4
|
+
const {
|
|
5
|
+
listExpectedAgentFiles,
|
|
6
|
+
listLegacyAgentFiles,
|
|
7
|
+
readAgentRegistry,
|
|
8
|
+
validateAgentRegistry,
|
|
9
|
+
AGENT_TARGETS,
|
|
10
|
+
} = require('./agents');
|
|
11
|
+
const { readInstallState, STATE_RELATIVE_PATH } = require('./install-state');
|
|
12
|
+
const { validateSkillRegistry, readSkillRegistry } = require('./skill-registry');
|
|
13
|
+
const removedCommands = require('./removed-commands');
|
|
14
|
+
|
|
15
|
+
function run() {
|
|
16
|
+
const projectRoot = process.cwd();
|
|
17
|
+
const state = readInstallState(projectRoot);
|
|
18
|
+
const issues = [];
|
|
19
|
+
const warnings = [];
|
|
20
|
+
|
|
21
|
+
if (!state) {
|
|
22
|
+
issues.push(`Missing ${STATE_RELATIVE_PATH}. Run \`dev-workflow repair\`.`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const lang = (state && state.lang) || 'en';
|
|
26
|
+
const modules = (state && state.modules) || ['core', 'security', 'frontend'];
|
|
27
|
+
const commands = COMMANDS[lang] || COMMANDS.en;
|
|
28
|
+
|
|
29
|
+
checkCommands(projectRoot, commands, issues);
|
|
30
|
+
checkWrappers(projectRoot, commands, issues);
|
|
31
|
+
checkAgents(projectRoot, modules, issues);
|
|
32
|
+
checkAgentRegistry(issues, warnings);
|
|
33
|
+
checkSubtaskLayout(projectRoot, issues);
|
|
34
|
+
checkSkillRegistry(projectRoot, issues, warnings);
|
|
35
|
+
checkMcp(projectRoot, issues);
|
|
36
|
+
checkLegacyCommands(projectRoot, warnings);
|
|
37
|
+
|
|
38
|
+
console.log('dev-workflow doctor\n');
|
|
39
|
+
if (issues.length === 0 && warnings.length === 0) {
|
|
40
|
+
console.log('PASS: managed commands, wrappers, agents, skills, and MCP config look healthy.');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (issues.length) {
|
|
45
|
+
console.log('Issues:');
|
|
46
|
+
for (const issue of issues) console.log(`- ${issue}`);
|
|
47
|
+
console.log();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (warnings.length) {
|
|
51
|
+
console.log('Warnings:');
|
|
52
|
+
for (const warning of warnings) console.log(`- ${warning}`);
|
|
53
|
+
console.log();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
console.log('Run `npx @brunosps00/dev-workflow repair` to reconcile managed files.');
|
|
57
|
+
if (issues.length) process.exitCode = 1;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function checkCommands(projectRoot, commands, issues) {
|
|
61
|
+
for (const cmd of commands) {
|
|
62
|
+
const file = path.join(projectRoot, '.dw', 'commands', `${cmd.name}.md`);
|
|
63
|
+
if (!fs.existsSync(file)) issues.push(`Missing command source: ${path.relative(projectRoot, file)}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function checkWrappers(projectRoot, commands, issues) {
|
|
68
|
+
for (const [, platform] of Object.entries(PLATFORMS)) {
|
|
69
|
+
for (const cmd of commands) {
|
|
70
|
+
const file = platform.flat
|
|
71
|
+
? path.join(projectRoot, platform.dir, `${cmd.name}.md`)
|
|
72
|
+
: path.join(projectRoot, platform.dir, cmd.name, 'SKILL.md');
|
|
73
|
+
if (!fs.existsSync(file)) issues.push(`Missing wrapper: ${path.relative(projectRoot, file)}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function checkAgents(projectRoot, modules, issues) {
|
|
79
|
+
for (const file of listExpectedAgentFiles(projectRoot, modules)) {
|
|
80
|
+
if (!fs.existsSync(file)) issues.push(`Missing agent: ${path.relative(projectRoot, file)}`);
|
|
81
|
+
}
|
|
82
|
+
for (const file of listLegacyAgentFiles(projectRoot, modules)) {
|
|
83
|
+
if (fs.existsSync(file)) issues.push(`Legacy Copilot agent path still exists: ${path.relative(projectRoot, file)}`);
|
|
84
|
+
}
|
|
85
|
+
for (const file of listExpectedAgentFiles(projectRoot, modules)) {
|
|
86
|
+
if (fs.existsSync(file) && file.endsWith('.md') && fs.statSync(file).size > 8 * 1024) {
|
|
87
|
+
issues.push(`Agent prompt exceeds 8KB: ${path.relative(projectRoot, file)}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
checkProviderAgentCompatibility(projectRoot, modules, issues);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function checkAgentRegistry(issues, warnings) {
|
|
94
|
+
const registry = readAgentRegistry();
|
|
95
|
+
const validation = validateAgentRegistry(registry);
|
|
96
|
+
issues.push(...validation.issues);
|
|
97
|
+
warnings.push(...validation.warnings);
|
|
98
|
+
const installedRegistryPath = path.join(process.cwd(), '.dw', 'agent-registry.json');
|
|
99
|
+
if (!fs.existsSync(installedRegistryPath)) {
|
|
100
|
+
issues.push(`Missing agent registry: ${path.relative(process.cwd(), installedRegistryPath)}`);
|
|
101
|
+
} else {
|
|
102
|
+
try {
|
|
103
|
+
const installedRegistry = JSON.parse(fs.readFileSync(installedRegistryPath, 'utf-8'));
|
|
104
|
+
if (installedRegistry.schema_version !== '2.0') {
|
|
105
|
+
issues.push('Installed agent registry must use schema_version 2.0.');
|
|
106
|
+
}
|
|
107
|
+
} catch {
|
|
108
|
+
issues.push(`Malformed agent registry: ${path.relative(process.cwd(), installedRegistryPath)}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const agentNames = new Set(registry.agents.map((agent) => agent.name));
|
|
112
|
+
for (const [commandName, agents] of Object.entries(registry.commands || {})) {
|
|
113
|
+
if (!COMMANDS.en.some((cmd) => cmd.name === commandName)) {
|
|
114
|
+
issues.push(`Agent registry references unknown command: ${commandName}`);
|
|
115
|
+
}
|
|
116
|
+
for (const agentName of agents) {
|
|
117
|
+
if (!agentNames.has(agentName)) {
|
|
118
|
+
issues.push(`Agent registry references missing agent: ${agentName}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function checkProviderAgentCompatibility(projectRoot, modules, issues) {
|
|
125
|
+
const expected = listExpectedAgentFiles(projectRoot, modules);
|
|
126
|
+
for (const file of expected) {
|
|
127
|
+
if (!fs.existsSync(file)) continue;
|
|
128
|
+
const rel = path.relative(projectRoot, file);
|
|
129
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
130
|
+
if (rel.startsWith(`${AGENT_TARGETS.copilot.dir}/`)) {
|
|
131
|
+
for (const unsupported of ['permissionMode:', 'maxTurns:', 'model:', 'mode: subagent']) {
|
|
132
|
+
if (content.includes(unsupported)) issues.push(`Copilot agent contains unsupported field ${unsupported} ${rel}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (rel.startsWith(`${AGENT_TARGETS.claude.dir}/`) && !content.includes('permissionMode:')) {
|
|
136
|
+
issues.push(`Claude agent missing permissionMode: ${rel}`);
|
|
137
|
+
}
|
|
138
|
+
if (rel.startsWith(`${AGENT_TARGETS.opencode.dir}/`) && content.includes('\ntools:')) {
|
|
139
|
+
issues.push(`OpenCode agent should use permission instead of tools: ${rel}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function checkSubtaskLayout(projectRoot, issues) {
|
|
145
|
+
const gitignore = path.join(projectRoot, '.dw', 'subtasks', '.gitignore');
|
|
146
|
+
if (!fs.existsSync(gitignore)) issues.push(`Missing subtask gitignore: ${path.relative(projectRoot, gitignore)}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function checkSkillRegistry(projectRoot, issues, warnings) {
|
|
150
|
+
const validation = validateSkillRegistry();
|
|
151
|
+
issues.push(...validation.issues);
|
|
152
|
+
warnings.push(...validation.warnings);
|
|
153
|
+
|
|
154
|
+
const installedRegistryPath = path.join(projectRoot, '.dw', 'skill-registry.json');
|
|
155
|
+
if (!fs.existsSync(installedRegistryPath)) {
|
|
156
|
+
issues.push(`Missing skill registry: ${path.relative(projectRoot, installedRegistryPath)}`);
|
|
157
|
+
} else {
|
|
158
|
+
try {
|
|
159
|
+
const installedRegistry = JSON.parse(fs.readFileSync(installedRegistryPath, 'utf-8'));
|
|
160
|
+
const currentRegistry = readSkillRegistry();
|
|
161
|
+
if (installedRegistry.schema_version !== currentRegistry.schema_version) {
|
|
162
|
+
warnings.push('Installed skill registry schema differs from package registry; run repair.');
|
|
163
|
+
}
|
|
164
|
+
const installedNames = new Set((installedRegistry.skills || []).map((entry) => entry.name));
|
|
165
|
+
for (const entry of currentRegistry.skills) {
|
|
166
|
+
if (entry.bundled && !installedNames.has(entry.name)) {
|
|
167
|
+
warnings.push(`Installed skill registry missing bundled entry: ${entry.name}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
} catch {
|
|
171
|
+
issues.push(`Malformed skill registry: ${path.relative(projectRoot, installedRegistryPath)}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const registry = readSkillRegistry();
|
|
176
|
+
for (const entry of registry.skills) {
|
|
177
|
+
if (!entry.bundled) continue;
|
|
178
|
+
const skillPath = path.join(projectRoot, '.agents', 'skills', entry.name, 'SKILL.md');
|
|
179
|
+
if (!fs.existsSync(skillPath)) {
|
|
180
|
+
issues.push(`Missing bundled skill: ${path.relative(projectRoot, skillPath)}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function checkMcp(projectRoot, issues) {
|
|
186
|
+
const settingsPath = path.join(projectRoot, '.claude', 'settings.json');
|
|
187
|
+
if (!fs.existsSync(settingsPath)) {
|
|
188
|
+
issues.push('Missing .claude/settings.json with MCP configuration');
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
194
|
+
for (const name of Object.keys(MCP_SERVERS)) {
|
|
195
|
+
if (!settings.mcpServers || !settings.mcpServers[name]) {
|
|
196
|
+
issues.push(`Missing MCP server: ${name}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
} catch {
|
|
200
|
+
issues.push('Malformed .claude/settings.json');
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function checkLegacyCommands(projectRoot, warnings) {
|
|
205
|
+
for (const entry of removedCommands) {
|
|
206
|
+
const file = path.join(projectRoot, '.dw', 'commands', `${entry.name}.md`);
|
|
207
|
+
if (fs.existsSync(file)) {
|
|
208
|
+
warnings.push(`Legacy command source still exists: ${path.relative(projectRoot, file)} -> ${entry.replacedBy}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
module.exports = { run };
|
package/lib/init.js
CHANGED
|
@@ -1,38 +1,55 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const { COMMANDS } = require('./constants');
|
|
3
|
-
const { ensureDir, copyDir, writeFile, upsertDelimitedBlock, log } = require('./utils');
|
|
3
|
+
const { ensureDir, copyDir, writeFile, upsertDelimitedBlock, log, listFilesRecursive } = require('./utils');
|
|
4
4
|
const { selectLanguage } = require('./prompts');
|
|
5
5
|
const { generateWrappers } = require('./wrappers');
|
|
6
6
|
const { installMCPs } = require('./mcp');
|
|
7
|
+
const { generateAgents } = require('./agents');
|
|
7
8
|
const { migrate: migrateGsd } = require('./migrate-gsd');
|
|
8
9
|
const { migrate: migrateSkills } = require('./migrate-skills');
|
|
10
|
+
const { hasExistingInstall, readInstallState, writeInstallState, listManagedFiles } = require('./install-state');
|
|
11
|
+
const { resolveInstallSelection } = require('./profiles');
|
|
12
|
+
const { ensureSubtaskLayout } = require('./subtasks');
|
|
9
13
|
|
|
10
14
|
const SCAFFOLD_DIR = path.join(__dirname, '..', 'scaffold');
|
|
11
15
|
|
|
12
|
-
async function run({ force = false, lang = null, mode = 'init' }) {
|
|
16
|
+
async function run({ force = false, lang = null, mode = 'init', profile = null, modules = null }) {
|
|
13
17
|
const projectRoot = process.cwd();
|
|
14
18
|
const isUpdate = mode === 'update';
|
|
19
|
+
const isRepair = mode === 'repair';
|
|
20
|
+
const existingInstall = hasExistingInstall(projectRoot);
|
|
21
|
+
const priorState = readInstallState(projectRoot);
|
|
15
22
|
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
const
|
|
23
|
+
// Update/repair and init over an existing dev-workflow install reconcile managed
|
|
24
|
+
// files to the current scaffold. Fresh init keeps normal non-destructive behavior.
|
|
25
|
+
const shouldReconcile = isUpdate || isRepair || (mode === 'init' && existingInstall);
|
|
26
|
+
const managedForce = shouldReconcile ? true : force;
|
|
19
27
|
|
|
20
|
-
console.log(`\n dev-workflow ${isUpdate ? 'update' : 'init'}`);
|
|
28
|
+
console.log(`\n dev-workflow ${isRepair ? 'repair' : (isUpdate ? 'update' : 'init')}`);
|
|
21
29
|
console.log(` ${'='.repeat(40)}\n`);
|
|
30
|
+
if (mode === 'init' && existingInstall) {
|
|
31
|
+
console.log(' Existing dev-workflow install detected; reconciling managed files.\n');
|
|
32
|
+
}
|
|
22
33
|
|
|
23
34
|
// 0. GSD migration (idempotent; only runs if legacy GSD artifacts are detected)
|
|
24
35
|
// 0.1. Skill migration: removed bundled skills + orphan dw-* wrappers (idempotent)
|
|
25
|
-
if (
|
|
36
|
+
if (shouldReconcile) {
|
|
26
37
|
migrateGsd(projectRoot);
|
|
27
38
|
migrateSkills(projectRoot);
|
|
28
39
|
}
|
|
29
40
|
|
|
30
41
|
// 1. Select language
|
|
31
|
-
const selectedLang = await selectLanguage(lang);
|
|
42
|
+
const selectedLang = await selectLanguage(lang || (priorState && priorState.lang));
|
|
43
|
+
const selection = resolveInstallSelection({
|
|
44
|
+
profile: profile || (priorState && priorState.profile),
|
|
45
|
+
modules: modules || (priorState && Array.isArray(priorState.modules) ? priorState.modules.join(',') : null),
|
|
46
|
+
});
|
|
32
47
|
console.log(`\n Language: ${selectedLang}\n`);
|
|
48
|
+
console.log(` Profile: ${selection.profile} (${selection.modules.join(', ')})\n`);
|
|
33
49
|
|
|
34
50
|
const commands = COMMANDS[selectedLang];
|
|
35
51
|
const langDir = path.join(SCAFFOLD_DIR, selectedLang);
|
|
52
|
+
const touchedFiles = [];
|
|
36
53
|
|
|
37
54
|
// Track totals
|
|
38
55
|
let totalCreated = 0;
|
|
@@ -49,6 +66,7 @@ async function run({ force = false, lang = null, mode = 'init' }) {
|
|
|
49
66
|
totalCreated += cmdResults.created;
|
|
50
67
|
totalSkipped += cmdResults.skipped;
|
|
51
68
|
totalOverwritten += cmdResults.overwritten;
|
|
69
|
+
touchedFiles.push(...listFilesRecursive(path.join(projectRoot, '.dw', 'commands')));
|
|
52
70
|
console.log(` ${cmdResults.created} created, ${cmdResults.skipped} skipped, ${cmdResults.overwritten} overwritten\n`);
|
|
53
71
|
|
|
54
72
|
// 3. Copy templates (override-aware: .dw/templates/overrides/<name> wins over scaffold core)
|
|
@@ -64,6 +82,7 @@ async function run({ force = false, lang = null, mode = 'init' }) {
|
|
|
64
82
|
totalCreated += tplResults.created;
|
|
65
83
|
totalSkipped += tplResults.skipped;
|
|
66
84
|
totalOverwritten += tplResults.overwritten;
|
|
85
|
+
touchedFiles.push(...listFilesRecursive(templatesDir));
|
|
67
86
|
console.log(` ${tplResults.created} created, ${tplResults.skipped} skipped, ${tplResults.overwritten} overwritten\n`);
|
|
68
87
|
|
|
69
88
|
// 3.1. Seed .dw/templates/overrides/ with README on first init (never overwritten)
|
|
@@ -78,6 +97,7 @@ async function run({ force = false, lang = null, mode = 'init' }) {
|
|
|
78
97
|
);
|
|
79
98
|
if (ovrStatus === 'created') totalCreated++;
|
|
80
99
|
else totalSkipped++;
|
|
100
|
+
touchedFiles.push(overridesReadmeDest);
|
|
81
101
|
}
|
|
82
102
|
|
|
83
103
|
// 3.5. Copy references (language-specific)
|
|
@@ -92,6 +112,7 @@ async function run({ force = false, lang = null, mode = 'init' }) {
|
|
|
92
112
|
totalCreated += refsResults.created;
|
|
93
113
|
totalSkipped += refsResults.skipped;
|
|
94
114
|
totalOverwritten += refsResults.overwritten;
|
|
115
|
+
touchedFiles.push(...listFilesRecursive(path.join(projectRoot, '.dw', 'references')));
|
|
95
116
|
console.log(` ${refsResults.created} created, ${refsResults.skipped} skipped, ${refsResults.overwritten} overwritten\n`);
|
|
96
117
|
}
|
|
97
118
|
|
|
@@ -107,9 +128,46 @@ async function run({ force = false, lang = null, mode = 'init' }) {
|
|
|
107
128
|
totalCreated += scriptsResults.created;
|
|
108
129
|
totalSkipped += scriptsResults.skipped;
|
|
109
130
|
totalOverwritten += scriptsResults.overwritten;
|
|
131
|
+
touchedFiles.push(...listFilesRecursive(path.join(projectRoot, '.dw', 'scripts')));
|
|
110
132
|
console.log(` ${scriptsResults.created} created, ${scriptsResults.skipped} skipped, ${scriptsResults.overwritten} overwritten\n`);
|
|
111
133
|
}
|
|
112
134
|
|
|
135
|
+
// 3.7. Copy skill registry for agent-facing skill health audits.
|
|
136
|
+
const skillRegistrySrc = path.join(SCAFFOLD_DIR, 'skill-registry.json');
|
|
137
|
+
if (require('fs').existsSync(skillRegistrySrc)) {
|
|
138
|
+
console.log(' Skill registry:');
|
|
139
|
+
const skillRegistryDest = path.join(projectRoot, '.dw', 'skill-registry.json');
|
|
140
|
+
const registryStatus = writeFile(
|
|
141
|
+
skillRegistryDest,
|
|
142
|
+
require('fs').readFileSync(skillRegistrySrc, 'utf-8'),
|
|
143
|
+
managedForce
|
|
144
|
+
);
|
|
145
|
+
log(registryStatus, skillRegistryDest);
|
|
146
|
+
if (registryStatus === 'created') totalCreated++;
|
|
147
|
+
else if (registryStatus === 'overwritten') totalOverwritten++;
|
|
148
|
+
else totalSkipped++;
|
|
149
|
+
touchedFiles.push(skillRegistryDest);
|
|
150
|
+
console.log();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 3.8. Copy agent registry for dispatch, budgets, and provider health audits.
|
|
154
|
+
const agentRegistrySrc = path.join(SCAFFOLD_DIR, 'agent-registry.json');
|
|
155
|
+
if (require('fs').existsSync(agentRegistrySrc)) {
|
|
156
|
+
console.log(' Agent registry:');
|
|
157
|
+
const agentRegistryDest = path.join(projectRoot, '.dw', 'agent-registry.json');
|
|
158
|
+
const registryStatus = writeFile(
|
|
159
|
+
agentRegistryDest,
|
|
160
|
+
require('fs').readFileSync(agentRegistrySrc, 'utf-8'),
|
|
161
|
+
managedForce
|
|
162
|
+
);
|
|
163
|
+
log(registryStatus, agentRegistryDest);
|
|
164
|
+
if (registryStatus === 'created') totalCreated++;
|
|
165
|
+
else if (registryStatus === 'overwritten') totalOverwritten++;
|
|
166
|
+
else totalSkipped++;
|
|
167
|
+
touchedFiles.push(agentRegistryDest);
|
|
168
|
+
console.log();
|
|
169
|
+
}
|
|
170
|
+
|
|
113
171
|
// 4. Create .dw/rules/ with README
|
|
114
172
|
if (!isUpdate) {
|
|
115
173
|
console.log(' Rules:');
|
|
@@ -124,6 +182,7 @@ async function run({ force = false, lang = null, mode = 'init' }) {
|
|
|
124
182
|
log(status, rulesReadmeDest);
|
|
125
183
|
if (status === 'created') totalCreated++;
|
|
126
184
|
else totalSkipped++;
|
|
185
|
+
touchedFiles.push(rulesReadmeDest);
|
|
127
186
|
console.log();
|
|
128
187
|
}
|
|
129
188
|
|
|
@@ -146,6 +205,7 @@ async function run({ force = false, lang = null, mode = 'init' }) {
|
|
|
146
205
|
log(stateStatus, stateDestPath);
|
|
147
206
|
if (stateStatus === 'created') totalCreated++;
|
|
148
207
|
else totalSkipped++;
|
|
208
|
+
touchedFiles.push(stateDestPath);
|
|
149
209
|
}
|
|
150
210
|
console.log();
|
|
151
211
|
}
|
|
@@ -158,8 +218,13 @@ async function run({ force = false, lang = null, mode = 'init' }) {
|
|
|
158
218
|
const gitkeepStatus = writeFile(gitkeepPath, '', false);
|
|
159
219
|
if (gitkeepStatus === 'created') totalCreated++;
|
|
160
220
|
else totalSkipped++;
|
|
221
|
+
touchedFiles.push(gitkeepPath);
|
|
161
222
|
}
|
|
162
223
|
|
|
224
|
+
// 5.3. Create local ephemeral subtask handoff directories.
|
|
225
|
+
ensureSubtaskLayout(projectRoot);
|
|
226
|
+
touchedFiles.push(path.join(projectRoot, '.dw', 'subtasks', '.gitignore'));
|
|
227
|
+
|
|
163
228
|
// 5.5. Copy bundled skills to .agents/skills/
|
|
164
229
|
const skillsSrcDir = path.join(SCAFFOLD_DIR, 'skills');
|
|
165
230
|
if (require('fs').existsSync(skillsSrcDir)) {
|
|
@@ -172,9 +237,19 @@ async function run({ force = false, lang = null, mode = 'init' }) {
|
|
|
172
237
|
totalCreated += skillsResults.created;
|
|
173
238
|
totalSkipped += skillsResults.skipped;
|
|
174
239
|
totalOverwritten += skillsResults.overwritten;
|
|
240
|
+
touchedFiles.push(...listFilesRecursive(path.join(projectRoot, '.agents', 'skills')));
|
|
175
241
|
console.log(` ${skillsResults.created} created, ${skillsResults.skipped} skipped, ${skillsResults.overwritten} overwritten\n`);
|
|
176
242
|
}
|
|
177
243
|
|
|
244
|
+
// 5.6. Generate platform-native/fallback agents from the selected modules.
|
|
245
|
+
console.log(' Agents:');
|
|
246
|
+
const agentResults = generateAgents(projectRoot, selection.modules, managedForce);
|
|
247
|
+
totalCreated += agentResults.created;
|
|
248
|
+
totalSkipped += agentResults.skipped;
|
|
249
|
+
totalOverwritten += agentResults.overwritten;
|
|
250
|
+
touchedFiles.push(...agentResults.files);
|
|
251
|
+
console.log(` ${agentResults.created} created, ${agentResults.skipped} skipped, ${agentResults.overwritten} overwritten\n`);
|
|
252
|
+
|
|
178
253
|
// 5.7. Install CLAUDE.md + AGENTS.md with auto-trigger decision tree
|
|
179
254
|
// Both files receive the same content (Claude Code reads CLAUDE.md; Codex/Copilot/OpenCode
|
|
180
255
|
// converge on AGENTS.md). Merge-aware: user edits outside the dev-workflow markers are preserved.
|
|
@@ -191,6 +266,7 @@ async function run({ force = false, lang = null, mode = 'init' }) {
|
|
|
191
266
|
if (status === 'created') totalCreated++;
|
|
192
267
|
else if (status === 'updated') totalOverwritten++;
|
|
193
268
|
else totalSkipped++;
|
|
269
|
+
touchedFiles.push(target);
|
|
194
270
|
}
|
|
195
271
|
console.log();
|
|
196
272
|
}
|
|
@@ -201,6 +277,7 @@ async function run({ force = false, lang = null, mode = 'init' }) {
|
|
|
201
277
|
const opcStatus = writeFile(opencodePackageJson, opencodeContent, false);
|
|
202
278
|
if (opcStatus === 'created') totalCreated++;
|
|
203
279
|
else totalSkipped++;
|
|
280
|
+
touchedFiles.push(opencodePackageJson);
|
|
204
281
|
|
|
205
282
|
// 6.5. Clean up legacy .codex/skills/ (now served by .agents/skills/)
|
|
206
283
|
const legacyCodexDir = path.join(projectRoot, '.codex', 'skills');
|
|
@@ -216,6 +293,7 @@ async function run({ force = false, lang = null, mode = 'init' }) {
|
|
|
216
293
|
totalCreated += wrapperResults.created;
|
|
217
294
|
totalSkipped += wrapperResults.skipped;
|
|
218
295
|
totalOverwritten += wrapperResults.overwritten;
|
|
296
|
+
touchedFiles.push(...wrapperResults.files);
|
|
219
297
|
console.log();
|
|
220
298
|
|
|
221
299
|
// 9. Install MCPs (always, including updates)
|
|
@@ -223,6 +301,16 @@ async function run({ force = false, lang = null, mode = 'init' }) {
|
|
|
223
301
|
const mcpResults = installMCPs(projectRoot);
|
|
224
302
|
console.log(` ${mcpResults.added} configured, ${mcpResults.skipped} already present\n`);
|
|
225
303
|
|
|
304
|
+
// 9.5. Persist install state for diagnostics and repair.
|
|
305
|
+
const installState = writeInstallState(projectRoot, {
|
|
306
|
+
lang: selectedLang,
|
|
307
|
+
profile: selection.profile,
|
|
308
|
+
modules: selection.modules,
|
|
309
|
+
platforms: ['claude', 'agents', 'opencode', 'copilot'],
|
|
310
|
+
managedFiles: listManagedFiles(projectRoot, touchedFiles),
|
|
311
|
+
});
|
|
312
|
+
console.log(` Install state: ${path.relative(projectRoot, path.join(projectRoot, '.dw', 'install-state.json'))} (${installState.managed_files.length} files tracked)\n`);
|
|
313
|
+
|
|
226
314
|
// 10. Summary
|
|
227
315
|
console.log(` ${'='.repeat(40)}`);
|
|
228
316
|
console.log(` Done! ${totalCreated} created, ${totalSkipped} skipped, ${totalOverwritten} overwritten`);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const pkg = require('../package.json');
|
|
4
|
+
const { readJson, writeJson } = require('./utils');
|
|
5
|
+
|
|
6
|
+
const STATE_RELATIVE_PATH = path.join('.dw', 'install-state.json');
|
|
7
|
+
|
|
8
|
+
function statePath(projectRoot) {
|
|
9
|
+
return path.join(projectRoot, STATE_RELATIVE_PATH);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function readInstallState(projectRoot) {
|
|
13
|
+
return readJson(statePath(projectRoot), null);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function hasExistingInstall(projectRoot) {
|
|
17
|
+
return Boolean(
|
|
18
|
+
fs.existsSync(path.join(projectRoot, '.dw', 'commands')) ||
|
|
19
|
+
fs.existsSync(path.join(projectRoot, 'CLAUDE.md')) ||
|
|
20
|
+
fs.existsSync(path.join(projectRoot, 'AGENTS.md')) ||
|
|
21
|
+
readInstallState(projectRoot)
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function writeInstallState(projectRoot, state) {
|
|
26
|
+
const existing = readInstallState(projectRoot);
|
|
27
|
+
const now = new Date().toISOString();
|
|
28
|
+
const next = {
|
|
29
|
+
schema_version: '1.0',
|
|
30
|
+
package: pkg.name,
|
|
31
|
+
version: pkg.version,
|
|
32
|
+
lang: state.lang,
|
|
33
|
+
profile: state.profile,
|
|
34
|
+
modules: state.modules || [],
|
|
35
|
+
platforms: state.platforms || [],
|
|
36
|
+
managed_files: state.managedFiles || [],
|
|
37
|
+
installed_at: existing && existing.installed_at ? existing.installed_at : now,
|
|
38
|
+
updated_at: now,
|
|
39
|
+
};
|
|
40
|
+
writeJson(statePath(projectRoot), next);
|
|
41
|
+
return next;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function listManagedFiles(projectRoot, relativeFiles) {
|
|
45
|
+
return relativeFiles
|
|
46
|
+
.filter(Boolean)
|
|
47
|
+
.map((filePath) => path.relative(projectRoot, filePath))
|
|
48
|
+
.sort();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = {
|
|
52
|
+
STATE_RELATIVE_PATH,
|
|
53
|
+
hasExistingInstall,
|
|
54
|
+
readInstallState,
|
|
55
|
+
writeInstallState,
|
|
56
|
+
listManagedFiles,
|
|
57
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { readInstallState, STATE_RELATIVE_PATH } = require('./install-state');
|
|
3
|
+
|
|
4
|
+
function run() {
|
|
5
|
+
const projectRoot = process.cwd();
|
|
6
|
+
const state = readInstallState(projectRoot);
|
|
7
|
+
|
|
8
|
+
if (!state) {
|
|
9
|
+
console.log(`No dev-workflow install state found at ${STATE_RELATIVE_PATH}.`);
|
|
10
|
+
console.log('Run `npx @brunosps00/dev-workflow init` or `update` to create it.');
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
console.log('dev-workflow install\n');
|
|
15
|
+
console.log(`Root: ${projectRoot}`);
|
|
16
|
+
console.log(`Package: ${state.package || '@brunosps00/dev-workflow'}@${state.version || '(unknown)'}`);
|
|
17
|
+
console.log(`Language: ${state.lang || '(unknown)'}`);
|
|
18
|
+
console.log(`Profile: ${state.profile || '(unknown)'}`);
|
|
19
|
+
console.log(`Modules: ${(state.modules || []).join(', ') || '(none)'}`);
|
|
20
|
+
console.log(`Platforms: ${(state.platforms || []).join(', ') || '(none)'}`);
|
|
21
|
+
console.log(`Managed files tracked: ${(state.managed_files || []).length}`);
|
|
22
|
+
console.log(`Installed at: ${state.installed_at || '(unknown)'}`);
|
|
23
|
+
console.log(`Updated at: ${state.updated_at || '(unknown)'}`);
|
|
24
|
+
console.log(`State: ${path.join(projectRoot, STATE_RELATIVE_PATH)}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = { run };
|
package/lib/migrate-skills.js
CHANGED
|
@@ -28,8 +28,9 @@ const removedCommands = require('./removed-commands');
|
|
|
28
28
|
function migrate(projectRoot) {
|
|
29
29
|
const removed = scanRemovedBundledSkills(projectRoot);
|
|
30
30
|
const orphans = scanOrphanCommandWrappers(projectRoot);
|
|
31
|
+
const commandSources = scanRemovedCommandSources(projectRoot);
|
|
31
32
|
|
|
32
|
-
if (removed.length === 0 && orphans.length === 0) {
|
|
33
|
+
if (removed.length === 0 && orphans.length === 0 && commandSources.length === 0) {
|
|
33
34
|
return null; // silent — nothing to clean
|
|
34
35
|
}
|
|
35
36
|
|
|
@@ -56,8 +57,16 @@ function migrate(projectRoot) {
|
|
|
56
57
|
rmRecursive(entry.path);
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
for (const entry of commandSources) {
|
|
61
|
+
const rel = path.relative(projectRoot, entry.path);
|
|
62
|
+
console.log(
|
|
63
|
+
` \x1b[33m-\x1b[0m [legacy] Removing command source ${rel} (replaced in v${entry.removedIn} by \`${entry.replacedBy}\`)`
|
|
64
|
+
);
|
|
65
|
+
rmRecursive(entry.path);
|
|
66
|
+
}
|
|
67
|
+
|
|
59
68
|
console.log();
|
|
60
|
-
return { removed: removed.length, orphans: orphans.length };
|
|
69
|
+
return { removed: removed.length, orphans: orphans.length, commandSources: commandSources.length };
|
|
61
70
|
}
|
|
62
71
|
|
|
63
72
|
function scanRemovedBundledSkills(projectRoot) {
|
|
@@ -128,6 +137,21 @@ function scanOrphanCommandWrappers(projectRoot) {
|
|
|
128
137
|
return orphans;
|
|
129
138
|
}
|
|
130
139
|
|
|
140
|
+
function scanRemovedCommandSources(projectRoot) {
|
|
141
|
+
const matches = [];
|
|
142
|
+
const commandsDir = path.join(projectRoot, '.dw', 'commands');
|
|
143
|
+
if (!fs.existsSync(commandsDir)) return matches;
|
|
144
|
+
|
|
145
|
+
for (const entry of removedCommands) {
|
|
146
|
+
const target = path.join(commandsDir, `${entry.name}.md`);
|
|
147
|
+
if (fs.existsSync(target)) {
|
|
148
|
+
matches.push({ ...entry, path: target });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return matches;
|
|
153
|
+
}
|
|
154
|
+
|
|
131
155
|
function rmRecursive(target) {
|
|
132
156
|
if (!fs.existsSync(target)) return;
|
|
133
157
|
fs.rmSync(target, { recursive: true, force: true });
|
package/lib/profiles.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const PROFILES = {
|
|
2
|
+
core: {
|
|
3
|
+
description: 'Core workflow agents only.',
|
|
4
|
+
modules: ['core'],
|
|
5
|
+
},
|
|
6
|
+
standard: {
|
|
7
|
+
description: 'Core workflow plus security and frontend QA agents.',
|
|
8
|
+
modules: ['core', 'security', 'frontend'],
|
|
9
|
+
},
|
|
10
|
+
full: {
|
|
11
|
+
description: 'All bundled dev-workflow agents.',
|
|
12
|
+
modules: ['core', 'security', 'frontend', 'typescript', 'python', 'csharp', 'rust'],
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const DEFAULT_PROFILE = 'standard';
|
|
17
|
+
|
|
18
|
+
function parseCsv(value) {
|
|
19
|
+
if (!value || value === true) return [];
|
|
20
|
+
return String(value)
|
|
21
|
+
.split(',')
|
|
22
|
+
.map((item) => item.trim())
|
|
23
|
+
.filter(Boolean);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function resolveInstallSelection({ profile, modules }) {
|
|
27
|
+
const selectedProfile = profile || DEFAULT_PROFILE;
|
|
28
|
+
if (!PROFILES[selectedProfile]) {
|
|
29
|
+
throw new Error(`Unknown profile: ${selectedProfile}. Valid profiles: ${Object.keys(PROFILES).join(', ')}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const explicitModules = parseCsv(modules);
|
|
33
|
+
const selectedModules = new Set(PROFILES[selectedProfile].modules);
|
|
34
|
+
for (const moduleName of explicitModules) {
|
|
35
|
+
selectedModules.add(moduleName);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
profile: selectedProfile,
|
|
40
|
+
modules: Array.from(selectedModules).sort(),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = { DEFAULT_PROFILE, PROFILES, resolveInstallSelection };
|