@cleocode/core 2026.4.51 → 2026.4.53
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 +193 -112
- package/dist/index.js.map +3 -3
- package/dist/init.d.ts.map +1 -1
- package/dist/internal.d.ts +2 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +370 -125
- package/dist/internal.js.map +4 -4
- package/dist/memory/brain-export.d.ts +70 -0
- package/dist/memory/brain-export.d.ts.map +1 -0
- package/dist/memory/brain-retrieval.d.ts.map +1 -1
- package/dist/scaffold.d.ts +30 -0
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/store/brain-schema.d.ts +16 -0
- package/dist/store/brain-schema.d.ts.map +1 -1
- package/dist/system/health.d.ts.map +1 -1
- package/dist/upgrade.d.ts.map +1 -1
- package/dist/validation/verification.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/init.ts +15 -18
- package/src/internal.ts +7 -0
- package/src/memory/brain-embedding.ts +1 -1
- package/src/memory/brain-export.ts +286 -0
- package/src/memory/brain-retrieval.ts +80 -14
- package/src/memory/brain-similarity.ts +1 -1
- package/src/memory/claude-mem-migration.ts +1 -1
- package/src/scaffold.ts +152 -0
- package/src/sessions/briefing.ts +1 -1
- package/src/skills/dispatch.ts +1 -1
- package/src/skills/injection/subagent.ts +1 -1
- package/src/skills/orchestrator/spawn.ts +1 -1
- package/src/store/brain-schema.ts +4 -0
- package/src/store/json.ts +2 -2
- package/src/system/archive-analytics.ts +1 -1
- package/src/system/health.ts +2 -0
- package/src/tasks/task-ops.ts +2 -2
- package/src/upgrade.ts +17 -0
- package/src/validation/verification.ts +2 -6
|
@@ -259,9 +259,18 @@ export async function searchBrainCompact(
|
|
|
259
259
|
const returnedIds = results.map((r) => r.id);
|
|
260
260
|
setImmediate(() => {
|
|
261
261
|
incrementCitationCounts(projectRoot, returnedIds).catch(() => {});
|
|
262
|
-
|
|
263
|
-
() => {
|
|
264
|
-
|
|
262
|
+
getCurrentSessionId(projectRoot)
|
|
263
|
+
.then((sessionId) => {
|
|
264
|
+
return logRetrieval(
|
|
265
|
+
projectRoot,
|
|
266
|
+
query,
|
|
267
|
+
returnedIds,
|
|
268
|
+
'find-rrf',
|
|
269
|
+
results.length * 50,
|
|
270
|
+
sessionId,
|
|
271
|
+
);
|
|
272
|
+
})
|
|
273
|
+
.catch(() => {});
|
|
265
274
|
});
|
|
266
275
|
}
|
|
267
276
|
|
|
@@ -345,7 +354,18 @@ export async function searchBrainCompact(
|
|
|
345
354
|
const returnedIds = results.map((r) => r.id);
|
|
346
355
|
setImmediate(() => {
|
|
347
356
|
incrementCitationCounts(projectRoot, returnedIds).catch(() => {});
|
|
348
|
-
|
|
357
|
+
getCurrentSessionId(projectRoot)
|
|
358
|
+
.then((sessionId) => {
|
|
359
|
+
return logRetrieval(
|
|
360
|
+
projectRoot,
|
|
361
|
+
query,
|
|
362
|
+
returnedIds,
|
|
363
|
+
'find',
|
|
364
|
+
results.length * 50,
|
|
365
|
+
sessionId,
|
|
366
|
+
);
|
|
367
|
+
})
|
|
368
|
+
.catch(() => {});
|
|
349
369
|
});
|
|
350
370
|
}
|
|
351
371
|
|
|
@@ -604,13 +624,18 @@ export async function fetchBrainEntries(
|
|
|
604
624
|
const fetchedIds = results.map((r) => r.id);
|
|
605
625
|
setImmediate(() => {
|
|
606
626
|
incrementCitationCounts(projectRoot, fetchedIds).catch(() => {});
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
627
|
+
getCurrentSessionId(projectRoot)
|
|
628
|
+
.then((sessionId) => {
|
|
629
|
+
return logRetrieval(
|
|
630
|
+
projectRoot,
|
|
631
|
+
fetchedIds.join(','),
|
|
632
|
+
fetchedIds,
|
|
633
|
+
'fetch',
|
|
634
|
+
results.length * 500,
|
|
635
|
+
sessionId,
|
|
636
|
+
);
|
|
637
|
+
})
|
|
638
|
+
.catch(() => {});
|
|
614
639
|
});
|
|
615
640
|
}
|
|
616
641
|
|
|
@@ -1364,6 +1389,31 @@ export async function retrieveWithBudget(
|
|
|
1364
1389
|
};
|
|
1365
1390
|
}
|
|
1366
1391
|
|
|
1392
|
+
// ============================================================================
|
|
1393
|
+
// Session ID Retrieval (for logRetrieval)
|
|
1394
|
+
// ============================================================================
|
|
1395
|
+
|
|
1396
|
+
/**
|
|
1397
|
+
* Get the current session ID from the session manager.
|
|
1398
|
+
*
|
|
1399
|
+
* This is a best-effort operation — if no session is active or session
|
|
1400
|
+
* manager is unavailable, returns null. Used by logRetrieval to group
|
|
1401
|
+
* retrievals by session for STDP analysis.
|
|
1402
|
+
*
|
|
1403
|
+
* @param projectRoot - Project root directory
|
|
1404
|
+
* @returns Current session ID or null if unavailable
|
|
1405
|
+
*/
|
|
1406
|
+
async function getCurrentSessionId(projectRoot: string): Promise<string | undefined> {
|
|
1407
|
+
try {
|
|
1408
|
+
const { sessionStatus } = await import('../sessions/index.js');
|
|
1409
|
+
const session = await sessionStatus(projectRoot);
|
|
1410
|
+
return session?.id;
|
|
1411
|
+
} catch {
|
|
1412
|
+
// Session manager unavailable or other error — log retrievals without session
|
|
1413
|
+
return undefined;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1367
1417
|
// ============================================================================
|
|
1368
1418
|
// Citation Count Increment (non-blocking helper)
|
|
1369
1419
|
// ============================================================================
|
|
@@ -1416,6 +1466,13 @@ async function incrementCitationCounts(projectRoot: string, ids: string[]): Prom
|
|
|
1416
1466
|
*
|
|
1417
1467
|
* Creates the table on first use if it doesn't exist (self-healing).
|
|
1418
1468
|
* Best-effort: errors are silently swallowed.
|
|
1469
|
+
*
|
|
1470
|
+
* @param projectRoot - Project root directory
|
|
1471
|
+
* @param query - The search query or fetch IDs
|
|
1472
|
+
* @param entryIds - Array of entry IDs returned in this retrieval
|
|
1473
|
+
* @param source - Retrieval source ('find', 'fetch', 'hybrid', 'timeline', 'budget')
|
|
1474
|
+
* @param tokensUsed - Estimated tokens consumed (optional)
|
|
1475
|
+
* @param sessionId - Session ID for grouping retrievals by session (optional, soft FK to tasks.db)
|
|
1419
1476
|
*/
|
|
1420
1477
|
async function logRetrieval(
|
|
1421
1478
|
projectRoot: string,
|
|
@@ -1423,6 +1480,7 @@ async function logRetrieval(
|
|
|
1423
1480
|
entryIds: string[],
|
|
1424
1481
|
source: string,
|
|
1425
1482
|
tokensUsed?: number,
|
|
1483
|
+
sessionId?: string,
|
|
1426
1484
|
): Promise<void> {
|
|
1427
1485
|
if (entryIds.length === 0) return;
|
|
1428
1486
|
|
|
@@ -1431,7 +1489,7 @@ async function logRetrieval(
|
|
|
1431
1489
|
const nativeDb = getBrainNativeDb();
|
|
1432
1490
|
if (!nativeDb) return;
|
|
1433
1491
|
|
|
1434
|
-
// Self-healing: create table if not exists
|
|
1492
|
+
// Self-healing: create table if not exists (includes session_id column)
|
|
1435
1493
|
const createSql =
|
|
1436
1494
|
'CREATE TABLE IF NOT EXISTS brain_retrieval_log (' +
|
|
1437
1495
|
'id INTEGER PRIMARY KEY AUTOINCREMENT,' +
|
|
@@ -1440,6 +1498,7 @@ async function logRetrieval(
|
|
|
1440
1498
|
'entry_count INTEGER NOT NULL,' +
|
|
1441
1499
|
'source TEXT NOT NULL,' +
|
|
1442
1500
|
'tokens_used INTEGER,' +
|
|
1501
|
+
'session_id TEXT,' +
|
|
1443
1502
|
"created_at TEXT NOT NULL DEFAULT (datetime('now'))" +
|
|
1444
1503
|
')';
|
|
1445
1504
|
try {
|
|
@@ -1451,9 +1510,16 @@ async function logRetrieval(
|
|
|
1451
1510
|
try {
|
|
1452
1511
|
nativeDb
|
|
1453
1512
|
.prepare(
|
|
1454
|
-
'INSERT INTO brain_retrieval_log (query, entry_ids, entry_count, source, tokens_used) VALUES (?, ?, ?, ?, ?)',
|
|
1513
|
+
'INSERT INTO brain_retrieval_log (query, entry_ids, entry_count, source, tokens_used, session_id) VALUES (?, ?, ?, ?, ?, ?)',
|
|
1455
1514
|
)
|
|
1456
|
-
.run(
|
|
1515
|
+
.run(
|
|
1516
|
+
query,
|
|
1517
|
+
entryIds.join(','),
|
|
1518
|
+
entryIds.length,
|
|
1519
|
+
source,
|
|
1520
|
+
tokensUsed ?? null,
|
|
1521
|
+
sessionId ?? null,
|
|
1522
|
+
);
|
|
1457
1523
|
} catch {
|
|
1458
1524
|
/* best-effort */
|
|
1459
1525
|
}
|
|
@@ -61,7 +61,7 @@ export async function searchSimilar(
|
|
|
61
61
|
projectRoot: string,
|
|
62
62
|
limit?: number,
|
|
63
63
|
): Promise<SimilarityResult[]> {
|
|
64
|
-
if (!query
|
|
64
|
+
if (!query?.trim()) return [];
|
|
65
65
|
if (!isEmbeddingAvailable()) return [];
|
|
66
66
|
|
|
67
67
|
const maxResults = limit ?? 10;
|
package/src/scaffold.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
15
15
|
import type { Dirent } from 'node:fs';
|
|
16
16
|
import { existsSync, constants as fsConstants, readFileSync, statSync } from 'node:fs';
|
|
17
17
|
import { access, copyFile, mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
18
|
+
import { createRequire } from 'node:module';
|
|
18
19
|
import { homedir as getHomedir } from 'node:os';
|
|
19
20
|
import { dirname, join, resolve } from 'node:path';
|
|
20
21
|
import { fileURLToPath } from 'node:url';
|
|
@@ -1836,8 +1837,159 @@ export async function ensureCleoOsHub(): Promise<ScaffoldResult> {
|
|
|
1836
1837
|
};
|
|
1837
1838
|
}
|
|
1838
1839
|
|
|
1840
|
+
/**
|
|
1841
|
+
* Resolve the source location of CLEOOS-IDENTITY.md from the cleo-os starter bundle.
|
|
1842
|
+
*
|
|
1843
|
+
* Search order:
|
|
1844
|
+
* 1. Monorepo development: `packages/cleo-os/starter-bundle/CLEOOS-IDENTITY.md`
|
|
1845
|
+
* 2. Installed package: `node_modules/@cleocode/cleo-os/starter-bundle/CLEOOS-IDENTITY.md`
|
|
1846
|
+
*
|
|
1847
|
+
* @returns Absolute path to the source identity file, or null if not found.
|
|
1848
|
+
* @internal Used by ensureGlobalIdentity.
|
|
1849
|
+
*/
|
|
1850
|
+
function resolveIdentitySourcePath(): string | null {
|
|
1851
|
+
// Prefer monorepo source (development)
|
|
1852
|
+
const monorepoPath = join(
|
|
1853
|
+
process.cwd(),
|
|
1854
|
+
'packages',
|
|
1855
|
+
'cleo-os',
|
|
1856
|
+
'starter-bundle',
|
|
1857
|
+
'CLEOOS-IDENTITY.md',
|
|
1858
|
+
);
|
|
1859
|
+
if (existsSync(monorepoPath)) return monorepoPath;
|
|
1860
|
+
|
|
1861
|
+
// Fall back to installed package via require resolution
|
|
1862
|
+
try {
|
|
1863
|
+
const require = createRequire(import.meta.url);
|
|
1864
|
+
const pkgJson = require.resolve('@cleocode/cleo-os/package.json');
|
|
1865
|
+
const pkgDir = pkgJson.replace(/\/package\.json$/, '');
|
|
1866
|
+
const installedPath = join(pkgDir, 'starter-bundle', 'CLEOOS-IDENTITY.md');
|
|
1867
|
+
if (existsSync(installedPath)) return installedPath;
|
|
1868
|
+
} catch {
|
|
1869
|
+
// Not installed — fall through
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
return null;
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
/**
|
|
1876
|
+
* Ensure the Cleo Prime identity file is deployed to the global XDG path.
|
|
1877
|
+
*
|
|
1878
|
+
* SSoT architecture (T631): CLEOOS-IDENTITY.md lives ONCE at the global path
|
|
1879
|
+
* (`~/.local/share/cleo/CLEOOS-IDENTITY.md`). Per-project override is OPTIONAL —
|
|
1880
|
+
* a project may place a customized copy at `.cleo/CLEOOS-IDENTITY.md` and the
|
|
1881
|
+
* loader (`cant-context.ts readIdentityFile`) reads project-first then global.
|
|
1882
|
+
*
|
|
1883
|
+
* Called by both `cleo init` (init.ts deployStarterBundle) and `cleo upgrade`
|
|
1884
|
+
* (upgrade.ts) so existing projects self-heal on upgrade if the file is missing
|
|
1885
|
+
* or out of date.
|
|
1886
|
+
*
|
|
1887
|
+
* Idempotent. Always overwrites if `forceRefresh` is true (used by `upgrade
|
|
1888
|
+
* --refresh-identity`); otherwise only writes when missing.
|
|
1889
|
+
*
|
|
1890
|
+
* @param forceRefresh - Overwrite even if the file exists. Default false.
|
|
1891
|
+
* @returns ScaffoldResult describing what happened.
|
|
1892
|
+
*/
|
|
1893
|
+
export async function ensureGlobalIdentity(forceRefresh = false): Promise<ScaffoldResult> {
|
|
1894
|
+
const sourcePath = resolveIdentitySourcePath();
|
|
1895
|
+
if (!sourcePath) {
|
|
1896
|
+
return {
|
|
1897
|
+
action: 'skipped',
|
|
1898
|
+
path: '',
|
|
1899
|
+
details: 'CLEOOS-IDENTITY.md source not found in monorepo or installed package',
|
|
1900
|
+
};
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
const cleoHome = getCleoHome();
|
|
1904
|
+
const dst = join(cleoHome, 'CLEOOS-IDENTITY.md');
|
|
1905
|
+
|
|
1906
|
+
try {
|
|
1907
|
+
await mkdir(cleoHome, { recursive: true });
|
|
1908
|
+
} catch (err) {
|
|
1909
|
+
return {
|
|
1910
|
+
action: 'skipped',
|
|
1911
|
+
path: dst,
|
|
1912
|
+
details: `Failed to create global cleo home: ${err instanceof Error ? err.message : String(err)}`,
|
|
1913
|
+
};
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
if (existsSync(dst) && !forceRefresh) {
|
|
1917
|
+
return { action: 'skipped', path: dst, details: 'identity already present' };
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
const existedBefore = existsSync(dst);
|
|
1921
|
+
try {
|
|
1922
|
+
const content = readFileSync(sourcePath, 'utf-8');
|
|
1923
|
+
await writeFile(dst, content);
|
|
1924
|
+
return {
|
|
1925
|
+
action: existedBefore ? 'repaired' : 'created',
|
|
1926
|
+
path: dst,
|
|
1927
|
+
details: `from ${sourcePath}`,
|
|
1928
|
+
};
|
|
1929
|
+
} catch (err) {
|
|
1930
|
+
return {
|
|
1931
|
+
action: 'skipped',
|
|
1932
|
+
path: dst,
|
|
1933
|
+
details: `Failed to write identity: ${err instanceof Error ? err.message : String(err)}`,
|
|
1934
|
+
};
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1839
1938
|
// ── Global check* functions (read-only diagnostics) ──────────────────
|
|
1840
1939
|
|
|
1940
|
+
/**
|
|
1941
|
+
* Check that the global CLEOOS-IDENTITY.md file is present and non-empty.
|
|
1942
|
+
* Read-only diagnostic for `cleo doctor`.
|
|
1943
|
+
*
|
|
1944
|
+
* @returns Check result with status, path details, and self-heal command.
|
|
1945
|
+
*
|
|
1946
|
+
* @remarks
|
|
1947
|
+
* Used by `cleo doctor` to verify the orchestrator persona is installed.
|
|
1948
|
+
* Self-heal: `cleo upgrade --refresh-identity` re-deploys from source.
|
|
1949
|
+
*/
|
|
1950
|
+
export function checkGlobalIdentity(): CheckResult {
|
|
1951
|
+
const cleoHome = getCleoHome();
|
|
1952
|
+
const identityPath = join(cleoHome, 'CLEOOS-IDENTITY.md');
|
|
1953
|
+
|
|
1954
|
+
if (!existsSync(identityPath)) {
|
|
1955
|
+
return {
|
|
1956
|
+
id: 'global_identity',
|
|
1957
|
+
category: 'global',
|
|
1958
|
+
status: 'failed',
|
|
1959
|
+
message: 'Global CLEOOS-IDENTITY.md not found — orchestrator persona missing',
|
|
1960
|
+
details: { path: identityPath, exists: false },
|
|
1961
|
+
fix: 'cleo upgrade (auto-deploys identity)',
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
let size = 0;
|
|
1966
|
+
try {
|
|
1967
|
+
size = statSync(identityPath).size;
|
|
1968
|
+
} catch {
|
|
1969
|
+
/* ignore */
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
if (size === 0) {
|
|
1973
|
+
return {
|
|
1974
|
+
id: 'global_identity',
|
|
1975
|
+
category: 'global',
|
|
1976
|
+
status: 'failed',
|
|
1977
|
+
message: 'Global CLEOOS-IDENTITY.md exists but is empty',
|
|
1978
|
+
details: { path: identityPath, exists: true, size: 0 },
|
|
1979
|
+
fix: 'cleo upgrade --refresh-identity',
|
|
1980
|
+
};
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
return {
|
|
1984
|
+
id: 'global_identity',
|
|
1985
|
+
category: 'global',
|
|
1986
|
+
status: 'passed',
|
|
1987
|
+
message: 'Global CLEOOS-IDENTITY.md present',
|
|
1988
|
+
details: { path: identityPath, exists: true, size },
|
|
1989
|
+
fix: '',
|
|
1990
|
+
};
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1841
1993
|
/**
|
|
1842
1994
|
* Check that the global ~/.cleo/ home and its required subdirectories exist.
|
|
1843
1995
|
* Read-only: no side effects.
|
package/src/sessions/briefing.ts
CHANGED
|
@@ -292,7 +292,7 @@ async function computeLastSession(
|
|
|
292
292
|
const allSessions = await accessor.loadSessions();
|
|
293
293
|
|
|
294
294
|
const session = allSessions.find((s) => s.id === sessionId);
|
|
295
|
-
if (!session
|
|
295
|
+
if (!session?.endedAt) return null;
|
|
296
296
|
|
|
297
297
|
// Calculate duration if startedAt is available
|
|
298
298
|
let duration = 0;
|
package/src/skills/dispatch.ts
CHANGED
|
@@ -205,7 +205,7 @@ export async function orchestratorSpawnSkill(
|
|
|
205
205
|
): Promise<string> {
|
|
206
206
|
// Find the skill
|
|
207
207
|
const skill = findSkill(skillName, cwd);
|
|
208
|
-
if (!skill
|
|
208
|
+
if (!skill?.content) {
|
|
209
209
|
throw new CleoError(ExitCode.NOT_FOUND, `Skill not found: ${skillName}`, {
|
|
210
210
|
fix: `Check skills directory for ${skillName}/SKILL.md`,
|
|
211
211
|
});
|
|
@@ -45,7 +45,7 @@ export async function buildPrompt(
|
|
|
45
45
|
|
|
46
46
|
// Find skill template
|
|
47
47
|
const skill = findSkill(templateName, cwd);
|
|
48
|
-
if (!skill
|
|
48
|
+
if (!skill?.content) {
|
|
49
49
|
const { canonical } = mapSkillName(templateName);
|
|
50
50
|
throw new CleoError(ExitCode.NOT_FOUND, `Skill template ${templateName} not found`, {
|
|
51
51
|
fix: `Expected at skills/${canonical}/SKILL.md`,
|
|
@@ -707,11 +707,15 @@ export const brainRetrievalLog = sqliteTable(
|
|
|
707
707
|
/** Estimated tokens consumed by this retrieval. */
|
|
708
708
|
tokensUsed: integer('tokens_used'),
|
|
709
709
|
|
|
710
|
+
/** Session ID (soft FK to tasks.db sessions). Enables grouping retrievals by session for STDP analysis. */
|
|
711
|
+
sessionId: text('session_id'),
|
|
712
|
+
|
|
710
713
|
createdAt: text('created_at').notNull().default(sql`(datetime('now'))`),
|
|
711
714
|
},
|
|
712
715
|
(table) => [
|
|
713
716
|
index('idx_retrieval_log_created').on(table.createdAt),
|
|
714
717
|
index('idx_retrieval_log_source').on(table.source),
|
|
718
|
+
index('idx_retrieval_log_session').on(table.sessionId),
|
|
715
719
|
],
|
|
716
720
|
);
|
|
717
721
|
|
package/src/store/json.ts
CHANGED
|
@@ -206,7 +206,7 @@ export async function readLogEntries(filePath: string): Promise<Record<string, u
|
|
|
206
206
|
if (remainder) {
|
|
207
207
|
for (const line of remainder.split('\n')) {
|
|
208
208
|
const l = line.trim();
|
|
209
|
-
if (!l
|
|
209
|
+
if (!l?.startsWith('{')) continue;
|
|
210
210
|
try {
|
|
211
211
|
entries.push(JSON.parse(l) as Record<string, unknown>);
|
|
212
212
|
} catch {
|
|
@@ -219,7 +219,7 @@ export async function readLogEntries(filePath: string): Promise<Record<string, u
|
|
|
219
219
|
// Pure JSONL (no initial JSON object)
|
|
220
220
|
for (const line of trimmed.split('\n')) {
|
|
221
221
|
const l = line.trim();
|
|
222
|
-
if (!l
|
|
222
|
+
if (!l?.startsWith('{')) continue;
|
|
223
223
|
try {
|
|
224
224
|
entries.push(JSON.parse(l) as Record<string, unknown>);
|
|
225
225
|
} catch {
|
package/src/system/health.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
checkCleoStructure,
|
|
19
19
|
checkConfig,
|
|
20
20
|
checkGlobalHome,
|
|
21
|
+
checkGlobalIdentity,
|
|
21
22
|
checkGlobalTemplates,
|
|
22
23
|
checkLogDir,
|
|
23
24
|
checkMemoryBridge,
|
|
@@ -842,6 +843,7 @@ export async function coreDoctorReport(projectRoot: string): Promise<DoctorRepor
|
|
|
842
843
|
// 5c. Global scaffold checks: home, templates, schemas
|
|
843
844
|
checks.push(mapCheckResult(checkGlobalHome()));
|
|
844
845
|
checks.push(mapCheckResult(checkGlobalTemplates()));
|
|
846
|
+
checks.push(mapCheckResult(checkGlobalIdentity()));
|
|
845
847
|
checks.push(mapSchemaCheckResult(checkGlobalSchemas()));
|
|
846
848
|
|
|
847
849
|
// 5d. Project scaffold checks: log dir, structure, git hooks, project-info, injection
|
package/src/tasks/task-ops.ts
CHANGED
|
@@ -144,7 +144,7 @@ function measureDependencyDepth(
|
|
|
144
144
|
visited.add(taskId);
|
|
145
145
|
|
|
146
146
|
const task = taskMap.get(taskId);
|
|
147
|
-
if (!task
|
|
147
|
+
if (!task?.depends || task.depends.length === 0) return 0;
|
|
148
148
|
|
|
149
149
|
let maxDepth = 0;
|
|
150
150
|
for (const depId of task.depends) {
|
|
@@ -921,7 +921,7 @@ export async function coreTaskUnarchive(
|
|
|
921
921
|
}
|
|
922
922
|
|
|
923
923
|
const archive = await accessor.loadArchive();
|
|
924
|
-
if (!archive
|
|
924
|
+
if (!archive?.archivedTasks) {
|
|
925
925
|
throw new Error('No archive file found');
|
|
926
926
|
}
|
|
927
927
|
|
package/src/upgrade.ts
CHANGED
|
@@ -940,6 +940,23 @@ export async function runUpgrade(
|
|
|
940
940
|
/* best-effort */
|
|
941
941
|
}
|
|
942
942
|
|
|
943
|
+
// Ensure global CLEOOS-IDENTITY.md is present (T631 — single SSoT).
|
|
944
|
+
// Self-heals if file is missing on existing projects after upgrade.
|
|
945
|
+
try {
|
|
946
|
+
const { ensureGlobalIdentity } = await import('./scaffold.js');
|
|
947
|
+
const identityResult = await ensureGlobalIdentity();
|
|
948
|
+
actions.push({
|
|
949
|
+
action: 'global_identity',
|
|
950
|
+
status:
|
|
951
|
+
identityResult.action === 'created' || identityResult.action === 'repaired'
|
|
952
|
+
? 'applied'
|
|
953
|
+
: 'skipped',
|
|
954
|
+
details: `${identityResult.path} (${identityResult.details ?? identityResult.action})`,
|
|
955
|
+
});
|
|
956
|
+
} catch {
|
|
957
|
+
/* best-effort — identity is already-kept-or-skipped on failure */
|
|
958
|
+
}
|
|
959
|
+
|
|
943
960
|
// Install core skills
|
|
944
961
|
try {
|
|
945
962
|
const skillsCreated: string[] = [];
|
|
@@ -438,9 +438,7 @@ export function allEpicChildrenVerified(epicId: string, tasks: TaskForVerificati
|
|
|
438
438
|
const incomplete = children.filter((t) => t.status !== 'done');
|
|
439
439
|
if (incomplete.length > 0) return false;
|
|
440
440
|
|
|
441
|
-
const unverified = children.filter(
|
|
442
|
-
(t) => t.status === 'done' && (!t.verification || !t.verification.passed),
|
|
443
|
-
);
|
|
441
|
+
const unverified = children.filter((t) => t.status === 'done' && !t.verification?.passed);
|
|
444
442
|
return unverified.length === 0;
|
|
445
443
|
}
|
|
446
444
|
|
|
@@ -451,9 +449,7 @@ export function allEpicChildrenVerified(epicId: string, tasks: TaskForVerificati
|
|
|
451
449
|
export function allSiblingsVerified(parentId: string, tasks: TaskForVerification[]): boolean {
|
|
452
450
|
const siblings = tasks.filter((t) => t.parentId === parentId);
|
|
453
451
|
|
|
454
|
-
const unverifiedDone = siblings.filter(
|
|
455
|
-
(t) => t.status === 'done' && (!t.verification || !t.verification.passed),
|
|
456
|
-
);
|
|
452
|
+
const unverifiedDone = siblings.filter((t) => t.status === 'done' && !t.verification?.passed);
|
|
457
453
|
|
|
458
454
|
const incomplete = siblings.filter(
|
|
459
455
|
(t) => t.status === 'pending' || t.status === 'active' || t.status === 'blocked',
|