@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.
- package/.agents/instructions/learned-knowledge.instructions.md +150 -0
- package/.agents/instructions/memory-schema.instructions.md +22 -0
- package/.husky/pre-commit +1 -0
- package/.opencode/agents/reviewer.agent.md +1 -0
- package/README.md +124 -304
- package/memory-bank/.vocabulary.json +115 -31
- package/package.json +1 -1
- package/scripts/mcp-knowledgebase-server.js +90 -20
- package/scripts/mcp-knowledgebase-server.test.js +745 -0
- package/scripts/setup/constants.js +2 -1
- package/scripts/sync.js +1 -0
- package/scripts/sync.test.js +4 -3
- package/scripts/validate-memory-schema.js +28 -3
- package/scripts/validate-memory-schema.test.js +328 -0
- package/scripts/vocab-sync.js +216 -0
- package/scripts/vocab-sync.test.js +814 -0
|
@@ -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",
|
|
16
|
-
"
|
|
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",
|
|
20
|
-
"
|
|
21
|
-
"
|
|
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",
|
|
26
|
-
"
|
|
42
|
+
"decision",
|
|
43
|
+
"discovery",
|
|
44
|
+
"status-update",
|
|
45
|
+
"task-plan",
|
|
46
|
+
"retrospective",
|
|
47
|
+
"bug-fix",
|
|
48
|
+
"verification"
|
|
27
49
|
],
|
|
28
50
|
"topic": [
|
|
29
|
-
"
|
|
30
|
-
"chunk-parser",
|
|
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":
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"
|
|
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":
|
|
50
|
-
"productContext":
|
|
51
|
-
"systemPatterns":
|
|
52
|
-
"techContext":
|
|
53
|
-
"activeContext":
|
|
54
|
-
"progress":
|
|
127
|
+
"projectbrief": "brief",
|
|
128
|
+
"productContext": "product",
|
|
129
|
+
"systemPatterns": "architecture",
|
|
130
|
+
"techContext": "tech",
|
|
131
|
+
"activeContext": "context",
|
|
132
|
+
"progress": "progress"
|
|
55
133
|
},
|
|
56
|
-
|
|
57
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
140
|
-
limit: args.limit
|
|
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,
|
|
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 "${
|
|
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 "${
|
|
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
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
+
}
|