@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,215 @@
1
+ /**
2
+ * T-006: Verify hooks/on-prompt-submit.js
3
+ *
4
+ * Tests are written BEFORE implementation (TDD RED phase).
5
+ * Verifies:
6
+ * - hook file exists
7
+ * - HONCHO_DRY_RUN=1 + stdin "lembra que prefiro records" → exit 0 + stdout contains "saved": true
8
+ * - HONCHO_DRY_RUN=1 + generic stdin → exit 0 + stdout contains peerSearchExecuted:true + sessionListExecuted:true
9
+ * - HONCHO_BASE_URL=http://localhost:19999 HONCHO_TIMEOUT_MS=200 → exits < 1s, exit 0, stderr contains HONCHO_WARN
10
+ * - HONCHO_DRY_RUN=1 + enabled=false in config → exits 0 with no-op
11
+ */
12
+
13
+ import { existsSync } from "fs";
14
+ import { join } from "path";
15
+ import { spawnSync } from "child_process";
16
+
17
+ const SKILL_DIR = new URL("../", import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1");
18
+ const HOOK_FILE = join(SKILL_DIR, "hooks", "on-prompt-submit.js");
19
+ const BUN_EXE = "C:\\Users\\elton\\.bun\\bin\\bun.exe";
20
+
21
+ function runHook(stdin, env = {}) {
22
+ const result = spawnSync(
23
+ BUN_EXE,
24
+ ["run", HOOK_FILE],
25
+ {
26
+ input: stdin,
27
+ encoding: "utf8",
28
+ timeout: 5000,
29
+ env: { ...process.env, ...env },
30
+ }
31
+ );
32
+ return {
33
+ exitCode: result.status,
34
+ stdout: result.stdout ?? "",
35
+ stderr: result.stderr ?? "",
36
+ timedOut: result.error?.code === "ETIMEDOUT",
37
+ };
38
+ }
39
+
40
+ // Test 1: hooks/on-prompt-submit.js must exist
41
+ const test1 = () => {
42
+ if (!existsSync(HOOK_FILE)) {
43
+ throw new Error(`hooks/on-prompt-submit.js does not exist at: ${HOOK_FILE}`);
44
+ }
45
+ console.log("PASS: hooks/on-prompt-submit.js exists");
46
+ };
47
+
48
+ // Test 2: HONCHO_DRY_RUN=1 + "lembra que prefiro records" → exit 0
49
+ const test2 = () => {
50
+ const { exitCode, stdout, stderr } = runHook(
51
+ "lembra que prefiro records imutáveis em DTOs Java",
52
+ { HONCHO_DRY_RUN: "1" }
53
+ );
54
+ if (exitCode !== 0) {
55
+ throw new Error(`Expected exit 0, got ${exitCode}. stderr: ${stderr}`);
56
+ }
57
+ console.log("PASS: HONCHO_DRY_RUN=1 with 'lembra que' → exit 0");
58
+ };
59
+
60
+ // Test 3: HONCHO_DRY_RUN=1 + "lembra que" trigger → stdout contains "saved": true
61
+ const test3 = () => {
62
+ const { exitCode, stdout, stderr } = runHook(
63
+ "lembra que prefiro records imutáveis em DTOs Java",
64
+ { HONCHO_DRY_RUN: "1" }
65
+ );
66
+ if (!stdout.includes('"saved"') && !stdout.includes('"saved":')) {
67
+ throw new Error(`stdout does not contain "saved". stdout: ${stdout}, stderr: ${stderr}`);
68
+ }
69
+ // Accept "saved": true or "saved":true
70
+ const hasSaved = stdout.includes('"saved": true') || stdout.includes('"saved":true');
71
+ if (!hasSaved) {
72
+ throw new Error(`stdout does not contain "saved": true. stdout: ${stdout}`);
73
+ }
74
+ console.log('PASS: HONCHO_DRY_RUN=1 with explicit trigger → stdout contains "saved": true');
75
+ };
76
+
77
+ // Test 4: HONCHO_DRY_RUN=1 + generic stdin → stdout contains peerSearchExecuted: true
78
+ const test4 = () => {
79
+ const { exitCode, stdout, stderr } = runHook(
80
+ "help me refactor this controller",
81
+ { HONCHO_DRY_RUN: "1" }
82
+ );
83
+ if (exitCode !== 0) {
84
+ throw new Error(`Expected exit 0, got ${exitCode}. stderr: ${stderr}`);
85
+ }
86
+ const hasPeer = stdout.includes('"peerSearchExecuted": true') || stdout.includes('"peerSearchExecuted":true');
87
+ if (!hasPeer) {
88
+ throw new Error(`stdout does not contain peerSearchExecuted: true. stdout: ${stdout}`);
89
+ }
90
+ console.log("PASS: generic stdin → stdout contains peerSearchExecuted: true");
91
+ };
92
+
93
+ // Test 5: HONCHO_DRY_RUN=1 + generic stdin → stdout contains sessionListExecuted: true
94
+ const test5 = () => {
95
+ const { exitCode, stdout, stderr } = runHook(
96
+ "help me refactor this controller",
97
+ { HONCHO_DRY_RUN: "1" }
98
+ );
99
+ if (exitCode !== 0) {
100
+ throw new Error(`Expected exit 0, got ${exitCode}. stderr: ${stderr}`);
101
+ }
102
+ const hasSession = stdout.includes('"sessionListExecuted": true') || stdout.includes('"sessionListExecuted":true');
103
+ if (!hasSession) {
104
+ throw new Error(`stdout does not contain sessionListExecuted: true. stdout: ${stdout}`);
105
+ }
106
+ console.log("PASS: generic stdin → stdout contains sessionListExecuted: true");
107
+ };
108
+
109
+ // Test 6: HONCHO_BASE_URL=http://localhost:19999 HONCHO_TIMEOUT_MS=200 → completes < 1s
110
+ const test6 = () => {
111
+ const start = Date.now();
112
+ const { exitCode, stdout, stderr } = runHook(
113
+ "what is the current sprint?",
114
+ { HONCHO_BASE_URL: "http://localhost:19999", HONCHO_TIMEOUT_MS: "200" }
115
+ );
116
+ const elapsed = Date.now() - start;
117
+ if (elapsed >= 1000) {
118
+ throw new Error(`Expected completion < 1000ms, took ${elapsed}ms`);
119
+ }
120
+ if (exitCode !== 0) {
121
+ throw new Error(`Expected exit 0, got ${exitCode}. stderr: ${stderr}`);
122
+ }
123
+ console.log(`PASS: unreachable host completes in ${elapsed}ms < 1000ms with exit 0`);
124
+ };
125
+
126
+ // Test 7: HONCHO_BASE_URL=http://localhost:19999 HONCHO_TIMEOUT_MS=200 → stderr contains HONCHO_WARN
127
+ const test7 = () => {
128
+ const { exitCode, stdout, stderr } = runHook(
129
+ "what is the current sprint?",
130
+ { HONCHO_BASE_URL: "http://localhost:19999", HONCHO_TIMEOUT_MS: "200" }
131
+ );
132
+ if (!stderr.includes("HONCHO_WARN")) {
133
+ throw new Error(`stderr does not contain HONCHO_WARN. stderr: ${stderr}`);
134
+ }
135
+ console.log("PASS: unreachable host → stderr contains HONCHO_WARN");
136
+ };
137
+
138
+ // Test 8: "remember that" trigger also triggers save
139
+ const test8 = () => {
140
+ const { exitCode, stdout, stderr } = runHook(
141
+ "remember that I always use TypeScript strict mode",
142
+ { HONCHO_DRY_RUN: "1" }
143
+ );
144
+ if (exitCode !== 0) {
145
+ throw new Error(`Expected exit 0, got ${exitCode}. stderr: ${stderr}`);
146
+ }
147
+ const hasSaved = stdout.includes('"saved": true') || stdout.includes('"saved":true');
148
+ if (!hasSaved) {
149
+ throw new Error(`"remember that" trigger did not produce saved:true. stdout: ${stdout}`);
150
+ }
151
+ console.log('PASS: "remember that" trigger → saved: true');
152
+ };
153
+
154
+ // Test 9: "memoriza" trigger also triggers save
155
+ const test9 = () => {
156
+ const { exitCode, stdout, stderr } = runHook(
157
+ "memoriza que uso sempre Conventional Commits",
158
+ { HONCHO_DRY_RUN: "1" }
159
+ );
160
+ if (exitCode !== 0) {
161
+ throw new Error(`Expected exit 0, got ${exitCode}. stderr: ${stderr}`);
162
+ }
163
+ const hasSaved = stdout.includes('"saved": true') || stdout.includes('"saved":true');
164
+ if (!hasSaved) {
165
+ throw new Error(`"memoriza" trigger did not produce saved:true. stdout: ${stdout}`);
166
+ }
167
+ console.log('PASS: "memoriza" trigger → saved: true');
168
+ };
169
+
170
+ // Test 10: "never " trigger also triggers save
171
+ const test10 = () => {
172
+ const { exitCode, stdout, stderr } = runHook(
173
+ "never use var in TypeScript",
174
+ { HONCHO_DRY_RUN: "1" }
175
+ );
176
+ if (exitCode !== 0) {
177
+ throw new Error(`Expected exit 0, got ${exitCode}. stderr: ${stderr}`);
178
+ }
179
+ const hasSaved = stdout.includes('"saved": true') || stdout.includes('"saved":true');
180
+ if (!hasSaved) {
181
+ throw new Error(`"never " trigger did not produce saved:true. stdout: ${stdout}`);
182
+ }
183
+ console.log('PASS: "never " trigger → saved: true');
184
+ };
185
+
186
+ // Test 11: "always " trigger also triggers save
187
+ const test11 = () => {
188
+ const { exitCode, stdout, stderr } = runHook(
189
+ "always write tests before implementation",
190
+ { HONCHO_DRY_RUN: "1" }
191
+ );
192
+ if (exitCode !== 0) {
193
+ throw new Error(`Expected exit 0, got ${exitCode}. stderr: ${stderr}`);
194
+ }
195
+ const hasSaved = stdout.includes('"saved": true') || stdout.includes('"saved":true');
196
+ if (!hasSaved) {
197
+ throw new Error(`"always " trigger did not produce saved:true. stdout: ${stdout}`);
198
+ }
199
+ console.log('PASS: "always " trigger → saved: true');
200
+ };
201
+
202
+ // Run all tests
203
+ let failed = 0;
204
+ const tests = [test1, test2, test3, test4, test5, test6, test7, test8, test9, test10, test11];
205
+ for (const t of tests) {
206
+ try {
207
+ await t();
208
+ } catch (e) {
209
+ console.error(`FAIL: ${e.message}`);
210
+ failed++;
211
+ }
212
+ }
213
+
214
+ console.log(`\nT-006 Results: ${tests.length - failed}/${tests.length} passed`);
215
+ process.exit(failed > 0 ? 1 : 0);
@@ -0,0 +1,165 @@
1
+ /**
2
+ * T-007: Verify hooks/on-stop.js
3
+ *
4
+ * Tests are written BEFORE implementation (TDD RED phase).
5
+ * Verifies:
6
+ * - hook file exists
7
+ * - HONCHO_DRY_RUN=1 → exit 0 + stdout contains "type": "project-context"
8
+ * - HONCHO_INFERENCE_ENABLED=true HONCHO_DRY_RUN=1 → stderr contains "HONCHO_INFO: inference deferred"
9
+ * - hook completes quickly (< 2s in dry-run mode)
10
+ * - exit code is always 0 (never blocks)
11
+ */
12
+
13
+ import { existsSync } from "fs";
14
+ import { join } from "path";
15
+ import { spawnSync } from "child_process";
16
+
17
+ const SKILL_DIR = new URL("../", import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1");
18
+ const HOOK_FILE = join(SKILL_DIR, "hooks", "on-stop.js");
19
+ const BUN_EXE = "C:\\Users\\elton\\.bun\\bin\\bun.exe";
20
+
21
+ function runHook(env = {}) {
22
+ const result = spawnSync(
23
+ BUN_EXE,
24
+ ["run", HOOK_FILE],
25
+ {
26
+ encoding: "utf8",
27
+ timeout: 10000,
28
+ env: { ...process.env, ...env },
29
+ }
30
+ );
31
+ return {
32
+ exitCode: result.status,
33
+ stdout: result.stdout ?? "",
34
+ stderr: result.stderr ?? "",
35
+ timedOut: result.error?.code === "ETIMEDOUT",
36
+ };
37
+ }
38
+
39
+ // Test 1: hooks/on-stop.js must exist
40
+ const test1 = () => {
41
+ if (!existsSync(HOOK_FILE)) {
42
+ throw new Error(`hooks/on-stop.js does not exist at: ${HOOK_FILE}`);
43
+ }
44
+ console.log("PASS: hooks/on-stop.js exists");
45
+ };
46
+
47
+ // Test 2: HONCHO_DRY_RUN=1 → exit 0
48
+ const test2 = () => {
49
+ const { exitCode, stderr } = runHook({ HONCHO_DRY_RUN: "1" });
50
+ if (exitCode !== 0) {
51
+ throw new Error(`Expected exit 0, got ${exitCode}. stderr: ${stderr}`);
52
+ }
53
+ console.log("PASS: HONCHO_DRY_RUN=1 → exit 0");
54
+ };
55
+
56
+ // Test 3: HONCHO_DRY_RUN=1 → stdout contains "type": "project-context"
57
+ const test3 = () => {
58
+ const { exitCode, stdout, stderr } = runHook({ HONCHO_DRY_RUN: "1" });
59
+ const hasType =
60
+ stdout.includes('"type": "project-context"') ||
61
+ stdout.includes('"type":"project-context"');
62
+ if (!hasType) {
63
+ throw new Error(`stdout does not contain "type": "project-context". stdout: ${stdout}, stderr: ${stderr}`);
64
+ }
65
+ console.log('PASS: HONCHO_DRY_RUN=1 → stdout contains "type": "project-context"');
66
+ };
67
+
68
+ // Test 4: HONCHO_INFERENCE_ENABLED=true HONCHO_DRY_RUN=1 → stderr contains "HONCHO_INFO: inference deferred"
69
+ const test4 = () => {
70
+ const { exitCode, stdout, stderr } = runHook({
71
+ HONCHO_DRY_RUN: "1",
72
+ HONCHO_INFERENCE_ENABLED: "true",
73
+ });
74
+ if (!stderr.includes("HONCHO_INFO: inference deferred")) {
75
+ throw new Error(`stderr does not contain "HONCHO_INFO: inference deferred". stderr: ${stderr}`);
76
+ }
77
+ console.log("PASS: HONCHO_INFERENCE_ENABLED=true → stderr contains HONCHO_INFO: inference deferred");
78
+ };
79
+
80
+ // Test 5: HONCHO_INFERENCE_ENABLED=true HONCHO_DRY_RUN=1 → still exits 0
81
+ const test5 = () => {
82
+ const { exitCode, stderr } = runHook({
83
+ HONCHO_DRY_RUN: "1",
84
+ HONCHO_INFERENCE_ENABLED: "true",
85
+ });
86
+ if (exitCode !== 0) {
87
+ throw new Error(`Expected exit 0 with inference enabled, got ${exitCode}. stderr: ${stderr}`);
88
+ }
89
+ console.log("PASS: HONCHO_INFERENCE_ENABLED=true still exits 0");
90
+ };
91
+
92
+ // Test 6: dry-run completes in < 2s
93
+ const test6 = () => {
94
+ const start = Date.now();
95
+ const { exitCode, stderr } = runHook({ HONCHO_DRY_RUN: "1" });
96
+ const elapsed = Date.now() - start;
97
+ if (elapsed >= 2000) {
98
+ throw new Error(`Expected completion < 2000ms in dry-run, took ${elapsed}ms`);
99
+ }
100
+ console.log(`PASS: dry-run completes in ${elapsed}ms < 2000ms`);
101
+ };
102
+
103
+ // Test 7: stdout is valid JSON in dry-run
104
+ const test7 = () => {
105
+ const { stdout, stderr } = runHook({ HONCHO_DRY_RUN: "1" });
106
+ // Find JSON content in stdout (may be one JSON line among others)
107
+ const lines = stdout.trim().split("\n").filter((l) => l.trim().startsWith("{"));
108
+ if (lines.length === 0) {
109
+ throw new Error(`No JSON line found in stdout. stdout: ${stdout}`);
110
+ }
111
+ let parsed;
112
+ try {
113
+ parsed = JSON.parse(lines[0]);
114
+ } catch (e) {
115
+ throw new Error(`stdout JSON is not valid: ${lines[0]}. Error: ${e.message}`);
116
+ }
117
+ if (!parsed.type) {
118
+ throw new Error(`Parsed JSON missing 'type' field. got: ${JSON.stringify(parsed)}`);
119
+ }
120
+ console.log(`PASS: stdout contains valid JSON with type field: ${parsed.type}`);
121
+ };
122
+
123
+ // Test 8: unreachable host + no dry-run → still exits 0 (non-blocking)
124
+ const test8 = () => {
125
+ const { exitCode, stderr } = runHook({
126
+ HONCHO_BASE_URL: "http://localhost:19999",
127
+ HONCHO_TIMEOUT_MS: "200",
128
+ });
129
+ if (exitCode !== 0) {
130
+ throw new Error(`Expected exit 0 on unreachable host, got ${exitCode}. stderr: ${stderr}`);
131
+ }
132
+ console.log("PASS: unreachable host → still exits 0 (non-blocking)");
133
+ };
134
+
135
+ // Test 9: HONCHO_INFERENCE_ENABLED=true stdout still contains "project-context"
136
+ const test9 = () => {
137
+ const { stdout, stderr } = runHook({
138
+ HONCHO_DRY_RUN: "1",
139
+ HONCHO_INFERENCE_ENABLED: "true",
140
+ });
141
+ const hasType =
142
+ stdout.includes('"type": "project-context"') ||
143
+ stdout.includes('"type":"project-context"');
144
+ if (!hasType) {
145
+ throw new Error(
146
+ `With inferenceEnabled, stdout still must contain project-context. stdout: ${stdout}`
147
+ );
148
+ }
149
+ console.log("PASS: HONCHO_INFERENCE_ENABLED=true → stdout still contains project-context");
150
+ };
151
+
152
+ // Run all tests
153
+ let failed = 0;
154
+ const tests = [test1, test2, test3, test4, test5, test6, test7, test8, test9];
155
+ for (const t of tests) {
156
+ try {
157
+ await t();
158
+ } catch (e) {
159
+ console.error(`FAIL: ${e.message}`);
160
+ failed++;
161
+ }
162
+ }
163
+
164
+ console.log(`\nT-007 Results: ${tests.length - failed}/${tests.length} passed`);
165
+ process.exit(failed > 0 ? 1 : 0);
@@ -0,0 +1,214 @@
1
+ /**
2
+ * T-008: Verify scripts/cli.js — subcommands save, search, list, forget, status
3
+ *
4
+ * Tests written BEFORE implementation (TDD RED phase).
5
+ *
6
+ * DoD assertions:
7
+ * - With HONCHO_DRY_RUN=1:
8
+ * - `status` → exit 0, stdout contains "connection:", "memories:", "enabled:", "inferenceEnabled:"
9
+ * - `save "test"` → exit 0
10
+ * - `list` → exit 0
11
+ * - `search "test"` → exit 0
12
+ * - `forget someId` → exit 0, stdout contains "method": "PUT"
13
+ * - `forget` (no ID)→ exit 1
14
+ */
15
+
16
+ import { existsSync } from "fs";
17
+ import { join } from "path";
18
+ import { spawnSync } from "child_process";
19
+
20
+ const SKILL_DIR = new URL("../", import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1");
21
+ const CLI_FILE = join(SKILL_DIR, "scripts", "cli.js");
22
+ const BUN_EXE = "C:\\Users\\elton\\.bun\\bin\\bun.exe";
23
+
24
+ function runCli(args, env = {}) {
25
+ const result = spawnSync(
26
+ BUN_EXE,
27
+ ["run", CLI_FILE, ...args],
28
+ {
29
+ encoding: "utf8",
30
+ timeout: 10000,
31
+ env: { ...process.env, HONCHO_DRY_RUN: "1", ...env },
32
+ }
33
+ );
34
+ return {
35
+ exitCode: result.status,
36
+ stdout: result.stdout ?? "",
37
+ stderr: result.stderr ?? "",
38
+ timedOut: result.error?.code === "ETIMEDOUT",
39
+ };
40
+ }
41
+
42
+ // Test 1: scripts/cli.js must exist
43
+ const test1 = () => {
44
+ if (!existsSync(CLI_FILE)) {
45
+ throw new Error(`scripts/cli.js does not exist at: ${CLI_FILE}`);
46
+ }
47
+ console.log("PASS: scripts/cli.js exists");
48
+ };
49
+
50
+ // Test 2: `status` → exit 0
51
+ const test2 = () => {
52
+ const { exitCode, stdout, stderr } = runCli(["status"]);
53
+ if (exitCode !== 0) {
54
+ throw new Error(`status: expected exit 0, got ${exitCode}. stderr: ${stderr}`);
55
+ }
56
+ console.log("PASS: `status` → exit 0");
57
+ };
58
+
59
+ // Test 3: `status` stdout contains "connection:"
60
+ const test3 = () => {
61
+ const { exitCode, stdout, stderr } = runCli(["status"]);
62
+ if (!stdout.includes("connection:")) {
63
+ throw new Error(`status: stdout missing "connection:". stdout: ${stdout}`);
64
+ }
65
+ console.log("PASS: `status` stdout contains \"connection:\"");
66
+ };
67
+
68
+ // Test 4: `status` stdout contains "memories:"
69
+ const test4 = () => {
70
+ const { stdout } = runCli(["status"]);
71
+ if (!stdout.includes("memories:")) {
72
+ throw new Error(`status: stdout missing "memories:". stdout: ${stdout}`);
73
+ }
74
+ console.log("PASS: `status` stdout contains \"memories:\"");
75
+ };
76
+
77
+ // Test 5: `status` stdout contains "enabled:"
78
+ const test5 = () => {
79
+ const { stdout } = runCli(["status"]);
80
+ if (!stdout.includes("enabled:")) {
81
+ throw new Error(`status: stdout missing "enabled:". stdout: ${stdout}`);
82
+ }
83
+ console.log("PASS: `status` stdout contains \"enabled:\"");
84
+ };
85
+
86
+ // Test 6: `status` stdout contains "inferenceEnabled:"
87
+ const test6 = () => {
88
+ const { stdout } = runCli(["status"]);
89
+ if (!stdout.includes("inferenceEnabled:")) {
90
+ throw new Error(`status: stdout missing "inferenceEnabled:". stdout: ${stdout}`);
91
+ }
92
+ console.log("PASS: `status` stdout contains \"inferenceEnabled:\"");
93
+ };
94
+
95
+ // Test 7: `save "test"` → exit 0
96
+ const test7 = () => {
97
+ const { exitCode, stderr } = runCli(["save", "test"]);
98
+ if (exitCode !== 0) {
99
+ throw new Error(`save: expected exit 0, got ${exitCode}. stderr: ${stderr}`);
100
+ }
101
+ console.log("PASS: `save \"test\"` → exit 0");
102
+ };
103
+
104
+ // Test 8: `list` → exit 0
105
+ const test8 = () => {
106
+ const { exitCode, stderr } = runCli(["list"]);
107
+ if (exitCode !== 0) {
108
+ throw new Error(`list: expected exit 0, got ${exitCode}. stderr: ${stderr}`);
109
+ }
110
+ console.log("PASS: `list` → exit 0");
111
+ };
112
+
113
+ // Test 9: `search "test"` → exit 0
114
+ const test9 = () => {
115
+ const { exitCode, stderr } = runCli(["search", "test"]);
116
+ if (exitCode !== 0) {
117
+ throw new Error(`search: expected exit 0, got ${exitCode}. stderr: ${stderr}`);
118
+ }
119
+ console.log("PASS: `search \"test\"` → exit 0");
120
+ };
121
+
122
+ // Test 10: `forget someId` → exit 0
123
+ const test10 = () => {
124
+ const { exitCode, stderr } = runCli(["forget", "someId"]);
125
+ if (exitCode !== 0) {
126
+ throw new Error(`forget someId: expected exit 0, got ${exitCode}. stderr: ${stderr}`);
127
+ }
128
+ console.log("PASS: `forget someId` → exit 0");
129
+ };
130
+
131
+ // Test 11: `forget someId` stdout contains "method": "PUT"
132
+ const test11 = () => {
133
+ const { stdout, stderr } = runCli(["forget", "someId"]);
134
+ const hasPut =
135
+ stdout.includes('"method": "PUT"') ||
136
+ stdout.includes('"method":"PUT"');
137
+ if (!hasPut) {
138
+ throw new Error(`forget someId: stdout missing "method": "PUT". stdout: ${stdout}`);
139
+ }
140
+ console.log("PASS: `forget someId` stdout contains \"method\": \"PUT\"");
141
+ };
142
+
143
+ // Test 12: `forget` (no ID) → exit 1
144
+ const test12 = () => {
145
+ const { exitCode, stdout, stderr } = runCli(["forget"]);
146
+ if (exitCode !== 1) {
147
+ throw new Error(`forget (no ID): expected exit 1, got ${exitCode}. stdout: ${stdout}, stderr: ${stderr}`);
148
+ }
149
+ console.log("PASS: `forget` (no ID) → exit 1");
150
+ };
151
+
152
+ // Test 13: `forget` (no ID) outputs an error message
153
+ const test13 = () => {
154
+ const { exitCode, stdout, stderr } = runCli(["forget"]);
155
+ const combinedOutput = stdout + stderr;
156
+ if (!combinedOutput.includes("error") && !combinedOutput.includes("Error") &&
157
+ !combinedOutput.includes("required") && !combinedOutput.includes("missing") &&
158
+ !combinedOutput.includes("Usage") && !combinedOutput.includes("ID")) {
159
+ throw new Error(`forget (no ID): expected an error/usage message. stdout: ${stdout}, stderr: ${stderr}`);
160
+ }
161
+ console.log("PASS: `forget` (no ID) outputs an error/usage message");
162
+ };
163
+
164
+ // Test 14: unknown subcommand → non-zero exit or helpful message
165
+ const test14 = () => {
166
+ const { exitCode, stdout, stderr } = runCli(["unknown-subcommand"]);
167
+ // Should not succeed silently
168
+ const combinedOutput = stdout + stderr;
169
+ if (exitCode === 0 && combinedOutput.trim() === "") {
170
+ throw new Error("unknown subcommand: expected non-zero exit or output. Got silent exit 0");
171
+ }
172
+ console.log(`PASS: unknown subcommand → handled (exit ${exitCode})`);
173
+ };
174
+
175
+ // Test 15: `status` completes in < 3s (dry-run should be fast)
176
+ const test15 = () => {
177
+ const start = Date.now();
178
+ runCli(["status"]);
179
+ const elapsed = Date.now() - start;
180
+ if (elapsed >= 3000) {
181
+ throw new Error(`status: expected completion < 3000ms, took ${elapsed}ms`);
182
+ }
183
+ console.log(`PASS: status completes in ${elapsed}ms < 3000ms`);
184
+ };
185
+
186
+ // Test 16: `save` without text argument → handles gracefully (exit 0 or 1, but no crash)
187
+ const test16 = () => {
188
+ const { exitCode, timedOut } = runCli(["save"]);
189
+ if (timedOut) {
190
+ throw new Error("save (no text): command timed out");
191
+ }
192
+ // Accept either exit code — important it does not hang
193
+ console.log(`PASS: save (no text) completes without hanging (exit ${exitCode})`);
194
+ };
195
+
196
+ // Run all tests
197
+ let failed = 0;
198
+ const tests = [
199
+ test1, test2, test3, test4, test5, test6,
200
+ test7, test8, test9, test10, test11, test12,
201
+ test13, test14, test15, test16,
202
+ ];
203
+
204
+ for (const t of tests) {
205
+ try {
206
+ await t();
207
+ } catch (e) {
208
+ console.error(`FAIL: ${e.message}`);
209
+ failed++;
210
+ }
211
+ }
212
+
213
+ console.log(`\nT-008 Results: ${tests.length - failed}/${tests.length} passed`);
214
+ process.exit(failed > 0 ? 1 : 0);