@abarcenas/ai-workflow-template 1.40.0 → 1.43.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.
@@ -9,50 +9,134 @@
9
9
  "task": "Individual task tracking (tasks/TASK*.md)",
10
10
  "decision": "ADR-style architecture/technical decisions"
11
11
  },
12
-
13
12
  "tags": {
14
13
  "agent_roles": [
15
- "designer", "architect", "researcher", "coder", "implementer",
16
- "tester", "reviewer", "tracker", "deployer", "orchestrator"
14
+ "designer",
15
+ "architect",
16
+ "researcher",
17
+ "coder",
18
+ "implementer",
19
+ "tester",
20
+ "reviewer",
21
+ "tracker",
22
+ "deployer",
23
+ "orchestrator"
17
24
  ],
18
25
  "workflow": [
19
- "bootstrap", "setup", "configuration", "migration", "refactor",
20
- "tdd", "feature-pipeline", "onboarding", "normalization",
21
- "research", "spike", "implementation", "implementation-planning",
26
+ "bootstrap",
27
+ "setup",
28
+ "configuration",
29
+ "migration",
30
+ "refactor",
31
+ "tdd",
32
+ "feature-pipeline",
33
+ "onboarding",
34
+ "normalization",
35
+ "research",
36
+ "spike",
37
+ "implementation",
38
+ "implementation-planning",
22
39
  "documentation"
23
40
  ],
24
41
  "memory_ops": [
25
- "decision", "discovery", "status-update", "task-plan", "retrospective",
26
- "bug-fix", "verification"
42
+ "decision",
43
+ "discovery",
44
+ "status-update",
45
+ "task-plan",
46
+ "retrospective",
47
+ "bug-fix",
48
+ "verification"
27
49
  ],
28
50
  "topic": [
29
- "knowledgebase", "pgvector", "mcp", "embeddings", "dotenv",
30
- "chunk-parser", "agent-exercise"
51
+ "agent-exercise",
52
+ "chunk-parser",
53
+ "dotenv",
54
+ "embeddings",
55
+ "feature-vocab-sync",
56
+ "fix-kb-registerproject",
57
+ "float32array",
58
+ "knowledgebase",
59
+ "learned-knowledge",
60
+ "mcp",
61
+ "package-structure",
62
+ "pgvector",
63
+ "pre-commit",
64
+ "readme",
65
+ "vocabulary"
31
66
  ]
32
67
  },
33
-
34
68
  "entity_patterns": {
35
- "vitest": ["vitest", "vitest\\.config", "@vitest\\/coverage"],
36
- "playwright": ["playwright", "@playwright", "browser.*test"],
37
- "graphify": ["graphify", "knowledge graph", "graph\\.json"],
38
- "memory-bank": ["memory-bank", "memory bank", "activeContext", "systemPatterns"],
39
- "husky": ["husky", "pre-commit", "git hook"],
40
- "tdd-orchestrator": ["tdd orchestrator", "RED.*GREEN.*REFACTOR"],
41
- "mcp-server": ["mcp", "modelcontextprotocol", "stdio", "mcp server"],
42
- "sqlite-vec": ["sqlite-vec", "sqlite.vec", "better-sqlite3"],
43
- "transformers.js": ["@xenova\\/transformers", "transformers\\.js", "all-MiniLM"],
44
- "opencode": ["opencode", "OpenCode", "open code"],
45
- "npm": ["npm", "npm package", "npm install"]
69
+ "vitest": [
70
+ "vitest",
71
+ "vitest\\.config",
72
+ "@vitest\\/coverage"
73
+ ],
74
+ "playwright": [
75
+ "playwright",
76
+ "@playwright",
77
+ "browser.*test"
78
+ ],
79
+ "graphify": [
80
+ "graphify",
81
+ "knowledge graph",
82
+ "graph\\.json"
83
+ ],
84
+ "memory-bank": [
85
+ "memory-bank",
86
+ "memory bank",
87
+ "activeContext",
88
+ "systemPatterns"
89
+ ],
90
+ "husky": [
91
+ "husky",
92
+ "pre-commit",
93
+ "git hook"
94
+ ],
95
+ "tdd-orchestrator": [
96
+ "tdd orchestrator",
97
+ "RED.*GREEN.*REFACTOR"
98
+ ],
99
+ "mcp-server": [
100
+ "mcp",
101
+ "modelcontextprotocol",
102
+ "stdio",
103
+ "mcp server"
104
+ ],
105
+ "sqlite-vec": [
106
+ "sqlite-vec",
107
+ "sqlite.vec",
108
+ "better-sqlite3"
109
+ ],
110
+ "transformers.js": [
111
+ "@xenova\\/transformers",
112
+ "transformers\\.js",
113
+ "all-MiniLM"
114
+ ],
115
+ "opencode": [
116
+ "opencode",
117
+ "OpenCode",
118
+ "open code"
119
+ ],
120
+ "npm": [
121
+ "npm",
122
+ "npm package",
123
+ "npm install"
124
+ ]
46
125
  },
47
-
48
126
  "file_category_map": {
49
- "projectbrief": "brief",
50
- "productContext": "product",
51
- "systemPatterns": "architecture",
52
- "techContext": "tech",
53
- "activeContext": "context",
54
- "progress": "progress"
127
+ "projectbrief": "brief",
128
+ "productContext": "product",
129
+ "systemPatterns": "architecture",
130
+ "techContext": "tech",
131
+ "activeContext": "context",
132
+ "progress": "progress"
55
133
  },
56
-
57
- "doc_types": ["spike", "architecture", "configuration", "orchestrator-log", "tracker-log", "decision"]
134
+ "doc_types": [
135
+ "spike",
136
+ "architecture",
137
+ "configuration",
138
+ "orchestrator-log",
139
+ "tracker-log",
140
+ "decision"
141
+ ]
58
142
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abarcenas/ai-workflow-template",
3
- "version": "1.40.0",
3
+ "version": "1.43.0",
4
4
  "description": "A comprehensive template repository for AI-assisted development workflows using GitHub Copilot. This template provides a structured approach to leveraging AI assistance through pre-configured skills, prompts, agents, and coding standards.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -20,17 +20,23 @@ import {
20
20
  CallToolRequestSchema,
21
21
  ListToolsRequestSchema,
22
22
  } from '@modelcontextprotocol/sdk/types.js';
23
+
24
+ // Load .env file into process.env before any configuration reads
25
+ import 'dotenv/config';
26
+
23
27
  import {
24
28
  search,
25
29
  upsertChunks,
26
30
  chunkLearnedKnowledge,
27
31
  getStats,
28
32
  listProjects,
33
+ registerProject,
29
34
  getPool,
30
35
  closePool,
31
36
  } from './knowledgebase-index.js';
32
37
  import { readFileSync } from 'node:fs';
33
38
  import { resolve } from 'node:path';
39
+ import { fileURLToPath } from 'node:url';
34
40
 
35
41
  const server = new Server(
36
42
  {
@@ -68,7 +74,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
68
74
  },
69
75
  threshold: {
70
76
  type: 'number',
71
- default: 0.6,
77
+ default: 0.1,
72
78
  description: 'Minimum similarity threshold (0.0–1.0). Higher values return fewer but more relevant results.',
73
79
  },
74
80
  limit: {
@@ -126,18 +132,65 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
126
132
  ],
127
133
  }));
128
134
 
135
+ // ── Helpers ──────────────────────────────────────────────────────────────
136
+
137
+ /**
138
+ * Redact connection-string credentials from an error message.
139
+ *
140
+ * Every postgres:// or postgresql:// URL token in the message has its
141
+ * userinfo credentials replaced with *** and its query string (or fragment)
142
+ * replaced with ?*** / #***, while the host and path are preserved verbatim
143
+ * (e.g. postgres://user:secret@host:5432/kb?password=hunter2 →
144
+ * postgres://***@host:5432/kb?***). Messages without a URL token are returned
145
+ * unchanged so generic errors (e.g. "Unknown tool: ...") stay readable — the
146
+ * engine helper returns '***' for non-URLs, which would hide every error
147
+ * message.
148
+ *
149
+ * Unlike the engine helper (knowledgebase-index.js:62) — which fails closed
150
+ * by returning '***' when `new URL()` throws — this helper FAILS CLOSED on
151
+ * unparseable tokens by using a pure regex instead of URL parsing: libpq
152
+ * unix-socket authorities such as postgres://user:secret@/var/run/postgresql
153
+ * (or postgresql://user:secret@/tmp?host=/tmp) make `new URL()` throw, so a
154
+ * parse-based helper would return the message unchanged and leak the
155
+ * credentials. The regex strips the userinfo and the query/fragment from
156
+ * EVERY token and preserves the host/path even when the URL cannot be parsed.
157
+ *
158
+ * @param {string} message
159
+ * @returns {string}
160
+ */
161
+ function redactConnectionString(message) {
162
+ if (typeof message !== 'string' || message.length === 0) return message;
163
+ return message.replace(
164
+ /(postgres(?:ql)?:\/\/)([^/\s]+)@([^?#\s]*)([?#][^\s]*)?/gi,
165
+ (match, scheme, userinfo, hostAndPath, queryOrFragment) =>
166
+ `${scheme}***@${hostAndPath}${queryOrFragment ? queryOrFragment[0] + '***' : ''}`
167
+ );
168
+ }
169
+
129
170
  // ── Handle tool calls ────────────────────────────────────────────────
130
171
 
131
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
172
+ /**
173
+ * Handle an MCP tool call.
174
+ *
175
+ * Exported for testability — the stdio bootstrap (main) is guarded to
176
+ * direct-run only, so tests can import this handler without opening a
177
+ * stdio transport or a database connection.
178
+ * @param {{ params: { name: string, arguments: Record<string, any> } }} request
179
+ * @returns {Promise<{ content: Array<{ type: string, text: string }>, isError?: boolean }>}
180
+ */
181
+ export async function handleToolCall(request) {
132
182
  const { name, arguments: args } = request.params;
133
183
 
134
184
  try {
135
185
  switch (name) {
136
186
  case 'knowledgebase_search': {
187
+ // projectId is an optional filter — a whitespace-only value is dropped
188
+ // to match-all (graceful degradation, mirroring the INDEX handler's
189
+ // trim/validate parity) and the trimmed value is passed to the engine.
137
190
  const results = await search(args.query, {
138
- project_id: args.projectId || undefined,
139
- threshold: args.threshold || 0.6,
140
- limit: args.limit || 5,
191
+ project_id: args.projectId?.trim() || undefined,
192
+ threshold: args.threshold ?? 0.1,
193
+ limit: args.limit ?? 5,
141
194
  });
142
195
 
143
196
  if (results.length === 0) {
@@ -176,7 +229,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
176
229
 
177
230
  case 'knowledgebase_index': {
178
231
  const projectId = args.projectId;
179
- if (!projectId) {
232
+ if (!projectId?.trim()) {
180
233
  return {
181
234
  content: [{
182
235
  type: 'text',
@@ -185,6 +238,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
185
238
  isError: true,
186
239
  };
187
240
  }
241
+ const pid = projectId.trim();
188
242
 
189
243
  let content = args.content;
190
244
  if (!content) {
@@ -209,25 +263,28 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
209
263
  }
210
264
  }
211
265
 
212
- const chunks = chunkLearnedKnowledge(content, projectId);
266
+ const chunks = chunkLearnedKnowledge(content, pid);
213
267
  if (chunks.length === 0) {
214
268
  return {
215
269
  content: [{
216
270
  type: 'text',
217
- text: `No valid knowledge chunks found for project "${projectId}". ` +
271
+ text: `No valid knowledge chunks found for project "${pid}". ` +
218
272
  'Ensure the content contains "## Session:" headers with ' +
219
273
  '"**New knowledge:**" sections.',
220
274
  }],
221
275
  };
222
276
  }
223
277
 
278
+ // Register/update the project in the projects table (idempotent upsert)
279
+ await registerProject(pid, pid);
280
+
224
281
  const result = await upsertChunks(chunks);
225
282
  return {
226
283
  content: [{
227
284
  type: 'text',
228
285
  text:
229
286
  `Indexed ${result.inserted} chunks, updated ${result.updated}, ` +
230
- `skipped ${result.skipped} for project "${projectId}".`,
287
+ `skipped ${result.skipped} for project "${pid}".`,
231
288
  }],
232
289
  };
233
290
  }
@@ -303,12 +360,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
303
360
  return {
304
361
  content: [{
305
362
  type: 'text',
306
- text: `Error: ${err.message}`,
363
+ text: `Error: ${redactConnectionString(err.message)}`,
307
364
  }],
308
365
  isError: true,
309
366
  };
310
367
  }
311
- });
368
+ }
369
+
370
+ server.setRequestHandler(CallToolRequestSchema, handleToolCall);
312
371
 
313
372
  // ── Start server ─────────────────────────────────────────────────────
314
373
 
@@ -318,13 +377,24 @@ async function main() {
318
377
  console.error('[knowledgebase-mcp] Server started via stdio transport');
319
378
  }
320
379
 
321
- process.on('SIGTERM', () => {
322
- console.error('[knowledgebase-mcp] SIGTERM received, closing pool');
323
- closePool().catch(() => {});
324
- process.exit(0);
325
- });
380
+ /**
381
+ * Only start the stdio server when this file is executed directly
382
+ * (node scripts/mcp-knowledgebase-server.js). When the module is imported
383
+ * — e.g. by tests exercising the exported handleToolCall — no stdio
384
+ * transport is opened and the process can exit cleanly.
385
+ */
386
+ const isDirectRun =
387
+ process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url);
326
388
 
327
- main().catch((err) => {
328
- console.error('[knowledgebase-mcp] Fatal error:', err);
329
- process.exit(1);
330
- });
389
+ if (isDirectRun) {
390
+ process.on('SIGTERM', () => {
391
+ console.error('[knowledgebase-mcp] SIGTERM received, closing pool');
392
+ closePool().catch(() => {});
393
+ process.exit(0);
394
+ });
395
+
396
+ main().catch((err) => {
397
+ console.error('[knowledgebase-mcp] Fatal error:', err);
398
+ process.exit(1);
399
+ });
400
+ }