@abarcenas/ai-workflow-template 1.38.0 → 1.40.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.
@@ -164,3 +164,23 @@
164
164
  - **coder**: When executing a config rename, use the 3-name-context mental model (template name, consumer target name, documentation name) to ensure all changes are internally consistent. Verify each context independently — a change that's correct in one context may be wrong in another. The `.gitignore` entry should future-proof against related file spillover (e.g., `opencode.mcp` covers `opencode.mcp.json`, `opencode.mcp.bak`, etc.).
165
165
  - **reviewer**: Pre-existing test coverage gaps should be noted separately from issues introduced by the change. A change that is correct but reveals an existing gap is still APPROVED — the gap is a pre-existing concern, not a regression. Use the "pre-existing" label to distinguish.
166
166
  - **tracker**: The "Three naming contexts" pattern (repo template, consumer provisioning, documentation) is a reusable architectural insight for ANY config rename pipeline. It should be recorded in learned knowledge as a universal pattern, not a one-time fix. Also note the user correction acceptance loop as a pattern — researcher recommends, user validates, implementer incorporates — this validates the confirmation gate architecture.
167
+
168
+ ## Session: 2026-08-01 — Float32Array Test Miss Investigation
169
+
170
+ **Pipeline:** Research spike → knowledgebase sync (root-cause investigation of a test failure missed by the previous pipeline)
171
+ **Coverage:** N/A (investigation — no new production code; findings persisted to knowledgebase)
172
+ **TDD Iterations:** 0 (spike, not a pipeline run)
173
+
174
+ **New knowledge:**
175
+ - **A failing test can ship through a 3-layer defense failure** — The `Float32Array` test bug (test expects `toBeInstanceOf(Float32Array)`, source returns plain `Array` via `Array.from(result.data)`) was missed by ALL three defenses simultaneously: (1) **Design level**: parallel coder batches (source T6/Batch A vs test T15/Batch E) wrote contradictory contracts with no cross-batch validation — source and test were even created in the SAME commit (`d788f68`) yet contradicted each other; (2) **Verification level**: the orchestrator's AI unit-tester reported "158 tests, 0 failures" when the actual vitest run was failing (`AssertionError: expected [0.1, …(383)] to be an instance of Float32Array`) — an AI analytical error, not a real test run; (3) **CI level**: GitHub Actions only triggers on `pull_request` targeting `main`, PR #23 merged in ~4 minutes (before CI could gate), and the fix commit `d93cdd3` was pushed directly to `feat/update-setup` with no PR trigger — so `main` never got external validation.
176
+ - **pgvector 0.3.0 `toSql()` REJECTS typed arrays — verified empirically** — `toSql()` in `node_modules/pgvector/src/index.js` does `Array.isArray(value)` (line 27) and throws `'expected array or sparse vector'` otherwise. `Array.isArray(new Float32Array(384))` is `false`, so a `Float32Array` passed to `_toSql()` ALWAYS throws. The spike's Section 7 conclusion ("pgvector accepts typed arrays; `Array.from` was an unnecessary conversion") is **FALSE** for the installed version. Verified three ways: source read, `node --input-type=module` repro (`toSql(Float32Array)` → throws; `toSql(Array.from(...))` → `[0.1,…]`), and live `kb:sync` run (7/7 chunks → "Embedding failed for chunk, inserting without vector: expected array or sparse vector").
177
+ - **The d93cdd3 "fix" (Float32Array) breaks pgvector serialization while fixing the test** — Neither extreme is correct alone: `Array.from()` in `embed()` makes the TEST fail but pgvector WORK; `new Float32Array()` makes the TEST pass but pgvector THROW, silently degrading the knowledgebase to a text store (null embeddings). The correct contract is: `embed()` returns `Float32Array` (matches the API contract AND the test), and the conversion to plain `Array` (`Array.from(vec)`) must happen at the pgvector boundary — i.e., callers do `_toSql(Array.from(vec))` before passing to pgvector. `main` (at `641240f`) currently has the working `Array.from()` version; `feat/update-setup` (`d93cdd3`) has the broken Float32Array version.
178
+ - **Silent degradation hides library-boundary errors** — The `try/catch` in `upsertChunks()` (lines 402-405) catches the pgvector throw, logs a warning, and inserts the chunk with `null` embedding. The CLI still exits 0 and reports "Indexed X new, updated Y". Without checking vector storage (e.g., `kb:search` returning meaningful results), a broken embedding pipeline is invisible. The earlier 2026-07-30 session already documented this exact failure mode — it regressed because the "fix" was based on an unverified library assumption.
179
+ - **Same-author same-commit contradictions** — The buggy source AND the test that would catch it were written in the same commit by the same author 44 minutes apart. Cross-file contract verification matters even within a single commit, not just across parallel batches.
180
+
181
+ **Agent tuning notes:**
182
+ - **orchestrator**: Add cross-batch contract verification to the pipeline — before declaring a batch complete, verify that exported function signatures/return types match what tests and dependent batches expect. Source contract (T6) and test contract (T15) were never compared.
183
+ - **unit-tester**: MUST run real `npx vitest run` and parse actual output instead of relying on AI analysis alone. The reported "158 tests, 0 failures" was a hallucination/analytical error — the actual vitest run was failing. If a real test run is not possible, explicitly label the result as "AI-analyzed, not executed" so downstream agents know its confidence level.
184
+ - **orchestrator/CI**: Make CI a required check on `main` (branch protection) so a 4-minute PR merge window cannot bypass it. Any fix must go through a PR that runs CI, not be pushed directly to a branch.
185
+ - **researcher/coder**: Verify contested library behavior claims against the INSTALLED source before persisting them. The spike endorsed "pgvector accepts typed arrays" based on an unverified note in a fix commit; the installed `pgvector@0.3.0` source and an empirical repro prove the opposite. When a knowledgebase contains contradictory sessions about library behavior (2026-07-30 says Float32Array fails, 2026-08-01 spike says it works), run a minimal repro to settle it — never trust the newer claim by default.
186
+ - **coder**: When fixing a type mismatch, check BOTH consumers of the value. `embed()`'s result feeds both the test assertion AND pgvector's `toSql()`. A fix that satisfies the test but breaks the DB boundary (or vice versa) is not a fix — convert at the boundary instead of changing the function's contract.
package/bin/setup.js CHANGED
@@ -13,6 +13,9 @@
13
13
  // 2 — Fatal error (discovery failure or unexpected exception)
14
14
  // ═════════════════════════════════════════════════════════════════════════════
15
15
 
16
+ // Load consumer's .env into process.env before any phase runs
17
+ import 'dotenv/config'
18
+
16
19
  try {
17
20
  const { main } = await import('../scripts/setup/index.js')
18
21
  const exitCode = await main(process.argv.slice(2))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abarcenas/ai-workflow-template",
3
- "version": "1.38.0",
3
+ "version": "1.40.0",
4
4
  "description": "A comprehensive template repository for AI-assisted development workflows using GitHub Copilot. This template provides a structured approach to leveraging AI assistance through pre-configured skills, prompts, agents, and coding standards.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -57,14 +57,14 @@
57
57
  "devDependencies": {
58
58
  "@playwright/test": "^1.59.1",
59
59
  "@vitest/coverage-v8": "^4.1.8",
60
- "dotenv": "^17.4.2",
61
60
  "husky": "^9.0.0",
62
61
  "vitest": "^4.1.8"
63
62
  },
64
63
  "dependencies": {
65
64
  "@modelcontextprotocol/sdk": "^1.0.0",
66
65
  "better-sqlite3": "^12.11.0",
67
- "sqlite-vec": "^0.1.9"
66
+ "sqlite-vec": "^0.1.9",
67
+ "dotenv": "^17.4.2"
68
68
  },
69
69
  "optionalDependencies": {
70
70
  "@xenova/transformers": "^2.17.0",
@@ -290,8 +290,11 @@ async function getEmbedder() {
290
290
  *
291
291
  * Uses the local @xenova/transformers pipeline with all-MiniLM-L6-v2 (384d).
292
292
  * Returns a Float32Array of 384 floats using mean pooling + L2 normalization.
293
- * pgvector's `toSql()` accepts both Float32Array and plain arrays, so callers
294
- * can rely on receiving a Float32Array.
293
+ *
294
+ * NOTE: pgvector@0.3.0's `toSql()` rejects typed arrays (`Array.isArray()`
295
+ * returns false for Float32Array and it throws 'expected array or sparse
296
+ * vector'). Callers MUST convert to a plain Array (`Array.from(vec)`) at the
297
+ * pgvector boundary before passing to `_toSql()`.
295
298
  *
296
299
  * @param {string} text - Text to embed
297
300
  * @returns {Promise<Float32Array>} Float32Array of 384 floats
@@ -398,7 +401,9 @@ async function upsertChunks(chunks) {
398
401
  (chunk.pipeline ? ` — Pipeline: ${chunk.pipeline}` : '') +
399
402
  `\n${chunk.content}`;
400
403
  const vec = await embed(contextText);
401
- embeddingSql = _toSql(vec);
404
+ // pgvector toSql() requires a plain Array (Array.isArray() === true);
405
+ // Float32Array would throw 'expected array or sparse vector'.
406
+ embeddingSql = _toSql(Array.from(vec));
402
407
  } catch (embedErr) {
403
408
  console.warn('[knowledgebase] Embedding failed for chunk, ' +
404
409
  'inserting without vector:', embedErr.message);
@@ -502,7 +507,8 @@ async function search(query, options = {}) {
502
507
  WHERE embedding IS NOT NULL
503
508
  AND 1 - (embedding <=> $1) >= $2
504
509
  `;
505
- const params = [_toSql(queryEmbedding), threshold];
510
+ // pgvector toSql() requires a plain Array (see embed() note)
511
+ const params = [_toSql(Array.from(queryEmbedding)), threshold];
506
512
 
507
513
  if (project_id) {
508
514
  sql += ` AND project_id = $${params.length + 1}`;
@@ -139,6 +139,7 @@ export const SUPPORTED_FLAGS = {
139
139
  '--skip-prepare': 'skipPrepare',
140
140
  '--skip-sync': 'skipSync',
141
141
  '--skip-knowledgebase': 'skipKnowledgebase',
142
+ '--knowledgebase': 'knowledgebase',
142
143
  '--help': 'help',
143
144
  '-h': 'help',
144
145
  '--version': 'version',
@@ -141,6 +141,14 @@ export async function main(argv) {
141
141
  return EXIT_CODES.SUCCESS
142
142
  }
143
143
 
144
+ // When --knowledgebase is passed, run ONLY the knowledgebase phase
145
+ if (flags.knowledgebase) {
146
+ flags.skipHooks = true
147
+ flags.skipPrepare = true
148
+ flags.skipSync = true
149
+ flags.skipKnowledgebase = false
150
+ }
151
+
144
152
  // ── 3. Display header banner ────────────────────────────────────────────
145
153
  if (!flags.quiet) {
146
154
  header()
@@ -116,6 +116,7 @@ const defaultFlags = {
116
116
  skipPrepare: false,
117
117
  skipSync: false,
118
118
  skipKnowledgebase: false,
119
+ knowledgebase: false,
119
120
  help: false,
120
121
  version: false,
121
122
  quiet: false,
@@ -549,6 +550,48 @@ describe('main() — setup orchestrator', () => {
549
550
  expect(registerKnowledgebase).toHaveBeenCalledTimes(1)
550
551
  })
551
552
 
553
+ // ── 16b. --knowledgebase flag (T6: run ONLY the knowledgebase phase) ─────
554
+
555
+ it('--knowledgebase: runs only discovery + knowledgebase phases, exits 0', async () => {
556
+ parseCliArgs.mockReturnValue({ ...defaultFlags, knowledgebase: true })
557
+
558
+ const code = await main(['--knowledgebase'])
559
+
560
+ expect(code).toBe(EXIT_CODES.SUCCESS)
561
+ expect(parseCliArgs).toHaveBeenCalledWith(['--knowledgebase'])
562
+ // Discovery always runs
563
+ expect(discover).toHaveBeenCalledTimes(1)
564
+ // All other phases are skipped by the --knowledgebase shortcut
565
+ expect(installHooks).not.toHaveBeenCalled()
566
+ expect(handlePrepare).not.toHaveBeenCalled()
567
+ expect(initHusky).not.toHaveBeenCalled()
568
+ expect(runSyncPhase).not.toHaveBeenCalled()
569
+ // Knowledgebase phase runs
570
+ expect(registerKnowledgebase).toHaveBeenCalledTimes(1)
571
+ // Header and summary shown (not quiet)
572
+ expect(header).toHaveBeenCalledTimes(1)
573
+ expect(summary).toHaveBeenCalledTimes(1)
574
+ })
575
+
576
+ it('--knowledgebase: still runs knowledgebase phase when skipKnowledgebase was also passed', async () => {
577
+ // --knowledgebase forces skipKnowledgebase back to false so the phase runs
578
+ parseCliArgs.mockReturnValue({
579
+ ...defaultFlags,
580
+ knowledgebase: true,
581
+ skipKnowledgebase: true,
582
+ })
583
+
584
+ const code = await main(['--knowledgebase', '--skip-knowledgebase'])
585
+
586
+ expect(code).toBe(EXIT_CODES.SUCCESS)
587
+ expect(discover).toHaveBeenCalledTimes(1)
588
+ expect(installHooks).not.toHaveBeenCalled()
589
+ expect(handlePrepare).not.toHaveBeenCalled()
590
+ expect(initHusky).not.toHaveBeenCalled()
591
+ expect(runSyncPhase).not.toHaveBeenCalled()
592
+ expect(registerKnowledgebase).toHaveBeenCalledTimes(1)
593
+ })
594
+
552
595
  // ── 17. Verify parseCliArgs receives the argv ───────────────────────────
553
596
 
554
597
  it('passes argv array to parseCliArgs unchanged', async () => {
@@ -63,8 +63,8 @@ export async function registerKnowledgebase(context) {
63
63
  // ── DATABASE_URL check ───────────────────────────────────────────────
64
64
  if (!process.env.DATABASE_URL) {
65
65
  const message =
66
- 'DATABASE_URL not configured. Set up later with: ' +
67
- 'npx ai-workflow-template setup --knowledgebase'
66
+ 'DATABASE_URL not configured. Set DATABASE_URL in your .env file, ' +
67
+ 'then re-run: npx @abarcenas/ai-workflow-template --knowledgebase'
68
68
  logWarn(
69
69
  `[knowledgebase] Skipping Phase 6 \u2014 ${message}`,
70
70
  )
@@ -161,8 +161,12 @@ describe('registerKnowledgebase', () => {
161
161
 
162
162
  expect(result).toEqual({
163
163
  action: 'skipped',
164
- message: expect.stringContaining('DATABASE_URL'),
164
+ message: expect.stringContaining('DATABASE_URL not configured'),
165
165
  })
166
+ // The warning must guide the consumer to set DATABASE_URL and re-run
167
+ // with the scoped package name and the --knowledgebase flag.
168
+ expect(result.message).toContain('@abarcenas/ai-workflow-template')
169
+ expect(result.message).toContain('--knowledgebase')
166
170
  expect(spawn).not.toHaveBeenCalled()
167
171
  })
168
172
 
@@ -422,6 +422,8 @@ export function help() {
422
422
  skipHooks: 'Skip git hook installation phase',
423
423
  skipPrepare: 'Skip prepare script modification phase',
424
424
  skipSync: 'Skip file sync phase',
425
+ skipKnowledgebase: 'Skip knowledgebase registration phase',
426
+ knowledgebase: 'Run ONLY the knowledgebase registration phase',
425
427
  help: 'Show this help message and exit',
426
428
  version: 'Show the version number and exit',
427
429
  quiet: 'Suppress non-error output (quiet mode)',
@@ -435,6 +437,8 @@ export function help() {
435
437
  'skipHooks',
436
438
  'skipPrepare',
437
439
  'skipSync',
440
+ 'skipKnowledgebase',
441
+ 'knowledgebase',
438
442
  'help',
439
443
  'version',
440
444
  'quiet',
@@ -446,7 +450,7 @@ export function help() {
446
450
  write(`${BLD}${PACKAGE_NAME}${RST} ${GRN}v${version}${RST}`)
447
451
  write()
448
452
  write(` ${BLD}Usage:${RST}`)
449
- write(` npx ${PACKAGE_NAME} setup [options]`)
453
+ write(` npx ${PACKAGE_NAME} [options]`)
450
454
  write(` ${BIN_NAME} [options]`)
451
455
  write()
452
456
  write(` ${BLD}Description:${RST}`)
@@ -468,9 +472,10 @@ export function help() {
468
472
 
469
473
  write()
470
474
  write(` ${BLD}Examples:${RST}`)
471
- write(` npx ${PACKAGE_NAME} setup`)
472
- write(` npx ${PACKAGE_NAME} setup --dry-run --verbose`)
473
- write(` npx ${PACKAGE_NAME} setup --force --skip-hooks`)
475
+ write(` npx ${PACKAGE_NAME}`)
476
+ write(` npx ${PACKAGE_NAME} --dry-run --verbose`)
477
+ write(` npx ${PACKAGE_NAME} --knowledgebase`)
478
+ write(` npx ${PACKAGE_NAME} --force --skip-hooks`)
474
479
  write(` ${BIN_NAME} --help`)
475
480
  write()
476
481
  }