@abarcenas/ai-workflow-template 1.25.0 β†’ 1.26.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # πŸ€– AI Workflow Template
2
2
 
3
- **Publishable AI workflow starter pack for GitHub Copilot** - Auto-syncing, version-managed, npm-distributed agent configuration suite.
3
+ **Publishable AI workflow starter pack for OpenCode** β€” Auto-syncing, version-managed, npm-distributed agent configuration suite with vector search memory.
4
4
 
5
5
  ---
6
6
 
@@ -16,7 +16,8 @@ This isn't a one-off template to cloneβ€”it's a **live npm package** that distri
16
16
  - πŸ§ͺ **TDD Orchestrator** β€” Strict REDβ†’GREENβ†’REFACTOR/VERIFY test-driven pipeline (90% coverage gate)
17
17
  - πŸ“ **Framework Instructions** - Technology-specific guidance (`.agents/instructions/`)
18
18
  - πŸ’¬ **Prompt Templates** - Ready-to-use prompt blueprints (`.agents/prompts/`)
19
- - 🧠 **Memory Bank** β€” Persistent project context across AI sessions
19
+ - 🧠 **Memory Bank** β€” Persistent project context across AI sessions with semantic search
20
+ - πŸ” **Vector Memory Search** β€” sqlite-vec powered semantic search (token-efficient, zero deps)
20
21
  - πŸƒβ€β™‚οΈ **Caveman Mode** - Ultra-compressed communication for token efficiency
21
22
  - 🎭 **Playwright Testing** - Automated E2E testing with MCP integration
22
23
  - ⚑ **Vitest Unit Testing** β€” Fast unit tests with v8 coverage (β‰₯90% threshold)
@@ -241,9 +242,38 @@ implementer (PLAN) β†’ unit-testerβˆ₯ (RED) β†’ coderβˆ₯ (GREEN) β†’ unit-tester
241
242
 
242
243
  The TDD orchestrator uses `subagent_type "tdd-orchestrator"` and auto-selects the strict TDD pipeline when testable code changes are requested.
243
244
 
244
- ### 🧠 Memory Bank β€” Persistent Project Context
245
+ ### 🧠 Memory Bank β€” Persistent Project Context with Vector Search
245
246
 
246
- The memory bank preserves project knowledge across AI sessions, preventing context loss after resets:
247
+ The memory bank preserves project knowledge across AI sessions, preventing context loss after resets. **New: semantic vector search** lets agents query project memory without loading all files into context β€” saving thousands of tokens per session.
248
+
249
+ #### Architecture
250
+
251
+ ```
252
+ Markdown files (git-committed) Vector Index (.gitignore'd)
253
+ ══════════════════════════════ ════════════════════════════
254
+ memory-bank/ memory-bank/.index/
255
+ β”œβ”€β”€ activeContext.md └── memory.db ← sqlite-vec + better-sqlite3
256
+ β”œβ”€β”€ progress.md β”‚
257
+ β”œβ”€β”€ systemPatterns.md β”‚ rebuilt from .md files
258
+ β”œβ”€β”€ ... β–Ό on demand
259
+ memory_search("tdd orchestrator")
260
+ β†’ top 5 relevant chunks (~500 tokens)
261
+ ```
262
+
263
+ **Source of truth:** Markdown files (git-versioned, human-readable).
264
+ **Search index:** `sqlite-vec` vector database (disposable, rebuildable, never committed).
265
+
266
+ #### MCP Tools (OpenCode)
267
+
268
+ | Tool | Purpose |
269
+ |---|---|
270
+ | `memory_search` | Semantic search across memory-bank + docs (query + topK) |
271
+ | `memory_update` | Incremental sync β€” only changed files (~2 seconds) |
272
+ | `memory_rebuild` | Full rebuild from markdown files (rare β€” clone/corruption) |
273
+ | `memory_get` | Read a specific memory file by name |
274
+ | `memory_stats` | Index statistics (vector count, DB size) |
275
+
276
+ #### File Structure
247
277
 
248
278
  ```
249
279
  projectbrief.md β†’ productContext.md / systemPatterns.md / techContext.md β†’ activeContext.md β†’ progress.md / tasks/
@@ -259,8 +289,35 @@ projectbrief.md β†’ productContext.md / systemPatterns.md / techContext.md β†’ a
259
289
  | `memory-bank/progress.md` | What works, what's left, known issues |
260
290
  | `memory-bank/tasks/` | Per-task tracking with `_index.md` |
261
291
 
292
+ #### Document Schema
293
+
294
+ All memory-bank files use YAML frontmatter for search indexing:
295
+
296
+ ```yaml
297
+ ---
298
+ id: activeContext
299
+ title: "Active Context"
300
+ updated: 2026-07-23
301
+ tags: [orchestrator, bootstrap, tdd]
302
+ entities: [vitest, graphify, memory-bank]
303
+ category: context
304
+ ---
305
+ ```
306
+
307
+ See `memory-bank/.vocabulary.json` for the controlled vocabulary (user-extensible).
308
+
309
+ **Auto-normalization:** `npm install` runs `normalize-memory.js` to add frontmatter to existing files.
310
+ **Pre-commit validation:** Husky hook rejects commits with invalid frontmatter.
311
+
262
312
  **Task commands:** `add task`, `update task [ID]`, `show tasks [filter]`
263
313
 
314
+ #### Token Efficiency
315
+
316
+ | Approach | Tokens per session |
317
+ |---|---|
318
+ | Read all memory files | ~5,000–15,000+ |
319
+ | Semantic search | ~500–2,000 (top 3–5 chunks) |
320
+
264
321
  ---
265
322
 
266
323
  ## πŸ“¦ Versioning
@@ -314,6 +371,20 @@ npm run test:unit:coverage
314
371
 
315
372
  # Watch mode for unit tests
316
373
  npm run test:unit:watch
374
+
375
+ # ─── Memory Bank (Vector Search) ───
376
+
377
+ # Build the vector search index (one-time, ~8 min for 5K files)
378
+ npm run memory:rebuild
379
+
380
+ # Incremental update (seconds β€” only changed files)
381
+ npm run memory:update
382
+
383
+ # Search project memory
384
+ npm run memory:search "tdd orchestrator status"
385
+
386
+ # Normalize document frontmatter (runs automatically on install)
387
+ npm run memory:normalize
317
388
  ```
318
389
 
319
390
  ### Testing
@@ -0,0 +1,52 @@
1
+ {
2
+ "categories": {
3
+ "brief": "Project scope, goals (projectbrief.md)",
4
+ "product": "Product context, UX goals (productContext.md)",
5
+ "architecture": "System patterns, design decisions, component relationships",
6
+ "tech": "Technology stack, dev setup, constraints",
7
+ "context": "Current focus, recent changes, next steps (activeContext.md)",
8
+ "progress": "What works, what's left, known issues (progress.md)",
9
+ "task": "Individual task tracking (tasks/TASK*.md)",
10
+ "decision": "ADR-style architecture/technical decisions"
11
+ },
12
+
13
+ "tags": {
14
+ "agent_roles": [
15
+ "designer", "architect", "researcher", "coder", "implementer",
16
+ "tester", "reviewer", "tracker", "deployer", "orchestrator"
17
+ ],
18
+ "workflow": [
19
+ "bootstrap", "setup", "configuration", "migration", "refactor",
20
+ "tdd", "feature-pipeline", "onboarding", "normalization",
21
+ "research", "spike", "implementation"
22
+ ],
23
+ "memory_ops": [
24
+ "decision", "discovery", "status-update", "task-plan", "retrospective"
25
+ ]
26
+ },
27
+
28
+ "entity_patterns": {
29
+ "vitest": ["vitest", "vitest\\.config", "@vitest\\/coverage"],
30
+ "playwright": ["playwright", "@playwright", "browser.*test"],
31
+ "graphify": ["graphify", "knowledge graph", "graph\\.json"],
32
+ "memory-bank": ["memory-bank", "memory bank", "activeContext", "systemPatterns"],
33
+ "husky": ["husky", "pre-commit", "git hook"],
34
+ "tdd-orchestrator": ["tdd orchestrator", "RED.*GREEN.*REFACTOR"],
35
+ "mcp-server": ["mcp", "modelcontextprotocol", "stdio", "mcp server"],
36
+ "sqlite-vec": ["sqlite-vec", "sqlite.vec", "better-sqlite3"],
37
+ "transformers.js": ["@xenova\\/transformers", "transformers\\.js", "all-MiniLM"],
38
+ "opencode": ["opencode", "OpenCode", "open code"],
39
+ "npm": ["npm", "npm package", "npm install"]
40
+ },
41
+
42
+ "file_category_map": {
43
+ "projectbrief": "brief",
44
+ "productContext": "product",
45
+ "systemPatterns": "architecture",
46
+ "techContext": "tech",
47
+ "activeContext": "context",
48
+ "progress": "progress"
49
+ },
50
+
51
+ "doc_types": ["spike", "architecture", "configuration", "orchestrator-log", "tracker-log", "decision"]
52
+ }
@@ -53,6 +53,11 @@
53
53
  "type": "remote",
54
54
  "url": "https://your-mcp-server.com/mcp",
55
55
  "enabled": false
56
+ },
57
+ "memory-bank": {
58
+ "type": "local",
59
+ "command": ["node", "scripts/mcp-memory-server.js"],
60
+ "enabled": true
56
61
  }
57
62
  },
58
63
  "permission": {
@@ -60,6 +65,7 @@
60
65
  "github_*": "allow",
61
66
  "mcp-everything_*": "allow",
62
67
  "angular-cli_*": "allow",
63
- "playwright_*": "allow"
68
+ "playwright_*": "allow",
69
+ "memory-bank_*": "allow"
64
70
  }
65
71
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@abarcenas/ai-workflow-template",
3
- "version": "1.25.0",
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.",
3
+ "version": "1.26.0",
4
+ "description": "A comprehensive template repository for AI-assisted development workflows using OpenCode. Provides structured agent pipelines, TDD orchestrator, memory bank with vector search, skills, prompts, and coding standards distributed as an npm package.",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "test": "playwright test",
@@ -14,7 +14,11 @@
14
14
  "test:unit:watch": "vitest",
15
15
  "prepare": "husky",
16
16
  "sync": "node ./scripts/sync.js",
17
- "postinstall": "node ./scripts/sync.js"
17
+ "postinstall": "node ./scripts/sync.js && node ./scripts/normalize-memory.js",
18
+ "memory:search": "node ./scripts/memory-cli.js search",
19
+ "memory:rebuild": "node ./scripts/memory-cli.js rebuild",
20
+ "memory:update": "node ./scripts/memory-cli.js update",
21
+ "memory:normalize": "node ./scripts/normalize-memory.js"
18
22
  },
19
23
  "repository": {
20
24
  "type": "git",
@@ -34,9 +38,12 @@
34
38
  "scripts/",
35
39
  ".opencode/agents/",
36
40
  ".opencode/commands/",
41
+ ".husky/",
42
+ "memory-bank/.vocabulary.json",
37
43
  "AGENTS.md",
38
44
  "opencode.mcp.example.json",
39
- "skills-lock.json"
45
+ "skills-lock.json",
46
+ ".husky/post-merge"
40
47
  ],
41
48
  "devDependencies": {
42
49
  "@playwright/test": "^1.59.1",
@@ -44,5 +51,13 @@
44
51
  "dotenv": "^17.4.2",
45
52
  "husky": "^9.0.0",
46
53
  "vitest": "^4.1.8"
54
+ },
55
+ "dependencies": {
56
+ "better-sqlite3": "^12.11.0",
57
+ "sqlite-vec": "^0.1.9",
58
+ "@modelcontextprotocol/sdk": "^1.0.0"
59
+ },
60
+ "optionalDependencies": {
61
+ "@xenova/transformers": "^2.17.0"
47
62
  }
48
63
  }
@@ -0,0 +1,243 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * mcp-memory-server.js β€” MCP server exposing memory-bank tools to OpenCode.
5
+ *
6
+ * Provides: memory_search, memory_rebuild, memory_get
7
+ * Uses stdio transport β€” OpenCode spawns this as a child process.
8
+ *
9
+ * Configuration (opencode.json):
10
+ * "memory-bank": {
11
+ * "type": "local",
12
+ * "command": ["node", "scripts/mcp-memory-server.js"],
13
+ * "enabled": true
14
+ * }
15
+ */
16
+
17
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
18
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
19
+ import {
20
+ CallToolRequestSchema,
21
+ ListToolsRequestSchema,
22
+ } from '@modelcontextprotocol/sdk/types.js';
23
+ import { search, rebuildIndex, incrementalUpdate, getFile, getStats } from './memory-index.js';
24
+
25
+ const VALID_FILES = [
26
+ 'activeContext', 'systemPatterns', 'techContext',
27
+ 'productContext', 'progress', 'projectbrief',
28
+ 'tracker-log', 'architecture-context', 'orchestrator-log',
29
+ 'spike-vector-db-memory', 'playwright-mcp-configuration',
30
+ ];
31
+
32
+ const server = new Server(
33
+ {
34
+ name: 'memory-bank',
35
+ version: '1.0.0',
36
+ },
37
+ {
38
+ capabilities: {
39
+ tools: {},
40
+ },
41
+ }
42
+ );
43
+
44
+ // ── List tools ───────────────────────────────────────────────────────
45
+
46
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
47
+ tools: [
48
+ {
49
+ name: 'memory_search',
50
+ description:
51
+ 'Semantic search across the project memory bank (activeContext, systemPatterns, progress, decisions, tasks). ' +
52
+ 'Returns ranked chunks with source file and similarity score. ' +
53
+ 'Use this instead of reading all memory files to save tokens.',
54
+ inputSchema: {
55
+ type: 'object',
56
+ properties: {
57
+ query: {
58
+ type: 'string',
59
+ description: 'Natural language query about project context, decisions, or status',
60
+ },
61
+ topK: {
62
+ type: 'number',
63
+ default: 5,
64
+ description: 'Number of results to return (1-20)',
65
+ },
66
+ sourceFile: {
67
+ type: 'string',
68
+ description: 'Optional: filter to a specific memory file (e.g., "activeContext", "systemPatterns")',
69
+ },
70
+ },
71
+ required: ['query'],
72
+ },
73
+ },
74
+ {
75
+ name: 'memory_rebuild',
76
+ description:
77
+ 'Full rebuild of the memory vector index from memory-bank/*.md and docs/*.md files. ' +
78
+ 'Re-indexes every file regardless of change. ' +
79
+ 'Use memory_update instead for faster incremental sync (only changed files). ' +
80
+ 'The index is disposable β€” rebuilding is safe.',
81
+ inputSchema: {
82
+ type: 'object',
83
+ properties: {},
84
+ },
85
+ },
86
+ {
87
+ name: 'memory_update',
88
+ description:
89
+ 'Incremental update β€” only re-indexes files whose content changed (SHA-256 content hash). ' +
90
+ 'Much faster than memory_rebuild for daily use (~2 seconds vs minutes). ' +
91
+ 'Run this after completing tasks, writing to memory-bank, or after git pull.',
92
+ inputSchema: {
93
+ type: 'object',
94
+ properties: {},
95
+ },
96
+ },
97
+ {
98
+ name: 'memory_get',
99
+ description:
100
+ 'Read the full content of a specific memory file by name. ' +
101
+ 'Use when you need the complete file, not just search snippets.',
102
+ inputSchema: {
103
+ type: 'object',
104
+ properties: {
105
+ file: {
106
+ type: 'string',
107
+ description: 'File ID without extension. One of: ' + VALID_FILES.join(', '),
108
+ enum: VALID_FILES,
109
+ },
110
+ },
111
+ required: ['file'],
112
+ },
113
+ },
114
+ {
115
+ name: 'memory_stats',
116
+ description: 'Get statistics about the memory index (vector count, file count, database size).',
117
+ inputSchema: {
118
+ type: 'object',
119
+ properties: {},
120
+ },
121
+ },
122
+ ],
123
+ }));
124
+
125
+ // ── Handle tool calls ────────────────────────────────────────────────
126
+
127
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
128
+ const { name, arguments: args } = request.params;
129
+
130
+ try {
131
+ switch (name) {
132
+ case 'memory_search': {
133
+ const results = await search(args.query, {
134
+ topK: args.topK || 5,
135
+ sourceFile: args.sourceFile || undefined,
136
+ });
137
+
138
+ if (results.length === 0) {
139
+ return {
140
+ content: [{
141
+ type: 'text',
142
+ text: 'No results found. The index may be empty. Try running memory_rebuild first.',
143
+ }],
144
+ };
145
+ }
146
+
147
+ const formatted = results
148
+ .map((r, i) =>
149
+ `### Result ${i + 1} (score: ${r.score})\n` +
150
+ `**File:** \`${r.source_file}\`\n\n${r.content}`
151
+ )
152
+ .join('\n\n---\n\n');
153
+
154
+ return {
155
+ content: [{ type: 'text', text: formatted }],
156
+ };
157
+ }
158
+
159
+ case 'memory_rebuild': {
160
+ const result = await rebuildIndex();
161
+ return {
162
+ content: [{
163
+ type: 'text',
164
+ text: `Index rebuilt successfully.\n` +
165
+ `- Chunks indexed: ${result.indexed}\n` +
166
+ `- Chunks skipped (unchanged): ${result.skipped}\n` +
167
+ `- Total vectors: ${result.stats.totalVectors}\n` +
168
+ `- Total files: ${result.stats.totalFiles}\n` +
169
+ `- DB size: ${result.stats.dbSize}`,
170
+ }],
171
+ };
172
+ }
173
+
174
+ case 'memory_update': {
175
+ const result = await incrementalUpdate();
176
+ return {
177
+ content: [{
178
+ type: 'text',
179
+ text: `Index updated incrementally.\n` +
180
+ `- Chunks re-indexed: ${result.indexed}\n` +
181
+ `- Chunks cached (unchanged): ${result.skipped}\n` +
182
+ (result.changed.length > 0
183
+ ? `- Changed files: ${result.changed.join(', ')}\n`
184
+ : '- No files changed.\n') +
185
+ `- Total vectors: ${(await getStats()).totalVectors}`,
186
+ }],
187
+ };
188
+ }
189
+
190
+ case 'memory_get': {
191
+ const content = getFile(args.file);
192
+ if (!content) {
193
+ return {
194
+ content: [{
195
+ type: 'text',
196
+ text: `File not found: ${args.file}.md. Available files: ${VALID_FILES.join(', ')}`,
197
+ }],
198
+ };
199
+ }
200
+ return {
201
+ content: [{ type: 'text', text: content }],
202
+ };
203
+ }
204
+
205
+ case 'memory_stats': {
206
+ const stats = getStats();
207
+ return {
208
+ content: [{
209
+ type: 'text',
210
+ text: `**Memory Index Stats**\n` +
211
+ `- Total vectors: ${stats.totalVectors}\n` +
212
+ `- Total files indexed: ${stats.totalFiles}\n` +
213
+ `- Database size: ${stats.dbSize}`,
214
+ }],
215
+ };
216
+ }
217
+
218
+ default:
219
+ throw new Error(`Unknown tool: ${name}`);
220
+ }
221
+ } catch (err) {
222
+ return {
223
+ content: [{
224
+ type: 'text',
225
+ text: `Error: ${err.message}`,
226
+ }],
227
+ isError: true,
228
+ };
229
+ }
230
+ });
231
+
232
+ // ── Start server ─────────────────────────────────────────────────────
233
+
234
+ async function main() {
235
+ const transport = new StdioServerTransport();
236
+ await server.connect(transport);
237
+ console.error('[memory-bank-mcp] Server started via stdio transport');
238
+ }
239
+
240
+ main().catch((err) => {
241
+ console.error('[memory-bank-mcp] Fatal error:', err);
242
+ process.exit(1);
243
+ });
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * memory-cli.js β€” CLI for manual memory index operations.
5
+ *
6
+ * Usage:
7
+ * node scripts/memory-cli.js search "<query>" [--topK 5] [--file activeContext]
8
+ * node scripts/memory-cli.js rebuild
9
+ * node scripts/memory-cli.js update (incremental)
10
+ * node scripts/memory-cli.js get <fileId>
11
+ * node scripts/memory-cli.js stats
12
+ */
13
+
14
+ import { search, rebuildIndex, incrementalUpdate, getFile, getStats, closeDB } from './memory-index.js';
15
+
16
+ const args = process.argv.slice(2);
17
+ const command = args[0];
18
+
19
+ function parseFlags(argv) {
20
+ const flags = {};
21
+ const positional = [];
22
+ for (let i = 0; i < argv.length; i++) {
23
+ if (argv[i] === '--topK' && i + 1 < argv.length) {
24
+ flags.topK = parseInt(argv[++i], 10);
25
+ } else if (argv[i] === '--file' && i + 1 < argv.length) {
26
+ flags.sourceFile = argv[++i];
27
+ } else {
28
+ positional.push(argv[i]);
29
+ }
30
+ }
31
+ return { flags, positional };
32
+ }
33
+
34
+ async function main() {
35
+ try {
36
+ switch (command) {
37
+ case 'search': {
38
+ const { flags, positional } = parseFlags(args.slice(1));
39
+ const query = positional.join(' ');
40
+ if (!query) {
41
+ console.error('Usage: node scripts/memory-cli.js search "<query>" [--topK 5] [--file name]');
42
+ process.exit(1);
43
+ }
44
+
45
+ const results = await search(query, {
46
+ topK: flags.topK || 5,
47
+ sourceFile: flags.sourceFile,
48
+ });
49
+
50
+ if (results.length === 0) {
51
+ console.log(JSON.stringify({ results: [], hint: 'No results. Try running "rebuild" first.' }));
52
+ } else {
53
+ console.log(JSON.stringify({ results }, null, 2));
54
+ }
55
+ break;
56
+ }
57
+
58
+ case 'rebuild': {
59
+ const result = await rebuildIndex();
60
+ console.log(JSON.stringify({ status: 'ok', ...result }, null, 2));
61
+ break;
62
+ }
63
+
64
+ case 'update': {
65
+ const result = await incrementalUpdate();
66
+ console.log(JSON.stringify({ status: 'ok', ...result }, null, 2));
67
+ break;
68
+ }
69
+
70
+ case 'get': {
71
+ const fileId = args[1];
72
+ if (!fileId) {
73
+ console.error('Usage: node scripts/memory-cli.js get <fileId>');
74
+ process.exit(1);
75
+ }
76
+ const content = getFile(fileId);
77
+ if (content) {
78
+ console.log(content);
79
+ } else {
80
+ console.error(`File not found: ${fileId}`);
81
+ process.exit(1);
82
+ }
83
+ break;
84
+ }
85
+
86
+ case 'stats': {
87
+ console.log(JSON.stringify(getStats(), null, 2));
88
+ break;
89
+ }
90
+
91
+ default:
92
+ console.error(`Usage: node scripts/memory-cli.js <search|rebuild|update|get|stats> [args...]`);
93
+ console.error('');
94
+ console.error('Commands:');
95
+ console.error(' search "<query>" [--topK N] [--file name] Semantic search');
96
+ console.error(' rebuild Full index rebuild');
97
+ console.error(' update Incremental update');
98
+ console.error(' get <fileId> Read a memory file');
99
+ console.error(' stats Index statistics');
100
+ process.exit(1);
101
+ }
102
+ } catch (err) {
103
+ console.error(`Error: ${err.message}`);
104
+ process.exit(1);
105
+ } finally {
106
+ closeDB();
107
+ }
108
+ }
109
+
110
+ main();