@codexa/cli 9.0.2 → 9.0.4
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/commands/architect.test.ts +531 -0
- package/commands/architect.ts +75 -17
- package/commands/check.ts +7 -17
- package/commands/clear.ts +40 -1
- package/commands/decide.ts +37 -49
- package/commands/discover.ts +136 -28
- package/commands/knowledge.test.ts +160 -0
- package/commands/knowledge.ts +192 -102
- package/commands/patterns.test.ts +169 -0
- package/commands/patterns.ts +6 -13
- package/commands/plan.test.ts +73 -0
- package/commands/plan.ts +18 -66
- package/commands/product.ts +8 -17
- package/commands/research.ts +4 -3
- package/commands/review.ts +190 -28
- package/commands/spec-resolver.test.ts +119 -0
- package/commands/spec-resolver.ts +90 -0
- package/commands/standards.ts +7 -15
- package/commands/sync.ts +89 -0
- package/commands/task.ts +72 -167
- package/commands/utils.test.ts +100 -0
- package/commands/utils.ts +78 -706
- package/db/schema.test.ts +760 -0
- package/db/schema.ts +284 -130
- package/gates/validator.test.ts +675 -0
- package/gates/validator.ts +112 -27
- package/package.json +3 -1
- package/protocol/process-return.ts +25 -93
- package/protocol/subagent-protocol.test.ts +936 -0
- package/protocol/subagent-protocol.ts +19 -1
- package/workflow.ts +176 -67
package/commands/discover.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
getDetailedTechnologies,
|
|
12
12
|
type UnifiedDetectionResult,
|
|
13
13
|
} from "../detectors/loader";
|
|
14
|
+
import { CodexaError } from "../errors";
|
|
14
15
|
|
|
15
16
|
interface StackDetection {
|
|
16
17
|
frontend?: string;
|
|
@@ -172,9 +173,7 @@ export function discoverConfirm(): void {
|
|
|
172
173
|
|
|
173
174
|
const pending = db.query("SELECT * FROM project WHERE id = 'pending'").get() as any;
|
|
174
175
|
if (!pending) {
|
|
175
|
-
|
|
176
|
-
console.error("Execute: discover start primeiro\n");
|
|
177
|
-
process.exit(1);
|
|
176
|
+
throw new CodexaError("Nenhuma descoberta pendente.\nExecute: discover start primeiro");
|
|
178
177
|
}
|
|
179
178
|
|
|
180
179
|
const data = JSON.parse(pending.stack);
|
|
@@ -183,9 +182,9 @@ export function discoverConfirm(): void {
|
|
|
183
182
|
// Mover de pending para default
|
|
184
183
|
db.run("DELETE FROM project WHERE id = 'pending'");
|
|
185
184
|
db.run(
|
|
186
|
-
`INSERT INTO project (id, name, stack, discovered_at, updated_at, cli_version)
|
|
187
|
-
VALUES ('default', ?, ?, ?, ?, ?)`,
|
|
188
|
-
["Projeto", JSON.stringify(data.stack), now, now, pkg.version]
|
|
185
|
+
`INSERT INTO project (id, name, stack, discovered_at, updated_at, cli_version, last_discover_at)
|
|
186
|
+
VALUES ('default', ?, ?, ?, ?, ?, ?)`,
|
|
187
|
+
["Projeto", JSON.stringify(data.stack), now, now, pkg.version, now]
|
|
189
188
|
);
|
|
190
189
|
|
|
191
190
|
// Criar standards baseados na estrutura detectada
|
|
@@ -283,9 +282,7 @@ export function discoverShow(json: boolean = false): void {
|
|
|
283
282
|
const project = db.query("SELECT * FROM project WHERE id = 'default'").get() as any;
|
|
284
283
|
|
|
285
284
|
if (!project) {
|
|
286
|
-
|
|
287
|
-
console.error("Execute: discover start\n");
|
|
288
|
-
process.exit(1);
|
|
285
|
+
throw new CodexaError("Projeto nao descoberto.\nExecute: discover start");
|
|
289
286
|
}
|
|
290
287
|
|
|
291
288
|
const standards = db.query("SELECT * FROM standards ORDER BY category, scope").all() as any[];
|
|
@@ -381,9 +378,7 @@ export async function discoverRefresh(options: { force?: boolean } = {}): Promis
|
|
|
381
378
|
|
|
382
379
|
const currentProject = db.query("SELECT * FROM project WHERE id = 'default'").get() as any;
|
|
383
380
|
if (!currentProject) {
|
|
384
|
-
|
|
385
|
-
console.error("Execute: discover start primeiro\n");
|
|
386
|
-
process.exit(1);
|
|
381
|
+
throw new CodexaError("Projeto nao descoberto.\nExecute: discover start primeiro");
|
|
387
382
|
}
|
|
388
383
|
|
|
389
384
|
const currentStack = JSON.parse(currentProject.stack);
|
|
@@ -460,6 +455,129 @@ export async function discoverRefresh(options: { force?: boolean } = {}): Promis
|
|
|
460
455
|
}
|
|
461
456
|
}
|
|
462
457
|
|
|
458
|
+
export async function discoverIncremental(): Promise<void> {
|
|
459
|
+
initSchema();
|
|
460
|
+
const db = getDb();
|
|
461
|
+
|
|
462
|
+
const project = db.query("SELECT * FROM project WHERE id = 'default'").get() as any;
|
|
463
|
+
if (!project) {
|
|
464
|
+
throw new CodexaError("Projeto nao descoberto.\nExecute: discover start primeiro");
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Data de referencia: last_discover_at > discovered_at > fallback 30 dias
|
|
468
|
+
const sinceDate = project.last_discover_at || project.discovered_at ||
|
|
469
|
+
new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
|
|
470
|
+
|
|
471
|
+
console.log(`\nDiscover incremental desde: ${sinceDate}\n`);
|
|
472
|
+
|
|
473
|
+
// Listar arquivos modificados via git
|
|
474
|
+
const gitResult = spawnSync("git", [
|
|
475
|
+
"diff", "--name-only", "--diff-filter=ACMR",
|
|
476
|
+
`--since=${sinceDate}`, "HEAD"
|
|
477
|
+
], { encoding: "utf-8", timeout: 10000 });
|
|
478
|
+
|
|
479
|
+
// Fallback: se --since nao funciona com diff, usar log
|
|
480
|
+
let modifiedFiles: string[] = [];
|
|
481
|
+
if (gitResult.status !== 0 || !gitResult.stdout.trim()) {
|
|
482
|
+
const logResult = spawnSync("git", [
|
|
483
|
+
"log", "--since", sinceDate, "--name-only", "--pretty=format:", "--diff-filter=ACMR"
|
|
484
|
+
], { encoding: "utf-8", timeout: 10000 });
|
|
485
|
+
|
|
486
|
+
if (logResult.status === 0 && logResult.stdout.trim()) {
|
|
487
|
+
modifiedFiles = [...new Set(logResult.stdout.trim().split("\n").filter(f => f.trim()))];
|
|
488
|
+
}
|
|
489
|
+
} else {
|
|
490
|
+
modifiedFiles = [...new Set(gitResult.stdout.trim().split("\n").filter(f => f.trim()))];
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Filtrar apenas arquivos que ainda existem e sao codigo
|
|
494
|
+
const codeExtensions = [".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".cs", ".dart"];
|
|
495
|
+
const existingFiles = modifiedFiles.filter(f => {
|
|
496
|
+
const fullPath = join(process.cwd(), f);
|
|
497
|
+
return existsSync(fullPath) && codeExtensions.some(ext => f.endsWith(ext));
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
if (existingFiles.length === 0) {
|
|
501
|
+
console.log("Nenhum arquivo de codigo modificado desde ultimo discover.");
|
|
502
|
+
console.log("Stack e utilities estao atualizados.\n");
|
|
503
|
+
|
|
504
|
+
// Atualizar timestamp mesmo sem mudancas
|
|
505
|
+
const now = new Date().toISOString();
|
|
506
|
+
db.run("UPDATE project SET last_discover_at = ? WHERE id = 'default'", [now]);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
console.log(`[1/3] Analisando ${existingFiles.length} arquivo(s) modificado(s)...`);
|
|
511
|
+
|
|
512
|
+
// Importar funcoes de patterns
|
|
513
|
+
const { extractUtilitiesFromFile, inferScopeFromPath } = await import("./patterns");
|
|
514
|
+
const { upsertUtility } = await import("../db/schema");
|
|
515
|
+
|
|
516
|
+
// Extrair utilities dos arquivos modificados
|
|
517
|
+
let utilitiesUpdated = 0;
|
|
518
|
+
for (const file of existingFiles) {
|
|
519
|
+
const fullPath = join(process.cwd(), file);
|
|
520
|
+
const utilities = extractUtilitiesFromFile(fullPath);
|
|
521
|
+
const scope = inferScopeFromPath(file);
|
|
522
|
+
|
|
523
|
+
for (const util of utilities) {
|
|
524
|
+
upsertUtility({
|
|
525
|
+
filePath: file,
|
|
526
|
+
utilityName: util.name,
|
|
527
|
+
utilityType: util.type,
|
|
528
|
+
scope,
|
|
529
|
+
signature: util.signature,
|
|
530
|
+
});
|
|
531
|
+
utilitiesUpdated++;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
console.log(` ✓ ${utilitiesUpdated} utilities registradas/atualizadas`);
|
|
536
|
+
|
|
537
|
+
// Re-detectar stack e comparar
|
|
538
|
+
console.log("\n[2/3] Verificando mudancas no stack...");
|
|
539
|
+
const currentStack = JSON.parse(project.stack);
|
|
540
|
+
const newStack = await detectStack();
|
|
541
|
+
|
|
542
|
+
const stackChanges: { key: string; from: string | undefined; to: string | undefined }[] = [];
|
|
543
|
+
const allKeys = new Set([...Object.keys(currentStack), ...Object.keys(newStack)]);
|
|
544
|
+
|
|
545
|
+
for (const key of allKeys) {
|
|
546
|
+
const current = currentStack[key as keyof StackDetection];
|
|
547
|
+
const detected = newStack[key as keyof StackDetection];
|
|
548
|
+
if (current !== detected) {
|
|
549
|
+
stackChanges.push({ key, from: current, to: detected });
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (stackChanges.length > 0) {
|
|
554
|
+
console.log(" ⚠ Mudancas detectadas no stack:");
|
|
555
|
+
for (const change of stackChanges) {
|
|
556
|
+
const from = change.from || "(nao definido)";
|
|
557
|
+
const to = change.to || "(removido)";
|
|
558
|
+
console.log(` ${change.key}: ${from} → ${to}`);
|
|
559
|
+
}
|
|
560
|
+
console.log(" Use: discover refresh --force para aplicar");
|
|
561
|
+
} else {
|
|
562
|
+
console.log(" ✓ Stack inalterado");
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Atualizar timestamp
|
|
566
|
+
console.log("\n[3/3] Atualizando timestamp...");
|
|
567
|
+
const now = new Date().toISOString();
|
|
568
|
+
db.run("UPDATE project SET last_discover_at = ?, updated_at = ? WHERE id = 'default'", [now, now]);
|
|
569
|
+
|
|
570
|
+
// Resumo
|
|
571
|
+
console.log("\n" + "═".repeat(50));
|
|
572
|
+
console.log("DISCOVER INCREMENTAL CONCLUIDO");
|
|
573
|
+
console.log("═".repeat(50));
|
|
574
|
+
console.log(` Arquivos analisados: ${existingFiles.length}`);
|
|
575
|
+
console.log(` Utilities atualizadas: ${utilitiesUpdated}`);
|
|
576
|
+
console.log(` Mudancas no stack: ${stackChanges.length}`);
|
|
577
|
+
console.log(` Timestamp atualizado: ${now}`);
|
|
578
|
+
console.log("═".repeat(50) + "\n");
|
|
579
|
+
}
|
|
580
|
+
|
|
463
581
|
function generateStandardsMarkdown(): void {
|
|
464
582
|
const db = getDb();
|
|
465
583
|
const project = db.query("SELECT * FROM project WHERE id = 'default'").get() as any;
|
|
@@ -649,9 +767,7 @@ export function discoverPatternsShow(name: string, json: boolean = false): void
|
|
|
649
767
|
const pattern = db.query("SELECT * FROM implementation_patterns WHERE name = ?").get(name) as any;
|
|
650
768
|
|
|
651
769
|
if (!pattern) {
|
|
652
|
-
|
|
653
|
-
console.error("Use: discover patterns para listar todos\n");
|
|
654
|
-
process.exit(1);
|
|
770
|
+
throw new CodexaError(`Pattern '${name}' nao encontrado.\nUse: discover patterns para listar todos`);
|
|
655
771
|
}
|
|
656
772
|
|
|
657
773
|
const structure = JSON.parse(pattern.structure || "{}");
|
|
@@ -751,9 +867,7 @@ export function discoverPatternAdd(options: {
|
|
|
751
867
|
// Verificar se pattern ja existe
|
|
752
868
|
const existing = db.query("SELECT id FROM implementation_patterns WHERE name = ?").get(options.name);
|
|
753
869
|
if (existing) {
|
|
754
|
-
|
|
755
|
-
console.error("Use: discover pattern-edit para modificar\n");
|
|
756
|
-
process.exit(1);
|
|
870
|
+
throw new CodexaError(`Pattern '${options.name}' ja existe.\nUse: discover pattern-edit para modificar`);
|
|
757
871
|
}
|
|
758
872
|
|
|
759
873
|
const now = new Date().toISOString();
|
|
@@ -800,9 +914,7 @@ export function discoverPatternEdit(name: string, options: {
|
|
|
800
914
|
|
|
801
915
|
const existing = db.query("SELECT * FROM implementation_patterns WHERE name = ?").get(name) as any;
|
|
802
916
|
if (!existing) {
|
|
803
|
-
|
|
804
|
-
console.error("Use: discover patterns para listar todos\n");
|
|
805
|
-
process.exit(1);
|
|
917
|
+
throw new CodexaError(`Pattern '${name}' nao encontrado.\nUse: discover patterns para listar todos`);
|
|
806
918
|
}
|
|
807
919
|
|
|
808
920
|
const updates: string[] = [];
|
|
@@ -842,8 +954,7 @@ export function discoverPatternEdit(name: string, options: {
|
|
|
842
954
|
}
|
|
843
955
|
|
|
844
956
|
if (updates.length === 0) {
|
|
845
|
-
|
|
846
|
-
process.exit(1);
|
|
957
|
+
throw new CodexaError("Nenhuma opcao de atualizacao fornecida.");
|
|
847
958
|
}
|
|
848
959
|
|
|
849
960
|
updates.push("updated_at = ?");
|
|
@@ -868,8 +979,7 @@ export function discoverPatternRemove(name: string): void {
|
|
|
868
979
|
|
|
869
980
|
const existing = db.query("SELECT id FROM implementation_patterns WHERE name = ?").get(name);
|
|
870
981
|
if (!existing) {
|
|
871
|
-
|
|
872
|
-
process.exit(1);
|
|
982
|
+
throw new CodexaError(`Pattern '${name}' nao encontrado.`);
|
|
873
983
|
}
|
|
874
984
|
|
|
875
985
|
db.run("DELETE FROM implementation_patterns WHERE name = ?", [name]);
|
|
@@ -888,9 +998,7 @@ export function discoverRefreshPatterns(): void {
|
|
|
888
998
|
// Verificar se projeto foi descoberto
|
|
889
999
|
const project = db.query("SELECT * FROM project WHERE id = 'default'").get() as any;
|
|
890
1000
|
if (!project) {
|
|
891
|
-
|
|
892
|
-
console.error("Execute: discover start primeiro\n");
|
|
893
|
-
process.exit(1);
|
|
1001
|
+
throw new CodexaError("Projeto nao descoberto.\nExecute: discover start primeiro");
|
|
894
1002
|
}
|
|
895
1003
|
|
|
896
1004
|
console.log("\n⚠ Refresh de patterns requer analise manual do codigo.");
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "bun:test";
|
|
2
|
+
import { jaccardSimilarity, compactKnowledge } from "./knowledge";
|
|
3
|
+
import { getDb } from "../db/connection";
|
|
4
|
+
import { initSchema } from "../db/schema";
|
|
5
|
+
|
|
6
|
+
describe("jaccardSimilarity", () => {
|
|
7
|
+
it("returns 1 for identical strings", () => {
|
|
8
|
+
expect(jaccardSimilarity("hello world test", "hello world test")).toBe(1);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("returns 0 for completely different strings", () => {
|
|
12
|
+
expect(jaccardSimilarity("apple banana cherry", "delta echo foxtrot")).toBe(0);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("returns high similarity for near-duplicates", () => {
|
|
16
|
+
const sim = jaccardSimilarity(
|
|
17
|
+
"Implemented user authentication with JWT tokens",
|
|
18
|
+
"Implemented user authentication using JWT tokens"
|
|
19
|
+
);
|
|
20
|
+
expect(sim).toBeGreaterThan(0.7);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("returns 1 for two empty strings", () => {
|
|
24
|
+
expect(jaccardSimilarity("", "")).toBe(1);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("returns 0 when one string is empty", () => {
|
|
28
|
+
expect(jaccardSimilarity("hello world test", "")).toBe(0);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("is case insensitive", () => {
|
|
32
|
+
expect(jaccardSimilarity("Hello World Test", "hello world test")).toBe(1);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("ignores short words (<=2 chars)", () => {
|
|
36
|
+
// "a" and "is" are filtered out, only "cat" and "dog" matter
|
|
37
|
+
const sim = jaccardSimilarity("a cat is here", "a dog is here");
|
|
38
|
+
// "cat" vs "dog" + "here" in common
|
|
39
|
+
expect(sim).toBeGreaterThan(0);
|
|
40
|
+
expect(sim).toBeLessThan(1);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("strips punctuation", () => {
|
|
44
|
+
const sim = jaccardSimilarity("hello, world! test.", "hello world test");
|
|
45
|
+
expect(sim).toBe(1);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("compactKnowledge", () => {
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
initSchema();
|
|
52
|
+
const db = getDb();
|
|
53
|
+
db.run("DELETE FROM knowledge_graph");
|
|
54
|
+
db.run("DELETE FROM knowledge");
|
|
55
|
+
db.run("DELETE FROM tasks");
|
|
56
|
+
db.run("DELETE FROM context");
|
|
57
|
+
db.run("DELETE FROM specs");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
function createSpec(id: string, phase: string) {
|
|
61
|
+
const db = getDb();
|
|
62
|
+
const now = new Date().toISOString();
|
|
63
|
+
db.run(
|
|
64
|
+
"INSERT INTO specs (id, name, phase, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",
|
|
65
|
+
[id, `Feature ${id}`, phase, now, now]
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function addKnowledgeEntry(specId: string, content: string, category: string, severity: string, createdAt?: string) {
|
|
70
|
+
const db = getDb();
|
|
71
|
+
const now = createdAt || new Date().toISOString();
|
|
72
|
+
db.run(
|
|
73
|
+
`INSERT INTO knowledge (spec_id, task_origin, category, content, severity, broadcast_to, created_at)
|
|
74
|
+
VALUES (?, 0, ?, ?, ?, 'all', ?)`,
|
|
75
|
+
[specId, category, content, severity, now]
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
it("merges similar entries in same category and spec", () => {
|
|
80
|
+
createSpec("spec-a", "implementing");
|
|
81
|
+
addKnowledgeEntry("spec-a", "Implemented user authentication with JWT tokens for the login flow", "discovery", "info");
|
|
82
|
+
addKnowledgeEntry("spec-a", "Implemented user authentication with JWT tokens for login flow", "discovery", "info");
|
|
83
|
+
|
|
84
|
+
// Dry run first
|
|
85
|
+
compactKnowledge({ dryRun: true, json: true });
|
|
86
|
+
|
|
87
|
+
// Actual compact
|
|
88
|
+
compactKnowledge({ json: true });
|
|
89
|
+
|
|
90
|
+
const db = getDb();
|
|
91
|
+
const archived = db.query("SELECT COUNT(*) as c FROM knowledge WHERE severity = 'archived'").get() as any;
|
|
92
|
+
expect(archived.c).toBe(1);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("keeps higher severity entry when merging", () => {
|
|
96
|
+
createSpec("spec-a", "implementing");
|
|
97
|
+
addKnowledgeEntry("spec-a", "Database connection timeout causing failures in production", "blocker", "critical");
|
|
98
|
+
addKnowledgeEntry("spec-a", "Database connection timeout causing failures in production system", "blocker", "info");
|
|
99
|
+
|
|
100
|
+
compactKnowledge({ json: true });
|
|
101
|
+
|
|
102
|
+
const db = getDb();
|
|
103
|
+
const remaining = db.query("SELECT * FROM knowledge WHERE severity != 'archived'").all() as any[];
|
|
104
|
+
expect(remaining.length).toBe(1);
|
|
105
|
+
expect(remaining[0].severity).toBe("critical");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("archives old info entries (>7 days)", () => {
|
|
109
|
+
createSpec("spec-a", "implementing");
|
|
110
|
+
const oldDate = new Date(Date.now() - 10 * 24 * 60 * 60 * 1000).toISOString();
|
|
111
|
+
addKnowledgeEntry("spec-a", "Some old discovery that is not referenced anywhere in the graph", "discovery", "info", oldDate);
|
|
112
|
+
addKnowledgeEntry("spec-a", "A recent discovery that should not be archived by this compaction", "discovery", "info");
|
|
113
|
+
|
|
114
|
+
compactKnowledge({ json: true });
|
|
115
|
+
|
|
116
|
+
const db = getDb();
|
|
117
|
+
const archived = db.query("SELECT COUNT(*) as c FROM knowledge WHERE severity = 'archived'").get() as any;
|
|
118
|
+
expect(archived.c).toBe(1);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("archives entries from completed specs", () => {
|
|
122
|
+
createSpec("spec-done", "completed");
|
|
123
|
+
addKnowledgeEntry("spec-done", "Something learned during completed feature implementation", "discovery", "info");
|
|
124
|
+
addKnowledgeEntry("spec-done", "A critical blocker that was found during the implementation phase", "blocker", "critical");
|
|
125
|
+
|
|
126
|
+
compactKnowledge({ json: true });
|
|
127
|
+
|
|
128
|
+
const db = getDb();
|
|
129
|
+
const archived = db.query("SELECT COUNT(*) as c FROM knowledge WHERE severity = 'archived'").get() as any;
|
|
130
|
+
expect(archived.c).toBe(2);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("dry-run mode does not modify DB", () => {
|
|
134
|
+
createSpec("spec-done", "completed");
|
|
135
|
+
addKnowledgeEntry("spec-done", "Something that would be archived in a real compaction run", "discovery", "info");
|
|
136
|
+
|
|
137
|
+
compactKnowledge({ dryRun: true, json: true });
|
|
138
|
+
|
|
139
|
+
const db = getDb();
|
|
140
|
+
const archived = db.query("SELECT COUNT(*) as c FROM knowledge WHERE severity = 'archived'").get() as any;
|
|
141
|
+
expect(archived.c).toBe(0);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("respects --spec filter", () => {
|
|
145
|
+
createSpec("spec-a", "implementing");
|
|
146
|
+
createSpec("spec-b", "implementing");
|
|
147
|
+
addKnowledgeEntry("spec-a", "Implemented user authentication with JWT tokens for the system", "discovery", "info");
|
|
148
|
+
addKnowledgeEntry("spec-a", "Implemented user authentication with JWT tokens for the system flow", "discovery", "info");
|
|
149
|
+
addKnowledgeEntry("spec-b", "Implemented user authentication with JWT tokens for the login", "discovery", "info");
|
|
150
|
+
addKnowledgeEntry("spec-b", "Implemented user authentication with JWT tokens for the login flow", "discovery", "info");
|
|
151
|
+
|
|
152
|
+
compactKnowledge({ specId: "spec-a", json: true });
|
|
153
|
+
|
|
154
|
+
const db = getDb();
|
|
155
|
+
const archivedA = db.query("SELECT COUNT(*) as c FROM knowledge WHERE severity = 'archived' AND spec_id = 'spec-a'").get() as any;
|
|
156
|
+
const archivedB = db.query("SELECT COUNT(*) as c FROM knowledge WHERE severity = 'archived' AND spec_id = 'spec-b'").get() as any;
|
|
157
|
+
expect(archivedA.c).toBe(1);
|
|
158
|
+
expect(archivedB.c).toBe(0);
|
|
159
|
+
});
|
|
160
|
+
});
|