@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
@@ -154,7 +154,12 @@ function spawnScript(name, scriptPath, cwd, extraArgs, verbose = false) {
154
154
  })
155
155
 
156
156
  child.stderr.on('data', (data) => {
157
- output += data.toString()
157
+ const chunk = data.toString()
158
+ output += chunk
159
+ // Surface warnings immediately. sync.js reports skipped locally
160
+ // modified files on stderr, and this phase is often the only place a
161
+ // consumer would ever see them (CON-04). stdout stays captured only.
162
+ process.stderr.write(chunk)
158
163
  })
159
164
  }
160
165
 
@@ -34,6 +34,7 @@ vi.mock('./utils.js', () => ({
34
34
 
35
35
  import { spawn } from 'child_process'
36
36
  import { runSyncPhase } from './sync-phase.js'
37
+ import { logInfo, logWarn } from './utils.js'
37
38
 
38
39
  // ── Test helpers ──────────────────────────────────────────────────────────
39
40
 
@@ -45,15 +46,22 @@ import { runSyncPhase } from './sync-phase.js'
45
46
  * This allows the Promise-based flow in spawnScript() to resolve naturally.
46
47
  *
47
48
  * @param {object} opts
48
- * @param {number} [opts.exitCode=0] - Exit code for the 'close' event
49
+ * @param {number|null} [opts.exitCode=0] - Exit code for the 'close' event
49
50
  * @param {string} [opts.stdoutData=''] - Data emitted via stdout 'data'
50
51
  * @param {string} [opts.stderrData=''] - Data emitted via stderr 'data'
52
+ * @param {boolean} [opts.emitError=false] - Emit an 'error' event instead
51
53
  * @returns {object} Mock child process with .stdout.on, .stderr.on, .on
52
54
  */
53
- function createMockChild({ exitCode = 0, stdoutData = '', stderrData = '' } = {}) {
55
+ function createMockChild({
56
+ exitCode = 0,
57
+ stdoutData = '',
58
+ stderrData = '',
59
+ emitError = false,
60
+ } = {}) {
54
61
  const stdoutHandlers = []
55
62
  const stderrHandlers = []
56
63
  let closeHandler = null
64
+ let errorHandler = null
57
65
 
58
66
  const child = {
59
67
  stdout: {
@@ -68,11 +76,18 @@ function createMockChild({ exitCode = 0, stdoutData = '', stderrData = '' } = {}
68
76
  },
69
77
  on: (event, handler) => {
70
78
  if (event === 'close') closeHandler = handler
79
+ if (event === 'error') errorHandler = handler
71
80
  },
72
81
  }
73
82
 
74
83
  // Fire events on a microtask so handlers are registered first
75
84
  Promise.resolve().then(() => {
85
+ if (emitError) {
86
+ if (errorHandler) {
87
+ errorHandler(new Error('spawn ENOENT'))
88
+ }
89
+ return
90
+ }
76
91
  if (stdoutData) {
77
92
  stdoutHandlers.forEach((h) => h(Buffer.from(stdoutData)))
78
93
  }
@@ -193,3 +208,166 @@ describe('sync-phase verbose spawn behavior', () => {
193
208
  }
194
209
  })
195
210
  })
211
+
212
+ // ── Execution paths (skip / dry-run / failures / forwarding) ───────────────
213
+
214
+ describe('sync-phase execution paths', () => {
215
+ beforeEach(() => {
216
+ vi.clearAllMocks()
217
+ })
218
+
219
+ it('skipSync=true returns "skipped" without spawning anything', async () => {
220
+ const result = await runSyncPhase(makeContext({ skipSync: true }))
221
+
222
+ expect(result).toEqual({
223
+ action: 'skipped',
224
+ message: 'Sync phase skipped (--skip-sync)',
225
+ results: [],
226
+ })
227
+ expect(spawn).not.toHaveBeenCalled()
228
+ expect(logInfo).toHaveBeenCalledWith(
229
+ '[setup] Sync phase skipped (--skip-sync)',
230
+ )
231
+ })
232
+
233
+ it('dryRun=true returns "dry-run" and mentions the consumer root', async () => {
234
+ const result = await runSyncPhase(makeContext({ dryRun: true }))
235
+
236
+ expect(result.action).toBe('dry-run')
237
+ expect(result.message).toContain('sync.js')
238
+ expect(result.message).toContain('normalize-memory.js')
239
+ expect(result.message).toContain('/fake/consumer')
240
+ expect(result.results).toEqual([])
241
+ expect(spawn).not.toHaveBeenCalled()
242
+ })
243
+
244
+ it('forwards --force to both child scripts when flags.force is set', async () => {
245
+ vi.mocked(spawn).mockImplementation(() => createMockChild({ exitCode: 0 }))
246
+
247
+ await runSyncPhase(makeContext({ flags: { force: true } }))
248
+
249
+ const argv = vi.mocked(spawn).mock.calls.map((c) => c[1])
250
+ expect(argv).toHaveLength(2)
251
+ for (const args of argv) {
252
+ expect(args).toContain('--force')
253
+ }
254
+ })
255
+
256
+ it('does not forward --force by default', async () => {
257
+ vi.mocked(spawn).mockImplementation(() => createMockChild({ exitCode: 0 }))
258
+
259
+ await runSyncPhase(makeContext({ flags: {} }))
260
+
261
+ for (const call of vi.mocked(spawn).mock.calls) {
262
+ expect(call[1]).not.toContain('--force')
263
+ }
264
+ })
265
+
266
+ it('treats a non-zero exit as partial and logs each failing script', async () => {
267
+ vi.mocked(spawn)
268
+ .mockImplementationOnce(() => createMockChild({ exitCode: 1 }))
269
+ .mockImplementationOnce(() => createMockChild({ exitCode: 0 }))
270
+
271
+ const result = await runSyncPhase(makeContext())
272
+
273
+ expect(result.action).toBe('partial')
274
+ expect(result.message).toBe(
275
+ 'Sync phase completed with 1 script failure(s)',
276
+ )
277
+ expect(result.results).toHaveLength(2)
278
+ expect(result.results[0]).toMatchObject({ script: 'sync.js', success: false, exitCode: 1 })
279
+ expect(result.results[1]).toMatchObject({ script: 'normalize-memory.js', success: true })
280
+
281
+ expect(logWarn).toHaveBeenCalledWith(
282
+ '[setup] Sync phase completed with 1 script failure(s)',
283
+ )
284
+ expect(logWarn).toHaveBeenCalledWith(
285
+ '[setup] sync.js exited with code 1',
286
+ )
287
+ })
288
+
289
+ it('counts every failing script in the partial message', async () => {
290
+ vi.mocked(spawn)
291
+ .mockImplementationOnce(() => createMockChild({ exitCode: 2 }))
292
+ .mockImplementationOnce(() => createMockChild({ exitCode: 3 }))
293
+
294
+ const result = await runSyncPhase(makeContext())
295
+
296
+ expect(result.action).toBe('partial')
297
+ expect(result.message).toBe('Sync phase completed with 2 script failure(s)')
298
+ expect(logWarn).toHaveBeenCalledWith('[setup] sync.js exited with code 2')
299
+ expect(logWarn).toHaveBeenCalledWith(
300
+ '[setup] normalize-memory.js exited with code 3',
301
+ )
302
+ })
303
+
304
+ it('forwards child stderr to the parent stderr in non-verbose mode', async () => {
305
+ const writeSpy = vi
306
+ .spyOn(process.stderr, 'write')
307
+ .mockImplementation(() => true)
308
+ vi.mocked(spawn).mockImplementation(() =>
309
+ createMockChild({
310
+ exitCode: 0,
311
+ stdoutData: 'copied files\n',
312
+ stderrData: 'skipped locally modified file\n',
313
+ }),
314
+ )
315
+
316
+ try {
317
+ const result = await runSyncPhase(makeContext({ verbose: false }))
318
+
319
+ // Warning text reaches the terminal even though stdout is captured
320
+ expect(writeSpy).toHaveBeenCalledWith('skipped locally modified file\n')
321
+ // ...and the same text is preserved in the captured output
322
+ for (const r of result.results) {
323
+ expect(r.output).toContain('copied files')
324
+ expect(r.output).toContain('skipped locally modified file')
325
+ }
326
+ } finally {
327
+ writeSpy.mockRestore()
328
+ }
329
+ })
330
+
331
+ it('verbose mode streams output and does not capture it', async () => {
332
+ vi.mocked(spawn).mockImplementation(() => createMockChild({ exitCode: 0 }))
333
+
334
+ const result = await runSyncPhase(makeContext({ verbose: true }))
335
+
336
+ expect(result.action).toBe('completed')
337
+ for (const r of result.results) {
338
+ expect(r.output).toBe('(streamed to terminal)')
339
+ }
340
+ for (const call of vi.mocked(spawn).mock.calls) {
341
+ expect(call[2].stdio).toBe('inherit')
342
+ }
343
+ })
344
+
345
+ it('resolves a spawn error event into a failed result instead of throwing', async () => {
346
+ vi.mocked(spawn).mockImplementation(() => createMockChild({ emitError: true }))
347
+
348
+ const result = await runSyncPhase(makeContext())
349
+
350
+ expect(result.action).toBe('partial')
351
+ expect(result.results).toHaveLength(2)
352
+ for (const r of result.results) {
353
+ expect(r.success).toBe(false)
354
+ expect(r.exitCode).toBe(-1)
355
+ expect(r.output).toContain('Failed to spawn child process')
356
+ expect(r.output).toContain('spawn ENOENT')
357
+ }
358
+ })
359
+
360
+ it('normalises a null exit code (killed by signal) to -1', async () => {
361
+ vi.mocked(spawn).mockImplementation(() =>
362
+ createMockChild({ exitCode: null }),
363
+ )
364
+
365
+ const result = await runSyncPhase(makeContext())
366
+
367
+ expect(result.action).toBe('partial')
368
+ for (const r of result.results) {
369
+ expect(r.success).toBe(false)
370
+ expect(r.exitCode).toBe(-1)
371
+ }
372
+ })
373
+ })