@nathanramorim/forge-sdd 1.1.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/README.md +94 -0
- package/bin/run.js +172 -0
- package/package.json +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# forge-sdd
|
|
2
|
+
|
|
3
|
+
> CLI que scaffolda a estrutura **Forge-SDD** em qualquer projeto — pronta para uso com GitHub Copilot.
|
|
4
|
+
|
|
5
|
+
## Uso rápido
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx forge-sdd@latest init
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Apresenta um formulário interativo e cria **32 arquivos** com memória de projeto, spec, chatmodes, prompts e configuração de MCPs.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Requisitos
|
|
16
|
+
|
|
17
|
+
- Node.js ≥ 18
|
|
18
|
+
- Acesso à internet (primeiro uso baixa o binário ~5 MB)
|
|
19
|
+
|
|
20
|
+
O binário Go é baixado automaticamente do GitHub Releases, validado por SHA256 e cacheado em `~/.cache/forge-sdd/`. Execuções seguintes são instantâneas.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Opções
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Modo interativo (padrão)
|
|
28
|
+
npx forge-sdd@latest init
|
|
29
|
+
|
|
30
|
+
# Pular formulário, usar valores padrão
|
|
31
|
+
npx forge-sdd@latest init --yes
|
|
32
|
+
|
|
33
|
+
# Especificar diretório destino
|
|
34
|
+
npx forge-sdd@latest init /caminho/do/projeto
|
|
35
|
+
|
|
36
|
+
# Preview sem criar arquivos
|
|
37
|
+
npx forge-sdd@latest init --yes --dry-run
|
|
38
|
+
|
|
39
|
+
# Ver versão
|
|
40
|
+
npx forge-sdd@latest version
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## O que é gerado
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
sdd/ → memória e especificação do projeto
|
|
49
|
+
memory/
|
|
50
|
+
progress.md → estado ativo (leia primeiro a cada sessão)
|
|
51
|
+
constitution.md → regras imutáveis do projeto
|
|
52
|
+
mcps.md → MCPs configurados
|
|
53
|
+
progress-log.md → histórico de sessões
|
|
54
|
+
spec/
|
|
55
|
+
overview.md, stack.md, modules.md, flows.md, decisions.md
|
|
56
|
+
features/
|
|
57
|
+
feat-00-foundation.md
|
|
58
|
+
index.md
|
|
59
|
+
skills/index.md
|
|
60
|
+
plan.md, README.md
|
|
61
|
+
|
|
62
|
+
.github/
|
|
63
|
+
copilot-instructions.md → instruções globais para o Copilot
|
|
64
|
+
chatmodes/ → 6 modos de agente (orquestrador, builder, revisor…)
|
|
65
|
+
prompts/ → 7 prompts reutilizáveis
|
|
66
|
+
|
|
67
|
+
.vscode/
|
|
68
|
+
mcp.json → configuração dos MCPs (context7, git)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Instalação permanente (opcional)
|
|
74
|
+
|
|
75
|
+
Se preferir ter o comando disponível globalmente sem `npx`:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npm install -g forge-sdd
|
|
79
|
+
forge-sdd init
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Ou via Homebrew (macOS/Linux):
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
brew install nathanramorim/forge-sdd/forge-sdd
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Links
|
|
91
|
+
|
|
92
|
+
- [Repositório do projeto](https://github.com/nathanramorim/homebrew-forge-sdd)
|
|
93
|
+
- [Releases / Changelog](https://github.com/nathanramorim/homebrew-forge-sdd/releases)
|
|
94
|
+
- [Metodologia Forge-SDD](https://github.com/nathanramorim/homebrew-forge-sdd#readme)
|
package/bin/run.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const https = require('https');
|
|
5
|
+
const http = require('http');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
const { execFileSync, spawnSync } = require('child_process');
|
|
10
|
+
const crypto = require('crypto');
|
|
11
|
+
const zlib = require('zlib');
|
|
12
|
+
|
|
13
|
+
// ─── helpers ──────────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
const VERSION = require('../package.json').version;
|
|
16
|
+
const BASE_URL = `https://github.com/nathanramorim/homebrew-forge-sdd/releases/download/v${VERSION}`;
|
|
17
|
+
|
|
18
|
+
function platformAsset() {
|
|
19
|
+
const plat = process.platform;
|
|
20
|
+
const arch = process.arch;
|
|
21
|
+
|
|
22
|
+
const map = {
|
|
23
|
+
'linux-x64': { file: `homebrew-forge-sdd_linux_amd64.tar.gz`, ext: 'tar.gz' },
|
|
24
|
+
'linux-arm64': { file: `homebrew-forge-sdd_linux_arm64.tar.gz`, ext: 'tar.gz' },
|
|
25
|
+
'darwin-x64': { file: `homebrew-forge-sdd_darwin_amd64.tar.gz`, ext: 'tar.gz' },
|
|
26
|
+
'darwin-arm64': { file: `homebrew-forge-sdd_darwin_arm64.tar.gz`, ext: 'tar.gz' },
|
|
27
|
+
'win32-x64': { file: `homebrew-forge-sdd_windows_amd64.zip`, ext: 'zip' },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const key = `${plat}-${arch}`;
|
|
31
|
+
const asset = map[key];
|
|
32
|
+
if (!asset) {
|
|
33
|
+
console.error(`forge-sdd: unsupported platform ${key}`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
return asset;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function cacheDir() {
|
|
40
|
+
const base = process.env.XDG_CACHE_HOME
|
|
41
|
+
|| (process.platform === 'win32'
|
|
42
|
+
? path.join(os.homedir(), 'AppData', 'Local')
|
|
43
|
+
: path.join(os.homedir(), '.cache'));
|
|
44
|
+
return path.join(base, 'forge-sdd', VERSION);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function binaryPath(dir) {
|
|
48
|
+
const name = process.platform === 'win32' ? 'forge-sdd.exe' : 'forge-sdd';
|
|
49
|
+
return path.join(dir, name);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ─── download ─────────────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
function download(url, dest) {
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
const file = fs.createWriteStream(dest);
|
|
57
|
+
const protocol = url.startsWith('https') ? https : http;
|
|
58
|
+
const req = protocol.get(url, (res) => {
|
|
59
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
60
|
+
file.close();
|
|
61
|
+
fs.unlinkSync(dest);
|
|
62
|
+
return download(res.headers.location, dest).then(resolve).catch(reject);
|
|
63
|
+
}
|
|
64
|
+
if (res.statusCode !== 200) {
|
|
65
|
+
file.close();
|
|
66
|
+
fs.unlinkSync(dest);
|
|
67
|
+
return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
|
|
68
|
+
}
|
|
69
|
+
res.pipe(file);
|
|
70
|
+
file.on('finish', () => { file.close(); resolve(); });
|
|
71
|
+
});
|
|
72
|
+
req.on('error', (err) => { fs.unlinkSync(dest); reject(err); });
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function sha256(filePath) {
|
|
77
|
+
const data = fs.readFileSync(filePath);
|
|
78
|
+
return crypto.createHash('sha256').update(data).digest('hex');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function verifyChecksum(assetFile, binDir) {
|
|
82
|
+
const checksumsUrl = `${BASE_URL}/checksums.txt`;
|
|
83
|
+
const checksumsPath = path.join(binDir, 'checksums.txt');
|
|
84
|
+
await download(checksumsUrl, checksumsPath);
|
|
85
|
+
|
|
86
|
+
const lines = fs.readFileSync(checksumsPath, 'utf8').split('\n');
|
|
87
|
+
const entry = lines.find(l => l.includes(assetFile));
|
|
88
|
+
if (!entry) {
|
|
89
|
+
throw new Error(`checksum not found for ${assetFile}`);
|
|
90
|
+
}
|
|
91
|
+
return entry.split(/\s+/)[0].toLowerCase();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ─── extract ──────────────────────────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
function extractTarGz(archive, destDir) {
|
|
97
|
+
// Use system tar — available on all platforms we target
|
|
98
|
+
const result = spawnSync('tar', ['-xzf', archive, '-C', destDir, '--strip-components=0'], {
|
|
99
|
+
stdio: 'inherit',
|
|
100
|
+
});
|
|
101
|
+
if (result.status !== 0) {
|
|
102
|
+
throw new Error('tar extraction failed');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function extractZip(archive, destDir) {
|
|
107
|
+
// Use system unzip or PowerShell on Windows
|
|
108
|
+
if (process.platform === 'win32') {
|
|
109
|
+
const ps = spawnSync('powershell', [
|
|
110
|
+
'-NoProfile', '-Command',
|
|
111
|
+
`Expand-Archive -Path "${archive}" -DestinationPath "${destDir}" -Force`,
|
|
112
|
+
], { stdio: 'inherit' });
|
|
113
|
+
if (ps.status !== 0) throw new Error('zip extraction failed');
|
|
114
|
+
} else {
|
|
115
|
+
const result = spawnSync('unzip', ['-o', archive, '-d', destDir], { stdio: 'inherit' });
|
|
116
|
+
if (result.status !== 0) throw new Error('unzip extraction failed');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ─── main ─────────────────────────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
async function ensureBinary() {
|
|
123
|
+
const dir = cacheDir();
|
|
124
|
+
const bin = binaryPath(dir);
|
|
125
|
+
|
|
126
|
+
if (fs.existsSync(bin)) {
|
|
127
|
+
return bin;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
131
|
+
|
|
132
|
+
const asset = platformAsset();
|
|
133
|
+
const archivePath = path.join(dir, asset.file);
|
|
134
|
+
|
|
135
|
+
process.stderr.write(`forge-sdd: downloading v${VERSION} for ${process.platform}/${process.arch}...\n`);
|
|
136
|
+
|
|
137
|
+
const assetUrl = `${BASE_URL}/${asset.file}`;
|
|
138
|
+
await download(assetUrl, archivePath);
|
|
139
|
+
|
|
140
|
+
// Verify SHA256
|
|
141
|
+
const expectedHash = await verifyChecksum(asset.file, dir);
|
|
142
|
+
const actualHash = sha256(archivePath);
|
|
143
|
+
if (actualHash !== expectedHash) {
|
|
144
|
+
fs.unlinkSync(archivePath);
|
|
145
|
+
throw new Error(`SHA256 mismatch: expected ${expectedHash}, got ${actualHash}`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Extract
|
|
149
|
+
if (asset.ext === 'tar.gz') {
|
|
150
|
+
extractTarGz(archivePath, dir);
|
|
151
|
+
} else {
|
|
152
|
+
extractZip(archivePath, dir);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Ensure executable bit on Unix
|
|
156
|
+
if (process.platform !== 'win32') {
|
|
157
|
+
fs.chmodSync(bin, 0o755);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return bin;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
ensureBinary()
|
|
164
|
+
.then((bin) => {
|
|
165
|
+
const args = process.argv.slice(2);
|
|
166
|
+
const result = spawnSync(bin, args, { stdio: 'inherit' });
|
|
167
|
+
process.exit(result.status ?? 1);
|
|
168
|
+
})
|
|
169
|
+
.catch((err) => {
|
|
170
|
+
console.error(`forge-sdd error: ${err.message}`);
|
|
171
|
+
process.exit(1);
|
|
172
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nathanramorim/forge-sdd",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "CLI que scaffolda estruturas Forge-SDD em segundos",
|
|
5
|
+
"homepage": "https://github.com/nathanramorim/homebrew-forge-sdd",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/nathanramorim/forge-sdd.git"
|
|
9
|
+
},
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"bin": {
|
|
12
|
+
"forge-sdd": "bin/run.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"bin/run.js"
|
|
16
|
+
],
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=18"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"scaffold",
|
|
22
|
+
"cli",
|
|
23
|
+
"sdd",
|
|
24
|
+
"forge",
|
|
25
|
+
"copilot"
|
|
26
|
+
]
|
|
27
|
+
}
|