@abarcenas/ai-workflow-template 1.45.0 → 1.46.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/config/README.md +43 -0
- package/.agents/config/domain.md +37 -0
- package/.agents/instructions/learned-knowledge.instructions.md +110 -0
- package/.agents/instructions/memory-bank.instructions.md +11 -0
- package/.agents/skills/domain-modeling/ADR-FORMAT.md +47 -0
- package/.agents/skills/domain-modeling/GLOSSARY-FORMAT.md +60 -0
- package/.agents/skills/domain-modeling/SKILL.md +74 -0
- package/.agents/skills/grill-me/SKILL.md +7 -0
- package/.agents/skills/grill-with-docs/SKILL.md +7 -0
- package/.agents/skills/grilling/SKILL.md +28 -0
- package/.agents/skills/prototype/LOGIC.md +67 -0
- package/.agents/skills/prototype/SKILL.md +26 -0
- package/.agents/skills/prototype/UI.md +112 -0
- package/.github/workflows/npm-publish.yml +10 -0
- package/.github/workflows/pr-test.yml +31 -0
- package/.opencode/agents/orchestrator/orchestrator.agent.md +68 -3
- package/.opencode/agents/scope.agent.md +299 -0
- package/AGENTS.md +42 -3
- package/README.md +5 -2
- package/package.json +1 -1
- package/scripts/bump-version.test.js +238 -0
- package/scripts/mcp/playwright-mcp-launcher.js +55 -29
- package/scripts/mcp/playwright-mcp-launcher.test.js +217 -0
- package/scripts/mcp-memory-server.js +93 -76
- package/scripts/mcp-memory-server.test.js +398 -0
- package/scripts/memory-cli.test.js +297 -0
- package/scripts/memory-index.js +119 -31
- package/scripts/memory-index.test.js +1173 -0
- package/scripts/normalize-memory.js +26 -1
- package/scripts/normalize-memory.test.js +753 -0
- package/scripts/setup/discover.test.js +124 -2
- package/scripts/setup/hooks.test.js +188 -1
- package/scripts/setup/husky-init.test.js +311 -0
- package/scripts/setup/index.js +35 -5
- package/scripts/setup/index.test.js +279 -0
- package/scripts/setup/knowledgebase.test.js +100 -0
- package/scripts/setup/skills-phase.js +568 -0
- package/scripts/setup/skills-phase.test.js +1274 -0
- package/scripts/setup/sync-phase.js +6 -1
- package/scripts/setup/sync-phase.test.js +180 -2
- package/scripts/setup/ui.test.js +579 -0
- package/scripts/setup/utils.test.js +586 -0
- package/scripts/sync-unit.test.js +1342 -0
- package/scripts/sync.js +540 -246
- package/scripts/sync.test.js +283 -0
- package/scripts/vocab-sync.test.js +88 -0
- package/skills-lock.json +90 -0
package/scripts/memory-index.js
CHANGED
|
@@ -238,55 +238,132 @@ async function indexFile(filePath) {
|
|
|
238
238
|
for (let i = 0; i < chunks.length; i++) {
|
|
239
239
|
const chunkHash = hashContent(chunks[i]);
|
|
240
240
|
|
|
241
|
-
// Check if already indexed with same
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
241
|
+
// Check if already indexed with the same content AND a resolvable vector.
|
|
242
|
+
// The LEFT JOIN distinguishes a live link from a dangling one (legacy DB or
|
|
243
|
+
// a crash between the vector insert and the link update).
|
|
244
|
+
const existing = db.prepare(`
|
|
245
|
+
SELECT mc.id, mc.content_hash, mc.vec_rowid, v.rowid AS vec_exists
|
|
246
|
+
FROM memory_chunks mc
|
|
247
|
+
LEFT JOIN vec_memory v ON v.rowid = mc.vec_rowid
|
|
248
|
+
WHERE mc.source_file = ? AND mc.chunk_index = ?
|
|
249
|
+
`).get(sourceFile, i);
|
|
250
|
+
|
|
251
|
+
if (existing && existing.content_hash === chunkHash && existing.vec_exists != null) {
|
|
247
252
|
skipped++;
|
|
248
253
|
continue;
|
|
249
254
|
}
|
|
250
255
|
|
|
251
|
-
let
|
|
256
|
+
let chunkId;
|
|
252
257
|
if (existing) {
|
|
253
|
-
|
|
258
|
+
chunkId = Number(existing.id);
|
|
254
259
|
const oldVecRowid = existing.vec_rowid;
|
|
255
260
|
|
|
256
|
-
//
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
261
|
+
// Detach the old vector before re-embedding so a failed embed cannot
|
|
262
|
+
// leave a dangling vec_rowid, then delete it: vec0 has no foreign key,
|
|
263
|
+
// so an unlinked row would be an invisible storage leak.
|
|
264
|
+
db.transaction(() => {
|
|
265
|
+
db.prepare(
|
|
266
|
+
'UPDATE memory_chunks SET content = ?, content_hash = ?, vec_rowid = NULL, updated_at = CURRENT_TIMESTAMP WHERE id = ?'
|
|
267
|
+
).run(chunks[i], chunkHash, chunkId);
|
|
268
|
+
|
|
269
|
+
if (oldVecRowid != null) {
|
|
270
|
+
db.prepare('DELETE FROM vec_memory WHERE rowid = ?').run(Number(oldVecRowid));
|
|
271
|
+
}
|
|
272
|
+
})();
|
|
266
273
|
} else {
|
|
267
274
|
// Insert new chunk
|
|
268
275
|
const info = db.prepare(
|
|
269
276
|
'INSERT INTO memory_chunks (source_file, chunk_index, content, content_hash) VALUES (?, ?, ?, ?)'
|
|
270
277
|
).run(sourceFile, i, chunks[i], chunkHash);
|
|
271
|
-
|
|
278
|
+
chunkId = Number(info.lastInsertRowid);
|
|
272
279
|
}
|
|
273
280
|
|
|
274
281
|
try {
|
|
275
282
|
const embedding = await embed(chunks[i]);
|
|
276
|
-
// Insert
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
283
|
+
// Insert + link atomically: a vector inserted without its link (or vice
|
|
284
|
+
// versa) would be an orphan.
|
|
285
|
+
db.transaction(() => {
|
|
286
|
+
// Insert into vec0 without explicit rowid (auto-assigned)
|
|
287
|
+
const vecInfo = db.prepare('INSERT INTO vec_memory (embedding) VALUES (?)').run(embedding);
|
|
288
|
+
const vecRowid = Number(vecInfo.lastInsertRowid);
|
|
289
|
+
// Link vec0 rowid back to memory_chunks
|
|
290
|
+
db.prepare('UPDATE memory_chunks SET vec_rowid = ? WHERE id = ?').run(vecRowid, chunkId);
|
|
291
|
+
})();
|
|
281
292
|
indexed++;
|
|
282
293
|
} catch (err) {
|
|
283
294
|
console.warn(`[memory-index] Failed to embed chunk ${i} of ${sourceFile}: ${err.message}`);
|
|
284
295
|
}
|
|
285
296
|
}
|
|
286
297
|
|
|
298
|
+
// A file that lost sections keeps its old trailing chunks (and their
|
|
299
|
+
// vectors) forever otherwise: (source_file, chunk_index) is the identity, so
|
|
300
|
+
// indices past the new chunk count are unreachable debris.
|
|
301
|
+
const removedChunks = db.prepare(
|
|
302
|
+
'SELECT id, vec_rowid FROM memory_chunks WHERE source_file = ? AND chunk_index >= ?'
|
|
303
|
+
).all(sourceFile, chunks.length);
|
|
304
|
+
|
|
305
|
+
if (removedChunks.length > 0) {
|
|
306
|
+
db.transaction(() => {
|
|
307
|
+
const deleteVector = db.prepare('DELETE FROM vec_memory WHERE rowid = ?');
|
|
308
|
+
const deleteChunk = db.prepare('DELETE FROM memory_chunks WHERE id = ?');
|
|
309
|
+
for (const row of removedChunks) {
|
|
310
|
+
if (row.vec_rowid != null) deleteVector.run(Number(row.vec_rowid));
|
|
311
|
+
deleteChunk.run(Number(row.id));
|
|
312
|
+
}
|
|
313
|
+
})();
|
|
314
|
+
}
|
|
315
|
+
|
|
287
316
|
return { indexed, skipped };
|
|
288
317
|
}
|
|
289
318
|
|
|
319
|
+
/**
|
|
320
|
+
* Delete chunks (and their vectors) whose source file is no longer present.
|
|
321
|
+
* Returns the number of chunks pruned.
|
|
322
|
+
*/
|
|
323
|
+
function pruneMissingSources(db, activeSources) {
|
|
324
|
+
const stale = db
|
|
325
|
+
.prepare('SELECT id, source_file, vec_rowid FROM memory_chunks')
|
|
326
|
+
.all()
|
|
327
|
+
.filter((row) => !activeSources.has(row.source_file));
|
|
328
|
+
|
|
329
|
+
if (stale.length === 0) return 0;
|
|
330
|
+
|
|
331
|
+
db.transaction(() => {
|
|
332
|
+
const deleteVector = db.prepare('DELETE FROM vec_memory WHERE rowid = ?');
|
|
333
|
+
const deleteChunk = db.prepare('DELETE FROM memory_chunks WHERE id = ?');
|
|
334
|
+
for (const row of stale) {
|
|
335
|
+
if (row.vec_rowid != null) deleteVector.run(Number(row.vec_rowid));
|
|
336
|
+
deleteChunk.run(Number(row.id));
|
|
337
|
+
}
|
|
338
|
+
})();
|
|
339
|
+
|
|
340
|
+
return stale.length;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Delete vector rows that no chunk links to. Older versions of this module
|
|
345
|
+
* orphaned them on every content change; a full rebuild would eventually clear
|
|
346
|
+
* them, but an incremental update should heal the index in place.
|
|
347
|
+
* Returns the number of orphaned vectors removed.
|
|
348
|
+
*/
|
|
349
|
+
function pruneOrphanVectors(db) {
|
|
350
|
+
const orphans = db.prepare(`
|
|
351
|
+
SELECT v.rowid AS rowid
|
|
352
|
+
FROM vec_memory v
|
|
353
|
+
LEFT JOIN memory_chunks mc ON mc.vec_rowid = v.rowid
|
|
354
|
+
WHERE mc.id IS NULL
|
|
355
|
+
`).all();
|
|
356
|
+
|
|
357
|
+
if (orphans.length === 0) return 0;
|
|
358
|
+
|
|
359
|
+
db.transaction(() => {
|
|
360
|
+
const deleteVector = db.prepare('DELETE FROM vec_memory WHERE rowid = ?');
|
|
361
|
+
for (const row of orphans) deleteVector.run(Number(row.rowid));
|
|
362
|
+
})();
|
|
363
|
+
|
|
364
|
+
return orphans.length;
|
|
365
|
+
}
|
|
366
|
+
|
|
290
367
|
/**
|
|
291
368
|
* Collect all .md files from a directory recursively.
|
|
292
369
|
*/
|
|
@@ -360,29 +437,35 @@ async function incrementalUpdate() {
|
|
|
360
437
|
let totalIndexed = 0;
|
|
361
438
|
let totalSkipped = 0;
|
|
362
439
|
const changed = [];
|
|
440
|
+
const activeSources = new Set();
|
|
441
|
+
const db = await getDB();
|
|
363
442
|
|
|
364
443
|
for (const file of allFiles) {
|
|
365
444
|
if (file.includes('/.index/')) continue;
|
|
366
445
|
|
|
367
446
|
const content = readFileSync(file, 'utf8');
|
|
368
447
|
const sourceFile = basename(file);
|
|
448
|
+
activeSources.add(sourceFile);
|
|
369
449
|
const { body } = parseFrontmatter(content);
|
|
370
|
-
const bodyHash = hashContent(body);
|
|
371
450
|
|
|
372
|
-
// Quick check: if the full body hash matches the last indexed state
|
|
373
|
-
|
|
374
|
-
const existingChunks = db.prepare(
|
|
375
|
-
|
|
376
|
-
|
|
451
|
+
// Quick check: if the full body hash matches the last indexed state and
|
|
452
|
+
// every chunk still resolves to a vector, skip.
|
|
453
|
+
const existingChunks = db.prepare(`
|
|
454
|
+
SELECT mc.content_hash, mc.vec_rowid, v.rowid AS vec_exists
|
|
455
|
+
FROM memory_chunks mc
|
|
456
|
+
LEFT JOIN vec_memory v ON v.rowid = mc.vec_rowid
|
|
457
|
+
WHERE mc.source_file = ?
|
|
458
|
+
`).all(sourceFile);
|
|
377
459
|
|
|
378
460
|
const combinedHash = existingChunks.map(c => c.content_hash).join('|');
|
|
461
|
+
const allVectorsPresent = existingChunks.every((c) => c.vec_exists != null);
|
|
379
462
|
|
|
380
463
|
if (existingChunks.length > 0) {
|
|
381
464
|
// Check if any chunk's content changed by recomputing hashes
|
|
382
465
|
const chunks = chunkMarkdown(body);
|
|
383
466
|
const newCombinedHash = chunks.map(c => hashContent(c)).join('|');
|
|
384
467
|
|
|
385
|
-
if (newCombinedHash === combinedHash) {
|
|
468
|
+
if (newCombinedHash === combinedHash && allVectorsPresent) {
|
|
386
469
|
totalSkipped += existingChunks.length;
|
|
387
470
|
continue;
|
|
388
471
|
}
|
|
@@ -396,6 +479,11 @@ async function incrementalUpdate() {
|
|
|
396
479
|
totalSkipped += skipped;
|
|
397
480
|
}
|
|
398
481
|
|
|
482
|
+
// Reclaim rows for files that disappeared and vectors that lost their link
|
|
483
|
+
// (including orphans left behind by older versions of this module).
|
|
484
|
+
pruneMissingSources(db, activeSources);
|
|
485
|
+
pruneOrphanVectors(db);
|
|
486
|
+
|
|
399
487
|
return { indexed: totalIndexed, skipped: totalSkipped, changed };
|
|
400
488
|
}
|
|
401
489
|
|