@abarcenas/ai-workflow-template 1.34.0 → 1.39.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 +238 -0
- package/.agents/instructions/learned-knowledge.instructions.md +91 -0
- package/.husky/post-commit +10 -0
- package/.husky/post-merge +0 -1
- package/.opencode/agents/architect.agent.md +1 -0
- package/.opencode/agents/coder.agent.md +1 -0
- package/.opencode/agents/deployer.agent.md +1 -0
- package/.opencode/agents/designer.agent.md +1 -0
- package/.opencode/agents/e2e-tester.agent.md +1 -0
- package/.opencode/agents/implementer.agent.md +1 -0
- package/.opencode/agents/orchestrator/feature-pipeline.agent.md +1 -0
- package/.opencode/agents/orchestrator/orchestrator.agent.md +1 -0
- package/.opencode/agents/orchestrator/tdd-orchestrator.agent.md +1 -0
- package/.opencode/agents/researcher.agent.md +1 -0
- package/.opencode/agents/reviewer.agent.md +1 -0
- package/.opencode/agents/tracker.agent.md +2 -0
- package/.opencode/agents/unit-tester.agent.md +1 -0
- package/README.md +1 -1
- package/memory-bank/.vocabulary.json +8 -2
- package/package.json +12 -6
- package/scripts/knowledgebase-cli.js +228 -0
- package/scripts/knowledgebase-cli.test.js +576 -0
- package/scripts/knowledgebase-index.js +786 -0
- package/scripts/knowledgebase-index.test.js +759 -0
- package/scripts/knowledgebase-init.sql +193 -0
- package/scripts/mcp-knowledgebase-server.js +330 -0
- package/scripts/setup/constants.js +36 -3
- package/scripts/setup/hooks.js +2 -2
- package/scripts/setup/hooks.test.js +13 -5
- package/scripts/setup/index.js +29 -2
- package/scripts/setup/index.test.js +36 -1
- package/scripts/setup/knowledgebase.js +204 -0
- package/scripts/setup/knowledgebase.test.js +248 -0
- package/scripts/sync.js +40 -6
package/scripts/setup/index.js
CHANGED
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
// 6. Phase 3 — Prepare (configure consumer package.json prepare script)
|
|
12
12
|
// 7. Phase 4 — Husky Init (generate .husky/_/ shims, unless --skip-hooks)
|
|
13
13
|
// 8. Phase 5 — Sync (spawn sync.js + normalize-memory.js, unless --skip-sync)
|
|
14
|
-
// 9.
|
|
15
|
-
// 10.
|
|
14
|
+
// 9. Phase 6 — Knowledgebase (register with centralized knowledgebase, unless --skip-knowledgebase)
|
|
15
|
+
// 10. Render summary table via ui.summary()
|
|
16
|
+
// 11. Determine exit code and return it
|
|
16
17
|
//
|
|
17
18
|
// Each phase is wrapped in try/catch. Only discovery failure is fatal.
|
|
18
19
|
// All other phase errors are logged and the pipeline continues (REQ-04).
|
|
@@ -38,6 +39,7 @@ import { installHooks } from './hooks.js'
|
|
|
38
39
|
import { handlePrepare } from './prepare.js'
|
|
39
40
|
import { initHusky } from './husky-init.js'
|
|
40
41
|
import { runSyncPhase } from './sync-phase.js'
|
|
42
|
+
import { registerKnowledgebase } from './knowledgebase.js'
|
|
41
43
|
|
|
42
44
|
// ── Package version (lazy, cached) ───────────────────────────────────────────
|
|
43
45
|
|
|
@@ -85,6 +87,7 @@ function actionStatus(action) {
|
|
|
85
87
|
case 'dry-run':
|
|
86
88
|
case 'overwritten':
|
|
87
89
|
case 'merged':
|
|
90
|
+
case 'indexed':
|
|
88
91
|
return 'success'
|
|
89
92
|
// These actions indicate the phase was intentionally skipped or
|
|
90
93
|
// only partially completed — not an error but worth noting
|
|
@@ -331,6 +334,30 @@ export async function main(argv) {
|
|
|
331
334
|
}
|
|
332
335
|
}
|
|
333
336
|
|
|
337
|
+
// ═════════════════════════════════════════════════════════════════════════
|
|
338
|
+
// Phase 6 — Knowledgebase (unless --skip-knowledgebase)
|
|
339
|
+
// ═════════════════════════════════════════════════════════════════════════
|
|
340
|
+
if (!flags.skipKnowledgebase) {
|
|
341
|
+
section('Phase 6: Knowledgebase')
|
|
342
|
+
|
|
343
|
+
try {
|
|
344
|
+
const kbResult = await registerKnowledgebase(context)
|
|
345
|
+
context.kbResult = kbResult
|
|
346
|
+
phases.push({
|
|
347
|
+
phase: 'knowledgebase',
|
|
348
|
+
status: actionStatus(kbResult.action),
|
|
349
|
+
message: kbResult.message,
|
|
350
|
+
})
|
|
351
|
+
} catch (err) {
|
|
352
|
+
stepWarn(`Knowledgebase registration skipped: ${err.message}`)
|
|
353
|
+
phases.push({
|
|
354
|
+
phase: 'knowledgebase',
|
|
355
|
+
status: 'warn',
|
|
356
|
+
message: `Skipped \u2014 ${err.message}`,
|
|
357
|
+
})
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
334
361
|
// ═════════════════════════════════════════════════════════════════════════
|
|
335
362
|
// Summary
|
|
336
363
|
// ═════════════════════════════════════════════════════════════════════════
|
|
@@ -72,6 +72,10 @@ vi.mock('./sync-phase.js', () => ({
|
|
|
72
72
|
runSyncPhase: vi.fn(),
|
|
73
73
|
}))
|
|
74
74
|
|
|
75
|
+
vi.mock('./knowledgebase.js', () => ({
|
|
76
|
+
registerKnowledgebase: vi.fn(),
|
|
77
|
+
}))
|
|
78
|
+
|
|
75
79
|
// ═════════════════════════════════════════════════════════════════════════════
|
|
76
80
|
// Imports — these resolve to the mocked modules above
|
|
77
81
|
// ═════════════════════════════════════════════════════════════════════════════
|
|
@@ -93,6 +97,7 @@ import { installHooks } from './hooks.js'
|
|
|
93
97
|
import { handlePrepare } from './prepare.js'
|
|
94
98
|
import { initHusky } from './husky-init.js'
|
|
95
99
|
import { runSyncPhase } from './sync-phase.js'
|
|
100
|
+
import { registerKnowledgebase } from './knowledgebase.js'
|
|
96
101
|
import { EXIT_CODES } from './constants.js'
|
|
97
102
|
|
|
98
103
|
// ═════════════════════════════════════════════════════════════════════════════
|
|
@@ -110,6 +115,7 @@ const defaultFlags = {
|
|
|
110
115
|
skipHooks: false,
|
|
111
116
|
skipPrepare: false,
|
|
112
117
|
skipSync: false,
|
|
118
|
+
skipKnowledgebase: false,
|
|
113
119
|
help: false,
|
|
114
120
|
version: false,
|
|
115
121
|
quiet: false,
|
|
@@ -190,6 +196,12 @@ describe('main() — setup orchestrator', () => {
|
|
|
190
196
|
handlePrepare.mockResolvedValue(defaultPrepareResult)
|
|
191
197
|
initHusky.mockResolvedValue(defaultHuskyResult)
|
|
192
198
|
runSyncPhase.mockResolvedValue(defaultSyncResult)
|
|
199
|
+
registerKnowledgebase.mockResolvedValue({
|
|
200
|
+
action: 'indexed',
|
|
201
|
+
message: 'Registered "test-consumer" — 5 chunks indexed, 0 skipped',
|
|
202
|
+
chunks_indexed: 5,
|
|
203
|
+
chunks_skipped: 0,
|
|
204
|
+
})
|
|
193
205
|
|
|
194
206
|
// Spy on console.log for --version tests
|
|
195
207
|
logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
@@ -214,6 +226,7 @@ describe('main() — setup orchestrator', () => {
|
|
|
214
226
|
expect(handlePrepare).not.toHaveBeenCalled()
|
|
215
227
|
expect(initHusky).not.toHaveBeenCalled()
|
|
216
228
|
expect(runSyncPhase).not.toHaveBeenCalled()
|
|
229
|
+
expect(registerKnowledgebase).not.toHaveBeenCalled()
|
|
217
230
|
// No header or summary for early-exit flags
|
|
218
231
|
expect(header).not.toHaveBeenCalled()
|
|
219
232
|
expect(summary).not.toHaveBeenCalled()
|
|
@@ -231,6 +244,7 @@ describe('main() — setup orchestrator', () => {
|
|
|
231
244
|
expect(discover).not.toHaveBeenCalled()
|
|
232
245
|
expect(installHooks).not.toHaveBeenCalled()
|
|
233
246
|
expect(header).not.toHaveBeenCalled()
|
|
247
|
+
expect(registerKnowledgebase).not.toHaveBeenCalled()
|
|
234
248
|
})
|
|
235
249
|
|
|
236
250
|
// ── 3. --version flag ────────────────────────────────────────────────────
|
|
@@ -246,6 +260,7 @@ describe('main() — setup orchestrator', () => {
|
|
|
246
260
|
// Early exit means no pipeline phases run
|
|
247
261
|
expect(discover).not.toHaveBeenCalled()
|
|
248
262
|
expect(help).not.toHaveBeenCalled()
|
|
263
|
+
expect(registerKnowledgebase).not.toHaveBeenCalled()
|
|
249
264
|
})
|
|
250
265
|
|
|
251
266
|
// ── 4. -v short flag ─────────────────────────────────────────────────────
|
|
@@ -259,6 +274,7 @@ describe('main() — setup orchestrator', () => {
|
|
|
259
274
|
expect(logSpy).toHaveBeenCalled()
|
|
260
275
|
expect(logSpy.mock.calls[0][0]).toEqual(expect.any(String))
|
|
261
276
|
expect(discover).not.toHaveBeenCalled()
|
|
277
|
+
expect(registerKnowledgebase).not.toHaveBeenCalled()
|
|
262
278
|
})
|
|
263
279
|
|
|
264
280
|
// ── 5. --dry-run flag ────────────────────────────────────────────────────
|
|
@@ -287,6 +303,7 @@ describe('main() — setup orchestrator', () => {
|
|
|
287
303
|
expect(handlePrepare).toHaveBeenCalledTimes(1)
|
|
288
304
|
expect(initHusky).toHaveBeenCalledTimes(1)
|
|
289
305
|
expect(runSyncPhase).toHaveBeenCalledTimes(1)
|
|
306
|
+
expect(registerKnowledgebase).toHaveBeenCalledTimes(1)
|
|
290
307
|
// Header and summary shown (not quiet)
|
|
291
308
|
expect(header).toHaveBeenCalledTimes(1)
|
|
292
309
|
expect(summary).toHaveBeenCalledTimes(1)
|
|
@@ -307,6 +324,7 @@ describe('main() — setup orchestrator', () => {
|
|
|
307
324
|
expect(discover).toHaveBeenCalledTimes(1)
|
|
308
325
|
expect(handlePrepare).toHaveBeenCalledTimes(1)
|
|
309
326
|
expect(runSyncPhase).toHaveBeenCalledTimes(1)
|
|
327
|
+
expect(registerKnowledgebase).toHaveBeenCalledTimes(1)
|
|
310
328
|
})
|
|
311
329
|
|
|
312
330
|
// ── 7. --skip-sync flag ──────────────────────────────────────────────────
|
|
@@ -323,6 +341,7 @@ describe('main() — setup orchestrator', () => {
|
|
|
323
341
|
expect(installHooks).toHaveBeenCalledTimes(1)
|
|
324
342
|
expect(handlePrepare).toHaveBeenCalledTimes(1)
|
|
325
343
|
expect(initHusky).toHaveBeenCalledTimes(1)
|
|
344
|
+
expect(registerKnowledgebase).toHaveBeenCalledTimes(1)
|
|
326
345
|
})
|
|
327
346
|
|
|
328
347
|
// ── 8. --skip-prepare flag ───────────────────────────────────────────────
|
|
@@ -339,6 +358,7 @@ describe('main() — setup orchestrator', () => {
|
|
|
339
358
|
expect(installHooks).toHaveBeenCalledTimes(1)
|
|
340
359
|
expect(initHusky).toHaveBeenCalledTimes(1)
|
|
341
360
|
expect(runSyncPhase).toHaveBeenCalledTimes(1)
|
|
361
|
+
expect(registerKnowledgebase).toHaveBeenCalledTimes(1)
|
|
342
362
|
})
|
|
343
363
|
|
|
344
364
|
// ── 9. Unknown flag ──────────────────────────────────────────────────────
|
|
@@ -362,7 +382,7 @@ describe('main() — setup orchestrator', () => {
|
|
|
362
382
|
|
|
363
383
|
// ── 10. Full pipeline (default flags) ────────────────────────────────────
|
|
364
384
|
|
|
365
|
-
it('full pipeline: all
|
|
385
|
+
it('full pipeline: all 6 phases called in order, exits 0', async () => {
|
|
366
386
|
const code = await main([])
|
|
367
387
|
|
|
368
388
|
expect(code).toBe(EXIT_CODES.SUCCESS)
|
|
@@ -372,6 +392,7 @@ describe('main() — setup orchestrator', () => {
|
|
|
372
392
|
expect(handlePrepare).toHaveBeenCalledTimes(1)
|
|
373
393
|
expect(initHusky).toHaveBeenCalledTimes(1)
|
|
374
394
|
expect(runSyncPhase).toHaveBeenCalledTimes(1)
|
|
395
|
+
expect(registerKnowledgebase).toHaveBeenCalledTimes(1)
|
|
375
396
|
// Header displayed
|
|
376
397
|
expect(header).toHaveBeenCalledTimes(1)
|
|
377
398
|
// Summary displayed
|
|
@@ -398,6 +419,7 @@ describe('main() — setup orchestrator', () => {
|
|
|
398
419
|
expect(handlePrepare).not.toHaveBeenCalled()
|
|
399
420
|
expect(initHusky).not.toHaveBeenCalled()
|
|
400
421
|
expect(runSyncPhase).not.toHaveBeenCalled()
|
|
422
|
+
expect(registerKnowledgebase).not.toHaveBeenCalled()
|
|
401
423
|
// Summary is shown even on fatal (not quiet mode)
|
|
402
424
|
expect(summary).toHaveBeenCalledTimes(1)
|
|
403
425
|
})
|
|
@@ -419,6 +441,7 @@ describe('main() — setup orchestrator', () => {
|
|
|
419
441
|
expect(handlePrepare).toHaveBeenCalledTimes(1)
|
|
420
442
|
expect(initHusky).toHaveBeenCalledTimes(1)
|
|
421
443
|
expect(runSyncPhase).toHaveBeenCalledTimes(1)
|
|
444
|
+
expect(registerKnowledgebase).toHaveBeenCalledTimes(1)
|
|
422
445
|
// Summary is shown
|
|
423
446
|
expect(summary).toHaveBeenCalledTimes(1)
|
|
424
447
|
})
|
|
@@ -445,6 +468,7 @@ describe('main() — setup orchestrator', () => {
|
|
|
445
468
|
expect(discover).toHaveBeenCalledTimes(1)
|
|
446
469
|
expect(handlePrepare).toHaveBeenCalledTimes(1)
|
|
447
470
|
expect(runSyncPhase).toHaveBeenCalledTimes(1)
|
|
471
|
+
expect(registerKnowledgebase).toHaveBeenCalledTimes(1)
|
|
448
472
|
// Summary shown
|
|
449
473
|
expect(summary).toHaveBeenCalledTimes(1)
|
|
450
474
|
})
|
|
@@ -463,6 +487,7 @@ describe('main() — setup orchestrator', () => {
|
|
|
463
487
|
expect(ciModeBanner).toHaveBeenCalledTimes(1)
|
|
464
488
|
// CI mode doesn't skip phases (that's done by --skip-hooks)
|
|
465
489
|
expect(discover).toHaveBeenCalledTimes(1)
|
|
490
|
+
expect(registerKnowledgebase).toHaveBeenCalledTimes(1)
|
|
466
491
|
})
|
|
467
492
|
|
|
468
493
|
it('CI mode: ciModeBanner NOT shown in quiet mode even when isCI is true', async () => {
|
|
@@ -496,6 +521,7 @@ describe('main() — setup orchestrator', () => {
|
|
|
496
521
|
expect(handlePrepare).toHaveBeenCalledTimes(1)
|
|
497
522
|
expect(initHusky).toHaveBeenCalledTimes(1)
|
|
498
523
|
expect(runSyncPhase).toHaveBeenCalledTimes(1)
|
|
524
|
+
expect(registerKnowledgebase).toHaveBeenCalledTimes(1)
|
|
499
525
|
})
|
|
500
526
|
|
|
501
527
|
// ── 16. Multiple skips combined ─────────────────────────────────────────
|
|
@@ -520,6 +546,7 @@ describe('main() — setup orchestrator', () => {
|
|
|
520
546
|
expect(handlePrepare).not.toHaveBeenCalled()
|
|
521
547
|
expect(initHusky).not.toHaveBeenCalled()
|
|
522
548
|
expect(runSyncPhase).not.toHaveBeenCalled()
|
|
549
|
+
expect(registerKnowledgebase).toHaveBeenCalledTimes(1)
|
|
523
550
|
})
|
|
524
551
|
|
|
525
552
|
// ── 17. Verify parseCliArgs receives the argv ───────────────────────────
|
|
@@ -536,6 +563,7 @@ describe('main() — setup orchestrator', () => {
|
|
|
536
563
|
await main(argv)
|
|
537
564
|
|
|
538
565
|
expect(parseCliArgs).toHaveBeenCalledWith(argv)
|
|
566
|
+
expect(registerKnowledgebase).toHaveBeenCalledTimes(1)
|
|
539
567
|
})
|
|
540
568
|
|
|
541
569
|
// ── 18. Discovery context is passed to each phase ───────────────────────
|
|
@@ -556,6 +584,7 @@ describe('main() — setup orchestrator', () => {
|
|
|
556
584
|
expect(handlePrepare).toHaveBeenCalledWith(ctx)
|
|
557
585
|
expect(initHusky).toHaveBeenCalledWith(ctx)
|
|
558
586
|
expect(runSyncPhase).toHaveBeenCalledWith(ctx)
|
|
587
|
+
expect(registerKnowledgebase).toHaveBeenCalledWith(ctx)
|
|
559
588
|
})
|
|
560
589
|
|
|
561
590
|
// ── 19a. --verbose flag passthrough to Context ──────────────────────────
|
|
@@ -577,6 +606,7 @@ describe('main() — setup orchestrator', () => {
|
|
|
577
606
|
expect(handlePrepare).toHaveBeenCalledWith(ctx)
|
|
578
607
|
expect(initHusky).toHaveBeenCalledWith(ctx)
|
|
579
608
|
expect(runSyncPhase).toHaveBeenCalledWith(ctx)
|
|
609
|
+
expect(registerKnowledgebase).toHaveBeenCalledWith(ctx)
|
|
580
610
|
// Normal pipeline — header and summary shown
|
|
581
611
|
expect(header).toHaveBeenCalledTimes(1)
|
|
582
612
|
expect(summary).toHaveBeenCalledTimes(1)
|
|
@@ -591,6 +621,7 @@ describe('main() — setup orchestrator', () => {
|
|
|
591
621
|
await main(['--dry-run'])
|
|
592
622
|
|
|
593
623
|
expect(discover).toHaveBeenCalledWith(flags)
|
|
624
|
+
expect(registerKnowledgebase).toHaveBeenCalledTimes(1)
|
|
594
625
|
})
|
|
595
626
|
|
|
596
627
|
// ── 20. Verify --help takes priority over --version ─────────────────────
|
|
@@ -608,6 +639,7 @@ describe('main() — setup orchestrator', () => {
|
|
|
608
639
|
expect(help).toHaveBeenCalledTimes(1)
|
|
609
640
|
expect(logSpy).not.toHaveBeenCalled() // console.log NOT called for version
|
|
610
641
|
expect(discover).not.toHaveBeenCalled()
|
|
642
|
+
expect(registerKnowledgebase).not.toHaveBeenCalled()
|
|
611
643
|
})
|
|
612
644
|
|
|
613
645
|
// ── 21. Error in prepare phase → continues ──────────────────────────────
|
|
@@ -626,6 +658,7 @@ describe('main() — setup orchestrator', () => {
|
|
|
626
658
|
expect(installHooks).toHaveBeenCalledTimes(1)
|
|
627
659
|
expect(initHusky).toHaveBeenCalledTimes(1)
|
|
628
660
|
expect(runSyncPhase).toHaveBeenCalledTimes(1)
|
|
661
|
+
expect(registerKnowledgebase).toHaveBeenCalledTimes(1)
|
|
629
662
|
})
|
|
630
663
|
|
|
631
664
|
// ── 22. Error in husky-init → continues ────────────────────────────────
|
|
@@ -644,6 +677,7 @@ describe('main() — setup orchestrator', () => {
|
|
|
644
677
|
expect(installHooks).toHaveBeenCalledTimes(1)
|
|
645
678
|
expect(handlePrepare).toHaveBeenCalledTimes(1)
|
|
646
679
|
expect(runSyncPhase).toHaveBeenCalledTimes(1)
|
|
680
|
+
expect(registerKnowledgebase).toHaveBeenCalledTimes(1)
|
|
647
681
|
})
|
|
648
682
|
|
|
649
683
|
// ── 23. Error in sync phase → continues ────────────────────────────────
|
|
@@ -660,5 +694,6 @@ describe('main() — setup orchestrator', () => {
|
|
|
660
694
|
expect(installHooks).toHaveBeenCalledTimes(1)
|
|
661
695
|
expect(handlePrepare).toHaveBeenCalledTimes(1)
|
|
662
696
|
expect(initHusky).toHaveBeenCalledTimes(1)
|
|
697
|
+
expect(registerKnowledgebase).toHaveBeenCalledTimes(1)
|
|
663
698
|
})
|
|
664
699
|
})
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// ═════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// knowledgebase.js — Setup Phase 6: register consumer project in the
|
|
3
|
+
// centralized knowledgebase (PostgreSQL + pgvector)
|
|
4
|
+
// ═════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
//
|
|
6
|
+
// Spawns `knowledgebase-cli.js sync --project <id>` as a child process to
|
|
7
|
+
// register the consumer project and index its learned-knowledge chunks.
|
|
8
|
+
//
|
|
9
|
+
// Gracefully degrades when DATABASE_URL is not configured or when the CLI
|
|
10
|
+
// script is unavailable — never blocks the setup pipeline.
|
|
11
|
+
// ═════════════════════════════════════════════════════════════════════════════
|
|
12
|
+
|
|
13
|
+
import { spawn } from 'child_process'
|
|
14
|
+
import { join } from 'path'
|
|
15
|
+
|
|
16
|
+
import { UNICODE_CHARS } from './constants.js'
|
|
17
|
+
import { verbose } from './ui.js'
|
|
18
|
+
import { logInfo, logWarn, resolvePackageRoot } from './utils.js'
|
|
19
|
+
|
|
20
|
+
// ── Main export ─────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Registers the consumer project in the centralized knowledgebase.
|
|
24
|
+
*
|
|
25
|
+
* Spawns `scripts/knowledgebase-cli.js sync --project <projectId>` as a child
|
|
26
|
+
* process so the CLI's own graceful degradation (DATABASE_URL check, optional
|
|
27
|
+
* dependency loading) is reused rather than duplicated.
|
|
28
|
+
*
|
|
29
|
+
* **Early exits (all return `{ action: 'skipped' }`):**
|
|
30
|
+
* - `flags.skipKnowledgebase` is `true` — user opted out via flag
|
|
31
|
+
* - `process.env.DATABASE_URL` is not set — not configured yet
|
|
32
|
+
* - `consumerPackageJson` has no `name` field — can't identify the project
|
|
33
|
+
*
|
|
34
|
+
* **Normal execution:**
|
|
35
|
+
* The CLI is spawned with `INIT_CWD` set to `consumerRoot`. If the child
|
|
36
|
+
* process exits with a non-zero code, the action becomes `'failed'` but the
|
|
37
|
+
* pipeline continues (graceful degradation per REQ-08).
|
|
38
|
+
*
|
|
39
|
+
* @param {object} context - Setup context object.
|
|
40
|
+
* @param {object} [context.flags={}] - Parsed CLI flags.
|
|
41
|
+
* @param {boolean} [context.flags.skipKnowledgebase=false] - If `true`, skip.
|
|
42
|
+
* @param {boolean} [context.verbose=false] - Enable verbose child output.
|
|
43
|
+
* @param {string} [context.consumerRoot] - Absolute consumer project root.
|
|
44
|
+
* @param {object} [context.consumerPackageJson] - Consumer's package.json.
|
|
45
|
+
* @returns {Promise<{
|
|
46
|
+
* action: 'indexed'|'skipped'|'failed',
|
|
47
|
+
* message: string,
|
|
48
|
+
* chunks_indexed?: number,
|
|
49
|
+
* chunks_skipped?: number
|
|
50
|
+
* }>}
|
|
51
|
+
*/
|
|
52
|
+
export async function registerKnowledgebase(context) {
|
|
53
|
+
const { flags = {}, verbose: verboseFlag = false } = context
|
|
54
|
+
|
|
55
|
+
// ── Skip flag check ──────────────────────────────────────────────────
|
|
56
|
+
if (flags.skipKnowledgebase) {
|
|
57
|
+
const message =
|
|
58
|
+
'[knowledgebase] Skipping Phase 6 \u2014 --skip-knowledgebase flag set'
|
|
59
|
+
logInfo(message)
|
|
60
|
+
return { action: 'skipped', message }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── DATABASE_URL check ───────────────────────────────────────────────
|
|
64
|
+
if (!process.env.DATABASE_URL) {
|
|
65
|
+
const message =
|
|
66
|
+
'DATABASE_URL not configured. Set up later with: ' +
|
|
67
|
+
'npx ai-workflow-template setup --knowledgebase'
|
|
68
|
+
logWarn(
|
|
69
|
+
`[knowledgebase] Skipping Phase 6 \u2014 ${message}`,
|
|
70
|
+
)
|
|
71
|
+
return { action: 'skipped', message }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── Resolve project ID from consumer package.json ────────────────────
|
|
75
|
+
const projectId = context.consumerPackageJson?.name
|
|
76
|
+
if (!projectId) {
|
|
77
|
+
const message = 'No project name in consumer package.json'
|
|
78
|
+
logWarn(
|
|
79
|
+
`[knowledgebase] Skipping Phase 6 \u2014 ${message}`,
|
|
80
|
+
)
|
|
81
|
+
return { action: 'skipped', message }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ── Spawn knowledgebase-cli.js sync ──────────────────────────────────
|
|
85
|
+
const packageRoot = resolvePackageRoot()
|
|
86
|
+
const cliPath = join(packageRoot, 'scripts', 'knowledgebase-cli.js')
|
|
87
|
+
const cwd = context.consumerRoot || process.cwd()
|
|
88
|
+
|
|
89
|
+
verbose(verboseFlag, `Spawning knowledgebase-cli.js sync for project "${projectId}"\u2026`)
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const result = await spawnScript(
|
|
93
|
+
'knowledgebase-cli.js',
|
|
94
|
+
cliPath,
|
|
95
|
+
cwd,
|
|
96
|
+
['sync', '--project', projectId],
|
|
97
|
+
verboseFlag,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
// Non-zero exit from the CLI is a warning, not a blocker
|
|
101
|
+
if (!result.success) {
|
|
102
|
+
const trimmedOutput = result.output
|
|
103
|
+
const message =
|
|
104
|
+
`Knowledgebase sync failed (exit code ${result.exitCode}): ${trimmedOutput}`
|
|
105
|
+
logWarn(`[knowledgebase] ${message}`)
|
|
106
|
+
return { action: 'failed', message }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Parse the human-readable output for chunk counts.
|
|
110
|
+
// The CLI outputs: "[knowledgebase] Indexed X new, updated Y, skipped Z chunks from project <name>"
|
|
111
|
+
let chunksIndexed = 0
|
|
112
|
+
let chunksSkipped = 0
|
|
113
|
+
const outputLine = result.output
|
|
114
|
+
const match = outputLine.match(
|
|
115
|
+
/Indexed (\d+) new, updated (\d+), skipped (\d+) chunks/,
|
|
116
|
+
)
|
|
117
|
+
if (match) {
|
|
118
|
+
// "indexed" = new chunks + updated chunks (both were processed)
|
|
119
|
+
chunksIndexed = parseInt(match[1], 10) + parseInt(match[2], 10)
|
|
120
|
+
chunksSkipped = parseInt(match[3], 10)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const message =
|
|
124
|
+
`Registered "${projectId}" \u2014 ${chunksIndexed} chunks indexed, ` +
|
|
125
|
+
`${chunksSkipped} skipped`
|
|
126
|
+
logInfo(`[knowledgebase] ${UNICODE_CHARS.CHECK} ${message}`)
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
action: 'indexed',
|
|
130
|
+
message,
|
|
131
|
+
chunks_indexed: chunksIndexed,
|
|
132
|
+
chunks_skipped: chunksSkipped,
|
|
133
|
+
}
|
|
134
|
+
} catch (err) {
|
|
135
|
+
// Catch spawn-level errors (ENOENT when script doesn't exist, etc.)
|
|
136
|
+
const message = `knowledgebase-cli.js not available: ${err.message}`
|
|
137
|
+
logWarn(
|
|
138
|
+
`[knowledgebase] Skipping Phase 6 \u2014 ${message}`,
|
|
139
|
+
)
|
|
140
|
+
return { action: 'skipped', message }
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ── Internal helpers ───────────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Spawns a Node.js script as a child process and waits for completion.
|
|
148
|
+
*
|
|
149
|
+
* Matches the `spawnScript` pattern from `sync-phase.js`: uses
|
|
150
|
+
* `process.execPath` so the child runs the same Node binary as the parent,
|
|
151
|
+
* streams output in real-time when `verbose` is `true`, and captures
|
|
152
|
+
* interleaved stdout + stderr when not.
|
|
153
|
+
*
|
|
154
|
+
* @param {string} name - Display name for the script.
|
|
155
|
+
* @param {string} scriptPath - Absolute path to the script to execute.
|
|
156
|
+
* @param {string} cwd - Working directory for the child process.
|
|
157
|
+
* @param {string[]} args - CLI arguments forwarded to the script.
|
|
158
|
+
* @param {boolean} [verbose=false] - Stream output in real-time when `true`.
|
|
159
|
+
* @returns {Promise<{
|
|
160
|
+
* script: string,
|
|
161
|
+
* success: boolean,
|
|
162
|
+
* exitCode: number,
|
|
163
|
+
* output: string
|
|
164
|
+
* }>}
|
|
165
|
+
*/
|
|
166
|
+
function spawnScript(name, scriptPath, cwd, args, verbose = false) {
|
|
167
|
+
return new Promise((resolve) => {
|
|
168
|
+
const child = spawn(process.execPath, [scriptPath, ...args], {
|
|
169
|
+
cwd,
|
|
170
|
+
env: { ...process.env, INIT_CWD: cwd },
|
|
171
|
+
stdio: verbose ? 'inherit' : 'pipe',
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
let output = ''
|
|
175
|
+
|
|
176
|
+
if (!verbose) {
|
|
177
|
+
child.stdout.on('data', (data) => {
|
|
178
|
+
output += data.toString()
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
child.stderr.on('data', (data) => {
|
|
182
|
+
output += data.toString()
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
child.on('close', (exitCode) => {
|
|
187
|
+
resolve({
|
|
188
|
+
script: name,
|
|
189
|
+
success: exitCode === 0,
|
|
190
|
+
exitCode: exitCode ?? -1,
|
|
191
|
+
output: verbose ? '(streamed to terminal)' : output.trim(),
|
|
192
|
+
})
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
child.on('error', (err) => {
|
|
196
|
+
resolve({
|
|
197
|
+
script: name,
|
|
198
|
+
success: false,
|
|
199
|
+
exitCode: -1,
|
|
200
|
+
output: `Failed to spawn child process: ${err.message}`,
|
|
201
|
+
})
|
|
202
|
+
})
|
|
203
|
+
})
|
|
204
|
+
}
|