@basou/core 0.11.0 → 0.12.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/dist/index.d.ts +189 -6
- package/dist/index.js +583 -191
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1720,6 +1720,8 @@ function splitLinesBytes2(buf) {
|
|
|
1720
1720
|
}
|
|
1721
1721
|
|
|
1722
1722
|
// src/git/snapshot.ts
|
|
1723
|
+
import { readdir as readdir3, stat as stat2 } from "fs/promises";
|
|
1724
|
+
import { join as join9 } from "path";
|
|
1723
1725
|
import { simpleGit } from "simple-git";
|
|
1724
1726
|
|
|
1725
1727
|
// src/storage/status.ts
|
|
@@ -1757,19 +1759,19 @@ var DIRECTORY_CHECKS = {
|
|
|
1757
1759
|
tmp: (p) => p.tmp
|
|
1758
1760
|
};
|
|
1759
1761
|
async function assertBasouRootSafe(rootPath) {
|
|
1760
|
-
let
|
|
1762
|
+
let stat4;
|
|
1761
1763
|
try {
|
|
1762
|
-
|
|
1764
|
+
stat4 = await fsp.lstat(rootPath);
|
|
1763
1765
|
} catch (error) {
|
|
1764
1766
|
if (hasErrorCode2(error) && error.code === "ENOENT") {
|
|
1765
1767
|
throw new Error("Basou workspace not found", { cause: error });
|
|
1766
1768
|
}
|
|
1767
1769
|
throw new Error("Failed to inspect .basou root", { cause: error });
|
|
1768
1770
|
}
|
|
1769
|
-
if (
|
|
1771
|
+
if (stat4.isSymbolicLink()) {
|
|
1770
1772
|
throw new Error(".basou root is a symlink; refusing to operate");
|
|
1771
1773
|
}
|
|
1772
|
-
if (!
|
|
1774
|
+
if (!stat4.isDirectory()) {
|
|
1773
1775
|
throw new Error(".basou root exists but is not a directory");
|
|
1774
1776
|
}
|
|
1775
1777
|
}
|
|
@@ -1849,6 +1851,14 @@ function isGitNotFound(error) {
|
|
|
1849
1851
|
}
|
|
1850
1852
|
return false;
|
|
1851
1853
|
}
|
|
1854
|
+
function isNotAGitRepository(error) {
|
|
1855
|
+
let cur = error;
|
|
1856
|
+
for (let i = 0; i < 4 && cur instanceof Error; i++) {
|
|
1857
|
+
if (/not a git repository/i.test(cur.message)) return true;
|
|
1858
|
+
cur = cur.cause;
|
|
1859
|
+
}
|
|
1860
|
+
return false;
|
|
1861
|
+
}
|
|
1852
1862
|
async function resolveRepositoryRoot(cwd) {
|
|
1853
1863
|
const git = safeSimpleGit(cwd);
|
|
1854
1864
|
try {
|
|
@@ -1864,9 +1874,54 @@ async function resolveRepositoryRoot(cwd) {
|
|
|
1864
1874
|
if (error instanceof Error && error.message === "Not a git repository") {
|
|
1865
1875
|
throw error;
|
|
1866
1876
|
}
|
|
1867
|
-
|
|
1877
|
+
if (isNotAGitRepository(error)) {
|
|
1878
|
+
throw new Error("Not a git repository", { cause: error });
|
|
1879
|
+
}
|
|
1880
|
+
throw new Error("Git command failed", { cause: error });
|
|
1868
1881
|
}
|
|
1869
1882
|
}
|
|
1883
|
+
async function resolveBasouRepositoryRoot(cwd, opts) {
|
|
1884
|
+
try {
|
|
1885
|
+
return await resolveRepositoryRoot(cwd);
|
|
1886
|
+
} catch (error) {
|
|
1887
|
+
if (!(error instanceof Error) || error.message !== "Not a git repository") throw error;
|
|
1888
|
+
const linked = await findLinkedBasouRepos(cwd);
|
|
1889
|
+
const only = linked[0];
|
|
1890
|
+
if (only !== void 0 && linked.length === 1) {
|
|
1891
|
+
opts?.onRedirect?.({ via: only.name, root: only.root });
|
|
1892
|
+
return only.root;
|
|
1893
|
+
}
|
|
1894
|
+
if (linked.length > 1) {
|
|
1895
|
+
const names = linked.map((l) => l.name).join(", ");
|
|
1896
|
+
throw new Error(
|
|
1897
|
+
`Ambiguous workspace view: ${linked.length} linked repos have a .basou store (${names}). cd into the one you want and re-run.`
|
|
1898
|
+
);
|
|
1899
|
+
}
|
|
1900
|
+
throw error;
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
async function findLinkedBasouRepos(dir) {
|
|
1904
|
+
const entries = await readdir3(dir, { withFileTypes: true }).catch(() => null);
|
|
1905
|
+
if (entries === null) return [];
|
|
1906
|
+
const byRoot = /* @__PURE__ */ new Map();
|
|
1907
|
+
for (const entry of entries) {
|
|
1908
|
+
if (!entry.isSymbolicLink()) continue;
|
|
1909
|
+
let root;
|
|
1910
|
+
try {
|
|
1911
|
+
root = await resolveRepositoryRoot(join9(dir, entry.name));
|
|
1912
|
+
} catch {
|
|
1913
|
+
continue;
|
|
1914
|
+
}
|
|
1915
|
+
try {
|
|
1916
|
+
if (!(await stat2(join9(root, ".basou"))).isDirectory()) continue;
|
|
1917
|
+
} catch {
|
|
1918
|
+
continue;
|
|
1919
|
+
}
|
|
1920
|
+
const existing = byRoot.get(root);
|
|
1921
|
+
if (existing === void 0 || entry.name < existing) byRoot.set(root, entry.name);
|
|
1922
|
+
}
|
|
1923
|
+
return [...byRoot.entries()].map(([root, name]) => ({ name, root })).sort((a, b) => a.name.localeCompare(b.name));
|
|
1924
|
+
}
|
|
1870
1925
|
async function tryRemoteUrl(repositoryRoot) {
|
|
1871
1926
|
const git = safeSimpleGit(repositoryRoot);
|
|
1872
1927
|
try {
|
|
@@ -2013,12 +2068,12 @@ function parseDiffNameStatus(raw) {
|
|
|
2013
2068
|
}
|
|
2014
2069
|
|
|
2015
2070
|
// src/handoff/handoff-renderer.ts
|
|
2016
|
-
import { join as
|
|
2071
|
+
import { join as join13 } from "path";
|
|
2017
2072
|
|
|
2018
2073
|
// src/storage/tasks.ts
|
|
2019
2074
|
import { createHash as createHash2 } from "crypto";
|
|
2020
|
-
import { mkdir as mkdir3, readdir as
|
|
2021
|
-
import { join as
|
|
2075
|
+
import { mkdir as mkdir3, readdir as readdir4, readFile as readFile7, rename as rename2, stat as stat3, unlink as unlink3 } from "fs/promises";
|
|
2076
|
+
import { join as join12 } from "path";
|
|
2022
2077
|
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
2023
2078
|
import { z as z8 } from "zod";
|
|
2024
2079
|
|
|
@@ -2062,7 +2117,7 @@ var TaskSchema = z6.object({
|
|
|
2062
2117
|
// src/storage/ad-hoc-session.ts
|
|
2063
2118
|
import { mkdir as mkdir2, rm } from "fs/promises";
|
|
2064
2119
|
import { homedir } from "os";
|
|
2065
|
-
import { join as
|
|
2120
|
+
import { join as join10 } from "path";
|
|
2066
2121
|
|
|
2067
2122
|
// src/lib/path-sanitizer.ts
|
|
2068
2123
|
import { posix as path } from "path";
|
|
@@ -2142,8 +2197,8 @@ async function createAdHocSessionWithEvent(input) {
|
|
|
2142
2197
|
taskId: input.taskId ?? null
|
|
2143
2198
|
})
|
|
2144
2199
|
);
|
|
2145
|
-
const sessionDir =
|
|
2146
|
-
const sessionYamlPath =
|
|
2200
|
+
const sessionDir = join10(input.paths.sessions, sessionId);
|
|
2201
|
+
const sessionYamlPath = join10(sessionDir, "session.yaml");
|
|
2147
2202
|
const lock = await acquireLock(input.paths, "session", sessionId);
|
|
2148
2203
|
let bulkResult = null;
|
|
2149
2204
|
try {
|
|
@@ -2293,7 +2348,7 @@ function assertTargetEventIdentity(event, expectedSessionId, expectedEventId) {
|
|
|
2293
2348
|
|
|
2294
2349
|
// src/storage/task-index.ts
|
|
2295
2350
|
import { readFile as readFile6 } from "fs/promises";
|
|
2296
|
-
import { join as
|
|
2351
|
+
import { join as join11 } from "path";
|
|
2297
2352
|
|
|
2298
2353
|
// src/schemas/task-index.schema.ts
|
|
2299
2354
|
import { z as z7 } from "zod";
|
|
@@ -2312,7 +2367,7 @@ var TASK_INDEX_SCHEMA_VERSION = "0.1.0";
|
|
|
2312
2367
|
|
|
2313
2368
|
// src/storage/task-index.ts
|
|
2314
2369
|
function taskIndexPath(paths) {
|
|
2315
|
-
return
|
|
2370
|
+
return join11(paths.tasks, "index.json");
|
|
2316
2371
|
}
|
|
2317
2372
|
async function readTaskIndex(paths) {
|
|
2318
2373
|
const filePath = taskIndexPath(paths);
|
|
@@ -2426,7 +2481,7 @@ function splitFrontMatter(raw) {
|
|
|
2426
2481
|
return { yamlText, body };
|
|
2427
2482
|
}
|
|
2428
2483
|
async function readTaskFile(paths, taskId) {
|
|
2429
|
-
const filePath =
|
|
2484
|
+
const filePath = join12(paths.tasks, `${taskId}.md`);
|
|
2430
2485
|
let raw;
|
|
2431
2486
|
try {
|
|
2432
2487
|
raw = await readFile7(filePath, "utf8");
|
|
@@ -2459,7 +2514,7 @@ async function readTaskFile(paths, taskId) {
|
|
|
2459
2514
|
}
|
|
2460
2515
|
async function writeTaskFile(paths, taskId, doc, options) {
|
|
2461
2516
|
const validated = TaskSchema.parse(doc.task);
|
|
2462
|
-
const filePath =
|
|
2517
|
+
const filePath = join12(paths.tasks, `${taskId}.md`);
|
|
2463
2518
|
const yamlText = stringifyYaml(validated);
|
|
2464
2519
|
const trimmedBody = doc.body.length === 0 ? "" : `
|
|
2465
2520
|
${doc.body.endsWith("\n") ? doc.body : `${doc.body}
|
|
@@ -2511,7 +2566,7 @@ async function enumerateTaskIds(paths) {
|
|
|
2511
2566
|
async function enumerateTaskIdsFromDisk(paths) {
|
|
2512
2567
|
let entries;
|
|
2513
2568
|
try {
|
|
2514
|
-
entries = (await
|
|
2569
|
+
entries = (await readdir4(paths.tasks, { withFileTypes: true })).filter((d) => d.isFile()).map((d) => d.name);
|
|
2515
2570
|
} catch (error) {
|
|
2516
2571
|
if (findErrorCode(error, "ENOENT")) return [];
|
|
2517
2572
|
throw new Error("Failed to enumerate tasks", { cause: error });
|
|
@@ -2544,12 +2599,12 @@ async function safeUpdateTaskIndex(paths, op) {
|
|
|
2544
2599
|
}
|
|
2545
2600
|
var ARCHIVE_DIR_NAME = "archive";
|
|
2546
2601
|
function archiveTasksDir(paths) {
|
|
2547
|
-
return
|
|
2602
|
+
return join12(paths.tasks, ARCHIVE_DIR_NAME);
|
|
2548
2603
|
}
|
|
2549
2604
|
async function enumerateArchivedTaskIds(paths) {
|
|
2550
2605
|
let entries;
|
|
2551
2606
|
try {
|
|
2552
|
-
entries = (await
|
|
2607
|
+
entries = (await readdir4(archiveTasksDir(paths), { withFileTypes: true })).filter((d) => d.isFile()).map((d) => d.name);
|
|
2553
2608
|
} catch (error) {
|
|
2554
2609
|
if (findErrorCode(error, "ENOENT")) return [];
|
|
2555
2610
|
throw new Error("Failed to enumerate archived tasks", { cause: error });
|
|
@@ -2574,7 +2629,7 @@ async function readTaskFileWithArchiveFallback(paths, taskId) {
|
|
|
2574
2629
|
throw error;
|
|
2575
2630
|
}
|
|
2576
2631
|
}
|
|
2577
|
-
const archiveFilePath =
|
|
2632
|
+
const archiveFilePath = join12(archiveTasksDir(paths), `${taskId}.md`);
|
|
2578
2633
|
let raw;
|
|
2579
2634
|
try {
|
|
2580
2635
|
raw = await readFile7(archiveFilePath, "utf8");
|
|
@@ -2868,7 +2923,7 @@ async function createTaskAttachLocked(input) {
|
|
|
2868
2923
|
...sessionDoc,
|
|
2869
2924
|
session: { ...sessionDoc.session, task_id: input.taskId }
|
|
2870
2925
|
};
|
|
2871
|
-
await overwriteYamlFile(
|
|
2926
|
+
await overwriteYamlFile(join12(input.paths.sessions, input.sessionId, "session.yaml"), updated);
|
|
2872
2927
|
} catch (error) {
|
|
2873
2928
|
throw new TaskWriteAfterEventError({
|
|
2874
2929
|
taskId: input.taskId,
|
|
@@ -3127,17 +3182,17 @@ function buildUpdatedDoc(input) {
|
|
|
3127
3182
|
return { task: next, body: input.currentDoc.body };
|
|
3128
3183
|
}
|
|
3129
3184
|
async function computeTaskMdSnapshot(paths, taskId) {
|
|
3130
|
-
const filePath =
|
|
3131
|
-
const [stats, raw] = await Promise.all([
|
|
3185
|
+
const filePath = join12(paths.tasks, `${taskId}.md`);
|
|
3186
|
+
const [stats, raw] = await Promise.all([stat3(filePath), readFile7(filePath)]);
|
|
3132
3187
|
const hash = createHash2("sha256").update(raw).digest("hex");
|
|
3133
3188
|
return { mtimeMs: stats.mtimeMs, hash };
|
|
3134
3189
|
}
|
|
3135
3190
|
async function readTaskFileWithSnapshot(paths, taskId) {
|
|
3136
|
-
const filePath =
|
|
3191
|
+
const filePath = join12(paths.tasks, `${taskId}.md`);
|
|
3137
3192
|
let rawBuffer;
|
|
3138
3193
|
let stats;
|
|
3139
3194
|
try {
|
|
3140
|
-
[rawBuffer, stats] = await Promise.all([readFile7(filePath),
|
|
3195
|
+
[rawBuffer, stats] = await Promise.all([readFile7(filePath), stat3(filePath)]);
|
|
3141
3196
|
} catch (error) {
|
|
3142
3197
|
if (findErrorCode(error, "ENOENT")) {
|
|
3143
3198
|
throw new Error("Task file not found", { cause: error });
|
|
@@ -3625,7 +3680,7 @@ async function deleteTaskLocked(input) {
|
|
|
3625
3680
|
});
|
|
3626
3681
|
const eventId = adHoc.targetEventIds[0];
|
|
3627
3682
|
try {
|
|
3628
|
-
await unlink3(
|
|
3683
|
+
await unlink3(join12(input.paths.tasks, `${input.taskId}.md`));
|
|
3629
3684
|
} catch (error) {
|
|
3630
3685
|
throw new TaskWriteAfterEventError({
|
|
3631
3686
|
taskId: input.taskId,
|
|
@@ -3697,8 +3752,8 @@ async function archiveTaskLocked(input) {
|
|
|
3697
3752
|
);
|
|
3698
3753
|
await mkdir3(archiveTasksDir(input.paths), { recursive: true });
|
|
3699
3754
|
await rename2(
|
|
3700
|
-
|
|
3701
|
-
|
|
3755
|
+
join12(input.paths.tasks, `${input.taskId}.md`),
|
|
3756
|
+
join12(archiveTasksDir(input.paths), `${input.taskId}.md`)
|
|
3702
3757
|
);
|
|
3703
3758
|
} catch (error) {
|
|
3704
3759
|
throw new TaskWriteAfterEventError({
|
|
@@ -3734,7 +3789,7 @@ async function renderHandoff(input) {
|
|
|
3734
3789
|
const tasksCreated = [];
|
|
3735
3790
|
const tasksStatusChanged = [];
|
|
3736
3791
|
for (const entry of entries) {
|
|
3737
|
-
const sessionDir =
|
|
3792
|
+
const sessionDir = join13(input.paths.sessions, entry.sessionId);
|
|
3738
3793
|
try {
|
|
3739
3794
|
for await (const ev of replayEvents(sessionDir, {
|
|
3740
3795
|
onWarning: (w) => input.onWarning?.(w, entry.sessionId)
|
|
@@ -3850,11 +3905,11 @@ function formatHandoffBody(args) {
|
|
|
3850
3905
|
if (args.latestSession !== void 0) {
|
|
3851
3906
|
const status = args.latestSession.session.session.status;
|
|
3852
3907
|
const label = args.latestSession.session.session.label;
|
|
3853
|
-
const
|
|
3908
|
+
const shortId2 = shortIdWithPrefix(args.latestSession.sessionId);
|
|
3854
3909
|
if (label !== void 0 && label !== "") {
|
|
3855
|
-
lines.push(`- \u6700\u7D42 session: ${label} (${status}) [${
|
|
3910
|
+
lines.push(`- \u6700\u7D42 session: ${label} (${status}) [${shortId2}]`);
|
|
3856
3911
|
} else {
|
|
3857
|
-
lines.push(`- \u6700\u7D42 session: ${
|
|
3912
|
+
lines.push(`- \u6700\u7D42 session: ${shortId2} (${status})`);
|
|
3858
3913
|
}
|
|
3859
3914
|
} else {
|
|
3860
3915
|
lines.push("- \u6700\u7D42 session: (no live sessions)");
|
|
@@ -4091,11 +4146,455 @@ async function resolveIdInternal(paths, input, kind, options = {}) {
|
|
|
4091
4146
|
return matches[0];
|
|
4092
4147
|
}
|
|
4093
4148
|
|
|
4094
|
-
// src/
|
|
4149
|
+
// src/orientation/orientation-renderer.ts
|
|
4095
4150
|
import { join as join14 } from "path";
|
|
4096
4151
|
|
|
4152
|
+
// src/storage/manifest.ts
|
|
4153
|
+
import { lstat as lstat3 } from "fs/promises";
|
|
4154
|
+
|
|
4155
|
+
// src/schemas/manifest.schema.ts
|
|
4156
|
+
import { z as z9 } from "zod";
|
|
4157
|
+
var ProjectSchema = z9.object({
|
|
4158
|
+
name: z9.string().optional(),
|
|
4159
|
+
description: z9.string().optional(),
|
|
4160
|
+
repository_url: z9.string().nullable().optional()
|
|
4161
|
+
});
|
|
4162
|
+
var CapabilitiesSchema = z9.object({
|
|
4163
|
+
enabled: z9.array(z9.string())
|
|
4164
|
+
});
|
|
4165
|
+
var ApprovalConfigSchema = z9.object({
|
|
4166
|
+
required_for: z9.array(z9.string()).optional(),
|
|
4167
|
+
default_risk_level: z9.enum(["low", "medium", "high", "critical"])
|
|
4168
|
+
});
|
|
4169
|
+
var ClaudeCodeAdapterConfigSchema = z9.object({
|
|
4170
|
+
enabled: z9.boolean(),
|
|
4171
|
+
config_path: z9.string().optional()
|
|
4172
|
+
});
|
|
4173
|
+
var AdaptersSchema = z9.object({
|
|
4174
|
+
"claude-code": ClaudeCodeAdapterConfigSchema
|
|
4175
|
+
});
|
|
4176
|
+
var GitConfigSchema = z9.object({
|
|
4177
|
+
events_log: z9.enum(["ignore", "commit"]).default("ignore")
|
|
4178
|
+
});
|
|
4179
|
+
var SOURCE_ROOT_PATTERN = /^(?![~/\\])(?![A-Za-z]:)[^\0\\]+$/;
|
|
4180
|
+
var SourceRootSchema = z9.string().min(1).regex(SOURCE_ROOT_PATTERN, {
|
|
4181
|
+
message: "source_roots entries must be relative paths (no absolute path, '~', '\\', or null byte)"
|
|
4182
|
+
});
|
|
4183
|
+
var ImportConfigSchema = z9.object({
|
|
4184
|
+
source_roots: z9.array(SourceRootSchema).min(1).optional()
|
|
4185
|
+
});
|
|
4186
|
+
var WorkspaceMetaSchema = z9.object({
|
|
4187
|
+
id: WorkspaceIdSchema,
|
|
4188
|
+
name: z9.string().min(1),
|
|
4189
|
+
created_at: IsoTimestampSchema,
|
|
4190
|
+
updated_at: IsoTimestampSchema
|
|
4191
|
+
});
|
|
4192
|
+
var ManifestSchema = z9.object({
|
|
4193
|
+
schema_version: SchemaVersionSchema,
|
|
4194
|
+
basou_version: z9.literal("0.1.0"),
|
|
4195
|
+
workspace: WorkspaceMetaSchema,
|
|
4196
|
+
project: ProjectSchema,
|
|
4197
|
+
capabilities: CapabilitiesSchema,
|
|
4198
|
+
approval: ApprovalConfigSchema,
|
|
4199
|
+
adapters: AdaptersSchema,
|
|
4200
|
+
git: GitConfigSchema,
|
|
4201
|
+
import: ImportConfigSchema.optional()
|
|
4202
|
+
});
|
|
4203
|
+
|
|
4204
|
+
// src/storage/manifest.ts
|
|
4205
|
+
function createManifest(input) {
|
|
4206
|
+
if (input.workspaceName.length === 0) {
|
|
4207
|
+
throw new Error("Workspace name is empty. Pass --name explicitly.");
|
|
4208
|
+
}
|
|
4209
|
+
const now = (input.now ?? /* @__PURE__ */ new Date()).toISOString();
|
|
4210
|
+
const workspaceId = input.workspaceId ?? prefixedUlid("ws");
|
|
4211
|
+
const project = {
|
|
4212
|
+
...input.projectName !== void 0 ? { name: input.projectName } : {},
|
|
4213
|
+
...input.projectDescription !== void 0 ? { description: input.projectDescription } : {},
|
|
4214
|
+
...input.repositoryUrl !== void 0 ? { repository_url: input.repositoryUrl } : {}
|
|
4215
|
+
};
|
|
4216
|
+
const manifest = {
|
|
4217
|
+
schema_version: "0.1.0",
|
|
4218
|
+
basou_version: "0.1.0",
|
|
4219
|
+
workspace: {
|
|
4220
|
+
id: workspaceId,
|
|
4221
|
+
name: input.workspaceName,
|
|
4222
|
+
created_at: now,
|
|
4223
|
+
updated_at: now
|
|
4224
|
+
},
|
|
4225
|
+
project,
|
|
4226
|
+
capabilities: {
|
|
4227
|
+
enabled: ["core", "claude-code-adapter", "terminal-recording", "git-capability", "approval"]
|
|
4228
|
+
},
|
|
4229
|
+
approval: {
|
|
4230
|
+
required_for: ["destructive_command", "external_send"],
|
|
4231
|
+
default_risk_level: "medium"
|
|
4232
|
+
},
|
|
4233
|
+
adapters: {
|
|
4234
|
+
"claude-code": { enabled: true }
|
|
4235
|
+
},
|
|
4236
|
+
git: { events_log: "ignore" },
|
|
4237
|
+
...input.sourceRoots !== void 0 && input.sourceRoots.length > 0 ? { import: { source_roots: input.sourceRoots } } : {}
|
|
4238
|
+
};
|
|
4239
|
+
return ManifestSchema.parse(manifest);
|
|
4240
|
+
}
|
|
4241
|
+
async function writeManifest(paths, manifest, options) {
|
|
4242
|
+
const force = options?.force === true;
|
|
4243
|
+
const validated = ManifestSchema.parse(manifest);
|
|
4244
|
+
if (!force) {
|
|
4245
|
+
let existed = false;
|
|
4246
|
+
try {
|
|
4247
|
+
await lstat3(paths.files.manifest);
|
|
4248
|
+
existed = true;
|
|
4249
|
+
} catch (error) {
|
|
4250
|
+
if (!hasErrorCode3(error) || error.code !== "ENOENT") {
|
|
4251
|
+
throw new Error("Failed to inspect existing manifest", { cause: error });
|
|
4252
|
+
}
|
|
4253
|
+
}
|
|
4254
|
+
if (existed) {
|
|
4255
|
+
throw new Error("Already initialized. Use --force to overwrite.");
|
|
4256
|
+
}
|
|
4257
|
+
}
|
|
4258
|
+
await writeYamlFile(paths.files.manifest, validated);
|
|
4259
|
+
}
|
|
4260
|
+
async function readManifest(paths) {
|
|
4261
|
+
const raw = await readYamlFile(paths.files.manifest);
|
|
4262
|
+
return ManifestSchema.parse(raw);
|
|
4263
|
+
}
|
|
4264
|
+
function hasErrorCode3(error) {
|
|
4265
|
+
if (!(error instanceof Error)) return false;
|
|
4266
|
+
return typeof error.code === "string";
|
|
4267
|
+
}
|
|
4268
|
+
|
|
4269
|
+
// src/orientation/orientation-renderer.ts
|
|
4270
|
+
async function summarizeOrientation(input) {
|
|
4271
|
+
const limit = input.relatedFilesLimit ?? 10;
|
|
4272
|
+
const now = new Date(input.nowIso);
|
|
4273
|
+
const loadOpts = { now };
|
|
4274
|
+
if (input.onSessionSkip !== void 0) loadOpts.onSkip = input.onSessionSkip;
|
|
4275
|
+
if (input.onWarning !== void 0) loadOpts.onWarning = input.onWarning;
|
|
4276
|
+
const entries = await loadSessionEntries(input.paths, loadOpts);
|
|
4277
|
+
const decisions = [];
|
|
4278
|
+
for (const entry of entries) {
|
|
4279
|
+
const sessionDir = join14(input.paths.sessions, entry.sessionId);
|
|
4280
|
+
try {
|
|
4281
|
+
for await (const ev of replayEvents(sessionDir, {
|
|
4282
|
+
onWarning: (w) => input.onWarning?.(w, entry.sessionId)
|
|
4283
|
+
})) {
|
|
4284
|
+
if (ev.type === "decision_recorded") {
|
|
4285
|
+
decisions.push({
|
|
4286
|
+
decisionId: ev.decision_id,
|
|
4287
|
+
title: ev.title,
|
|
4288
|
+
occurredAt: ev.occurred_at
|
|
4289
|
+
});
|
|
4290
|
+
}
|
|
4291
|
+
}
|
|
4292
|
+
} catch {
|
|
4293
|
+
input.onSessionSkip?.(entry.sessionId, "events_jsonl_unreadable");
|
|
4294
|
+
}
|
|
4295
|
+
}
|
|
4296
|
+
decisions.sort((a, b) => {
|
|
4297
|
+
const c = Date.parse(a.occurredAt) - Date.parse(b.occurredAt);
|
|
4298
|
+
return c !== 0 ? c : a.decisionId.localeCompare(b.decisionId);
|
|
4299
|
+
});
|
|
4300
|
+
const latestDecision = decisions[decisions.length - 1];
|
|
4301
|
+
const taskLoadOpts = {};
|
|
4302
|
+
if (input.onTaskSkip !== void 0) taskLoadOpts.onSkip = input.onTaskSkip;
|
|
4303
|
+
const taskEntries = await loadTaskEntries(input.paths, taskLoadOpts);
|
|
4304
|
+
const inFlightTasks = taskEntries.filter((t) => t.task.task.status === "in_progress" || t.task.task.status === "planned").map((t) => ({
|
|
4305
|
+
id: t.task.task.id,
|
|
4306
|
+
title: t.task.task.title,
|
|
4307
|
+
status: t.task.task.status,
|
|
4308
|
+
linkedSessions: t.task.task.linked_sessions?.length ?? 0
|
|
4309
|
+
}));
|
|
4310
|
+
const plannedTasks = taskEntries.filter((t) => t.task.task.status === "planned").map((t) => ({ id: t.task.task.id, title: t.task.task.title }));
|
|
4311
|
+
const { pending: pendingIds } = await enumerateApprovals(input.paths);
|
|
4312
|
+
const pendingApprovals = [];
|
|
4313
|
+
for (const id of [...pendingIds].sort()) {
|
|
4314
|
+
const loaded = await loadApproval(input.paths, id);
|
|
4315
|
+
if (loaded === null) continue;
|
|
4316
|
+
const a = loaded.approval;
|
|
4317
|
+
pendingApprovals.push({
|
|
4318
|
+
id,
|
|
4319
|
+
risk: a.risk_level,
|
|
4320
|
+
kind: a.action.kind,
|
|
4321
|
+
reason: a.reason,
|
|
4322
|
+
sessionId: a.session_id,
|
|
4323
|
+
createdAt: a.created_at,
|
|
4324
|
+
expired: isLazyExpired(a, now)
|
|
4325
|
+
});
|
|
4326
|
+
}
|
|
4327
|
+
const suspects = entries.filter((e) => e.suspect).map((e) => ({
|
|
4328
|
+
sessionId: e.sessionId,
|
|
4329
|
+
status: e.session.session.status,
|
|
4330
|
+
reason: e.suspectReason
|
|
4331
|
+
}));
|
|
4332
|
+
const liveEntries = entries.filter(
|
|
4333
|
+
(e) => e.session.session.status !== "archived" && e.session.session.source.kind !== "import"
|
|
4334
|
+
);
|
|
4335
|
+
const latestEntry = [...liveEntries].sort(
|
|
4336
|
+
(a, b) => Date.parse(b.session.session.started_at) - Date.parse(a.session.session.started_at)
|
|
4337
|
+
)[0];
|
|
4338
|
+
const latestSession = latestEntry !== void 0 ? {
|
|
4339
|
+
sessionId: latestEntry.sessionId,
|
|
4340
|
+
label: latestEntry.session.session.label ?? null,
|
|
4341
|
+
status: latestEntry.session.session.status
|
|
4342
|
+
} : null;
|
|
4343
|
+
const activityEntries = entries.filter((e) => e.session.session.status !== "archived");
|
|
4344
|
+
const newest = [...activityEntries].sort(
|
|
4345
|
+
(a, b) => Date.parse(b.session.session.started_at) - Date.parse(a.session.session.started_at)
|
|
4346
|
+
)[0];
|
|
4347
|
+
const bySourceMap = /* @__PURE__ */ new Map();
|
|
4348
|
+
for (const e of entries) {
|
|
4349
|
+
const k = e.session.session.source.kind;
|
|
4350
|
+
bySourceMap.set(k, (bySourceMap.get(k) ?? 0) + 1);
|
|
4351
|
+
}
|
|
4352
|
+
const bySource = [...bySourceMap.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([kind, count]) => ({ kind, count }));
|
|
4353
|
+
let sourceRoots = null;
|
|
4354
|
+
try {
|
|
4355
|
+
const manifest = await readManifest(input.paths);
|
|
4356
|
+
sourceRoots = manifest.import?.source_roots ?? null;
|
|
4357
|
+
} catch {
|
|
4358
|
+
sourceRoots = null;
|
|
4359
|
+
}
|
|
4360
|
+
const latestFiles = latestEntry?.session.session.related_files ?? [];
|
|
4361
|
+
const uniqueFiles = new Set(latestFiles);
|
|
4362
|
+
const displayed = [...uniqueFiles].sort().slice(0, limit);
|
|
4363
|
+
const overflow = Math.max(0, uniqueFiles.size - limit);
|
|
4364
|
+
return {
|
|
4365
|
+
generatedAt: input.nowIso,
|
|
4366
|
+
sessionCount: entries.length,
|
|
4367
|
+
latestSession,
|
|
4368
|
+
latestDecision: latestDecision ?? null,
|
|
4369
|
+
decisionCount: decisions.length,
|
|
4370
|
+
relatedFiles: { displayed, overflow },
|
|
4371
|
+
inFlightTasks,
|
|
4372
|
+
plannedTasks,
|
|
4373
|
+
pendingApprovals,
|
|
4374
|
+
suspects,
|
|
4375
|
+
freshness: {
|
|
4376
|
+
newestStartedAt: newest?.session.session.started_at ?? null,
|
|
4377
|
+
newestSource: newest?.session.session.source.kind ?? null,
|
|
4378
|
+
bySource,
|
|
4379
|
+
sourceRoots
|
|
4380
|
+
}
|
|
4381
|
+
};
|
|
4382
|
+
}
|
|
4383
|
+
async function renderOrientation(input) {
|
|
4384
|
+
const summary = await summarizeOrientation(input);
|
|
4385
|
+
return {
|
|
4386
|
+
body: formatOrientationBody(summary, {
|
|
4387
|
+
staleness: input.staleness ?? null,
|
|
4388
|
+
verbose: input.verbose === true
|
|
4389
|
+
}),
|
|
4390
|
+
sessionCount: summary.sessionCount,
|
|
4391
|
+
pendingApprovalsCount: summary.pendingApprovals.length,
|
|
4392
|
+
suspectCount: summary.suspects.length,
|
|
4393
|
+
inFlightTaskCount: summary.inFlightTasks.length,
|
|
4394
|
+
decisionCount: summary.decisionCount
|
|
4395
|
+
};
|
|
4396
|
+
}
|
|
4397
|
+
function formatOrientationBody(summary, opts) {
|
|
4398
|
+
const lines = [];
|
|
4399
|
+
const now = new Date(summary.generatedAt);
|
|
4400
|
+
const newestRel = relativeAge(summary.freshness.newestStartedAt ?? void 0, now);
|
|
4401
|
+
lines.push("# Orientation");
|
|
4402
|
+
lines.push("");
|
|
4403
|
+
lines.push(
|
|
4404
|
+
`> Generated at ${summary.generatedAt} \xB7 sessions ${summary.sessionCount} \xB7 newest ${newestRel} \xB7 pending ${summary.pendingApprovals.length} \xB7 suspect ${summary.suspects.length}`
|
|
4405
|
+
);
|
|
4406
|
+
lines.push("");
|
|
4407
|
+
lines.push("## \u4ECA\u3069\u3053\u306B\u3044\u308B");
|
|
4408
|
+
lines.push("");
|
|
4409
|
+
if (summary.latestSession !== null) {
|
|
4410
|
+
const s = summary.latestSession;
|
|
4411
|
+
const sid = shortId(s.sessionId);
|
|
4412
|
+
if (s.label !== null && s.label !== "") {
|
|
4413
|
+
lines.push(`- \u6700\u7D42 session: ${s.label} (${s.status}) [${sid}]`);
|
|
4414
|
+
} else {
|
|
4415
|
+
lines.push(`- \u6700\u7D42 session: ${sid} (${s.status})`);
|
|
4416
|
+
}
|
|
4417
|
+
} else {
|
|
4418
|
+
lines.push("- \u6700\u7D42 session: (no live sessions)");
|
|
4419
|
+
}
|
|
4420
|
+
if (summary.latestDecision !== null) {
|
|
4421
|
+
lines.push(
|
|
4422
|
+
`- \u76F4\u8FD1\u306E\u5224\u65AD: ${summary.latestDecision.title} [${shortId(summary.latestDecision.decisionId)}]`
|
|
4423
|
+
);
|
|
4424
|
+
if (summary.decisionCount > 1) {
|
|
4425
|
+
lines.push(` - ${summary.decisionCount} decisions total \u2014 see decisions.md`);
|
|
4426
|
+
}
|
|
4427
|
+
} else {
|
|
4428
|
+
lines.push("- \u76F4\u8FD1\u306E\u5224\u65AD: (no decisions recorded yet)");
|
|
4429
|
+
}
|
|
4430
|
+
if (summary.relatedFiles.displayed.length > 0) {
|
|
4431
|
+
const shown = summary.relatedFiles.displayed.join(", ");
|
|
4432
|
+
const more = summary.relatedFiles.overflow > 0 ? ` (... +${summary.relatedFiles.overflow} more)` : "";
|
|
4433
|
+
lines.push(`- \u76F4\u8FD1\u306E\u5909\u66F4\u30D5\u30A1\u30A4\u30EB: ${shown}${more}`);
|
|
4434
|
+
} else {
|
|
4435
|
+
lines.push("- \u76F4\u8FD1\u306E\u5909\u66F4\u30D5\u30A1\u30A4\u30EB: (none recorded)");
|
|
4436
|
+
}
|
|
4437
|
+
lines.push("");
|
|
4438
|
+
lines.push("## \u4F55\u304C\u52D5\u304F");
|
|
4439
|
+
lines.push("");
|
|
4440
|
+
lines.push(`### \u9032\u884C\u4E2D task (${summary.inFlightTasks.length})`);
|
|
4441
|
+
if (summary.inFlightTasks.length === 0) {
|
|
4442
|
+
lines.push("- (none)");
|
|
4443
|
+
} else {
|
|
4444
|
+
for (const t of summary.inFlightTasks) {
|
|
4445
|
+
const linkedSuffix = t.linkedSessions > 1 ? ` \u2014 linked_sessions: ${t.linkedSessions}` : "";
|
|
4446
|
+
lines.push(`- ${t.title} (${t.status}) [${shortId(t.id)}]${linkedSuffix}`);
|
|
4447
|
+
}
|
|
4448
|
+
}
|
|
4449
|
+
lines.push("");
|
|
4450
|
+
lines.push(`### \u627F\u8A8D\u5F85\u3061 (${summary.pendingApprovals.length})`);
|
|
4451
|
+
if (summary.pendingApprovals.length === 0) {
|
|
4452
|
+
lines.push("- (none)");
|
|
4453
|
+
} else {
|
|
4454
|
+
for (const a of summary.pendingApprovals) {
|
|
4455
|
+
const expired = a.expired ? " (expired)" : "";
|
|
4456
|
+
lines.push(
|
|
4457
|
+
`- [${a.risk}] ${a.kind}: ${a.reason} \u2014 session ${shortId(a.sessionId)}, since ${a.createdAt}${expired}`
|
|
4458
|
+
);
|
|
4459
|
+
}
|
|
4460
|
+
}
|
|
4461
|
+
lines.push("");
|
|
4462
|
+
lines.push(`### \u8981\u6CE8\u610F session (${summary.suspects.length})`);
|
|
4463
|
+
if (summary.suspects.length === 0) {
|
|
4464
|
+
lines.push("- (none)");
|
|
4465
|
+
} else {
|
|
4466
|
+
for (const e of summary.suspects) {
|
|
4467
|
+
lines.push(`- ${shortId(e.sessionId)} (${e.status}) \u2014 ${suspectText(e.reason)}`);
|
|
4468
|
+
}
|
|
4469
|
+
}
|
|
4470
|
+
lines.push("");
|
|
4471
|
+
lines.push("## \u3069\u3053\u3078\u5411\u304B\u3046");
|
|
4472
|
+
lines.push("");
|
|
4473
|
+
if (summary.plannedTasks.length === 0) {
|
|
4474
|
+
lines.push("- (no planned tasks \u2014 direction is inferred from recent decisions)");
|
|
4475
|
+
if (summary.latestDecision !== null) {
|
|
4476
|
+
lines.push(` - \u76F4\u8FD1\u306E\u5224\u65AD: ${summary.latestDecision.title}`);
|
|
4477
|
+
}
|
|
4478
|
+
} else {
|
|
4479
|
+
for (const t of summary.plannedTasks) {
|
|
4480
|
+
lines.push(`- ${t.title} [${shortId(t.id)}]`);
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4483
|
+
lines.push("");
|
|
4484
|
+
lines.push("## \u3053\u308C\u306F\u6700\u65B0\u304B");
|
|
4485
|
+
lines.push("");
|
|
4486
|
+
for (const line of freshnessVerdict(summary, opts.staleness, now)) lines.push(line);
|
|
4487
|
+
if (opts.verbose) {
|
|
4488
|
+
lines.push("");
|
|
4489
|
+
lines.push("<!-- verbose: raw freshness telemetry -->");
|
|
4490
|
+
if (summary.freshness.newestStartedAt !== null) {
|
|
4491
|
+
lines.push(`- newest captured session: ${summary.freshness.newestStartedAt} (${newestRel})`);
|
|
4492
|
+
} else {
|
|
4493
|
+
lines.push("- newest captured session: (no sessions captured yet)");
|
|
4494
|
+
}
|
|
4495
|
+
const sourceBreakdown = summary.freshness.bySource.map(({ kind, count }) => `${kind} ${count}`).join(", ");
|
|
4496
|
+
lines.push(
|
|
4497
|
+
`- sessions: ${summary.sessionCount}${sourceBreakdown !== "" ? ` (${sourceBreakdown})` : ""}`
|
|
4498
|
+
);
|
|
4499
|
+
if (summary.freshness.sourceRoots !== null && summary.freshness.sourceRoots.length > 0) {
|
|
4500
|
+
lines.push(`- source roots: ${summary.freshness.sourceRoots.join(", ")}`);
|
|
4501
|
+
} else {
|
|
4502
|
+
lines.push("- source roots: (single root)");
|
|
4503
|
+
}
|
|
4504
|
+
lines.push(`- suspect sessions: ${summary.suspects.length}`);
|
|
4505
|
+
const probe = opts.staleness === null ? "not run" : `new ${opts.staleness.newSessions}, updated ${opts.staleness.updatedSessions}, unverifiable ${opts.staleness.unverifiableSessions ?? 0}`;
|
|
4506
|
+
lines.push(`- staleness probe: ${probe}`);
|
|
4507
|
+
}
|
|
4508
|
+
return lines.join("\n");
|
|
4509
|
+
}
|
|
4510
|
+
function toolDisplayName(kind) {
|
|
4511
|
+
switch (kind) {
|
|
4512
|
+
case "claude-code-import":
|
|
4513
|
+
case "claude-code-adapter":
|
|
4514
|
+
return "Claude Code";
|
|
4515
|
+
case "codex-import":
|
|
4516
|
+
return "Codex";
|
|
4517
|
+
case "terminal":
|
|
4518
|
+
return "\u30BF\u30FC\u30DF\u30CA\u30EB";
|
|
4519
|
+
case "human":
|
|
4520
|
+
return "\u624B\u52D5\u30E1\u30E2";
|
|
4521
|
+
case "import":
|
|
4522
|
+
return "\u4ED6\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9";
|
|
4523
|
+
default:
|
|
4524
|
+
return kind ?? "\u4E0D\u660E";
|
|
4525
|
+
}
|
|
4526
|
+
}
|
|
4527
|
+
function freshnessVerdict(summary, staleness, now) {
|
|
4528
|
+
if (staleness !== null && (staleness.unverifiableSessions ?? 0) > 0) {
|
|
4529
|
+
return [
|
|
4530
|
+
`\u26A0\uFE0F \u6700\u65B0\u304B\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3002\u5909\u5316\u3057\u305F\u304C\u5B89\u5168\u306B\u53D6\u308A\u8FBC\u3081\u306A\u3044\u30BB\u30C3\u30B7\u30E7\u30F3\u304C ${staleness.unverifiableSessions} \u4EF6\u3042\u308A\u307E\u3059(\u30CF\u30C3\u30B7\u30E5\u30C1\u30A7\u30FC\u30F3\u7834\u640D\u30FB\u975E\u8FFD\u8A18\u5909\u66F4\u306A\u3069)\u3002`,
|
|
4531
|
+
"`basou verify` \u3067\u78BA\u8A8D\u3057\u3001`basou refresh --force` \u3067\u518D\u53D6\u308A\u8FBC\u307F\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
|
|
4532
|
+
];
|
|
4533
|
+
}
|
|
4534
|
+
if (staleness !== null && (staleness.newSessions > 0 || staleness.updatedSessions > 0)) {
|
|
4535
|
+
const parts = [];
|
|
4536
|
+
if (staleness.newSessions > 0) parts.push(`\u65B0\u898F ${staleness.newSessions} \u4EF6`);
|
|
4537
|
+
if (staleness.updatedSessions > 0) parts.push(`\u66F4\u65B0 ${staleness.updatedSessions} \u4EF6`);
|
|
4538
|
+
return [
|
|
4539
|
+
`\u26A0\uFE0F \u53E4\u3044\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002\u6700\u5F8C\u306E\u53D6\u308A\u8FBC\u307F\u4EE5\u964D\u306B\u672A\u53D6\u308A\u8FBC\u307F\u306E\u4F5C\u696D\u304C\u3042\u308A\u307E\u3059(${parts.join("\u30FB")})\u3002`,
|
|
4540
|
+
"`basou refresh` \u3067\u66F4\u65B0\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
|
|
4541
|
+
];
|
|
4542
|
+
}
|
|
4543
|
+
if (summary.freshness.newestStartedAt === null) {
|
|
4544
|
+
return [
|
|
4545
|
+
"\u2139\uFE0F \u307E\u3060\u8A18\u9332\u304C\u3042\u308A\u307E\u305B\u3093\u3002",
|
|
4546
|
+
"\u3053\u306E\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u3067\u4F5C\u696D\u3059\u308B\u3068\u3001\u3053\u3053\u306B\u73FE\u5728\u5730\u304C\u8868\u793A\u3055\u308C\u307E\u3059\u3002"
|
|
4547
|
+
];
|
|
4548
|
+
}
|
|
4549
|
+
const rel = relativeAgeJa(summary.freshness.newestStartedAt, now);
|
|
4550
|
+
const tool = toolDisplayName(summary.freshness.newestSource);
|
|
4551
|
+
const suspectCount = summary.suspects.length;
|
|
4552
|
+
const suspectClause = suspectCount > 0 ? `\u8981\u6CE8\u610F\u30BB\u30C3\u30B7\u30E7\u30F3\u304C ${suspectCount} \u4EF6\u3042\u308A\u307E\u3059\u3002` : "\u53D6\u308A\u3053\u307C\u3057\u30FB\u8981\u6CE8\u610F\u306A\u3057\u3002";
|
|
4553
|
+
if (staleness === null) {
|
|
4554
|
+
return [
|
|
4555
|
+
`\u2139\uFE0F \u53D6\u308A\u8FBC\u307F\u6E08\u307F\u306E\u72B6\u614B\u3092\u8868\u793A\u3057\u3066\u3044\u307E\u3059\u3002\u6700\u5F8C\u306E\u4F5C\u696D\u306F ${rel}(${tool})\u3002`,
|
|
4556
|
+
"\u6700\u65B0\u304B\u78BA\u8A8D\u3059\u308B\u306B\u306F `basou refresh` \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
|
|
4557
|
+
];
|
|
4558
|
+
}
|
|
4559
|
+
return [`\u2705 \u6700\u65B0\u3067\u3059\u3002\u6700\u5F8C\u306E\u4F5C\u696D\u306F ${rel}(${tool})\u3002${suspectClause}`];
|
|
4560
|
+
}
|
|
4561
|
+
function relativeAgeJa(startedAt, now) {
|
|
4562
|
+
if (startedAt === null) return "(\u4E0D\u660E)";
|
|
4563
|
+
const ms = now.getTime() - Date.parse(startedAt);
|
|
4564
|
+
if (!Number.isFinite(ms) || ms < 0) return "\u305F\u3063\u305F\u4ECA";
|
|
4565
|
+
if (ms < 6e4) return "\u305F\u3063\u305F\u4ECA";
|
|
4566
|
+
const totalMin = Math.floor(ms / 6e4);
|
|
4567
|
+
const days = Math.floor(totalMin / 1440);
|
|
4568
|
+
const hours = Math.floor(totalMin % 1440 / 60);
|
|
4569
|
+
const mins = totalMin % 60;
|
|
4570
|
+
if (days > 0) return hours > 0 ? `${days}\u65E5${hours}\u6642\u9593\u524D` : `${days}\u65E5\u524D`;
|
|
4571
|
+
if (hours > 0) return mins > 0 ? `${hours}\u6642\u9593${mins}\u5206\u524D` : `${hours}\u6642\u9593\u524D`;
|
|
4572
|
+
return `${mins}\u5206\u524D`;
|
|
4573
|
+
}
|
|
4574
|
+
function relativeAge(startedAt, now) {
|
|
4575
|
+
if (startedAt === void 0) return "(unknown)";
|
|
4576
|
+
const ms = now.getTime() - Date.parse(startedAt);
|
|
4577
|
+
if (!Number.isFinite(ms)) return "(unknown)";
|
|
4578
|
+
if (ms < 0) return "just now";
|
|
4579
|
+
if (ms < 1e3) return "just now";
|
|
4580
|
+
return `${formatDurationMs(ms)} ago`;
|
|
4581
|
+
}
|
|
4582
|
+
function suspectText(reason) {
|
|
4583
|
+
if (reason === "events_say_ended_but_yaml_running") return "ended (yaml stale)";
|
|
4584
|
+
if (reason === "running_no_end_event") return "no end event";
|
|
4585
|
+
return "suspect";
|
|
4586
|
+
}
|
|
4587
|
+
function shortId(id) {
|
|
4588
|
+
const sep = id.indexOf("_");
|
|
4589
|
+
if (sep === -1) return id.slice(0, 10);
|
|
4590
|
+
return id.slice(0, sep + 1) + id.slice(sep + 1, sep + 1 + 10);
|
|
4591
|
+
}
|
|
4592
|
+
|
|
4593
|
+
// src/report/report-renderer.ts
|
|
4594
|
+
import { join as join16 } from "path";
|
|
4595
|
+
|
|
4097
4596
|
// src/stats/work-stats.ts
|
|
4098
|
-
import { join as
|
|
4597
|
+
import { join as join15 } from "path";
|
|
4099
4598
|
function resolveTimeZone(timeZone) {
|
|
4100
4599
|
if (timeZone !== void 0 && timeZone.length > 0) return timeZone;
|
|
4101
4600
|
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
@@ -4126,7 +4625,7 @@ async function computeWorkStats(input) {
|
|
|
4126
4625
|
const events = [];
|
|
4127
4626
|
let eventsUnreadable = false;
|
|
4128
4627
|
try {
|
|
4129
|
-
for await (const ev of replayEvents(
|
|
4628
|
+
for await (const ev of replayEvents(join15(input.paths.sessions, entry.sessionId), {
|
|
4130
4629
|
onWarning: (w) => input.onWarning?.(w, entry.sessionId)
|
|
4131
4630
|
})) {
|
|
4132
4631
|
events.push(ev);
|
|
@@ -4413,7 +4912,7 @@ async function renderReport(input) {
|
|
|
4413
4912
|
const statsBySession = new Map(stats.sessions.map((s) => [s.sessionId, s]));
|
|
4414
4913
|
const decisions = [];
|
|
4415
4914
|
for (const entry of entries) {
|
|
4416
|
-
const sessionDir =
|
|
4915
|
+
const sessionDir = join16(input.paths.sessions, entry.sessionId);
|
|
4417
4916
|
try {
|
|
4418
4917
|
for await (const ev of replayEvents(sessionDir, {
|
|
4419
4918
|
onWarning: (w) => input.onWarning?.(w, entry.sessionId)
|
|
@@ -4818,55 +5317,6 @@ function classifySpawnError(error) {
|
|
|
4818
5317
|
// src/schemas/json-schema.ts
|
|
4819
5318
|
import { z as z11 } from "zod";
|
|
4820
5319
|
|
|
4821
|
-
// src/schemas/manifest.schema.ts
|
|
4822
|
-
import { z as z9 } from "zod";
|
|
4823
|
-
var ProjectSchema = z9.object({
|
|
4824
|
-
name: z9.string().optional(),
|
|
4825
|
-
description: z9.string().optional(),
|
|
4826
|
-
repository_url: z9.string().nullable().optional()
|
|
4827
|
-
});
|
|
4828
|
-
var CapabilitiesSchema = z9.object({
|
|
4829
|
-
enabled: z9.array(z9.string())
|
|
4830
|
-
});
|
|
4831
|
-
var ApprovalConfigSchema = z9.object({
|
|
4832
|
-
required_for: z9.array(z9.string()).optional(),
|
|
4833
|
-
default_risk_level: z9.enum(["low", "medium", "high", "critical"])
|
|
4834
|
-
});
|
|
4835
|
-
var ClaudeCodeAdapterConfigSchema = z9.object({
|
|
4836
|
-
enabled: z9.boolean(),
|
|
4837
|
-
config_path: z9.string().optional()
|
|
4838
|
-
});
|
|
4839
|
-
var AdaptersSchema = z9.object({
|
|
4840
|
-
"claude-code": ClaudeCodeAdapterConfigSchema
|
|
4841
|
-
});
|
|
4842
|
-
var GitConfigSchema = z9.object({
|
|
4843
|
-
events_log: z9.enum(["ignore", "commit"]).default("ignore")
|
|
4844
|
-
});
|
|
4845
|
-
var SOURCE_ROOT_PATTERN = /^(?![~/\\])(?![A-Za-z]:)[^\0\\]+$/;
|
|
4846
|
-
var SourceRootSchema = z9.string().min(1).regex(SOURCE_ROOT_PATTERN, {
|
|
4847
|
-
message: "source_roots entries must be relative paths (no absolute path, '~', '\\', or null byte)"
|
|
4848
|
-
});
|
|
4849
|
-
var ImportConfigSchema = z9.object({
|
|
4850
|
-
source_roots: z9.array(SourceRootSchema).min(1).optional()
|
|
4851
|
-
});
|
|
4852
|
-
var WorkspaceMetaSchema = z9.object({
|
|
4853
|
-
id: WorkspaceIdSchema,
|
|
4854
|
-
name: z9.string().min(1),
|
|
4855
|
-
created_at: IsoTimestampSchema,
|
|
4856
|
-
updated_at: IsoTimestampSchema
|
|
4857
|
-
});
|
|
4858
|
-
var ManifestSchema = z9.object({
|
|
4859
|
-
schema_version: SchemaVersionSchema,
|
|
4860
|
-
basou_version: z9.literal("0.1.0"),
|
|
4861
|
-
workspace: WorkspaceMetaSchema,
|
|
4862
|
-
project: ProjectSchema,
|
|
4863
|
-
capabilities: CapabilitiesSchema,
|
|
4864
|
-
approval: ApprovalConfigSchema,
|
|
4865
|
-
adapters: AdaptersSchema,
|
|
4866
|
-
git: GitConfigSchema,
|
|
4867
|
-
import: ImportConfigSchema.optional()
|
|
4868
|
-
});
|
|
4869
|
-
|
|
4870
5320
|
// src/schemas/session-import.schema.ts
|
|
4871
5321
|
import { z as z10 } from "zod";
|
|
4872
5322
|
var SessionInnerImportSchema = z10.object({
|
|
@@ -4986,28 +5436,29 @@ function serializeJsonSchema(schema) {
|
|
|
4986
5436
|
}
|
|
4987
5437
|
|
|
4988
5438
|
// src/storage/basou-dir.ts
|
|
4989
|
-
import { lstat as
|
|
4990
|
-
import { join as
|
|
5439
|
+
import { lstat as lstat4, mkdir as mkdir4 } from "fs/promises";
|
|
5440
|
+
import { join as join17 } from "path";
|
|
4991
5441
|
function basouPaths(repositoryRoot) {
|
|
4992
|
-
const root =
|
|
4993
|
-
const approvalsBase =
|
|
5442
|
+
const root = join17(repositoryRoot, ".basou");
|
|
5443
|
+
const approvalsBase = join17(root, "approvals");
|
|
4994
5444
|
return {
|
|
4995
5445
|
root,
|
|
4996
|
-
sessions:
|
|
4997
|
-
tasks:
|
|
5446
|
+
sessions: join17(root, "sessions"),
|
|
5447
|
+
tasks: join17(root, "tasks"),
|
|
4998
5448
|
approvals: {
|
|
4999
|
-
pending:
|
|
5000
|
-
resolved:
|
|
5449
|
+
pending: join17(approvalsBase, "pending"),
|
|
5450
|
+
resolved: join17(approvalsBase, "resolved")
|
|
5001
5451
|
},
|
|
5002
|
-
locks:
|
|
5003
|
-
logs:
|
|
5004
|
-
raw:
|
|
5005
|
-
tmp:
|
|
5452
|
+
locks: join17(root, "locks"),
|
|
5453
|
+
logs: join17(root, "logs"),
|
|
5454
|
+
raw: join17(root, "raw"),
|
|
5455
|
+
tmp: join17(root, "tmp"),
|
|
5006
5456
|
files: {
|
|
5007
|
-
manifest:
|
|
5008
|
-
status:
|
|
5009
|
-
handoff:
|
|
5010
|
-
decisions:
|
|
5457
|
+
manifest: join17(root, "manifest.yaml"),
|
|
5458
|
+
status: join17(root, "status.json"),
|
|
5459
|
+
handoff: join17(root, "handoff.md"),
|
|
5460
|
+
decisions: join17(root, "decisions.md"),
|
|
5461
|
+
orientation: join17(root, "orientation.md")
|
|
5011
5462
|
}
|
|
5012
5463
|
};
|
|
5013
5464
|
}
|
|
@@ -5025,9 +5476,9 @@ async function ensureBasouDirectory(repositoryRoot) {
|
|
|
5025
5476
|
const paths = basouPaths(repositoryRoot);
|
|
5026
5477
|
let existing;
|
|
5027
5478
|
try {
|
|
5028
|
-
existing = await
|
|
5479
|
+
existing = await lstat4(paths.root);
|
|
5029
5480
|
} catch (error) {
|
|
5030
|
-
if (!
|
|
5481
|
+
if (!hasErrorCode4(error) || error.code !== "ENOENT") {
|
|
5031
5482
|
throw new Error("Failed to inspect .basou directory", { cause: error });
|
|
5032
5483
|
}
|
|
5033
5484
|
}
|
|
@@ -5050,13 +5501,13 @@ async function mkdirLabeled(target, label) {
|
|
|
5050
5501
|
try {
|
|
5051
5502
|
await mkdir4(target, { recursive: true });
|
|
5052
5503
|
} catch (error) {
|
|
5053
|
-
if (
|
|
5504
|
+
if (hasErrorCode4(error) && (error.code === "ENOTDIR" || error.code === "EEXIST")) {
|
|
5054
5505
|
throw new Error(`${label} exists but is not a directory`, { cause: error });
|
|
5055
5506
|
}
|
|
5056
5507
|
throw new Error(`Failed to create ${label}`, { cause: error });
|
|
5057
5508
|
}
|
|
5058
5509
|
}
|
|
5059
|
-
function
|
|
5510
|
+
function hasErrorCode4(error) {
|
|
5060
5511
|
if (!(error instanceof Error)) return false;
|
|
5061
5512
|
const codeProp = error.code;
|
|
5062
5513
|
return typeof codeProp === "string";
|
|
@@ -5064,28 +5515,30 @@ function hasErrorCode3(error) {
|
|
|
5064
5515
|
|
|
5065
5516
|
// src/storage/gitignore.ts
|
|
5066
5517
|
import { readFile as readFile8, writeFile as writeFile2 } from "fs/promises";
|
|
5067
|
-
import { join as
|
|
5518
|
+
import { join as join18 } from "path";
|
|
5068
5519
|
var MARKER = "# Basou - default ignore";
|
|
5069
|
-
var BASOU_GITIGNORE_BLOCK = "# Basou - default ignore\n.basou/logs/\n.basou/raw/\n.basou/tmp/\n.basou/locks/\n.basou/status.json\n.basou/sessions/*/events.jsonl\n.basou/sessions/*/artifacts/\n.basou/approvals/pending/\n.basou/approvals/resolved/\n\n# Basou - default commit\n# .basou/manifest.yaml\n# .basou/handoff.md\n# .basou/decisions.md\n# .basou/tasks/\n# .basou/sessions/*/session.yaml\n# .basou/sessions/*/transcript.md\n# .basou/sessions/*/changed-files.json\n";
|
|
5070
|
-
|
|
5071
|
-
|
|
5520
|
+
var BASOU_GITIGNORE_BLOCK = "# Basou - default ignore\n.basou/logs/\n.basou/raw/\n.basou/tmp/\n.basou/locks/\n.basou/status.json\n.basou/orientation.md\n.basou/sessions/*/events.jsonl\n.basou/sessions/*/artifacts/\n.basou/approvals/pending/\n.basou/approvals/resolved/\n\n# Basou - default commit\n# .basou/manifest.yaml\n# .basou/handoff.md\n# .basou/decisions.md\n# .basou/tasks/\n# .basou/sessions/*/session.yaml\n# .basou/sessions/*/transcript.md\n# .basou/sessions/*/changed-files.json\n";
|
|
5521
|
+
var BASOU_GITIGNORE_BLOCK_LOCAL_ONLY = "# Basou - default ignore\n# Local-only: basou's trail is never committed (personal/local state,\n# regenerable by re-importing from the agents' own logs). Recommended for\n# monitored repos and any workspace kept out of version control.\n.basou/\n";
|
|
5522
|
+
async function appendBasouGitignore(repositoryRoot, options = {}) {
|
|
5523
|
+
const gitignorePath = join18(repositoryRoot, ".gitignore");
|
|
5072
5524
|
let body;
|
|
5073
5525
|
let existed;
|
|
5074
5526
|
try {
|
|
5075
5527
|
body = await readFile8(gitignorePath, "utf8");
|
|
5076
5528
|
existed = true;
|
|
5077
5529
|
} catch (error) {
|
|
5078
|
-
if (
|
|
5530
|
+
if (hasErrorCode5(error) && error.code === "ENOENT") {
|
|
5079
5531
|
body = "";
|
|
5080
5532
|
existed = false;
|
|
5081
5533
|
} else {
|
|
5082
5534
|
throw new Error("Failed to read .gitignore", { cause: error });
|
|
5083
5535
|
}
|
|
5084
5536
|
}
|
|
5085
|
-
if (existed &&
|
|
5537
|
+
if (existed && hasBasouGitignore(body)) {
|
|
5086
5538
|
return { appended: false };
|
|
5087
5539
|
}
|
|
5088
|
-
const
|
|
5540
|
+
const block = options.localOnly === true ? BASOU_GITIGNORE_BLOCK_LOCAL_ONLY : BASOU_GITIGNORE_BLOCK;
|
|
5541
|
+
const next = composeNextBody(body, block);
|
|
5089
5542
|
try {
|
|
5090
5543
|
await writeFile2(gitignorePath, next, { encoding: "utf8" });
|
|
5091
5544
|
} catch (error) {
|
|
@@ -5093,84 +5546,20 @@ async function appendBasouGitignore(repositoryRoot) {
|
|
|
5093
5546
|
}
|
|
5094
5547
|
return { appended: true };
|
|
5095
5548
|
}
|
|
5096
|
-
function
|
|
5549
|
+
function hasBasouGitignore(body) {
|
|
5097
5550
|
for (const rawLine of body.split("\n")) {
|
|
5098
|
-
|
|
5551
|
+
const line = rawLine.trimEnd();
|
|
5552
|
+
if (line.startsWith(MARKER)) return true;
|
|
5553
|
+
if (line === ".basou/" || line === "/.basou/") return true;
|
|
5099
5554
|
}
|
|
5100
5555
|
return false;
|
|
5101
5556
|
}
|
|
5102
|
-
function composeNextBody(existing) {
|
|
5103
|
-
if (existing.length === 0) return
|
|
5557
|
+
function composeNextBody(existing, block) {
|
|
5558
|
+
if (existing.length === 0) return block;
|
|
5104
5559
|
const normalized = existing.endsWith("\n") ? existing : `${existing}
|
|
5105
5560
|
`;
|
|
5106
5561
|
return `${normalized}
|
|
5107
|
-
${
|
|
5108
|
-
}
|
|
5109
|
-
function hasErrorCode4(error) {
|
|
5110
|
-
if (!(error instanceof Error)) return false;
|
|
5111
|
-
return typeof error.code === "string";
|
|
5112
|
-
}
|
|
5113
|
-
|
|
5114
|
-
// src/storage/manifest.ts
|
|
5115
|
-
import { lstat as lstat4 } from "fs/promises";
|
|
5116
|
-
function createManifest(input) {
|
|
5117
|
-
if (input.workspaceName.length === 0) {
|
|
5118
|
-
throw new Error("Workspace name is empty. Pass --name explicitly.");
|
|
5119
|
-
}
|
|
5120
|
-
const now = (input.now ?? /* @__PURE__ */ new Date()).toISOString();
|
|
5121
|
-
const workspaceId = input.workspaceId ?? prefixedUlid("ws");
|
|
5122
|
-
const project = {
|
|
5123
|
-
...input.projectName !== void 0 ? { name: input.projectName } : {},
|
|
5124
|
-
...input.projectDescription !== void 0 ? { description: input.projectDescription } : {},
|
|
5125
|
-
...input.repositoryUrl !== void 0 ? { repository_url: input.repositoryUrl } : {}
|
|
5126
|
-
};
|
|
5127
|
-
const manifest = {
|
|
5128
|
-
schema_version: "0.1.0",
|
|
5129
|
-
basou_version: "0.1.0",
|
|
5130
|
-
workspace: {
|
|
5131
|
-
id: workspaceId,
|
|
5132
|
-
name: input.workspaceName,
|
|
5133
|
-
created_at: now,
|
|
5134
|
-
updated_at: now
|
|
5135
|
-
},
|
|
5136
|
-
project,
|
|
5137
|
-
capabilities: {
|
|
5138
|
-
enabled: ["core", "claude-code-adapter", "terminal-recording", "git-capability", "approval"]
|
|
5139
|
-
},
|
|
5140
|
-
approval: {
|
|
5141
|
-
required_for: ["destructive_command", "external_send"],
|
|
5142
|
-
default_risk_level: "medium"
|
|
5143
|
-
},
|
|
5144
|
-
adapters: {
|
|
5145
|
-
"claude-code": { enabled: true }
|
|
5146
|
-
},
|
|
5147
|
-
git: { events_log: "ignore" },
|
|
5148
|
-
...input.sourceRoots !== void 0 && input.sourceRoots.length > 0 ? { import: { source_roots: input.sourceRoots } } : {}
|
|
5149
|
-
};
|
|
5150
|
-
return ManifestSchema.parse(manifest);
|
|
5151
|
-
}
|
|
5152
|
-
async function writeManifest(paths, manifest, options) {
|
|
5153
|
-
const force = options?.force === true;
|
|
5154
|
-
const validated = ManifestSchema.parse(manifest);
|
|
5155
|
-
if (!force) {
|
|
5156
|
-
let existed = false;
|
|
5157
|
-
try {
|
|
5158
|
-
await lstat4(paths.files.manifest);
|
|
5159
|
-
existed = true;
|
|
5160
|
-
} catch (error) {
|
|
5161
|
-
if (!hasErrorCode5(error) || error.code !== "ENOENT") {
|
|
5162
|
-
throw new Error("Failed to inspect existing manifest", { cause: error });
|
|
5163
|
-
}
|
|
5164
|
-
}
|
|
5165
|
-
if (existed) {
|
|
5166
|
-
throw new Error("Already initialized. Use --force to overwrite.");
|
|
5167
|
-
}
|
|
5168
|
-
}
|
|
5169
|
-
await writeYamlFile(paths.files.manifest, validated);
|
|
5170
|
-
}
|
|
5171
|
-
async function readManifest(paths) {
|
|
5172
|
-
const raw = await readYamlFile(paths.files.manifest);
|
|
5173
|
-
return ManifestSchema.parse(raw);
|
|
5562
|
+
${block}`;
|
|
5174
5563
|
}
|
|
5175
5564
|
function hasErrorCode5(error) {
|
|
5176
5565
|
if (!(error instanceof Error)) return false;
|
|
@@ -5283,7 +5672,7 @@ function hasErrorCode6(error) {
|
|
|
5283
5672
|
// src/storage/session-import.ts
|
|
5284
5673
|
import { mkdir as mkdir5, readFile as readFile10, rm as rm2 } from "fs/promises";
|
|
5285
5674
|
import { homedir as homedir2 } from "os";
|
|
5286
|
-
import { join as
|
|
5675
|
+
import { join as join19 } from "path";
|
|
5287
5676
|
async function importSessionFromJson(paths, manifest, payload, options) {
|
|
5288
5677
|
if (options.taskIdOverride !== void 0 && !TaskIdSchema.safeParse(options.taskIdOverride).success) {
|
|
5289
5678
|
throw new Error(`Invalid task_id: ${options.taskIdOverride}`);
|
|
@@ -5308,7 +5697,7 @@ async function importSessionFromJson(paths, manifest, payload, options) {
|
|
|
5308
5697
|
pathSanitizeReport
|
|
5309
5698
|
};
|
|
5310
5699
|
}
|
|
5311
|
-
const sessionDir =
|
|
5700
|
+
const sessionDir = join19(paths.sessions, newSessionId);
|
|
5312
5701
|
try {
|
|
5313
5702
|
await mkdir5(sessionDir, { recursive: true });
|
|
5314
5703
|
} catch (error) {
|
|
@@ -5322,7 +5711,7 @@ async function importSessionFromJson(paths, manifest, payload, options) {
|
|
|
5322
5711
|
throw error;
|
|
5323
5712
|
}
|
|
5324
5713
|
try {
|
|
5325
|
-
const sessionYamlPath =
|
|
5714
|
+
const sessionYamlPath = join19(sessionDir, "session.yaml");
|
|
5326
5715
|
await linkYamlFile(sessionYamlPath, withIntegrity(sessionRecord, chainResult));
|
|
5327
5716
|
} catch (error) {
|
|
5328
5717
|
await rm2(sessionDir, { recursive: true, force: true }).catch(() => void 0);
|
|
@@ -5490,7 +5879,7 @@ function reuseDerivedIds(priorDerived, freshDerived, sessionId) {
|
|
|
5490
5879
|
async function reimportPreservingId(paths, manifest, priorSessionId, freshPayload, options = {}) {
|
|
5491
5880
|
const sessionId = priorSessionId;
|
|
5492
5881
|
const importSource = freshPayload.session.source.kind;
|
|
5493
|
-
const sessionDir =
|
|
5882
|
+
const sessionDir = join19(paths.sessions, priorSessionId);
|
|
5494
5883
|
const lock = options.dryRun === true ? null : await acquireLock(paths, "session", priorSessionId);
|
|
5495
5884
|
try {
|
|
5496
5885
|
const priorVerdict = await verifyEventsChain(paths, priorSessionId);
|
|
@@ -5532,7 +5921,7 @@ async function reimportPreservingId(paths, manifest, priorSessionId, freshPayloa
|
|
|
5532
5921
|
};
|
|
5533
5922
|
const updatedRecord = { schema_version: "0.1.0", session: preservedInner };
|
|
5534
5923
|
if (options.dryRun !== true) {
|
|
5535
|
-
const eventsPath =
|
|
5924
|
+
const eventsPath = join19(sessionDir, "events.jsonl");
|
|
5536
5925
|
let priorEventsRaw = null;
|
|
5537
5926
|
try {
|
|
5538
5927
|
priorEventsRaw = await readFile10(eventsPath);
|
|
@@ -5544,7 +5933,7 @@ async function reimportPreservingId(paths, manifest, priorSessionId, freshPayloa
|
|
|
5544
5933
|
const chainResult = await writeEventsBulk(sessionDir, mergedEvents, { chain: true });
|
|
5545
5934
|
try {
|
|
5546
5935
|
await overwriteYamlFile(
|
|
5547
|
-
|
|
5936
|
+
join19(sessionDir, "session.yaml"),
|
|
5548
5937
|
withIntegrity(updatedRecord, chainResult)
|
|
5549
5938
|
);
|
|
5550
5939
|
} catch (error) {
|
|
@@ -5568,7 +5957,7 @@ async function reimportPreservingId(paths, manifest, priorSessionId, freshPayloa
|
|
|
5568
5957
|
}
|
|
5569
5958
|
}
|
|
5570
5959
|
async function rechainSessionInPlace(paths, sessionId, options = {}) {
|
|
5571
|
-
const sessionDir =
|
|
5960
|
+
const sessionDir = join19(paths.sessions, sessionId);
|
|
5572
5961
|
let lock;
|
|
5573
5962
|
try {
|
|
5574
5963
|
lock = await acquireLock(paths, "session", sessionId);
|
|
@@ -5601,7 +5990,7 @@ async function rechainSessionInPlace(paths, sessionId, options = {}) {
|
|
|
5601
5990
|
if (verdict.status !== "unchained") {
|
|
5602
5991
|
return { status: "skipped", reason: "tampered" };
|
|
5603
5992
|
}
|
|
5604
|
-
const eventsPath =
|
|
5993
|
+
const eventsPath = join19(sessionDir, "events.jsonl");
|
|
5605
5994
|
let priorRaw;
|
|
5606
5995
|
try {
|
|
5607
5996
|
priorRaw = await readFile10(eventsPath);
|
|
@@ -5649,7 +6038,7 @@ async function rechainSessionInPlace(paths, sessionId, options = {}) {
|
|
|
5649
6038
|
}
|
|
5650
6039
|
try {
|
|
5651
6040
|
await overwriteYamlFile(
|
|
5652
|
-
|
|
6041
|
+
join19(sessionDir, "session.yaml"),
|
|
5653
6042
|
withIntegrity(record, { headHash: chainResult.headHash, count: chainResult.count })
|
|
5654
6043
|
);
|
|
5655
6044
|
} catch (error) {
|
|
@@ -5764,9 +6153,11 @@ export {
|
|
|
5764
6153
|
reimportPreservingId,
|
|
5765
6154
|
renderDecisions,
|
|
5766
6155
|
renderHandoff,
|
|
6156
|
+
renderOrientation,
|
|
5767
6157
|
renderReport,
|
|
5768
6158
|
renderWithMarkers,
|
|
5769
6159
|
replayEvents,
|
|
6160
|
+
resolveBasouRepositoryRoot,
|
|
5770
6161
|
resolveClaudeCodeCommand,
|
|
5771
6162
|
resolveRepositoryRoot,
|
|
5772
6163
|
resolveSessionId,
|
|
@@ -5778,6 +6169,7 @@ export {
|
|
|
5778
6169
|
serializeJsonSchema,
|
|
5779
6170
|
sessionWorkStatsFromEvents,
|
|
5780
6171
|
summarizeAdapterOutput,
|
|
6172
|
+
summarizeOrientation,
|
|
5781
6173
|
tryRemoteUrl,
|
|
5782
6174
|
ulid,
|
|
5783
6175
|
updateTaskStatusWithEvent,
|