@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.
- package/.agents/instructions/knowledgebase.instructions.md +2 -2
- package/.agents/instructions/learned-knowledge.instructions.md +47 -2
- package/.agents/instructions/memory-schema.instructions.md +1 -1
- package/.github/workflows/npm-publish.yml +69 -1
- package/.opencode/plugins/graphify.js +22 -0
- package/LICENSE +15 -0
- package/README.md +56 -22
- package/memory-bank/.vocabulary.json +3 -0
- package/opencode.mcp.example.json +13 -0
- package/package.json +20 -10
- package/scripts/bump-version.js +18 -2
- package/scripts/mcp/playwright-mcp-launcher.js +45 -2
- package/scripts/bump-version.test.js +0 -238
- package/scripts/knowledgebase-cli.test.js +0 -576
- package/scripts/knowledgebase-index.test.js +0 -759
- package/scripts/mcp/playwright-mcp-launcher.test.js +0 -217
- package/scripts/mcp-knowledgebase-server.test.js +0 -745
- package/scripts/mcp-memory-server.test.js +0 -398
- package/scripts/memory-cli.test.js +0 -297
- package/scripts/memory-index.test.js +0 -1173
- package/scripts/normalize-memory.test.js +0 -753
- package/scripts/setup/discover.test.js +0 -530
- package/scripts/setup/hooks.test.js +0 -722
- package/scripts/setup/husky-init.test.js +0 -311
- package/scripts/setup/index.test.js +0 -1021
- package/scripts/setup/knowledgebase.test.js +0 -352
- package/scripts/setup/prepare.test.js +0 -718
- package/scripts/setup/skills-phase.test.js +0 -1274
- package/scripts/setup/sync-phase.test.js +0 -373
- package/scripts/setup/ui.test.js +0 -579
- package/scripts/setup/utils.test.js +0 -586
- package/scripts/sync-unit.test.js +0 -1881
- package/scripts/sync.test.js +0 -472
- package/scripts/validate-memory-schema.test.js +0 -328
- 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
|
-
})
|