@guilhermefsousa/open-spec-kit 0.0.4 → 0.0.6
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/package.json +1 -1
- package/src/commands/doctor.js +30 -4
- package/src/commands/init.js +52 -63
- package/src/commands/validate.js +1 -1
- package/src/utils/http.js +19 -2
- package/templates/agents/rules/ownership.instructions.md +9 -2
- package/templates/agents/skills/dev-orchestrator/SKILL.md +38 -1
- package/templates/agents/skills/discovery/SKILL.md +14 -1
- package/templates/agents/skills/setup-project/SKILL.md +35 -4
- package/templates/agents/skills/specifying-features/SKILL.md +28 -1
package/package.json
CHANGED
package/src/commands/doctor.js
CHANGED
|
@@ -4,7 +4,7 @@ import { join } from 'path';
|
|
|
4
4
|
import { execSync } from 'child_process';
|
|
5
5
|
import yaml from 'yaml';
|
|
6
6
|
import { detectMcpRunner, getInstallInstructions } from '../utils/mcp-detect.js';
|
|
7
|
-
import { confluenceRequest, figmaRequest } from '../utils/http.js';
|
|
7
|
+
import { confluenceRequest, figmaRequest, isSslError } from '../utils/http.js';
|
|
8
8
|
|
|
9
9
|
export async function doctorCommand() {
|
|
10
10
|
console.log(chalk.bold('\n open-spec-kit doctor\n'));
|
|
@@ -67,7 +67,10 @@ export async function doctorCommand() {
|
|
|
67
67
|
if (agents.includes('claude')) {
|
|
68
68
|
try {
|
|
69
69
|
claudeMcpContent = await readFile(join(cwd, '.mcp.json'), 'utf-8');
|
|
70
|
-
if (claudeMcpContent.includes('SEU-TOKEN') || claudeMcpContent.includes('SEU-DOMINIO')
|
|
70
|
+
if (claudeMcpContent.includes('SEU-TOKEN') || claudeMcpContent.includes('SEU-DOMINIO')
|
|
71
|
+
|| claudeMcpContent.includes('seu-token-aqui') || claudeMcpContent.includes('seu-dominio')
|
|
72
|
+
|| claudeMcpContent.includes('seu-email') || claudeMcpContent.includes('${CONFLUENCE_URL}')
|
|
73
|
+
|| claudeMcpContent.includes('${CONFLUENCE_API_TOKEN}')) {
|
|
71
74
|
console.log(chalk.yellow(' ⚠ .mcp.json (Claude) existe mas tem placeholders'));
|
|
72
75
|
fail++;
|
|
73
76
|
} else {
|
|
@@ -93,8 +96,16 @@ export async function doctorCommand() {
|
|
|
93
96
|
if (agents.includes('copilot')) {
|
|
94
97
|
try {
|
|
95
98
|
copilotMcpContent = await readFile(join(cwd, '.vscode/mcp.json'), 'utf-8');
|
|
96
|
-
|
|
97
|
-
|
|
99
|
+
if (copilotMcpContent.includes('SEU-TOKEN') || copilotMcpContent.includes('SEU-DOMINIO')
|
|
100
|
+
|| copilotMcpContent.includes('seu-token-aqui') || copilotMcpContent.includes('seu-dominio')
|
|
101
|
+
|| copilotMcpContent.includes('seu-email') || copilotMcpContent.includes('${CONFLUENCE_URL}')
|
|
102
|
+
|| copilotMcpContent.includes('${CONFLUENCE_API_TOKEN}')) {
|
|
103
|
+
console.log(chalk.yellow(' ⚠ .vscode/mcp.json (Copilot) existe mas tem placeholders'));
|
|
104
|
+
fail++;
|
|
105
|
+
} else {
|
|
106
|
+
console.log(chalk.green(' ✓ .vscode/mcp.json (Copilot) configurado'));
|
|
107
|
+
pass++;
|
|
108
|
+
}
|
|
98
109
|
} catch {
|
|
99
110
|
console.log(chalk.red(' ✗ .vscode/mcp.json (Copilot) ausente'));
|
|
100
111
|
fail++;
|
|
@@ -319,6 +330,21 @@ export async function doctorCommand() {
|
|
|
319
330
|
console.log(chalk.dim(' Renove em: Figma > Account Settings > Personal Access Tokens'));
|
|
320
331
|
console.log(chalk.dim(' Atualize FIGMA_API_KEY no .env'));
|
|
321
332
|
fail++;
|
|
333
|
+
} else if (isSslError(err)) {
|
|
334
|
+
try {
|
|
335
|
+
const result = await figmaRequest(figmaKey, 10000, true);
|
|
336
|
+
console.log(chalk.green(` ✓ Token Figma válido — SSL corporativo ignorado (usuário: ${result.data.handle || result.data.email || 'OK'})`));
|
|
337
|
+
pass++;
|
|
338
|
+
} catch (retryErr) {
|
|
339
|
+
if (retryErr.status === 401 || retryErr.status === 403) {
|
|
340
|
+
console.log(chalk.red(` ✗ Token Figma inválido ou expirado (HTTP ${retryErr.status})`));
|
|
341
|
+
console.log(chalk.dim(' Renove em: Figma > Account Settings > Personal Access Tokens'));
|
|
342
|
+
console.log(chalk.dim(' Atualize FIGMA_API_KEY no .env'));
|
|
343
|
+
} else {
|
|
344
|
+
console.log(chalk.yellow(` ⚠ Não foi possível verificar token Figma: ${retryErr.message}`));
|
|
345
|
+
}
|
|
346
|
+
fail++;
|
|
347
|
+
}
|
|
322
348
|
} else {
|
|
323
349
|
console.log(chalk.yellow(` ⚠ Não foi possível verificar token Figma: ${err.message}`));
|
|
324
350
|
fail++;
|
package/src/commands/init.js
CHANGED
|
@@ -6,7 +6,7 @@ import { join, dirname } from 'path';
|
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
7
|
import { execSync } from 'child_process';
|
|
8
8
|
import { detectMcpRunner, detectNpxRunner, tryInstallMcpAtlassian, getInstallInstructions } from '../utils/mcp-detect.js';
|
|
9
|
-
import { confluenceRequest, figmaRequest } from '../utils/http.js';
|
|
9
|
+
import { confluenceRequest, figmaRequest, isSslError } from '../utils/http.js';
|
|
10
10
|
|
|
11
11
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
12
|
const TEMPLATES_DIR = join(__dirname, '..', '..', 'templates');
|
|
@@ -47,26 +47,6 @@ const KNOWN_TECH_KEYWORDS = [
|
|
|
47
47
|
'SQS', 'SNS', 'DynamoDB', 'Cassandra', 'Nginx', 'Apache',
|
|
48
48
|
];
|
|
49
49
|
|
|
50
|
-
// ──────────────────────────────────────────────────────
|
|
51
|
-
// SSL error detection (corporate proxy workaround)
|
|
52
|
-
// ──────────────────────────────────────────────────────
|
|
53
|
-
const SSL_ERROR_CODES = new Set([
|
|
54
|
-
'UNABLE_TO_VERIFY_LEAF_SIGNATURE',
|
|
55
|
-
'SELF_SIGNED_CERT_IN_CHAIN',
|
|
56
|
-
'DEPTH_ZERO_SELF_SIGNED_CERT',
|
|
57
|
-
'CERT_HAS_EXPIRED',
|
|
58
|
-
'ERR_TLS_CERT_ALTNAME_INVALID',
|
|
59
|
-
'UNABLE_TO_GET_ISSUER_CERT_LOCALLY',
|
|
60
|
-
]);
|
|
61
|
-
|
|
62
|
-
function isSslError(err) {
|
|
63
|
-
return err.status === 0 && (
|
|
64
|
-
SSL_ERROR_CODES.has(err.code) ||
|
|
65
|
-
SSL_ERROR_CODES.has(err.message) ||
|
|
66
|
-
(err.message && SSL_ERROR_CODES.has(err.message.split(':').pop()?.trim()))
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
50
|
function stripHtml(html) {
|
|
71
51
|
if (!html) return '';
|
|
72
52
|
return html
|
|
@@ -123,7 +103,7 @@ function deriveSigla(projectName) {
|
|
|
123
103
|
.filter(w => !skip.has(w.toLowerCase()))
|
|
124
104
|
.map(w => w[0].toUpperCase())
|
|
125
105
|
.join('');
|
|
126
|
-
return letters.slice(0, 4) || projectName.slice(0, 3).toUpperCase();
|
|
106
|
+
return (letters.slice(0, 4) || projectName.trim().slice(0, 3).toUpperCase()).trim();
|
|
127
107
|
}
|
|
128
108
|
|
|
129
109
|
// ──────────────────────────────────────────────────────
|
|
@@ -362,15 +342,7 @@ Thumbs.db
|
|
|
362
342
|
*.swp
|
|
363
343
|
`;
|
|
364
344
|
|
|
365
|
-
|
|
366
|
-
content += `
|
|
367
|
-
# VS Code (exceto MCP config compartilhada)
|
|
368
|
-
.vscode/*
|
|
369
|
-
!.vscode/mcp.json
|
|
370
|
-
`;
|
|
371
|
-
} else {
|
|
372
|
-
content += '\n.vscode/\n';
|
|
373
|
-
}
|
|
345
|
+
content += '\n.vscode/\n';
|
|
374
346
|
|
|
375
347
|
return content;
|
|
376
348
|
}
|
|
@@ -411,53 +383,38 @@ FIGMA_API_KEY=seu-figma-api-key
|
|
|
411
383
|
return content;
|
|
412
384
|
}
|
|
413
385
|
|
|
414
|
-
function
|
|
386
|
+
function buildMcpServers(config, { stdio = false } = {}) {
|
|
415
387
|
const runner = detectMcpRunner();
|
|
416
|
-
const
|
|
417
|
-
...runner.prefix,
|
|
418
|
-
'--confluence-url', '${CONFLUENCE_URL}',
|
|
419
|
-
'--confluence-username', '${CONFLUENCE_USERNAME}',
|
|
420
|
-
'--confluence-token', '${CONFLUENCE_API_TOKEN}'
|
|
421
|
-
];
|
|
388
|
+
const base = stdio ? { type: 'stdio' } : {};
|
|
422
389
|
const servers = {
|
|
423
390
|
confluence: {
|
|
391
|
+
...base,
|
|
424
392
|
command: runner.command,
|
|
425
|
-
args:
|
|
393
|
+
args: [
|
|
394
|
+
...runner.prefix,
|
|
395
|
+
'--confluence-url', config.confluenceUrl,
|
|
396
|
+
'--confluence-username', config.confluenceUser,
|
|
397
|
+
'--confluence-token', config.confluenceToken
|
|
398
|
+
],
|
|
426
399
|
}
|
|
427
400
|
};
|
|
428
401
|
if (config.hasFigma && config.figmaFileUrl) {
|
|
429
402
|
const npx = detectNpxRunner();
|
|
430
403
|
servers.figma = {
|
|
404
|
+
...base,
|
|
431
405
|
command: npx.command,
|
|
432
|
-
args: [...npx.prefix, FIGMA_MCP_PACKAGE, '--figma-api-key',
|
|
406
|
+
args: [...npx.prefix, FIGMA_MCP_PACKAGE, '--figma-api-key', config.figmaToken]
|
|
433
407
|
};
|
|
434
408
|
}
|
|
435
|
-
return
|
|
409
|
+
return servers;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function generateClaudeMcp(config) {
|
|
413
|
+
return JSON.stringify({ mcpServers: buildMcpServers(config) }, null, 2) + '\n';
|
|
436
414
|
}
|
|
437
415
|
|
|
438
416
|
function generateCopilotMcp(config) {
|
|
439
|
-
|
|
440
|
-
const confluenceArgs = [
|
|
441
|
-
...runner.prefix,
|
|
442
|
-
'--env-file', '.env',
|
|
443
|
-
'--no-confluence-ssl-verify'
|
|
444
|
-
];
|
|
445
|
-
const servers = {
|
|
446
|
-
confluence: {
|
|
447
|
-
type: 'stdio',
|
|
448
|
-
command: runner.command,
|
|
449
|
-
args: confluenceArgs,
|
|
450
|
-
}
|
|
451
|
-
};
|
|
452
|
-
if (config.hasFigma && config.figmaFileUrl) {
|
|
453
|
-
const npx = detectNpxRunner();
|
|
454
|
-
servers.figma = {
|
|
455
|
-
type: 'stdio',
|
|
456
|
-
command: npx.command,
|
|
457
|
-
args: [...npx.prefix, FIGMA_MCP_PACKAGE, '--figma-api-key', '${input:figma-api-key}']
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
return JSON.stringify({ servers }, null, 2) + '\n';
|
|
417
|
+
return JSON.stringify({ servers: buildMcpServers(config, { stdio: true }) }, null, 2) + '\n';
|
|
461
418
|
}
|
|
462
419
|
|
|
463
420
|
function generateArchitectureSkeleton(config) {
|
|
@@ -785,6 +742,25 @@ export async function initCommand() {
|
|
|
785
742
|
phase3.hasFigma = false;
|
|
786
743
|
phase3.figmaFileUrl = '';
|
|
787
744
|
phase3.figmaToken = '';
|
|
745
|
+
} else if (isSslError(err)) {
|
|
746
|
+
figmaSpinner.text = 'Certificado SSL corporativo detectado — tentando sem verificação...';
|
|
747
|
+
try {
|
|
748
|
+
const result = await figmaRequest(phase3.figmaToken.trim(), 15000, true);
|
|
749
|
+
figmaSpinner.succeed(`Token Figma validado (SSL corporativo ignorado) — usuário: ${result.data.handle || result.data.email || 'OK'}`);
|
|
750
|
+
figmaTokenValidated = true;
|
|
751
|
+
} catch (retryErr) {
|
|
752
|
+
if (retryErr.status === 401 || retryErr.status === 403) {
|
|
753
|
+
figmaSpinner.fail(`Token Figma inválido (HTTP ${retryErr.status}). Verifique o Personal Access Token.`);
|
|
754
|
+
console.log(chalk.dim(' Gere em: Figma > Account Settings > Personal Access Tokens'));
|
|
755
|
+
console.log(chalk.yellow(' Continuando sem Figma...\n'));
|
|
756
|
+
phase3.hasFigma = false;
|
|
757
|
+
phase3.figmaFileUrl = '';
|
|
758
|
+
phase3.figmaToken = '';
|
|
759
|
+
} else {
|
|
760
|
+
figmaSpinner.warn(`Não foi possível validar o token Figma: ${retryErr.message}`);
|
|
761
|
+
console.log(chalk.yellow(' Token será salvo, mas verifique com "open-spec-kit doctor".\n'));
|
|
762
|
+
}
|
|
763
|
+
}
|
|
788
764
|
} else {
|
|
789
765
|
figmaSpinner.warn(`Não foi possível validar o token Figma: ${err.message}`);
|
|
790
766
|
console.log(chalk.yellow(' Token será salvo, mas verifique com "open-spec-kit doctor".\n'));
|
|
@@ -983,3 +959,16 @@ export async function initCommand() {
|
|
|
983
959
|
process.exit(1);
|
|
984
960
|
}
|
|
985
961
|
}
|
|
962
|
+
|
|
963
|
+
// Exported for testing — pure functions with no side effects
|
|
964
|
+
export {
|
|
965
|
+
stripHtml,
|
|
966
|
+
detectTechInText,
|
|
967
|
+
detectRepoNames,
|
|
968
|
+
deriveSigla,
|
|
969
|
+
generateProjectsYml,
|
|
970
|
+
generateEnvFile,
|
|
971
|
+
generateEnvExample,
|
|
972
|
+
generateGitignore,
|
|
973
|
+
generateArchitectureSkeleton,
|
|
974
|
+
};
|
package/src/commands/validate.js
CHANGED
|
@@ -24,7 +24,7 @@ import * as rules from '../schemas/spec.schema.js';
|
|
|
24
24
|
function parseBrief(content) {
|
|
25
25
|
const sections = parseSections(content);
|
|
26
26
|
return {
|
|
27
|
-
lineCount: content.split(
|
|
27
|
+
lineCount: content.split(/\r?\n/).length,
|
|
28
28
|
hasProblema: !!findSection(sections, /problema|contexto|visão\s*geral|overview|background|objetivo\s+do\s+produto|situação\s*atual|descri[çc][aã]o|identifica[çc][aã]o/i),
|
|
29
29
|
hasForaDeEscopo: !!findSection(sections, /fora\s+de\s+escopo/i),
|
|
30
30
|
reqIds: content.match(/REQ-\w+/g) || [],
|
package/src/utils/http.js
CHANGED
|
@@ -49,6 +49,23 @@ export function confluenceRequest(baseUrl, path, user, token, timeoutMs = 15000,
|
|
|
49
49
|
return httpGetJson(url, { 'Authorization': `Basic ${auth}` }, { timeoutMs, allowInsecure });
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
export function figmaRequest(token, timeoutMs = 15000) {
|
|
53
|
-
return httpGetJson('https://api.figma.com/v1/me', { 'X-Figma-Token': token }, { timeoutMs });
|
|
52
|
+
export function figmaRequest(token, timeoutMs = 15000, allowInsecure = false) {
|
|
53
|
+
return httpGetJson('https://api.figma.com/v1/me', { 'X-Figma-Token': token }, { timeoutMs, allowInsecure });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const SSL_ERROR_CODES = new Set([
|
|
57
|
+
'UNABLE_TO_VERIFY_LEAF_SIGNATURE',
|
|
58
|
+
'SELF_SIGNED_CERT_IN_CHAIN',
|
|
59
|
+
'DEPTH_ZERO_SELF_SIGNED_CERT',
|
|
60
|
+
'CERT_HAS_EXPIRED',
|
|
61
|
+
'ERR_TLS_CERT_ALTNAME_INVALID',
|
|
62
|
+
'UNABLE_TO_GET_ISSUER_CERT_LOCALLY',
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
export function isSslError(err) {
|
|
66
|
+
return err.status === 0 && !!(
|
|
67
|
+
SSL_ERROR_CODES.has(err.code) ||
|
|
68
|
+
SSL_ERROR_CODES.has(err.message) ||
|
|
69
|
+
(err.message && SSL_ERROR_CODES.has(err.message.split(':').pop()?.trim()))
|
|
70
|
+
);
|
|
54
71
|
}
|
|
@@ -17,7 +17,11 @@ This rule exists to prevent the "duplicate responsibility" anti-pattern where mu
|
|
|
17
17
|
| Demandas/ labels | /setup | — | /discovery | — |
|
|
18
18
|
| Visão do Produto | /setup | — | all | — |
|
|
19
19
|
| **Glossário** | /setup | **/discovery** (adds domain terms found in PRD analysis, exit gate), **/dev** (post-merge only — adds terms that emerged during implementation and weren't in the PRD) | /spec | /spec (read-only validate — if terms are missing, stop and ask dev to re-run /discovery) |
|
|
20
|
+
|
|
21
|
+
> **Glossário conflict rule**: if /dev (post-merge) needs to update a term that /discovery defined, /dev's update wins — it reflects implemented reality. /dev adds a note: "Atualizado pós-merge: {reason}". If /dev adds a term that already exists with a different definition, update the definition to match implementation.
|
|
20
22
|
| DD (Design Document) | /spec (via `design-doc` agent, fallback: MCP direct) | /dev (via `design-doc` agent, fallback: MCP direct) | — | — |
|
|
23
|
+
|
|
24
|
+
> **DD update coordination**: /spec updates the DD FIRST (during spec phase), /dev updates AFTER MERGE (during Phase D). If /dev needs to change something /spec wrote, /dev overwrites with implemented reality (code is source of truth post-merge). No locking needed — the sequential workflow prevents simultaneous updates.
|
|
21
25
|
| Domínio/Regras | /setup | /dev (post-merge only) | /discovery, /spec | — |
|
|
22
26
|
| Domínio/Fluxos | /setup | /dev (post-merge only) | /discovery | — |
|
|
23
27
|
| Domínio/Tabelas | /setup | — | /discovery | — |
|
|
@@ -32,7 +36,7 @@ This rule exists to prevent the "duplicate responsibility" anti-pattern where mu
|
|
|
32
36
|
| Artifact | Owner (creates) | Updater | Readers | Validators |
|
|
33
37
|
|----------|----------------|---------|---------|------------|
|
|
34
38
|
| `projects.yml` | /setup | /spec (adds planned repos), /dev (repo URLs + status) | all | — |
|
|
35
|
-
| `docs/architecture.md` | /setup | /discovery (resolves decisions) | /spec, /dev | /spec (validates no blockers) |
|
|
39
|
+
| `docs/architecture.md` | /setup | /discovery (resolves decisions), /dev (adds decisions discovered during implementation — post-merge only) | /spec, /dev | /spec (validates no blockers) |
|
|
36
40
|
| `docs/lessons/` | /dev | /dev | all | — |
|
|
37
41
|
| `docs/decisions/` | manual / spec-agent | — | all | — |
|
|
38
42
|
| `specs/NNN/brief.md` | /spec | — | /dev | — |
|
|
@@ -41,7 +45,10 @@ This rule exists to prevent the "duplicate responsibility" anti-pattern where mu
|
|
|
41
45
|
| `specs/NNN/tasks.md` | /spec | — | /dev | — |
|
|
42
46
|
| `specs/NNN/links.md` | /spec (template) | /dev (MR links) | — | — |
|
|
43
47
|
| `specs/NNN/audit-report.md` | /spec (self-review) | — | /dev (reads before Phase B, blocks if ❌) | — |
|
|
44
|
-
| `specs/NNN/conformance-report.json` | /dev (Phase C.3) |
|
|
48
|
+
| `specs/NNN/conformance-report.json` | /dev (Phase C.3) | /dev (overwrites per run) | CI Guard | — |
|
|
49
|
+
| `specs/NNN/conformance-history.log` | /dev (Phase C.3) | /dev (appends per run) | — | — |
|
|
50
|
+
|
|
51
|
+
> **Conformance versioning**: `conformance-report.json` is overwritten on each /dev run. To preserve history, /dev appends a summary line to `specs/NNN/conformance-history.log`: `{timestamp} | {result} | {matching}/{total} endpoints | {matching}/{total} fields`
|
|
45
52
|
|
|
46
53
|
## Key Rules
|
|
47
54
|
|
|
@@ -315,6 +315,17 @@ If the code reviewer (human or agent) rejects the MR:
|
|
|
315
315
|
After the dev merges the MR:
|
|
316
316
|
|
|
317
317
|
D.1. Update `specs/NNN-name/links.md` with merged MR link
|
|
318
|
+
|
|
319
|
+
D.1.5. **Sync spec with implementation reality**
|
|
320
|
+
If the conformance report (Phase C.3) detected INTENTIONAL divergences (spec was wrong, implementation corrected it), update the LOCAL spec files to match what was actually implemented:
|
|
321
|
+
- `contracts.md`: fix endpoint paths, response fields, or types that diverged
|
|
322
|
+
- `scenarios.md`: update Given/When/Then if behavior changed
|
|
323
|
+
- Commit: `fix(NNN): sync spec with implementation — {summary of changes}`
|
|
324
|
+
|
|
325
|
+
This ensures specs remain the source of truth for FUTURE features that reference them. Without this update, /discovery and /spec for next features read stale contracts.
|
|
326
|
+
|
|
327
|
+
If conformance report shows NO divergences → skip (specs already match).
|
|
328
|
+
|
|
318
329
|
D.2. Move VCS Issue → `done`
|
|
319
330
|
D.3. **Update living docs on Confluence** (via MCP):
|
|
320
331
|
|
|
@@ -336,7 +347,13 @@ D.3. **Update living docs on Confluence** (via MCP):
|
|
|
336
347
|
- Notify the dev with message: "Confluence indisponível — atualizações salvas em docs/pending-confluence-updates.md. Execute manualmente quando o Confluence voltar."
|
|
337
348
|
- On the next /dev execution, check if `docs/pending-confluence-updates.md` exists and attempt to apply pending updates
|
|
338
349
|
|
|
339
|
-
D.4.
|
|
350
|
+
D.4. **Post-implementation discoveries:**
|
|
351
|
+
|
|
352
|
+
If implementation revealed:
|
|
353
|
+
- a) A SURPRISE (unexpected behavior, edge case) → save to `docs/lessons/NNN-title.md` (template below)
|
|
354
|
+
- b) A NEW ARCHITECTURE DECISION (chose a pattern, discovered a constraint) → add to `docs/architecture.md` "Decisões em Aberto" table with Status: `resolvida — [decisão tomada durante implementação de {SIGLA}-NNN]`. This ensures architecture.md reflects decisions made during /dev, not just during /discovery.
|
|
355
|
+
|
|
356
|
+
If something surprised during implementation → generate `docs/lessons/NNN-title.md` with template:
|
|
340
357
|
```
|
|
341
358
|
## O que aconteceu
|
|
342
359
|
[description]
|
|
@@ -345,6 +362,26 @@ D.4. If something surprised during implementation → generate `docs/lessons/NNN
|
|
|
345
362
|
## Como evitar
|
|
346
363
|
[what to do differently next time]
|
|
347
364
|
```
|
|
365
|
+
D.4.5. **Update CHANGELOG.md**
|
|
366
|
+
|
|
367
|
+
In the MAIN repo (first repo in projects.yml), create or update `CHANGELOG.md` with an entry for this feature:
|
|
368
|
+
|
|
369
|
+
```markdown
|
|
370
|
+
## [{SIGLA}-NNN] Feature Name — YYYY-MM-DD
|
|
371
|
+
|
|
372
|
+
### Adicionado
|
|
373
|
+
- {list new endpoints from conformance report}
|
|
374
|
+
- {list new entities from contracts.md}
|
|
375
|
+
|
|
376
|
+
### Alterado
|
|
377
|
+
- {list modified entities/endpoints, if evolution feature}
|
|
378
|
+
|
|
379
|
+
### Observações
|
|
380
|
+
- {any "A CONFIRMAR" items still pending}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
If `CHANGELOG.md` doesn't exist, create it with header: `# Changelog` and a reference to [Keep a Changelog](https://keepachangelog.com/).
|
|
384
|
+
|
|
348
385
|
D.5. Notify Google Chat (rich card format):
|
|
349
386
|
```bash
|
|
350
387
|
./scripts/notify-gchat.sh --card \
|
|
@@ -47,8 +47,12 @@ If NO argument was passed (user ran `/discovery` without a feature name):
|
|
|
47
47
|
|
|
48
48
|
Bloqueadas:
|
|
49
49
|
- 🚀 TFB-002 - Conferência (depende de TFB-001)
|
|
50
|
+
|
|
51
|
+
Caminho crítico recomendado: TFB-000 → TFB-001 → TFB-003 → TFB-002
|
|
52
|
+
(002 depende de 001; 003 pode ser paralela com 001)
|
|
50
53
|
```
|
|
51
|
-
6.
|
|
54
|
+
6. **Suggest execution order**: analyze the dependency graph from all features' "Depende de" fields and suggest the order that minimizes blocked time. This is a SUGGESTION — the dev chooses the final order.
|
|
55
|
+
7. Ask: "Qual feature deseja trabalhar?"
|
|
52
56
|
7. Proceed with the chosen feature.
|
|
53
57
|
|
|
54
58
|
If argument WAS passed (e.g., `/discovery TFB-001`):
|
|
@@ -178,6 +182,7 @@ Covered / Partial / Missing.
|
|
|
178
182
|
| SECURITY | Sensitive data? Auth requirements? Redaction needed? |
|
|
179
183
|
| PERFORMANCE | SLAs? Expected load? Rate limits? |
|
|
180
184
|
| UX | Error messages? Empty states? (if frontend) |
|
|
185
|
+
| TECHNOLOGY | Stack in projects.yml compatible with requirements? WebSocket needed but no support? Background jobs needed but no worker declared? Auth mechanism defined? DB supports required queries (full-text search, JSONB, geo)? |
|
|
181
186
|
|
|
182
187
|
**Special rule — PERFORMANCE**: this category can NEVER be marked as "Covered" without at least one question about: data limits (how many records per entity?), response SLA (max acceptable response time per endpoint), or expected throughput (requests/second, concurrent users). If the PO demand does not mention performance, ASK — absence of a performance requirement does not mean "no requirement", it means "undocumented implicit requirement".
|
|
183
188
|
|
|
@@ -297,6 +302,14 @@ Generate the PRD on the feature page in Confluence:
|
|
|
297
302
|
### Decisões tomadas
|
|
298
303
|
- Duvida N.1: [decision] — Justificativa: [why]
|
|
299
304
|
- ...
|
|
305
|
+
|
|
306
|
+
### Requisitos Não-Funcionais (se aplicável)
|
|
307
|
+
- **Performance**: [target, e.g., "GET /bills < 200ms p95, 100 req/s"]
|
|
308
|
+
- **Disponibilidade**: [target, e.g., "99.9% uptime"]
|
|
309
|
+
- **Escalabilidade**: [constraints, e.g., "suportar 10k faturas/mês"]
|
|
310
|
+
|
|
311
|
+
_Include this section only if the gap analysis identified PERFORMANCE or TECHNOLOGY
|
|
312
|
+
gaps with concrete targets. If no NFRs were identified, omit entirely._
|
|
300
313
|
```
|
|
301
314
|
|
|
302
315
|
Then:
|
|
@@ -165,6 +165,7 @@ Must contain these sections:
|
|
|
165
165
|
- **Premissas**: assumptions stated in the PO docs (copy them, don't summarize)
|
|
166
166
|
- **Escopo da v1**: what's in
|
|
167
167
|
- **Fora de escopo**: what's explicitly out
|
|
168
|
+
- **Riscos e dependencias criticas**: external systems without defined contracts, teams/knowledge dependencies, technical unknowns. If PO docs mention integration with an external system but no contract or API is defined, register as risk. If PO mentions a technology the team has no experience with, register as risk. If no risks identified, write "Nenhum risco critico identificado nos documentos do PO."
|
|
168
169
|
- **Metricas de sucesso**: if the PO defined metrics, use them. If not, write "Metricas de negocio — A CONFIRMAR. Nao definidas pelo PO." Do NOT invent metrics. Do NOT use functional test criteria as metrics.
|
|
169
170
|
- **Restricoes**: mandatory tech, environment, compliance constraints
|
|
170
171
|
|
|
@@ -296,10 +297,31 @@ All subsequent features ({SIGLA}-001 onwards) have an implicit dependency on {SI
|
|
|
296
297
|
|
|
297
298
|
**Reminder: feature pages are children of `{SIGLA} — Features` (which is a child of `ROOT_ID`). Do NOT create features inside `Demandas/`.**
|
|
298
299
|
|
|
299
|
-
|
|
300
|
+
**Follow this feature identification algorithm — both phases are mandatory:**
|
|
301
|
+
|
|
302
|
+
**Phase A — Map PO sections to feature candidates:**
|
|
303
|
+
1. Read each PO document in `Demandas/`
|
|
304
|
+
2. Identify every NUMBERED SECTION or HEADING that describes a distinct user capability (look for: "Objetivo:", "Como [persona]...", numbered items "0.", "1.", "2.", or distinct headings with acceptance criteria)
|
|
305
|
+
3. One section with a distinct objective = one feature candidate
|
|
306
|
+
4. Record for each candidate: source reference (e.g., "Regra de Negocio §2"), title, number of acceptance criteria
|
|
307
|
+
5. If a document has NO numbered sections (free-form text), identify capabilities by change of theme, persona, or objective — each distinct capability = one candidate
|
|
308
|
+
|
|
309
|
+
**Phase B — Cross-cutting concern sweep:**
|
|
310
|
+
After listing business features from Phase A, scan ALL PO documents AND architecture docs for these triggers. If found AND no Phase A candidate already covers the concern explicitly, CREATE a dedicated feature:
|
|
311
|
+
|
|
312
|
+
| Trigger keywords in input docs | Feature to create |
|
|
313
|
+
|-------------------------------|-------------------|
|
|
314
|
+
| SSO, JWT, OAuth, roles, permissões, autorização, autenticação, LGPD, rate limit | {SIGLA}-NNN - Autenticação e Autorização |
|
|
315
|
+
| automático, SLA, prazo, timeout, scheduled, job, cron, fechamento automático | {SIGLA}-NNN - Processos Automáticos e SLA |
|
|
316
|
+
|
|
317
|
+
General principle: any capability mentioned in the PO that is NOT tied to a specific user screen or flow but affects multiple features should be evaluated as a potential cross-cutting feature.
|
|
318
|
+
|
|
319
|
+
If the concern IS already an explicit PO section (identified in Phase A as its own candidate), do NOT create a duplicate.
|
|
320
|
+
|
|
321
|
+
**After identifying all features, create a page for EACH.** For each feature:
|
|
300
322
|
|
|
301
323
|
1. Create a child page under `{SIGLA} — Features` with title: `🚀 {SIGLA}-NNN - Feature name` (numbering sequential: 001, 002, ...). E.g., `🚀 FT-001 - Cadastro e Autenticação`, `🚀 TD-003 - Gerenciamento de Tarefas`
|
|
302
|
-
2. Content MUST
|
|
324
|
+
2. Content MUST follow this template (in pt-BR):
|
|
303
325
|
```
|
|
304
326
|
| Campo | Valor |
|
|
305
327
|
|-------|-------|
|
|
@@ -307,8 +329,11 @@ Analyze the PO documents in `Demandas/` and identify distinct features/capabilit
|
|
|
307
329
|
| Depende de | {SIGLA}-000 (e outras se houver, separadas por vírgula) |
|
|
308
330
|
| Complexidade | baixa / média / alta |
|
|
309
331
|
| Repos envolvidos | {quais repos participam} |
|
|
332
|
+
| Seções do PO | {source sections, e.g., "Regra de Negocio §2, §7; DefinicoesTecnicas §2.2"} |
|
|
333
|
+
|
|
334
|
+
## Escopo
|
|
335
|
+
{bullet list of what this feature includes — extracted from PO docs, not invented}
|
|
310
336
|
```
|
|
311
|
-
After the table: 1-2 paragraphs extracted from PO documents describing what the feature does.
|
|
312
337
|
3. Add label `em-discovery` (ready for /discovery to process)
|
|
313
338
|
|
|
314
339
|
If the PO documents describe a SINGLE monolithic system without clear feature boundaries, create ONE feature page covering the full scope and note: "Feature única — considerar quebra em sub-features durante /discovery."
|
|
@@ -384,6 +409,8 @@ Before closing the loop, the agent MUST do a self-review:
|
|
|
384
409
|
- Did I invent anything that isn't in the PO documents?
|
|
385
410
|
3. **Fix any gaps found** by updating the pages
|
|
386
411
|
4. **Report** what was added/fixed in the self-review
|
|
412
|
+
5. **Feature coverage**: for EACH numbered section or distinct capability in the PO documents, verify it is covered by at least one feature page. If uncovered → create a feature or add the scope to an existing one. Report: "Cobertura de features: {N}/{total} seções do PO cobertas."
|
|
413
|
+
6. **Cross-cutting check**: if PO/architecture docs mention auth keywords (SSO, JWT, roles, LGPD) → verify a feature covers auth implementation. If they mention automation keywords (SLA, automático, scheduled) → verify a feature covers it. If missing → create the dedicated feature.
|
|
387
414
|
|
|
388
415
|
### 6. Close the loop
|
|
389
416
|
|
|
@@ -434,6 +461,7 @@ The output is considered good when:
|
|
|
434
461
|
- **Architecture.md**: has an "open decisions" table ranked by impact
|
|
435
462
|
- **projects.yml**: has all detected repos declared (not commented), with stack and status
|
|
436
463
|
- **Self-review**: was performed, gaps were found and fixed (or confirmed none exist)
|
|
464
|
+
- **Features**: every numbered section/capability in the PO documents is covered by at least one feature. Cross-cutting concerns (auth, automated processes) have dedicated features when triggered by keywords in the input. Feature pages include Escopo and Seções do PO fields.
|
|
437
465
|
|
|
438
466
|
## Validation checklist
|
|
439
467
|
|
|
@@ -443,7 +471,7 @@ The output is considered good when:
|
|
|
443
471
|
- [ ] Demand label transition: (none/novo) → processando → processado
|
|
444
472
|
|
|
445
473
|
### Content generated
|
|
446
|
-
- [ ] Visao do Produto: all required sections present (O que é, Para quem, Por que existe, Premissas, Escopo da v1, Fora de escopo, Métricas de sucesso, Restrições)
|
|
474
|
+
- [ ] Visao do Produto: all required sections present (O que é, Para quem, Por que existe, Premissas, Escopo da v1, Fora de escopo, Riscos e dependências críticas, Métricas de sucesso, Restrições)
|
|
447
475
|
- [ ] Glossario: covers all domain terms from PRD (verified by re-read)
|
|
448
476
|
- [ ] Regras: all rules have ID + Fonte (PRD section) + Verificavel
|
|
449
477
|
- [ ] Fluxos: all flows have happy path + exceptions + Mermaid flowchart TD
|
|
@@ -451,6 +479,9 @@ The output is considered good when:
|
|
|
451
479
|
- [ ] Integracoes: all events have producer + consumer + expected payload
|
|
452
480
|
- [ ] {SIGLA} — Features parent page created
|
|
453
481
|
- [ ] Initial feature pages created under {SIGLA} — Features (with label `em-discovery`)
|
|
482
|
+
- [ ] Every PO section covered by at least one feature (verified in self-review)
|
|
483
|
+
- [ ] Cross-cutting features created when PO mentions auth/SSO/JWT or SLA/automático keywords
|
|
484
|
+
- [ ] Feature pages have: Seções do PO field + Escopo section
|
|
454
485
|
- [ ] {SIGLA} — Arquivados parent page created
|
|
455
486
|
|
|
456
487
|
### Spec repo updated
|
|
@@ -43,6 +43,13 @@ Read these files FIRST:
|
|
|
43
43
|
- **Reuse** shared types (ErrorResponse, PaginatedResponse) by reference
|
|
44
44
|
- If the new feature MODIFIES behavior of an existing endpoint, document the change explicitly in brief.md: "Modifica GET /api/contas para incluir campo `saldoAtual` no response"
|
|
45
45
|
|
|
46
|
+
**Figma freshness check (if Figma configured):**
|
|
47
|
+
If `projects.yml` has `figma.file_url` and Figma MCP is available:
|
|
48
|
+
1. Read `get_metadata()` for the feature's frames
|
|
49
|
+
2. Compare key elements (screen count, form fields) against what /discovery captured in the PRD
|
|
50
|
+
3. If significant changes detected (new screens, removed fields) → WARN: "Design no Figma mudou desde o /discovery. Diferenças: {list}. Continuar com o design atual ou re-executar /discovery?"
|
|
51
|
+
4. If no changes or Figma unavailable → proceed silently
|
|
52
|
+
|
|
46
53
|
## Inputs
|
|
47
54
|
|
|
48
55
|
- Approved PRD on Confluence (label `prd-aprovado`) — read via MCP
|
|
@@ -123,7 +130,11 @@ Numbering is sequential: find the highest existing number in `specs/` and increm
|
|
|
123
130
|
- Every REQ-NNN from the PRD MUST have at least one scenario covering it
|
|
124
131
|
- If a REQ has no scenario, either the REQ is unverifiable (fix the PRD) or a scenario is missing (add it)
|
|
125
132
|
- **Cross-check with business rules**: read `Domínio/Regras` on Confluence and verify that every RN referenced in the scenarios exists. If a scenario contradicts an RN, flag it as error.
|
|
126
|
-
- **Mandatory
|
|
133
|
+
- **Mandatory security scenarios**: if the feature has ANY authenticated endpoint, include:
|
|
134
|
+
- At least 1 scenario: request WITHOUT token → expected 401 NAO_AUTORIZADO
|
|
135
|
+
- At least 1 scenario: request with WRONG role → expected 403 ACESSO_NEGADO
|
|
136
|
+
- At least 1 scenario per endpoint accepting user input: malformed/malicious input → expected 400 with validation error (NOT 500)
|
|
137
|
+
If feature has NO authenticated endpoints, skip auth scenarios.
|
|
127
138
|
- **Mandatory pagination scenarios**: if ANY endpoint uses explicit pagination (PaginatedResponse, page/pageSize params, or similar envelope), include scenarios for:
|
|
128
139
|
- Default pagination (no params → uses defaults)
|
|
129
140
|
- Last page (partial results)
|
|
@@ -175,6 +186,11 @@ Additional rules:
|
|
|
175
186
|
- **Dependency between blocks**: if a repo block REQUIRES another block to be COMPLETED first, declare explicitly with `> Dependency: todo-api block must be completed before starting`
|
|
176
187
|
- Each task = 1 PR
|
|
177
188
|
- Reference REQ-NNN from scenarios
|
|
189
|
+
- **Size estimate**: each task line should include a size indicator (P/M/G):
|
|
190
|
+
- P (pequeno): < 2h, single file change, well-understood pattern
|
|
191
|
+
- M (médio): 2-8h, multiple files, may need investigation
|
|
192
|
+
- G (grande): > 8h, complex logic, new patterns, integration work
|
|
193
|
+
- Format: `- [ ] [P] T-NNN-REPO-XX: description (REQ-NNN)`
|
|
178
194
|
- **Feature grande**: if the PRD has more than 15 behaviors/requirements or spans 4+ repos, /discovery should have flagged it already. If it arrived here approved, proceed without questioning. Just note in brief.md: "Feature com {N} requisitos — escopo aprovado pelo PO."
|
|
179
195
|
|
|
180
196
|
#### links.md
|
|
@@ -202,6 +218,17 @@ Confirm you generated all **5 files** for this feature:
|
|
|
202
218
|
|
|
203
219
|
If any file is missing, **create it now** before continuing.
|
|
204
220
|
|
|
221
|
+
#### Optional: OpenAPI stub (if API endpoints exist)
|
|
222
|
+
|
|
223
|
+
If contracts.md defines REST endpoints, generate a minimal OpenAPI 3.0 stub file at `specs/NNN-name/openapi.yaml` containing:
|
|
224
|
+
- paths (from endpoint table: method + route)
|
|
225
|
+
- request/response schemas (from contract types)
|
|
226
|
+
- auth scheme (if authenticated endpoints: bearerAuth)
|
|
227
|
+
|
|
228
|
+
This is a STUB — /dev may extend it during implementation. Mark as draft: `info.description: "Auto-generated from contracts.md — draft"`
|
|
229
|
+
|
|
230
|
+
If the feature has no REST endpoints (e.g., worker-only feature), skip this step.
|
|
231
|
+
|
|
205
232
|
|
|
206
233
|
|
|
207
234
|
Before generating the DD or closing the loop, run ALL checks below. Fix any failures, then generate the audit report.
|