@abarcenas/ai-workflow-template 1.45.0 → 1.47.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 (49) hide show
  1. package/.agents/config/README.md +43 -0
  2. package/.agents/config/domain.md +37 -0
  3. package/.agents/instructions/learned-knowledge.instructions.md +128 -0
  4. package/.agents/instructions/memory-bank.instructions.md +11 -0
  5. package/.agents/skills/domain-modeling/ADR-FORMAT.md +47 -0
  6. package/.agents/skills/domain-modeling/GLOSSARY-FORMAT.md +60 -0
  7. package/.agents/skills/domain-modeling/SKILL.md +74 -0
  8. package/.agents/skills/grill-me/SKILL.md +7 -0
  9. package/.agents/skills/grill-with-docs/SKILL.md +7 -0
  10. package/.agents/skills/grilling/SKILL.md +28 -0
  11. package/.agents/skills/prototype/LOGIC.md +67 -0
  12. package/.agents/skills/prototype/SKILL.md +26 -0
  13. package/.agents/skills/prototype/UI.md +112 -0
  14. package/.github/workflows/npm-publish.yml +10 -0
  15. package/.github/workflows/pr-test.yml +31 -0
  16. package/.opencode/agents/orchestrator/orchestrator.agent.md +68 -3
  17. package/.opencode/agents/scope.agent.md +299 -0
  18. package/.prettierignore +26 -0
  19. package/AGENTS.md +46 -3
  20. package/README.md +29 -2
  21. package/opencode.mcp.example.json +1 -0
  22. package/package.json +3 -1
  23. package/scripts/bump-version.test.js +238 -0
  24. package/scripts/mcp/playwright-mcp-launcher.js +55 -29
  25. package/scripts/mcp/playwright-mcp-launcher.test.js +217 -0
  26. package/scripts/mcp-memory-server.js +93 -76
  27. package/scripts/mcp-memory-server.test.js +398 -0
  28. package/scripts/memory-cli.test.js +297 -0
  29. package/scripts/memory-index.js +119 -31
  30. package/scripts/memory-index.test.js +1173 -0
  31. package/scripts/normalize-memory.js +26 -1
  32. package/scripts/normalize-memory.test.js +753 -0
  33. package/scripts/setup/discover.test.js +124 -2
  34. package/scripts/setup/hooks.test.js +188 -1
  35. package/scripts/setup/husky-init.test.js +311 -0
  36. package/scripts/setup/index.js +35 -5
  37. package/scripts/setup/index.test.js +279 -0
  38. package/scripts/setup/knowledgebase.test.js +100 -0
  39. package/scripts/setup/skills-phase.js +568 -0
  40. package/scripts/setup/skills-phase.test.js +1274 -0
  41. package/scripts/setup/sync-phase.js +6 -1
  42. package/scripts/setup/sync-phase.test.js +180 -2
  43. package/scripts/setup/ui.test.js +579 -0
  44. package/scripts/setup/utils.test.js +586 -0
  45. package/scripts/sync-unit.test.js +1881 -0
  46. package/scripts/sync.js +816 -239
  47. package/scripts/sync.test.js +283 -0
  48. package/scripts/vocab-sync.test.js +88 -0
  49. package/skills-lock.json +90 -0
@@ -0,0 +1,217 @@
1
+ // ═════════════════════════════════════════════════════════════════════════════
2
+ // playwright-mcp-launcher.test.js — Unit tests for the MCP launcher
3
+ // ═════════════════════════════════════════════════════════════════════════════
4
+ //
5
+ // `spawn` is mocked so no MCP server is ever launched. Signal handling and
6
+ // exit-code propagation are exercised through injected fakes (an EventEmitter
7
+ // double for the process and a fake child), plus a direct-run import that
8
+ // verifies the CLI bootstrap branch.
9
+ //
10
+ // Covered scenarios:
11
+ // 1. default args (no env flags)
12
+ // 2. HEADLESS=true adds --headless
13
+ // 3. HEADLESS=false does not
14
+ // 4. VIEWPORT adds --viewport <size>
15
+ // 5. HEADLESS + VIEWPORT together
16
+ // 6. spawn is called as `npx <args>` with inherited stdio + shell
17
+ // 7. exit code 0 is forwarded
18
+ // 8. non-zero exit code is forwarded
19
+ // 9. missing exit code falls back to 0
20
+ // 10. SIGINT / SIGTERM are forwarded to the child
21
+ // 11. launch returns the child
22
+ // 12. direct run auto-launches; imported elsewhere it does not
23
+ // ═════════════════════════════════════════════════════════════════════════════
24
+
25
+ import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest'
26
+ import { EventEmitter } from 'node:events'
27
+ import { fileURLToPath } from 'node:url'
28
+
29
+ const cp = vi.hoisted(() => ({ spawn: vi.fn() }))
30
+
31
+ vi.mock('node:child_process', () => ({
32
+ spawn: cp.spawn,
33
+ }))
34
+
35
+ import { spawn } from 'node:child_process'
36
+ import { buildArgs, launch } from './playwright-mcp-launcher.js'
37
+
38
+ /**
39
+ * Creates a fake ChildProcess.
40
+ *
41
+ * @returns {object} Fake child with kill/on and a helper to emit 'exit'.
42
+ */
43
+ function makeChild() {
44
+ const handlers = {}
45
+ return {
46
+ kill: vi.fn(),
47
+ on: vi.fn((event, handler) => {
48
+ handlers[event] = handler
49
+ }),
50
+ emitExit(code) {
51
+ handlers.exit?.(code)
52
+ },
53
+ }
54
+ }
55
+
56
+ describe('playwright-mcp-launcher', () => {
57
+ let child
58
+
59
+ beforeEach(() => {
60
+ vi.clearAllMocks()
61
+ child = makeChild()
62
+ spawn.mockReturnValue(child)
63
+ })
64
+
65
+ afterEach(() => {
66
+ vi.restoreAllMocks()
67
+ })
68
+
69
+ // ── buildArgs ────────────────────────────────────────────────────────────
70
+
71
+ it('buildArgs: returns only the package when no env flags are set', () => {
72
+ expect(buildArgs({})).toEqual(['@playwright/mcp@latest'])
73
+ })
74
+
75
+ it('buildArgs: HEADLESS=true adds --headless', () => {
76
+ expect(buildArgs({ HEADLESS: 'true' })).toEqual([
77
+ '@playwright/mcp@latest',
78
+ '--headless',
79
+ ])
80
+ })
81
+
82
+ it('buildArgs: HEADLESS=false does not add --headless', () => {
83
+ expect(buildArgs({ HEADLESS: 'false' })).toEqual(['@playwright/mcp@latest'])
84
+ })
85
+
86
+ it('buildArgs: VIEWPORT adds a two-token viewport flag', () => {
87
+ expect(buildArgs({ VIEWPORT: '1280x720' })).toEqual([
88
+ '@playwright/mcp@latest',
89
+ '--viewport',
90
+ '1280x720',
91
+ ])
92
+ })
93
+
94
+ it('buildArgs: combines headless and viewport flags', () => {
95
+ expect(buildArgs({ HEADLESS: 'true', VIEWPORT: '800x600' })).toEqual([
96
+ '@playwright/mcp@latest',
97
+ '--headless',
98
+ '--viewport',
99
+ '800x600',
100
+ ])
101
+ })
102
+
103
+ // ── launch ───────────────────────────────────────────────────────────────
104
+
105
+ it('launch: spawns npx with inherited stdio and shell', () => {
106
+ launch({ env: {}, proc: new EventEmitter(), exit: vi.fn() })
107
+
108
+ expect(spawn).toHaveBeenCalledWith('npx', ['@playwright/mcp@latest'], {
109
+ stdio: 'inherit',
110
+ shell: true,
111
+ })
112
+ })
113
+
114
+ it('launch: returns the spawned child', () => {
115
+ const returned = launch({
116
+ env: {},
117
+ proc: new EventEmitter(),
118
+ exit: vi.fn(),
119
+ })
120
+
121
+ expect(returned).toBe(child)
122
+ })
123
+
124
+ it('launch: forwards exit code 0', () => {
125
+ const exit = vi.fn()
126
+ launch({ env: {}, proc: new EventEmitter(), exit })
127
+
128
+ child.emitExit(0)
129
+
130
+ expect(exit).toHaveBeenCalledWith(0)
131
+ })
132
+
133
+ it('launch: forwards a non-zero exit code', () => {
134
+ const exit = vi.fn()
135
+ launch({ env: {}, proc: new EventEmitter(), exit })
136
+
137
+ child.emitExit(5)
138
+
139
+ expect(exit).toHaveBeenCalledWith(5)
140
+ })
141
+
142
+ it('launch: falls back to 0 when no exit code is provided', () => {
143
+ const exit = vi.fn()
144
+ launch({ env: {}, proc: new EventEmitter(), exit })
145
+
146
+ child.emitExit(undefined)
147
+
148
+ expect(exit).toHaveBeenCalledWith(0)
149
+ })
150
+
151
+ it('launch: forwards SIGINT to the child', () => {
152
+ const proc = new EventEmitter()
153
+ launch({ env: {}, proc, exit: vi.fn() })
154
+
155
+ proc.emit('SIGINT')
156
+
157
+ expect(child.kill).toHaveBeenCalledWith('SIGINT')
158
+ })
159
+
160
+ it('launch: forwards SIGTERM to the child', () => {
161
+ const proc = new EventEmitter()
162
+ launch({ env: {}, proc, exit: vi.fn() })
163
+
164
+ proc.emit('SIGTERM')
165
+
166
+ expect(child.kill).toHaveBeenCalledWith('SIGTERM')
167
+ })
168
+
169
+ // ── Direct-run guard ─────────────────────────────────────────────────────
170
+
171
+ it('direct run: auto-launches when argv[1] is this module', async () => {
172
+ const modulePath = fileURLToPath(
173
+ new URL('./playwright-mcp-launcher.js', import.meta.url),
174
+ )
175
+ const originalArgv1 = process.argv[1]
176
+ const intListeners = process.listeners('SIGINT')
177
+ const termListeners = process.listeners('SIGTERM')
178
+ process.argv[1] = modulePath
179
+
180
+ try {
181
+ vi.resetModules()
182
+ await import('./playwright-mcp-launcher.js')
183
+
184
+ expect(spawn).toHaveBeenCalledWith(
185
+ 'npx',
186
+ expect.arrayContaining(['@playwright/mcp@latest']),
187
+ { stdio: 'inherit', shell: true },
188
+ )
189
+ } finally {
190
+ process.argv[1] = originalArgv1
191
+ for (const listener of process.listeners('SIGINT')) {
192
+ if (!intListeners.includes(listener)) {
193
+ process.removeListener('SIGINT', listener)
194
+ }
195
+ }
196
+ for (const listener of process.listeners('SIGTERM')) {
197
+ if (!termListeners.includes(listener)) {
198
+ process.removeListener('SIGTERM', listener)
199
+ }
200
+ }
201
+ }
202
+ })
203
+
204
+ it('imported with an empty argv[1]: does not launch', async () => {
205
+ const originalArgv1 = process.argv[1]
206
+ process.argv[1] = ''
207
+
208
+ try {
209
+ vi.resetModules()
210
+ await import('./playwright-mcp-launcher.js')
211
+
212
+ expect(spawn).not.toHaveBeenCalled()
213
+ } finally {
214
+ process.argv[1] = originalArgv1
215
+ }
216
+ })
217
+ })
@@ -20,6 +20,8 @@ import {
20
20
  CallToolRequestSchema,
21
21
  ListToolsRequestSchema,
22
22
  } from '@modelcontextprotocol/sdk/types.js';
23
+ import { resolve } from 'path';
24
+ import { fileURLToPath } from 'url';
23
25
  import { search, rebuildIndex, incrementalUpdate, getFile, getStats } from './memory-index.js';
24
26
 
25
27
  const VALID_FILES = [
@@ -43,88 +45,92 @@ const server = new Server(
43
45
 
44
46
  // ── List tools ───────────────────────────────────────────────────────
45
47
 
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")',
48
+ export async function listTools() {
49
+ return {
50
+ tools: [
51
+ {
52
+ name: 'memory_search',
53
+ description:
54
+ 'Semantic search across the project memory bank (activeContext, systemPatterns, progress, decisions, tasks). ' +
55
+ 'Returns ranked chunks with source file and similarity score. ' +
56
+ 'Use this instead of reading all memory files to save tokens.',
57
+ inputSchema: {
58
+ type: 'object',
59
+ properties: {
60
+ query: {
61
+ type: 'string',
62
+ description: 'Natural language query about project context, decisions, or status',
63
+ },
64
+ topK: {
65
+ type: 'number',
66
+ default: 5,
67
+ description: 'Number of results to return (1-20)',
68
+ },
69
+ sourceFile: {
70
+ type: 'string',
71
+ description: 'Optional: filter to a specific memory file (e.g., "activeContext", "systemPatterns")',
72
+ },
69
73
  },
74
+ required: ['query'],
70
75
  },
71
- required: ['query'],
72
76
  },
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: {},
77
+ {
78
+ name: 'memory_rebuild',
79
+ description:
80
+ 'Full rebuild of the memory vector index from memory-bank/*.md and docs/*.md files. ' +
81
+ 'Re-indexes every file regardless of change. ' +
82
+ 'Use memory_update instead for faster incremental sync (only changed files). ' +
83
+ 'The index is disposable — rebuilding is safe.',
84
+ inputSchema: {
85
+ type: 'object',
86
+ properties: {},
87
+ },
84
88
  },
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: {},
89
+ {
90
+ name: 'memory_update',
91
+ description:
92
+ 'Incremental update — only re-indexes files whose content changed (SHA-256 content hash). ' +
93
+ 'Much faster than memory_rebuild for daily use (~2 seconds vs minutes). ' +
94
+ 'Run this after completing tasks, writing to memory-bank, or after git pull.',
95
+ inputSchema: {
96
+ type: 'object',
97
+ properties: {},
98
+ },
95
99
  },
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,
100
+ {
101
+ name: 'memory_get',
102
+ description:
103
+ 'Read the full content of a specific memory file by name. ' +
104
+ 'Use when you need the complete file, not just search snippets.',
105
+ inputSchema: {
106
+ type: 'object',
107
+ properties: {
108
+ file: {
109
+ type: 'string',
110
+ description: 'File ID without extension. One of: ' + VALID_FILES.join(', '),
111
+ enum: VALID_FILES,
112
+ },
109
113
  },
114
+ required: ['file'],
110
115
  },
111
- required: ['file'],
112
116
  },
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: {},
117
+ {
118
+ name: 'memory_stats',
119
+ description: 'Get statistics about the memory index (vector count, file count, database size).',
120
+ inputSchema: {
121
+ type: 'object',
122
+ properties: {},
123
+ },
120
124
  },
121
- },
122
- ],
123
- }));
125
+ ],
126
+ };
127
+ }
128
+
129
+ server.setRequestHandler(ListToolsRequestSchema, listTools);
124
130
 
125
131
  // ── Handle tool calls ────────────────────────────────────────────────
126
132
 
127
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
133
+ export async function handleToolCall(request) {
128
134
  const { name, arguments: args } = request.params;
129
135
 
130
136
  try {
@@ -227,17 +233,28 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
227
233
  isError: true,
228
234
  };
229
235
  }
230
- });
236
+ }
237
+
238
+ server.setRequestHandler(CallToolRequestSchema, handleToolCall);
231
239
 
232
240
  // ── Start server ─────────────────────────────────────────────────────
233
241
 
234
- async function main() {
242
+ export async function main() {
235
243
  const transport = new StdioServerTransport();
236
244
  await server.connect(transport);
237
245
  console.error('[memory-bank-mcp] Server started via stdio transport');
238
246
  }
239
247
 
240
- main().catch((err) => {
241
- console.error('[memory-bank-mcp] Fatal error:', err);
242
- process.exit(1);
243
- });
248
+ export function bootstrap() {
249
+ return main().catch((err) => {
250
+ console.error('[memory-bank-mcp] Fatal error:', err);
251
+ process.exit(1);
252
+ });
253
+ }
254
+
255
+ const isDirectRun =
256
+ process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url);
257
+
258
+ if (isDirectRun) {
259
+ bootstrap();
260
+ }