@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,560 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Sprint 3 failing tests — written before implementation (TDD RED phase).
5
+ * Tests for T-014 docs panel wiring, T-015-T-019 docs content files, T-020 QA final.
6
+ * Run: node --test dashboard/test/sprint3.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 DOCS_DIR = path.join(PUBLIC_DIR, 'content', 'docs');
17
+
18
+ // ── T-014: Docs panel wiring in app.js ───────────────────────────────────────
19
+
20
+ describe('T-014: docs panel dynamic wiring in app.js', () => {
21
+ let js;
22
+ before(() => {
23
+ js = fs.readFileSync(path.join(PUBLIC_DIR, 'app.js'), 'utf8');
24
+ });
25
+
26
+ test('contains api/docs/skills call', () => {
27
+ assert.ok(js.includes('/api/docs/skills'), 'Missing /api/docs/skills fetch in app.js');
28
+ });
29
+
30
+ test('contains api/docs/agents call', () => {
31
+ assert.ok(js.includes('/api/docs/agents'), 'Missing /api/docs/agents fetch in app.js');
32
+ });
33
+
34
+ test('contains api/docs/adrs call', () => {
35
+ assert.ok(js.includes('/api/docs/adrs'), 'Missing /api/docs/adrs fetch in app.js');
36
+ });
37
+
38
+ test('contains api/docs/file call', () => {
39
+ assert.ok(js.includes('/api/docs/file'), 'Missing /api/docs/file fetch in app.js');
40
+ });
41
+
42
+ test('total api/docs/* references >= 4', () => {
43
+ const count = (js.match(/api\/docs\/skills|api\/docs\/agents|api\/docs\/adrs|api\/docs\/file/g) || []).length;
44
+ assert.ok(count >= 4, `Expected >= 4 api/docs/* references in app.js, got ${count}`);
45
+ });
46
+
47
+ test('contains sessionStorage usage', () => {
48
+ const count = (js.match(/sessionStorage/g) || []).length;
49
+ assert.ok(count >= 1, `Expected >= 1 sessionStorage usage in app.js, got ${count}`);
50
+ });
51
+
52
+ test('contains getCached or sessionStorage.getItem', () => {
53
+ assert.ok(
54
+ js.includes('getCached') || js.includes('sessionStorage.getItem'),
55
+ 'Missing cache retrieval (getCached or sessionStorage.getItem)'
56
+ );
57
+ });
58
+
59
+ test('contains setCache or sessionStorage.setItem', () => {
60
+ assert.ok(
61
+ js.includes('setCache') || js.includes('sessionStorage.setItem'),
62
+ 'Missing cache write (setCache or sessionStorage.setItem)'
63
+ );
64
+ });
65
+
66
+ test('contains kud_cache_skills key', () => {
67
+ assert.ok(js.includes('kud_cache_skills'), 'Missing kud_cache_skills session key');
68
+ });
69
+
70
+ test('contains kud_cache_agents key', () => {
71
+ assert.ok(js.includes('kud_cache_agents'), 'Missing kud_cache_agents session key');
72
+ });
73
+
74
+ test('contains kud_cache_adrs key', () => {
75
+ assert.ok(js.includes('kud_cache_adrs'), 'Missing kud_cache_adrs session key');
76
+ });
77
+
78
+ test('contains initDocsPanel function', () => {
79
+ assert.ok(js.includes('initDocsPanel'), 'Missing initDocsPanel function');
80
+ });
81
+
82
+ test('contains loadDynamicSection function', () => {
83
+ assert.ok(js.includes('loadDynamicSection'), 'Missing loadDynamicSection function');
84
+ });
85
+
86
+ test('inline markdown render via marked.parse or renderMarkdown', () => {
87
+ assert.ok(
88
+ js.includes('renderMarkdown') || js.includes('marked.parse'),
89
+ 'Missing inline markdown rendering'
90
+ );
91
+ });
92
+ });
93
+
94
+ // ── T-015: architecture-overview.{pt,en}.md ──────────────────────────────────
95
+
96
+ describe('T-015: docs content — architecture overview', () => {
97
+ const ptFile = path.join(DOCS_DIR, 'architecture-overview.pt.md');
98
+ const enFile = path.join(DOCS_DIR, 'architecture-overview.en.md');
99
+
100
+ test('architecture-overview.pt.md exists', () => {
101
+ assert.ok(fs.existsSync(ptFile), 'architecture-overview.pt.md does not exist');
102
+ });
103
+
104
+ test('architecture-overview.en.md exists', () => {
105
+ assert.ok(fs.existsSync(enFile), 'architecture-overview.en.md does not exist');
106
+ });
107
+
108
+ test('architecture-overview.pt.md has size > 800 bytes', () => {
109
+ const stat = fs.statSync(ptFile);
110
+ assert.ok(stat.size > 800, `architecture-overview.pt.md too small: ${stat.size} bytes`);
111
+ });
112
+
113
+ test('architecture-overview.en.md has size > 800 bytes', () => {
114
+ const stat = fs.statSync(enFile);
115
+ assert.ok(stat.size > 800, `architecture-overview.en.md too small: ${stat.size} bytes`);
116
+ });
117
+
118
+ test('architecture-overview.pt.md contains ASCII diagram or prompt flow reference', () => {
119
+ const content = fs.readFileSync(ptFile, 'utf8');
120
+ const count = (content.match(/ASCII|prompt →|skill match|entry point|entry-point/g) || []).length;
121
+ assert.ok(count >= 1, `Expected >= 1 match for ASCII/prompt/skill-match in architecture-overview.pt.md, got ${count}`);
122
+ });
123
+
124
+ test('architecture-overview.pt.md mentions 2-layer architecture (skill + agent)', () => {
125
+ const content = fs.readFileSync(ptFile, 'utf8');
126
+ assert.ok(
127
+ (content.toLowerCase().includes('skill') || content.toLowerCase().includes('skills')) &&
128
+ (content.toLowerCase().includes('agent') || content.toLowerCase().includes('agents')),
129
+ 'Missing skill/agent architecture mention in architecture-overview.pt.md'
130
+ );
131
+ });
132
+
133
+ test('architecture-overview.pt.md mentions Task tool dispatch', () => {
134
+ const content = fs.readFileSync(ptFile, 'utf8');
135
+ assert.ok(
136
+ content.includes('Task') || content.includes('task tool') || content.includes('dispatch'),
137
+ 'Missing Task tool dispatch mention in architecture-overview.pt.md'
138
+ );
139
+ });
140
+
141
+ test('architecture-overview.en.md contains ASCII diagram or prompt flow reference', () => {
142
+ const content = fs.readFileSync(enFile, 'utf8');
143
+ const count = (content.match(/ASCII|prompt →|skill match|entry point|entry-point/g) || []).length;
144
+ assert.ok(count >= 1, `Expected >= 1 match in architecture-overview.en.md, got ${count}`);
145
+ });
146
+ });
147
+
148
+ // ── T-016: autonomy-matrix.{pt,en}.md ────────────────────────────────────────
149
+
150
+ describe('T-016: docs content — autonomy matrix', () => {
151
+ const ptFile = path.join(DOCS_DIR, 'autonomy-matrix.pt.md');
152
+ const enFile = path.join(DOCS_DIR, 'autonomy-matrix.en.md');
153
+
154
+ test('autonomy-matrix.pt.md exists', () => {
155
+ assert.ok(fs.existsSync(ptFile), 'autonomy-matrix.pt.md does not exist');
156
+ });
157
+
158
+ test('autonomy-matrix.en.md exists', () => {
159
+ assert.ok(fs.existsSync(enFile), 'autonomy-matrix.en.md does not exist');
160
+ });
161
+
162
+ test('autonomy-matrix.pt.md has size > 800 bytes', () => {
163
+ const stat = fs.statSync(ptFile);
164
+ assert.ok(stat.size > 800, `autonomy-matrix.pt.md too small: ${stat.size} bytes`);
165
+ });
166
+
167
+ test('autonomy-matrix.en.md has size > 800 bytes', () => {
168
+ const stat = fs.statSync(enFile);
169
+ assert.ok(stat.size > 800, `autonomy-matrix.en.md too small: ${stat.size} bytes`);
170
+ });
171
+
172
+ test('autonomy-matrix.pt.md mentions product-owner >= 4 times', () => {
173
+ const content = fs.readFileSync(ptFile, 'utf8');
174
+ const count = (content.match(/product-owner|tech-lead/g) || []).length;
175
+ assert.ok(count >= 4, `Expected >= 4 product-owner/tech-lead matches in autonomy-matrix.pt.md, got ${count}`);
176
+ });
177
+
178
+ test('autonomy-matrix.pt.md contains decision table', () => {
179
+ const content = fs.readFileSync(ptFile, 'utf8');
180
+ assert.ok(content.includes('|'), 'Missing table (|) in autonomy-matrix.pt.md');
181
+ });
182
+
183
+ test('autonomy-matrix.pt.md mentions escalation rules', () => {
184
+ const content = fs.readFileSync(ptFile, 'utf8');
185
+ assert.ok(
186
+ content.includes('escala') || content.includes('escalate') || content.includes('escalar'),
187
+ 'Missing escalation rules in autonomy-matrix.pt.md'
188
+ );
189
+ });
190
+
191
+ test('autonomy-matrix.en.md mentions product-owner >= 4 times', () => {
192
+ const content = fs.readFileSync(enFile, 'utf8');
193
+ const count = (content.match(/product-owner|tech-lead/g) || []).length;
194
+ assert.ok(count >= 4, `Expected >= 4 product-owner/tech-lead matches in autonomy-matrix.en.md, got ${count}`);
195
+ });
196
+ });
197
+
198
+ // ── T-017: pipeline.{pt,en}.md ───────────────────────────────────────────────
199
+
200
+ describe('T-017: docs content — pipeline', () => {
201
+ const ptFile = path.join(DOCS_DIR, 'pipeline.pt.md');
202
+ const enFile = path.join(DOCS_DIR, 'pipeline.en.md');
203
+
204
+ test('pipeline.pt.md exists', () => {
205
+ assert.ok(fs.existsSync(ptFile), 'pipeline.pt.md does not exist');
206
+ });
207
+
208
+ test('pipeline.en.md exists', () => {
209
+ assert.ok(fs.existsSync(enFile), 'pipeline.en.md does not exist');
210
+ });
211
+
212
+ test('pipeline.pt.md has size > 800 bytes', () => {
213
+ const stat = fs.statSync(ptFile);
214
+ assert.ok(stat.size > 800, `pipeline.pt.md too small: ${stat.size} bytes`);
215
+ });
216
+
217
+ test('pipeline.en.md has size > 800 bytes', () => {
218
+ const stat = fs.statSync(enFile);
219
+ assert.ok(stat.size > 800, `pipeline.en.md too small: ${stat.size} bytes`);
220
+ });
221
+
222
+ test('pipeline.pt.md mentions grill-me, sprint-runner, gate-keeper, brain-keeper (>= 4 total)', () => {
223
+ const content = fs.readFileSync(ptFile, 'utf8');
224
+ const count = (content.match(/grill-me|sprint-runner|gate-keeper|brain-keeper/g) || []).length;
225
+ assert.ok(count >= 4, `Expected >= 4 pipeline keyword matches in pipeline.pt.md, got ${count}`);
226
+ });
227
+
228
+ test('pipeline.pt.md mentions artifacts (DISCOVERY, PRD, PLAN)', () => {
229
+ const content = fs.readFileSync(ptFile, 'utf8');
230
+ assert.ok(
231
+ content.includes('DISCOVERY') || content.includes('discovery'),
232
+ 'Missing DISCOVERY artifact mention in pipeline.pt.md'
233
+ );
234
+ assert.ok(
235
+ content.includes('PRD') || content.includes('prd'),
236
+ 'Missing PRD artifact mention in pipeline.pt.md'
237
+ );
238
+ assert.ok(
239
+ content.includes('PLAN') || content.includes('plan'),
240
+ 'Missing PLAN artifact mention in pipeline.pt.md'
241
+ );
242
+ });
243
+
244
+ test('pipeline.en.md mentions grill-me, sprint-runner, gate-keeper, brain-keeper (>= 4)', () => {
245
+ const content = fs.readFileSync(enFile, 'utf8');
246
+ const count = (content.match(/grill-me|sprint-runner|gate-keeper|brain-keeper/g) || []).length;
247
+ assert.ok(count >= 4, `Expected >= 4 pipeline keyword matches in pipeline.en.md, got ${count}`);
248
+ });
249
+ });
250
+
251
+ // ── T-018: quality-gate.{pt,en}.md ───────────────────────────────────────────
252
+
253
+ describe('T-018: docs content — quality gate', () => {
254
+ const ptFile = path.join(DOCS_DIR, 'quality-gate.pt.md');
255
+ const enFile = path.join(DOCS_DIR, 'quality-gate.en.md');
256
+
257
+ test('quality-gate.pt.md exists', () => {
258
+ assert.ok(fs.existsSync(ptFile), 'quality-gate.pt.md does not exist');
259
+ });
260
+
261
+ test('quality-gate.en.md exists', () => {
262
+ assert.ok(fs.existsSync(enFile), 'quality-gate.en.md does not exist');
263
+ });
264
+
265
+ test('quality-gate.pt.md has size > 800 bytes', () => {
266
+ const stat = fs.statSync(ptFile);
267
+ assert.ok(stat.size > 800, `quality-gate.pt.md too small: ${stat.size} bytes`);
268
+ });
269
+
270
+ test('quality-gate.en.md has size > 800 bytes', () => {
271
+ const stat = fs.statSync(enFile);
272
+ assert.ok(stat.size > 800, `quality-gate.en.md too small: ${stat.size} bytes`);
273
+ });
274
+
275
+ test('quality-gate.pt.md contains >= 5 threshold keyword matches', () => {
276
+ const content = fs.readFileSync(ptFile, 'utf8');
277
+ const count = (content.match(/85|70|80|0\.80|2500|300ms|JaCoCo|PIT|mutation/g) || []).length;
278
+ assert.ok(count >= 5, `Expected >= 5 threshold matches in quality-gate.pt.md, got ${count}`);
279
+ });
280
+
281
+ test('quality-gate.pt.md mentions JaCoCo', () => {
282
+ const content = fs.readFileSync(ptFile, 'utf8');
283
+ assert.ok(content.includes('JaCoCo'), 'Missing JaCoCo in quality-gate.pt.md');
284
+ });
285
+
286
+ test('quality-gate.pt.md mentions mutation or PIT', () => {
287
+ const content = fs.readFileSync(ptFile, 'utf8');
288
+ assert.ok(
289
+ content.includes('mutation') || content.includes('PIT') || content.includes('Pitest'),
290
+ 'Missing mutation/PIT in quality-gate.pt.md'
291
+ );
292
+ });
293
+
294
+ test('quality-gate.pt.md mentions Lighthouse threshold', () => {
295
+ const content = fs.readFileSync(ptFile, 'utf8');
296
+ assert.ok(content.includes('0.80') || content.includes('Lighthouse'), 'Missing Lighthouse threshold in quality-gate.pt.md');
297
+ });
298
+
299
+ test('quality-gate.pt.md mentions LCP 2500ms', () => {
300
+ const content = fs.readFileSync(ptFile, 'utf8');
301
+ assert.ok(content.includes('2500'), 'Missing 2500ms LCP threshold in quality-gate.pt.md');
302
+ });
303
+
304
+ test('quality-gate.pt.md mentions TBT 300ms', () => {
305
+ const content = fs.readFileSync(ptFile, 'utf8');
306
+ assert.ok(content.includes('300ms') || content.includes('300 ms'), 'Missing 300ms TBT threshold in quality-gate.pt.md');
307
+ });
308
+
309
+ test('quality-gate.en.md contains >= 5 threshold keyword matches', () => {
310
+ const content = fs.readFileSync(enFile, 'utf8');
311
+ const count = (content.match(/85|70|80|0\.80|2500|300ms|JaCoCo|PIT|mutation/g) || []).length;
312
+ assert.ok(count >= 5, `Expected >= 5 threshold matches in quality-gate.en.md, got ${count}`);
313
+ });
314
+ });
315
+
316
+ // ── T-019: stack-rules.{pt,en}.md ────────────────────────────────────────────
317
+
318
+ describe('T-019: docs content — stack rules', () => {
319
+ const ptFile = path.join(DOCS_DIR, 'stack-rules.pt.md');
320
+ const enFile = path.join(DOCS_DIR, 'stack-rules.en.md');
321
+
322
+ test('stack-rules.pt.md exists', () => {
323
+ assert.ok(fs.existsSync(ptFile), 'stack-rules.pt.md does not exist');
324
+ });
325
+
326
+ test('stack-rules.en.md exists', () => {
327
+ assert.ok(fs.existsSync(enFile), 'stack-rules.en.md does not exist');
328
+ });
329
+
330
+ test('stack-rules.pt.md has size > 800 bytes', () => {
331
+ const stat = fs.statSync(ptFile);
332
+ assert.ok(stat.size > 800, `stack-rules.pt.md too small: ${stat.size} bytes`);
333
+ });
334
+
335
+ test('stack-rules.en.md has size > 800 bytes', () => {
336
+ const stat = fs.statSync(enFile);
337
+ assert.ok(stat.size > 800, `stack-rules.en.md too small: ${stat.size} bytes`);
338
+ });
339
+
340
+ test('stack-rules.pt.md contains Java 25 >= 3 times (various mandatory versions)', () => {
341
+ const content = fs.readFileSync(ptFile, 'utf8');
342
+ const count = (content.match(/Java 25|Spring Boot 4|Angular 21|React Native 0\.84/g) || []).length;
343
+ assert.ok(count >= 3, `Expected >= 3 mandatory version matches in stack-rules.pt.md, got ${count}. Ensure inline references like "Java 25+", "Spring Boot 4.0.x+", "Angular 21+", "React Native 0.84+" appear.`);
344
+ });
345
+
346
+ test('stack-rules.pt.md mentions Java 25', () => {
347
+ const content = fs.readFileSync(ptFile, 'utf8');
348
+ assert.ok(content.includes('Java 25'), 'Missing "Java 25" in stack-rules.pt.md');
349
+ });
350
+
351
+ test('stack-rules.pt.md mentions Spring Boot 4', () => {
352
+ const content = fs.readFileSync(ptFile, 'utf8');
353
+ assert.ok(content.includes('Spring Boot 4'), 'Missing "Spring Boot 4" in stack-rules.pt.md');
354
+ });
355
+
356
+ test('stack-rules.pt.md mentions Angular 21', () => {
357
+ const content = fs.readFileSync(ptFile, 'utf8');
358
+ assert.ok(content.includes('Angular 21'), 'Missing "Angular 21" in stack-rules.pt.md');
359
+ });
360
+
361
+ test('stack-rules.pt.md mentions React Native 0.84', () => {
362
+ const content = fs.readFileSync(ptFile, 'utf8');
363
+ assert.ok(content.includes('React Native 0.84'), 'Missing "React Native 0.84" in stack-rules.pt.md');
364
+ });
365
+
366
+ test('stack-rules.en.md contains Java 25, Spring Boot 4, Angular 21, React Native 0.84 (>= 3 matches)', () => {
367
+ const content = fs.readFileSync(enFile, 'utf8');
368
+ const count = (content.match(/Java 25|Spring Boot 4|Angular 21|React Native 0\.84/g) || []).length;
369
+ assert.ok(count >= 3, `Expected >= 3 mandatory version matches in stack-rules.en.md, got ${count}`);
370
+ });
371
+
372
+ test('stack-rules.pt.md mentions record DTO and ProblemDetail', () => {
373
+ const content = fs.readFileSync(ptFile, 'utf8');
374
+ assert.ok(
375
+ content.includes('record') || content.includes('ProblemDetail'),
376
+ 'Missing record/ProblemDetail in stack-rules.pt.md'
377
+ );
378
+ });
379
+
380
+ test('stack-rules.pt.md mentions separate files rule', () => {
381
+ const content = fs.readFileSync(ptFile, 'utf8');
382
+ assert.ok(
383
+ content.includes('templateUrl') || content.includes('ARQUIVOS SEPARADOS') || content.includes('arquivos separados'),
384
+ 'Missing separate files rule in stack-rules.pt.md'
385
+ );
386
+ });
387
+ });
388
+
389
+ // ── T-020: QA final ───────────────────────────────────────────────────────────
390
+
391
+ describe('T-020: QA final — manifest paths, security, vendor fallback', () => {
392
+ test('all manifest paths resolve to existing files (PT + EN pairs)', () => {
393
+ const manifestPath = path.join(PUBLIC_DIR, 'content', 'manifest.json');
394
+ assert.ok(fs.existsSync(manifestPath), 'manifest.json does not exist');
395
+
396
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
397
+ assert.ok(manifest.manual, 'manifest missing .manual');
398
+ assert.ok(manifest.docs, 'manifest missing .docs');
399
+ assert.equal(manifest.manual.length, 5, 'manifest.manual must have 5 entries');
400
+ assert.equal(manifest.docs.length, 5, 'manifest.docs must have 5 entries');
401
+
402
+ const all = [...manifest.manual, ...manifest.docs];
403
+ for (const entry of all) {
404
+ const ptPath = path.join(REPO_ROOT, entry.path_template.replace('{lang}', 'pt'));
405
+ const enPath = path.join(REPO_ROOT, entry.path_template.replace('{lang}', 'en'));
406
+ assert.ok(fs.existsSync(ptPath), `PT file missing: ${ptPath}`);
407
+ assert.ok(fs.existsSync(enPath), `EN file missing: ${enPath}`);
408
+ }
409
+ });
410
+
411
+ test('vendor/marked.min.js fallback exists', () => {
412
+ const vendorPath = path.join(PUBLIC_DIR, 'vendor', 'marked.min.js');
413
+ assert.ok(fs.existsSync(vendorPath), 'vendor/marked.min.js does not exist');
414
+ const stat = fs.statSync(vendorPath);
415
+ assert.ok(stat.size > 10000, `vendor/marked.min.js too small: ${stat.size} bytes`);
416
+ });
417
+
418
+ test('all 10 docs files exist and have size > 800 bytes', () => {
419
+ const slugs = ['architecture-overview', 'autonomy-matrix', 'pipeline', 'quality-gate', 'stack-rules'];
420
+ for (const slug of slugs) {
421
+ for (const lang of ['pt', 'en']) {
422
+ const filePath = path.join(DOCS_DIR, `${slug}.${lang}.md`);
423
+ assert.ok(fs.existsSync(filePath), `${slug}.${lang}.md does not exist`);
424
+ const stat = fs.statSync(filePath);
425
+ assert.ok(stat.size > 800, `${slug}.${lang}.md too small: ${stat.size} bytes`);
426
+ }
427
+ }
428
+ });
429
+
430
+ test('path traversal check: server-app.js contains whitelist and 403 response', async () => {
431
+ // Static check: server-app.js must contain whitelist validation logic
432
+ const serverAppPath = path.join(REPO_ROOT, 'dashboard', 'server-app.js');
433
+ assert.ok(fs.existsSync(serverAppPath), 'server-app.js does not exist');
434
+ const serverAppJs = fs.readFileSync(serverAppPath, 'utf8');
435
+ assert.ok(
436
+ serverAppJs.includes('..') || serverAppJs.includes('normalize') || serverAppJs.includes('whitelist') || serverAppJs.includes('WHITELIST'),
437
+ 'server-app.js missing path traversal protection'
438
+ );
439
+ assert.ok(
440
+ serverAppJs.includes('403') || serverAppJs.includes('Forbidden'),
441
+ 'server-app.js missing 403 Forbidden response for blocked paths'
442
+ );
443
+ });
444
+
445
+ test('app.js has >= 4 api/docs/* references', () => {
446
+ const js = fs.readFileSync(path.join(PUBLIC_DIR, 'app.js'), 'utf8');
447
+ const count = (js.match(/api\/docs\/skills|api\/docs\/agents|api\/docs\/adrs|api\/docs\/file/g) || []).length;
448
+ assert.ok(count >= 4, `Expected >= 4 api/docs references in app.js, got ${count}`);
449
+ });
450
+
451
+ test('app.js has >= 1 sessionStorage reference', () => {
452
+ const js = fs.readFileSync(path.join(PUBLIC_DIR, 'app.js'), 'utf8');
453
+ const count = (js.match(/sessionStorage/g) || []).length;
454
+ assert.ok(count >= 1, `Expected >= 1 sessionStorage reference in app.js, got ${count}`);
455
+ });
456
+
457
+ test('index.html references vendor/marked.min.js (offline fallback)', () => {
458
+ const html = fs.readFileSync(path.join(PUBLIC_DIR, 'index.html'), 'utf8');
459
+ assert.ok(html.includes('marked.min.js'), 'index.html missing marked.min.js reference');
460
+ const markedCount = (html.match(/marked\.min\.js/g) || []).length;
461
+ assert.ok(markedCount >= 2, `Expected >= 2 marked.min.js references in index.html (CDN + vendor), got ${markedCount}`);
462
+ });
463
+ });
464
+
465
+ // ── T-020: HTTP endpoint smoke tests ─────────────────────────────────────────
466
+
467
+ describe('T-020: HTTP endpoint smoke tests', () => {
468
+ let request;
469
+
470
+ before(async () => {
471
+ const appPath = path.join(REPO_ROOT, 'dashboard', 'server-app.js');
472
+ assert.ok(fs.existsSync(appPath), 'server-app.js must exist');
473
+ const supertest = require('supertest');
474
+ const { app } = require(appPath);
475
+ request = supertest(app);
476
+ });
477
+
478
+ test('GET /api/docs/skills returns 200 with array', async () => {
479
+ const res = await request.get('/api/docs/skills');
480
+ assert.equal(res.status, 200, `Expected 200, got ${res.status}`);
481
+ assert.ok(Array.isArray(res.body), 'Expected array response from /api/docs/skills');
482
+ });
483
+
484
+ test('GET /api/docs/skills returns >= 10 entries', async () => {
485
+ const res = await request.get('/api/docs/skills');
486
+ assert.ok(res.body.length >= 10, `Expected >= 10 skills, got ${res.body.length}`);
487
+ });
488
+
489
+ test('GET /api/docs/skills entries have name and path fields', async () => {
490
+ const res = await request.get('/api/docs/skills');
491
+ const first = res.body[0];
492
+ assert.ok(first.name, 'Skills entry missing name field');
493
+ assert.ok(first.path, 'Skills entry missing path field');
494
+ });
495
+
496
+ test('GET /api/docs/agents returns 200 with array', async () => {
497
+ const res = await request.get('/api/docs/agents');
498
+ assert.equal(res.status, 200, `Expected 200, got ${res.status}`);
499
+ assert.ok(Array.isArray(res.body), 'Expected array response from /api/docs/agents');
500
+ });
501
+
502
+ test('GET /api/docs/agents returns >= 15 entries', async () => {
503
+ const res = await request.get('/api/docs/agents');
504
+ assert.ok(res.body.length >= 15, `Expected >= 15 agents, got ${res.body.length}`);
505
+ });
506
+
507
+ test('GET /api/docs/agents entries have model field', async () => {
508
+ const res = await request.get('/api/docs/agents');
509
+ const first = res.body[0];
510
+ assert.ok(first.model, 'Agents entry missing model field');
511
+ });
512
+
513
+ test('GET /api/docs/adrs returns 200 with array', async () => {
514
+ const res = await request.get('/api/docs/adrs');
515
+ assert.equal(res.status, 200, `Expected 200, got ${res.status}`);
516
+ assert.ok(Array.isArray(res.body), 'Expected array response from /api/docs/adrs');
517
+ });
518
+
519
+ test('GET /api/docs/adrs returns >= 10 entries', async () => {
520
+ const res = await request.get('/api/docs/adrs');
521
+ assert.ok(res.body.length >= 10, `Expected >= 10 ADRs, got ${res.body.length}`);
522
+ });
523
+
524
+ test('GET /api/docs/adrs entries are sorted ascending by number', async () => {
525
+ const res = await request.get('/api/docs/adrs');
526
+ const nums = res.body.map((x) => x.number);
527
+ assert.ok(nums[0] < nums[nums.length - 1], 'ADRs not sorted ascending by number');
528
+ });
529
+
530
+ test('GET /api/docs/adrs entries have status field', async () => {
531
+ const res = await request.get('/api/docs/adrs');
532
+ const first = res.body[0];
533
+ assert.ok(first.status !== undefined, 'ADR entry missing status field');
534
+ });
535
+
536
+ test('GET /api/docs/file with path traversal ../../package.json returns 403', async () => {
537
+ const res = await request.get('/api/docs/file?path=../../package.json');
538
+ assert.equal(res.status, 403, `Expected 403 for path traversal, got ${res.status}`);
539
+ });
540
+
541
+ test('GET /api/docs/file with absolute path /etc/passwd returns 403', async () => {
542
+ const res = await request.get('/api/docs/file?path=/etc/passwd');
543
+ assert.equal(res.status, 403, `Expected 403 for absolute path, got ${res.status}`);
544
+ });
545
+
546
+ test('GET /api/docs/file with out-of-whitelist path bin/cli.js returns 403', async () => {
547
+ const res = await request.get('/api/docs/file?path=bin/cli.js');
548
+ assert.equal(res.status, 403, `Expected 403 for out-of-whitelist path, got ${res.status}`);
549
+ });
550
+
551
+ test('GET /api/docs/file with valid whitelist path returns 200', async () => {
552
+ const res = await request.get('/api/docs/file?path=dashboard/public/content/manifest.json');
553
+ assert.equal(res.status, 200, `Expected 200 for valid path, got ${res.status}`);
554
+ });
555
+
556
+ test('GET /api/stats still returns 200', async () => {
557
+ const res = await request.get('/api/stats');
558
+ assert.equal(res.status, 200, `Expected 200 for /api/stats, got ${res.status}`);
559
+ });
560
+ });
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@eltonssouza/development-utility-kit",
3
+ "version": "1.0.0",
4
+ "description": "npx installer for the development-utility-kit harness",
5
+ "bin": {
6
+ "duk": "bin/cli.js"
7
+ },
8
+ "files": [
9
+ "bin",
10
+ "bin/lib",
11
+ "dashboard",
12
+ "scripts/hooks",
13
+ "scripts/latest-versions.json",
14
+ ".claude",
15
+ "CLAUDE.md"
16
+ ],
17
+ "engines": {
18
+ "node": ">=18"
19
+ },
20
+ "license": "MIT",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/eltonssouza/development-utility-kit.git"
24
+ },
25
+ "keywords": [
26
+ "harness",
27
+ "installer",
28
+ "devtools"
29
+ ],
30
+ "publishConfig": {
31
+ "access": "public"
32
+ }
33
+ }
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env bash
2
+ # SubagentStop hook — forwards stdin JSON to the telemetry writer.
3
+ # Fails silently: if node or the writer is missing the hook exits 0.
4
+
5
+ set -uo pipefail
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ WRITER="${SCRIPT_DIR}/telemetry-writer.js"
9
+
10
+ if [ ! -f "$WRITER" ]; then
11
+ exit 0
12
+ fi
13
+
14
+ node "$WRITER" || exit 0