@abarcenas/ai-workflow-template 1.49.0 → 2.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 (35) hide show
  1. package/.agents/instructions/knowledgebase.instructions.md +2 -2
  2. package/.agents/instructions/learned-knowledge.instructions.md +47 -2
  3. package/.agents/instructions/memory-schema.instructions.md +1 -1
  4. package/.github/workflows/npm-publish.yml +69 -1
  5. package/.opencode/plugins/graphify.js +22 -0
  6. package/LICENSE +15 -0
  7. package/README.md +56 -22
  8. package/memory-bank/.vocabulary.json +3 -0
  9. package/opencode.mcp.example.json +13 -0
  10. package/package.json +20 -10
  11. package/scripts/bump-version.js +18 -2
  12. package/scripts/mcp/playwright-mcp-launcher.js +45 -2
  13. package/scripts/bump-version.test.js +0 -238
  14. package/scripts/knowledgebase-cli.test.js +0 -576
  15. package/scripts/knowledgebase-index.test.js +0 -759
  16. package/scripts/mcp/playwright-mcp-launcher.test.js +0 -217
  17. package/scripts/mcp-knowledgebase-server.test.js +0 -745
  18. package/scripts/mcp-memory-server.test.js +0 -398
  19. package/scripts/memory-cli.test.js +0 -297
  20. package/scripts/memory-index.test.js +0 -1173
  21. package/scripts/normalize-memory.test.js +0 -753
  22. package/scripts/setup/discover.test.js +0 -530
  23. package/scripts/setup/hooks.test.js +0 -722
  24. package/scripts/setup/husky-init.test.js +0 -311
  25. package/scripts/setup/index.test.js +0 -1021
  26. package/scripts/setup/knowledgebase.test.js +0 -352
  27. package/scripts/setup/prepare.test.js +0 -718
  28. package/scripts/setup/skills-phase.test.js +0 -1274
  29. package/scripts/setup/sync-phase.test.js +0 -373
  30. package/scripts/setup/ui.test.js +0 -579
  31. package/scripts/setup/utils.test.js +0 -586
  32. package/scripts/sync-unit.test.js +0 -1881
  33. package/scripts/sync.test.js +0 -472
  34. package/scripts/validate-memory-schema.test.js +0 -328
  35. package/scripts/vocab-sync.test.js +0 -902
@@ -1,311 +0,0 @@
1
- // ═════════════════════════════════════════════════════════════════════════════
2
- // husky-init.test.js — Unit tests for the husky initialization phase
3
- // ═════════════════════════════════════════════════════════════════════════════
4
- //
5
- // The module resolves husky from the CONSUMER's node_modules and calls its
6
- // programmatic API. Tests build throwaway consumer projects in temp dirs with
7
- // a fake `node_modules/husky` fixture, so the real repository is never touched.
8
- // `child_process.execSync` and `./ui.js` are mocked; `fs` is real.
9
- //
10
- // Covered scenarios:
11
- // 1. dryRun wins over every other flag → action=dry-run, nothing runs
12
- // 2. CI environment → action=skipped + warning
13
- // 3. No .git directory → action=skipped + console.warn
14
- // 4. husky not installed (resolve fails) → action=skipped + install hint
15
- // 5. husky module throws on import → action=skipped (same catch)
16
- // 6. core.hooksPath unset succeeds → execSync called, action=initialized
17
- // 7. core.hooksPath unset throws → swallowed, action=initialized
18
- // 8. husky() throws → action=failed with message
19
- // 9. .husky/_/h missing post-condition → action=failed
20
- // 10. idempotent second run → action=initialized both times
21
- // 11. verbose flag forwards progress messages
22
- // ═════════════════════════════════════════════════════════════════════════════
23
-
24
- import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest'
25
- import {
26
- existsSync,
27
- mkdirSync,
28
- mkdtempSync,
29
- realpathSync,
30
- rmSync,
31
- writeFileSync,
32
- } from 'fs'
33
- import { join } from 'path'
34
- import { tmpdir } from 'os'
35
-
36
- vi.mock('child_process', () => ({
37
- execSync: vi.fn(),
38
- }))
39
-
40
- vi.mock('./ui.js', () => ({
41
- info: vi.fn(),
42
- warn: vi.fn(),
43
- verbose: vi.fn(),
44
- }))
45
-
46
- import { execSync } from 'child_process'
47
- import { info, warn, verbose } from './ui.js'
48
- import { initHusky } from './husky-init.js'
49
-
50
- // ── Fake husky fixtures ───────────────────────────────────────────────────
51
-
52
- const HUSKY_IMPLEMENTATIONS = {
53
- success: `
54
- const fs = require('fs')
55
- const path = require('path')
56
- module.exports = function husky(root) {
57
- const dir = path.join(root, '.husky', '_')
58
- fs.mkdirSync(dir, { recursive: true })
59
- fs.writeFileSync(path.join(dir, 'h'), '#!/bin/sh\\n')
60
- }
61
- `,
62
- throws: `
63
- module.exports = function husky() {
64
- throw new Error('husky boom')
65
- }
66
- `,
67
- noop: `
68
- module.exports = function husky() {}
69
- `,
70
- importThrows: `
71
- throw new Error('corrupt husky module')
72
- `,
73
- }
74
-
75
- const tempDirs = new Set()
76
-
77
- /**
78
- * Creates a temporary consumer project.
79
- *
80
- * @param {object} [options]
81
- * @param {string} [options.husky='success'] - Fake husky behaviour key.
82
- * @param {boolean} [options.withPackageJson=true]
83
- * @returns {string} Absolute consumer root.
84
- */
85
- function createConsumer({ husky = 'success', withPackageJson = true } = {}) {
86
- const dir = mkdtempSync(join(realpathSync(tmpdir()), 'husky-init-test-'))
87
- tempDirs.add(dir)
88
-
89
- if (withPackageJson) {
90
- writeFileSync(
91
- join(dir, 'package.json'),
92
- JSON.stringify({ name: 'fixture', version: '1.0.0' }, null, 2) + '\n',
93
- )
94
- }
95
-
96
- if (husky) {
97
- const huskyDir = join(dir, 'node_modules', 'husky')
98
- mkdirSync(huskyDir, { recursive: true })
99
- writeFileSync(
100
- join(huskyDir, 'package.json'),
101
- JSON.stringify({ name: 'husky', version: '9.0.0', main: 'index.js' }),
102
- )
103
- writeFileSync(join(huskyDir, 'index.js'), HUSKY_IMPLEMENTATIONS[husky])
104
- }
105
-
106
- return dir
107
- }
108
-
109
- /**
110
- * Builds the context object accepted by initHusky().
111
- *
112
- * @param {string} consumerRoot
113
- * @param {object} [overrides]
114
- * @returns {object}
115
- */
116
- function makeContext(consumerRoot, overrides = {}) {
117
- return {
118
- consumerRoot,
119
- hasGit: true,
120
- dryRun: false,
121
- isCI: false,
122
- verbose: false,
123
- ...overrides,
124
- }
125
- }
126
-
127
- describe('initHusky', () => {
128
- let consoleWarnSpy
129
-
130
- beforeEach(() => {
131
- vi.clearAllMocks()
132
- execSync.mockReturnValue('')
133
- consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
134
- })
135
-
136
- afterEach(() => {
137
- consoleWarnSpy.mockRestore()
138
- for (const dir of tempDirs) {
139
- try {
140
- rmSync(dir, { recursive: true, force: true })
141
- } catch {
142
- /* best-effort cleanup */
143
- }
144
- }
145
- tempDirs.clear()
146
- })
147
-
148
- // ── 1. dry-run ───────────────────────────────────────────────────────────
149
-
150
- it('dryRun: reports the planned action and touches nothing (wins over other flags)', async () => {
151
- const dir = createConsumer()
152
-
153
- const result = await initHusky(
154
- makeContext(dir, { dryRun: true, isCI: true, hasGit: false }),
155
- )
156
-
157
- expect(result).toEqual({
158
- action: 'dry-run',
159
- message: 'Would initialize husky',
160
- })
161
- expect(info).toHaveBeenCalledWith(
162
- '[setup] Would initialize husky (generate .husky/_/ shims, set core.hooksPath)',
163
- )
164
- expect(execSync).not.toHaveBeenCalled()
165
- expect(existsSync(join(dir, '.husky'))).toBe(false)
166
- })
167
-
168
- // ── 2. CI ────────────────────────────────────────────────────────────────
169
-
170
- it('CI environment: skips with a warning', async () => {
171
- const dir = createConsumer()
172
-
173
- const result = await initHusky(makeContext(dir, { isCI: true }))
174
-
175
- expect(result).toEqual({
176
- action: 'skipped',
177
- message: 'Skipping husky init in CI environment',
178
- })
179
- expect(warn).toHaveBeenCalledWith(
180
- '[setup] CI environment detected — skipping husky init',
181
- )
182
- expect(execSync).not.toHaveBeenCalled()
183
- })
184
-
185
- // ── 3. No git repository ────────────────────────────────────────────────
186
-
187
- it('no .git directory: skips and warns on the console', async () => {
188
- const dir = createConsumer()
189
-
190
- const result = await initHusky(makeContext(dir, { hasGit: false }))
191
-
192
- expect(result).toEqual({
193
- action: 'skipped',
194
- message: 'No .git directory — not a git repository',
195
- })
196
- expect(consoleWarnSpy).toHaveBeenCalledWith(
197
- '[setup] No .git directory — not a git repository',
198
- )
199
- expect(execSync).not.toHaveBeenCalled()
200
- })
201
-
202
- // ── 4. husky not installed ──────────────────────────────────────────────
203
-
204
- it('husky not installed: skips with an install hint', async () => {
205
- const dir = createConsumer({ husky: null })
206
-
207
- const result = await initHusky(makeContext(dir))
208
-
209
- expect(result.action).toBe('skipped')
210
- expect(result.message).toContain('npm install husky --save-dev')
211
- expect(verbose).toHaveBeenCalledWith(
212
- false,
213
- 'husky not found — skipping init',
214
- )
215
- expect(execSync).not.toHaveBeenCalled()
216
- })
217
-
218
- // ── 5. husky import failure ─────────────────────────────────────────────
219
-
220
- it('corrupt husky module: import throws, treated as not installed', async () => {
221
- const dir = createConsumer({ husky: 'importThrows' })
222
-
223
- const result = await initHusky(makeContext(dir))
224
-
225
- expect(result.action).toBe('skipped')
226
- expect(result.message).toContain('husky is not installed')
227
- })
228
-
229
- // ── 6. Success ───────────────────────────────────────────────────────────
230
-
231
- it('success: clears core.hooksPath and verifies .husky/_/h', async () => {
232
- const dir = createConsumer()
233
-
234
- const result = await initHusky(makeContext(dir))
235
-
236
- expect(result).toEqual({
237
- action: 'initialized',
238
- message: 'Husky initialized successfully',
239
- })
240
- expect(execSync).toHaveBeenCalledWith(
241
- 'git config --local --unset core.hooksPath',
242
- { cwd: dir, stdio: 'pipe' },
243
- )
244
- expect(existsSync(join(dir, '.husky', '_', 'h'))).toBe(true)
245
- })
246
-
247
- // ── 7. Unset failure is swallowed ───────────────────────────────────────
248
-
249
- it('unset failure: continues and still initializes', async () => {
250
- const dir = createConsumer()
251
- execSync.mockImplementation(() => {
252
- throw new Error('key does not exist')
253
- })
254
-
255
- const result = await initHusky(makeContext(dir))
256
-
257
- expect(result.action).toBe('initialized')
258
- expect(existsSync(join(dir, '.husky', '_', 'h'))).toBe(true)
259
- })
260
-
261
- // ── 8. husky() throws ───────────────────────────────────────────────────
262
-
263
- it('husky() throwing: returns action=failed with the error message', async () => {
264
- const dir = createConsumer({ husky: 'throws' })
265
-
266
- const result = await initHusky(makeContext(dir))
267
-
268
- expect(result).toEqual({ action: 'failed', message: 'husky boom' })
269
- })
270
-
271
- // ── 9. Post-condition failure ───────────────────────────────────────────
272
-
273
- it('post-condition: missing .husky/_/h returns action=failed', async () => {
274
- const dir = createConsumer({ husky: 'noop' })
275
-
276
- const result = await initHusky(makeContext(dir))
277
-
278
- expect(result.action).toBe('failed')
279
- expect(result.message).toContain('.husky/_/h not found after init')
280
- })
281
-
282
- // ── 10. Idempotency ─────────────────────────────────────────────────────
283
-
284
- it('is idempotent: a second run still reports initialized', async () => {
285
- const dir = createConsumer()
286
-
287
- const first = await initHusky(makeContext(dir))
288
- const second = await initHusky(makeContext(dir))
289
-
290
- expect(first.action).toBe('initialized')
291
- expect(second.action).toBe('initialized')
292
- expect(existsSync(join(dir, '.husky', '_', 'h'))).toBe(true)
293
- })
294
-
295
- // ── 11. Verbose progress ────────────────────────────────────────────────
296
-
297
- it('verbose: emits progress messages for each step', async () => {
298
- const dir = createConsumer()
299
-
300
- await initHusky(makeContext(dir, { verbose: true }))
301
-
302
- const messages = verbose.mock.calls.map(([, message]) => message)
303
- expect(verbose).toHaveBeenCalledWith(
304
- true,
305
- 'Resolving husky from consumer node_modules…',
306
- )
307
- expect(messages).toContain('Clearing stale core.hooksPath…')
308
- expect(messages).toContain('Calling husky()…')
309
- expect(messages).toContain('Verifying .husky/_/h exists…')
310
- })
311
- })