@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.
- package/.agents/instructions/learned-knowledge.instructions.md +20 -0
- package/bin/setup.js +3 -0
- package/package.json +3 -3
- package/scripts/knowledgebase-index.js +10 -4
- package/scripts/setup/constants.js +1 -0
- package/scripts/setup/index.js +8 -0
- package/scripts/setup/index.test.js +43 -0
- package/scripts/setup/knowledgebase.js +2 -2
- package/scripts/setup/knowledgebase.test.js +5 -1
- package/scripts/setup/ui.js +9 -4
|
@@ -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.
|
|
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
|
-
*
|
|
294
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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',
|
package/scripts/setup/index.js
CHANGED
|
@@ -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
|
|
67
|
-
'npx ai-workflow-template
|
|
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
|
|
package/scripts/setup/ui.js
CHANGED
|
@@ -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}
|
|
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}
|
|
472
|
-
write(` npx ${PACKAGE_NAME}
|
|
473
|
-
write(` npx ${PACKAGE_NAME}
|
|
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
|
}
|