@cleocode/core 2026.4.39 → 2026.4.41
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/dist/index.js +54 -47
- package/dist/index.js.map +2 -2
- package/dist/init.d.ts +15 -0
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +73 -51
- package/dist/init.js.map +1 -1
- package/dist/internal.d.ts +1 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +1 -0
- package/dist/internal.js.map +1 -1
- package/dist/memory/brain-lifecycle.js +11 -0
- package/dist/memory/brain-lifecycle.js.map +1 -1
- package/dist/memory/temporal-supersession.js +0 -3
- package/dist/memory/temporal-supersession.js.map +1 -1
- package/dist/tasks/complete.js +20 -0
- package/dist/tasks/complete.js.map +1 -1
- package/dist/upgrade.d.ts.map +1 -1
- package/dist/upgrade.js +224 -1
- package/dist/upgrade.js.map +1 -1
- package/package.json +8 -8
- package/src/init.ts +90 -59
- package/src/internal.ts +7 -0
- package/src/memory/__tests__/{graph-memory-bridge.test.ts → graph-memory-bridge-integration.test.ts} +20 -1
- package/src/memory/__tests__/observer-reflector.test.ts +10 -5
- package/src/memory/__tests__/sleep-consolidation.test.ts +5 -1
- package/src/upgrade.ts +223 -1
package/src/init.ts
CHANGED
|
@@ -832,65 +832,7 @@ export async function initProject(opts: InitOptions = {}): Promise<InitResult> {
|
|
|
832
832
|
// This gives the CANT bridge a working team topology on first `cleoos` run.
|
|
833
833
|
// Only deploys if .cleo/cant/ does not already contain .cant files (idempotent).
|
|
834
834
|
try {
|
|
835
|
-
|
|
836
|
-
const cantAgentsDir = join(cantDir, 'agents');
|
|
837
|
-
const hasCantFiles =
|
|
838
|
-
existsSync(cantDir) &&
|
|
839
|
-
readdirSync(cantDir, { recursive: true }).some(
|
|
840
|
-
(f) => typeof f === 'string' && f.endsWith('.cant'),
|
|
841
|
-
);
|
|
842
|
-
|
|
843
|
-
if (!hasCantFiles) {
|
|
844
|
-
// Resolve the starter-bundle from @cleocode/cleo-os package
|
|
845
|
-
let starterBundleSrc: string | null = null;
|
|
846
|
-
try {
|
|
847
|
-
const { createRequire } = await import('node:module');
|
|
848
|
-
const req = createRequire(import.meta.url);
|
|
849
|
-
const cleoOsPkgMain = req.resolve('@cleocode/cleo-os/package.json');
|
|
850
|
-
const cleoOsPkgRoot = dirname(cleoOsPkgMain);
|
|
851
|
-
const candidate = join(cleoOsPkgRoot, 'starter-bundle');
|
|
852
|
-
if (existsSync(candidate)) {
|
|
853
|
-
starterBundleSrc = candidate;
|
|
854
|
-
}
|
|
855
|
-
} catch {
|
|
856
|
-
// Not resolvable via require.resolve — try workspace fallbacks
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
if (!starterBundleSrc) {
|
|
860
|
-
const packageRoot = getPackageRoot();
|
|
861
|
-
const fallbacks = [
|
|
862
|
-
join(packageRoot, '..', 'cleo-os', 'starter-bundle'),
|
|
863
|
-
join(packageRoot, '..', '..', 'packages', 'cleo-os', 'starter-bundle'),
|
|
864
|
-
];
|
|
865
|
-
starterBundleSrc = fallbacks.find((p) => existsSync(p)) ?? null;
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
if (starterBundleSrc) {
|
|
869
|
-
await mkdir(cantDir, { recursive: true });
|
|
870
|
-
await mkdir(cantAgentsDir, { recursive: true });
|
|
871
|
-
|
|
872
|
-
// Copy team.cant
|
|
873
|
-
const teamSrc = join(starterBundleSrc, 'team.cant');
|
|
874
|
-
const teamDst = join(cantDir, 'team.cant');
|
|
875
|
-
if (existsSync(teamSrc) && !existsSync(teamDst)) {
|
|
876
|
-
await copyFile(teamSrc, teamDst);
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
// Copy agent .cant files
|
|
880
|
-
const agentsSrc = join(starterBundleSrc, 'agents');
|
|
881
|
-
if (existsSync(agentsSrc)) {
|
|
882
|
-
const agentFiles = readdirSync(agentsSrc).filter((f) => f.endsWith('.cant'));
|
|
883
|
-
for (const agentFile of agentFiles) {
|
|
884
|
-
const dst = join(cantAgentsDir, agentFile);
|
|
885
|
-
if (!existsSync(dst)) {
|
|
886
|
-
await copyFile(join(agentsSrc, agentFile), dst);
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
created.push('starter-bundle: team + agent .cant files deployed to .cleo/cant/');
|
|
892
|
-
}
|
|
893
|
-
}
|
|
835
|
+
await deployStarterBundle(cleoDir, created, warnings);
|
|
894
836
|
} catch (err) {
|
|
895
837
|
warnings.push(`Starter bundle deploy: ${err instanceof Error ? err.message : String(err)}`);
|
|
896
838
|
}
|
|
@@ -1068,3 +1010,92 @@ export async function getVersion(projectRoot?: string): Promise<{ version: strin
|
|
|
1068
1010
|
|
|
1069
1011
|
return { version: '0.0.0' };
|
|
1070
1012
|
}
|
|
1013
|
+
|
|
1014
|
+
// ---------------------------------------------------------------------------
|
|
1015
|
+
// Starter bundle deployment (T441) — shared between init and upgrade
|
|
1016
|
+
// ---------------------------------------------------------------------------
|
|
1017
|
+
|
|
1018
|
+
/**
|
|
1019
|
+
* Deploy the starter CANT bundle (team + agents) to a project's `.cleo/cant/`.
|
|
1020
|
+
*
|
|
1021
|
+
* Idempotent: skips deployment if `.cleo/cant/` already contains `.cant` files.
|
|
1022
|
+
* Does not overwrite existing files. Resolves the starter bundle from
|
|
1023
|
+
* `@cleocode/cleo-os/starter-bundle` or workspace fallback paths.
|
|
1024
|
+
*
|
|
1025
|
+
* Called by both `initProject()` and `runUpgrade()` to ensure every project
|
|
1026
|
+
* gets a working team topology for the CANT bridge.
|
|
1027
|
+
*
|
|
1028
|
+
* @param cleoDir - Absolute path to the project's `.cleo/` directory.
|
|
1029
|
+
* @param created - Array to push created-file descriptions into.
|
|
1030
|
+
* @param warnings - Array to push warning messages into.
|
|
1031
|
+
*/
|
|
1032
|
+
export async function deployStarterBundle(
|
|
1033
|
+
cleoDir: string,
|
|
1034
|
+
created: string[],
|
|
1035
|
+
warnings: string[],
|
|
1036
|
+
): Promise<void> {
|
|
1037
|
+
const cantDir = join(cleoDir, 'cant');
|
|
1038
|
+
const cantAgentsDir = join(cantDir, 'agents');
|
|
1039
|
+
const hasCantFiles =
|
|
1040
|
+
existsSync(cantDir) &&
|
|
1041
|
+
readdirSync(cantDir, { recursive: true }).some(
|
|
1042
|
+
(f) => typeof f === 'string' && f.endsWith('.cant'),
|
|
1043
|
+
);
|
|
1044
|
+
|
|
1045
|
+
if (hasCantFiles) return; // Already deployed — idempotent
|
|
1046
|
+
|
|
1047
|
+
// Resolve the starter-bundle from @cleocode/cleo-os package
|
|
1048
|
+
let starterBundleSrc: string | null = null;
|
|
1049
|
+
try {
|
|
1050
|
+
const { createRequire } = await import('node:module');
|
|
1051
|
+
const req = createRequire(import.meta.url);
|
|
1052
|
+
const cleoOsPkgMain = req.resolve('@cleocode/cleo-os/package.json');
|
|
1053
|
+
const cleoOsPkgRoot = dirname(cleoOsPkgMain);
|
|
1054
|
+
const candidate = join(cleoOsPkgRoot, 'starter-bundle');
|
|
1055
|
+
if (existsSync(candidate)) {
|
|
1056
|
+
starterBundleSrc = candidate;
|
|
1057
|
+
}
|
|
1058
|
+
} catch {
|
|
1059
|
+
// Not resolvable via require.resolve — try workspace fallbacks
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
if (!starterBundleSrc) {
|
|
1063
|
+
const packageRoot = getPackageRoot();
|
|
1064
|
+
const fallbacks = [
|
|
1065
|
+
join(packageRoot, '..', 'cleo-os', 'starter-bundle'),
|
|
1066
|
+
join(packageRoot, '..', '..', 'packages', 'cleo-os', 'starter-bundle'),
|
|
1067
|
+
];
|
|
1068
|
+
starterBundleSrc = fallbacks.find((p) => existsSync(p)) ?? null;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
if (!starterBundleSrc) {
|
|
1072
|
+
warnings.push(
|
|
1073
|
+
'Starter bundle not found — .cleo/cant/ will remain empty. Run cleo init in a project with @cleocode/cleo-os installed.',
|
|
1074
|
+
);
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
await mkdir(cantDir, { recursive: true });
|
|
1079
|
+
await mkdir(cantAgentsDir, { recursive: true });
|
|
1080
|
+
|
|
1081
|
+
// Copy team.cant
|
|
1082
|
+
const teamSrc = join(starterBundleSrc, 'team.cant');
|
|
1083
|
+
const teamDst = join(cantDir, 'team.cant');
|
|
1084
|
+
if (existsSync(teamSrc) && !existsSync(teamDst)) {
|
|
1085
|
+
await copyFile(teamSrc, teamDst);
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
// Copy agent .cant files
|
|
1089
|
+
const agentsSrc = join(starterBundleSrc, 'agents');
|
|
1090
|
+
if (existsSync(agentsSrc)) {
|
|
1091
|
+
const agentFiles = readdirSync(agentsSrc).filter((f) => f.endsWith('.cant'));
|
|
1092
|
+
for (const agentFile of agentFiles) {
|
|
1093
|
+
const dst = join(cantAgentsDir, agentFile);
|
|
1094
|
+
if (!existsSync(dst)) {
|
|
1095
|
+
await copyFile(join(agentsSrc, agentFile), dst);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
created.push('starter-bundle: team + agent .cant files deployed to .cleo/cant/');
|
|
1101
|
+
}
|
package/src/internal.ts
CHANGED
|
@@ -897,6 +897,13 @@ export type {
|
|
|
897
897
|
BrainSearchHit,
|
|
898
898
|
BrainTimelineNeighborRow,
|
|
899
899
|
} from './memory/brain-row-types.js';
|
|
900
|
+
export {
|
|
901
|
+
autoLinkMemories,
|
|
902
|
+
linkMemoryToCode,
|
|
903
|
+
listCodeLinks,
|
|
904
|
+
queryCodeForMemory,
|
|
905
|
+
queryMemoriesForCode,
|
|
906
|
+
} from './memory/graph-memory-bridge.js';
|
|
900
907
|
// Memory — LLM extraction gate (additional)
|
|
901
908
|
export type {
|
|
902
909
|
ExtractedMemory,
|
package/src/memory/__tests__/{graph-memory-bridge.test.ts → graph-memory-bridge-integration.test.ts}
RENAMED
|
@@ -12,7 +12,22 @@
|
|
|
12
12
|
import { mkdir, mkdtemp, rm } from 'node:fs/promises';
|
|
13
13
|
import { tmpdir } from 'node:os';
|
|
14
14
|
import { join } from 'node:path';
|
|
15
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
15
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
16
|
+
|
|
17
|
+
// Force-pass-through the real modules so that any leaked mocks from other
|
|
18
|
+
// test files in the same vitest shard cannot pollute this integration test.
|
|
19
|
+
// vitest resolves mocks at file-load time; vi.unmock is not sufficient when
|
|
20
|
+
// another file's vi.mock('../../paths.js') already poisoned the module registry.
|
|
21
|
+
vi.mock('../../paths.js', async () => await vi.importActual('../../paths.js'));
|
|
22
|
+
vi.mock(
|
|
23
|
+
'../../store/brain-sqlite.js',
|
|
24
|
+
async () => await vi.importActual('../../store/brain-sqlite.js'),
|
|
25
|
+
);
|
|
26
|
+
vi.mock(
|
|
27
|
+
'../../store/nexus-sqlite.js',
|
|
28
|
+
async () => await vi.importActual('../../store/nexus-sqlite.js'),
|
|
29
|
+
);
|
|
30
|
+
vi.mock('../../config.js', async () => await vi.importActual('../../config.js'));
|
|
16
31
|
|
|
17
32
|
let tempDir: string;
|
|
18
33
|
|
|
@@ -21,6 +36,9 @@ describe('graph-memory-bridge', () => {
|
|
|
21
36
|
tempDir = await mkdtemp(join(tmpdir(), 'cleo-gmb-'));
|
|
22
37
|
await mkdir(join(tempDir, '.cleo'), { recursive: true });
|
|
23
38
|
process.env['CLEO_DIR'] = join(tempDir, '.cleo');
|
|
39
|
+
// nexus.db is global (ADR-036) — point CLEO_HOME to temp dir so
|
|
40
|
+
// getNexusDb() creates it here instead of ~/.cleo/ on CI.
|
|
41
|
+
process.env['CLEO_HOME'] = join(tempDir, '.cleo');
|
|
24
42
|
});
|
|
25
43
|
|
|
26
44
|
afterEach(async () => {
|
|
@@ -29,6 +47,7 @@ describe('graph-memory-bridge', () => {
|
|
|
29
47
|
closeBrainDb();
|
|
30
48
|
resetNexusDbState();
|
|
31
49
|
delete process.env['CLEO_DIR'];
|
|
50
|
+
delete process.env['CLEO_HOME'];
|
|
32
51
|
await rm(tempDir, { recursive: true, force: true });
|
|
33
52
|
});
|
|
34
53
|
|
|
@@ -26,12 +26,14 @@ const {
|
|
|
26
26
|
mockStoreLearning,
|
|
27
27
|
mockStorePattern,
|
|
28
28
|
mockLoadConfig,
|
|
29
|
+
mockResolveKey,
|
|
29
30
|
} = vi.hoisted(() => ({
|
|
30
31
|
mockGetBrainDb: vi.fn().mockResolvedValue({}),
|
|
31
32
|
mockGetBrainNativeDb: vi.fn(),
|
|
32
33
|
mockStoreLearning: vi.fn().mockResolvedValue({ id: 'L-test-001' }),
|
|
33
34
|
mockStorePattern: vi.fn().mockResolvedValue({ id: 'P-test-001' }),
|
|
34
35
|
mockLoadConfig: vi.fn(),
|
|
36
|
+
mockResolveKey: vi.fn().mockReturnValue(null),
|
|
35
37
|
}));
|
|
36
38
|
|
|
37
39
|
vi.mock('../../store/brain-sqlite.js', () => ({
|
|
@@ -56,6 +58,13 @@ vi.mock('../../config.js', () => ({
|
|
|
56
58
|
loadConfig: mockLoadConfig,
|
|
57
59
|
}));
|
|
58
60
|
|
|
61
|
+
// Mock the key resolver so tests don't depend on filesystem state
|
|
62
|
+
// (~/.claude/.credentials.json, ~/.local/share/cleo/anthropic-key).
|
|
63
|
+
vi.mock('../anthropic-key-resolver.js', () => ({
|
|
64
|
+
resolveAnthropicApiKey: (...args: unknown[]) => mockResolveKey(...args),
|
|
65
|
+
clearAnthropicKeyCache: vi.fn(),
|
|
66
|
+
}));
|
|
67
|
+
|
|
59
68
|
// ============================================================================
|
|
60
69
|
// Import module under test (after all mocks)
|
|
61
70
|
// ============================================================================
|
|
@@ -69,11 +78,7 @@ import { runObserver, runReflector } from '../observer-reflector.js';
|
|
|
69
78
|
const FAKE_API_KEY = 'sk-ant-test-key';
|
|
70
79
|
|
|
71
80
|
function setApiKey(key: string | undefined): void {
|
|
72
|
-
|
|
73
|
-
delete process.env['ANTHROPIC_API_KEY'];
|
|
74
|
-
} else {
|
|
75
|
-
process.env['ANTHROPIC_API_KEY'] = key;
|
|
76
|
-
}
|
|
81
|
+
mockResolveKey.mockReturnValue(key ?? null);
|
|
77
82
|
}
|
|
78
83
|
|
|
79
84
|
type RawObs = {
|
|
@@ -171,7 +171,11 @@ function buildMockNativeDb(options: {
|
|
|
171
171
|
return { run: mockRun, all: mockAll, get: vi.fn().mockReturnValue({ cnt: 0 }) };
|
|
172
172
|
});
|
|
173
173
|
|
|
174
|
-
const stmtMock = {
|
|
174
|
+
const stmtMock = {
|
|
175
|
+
run: mockRun,
|
|
176
|
+
all: vi.fn().mockReturnValue([]),
|
|
177
|
+
get: vi.fn().mockReturnValue({ cnt: 0 }),
|
|
178
|
+
};
|
|
175
179
|
return { prepare, _stmtMock: stmtMock };
|
|
176
180
|
}
|
|
177
181
|
|
package/src/upgrade.ts
CHANGED
|
@@ -855,6 +855,21 @@ export async function runUpgrade(
|
|
|
855
855
|
/* best-effort — signaldock.db will be created on first agent operation */
|
|
856
856
|
}
|
|
857
857
|
|
|
858
|
+
// Initialize conduit.db for project-tier agent messaging (T310)
|
|
859
|
+
try {
|
|
860
|
+
const { ensureConduitDb } = await import('./store/conduit-sqlite.js');
|
|
861
|
+
const cdResult = ensureConduitDb(projectRootForMaint);
|
|
862
|
+
if (cdResult.action === 'created') {
|
|
863
|
+
actions.push({
|
|
864
|
+
action: 'ensure_conduit_db',
|
|
865
|
+
status: 'applied',
|
|
866
|
+
details: 'conduit.db created with full schema',
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
} catch {
|
|
870
|
+
/* best-effort — conduit.db will be created on first agent operation */
|
|
871
|
+
}
|
|
872
|
+
|
|
858
873
|
// Regenerate memory-bridge.md
|
|
859
874
|
try {
|
|
860
875
|
const { writeMemoryBridge } = await import('./memory/memory-bridge.js');
|
|
@@ -900,7 +915,30 @@ export async function runUpgrade(
|
|
|
900
915
|
/* best-effort */
|
|
901
916
|
}
|
|
902
917
|
|
|
903
|
-
// (
|
|
918
|
+
// Deploy starter CANT bundle (team + agents) to .cleo/cant/ if missing.
|
|
919
|
+
// Ensures existing projects get agent definitions on upgrade, not just init. (T555)
|
|
920
|
+
try {
|
|
921
|
+
const { deployStarterBundle } = await import('./init.js');
|
|
922
|
+
const cantCreated: string[] = [];
|
|
923
|
+
const cantWarnings: string[] = [];
|
|
924
|
+
await deployStarterBundle(cleoDir, cantCreated, cantWarnings);
|
|
925
|
+
if (cantCreated.length > 0) {
|
|
926
|
+
actions.push({
|
|
927
|
+
action: 'cant_starter_bundle',
|
|
928
|
+
status: 'applied',
|
|
929
|
+
details: cantCreated.join(', '),
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
for (const w of cantWarnings) {
|
|
933
|
+
actions.push({
|
|
934
|
+
action: 'cant_starter_bundle',
|
|
935
|
+
status: 'skipped',
|
|
936
|
+
details: w,
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
} catch {
|
|
940
|
+
/* best-effort */
|
|
941
|
+
}
|
|
904
942
|
|
|
905
943
|
// Install core skills
|
|
906
944
|
try {
|
|
@@ -992,6 +1030,51 @@ export async function runUpgrade(
|
|
|
992
1030
|
} catch {
|
|
993
1031
|
/* best-effort */
|
|
994
1032
|
}
|
|
1033
|
+
|
|
1034
|
+
// Adapter discovery, activation, and install (T5240)
|
|
1035
|
+
// Ensures Claude Code settings.json hooks and other adapter configs stay current.
|
|
1036
|
+
try {
|
|
1037
|
+
const { AdapterManager } = await import('./adapters/index.js');
|
|
1038
|
+
const mgr = AdapterManager.getInstance(projectRootForMaint);
|
|
1039
|
+
const manifests = mgr.discover();
|
|
1040
|
+
if (manifests.length > 0) {
|
|
1041
|
+
const detected = mgr.detectActive();
|
|
1042
|
+
for (const adapterId of detected) {
|
|
1043
|
+
try {
|
|
1044
|
+
const adapter = await mgr.activate(adapterId);
|
|
1045
|
+
const installResult = await adapter.install.install({
|
|
1046
|
+
projectDir: projectRootForMaint,
|
|
1047
|
+
});
|
|
1048
|
+
if (installResult.success) {
|
|
1049
|
+
actions.push({
|
|
1050
|
+
action: 'adapter_install',
|
|
1051
|
+
status: 'applied',
|
|
1052
|
+
details: `Adapter ${adapterId}: installed/updated`,
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
} catch {
|
|
1056
|
+
/* best-effort — adapter may not support install */
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
} catch {
|
|
1061
|
+
/* best-effort — adapters are optional */
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
// Ensure the global CleoOS Hub exists (idempotent)
|
|
1065
|
+
try {
|
|
1066
|
+
const { ensureCleoOsHub } = await import('./scaffold.js');
|
|
1067
|
+
const hubResult = await ensureCleoOsHub();
|
|
1068
|
+
if (hubResult.action === 'created') {
|
|
1069
|
+
actions.push({
|
|
1070
|
+
action: 'cleoos_hub',
|
|
1071
|
+
status: 'applied',
|
|
1072
|
+
details: hubResult.details ?? 'CleoOS hub scaffolded',
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
} catch {
|
|
1076
|
+
/* best-effort */
|
|
1077
|
+
}
|
|
995
1078
|
} else {
|
|
996
1079
|
// Dry-run reporting for new steps
|
|
997
1080
|
const { existsSync: fsExistsSync } = await import('node:fs');
|
|
@@ -1268,6 +1351,66 @@ export async function diagnoseUpgrade(options: { cwd?: string } = {}): Promise<D
|
|
|
1268
1351
|
fix: 'Run: cleo upgrade',
|
|
1269
1352
|
});
|
|
1270
1353
|
}
|
|
1354
|
+
|
|
1355
|
+
// T528/T531/T549 column validation — ensure brain schema expansions applied
|
|
1356
|
+
const brainColumnChecks: Array<{ table: string; columns: string[]; task: string }> = [
|
|
1357
|
+
{
|
|
1358
|
+
table: 'brain_page_nodes',
|
|
1359
|
+
columns: ['quality_score', 'content_hash', 'last_activity_at', 'updated_at'],
|
|
1360
|
+
task: 'T528',
|
|
1361
|
+
},
|
|
1362
|
+
{
|
|
1363
|
+
table: 'brain_decisions',
|
|
1364
|
+
columns: [
|
|
1365
|
+
'quality_score',
|
|
1366
|
+
'memory_tier',
|
|
1367
|
+
'memory_type',
|
|
1368
|
+
'verified',
|
|
1369
|
+
'valid_at',
|
|
1370
|
+
'invalid_at',
|
|
1371
|
+
'source_confidence',
|
|
1372
|
+
'citation_count',
|
|
1373
|
+
],
|
|
1374
|
+
task: 'T531/T549',
|
|
1375
|
+
},
|
|
1376
|
+
{
|
|
1377
|
+
table: 'brain_observations',
|
|
1378
|
+
columns: ['quality_score', 'memory_tier', 'memory_type', 'verified'],
|
|
1379
|
+
task: 'T531/T549',
|
|
1380
|
+
},
|
|
1381
|
+
];
|
|
1382
|
+
|
|
1383
|
+
const allBrainColsMissing: string[] = [];
|
|
1384
|
+
for (const { table, columns, task } of brainColumnChecks) {
|
|
1385
|
+
const hasTable = nativeDb
|
|
1386
|
+
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?")
|
|
1387
|
+
.get(table) as { name?: string } | undefined;
|
|
1388
|
+
if (!hasTable?.name) continue;
|
|
1389
|
+
|
|
1390
|
+
const cols = nativeDb.prepare(`PRAGMA table_info(${table})`).all() as Array<{
|
|
1391
|
+
name: string;
|
|
1392
|
+
}>;
|
|
1393
|
+
const colNames = new Set(cols.map((c: { name: string }) => c.name));
|
|
1394
|
+
const missing = columns.filter((c) => !colNames.has(c));
|
|
1395
|
+
if (missing.length > 0) {
|
|
1396
|
+
allBrainColsMissing.push(`${table}: ${missing.join(', ')} (${task})`);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
if (allBrainColsMissing.length > 0) {
|
|
1401
|
+
findings.push({
|
|
1402
|
+
check: 'brain.db.schema_columns',
|
|
1403
|
+
status: 'error',
|
|
1404
|
+
details: `Missing brain schema columns: ${allBrainColsMissing.join('; ')}`,
|
|
1405
|
+
fix: 'Run: cleo upgrade (will add missing columns automatically)',
|
|
1406
|
+
});
|
|
1407
|
+
} else {
|
|
1408
|
+
findings.push({
|
|
1409
|
+
check: 'brain.db.schema_columns',
|
|
1410
|
+
status: 'ok',
|
|
1411
|
+
details: 'All T528/T531/T549 brain schema columns present',
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1271
1414
|
} else {
|
|
1272
1415
|
findings.push({
|
|
1273
1416
|
check: 'brain.db.connection',
|
|
@@ -1287,6 +1430,85 @@ export async function diagnoseUpgrade(options: { cwd?: string } = {}): Promise<D
|
|
|
1287
1430
|
check: 'brain.db',
|
|
1288
1431
|
status: 'warning',
|
|
1289
1432
|
details: 'brain.db not found (will be created on first use)',
|
|
1433
|
+
fix: 'Run: cleo upgrade',
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
// ── signaldock.db validation ──
|
|
1438
|
+
const signaldockDbPath = join(cleoDir, 'signaldock.db');
|
|
1439
|
+
if (existsSync(signaldockDbPath)) {
|
|
1440
|
+
findings.push({
|
|
1441
|
+
check: 'signaldock.db',
|
|
1442
|
+
status: 'ok',
|
|
1443
|
+
details: 'signaldock.db exists',
|
|
1444
|
+
});
|
|
1445
|
+
} else {
|
|
1446
|
+
findings.push({
|
|
1447
|
+
check: 'signaldock.db',
|
|
1448
|
+
status: 'warning',
|
|
1449
|
+
details: 'signaldock.db not found',
|
|
1450
|
+
fix: 'Run: cleo upgrade',
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
// ── conduit.db validation ──
|
|
1455
|
+
const conduitDbPath = join(cleoDir, 'conduit.db');
|
|
1456
|
+
if (existsSync(conduitDbPath)) {
|
|
1457
|
+
findings.push({
|
|
1458
|
+
check: 'conduit.db',
|
|
1459
|
+
status: 'ok',
|
|
1460
|
+
details: 'conduit.db exists',
|
|
1461
|
+
});
|
|
1462
|
+
} else {
|
|
1463
|
+
findings.push({
|
|
1464
|
+
check: 'conduit.db',
|
|
1465
|
+
status: 'warning',
|
|
1466
|
+
details: 'conduit.db not found',
|
|
1467
|
+
fix: 'Run: cleo upgrade',
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
// ── memory-bridge.md validation ──
|
|
1472
|
+
const memoryBridgePath = join(cleoDir, 'memory-bridge.md');
|
|
1473
|
+
if (existsSync(memoryBridgePath)) {
|
|
1474
|
+
try {
|
|
1475
|
+
const content = readFileSync(memoryBridgePath, 'utf-8');
|
|
1476
|
+
const hasAutoGenMarker = content.includes('Auto-generated');
|
|
1477
|
+
const hasGarbage = content.includes('undefined') && content.length < 100;
|
|
1478
|
+
if (hasGarbage) {
|
|
1479
|
+
findings.push({
|
|
1480
|
+
check: 'memory-bridge.md',
|
|
1481
|
+
status: 'error',
|
|
1482
|
+
details: 'memory-bridge.md contains garbage content',
|
|
1483
|
+
fix: 'Run: cleo upgrade (will regenerate)',
|
|
1484
|
+
});
|
|
1485
|
+
} else if (hasAutoGenMarker) {
|
|
1486
|
+
findings.push({
|
|
1487
|
+
check: 'memory-bridge.md',
|
|
1488
|
+
status: 'ok',
|
|
1489
|
+
details: 'memory-bridge.md exists and has valid content',
|
|
1490
|
+
});
|
|
1491
|
+
} else {
|
|
1492
|
+
findings.push({
|
|
1493
|
+
check: 'memory-bridge.md',
|
|
1494
|
+
status: 'warning',
|
|
1495
|
+
details: 'memory-bridge.md exists but may be stale (no auto-generated marker)',
|
|
1496
|
+
fix: 'Run: cleo upgrade (will regenerate)',
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1499
|
+
} catch {
|
|
1500
|
+
findings.push({
|
|
1501
|
+
check: 'memory-bridge.md',
|
|
1502
|
+
status: 'warning',
|
|
1503
|
+
details: 'memory-bridge.md exists but could not be read',
|
|
1504
|
+
});
|
|
1505
|
+
}
|
|
1506
|
+
} else {
|
|
1507
|
+
findings.push({
|
|
1508
|
+
check: 'memory-bridge.md',
|
|
1509
|
+
status: 'warning',
|
|
1510
|
+
details: 'memory-bridge.md not found',
|
|
1511
|
+
fix: 'Run: cleo upgrade (will regenerate)',
|
|
1290
1512
|
});
|
|
1291
1513
|
}
|
|
1292
1514
|
|