@basou/core 0.45.0 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +163 -16
- package/dist/index.js +305 -184
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -328,6 +328,13 @@ function toolUsesOf2(record) {
|
|
|
328
328
|
}
|
|
329
329
|
return result;
|
|
330
330
|
}
|
|
331
|
+
function transcriptStartedAt(records) {
|
|
332
|
+
for (const record of records) {
|
|
333
|
+
const timestamp = readString2(record.timestamp);
|
|
334
|
+
if (timestamp !== void 0 && !Number.isNaN(Date.parse(timestamp))) return timestamp;
|
|
335
|
+
}
|
|
336
|
+
return void 0;
|
|
337
|
+
}
|
|
331
338
|
|
|
332
339
|
// src/ids/ulid.ts
|
|
333
340
|
import { isValid as isValidUlid, monotonicFactory } from "ulid";
|
|
@@ -2920,32 +2927,48 @@ ${trimmedBody}`;
|
|
|
2920
2927
|
}
|
|
2921
2928
|
var TASK_FILENAME_RE = /^(.+)\.md$/;
|
|
2922
2929
|
async function enumerateTaskIds(paths) {
|
|
2930
|
+
let indexed = null;
|
|
2923
2931
|
try {
|
|
2924
|
-
|
|
2925
|
-
return index.tasks.map((t) => t.id);
|
|
2932
|
+
indexed = (await readTaskIndex(paths)).tasks.map((t) => t.id);
|
|
2926
2933
|
} catch {
|
|
2927
2934
|
}
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2935
|
+
let onDisk;
|
|
2936
|
+
try {
|
|
2937
|
+
onDisk = await enumerateTaskIdsFromDisk(paths);
|
|
2938
|
+
} catch (error) {
|
|
2939
|
+
if (indexed !== null) return indexed;
|
|
2940
|
+
throw error;
|
|
2941
|
+
}
|
|
2942
|
+
if (indexed !== null && sameTaskIdSet(indexed, onDisk)) return indexed;
|
|
2943
|
+
if (onDisk.length === 0) {
|
|
2944
|
+
return onDisk;
|
|
2931
2945
|
}
|
|
2932
2946
|
const entries = [];
|
|
2933
|
-
|
|
2947
|
+
let anyUnreadable = false;
|
|
2948
|
+
for (const id of onDisk) {
|
|
2934
2949
|
try {
|
|
2935
2950
|
const doc = await readTaskFile(paths, id);
|
|
2936
2951
|
entries.push(buildTaskIndexEntry(doc.task.task));
|
|
2937
2952
|
} catch {
|
|
2953
|
+
anyUnreadable = true;
|
|
2938
2954
|
}
|
|
2939
2955
|
}
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2956
|
+
if (!anyUnreadable) {
|
|
2957
|
+
await rebuildTaskIndex(paths, entries).catch(() => {
|
|
2958
|
+
console.warn("Failed to rebuild tasks/index.json; subsequent reads will retry");
|
|
2959
|
+
});
|
|
2960
|
+
}
|
|
2961
|
+
return onDisk;
|
|
2962
|
+
}
|
|
2963
|
+
function sameTaskIdSet(a, b) {
|
|
2964
|
+
if (a.length !== b.length) return false;
|
|
2965
|
+
const seen = new Set(a);
|
|
2966
|
+
return b.every((id) => seen.has(id));
|
|
2944
2967
|
}
|
|
2945
2968
|
async function enumerateTaskIdsFromDisk(paths) {
|
|
2946
2969
|
let entries;
|
|
2947
2970
|
try {
|
|
2948
|
-
entries = (await readdir3(paths.tasks, { withFileTypes: true })).filter((d) => d.
|
|
2971
|
+
entries = (await readdir3(paths.tasks, { withFileTypes: true })).filter((d) => !d.isDirectory()).map((d) => d.name);
|
|
2949
2972
|
} catch (error) {
|
|
2950
2973
|
if (findErrorCode(error, "ENOENT")) return [];
|
|
2951
2974
|
throw new Error("Failed to enumerate tasks", { cause: error });
|
|
@@ -4603,6 +4626,7 @@ var EN = {
|
|
|
4603
4626
|
noTasksRecorded: "(no tasks recorded)",
|
|
4604
4627
|
noTasksInFlight: "(tasks on record, none in flight)",
|
|
4605
4628
|
tasksUnreadable: "(tasks on record, some unreadable -- in flight unknown)",
|
|
4629
|
+
tasksUnreadableAlongside: (n) => n === 1 ? "(and 1 task file could not be read -- what it holds is unknown)" : `(and ${n} task files could not be read -- what they hold is unknown)`,
|
|
4606
4630
|
pendingApprovalsHeading: (n) => `### Pending approvals (${n})`,
|
|
4607
4631
|
suspectSessionsHeading: (n) => `### Suspect sessions (${n})`,
|
|
4608
4632
|
openTracksHeading: (n) => `### Open tracks (shown until closed) (${n})`,
|
|
@@ -4705,6 +4729,7 @@ var JA = {
|
|
|
4705
4729
|
noTasksRecorded: "(task \u304C 1 \u4EF6\u3082\u8A18\u9332\u3055\u308C\u3066\u3044\u307E\u305B\u3093)",
|
|
4706
4730
|
noTasksInFlight: "(\u8A18\u9332\u6E08\u307F\u306E task \u306F\u3042\u308A\u307E\u3059\u304C\u3001\u9032\u884C\u4E2D\u306F\u3042\u308A\u307E\u305B\u3093)",
|
|
4707
4731
|
tasksUnreadable: "(\u8A18\u9332\u6E08\u307F\u306E task \u306B\u8AAD\u3081\u306A\u3044\u3082\u306E\u304C\u3042\u308A\u3001\u9032\u884C\u4E2D\u304B\u306F\u4E0D\u660E\u3067\u3059)",
|
|
4732
|
+
tasksUnreadableAlongside: (n) => `(\u307B\u304B\u306B\u8AAD\u3081\u306A\u3044 task \u30D5\u30A1\u30A4\u30EB\u304C ${n} \u4EF6\u3042\u308A\u3001\u5185\u5BB9\u306F\u4E0D\u660E\u3067\u3059)`,
|
|
4708
4733
|
pendingApprovalsHeading: (n) => `### \u627F\u8A8D\u5F85\u3061 (${n})`,
|
|
4709
4734
|
suspectSessionsHeading: (n) => `### \u8981\u6CE8\u610F session (${n})`,
|
|
4710
4735
|
openTracksHeading: (n) => `### \u672A\u5B8C\u30C8\u30E9\u30C3\u30AF (close \u307E\u3067\u7D99\u7D9A\u8868\u793A) (${n})`,
|
|
@@ -6045,7 +6070,7 @@ function parseBuildStamp(raw) {
|
|
|
6045
6070
|
}
|
|
6046
6071
|
}
|
|
6047
6072
|
var BASOU_CORE_BUILD = parseBuildStamp(
|
|
6048
|
-
true ? '{"version":"0.
|
|
6073
|
+
true ? '{"version":"0.46.0","commit":"dbc9c57","committedAt":"2026-09-19T15:43:35+09:00"}' : void 0
|
|
6049
6074
|
);
|
|
6050
6075
|
|
|
6051
6076
|
// src/lib/duration.ts
|
|
@@ -6624,6 +6649,9 @@ function formatOrientationBody(summary, opts) {
|
|
|
6624
6649
|
const linkedSuffix = t2.linkedSessions > 1 ? ` \u2014 linked_sessions: ${t2.linkedSessions}` : "";
|
|
6625
6650
|
lines.push(`- ${oneLine(t2.title)} (${t2.status}) [${shortId(t2.id)}]${linkedSuffix}`);
|
|
6626
6651
|
}
|
|
6652
|
+
if (summary.unreadableTaskCount > 0) {
|
|
6653
|
+
lines.push(`- ${t.orientation.tasksUnreadableAlongside(summary.unreadableTaskCount)}`);
|
|
6654
|
+
}
|
|
6627
6655
|
}
|
|
6628
6656
|
lines.push("");
|
|
6629
6657
|
lines.push(t.orientation.pendingApprovalsHeading(summary.pendingApprovals.length));
|
|
@@ -7560,6 +7588,253 @@ function planWorkspaceView(facts, existing = [], rosterNames = []) {
|
|
|
7560
7588
|
};
|
|
7561
7589
|
}
|
|
7562
7590
|
|
|
7591
|
+
// src/protocol/protocol-stamp.ts
|
|
7592
|
+
import { createHash as createHash3 } from "crypto";
|
|
7593
|
+
|
|
7594
|
+
// src/storage/markdown-store.ts
|
|
7595
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
7596
|
+
var GENERATED_START = "<!-- BASOU:GENERATED:START -->";
|
|
7597
|
+
var GENERATED_END = "<!-- BASOU:GENERATED:END -->";
|
|
7598
|
+
var PROTOCOL_START = "<!-- BASOU:PROTOCOLS:START -->";
|
|
7599
|
+
var PROTOCOL_END = "<!-- BASOU:PROTOCOLS:END -->";
|
|
7600
|
+
var ORIENTATION_START = "<!-- BASOU:ORIENTATION:START -->";
|
|
7601
|
+
var ORIENTATION_END = "<!-- BASOU:ORIENTATION:END -->";
|
|
7602
|
+
var DEFAULT_MARKERS = { start: GENERATED_START, end: GENERATED_END };
|
|
7603
|
+
async function readMarkdownFile(filePath) {
|
|
7604
|
+
try {
|
|
7605
|
+
return await readFile9(filePath, "utf8");
|
|
7606
|
+
} catch (error) {
|
|
7607
|
+
if (hasErrorCode4(error) && error.code === "ENOENT") return null;
|
|
7608
|
+
throw new Error("Failed to read markdown file", { cause: error });
|
|
7609
|
+
}
|
|
7610
|
+
}
|
|
7611
|
+
async function writeMarkdownFile(filePath, body) {
|
|
7612
|
+
try {
|
|
7613
|
+
await atomicReplace(filePath, body);
|
|
7614
|
+
} catch (error) {
|
|
7615
|
+
throw new Error("Failed to write markdown file", { cause: error });
|
|
7616
|
+
}
|
|
7617
|
+
}
|
|
7618
|
+
function parseMarkers(content, markers = DEFAULT_MARKERS) {
|
|
7619
|
+
const bom = content.charCodeAt(0) === 65279 ? "\uFEFF" : "";
|
|
7620
|
+
const body = bom === "" ? content : content.slice(1);
|
|
7621
|
+
const lines = body.split(/\r?\n/);
|
|
7622
|
+
const startLines = [];
|
|
7623
|
+
const endLines = [];
|
|
7624
|
+
for (let i = 0; i < lines.length; i++) {
|
|
7625
|
+
if (lines[i] === markers.start) startLines.push(i);
|
|
7626
|
+
else if (lines[i] === markers.end) endLines.push(i);
|
|
7627
|
+
}
|
|
7628
|
+
if (startLines.length === 0 && endLines.length === 0) return { kind: "no_markers" };
|
|
7629
|
+
if (startLines.length === 0) return { kind: "missing_start" };
|
|
7630
|
+
if (endLines.length === 0) return { kind: "missing_end" };
|
|
7631
|
+
if (startLines.length >= 2 || endLines.length >= 2) return { kind: "multiple_pairs" };
|
|
7632
|
+
const startLineIdx = startLines[0];
|
|
7633
|
+
const endLineIdx = endLines[0];
|
|
7634
|
+
if (endLineIdx < startLineIdx) return { kind: "wrong_order" };
|
|
7635
|
+
const startOffset = lineStartOffset(body, startLineIdx);
|
|
7636
|
+
const endLineStart = lineStartOffset(body, endLineIdx);
|
|
7637
|
+
const startLineEnd = startOffset + markers.start.length;
|
|
7638
|
+
const endLineEnd = endLineStart + markers.end.length;
|
|
7639
|
+
const before = bom + body.slice(0, startOffset);
|
|
7640
|
+
const afterStartNewline = skipOneNewline(body, startLineEnd);
|
|
7641
|
+
const beforeEndNewline = trimOneNewline(body, endLineStart);
|
|
7642
|
+
const generated = body.slice(afterStartNewline, beforeEndNewline);
|
|
7643
|
+
const after = body.slice(endLineEnd);
|
|
7644
|
+
return { kind: "ok", before, generated, after };
|
|
7645
|
+
}
|
|
7646
|
+
function defuseMarkerLines(body, markers) {
|
|
7647
|
+
const isMarker = (line) => {
|
|
7648
|
+
const bare = line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
7649
|
+
return bare === markers.start || bare === markers.end;
|
|
7650
|
+
};
|
|
7651
|
+
const lines = body.split("\n");
|
|
7652
|
+
if (!lines.some(isMarker)) return body;
|
|
7653
|
+
return lines.map((line) => isMarker(line) ? ` ${line}` : line).join("\n");
|
|
7654
|
+
}
|
|
7655
|
+
function renderWithMarkers(existing, generated, fileLabel, markers = DEFAULT_MARKERS) {
|
|
7656
|
+
const normalized = defuseMarkerLines(
|
|
7657
|
+
generated.endsWith("\n") ? generated : `${generated}
|
|
7658
|
+
`,
|
|
7659
|
+
markers
|
|
7660
|
+
);
|
|
7661
|
+
if (existing === null) {
|
|
7662
|
+
return `${markers.start}
|
|
7663
|
+
${normalized}${markers.end}
|
|
7664
|
+
`;
|
|
7665
|
+
}
|
|
7666
|
+
const section = parseMarkers(existing, markers);
|
|
7667
|
+
switch (section.kind) {
|
|
7668
|
+
case "ok":
|
|
7669
|
+
return `${section.before}${markers.start}
|
|
7670
|
+
${normalized}${markers.end}${section.after}`;
|
|
7671
|
+
case "no_markers":
|
|
7672
|
+
throw new Error(`Markers missing in ${fileLabel}`);
|
|
7673
|
+
case "missing_start":
|
|
7674
|
+
case "missing_end":
|
|
7675
|
+
case "multiple_pairs":
|
|
7676
|
+
case "wrong_order":
|
|
7677
|
+
throw new Error(`Markers mismatched in ${fileLabel}`);
|
|
7678
|
+
}
|
|
7679
|
+
}
|
|
7680
|
+
function seedMarkers(existing, generated, fileLabel, markers = DEFAULT_MARKERS) {
|
|
7681
|
+
const normalized = generated.endsWith("\n") ? generated : `${generated}
|
|
7682
|
+
`;
|
|
7683
|
+
if (existing === null) {
|
|
7684
|
+
return `${markers.start}
|
|
7685
|
+
${normalized}${markers.end}
|
|
7686
|
+
`;
|
|
7687
|
+
}
|
|
7688
|
+
const section = parseMarkers(existing, markers);
|
|
7689
|
+
switch (section.kind) {
|
|
7690
|
+
case "ok":
|
|
7691
|
+
return renderWithMarkers(existing, generated, fileLabel, markers);
|
|
7692
|
+
case "no_markers": {
|
|
7693
|
+
const bom = existing.charCodeAt(0) === 65279 ? "\uFEFF" : "";
|
|
7694
|
+
const body = bom === "" ? existing : existing.slice(1);
|
|
7695
|
+
return `${bom}${markers.start}
|
|
7696
|
+
${normalized}${markers.end}
|
|
7697
|
+
|
|
7698
|
+
${body}`;
|
|
7699
|
+
}
|
|
7700
|
+
case "missing_start":
|
|
7701
|
+
case "missing_end":
|
|
7702
|
+
case "multiple_pairs":
|
|
7703
|
+
case "wrong_order":
|
|
7704
|
+
throw new Error(`Markers mismatched in ${fileLabel}`);
|
|
7705
|
+
}
|
|
7706
|
+
}
|
|
7707
|
+
function removeMarkerSection(existing, fileLabel, markers = DEFAULT_MARKERS) {
|
|
7708
|
+
const section = parseMarkers(existing, markers);
|
|
7709
|
+
switch (section.kind) {
|
|
7710
|
+
case "no_markers":
|
|
7711
|
+
return existing;
|
|
7712
|
+
case "ok": {
|
|
7713
|
+
const after = section.after.replace(/^\r?\n/, "");
|
|
7714
|
+
return section.before + after;
|
|
7715
|
+
}
|
|
7716
|
+
case "missing_start":
|
|
7717
|
+
case "missing_end":
|
|
7718
|
+
case "multiple_pairs":
|
|
7719
|
+
case "wrong_order":
|
|
7720
|
+
throw new Error(`Markers mismatched in ${fileLabel}`);
|
|
7721
|
+
}
|
|
7722
|
+
}
|
|
7723
|
+
function lineStartOffset(content, lineIdx) {
|
|
7724
|
+
if (lineIdx === 0) return 0;
|
|
7725
|
+
let offset = 0;
|
|
7726
|
+
let line = 0;
|
|
7727
|
+
while (offset < content.length && line < lineIdx) {
|
|
7728
|
+
const ch = content[offset];
|
|
7729
|
+
if (ch === "\n") {
|
|
7730
|
+
line += 1;
|
|
7731
|
+
offset += 1;
|
|
7732
|
+
} else if (ch === "\r") {
|
|
7733
|
+
offset += 1;
|
|
7734
|
+
if (content[offset] === "\n") offset += 1;
|
|
7735
|
+
line += 1;
|
|
7736
|
+
} else {
|
|
7737
|
+
offset += 1;
|
|
7738
|
+
}
|
|
7739
|
+
}
|
|
7740
|
+
return offset;
|
|
7741
|
+
}
|
|
7742
|
+
function skipOneNewline(content, offset) {
|
|
7743
|
+
if (content[offset] === "\r" && content[offset + 1] === "\n") return offset + 2;
|
|
7744
|
+
if (content[offset] === "\n") return offset + 1;
|
|
7745
|
+
return offset;
|
|
7746
|
+
}
|
|
7747
|
+
function trimOneNewline(content, offset) {
|
|
7748
|
+
if (offset >= 2 && content[offset - 2] === "\r" && content[offset - 1] === "\n")
|
|
7749
|
+
return offset - 2;
|
|
7750
|
+
if (offset >= 1 && content[offset - 1] === "\n") return offset - 1;
|
|
7751
|
+
return offset;
|
|
7752
|
+
}
|
|
7753
|
+
function hasErrorCode4(error) {
|
|
7754
|
+
if (!(error instanceof Error)) return false;
|
|
7755
|
+
const codeProp = error.code;
|
|
7756
|
+
return typeof codeProp === "string";
|
|
7757
|
+
}
|
|
7758
|
+
|
|
7759
|
+
// src/protocol/protocol-stamp.ts
|
|
7760
|
+
var STAMP_PREFIX = "<!-- basou:protocols v1 ";
|
|
7761
|
+
var CONTENT_HASH_LENGTH = 16;
|
|
7762
|
+
var PROTOCOL_UPDATE_TOKEN_PREFIX = "basou:protocol-updated";
|
|
7763
|
+
function protocolBlockHash(sections) {
|
|
7764
|
+
return createHash3("sha256").update(sections, "utf8").digest("hex").slice(0, CONTENT_HASH_LENGTH);
|
|
7765
|
+
}
|
|
7766
|
+
function protocolUpdateToken(contentHash) {
|
|
7767
|
+
return `${PROTOCOL_UPDATE_TOKEN_PREFIX}:${contentHash}`;
|
|
7768
|
+
}
|
|
7769
|
+
function renderProtocolStamp(stamp) {
|
|
7770
|
+
return `${STAMP_PREFIX}changed=${stamp.changedAt} content=${stamp.contentHash} -->`;
|
|
7771
|
+
}
|
|
7772
|
+
function parseProtocolStamp(blockBody) {
|
|
7773
|
+
const line = blockBody.split(/\r?\n/).find((l) => l.startsWith(STAMP_PREFIX));
|
|
7774
|
+
if (line === void 0) return null;
|
|
7775
|
+
const inner = line.slice(STAMP_PREFIX.length).replace(/-->\s*$/, "").trim();
|
|
7776
|
+
let changedAt;
|
|
7777
|
+
let contentHash;
|
|
7778
|
+
for (const field of inner.split(/\s+/)) {
|
|
7779
|
+
const eq = field.indexOf("=");
|
|
7780
|
+
if (eq <= 0) continue;
|
|
7781
|
+
const name = field.slice(0, eq);
|
|
7782
|
+
const value = field.slice(eq + 1);
|
|
7783
|
+
if (name === "changed") changedAt = value;
|
|
7784
|
+
else if (name === "content") contentHash = value;
|
|
7785
|
+
}
|
|
7786
|
+
if (changedAt === void 0 || contentHash === void 0) return null;
|
|
7787
|
+
if (changedAt.length === 0 || contentHash.length === 0) return null;
|
|
7788
|
+
if (Number.isNaN(Date.parse(changedAt))) return null;
|
|
7789
|
+
return { changedAt, contentHash };
|
|
7790
|
+
}
|
|
7791
|
+
function carryForwardProtocolStamp(input) {
|
|
7792
|
+
const contentHash = protocolBlockHash(input.sections);
|
|
7793
|
+
if (input.previous !== null && input.previous.contentHash === contentHash) {
|
|
7794
|
+
return input.previous;
|
|
7795
|
+
}
|
|
7796
|
+
return { changedAt: forwardOf(input.now, input.previous), contentHash };
|
|
7797
|
+
}
|
|
7798
|
+
function forwardOf(now, previous) {
|
|
7799
|
+
if (previous === null) return now;
|
|
7800
|
+
const nowMs = Date.parse(now);
|
|
7801
|
+
const beforeMs = Date.parse(previous.changedAt);
|
|
7802
|
+
if (Number.isNaN(nowMs) || Number.isNaN(beforeMs) || nowMs > beforeMs) return now;
|
|
7803
|
+
return new Date(beforeMs + 1).toISOString();
|
|
7804
|
+
}
|
|
7805
|
+
function protocolSectionsFrom(blockBody) {
|
|
7806
|
+
const lines = blockBody.split(/\r?\n/);
|
|
7807
|
+
const at = lines.findIndex((l) => l.startsWith(STAMP_PREFIX));
|
|
7808
|
+
if (at === -1) return null;
|
|
7809
|
+
return sectionsAfter(lines.slice(at + 1));
|
|
7810
|
+
}
|
|
7811
|
+
function unstampedProtocolSectionsFrom(blockBody) {
|
|
7812
|
+
const lines = blockBody.split(/\r?\n/);
|
|
7813
|
+
while (lines.length > 0) {
|
|
7814
|
+
const first = (lines[0] ?? "").trim();
|
|
7815
|
+
if (first.length === 0 || first.startsWith("<!--")) lines.shift();
|
|
7816
|
+
else break;
|
|
7817
|
+
}
|
|
7818
|
+
return sectionsAfter(lines);
|
|
7819
|
+
}
|
|
7820
|
+
function sectionsAfter(lines) {
|
|
7821
|
+
const closing = lines.indexOf(PROTOCOL_END);
|
|
7822
|
+
const body = closing === -1 ? [...lines] : lines.slice(0, closing);
|
|
7823
|
+
while (body.length > 0 && (body[0] ?? "").trim().length === 0) body.shift();
|
|
7824
|
+
return body.join("\n").replace(/\s+$/, "");
|
|
7825
|
+
}
|
|
7826
|
+
function isProtocolUpdateDue(input) {
|
|
7827
|
+
const startedMs = Date.parse(input.sessionStartedAt);
|
|
7828
|
+
if (Number.isNaN(startedMs)) return false;
|
|
7829
|
+
return Date.parse(input.stamp.changedAt) > startedMs;
|
|
7830
|
+
}
|
|
7831
|
+
function renderProtocolUpdate(sections, stamp) {
|
|
7832
|
+
return [
|
|
7833
|
+
`The standing protocols changed after this session started (${protocolUpdateToken(stamp.contentHash)}). What follows is the COMPLETE current set and SUPERSEDES the copy read at session start \u2014 follow it for the rest of the session, and treat anything read earlier but absent below as withdrawn.`,
|
|
7834
|
+
sections
|
|
7835
|
+
].join("\n\n");
|
|
7836
|
+
}
|
|
7837
|
+
|
|
7563
7838
|
// src/report/report-renderer.ts
|
|
7564
7839
|
import { join as join18 } from "path";
|
|
7565
7840
|
|
|
@@ -9381,7 +9656,7 @@ async function ensureBasouDirectory(repositoryRoot) {
|
|
|
9381
9656
|
try {
|
|
9382
9657
|
existing = await lstat4(paths.root);
|
|
9383
9658
|
} catch (error) {
|
|
9384
|
-
if (!
|
|
9659
|
+
if (!hasErrorCode5(error) || error.code !== "ENOENT") {
|
|
9385
9660
|
throw new Error("Failed to inspect .basou directory", { cause: error });
|
|
9386
9661
|
}
|
|
9387
9662
|
}
|
|
@@ -9404,20 +9679,20 @@ async function mkdirLabeled(target, label) {
|
|
|
9404
9679
|
try {
|
|
9405
9680
|
await mkdir4(target, { recursive: true });
|
|
9406
9681
|
} catch (error) {
|
|
9407
|
-
if (
|
|
9682
|
+
if (hasErrorCode5(error) && (error.code === "ENOTDIR" || error.code === "EEXIST")) {
|
|
9408
9683
|
throw new Error(`${label} exists but is not a directory`, { cause: error });
|
|
9409
9684
|
}
|
|
9410
9685
|
throw new Error(`Failed to create ${label}`, { cause: error });
|
|
9411
9686
|
}
|
|
9412
9687
|
}
|
|
9413
|
-
function
|
|
9688
|
+
function hasErrorCode5(error) {
|
|
9414
9689
|
if (!(error instanceof Error)) return false;
|
|
9415
9690
|
const codeProp = error.code;
|
|
9416
9691
|
return typeof codeProp === "string";
|
|
9417
9692
|
}
|
|
9418
9693
|
|
|
9419
9694
|
// src/storage/gitignore.ts
|
|
9420
|
-
import { readFile as
|
|
9695
|
+
import { readFile as readFile10, writeFile as writeFile2 } from "fs/promises";
|
|
9421
9696
|
import { join as join21 } from "path";
|
|
9422
9697
|
var MARKER = "# Basou - default ignore";
|
|
9423
9698
|
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";
|
|
@@ -9427,10 +9702,10 @@ async function appendBasouGitignore(repositoryRoot, options = {}) {
|
|
|
9427
9702
|
let body;
|
|
9428
9703
|
let existed;
|
|
9429
9704
|
try {
|
|
9430
|
-
body = await
|
|
9705
|
+
body = await readFile10(gitignorePath, "utf8");
|
|
9431
9706
|
existed = true;
|
|
9432
9707
|
} catch (error) {
|
|
9433
|
-
if (
|
|
9708
|
+
if (hasErrorCode6(error) && error.code === "ENOENT") {
|
|
9434
9709
|
body = "";
|
|
9435
9710
|
existed = false;
|
|
9436
9711
|
} else {
|
|
@@ -9464,174 +9739,9 @@ function composeNextBody(existing, block) {
|
|
|
9464
9739
|
return `${normalized}
|
|
9465
9740
|
${block}`;
|
|
9466
9741
|
}
|
|
9467
|
-
function hasErrorCode5(error) {
|
|
9468
|
-
if (!(error instanceof Error)) return false;
|
|
9469
|
-
return typeof error.code === "string";
|
|
9470
|
-
}
|
|
9471
|
-
|
|
9472
|
-
// src/storage/markdown-store.ts
|
|
9473
|
-
import { readFile as readFile10 } from "fs/promises";
|
|
9474
|
-
var GENERATED_START = "<!-- BASOU:GENERATED:START -->";
|
|
9475
|
-
var GENERATED_END = "<!-- BASOU:GENERATED:END -->";
|
|
9476
|
-
var PROTOCOL_START = "<!-- BASOU:PROTOCOLS:START -->";
|
|
9477
|
-
var PROTOCOL_END = "<!-- BASOU:PROTOCOLS:END -->";
|
|
9478
|
-
var ORIENTATION_START = "<!-- BASOU:ORIENTATION:START -->";
|
|
9479
|
-
var ORIENTATION_END = "<!-- BASOU:ORIENTATION:END -->";
|
|
9480
|
-
var DEFAULT_MARKERS = { start: GENERATED_START, end: GENERATED_END };
|
|
9481
|
-
async function readMarkdownFile(filePath) {
|
|
9482
|
-
try {
|
|
9483
|
-
return await readFile10(filePath, "utf8");
|
|
9484
|
-
} catch (error) {
|
|
9485
|
-
if (hasErrorCode6(error) && error.code === "ENOENT") return null;
|
|
9486
|
-
throw new Error("Failed to read markdown file", { cause: error });
|
|
9487
|
-
}
|
|
9488
|
-
}
|
|
9489
|
-
async function writeMarkdownFile(filePath, body) {
|
|
9490
|
-
try {
|
|
9491
|
-
await atomicReplace(filePath, body);
|
|
9492
|
-
} catch (error) {
|
|
9493
|
-
throw new Error("Failed to write markdown file", { cause: error });
|
|
9494
|
-
}
|
|
9495
|
-
}
|
|
9496
|
-
function parseMarkers(content, markers = DEFAULT_MARKERS) {
|
|
9497
|
-
const bom = content.charCodeAt(0) === 65279 ? "\uFEFF" : "";
|
|
9498
|
-
const body = bom === "" ? content : content.slice(1);
|
|
9499
|
-
const lines = body.split(/\r?\n/);
|
|
9500
|
-
const startLines = [];
|
|
9501
|
-
const endLines = [];
|
|
9502
|
-
for (let i = 0; i < lines.length; i++) {
|
|
9503
|
-
if (lines[i] === markers.start) startLines.push(i);
|
|
9504
|
-
else if (lines[i] === markers.end) endLines.push(i);
|
|
9505
|
-
}
|
|
9506
|
-
if (startLines.length === 0 && endLines.length === 0) return { kind: "no_markers" };
|
|
9507
|
-
if (startLines.length === 0) return { kind: "missing_start" };
|
|
9508
|
-
if (endLines.length === 0) return { kind: "missing_end" };
|
|
9509
|
-
if (startLines.length >= 2 || endLines.length >= 2) return { kind: "multiple_pairs" };
|
|
9510
|
-
const startLineIdx = startLines[0];
|
|
9511
|
-
const endLineIdx = endLines[0];
|
|
9512
|
-
if (endLineIdx < startLineIdx) return { kind: "wrong_order" };
|
|
9513
|
-
const startOffset = lineStartOffset(body, startLineIdx);
|
|
9514
|
-
const endLineStart = lineStartOffset(body, endLineIdx);
|
|
9515
|
-
const startLineEnd = startOffset + markers.start.length;
|
|
9516
|
-
const endLineEnd = endLineStart + markers.end.length;
|
|
9517
|
-
const before = bom + body.slice(0, startOffset);
|
|
9518
|
-
const afterStartNewline = skipOneNewline(body, startLineEnd);
|
|
9519
|
-
const beforeEndNewline = trimOneNewline(body, endLineStart);
|
|
9520
|
-
const generated = body.slice(afterStartNewline, beforeEndNewline);
|
|
9521
|
-
const after = body.slice(endLineEnd);
|
|
9522
|
-
return { kind: "ok", before, generated, after };
|
|
9523
|
-
}
|
|
9524
|
-
function defuseMarkerLines(body, markers) {
|
|
9525
|
-
const isMarker = (line) => {
|
|
9526
|
-
const bare = line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
9527
|
-
return bare === markers.start || bare === markers.end;
|
|
9528
|
-
};
|
|
9529
|
-
const lines = body.split("\n");
|
|
9530
|
-
if (!lines.some(isMarker)) return body;
|
|
9531
|
-
return lines.map((line) => isMarker(line) ? ` ${line}` : line).join("\n");
|
|
9532
|
-
}
|
|
9533
|
-
function renderWithMarkers(existing, generated, fileLabel, markers = DEFAULT_MARKERS) {
|
|
9534
|
-
const normalized = defuseMarkerLines(
|
|
9535
|
-
generated.endsWith("\n") ? generated : `${generated}
|
|
9536
|
-
`,
|
|
9537
|
-
markers
|
|
9538
|
-
);
|
|
9539
|
-
if (existing === null) {
|
|
9540
|
-
return `${markers.start}
|
|
9541
|
-
${normalized}${markers.end}
|
|
9542
|
-
`;
|
|
9543
|
-
}
|
|
9544
|
-
const section = parseMarkers(existing, markers);
|
|
9545
|
-
switch (section.kind) {
|
|
9546
|
-
case "ok":
|
|
9547
|
-
return `${section.before}${markers.start}
|
|
9548
|
-
${normalized}${markers.end}${section.after}`;
|
|
9549
|
-
case "no_markers":
|
|
9550
|
-
throw new Error(`Markers missing in ${fileLabel}`);
|
|
9551
|
-
case "missing_start":
|
|
9552
|
-
case "missing_end":
|
|
9553
|
-
case "multiple_pairs":
|
|
9554
|
-
case "wrong_order":
|
|
9555
|
-
throw new Error(`Markers mismatched in ${fileLabel}`);
|
|
9556
|
-
}
|
|
9557
|
-
}
|
|
9558
|
-
function seedMarkers(existing, generated, fileLabel, markers = DEFAULT_MARKERS) {
|
|
9559
|
-
const normalized = generated.endsWith("\n") ? generated : `${generated}
|
|
9560
|
-
`;
|
|
9561
|
-
if (existing === null) {
|
|
9562
|
-
return `${markers.start}
|
|
9563
|
-
${normalized}${markers.end}
|
|
9564
|
-
`;
|
|
9565
|
-
}
|
|
9566
|
-
const section = parseMarkers(existing, markers);
|
|
9567
|
-
switch (section.kind) {
|
|
9568
|
-
case "ok":
|
|
9569
|
-
return renderWithMarkers(existing, generated, fileLabel, markers);
|
|
9570
|
-
case "no_markers": {
|
|
9571
|
-
const bom = existing.charCodeAt(0) === 65279 ? "\uFEFF" : "";
|
|
9572
|
-
const body = bom === "" ? existing : existing.slice(1);
|
|
9573
|
-
return `${bom}${markers.start}
|
|
9574
|
-
${normalized}${markers.end}
|
|
9575
|
-
|
|
9576
|
-
${body}`;
|
|
9577
|
-
}
|
|
9578
|
-
case "missing_start":
|
|
9579
|
-
case "missing_end":
|
|
9580
|
-
case "multiple_pairs":
|
|
9581
|
-
case "wrong_order":
|
|
9582
|
-
throw new Error(`Markers mismatched in ${fileLabel}`);
|
|
9583
|
-
}
|
|
9584
|
-
}
|
|
9585
|
-
function removeMarkerSection(existing, fileLabel, markers = DEFAULT_MARKERS) {
|
|
9586
|
-
const section = parseMarkers(existing, markers);
|
|
9587
|
-
switch (section.kind) {
|
|
9588
|
-
case "no_markers":
|
|
9589
|
-
return existing;
|
|
9590
|
-
case "ok": {
|
|
9591
|
-
const after = section.after.replace(/^\r?\n/, "");
|
|
9592
|
-
return section.before + after;
|
|
9593
|
-
}
|
|
9594
|
-
case "missing_start":
|
|
9595
|
-
case "missing_end":
|
|
9596
|
-
case "multiple_pairs":
|
|
9597
|
-
case "wrong_order":
|
|
9598
|
-
throw new Error(`Markers mismatched in ${fileLabel}`);
|
|
9599
|
-
}
|
|
9600
|
-
}
|
|
9601
|
-
function lineStartOffset(content, lineIdx) {
|
|
9602
|
-
if (lineIdx === 0) return 0;
|
|
9603
|
-
let offset = 0;
|
|
9604
|
-
let line = 0;
|
|
9605
|
-
while (offset < content.length && line < lineIdx) {
|
|
9606
|
-
const ch = content[offset];
|
|
9607
|
-
if (ch === "\n") {
|
|
9608
|
-
line += 1;
|
|
9609
|
-
offset += 1;
|
|
9610
|
-
} else if (ch === "\r") {
|
|
9611
|
-
offset += 1;
|
|
9612
|
-
if (content[offset] === "\n") offset += 1;
|
|
9613
|
-
line += 1;
|
|
9614
|
-
} else {
|
|
9615
|
-
offset += 1;
|
|
9616
|
-
}
|
|
9617
|
-
}
|
|
9618
|
-
return offset;
|
|
9619
|
-
}
|
|
9620
|
-
function skipOneNewline(content, offset) {
|
|
9621
|
-
if (content[offset] === "\r" && content[offset + 1] === "\n") return offset + 2;
|
|
9622
|
-
if (content[offset] === "\n") return offset + 1;
|
|
9623
|
-
return offset;
|
|
9624
|
-
}
|
|
9625
|
-
function trimOneNewline(content, offset) {
|
|
9626
|
-
if (offset >= 2 && content[offset - 2] === "\r" && content[offset - 1] === "\n")
|
|
9627
|
-
return offset - 2;
|
|
9628
|
-
if (offset >= 1 && content[offset - 1] === "\n") return offset - 1;
|
|
9629
|
-
return offset;
|
|
9630
|
-
}
|
|
9631
9742
|
function hasErrorCode6(error) {
|
|
9632
9743
|
if (!(error instanceof Error)) return false;
|
|
9633
|
-
|
|
9634
|
-
return typeof codeProp === "string";
|
|
9744
|
+
return typeof error.code === "string";
|
|
9635
9745
|
}
|
|
9636
9746
|
|
|
9637
9747
|
// src/storage/session-import.ts
|
|
@@ -10053,6 +10163,7 @@ export {
|
|
|
10053
10163
|
ORIENTATION_START,
|
|
10054
10164
|
PROTOCOL_END,
|
|
10055
10165
|
PROTOCOL_START,
|
|
10166
|
+
PROTOCOL_UPDATE_TOKEN_PREFIX,
|
|
10056
10167
|
REVIEW_RECORD_NO_INPUT_HINT,
|
|
10057
10168
|
RiskLevelSchema,
|
|
10058
10169
|
SESSION_IMPORT_SCHEMA_VERSION,
|
|
@@ -10095,6 +10206,7 @@ export {
|
|
|
10095
10206
|
buildSessionStartHookCommand,
|
|
10096
10207
|
buildStatusSnapshot,
|
|
10097
10208
|
buildStopHookCommand,
|
|
10209
|
+
carryForwardProtocolStamp,
|
|
10098
10210
|
chainEvents,
|
|
10099
10211
|
chainRawJsonLines,
|
|
10100
10212
|
classifyFilesBySourceRoot,
|
|
@@ -10136,6 +10248,7 @@ export {
|
|
|
10136
10248
|
isGitNotFound,
|
|
10137
10249
|
isImportDerivedSource,
|
|
10138
10250
|
isLazyExpired,
|
|
10251
|
+
isProtocolUpdateDue,
|
|
10139
10252
|
isRenderable,
|
|
10140
10253
|
isValidPrefixedId,
|
|
10141
10254
|
lineHash,
|
|
@@ -10150,6 +10263,7 @@ export {
|
|
|
10150
10263
|
parseBuildStamp,
|
|
10151
10264
|
parseDuration,
|
|
10152
10265
|
parseMarkers,
|
|
10266
|
+
parseProtocolStamp,
|
|
10153
10267
|
parseReviewRecordInput,
|
|
10154
10268
|
pathBasename,
|
|
10155
10269
|
planArchive,
|
|
@@ -10159,6 +10273,9 @@ export {
|
|
|
10159
10273
|
planWorkspaceView,
|
|
10160
10274
|
prefixedUlid,
|
|
10161
10275
|
presetStrings,
|
|
10276
|
+
protocolBlockHash,
|
|
10277
|
+
protocolSectionsFrom,
|
|
10278
|
+
protocolUpdateToken,
|
|
10162
10279
|
readAllEvents,
|
|
10163
10280
|
readManifest,
|
|
10164
10281
|
readMarkdownFile,
|
|
@@ -10182,6 +10299,8 @@ export {
|
|
|
10182
10299
|
renderHandoff,
|
|
10183
10300
|
renderOrientation,
|
|
10184
10301
|
renderPresetBlock,
|
|
10302
|
+
renderProtocolStamp,
|
|
10303
|
+
renderProtocolUpdate,
|
|
10185
10304
|
renderReport,
|
|
10186
10305
|
renderViewPresetBlock,
|
|
10187
10306
|
renderWithMarkers,
|
|
@@ -10212,9 +10331,11 @@ export {
|
|
|
10212
10331
|
summarizeSymlinkPlan,
|
|
10213
10332
|
summarizeWiring,
|
|
10214
10333
|
summarizeWiringDrift,
|
|
10334
|
+
transcriptStartedAt,
|
|
10215
10335
|
tryRemoteUrl,
|
|
10216
10336
|
ulid,
|
|
10217
10337
|
unknownManifestKeys,
|
|
10338
|
+
unstampedProtocolSectionsFrom,
|
|
10218
10339
|
updateTaskStatusWithEvent,
|
|
10219
10340
|
upsertSessionStartHook,
|
|
10220
10341
|
upsertStopHook,
|