bmad-module-skill-forge 0.2.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/.nvmrc +1 -0
- package/LICENSE +26 -0
- package/README.md +213 -0
- package/docs/404.md +20 -0
- package/docs/agents.md +73 -0
- package/docs/architecture.md +301 -0
- package/docs/examples.md +144 -0
- package/docs/getting-started.md +198 -0
- package/docs/index.md +75 -0
- package/docs/workflows.md +190 -0
- package/package.json +109 -0
- package/src/agents/forger.agent.yaml +93 -0
- package/src/forger/README.md +17 -0
- package/src/forger/forge-tier.yaml +13 -0
- package/src/forger/preferences.yaml +10 -0
- package/src/knowledge/agentskills-spec.md +158 -0
- package/src/knowledge/confidence-tiers.md +153 -0
- package/src/knowledge/manual-section-integrity.md +156 -0
- package/src/knowledge/overview.md +56 -0
- package/src/knowledge/progressive-capability.md +120 -0
- package/src/knowledge/provenance-tracking.md +163 -0
- package/src/knowledge/skf-knowledge-index.csv +9 -0
- package/src/knowledge/skill-lifecycle.md +150 -0
- package/src/knowledge/zero-hallucination.md +101 -0
- package/src/module-help.csv +12 -0
- package/src/module.yaml +22 -0
- package/src/workflows/README.md +148 -0
- package/src/workflows/analyze-source/data/skill-brief-schema.md +75 -0
- package/src/workflows/analyze-source/data/unit-detection-heuristics.md +88 -0
- package/src/workflows/analyze-source/steps-c/step-01-init.md +191 -0
- package/src/workflows/analyze-source/steps-c/step-01b-continue.md +144 -0
- package/src/workflows/analyze-source/steps-c/step-02-scan-project.md +208 -0
- package/src/workflows/analyze-source/steps-c/step-03-identify-units.md +199 -0
- package/src/workflows/analyze-source/steps-c/step-04-map-and-detect.md +222 -0
- package/src/workflows/analyze-source/steps-c/step-05-recommend.md +236 -0
- package/src/workflows/analyze-source/steps-c/step-06-generate-briefs.md +239 -0
- package/src/workflows/analyze-source/templates/analysis-report-template.md +41 -0
- package/src/workflows/analyze-source/validation-report.md +657 -0
- package/src/workflows/analyze-source/workflow-plan-analyze-source.md +385 -0
- package/src/workflows/analyze-source/workflow.md +61 -0
- package/src/workflows/audit-skill/data/drift-report-template.md +62 -0
- package/src/workflows/audit-skill/data/severity-rules.md +52 -0
- package/src/workflows/audit-skill/steps-c/step-01-init.md +195 -0
- package/src/workflows/audit-skill/steps-c/step-02-re-index.md +191 -0
- package/src/workflows/audit-skill/steps-c/step-03-structural-diff.md +194 -0
- package/src/workflows/audit-skill/steps-c/step-04-semantic-diff.md +199 -0
- package/src/workflows/audit-skill/steps-c/step-05-severity-classify.md +210 -0
- package/src/workflows/audit-skill/steps-c/step-06-report.md +217 -0
- package/src/workflows/audit-skill/validation-report.md +545 -0
- package/src/workflows/audit-skill/workflow-plan-audit-skill.md +318 -0
- package/src/workflows/audit-skill/workflow.md +63 -0
- package/src/workflows/brief-skill/data/skill-brief-schema.md +82 -0
- package/src/workflows/brief-skill/steps-c/step-01-gather-intent.md +184 -0
- package/src/workflows/brief-skill/steps-c/step-02-analyze-target.md +203 -0
- package/src/workflows/brief-skill/steps-c/step-03-scope-definition.md +225 -0
- package/src/workflows/brief-skill/steps-c/step-04-confirm-brief.md +185 -0
- package/src/workflows/brief-skill/steps-c/step-05-write-brief.md +156 -0
- package/src/workflows/brief-skill/validation-report.md +630 -0
- package/src/workflows/brief-skill/workflow-plan-brief-skill.md +360 -0
- package/src/workflows/brief-skill/workflow.md +57 -0
- package/src/workflows/create-skill/data/extraction-patterns.md +63 -0
- package/src/workflows/create-skill/data/skill-sections.md +166 -0
- package/src/workflows/create-skill/steps-c/step-01-load-brief.md +176 -0
- package/src/workflows/create-skill/steps-c/step-02-ecosystem-check.md +143 -0
- package/src/workflows/create-skill/steps-c/step-03-extract.md +184 -0
- package/src/workflows/create-skill/steps-c/step-04-enrich.md +152 -0
- package/src/workflows/create-skill/steps-c/step-05-compile.md +198 -0
- package/src/workflows/create-skill/steps-c/step-06-validate.md +162 -0
- package/src/workflows/create-skill/steps-c/step-07-generate-artifacts.md +165 -0
- package/src/workflows/create-skill/steps-c/step-08-report.md +160 -0
- package/src/workflows/create-skill/validation-report.md +189 -0
- package/src/workflows/create-skill/workflow-plan-create-skill.md +491 -0
- package/src/workflows/create-skill/workflow.md +52 -0
- package/src/workflows/create-stack-skill/data/integration-patterns.md +61 -0
- package/src/workflows/create-stack-skill/data/manifest-patterns.md +35 -0
- package/src/workflows/create-stack-skill/data/stack-skill-template.md +110 -0
- package/src/workflows/create-stack-skill/steps-c/step-01-init.md +142 -0
- package/src/workflows/create-stack-skill/steps-c/step-02-detect-manifests.md +145 -0
- package/src/workflows/create-stack-skill/steps-c/step-03-rank-and-confirm.md +161 -0
- package/src/workflows/create-stack-skill/steps-c/step-04-parallel-extract.md +155 -0
- package/src/workflows/create-stack-skill/steps-c/step-05-detect-integrations.md +154 -0
- package/src/workflows/create-stack-skill/steps-c/step-06-compile-stack.md +160 -0
- package/src/workflows/create-stack-skill/steps-c/step-07-generate-output.md +202 -0
- package/src/workflows/create-stack-skill/steps-c/step-08-validate.md +193 -0
- package/src/workflows/create-stack-skill/steps-c/step-09-report.md +137 -0
- package/src/workflows/create-stack-skill/validation-report.md +401 -0
- package/src/workflows/create-stack-skill/workflow-plan-create-stack-skill.md +481 -0
- package/src/workflows/create-stack-skill/workflow.md +59 -0
- package/src/workflows/export-skill/data/managed-section-format.md +70 -0
- package/src/workflows/export-skill/data/snippet-format.md +32 -0
- package/src/workflows/export-skill/steps-c/step-01-load-skill.md +184 -0
- package/src/workflows/export-skill/steps-c/step-02-package.md +153 -0
- package/src/workflows/export-skill/steps-c/step-03-generate-snippet.md +166 -0
- package/src/workflows/export-skill/steps-c/step-04-update-context.md +222 -0
- package/src/workflows/export-skill/steps-c/step-05-token-report.md +126 -0
- package/src/workflows/export-skill/steps-c/step-06-summary.md +167 -0
- package/src/workflows/export-skill/validation-report.md +263 -0
- package/src/workflows/export-skill/workflow-plan-export-skill.md +369 -0
- package/src/workflows/export-skill/workflow.md +56 -0
- package/src/workflows/quick-skill/data/registry-resolution.md +91 -0
- package/src/workflows/quick-skill/data/skill-template.md +66 -0
- package/src/workflows/quick-skill/steps-c/step-01-resolve-target.md +165 -0
- package/src/workflows/quick-skill/steps-c/step-02-ecosystem-check.md +143 -0
- package/src/workflows/quick-skill/steps-c/step-03-quick-extract.md +177 -0
- package/src/workflows/quick-skill/steps-c/step-04-compile.md +180 -0
- package/src/workflows/quick-skill/steps-c/step-05-validate.md +150 -0
- package/src/workflows/quick-skill/steps-c/step-06-write.md +160 -0
- package/src/workflows/quick-skill/validation-report.md +543 -0
- package/src/workflows/quick-skill/workflow-plan-quick-skill.md +405 -0
- package/src/workflows/quick-skill/workflow.md +56 -0
- package/src/workflows/setup-forge/data/tier-rules.md +50 -0
- package/src/workflows/setup-forge/steps-c/step-01-detect-and-tier.md +140 -0
- package/src/workflows/setup-forge/steps-c/step-02-write-config.md +141 -0
- package/src/workflows/setup-forge/steps-c/step-03-auto-index.md +116 -0
- package/src/workflows/setup-forge/steps-c/step-04-report.md +131 -0
- package/src/workflows/setup-forge/validation-report.md +504 -0
- package/src/workflows/setup-forge/workflow-plan-setup-forge.md +428 -0
- package/src/workflows/setup-forge/workflow.md +49 -0
- package/src/workflows/test-skill/data/output-section-formats.md +96 -0
- package/src/workflows/test-skill/data/scoring-rules.md +74 -0
- package/src/workflows/test-skill/steps-c/step-01-init.md +185 -0
- package/src/workflows/test-skill/steps-c/step-02-detect-mode.md +162 -0
- package/src/workflows/test-skill/steps-c/step-03-coverage-check.md +222 -0
- package/src/workflows/test-skill/steps-c/step-04-coherence-check.md +249 -0
- package/src/workflows/test-skill/steps-c/step-05-score.md +222 -0
- package/src/workflows/test-skill/steps-c/step-06-report.md +175 -0
- package/src/workflows/test-skill/templates/test-report-template.md +24 -0
- package/src/workflows/test-skill/validation-report.md +390 -0
- package/src/workflows/test-skill/workflow-plan-test-skill.md +288 -0
- package/src/workflows/test-skill/workflow.md +57 -0
- package/src/workflows/update-skill/data/manual-section-rules.md +44 -0
- package/src/workflows/update-skill/data/merge-conflict-rules.md +62 -0
- package/src/workflows/update-skill/steps-c/step-01-init.md +208 -0
- package/src/workflows/update-skill/steps-c/step-02-detect-changes.md +184 -0
- package/src/workflows/update-skill/steps-c/step-03-re-extract.md +187 -0
- package/src/workflows/update-skill/steps-c/step-04-merge.md +230 -0
- package/src/workflows/update-skill/steps-c/step-05-validate.md +187 -0
- package/src/workflows/update-skill/steps-c/step-06-write.md +204 -0
- package/src/workflows/update-skill/steps-c/step-07-report.md +201 -0
- package/src/workflows/update-skill/validation-report.md +559 -0
- package/src/workflows/update-skill/workflow-plan-update-skill.md +441 -0
- package/src/workflows/update-skill/workflow.md +56 -0
- package/tools/cli/commands/install.js +33 -0
- package/tools/cli/commands/status.js +195 -0
- package/tools/cli/commands/uninstall.js +226 -0
- package/tools/cli/commands/update.js +63 -0
- package/tools/cli/lib/compiler.js +306 -0
- package/tools/cli/lib/ide-commands.js +244 -0
- package/tools/cli/lib/installer.js +307 -0
- package/tools/cli/lib/manifest.js +109 -0
- package/tools/cli/lib/ui.js +277 -0
- package/tools/cli/lib/version-check.js +86 -0
- package/tools/cli/skf-cli.js +45 -0
- package/tools/skf-npx-wrapper.js +36 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SKF Status Command
|
|
3
|
+
* Shows installation state, version, tier, configured IDEs, and sidecar status.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
const path = require('node:path');
|
|
8
|
+
const fs = require('fs-extra');
|
|
9
|
+
const yaml = require('js-yaml');
|
|
10
|
+
const { readManifest } = require('../lib/manifest');
|
|
11
|
+
|
|
12
|
+
const SKF_FOLDER = '_bmad/skf';
|
|
13
|
+
const SIDECAR_FOLDER = '_bmad/_memory/forger-sidecar';
|
|
14
|
+
|
|
15
|
+
const IDE_NAMES = {
|
|
16
|
+
'claude-code': 'Claude Code',
|
|
17
|
+
cline: 'Cline',
|
|
18
|
+
codex: 'Codex',
|
|
19
|
+
cursor: 'Cursor',
|
|
20
|
+
'github-copilot': 'GitHub Copilot',
|
|
21
|
+
roo: 'Roo Code',
|
|
22
|
+
windsurf: 'Windsurf',
|
|
23
|
+
other: 'Other',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
async function readYaml(filePath) {
|
|
27
|
+
try {
|
|
28
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
29
|
+
return yaml.load(content) || {};
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function getStatus(projectDir) {
|
|
36
|
+
const skfDir = path.join(projectDir, SKF_FOLDER);
|
|
37
|
+
const sidecarDir = path.join(projectDir, SIDECAR_FOLDER);
|
|
38
|
+
|
|
39
|
+
const installed = await fs.pathExists(skfDir);
|
|
40
|
+
if (!installed) {
|
|
41
|
+
return { installed: false };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Read config
|
|
45
|
+
const config = await readYaml(path.join(skfDir, 'config.yaml'));
|
|
46
|
+
|
|
47
|
+
// Read forge tier
|
|
48
|
+
const forgeTier = await readYaml(path.join(sidecarDir, 'forge-tier.yaml'));
|
|
49
|
+
|
|
50
|
+
// Read preferences
|
|
51
|
+
const preferences = await readYaml(path.join(sidecarDir, 'preferences.yaml'));
|
|
52
|
+
|
|
53
|
+
// Check agent file
|
|
54
|
+
const agentFile = path.join(skfDir, 'agents', 'forger.md');
|
|
55
|
+
const agentCompiled = await fs.pathExists(agentFile);
|
|
56
|
+
|
|
57
|
+
// Count workflows
|
|
58
|
+
const workflowsDir = path.join(skfDir, 'workflows', 'skillforge');
|
|
59
|
+
let workflowCount = 0;
|
|
60
|
+
if (await fs.pathExists(workflowsDir)) {
|
|
61
|
+
const entries = await fs.readdir(workflowsDir);
|
|
62
|
+
for (const entry of entries) {
|
|
63
|
+
const wfPath = path.join(workflowsDir, entry, 'workflow.md');
|
|
64
|
+
if (await fs.pathExists(wfPath)) {
|
|
65
|
+
workflowCount++;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Check output folders
|
|
71
|
+
const skillsFolder = config?.skills_output_folder || 'skills';
|
|
72
|
+
const forgeDataFolder = config?.forge_data_folder || 'forge-data';
|
|
73
|
+
const skillsFolderExists = await fs.pathExists(path.join(projectDir, skillsFolder));
|
|
74
|
+
const forgeDataFolderExists = await fs.pathExists(path.join(projectDir, forgeDataFolder));
|
|
75
|
+
|
|
76
|
+
// Check sidecar state
|
|
77
|
+
const sidecarExists = await fs.pathExists(sidecarDir);
|
|
78
|
+
const tierDetected = forgeTier?.tier != null;
|
|
79
|
+
|
|
80
|
+
// Read manifest
|
|
81
|
+
const manifest = await readManifest(projectDir);
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
installed: true,
|
|
85
|
+
config,
|
|
86
|
+
forgeTier,
|
|
87
|
+
preferences,
|
|
88
|
+
agentCompiled,
|
|
89
|
+
workflowCount,
|
|
90
|
+
skillsFolder,
|
|
91
|
+
skillsFolderExists,
|
|
92
|
+
forgeDataFolder,
|
|
93
|
+
forgeDataFolderExists,
|
|
94
|
+
sidecarExists,
|
|
95
|
+
tierDetected,
|
|
96
|
+
manifest,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function displayStatus(status, version) {
|
|
101
|
+
console.log('');
|
|
102
|
+
console.log(chalk.cyan.bold(' Skill Forge — Status'));
|
|
103
|
+
console.log(chalk.dim(` v${version}`));
|
|
104
|
+
console.log('');
|
|
105
|
+
|
|
106
|
+
if (!status.installed) {
|
|
107
|
+
console.log(chalk.yellow(' Not installed.'));
|
|
108
|
+
console.log(chalk.dim(' Run: npx skill-forge install'));
|
|
109
|
+
console.log('');
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const config = status.config || {};
|
|
114
|
+
|
|
115
|
+
// Installation
|
|
116
|
+
const manifest = status.manifest;
|
|
117
|
+
console.log(chalk.white.bold(' Installation'));
|
|
118
|
+
console.log(` Project: ${chalk.cyan(config.project_name || '(unknown)')}`);
|
|
119
|
+
console.log(` SKF folder: ${chalk.dim(SKF_FOLDER + '/')}`);
|
|
120
|
+
console.log(` Agent: ${status.agentCompiled ? chalk.green('compiled') : chalk.yellow('not compiled')}`);
|
|
121
|
+
console.log(` Workflows: ${chalk.white(status.workflowCount)}`);
|
|
122
|
+
if (manifest) {
|
|
123
|
+
console.log(
|
|
124
|
+
` Installed: ${chalk.dim(manifest.installed_at ? new Date(manifest.installed_at).toLocaleDateString() : '(unknown)')}`,
|
|
125
|
+
);
|
|
126
|
+
console.log(` Manifest: ${chalk.green('present')}`);
|
|
127
|
+
} else {
|
|
128
|
+
console.log(` Manifest: ${chalk.yellow('missing')} ${chalk.dim('(reinstall to generate)')}`);
|
|
129
|
+
}
|
|
130
|
+
console.log('');
|
|
131
|
+
|
|
132
|
+
// IDEs
|
|
133
|
+
const ides = config.ides || [];
|
|
134
|
+
console.log(chalk.white.bold(' IDEs'));
|
|
135
|
+
if (ides.length > 0) {
|
|
136
|
+
for (const ide of ides) {
|
|
137
|
+
console.log(` ${chalk.green('●')} ${IDE_NAMES[ide] || ide}`);
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
console.log(chalk.dim(' None configured'));
|
|
141
|
+
}
|
|
142
|
+
console.log('');
|
|
143
|
+
|
|
144
|
+
// Forge Tier
|
|
145
|
+
const ft = status.forgeTier || {};
|
|
146
|
+
const tools = ft.tools || {};
|
|
147
|
+
console.log(chalk.white.bold(' Forge Tier'));
|
|
148
|
+
if (status.tierDetected) {
|
|
149
|
+
const tierColors = { Quick: chalk.yellow, Forge: chalk.cyan, Deep: chalk.magenta };
|
|
150
|
+
const tierColor = tierColors[ft.tier] || chalk.white;
|
|
151
|
+
console.log(` Tier: ${tierColor(ft.tier)}`);
|
|
152
|
+
console.log(` Detected: ${chalk.dim(ft.tier_detected_at || '(unknown)')}`);
|
|
153
|
+
} else {
|
|
154
|
+
console.log(chalk.dim(' Not detected yet — run @Ferris SF'));
|
|
155
|
+
}
|
|
156
|
+
console.log(` ast-grep: ${formatTool(tools.ast_grep)}`);
|
|
157
|
+
console.log(` gh CLI: ${formatTool(tools.gh_cli)}`);
|
|
158
|
+
console.log(` QMD: ${formatTool(tools.qmd)}`);
|
|
159
|
+
console.log('');
|
|
160
|
+
|
|
161
|
+
// Output Folders
|
|
162
|
+
console.log(chalk.white.bold(' Output Folders'));
|
|
163
|
+
console.log(` Skills: ${status.skillsFolder}/ ${status.skillsFolderExists ? chalk.green('✓') : chalk.yellow('missing')}`);
|
|
164
|
+
console.log(` Forge data: ${status.forgeDataFolder}/ ${status.forgeDataFolderExists ? chalk.green('✓') : chalk.yellow('missing')}`);
|
|
165
|
+
console.log('');
|
|
166
|
+
|
|
167
|
+
// Sidecar
|
|
168
|
+
console.log(chalk.white.bold(' Sidecar'));
|
|
169
|
+
console.log(` State: ${status.sidecarExists ? chalk.green('initialized') : chalk.yellow('missing')}`);
|
|
170
|
+
console.log(` Location: ${chalk.dim(SIDECAR_FOLDER + '/')}`);
|
|
171
|
+
console.log('');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function formatTool(value) {
|
|
175
|
+
if (value === true || value === 'available') return chalk.green('available');
|
|
176
|
+
if (value == null) return chalk.dim('not detected');
|
|
177
|
+
return chalk.yellow(String(value));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
module.exports = {
|
|
181
|
+
command: 'status',
|
|
182
|
+
description: 'Show SKF installation state, version, tier, and configuration',
|
|
183
|
+
options: [],
|
|
184
|
+
action: async () => {
|
|
185
|
+
try {
|
|
186
|
+
const projectDir = process.cwd();
|
|
187
|
+
const packageJson = require('../../../package.json');
|
|
188
|
+
const status = await getStatus(projectDir);
|
|
189
|
+
displayStatus(status, packageJson.version);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
console.error(chalk.red('\nFailed to read status:'), error.message);
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
};
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SKF Uninstall Command
|
|
3
|
+
* Clean removal of SKF files, IDE commands, sidecar, and output folders.
|
|
4
|
+
* Uses the manifest to know exactly what to remove.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const path = require('node:path');
|
|
9
|
+
const fs = require('fs-extra');
|
|
10
|
+
const { confirm, isCancel, spinner, log, outro } = require('@clack/prompts');
|
|
11
|
+
const { readManifest, MANIFEST_DIR, MANIFEST_FILE } = require('../lib/manifest');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Count files that still exist on disk from manifest lists.
|
|
15
|
+
*/
|
|
16
|
+
async function countExistingFiles(projectDir, manifest) {
|
|
17
|
+
const allFiles = [
|
|
18
|
+
...(manifest.files.skf || []),
|
|
19
|
+
...(manifest.files.sidecar || []),
|
|
20
|
+
...(manifest.files.ide_commands || []),
|
|
21
|
+
...(manifest.files.learning || []),
|
|
22
|
+
...(manifest.files.output || []),
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
let existing = 0;
|
|
26
|
+
for (const file of allFiles) {
|
|
27
|
+
if (await fs.pathExists(path.join(projectDir, file))) {
|
|
28
|
+
existing++;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return { total: allFiles.length, existing };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Display what will be removed, grouped by category.
|
|
36
|
+
*/
|
|
37
|
+
async function displayRemovalPlan(projectDir, manifest) {
|
|
38
|
+
const categories = [
|
|
39
|
+
{ key: 'skf', label: 'SKF module files', dir: manifest.skf_folder },
|
|
40
|
+
{ key: 'sidecar', label: 'Agent sidecar state', dir: '_bmad/_memory/forger-sidecar' },
|
|
41
|
+
{ key: 'ide_commands', label: 'IDE command files' },
|
|
42
|
+
{ key: 'learning', label: 'Learning material', dir: '_skf-learn' },
|
|
43
|
+
{ key: 'output', label: 'Output folder scaffolding' },
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const lines = [];
|
|
47
|
+
for (const cat of categories) {
|
|
48
|
+
const files = manifest.files[cat.key] || [];
|
|
49
|
+
if (files.length === 0) continue;
|
|
50
|
+
|
|
51
|
+
// Count how many still exist
|
|
52
|
+
let existCount = 0;
|
|
53
|
+
for (const f of files) {
|
|
54
|
+
if (await fs.pathExists(path.join(projectDir, f))) existCount++;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (existCount === 0) continue;
|
|
58
|
+
|
|
59
|
+
if (cat.dir) {
|
|
60
|
+
lines.push(`${chalk.red('×')} ${cat.label} ${chalk.dim(`(${cat.dir}/ — ${existCount} files)`)}`);
|
|
61
|
+
} else {
|
|
62
|
+
lines.push(`${chalk.red('×')} ${cat.label} ${chalk.dim(`(${existCount} files)`)}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Manifest itself
|
|
67
|
+
lines.push(`${chalk.red('×')} Installation manifest ${chalk.dim(`(${MANIFEST_DIR}/${MANIFEST_FILE})`)}`);
|
|
68
|
+
|
|
69
|
+
log.warn(`The following will be removed:\n${lines.join('\n')}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Remove files listed in the manifest, then clean empty parent directories.
|
|
74
|
+
*/
|
|
75
|
+
async function removeFiles(projectDir, fileList) {
|
|
76
|
+
let removed = 0;
|
|
77
|
+
for (const file of fileList) {
|
|
78
|
+
const fullPath = path.join(projectDir, file);
|
|
79
|
+
if (await fs.pathExists(fullPath)) {
|
|
80
|
+
await fs.remove(fullPath);
|
|
81
|
+
removed++;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return removed;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Remove a directory if it exists and is empty (or force remove).
|
|
89
|
+
*/
|
|
90
|
+
async function removeEmptyDir(dirPath) {
|
|
91
|
+
if (!(await fs.pathExists(dirPath))) return;
|
|
92
|
+
try {
|
|
93
|
+
const entries = await fs.readdir(dirPath);
|
|
94
|
+
if (entries.length === 0) {
|
|
95
|
+
await fs.remove(dirPath);
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
// ignore
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = {
|
|
103
|
+
command: 'uninstall',
|
|
104
|
+
description: 'Remove SKF installation from the current project',
|
|
105
|
+
options: [],
|
|
106
|
+
action: async () => {
|
|
107
|
+
try {
|
|
108
|
+
const projectDir = process.cwd();
|
|
109
|
+
const manifest = await readManifest(projectDir);
|
|
110
|
+
|
|
111
|
+
if (!manifest) {
|
|
112
|
+
// Check if SKF exists without manifest
|
|
113
|
+
const skfExists = await fs.pathExists(path.join(projectDir, '_bmad/skf'));
|
|
114
|
+
if (skfExists) {
|
|
115
|
+
log.warn(
|
|
116
|
+
'No manifest found. Reinstall first to generate one,\nthen run uninstall again for clean removal.\nRun: npx skill-forge install',
|
|
117
|
+
);
|
|
118
|
+
} else {
|
|
119
|
+
log.warn('SKF is not installed in this directory.');
|
|
120
|
+
}
|
|
121
|
+
process.exit(0);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const { existing } = await countExistingFiles(projectDir, manifest);
|
|
126
|
+
if (existing === 0) {
|
|
127
|
+
log.warn('No SKF files found to remove.');
|
|
128
|
+
// Clean up stale manifest
|
|
129
|
+
await fs.remove(path.join(projectDir, MANIFEST_DIR, MANIFEST_FILE));
|
|
130
|
+
process.exit(0);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
await displayRemovalPlan(projectDir, manifest);
|
|
135
|
+
|
|
136
|
+
const shouldProceed = await confirm({
|
|
137
|
+
message: 'Proceed with uninstall?',
|
|
138
|
+
initialValue: false,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
if (isCancel(shouldProceed) || !shouldProceed) {
|
|
142
|
+
log.warn('Uninstall cancelled.');
|
|
143
|
+
process.exit(0);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const s = spinner();
|
|
148
|
+
|
|
149
|
+
// Remove IDE commands first (scattered across project)
|
|
150
|
+
const ideFiles = manifest.files.ide_commands || [];
|
|
151
|
+
if (ideFiles.length > 0) {
|
|
152
|
+
s.start('Removing IDE commands...');
|
|
153
|
+
const ideRemoved = await removeFiles(projectDir, ideFiles);
|
|
154
|
+
// Clean empty IDE directories
|
|
155
|
+
const cleanedDirs = new Set();
|
|
156
|
+
for (const f of ideFiles) {
|
|
157
|
+
const dir = path.dirname(f);
|
|
158
|
+
if (!cleanedDirs.has(dir)) {
|
|
159
|
+
cleanedDirs.add(dir);
|
|
160
|
+
await removeEmptyDir(path.join(projectDir, dir));
|
|
161
|
+
await removeEmptyDir(path.join(projectDir, path.dirname(dir)));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
s.stop(`Removed ${ideRemoved} IDE command files`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Remove learning material directory
|
|
168
|
+
const learnFiles = manifest.files.learning || [];
|
|
169
|
+
if (learnFiles.length > 0) {
|
|
170
|
+
s.start('Removing learning material...');
|
|
171
|
+
const learnDir = path.join(projectDir, '_skf-learn');
|
|
172
|
+
if (await fs.pathExists(learnDir)) {
|
|
173
|
+
await fs.remove(learnDir);
|
|
174
|
+
}
|
|
175
|
+
s.stop('Learning material removed');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Remove output folder scaffolding (.gitkeep only)
|
|
179
|
+
const outputFiles = manifest.files.output || [];
|
|
180
|
+
if (outputFiles.length > 0) {
|
|
181
|
+
s.start('Removing output scaffolding...');
|
|
182
|
+
await removeFiles(projectDir, outputFiles);
|
|
183
|
+
// Remove empty output folders
|
|
184
|
+
for (const folder of [manifest.skills_output_folder, manifest.forge_data_folder]) {
|
|
185
|
+
if (folder) {
|
|
186
|
+
await removeEmptyDir(path.join(projectDir, folder));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
s.stop('Output scaffolding removed');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Remove sidecar
|
|
193
|
+
s.start('Removing agent sidecar...');
|
|
194
|
+
const sidecarDir = path.join(projectDir, '_bmad', '_memory', 'forger-sidecar');
|
|
195
|
+
if (await fs.pathExists(sidecarDir)) {
|
|
196
|
+
await fs.remove(sidecarDir);
|
|
197
|
+
}
|
|
198
|
+
await removeEmptyDir(path.join(projectDir, '_bmad', '_memory'));
|
|
199
|
+
s.stop('Agent sidecar removed');
|
|
200
|
+
|
|
201
|
+
// Remove SKF module directory
|
|
202
|
+
s.start('Removing SKF module...');
|
|
203
|
+
const skfDir = path.join(projectDir, manifest.skf_folder);
|
|
204
|
+
if (await fs.pathExists(skfDir)) {
|
|
205
|
+
await fs.remove(skfDir);
|
|
206
|
+
}
|
|
207
|
+
s.stop('SKF module removed');
|
|
208
|
+
|
|
209
|
+
// Remove manifest
|
|
210
|
+
const manifestPath = path.join(projectDir, MANIFEST_DIR, MANIFEST_FILE);
|
|
211
|
+
if (await fs.pathExists(manifestPath)) {
|
|
212
|
+
await fs.remove(manifestPath);
|
|
213
|
+
}
|
|
214
|
+
// Clean empty _bmad/_config/ and _bmad/ if we were the only occupant
|
|
215
|
+
await removeEmptyDir(path.join(projectDir, MANIFEST_DIR));
|
|
216
|
+
await removeEmptyDir(path.join(projectDir, '_bmad'));
|
|
217
|
+
|
|
218
|
+
outro('SKF uninstalled successfully.');
|
|
219
|
+
|
|
220
|
+
process.exit(0);
|
|
221
|
+
} catch (error) {
|
|
222
|
+
console.error(chalk.red('\nUninstall failed:'), error.message);
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SKF Quick Update Command
|
|
3
|
+
* Replaces SKF files and recompiles agents without re-prompting.
|
|
4
|
+
* Preserves config.yaml and sidecar state.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const path = require('node:path');
|
|
9
|
+
const fs = require('fs-extra');
|
|
10
|
+
const yaml = require('js-yaml');
|
|
11
|
+
const { Installer } = require('../lib/installer');
|
|
12
|
+
const { UI } = require('../lib/ui');
|
|
13
|
+
|
|
14
|
+
const SKF_FOLDER = '_bmad/skf';
|
|
15
|
+
|
|
16
|
+
module.exports = {
|
|
17
|
+
command: 'update',
|
|
18
|
+
description: 'Update SKF files and recompile agents (preserves config and sidecar)',
|
|
19
|
+
options: [],
|
|
20
|
+
action: async () => {
|
|
21
|
+
try {
|
|
22
|
+
const projectDir = process.cwd();
|
|
23
|
+
const skfDir = path.join(projectDir, SKF_FOLDER);
|
|
24
|
+
|
|
25
|
+
if (!(await fs.pathExists(skfDir))) {
|
|
26
|
+
console.log(chalk.yellow('\n SKF is not installed in this directory.'));
|
|
27
|
+
console.log(chalk.dim(' Run: npx skill-forge install\n'));
|
|
28
|
+
process.exit(0);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
console.log('');
|
|
33
|
+
console.log(chalk.cyan.bold(' Skill Forge — Quick Update'));
|
|
34
|
+
console.log(chalk.dim(' Replacing SKF files, preserving config and sidecar.\n'));
|
|
35
|
+
|
|
36
|
+
const installer = new Installer();
|
|
37
|
+
const result = await installer.install({
|
|
38
|
+
projectDir,
|
|
39
|
+
skfFolder: SKF_FOLDER,
|
|
40
|
+
_action: 'update',
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (result && result.success) {
|
|
44
|
+
// Read config to get IDEs for post-update notes
|
|
45
|
+
let ides = [];
|
|
46
|
+
try {
|
|
47
|
+
const configContent = await fs.readFile(path.join(skfDir, 'config.yaml'), 'utf8');
|
|
48
|
+
const config = yaml.load(configContent);
|
|
49
|
+
ides = config?.ides || [];
|
|
50
|
+
} catch {
|
|
51
|
+
/* use empty */
|
|
52
|
+
}
|
|
53
|
+
const ui = new UI();
|
|
54
|
+
ui.displaySuccess(SKF_FOLDER, ides, 'update');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
process.exit(0);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error(chalk.red('\nUpdate failed:'), error.message);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
};
|