@eltonssouza/development-utility-kit 1.0.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.
Files changed (137) hide show
  1. package/.claude/agents/analyst.md +198 -0
  2. package/.claude/agents/backend-developer.md +126 -0
  3. package/.claude/agents/brain-keeper.md +229 -0
  4. package/.claude/agents/code-reviewer.md +181 -0
  5. package/.claude/agents/database-engineer.md +94 -0
  6. package/.claude/agents/devops-engineer.md +141 -0
  7. package/.claude/agents/frontend-developer.md +97 -0
  8. package/.claude/agents/gate-keeper.md +118 -0
  9. package/.claude/agents/migrator.md +291 -0
  10. package/.claude/agents/mobile-developer.md +80 -0
  11. package/.claude/agents/n8n-specialist.md +94 -0
  12. package/.claude/agents/product-owner.md +115 -0
  13. package/.claude/agents/qa-engineer.md +232 -0
  14. package/.claude/agents/release-engineer.md +204 -0
  15. package/.claude/agents/scaffold.md +87 -0
  16. package/.claude/agents/security-engineer.md +199 -0
  17. package/.claude/agents/sprint-runner.md +44 -0
  18. package/.claude/agents/stack-resolver.md +84 -0
  19. package/.claude/agents/tech-lead.md +182 -0
  20. package/.claude/agents/update-template.md +54 -0
  21. package/.claude/agents/ux-designer.md +118 -0
  22. package/.claude/settings.json +44 -0
  23. package/.claude/skills/README.md +332 -0
  24. package/.claude/skills/active-project/SKILL.md +129 -0
  25. package/.claude/skills/api-integration-test/SKILL.md +64 -0
  26. package/.claude/skills/auto-test-guard/SKILL.md +237 -0
  27. package/.claude/skills/auto-test-guard/resources/backend-tests.md +20 -0
  28. package/.claude/skills/auto-test-guard/resources/e2e-tests.md +24 -0
  29. package/.claude/skills/auto-test-guard/resources/execution-report.md +49 -0
  30. package/.claude/skills/auto-test-guard/resources/frontend-tests.md +18 -0
  31. package/.claude/skills/auto-test-guard/resources/initial-setup.md +108 -0
  32. package/.claude/skills/auto-test-guard/resources/run-suite.md +48 -0
  33. package/.claude/skills/auto-test-guard/resources/senior-gate.md +19 -0
  34. package/.claude/skills/brain-keeper/SKILL.md +60 -0
  35. package/.claude/skills/brain-keeper/obsidian/app.json +9 -0
  36. package/.claude/skills/brain-keeper/obsidian/appearance.json +4 -0
  37. package/.claude/skills/brain-keeper/obsidian/core-plugins.json +20 -0
  38. package/.claude/skills/brain-keeper/obsidian/daily-notes.json +5 -0
  39. package/.claude/skills/brain-keeper/obsidian/graph.json +32 -0
  40. package/.claude/skills/brain-keeper/obsidian/snippets/folder-colors.css +90 -0
  41. package/.claude/skills/brain-keeper/obsidian/templates.json +5 -0
  42. package/.claude/skills/brain-keeper/templates/README.md +51 -0
  43. package/.claude/skills/brain-keeper/templates/adr.md +40 -0
  44. package/.claude/skills/brain-keeper/templates/bug.md +35 -0
  45. package/.claude/skills/brain-keeper/templates/daily.md +38 -0
  46. package/.claude/skills/brain-keeper/templates/feature.md +62 -0
  47. package/.claude/skills/brain-keeper/templates/meeting.md +34 -0
  48. package/.claude/skills/brain-keeper/templates/tech-debt.md +21 -0
  49. package/.claude/skills/caveman/SKILL.md +187 -0
  50. package/.claude/skills/create-stack-pack/SKILL.md +281 -0
  51. package/.claude/skills/grill-me/SKILL.md +79 -0
  52. package/.claude/skills/honcho-memory/SKILL.md +207 -0
  53. package/.claude/skills/honcho-memory/docs/api-endpoints-verified.md +75 -0
  54. package/.claude/skills/honcho-memory/hooks/on-prompt-submit.js +221 -0
  55. package/.claude/skills/honcho-memory/hooks/on-stop.js +193 -0
  56. package/.claude/skills/honcho-memory/lib/honcho-client.js +363 -0
  57. package/.claude/skills/honcho-memory/lib/memory-injector.js +93 -0
  58. package/.claude/skills/honcho-memory/package.json +32 -0
  59. package/.claude/skills/honcho-memory/scripts/cli.js +370 -0
  60. package/.claude/skills/honcho-memory/scripts/setup.js +109 -0
  61. package/.claude/skills/honcho-memory/tests/t001-api-endpoints-verified.test.js +89 -0
  62. package/.claude/skills/honcho-memory/tests/t002-structure.test.js +97 -0
  63. package/.claude/skills/honcho-memory/tests/t003-honcho-client.test.js +162 -0
  64. package/.claude/skills/honcho-memory/tests/t004-soft-delete.test.js +259 -0
  65. package/.claude/skills/honcho-memory/tests/t005-memory-injector.test.js +175 -0
  66. package/.claude/skills/honcho-memory/tests/t006-on-prompt-submit.test.js +215 -0
  67. package/.claude/skills/honcho-memory/tests/t007-on-stop.test.js +165 -0
  68. package/.claude/skills/honcho-memory/tests/t008-cli.test.js +214 -0
  69. package/.claude/skills/honcho-memory/tests/t009-setup.test.js +232 -0
  70. package/.claude/skills/honcho-memory/tests/t010-skill-md.test.js +114 -0
  71. package/.claude/skills/honcho-memory/tests/t011-settings-hooks.test.js +105 -0
  72. package/.claude/skills/honcho-memory/tests/t012-docs-update.test.js +106 -0
  73. package/.claude/skills/honcho-memory/tests/t013-smoke-e2e.test.js +90 -0
  74. package/.claude/skills/pair-debug/SKILL.md +288 -0
  75. package/.claude/skills/prd-ready-check/SKILL.md +58 -0
  76. package/.claude/skills/project-manager/SKILL.md +167 -0
  77. package/.claude/skills/quality-standards/SKILL.md +201 -0
  78. package/.claude/skills/quick-feature/SKILL.md +264 -0
  79. package/.claude/skills/run-sprint/SKILL.md +342 -0
  80. package/.claude/skills/scaffold/SKILL.md +58 -0
  81. package/.claude/skills/stack-discovery/SKILL.md +159 -0
  82. package/.claude/skills/test-coverage-auditor/SKILL.md +59 -0
  83. package/.claude/skills/to-issues/SKILL.md +163 -0
  84. package/.claude/skills/to-prd/SKILL.md +130 -0
  85. package/.claude/skills/update-template/SKILL.md +254 -0
  86. package/.claude/stacks/CODEOWNERS +30 -0
  87. package/.claude/stacks/README.md +88 -0
  88. package/.claude/stacks/_template.md +116 -0
  89. package/.claude/stacks/java/spring-boot-3.md +376 -0
  90. package/.claude/stacks/java/spring-boot-4.md +438 -0
  91. package/.claude/stacks/typescript/angular-18.md +420 -0
  92. package/.claude/stacks/typescript/angular-19.md +397 -0
  93. package/.claude/stacks/typescript/angular-21.md +494 -0
  94. package/CLAUDE.md +453 -0
  95. package/README.md +391 -0
  96. package/bin/cli.js +773 -0
  97. package/bin/lib/backup.js +62 -0
  98. package/bin/lib/detect-stack.js +476 -0
  99. package/bin/lib/help.js +233 -0
  100. package/bin/lib/identity.js +108 -0
  101. package/bin/lib/local-dir.js +69 -0
  102. package/bin/lib/manifest.js +236 -0
  103. package/bin/lib/sync-all.js +394 -0
  104. package/bin/lib/version-check.js +398 -0
  105. package/dashboard/db.js +199 -0
  106. package/dashboard/package.json +22 -0
  107. package/dashboard/public/app.js +709 -0
  108. package/dashboard/public/content/docs/agents-reference.en.md +911 -0
  109. package/dashboard/public/content/docs/architecture-overview.en.md +260 -0
  110. package/dashboard/public/content/docs/autonomy-matrix.en.md +186 -0
  111. package/dashboard/public/content/docs/git-flow.en.md +525 -0
  112. package/dashboard/public/content/docs/honcho-memory.en.md +394 -0
  113. package/dashboard/public/content/docs/hooks-reference.en.md +420 -0
  114. package/dashboard/public/content/docs/pipeline.en.md +400 -0
  115. package/dashboard/public/content/docs/quality-gate.en.md +315 -0
  116. package/dashboard/public/content/docs/skills-reference.en.md +500 -0
  117. package/dashboard/public/content/docs/stack-rules.en.md +362 -0
  118. package/dashboard/public/content/docs/troubleshooting.en.md +637 -0
  119. package/dashboard/public/content/manifest.json +102 -0
  120. package/dashboard/public/content/manual/backend.en.md +1138 -0
  121. package/dashboard/public/content/manual/existing-project.en.md +831 -0
  122. package/dashboard/public/content/manual/frontend.en.md +1065 -0
  123. package/dashboard/public/content/manual/fullstack.en.md +1508 -0
  124. package/dashboard/public/content/manual/mobile.en.md +866 -0
  125. package/dashboard/public/index.html +108 -0
  126. package/dashboard/public/style.css +610 -0
  127. package/dashboard/public/vendor/marked.min.js +69 -0
  128. package/dashboard/rtk.js +143 -0
  129. package/dashboard/server-app.js +403 -0
  130. package/dashboard/server.js +104 -0
  131. package/dashboard/test/sprint1.test.js +406 -0
  132. package/dashboard/test/sprint2.test.js +571 -0
  133. package/dashboard/test/sprint3.test.js +560 -0
  134. package/package.json +33 -0
  135. package/scripts/hooks/subagent-telemetry.sh +14 -0
  136. package/scripts/hooks/telemetry-writer.js +250 -0
  137. package/scripts/latest-versions.json +56 -0
@@ -0,0 +1,571 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Sprint 2 failing tests — written before implementation (TDD RED phase).
5
+ * Tests for T-007 accordion logic (DoD checks) + T-008-T-012 content files + T-013 non-regression.
6
+ * Run: node --test dashboard/test/sprint2.test.js
7
+ */
8
+
9
+ const { test, describe, before } = require('node:test');
10
+ const assert = require('node:assert/strict');
11
+ const fs = require('node:fs');
12
+ const path = require('node:path');
13
+
14
+ const REPO_ROOT = path.resolve(__dirname, '..', '..');
15
+ const PUBLIC_DIR = path.join(REPO_ROOT, 'dashboard', 'public');
16
+ const MANUAL_DIR = path.join(PUBLIC_DIR, 'content', 'manual');
17
+
18
+ // ── T-007: accordion logic in app.js ─────────────────────────────────────────
19
+
20
+ describe('T-007: accordion logic in app.js', () => {
21
+ let js;
22
+ before(() => {
23
+ js = fs.readFileSync(path.join(PUBLIC_DIR, 'app.js'), 'utf8');
24
+ });
25
+
26
+ test('contains accordion class reference (>= 3 occurrences)', () => {
27
+ const count = (js.match(/accordion/g) || []).length;
28
+ assert.ok(count >= 3, `Expected >= 3 occurrences of 'accordion', got ${count}`);
29
+ });
30
+
31
+ test('contains Quick-start exception logic', () => {
32
+ const count = (js.match(/Quick-start|quickstart/gi) || []).length;
33
+ assert.ok(count >= 1, `Expected >= 1 occurrence of Quick-start/quickstart, got ${count}`);
34
+ });
35
+
36
+ test('buildAccordions function exists', () => {
37
+ assert.ok(js.includes('buildAccordions'), 'Missing buildAccordions function');
38
+ });
39
+
40
+ test('loadScenario function exists', () => {
41
+ assert.ok(js.includes('loadScenario'), 'Missing loadScenario function');
42
+ });
43
+
44
+ test('manifest is loaded for scenario buttons', () => {
45
+ assert.ok(js.includes('manifest.json') || js.includes('manifest'), 'Missing manifest loading logic');
46
+ });
47
+
48
+ test('scenario buttons reference path_template', () => {
49
+ assert.ok(js.includes('path_template') || js.includes('pathTemplate'), 'Missing path_template handling');
50
+ });
51
+
52
+ test('accordion-header button created for non-quick-start sections', () => {
53
+ assert.ok(js.includes('accordion-header'), 'Missing accordion-header class');
54
+ });
55
+
56
+ test('accordion-body created for collapsible sections', () => {
57
+ assert.ok(js.includes('accordion-body'), 'Missing accordion-body class');
58
+ });
59
+ });
60
+
61
+ // ── T-008: backend.{pt,en}.md content ────────────────────────────────────────
62
+
63
+ describe('T-008: manual content — backend scenario', () => {
64
+ const ptFile = path.join(MANUAL_DIR, 'backend.pt.md');
65
+ const enFile = path.join(MANUAL_DIR, 'backend.en.md');
66
+
67
+ test('backend.pt.md exists', () => {
68
+ assert.ok(fs.existsSync(ptFile), 'backend.pt.md does not exist');
69
+ });
70
+
71
+ test('backend.en.md exists', () => {
72
+ assert.ok(fs.existsSync(enFile), 'backend.en.md does not exist');
73
+ });
74
+
75
+ test('backend.pt.md has size > 1000 bytes', () => {
76
+ const stat = fs.statSync(ptFile);
77
+ assert.ok(stat.size > 1000, `backend.pt.md too small: ${stat.size} bytes`);
78
+ });
79
+
80
+ test('backend.en.md has size > 1000 bytes', () => {
81
+ const stat = fs.statSync(enFile);
82
+ assert.ok(stat.size > 1000, `backend.en.md too small: ${stat.size} bytes`);
83
+ });
84
+
85
+ test('backend.pt.md contains exactly 6 ## headings', () => {
86
+ const content = fs.readFileSync(ptFile, 'utf8');
87
+ const matches = content.match(/^## /gm) || [];
88
+ assert.equal(matches.length, 6, `Expected 6 ## headings in backend.pt.md, got ${matches.length}`);
89
+ });
90
+
91
+ test('backend.en.md contains exactly 6 ## headings', () => {
92
+ const content = fs.readFileSync(enFile, 'utf8');
93
+ const matches = content.match(/^## /gm) || [];
94
+ assert.equal(matches.length, 6, `Expected 6 ## headings in backend.en.md, got ${matches.length}`);
95
+ });
96
+
97
+ test('backend.pt.md contains Quick-start heading', () => {
98
+ const content = fs.readFileSync(ptFile, 'utf8');
99
+ assert.ok(content.includes('## Quick-start'), 'Missing ## Quick-start in backend.pt.md');
100
+ });
101
+
102
+ test('backend.pt.md contains Setup detalhado heading', () => {
103
+ const content = fs.readFileSync(ptFile, 'utf8');
104
+ assert.ok(content.includes('## Setup detalhado'), 'Missing ## Setup detalhado in backend.pt.md');
105
+ });
106
+
107
+ test('backend.pt.md contains Primeira feature heading', () => {
108
+ const content = fs.readFileSync(ptFile, 'utf8');
109
+ assert.ok(content.includes('## Primeira feature'), 'Missing ## Primeira feature in backend.pt.md');
110
+ });
111
+
112
+ test('backend.pt.md contains Testes heading', () => {
113
+ const content = fs.readFileSync(ptFile, 'utf8');
114
+ assert.ok(content.includes('## Testes'), 'Missing ## Testes in backend.pt.md');
115
+ });
116
+
117
+ test('backend.pt.md contains Deploy heading', () => {
118
+ const content = fs.readFileSync(ptFile, 'utf8');
119
+ assert.ok(content.includes('## Deploy'), 'Missing ## Deploy in backend.pt.md');
120
+ });
121
+
122
+ test('backend.pt.md contains Troubleshooting heading', () => {
123
+ const content = fs.readFileSync(ptFile, 'utf8');
124
+ assert.ok(content.includes('## Troubleshooting'), 'Missing ## Troubleshooting in backend.pt.md');
125
+ });
126
+
127
+ test('backend.pt.md mentions bootstrap-backend-java or duk command', () => {
128
+ const content = fs.readFileSync(ptFile, 'utf8');
129
+ assert.ok(
130
+ content.includes('bootstrap-backend-java') || content.includes('duk '),
131
+ 'Missing bootstrap-backend-java or duk command reference'
132
+ );
133
+ });
134
+
135
+ test('backend.pt.md mentions DDD structure (domain, application, infrastructure, web)', () => {
136
+ const content = fs.readFileSync(ptFile, 'utf8');
137
+ assert.ok(
138
+ content.includes('domain') && content.includes('application') && content.includes('infrastructure'),
139
+ 'Missing DDD package structure in backend.pt.md'
140
+ );
141
+ });
142
+
143
+ test('backend.pt.md mentions Flyway', () => {
144
+ const content = fs.readFileSync(ptFile, 'utf8');
145
+ assert.ok(content.includes('Flyway'), 'Missing Flyway mention in backend.pt.md');
146
+ });
147
+
148
+ test('backend.pt.md mentions JUnit or Testcontainers or JaCoCo', () => {
149
+ const content = fs.readFileSync(ptFile, 'utf8');
150
+ assert.ok(
151
+ content.includes('JUnit') || content.includes('Testcontainers') || content.includes('JaCoCo'),
152
+ 'Missing JUnit/Testcontainers/JaCoCo in backend.pt.md'
153
+ );
154
+ });
155
+
156
+ test('backend.pt.md mentions Docker or docker', () => {
157
+ const content = fs.readFileSync(ptFile, 'utf8');
158
+ assert.ok(content.toLowerCase().includes('docker'), 'Missing Docker mention in backend.pt.md');
159
+ });
160
+
161
+ test('backend.en.md contains Quick-start heading', () => {
162
+ const content = fs.readFileSync(enFile, 'utf8');
163
+ assert.ok(content.includes('## Quick-start'), 'Missing ## Quick-start in backend.en.md');
164
+ });
165
+
166
+ test('backend.en.md contains all 6 canonical headings', () => {
167
+ const content = fs.readFileSync(enFile, 'utf8');
168
+ const required = ['## Quick-start', '## Detailed setup', '## First feature', '## Tests', '## Deploy', '## Troubleshooting'];
169
+ for (const h of required) {
170
+ assert.ok(content.includes(h), `Missing '${h}' in backend.en.md`);
171
+ }
172
+ });
173
+ });
174
+
175
+ // ── T-009: frontend.{pt,en}.md content ───────────────────────────────────────
176
+
177
+ describe('T-009: manual content — frontend scenario', () => {
178
+ const ptFile = path.join(MANUAL_DIR, 'frontend.pt.md');
179
+ const enFile = path.join(MANUAL_DIR, 'frontend.en.md');
180
+
181
+ test('frontend.pt.md has size > 1000 bytes', () => {
182
+ const stat = fs.statSync(ptFile);
183
+ assert.ok(stat.size > 1000, `frontend.pt.md too small: ${stat.size} bytes`);
184
+ });
185
+
186
+ test('frontend.en.md has size > 1000 bytes', () => {
187
+ const stat = fs.statSync(enFile);
188
+ assert.ok(stat.size > 1000, `frontend.en.md too small: ${stat.size} bytes`);
189
+ });
190
+
191
+ test('frontend.pt.md contains exactly 6 ## headings', () => {
192
+ const content = fs.readFileSync(ptFile, 'utf8');
193
+ const matches = content.match(/^## /gm) || [];
194
+ assert.equal(matches.length, 6, `Expected 6 ## headings in frontend.pt.md, got ${matches.length}`);
195
+ });
196
+
197
+ test('frontend.en.md contains exactly 6 ## headings', () => {
198
+ const content = fs.readFileSync(enFile, 'utf8');
199
+ const matches = content.match(/^## /gm) || [];
200
+ assert.equal(matches.length, 6, `Expected 6 ## headings in frontend.en.md, got ${matches.length}`);
201
+ });
202
+
203
+ test('frontend.pt.md contains all 6 canonical headings (PT)', () => {
204
+ const content = fs.readFileSync(ptFile, 'utf8');
205
+ const required = ['## Quick-start', '## Setup detalhado', '## Primeira feature', '## Testes', '## Deploy', '## Troubleshooting'];
206
+ for (const h of required) {
207
+ assert.ok(content.includes(h), `Missing '${h}' in frontend.pt.md`);
208
+ }
209
+ });
210
+
211
+ test('frontend.en.md contains all 6 canonical headings (EN)', () => {
212
+ const content = fs.readFileSync(enFile, 'utf8');
213
+ const required = ['## Quick-start', '## Detailed setup', '## First feature', '## Tests', '## Deploy', '## Troubleshooting'];
214
+ for (const h of required) {
215
+ assert.ok(content.includes(h), `Missing '${h}' in frontend.en.md`);
216
+ }
217
+ });
218
+
219
+ test('frontend.pt.md mentions bootstrap-frontend or duk command', () => {
220
+ const content = fs.readFileSync(ptFile, 'utf8');
221
+ assert.ok(
222
+ content.includes('bootstrap-frontend') || content.includes('duk '),
223
+ 'Missing bootstrap-frontend or duk command reference'
224
+ );
225
+ });
226
+
227
+ test('frontend.pt.md mentions standalone component', () => {
228
+ const content = fs.readFileSync(ptFile, 'utf8');
229
+ assert.ok(content.includes('standalone'), 'Missing standalone component mention in frontend.pt.md');
230
+ });
231
+
232
+ test('frontend.pt.md mentions Signal or Signals', () => {
233
+ const content = fs.readFileSync(ptFile, 'utf8');
234
+ assert.ok(content.includes('Signal') || content.includes('signal'), 'Missing Signal mention in frontend.pt.md');
235
+ });
236
+
237
+ test('frontend.pt.md mentions separate files rule (.ts, .html, .scss)', () => {
238
+ const content = fs.readFileSync(ptFile, 'utf8');
239
+ assert.ok(
240
+ (content.includes('.ts') && content.includes('.html') && content.includes('.scss')) ||
241
+ content.includes('3 arquivos') || content.includes('arquivos separados') || content.includes('templateUrl'),
242
+ 'Missing separate files rule in frontend.pt.md'
243
+ );
244
+ });
245
+
246
+ test('frontend.pt.md mentions Jest or Playwright', () => {
247
+ const content = fs.readFileSync(ptFile, 'utf8');
248
+ assert.ok(
249
+ content.includes('Jest') || content.includes('Playwright'),
250
+ 'Missing Jest/Playwright mention in frontend.pt.md'
251
+ );
252
+ });
253
+
254
+ test('frontend.pt.md mentions OnPush', () => {
255
+ const content = fs.readFileSync(ptFile, 'utf8');
256
+ assert.ok(content.includes('OnPush'), 'Missing OnPush mention in frontend.pt.md');
257
+ });
258
+ });
259
+
260
+ // ── T-010: fullstack.{pt,en}.md content ──────────────────────────────────────
261
+
262
+ describe('T-010: manual content — fullstack scenario', () => {
263
+ const ptFile = path.join(MANUAL_DIR, 'fullstack.pt.md');
264
+ const enFile = path.join(MANUAL_DIR, 'fullstack.en.md');
265
+
266
+ test('fullstack.pt.md has size > 1000 bytes', () => {
267
+ const stat = fs.statSync(ptFile);
268
+ assert.ok(stat.size > 1000, `fullstack.pt.md too small: ${stat.size} bytes`);
269
+ });
270
+
271
+ test('fullstack.en.md has size > 1000 bytes', () => {
272
+ const stat = fs.statSync(enFile);
273
+ assert.ok(stat.size > 1000, `fullstack.en.md too small: ${stat.size} bytes`);
274
+ });
275
+
276
+ test('fullstack.pt.md contains exactly 6 ## headings', () => {
277
+ const content = fs.readFileSync(ptFile, 'utf8');
278
+ const matches = content.match(/^## /gm) || [];
279
+ assert.equal(matches.length, 6, `Expected 6 ## headings in fullstack.pt.md, got ${matches.length}`);
280
+ });
281
+
282
+ test('fullstack.en.md contains exactly 6 ## headings', () => {
283
+ const content = fs.readFileSync(enFile, 'utf8');
284
+ const matches = content.match(/^## /gm) || [];
285
+ assert.equal(matches.length, 6, `Expected 6 ## headings in fullstack.en.md, got ${matches.length}`);
286
+ });
287
+
288
+ test('fullstack.pt.md contains all 6 canonical headings (PT)', () => {
289
+ const content = fs.readFileSync(ptFile, 'utf8');
290
+ const required = ['## Quick-start', '## Setup detalhado', '## Primeira feature', '## Testes', '## Deploy', '## Troubleshooting'];
291
+ for (const h of required) {
292
+ assert.ok(content.includes(h), `Missing '${h}' in fullstack.pt.md`);
293
+ }
294
+ });
295
+
296
+ test('fullstack.en.md contains all 6 canonical headings (EN)', () => {
297
+ const content = fs.readFileSync(enFile, 'utf8');
298
+ const required = ['## Quick-start', '## Detailed setup', '## First feature', '## Tests', '## Deploy', '## Troubleshooting'];
299
+ for (const h of required) {
300
+ assert.ok(content.includes(h), `Missing '${h}' in fullstack.en.md`);
301
+ }
302
+ });
303
+
304
+ test('fullstack.pt.md mentions bootstrap-fullstack or duk command', () => {
305
+ const content = fs.readFileSync(ptFile, 'utf8');
306
+ assert.ok(
307
+ content.includes('bootstrap-fullstack') || content.includes('duk '),
308
+ 'Missing bootstrap-fullstack or duk command reference'
309
+ );
310
+ });
311
+
312
+ test('fullstack.pt.md mentions monorepo (backend/ + frontend/)', () => {
313
+ const content = fs.readFileSync(ptFile, 'utf8');
314
+ assert.ok(
315
+ content.includes('monorepo') || (content.includes('backend/') && content.includes('frontend/')),
316
+ 'Missing monorepo structure mention in fullstack.pt.md'
317
+ );
318
+ });
319
+
320
+ test('fullstack.pt.md mentions docker compose or docker-compose', () => {
321
+ const content = fs.readFileSync(ptFile, 'utf8');
322
+ assert.ok(
323
+ content.toLowerCase().includes('docker compose') || content.toLowerCase().includes('docker-compose'),
324
+ 'Missing docker compose mention in fullstack.pt.md'
325
+ );
326
+ });
327
+
328
+ test('fullstack.pt.md mentions CORS', () => {
329
+ const content = fs.readFileSync(ptFile, 'utf8');
330
+ assert.ok(content.includes('CORS') || content.includes('cors'), 'Missing CORS mention in fullstack.pt.md');
331
+ });
332
+
333
+ test('fullstack.pt.md mentions Angular → Spring Boot integration', () => {
334
+ const content = fs.readFileSync(ptFile, 'utf8');
335
+ assert.ok(
336
+ (content.includes('Angular') || content.includes('frontend')) &&
337
+ (content.includes('Spring Boot') || content.includes('backend')),
338
+ 'Missing Angular<->Spring Boot integration mention'
339
+ );
340
+ });
341
+ });
342
+
343
+ // ── T-011: mobile.{pt,en}.md content ─────────────────────────────────────────
344
+
345
+ describe('T-011: manual content — mobile scenario', () => {
346
+ const ptFile = path.join(MANUAL_DIR, 'mobile.pt.md');
347
+ const enFile = path.join(MANUAL_DIR, 'mobile.en.md');
348
+
349
+ test('mobile.pt.md has size > 1000 bytes', () => {
350
+ const stat = fs.statSync(ptFile);
351
+ assert.ok(stat.size > 1000, `mobile.pt.md too small: ${stat.size} bytes`);
352
+ });
353
+
354
+ test('mobile.en.md has size > 1000 bytes', () => {
355
+ const stat = fs.statSync(enFile);
356
+ assert.ok(stat.size > 1000, `mobile.en.md too small: ${stat.size} bytes`);
357
+ });
358
+
359
+ test('mobile.pt.md contains exactly 6 ## headings', () => {
360
+ const content = fs.readFileSync(ptFile, 'utf8');
361
+ const matches = content.match(/^## /gm) || [];
362
+ assert.equal(matches.length, 6, `Expected 6 ## headings in mobile.pt.md, got ${matches.length}`);
363
+ });
364
+
365
+ test('mobile.en.md contains exactly 6 ## headings', () => {
366
+ const content = fs.readFileSync(enFile, 'utf8');
367
+ const matches = content.match(/^## /gm) || [];
368
+ assert.equal(matches.length, 6, `Expected 6 ## headings in mobile.en.md, got ${matches.length}`);
369
+ });
370
+
371
+ test('mobile.pt.md contains all 6 canonical headings (PT)', () => {
372
+ const content = fs.readFileSync(ptFile, 'utf8');
373
+ const required = ['## Quick-start', '## Setup detalhado', '## Primeira feature', '## Testes', '## Deploy', '## Troubleshooting'];
374
+ for (const h of required) {
375
+ assert.ok(content.includes(h), `Missing '${h}' in mobile.pt.md`);
376
+ }
377
+ });
378
+
379
+ test('mobile.en.md contains all 6 canonical headings (EN)', () => {
380
+ const content = fs.readFileSync(enFile, 'utf8');
381
+ const required = ['## Quick-start', '## Detailed setup', '## First feature', '## Tests', '## Deploy', '## Troubleshooting'];
382
+ for (const h of required) {
383
+ assert.ok(content.includes(h), `Missing '${h}' in mobile.en.md`);
384
+ }
385
+ });
386
+
387
+ test('mobile.pt.md mentions Expo SDK 54 or Expo', () => {
388
+ const content = fs.readFileSync(ptFile, 'utf8');
389
+ assert.ok(content.includes('Expo'), 'Missing Expo mention in mobile.pt.md');
390
+ });
391
+
392
+ test('mobile.pt.md mentions React Native or RN', () => {
393
+ const content = fs.readFileSync(ptFile, 'utf8');
394
+ assert.ok(
395
+ content.includes('React Native') || content.includes('RN '),
396
+ 'Missing React Native mention in mobile.pt.md'
397
+ );
398
+ });
399
+
400
+ test('mobile.pt.md mentions New Architecture', () => {
401
+ const content = fs.readFileSync(ptFile, 'utf8');
402
+ assert.ok(
403
+ content.includes('New Architecture') || content.includes('nova arquitetura') || content.includes('newArchEnabled'),
404
+ 'Missing New Architecture mention in mobile.pt.md'
405
+ );
406
+ });
407
+
408
+ test('mobile.pt.md mentions EAS Build or eas build', () => {
409
+ const content = fs.readFileSync(ptFile, 'utf8');
410
+ assert.ok(
411
+ content.includes('EAS') || content.includes('eas build'),
412
+ 'Missing EAS Build mention in mobile.pt.md'
413
+ );
414
+ });
415
+
416
+ test('mobile.pt.md mentions navigation (React Navigation or Expo Router)', () => {
417
+ const content = fs.readFileSync(ptFile, 'utf8');
418
+ assert.ok(
419
+ content.includes('navigation') || content.includes('Navigation') || content.includes('Expo Router'),
420
+ 'Missing navigation mention in mobile.pt.md'
421
+ );
422
+ });
423
+ });
424
+
425
+ // ── T-012: existing-project.{pt,en}.md content ───────────────────────────────
426
+
427
+ describe('T-012: manual content — existing project adoption scenario', () => {
428
+ const ptFile = path.join(MANUAL_DIR, 'existing-project.pt.md');
429
+ const enFile = path.join(MANUAL_DIR, 'existing-project.en.md');
430
+
431
+ test('existing-project.pt.md has size > 1000 bytes', () => {
432
+ const stat = fs.statSync(ptFile);
433
+ assert.ok(stat.size > 1000, `existing-project.pt.md too small: ${stat.size} bytes`);
434
+ });
435
+
436
+ test('existing-project.en.md has size > 1000 bytes', () => {
437
+ const stat = fs.statSync(enFile);
438
+ assert.ok(stat.size > 1000, `existing-project.en.md too small: ${stat.size} bytes`);
439
+ });
440
+
441
+ test('existing-project.pt.md contains exactly 6 ## headings', () => {
442
+ const content = fs.readFileSync(ptFile, 'utf8');
443
+ const matches = content.match(/^## /gm) || [];
444
+ assert.equal(matches.length, 6, `Expected 6 ## headings in existing-project.pt.md, got ${matches.length}`);
445
+ });
446
+
447
+ test('existing-project.en.md contains exactly 6 ## headings', () => {
448
+ const content = fs.readFileSync(enFile, 'utf8');
449
+ const matches = content.match(/^## /gm) || [];
450
+ assert.equal(matches.length, 6, `Expected 6 ## headings in existing-project.en.md, got ${matches.length}`);
451
+ });
452
+
453
+ test('existing-project.pt.md contains all 6 canonical headings (PT)', () => {
454
+ const content = fs.readFileSync(ptFile, 'utf8');
455
+ const required = ['## Quick-start', '## Setup detalhado', '## Primeira feature', '## Testes', '## Deploy', '## Troubleshooting'];
456
+ for (const h of required) {
457
+ assert.ok(content.includes(h), `Missing '${h}' in existing-project.pt.md`);
458
+ }
459
+ });
460
+
461
+ test('existing-project.en.md contains all 6 canonical headings (EN)', () => {
462
+ const content = fs.readFileSync(enFile, 'utf8');
463
+ const required = ['## Quick-start', '## Detailed setup', '## First feature', '## Tests', '## Deploy', '## Troubleshooting'];
464
+ for (const h of required) {
465
+ assert.ok(content.includes(h), `Missing '${h}' in existing-project.en.md`);
466
+ }
467
+ });
468
+
469
+ test('existing-project.pt.md mentions active-project', () => {
470
+ const content = fs.readFileSync(ptFile, 'utf8');
471
+ assert.ok(content.includes('active-project'), 'Missing active-project mention in existing-project.pt.md');
472
+ });
473
+
474
+ test('existing-project.pt.md mentions update-template', () => {
475
+ const content = fs.readFileSync(ptFile, 'utf8');
476
+ assert.ok(content.includes('update-template'), 'Missing update-template mention in existing-project.pt.md');
477
+ });
478
+
479
+ test('existing-project.pt.md mentions brain-keeper', () => {
480
+ const content = fs.readFileSync(ptFile, 'utf8');
481
+ assert.ok(content.includes('brain-keeper'), 'Missing brain-keeper mention in existing-project.pt.md');
482
+ });
483
+
484
+ test('existing-project.pt.md mentions Project Identity', () => {
485
+ const content = fs.readFileSync(ptFile, 'utf8');
486
+ assert.ok(content.includes('Project Identity'), 'Missing Project Identity mention in existing-project.pt.md');
487
+ });
488
+
489
+ test('existing-project.en.md mentions active-project', () => {
490
+ const content = fs.readFileSync(enFile, 'utf8');
491
+ assert.ok(content.includes('active-project'), 'Missing active-project mention in existing-project.en.md');
492
+ });
493
+
494
+ test('existing-project.en.md mentions update-template', () => {
495
+ const content = fs.readFileSync(enFile, 'utf8');
496
+ assert.ok(content.includes('update-template'), 'Missing update-template mention in existing-project.en.md');
497
+ });
498
+ });
499
+
500
+ // ── T-013: non-regression Sprint 2 ────────────────────────────────────────────
501
+
502
+ describe('T-013: non-regression Sprint 2', () => {
503
+ const supertest = require('supertest');
504
+ let request;
505
+
506
+ before(async () => {
507
+ const appPath = path.join(REPO_ROOT, 'dashboard', 'server-app.js');
508
+ assert.ok(fs.existsSync(appPath), 'server-app.js must exist');
509
+ const { app } = require(appPath);
510
+ request = supertest(app);
511
+ });
512
+
513
+ test('GET /api/stats still returns 200', async () => {
514
+ const res = await request.get('/api/stats');
515
+ assert.equal(res.status, 200);
516
+ });
517
+
518
+ test('all 10 manual scenario files exist', () => {
519
+ const slugs = ['backend', 'frontend', 'fullstack', 'mobile', 'existing-project'];
520
+ for (const slug of slugs) {
521
+ assert.ok(
522
+ fs.existsSync(path.join(MANUAL_DIR, `${slug}.pt.md`)),
523
+ `${slug}.pt.md does not exist`
524
+ );
525
+ assert.ok(
526
+ fs.existsSync(path.join(MANUAL_DIR, `${slug}.en.md`)),
527
+ `${slug}.en.md does not exist`
528
+ );
529
+ }
530
+ });
531
+
532
+ test('all 10 manual files are non-empty (> 1000 bytes)', () => {
533
+ const slugs = ['backend', 'frontend', 'fullstack', 'mobile', 'existing-project'];
534
+ for (const slug of slugs) {
535
+ for (const lang of ['pt', 'en']) {
536
+ const file = path.join(MANUAL_DIR, `${slug}.${lang}.md`);
537
+ const stat = fs.statSync(file);
538
+ assert.ok(stat.size > 1000, `${slug}.${lang}.md too small: ${stat.size} bytes`);
539
+ }
540
+ }
541
+ });
542
+
543
+ test('all 10 manual files contain exactly 6 ## headings', () => {
544
+ const slugs = ['backend', 'frontend', 'fullstack', 'mobile', 'existing-project'];
545
+ for (const slug of slugs) {
546
+ for (const lang of ['pt', 'en']) {
547
+ const file = path.join(MANUAL_DIR, `${slug}.${lang}.md`);
548
+ const content = fs.readFileSync(file, 'utf8');
549
+ const matches = content.match(/^## /gm) || [];
550
+ assert.equal(matches.length, 6, `Expected 6 ## headings in ${slug}.${lang}.md, got ${matches.length}`);
551
+ }
552
+ }
553
+ });
554
+
555
+ test('GET /api/docs/file for backend.pt.md returns 200', async () => {
556
+ const res = await request.get('/api/docs/file?path=dashboard/public/content/manual/backend.pt.md');
557
+ assert.equal(res.status, 200);
558
+ });
559
+
560
+ test('app.js accordion count >= 3', () => {
561
+ const js = fs.readFileSync(path.join(PUBLIC_DIR, 'app.js'), 'utf8');
562
+ const count = (js.match(/accordion/g) || []).length;
563
+ assert.ok(count >= 3, `Expected >= 3 occurrences of 'accordion', got ${count}`);
564
+ });
565
+
566
+ test('app.js Quick-start exception count >= 1', () => {
567
+ const js = fs.readFileSync(path.join(PUBLIC_DIR, 'app.js'), 'utf8');
568
+ const count = (js.match(/Quick-start|quickstart/gi) || []).length;
569
+ assert.ok(count >= 1, `Expected >= 1 Quick-start reference, got ${count}`);
570
+ });
571
+ });