spec-first-copilot 0.5.0-beta.6 → 0.5.0-beta.8
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/bin/cli.js +23 -5
- package/lib/update.js +130 -0
- package/package.json +1 -1
- package/templates/.github/CHANGELOG.md +112 -0
package/bin/cli.js
CHANGED
|
@@ -2,26 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const { init } = require('../lib/init');
|
|
5
|
+
const { update } = require('../lib/update');
|
|
5
6
|
|
|
6
7
|
const args = process.argv.slice(2);
|
|
7
8
|
const command = args[0];
|
|
8
9
|
|
|
9
10
|
function printUsage() {
|
|
10
|
-
console.log('Usage:
|
|
11
|
+
console.log('Usage:');
|
|
12
|
+
console.log(' spec-first-copilot init --name=<project-name> [--target=<path>]');
|
|
13
|
+
console.log(' spec-first-copilot update [--target=<path>] [--force]');
|
|
11
14
|
console.log('');
|
|
12
|
-
console.log('
|
|
15
|
+
console.log('Commands:');
|
|
16
|
+
console.log(' init Create a new spec-first project');
|
|
17
|
+
console.log(' update Update an existing project with new framework files');
|
|
18
|
+
console.log(' (adds missing files, updates framework files like skills/adapters/agents)');
|
|
13
19
|
console.log('');
|
|
14
20
|
console.log('Options:');
|
|
15
|
-
console.log(' --name=<name> Project name (required)');
|
|
16
|
-
console.log(' --target=<path> Target directory (defaults to ./<name>)');
|
|
21
|
+
console.log(' --name=<name> Project name (required for init)');
|
|
22
|
+
console.log(' --target=<path> Target directory (defaults to ./<name> for init, cwd for update)');
|
|
23
|
+
console.log(' --force Force update all files, not just framework files (use with caution)');
|
|
17
24
|
}
|
|
18
25
|
|
|
19
26
|
function parseArgs(args) {
|
|
20
|
-
const parsed = {};
|
|
27
|
+
const parsed = { flags: [] };
|
|
21
28
|
for (const arg of args) {
|
|
22
29
|
const match = arg.match(/^--(\w+)=(.+)$/);
|
|
23
30
|
if (match) {
|
|
24
31
|
parsed[match[1]] = match[2];
|
|
32
|
+
} else if (arg.startsWith('--')) {
|
|
33
|
+
parsed.flags.push(arg.slice(2));
|
|
25
34
|
}
|
|
26
35
|
}
|
|
27
36
|
return parsed;
|
|
@@ -43,6 +52,15 @@ if (command === 'init') {
|
|
|
43
52
|
templatesDir,
|
|
44
53
|
targetDir: opts.target || undefined,
|
|
45
54
|
});
|
|
55
|
+
} else if (command === 'update') {
|
|
56
|
+
const opts = parseArgs(args.slice(1));
|
|
57
|
+
const templatesDir = path.join(__dirname, '..', 'templates');
|
|
58
|
+
|
|
59
|
+
update({
|
|
60
|
+
templatesDir,
|
|
61
|
+
targetDir: opts.target || undefined,
|
|
62
|
+
force: opts.flags.includes('force'),
|
|
63
|
+
});
|
|
46
64
|
} else {
|
|
47
65
|
printUsage();
|
|
48
66
|
if (command) {
|
package/lib/update.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const FRAMEWORK_DIRS = [
|
|
5
|
+
path.join('.github', 'adapters'),
|
|
6
|
+
path.join('.github', 'skills'),
|
|
7
|
+
path.join('.github', 'agents'),
|
|
8
|
+
path.join('.github', 'templates'),
|
|
9
|
+
path.join('.github', 'instructions'),
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
const FRAMEWORK_FILES = [
|
|
13
|
+
path.join('.github', 'rules.md'),
|
|
14
|
+
path.join('.github', 'copilot-instructions.md'),
|
|
15
|
+
path.join('.github', 'CHANGELOG.md'),
|
|
16
|
+
'sfw.config.yml.example',
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
function update({ templatesDir, targetDir, force }) {
|
|
20
|
+
const dest = targetDir || process.cwd();
|
|
21
|
+
|
|
22
|
+
if (!fs.existsSync(dest)) {
|
|
23
|
+
console.error(`Error: directory "${dest}" does not exist.`);
|
|
24
|
+
console.error('Run "spec-first-copilot init --name=<name>" first to create a project.');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const githubDir = path.join(dest, '.github');
|
|
29
|
+
if (!fs.existsSync(githubDir)) {
|
|
30
|
+
console.error('Error: not a spec-first project (missing .github/ directory).');
|
|
31
|
+
console.error('Run "spec-first-copilot init --name=<name>" first.');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.log(`\nUpdating project in ${dest}\n`);
|
|
36
|
+
|
|
37
|
+
const stats = { added: [], updated: [], skipped: 0 };
|
|
38
|
+
syncDir(templatesDir, dest, force, stats, dest);
|
|
39
|
+
|
|
40
|
+
console.log('');
|
|
41
|
+
if (stats.added.length > 0) {
|
|
42
|
+
console.log(`Added (${stats.added.length}):`);
|
|
43
|
+
for (const f of stats.added) console.log(` + ${f}`);
|
|
44
|
+
}
|
|
45
|
+
if (stats.updated.length > 0) {
|
|
46
|
+
console.log(`Updated (${stats.updated.length}):`);
|
|
47
|
+
for (const f of stats.updated) console.log(` ~ ${f}`);
|
|
48
|
+
}
|
|
49
|
+
if (stats.added.length === 0 && stats.updated.length === 0) {
|
|
50
|
+
console.log('Already up to date — no new files to add.');
|
|
51
|
+
} else {
|
|
52
|
+
console.log(`\n${stats.added.length} added, ${stats.updated.length} updated, ${stats.skipped} unchanged`);
|
|
53
|
+
}
|
|
54
|
+
console.log('');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function syncDir(src, dest, force, stats, root) {
|
|
58
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
59
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
60
|
+
const srcPath = path.join(src, entry.name);
|
|
61
|
+
const destPath = path.join(dest, entry.name);
|
|
62
|
+
if (entry.isDirectory()) {
|
|
63
|
+
syncDir(srcPath, destPath, force, stats, root);
|
|
64
|
+
} else {
|
|
65
|
+
syncFile(srcPath, destPath, force, stats, root);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function syncFile(src, dest, force, stats, root) {
|
|
71
|
+
const rel = path.relative(root, dest);
|
|
72
|
+
const exists = fs.existsSync(dest);
|
|
73
|
+
|
|
74
|
+
if (exists && !force) {
|
|
75
|
+
const isFramework = isFrameworkPath(rel);
|
|
76
|
+
if (!isFramework) {
|
|
77
|
+
stats.skipped++;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const srcContent = fs.readFileSync(src, 'utf-8');
|
|
81
|
+
const destContent = fs.readFileSync(dest, 'utf-8');
|
|
82
|
+
if (srcContent === destContent) {
|
|
83
|
+
stats.skipped++;
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
fs.writeFileSync(dest, srcContent, 'utf-8');
|
|
87
|
+
stats.updated.push(rel);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (exists && force) {
|
|
92
|
+
const srcContent = fs.readFileSync(src, 'utf-8');
|
|
93
|
+
const destContent = fs.readFileSync(dest, 'utf-8');
|
|
94
|
+
if (srcContent === destContent) {
|
|
95
|
+
stats.skipped++;
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
fs.writeFileSync(dest, srcContent, 'utf-8');
|
|
99
|
+
stats.updated.push(rel);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const textExtensions = ['.md', '.yaml', '.yml', '.json', '.js', '.ts', '.gitignore', '.gitkeep', ''];
|
|
104
|
+
const ext = path.extname(src).toLowerCase();
|
|
105
|
+
const basename = path.basename(src);
|
|
106
|
+
const isText = textExtensions.includes(ext) || basename.startsWith('.');
|
|
107
|
+
|
|
108
|
+
if (isText) {
|
|
109
|
+
const content = fs.readFileSync(src, 'utf-8');
|
|
110
|
+
fs.writeFileSync(dest, content, 'utf-8');
|
|
111
|
+
} else {
|
|
112
|
+
fs.copyFileSync(src, dest);
|
|
113
|
+
}
|
|
114
|
+
stats.added.push(rel);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function isFrameworkPath(rel) {
|
|
118
|
+
const normalized = rel.split(path.sep).join('/');
|
|
119
|
+
for (const dir of FRAMEWORK_DIRS) {
|
|
120
|
+
const normalizedDir = dir.split(path.sep).join('/');
|
|
121
|
+
if (normalized.startsWith(normalizedDir + '/')) return true;
|
|
122
|
+
}
|
|
123
|
+
for (const file of FRAMEWORK_FILES) {
|
|
124
|
+
const normalizedFile = file.split(path.sep).join('/');
|
|
125
|
+
if (normalized === normalizedFile) return true;
|
|
126
|
+
}
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports = { update };
|
package/package.json
CHANGED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# SFW Changelog — spec-first-copilot
|
|
2
|
+
|
|
3
|
+
> Este arquivo é atualizado automaticamente pelo `spec-first-copilot update`.
|
|
4
|
+
> O agent deve ler este arquivo quando o usuário perguntar sobre mudanças,
|
|
5
|
+
> funcionalidades novas, ou o que está disponível no framework.
|
|
6
|
+
>
|
|
7
|
+
> **Não editar manualmente** — será sobrescrito no próximo update.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 0.5.0-beta.7 (2026-04-12)
|
|
12
|
+
|
|
13
|
+
### Novo: Comando `/sf-load <nome>`
|
|
14
|
+
Puxa insumos do backend configurado (Confluence ou filesystem) para `workspace/Input/{nome}/`.
|
|
15
|
+
- Busca o scope pelo nome no backend (match exato)
|
|
16
|
+
- Desce recursivamente na árvore (Confluence `get_page_children` é apenas direto — `/sf-load` faz loop)
|
|
17
|
+
- Baixa conteúdo + attachments
|
|
18
|
+
- Log incremental em `.ai/sf-load-log.md` (NOVO/MODIFICADO/INALTERADO via sha256)
|
|
19
|
+
- Idempotente — rodar N vezes sem mudanças no backend não altera nada
|
|
20
|
+
- Referência: `.github/commands/sf-load.md`
|
|
21
|
+
|
|
22
|
+
### Novo: Adapter layer (Confluence + Filesystem)
|
|
23
|
+
Pipeline agora é backend-agnóstico via adapters plugáveis.
|
|
24
|
+
- **ConfluenceAdapter** — fala com Confluence via MCP `sooperset/mcp-atlassian`
|
|
25
|
+
- **FilesystemAdapter** — lê/escreve direto no disco (também serve como mock pra testes)
|
|
26
|
+
- Configurado em `sfw.config.yml` (copiar de `sfw.config.yml.example`)
|
|
27
|
+
- Interface: `.github/adapters/interface.md` (7 métodos)
|
|
28
|
+
- Erros tipados: `.github/adapters/errors.md` (6 classes)
|
|
29
|
+
- Naming engine: `.github/adapters/naming.md` (placeholders `{scope}`, `{type}`)
|
|
30
|
+
- Registry: `.github/adapters/registry.md`
|
|
31
|
+
- Setup guide: `.github/adapters/SETUP.md` (passo a passo de MCP + Confluence)
|
|
32
|
+
|
|
33
|
+
### Novo: `sfw.config.yml`
|
|
34
|
+
Manifesto do projeto que define de onde vêm e pra onde vão os artefatos.
|
|
35
|
+
- `input.adapter` — de onde puxar insumos (`confluence` ou `filesystem`)
|
|
36
|
+
- `output.targets[].mode` — ligar/desligar publish (`auto`, `manual`, `off`)
|
|
37
|
+
- `naming.output_container` — template do nome da subpasta no Output (ex: `out_{scope}`)
|
|
38
|
+
- `naming.output_artifact` — template do nome do artefato (ex: `{scope} - {type}`)
|
|
39
|
+
- Copiar `sfw.config.yml.example` e preencher. Zero segredos neste arquivo.
|
|
40
|
+
|
|
41
|
+
### Novo: `SETUP.md` — Guia de configuração
|
|
42
|
+
Guia passo a passo em `.github/adapters/SETUP.md`:
|
|
43
|
+
- Setup Confluence: uvx, .mcp.json, token Atlassian, space key, page IDs
|
|
44
|
+
- Setup 100% local (filesystem, zero dependências)
|
|
45
|
+
- Como ligar/desligar Confluence via `mode: auto|manual|off`
|
|
46
|
+
- Troubleshooting: tabela com todos os gotchas conhecidos
|
|
47
|
+
- **IMPORTANTE**: Confluence NÃO é CLI. Acesso via MCP tools prefixadas `mcp__atlassian__confluence_*`
|
|
48
|
+
|
|
49
|
+
### Novo: CLI `update`
|
|
50
|
+
```bash
|
|
51
|
+
npx spec-first-copilot update # adiciona novos, atualiza framework
|
|
52
|
+
npx spec-first-copilot update --force # sobrescreve tudo incluindo copilot-instructions.md
|
|
53
|
+
```
|
|
54
|
+
Atualiza projeto existente sem perder arquivos do usuário.
|
|
55
|
+
|
|
56
|
+
### Breaking: `/sf-setup-projeto` → `/sf-new-project <nome>`
|
|
57
|
+
- Antes: `/sf-setup-projeto` era hardcoded pra pasta `setup_projeto`
|
|
58
|
+
- Agora: `/sf-new-project <nome>` recebe o nome como argumento (simétrico com `/sf-feature <nome>`)
|
|
59
|
+
- `/sf-new-project` gera TRD (bootstrap técnico), `/sf-feature` gera PRD (feature)
|
|
60
|
+
- **Não existe mais pasta mágica** — usuário nomeia livremente em `workspace/Input/`
|
|
61
|
+
- O antigo `setup-projeto.md` foi removido. Usar `new-project.md`
|
|
62
|
+
|
|
63
|
+
### Breaking: `{sequence}` removido
|
|
64
|
+
- Antes: todo item tinha prefixo numérico `001 - ` pra unicidade no Confluence
|
|
65
|
+
- Agora: usuário nomeia livremente, agent segue o nome do Input
|
|
66
|
+
- Naming simplificado: só `{scope}` e `{type}` como placeholders
|
|
67
|
+
- Premissa: 1 space Confluence = 1 projeto (multi-projeto no mesmo space requer nomes únicos no Input)
|
|
68
|
+
|
|
69
|
+
### Breaking: Output agrupado por scope
|
|
70
|
+
- Antes: artefatos flat no Output (ex: `001 - TRD setup_projeto`)
|
|
71
|
+
- Agora: subpasta por scope (ex: `out_app_barbearia/app_barbearia - TRD`)
|
|
72
|
+
- Container configurável via `naming.output_container` (default: `out_{scope}`)
|
|
73
|
+
- Artifact configurável via `naming.output_artifact` (default: `{scope} - {type}`)
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 0.5.0-beta.2 (2026-04-11)
|
|
78
|
+
|
|
79
|
+
### Refactor: `docs/specs/` → `specs/` (top-level)
|
|
80
|
+
- Projeções do SDD (brief, contracts, scenarios, tasks) movidas pra raiz
|
|
81
|
+
- Regra: humano lê `workspace/Output/`, agent lê `specs/`
|
|
82
|
+
|
|
83
|
+
### Refactor: 3-zone separation
|
|
84
|
+
- `.github/` = AI config (rules, templates, commands, agents)
|
|
85
|
+
- `docs/` = system knowledge (5 docs de sistema)
|
|
86
|
+
- `workspace/` = team content (Input/ + Output/)
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## 0.4.0 (2026-04-10) — v1 stable
|
|
91
|
+
|
|
92
|
+
### Funcionalidades base
|
|
93
|
+
- 9 commands: setup-projeto, feature, discovery, extract, design, plan, dev, merge-delta, session-finish
|
|
94
|
+
- 7 agents: backend-coder, frontend-coder, db-coder, infra-coder, doc-writer, reviewer, security-reviewer
|
|
95
|
+
- Templates: PRD, TRD, SDD, tasks, Progresso, context, extract-log, backlog, projetos.yaml
|
|
96
|
+
- Templates de estrutura: architecture, domain, conventions, apiContracts, decisions
|
|
97
|
+
- Pipeline: extract → design → plan → dev com checkpoints de aprovação
|
|
98
|
+
- Delta Specs (SDD §11) + merge-delta automático
|
|
99
|
+
- Security Reviewer como gate pós-dev
|
|
100
|
+
- Multi-repo via projetos.yaml
|
|
101
|
+
- Entregáveis contínuos (fases de entrega)
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Como usar este arquivo
|
|
106
|
+
|
|
107
|
+
Se o usuário perguntar:
|
|
108
|
+
- **"O que tem de novo?"** → ler a versão mais recente acima
|
|
109
|
+
- **"Como uso o /sf-load?"** → apontar pra `.github/commands/sf-load.md`
|
|
110
|
+
- **"Como configuro Confluence?"** → apontar pra `.github/adapters/SETUP.md`
|
|
111
|
+
- **"O que é adapter?"** → apontar pra `.github/adapters/interface.md`
|
|
112
|
+
- **"O que mudou de setup-projeto?"** → seção "Breaking" do 0.5.0-beta.7
|