@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,162 @@
1
+ /**
2
+ * T-003: Verify honcho-client.js module
3
+ *
4
+ * Failing tests to be written BEFORE implementation.
5
+ * Verifies:
6
+ * - lib/honcho-client.js exists and is importable
7
+ * - exports all 6 required functions
8
+ * - all functions return null on error (never throw)
9
+ * - functions work with timeout
10
+ * - degradation graceful on unreachable host
11
+ */
12
+
13
+ import { existsSync } from "fs";
14
+ import { join } from "path";
15
+
16
+ const SKILL_DIR = new URL("../", import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1");
17
+ const CLIENT_FILE = join(SKILL_DIR, "lib", "honcho-client.js");
18
+
19
+ // Test 1: lib/honcho-client.js must exist
20
+ const test1 = () => {
21
+ if (!existsSync(CLIENT_FILE)) {
22
+ throw new Error(`lib/honcho-client.js does not exist at: ${CLIENT_FILE}`);
23
+ }
24
+ console.log("PASS: lib/honcho-client.js exists");
25
+ };
26
+
27
+ // Test 2: module must export 6 required functions
28
+ const test2 = async () => {
29
+ const mod = await import(CLIENT_FILE);
30
+ const required = ["getOrCreateApp", "getOrCreateSession", "saveMemory", "searchMemories", "listMemories", "deleteMemory"];
31
+ const missing = required.filter((fn) => typeof mod[fn] !== "function");
32
+ if (missing.length > 0) {
33
+ throw new Error(`lib/honcho-client.js missing exports: ${missing.join(", ")}`);
34
+ }
35
+ console.log("PASS: all 6 functions exported");
36
+ };
37
+
38
+ // Test 3: createClient must be exported and return a client object
39
+ const test3 = async () => {
40
+ const mod = await import(CLIENT_FILE);
41
+ if (typeof mod.createClient !== "function") {
42
+ throw new Error("lib/honcho-client.js missing export: createClient");
43
+ }
44
+ const client = mod.createClient({ baseUrl: "http://localhost:19999", apiKey: "test", timeoutMs: 100 });
45
+ if (!client || typeof client !== "object") {
46
+ throw new Error("createClient() did not return an object");
47
+ }
48
+ const required = ["getOrCreateApp", "getOrCreateSession", "saveMemory", "searchMemories", "listMemories", "deleteMemory"];
49
+ const missing = required.filter((fn) => typeof client[fn] !== "function");
50
+ if (missing.length > 0) {
51
+ throw new Error(`Client object missing methods: ${missing.join(", ")}`);
52
+ }
53
+ console.log("PASS: createClient() returns object with all 6 methods");
54
+ };
55
+
56
+ // Test 4: getOrCreateApp returns null on unreachable host (no throw)
57
+ const test4 = async () => {
58
+ const { createClient } = await import(CLIENT_FILE);
59
+ const client = createClient({ baseUrl: "http://localhost:19999", apiKey: "test-key", timeoutMs: 200 });
60
+ const result = await client.getOrCreateApp("test-workspace");
61
+ if (result !== null) {
62
+ throw new Error(`Expected null on connection failure, got: ${JSON.stringify(result)}`);
63
+ }
64
+ console.log("PASS: getOrCreateApp returns null on unreachable host");
65
+ };
66
+
67
+ // Test 5: getOrCreateSession returns null on unreachable host (no throw)
68
+ const test5 = async () => {
69
+ const { createClient } = await import(CLIENT_FILE);
70
+ const client = createClient({ baseUrl: "http://localhost:19999", apiKey: "test-key", timeoutMs: 200 });
71
+ const result = await client.getOrCreateSession("ws", "session-id");
72
+ if (result !== null) {
73
+ throw new Error(`Expected null on connection failure, got: ${JSON.stringify(result)}`);
74
+ }
75
+ console.log("PASS: getOrCreateSession returns null on unreachable host");
76
+ };
77
+
78
+ // Test 6: saveMemory returns null on unreachable host (no throw)
79
+ const test6 = async () => {
80
+ const { createClient } = await import(CLIENT_FILE);
81
+ const client = createClient({ baseUrl: "http://localhost:19999", apiKey: "test-key", timeoutMs: 200 });
82
+ const result = await client.saveMemory("ws", "session-id", "peer-id", "test content", {});
83
+ if (result !== null) {
84
+ throw new Error(`Expected null on connection failure, got: ${JSON.stringify(result)}`);
85
+ }
86
+ console.log("PASS: saveMemory returns null on unreachable host");
87
+ };
88
+
89
+ // Test 7: searchMemories returns null on unreachable host (no throw)
90
+ const test7 = async () => {
91
+ const { createClient } = await import(CLIENT_FILE);
92
+ const client = createClient({ baseUrl: "http://localhost:19999", apiKey: "test-key", timeoutMs: 200 });
93
+ const result = await client.searchMemories("ws", "peer-id", "query");
94
+ if (result !== null) {
95
+ throw new Error(`Expected null on connection failure, got: ${JSON.stringify(result)}`);
96
+ }
97
+ console.log("PASS: searchMemories returns null on unreachable host");
98
+ };
99
+
100
+ // Test 8: listMemories returns null on unreachable host (no throw)
101
+ const test8 = async () => {
102
+ const { createClient } = await import(CLIENT_FILE);
103
+ const client = createClient({ baseUrl: "http://localhost:19999", apiKey: "test-key", timeoutMs: 200 });
104
+ const result = await client.listMemories("ws", "session-id");
105
+ if (result !== null) {
106
+ throw new Error(`Expected null on connection failure, got: ${JSON.stringify(result)}`);
107
+ }
108
+ console.log("PASS: listMemories returns null on unreachable host");
109
+ };
110
+
111
+ // Test 9: deleteMemory returns null on unreachable host (no throw)
112
+ const test9 = async () => {
113
+ const { createClient } = await import(CLIENT_FILE);
114
+ const client = createClient({ baseUrl: "http://localhost:19999", apiKey: "test-key", timeoutMs: 200 });
115
+ const result = await client.deleteMemory("ws", "session-id");
116
+ if (result !== null) {
117
+ throw new Error(`Expected null on connection failure, got: ${JSON.stringify(result)}`);
118
+ }
119
+ console.log("PASS: deleteMemory returns null on unreachable host");
120
+ };
121
+
122
+ // Test 10: all operations complete within 2x timeout (graceful timeout enforcement)
123
+ const test10 = async () => {
124
+ const { createClient } = await import(CLIENT_FILE);
125
+ const client = createClient({ baseUrl: "http://localhost:19999", apiKey: "test-key", timeoutMs: 200 });
126
+ const start = Date.now();
127
+ await Promise.all([
128
+ client.getOrCreateApp("ws"),
129
+ client.getOrCreateSession("ws", "s"),
130
+ client.saveMemory("ws", "s", "p", "content", {}),
131
+ ]);
132
+ const elapsed = Date.now() - start;
133
+ // Each runs independently but all should complete within 3x timeout (generous allowance)
134
+ if (elapsed > 3000) {
135
+ throw new Error(`Operations took ${elapsed}ms, expected < 3000ms`);
136
+ }
137
+ console.log(`PASS: all operations completed within time limit (${elapsed}ms)`);
138
+ };
139
+
140
+ // Test 11: loadConfig function exported and readable
141
+ const test11 = async () => {
142
+ const mod = await import(CLIENT_FILE);
143
+ if (typeof mod.loadConfig !== "function") {
144
+ throw new Error("lib/honcho-client.js missing export: loadConfig");
145
+ }
146
+ console.log("PASS: loadConfig exported");
147
+ };
148
+
149
+ // Run all tests
150
+ let failed = 0;
151
+ const tests = [test1, test2, test3, test4, test5, test6, test7, test8, test9, test10, test11];
152
+ for (const t of tests) {
153
+ try {
154
+ await t();
155
+ } catch (e) {
156
+ console.error(`FAIL: ${e.message}`);
157
+ failed++;
158
+ }
159
+ }
160
+
161
+ console.log(`\nResults: ${tests.length - failed}/${tests.length} passed`);
162
+ process.exit(failed > 0 ? 1 : 0);
@@ -0,0 +1,259 @@
1
+ /**
2
+ * T-004: Verify softDeleteMemory + deleted-filter helpers in honcho-client.js
3
+ *
4
+ * Tests are written BEFORE implementation (TDD RED phase).
5
+ * Verifies:
6
+ * - softDeleteMemory is exported as a function
7
+ * - createClient() returns an object that includes softDeleteMemory
8
+ * - softDeleteMemory returns null on unreachable host (never throws)
9
+ * - softDeleteMemory uses PUT method (verified via mock)
10
+ * - listMemories client-side filter excludes metadata.deleted = true
11
+ * - searchMemories client-side filter excludes metadata.deleted = true
12
+ * - All 6 original functions remain intact and exported
13
+ */
14
+
15
+ import { existsSync } from "fs";
16
+ import { join } from "path";
17
+
18
+ const SKILL_DIR = new URL("../", import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1");
19
+ const CLIENT_FILE = join(SKILL_DIR, "lib", "honcho-client.js");
20
+
21
+ // Test 1: softDeleteMemory must be exported as standalone function
22
+ const test1 = async () => {
23
+ const mod = await import(CLIENT_FILE);
24
+ if (typeof mod.softDeleteMemory !== "function") {
25
+ throw new Error("lib/honcho-client.js missing export: softDeleteMemory");
26
+ }
27
+ console.log("PASS: softDeleteMemory exported as standalone function");
28
+ };
29
+
30
+ // Test 2: createClient() must return object with softDeleteMemory method
31
+ const test2 = async () => {
32
+ const { createClient } = await import(CLIENT_FILE);
33
+ const client = createClient({ baseUrl: "http://localhost:19999", apiKey: "test", timeoutMs: 100 });
34
+ if (typeof client.softDeleteMemory !== "function") {
35
+ throw new Error("createClient() object missing softDeleteMemory method");
36
+ }
37
+ console.log("PASS: createClient() includes softDeleteMemory method");
38
+ };
39
+
40
+ // Test 3: softDeleteMemory returns null on unreachable host (never throws)
41
+ const test3 = async () => {
42
+ const { createClient } = await import(CLIENT_FILE);
43
+ const client = createClient({ baseUrl: "http://localhost:19999", apiKey: "test-key", timeoutMs: 200 });
44
+ const result = await client.softDeleteMemory("ws", "session-id", "message-id");
45
+ if (result !== null) {
46
+ throw new Error(`Expected null on connection failure, got: ${JSON.stringify(result)}`);
47
+ }
48
+ console.log("PASS: softDeleteMemory returns null on unreachable host");
49
+ };
50
+
51
+ // Test 4: standalone softDeleteMemory returns null when config missing
52
+ const test4 = async () => {
53
+ // Call without config — should return null gracefully (config not found or test env)
54
+ const { softDeleteMemory } = await import(CLIENT_FILE);
55
+ // In test env config may not exist; if it does this still must not throw
56
+ let threw = false;
57
+ try {
58
+ await softDeleteMemory("ws", "session-id", "message-id");
59
+ } catch {
60
+ threw = true;
61
+ }
62
+ if (threw) {
63
+ throw new Error("standalone softDeleteMemory must not throw — must return null");
64
+ }
65
+ console.log("PASS: standalone softDeleteMemory never throws");
66
+ };
67
+
68
+ // Test 5: all 7 functions are now exported (6 original + softDeleteMemory)
69
+ const test5 = async () => {
70
+ const mod = await import(CLIENT_FILE);
71
+ const required = [
72
+ "getOrCreateApp",
73
+ "getOrCreateSession",
74
+ "saveMemory",
75
+ "searchMemories",
76
+ "listMemories",
77
+ "deleteMemory",
78
+ "softDeleteMemory",
79
+ ];
80
+ const missing = required.filter((fn) => typeof mod[fn] !== "function");
81
+ if (missing.length > 0) {
82
+ throw new Error(`lib/honcho-client.js missing exports: ${missing.join(", ")}`);
83
+ }
84
+ console.log("PASS: all 7 standalone functions exported");
85
+ };
86
+
87
+ // Test 6: createClient object has all 7 methods
88
+ const test6 = async () => {
89
+ const { createClient } = await import(CLIENT_FILE);
90
+ const client = createClient({ baseUrl: "http://localhost:19999", apiKey: "test", timeoutMs: 100 });
91
+ const required = [
92
+ "getOrCreateApp",
93
+ "getOrCreateSession",
94
+ "saveMemory",
95
+ "searchMemories",
96
+ "listMemories",
97
+ "deleteMemory",
98
+ "softDeleteMemory",
99
+ ];
100
+ const missing = required.filter((fn) => typeof client[fn] !== "function");
101
+ if (missing.length > 0) {
102
+ throw new Error(`createClient() object missing methods: ${missing.join(", ")}`);
103
+ }
104
+ console.log("PASS: createClient() object has all 7 methods");
105
+ };
106
+
107
+ // Test 7: softDeleteMemory uses PUT via interceptable mock
108
+ // We verify that the function signature accepts (workspaceId, sessionId, messageId)
109
+ const test7 = async () => {
110
+ const { createClient } = await import(CLIENT_FILE);
111
+
112
+ let capturedMethod = null;
113
+ let capturedUrl = null;
114
+ let capturedBody = null;
115
+
116
+ // Patch globalThis.fetch to intercept the request
117
+ const originalFetch = globalThis.fetch;
118
+ globalThis.fetch = async (url, opts) => {
119
+ capturedMethod = opts?.method;
120
+ capturedUrl = url;
121
+ capturedBody = opts?.body ? JSON.parse(opts.body) : null;
122
+ // Return a mock successful response
123
+ return {
124
+ ok: true,
125
+ status: 200,
126
+ json: async () => ({ id: "msg-1", metadata: { deleted: true } }),
127
+ };
128
+ };
129
+
130
+ try {
131
+ const client = createClient({ baseUrl: "http://mock-host", apiKey: "test-key", timeoutMs: 200 });
132
+ await client.softDeleteMemory("my-workspace", "my-session", "my-message-id");
133
+ } finally {
134
+ globalThis.fetch = originalFetch;
135
+ }
136
+
137
+ if (capturedMethod !== "PUT") {
138
+ throw new Error(`Expected PUT method, got: ${capturedMethod}`);
139
+ }
140
+ if (!capturedUrl.includes("my-workspace") || !capturedUrl.includes("my-session") || !capturedUrl.includes("my-message-id")) {
141
+ throw new Error(`URL does not contain expected path segments: ${capturedUrl}`);
142
+ }
143
+ if (!capturedBody?.metadata?.deleted) {
144
+ throw new Error(`Expected body { metadata: { deleted: true } }, got: ${JSON.stringify(capturedBody)}`);
145
+ }
146
+ console.log("PASS: softDeleteMemory uses PUT with correct URL and body");
147
+ };
148
+
149
+ // Test 8: listMemories client-side filter excludes deleted messages
150
+ const test8 = async () => {
151
+ const { createClient } = await import(CLIENT_FILE);
152
+
153
+ const originalFetch = globalThis.fetch;
154
+ globalThis.fetch = async (url, opts) => {
155
+ return {
156
+ ok: true,
157
+ status: 200,
158
+ json: async () => ({
159
+ items: [
160
+ { id: "m1", content: "keep me", metadata: { type: "explicit", deleted: false } },
161
+ { id: "m2", content: "soft deleted", metadata: { type: "explicit", deleted: true } },
162
+ { id: "m3", content: "also keep", metadata: { type: "inferred" } },
163
+ { id: "m4", content: "also deleted", metadata: { type: "project-context", deleted: true } },
164
+ ],
165
+ total: 4,
166
+ }),
167
+ };
168
+ };
169
+
170
+ let result;
171
+ try {
172
+ const client = createClient({ baseUrl: "http://mock-host", apiKey: "test-key", timeoutMs: 200 });
173
+ result = await client.listMemories("ws", "session-id");
174
+ } finally {
175
+ globalThis.fetch = originalFetch;
176
+ }
177
+
178
+ if (!result) {
179
+ throw new Error("listMemories returned null when it should return filtered results");
180
+ }
181
+ const items = result.items ?? result;
182
+ const deletedItems = (Array.isArray(items) ? items : []).filter((m) => m.metadata?.deleted === true);
183
+ if (deletedItems.length > 0) {
184
+ throw new Error(`listMemories returned ${deletedItems.length} soft-deleted item(s) — must be filtered out`);
185
+ }
186
+ const keptCount = Array.isArray(items) ? items.length : 0;
187
+ if (keptCount !== 2) {
188
+ throw new Error(`Expected 2 non-deleted items, got ${keptCount}: ${JSON.stringify(items)}`);
189
+ }
190
+ console.log("PASS: listMemories filters out metadata.deleted = true items");
191
+ };
192
+
193
+ // Test 9: searchMemories client-side filter excludes deleted messages
194
+ const test9 = async () => {
195
+ const { createClient } = await import(CLIENT_FILE);
196
+
197
+ const originalFetch = globalThis.fetch;
198
+ globalThis.fetch = async (url, opts) => {
199
+ return {
200
+ ok: true,
201
+ status: 200,
202
+ json: async () => ({
203
+ items: [
204
+ { id: "s1", content: "relevant result", metadata: { type: "explicit" }, score: 0.9 },
205
+ { id: "s2", content: "soft deleted result", metadata: { type: "explicit", deleted: true }, score: 0.8 },
206
+ { id: "s3", content: "another result", metadata: { type: "inferred" }, score: 0.7 },
207
+ ],
208
+ total: 3,
209
+ }),
210
+ };
211
+ };
212
+
213
+ let result;
214
+ try {
215
+ const client = createClient({ baseUrl: "http://mock-host", apiKey: "test-key", timeoutMs: 200 });
216
+ result = await client.searchMemories("ws", "peer-id", "test query");
217
+ } finally {
218
+ globalThis.fetch = originalFetch;
219
+ }
220
+
221
+ if (!result) {
222
+ throw new Error("searchMemories returned null when it should return filtered results");
223
+ }
224
+ const items = result.items ?? result;
225
+ const deletedItems = (Array.isArray(items) ? items : []).filter((m) => m.metadata?.deleted === true);
226
+ if (deletedItems.length > 0) {
227
+ throw new Error(`searchMemories returned ${deletedItems.length} soft-deleted item(s) — must be filtered out`);
228
+ }
229
+ const keptCount = Array.isArray(items) ? items.length : 0;
230
+ if (keptCount !== 2) {
231
+ throw new Error(`Expected 2 non-deleted items, got ${keptCount}: ${JSON.stringify(items)}`);
232
+ }
233
+ console.log("PASS: searchMemories filters out metadata.deleted = true items");
234
+ };
235
+
236
+ // Test 10: DoD check — bun -e import returns 0 for softDeleteMemory
237
+ const test10 = async () => {
238
+ const mod = await import(CLIENT_FILE);
239
+ const exitCode = typeof mod.softDeleteMemory === "function" ? 0 : 1;
240
+ if (exitCode !== 0) {
241
+ throw new Error("DoD FAIL: softDeleteMemory not exported as function");
242
+ }
243
+ console.log("PASS: DoD — softDeleteMemory is exported and is a function (exit 0)");
244
+ };
245
+
246
+ // Run all tests
247
+ let failed = 0;
248
+ const tests = [test1, test2, test3, test4, test5, test6, test7, test8, test9, test10];
249
+ for (const t of tests) {
250
+ try {
251
+ await t();
252
+ } catch (e) {
253
+ console.error(`FAIL: ${e.message}`);
254
+ failed++;
255
+ }
256
+ }
257
+
258
+ console.log(`\nT-004 Results: ${tests.length - failed}/${tests.length} passed`);
259
+ process.exit(failed > 0 ? 1 : 0);
@@ -0,0 +1,175 @@
1
+ /**
2
+ * T-005: Verify lib/memory-injector.js
3
+ *
4
+ * Tests are written BEFORE implementation (TDD RED phase).
5
+ * Verifies:
6
+ * - memory-injector.js exists and is importable
7
+ * - exports buildContextBlock function
8
+ * - output contains [HONCHO MEMORY] and [/HONCHO MEMORY] tags
9
+ * - formats metadata (type, timestamp, project)
10
+ * - limit 2000 chars (BR-007)
11
+ * - empty array returns empty string or minimal block
12
+ * - content is included in block
13
+ */
14
+
15
+ import { existsSync } from "fs";
16
+ import { join } from "path";
17
+
18
+ const SKILL_DIR = new URL("../", import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1");
19
+ const INJECTOR_FILE = join(SKILL_DIR, "lib", "memory-injector.js");
20
+
21
+ // Test 1: lib/memory-injector.js must exist
22
+ const test1 = () => {
23
+ if (!existsSync(INJECTOR_FILE)) {
24
+ throw new Error(`lib/memory-injector.js does not exist at: ${INJECTOR_FILE}`);
25
+ }
26
+ console.log("PASS: lib/memory-injector.js exists");
27
+ };
28
+
29
+ // Test 2: must export buildContextBlock
30
+ const test2 = async () => {
31
+ const mod = await import(INJECTOR_FILE);
32
+ if (typeof mod.buildContextBlock !== "function") {
33
+ throw new Error("lib/memory-injector.js missing export: buildContextBlock");
34
+ }
35
+ console.log("PASS: buildContextBlock exported");
36
+ };
37
+
38
+ // Test 3: buildContextBlock([]) returns empty string (no memories = no block)
39
+ const test3 = async () => {
40
+ const { buildContextBlock } = await import(INJECTOR_FILE);
41
+ const result = buildContextBlock([]);
42
+ if (typeof result !== "string") {
43
+ throw new Error(`buildContextBlock([]) must return string, got: ${typeof result}`);
44
+ }
45
+ // Empty array = no block injected (return empty string)
46
+ if (result.trim() !== "") {
47
+ // Also acceptable: return empty tags — but empty string is preferred
48
+ // Only fail if it contains actual content
49
+ if (!result.includes("[HONCHO MEMORY]")) {
50
+ throw new Error(`buildContextBlock([]) returned unexpected non-empty: ${result}`);
51
+ }
52
+ }
53
+ console.log("PASS: buildContextBlock([]) returns empty or empty block");
54
+ };
55
+
56
+ // Test 4: single memory produces block with [HONCHO MEMORY] and [/HONCHO MEMORY]
57
+ const test4 = async () => {
58
+ const { buildContextBlock } = await import(INJECTOR_FILE);
59
+ const memories = [{ content: "test content", metadata: { type: "explicit" } }];
60
+ const result = buildContextBlock(memories);
61
+ if (!result.includes("[HONCHO MEMORY]")) {
62
+ throw new Error("Output missing [HONCHO MEMORY] opening tag");
63
+ }
64
+ if (!result.includes("[/HONCHO MEMORY]")) {
65
+ throw new Error("Output missing [/HONCHO MEMORY] closing tag");
66
+ }
67
+ console.log("PASS: output contains [HONCHO MEMORY] and [/HONCHO MEMORY]");
68
+ };
69
+
70
+ // Test 5: output contains memory content
71
+ const test5 = async () => {
72
+ const { buildContextBlock } = await import(INJECTOR_FILE);
73
+ const memories = [{ content: "prefiro records imutáveis em DTOs Java", metadata: { type: "explicit" } }];
74
+ const result = buildContextBlock(memories);
75
+ if (!result.includes("prefiro records imutáveis em DTOs Java")) {
76
+ throw new Error("Output does not contain memory content");
77
+ }
78
+ console.log("PASS: output contains memory content");
79
+ };
80
+
81
+ // Test 6: output contains type metadata
82
+ const test6 = async () => {
83
+ const { buildContextBlock } = await import(INJECTOR_FILE);
84
+ const memories = [{ content: "test", metadata: { type: "explicit" } }];
85
+ const result = buildContextBlock(memories);
86
+ if (!result.includes("explicit")) {
87
+ throw new Error("Output does not include type metadata 'explicit'");
88
+ }
89
+ console.log("PASS: output contains type metadata");
90
+ };
91
+
92
+ // Test 7: output contains timestamp metadata when provided
93
+ const test7 = async () => {
94
+ const { buildContextBlock } = await import(INJECTOR_FILE);
95
+ const ts = "2026-05-26T10:00:00.000Z";
96
+ const memories = [{ content: "test", metadata: { type: "explicit", timestamp: ts } }];
97
+ const result = buildContextBlock(memories);
98
+ if (!result.includes(ts)) {
99
+ throw new Error(`Output does not include timestamp: ${ts}`);
100
+ }
101
+ console.log("PASS: output contains timestamp metadata");
102
+ };
103
+
104
+ // Test 8: output contains project metadata when provided
105
+ const test8 = async () => {
106
+ const { buildContextBlock } = await import(INJECTOR_FILE);
107
+ const memories = [{ content: "test", metadata: { type: "explicit", project: "development-utility-kit" } }];
108
+ const result = buildContextBlock(memories);
109
+ if (!result.includes("development-utility-kit")) {
110
+ throw new Error("Output does not include project metadata");
111
+ }
112
+ console.log("PASS: output contains project metadata");
113
+ };
114
+
115
+ // Test 9: output is limited to 2000 chars (BR-007)
116
+ const test9 = async () => {
117
+ const { buildContextBlock } = await import(INJECTOR_FILE);
118
+ // Create memories that would exceed 2000 chars
119
+ const bigContent = "x".repeat(500);
120
+ const memories = Array.from({ length: 10 }, (_, i) => ({
121
+ content: `Memory ${i}: ${bigContent}`,
122
+ metadata: { type: "explicit", project: "test-project" },
123
+ }));
124
+ const result = buildContextBlock(memories);
125
+ if (result.length > 2000) {
126
+ throw new Error(`Output exceeds 2000 chars: ${result.length} chars`);
127
+ }
128
+ console.log(`PASS: output limited to 2000 chars (got ${result.length} chars)`);
129
+ };
130
+
131
+ // Test 10: multiple memories are all included (within limit)
132
+ const test10 = async () => {
133
+ const { buildContextBlock } = await import(INJECTOR_FILE);
134
+ const memories = [
135
+ { content: "first memory", metadata: { type: "explicit" } },
136
+ { content: "second memory", metadata: { type: "inferred" } },
137
+ { content: "third memory", metadata: { type: "project-context" } },
138
+ ];
139
+ const result = buildContextBlock(memories);
140
+ if (!result.includes("first memory")) {
141
+ throw new Error("Output missing first memory content");
142
+ }
143
+ if (!result.includes("second memory")) {
144
+ throw new Error("Output missing second memory content");
145
+ }
146
+ if (!result.includes("third memory")) {
147
+ throw new Error("Output missing third memory content");
148
+ }
149
+ console.log("PASS: multiple memories included in output");
150
+ };
151
+
152
+ // Test 11: DoD check — import + buildContextBlock([{content:'test',metadata:{type:'explicit'}}]) contains [HONCHO MEMORY]
153
+ const test11 = async () => {
154
+ const { buildContextBlock } = await import(INJECTOR_FILE);
155
+ const result = buildContextBlock([{ content: "test", metadata: { type: "explicit" } }]);
156
+ if (!result.includes("[HONCHO MEMORY]")) {
157
+ throw new Error("DoD FAIL: result does not contain [HONCHO MEMORY]");
158
+ }
159
+ console.log("PASS: DoD — buildContextBlock returns string containing [HONCHO MEMORY]");
160
+ };
161
+
162
+ // Run all tests
163
+ let failed = 0;
164
+ const tests = [test1, test2, test3, test4, test5, test6, test7, test8, test9, test10, test11];
165
+ for (const t of tests) {
166
+ try {
167
+ await t();
168
+ } catch (e) {
169
+ console.error(`FAIL: ${e.message}`);
170
+ failed++;
171
+ }
172
+ }
173
+
174
+ console.log(`\nT-005 Results: ${tests.length - failed}/${tests.length} passed`);
175
+ process.exit(failed > 0 ? 1 : 0);